chamilo/chamilo-lms

View on GitHub
public/plugin/ai_helper/tool/learnpath.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
/* For license terms, see /license.txt */

/**
Create a learnpath with contents based on existing knowledge.
 */

use Chamilo\CoreBundle\Framework\Container;

require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__.'/../AiHelperPlugin.php';
require_once api_get_path(SYS_CODE_PATH).'exercise/export/aiken/aiken_classes.php';
require_once api_get_path(SYS_CODE_PATH).'exercise/export/aiken/aiken_import.inc.php';

$plugin = AiHelperPlugin::create();

$apiList = $plugin->getApiList();
$apiName = $plugin->get('api_name');

if (!in_array($apiName, array_keys($apiList))) {
    throw new Exception("Ai API is not available for this request.");
}

switch ($apiName) {
    case AiHelperPlugin::OPENAI_API:

        $courseLanguage = (string) $_REQUEST['language'];
        $chaptersCount = (int) $_REQUEST['nro_items'];
        $topic = (string) $_REQUEST['lp_name'];
        $wordsCount = (int) $_REQUEST['words_count'];
        $courseCode = (string) $_REQUEST['course_code'];
        $sessionId = (int) $_REQUEST['session_id'];
        $addTests = ('true' === $_REQUEST['add_tests']);
        $nQ = ($addTests ? (int) $_REQUEST['nro_questions'] : 0);

        $messageGetItems = 'Generate the table of contents of a course in "%s" in %d or less chapters on the topic of "%s" and return it as a list of items separated by CRLF. Do not provide chapter numbering. Do not include a conclusion chapter.';
        $prompt = sprintf($messageGetItems, $courseLanguage, $chaptersCount, $topic);
        $resultText = $plugin->openAiGetCompletionText($prompt, 'learnpath');

        if (isset($resultText['error']) && true === $resultText['error']) {
            echo json_encode([
                'success' => false,
                'text' => $resultText['message'],
            ]);
            exit;
        }

        $lpItems = [];
        if (!empty($resultText)) {
            $style = api_get_css_asset('bootstrap/dist/css/bootstrap.min.css');
            $style .= api_get_css_asset('fontawesome/css/font-awesome.min.css');
            $style .= Container::getThemeHelper()->getThemeAssetLinkTag('document.css');
            $style .= api_get_css_asset('ckeditor/plugins/codesnippet/lib/highlight/styles/default.css');
            $style .= api_get_asset('ckeditor/plugins/codesnippet/lib/highlight/highlight.pack.js');
            $style .= '<script>hljs.initHighlightingOnLoad();</script>';

            $items = explode("\n", $resultText);
            $position = 1;
            foreach ($items as $item) {
                if (substr($item, 0, 2) === '- ') {
                    $item = substr($item, 2);
                }
                $explodedItem = preg_split('/\d\./', $item);
                $title = count($explodedItem) > 1 ? $explodedItem[1] : $explodedItem[0];
                if (!empty($title)) {
                    $lpItems[$position]['title'] = trim($title);
                    $messageGetItemContent = 'In the context of "%s", generate a document with HTML tags in "%s" with %d words of content or less, about "%s", as to be included as one chapter in a larger document on "%s". Consider the context is established for the reader and you do not need to repeat it.';
                    $promptItem = sprintf($messageGetItemContent, $topic, $courseLanguage, $wordsCount, $title, $topic);
                    $resultContentText = $plugin->openAiGetCompletionText($promptItem, 'learnpath');
                    $lpItemContent = (!empty($resultContentText) ? trim($resultContentText) : '');
                    if (false !== stripos($lpItemContent, '</head>')) {
                        $lpItemContent = preg_replace("|</head>|i", "\r\n$style\r\n\\0", $lpItemContent);
                    } else {
                        $lpItemContent = '<html><head><title>'.trim($title).'</title>'.$style.'</head><body>'.$lpItemContent.'</body></html>';
                    }
                    $lpItems[$position]['content'] = $lpItemContent;
                    $position++;
                }
            }
        }

        // Create the learnpath and return the id generated.
        $return = ['success' => false, 'lp_id' => 0];
        if (!empty($lpItems)) {
            $lpId = learnpath::add_lp(
                $courseCode,
                $topic,
                '',
                'chamilo',
                'manual'
            );

            if (!empty($lpId)) {
                learnpath::toggle_visibility($lpId, 0);
                $courseInfo = api_get_course_info($courseCode);
                $lp = new \learnpath(
                    $courseCode,
                    $lpId,
                    api_get_user_id()
                );
                $lp->generate_lp_folder($courseInfo, $topic);
                $order = 1;
                $lpItemsIds = [];
                foreach ($lpItems as $dspOrder => $item) {
                    $documentId = $lp->create_document(
                        $courseInfo,
                        $item['content'],
                        $item['title'],
                        'html'
                    );

                    if (!empty($documentId)) {
                        $prevDocItem = (isset($lpItemsIds[$order - 1]) ? (int) $lpItemsIds[$order - 1]['item_id'] : 0);
                        $lpItemId = $lp->add_item(
                            0,
                            $prevDocItem,
                            'document',
                            $documentId,
                            $item['title'],
                            '',
                            0,
                            0,
                            api_get_user_id(),
                            $order
                        );
                        $lpItemsIds[$order]['item_id'] = $lpItemId;
                        $lpItemsIds[$order]['item_type'] = 'document';
                        if ($addTests && !empty($lpItemId)) {
                            $promptQuiz = 'Generate %d "%s" questions in Aiken format in the %s language about "%s", making sure there is a \'ANSWER\' line for each question. \'ANSWER\' lines must only mention the letter of the correct answer, not the full answer text and not a parenthesis. The line starting with \'ANSWER\' must not be separated from the last possible answer by a blank line. Each answer starts with an uppercase letter, a dot, one space and the answer text without quotes. Include an \'ANSWER_EXPLANATION\' line after the \'ANSWER\' line for each question. The terms between single quotes above must not be translated. There must be a blank line between each question. Show the question directly without any prefix.';
                            $promptQuiz = sprintf($promptQuiz, $nQ, $courseLanguage, $item['title'], $topic);
                            $resultQuizText = $plugin->openAiGetCompletionText($promptQuiz, 'quiz');
                            if (!empty($resultQuizText)) {
                                $request = [];
                                $request['quiz_name'] = get_lang('Exercise').': '.$item['title'];
                                $request['nro_questions'] = $nQ;
                                $request['course_id'] = api_get_course_int_id($courseCode);
                                $request['aiken_format'] = trim($resultQuizText);
                                $exerciseId = aikenImportExercise(null, $request);
                                if (!empty($exerciseId)) {
                                    $order++;
                                    $prevQuizItem = (isset($lpItemsIds[$order - 1]) ? (int) $lpItemsIds[$order - 1]['item_id'] : 0);
                                    $lpQuizItemId = $lp->add_item(
                                        0,
                                        $prevQuizItem,
                                        'quiz',
                                        $exerciseId,
                                        $request['quiz_name'],
                                        '',
                                        0,
                                        0,
                                        api_get_user_id(),
                                        $order
                                    );
                                    if (!empty($lpQuizItemId)) {
                                        $maxScore = (float) $nQ;
                                        $minScore = round($nQ / 2, 2);
                                        $lpItemsIds[$order]['item_id'] = $lpQuizItemId;
                                        $lpItemsIds[$order]['item_type'] = 'quiz';
                                        $lpItemsIds[$order]['min_score'] = $minScore;
                                        $lpItemsIds[$order]['max_score'] = $maxScore;
                                    }
                                }
                            }
                        }
                    }
                    $order++;
                }

                // Add the final item
                if ($addTests) {
                    $finalTitle = get_lang('EndOfLearningPath');
                    $finalContent = file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
                    $finalDocId = $lp->create_document(
                        $courseInfo,
                        $finalContent,
                        $finalTitle
                    );
                    $prevFinalItem = (isset($lpItemsIds[$order - 1]) ? (int) $lpItemsIds[$order - 1]['item_id'] : 0);
                    $lpFinalItemId = $lp->add_item(
                        0,
                        $prevFinalItem,
                        TOOL_LP_FINAL_ITEM,
                        $finalDocId,
                        $finalTitle,
                        '',
                        0,
                        0,
                        api_get_user_id(),
                        $order
                    );
                    $lpItemsIds[$order]['item_id'] = $lpFinalItemId;
                    $lpItemsIds[$order]['item_type'] = TOOL_LP_FINAL_ITEM;

                    // Set lp items prerequisites
                    if (count($lpItemsIds) > 0) {
                        for ($i = 1; $i <= count($lpItemsIds); $i++) {
                            $prevIndex = ($i - 1);
                            if (isset($lpItemsIds[$prevIndex])) {
                                $itemId = $lpItemsIds[$i]['item_id'];
                                $prerequisite = $lpItemsIds[$prevIndex]['item_id'];
                                $minScore = ('quiz' === $lpItemsIds[$prevIndex]['item_type'] ? $lpItemsIds[$prevIndex]['min_score'] : 0);
                                $maxScore = ('quiz' === $lpItemsIds[$prevIndex]['item_type'] ? $lpItemsIds[$prevIndex]['max_score'] : 100);
                                $lp->edit_item_prereq($itemId, $prerequisite, $minScore, $maxScore);
                            }
                        }
                    }
                }
            }
            $return = [
                'success' => true,
                'lp_id' => $lpId,
            ];
        }
        echo json_encode($return);
        break;
}