diff --git a/classes/completion/chat.php b/classes/completion/chat.php index 99b20ad..d107cd1 100755 --- a/classes/completion/chat.php +++ b/classes/completion/chat.php @@ -27,6 +27,8 @@ use block_openai_chat\completion; defined('MOODLE_INTERNAL') || die; +require_once($CFG->libdir.'/filelib.php'); + class chat extends \block_openai_chat\completion { public function __construct($model, $message, $history, $block_settings, $thread_id = null) { diff --git a/classes/external/answer.php b/classes/external/answer.php new file mode 100644 index 0000000..81ca09d --- /dev/null +++ b/classes/external/answer.php @@ -0,0 +1,147 @@ + + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class answer extends external_api { + + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function execute_parameters() { + return new external_function_parameters( + array( + 'contextid' => new external_value(PARAM_INT, 'the context of the block', VALUE_REQUIRED), + 'message' => new external_value(PARAM_RAW, 'The message from user', VALUE_REQUIRED), + ) + ); + } + + /** + * Get last accessed items by the logged user (activities or resources). + * + * @param int $contextid Context Id of the block + * @param string $message Message from user + * @return array List of items + */ + public static function execute(int $contextid, string $message) { + global $DB; + + // Parameter validation. + [ + 'contextid' => $contextid, + 'message' => $message, + ] = self::validate_parameters(self::execute_parameters(), [ + 'contextid' => $contextid, + 'message' => $message, + ]); + // Context validation and permission check. + // Get the context from the passed in ID. + $context = \context::instance_by_id($contextid); + + // Check the user has permission to use the AI service. + self::validate_context($context); + + $instance_record = $DB->get_record('block_instances', ['blockname' => 'openai_chat', 'id' => $context->instanceid], '*'); + $instance = block_instance('openai_chat', $instance_record); + + $block_settings = []; + $setting_names = [ + 'sourceoftruth', + 'prompt', + 'instructions', + 'username', + 'assistantname', + 'apikey', + 'model', + 'temperature', + 'maxlength', + 'topp', + 'frequency', + 'presence', + 'assistant' + ]; + foreach ($setting_names as $setting) { + if ($instance->config && property_exists($instance->config, $setting)) { + $block_settings[$setting] = $instance->config->$setting ? $instance->config->$setting : ""; + } else { + $block_settings[$setting] = ""; + } + } + + $engine_class; + $model = get_config('block_openai_chat', 'model'); + $api_type = get_config('block_openai_chat', 'type'); + $engine_class = "\block_openai_chat\completion\\$api_type"; + + $completion = new $engine_class(...[$model, $message, $history, $block_settings, $thread_id]); + $response = $completion->create_completion($context); + + // Format the markdown of each completion message into HTML. + $response = format_text($response["message"], FORMAT_MARKDOWN, ['context' => $context]); + + // Return the response. + return [ + 'success' => true, // TODO + 'generatedcontent' => $response, + 'finishreason' => '', // TODO + 'error' => '', // TODO + 'timecreated' => 0, // TODO + 'message' => $message, + ]; + } + + /** + * Generate content return value. + * + * @return external_function_parameters + */ + public static function execute_returns(): external_function_parameters { + return new external_function_parameters([ + 'success' => new external_value( + PARAM_BOOL, + 'Was the request successful', + VALUE_REQUIRED + ), + 'timecreated' => new external_value( + PARAM_INT, + 'The time the request was created', + VALUE_REQUIRED, + ), + 'message' => new external_value( + PARAM_RAW, + 'The prompt text for the AI service', + VALUE_REQUIRED, + ), + 'generatedcontent' => new external_value( + PARAM_RAW, + 'The text generated by AI.', + VALUE_DEFAULT, + ), + 'finishreason' => new external_value( + PARAM_ALPHA, + 'The reason generation was stopped', + VALUE_DEFAULT, + 'stop', + ), + 'error' => new external_value( + PARAM_TEXT, + 'Error message if any', + VALUE_DEFAULT, + '', + ), + ]); + } +} diff --git a/classes/output/mobile.php b/classes/output/mobile.php new file mode 100644 index 0000000..3264f33 --- /dev/null +++ b/classes/output/mobile.php @@ -0,0 +1,103 @@ +. + +namespace block_openai_chat\output; + +use context_course; + +/** + * Callbacks class for mobile app. + * + * @package block_openai_chat + * @copyright 2025 Daniel Neis Araujo + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mobile { + + /** + * Callback to render the block contents on mobile app. + * @param array $args Data provided by standard CoreBlockDelegate. + */ + public static function get_block_content($args) { + global $CFG, $DB, $OUTPUT; + + $context = \core\context\block::instance($args['blockid']); + + $config = get_config('block_openai_chat'); + + $persistconvo = get_config('block_openai_chat', 'persistconvo'); + if (!empty($config)) { + $persistconvo = (property_exists($config, 'persistconvo') && get_config('block_openai_chat', 'allowinstancesettings')) ? $config->persistconvo : $persistconvo; + } + + // Determine if name labels should be shown. + $showlabelscss = ''; + if (!empty($config) && empty($config->showlabels)) { + $showlabelscss = ' + .openai_message:before { + display: none; + } + .openai_message { + margin-bottom: 0.5rem; + } + '; + } + + // First, fetch the global settings for these (and the defaults if not set) + $assistantname = get_config('block_openai_chat', 'assistantname') ? get_config('block_openai_chat', 'assistantname') : get_string('defaultassistantname', 'block_openai_chat'); + $username = get_config('block_openai_chat', 'username') ? get_config('block_openai_chat', 'username') : get_string('defaultusername', 'block_openai_chat'); + + $title = get_string('openai_chat', 'block_openai_chat'); + $configdata = $DB->get_field('block_instances', 'configdata', ['id' => $context->instanceid]); + if (!empty($configdata)) { + $cfg = base64_decode($configdata); + $cfg = unserialize_object($cfg); + if (!empty($cfg->title)) { + $title = $cfg->title; + } + } + + // Then, override with local settings if available + if (!empty($config)) { + $assistantname = (property_exists($config, 'assistantname') && $config->assistantname) ? $config->assistantname : $assistantname; + $username = (property_exists($config, 'username') && $config->username) ? $config->username : $username; + } + $assistantname = format_string($assistantname, true, ['context' => $context]); + $username = format_string($username, true, ['context' => $context]); + + $contextdata = [ + 'logging_enabled' => get_config('block_openai_chat', 'logging'), + 'pix_popout' => '/blocks/openai_chat/pix/arrow-up-right-from-square.svg', + 'pix_arrow_right' => '/blocks/openai_chat/pix/arrow-right.svg', + 'pix_refresh' => '/blocks/openai_chat/pix/refresh.svg', + 'username' => $username, + 'assistantname' => $assistantname, + 'showlabelscss' => $showlabelscss, + 'contextid' => $context->id, + 'title' => $title, + ]; + + return [ + 'templates' => [ + [ + 'id' => 'main', + 'html' => $OUTPUT->render_from_template('block_openai_chat/mobile', $contextdata) + ] + ], + 'javascript' => file_get_contents("{$CFG->dirroot}/blocks/openai_chat/mobile.js") + ]; + } +} diff --git a/db/mobile.php b/db/mobile.php new file mode 100644 index 0000000..d69a212 --- /dev/null +++ b/db/mobile.php @@ -0,0 +1,46 @@ +. + +/** + * Mobile App addons definition. + * + * @package block_openai_chat + * @copyright 2025 Daniel Neis Araujo + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +global $CFG; + +$addons = [ + 'block_openai_chat' => [ + 'handlers' => [ + 'completionlevels' => [ + 'delegate' => 'CoreBlockDelegate', + 'method' => 'get_block_content', + 'displaydata' => [ + 'class' => 'block block_openai_chat', + ], + 'styles' => [ + 'url' => $CFG->wwwroot . '/blocks/openai_chat/styles.css', + 'version' => 11, + ], + ], + ], + 'lang' => [ + [ 'pluginname', 'block_openai_chat' ], + ], + ], +]; diff --git a/db/services.php b/db/services.php new file mode 100644 index 0000000..71f7ac7 --- /dev/null +++ b/db/services.php @@ -0,0 +1,37 @@ +. + +/** + * File description. + * + * @package block_openai_chat + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$functions = array( + + 'block_openai_chat_answer' => array( + 'classpath' => 'blocks/openai_chat/classes/external/answer.php', + 'classname' => 'block_openai_chat\external\answer', + 'methodname' => 'execute', + 'description' => 'Get answer.', + 'type' => 'read', + 'ajax' => true, + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), +); diff --git a/mobile.js b/mobile.js new file mode 100644 index 0000000..c0559f1 --- /dev/null +++ b/mobile.js @@ -0,0 +1,39 @@ +this.answer = function(result) { + var msg = ''; + if (result.error == "") { + msg = result.generatedcontent; + } else { + msg = 'error: ' + result.error; + } + this.update_history(msg); +}; + +this.update_history = function(msg) { + let question = document.getElementById('openai_input').value; + if (question == '') { + } else { + document.getElementById('openai_input').value = ''; + document.getElementById('openai_chat_log').insertAdjacentHTML( + 'beforeend', + '
' + + '