MDL-82627 AI: LMS Subsystem
Initial implementation of the LMS AI subsystem. Originally implemented in MDL-80889 Co-authored-by: Meirza <[email protected]>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Manage AI subsystem plugins settings.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHANUMEXT);
|
||||
$type = required_param('type', PARAM_PLUGIN); // Must be provider or placement.
|
||||
$name = required_param('name', PARAM_PLUGIN); // Plugin name, e.g openai.
|
||||
|
||||
$syscontext = context_system::instance();
|
||||
$PAGE->set_url('/admin/ai.php');
|
||||
$PAGE->set_context($syscontext);
|
||||
|
||||
require_admin();
|
||||
require_sesskey();
|
||||
|
||||
$return = new moodle_url('/admin/settings.php', ['section' => "aisettings$type"]);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type("ai$type");
|
||||
$sortorder = array_flip(array_keys($plugins));
|
||||
|
||||
if (!isset($plugins[$name])) {
|
||||
throw new moodle_exception('aipluginnotfound', 'core_ai', $return, $name);
|
||||
}
|
||||
|
||||
$plugintypename = $plugins[$name]->type . '_' . $plugins[$name]->name;
|
||||
|
||||
switch ($action) {
|
||||
case 'disable':
|
||||
if ($plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
|
||||
$class::enable_plugin($name, false);
|
||||
}
|
||||
break;
|
||||
case 'enable':
|
||||
if (!$plugins[$name]->is_enabled()) {
|
||||
$class = core_plugin_manager::resolve_plugininfo_class("aisettings$type");
|
||||
$class::enable_plugin($name, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
redirect($return);
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Adds settings links to admin tree.
|
||||
* AI gets top billing in general because it's the future.
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
// Add settings page for AI provider settings.
|
||||
$providers = new admin_settingpage('aiprovider', new lang_string('aiprovidersettings', 'ai'));
|
||||
$providers->add(new admin_setting_heading('availableproviders',
|
||||
get_string('availableproviders', 'core_ai'),
|
||||
get_string('availableproviders_desc', 'core_ai')));
|
||||
$providers->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
'aiprovider',
|
||||
\core_ai\table\aiprovider_management_table::class,
|
||||
'manageaiproviders',
|
||||
new lang_string('manageaiproviders', 'core_ai'),
|
||||
));
|
||||
$ADMIN->add('ai', $providers);
|
||||
|
||||
// Load settings for all AI providers.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiprovider');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\aiprovider $plugin */
|
||||
$plugin->load_settings($ADMIN, 'ai', $hassiteconfig);
|
||||
}
|
||||
|
||||
// Add settings page for AI placement settings.
|
||||
$placements = new admin_settingpage('aiplacement', new lang_string('aiplacementsettings', 'ai'));
|
||||
$placements->add(new admin_setting_heading('availableplacements',
|
||||
get_string('availableplacements', 'core_ai'),
|
||||
get_string('availableplacements_desc', 'core_ai')));
|
||||
$placements->add(new \core_admin\admin\admin_setting_plugin_manager(
|
||||
'aiplacement',
|
||||
\core_ai\table\aiplacement_management_table::class,
|
||||
'manageaiplacements',
|
||||
new lang_string('manageaiplacements', 'core_ai'),
|
||||
));
|
||||
$ADMIN->add('ai', $placements);
|
||||
|
||||
// Load settings for all placements.
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('aiplacement');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\aiprovider $plugin */
|
||||
$plugin->load_settings($ADMIN, 'ai', $hassiteconfig);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ if ($hassiteconfig) {
|
||||
$ADMIN->add('root', new admin_category('users', new lang_string('users','admin')));
|
||||
$ADMIN->add('root', new admin_category('courses', new lang_string('courses','admin')));
|
||||
$ADMIN->add('root', new admin_category('grades', new lang_string('grades')));
|
||||
$ADMIN->add('root', new admin_category('ai', new lang_string('ai', 'ai')));
|
||||
$ADMIN->add('root', new admin_category('analytics', new lang_string('analytics', 'analytics')));
|
||||
$ADMIN->add('root', new admin_category('competencies', new lang_string('competencies', 'core_competency')));
|
||||
$ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empty($CFG->enablebadges)));
|
||||
|
||||
@@ -51,7 +51,9 @@
|
||||
data-toggle-method="{{dataattributes.toggle-method}}"
|
||||
data-action="{{dataattributes.action}}"
|
||||
data-plugin="{{dataattributes.plugin}}"
|
||||
data-state="{{dataattributes.state}}">
|
||||
data-state="{{dataattributes.state}}"
|
||||
data-value="{{dataattributes.value}}"
|
||||
>
|
||||
{{> core/toggle }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_admin/setting_savebutton
|
||||
|
||||
Admin save button settings template.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
{{!
|
||||
Settings.
|
||||
}}
|
||||
<div class="row">
|
||||
<div class="offset-sm-3 col-sm-3">
|
||||
<button type="submit" class="btn btn-primary">{{#str}}savechanges, admin{{/str}}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_admin/table/namedesc
|
||||
|
||||
Admin setting template to display an name and description
|
||||
in a single table cell.
|
||||
|
||||
Context variables required for this template:
|
||||
* name - Name of the table setting
|
||||
* description - Description of the setting
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "Fancy Setting",
|
||||
"description": "Text that describes this setting well"
|
||||
}
|
||||
}}
|
||||
{{!
|
||||
Name and description.
|
||||
}}
|
||||
<div>
|
||||
<div><strong>{{name}}</strong></div>
|
||||
<div>{{description}}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\admin;
|
||||
|
||||
use admin_setting;
|
||||
|
||||
/**
|
||||
* Admin setting plugin manager.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_setting_action_manager extends admin_setting {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pluginname The name of the plugin these actions related too.
|
||||
* @param string $tableclass The class of the management table to use.
|
||||
* @param string $name The unique name.
|
||||
* @param string $visiblename The localised name.
|
||||
* @param string $description The localised long description in Markdown format.
|
||||
* @param string $defaultsetting The default setting.
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var string The name of the plugin these actions related too */
|
||||
protected string $pluginname,
|
||||
/** @var string The class of the management table to use */
|
||||
protected string $tableclass,
|
||||
string $name,
|
||||
string $visiblename,
|
||||
string $description = '',
|
||||
string $defaultsetting = '',
|
||||
) {
|
||||
$this->nosave = true;
|
||||
parent::__construct($name, $visiblename, $description, $defaultsetting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true, does nothing
|
||||
*
|
||||
* @return bool Always return true.
|
||||
*/
|
||||
public function get_setting(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns '', does not write anything.
|
||||
*
|
||||
* @param mixed $data string or array, must not be NULL
|
||||
* @return string Always returns ''.
|
||||
*/
|
||||
public function write_setting($data): string {
|
||||
// Do not write any setting.
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the XHTML to display the control.
|
||||
*
|
||||
* @param string $data Unused
|
||||
* @param string $query
|
||||
* @throws \coding_exception
|
||||
* @return string
|
||||
*/
|
||||
public function output_html($data, $query = ''): string {
|
||||
$table = new $this->tableclass($this->pluginname);
|
||||
if (!($table instanceof \core_ai\table\aiprovider_action_management_table)
|
||||
&& !($table instanceof \core_ai\table\aiplacement_action_management_table)) {
|
||||
throw new \coding_exception(
|
||||
$this->tableclass
|
||||
. 'must be an instance aiprovider_action_management_table or aiplacement_action_management_table'
|
||||
);
|
||||
}
|
||||
return highlight($query, $table->get_content());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\admin;
|
||||
|
||||
use admin_settingpage;
|
||||
|
||||
/**
|
||||
* AI provider plugin admin settings pages manager.
|
||||
*
|
||||
* This class extends the admin_settingpage class and is used to manage admin settings pages.
|
||||
* It includes a method to hide the "Save changes" button from the admin provider specific settings page.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_settingspage_provider extends admin_settingpage {
|
||||
/**
|
||||
* Disable the "Save Changes" button on the admin provider settings page.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function show_save(): bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use coding_exception;
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Base Action class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class base {
|
||||
/** @var int The context ID the action was created in */
|
||||
protected int $contextid;
|
||||
|
||||
/** @var int Timestamp the action object was created. */
|
||||
protected int $timecreated;
|
||||
|
||||
/**
|
||||
* Responsible for storing any action specific data in the database.
|
||||
*
|
||||
* @param response_base $response The response object to store.
|
||||
* @return int The id of the stored action.
|
||||
*/
|
||||
abstract public function store(response_base $response): int;
|
||||
|
||||
/**
|
||||
* Get the basename of the class.
|
||||
* This is used to generate the action name and description.
|
||||
*
|
||||
* @return string The basename of the class.
|
||||
*/
|
||||
public static function get_basename(): string {
|
||||
return basename(str_replace('\\', '/', static::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action name.
|
||||
* Defaults to the action name string.
|
||||
*
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
$stringid = 'action_' . self::get_basename();
|
||||
return get_string($stringid, 'core_ai');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action description.
|
||||
* Defaults to the action description string.
|
||||
*
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_description(): string {
|
||||
$stringid = 'action_' . self::get_basename() . '_desc';
|
||||
return get_string($stringid, 'core_ai');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system instruction for the action.
|
||||
*
|
||||
* @return string The system instruction for the action.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function get_system_instruction(): string {
|
||||
$stringid = 'action_' . $this->get_basename() . '_instruction';
|
||||
|
||||
// If the string doesn't exist, return an empty string.
|
||||
if (!get_string_manager()->string_exists($stringid, 'core_ai')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return get_string($stringid, 'core_ai');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration option.
|
||||
*
|
||||
* @param string $name The name of the configuration option to get.
|
||||
* @return mixed The value of the configuration option.
|
||||
*/
|
||||
public function get_configuration(string $name): mixed {
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the correct table name for the action.
|
||||
*
|
||||
* @return string The correct table name for the action.
|
||||
*/
|
||||
protected function get_tablename(): string {
|
||||
// Table name should always be in this format.
|
||||
return 'ai_action_' . $this->get_basename();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Generate images class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class generate_image extends base {
|
||||
/** @var int The user id requesting the action. */
|
||||
protected int $userid;
|
||||
|
||||
/** @var string The prompt text used to generate the image */
|
||||
protected string $prompttext;
|
||||
|
||||
/** @var string The aspect ratio of the generated image */
|
||||
protected string $aspectratio;
|
||||
|
||||
/** @var string The quality of the generated image */
|
||||
protected string $quality;
|
||||
|
||||
/** @var int The number of images to generate */
|
||||
protected int $numimages;
|
||||
|
||||
/** @var string The visual style of the generated image */
|
||||
protected string $style;
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
* It’s responsible for performing any setup tasks,
|
||||
* such as getting additional data from the database etc.
|
||||
*
|
||||
* @param int $contextid The context id the action was created in.
|
||||
* @param int $userid The user id making the request.
|
||||
* @param string $prompttext The prompt text used to generate the image.
|
||||
* @param string $quality The quality of the generated image.
|
||||
* @param string $aspectratio The aspect ratio of the generated image.
|
||||
* @param int $numimages The number of images to generate.
|
||||
* @param string $style The visual style of the generated image.
|
||||
* @return void.
|
||||
*/
|
||||
public function __construct(
|
||||
int $contextid,
|
||||
int $userid,
|
||||
string $prompttext,
|
||||
string $quality,
|
||||
string $aspectratio,
|
||||
int $numimages,
|
||||
string $style
|
||||
) {
|
||||
$this->timecreated = \core\di::get(\core\clock::class)->time();
|
||||
$this->contextid = $contextid;
|
||||
$this->userid = $userid;
|
||||
$this->prompttext = $prompttext;
|
||||
$this->quality = $quality;
|
||||
$this->aspectratio = $aspectratio;
|
||||
$this->numimages = $numimages;
|
||||
$this->style = $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the action in the database.
|
||||
*
|
||||
* @param response_base $response The result of the action.
|
||||
* @return int The id of the stored action.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function store(response_base $response): int {
|
||||
global $DB;
|
||||
|
||||
$responsearr = $response->get_response();
|
||||
|
||||
$tablename = $this->get_tablename();
|
||||
$record = new \stdClass();
|
||||
$record->prompt = $this->prompttext;
|
||||
$record->numberimages = $this->numimages;
|
||||
$record->quality = $this->quality;
|
||||
$record->aspectratio = $this->aspectratio;
|
||||
$record->style = $this->style;
|
||||
$record->sourceurl = $responsearr['sourceurl']; // Can be null.
|
||||
$record->revisedprompt = $responsearr['revisedprompt']; // Can be null.
|
||||
|
||||
return $DB->insert_record($tablename, $record);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Generate text class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class generate_text extends base {
|
||||
/** @var int The user id requesting the action. */
|
||||
protected int $userid;
|
||||
|
||||
/** @var string The prompt text used to generate the text */
|
||||
protected string $prompttext;
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
* It’s responsible for performing any setup tasks,
|
||||
* such as getting additional data from the database etc.
|
||||
*
|
||||
* @param int $contextid The context id the action was created in.
|
||||
* @param int $userid The user id making the request.
|
||||
* @param string $prompttext The prompt text used to generate the image.
|
||||
* @return void.
|
||||
*/
|
||||
public function __construct(
|
||||
int $contextid,
|
||||
int $userid,
|
||||
string $prompttext
|
||||
) {
|
||||
$this->timecreated = \core\di::get(\core\clock::class)->time();
|
||||
$this->contextid = $contextid;
|
||||
$this->userid = $userid;
|
||||
$this->prompttext = $prompttext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the action in the database.
|
||||
*
|
||||
* @param response_base $response The result of the action.
|
||||
* @return int The id of the stored action.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function store(response_base $response): int {
|
||||
global $DB;
|
||||
|
||||
$responsearr = $response->get_response();
|
||||
|
||||
$tablename = $this->get_tablename();
|
||||
$record = new \stdClass();
|
||||
$record->prompt = $this->prompttext;
|
||||
$record->responseid = $responsearr['id']; // Can be null.
|
||||
$record->fingerprint = $responsearr['fingerprint']; // Can be null.
|
||||
$record->generatedcontent = $responsearr['generatedcontent']; // Can be null.
|
||||
$record->finishreason = $responsearr['finishreason']; // Can be null.
|
||||
$record->prompttokens = $responsearr['prompttokens']; // Can be null.
|
||||
$record->completiontoken = $responsearr['completiontokens']; // Can be null.
|
||||
|
||||
return $DB->insert_record($tablename, $record);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions\responses;
|
||||
|
||||
use coding_exception;
|
||||
|
||||
/**
|
||||
* Action response base class.
|
||||
* Any method that processes an action must return an instance of this class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class response_base {
|
||||
/** @var int The timestamp of when the response was created. */
|
||||
private int $timecreated;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param bool $success The success status of the action.
|
||||
* @param string $actionname The name of the action that was processed.
|
||||
* @param int $errorcode Error code. Must exist if success is false.
|
||||
* @param string $errormessage Error message. Must exist if success is false
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var bool The success status of the action. */
|
||||
private bool $success,
|
||||
/** @var string The name of the action that was processed. */
|
||||
private string $actionname,
|
||||
/** @var int Error code. Must exist if status is error. */
|
||||
private int $errorcode = 0,
|
||||
/** @var string Error message. Must exist if status is error */
|
||||
private string $errormessage = '',
|
||||
|
||||
) {
|
||||
$this->timecreated = \core\di::get(\core\clock::class)->time();
|
||||
if (!$success && ($errorcode == 0 || empty($errormessage))) {
|
||||
throw new coding_exception('Error code and message must exist in an error response.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the response data returned by the AI provider.
|
||||
*
|
||||
* @param array $response The response data returned by the AI provider.
|
||||
* @return void
|
||||
*/
|
||||
abstract public function set_response(array $response): void;
|
||||
|
||||
/**
|
||||
* Get the response data returned by the AI provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function get_response(): array;
|
||||
|
||||
/**
|
||||
* Get the success status of the action.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_success(): bool {
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of when the response was created.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_timecreated(): int {
|
||||
return $this->timecreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the action that was processed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_actionname(): string {
|
||||
return $this->actionname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error code.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_errorcode(): int {
|
||||
return $this->errorcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_errormessage(): string {
|
||||
return $this->errormessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions\responses;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Generate image action response class.
|
||||
* Any method that processes an action must return an instance of this class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class response_generate_image extends response_base {
|
||||
/** @var \stored_file|null The URL of the generated image. */
|
||||
private ?\stored_file $draftfile = null;
|
||||
|
||||
/** @var string|null The revised prompt generated by the AI. */
|
||||
private ?string $revisedprompt = null;
|
||||
|
||||
/** @var string|null The URL of the source image used to generate the image. */
|
||||
private ?string $sourceurl = null;
|
||||
|
||||
/**
|
||||
* Set the response data returned by the AI provider.
|
||||
*
|
||||
* @param array $response The response data returned by the AI provider.
|
||||
* @return void
|
||||
*/
|
||||
public function set_response(array $response): void {
|
||||
$this->draftfile = $response['draftfile'] ?? null;
|
||||
$this->revisedprompt = $response['revisedprompt'] ?? null;
|
||||
$this->sourceurl = $response['sourceurl'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data returned by the AI provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_response(): array {
|
||||
return [
|
||||
'draftfile' => $this->draftfile,
|
||||
'revisedprompt' => $this->revisedprompt,
|
||||
'sourceurl' => $this->sourceurl,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions\responses;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Generate text action response class.
|
||||
* Any method that processes an action must return an instance of this class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class response_generate_text extends response_base {
|
||||
|
||||
/** @var string|null A unique identifier for the chat completion, returned by the AI. */
|
||||
private ?string $id = null;
|
||||
|
||||
/** @var string|null This fingerprint represents the backend configuration that the model runs with. */
|
||||
private ?string $fingerprint = null;
|
||||
|
||||
/** @var string|null The contents of the generated message. */
|
||||
private ?string $generatedcontent = null;
|
||||
|
||||
/** @var string|null The reason the model stopped generating tokens. */
|
||||
private ?string $finishreason = null;
|
||||
|
||||
/** @var string|null Number of tokens in the prompt. */
|
||||
private ?string $prompttokens = null;
|
||||
|
||||
/** @var string|null Number of tokens in the generated completion. */
|
||||
private ?string $completiontokens = null;
|
||||
|
||||
/**
|
||||
* Set the response data returned by the AI provider.
|
||||
*
|
||||
* @param array $response The response data returned by the AI provider.
|
||||
* @return void
|
||||
*/
|
||||
public function set_response(array $response): void {
|
||||
$this->id = $response['id'] ?? null;
|
||||
$this->fingerprint = $response['fingerprint'] ?? null;
|
||||
$this->generatedcontent = $response['generatedcontent'] ?? null;
|
||||
$this->finishreason = $response['finishreason'] ?? null;
|
||||
$this->prompttokens = $response['prompttokens'] ?? null;
|
||||
$this->completiontokens = $response['completiontokens'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data returned by the AI provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_response(): array {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'fingerprint' => $this->fingerprint,
|
||||
'generatedcontent' => $this->generatedcontent,
|
||||
'finishreason' => $this->finishreason,
|
||||
'prompttokens' => $this->prompttokens,
|
||||
'completiontokens' => $this->completiontokens,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions\responses;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Summarise text action response class.
|
||||
* Any method that processes an action must return an instance of this class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class response_summarise_text extends response_base {
|
||||
|
||||
/** @var string|null A unique identifier for the chat completion, returned by the AI. */
|
||||
private ?string $id = null;
|
||||
|
||||
/** @var string|null This fingerprint represents the backend configuration that the model runs with. */
|
||||
private ?string $fingerprint = null;
|
||||
|
||||
/** @var string|null The contents of the generated message. */
|
||||
private ?string $generatedcontent = null;
|
||||
|
||||
/** @var string|null The reason the model stopped generating tokens. */
|
||||
private ?string $finishreason = null;
|
||||
|
||||
/** @var string|null Number of tokens in the prompt. */
|
||||
private ?string $prompttokens = null;
|
||||
|
||||
/** @var string|null Number of tokens in the generated completion. */
|
||||
private ?string $completiontokens = null;
|
||||
|
||||
/**
|
||||
* Set the response data returned by the AI provider.
|
||||
*
|
||||
* @param array $response The response data returned by the AI provider.
|
||||
* @return void
|
||||
*/
|
||||
public function set_response(array $response): void {
|
||||
$this->id = $response['id'] ?? null;
|
||||
$this->fingerprint = $response['fingerprint'] ?? null;
|
||||
$this->generatedcontent = $response['generatedcontent'] ?? null;
|
||||
$this->finishreason = $response['finishreason'] ?? null;
|
||||
$this->prompttokens = $response['prompttokens'] ?? null;
|
||||
$this->completiontokens = $response['completiontokens'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response data returned by the AI provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_response(): array {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'fingerprint' => $this->fingerprint,
|
||||
'generatedcontent' => $this->generatedcontent,
|
||||
'finishreason' => $this->finishreason,
|
||||
'prompttokens' => $this->prompttokens,
|
||||
'completiontokens' => $this->completiontokens,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
|
||||
/**
|
||||
* Summarise text class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class summarise_text extends base {
|
||||
/** @var int The user id requesting the action. */
|
||||
protected int $userid;
|
||||
|
||||
/** @var string The prompt text used to generate the summary text */
|
||||
protected string $prompttext;
|
||||
|
||||
/**
|
||||
* Constructor for the class.
|
||||
* It’s responsible for performing any setup tasks,
|
||||
* such as getting additional data from the database etc.
|
||||
*
|
||||
* @param int $contextid The context id the action was created in.
|
||||
* @param int $userid The user id making the request.
|
||||
* @param string $prompttext The prompt text used to generate the image.
|
||||
* @return void.
|
||||
*/
|
||||
public function __construct(
|
||||
int $contextid,
|
||||
int $userid,
|
||||
string $prompttext
|
||||
) {
|
||||
$this->timecreated = \core\di::get(\core\clock::class)->time();
|
||||
$this->contextid = $contextid;
|
||||
$this->userid = $userid;
|
||||
$this->prompttext = $prompttext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the action in the database.
|
||||
*
|
||||
* @param response_base $response The result of the action.
|
||||
* @return int The id of the stored action.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public function store(response_base $response): int {
|
||||
global $DB;
|
||||
|
||||
$responsearr = $response->get_response();
|
||||
|
||||
$tablename = $this->get_tablename();
|
||||
$record = new \stdClass();
|
||||
$record->prompt = $this->prompttext;
|
||||
$record->responseid = $responsearr['id']; // Can be null.
|
||||
$record->fingerprint = $responsearr['fingerprint']; // Can be null.
|
||||
$record->generatedcontent = $responsearr['generatedcontent']; // Can be null.
|
||||
$record->finishreason = $responsearr['finishreason']; // Can be null.
|
||||
$record->prompttokens = $responsearr['prompttokens']; // Can be null.
|
||||
$record->completiontoken = $responsearr['completiontokens']; // Can be null.
|
||||
|
||||
return $DB->insert_record($tablename, $record);
|
||||
}
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\cache;
|
||||
|
||||
use cache_definition;
|
||||
|
||||
/**
|
||||
* Cache class for AI policy.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class policy implements \cache_data_source {
|
||||
|
||||
/** @var policy the singleton instance of this class. */
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* Returns an instance of the data source class that the cache can use for loading data using the other methods
|
||||
* specified by this interface.
|
||||
*
|
||||
* @param cache_definition $definition
|
||||
* @return object
|
||||
*/
|
||||
public static function get_instance_for_cache(cache_definition $definition): policy {
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new policy();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data for the key provided ready formatted for caching.
|
||||
*
|
||||
* @param string|int $key The key to load.
|
||||
* @return mixed What ever data should be returned, or false if it can't be loaded.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function load_for_cache($key) {
|
||||
global $DB;
|
||||
|
||||
return $DB->record_exists('ai_policy_register', ['userid' => $key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads several keys for the cache.
|
||||
*
|
||||
* @param array $keys An array of keys each of which will be string|int.
|
||||
* @return array An array of matching data items.
|
||||
*/
|
||||
public function load_many_for_cache(array $keys): array {
|
||||
global $DB;
|
||||
$return = [];
|
||||
[$insql, $inparams] = $DB->get_in_or_equal($keys);
|
||||
$sql = "SELECT userid
|
||||
FROM {ai_policy_register}
|
||||
WHERE userid " . $insql;
|
||||
|
||||
$results = $DB->get_fieldset_sql($sql, $inparams);
|
||||
foreach ($keys as $key) {
|
||||
$return[$key] = in_array($key, $results);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\external;
|
||||
|
||||
use core_ai\manager;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_value;
|
||||
|
||||
/**
|
||||
* External API to get a users AI policy acceptance.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class get_policy_status extends external_api {
|
||||
|
||||
/**
|
||||
* Get policy parameters.
|
||||
*
|
||||
* @since Moodle 4.5
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function get_policy_status_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'userid' => new external_value(
|
||||
PARAM_INT,
|
||||
'The user ID',
|
||||
VALUE_REQUIRED),
|
||||
'contextid' => new external_value(
|
||||
PARAM_INT,
|
||||
'The context ID',
|
||||
VALUE_REQUIRED),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a users AI policy acceptance.
|
||||
*
|
||||
* @since Moodle 4.5
|
||||
* @param int $userid The user ID.
|
||||
* @param int $contextid The context ID.
|
||||
* @return array The generated content.
|
||||
*/
|
||||
public static function get_policy_status(
|
||||
int $userid,
|
||||
int $contextid,
|
||||
): array {
|
||||
global $USER;
|
||||
// Parameter validation.
|
||||
[
|
||||
'userid' => $userid,
|
||||
'contextid' => $contextid,
|
||||
] = self::validate_parameters(self::get_policy_status_parameters(), [
|
||||
'userid' => $userid,
|
||||
'contextid' => $contextid,
|
||||
]);
|
||||
|
||||
// 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);
|
||||
require_capability('moodle/ai:getpolicy', $context);
|
||||
|
||||
// If the context level is that of a user, check the user is the same as the one passed in.
|
||||
if ($context->contextlevel == CONTEXT_USER && ((int) $USER->id !== $userid)) {
|
||||
throw new \moodle_exception('invaliduser', 'error');
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => manager::get_user_policy($userid),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate content return value.
|
||||
*
|
||||
* @since Moodle 4.5
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function get_policy_status_returns(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'status' => new external_value(
|
||||
PARAM_BOOL,
|
||||
'True if the policy was accepted, false otherwise.',
|
||||
VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\external;
|
||||
|
||||
use core_ai\manager;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_value;
|
||||
|
||||
/**
|
||||
* External API to set provider action enabled.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class set_action extends external_api {
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'plugin' => new external_value(PARAM_TEXT, 'The name of the plugin', VALUE_REQUIRED),
|
||||
'state' => new external_value(PARAM_INT, 'The target state', VALUE_REQUIRED),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the providers action state.
|
||||
*
|
||||
* @param string $plugin The name of the plugin
|
||||
* @param int $state The target state
|
||||
* @return null
|
||||
*/
|
||||
public static function execute(
|
||||
string $plugin,
|
||||
int $state,
|
||||
): array {
|
||||
// Parameter validation.
|
||||
[
|
||||
'plugin' => $plugin,
|
||||
'state' => $state,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'plugin' => $plugin,
|
||||
'state' => $state,
|
||||
]);
|
||||
|
||||
[$plugin, $action] = explode('-', $plugin);
|
||||
$actionname = get_string("action_$action", 'core_ai');
|
||||
|
||||
if (!empty($state)) {
|
||||
\core\notification::add(
|
||||
get_string('plugin_enabled', 'core_admin', $actionname),
|
||||
\core\notification::SUCCESS
|
||||
);
|
||||
} else {
|
||||
\core\notification::add(
|
||||
get_string('plugin_disabled', 'core_admin', $actionname),
|
||||
\core\notification::SUCCESS
|
||||
);
|
||||
}
|
||||
|
||||
manager::enable_action($plugin, $action, $state);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the return structure of the external service.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_returns(): external_function_parameters {
|
||||
return new external_function_parameters([]);
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\external;
|
||||
|
||||
use core_ai\manager;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_value;
|
||||
|
||||
/**
|
||||
* External API to set a users AI policy acceptance.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class set_policy_status extends external_api {
|
||||
|
||||
/**
|
||||
* Set policy parameters.
|
||||
*
|
||||
* @since Moodle 4.5
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function set_policy_status_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'userid' => new external_value(
|
||||
PARAM_INT,
|
||||
'The user ID',
|
||||
VALUE_REQUIRED),
|
||||
'contextid' => new external_value(
|
||||
PARAM_INT,
|
||||
'The context ID',
|
||||
VALUE_REQUIRED),
|
||||
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a users AI policy acceptance.
|
||||
*
|
||||
* @since Moodle 4.5
|
||||
* @param int $userid The user ID.
|
||||
* @param int $contextid The context ID.
|
||||
* @return array The generated content.
|
||||
*/
|
||||
public static function set_policy_status(
|
||||
int $userid,
|
||||
int $contextid,
|
||||
): array {
|
||||
global $USER;
|
||||
// Parameter validation.
|
||||
[
|
||||
'userid' => $userid,
|
||||
'contextid' => $contextid,
|
||||
] = self::validate_parameters(self::set_policy_status_parameters(), [
|
||||
'userid' => $userid,
|
||||
'contextid' => $contextid,
|
||||
]);
|
||||
|
||||
// No special permissions required to accept the policy.
|
||||
// Just amke sure the user id is the user that is logged in.
|
||||
// You can't accept a policy on behalf of someone else.
|
||||
if ($userid !== (int)$USER->id) {
|
||||
throw new \moodle_exception('invaliduser');
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => manager::set_user_policy($userid, $contextid),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate content return value.
|
||||
*
|
||||
* @since Moodle 4.5
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function set_policy_status_returns(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'success' => new external_value(
|
||||
PARAM_BOOL,
|
||||
'Was the request successful',
|
||||
VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\aiactions\responses;
|
||||
|
||||
/**
|
||||
* AI subsystem manager.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class manager {
|
||||
|
||||
/**
|
||||
* Get communication provider class name from the plugin name.
|
||||
*
|
||||
* @param string $plugin The component name.
|
||||
* @throws \coding_exception If the plugin name does not start with 'aiprovider_' or 'aiplacement_'.
|
||||
* @return string The class name of the provider.
|
||||
*/
|
||||
private function get_ai_plugin_classname(string $plugin): string {
|
||||
if (strpos($plugin, 'aiprovider_') === 0) {
|
||||
return "{$plugin}\\provider";
|
||||
} else if (strpos($plugin, 'aiplacement_') === 0) {
|
||||
return "{$plugin}\\placement";
|
||||
} else {
|
||||
// Explode if neither.
|
||||
throw new \coding_exception("Plugin name does not start with 'aiprovider_' or 'aiplacement_': " . $plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions that this provider or placement supports,
|
||||
* given the name of the plugin.
|
||||
*
|
||||
* @param string $pluginname The name of the plugin to get the actions for.
|
||||
* @throws \coding_exception
|
||||
* @return array An array of action class names.
|
||||
*/
|
||||
public static function get_supported_actions(string $pluginname): array {
|
||||
$instance = new self();
|
||||
$pluginclassname = $instance->get_ai_plugin_classname($pluginname);
|
||||
$plugin = new $pluginclassname();
|
||||
return $plugin->get_action_list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of actions get the provider plugins that support them.
|
||||
* Will return an array of arrays, indexed by action name.
|
||||
*
|
||||
* @param array $actions An array of action class names.
|
||||
* @param bool $enabledonly If true, only return enabled providers.
|
||||
* @throws \coding_exception
|
||||
* @return array An array of provider instances indexed by action name.
|
||||
*/
|
||||
public static function get_providers_for_actions(array $actions, bool $enabledonly = false): array {
|
||||
$instance = new self();
|
||||
$providers = [];
|
||||
$plugins = \core_plugin_manager::instance()->get_plugins_of_type('aiprovider');
|
||||
foreach ($actions as $action) {
|
||||
$providers[$action] = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($enabledonly && (!$plugin->is_enabled() || !static::is_action_enabled($plugin->component, $action))) {
|
||||
continue;
|
||||
}
|
||||
$pluginclassname = $instance->get_ai_plugin_classname($plugin->component);
|
||||
$plugin = new $pluginclassname();
|
||||
if (in_array($action, $plugin->get_action_list())) {
|
||||
$providers[$action][] = $plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the action provider.
|
||||
* The named provider will process the action and return the result.
|
||||
*
|
||||
* @param provider $provider The provider to call.
|
||||
* @param base $action The action to process.
|
||||
* @return responses\response_base The result of the action.
|
||||
*/
|
||||
protected function call_action_provider(provider $provider, base $action): responses\response_base {
|
||||
$classname = 'process_' . $action->get_basename();
|
||||
$classpath = substr($provider::class, 0, strpos($provider::class, '\\') + 1);
|
||||
$processclass = $classpath . $classname;
|
||||
$processor = new $processclass($provider, $action);
|
||||
|
||||
return $processor->process($action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an action.
|
||||
* This is the entry point for processing an action.
|
||||
*
|
||||
* @param base $action The action to process. Action must be configured.
|
||||
* @return responses\response_base The result of the action.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public function process_action(base $action): responses\response_base {
|
||||
// Get the action response_base name.
|
||||
$actionname = $action::class;
|
||||
$responseclassname = 'core_ai\\aiactions\\responses\\response_' . $action->get_basename();
|
||||
|
||||
// Get the providers that support the action.
|
||||
$providers = self::get_providers_for_actions([$actionname], true);
|
||||
|
||||
// Loop through the providers and process the action.
|
||||
foreach ($providers[$actionname] as $provider) {
|
||||
$result = $this->call_action_provider($provider, $action);
|
||||
|
||||
// Store the result (success or failure).
|
||||
$this->store_action_result($provider, $action, $result);
|
||||
|
||||
// If the result is successful, return the result.
|
||||
// No need to keep looping.
|
||||
if ($result->get_success()) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here we've all available providers have failed.
|
||||
// Return the result if we have one.
|
||||
if (isset($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Response if there are no providers available.
|
||||
return new $responseclassname(
|
||||
success: false,
|
||||
actionname: $actionname,
|
||||
errorcode: -1,
|
||||
errormessage: 'No providers available to process the action.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the action result.
|
||||
*
|
||||
* @param provider $provider The provider that processed the action.
|
||||
* @param base $action The action that was processed.
|
||||
* @param responses\response_base $response The result of the action.
|
||||
* @return int The id of the stored action result.
|
||||
*/
|
||||
private function store_action_result(
|
||||
provider $provider,
|
||||
base $action,
|
||||
responses\response_base $response
|
||||
): int {
|
||||
global $DB;
|
||||
try {
|
||||
// Do everything in a transaction.
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
// Create the record for the action result.
|
||||
$actionrecordid = $action->store($response);
|
||||
|
||||
// Store the action result.
|
||||
$record = new \stdClass();
|
||||
$record->actionname = $action->get_basename();
|
||||
$record->actionid = $actionrecordid;
|
||||
$record->success = $response->get_success();
|
||||
$record->userid = $action->get_configuration('userid');
|
||||
$record->contextid = $action->get_configuration('contextid');
|
||||
$record->provider = $provider->get_name();
|
||||
$record->errorcode = $response->get_errorcode();
|
||||
$record->errormessage = $response->get_errormessage();
|
||||
$record->timecreated = $action->get_configuration('timecreated');
|
||||
$record->timecompleted = $response->get_timecreated();
|
||||
|
||||
$recordid = $DB->insert_record('ai_action_register', $record);
|
||||
|
||||
// Commit the transaction.
|
||||
$transaction->allow_commit();
|
||||
} catch (\Exception $e) {
|
||||
// Rollback the transaction.
|
||||
$transaction->rollback($e);
|
||||
// Re throw the exception.
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $recordid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user policy.
|
||||
*
|
||||
* @param int $userid The user id.
|
||||
* @param int $contextid The context id the policy was accepted in.
|
||||
* @return bool True if the policy was set, false otherwise.
|
||||
* @throws \dml_exception
|
||||
*/
|
||||
public static function set_user_policy(int $userid, int $contextid): bool {
|
||||
global $DB;
|
||||
|
||||
$record = new \stdClass();
|
||||
$record->userid = $userid;
|
||||
$record->contextid = $contextid;
|
||||
$record->timeaccepted = time();
|
||||
|
||||
if ($DB->insert_record('ai_policy_register', $record)) {
|
||||
$policycache = \cache::make('core', 'ai_policy');
|
||||
return $policycache->set($userid, true);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user policy.
|
||||
*
|
||||
* @param int $userid The user id.
|
||||
* @return bool True if the policy was accepted, false otherwise.
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function get_user_policy(int $userid): bool {
|
||||
$policycache = \cache::make('core', 'ai_policy');
|
||||
return $policycache->get($userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the action state for a given plugin.
|
||||
*
|
||||
* @param string $plugin The name of the plugin.
|
||||
* @param string $action The action to be set.
|
||||
* @param int $enabled The state to be set (e.g., enabled or disabled).
|
||||
* @return bool Returns true if the configuration was successfully set, false otherwise.
|
||||
*/
|
||||
public static function enable_action(string $plugin, string $action, int $enabled): bool {
|
||||
$oldvalue = static::is_action_enabled($plugin, $action);
|
||||
// Only set value if there is no config setting or if the value is different from the previous one.
|
||||
if ($oldvalue !== $enabled) {
|
||||
set_config($action, $enabled, $plugin);
|
||||
add_to_config_log('disabled', !$oldvalue, !$enabled, $plugin);
|
||||
\core_plugin_manager::reset_caches();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an action is enabled for a given plugin.
|
||||
*
|
||||
* @param string $plugin The name of the plugin.
|
||||
* @param string $action The action to be checked.
|
||||
* @return mixed Returns the configuration value of the action for the given plugin.
|
||||
*/
|
||||
public static function is_action_enabled(string $plugin, string $action): bool {
|
||||
$value = get_config($plugin, $action);
|
||||
// If not exist in DB, set it to true (enabled).
|
||||
if ($value === false) {
|
||||
return true;
|
||||
}
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
/**
|
||||
* Class placement.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class placement {
|
||||
|
||||
/**
|
||||
* Get the actions that this placement supports.
|
||||
* Returns an array of action class names.
|
||||
*
|
||||
* @return array An array of action class names.
|
||||
*/
|
||||
abstract public function get_action_list(): array;
|
||||
|
||||
/**
|
||||
* Given an action class name, return an array of sub actions
|
||||
* that this placement supports.
|
||||
*
|
||||
* @param string $classname The action class name.
|
||||
* @return array An array of supported sub actions.
|
||||
*/
|
||||
public function get_sub_actions(string $classname): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\privacy;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for core_ai implementing null_provider.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\null_provider {
|
||||
|
||||
// TODO: Will be implemented in MDL-82781.
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_reason(): string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\aiactions\responses;
|
||||
|
||||
/**
|
||||
* Base class for provider processors.
|
||||
* Each provider processor should extend this class.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class process_base {
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param provider $provider The provider that will process the action.
|
||||
* @param base $action The action to process.
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var provider The provider that will process the action. */
|
||||
protected provider $provider,
|
||||
/** @var base The action to process. */
|
||||
protected base $action
|
||||
) {
|
||||
$this->provider = $provider;
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the AI request.
|
||||
*
|
||||
* @return responses\response_base The result of the action.
|
||||
*/
|
||||
abstract public function process(): responses\response_base;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
/**
|
||||
* Class provider.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class provider {
|
||||
/**
|
||||
* Get the actions that this provider supports.
|
||||
* Returns an array of action class names.
|
||||
*
|
||||
* @return array An array of action class names.
|
||||
*/
|
||||
abstract public function get_action_list(): array;
|
||||
|
||||
/**
|
||||
* Given an action class name, return an array of sub actions
|
||||
* that this provider supports.
|
||||
*
|
||||
* @param string $classname The action class name.
|
||||
* @return array An array of supported sub actions.
|
||||
*/
|
||||
public function get_sub_actions(string $classname): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the provider.
|
||||
*
|
||||
* @throws \coding_exception
|
||||
* @return string The name of the provider.
|
||||
*/
|
||||
public function get_name(): string {
|
||||
$name = explode('\\', get_class($this))[0];
|
||||
return get_string('pluginname', $name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use Psr\Clock\ClockInterface;
|
||||
|
||||
/**
|
||||
* Rate limiting functionality that can be used by AI providers.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class ratelimiter {
|
||||
/** @var int TIME_WINDOW Time window in seconds (1 hour). */
|
||||
public const TIME_WINDOW = 3600;
|
||||
|
||||
/** @var null|ratelimiter Singleton instance of the rate limiter. */
|
||||
private static ?ratelimiter $instance = null;
|
||||
|
||||
/** @var \cache_application Cache instance for rate limiter. */
|
||||
private \cache_application $cache;
|
||||
|
||||
/** @var ClockInterface Clock instance for time management. */
|
||||
private ClockInterface $clock;
|
||||
|
||||
/**
|
||||
* Get the singleton instance of the rate limiter.
|
||||
*
|
||||
* @param ClockInterface|null $clock Clock instance for time management.
|
||||
* @return ratelimiter Singleton instance of the rate limiter.
|
||||
*/
|
||||
public static function get_instance(?ClockInterface $clock = null): ratelimiter {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self($clock ?? new \core\system_clock);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param ClockInterface $clock Clock instance for time management.
|
||||
*/
|
||||
private function __construct(ClockInterface $clock) {
|
||||
$this->cache = \cache::make('core', 'ai_ratelimit');
|
||||
$this->clock = $clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the singleton instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset_instance(): void {
|
||||
self::$instance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check global rate limit for a component.
|
||||
*
|
||||
* @param string $component Name of the component.
|
||||
* @param int $ratelimit Number of requests per time window.
|
||||
* @return bool True if request is allowed, false otherwise.
|
||||
*/
|
||||
public function check_global_rate_limit(string $component, int $ratelimit): bool {
|
||||
$currenttime = $this->clock->now()->getTimestamp();
|
||||
return $this->check_limit("global_{$component}", $ratelimit, $currenttime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user rate limit for a component.
|
||||
*
|
||||
* @param string $component Name of the component.
|
||||
* @param int $ratelimit Number of requests per time window.
|
||||
* @param int $userid User ID for user-specific rate limit.
|
||||
* @return bool True if request is allowed, false otherwise.
|
||||
*/
|
||||
public function check_user_rate_limit(string $component, int $ratelimit, int $userid): bool {
|
||||
$currenttime = $this->clock->now()->getTimestamp();
|
||||
|
||||
// Check and update user limit.
|
||||
return $this->check_limit("user_{$component}_{$userid}", $ratelimit, $currenttime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check limit in cache.
|
||||
*
|
||||
* @param string $key Cache key.
|
||||
* @param int $ratelimit Number of requests per time window.
|
||||
* @param int $currenttime Current timestamp.
|
||||
* @return bool True if request is allowed, false otherwise.
|
||||
*/
|
||||
private function check_limit(string $key, int $ratelimit, int $currenttime): bool {
|
||||
$ratedata = $this->cache->get($key);
|
||||
|
||||
if ($ratedata === false) {
|
||||
// No data found, initialize rate data.
|
||||
$ratedata = ['count' => 0, 'start_time' => $currenttime];
|
||||
}
|
||||
|
||||
// Remove expired rate data.
|
||||
if ($currenttime - $ratedata['start_time'] > self::TIME_WINDOW) {
|
||||
$ratedata['count'] = 0;
|
||||
$ratedata['start_time'] = $currenttime;
|
||||
}
|
||||
|
||||
// Check rate limit.
|
||||
if ($ratedata['count'] < $ratelimit) {
|
||||
$ratedata['count']++;
|
||||
$this->cache->set($key, $ratedata);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rate limit exceeded.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\table;
|
||||
|
||||
use core_ai\aiactions\base;
|
||||
use core_table\dynamic as dynamic_table;
|
||||
use flexible_table;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Table to manage AI actions used in placement plugins.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiplacement_action_management_table extends flexible_table implements dynamic_table {
|
||||
/** @var string The name of the plugin these actions related too */
|
||||
protected string $pluginname;
|
||||
|
||||
/** @var array The list of actions this manager covers */
|
||||
protected array $actions;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $uniqueid The table unique id.
|
||||
*/
|
||||
public function __construct(string $uniqueid) {
|
||||
// Parse the unique id and get the plugin name.
|
||||
$parseuniqueid = explode('-', $uniqueid);
|
||||
$pluginname = end($parseuniqueid);
|
||||
$this->pluginname = $pluginname;
|
||||
|
||||
// Get the list of actions that this provider supports.
|
||||
$this->actions = \core_ai\manager::get_supported_actions($this->pluginname);
|
||||
|
||||
parent::__construct($this->get_table_id());
|
||||
|
||||
$this->setup_column_configuration();
|
||||
$this->set_filterset(new aiplacement_action_management_table_filterset());
|
||||
$this->setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context for this table.
|
||||
*
|
||||
* @return \context_system
|
||||
*/
|
||||
public function get_context(): \context_system {
|
||||
return \context_system::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the column configuration for this table.
|
||||
*/
|
||||
protected function setup_column_configuration(): void {
|
||||
$columnlist = $this->get_column_list();
|
||||
$this->define_columns(array_keys($columnlist));
|
||||
$this->define_headers(array_values($columnlist));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_id(): string {
|
||||
return "aiplacementaction_management_table-{$this->pluginname}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the web service method used to toggle state.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function get_toggle_service(): ?string {
|
||||
return 'core_ai_set_action';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JS module used to manage this table.
|
||||
*
|
||||
* This should be a class which extends 'core_admin/plugin_management_table'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_js_module(): string {
|
||||
return 'core_admin/plugin_management_table';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add JS specific to this implementation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_dynamic_table_html_end(): string {
|
||||
global $PAGE;
|
||||
|
||||
$PAGE->requires->js_call_amd($this->get_table_js_module(), 'init');
|
||||
return parent::get_dynamic_table_html_end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the column titles
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_column_list(): array {
|
||||
return [
|
||||
'namedesc' => get_string('name', 'core'),
|
||||
'enabled' => get_string('pluginenabled', 'core_plugin'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the name column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_namedesc(stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
|
||||
$providerurl = new moodle_url('/admin/settings.php', ['section' => 'aiprovider']);
|
||||
if (!$this->has_provider($row->action)) {
|
||||
return $OUTPUT->render_from_template('core_ai/admin_noproviders', [
|
||||
'providerurl' => $providerurl->out(),
|
||||
]);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'name' => $row->action::get_name(),
|
||||
'description' => $row->action::get_description(),
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/table/namedesc', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the enable/disable column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_enabled(stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
|
||||
$enabled = $row->enabled;
|
||||
$identifier = $enabled ? 'disableplugin' : 'enableplugin';
|
||||
$labelstr = get_string($identifier, 'core_admin', $row->action::get_name());
|
||||
|
||||
$params = [
|
||||
'id' => 'admin-toggle-' . $row->action::get_basename(),
|
||||
'checked' => $enabled,
|
||||
'dataattributes' => [
|
||||
'name' => 'id',
|
||||
'value' => $row->action,
|
||||
'toggle-method' => $this->get_toggle_service(),
|
||||
'action' => 'togglestate',
|
||||
'plugin' => $this->pluginname . "-" . $row->action::get_basename(),
|
||||
'state' => $enabled ? 1 : 0,
|
||||
],
|
||||
'title' => $labelstr,
|
||||
'label' => $labelstr,
|
||||
'labelclasses' => 'sr-only',
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/setting_configtoggle', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any class to add to the row.
|
||||
*
|
||||
* @param mixed $row
|
||||
* @return string
|
||||
*/
|
||||
protected function get_row_class($row): string {
|
||||
if (!$row->enabled) {
|
||||
return 'dimmed_text';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table content.
|
||||
*/
|
||||
public function get_content(): string {
|
||||
ob_start();
|
||||
$this->out();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the table.
|
||||
*/
|
||||
public function out(): void {
|
||||
foreach ($this->actions as $action) {
|
||||
// Construct the row data.
|
||||
$rowdata = (object) [
|
||||
'action' => $action,
|
||||
'enabled' => \core_ai\manager::is_action_enabled($this->pluginname, $action::get_basename()),
|
||||
];
|
||||
$this->add_data_keyed(
|
||||
$this->format_row($rowdata),
|
||||
$this->get_row_class($rowdata)
|
||||
);
|
||||
}
|
||||
|
||||
$this->finish_output(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This table is not downloadable.
|
||||
* @param bool $downloadable
|
||||
* @return bool
|
||||
*/
|
||||
// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
public function is_downloadable($downloadable = null): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess base URL.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function guess_base_url(): void {
|
||||
$url = new moodle_url('/');
|
||||
$this->define_baseurl($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the action has any enabled providers.
|
||||
* Returns True if the action has a provider.
|
||||
*
|
||||
* @param string $action The action to check.
|
||||
* @return bool True if the action has a provider.
|
||||
*/
|
||||
private function has_provider(string $action): bool {
|
||||
$providers = \core_ai\manager::get_providers_for_actions([$action], true);
|
||||
return !empty($providers[$action]);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function has_capability(): bool {
|
||||
return has_capability('moodle/site:config', $this->get_context());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\table;
|
||||
|
||||
/**
|
||||
* This file contains the dynamic interface.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiplacement_action_management_table_filterset extends \core_table\local\filter\filterset {
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\table;
|
||||
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Table to manage AI Placement plugins.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiplacement_management_table extends \core_admin\table\plugin_management_table {
|
||||
|
||||
/**
|
||||
* Get the name of the plugin type this table manages.
|
||||
*
|
||||
* @return string The name of the plugin type this table manages.
|
||||
*/
|
||||
protected function get_plugintype(): string {
|
||||
return 'aiplacement';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the action page.
|
||||
*
|
||||
* @param array $params The parameters to pass to the action page.
|
||||
* @return moodle_url The URL to the action page.
|
||||
* @throws \core\exception\moodle_exception
|
||||
*/
|
||||
protected function get_action_url(array $params = []): moodle_url {
|
||||
return new moodle_url('/admin/ai.php', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of columns for this table.
|
||||
*
|
||||
* @return array The list of columns for this table.
|
||||
*/
|
||||
protected function get_column_list(): array {
|
||||
$columns = [
|
||||
'name' => get_string('name', 'core'),
|
||||
];
|
||||
|
||||
if ($this->supports_disabling()) {
|
||||
$columns['enabled'] = get_string('pluginenabled', 'core_plugin');
|
||||
}
|
||||
|
||||
if ($this->supports_ordering()) {
|
||||
$columns['order'] = get_string('order', 'core');
|
||||
}
|
||||
|
||||
$columns['settings'] = get_string('settings', 'core');
|
||||
$columns['uninstall'] = get_string('uninstallplugin', 'core_admin');
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\table;
|
||||
|
||||
use core_table\dynamic as dynamic_table;
|
||||
use flexible_table;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Table to manage AI actions used in provider plugins.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiprovider_action_management_table extends flexible_table implements dynamic_table {
|
||||
/** @var string The name of the plugin these actions related too */
|
||||
protected string $pluginname;
|
||||
|
||||
/** @var array The list of actions this manager covers */
|
||||
protected array $actions;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $uniqueid The table unique id
|
||||
*/
|
||||
public function __construct(string $uniqueid) {
|
||||
// Check if the current user has the capability to access this table.
|
||||
require_capability('moodle/site:config', $this->get_context());
|
||||
|
||||
$parseuniqueid = explode('-', $uniqueid);
|
||||
$pluginname = end($parseuniqueid);
|
||||
$this->pluginname = $pluginname;
|
||||
|
||||
// Get the list of actions that this provider supports.
|
||||
$this->actions = \core_ai\manager::get_supported_actions($this->pluginname);
|
||||
|
||||
parent::__construct($this->get_table_id());
|
||||
|
||||
$this->setup_column_configuration();
|
||||
$this->set_filterset(new aiprovider_action_management_table_filterset());
|
||||
$this->setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context for this table.
|
||||
*
|
||||
* @return \context_system
|
||||
*/
|
||||
public function get_context(): \context_system {
|
||||
return \context_system::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the column configuration for this table.
|
||||
*/
|
||||
protected function setup_column_configuration(): void {
|
||||
$columnlist = $this->get_column_list();
|
||||
$this->define_columns(array_keys($columnlist));
|
||||
$this->define_headers(array_values($columnlist));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_id(): string {
|
||||
return "aiprovideraction_management_table-{$this->pluginname}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the web service method used to toggle state.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function get_toggle_service(): ?string {
|
||||
return 'core_ai_set_action';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JS module used to manage this table.
|
||||
*
|
||||
* This should be a class which extends 'core_admin/plugin_management_table'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_js_module(): string {
|
||||
return 'core_admin/plugin_management_table';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add JS specific to this implementation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_dynamic_table_html_end(): string {
|
||||
global $PAGE;
|
||||
|
||||
$PAGE->requires->js_call_amd($this->get_table_js_module(), 'init');
|
||||
return parent::get_dynamic_table_html_end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the column titles
|
||||
* @return string[]
|
||||
*/
|
||||
protected function get_column_list(): array {
|
||||
return [
|
||||
'namedesc' => get_string('name', 'core'),
|
||||
'enabled' => get_string('pluginenabled', 'core_plugin'),
|
||||
'settings' => get_string('settings', 'core'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the name column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_namedesc(stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
|
||||
$params = [
|
||||
'name' => $row->action::get_name(),
|
||||
'description' => $row->action::get_description(),
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/table/namedesc', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the enable/disable column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_enabled(stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
|
||||
$enabled = $row->enabled;
|
||||
$identifier = $enabled ? 'disableplugin' : 'enableplugin';
|
||||
$labelstr = get_string($identifier, 'core_admin', $row->action::get_name());
|
||||
|
||||
$params = [
|
||||
'id' => 'admin-toggle-' . $row->action::get_basename(),
|
||||
'checked' => $enabled,
|
||||
'dataattributes' => [
|
||||
'name' => 'id',
|
||||
'value' => $row->action,
|
||||
'toggle-method' => $this->get_toggle_service(),
|
||||
'action' => 'togglestate',
|
||||
'plugin' => $this->pluginname . "-" . $row->action::get_basename(),
|
||||
'state' => $enabled ? 1 : 0,
|
||||
],
|
||||
'title' => $labelstr,
|
||||
'label' => $labelstr,
|
||||
'labelclasses' => 'sr-only',
|
||||
];
|
||||
|
||||
return $OUTPUT->render_from_template('core_admin/setting_configtoggle', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the settings column content.
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
protected function col_settings(stdClass $row): string {
|
||||
// TODO: MDL-82609 - Add settings link.
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any class to add to the row.
|
||||
*
|
||||
* @param mixed $row
|
||||
* @return string
|
||||
*/
|
||||
protected function get_row_class($row): string {
|
||||
if (!$row->enabled) {
|
||||
return 'dimmed_text';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table content.
|
||||
*/
|
||||
public function get_content(): string {
|
||||
ob_start();
|
||||
$this->out();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the table.
|
||||
*/
|
||||
public function out(): void {
|
||||
foreach ($this->actions as $action) {
|
||||
// Construct the row data.
|
||||
$rowdata = (object) [
|
||||
'action' => $action,
|
||||
'enabled' => \core_ai\manager::is_action_enabled($this->pluginname, $action::get_basename()),
|
||||
];
|
||||
$this->add_data_keyed(
|
||||
$this->format_row($rowdata),
|
||||
$this->get_row_class($rowdata),
|
||||
);
|
||||
}
|
||||
|
||||
$this->finish_output(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This table is not downloadable.
|
||||
* @param bool $downloadable
|
||||
* @return bool
|
||||
*/
|
||||
// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
public function is_downloadable($downloadable = null): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the base URL for this table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function guess_base_url(): void {
|
||||
$url = new moodle_url('/');
|
||||
$this->define_baseurl($url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\table;
|
||||
|
||||
/**
|
||||
* This file contains the dynamic interface.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiprovider_action_management_table_filterset extends \core_table\local\filter\filterset {
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\table;
|
||||
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Table to manage AI Provider plugins.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiprovider_management_table extends \core_admin\table\plugin_management_table {
|
||||
|
||||
/**
|
||||
* Get the type of plugin this table manages.
|
||||
*
|
||||
* @return string The type of plugin this table manages.
|
||||
*/
|
||||
protected function get_plugintype(): string {
|
||||
return 'aiprovider';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the action.
|
||||
*
|
||||
* @param array $params The parameters to pass to the URL.
|
||||
* @return moodle_url The URL to the action.
|
||||
* @throws \core\exception\moodle_exception
|
||||
*/
|
||||
protected function get_action_url(array $params = []): moodle_url {
|
||||
return new moodle_url('/admin/ai.php', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column list for this table.
|
||||
*
|
||||
* @return array The column list for this table.
|
||||
*/
|
||||
protected function get_column_list(): array {
|
||||
$columns = [
|
||||
'name' => get_string('name', 'core'),
|
||||
];
|
||||
|
||||
if ($this->supports_disabling()) {
|
||||
$columns['enabled'] = get_string('pluginenabled', 'core_plugin');
|
||||
}
|
||||
|
||||
if ($this->supports_ordering()) {
|
||||
$columns['order'] = get_string('order', 'core');
|
||||
}
|
||||
|
||||
$columns['settings'] = get_string('settings', 'core');
|
||||
$columns['uninstall'] = get_string('uninstallplugin', 'core_admin');
|
||||
|
||||
return $columns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
TODO: remove this when the first plugin is added.
|
||||
@@ -0,0 +1 @@
|
||||
TODO: remove this when the first plugin is added.
|
||||
@@ -0,0 +1,32 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_ai/admin_noproviders
|
||||
|
||||
Template to display a message when there are no AI providers configured.
|
||||
|
||||
Context variables required for this template:
|
||||
* providerurl - URL to the AI provider settings page.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"providerurl": "http://example.com"
|
||||
}
|
||||
}}
|
||||
<div class="alert alert-danger w-75">
|
||||
{{#str}}noproviders, core_ai, {{{providerurl}}}{{/str}}
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_base;
|
||||
use \core_ai\aiactions;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Test response_base action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\base
|
||||
*/
|
||||
final class base_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_basename.
|
||||
*/
|
||||
public function test_get_basename(): void {
|
||||
$basename = aiactions\generate_text::get_basename();
|
||||
$this->assertEquals('generate_text', $basename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_name.
|
||||
*/
|
||||
public function test_get_name(): void {
|
||||
$this->assertEquals(
|
||||
get_string('action_generate_text', 'core_ai'),
|
||||
aiactions\generate_text::get_name()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_description.
|
||||
*/
|
||||
public function test_get_description(): void {
|
||||
$this->assertEquals(
|
||||
get_string('action_generate_text_desc', 'core_ai'),
|
||||
aiactions\generate_text::get_description()
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Test that every action class implements a constructor.
|
||||
*/
|
||||
public function test_constructor(): void {
|
||||
$classes = [];
|
||||
|
||||
// Create an anonymous class that extends the base class.
|
||||
$base = new class extends \core_ai\aiactions\base {
|
||||
public function store(response_base $response): int {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
$reflection = new ReflectionClass($base); // Create a ReflectionClass for the anonymous class.
|
||||
|
||||
// Use the location of the base class to get the AI action classes.
|
||||
$filePath = $reflection->getParentClass()->getFileName();
|
||||
$directory = dirname($filePath);
|
||||
$files = scandir($directory);
|
||||
foreach ($files as $file) {
|
||||
if (str_ends_with($file, '.php') &! str_starts_with($file, 'base')) {
|
||||
$classes[] = 'core_ai\\aiactions\\' . str_replace('.php', '', $file);
|
||||
}
|
||||
}
|
||||
|
||||
// For each action class, check that they have a constructor.
|
||||
foreach ($classes as $class) {
|
||||
$reflection = new ReflectionClass($class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$this->assertNotNull($constructor, 'Class ' . $class . ' does not have a constructor.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_generate_image;
|
||||
use core_ai\aiactions\generate_image;
|
||||
|
||||
|
||||
/**
|
||||
* Test generate_image action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\base
|
||||
*/
|
||||
final class generate_image_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test constructor method.
|
||||
*/
|
||||
public function test_constructor(): void {
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
$aspectratio = 'square';
|
||||
$quality = 'hd';
|
||||
$numimages = 1;
|
||||
$style = 'vivid';
|
||||
$action = new generate_image(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext,
|
||||
quality: $quality,
|
||||
aspectratio: $aspectratio,
|
||||
numimages: $numimages,
|
||||
style: $style
|
||||
);
|
||||
|
||||
$this->assertEquals($userid, $action->get_configuration('userid'));
|
||||
$this->assertEquals($prompttext, $action->get_configuration('prompttext'));
|
||||
$this->assertEquals($aspectratio, $action->get_configuration('aspectratio'));
|
||||
$this->assertEquals($quality, $action->get_configuration('quality'));
|
||||
$this->assertEquals($numimages, $action->get_configuration('numimages'));
|
||||
$this->assertEquals($style, $action->get_configuration('style'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test store method.
|
||||
*/
|
||||
public function test_store(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
$aspectratio = 'square';
|
||||
$quality = 'hd';
|
||||
$numimages = 1;
|
||||
$style = 'vivid';
|
||||
$action = new generate_image(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext,
|
||||
quality: $quality,
|
||||
aspectratio: $aspectratio,
|
||||
numimages: $numimages,
|
||||
style: $style
|
||||
);
|
||||
|
||||
$body = [
|
||||
'revisedprompt' => 'This is a revised prompt',
|
||||
'sourceurl' => 'https://example.com/image.png',
|
||||
];
|
||||
$actionresponse = new response_generate_image(
|
||||
success: true,
|
||||
actionname: 'generate_image',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$storeid = $action->store($actionresponse);
|
||||
|
||||
// Check the stored record.
|
||||
$record = $DB->get_record('ai_action_generate_image', ['id' => $storeid]);
|
||||
$this->assertEquals($prompttext, $record->prompt);
|
||||
$this->assertEquals($numimages, $record->numberimages);
|
||||
$this->assertEquals($quality, $record->quality);
|
||||
$this->assertEquals($aspectratio, $record->aspectratio);
|
||||
$this->assertEquals($style, $record->style);
|
||||
$this->assertEquals($body['sourceurl'], $record->sourceurl);
|
||||
$this->assertEquals($body['revisedprompt'], $record->revisedprompt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_generate_text;
|
||||
use core_ai\aiactions\generate_text;
|
||||
|
||||
/**
|
||||
* Test generate_text action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\base
|
||||
*/
|
||||
final class generate_text_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test configure method.
|
||||
*/
|
||||
public function test_configure(): void {
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
|
||||
$action = new generate_text(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext
|
||||
);
|
||||
$this->assertEquals($userid, $action->get_configuration('userid'));
|
||||
$this->assertEquals($prompttext, $action->get_configuration('prompttext'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test store method.
|
||||
*/
|
||||
public function test_store(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
|
||||
$action = new generate_text(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext
|
||||
);
|
||||
|
||||
$body = [
|
||||
'id' => 'chatcmpl-123',
|
||||
'fingerprint' => 'fp_44709d6fcb',
|
||||
'generatedcontent' => 'This is the generated content',
|
||||
'finishreason' => 'stop',
|
||||
'prompttokens' => 9,
|
||||
'completiontokens' => 12,
|
||||
];
|
||||
$actionresponse = new response_generate_text(
|
||||
success: true,
|
||||
actionname: 'generate_text',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$storeid = $action->store($actionresponse);
|
||||
|
||||
// Check the stored record.
|
||||
$record = $DB->get_record('ai_action_generate_text', ['id' => $storeid]);
|
||||
$this->assertEquals($prompttext, $record->prompt);
|
||||
$this->assertEquals($body['id'], $record->responseid);
|
||||
$this->assertEquals($body['fingerprint'], $record->fingerprint);
|
||||
$this->assertEquals($body['generatedcontent'], $record->generatedcontent);
|
||||
$this->assertEquals($body['finishreason'], $record->finishreason);
|
||||
$this->assertEquals($body['prompttokens'], $record->prompttokens);
|
||||
$this->assertEquals($body['completiontokens'], $record->completiontoken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use core_ai\aiactions\responses\response_generate_image;
|
||||
|
||||
/**
|
||||
* Test response_generate_image_test action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\responses\response_generate_imaget
|
||||
*/
|
||||
final class response_generate_image_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_basename.
|
||||
*/
|
||||
public function test_get_success(): void {
|
||||
$actionresponse = new response_generate_image(
|
||||
success: true,
|
||||
actionname: 'generate_image',
|
||||
);
|
||||
|
||||
$this->assertTrue($actionresponse->get_success());
|
||||
$this->assertEquals('generate_image', $actionresponse->get_actionname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constructor with error.
|
||||
*/
|
||||
public function test_construct_error(): void {
|
||||
$this->expectException(\coding_exception::class);
|
||||
$this->expectExceptionMessage('Error code and message must exist in an error response.');
|
||||
new response_generate_image(
|
||||
success: false,
|
||||
actionname: 'generate_image'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test set_response.
|
||||
*/
|
||||
public function test_set_response(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a file to store.
|
||||
$fs = get_file_storage();
|
||||
$filerecord = new \stdClass();
|
||||
$filerecord->contextid = 1;
|
||||
$filerecord->component = 'core_ai';
|
||||
$filerecord->filearea = 'draft';
|
||||
$filerecord->itemid = 0;
|
||||
$filerecord->filepath = '/';
|
||||
$filerecord->filename = 'test.txt';
|
||||
$file = $fs->create_file_from_string($filerecord, 'This is a test file');
|
||||
$body = [
|
||||
'draftfile' => $file,
|
||||
'revisedprompt' => 'This is a revised prompt',
|
||||
'sourceurl' => 'https://example.com/image.png',
|
||||
];
|
||||
$actionresponse = new response_generate_image(
|
||||
success: true,
|
||||
actionname: 'generate_image',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$this->assertEquals($file, $actionresponse->get_response()['draftfile']);
|
||||
$this->assertEquals('This is a revised prompt', $actionresponse->get_response()['revisedprompt']);
|
||||
$this->assertEquals('https://example.com/image.png', $actionresponse->get_response()['sourceurl']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use core_ai\aiactions\responses\response_generate_text;
|
||||
|
||||
/**
|
||||
* Test response_generate_image_test action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\responses\response_generate_text
|
||||
*/
|
||||
final class response_generate_text_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_basename.
|
||||
*/
|
||||
public function test_get_success(): void {
|
||||
$actionresponse = new response_generate_text(
|
||||
success: true,
|
||||
actionname: 'generate_text',
|
||||
);
|
||||
|
||||
$this->assertTrue($actionresponse->get_success());
|
||||
$this->assertEquals('generate_text', $actionresponse->get_actionname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constructor with error.
|
||||
*/
|
||||
public function test_construct_error(): void {
|
||||
$this->expectException(\coding_exception::class);
|
||||
$this->expectExceptionMessage('Error code and message must exist in an error response.');
|
||||
new response_generate_text(
|
||||
success: false,
|
||||
actionname: 'generate_text'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test set_response.
|
||||
*/
|
||||
public function test_set_response(): void {
|
||||
$this->resetAfterTest();
|
||||
$body = [
|
||||
'id' => 'chatcmpl-123',
|
||||
'fingerprint' => 'fp_44709d6fcb',
|
||||
'generatedcontent' => 'This is the generated content',
|
||||
'finishreason' => 'stop',
|
||||
'prompttokens' => 9,
|
||||
'completiontokens' => 12,
|
||||
];
|
||||
$actionresponse = new response_generate_text(
|
||||
success: true,
|
||||
actionname: 'generate_text',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$this->assertEquals($body['id'], $actionresponse->get_response()['id']);
|
||||
$this->assertEquals($body['fingerprint'], $actionresponse->get_response()['fingerprint']);
|
||||
$this->assertEquals($body['generatedcontent'], $actionresponse->get_response()['generatedcontent']);
|
||||
$this->assertEquals($body['finishreason'], $actionresponse->get_response()['finishreason']);
|
||||
$this->assertEquals($body['prompttokens'], $actionresponse->get_response()['prompttokens']);
|
||||
$this->assertEquals($body['completiontokens'], $actionresponse->get_response()['completiontokens']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use core_ai\aiactions\responses\response_summarise_text;
|
||||
|
||||
/**
|
||||
* Test response_generate_image_test action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\responses\response_summarise_text
|
||||
*/
|
||||
final class response_summarise_text_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_basename.
|
||||
*/
|
||||
public function test_get_success(): void {
|
||||
$actionresponse = new response_summarise_text(
|
||||
success: true,
|
||||
actionname: 'generate_text',
|
||||
);
|
||||
|
||||
$this->assertTrue($actionresponse->get_success());
|
||||
$this->assertEquals('generate_text', $actionresponse->get_actionname());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constructor with error.
|
||||
*/
|
||||
public function test_construct_error(): void {
|
||||
$this->expectException(\coding_exception::class);
|
||||
$this->expectExceptionMessage('Error code and message must exist in an error response.');
|
||||
new response_summarise_text(
|
||||
success: false,
|
||||
actionname: 'generate_text'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test set_response.
|
||||
*/
|
||||
public function test_set_response(): void {
|
||||
$this->resetAfterTest();
|
||||
$body = [
|
||||
'id' => 'chatcmpl-123',
|
||||
'fingerprint' => 'fp_44709d6fcb',
|
||||
'generatedcontent' => 'This is the generated content',
|
||||
'finishreason' => 'stop',
|
||||
'prompttokens' => 9,
|
||||
'completiontokens' => 12,
|
||||
];
|
||||
$actionresponse = new response_summarise_text(
|
||||
success: true,
|
||||
actionname: 'generate_text',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$this->assertEquals($body['id'], $actionresponse->get_response()['id']);
|
||||
$this->assertEquals($body['fingerprint'], $actionresponse->get_response()['fingerprint']);
|
||||
$this->assertEquals($body['generatedcontent'], $actionresponse->get_response()['generatedcontent']);
|
||||
$this->assertEquals($body['finishreason'], $actionresponse->get_response()['finishreason']);
|
||||
$this->assertEquals($body['prompttokens'], $actionresponse->get_response()['prompttokens']);
|
||||
$this->assertEquals($body['completiontokens'], $actionresponse->get_response()['completiontokens']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\aiactions;
|
||||
|
||||
use core_ai\aiactions\responses\response_summarise_text;
|
||||
use core_ai\aiactions\summarise_text;
|
||||
/**
|
||||
* Test summarise_text action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\aiactions\base
|
||||
*/
|
||||
final class summarise_text_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test configure method.
|
||||
*/
|
||||
public function test_configure(): void {
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
|
||||
$action = new summarise_text(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext
|
||||
);
|
||||
$this->assertEquals($userid, $action->get_configuration('userid'));
|
||||
$this->assertEquals($prompttext, $action->get_configuration('prompttext'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test store method.
|
||||
*/
|
||||
public function test_store(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
|
||||
$action = new summarise_text(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext
|
||||
);
|
||||
|
||||
$body = [
|
||||
'id' => 'chatcmpl-123',
|
||||
'fingerprint' => 'fp_44709d6fcb',
|
||||
'generatedcontent' => 'This is the generated content',
|
||||
'finishreason' => 'stop',
|
||||
'prompttokens' => 9,
|
||||
'completiontokens' => 12,
|
||||
];
|
||||
$actionresponse = new response_summarise_text(
|
||||
success: true,
|
||||
actionname: 'summarise_text',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$storeid = $action->store($actionresponse);
|
||||
|
||||
// Check the stored record.
|
||||
$record = $DB->get_record('ai_action_summarise_text', ['id' => $storeid]);
|
||||
$this->assertEquals($prompttext, $record->prompt);
|
||||
$this->assertEquals($body['id'], $record->responseid);
|
||||
$this->assertEquals($body['fingerprint'], $record->fingerprint);
|
||||
$this->assertEquals($body['generatedcontent'], $record->generatedcontent);
|
||||
$this->assertEquals($body['finishreason'], $record->finishreason);
|
||||
$this->assertEquals($body['prompttokens'], $record->prompttokens);
|
||||
$this->assertEquals($body['completiontokens'], $record->completiontoken);
|
||||
}
|
||||
}
|
||||
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\external;
|
||||
|
||||
/**
|
||||
* Test policy external api calls.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\external
|
||||
*/
|
||||
final class policy_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get policy failure.
|
||||
*/
|
||||
public function test_get_policy_failure(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a test user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user->id);
|
||||
|
||||
// Get system context.
|
||||
$context = \context_system::instance();
|
||||
|
||||
$_POST['sesskey'] = sesskey();
|
||||
$params = [
|
||||
'userid' => $user->id,
|
||||
'contextid' => $context->id,
|
||||
];
|
||||
|
||||
$result = \core_external\external_api::call_external_function(
|
||||
'core_ai_get_policy_status',
|
||||
$params
|
||||
);
|
||||
|
||||
$this->assertFalse($result['data']['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get policy success.
|
||||
*/
|
||||
public function test_get_policy_success(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
// Create a test user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user->id);
|
||||
|
||||
// Get system context.
|
||||
$context = \context_system::instance();
|
||||
|
||||
// Manually add record to the database.
|
||||
$record = new \stdClass();
|
||||
$record->userid = $user->id;
|
||||
$record->contextid = $context->id;
|
||||
$record->timeaccepted = time();
|
||||
|
||||
$DB->insert_record('ai_policy_register', $record);
|
||||
|
||||
$_POST['sesskey'] = sesskey();
|
||||
$params = [
|
||||
'userid' => $user->id,
|
||||
'contextid' => $context->id,
|
||||
];
|
||||
|
||||
$result = \core_external\external_api::call_external_function(
|
||||
'core_ai_get_policy_status',
|
||||
$params
|
||||
);
|
||||
|
||||
$this->assertTrue($result['data']['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test set policy.
|
||||
*/
|
||||
public function test_set_policy(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a test user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user->id);
|
||||
|
||||
// Get system context.
|
||||
$context = \context_system::instance();
|
||||
|
||||
$_POST['sesskey'] = sesskey();
|
||||
$params = [
|
||||
'userid' => $user->id,
|
||||
'contextid' => $context->id,
|
||||
];
|
||||
|
||||
$result = \core_external\external_api::call_external_function(
|
||||
'core_ai_set_policy_status',
|
||||
$params
|
||||
);
|
||||
|
||||
$this->assertTrue($result['data']['success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test load_many_for_cache.
|
||||
*/
|
||||
public function test_user_policy_caching(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Manually add records to the database.
|
||||
$record1 = new \stdClass();
|
||||
$record1->userid = $user1->id;
|
||||
$record1->contextid = 1;
|
||||
$record1->timeaccepted = time();
|
||||
|
||||
$record2 = new \stdClass();
|
||||
$record2->userid = $user2->id;
|
||||
$record2->contextid = 1;
|
||||
$record2->timeaccepted = time();
|
||||
|
||||
$DB->insert_records('ai_policy_register', [
|
||||
$record1,
|
||||
$record2,
|
||||
]);
|
||||
|
||||
$policycache = \cache::make('core', 'ai_policy');
|
||||
$this->assertNotEmpty($policycache->get_many([$user1->id, $user2->id]));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
use core\plugininfo\aiprovider;
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\aiactions\generate_image;
|
||||
use core_ai\aiactions\responses\response_generate_image;
|
||||
|
||||
/**
|
||||
* Test ai subsystem manager methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\manager
|
||||
*/
|
||||
final class manager_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_ai_plugin_classname.
|
||||
*/
|
||||
public function test_get_ai_plugin_classname(): void {
|
||||
$manager = new manager();
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($manager, 'get_ai_plugin_classname');
|
||||
|
||||
// Test a provider plugin.
|
||||
$classname = $method->invoke($manager, 'aiprovider_fooai');
|
||||
$this->assertEquals('aiprovider_fooai\\provider', $classname);
|
||||
|
||||
// Test a placement plugin.
|
||||
$classname = $method->invoke($manager, 'aiplacement_fooplacement');
|
||||
$this->assertEquals('aiplacement_fooplacement\\placement', $classname);
|
||||
|
||||
// Test an invalid plugin.
|
||||
$this->expectException(\coding_exception::class);
|
||||
$this->expectExceptionMessage('Plugin name does not start with \'aiprovider_\' or \'aiplacement_\': bar');
|
||||
$method->invoke($manager, 'bar');
|
||||
}
|
||||
/**
|
||||
* Test get_supported_actions.
|
||||
*/
|
||||
public function test_get_supported_actions(): void {
|
||||
$manager = new manager();
|
||||
$actions = $manager->get_supported_actions('aiprovider_openai');
|
||||
|
||||
// Assert array keys match the expected actions.
|
||||
$this->assertEquals([
|
||||
\core_ai\aiactions\generate_text::class,
|
||||
\core_ai\aiactions\generate_image::class,
|
||||
\core_ai\aiactions\summarise_text::class,
|
||||
], $actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_providers_for_actions.
|
||||
*/
|
||||
public function test_get_providers_for_actions(): void {
|
||||
$this->resetAfterTest();
|
||||
// Invoke the plugin manager and disable the AzureAI plugin.
|
||||
set_config('disabled', 1, 'aiprovider_azureai');
|
||||
set_config('enabled', 1, 'aiprovider_openai');
|
||||
|
||||
$manager = new manager();
|
||||
$actions = [
|
||||
\core_ai\aiactions\generate_text::class,
|
||||
\core_ai\aiactions\summarise_text::class,
|
||||
];
|
||||
|
||||
// Get the providers for the actions.
|
||||
$providers = $manager->get_providers_for_actions($actions);
|
||||
|
||||
// Assert that the providers array is indexed by action name.
|
||||
$this->assertEquals($actions, array_keys($providers));
|
||||
|
||||
// Assert that there are two providers for each action.
|
||||
$this->assertCount(2, $providers['core_ai\\aiactions\\generate_text']);
|
||||
$this->assertCount(2, $providers['core_ai\\aiactions\\summarise_text']);
|
||||
|
||||
// Assert that the AzureAI provider is not included in the list of providers for the actions when only selecting active.
|
||||
$providers = $manager->get_providers_for_actions($actions, true);
|
||||
|
||||
// Assert that there is only one provider for each action.
|
||||
$this->assertCount(1, $providers['core_ai\\aiactions\\generate_text']);
|
||||
$this->assertCount(1, $providers['core_ai\\aiactions\\summarise_text']);
|
||||
|
||||
// Disable the generate text action for the open ai provider.
|
||||
set_config('core_ai\\aiactions\\generate_text', 0, 'aiprovider_openai');
|
||||
$providers = $manager->get_providers_for_actions($actions, true);
|
||||
|
||||
// Assert that there is no provider for the generate text action.
|
||||
$this->assertCount(0, $providers['core_ai\\aiactions\\generate_text']);
|
||||
$this->assertCount(1, $providers['core_ai\\aiactions\\summarise_text']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test process_action fail.
|
||||
*/
|
||||
public function test_process_action_fail(): void {
|
||||
$this->resetAfterTest();
|
||||
$managermock = $this->getMockBuilder(manager::class)
|
||||
->onlyMethods(['call_action_provider'])
|
||||
->getMock();
|
||||
|
||||
$expectedresult = new aiactions\responses\response_generate_image(
|
||||
success: true,
|
||||
actionname: 'generate_image',
|
||||
);
|
||||
|
||||
// Set up the expectation for call_action_provider to return the defined result.
|
||||
$managermock->expects($this->any())
|
||||
->method('call_action_provider')
|
||||
->willReturn($expectedresult);
|
||||
|
||||
$action = new \core_ai\aiactions\generate_image(
|
||||
contextid: 1,
|
||||
userid: 1,
|
||||
prompttext: 'This is a test prompt',
|
||||
quality: 'hd',
|
||||
aspectratio: 'square',
|
||||
numimages: 1,
|
||||
style: 'vivid',
|
||||
);
|
||||
|
||||
// Success should be false as there are no enabled providers.
|
||||
$result = $managermock->process_action($action);
|
||||
$this->assertFalse($result->get_success());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test process_action.
|
||||
*/
|
||||
public function test_process_action(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Enable the providers.
|
||||
set_config('enabled', 1, 'aiprovider_openai');
|
||||
set_config('enabled', 1, 'aiprovider_azureai');
|
||||
|
||||
$managermock = $this->getMockBuilder(manager::class)
|
||||
->onlyMethods(['call_action_provider'])
|
||||
->getMock();
|
||||
|
||||
$expectedresult = new aiactions\responses\response_generate_image(
|
||||
success: true,
|
||||
actionname: 'generate_image',
|
||||
);
|
||||
|
||||
// Set up the expectation for call_action_provider to return the defined result.
|
||||
$managermock->expects($this->any())
|
||||
->method('call_action_provider')
|
||||
->willReturn($expectedresult);
|
||||
|
||||
$action = new \core_ai\aiactions\generate_image(
|
||||
contextid: 1,
|
||||
userid: 1,
|
||||
prompttext: 'This is a test prompt',
|
||||
quality: 'hd',
|
||||
aspectratio: 'square',
|
||||
numimages: 1,
|
||||
style: 'vivid',
|
||||
);
|
||||
|
||||
// Should now return the expected result.
|
||||
$result = $managermock->process_action($action);
|
||||
$this->assertEquals($expectedresult, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test set_user_policy.
|
||||
*/
|
||||
public function test_set_user_policy(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$result = manager::set_user_policy(1, 1);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Check record exists.
|
||||
$record = $DB->record_exists('ai_policy_register', ['userid' => 1, 'contextid' => 1]);
|
||||
$this->assertTrue($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_user_policy.
|
||||
*/
|
||||
public function test_get_user_policy(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
// Should be false for user initially.
|
||||
$result = manager::get_user_policy(1);
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Manually add record to the database.
|
||||
$record = new \stdClass();
|
||||
$record->userid = 1;
|
||||
$record->contextid = 1;
|
||||
$record->timeaccepted = time();
|
||||
|
||||
$DB->insert_record('ai_policy_register', $record);
|
||||
|
||||
// Should be true for user now.
|
||||
$result = manager::get_user_policy(1);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test policy cache data source.
|
||||
*/
|
||||
public function test_user_policy_caching(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Manually add records to the database.
|
||||
$record1 = new \stdClass();
|
||||
$record1->userid = $user1->id;
|
||||
$record1->contextid = 1;
|
||||
$record1->timeaccepted = time();
|
||||
|
||||
$record2 = new \stdClass();
|
||||
$record2->userid = $user2->id;
|
||||
$record2->contextid = 1;
|
||||
$record2->timeaccepted = time();
|
||||
|
||||
$DB->insert_records('ai_policy_register', [
|
||||
$record1,
|
||||
$record2,
|
||||
]);
|
||||
|
||||
$policycache = \cache::make('core', 'ai_policy');
|
||||
|
||||
// Test single user.
|
||||
$this->assertFalse($policycache->has($user1->id));
|
||||
$this->assertFalse($policycache->has($user2->id));
|
||||
$this->assertFalse($policycache->has($user3->id));
|
||||
|
||||
$result = $policycache->get($user1->id);
|
||||
$this->assertTrue($result);
|
||||
$result = $policycache->get($user2->id);
|
||||
$this->assertTrue($result);
|
||||
$result = $policycache->get($user3->id);
|
||||
$this->assertFalse($result);
|
||||
|
||||
$this->assertTrue($policycache->has($user1->id));
|
||||
$this->assertTrue($policycache->has($user2->id));
|
||||
|
||||
// Purge the cache.
|
||||
$policycache->purge();
|
||||
|
||||
// Test multiple users.
|
||||
$this->assertFalse($policycache->has($user1->id));
|
||||
$this->assertFalse($policycache->has($user2->id));
|
||||
$this->assertFalse($policycache->has($user3->id));
|
||||
|
||||
$result = $policycache->get_many([$user1->id, $user2->id, $user3->id]);
|
||||
$this->assertNotEmpty($policycache->get_many($result));
|
||||
$this->assertTrue($result[$user1->id]);
|
||||
$this->assertTrue($result[$user2->id]);
|
||||
$this->assertFalse($result[$user3->id]);
|
||||
|
||||
$this->assertTrue($policycache->has($user1->id));
|
||||
$this->assertTrue($policycache->has($user2->id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test store_action_result.
|
||||
*/
|
||||
public function test_store_action_result(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
$aspectratio = 'square';
|
||||
$quality = 'hd';
|
||||
$numimages = 1;
|
||||
$style = 'vivid';
|
||||
|
||||
$action = new generate_image(
|
||||
contextid: 1,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext,
|
||||
quality: $quality,
|
||||
aspectratio: $aspectratio,
|
||||
numimages: $numimages,
|
||||
style: $style
|
||||
);
|
||||
|
||||
$body = [
|
||||
'revisedprompt' => 'This is a revised prompt',
|
||||
'imageurl' => 'https://example.com/image.png',
|
||||
];
|
||||
$actionresponse = new response_generate_image(
|
||||
success: true,
|
||||
actionname: 'generate_image',
|
||||
);
|
||||
$actionresponse->set_response($body);
|
||||
|
||||
$provider = new \aiprovider_openai\provider();
|
||||
|
||||
$manager = new manager();
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
||||
$storeresult = $method->invoke($manager, $provider, $action, $actionresponse);
|
||||
|
||||
// Check the record was written to the DB with expected values.
|
||||
$record = $DB->get_record('ai_action_register', ['id' => $storeresult], '*', MUST_EXIST);
|
||||
$this->assertEquals($action->get_basename(), $record->actionname);
|
||||
$this->assertEquals($userid, $record->userid);
|
||||
$this->assertEquals($contextid, $record->contextid);
|
||||
$this->assertEquals($provider->get_name(), $record->provider);
|
||||
$this->assertEquals($actionresponse->get_errorcode(), $record->errorcode);
|
||||
$this->assertEquals($actionresponse->get_errormessage(), $record->errormessage);
|
||||
$this->assertEquals($action->get_configuration('timecreated'), $record->timecreated);
|
||||
$this->assertEquals($actionresponse->get_timecreated(), $record->timecompleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test call_action_provider.
|
||||
*/
|
||||
public function test_call_action_provider(): void {
|
||||
$contextid = 1;
|
||||
$userid = 1;
|
||||
$prompttext = 'This is a test prompt';
|
||||
$aspectratio = 'square';
|
||||
$quality = 'hd';
|
||||
$numimages = 1;
|
||||
$style = 'vivid';
|
||||
|
||||
$action = new generate_image(
|
||||
contextid: $contextid,
|
||||
userid: $userid,
|
||||
prompttext: $prompttext,
|
||||
quality: $quality,
|
||||
aspectratio: $aspectratio,
|
||||
numimages: $numimages,
|
||||
style: $style
|
||||
);
|
||||
|
||||
$provider = new \aiprovider_openai\provider();
|
||||
|
||||
$manager = new manager();
|
||||
|
||||
// We're working with a private method here, so we need to use reflection.
|
||||
$method = new \ReflectionMethod($manager, 'call_action_provider');
|
||||
$actionresult = $method->invoke($manager, $provider, $action);
|
||||
|
||||
// Assert the result was of the correct type.
|
||||
$this->assertInstanceOf(\core_ai\aiactions\responses\response_generate_image::class, $actionresult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is_action enabled.
|
||||
*/
|
||||
public function test_is_action_enabled(): void {
|
||||
$this->resetAfterTest();
|
||||
$plugin = 'aiprovider_openai';
|
||||
$action = 'core_ai\\aiactions\\generate_image';
|
||||
|
||||
// Should be enabled by default.
|
||||
$result = manager::is_action_enabled($plugin, $action);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Disable the action.
|
||||
set_config($action, 0, $plugin);
|
||||
|
||||
// Should now be disabled.
|
||||
$result = manager::is_action_enabled($plugin, $action);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enable_action.
|
||||
*/
|
||||
public function test_enable_action(): void {
|
||||
$this->resetAfterTest();
|
||||
$plugin = 'aiprovider_openai';
|
||||
$action = 'core_ai\\aiactions\\generate_image';
|
||||
|
||||
// Disable the action.
|
||||
set_config($action, 0, $plugin);
|
||||
|
||||
// Should now be disabled.
|
||||
$result = manager::is_action_enabled($plugin, $action);
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Enable the action.
|
||||
$result = manager::enable_action($plugin, $action, 1);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\provider;
|
||||
|
||||
/**
|
||||
* Test response_base action methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\provider
|
||||
*/
|
||||
final class provider_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_name.
|
||||
*/
|
||||
public function test_get_name(): void {
|
||||
$provider = new \aiprovider_openai\provider();
|
||||
|
||||
$this->assertEquals(get_string('pluginname', 'aiprovider_openai'), $provider->get_name());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/testing/classes/incrementing_clock.php');
|
||||
|
||||
/**
|
||||
* Test ai subsystem rate limiter methods.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright 2024 Matt Porritt
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\ratelimiter
|
||||
*/
|
||||
final class ratelimiter_test extends \advanced_testcase {
|
||||
|
||||
/** @var \cache_application Cache instance for rate limiter. */
|
||||
private \cache_application $cache;
|
||||
|
||||
/**
|
||||
* Set up before tests.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->resetAfterTest(true);
|
||||
ratelimiter::reset_instance(); // Reset the singleton instance.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test global rate limit is enforced correctly.
|
||||
*/
|
||||
public function test_global_rate_limit(): void {
|
||||
$ratelimiter = ratelimiter::get_instance();
|
||||
$component = 'testcomponent';
|
||||
$ratelimit = 5;
|
||||
|
||||
// Make 5 requests, all should be allowed.
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->assertTrue($ratelimiter->check_global_rate_limit($component, $ratelimit));
|
||||
}
|
||||
|
||||
// The 6th request should be denied.
|
||||
$this->assertFalse($ratelimiter->check_global_rate_limit($component, $ratelimit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user rate limit is enforced correctly.
|
||||
*/
|
||||
public function test_user_rate_limit(): void {
|
||||
$ratelimiter = ratelimiter::get_instance();
|
||||
$component = 'testcomponent';
|
||||
$ratelimit = 3;
|
||||
$userid = 12345;
|
||||
|
||||
// Make 3 requests for the user, all should be allowed.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->assertTrue($ratelimiter->check_user_rate_limit($component, $ratelimit, $userid));
|
||||
}
|
||||
|
||||
// The 4th request should be denied.
|
||||
$this->assertFalse($ratelimiter->check_user_rate_limit($component, $ratelimit, $userid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test global rate limit resets after time window.
|
||||
*/
|
||||
public function test_global_rate_limit_reset(): void {
|
||||
$clock = new \incrementing_clock(0);
|
||||
$ratelimiter = ratelimiter::get_instance($clock);
|
||||
$component = 'testcomponent';
|
||||
$ratelimit = 3;
|
||||
|
||||
// Make 3 requests, all should be allowed.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->assertTrue($ratelimiter->check_global_rate_limit($component, $ratelimit));
|
||||
}
|
||||
|
||||
// Simulate moving time forward by TIME_WINDOW to reset the rate limit.
|
||||
$clock->set_to(ratelimiter::TIME_WINDOW + 1);
|
||||
|
||||
// The next request should be allowed again.
|
||||
$this->assertTrue($ratelimiter->check_global_rate_limit($component, $ratelimit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user rate limit resets after time window.
|
||||
*/
|
||||
public function test_user_rate_limit_reset(): void {
|
||||
$clock = new \incrementing_clock(0);
|
||||
$ratelimiter = ratelimiter::get_instance($clock);
|
||||
$component = 'testcomponent';
|
||||
$ratelimit = 3;
|
||||
$userid = 12345;
|
||||
|
||||
// Make 3 requests for the user, all should be allowed.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$this->assertTrue($ratelimiter->check_user_rate_limit($component, $ratelimit, $userid));
|
||||
}
|
||||
|
||||
// Simulate moving time forward by TIME_WINDOW to reset the rate limit.
|
||||
$clock->set_to(ratelimiter::TIME_WINDOW + 1);
|
||||
|
||||
// The next user request should be allowed again.
|
||||
$this->assertTrue($ratelimiter->check_user_rate_limit($component, $ratelimit, $userid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component 'ai', language 'en'
|
||||
*
|
||||
* @package core_ai
|
||||
* @category string
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
$string['action'] = 'Action';
|
||||
$string['action_generate_image'] = 'Generate image';
|
||||
$string['action_generate_image_desc'] = 'Generates an image based on a text prompt.';
|
||||
$string['action_generate_text'] = 'Generate text';
|
||||
$string['action_generate_text_desc'] = 'Generates text based on a text prompt.';
|
||||
$string['action_summarise_text'] = 'Summarise text';
|
||||
$string['action_summarise_text_desc'] = 'Summarises text based on provided input text.';
|
||||
$string['action_summarise_text_instruction'] = 'You will receive a text input from the user. Your task is to summarize the provided text. Follow these guidelines:
|
||||
1. Condense: Shorten long passages into key points.
|
||||
2. Simplify: Make complex information easier to understand, especially for learners.
|
||||
|
||||
Important Instructions:
|
||||
1. Return the summary in plain text only.
|
||||
2. Do not include any markdown formatting, greetings, or platitudes.
|
||||
3. Focus on clarity, conciseness, and accessibility.
|
||||
|
||||
Ensure the summary is easy to read and effectively conveys the main points of the original text.';
|
||||
$string['action_translate_text'] = 'Translate text';
|
||||
$string['action_translate_text_desc'] = 'Translate provided text from one language to another.';
|
||||
$string['ai'] = 'AI';
|
||||
$string['aiplacementsettings'] = 'Manage settings for AI placements';
|
||||
$string['aiprovidersettings'] = 'Manage settings for AI providers';
|
||||
$string['availableplacements'] = 'Available AI placements';
|
||||
$string['availableplacements_desc'] = 'Select an AI placement to manage its settings.<br/>
|
||||
AI placements are responsible for determining where and how AI services are used within the Moodle interface. <br/>
|
||||
Each enabled placement uses one or more "AI Actions". Actions can be enabled or disabled for each provider in the provider plugin settings.';
|
||||
$string['availableproviders'] = 'Available AI providers';
|
||||
$string['availableproviders_desc'] = 'Select an AI provider to manage its settings.<br/>
|
||||
AI providers are responsible for providing the AI services used by the AI subsystem. <br/>
|
||||
Each enabled provider makes available one or more "AI Actions". Actions can be enabled or disabled for each provider in the provider plugin settings.';
|
||||
$string['manageaiplacements'] = 'Manage AI placements';
|
||||
$string['manageaiproviders'] = 'Manage AI providers';
|
||||
$string['noproviders'] = 'This action is unavailable. No <a href="{$a}">providers</a> are configured for this action';
|
||||
$string['placementactionsettings'] = 'Placement actions';
|
||||
$string['placementactionsettings_desc'] = 'These are the actions that are supported by this AI placement.<br/>
|
||||
Each action can be enabled or disabled for this placement.';
|
||||
$string['placementsettings'] = 'Placement specific settings';
|
||||
$string['placementsettings_desc'] = 'These settings control various aspects of this AI placement.<br/>
|
||||
They control how the placement connects to the AI service, and related operations';
|
||||
$string['privacy:metadata'] = 'The AI subsystem currently does not store any user data.';
|
||||
$string['provideractionsettings'] = 'Provider actions';
|
||||
$string['provideractionsettings_desc'] = 'These settings are for the actions that are supported by this AI provider.<br/>
|
||||
Each action can be enabled or disabled for this provider.<br/>
|
||||
Each action can also have its own settings that can be configured here.';
|
||||
$string['providers'] = 'Providers';
|
||||
$string['providersettings'] = 'Provider specific settings';
|
||||
$string['providersettings_desc'] = 'These settings control various aspects of this AI provider.<br/>
|
||||
They control how the provider connects to the AI service, and related operations';
|
||||
$string['userpolicy'] = 'Welcome to the AI-enhanced features of the Moodle LMS. Before you start, we\'d like to inform you about a few important points.<br/>
|
||||
|
||||
When you use the AI functionality, you\'re engaging with an advanced system designed to improve your experience. It\'s important to understand that while the AI strives to provide accurate and helpful responses, it may not always get everything right. Therefore, we encourage you to use your own judgment when interpreting the AI\'s suggestions. <br/>
|
||||
|
||||
To make the AI work effectively, it processes the information you provide by communicating with an external system. This means that your queries and the AI\'s responses are shared with this system to generate the best possible outcomes for you. <br/>
|
||||
|
||||
Additionally, a record of your interactions with the AI are kept within this LMS. <br/>
|
||||
|
||||
By continuing, you acknowledge that you understand and agree to these terms. We hope you enjoy using the AI features and find them beneficial to your learning journey.';
|
||||
$string['userpolicyheading'] = 'AI policy';
|
||||
@@ -36,6 +36,8 @@ $string['area'] = 'Area';
|
||||
$string['caching'] = 'Caching';
|
||||
$string['cacheadmin'] = 'Cache administration';
|
||||
$string['cacheconfig'] = 'Configuration';
|
||||
$string['cachedef_ai_policy'] = 'User AI policy acceptance.';
|
||||
$string['cachedef_ai_ratelimit'] = 'AI Provider call rate limiting.';
|
||||
$string['cachedef_calculablesinfo'] = 'Analytics calculables info';
|
||||
$string['cachedef_calendar_subscriptions'] = 'Calendar subscriptions';
|
||||
$string['cachedef_calendar_categories'] = 'Calendar course categories that a user can access';
|
||||
|
||||
@@ -110,6 +110,10 @@ $string['status_uptodate'] = 'Installed';
|
||||
$string['supportedmoodleversions'] = 'Supported Moodle versions';
|
||||
$string['supportedconversions'] = 'Supported document conversions';
|
||||
$string['systemname'] = 'Identifier';
|
||||
$string['type_aiplacement'] = 'AI placement plugin';
|
||||
$string['type_aiplacement_plural'] = 'AI placement plugins';
|
||||
$string['type_aiprovider'] = 'AI provider plugin';
|
||||
$string['type_aiprovider_plural'] = 'AI provider plugins';
|
||||
$string['type_antivirus'] = 'Antivirus plugin';
|
||||
$string['type_antivirus_plural'] = 'Antivirus plugins';
|
||||
$string['type_auth'] = 'Authentication method';
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['ai:getpolicy'] = 'Get a users AI policy acceptance';
|
||||
$string['ai:setpolicy'] = 'Set a users AI policy acceptance';
|
||||
$string['addinganewrole'] = 'Adding a new role';
|
||||
$string['addrole'] = 'Add a new role';
|
||||
$string['advancedoverride'] = 'Advanced role override';
|
||||
|
||||
@@ -11819,3 +11819,53 @@ class admin_setting_check extends admin_setting {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the save changes button.
|
||||
*/
|
||||
class admin_setting_savebutton extends admin_setting {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name unique ascii name.
|
||||
* @param string $visiblename localised name
|
||||
* @param string $description localised long description
|
||||
* @param mixed $defaultsetting string or array depending on implementation
|
||||
*/
|
||||
public function __construct(string $name, string $visiblename = "", string $description = "", $defaultsetting = "") {
|
||||
$this->nosave = true;
|
||||
parent::__construct($name, $visiblename, $description, $defaultsetting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true, does nothing
|
||||
*
|
||||
* @return bool Always return true.
|
||||
*/
|
||||
public function get_setting(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns '', does not write anything.
|
||||
*
|
||||
* @param mixed $data string or array, must not be NULL
|
||||
* @return string Always returns ''.
|
||||
*/
|
||||
public function write_setting($data): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return part of form with setting
|
||||
* This function should always be overwritten
|
||||
*
|
||||
* @param mixed $data array or string depending on setting
|
||||
* @param string $query
|
||||
* @return string
|
||||
*/
|
||||
public function output_html($data, $query = ''): string {
|
||||
global $OUTPUT;
|
||||
return $OUTPUT->render_from_template('core_admin/setting_savebutton', []);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
define("core/ai/policy",["exports","core/ajax"],(function(_exports,_ajax){var obj;
|
||||
/**
|
||||
* AI Subsystem policy functions.
|
||||
*
|
||||
* @module core/ai/policy
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.5
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setPolicyStatus=_exports.getPolicyStatus=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getPolicyStatus=async(userId,contextId)=>{const request={methodname:"core_ai_get_policy_status",args:{userid:userId,contextid:contextId}};return(await _ajax.default.call([request]))[0]};_exports.setPolicyStatus=async(userId,contextId)=>{const request={methodname:"core_ai_set_policy_status",args:{userid:userId,contextid:contextId}};return(await _ajax.default.call([request]))[0]}}));
|
||||
|
||||
//# sourceMappingURL=policy.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"policy.min.js","sources":["../../src/ai/policy.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * AI Subsystem policy functions.\n *\n * @module core/ai/policy\n * @copyright 2024 Matt Porritt <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.5\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the policy status for a user.\n *\n * @param {int} userId The user ID.\n * @param {int} contextId Context id request is made in.\n * @return {array} The policy status.\n */\nexport const getPolicyStatus = async(userId, contextId) => {\n const request = {\n methodname: 'core_ai_get_policy_status',\n args: {userid: userId, contextid: contextId},\n };\n\n const response = await Ajax.call([request]);\n return response[0];\n};\n\n/**\n * Set the policy status for a user.\n * @param {int} userId The user ID.\n * @param {int} contextId Context id request is made in.\n * @return {promise} Promise resolved with the policy set status.\n */\nexport const setPolicyStatus = async(userId, contextId) => {\n const request = {\n methodname: 'core_ai_set_policy_status',\n args: {userid: userId, contextid: contextId},\n };\n\n const response = await Ajax.call([request]);\n return response[0];\n};\n"],"names":["async","userId","contextId","request","methodname","args","userid","contextid","Ajax","call"],"mappings":";;;;;;;;mMAiC+BA,MAAMC,OAAQC,mBACnCC,QAAU,CACZC,WAAY,4BACZC,KAAM,CAACC,OAAQL,OAAQM,UAAWL,yBAGfM,cAAKC,KAAK,CAACN,WAClB,6BASWH,MAAMC,OAAQC,mBACnCC,QAAU,CACZC,WAAY,4BACZC,KAAM,CAACC,OAAQL,OAAQM,UAAWL,yBAGfM,cAAKC,KAAK,CAACN,WAClB"}
|
||||
@@ -0,0 +1,58 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* AI Subsystem policy functions.
|
||||
*
|
||||
* @module core/ai/policy
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.5
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
/**
|
||||
* Get the policy status for a user.
|
||||
*
|
||||
* @param {int} userId The user ID.
|
||||
* @param {int} contextId Context id request is made in.
|
||||
* @return {array} The policy status.
|
||||
*/
|
||||
export const getPolicyStatus = async(userId, contextId) => {
|
||||
const request = {
|
||||
methodname: 'core_ai_get_policy_status',
|
||||
args: {userid: userId, contextid: contextId},
|
||||
};
|
||||
|
||||
const response = await Ajax.call([request]);
|
||||
return response[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the policy status for a user.
|
||||
* @param {int} userId The user ID.
|
||||
* @param {int} contextId Context id request is made in.
|
||||
* @return {promise} Promise resolved with the policy set status.
|
||||
*/
|
||||
export const setPolicyStatus = async(userId, contextId) => {
|
||||
const request = {
|
||||
methodname: 'core_ai_set_policy_status',
|
||||
args: {userid: userId, contextid: contextId},
|
||||
};
|
||||
|
||||
const response = await Ajax.call([request]);
|
||||
return response[0];
|
||||
};
|
||||
@@ -14,6 +14,11 @@
|
||||
"allowedlevel2": true,
|
||||
"allowedspread": false
|
||||
},
|
||||
"aiactions": {
|
||||
"component": "core_ai",
|
||||
"allowedlevel2": true,
|
||||
"allowedspread": false
|
||||
},
|
||||
"analytics": {
|
||||
"component": "core_analytics",
|
||||
"allowedlevel2": true,
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\plugininfo;
|
||||
|
||||
use core_plugin_manager;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* AI placement plugin info class.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiplacement extends base {
|
||||
|
||||
/**
|
||||
* Should there be a way to uninstall the plugin via the administration UI.
|
||||
* By default, uninstallation is allowed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_uninstall_allowed(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This plugintype supports its plugins being disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function plugintype_supports_disabling(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node name used in admin settings menu for this plugin settings.
|
||||
*
|
||||
* @return string node name.
|
||||
*/
|
||||
public function get_settings_section_name(): string {
|
||||
return $this->type . '_' . $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads plugin settings to the settings tree.
|
||||
*
|
||||
* @param \part_of_admin_tree $adminroot
|
||||
* @param string $parentnodename
|
||||
* @param bool $hassiteconfig whether the current user has moodle/site:config capability
|
||||
*/
|
||||
public function load_settings(
|
||||
\part_of_admin_tree $adminroot,
|
||||
$parentnodename,
|
||||
$hassiteconfig,
|
||||
): void {
|
||||
global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
|
||||
/** @var \admin_root $ADMIN */
|
||||
$ADMIN = $adminroot; // May be used in settings.php.
|
||||
$plugininfo = $this; // Also can be used inside settings.php.
|
||||
|
||||
if (!$this->is_installed_and_upgraded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$hassiteconfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section = $this->get_settings_section_name();
|
||||
|
||||
// Load the specific settings.
|
||||
$settings = new \core_ai\admin\admin_settingspage_provider(
|
||||
name: $section,
|
||||
visiblename: $this->displayname,
|
||||
req_capability: 'moodle/site:config',
|
||||
hidden: $this->is_enabled() === false,
|
||||
);
|
||||
if (file_exists($this->full_path('settings.php'))) {
|
||||
include($this->full_path('settings.php')); // This may also set $settings to null.
|
||||
}
|
||||
|
||||
// Show the save changes button between the specific settings and the actions table.
|
||||
$settings->add(new \admin_setting_savebutton("{$section}/savebutton"));
|
||||
|
||||
// Load the actions table.
|
||||
if (file_exists($this->full_path('setting_actions.php'))) {
|
||||
include($this->full_path('setting_actions.php')); // This may also set $settings to null.
|
||||
} else {
|
||||
$settings->add(new \admin_setting_heading("{$section}/generals",
|
||||
new \lang_string('placementactionsettings', 'core_ai'),
|
||||
new \lang_string('placementactionsettings_desc', 'core_ai')));
|
||||
// Load the setting table of actions that this provider supports.
|
||||
$settings->add(new \core_ai\admin\admin_setting_action_manager(
|
||||
$section,
|
||||
\core_ai\table\aiplacement_action_management_table::class,
|
||||
'manageaiplacements',
|
||||
new \lang_string('manageaiproviders', 'core_ai'),
|
||||
));
|
||||
}
|
||||
|
||||
if ($settings) {
|
||||
$ADMIN->add($parentnodename, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL used for management of plugins of this type.
|
||||
* @return moodle_url
|
||||
*/
|
||||
public static function get_manage_url(): moodle_url {
|
||||
return new moodle_url('/admin/settings.php', [
|
||||
'section' => 'aiplacement',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable a plugin.
|
||||
* When possible, the change will be stored into the config_log table, to let admins check when/who has modified it.
|
||||
*
|
||||
* @param string $pluginname The plugin name to enable/disable.
|
||||
* @param int $enabled Whether the pluginname should be enabled (1) or not (0).
|
||||
*
|
||||
* @return bool Whether $pluginname has been updated or not.
|
||||
*/
|
||||
public static function enable_plugin(string $pluginname, int $enabled): bool {
|
||||
$plugin = "aiplacement_$pluginname";
|
||||
$oldvalue = self::is_plugin_enabled($pluginname);
|
||||
$newvalue = (bool)$enabled;
|
||||
|
||||
if ($oldvalue !== $newvalue) {
|
||||
if ($newvalue) {
|
||||
set_config('enabled', $enabled, $plugin);
|
||||
} else {
|
||||
unset_config('enabled', $plugin);
|
||||
}
|
||||
|
||||
add_to_config_log('enabled', $oldvalue, $newvalue, $plugin);
|
||||
core_plugin_manager::reset_caches();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all enabled plugins, the result may include missing plugins.
|
||||
*
|
||||
* @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown.
|
||||
*/
|
||||
public static function get_enabled_plugins(): ?array {
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
$plugins = $pluginmanager->get_installed_plugins('aiplacement');
|
||||
|
||||
if (!$plugins) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$plugins = array_keys($plugins);
|
||||
|
||||
// Filter to return only enabled plugins.
|
||||
$enabled = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
if (self::is_plugin_enabled($plugin)) {
|
||||
$enabled[$plugin] = $plugin;
|
||||
}
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provider plugin is enabled in config.
|
||||
*
|
||||
* @param string $plugin The plugin to check.
|
||||
* @return bool Return true if enabled.
|
||||
*/
|
||||
public static function is_plugin_enabled(string $plugin): bool {
|
||||
$config = get_config("aiplacement_$plugin", 'enabled');
|
||||
return $config == 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\plugininfo;
|
||||
|
||||
use core_plugin_manager;
|
||||
use moodle_url;
|
||||
use core\lang_string;
|
||||
|
||||
/**
|
||||
* AI placement plugin info class.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Matt Porritt <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiprovider extends base {
|
||||
/**
|
||||
* Should there be a way to uninstall the plugin via the administration UI.
|
||||
* By default, uninstallation is allowed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_uninstall_allowed(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This plugintype supports its plugins being disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function plugintype_supports_disabling(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node name used in admin settings menu for this plugin settings.
|
||||
*
|
||||
* @return string node name.
|
||||
*/
|
||||
public function get_settings_section_name(): string {
|
||||
return $this->type . '_' . $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads plugin settings to the settings tree.
|
||||
*
|
||||
* @param \part_of_admin_tree $adminroot
|
||||
* @param string $parentnodename
|
||||
* @param bool $hassiteconfig whether the current user has moodle/site:config capability
|
||||
*/
|
||||
public function load_settings(
|
||||
\part_of_admin_tree $adminroot,
|
||||
$parentnodename,
|
||||
$hassiteconfig,
|
||||
): void {
|
||||
global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
|
||||
/** @var \admin_root $ADMIN */
|
||||
$ADMIN = $adminroot; // May be used in settings.php.
|
||||
$plugininfo = $this; // Also can be used inside settings.php.
|
||||
|
||||
if (!$this->is_installed_and_upgraded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$hassiteconfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section = $this->get_settings_section_name();
|
||||
|
||||
// Load the specific settings.
|
||||
$settings = new \core_ai\admin\admin_settingspage_provider(
|
||||
name: $section,
|
||||
visiblename: $this->displayname,
|
||||
req_capability: 'moodle/site:config',
|
||||
hidden: $this->is_enabled() === false,
|
||||
);
|
||||
if (file_exists($this->full_path('settings.php'))) {
|
||||
include($this->full_path('settings.php')); // This may also set $settings to null.
|
||||
}
|
||||
|
||||
// Show the save changes button between the specific settings and the actions table.
|
||||
$settings->add(new \admin_setting_savebutton("{$section}/savebutton"));
|
||||
|
||||
// Load the actions table.
|
||||
if (file_exists($this->full_path('setting_actions.php'))) {
|
||||
include($this->full_path('setting_actions.php')); // This may also set $settings to null.
|
||||
} else {
|
||||
// Provider action settings heading.
|
||||
$settings->add(new \admin_setting_heading("{$section}/generals",
|
||||
new lang_string('provideractionsettings', 'core_ai'),
|
||||
new lang_string('provideractionsettings_desc', 'core_ai')));
|
||||
// Load the setting table of actions that this provider supports.
|
||||
$settings->add(new \core_ai\admin\admin_setting_action_manager(
|
||||
$section,
|
||||
\core_ai\table\aiprovider_action_management_table::class,
|
||||
'manageaiproviders',
|
||||
new lang_string('manageaiproviders', 'core_ai'),
|
||||
));
|
||||
}
|
||||
|
||||
$ADMIN->add($parentnodename, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL used for management of plugins of this type.
|
||||
* @return moodle_url
|
||||
*/
|
||||
public static function get_manage_url(): moodle_url {
|
||||
return new \moodle_url('/admin/settings.php', [
|
||||
'section' => 'aiprovider',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable a plugin.
|
||||
* When possible, the change will be stored into the config_log table, to let admins check when/who has modified it.
|
||||
*
|
||||
* @param string $pluginname The plugin name to enable/disable.
|
||||
* @param int $enabled Whether the pluginname should be enabled (1) or not (0).
|
||||
*
|
||||
* @return bool Whether $pluginname has been updated or not.
|
||||
*/
|
||||
public static function enable_plugin(string $pluginname, int $enabled): bool {
|
||||
$plugin = 'aiprovider_' . $pluginname;
|
||||
$oldvalue = self::is_plugin_enabled($pluginname);
|
||||
$newvalue = (bool)$enabled;
|
||||
|
||||
if ($oldvalue !== $newvalue) {
|
||||
if ($newvalue) {
|
||||
set_config('enabled', $enabled, $plugin);
|
||||
} else {
|
||||
unset_config('enabled', $plugin);
|
||||
}
|
||||
|
||||
add_to_config_log('enabled', $oldvalue, $newvalue, $plugin);
|
||||
core_plugin_manager::reset_caches();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all enabled plugins, the result may include missing plugins.
|
||||
*
|
||||
* @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown.
|
||||
*/
|
||||
public static function get_enabled_plugins(): ?array {
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
$plugins = $pluginmanager->get_installed_plugins('aiprovider');
|
||||
|
||||
if (!$plugins) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$plugins = array_keys($plugins);
|
||||
|
||||
// Filter to return only enabled plugins.
|
||||
$enabled = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
if (self::is_plugin_enabled($plugin)) {
|
||||
$enabled[$plugin] = $plugin;
|
||||
}
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provider plugin is enabled in config.
|
||||
*
|
||||
* @param string $plugin The plugin to check.
|
||||
* @return bool Return true if enabled.
|
||||
*/
|
||||
public static function is_plugin_enabled(string $plugin): bool {
|
||||
$config = get_config('aiprovider_' . $plugin, 'enabled');
|
||||
return $config == 1;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"plugintypes": {
|
||||
"aiplacement": "ai\/placement",
|
||||
"aiprovider": "ai\/provider",
|
||||
"antivirus": "lib\/antivirus",
|
||||
"availability": "availability\/condition",
|
||||
"qtype": "question\/type",
|
||||
@@ -44,6 +46,7 @@
|
||||
"paygw": "payment\/gateway"
|
||||
},
|
||||
"subsystems": {
|
||||
"ai": "ai",
|
||||
"access": null,
|
||||
"admin": "admin",
|
||||
"adminpresets": "admin/presets",
|
||||
|
||||
@@ -2785,4 +2785,21 @@ $capabilities = array(
|
||||
'manager' => CAP_ALLOW,
|
||||
],
|
||||
],
|
||||
|
||||
// Set a users acceptance of the AI policy.
|
||||
'moodle/ai:setpolicy' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW,
|
||||
],
|
||||
],
|
||||
// Get a users acceptance of the AI policy.
|
||||
'moodle/ai:getpolicy' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW,
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -625,4 +625,21 @@ $definitions = array(
|
||||
'simpledata' => true,
|
||||
'canuselocalstore' => true,
|
||||
],
|
||||
// Cache to store user AI policy acceptance status.
|
||||
'ai_policy' => [
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true, // Cache must use simple keys (a-zA-Z0-9_).
|
||||
'simpledata' => true, // Cache stores integer values which are simple data.
|
||||
'staticacceleration' => true,
|
||||
'datasource' => '\core_ai\cache\policy',
|
||||
'canuselocalstore' => true,
|
||||
],
|
||||
// Cache to store AI rate limits.
|
||||
// Used by AI Provider plugins to limit the number of requests to external services.
|
||||
'ai_ratelimit' => [
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true, // Cache must use simple keys (a-zA-Z0-9_).
|
||||
'simpledata' => true, // Cache stores integer values which are simple data.
|
||||
'staticacceleration' => true,
|
||||
],
|
||||
);
|
||||
|
||||
+81
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20240408" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20240710" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -4859,5 +4859,85 @@
|
||||
<INDEX NAME="uid_index" UNIQUE="false" FIELDS="idnumber"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="ai_policy_register" COMMENT="Register of users who have accepted this sites AI usage policy">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the user that has accepted the policy"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The context id that the policy was accepted in"/>
|
||||
<FIELD NAME="timeaccepted" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp of when the policy was accepted"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign-unique" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="Foreign key to user table"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="ai_action_generate_image" COMMENT="stores specific data about generate image actions">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="prompt" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The text from the user that was used to generate the image"/>
|
||||
<FIELD NAME="numberimages" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The number of images requested to be generated"/>
|
||||
<FIELD NAME="quality" TYPE="char" LENGTH="21" NOTNULL="true" SEQUENCE="false" COMMENT="The quality of the image, e.g. hd."/>
|
||||
<FIELD NAME="aspectratio" TYPE="char" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="The aspect ratio of the generate image, e.g landscape"/>
|
||||
<FIELD NAME="style" TYPE="char" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="The style of the image, e.g. vivid"/>
|
||||
<FIELD NAME="sourceurl" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="URL of the generated response."/>
|
||||
<FIELD NAME="revisedprompt" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The actual prompt the AI used to generate the image"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="ai_action_register" COMMENT="Stores information about processed ai actions.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="actionname" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="Name of the action"/>
|
||||
<FIELD NAME="actionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID in related action table with more details about the action"/>
|
||||
<FIELD NAME="success" TYPE="binary" NOTNULL="true" SEQUENCE="false" COMMENT="Was the action successful when run"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The user who made the request to run the action"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the context the action request was made in"/>
|
||||
<FIELD NAME="provider" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="The provider plugin name that processed the action"/>
|
||||
<FIELD NAME="errorcode" TYPE="int" LENGTH="4" NOTNULL="false" SEQUENCE="false" COMMENT="If there was an error this was the error code"/>
|
||||
<FIELD NAME="errormessage" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="If there was an error this was the message"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp of when the action was created"/>
|
||||
<FIELD NAME="timecompleted" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Timestamp of when the action was completed, as recorded by the manager"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="Relationship to user table"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="action" UNIQUE="true" FIELDS="actionname, actionid" COMMENT="unique index of actionname and actionid"/>
|
||||
<INDEX NAME="provider" UNIQUE="false" FIELDS="actionname, provider" COMMENT="Index of actionname and provider"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="ai_action_generate_text" COMMENT="Stores specific data about generate text actions.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="prompt" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The text from the user that was used to generate the text response"/>
|
||||
<FIELD NAME="responseid" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="A unique identifier for the chat completion, returned by the AI."/>
|
||||
<FIELD NAME="fingerprint" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="his fingerprint represents the backend configuration that the model runs with."/>
|
||||
<FIELD NAME="generatedcontent" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The contents of the generated message."/>
|
||||
<FIELD NAME="finishreason" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="The reason the model stopped generating tokens."/>
|
||||
<FIELD NAME="prompttokens" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Number of tokens in the prompt."/>
|
||||
<FIELD NAME="completiontoken" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Number of tokens in the generated completion."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="ai_action_summarise_text" COMMENT="Stores specific data about summarise text actions.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="prompt" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The text from the user that was used to generate the text response"/>
|
||||
<FIELD NAME="responseid" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="A unique identifier for the chat completion, returned by the AI."/>
|
||||
<FIELD NAME="fingerprint" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="his fingerprint represents the backend configuration that the model runs with."/>
|
||||
<FIELD NAME="generatedcontent" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The contents of the generated message."/>
|
||||
<FIELD NAME="finishreason" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="The reason the model stopped generating tokens."/>
|
||||
<FIELD NAME="prompttokens" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Number of tokens in the prompt."/>
|
||||
<FIELD NAME="completiontoken" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Number of tokens in the generated completion."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
||||
@@ -3244,6 +3244,28 @@ $functions = array(
|
||||
'ajax' => true,
|
||||
'readonlysession' => true,
|
||||
],
|
||||
'core_ai_set_policy_status' => [
|
||||
'classname' => 'core_ai\external\set_policy_status',
|
||||
'methodname' => 'set_policy_status',
|
||||
'description' => 'Set a users AI policy acceptance',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
'core_ai_get_policy_status' => [
|
||||
'classname' => 'core_ai\external\get_policy_status',
|
||||
'methodname' => 'get_policy_status',
|
||||
'description' => 'Get a users AI policy acceptance',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
'core_ai_set_action' => [
|
||||
'classname' => 'core_ai\external\set_action',
|
||||
'description' => 'Update action',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
);
|
||||
|
||||
$services = array(
|
||||
|
||||
@@ -1250,5 +1250,120 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2024082900.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2024091000.01) {
|
||||
// Define table ai_policy_register to be created.
|
||||
$table = new xmldb_table('ai_policy_register');
|
||||
|
||||
// Adding fields to table ai_policy_register.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timeaccepted', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table ai_policy_register.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('userid', XMLDB_KEY_FOREIGN_UNIQUE, ['userid'], 'user', ['id']);
|
||||
|
||||
// Conditionally launch create table for ai_policy_register.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table ai_action_generate_image to be created.
|
||||
$table = new xmldb_table('ai_action_generate_image');
|
||||
|
||||
// Adding fields to table ai_action_generate_image.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('prompt', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('numberimages', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('quality', XMLDB_TYPE_CHAR, '20', null, null, null, null);
|
||||
$table->add_field('aspectratio', XMLDB_TYPE_CHAR, '21', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('style', XMLDB_TYPE_CHAR, '20', null, null, null, null);
|
||||
$table->add_field('sourceurl', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
$table->add_field('revisedprompt', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
|
||||
// Adding keys to table ai_action_generate_image.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Conditionally launch create table for ai_action_generate_image.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table ai_action_register to be created.
|
||||
$table = new xmldb_table('ai_action_register');
|
||||
|
||||
// Adding fields to table ai_action_register.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('actionname', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('actionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('success', XMLDB_TYPE_BINARY, null, null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('provider', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('errorcode', XMLDB_TYPE_INTEGER, '4', null, null, null, null);
|
||||
$table->add_field('errormessage', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecompleted', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
|
||||
// Adding keys to table ai_action_register.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('userid', XMLDB_KEY_FOREIGN, ['userid'], 'user', ['id']);
|
||||
|
||||
// Adding indexes to table ai_action_register.
|
||||
$table->add_index('action', XMLDB_INDEX_UNIQUE, ['actionname', 'actionid']);
|
||||
$table->add_index('provider', XMLDB_INDEX_NOTUNIQUE, ['actionname', 'provider']);
|
||||
|
||||
// Conditionally launch create table for ai_action_register.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table ai_action_generate_text to be created.
|
||||
$table = new xmldb_table('ai_action_generate_text');
|
||||
|
||||
// Adding fields to table ai_action_generate_text.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('prompt', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('responseid', XMLDB_TYPE_CHAR, '128', null, null, null, null);
|
||||
$table->add_field('fingerprint', XMLDB_TYPE_CHAR, '128', null, null, null, null);
|
||||
$table->add_field('generatedcontent', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
$table->add_field('finishreason', XMLDB_TYPE_CHAR, '128', null, null, null, null);
|
||||
$table->add_field('prompttokens', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
$table->add_field('completiontoken', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
|
||||
// Adding keys to table ai_action_generate_text.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Conditionally launch create table for ai_action_generate_text.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table ai_action_summarise_text to be created.
|
||||
$table = new xmldb_table('ai_action_summarise_text');
|
||||
|
||||
// Adding fields to table ai_action_summarise_text.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('prompt', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('responseid', XMLDB_TYPE_CHAR, '128', null, null, null, null);
|
||||
$table->add_field('fingerprint', XMLDB_TYPE_CHAR, '128', null, null, null, null);
|
||||
$table->add_field('generatedcontent', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
$table->add_field('finishreason', XMLDB_TYPE_CHAR, '128', null, null, null, null);
|
||||
$table->add_field('prompttokens', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
$table->add_field('completiontoken', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
|
||||
// Adding keys to table ai_action_summarise_text.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Conditionally launch create table for ai_action_summarise_text.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2024091000.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ final class component_test extends \advanced_testcase {
|
||||
* this is defined here to annoy devs that try to add more without any thinking,
|
||||
* always verify that it does not collide with any existing add-on modules and subplugins!!!
|
||||
*/
|
||||
const SUBSYSTEMCOUNT = 78;
|
||||
const SUBSYSTEMCOUNT = 79;
|
||||
|
||||
public function test_get_core_subsystems(): void {
|
||||
global $CFG;
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2024091000.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2024091000.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.5dev+ (Build: 20240910)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user