Actualización

This commit is contained in:
Xes
2025-04-10 12:36:07 +02:00
parent 1da7c3f3b9
commit 4aff98e77b
3147 changed files with 320647 additions and 0 deletions

View File

@@ -0,0 +1,241 @@
<?php
/* For license terms, see /license.txt */
use Chamilo\PluginBundle\Entity\AiHelper\Requests;
use Doctrine\ORM\Tools\SchemaTool;
/**
* Description of AiHelperPlugin.
*
* @author Christian Beeznest <christian.fasanando@beeznest.com>
*/
class AiHelperPlugin extends Plugin
{
public const TABLE_REQUESTS = 'plugin_ai_helper_requests';
public const OPENAI_API = 'openai';
protected function __construct()
{
$version = '1.1';
$author = 'Christian Fasanando';
$message = 'Description';
$settings = [
$message => 'html',
'tool_enable' => 'boolean',
'api_name' => [
'type' => 'select',
'options' => $this->getApiList(),
],
'api_key' => 'text',
'organization_id' => 'text',
'tool_lp_enable' => 'boolean',
'tool_quiz_enable' => 'boolean',
'tokens_limit' => 'text',
];
parent::__construct($version, $author, $settings);
}
/**
* Get the list of apis availables.
*
* @return array
*/
public function getApiList()
{
$list = [
self::OPENAI_API => 'OpenAI',
];
return $list;
}
/**
* Get the completion text from openai.
*
* @return string
*/
public function openAiGetCompletionText(
string $prompt,
string $toolName
) {
if (!$this->validateUserTokensLimit(api_get_user_id())) {
return [
'error' => true,
'message' => $this->get_lang('ErrorTokensLimit'),
];
}
require_once __DIR__.'/src/openai/OpenAi.php';
$apiKey = $this->get('api_key');
$organizationId = $this->get('organization_id');
$ai = new OpenAi($apiKey, $organizationId);
$temperature = 0.2;
$model = 'gpt-3.5-turbo-instruct';
$maxTokens = 2000;
$frequencyPenalty = 0;
$presencePenalty = 0.6;
$topP = 1.0;
$complete = $ai->completion([
'model' => $model,
'prompt' => $prompt,
'temperature' => $temperature,
'max_tokens' => $maxTokens,
'frequency_penalty' => $frequencyPenalty,
'presence_penalty' => $presencePenalty,
'top_p' => $topP,
]);
$result = json_decode($complete, true);
$resultText = '';
if (!empty($result['choices'])) {
$resultText = $result['choices'][0]['text'];
// saves information of user results.
$values = [
'user_id' => api_get_user_id(),
'tool_name' => $toolName,
'prompt' => $prompt,
'prompt_tokens' => (int) $result['usage']['prompt_tokens'],
'completion_tokens' => (int) $result['usage']['completion_tokens'],
'total_tokens' => (int) $result['usage']['total_tokens'],
];
$this->saveRequest($values);
}
return $resultText;
}
/**
* Validates tokens limit of a user per current month.
*/
public function validateUserTokensLimit(int $userId): bool
{
$em = Database::getManager();
$repo = $em->getRepository('ChamiloPluginBundle:AiHelper\Requests');
$startDate = api_get_utc_datetime(
null,
false,
true)
->modify('first day of this month')->setTime(00, 00, 00)
;
$endDate = api_get_utc_datetime(
null,
false,
true)
->modify('last day of this month')->setTime(23, 59, 59)
;
$qb = $repo->createQueryBuilder('e')
->select('sum(e.totalTokens) as total')
->andWhere('e.requestedAt BETWEEN :dateMin AND :dateMax')
->andWhere('e.userId = :user')
->setMaxResults(1)
->setParameters(
[
'dateMin' => $startDate->format('Y-m-d h:i:s'),
'dateMax' => $endDate->format('Y-m-d h:i:s'),
'user' => $userId,
]
);
$result = $qb->getQuery()->getOneOrNullResult();
$totalTokens = !empty($result) ? (int) $result['total'] : 0;
$valid = true;
$tokensLimit = $this->get('tokens_limit');
if (!empty($tokensLimit)) {
$valid = ($totalTokens <= (int) $tokensLimit);
}
return $valid;
}
/**
* Get the plugin directory name.
*/
public function get_name(): string
{
return 'ai_helper';
}
/**
* Get the class instance.
*
* @staticvar AiHelperPlugin $result
*/
public static function create(): AiHelperPlugin
{
static $result = null;
return $result ?: $result = new self();
}
/**
* Save user information of openai request.
*
* @return int
*/
public function saveRequest(array $values)
{
$em = Database::getManager();
$objRequest = new Requests();
$objRequest
->setUserId($values['user_id'])
->setToolName($values['tool_name'])
->setRequestedAt(new DateTime())
->setRequestText($values['prompt'])
->setPromptTokens($values['prompt_tokens'])
->setCompletionTokens($values['completion_tokens'])
->setTotalTokens($values['total_tokens'])
;
$em->persist($objRequest);
$em->flush();
return $objRequest->getId();
}
/**
* Install the plugin. Set the database up.
*/
public function install()
{
$em = Database::getManager();
if ($em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_REQUESTS])) {
return;
}
$schemaTool = new SchemaTool($em);
$schemaTool->createSchema(
[
$em->getClassMetadata(Requests::class),
]
);
}
/**
* Unistall plugin. Clear the database.
*/
public function uninstall()
{
$em = Database::getManager();
if (!$em->getConnection()->getSchemaManager()->tablesExist([self::TABLE_REQUESTS])) {
return;
}
$schemaTool = new SchemaTool($em);
$schemaTool->dropSchema(
[
$em->getClassMetadata(Requests::class),
]
);
}
}

