Skip to content

Commit f86cf35

Browse files
Plugin: AI Helper: Add feature to use AI Helper to create learning paths - refs GH#4594
Author: @christianbeeznest
1 parent e9c14f1 commit f86cf35

File tree

6 files changed

+280
-0
lines changed

6 files changed

+280
-0
lines changed

main/lp/LpAiHelper.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
/* For licensing terms, see /license.txt */
3+
4+
class LpAiHelper
5+
{
6+
/**
7+
* AiHelper constructor.
8+
* Requires plugin ai_helper to connect to the api.
9+
*/
10+
public function __construct()
11+
{
12+
if ('true' !== api_get_plugin_setting('ai_helper', 'tool_enable')) {
13+
return false;
14+
}
15+
}
16+
17+
/**
18+
* Get the form to generate Lp items using Ai Helper.
19+
*/
20+
public function aiHelperForm()
21+
{
22+
$form = new FormValidator(
23+
'lp_ai_generate',
24+
'post',
25+
api_get_self()."?".api_get_cidreq(),
26+
null
27+
);
28+
$form->addElement('header', get_lang('LpAiGenerator'));
29+
$form->addElement('text', 'lp_name', [get_lang('LpAiTopic'), get_lang('LpAiTopicHelp')]);
30+
$form->addRule('lp_name', get_lang('ThisFieldIsRequired'), 'required');
31+
$form->addElement('number', 'nro_items', [get_lang('LpAiNumberOfItems'), get_lang('LpAiNumberOfItemsHelper')]);
32+
$form->addRule('nro_items', get_lang('ThisFieldIsRequired'), 'required');
33+
$form->addElement('number', 'words_count', [get_lang('LpAiWordsCount'), get_lang('LpAiWordsCountHelper')]);
34+
$form->addRule('words_count', get_lang('ThisFieldIsRequired'), 'required');
35+
36+
$generateUrl = api_get_path(WEB_PLUGIN_PATH).'ai_helper/tool/learnpath.php';
37+
$language = api_get_interface_language();
38+
$courseCode = api_get_course_id();
39+
$sessionId = api_get_session_id();
40+
$redirectSuccess = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&isStudentView=false&lp_id=';
41+
$form->addHtml('<script>
42+
$(function () {
43+
$("#create-lp-ai").on("click", function (e) {
44+
e.preventDefault();
45+
e.stopPropagation();
46+
47+
var btnGenerate = $(this);
48+
var lpName = $("[name=\'lp_name\']").val();
49+
var nroItems = parseInt($("[name=\'nro_items\']").val());
50+
var wordsCount = parseInt($("[name=\'words_count\']").val());
51+
var valid = (lpName != \'\' && nroItems > 0 && wordsCount > 0);
52+
53+
if (valid) {
54+
btnGenerate.attr("disabled", true);
55+
btnGenerate.text("'.get_lang('PleaseWaitThisCouldTakeAWhile').'");
56+
$.getJSON("'.$generateUrl.'", {
57+
"lp_name": lpName,
58+
"nro_items": nroItems,
59+
"words_count": wordsCount,
60+
"language": "'.$language.'",
61+
"course_code": "'.$courseCode.'",
62+
"session_id": "'.$sessionId.'"
63+
}).done(function (data) {
64+
btnGenerate.attr("disabled", false);
65+
btnGenerate.text("'.get_lang('Generate').'");
66+
if (data.success && data.success == true) {
67+
location.href = "'.$redirectSuccess.'" + data.lp_id;
68+
} else {
69+
alert("'.get_lang('NoSearchResults').'. '.get_lang('PleaseTryAgain').'");
70+
}
71+
});
72+
}
73+
});
74+
});
75+
</script>');
76+
77+
$form->addButton(
78+
'create_lp_button',
79+
get_lang('CreateLp'),
80+
'',
81+
'default',
82+
'default',
83+
null,
84+
['id' => 'create-lp-ai']
85+
);
86+
87+
echo $form->returnForm();
88+
}
89+
}

main/lp/lp_add_ai_helper.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
/* For licensing terms, see /license.txt */
3+
4+
$this_section = SECTION_COURSES;
5+
api_protect_course_script();
6+
7+
$interbreadcrumb[] = [
8+
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
9+
'name' => get_lang('LearningPaths'),
10+
];
11+
12+
Display::display_header(get_lang('CreateLpWithAiHelper'), 'Learnpath');
13+
14+
echo '<div class="actions">';
15+
echo '<a href="lp_controller.php?'.api_get_cidreq().'">'.
16+
Display::return_icon(
17+
'back.png',
18+
get_lang('ReturnToLearningPaths'),
19+
'',
20+
ICON_SIZE_MEDIUM
21+
).'</a>';
22+
echo '</div>';
23+
24+
$aiHelper = new LpAiHelper();
25+
26+
$aiHelper->aiHelperForm();
27+
28+
Display::display_footer();

main/lp/lp_controller.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,12 @@ function(reponse) {
738738
}
739739
require 'lp_add_category.php';
740740
break;
741+
case 'ai_helper':
742+
if (!$is_allowed_to_edit) {
743+
api_not_allowed(true);
744+
}
745+
require 'lp_add_ai_helper.php';
746+
break;
741747
case 'move_up_category':
742748
if (!$is_allowed_to_edit) {
743749
api_not_allowed(true);

main/lp/lp_list.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,19 @@ function confirmation(name) {
122122
api_get_self().'?'.api_get_cidreq().'&action=add_lp_category'
123123
);
124124
}
125+
126+
if ('true' === api_get_plugin_setting('ai_helper', 'tool_enable')) {
127+
$actionLeft .= Display::url(
128+
Display::return_icon(
129+
'help.png',
130+
get_lang('CreateLpWithAiHelper'),
131+
[],
132+
ICON_SIZE_MEDIUM
133+
),
134+
api_get_self().'?'.api_get_cidreq().'&action=ai_helper'
135+
);
136+
}
137+
125138
$actions = Display::toolbarAction('actions-lp', [$actionLeft]);
126139
}
127140

plugin/ai_helper/AiHelperPlugin.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,46 @@ public function getApiList()
4545
return $list;
4646
}
4747

