Passed
Pull Request — master (#6112)
by
unknown
09:39 queued 01:20
created

LpAiHelper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 0
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 1
rs 10
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
declare(strict_types=1);
5
6
use Chamilo\CoreBundle\Framework\Container;
7
8
class LpAiHelper
9
{
10
    /**
11
     * AiHelper constructor.
12
     * Requires AI helpers to be enabled in the settings.
13
     */
14
    public function __construct() {}
15
16
    /**
17
     * Get the form to generate Learning Path (LP) items using AI.
18
     */
19
    public function aiHelperForm()
20
    {
21
        if ('true' !== api_get_setting('ai_helpers.enable_ai_helpers') ||
22
            'true' !== api_get_course_setting('learning_path_generator')) {
23
24
            return false;
25
        }
26
27
        // Get AI providers from settings
28
        $aiProvidersJson = api_get_setting('ai_helpers.ai_providers');
29
        $configuredApi = api_get_setting('ai_helpers.default_ai_provider');
30
        $availableApis = json_decode($aiProvidersJson, true) ?? [];
31
        $hasSingleApi = count($availableApis) === 1 || isset($availableApis[$configuredApi]);
32
33
        $form = new FormValidator(
34
            'lp_ai_generate',
35
            'post',
36
            api_get_self()."?".api_get_cidreq(),
37
            null
38
        );
39
        $form->addElement('header', get_lang('Lp Ai generator'));
40
41
        // Show the AI provider being used
42
        if ($hasSingleApi) {
43
            $apiName = $availableApis[$configuredApi]['model'] ?? $configuredApi;
44
            $form->addHtml('<div style="margin-bottom: 10px; font-size: 14px; color: #555;">'
45
                .sprintf(get_lang('Using AI Provider: %s'), '<strong>'.htmlspecialchars($apiName).'</strong>').'</div>');
46
        }
47
48
        // Input fields for LP generation
49
        $form->addElement('text', 'lp_name', get_lang('Lp Ai Topic'));
50
        $form->addRule('lp_name', get_lang('This field is required'), 'required');
51
        $form->addElement('number', 'nro_items', get_lang('Lp Ai number of items'));
52
        $form->addRule('nro_items', get_lang('This field is required'), 'required');
53
        $form->addElement('number', 'words_count', get_lang('Lp Ai words count'));
54
        $form->addRule('words_count', get_lang('This field is required'), 'required');
55
56
        // Checkbox for adding quizzes
57
        $form->addElement('checkbox', 'add_lp_quiz', null, get_lang('Add test after each page'), ['id' => 'add-lp-quiz']);
58
        $form->addHtml('<div id="lp-quiz-area">');
59
        $form->addElement('number', 'nro_questions', get_lang('Number of questions'));
60
        $form->addRule('nro_questions', get_lang('This field is required'), 'required');
61
        $form->addHtml('</div>');
62
        $form->setDefaults(['nro_questions' => 2]);
63
64
        // Allow provider selection if multiple are available
65
        if (!$hasSingleApi) {
66
            $form->addSelect(
67
                'ai_provider',
68
                get_lang('AI Provider'),
69
                array_combine(array_keys($availableApis), array_keys($availableApis))
70
            );
71
        }
72
73
        // API URLs
74
        $generateUrl = api_get_path(WEB_PATH).'ai/generate_learnpath';
75
        $courseInfo = api_get_course_info();
76
        $language = $courseInfo['language'];
77
        $courseCode = api_get_course_id();
78
        $sessionId = api_get_session_id();
79
        $redirectSuccess = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&isStudentView=false&lp_id=';
80
81
        // JavaScript to handle form submission
82
        $form->addHtml('<script>
83
        $(function () {
84
            $("#lp-quiz-area").hide();
85
            $("#add-lp-quiz").change(function() {
86
                $("#lp-quiz-area").toggle(this.checked);
87
            });
88
89
            $("#create-lp-ai").on("click", function (e) {
90
                e.preventDefault();
91
                e.stopPropagation();
92
93
                var btnGenerate = $(this);
94
                var lpName = $("[name=\'lp_name\']").val().trim();
95
                var nroItems = parseInt($("[name=\'nro_items\']").val());
96
                var wordsCount = parseInt($("[name=\'words_count\']").val());
97
                var addTests = $("#add-lp-quiz").is(":checked");
98
                var nroQuestions = parseInt($("[name=\'nro_questions\']").val());
99
                var provider = '.(!$hasSingleApi ? '$("[name=\'ai_provider\']").val()' : '"'.$configuredApi.'"').';
100
101
                var isValid = true;
102
103
                $(".error-message").remove();
104
105
                if (lpName === "") {
106
                    $("[name=\'lp_name\']").after("<div class=\'error-message\' style=\'color: red;\'>'.get_lang('This field is required').'</div>");
107
                    isValid = false;
108
                }
109
110
                if (isNaN(nroItems) || nroItems <= 0) {
111
                    $("[name=\'nro_items\']").after("<div class=\'error-message\' style=\'color: red;\'>'.get_lang('Please enter a valid number').'</div>");
112
                    isValid = false;
113
                }
114
115
                if (isNaN(wordsCount) || wordsCount <= 0) {
116
                    $("[name=\'words_count\']").after("<div class=\'error-message\' style=\'color: red;\'>'.get_lang('Please enter a valid word count').'</div>");
117
                    isValid = false;
118
                }
119
120
                if (addTests && (isNaN(nroQuestions) || nroQuestions <= 0 || nroQuestions > 5)) {
121
                    $("[name=\'nro_questions\']").after("<div class=\'error-message\' style=\'color: red;\'>'.sprintf(get_lang('Number of questions limited from %d to %d'), 1, 5).'</div>");
122
                    isValid = false;
123
                }
124
125
                if (!isValid) {
126
                    return;
127
                }
128
129
                btnGenerate.attr("disabled", true).text("'.get_lang('Please wait this could take a while').'");
130
131
                var requestData = JSON.stringify({
132
                    "lp_name": lpName,
133
                    "nro_items": nroItems,
134
                    "words_count": wordsCount,
135
                    "language": "'.$language.'",
136
                    "add_tests": addTests,
137
                    "nro_questions": nroQuestions,
138
                    "ai_provider": provider,
139
                    "course_code": "'.$courseCode.'",
140
                });
141
142
                $.ajax({
143
                    url: "'.$generateUrl.'",
144
                    type: "POST",
145
                    contentType: "application/json",
146
                    data: requestData,
147
                    dataType: "json",
148
                    success: function (data) {
149
                        btnGenerate.attr("disabled", false).text("'.get_lang('Generate').'");
150
151
                        if (data.success) {
152
                            $.ajax({
153
                                url: "'.api_get_path(WEB_AJAX_PATH).'lp.ajax.php?a=add_lp_ai&'.api_get_cidreq().'",
154
                                type: "POST",
155
                                contentType: "application/json",
156
                                data: JSON.stringify({
157
                                    "lp_data": data.data,
158
                                    "course_code": "'.$courseCode.'"
159
                                }),
160
                                success: function (result) {
161
                                    try {
162
                                        let parsedResult = (typeof result === "string") ? JSON.parse(result) : result;
163
                                        let isSuccess = Boolean(parsedResult.success);
164
165
                                        if (isSuccess) {
166
                                            location.href = "'.$redirectSuccess.'" + parsedResult.lp_id;
167
                                        } else {
168
                                            alert("Error: " + (parsedResult.text || "'.get_lang('Error creating Learning Path').'"));
169
                                        }
170
                                    } catch (e) {
171
                                        alert("'.get_lang('Invalid server response').'");
172
                                    }
173
                                }
174
                            });
175
                        } else {
176
                            alert(data.text || "'.get_lang('No results found').'. '.get_lang('Please try again').'");
177
                        }
178
                    },
179
                    error: function (jqXHR) {
180
                        btnGenerate.attr("disabled", false).text("'.get_lang('Generate').'");
181
182
                        try {
183
                            var response = JSON.parse(jqXHR.responseText);
184
                            var errorMessage = "'.get_lang('An unexpected error occurred. Please try again later.').'";
185
186
                            if (response && response.text) {
187
                                errorMessage = response.text;
188
                            }
189
190
                            alert("'.get_lang('Request failed').': " + errorMessage);
191
                        } catch (e) {
192
                            alert("'.get_lang('Request failed').': " + "'.get_lang('An unexpected error occurred. Please contact support.').'");
193
                        }
194
                    }
195
                });
196
197
            });
198
        });
199
        </script>');
200
201
        $form->addButton('create_lp_button', get_lang('Add Learnpath'), 'check', 'primary', '', null, ['id' => 'create-lp-ai']);
202
        echo $form->returnForm();
203
    }
204
205
    public function createLearningPathFromAI(array $lpData, string $courseCode): array
206
    {
207
        if (!isset($lpData['topic'])) {
208
            return ['success' => false, 'text' => 'Error: Topic not set in AI response.'];
209
        }
210
211
        $lp = learnpath::add_lp(
212
            $courseCode,
213
            $lpData['topic'],
214
            '',
215
            'chamilo',
216
            'manual'
217
        );
218
219
        if (null === $lp) {
220
            return ['success' => false, 'text' => 'Failed to create Learning Path.'];
221
        }
222
223
        $lpId = $lp->getIid();
224
        if (empty($lpId)) {
225
            return ['success' => false, 'text' => 'Failed to retrieve Learning Path ID.'];
226
        }
227
228
        $courseInfo = api_get_course_info($courseCode);
229
        $learningPath = new learnpath($lp, $courseInfo, api_get_user_id());
230
231
        $lpItemRepo = Container::getLpItemRepository();
232
233
        $parent = $lpItemRepo->getRootItem($lpId);
234
        $order = 1;
235
        $lpItemsIds = [];
236
237
        foreach ($lpData['lp_items'] as $item) {
238
            $documentId = $learningPath->create_document(
239
                $courseInfo,
240
                $item['content'],
241
                $item['title']
242
            );
243
244
            if (!empty($documentId)) {
245
                $previousId = isset($lpItemsIds[$order - 1]) ? (int) $lpItemsIds[$order - 1]['item_id'] : 0;
246
                $lpItemId = $learningPath->add_item(
247
                    $parent,
248
                    $previousId,
249
                    TOOL_DOCUMENT,
250
                    $documentId,
251
                    $item['title']
252
                );
253
                $lpItemsIds[$order] = ['item_id' => $lpItemId, 'item_type' => TOOL_DOCUMENT];
254
            }
255
            $order++;
256
        }
257
258
        if (!empty($lpData['quiz_items'])) {
259
            require_once api_get_path(SYS_CODE_PATH).'exercise/export/aiken/aiken_import.inc.php';
260
            require_once api_get_path(SYS_CODE_PATH).'exercise/export/aiken/aiken_classes.php';
261
262
            foreach ($lpData['quiz_items'] as $quiz) {
263
                if (empty(trim($quiz['content']))) {
264
                    continue;
265
                }
266
267
                $request = [
268
                    'quiz_name' => get_lang('Exercise') . ': ' . $quiz['title'],
269
                    'nro_questions' => count(explode("\n", trim($quiz['content']))),
270
                    'course_id' => api_get_course_int_id($courseCode),
271
                    'aiken_format' => trim($quiz['content']),
272
                ];
273
274
                $exerciseId = aiken_import_exercise(null, $request);
275
276
                if (!empty($exerciseId)) {
277
                    $previousId = isset($lpItemsIds[$order - 1]) ? (int) $lpItemsIds[$order - 1]['item_id'] : 0;
278
                    $lpQuizItemId = $learningPath->add_item(
279
                        $parent,
280
                        $previousId,
281
                        TOOL_QUIZ,
282
                        $exerciseId,
283
                        $request['quiz_name']
284
                    );
285
286
                    if (!empty($lpQuizItemId)) {
287
                        $lpItemsIds[$order] = [
288
                            'item_id' => $lpQuizItemId,
289
                            'item_type' => TOOL_QUIZ,
290
                            'min_score' => round($request['nro_questions'] / 2, 2),
291
                            'max_score' => (float) $request['nro_questions'],
292
                        ];
293
                    }
294
                    $order++;
295
                }
296
            }
297
        }
298
299
        return ['success' => true, 'lp_id' => $lpId];
300
    }
301
}
302