View File

@@ -0,0 +1,171 @@
<?php
/* For licensing terms, see /license.txt */
namespace Chamilo\PluginBundle\Entity\AiHelper;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Platform.
*
* @package Chamilo\PluginBundle\Entity\AiHelper
*
* @ORM\Table(name="plugin_ai_helper_requests")
* @ORM\Entity()
*/
class Requests
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue()
*/
protected $id;
/**
* @var int
*
* @ORM\Column(name="user_id", type="integer")
*/
private $userId;
/**
* @var string
*
* @ORM\Column(name="tool_name", type="string")
*/
private $toolName;
/**
* @var \DateTime
*
* @ORM\Column(name="requested_at", type="datetime", nullable=true)
*/
private $requestedAt;
/**
* @var string
*
* @ORM\Column(name="request_text", type="string")
*/
private $requestText;
/**
* @var int
*
* @ORM\Column(name="prompt_tokens", type="integer")
*/
private $promptTokens;
/**
* @var int
*
* @ORM\Column(name="completion_tokens", type="integer")
*/
private $completionTokens;
/**
* @var int
*
* @ORM\Column(name="total_tokens", type="integer")
*/
private $totalTokens;
public function getUserId(): int
{
return $this->userId;
}
public function setUserId(int $userId): Requests
{
$this->userId = $userId;
return $this;
}
public function getId(): int
{
return $this->id;
}
public function setId(int $id): Requests
{
$this->id = $id;
return $this;
}
public function getRequestedAt(): \DateTime
{
return $this->requestedAt;
}
public function setRequestedAt(\DateTime $requestedAt): Requests
{
$this->requestedAt = $requestedAt;
return $this;
}
public function getRequestText(): string
{
return $this->requestText;
}
public function setRequestText(string $requestText): Requests
{
$this->requestText = $requestText;
return $this;
}
public function getPromptTokens(): int
{
return $this->promptTokens;
}
public function setPromptTokens(int $promptTokens): Requests
{
$this->promptTokens = $promptTokens;
return $this;
}
public function getCompletionTokens(): int
{
return $this->completionTokens;
}
public function setCompletionTokens(int $completionTokens): Requests
{
$this->completionTokens = $completionTokens;
return $this;
}
public function getTotalTokens(): int
{
return $this->totalTokens;
}
public function setTotalTokens(int $totalTokens): Requests
{
$this->totalTokens = $totalTokens;
return $this;
}
public function getToolName(): string
{
return $this->toolName;
}
public function setToolName(string $toolName): Requests
{
$this->toolName = $toolName;
return $this;
}
}

View File