48+
/**
49+
* Get the completion text from openai.
50+
*
51+
* @return string
52+
*/
53+
public function openAiGetCompletionText(string $prompt)
54+
{
55+
require_once __DIR__.'/src/openai/OpenAi.php';
56+
57+
$apiKey = $this->get('api_key');
58+
$organizationId = $this->get('organization_id');
59+
60+
$ai = new OpenAi($apiKey, $organizationId);
61+
62+
$temperature = 0.2;
63+
$model = 'text-davinci-003';
64+
$maxTokens = 2000;
65+
$frequencyPenalty = 0;
66+
$presencePenalty = 0.6;
67+
$topP = 1.0;
68+
69+
$complete = $ai->completion([
70+
'model' => $model,
71+
'prompt' => $prompt,
72+
'temperature' => $temperature,
73+
'max_tokens' => $maxTokens,
74+
'frequency_penalty' => $frequencyPenalty,
75+
'presence_penalty' => $presencePenalty,
76+
'top_p' => $topP,
77+
]);
78+
79+
$result = json_decode($complete, true);
80+
$resultText = '';
81+
if (!empty($result['choices'])) {
82+
$resultText = trim($result['choices'][0]['text']);
83+
}
84+
85+
return $resultText;
86+
}
87+
4888
/**
4989
* Get the plugin directory name.
5090
*/
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
/* For license terms, see /license.txt */
3+
4+
/**
5+
Create a learnpath with contents based on existing knowledge.
6+
*/
7+
require_once __DIR__.'/../../../main/inc/global.inc.php';
8+
require_once __DIR__.'/../AiHelperPlugin.php';
9+
10+
$plugin = AiHelperPlugin::create();
11+
12+
$apiList = $plugin->getApiList();
13+
$apiName = $plugin->get('api_name');
14+
15+
if (!in_array($apiName, array_keys($apiList))) {
16+
throw new Exception("Ai API is not available for this request.");
17+
}
18+
19+
switch ($apiName) {
20+
case AiHelperPlugin::OPENAI_API:
21+
22+
$courseLanguage = (string) $_REQUEST['language'];
23+
$chaptersCount = (int) $_REQUEST['nro_items'];
24+
$topic = (string) $_REQUEST['lp_name'];
25+
$wordsCount = (int) $_REQUEST['words_count'];
26+
$courseCode = (string) $_REQUEST['course_code'];
27+
$sessionId = (int) $_REQUEST['session_id'];
28+
29+
$messageGetItems = 'Generate the table of contents of a course in "%s" in %d or less chapters on the topic of "%s" in a list separated with comma, without chapter number';
30+
$prompt = sprintf($messageGetItems, $courseLanguage, $chaptersCount, $topic);
31+
$resultText = $plugin->openAiGetCompletionText($prompt);
32+
$lpItems = [];
33+
if (!empty($resultText)) {
34+
$items = explode(',', $resultText);
35+
$position = 1;
36+
foreach ($items as $item) {
37+
$explodedItem = preg_split('/\d\./', $item);
38+
$title = count($explodedItem) > 1 ? $explodedItem[1] : $explodedItem[0];
39+
if (!empty($title)) {
40+
$lpItems[$position]['title'] = trim($title);
41+
$messageGetItemContent = 'In the context of "%s", generate a document with HTML tags in "%s" with %d words of content or less, about "%s"';
42+
$promptItem = sprintf($messageGetItemContent, $topic, $courseLanguage, $wordsCount, $title);
43+
$resultContentText = $plugin->openAiGetCompletionText($promptItem);
44+
if (!empty($resultContentText)) {
45+
$lpItems[$position]['content'] = trim($resultContentText);
46+
}
47+
$position++;
48+
}
49+
}
50+
}
51+
52+
// Create the learnpath and return the id generated.
53+
$return = ['success' => false, 'lp_id' => 0];
54+
if (!empty($lpItems)) {
55+
$lpId = learnpath::add_lp(
56+
$courseCode,
57+
$topic,
58+
'',
59+
'chamilo',
60+
'manual'
61+
);
62+
63+
if (!empty($lpId)) {
64+
learnpath::toggle_visibility($lpId, 0);
65+
$courseInfo = api_get_course_info($courseCode);
66+
$lp = new \learnpath(
67+
$courseCode,
68+
$lpId,
69+
api_get_user_id()
70+
);
71+
$lp->generate_lp_folder($courseInfo, $topic);
72+
73+
foreach ($lpItems as $dspOrder => $item) {
74+
$documentId = $lp->create_document(
75+
$courseInfo,
76+
$item['content'],
77+
$item['title'],
78+
'html'
79+
);
80+
81+
if (!empty($documentId)) {
82+
$lpItemId = $lp->add_item(
83+
0,
84+
0,
85+
'document',
86+
$documentId,
87+
$item['title'],
88+
'',
89+
0,
90+
0,
91+
api_get_user_id(),
92+
$dspOrder
93+
);
94+
}
95+
}
96+
}
97+
$return = [
98+
'success' => true,
99+
'lp_id' => $lpId,
100+
];
101+
}
102+
echo json_encode($return);
103+
break;
104+
}

0 commit comments

Comments
 (0)