@@ -0,0 +1,46 @@
AI Helper plugin
======
Version 1.1
> This plugin is meant to be later integrated into Chamilo (in a major version
release).
The AI helper plugin integrates into parts of the platform that seem the most useful to teachers/trainers or learners.
Because available Artificial Intelligence (to use the broad term) now allows us to ask for meaningful texts to be generated, we can use those systems to pre-generate content, then let the teacher/trainer review the content before publication.
Currently, this plugin is only integrated into:
- exercises: in the Aiken import form, scrolling down
- learnpaths: option to create one with openai
### OpenAI/ChatGPT
The plugin, created in early 2023, currently only supports OpenAI's ChatGPT API.
Create an account at https://platform.openai.com/signup (if you already have an API account, go
to https://platform.openai.com/login), then generate a secret key at https://platform.openai.com/account/api-keys
or click on "Personal" -> "View API keys".
Click the "Create new secret key" button, copy the key and use it to fill the "API key" field on the
plugin configuration page.
# Changelog
## v1.1
Added tracking for requests and differential settings to enable only in exercises, only in learning paths, or both.
To update from v1.0, execute the following queries manually.
```sql
CREATE TABLE plugin_ai_helper_requests (
id int(11) NOT NULL AUTO_INCREMENT,
user_id int(11) NOT NULL,
tool_name varchar(255) COLLATE utf8_unicode_ci NOT NULL,
requested_at datetime DEFAULT NULL,
request_text varchar(255) COLLATE utf8_unicode_ci NOT NULL,
prompt_tokens int(11) NOT NULL,
completion_tokens int(11) NOT NULL,
total_tokens int(11) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB;
```
If you got this update through Git, you will also need to run `composer install` to update the autoload mechanism.

View File

@@ -0,0 +1,16 @@
<?php
/* For license terms, see /license.txt */
/**
* Install the Ai Helper Plugin.
*
* @package chamilo.plugin.ai_helper
*/
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/AiHelperPlugin.php';
if (!api_is_platform_admin()) {
exit('You must have admin permissions to install plugins');
}
AiHelperPlugin::create()->install();

View File

@@ -0,0 +1,18 @@
<?php
/* For license terms, see /license.txt */
$strings['plugin_title'] = 'AI Helper plugin';
$strings['plugin_comment'] = 'Connects to AI services to help teachers and students through the generation of smart content, like creating questions on any given topic.';
$strings['Description'] = 'Artificial Intelligence services (to use the broad term) now allow you to ask for meaningful texts to be generated, we can use those systems to pre-generate content, then let the teacher/trainer review the content before publication. See Aiken import page in the exercises tool once enabled.';
$strings['tool_enable'] = 'Enable plugin';
$strings['api_name'] = 'AI API to use';
$strings['api_key'] = 'Api key';
$strings['api_key_help'] = 'Secret key generated for your Ai api';
$strings['organization_id'] = 'Organization ID';
$strings['organization_id_help'] = 'In case your api account is from an organization.';
$strings['OpenAI'] = 'OpenAI';
$strings['tool_lp_enable'] = 'Enable in learning paths';
$strings['tool_quiz_enable'] = 'Enable in exercises (Aiken section)';
$strings['tokens_limit'] = 'AI tokens limit';
$strings['tokens_limit_help'] = 'Limit the maximum number of tokens each user has available per month, to avoid high costs of service.';
$strings['ErrorTokensLimit'] = 'Sorry, you have reached the maximum number of tokens or requests configured by the platform administrator for the current calendar month. Please contact your support team or wait for next month before you can issue new requests to the AI Helper.';

View File

@@ -0,0 +1,18 @@
<?php
/* For license terms, see /license.txt */
$strings['plugin_title'] = 'Assistant IA.';
$strings['plugin_comment'] = "Système d'intelligence artificielle (IA) intégré aux endroits qui semblent les plus utiles aux enseignants pour la génération de contenu d'exemple, comme la création de banques de questions sur n'importe quel sujet.";
$strings['Description'] = "L'intelligence artificielle (pour utiliser le terme large) disponible aujourd'hui vous permet de demander la génération de textes réalistes sur n'importe quel sujet. Vous pouvez utiliser ces systèmes pour pré-générer du contenu, et que l'enseignant/formateur puisse perfectionner le contenu avant sa publication.";
$strings['tool_enable'] = 'Activer le plug-in';
$strings['api_name'] = "API de l'IA pour se connecter";
$strings['api_key'] = "API key";
$strings['api_key_help'] = 'Clé secrète générée pour votre API AI';
$strings['organization_id'] = "ID de l'organisation";
$strings['organization_id_help'] = "Si votre compte api provient d'une organisation.";
$strings['OpenAI'] = 'OpenAI';
$strings['tool_lp_enable'] = "Activer dans les parcours";
$strings['tool_quiz_enable'] = "Activer dans les exercices";
$strings['tokens_limit'] = "Limite de jetons IA";
$strings['tokens_limit_help'] = 'Limiter le nombre maximum de jetons disponibles pour chaque utilisateur, par mois, pour éviter des frais de service surdimensionnés.';
$strings['ErrorTokensLimit'] = 'Désolé, vous avez atteint le nombre maximum de jetons ou de requêtes configurés par l\'administrateur de la plateforme pour le mois civil en cours. Veuillez contacter votre équipe d\'assistance ou attendre le mois prochain avant de pouvoir envoyer de nouvelles demandes à AI Helper.';

View File

@@ -0,0 +1,18 @@
<?php
/* For license terms, see /license.txt */
$strings['plugin_title'] = 'Asistente de IA.';
$strings['plugin_comment'] = 'Este plugin se integra en la plataforma para habilitar la generación automatizada de contenido inteligente, como preguntas de elección múltiple.';
$strings['Description'] = 'Debido a que la Inteligencia Artificial (para usar el término amplio) disponible ahora nos permite solicitar que se generen textos significativos, podemos usar esos sistemas para generar contenido previamente y luego dejar que el maestro/entrenador revise el contenido antes de publicarlo..';
$strings['tool_enable'] = 'Habilitar complemento';
$strings['api_name'] = 'API IA para conectar';
$strings['api_key'] = 'Clave API';
$strings['api_key_help'] = 'Clave secreta generada para su API de IA';
$strings['organization_id'] = 'Identificación de la organización';
$strings['organization_id_help'] = 'En caso de que su cuenta API sea de una organización.';
$strings['OpenAI'] = 'OpenAI';
$strings['tool_lp_enable'] = "Activarlo en las lecciones";
$strings['tool_quiz_enable'] = "Activarlo en los ejercicios";
$strings['tokens_limit'] = "Limite de tokens IA";
$strings['tokens_limit_help'] = 'Limitar la cantidad máxima de tokens disponibles por usuario por mes para evitar un alto costo de servicio.';
$strings['ErrorTokensLimit'] = 'Lo sentimos, ha alcanzado la cantidad máxima de tokens o solicitudes configuradas por el administrador de la plataforma para el mes calendario actual. Comuníquese con su equipo de soporte o espere hasta el próximo mes antes de poder enviar nuevas solicitudes a AI Helper.';

View File

@@ -0,0 +1,6 @@
<?php
/* For license terms, see /license.txt */
require_once __DIR__.'/AiHelperPlugin.php';
$plugin_info = AiHelperPlugin::create()->get_info();

View File

@@ -0,0 +1,332 @@
<?php
/* For licensing terms, see /license.txt */
require_once 'OpenAiUrl.php';
class OpenAi
{
private $model = "gpt-4o"; // See https://platform.openai.com/docs/models for possible models
private $headers;
private $contentTypes;
private $timeout = 0;
private $streamMethod;
public function __construct(
string $apiKey,
string $organizationId = ''
) {
$this->contentTypes = [
"application/json" => "Content-Type: application/json",
"multipart/form-data" => "Content-Type: multipart/form-data",
];
$this->headers = [
$this->contentTypes["application/json"],
"Authorization: Bearer $apiKey",
];
if (!empty($organizationId)) {
$this->headers[] = "OpenAI-Organization: $organizationId";
}
}
/**
* @return bool|string
*/
public function listModels()
{
$url = OpenAiUrl::fineTuneModel();
return $this->sendRequest($url, 'GET');
}
/**
* @param $model
*
* @return bool|string
*/
public function retrieveModel($model)
{
$model = "/$model";
$url = OpenAiUrl::fineTuneModel().$model;
return $this->sendRequest($url, 'GET');
}
/**
* @param $opts
* @param null $stream
*
* @return bool|string
*/
public function completion($opts, $stream = null)
{
if ($stream != null && array_key_exists('stream', $opts)) {
if (!$opts['stream']) {
throw new Exception('Please provide a stream function.');
}
$this->streamMethod = $stream;
}
$opts['model'] = $opts['model'] ?? $this->model;
$url = OpenAiUrl::completionsURL();
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @param $opts
*
* @return bool|string
*/
public function createEdit($opts)
{
$url = OpenAiUrl::editsUrl();
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @param $opts
*
* @return bool|string
*/
public function image($opts)
{
$url = OpenAiUrl::imageUrl()."/generations";
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @param $opts
*
* @return bool|string
*/
public function imageEdit($opts)
{
$url = OpenAiUrl::imageUrl()."/edits";
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @param $opts
*
* @return bool|string
*/
public function createImageVariation($opts)
{
$url = OpenAiUrl::imageUrl()."/variations";
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @param $opts
*
* @return bool|string
*/
public function moderation($opts)
{
$url = OpenAiUrl::moderationUrl();
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @param $opts
*
* @return bool|string
*/
public function uploadFile($opts)
{
$url = OpenAiUrl::filesUrl();
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @return bool|string
*/
public function listFiles()
{
$url = OpenAiUrl::filesUrl();
return $this->sendRequest($url, 'GET');
}
/**
* @param $fileId
*
* @return bool|string
*/
public function retrieveFile($fileId)
{
$fileId = "/$fileId";
$url = OpenAiUrl::filesUrl().$fileId;
return $this->sendRequest($url, 'GET');
}
/**
* @param $fileId
*
* @return bool|string
*/
public function retrieveFileContent($fileId)
{
$fileId = "/$fileId/content";
$url = OpenAiUrl::filesUrl().$fileId;
return $this->sendRequest($url, 'GET');
}
/**
* @param $fileId
*
* @return bool|string
*/
public function deleteFile($fileId)
{
$fileId = "/$fileId";
$url = OpenAiUrl::filesUrl().$fileId;
return $this->sendRequest($url, 'DELETE');
}
/**
* @param $opts
*
* @return bool|string
*/
public function createFineTune($opts)
{
$url = OpenAiUrl::fineTuneUrl();
return $this->sendRequest($url, 'POST', $opts);
}
/**
* @return bool|string
*/
public function listFineTunes()
{
$url = OpenAiUrl::fineTuneUrl();
return $this->sendRequest($url, 'GET');
}
/**
* @param $fineTuneId
*
* @return bool|string
*/
public function retrieveFineTune($fineTuneId)
{
$fineTuneId = "/$fineTuneId";
$url = OpenAiUrl::fineTuneUrl().$fineTuneId;
return $this->sendRequest($url, 'GET');
}
/**
* @param $fineTuneId
*
* @return bool|string
*/
public function cancelFineTune($fineTuneId)
{
$fineTuneId = "/$fineTuneId/cancel";
$url = OpenAiUrl::fineTuneUrl().$fineTuneId;
return $this->sendRequest($url, 'POST');
}
/**
* @param $fineTuneId
*
* @return bool|string
*/
public function listFineTuneEvents($fineTuneId)
{
$fineTuneId = "/$fineTuneId/events";
$url = OpenAiUrl::fineTuneUrl().$fineTuneId;
return $this->sendRequest($url, 'GET');
}
/**
* @param $fineTuneId
*
* @return bool|string
*/
public function deleteFineTune($fineTuneId)
{
$fineTuneId = "/$fineTuneId";
$url = OpenAiUrl::fineTuneModel().$fineTuneId;
return $this->sendRequest($url, 'DELETE');
}
/**
* @param $opts
*
* @return bool|string
*/
public function embeddings($opts)
{
$url = OpenAiUrl::embeddings();
return $this->sendRequest($url, 'POST', $opts);
}
public function setTimeout(int $timeout)
{
$this->timeout = $timeout;
}
private function sendRequest(
string $url,
string $method,
array $opts = []
) {
$post_fields = json_encode($opts);
if (array_key_exists('file', $opts) || array_key_exists('image', $opts)) {
$this->headers[0] = $this->contentTypes["multipart/form-data"];
$post_fields = $opts;
} else {
$this->headers[0] = $this->contentTypes["application/json"];
}
$curl_info = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => $post_fields,
CURLOPT_HTTPHEADER => $this->headers,
];
if ($opts == []) {
unset($curl_info[CURLOPT_POSTFIELDS]);
}
if (array_key_exists('stream', $opts) && $opts['stream']) {
$curl_info[CURLOPT_WRITEFUNCTION] = $this->streamMethod;
}
$curl = curl_init();
curl_setopt_array($curl, $curl_info);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/* For licensing terms, see /license.txt */
class OpenAiUrl
{
public const ORIGIN = 'https://api.openai.com';
public const API_VERSION = 'v1';
public const OPEN_AI_URL = self::ORIGIN."/".self::API_VERSION;
public static function completionsURL(): string
{
return self::OPEN_AI_URL."/completions";
}
public static function editsUrl(): string
{
return self::OPEN_AI_URL."/edits";
}
public static function searchURL(string $engine): string
{
return self::OPEN_AI_URL."/engines/$engine/search";
}
public static function enginesUrl(): string
{
return self::OPEN_AI_URL."/engines";
}
public static function engineUrl(string $engine): string
{
return self::OPEN_AI_URL."/engines/$engine";
}
public static function classificationsUrl(): string
{
return self::OPEN_AI_URL."/classifications";
}
public static function moderationUrl(): string
{
return self::OPEN_AI_URL."/moderations";
}
public static function filesUrl(): string
{
return self::OPEN_AI_URL."/files";
}
public static function fineTuneUrl(): string
{
return self::OPEN_AI_URL."/fine-tunes";
}
public static function fineTuneModel(): string
{
return self::OPEN_AI_URL."/models";
}
public static function answersUrl(): string
{
return self::OPEN_AI_URL."/answers";
}
public static function imageUrl(): string
{
return self::OPEN_AI_URL."/images";
}
public static function embeddings(): string
{
return self::OPEN_AI_URL."/embeddings";
}
}

View File

@@ -0,0 +1,57 @@
<?php
/* For license terms, see /license.txt */
/**
Answer questions based on existing knowledge.
*/
require_once __DIR__.'/../../../main/inc/global.inc.php';
require_once __DIR__.'/../AiHelperPlugin.php';
require_once __DIR__.'/../src/openai/OpenAi.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:
$questionTypes = [
'multiple_choice' => 'multiple choice',
'unique_answer' => 'unique answer',
];
$nQ = (int) $_REQUEST['nro_questions'];
$lang = (string) $_REQUEST['language'];
$topic = (string) $_REQUEST['quiz_name'];
$questionType = $questionTypes[$_REQUEST['question_type']] ?? $questionTypes['multiple_choice'];
$prompt = '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.';
$prompt = sprintf($prompt, $nQ, $questionType, $lang, $topic);
$resultText = $plugin->openAiGetCompletionText($prompt, 'quiz');
if (isset($resultText['error']) && true === $resultText['error']) {
echo json_encode([
'success' => false,
'text' => $resultText['message'],
]);
exit;
}
// Returns the text answers generated.
$return = ['success' => false, 'text' => ''];
if (!empty($resultText)) {
$return = [
'success' => true,
'text' => trim($resultText),
];
}
echo json_encode($return);
break;
}

View File

@@ -0,0 +1,216 @@
<?php
/* For license terms, see /license.txt */
/**
Create a learnpath with contents based on existing knowledge.
*/
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
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 .= api_get_css(ChamiloApi::getEditorDocStylePath());
$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;
}

View File

@@ -0,0 +1,16 @@
<?php
/* For license terms, see /license.txt */
/**
* Uninstall the Ai Helper Plugin.
*
* @package chamilo.plugin.ai_helper
*/
require_once __DIR__.'/../../main/inc/global.inc.php';
require_once __DIR__.'/AiHelperPlugin.php';
if (!api_is_platform_admin()) {
exit('You must have admin permissions to install plugins');
}
AiHelperPlugin::create()->uninstall();