mirror of
https://github.com/moodle/moodle.git
synced 2025-08-07 18:06:51 +02:00
MDL-82977 AI: Provider instances
Create provider instances for AI povider plugins. Each provider plugin can now have multiple instances, allowing for different configurations and models using the same base provider.
This commit is contained in:
parent
eabd31c5d3
commit
368114c7cd
56 changed files with 2987 additions and 477 deletions
9
.upgradenotes/MDL-82922-2025011702481097.yml
Normal file
9
.upgradenotes/MDL-82922-2025011702481097.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
issueNumber: MDL-82922
|
||||||
|
notes:
|
||||||
|
core_ai:
|
||||||
|
- message: >-
|
||||||
|
The ai_provider_management_table has been refactored to inherit from
|
||||||
|
flexible_table instead of plugin_management_table. As a result the
|
||||||
|
methods get_plugintype and get_action_url are now unused and have been
|
||||||
|
deprecated in the class.
|
||||||
|
type: deprecated
|
10
ai/amd/build/aiprovider_action_management_table.min.js
vendored
Normal file
10
ai/amd/build/aiprovider_action_management_table.min.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
define("core_ai/aiprovider_action_management_table",["exports","core_admin/plugin_management_table","core/ajax"],(function(_exports,_plugin_management_table,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_plugin_management_table=(obj=_plugin_management_table)&&obj.__esModule?obj:{default:obj};let watching=!1;
|
||||||
|
/**
|
||||||
|
* Handles setting plugin state for the AI provider management table.
|
||||||
|
*
|
||||||
|
* @module core_ai/aiprovider_action_management_table
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/class _default extends _plugin_management_table.default{constructor(providerid){super(),this.providerid=providerid}static init(providerid){watching||(watching=!0,new this(providerid))}setPluginState(methodname,pluginaction,state){const providerid=this.providerid;return(0,_ajax.call)([{methodname:methodname,args:{pluginaction:pluginaction,state:state,providerid:providerid}}])[0]}}return _exports.default=_default,_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=aiprovider_action_management_table.min.js.map
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"aiprovider_action_management_table.min.js","sources":["../src/aiprovider_action_management_table.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\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\nimport PluginManagementTable from 'core_admin/plugin_management_table';\nimport {call as fetchMany} from 'core/ajax';\n\nlet watching = false;\n\n/**\n * Handles setting plugin state for the AI provider management table.\n *\n * @module core_ai/aiprovider_action_management_table\n * @copyright 2024 Matt Porritt <matt.porritt@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends PluginManagementTable {\n\n /**\n * Constructor for the class.\n *\n * @param {int} providerid The provider id.\n */\n constructor(providerid) {\n super(); // Call the parent constructor, so inherited properties and methods initialize properly.\n this.providerid = providerid; // Store provider id as an instance field.\n }\n\n /**\n * Initialise an instance of the class.\n *\n * @param {int} providerid The provider id.\n */\n static init(providerid) {\n if (watching) {\n return;\n }\n watching = true;\n new this(providerid);\n }\n\n /**\n * Set the plugin state (enabled or disabled).\n *\n * @param {string} methodname The web service to call.\n * @param {string} pluginaction The name of the plugin and action to set the state for.\n * @param {number} state The state to set.\n * @returns {Promise}\n */\n setPluginState(methodname, pluginaction, state) {\n const providerid = this.providerid;\n return fetchMany([{\n methodname,\n args: {\n pluginaction,\n state,\n providerid,\n },\n }])[0];\n }\n\n}\n"],"names":["watching","PluginManagementTable","constructor","providerid","this","setPluginState","methodname","pluginaction","state","args"],"mappings":"0VAiBIA,UAAW;;;;;;;4BAScC,iCAOzBC,YAAYC,yBAEHA,WAAaA,uBAQVA,YACJH,WAGJA,UAAW,MACPI,KAAKD,aAWbE,eAAeC,WAAYC,aAAcC,aAC/BL,WAAaC,KAAKD,kBACjB,cAAU,CAAC,CACdG,WAAAA,WACAG,KAAM,CACFF,aAAAA,aACAC,MAAAA,MACAL,WAAAA,eAEJ"}
|
10
ai/amd/build/aiprovider_instance_management_table.min.js
vendored
Normal file
10
ai/amd/build/aiprovider_instance_management_table.min.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
define("core_ai/aiprovider_instance_management_table",["exports","core_admin/plugin_management_table","core/ajax","core_table/dynamic","core/pending","core/notification","core/prefetch","core/str","core/modal_delete_cancel","core/modal_events"],(function(_exports,_plugin_management_table,_ajax,_dynamic,_pending,_notification,_prefetch,_str,_modal_delete_cancel,_modal_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_plugin_management_table=_interopRequireDefault(_plugin_management_table),_pending=_interopRequireDefault(_pending),_modal_delete_cancel=_interopRequireDefault(_modal_delete_cancel),_modal_events=_interopRequireDefault(_modal_events);let watching=!1;
|
||||||
|
/**
|
||||||
|
* Handles setting plugin state for the AI provider management table.
|
||||||
|
*
|
||||||
|
* @module core_ai/aiprovider_instance_management_table
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/class _default extends _plugin_management_table.default{constructor(){super(),this.addClickHandler(this.handleDelete)}static init(){watching||((0,_prefetch.prefetchStrings)("core_ai",["providerinstancedelete","providerinstancedeleteconfirm"]),watching=!0,new this)}deleteProvider(methodname,providerid){return(0,_ajax.call)([{methodname:methodname,args:{providerid:providerid}}])[0]}async handleDelete(tableRoot,e){if(e.target.closest("[data-delete-method]")){e.preventDefault();const providerId=e.target.dataset.id,deleteMethod=e.target.dataset.deleteMethod,bodyParams={provider:e.target.dataset.provider,name:e.target.dataset.name},modal=await _modal_delete_cancel.default.create({title:(0,_str.getString)("providerinstancedelete","core_ai"),body:(0,_str.getString)("providerinstancedeleteconfirm","core_ai",bodyParams),show:!0,removeOnClose:!0});modal.getRoot().on(_modal_events.default.delete,(async e=>{e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:deleteProvider");await this.deleteProvider(deleteMethod,providerId),await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),modal.destroy(),pendingPromise.resolve()}))}}}return _exports.default=_default,_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=aiprovider_instance_management_table.min.js.map
|
File diff suppressed because one or more lines are too long
11
ai/amd/build/providerchooser.min.js
vendored
Normal file
11
ai/amd/build/providerchooser.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
define("core_ai/providerchooser",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
|
||||||
|
/**
|
||||||
|
* AI provider selection handler.
|
||||||
|
*
|
||||||
|
* @module core_ai/providerchooser
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
const Selectors_fields={selector:'[data-aiproviderchooser-field="selector"]',updateButton:'[data-aiproviderchooser-field="updateButton"]'};_exports.init=()=>{var _document$querySelect;null===(_document$querySelect=document.querySelector(Selectors_fields.selector))||void 0===_document$querySelect||_document$querySelect.addEventListener("change",(e=>{const form=e.target.closest("form"),updateButton=form.querySelector(Selectors_fields.updateButton),url=new URL(form.action);form.action=url.toString(),updateButton.click()}))}}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=providerchooser.min.js.map
|
1
ai/amd/build/providerchooser.min.js.map
Normal file
1
ai/amd/build/providerchooser.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"providerchooser.min.js","sources":["../src/providerchooser.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\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 provider selection handler.\n *\n * @module core_ai/providerchooser\n * @copyright 2024 Matt Porritt <matt.porritt@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n fields: {\n selector: '[data-aiproviderchooser-field=\"selector\"]',\n updateButton: '[data-aiproviderchooser-field=\"updateButton\"]',\n },\n};\n\n/**\n * Initialise the AI provider chooser.\n */\nexport const init = () => {\n document.querySelector(Selectors.fields.selector)?.addEventListener('change', e => {\n const form = e.target.closest('form');\n const updateButton = form.querySelector(Selectors.fields.updateButton);\n const url = new URL(form.action);\n\n form.action = url.toString();\n updateButton.click();\n });\n};\n"],"names":["Selectors","selector","updateButton","document","querySelector","addEventListener","e","form","target","closest","url","URL","action","toString","click"],"mappings":";;;;;;;;MAsBMA,iBACM,CACJC,SAAU,4CACVC,aAAc,+DAOF,6DAChBC,SAASC,cAAcJ,iBAAiBC,kEAAWI,iBAAiB,UAAUC,UACpEC,KAAOD,EAAEE,OAAOC,QAAQ,QACxBP,aAAeK,KAAKH,cAAcJ,iBAAiBE,cACnDQ,IAAM,IAAIC,IAAIJ,KAAKK,QAEzBL,KAAKK,OAASF,IAAIG,WAClBX,aAAaY"}
|
72
ai/amd/src/aiprovider_action_management_table.js
Normal file
72
ai/amd/src/aiprovider_action_management_table.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import PluginManagementTable from 'core_admin/plugin_management_table';
|
||||||
|
import {call as fetchMany} from 'core/ajax';
|
||||||
|
|
||||||
|
let watching = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles setting plugin state for the AI provider management table.
|
||||||
|
*
|
||||||
|
* @module core_ai/aiprovider_action_management_table
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
export default class extends PluginManagementTable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the class.
|
||||||
|
*
|
||||||
|
* @param {int} providerid The provider id.
|
||||||
|
*/
|
||||||
|
constructor(providerid) {
|
||||||
|
super(); // Call the parent constructor, so inherited properties and methods initialize properly.
|
||||||
|
this.providerid = providerid; // Store provider id as an instance field.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise an instance of the class.
|
||||||
|
*
|
||||||
|
* @param {int} providerid The provider id.
|
||||||
|
*/
|
||||||
|
static init(providerid) {
|
||||||
|
if (watching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
watching = true;
|
||||||
|
new this(providerid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the plugin state (enabled or disabled).
|
||||||
|
*
|
||||||
|
* @param {string} methodname The web service to call.
|
||||||
|
* @param {string} pluginaction The name of the plugin and action to set the state for.
|
||||||
|
* @param {number} state The state to set.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
setPluginState(methodname, pluginaction, state) {
|
||||||
|
const providerid = this.providerid;
|
||||||
|
return fetchMany([{
|
||||||
|
methodname,
|
||||||
|
args: {
|
||||||
|
pluginaction,
|
||||||
|
state,
|
||||||
|
providerid,
|
||||||
|
},
|
||||||
|
}])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
114
ai/amd/src/aiprovider_instance_management_table.js
Normal file
114
ai/amd/src/aiprovider_instance_management_table.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import PluginManagementTable from 'core_admin/plugin_management_table';
|
||||||
|
import {call as fetchMany} from 'core/ajax';
|
||||||
|
import {refreshTableContent} from 'core_table/dynamic';
|
||||||
|
import Pending from 'core/pending';
|
||||||
|
import {fetchNotifications} from 'core/notification';
|
||||||
|
import {prefetchStrings} from 'core/prefetch';
|
||||||
|
import {getString} from 'core/str';
|
||||||
|
import DeleteCancelModal from 'core/modal_delete_cancel';
|
||||||
|
import ModalEvents from 'core/modal_events';
|
||||||
|
|
||||||
|
let watching = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles setting plugin state for the AI provider management table.
|
||||||
|
*
|
||||||
|
* @module core_ai/aiprovider_instance_management_table
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
export default class extends PluginManagementTable {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.addClickHandler(this.handleDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise an instance of the class.
|
||||||
|
*
|
||||||
|
* This is just a way of making it easier to initialise an instance of the class from PHP.
|
||||||
|
*/
|
||||||
|
static init() {
|
||||||
|
if (watching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefetchStrings('core_ai', [
|
||||||
|
'providerinstancedelete',
|
||||||
|
'providerinstancedeleteconfirm',
|
||||||
|
]);
|
||||||
|
|
||||||
|
watching = true;
|
||||||
|
new this();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the delete service.
|
||||||
|
*
|
||||||
|
* @param {string} methodname The web service to call
|
||||||
|
* @param {number} providerid The provider id.
|
||||||
|
* @return {Promise} The promise.
|
||||||
|
*/
|
||||||
|
deleteProvider(methodname, providerid) {
|
||||||
|
return fetchMany([{
|
||||||
|
methodname,
|
||||||
|
args: {
|
||||||
|
providerid,
|
||||||
|
},
|
||||||
|
}])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle delete.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} tableRoot
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
async handleDelete(tableRoot, e) {
|
||||||
|
const deleteElement = e.target.closest('[data-delete-method]');
|
||||||
|
if (deleteElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
const providerId = e.target.dataset.id;
|
||||||
|
const deleteMethod = e.target.dataset.deleteMethod;
|
||||||
|
const bodyParams = {
|
||||||
|
provider: e.target.dataset.provider,
|
||||||
|
name: e.target.dataset.name,
|
||||||
|
};
|
||||||
|
const modal = await DeleteCancelModal.create({
|
||||||
|
title: getString('providerinstancedelete', 'core_ai'),
|
||||||
|
body: getString('providerinstancedeleteconfirm', 'core_ai', bodyParams),
|
||||||
|
show: true,
|
||||||
|
removeOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle delete event.
|
||||||
|
modal.getRoot().on(ModalEvents.delete, async(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const pendingPromise = new Pending('core_table/dynamic:deleteProvider');
|
||||||
|
await this.deleteProvider(deleteMethod, providerId);
|
||||||
|
// Reload the table, so we get the updated list of providers, and any messages.
|
||||||
|
await Promise.all([
|
||||||
|
refreshTableContent(tableRoot),
|
||||||
|
fetchNotifications(),
|
||||||
|
]);
|
||||||
|
modal.destroy();
|
||||||
|
pendingPromise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
ai/amd/src/providerchooser.js
Normal file
42
ai/amd/src/providerchooser.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// 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 provider selection handler.
|
||||||
|
*
|
||||||
|
* @module core_ai/providerchooser
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Selectors = {
|
||||||
|
fields: {
|
||||||
|
selector: '[data-aiproviderchooser-field="selector"]',
|
||||||
|
updateButton: '[data-aiproviderchooser-field="updateButton"]',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the AI provider chooser.
|
||||||
|
*/
|
||||||
|
export const init = () => {
|
||||||
|
document.querySelector(Selectors.fields.selector)?.addEventListener('change', e => {
|
||||||
|
const form = e.target.closest('form');
|
||||||
|
const updateButton = form.querySelector(Selectors.fields.updateButton);
|
||||||
|
const url = new URL(form.action);
|
||||||
|
|
||||||
|
form.action = url.toString();
|
||||||
|
updateButton.click();
|
||||||
|
});
|
||||||
|
};
|
78
ai/classes/admin/admin_setting_provider_manager.php
Normal file
78
ai/classes/admin/admin_setting_provider_manager.php
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<?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;
|
||||||
|
use coding_exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin setting provider manager.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class admin_setting_provider_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[\Override]
|
||||||
|
public function get_setting(): bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[\Override]
|
||||||
|
public function write_setting($data): string {
|
||||||
|
// Do not write any setting.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
#[\Override]
|
||||||
|
public function output_html($data, $query = ''): string {
|
||||||
|
$table = new $this->tableclass($this->pluginname);
|
||||||
|
if (
|
||||||
|
!($table instanceof \core_ai\table\aiprovider_management_table)
|
||||||
|
) {
|
||||||
|
throw new coding_exception(sprintf(
|
||||||
|
"% must be an instance aiprovider_management_table",
|
||||||
|
$this->tableclass
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return highlight($query, $table->get_content());
|
||||||
|
}
|
||||||
|
}
|
111
ai/classes/external/delete_provider_instance.php
vendored
Normal file
111
ai/classes/external/delete_provider_instance.php
vendored
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?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 context_system;
|
||||||
|
use core_external\external_api;
|
||||||
|
use core_external\external_function_parameters;
|
||||||
|
use core_external\external_single_structure;
|
||||||
|
use core_external\external_value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External API to delete a provider instance.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class delete_provider_instance extends external_api {
|
||||||
|
/**
|
||||||
|
* Get provider parameters.
|
||||||
|
*
|
||||||
|
* @since Moodel 5.0
|
||||||
|
* @return external_function_parameters
|
||||||
|
*/
|
||||||
|
public static function execute_parameters(): external_function_parameters {
|
||||||
|
return new external_function_parameters([
|
||||||
|
'providerid' => new external_value(PARAM_INT, 'Provider ID', VALUE_REQUIRED),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a provider instance.
|
||||||
|
*
|
||||||
|
* @since Moodel 5.0
|
||||||
|
* @param int $providerid The provider ID.
|
||||||
|
* @return array The generated content.
|
||||||
|
*/
|
||||||
|
public static function execute(int $providerid): array {
|
||||||
|
[
|
||||||
|
'providerid' => $providerid,
|
||||||
|
] = self::validate_parameters(self::execute_parameters(), [
|
||||||
|
'providerid' => $providerid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$context = context_system::instance();
|
||||||
|
self::validate_context($context);
|
||||||
|
require_capability('moodle/site:config', $context);
|
||||||
|
|
||||||
|
// Get AI provider instance.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$aiproviders = $manager->get_provider_instances(['id' => $providerid]);
|
||||||
|
$aiprovider = reset($aiproviders);
|
||||||
|
|
||||||
|
if (!$aiprovider) {
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'message' => get_string('notfound', 'error'),
|
||||||
|
'messagetype' => \core\notification::ERROR,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$providerresult = $manager->delete_provider_instance(provider: $aiprovider);
|
||||||
|
if (!$providerresult) {
|
||||||
|
$message = get_string('providerinstancedeletefailed', 'core_ai', $aiprovider->name);
|
||||||
|
$messagetype = \core\notification::ERROR;
|
||||||
|
} else {
|
||||||
|
$message = get_string('providerinstancedeleted', 'core_ai', $aiprovider->name);
|
||||||
|
$messagetype = \core\notification::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
\core\notification::add($message, $messagetype);
|
||||||
|
|
||||||
|
// Update and return the result array in one place.
|
||||||
|
return [
|
||||||
|
'result' => $providerresult,
|
||||||
|
'message' => $message,
|
||||||
|
'messagetype' => $messagetype,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate content return value.
|
||||||
|
*
|
||||||
|
* @since Moodel 5.0
|
||||||
|
* @return external_single_structure
|
||||||
|
*/
|
||||||
|
public static function execute_returns(): external_single_structure {
|
||||||
|
return new external_single_structure(
|
||||||
|
[
|
||||||
|
'result' => new external_value(PARAM_BOOL, 'Whether the status was changed, true or false'),
|
||||||
|
'message' => new external_value(PARAM_TEXT, 'Messages'),
|
||||||
|
'messagetype' => new external_value(PARAM_TEXT, 'Message type'),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
ai/classes/external/set_action.php
vendored
32
ai/classes/external/set_action.php
vendored
|
@ -37,9 +37,9 @@ class set_action extends external_api {
|
||||||
*/
|
*/
|
||||||
public static function execute_parameters(): external_function_parameters {
|
public static function execute_parameters(): external_function_parameters {
|
||||||
return new external_function_parameters([
|
return new external_function_parameters([
|
||||||
'plugin' => new external_value(
|
'pluginaction' => new external_value(
|
||||||
PARAM_TEXT,
|
PARAM_TEXT,
|
||||||
'The name of the plugin',
|
'The name of the plugin and the action to change state for',
|
||||||
VALUE_REQUIRED,
|
VALUE_REQUIRED,
|
||||||
),
|
),
|
||||||
'state' => new external_value(
|
'state' => new external_value(
|
||||||
|
@ -47,34 +47,44 @@ class set_action extends external_api {
|
||||||
'The target state',
|
'The target state',
|
||||||
VALUE_REQUIRED,
|
VALUE_REQUIRED,
|
||||||
),
|
),
|
||||||
|
'providerid' => new external_value(
|
||||||
|
PARAM_INT,
|
||||||
|
'The provider id',
|
||||||
|
VALUE_DEFAULT,
|
||||||
|
0
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the providers action state.
|
* Set the providers action state.
|
||||||
*
|
*
|
||||||
* @param string $plugin The name of the plugin.
|
* @param string $pluginaction The name of the plugin and the action to change state for.
|
||||||
* @param int $state The target state.
|
* @param int $state The target state.
|
||||||
|
* @param int $providerid The provider id.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function execute(
|
public static function execute(
|
||||||
string $plugin,
|
string $pluginaction,
|
||||||
int $state,
|
int $state,
|
||||||
|
int $providerid = 0
|
||||||
): array {
|
): array {
|
||||||
// Parameter validation.
|
// Parameter validation.
|
||||||
[
|
[
|
||||||
'plugin' => $plugin,
|
'pluginaction' => $pluginaction,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
|
'providerid' => $providerid,
|
||||||
] = self::validate_parameters(self::execute_parameters(), [
|
] = self::validate_parameters(self::execute_parameters(), [
|
||||||
'plugin' => $plugin,
|
'pluginaction' => $pluginaction,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
|
'providerid' => $providerid,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$context = system::instance();
|
$context = system::instance();
|
||||||
self::validate_context($context);
|
self::validate_context($context);
|
||||||
require_capability('moodle/site:config', $context);
|
require_capability('moodle/site:config', $context);
|
||||||
|
|
||||||
[$plugin, $action] = explode('-', $plugin);
|
[$plugin, $action] = explode('-', $pluginaction);
|
||||||
$actionname = get_string("action_$action", 'core_ai');
|
$actionname = get_string("action_$action", 'core_ai');
|
||||||
|
|
||||||
if (!empty($state)) {
|
if (!empty($state)) {
|
||||||
|
@ -89,7 +99,13 @@ class set_action extends external_api {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
manager::set_action_state($plugin, $action, $state);
|
$manager = \core\di::get(manager::class);
|
||||||
|
$manager->set_action_state(
|
||||||
|
plugin: $plugin,
|
||||||
|
actionbasename: $action,
|
||||||
|
enabled: $state,
|
||||||
|
instanceid: $providerid
|
||||||
|
);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
6
ai/classes/external/set_policy_status.php
vendored
6
ai/classes/external/set_policy_status.php
vendored
|
@ -32,7 +32,7 @@ class set_policy_status extends external_api {
|
||||||
/**
|
/**
|
||||||
* Set policy parameters.
|
* Set policy parameters.
|
||||||
*
|
*
|
||||||
* @since Moodle 4.5
|
* @since Moodel 5.0
|
||||||
* @return external_function_parameters
|
* @return external_function_parameters
|
||||||
*/
|
*/
|
||||||
public static function execute_parameters(): external_function_parameters {
|
public static function execute_parameters(): external_function_parameters {
|
||||||
|
@ -48,7 +48,7 @@ class set_policy_status extends external_api {
|
||||||
/**
|
/**
|
||||||
* Set a users AI policy acceptance.
|
* Set a users AI policy acceptance.
|
||||||
*
|
*
|
||||||
* @since Moodle 4.5
|
* @since Moodel 5.0
|
||||||
* @param int $contextid The context ID.
|
* @param int $contextid The context ID.
|
||||||
* @return array The generated content.
|
* @return array The generated content.
|
||||||
*/
|
*/
|
||||||
|
@ -75,7 +75,7 @@ class set_policy_status extends external_api {
|
||||||
/**
|
/**
|
||||||
* Generate content return value.
|
* Generate content return value.
|
||||||
*
|
*
|
||||||
* @since Moodle 4.5
|
* @since Moodel 5.0
|
||||||
* @return external_function_parameters
|
* @return external_function_parameters
|
||||||
*/
|
*/
|
||||||
public static function execute_returns(): external_function_parameters {
|
public static function execute_returns(): external_function_parameters {
|
||||||
|
|
120
ai/classes/external/set_provider_status.php
vendored
Normal file
120
ai/classes/external/set_provider_status.php
vendored
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
namespace core_ai\external;
|
||||||
|
|
||||||
|
use context_system;
|
||||||
|
use core_external\external_api;
|
||||||
|
use core_external\external_function_parameters;
|
||||||
|
use core_external\external_single_structure;
|
||||||
|
use core_external\external_value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webservice to enable or disable AI provider.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class set_provider_status extends external_api {
|
||||||
|
/**
|
||||||
|
* Set provider status parameters.
|
||||||
|
*
|
||||||
|
* @since Moodle 4.5
|
||||||
|
* @return external_function_parameters
|
||||||
|
*/
|
||||||
|
public static function execute_parameters(): external_function_parameters {
|
||||||
|
return new external_function_parameters([
|
||||||
|
'plugin' => new external_value(PARAM_INT, 'Provider ID', VALUE_REQUIRED),
|
||||||
|
'state' => new external_value(PARAM_INT, 'Enabled or disabled', VALUE_REQUIRED),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a provider status.
|
||||||
|
*
|
||||||
|
* @since Moodle 4.5
|
||||||
|
* @param int $plugin The provider ID.
|
||||||
|
* @param int $state The state of the provider.
|
||||||
|
* @return array The generated content.
|
||||||
|
*/
|
||||||
|
public static function execute(int $plugin, int $state): array {
|
||||||
|
// Parameter validation.
|
||||||
|
[
|
||||||
|
'plugin' => $providerid,
|
||||||
|
'state' => $state,
|
||||||
|
] = self::validate_parameters(self::execute_parameters(), [
|
||||||
|
'plugin' => $plugin,
|
||||||
|
'state' => $state,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$context = context_system::instance();
|
||||||
|
self::validate_context($context);
|
||||||
|
require_capability('moodle/site:config', $context);
|
||||||
|
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$aiproviders = $manager->get_provider_instances(['id' => $providerid]);
|
||||||
|
$aiprovider = reset($aiproviders);
|
||||||
|
|
||||||
|
if (!$aiprovider) {
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'message' => get_string('notfound', 'error'),
|
||||||
|
'messagetype' => \core\notification::ERROR,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($state)) {
|
||||||
|
$manager->enable_provider_instance(provider: $aiprovider);
|
||||||
|
$message = get_string('plugin_enabled', 'core_admin', $aiprovider->name);
|
||||||
|
$messagetype = \core\notification::SUCCESS;
|
||||||
|
} else {
|
||||||
|
$providerresult = $manager->disable_provider_instance(provider: $aiprovider);
|
||||||
|
if ($providerresult->enabled) {
|
||||||
|
$message = get_string('providerinstancedisablefailed', 'core_ai');
|
||||||
|
$messagetype = \core\notification::ERROR;
|
||||||
|
} else {
|
||||||
|
$message = get_string('plugin_disabled', 'core_admin', $aiprovider->name);
|
||||||
|
$messagetype = \core\notification::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\core\notification::add($message, $messagetype);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => $messagetype === \core\notification::SUCCESS ? true : false,
|
||||||
|
'message' => $message,
|
||||||
|
'messagetype' => $messagetype,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate content return value.
|
||||||
|
*
|
||||||
|
* @since Moodle 4.5
|
||||||
|
* @return external_single_structure
|
||||||
|
*/
|
||||||
|
public static function execute_returns(): external_single_structure {
|
||||||
|
return new external_single_structure(
|
||||||
|
[
|
||||||
|
'result' => new external_value(PARAM_BOOL, 'Whether the status was changed, true or false'),
|
||||||
|
'message' => new external_value(PARAM_TEXT, 'Messages'),
|
||||||
|
'messagetype' => new external_value(PARAM_TEXT, 'Message type'),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
57
ai/classes/form/action_settings_form.php
Normal file
57
ai/classes/form/action_settings_form.php
Normal file
|
@ -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\form;
|
||||||
|
|
||||||
|
use moodleform;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
require_once($CFG->libdir . '/formslib.php');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate action settings form.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class action_settings_form extends moodleform {
|
||||||
|
#[\Override]
|
||||||
|
protected function definition() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default values for the form.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_defaults(): array {
|
||||||
|
$data = $this->_form->exportValues();
|
||||||
|
unset(
|
||||||
|
$data['sesskey'], // We do not need to return sesskey.
|
||||||
|
$data['_qf__'.$this->_formname], // We do not need the submission marker.
|
||||||
|
$data['provider'], // We do not need the provider.
|
||||||
|
$data['providerid'], // We do not need the provider id.
|
||||||
|
$data['action'] // We do not need the action.
|
||||||
|
);
|
||||||
|
if (empty($data)) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
ai/classes/form/ai_provider_form.php
Normal file
170
ai/classes/form/ai_provider_form.php
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
<?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\form;
|
||||||
|
|
||||||
|
use moodleform;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
require_once($CFG->libdir . '/formslib.php');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI provider instance form.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class ai_provider_form extends moodleform {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the custom data.
|
||||||
|
*
|
||||||
|
* @return mixed The custom data.
|
||||||
|
*/
|
||||||
|
public function get_customdata(): mixed {
|
||||||
|
return $this->_customdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[\Override]
|
||||||
|
protected function definition() {
|
||||||
|
global $PAGE;
|
||||||
|
$PAGE->requires->js_call_amd('core_ai/providerchooser', 'init');
|
||||||
|
|
||||||
|
$mform = $this->_form;
|
||||||
|
$providerconfigs = $this->_customdata['providerconfigs'] ?? [];
|
||||||
|
$returnurl = $this->_customdata['returnurl'] ?? null;
|
||||||
|
|
||||||
|
// AI provider chooser.
|
||||||
|
// Get all enabled AI provider plugins. Users can select one of them to create a new AI provider instance.
|
||||||
|
$providerplugins = [];
|
||||||
|
$enabledproviderplugins = \core\plugininfo\aiprovider::get_enabled_plugins();
|
||||||
|
foreach ($enabledproviderplugins as $pluginname => $notusing) {
|
||||||
|
$plugin = 'aiprovider_' . $pluginname;
|
||||||
|
$providerplugins[$plugin] = get_string('pluginname', $plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider chooser.
|
||||||
|
$mform->addElement(
|
||||||
|
'select',
|
||||||
|
'aiprovider',
|
||||||
|
get_string('providertype', 'core_ai'),
|
||||||
|
$providerplugins,
|
||||||
|
['data-aiproviderchooser-field' => 'selector'],
|
||||||
|
);
|
||||||
|
$mform->setDefault('aiprovider', 'aiprovider_openai');
|
||||||
|
if (isset($providerconfigs['id'])) {
|
||||||
|
$mform->hardFreeze('aiprovider');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider instance name.
|
||||||
|
$mform->addElement(
|
||||||
|
'text',
|
||||||
|
'name',
|
||||||
|
get_string('providername', 'core_ai'),
|
||||||
|
'maxlength="255" size="20"',
|
||||||
|
);
|
||||||
|
$mform->setType('name', PARAM_TEXT);
|
||||||
|
$mform->addRule('name', get_string('required'), 'required', null, 'client');
|
||||||
|
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255);
|
||||||
|
|
||||||
|
$mform->registerNoSubmitButton('updateaiprovider');
|
||||||
|
$mform->addElement(
|
||||||
|
'submit',
|
||||||
|
'updateaiprovider',
|
||||||
|
'update AI provider',
|
||||||
|
['data-aiproviderchooser-field' => 'updateButton', 'class' => 'd-none']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dispatch a hook for plugins to add their fields.
|
||||||
|
$hook = new \core_ai\hook\after_ai_provider_form_hook(
|
||||||
|
mform: $mform,
|
||||||
|
plugin: $providerconfigs['aiprovider'] ?? 'aiprovider_openai',
|
||||||
|
);
|
||||||
|
\core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||||
|
|
||||||
|
// Add the provider plugin name to form customdata.
|
||||||
|
$this->_customdata['aiprovider'] = $hook->plugin;
|
||||||
|
|
||||||
|
// Add rate limiting settings.
|
||||||
|
// Setting to enable/disable global rate limiting.
|
||||||
|
$mform->addElement(
|
||||||
|
'checkbox',
|
||||||
|
'enableglobalratelimit',
|
||||||
|
get_string('enableglobalratelimit', 'core_ai'),
|
||||||
|
get_string('enableglobalratelimit_help', 'core_ai'),
|
||||||
|
);
|
||||||
|
// Setting to set how many requests per hour are allowed for the global rate limit.
|
||||||
|
// Should only be enabled when global rate limiting is enabled.
|
||||||
|
$mform->addElement(
|
||||||
|
'text',
|
||||||
|
'globalratelimit',
|
||||||
|
get_string('globalratelimit', 'core_ai'),
|
||||||
|
'maxlength="10" size="4"',
|
||||||
|
);
|
||||||
|
$mform->setType('globalratelimit', PARAM_INT);
|
||||||
|
$mform->addHelpButton('globalratelimit', 'globalratelimit', 'core_ai');
|
||||||
|
$mform->hideIf('globalratelimit', 'enableglobalratelimit', 'notchecked');
|
||||||
|
|
||||||
|
// Setting to enable/disable user rate limiting.
|
||||||
|
$mform->addElement(
|
||||||
|
'checkbox',
|
||||||
|
'enableuserratelimit',
|
||||||
|
get_string('enableuserratelimit', 'core_ai'),
|
||||||
|
get_string('enableuserratelimit_help', 'core_ai'),
|
||||||
|
);
|
||||||
|
// Setting to set how many requests per hour are allowed for the user rate limit.
|
||||||
|
// Should only be enabled when user rate limiting is enabled.
|
||||||
|
$mform->addElement(
|
||||||
|
'text',
|
||||||
|
'userratelimit',
|
||||||
|
get_string('userratelimit', 'core_ai'),
|
||||||
|
'maxlength="10" size="4"',
|
||||||
|
);
|
||||||
|
$mform->setType('userratelimit', PARAM_INT);
|
||||||
|
$mform->addHelpButton('userratelimit', 'userratelimit', 'core_ai');
|
||||||
|
$mform->hideIf('userratelimit', 'enableuserratelimit', 'notchecked');
|
||||||
|
|
||||||
|
// Form buttons.
|
||||||
|
$buttonarray = [];
|
||||||
|
// If provider config is empty this is a new instance.
|
||||||
|
if (empty($providerconfigs['id'])) {
|
||||||
|
$buttonarray[] = $mform->createElement('submit', 'createandreturn',
|
||||||
|
get_string('btninstancecreate', 'core_ai'));
|
||||||
|
} else {
|
||||||
|
// We're updating an existing provider.
|
||||||
|
$buttonarray[] = $mform->createElement('submit', 'updateandreturn',
|
||||||
|
get_string('btninstanceupdate', 'core_ai'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$buttonarray[] = $mform->createElement('cancel');
|
||||||
|
$mform->addGroup($buttonarray, 'buttonar', '', [' '], false);
|
||||||
|
$mform->closeHeaderBefore('buttonar');
|
||||||
|
|
||||||
|
if ($returnurl) {
|
||||||
|
$mform->addElement('hidden', 'returnurl', $returnurl);
|
||||||
|
$mform->setType('returnurl', PARAM_LOCALURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($providerconfigs['id'])) {
|
||||||
|
$mform->addElement('hidden', 'id', $providerconfigs['id']);
|
||||||
|
$mform->setType('id', PARAM_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->set_data($providerconfigs);
|
||||||
|
}
|
||||||
|
}
|
48
ai/classes/hook/after_ai_provider_form_hook.php
Normal file
48
ai/classes/hook/after_ai_provider_form_hook.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?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\hook;
|
||||||
|
|
||||||
|
use MoodleQuickForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook after AI provider setup form is initiated.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
* @property-read MoodleQuickForm $mform The form element
|
||||||
|
* @property-read ?string $plugin Name of the plugin
|
||||||
|
*/
|
||||||
|
#[\core\attribute\label('Allows plugins to add form elements for ai provider setup.')]
|
||||||
|
#[\core\attribute\tags('ai')]
|
||||||
|
class after_ai_provider_form_hook {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the hook.
|
||||||
|
*
|
||||||
|
* @param MoodleQuickForm $mform The moodle form instance.
|
||||||
|
* @param ?string $plugin The name of the plugin
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
/** @var MoodleQuickForm The moodle form instance. */
|
||||||
|
public readonly MoodleQuickForm $mform,
|
||||||
|
|
||||||
|
/** @var ?string The name of the plugin */
|
||||||
|
public readonly ?string $plugin,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
47
ai/classes/hook/before_provider_deleted.php
Normal file
47
ai/classes/hook/before_provider_deleted.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
namespace core_ai\hook;
|
||||||
|
|
||||||
|
use core_ai\provider;
|
||||||
|
use core\hook\stoppable_trait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook before AI provider is deleted.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
* @property-read provider $provider The provider instance
|
||||||
|
*/
|
||||||
|
#[\core\attribute\label('Allows plugins to check the usage of an ai provider before deleting the instance record.')]
|
||||||
|
#[\core\attribute\tags('ai')]
|
||||||
|
class before_provider_deleted implements
|
||||||
|
\Psr\EventDispatcher\StoppableEventInterface {
|
||||||
|
|
||||||
|
use stoppable_trait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the hook.
|
||||||
|
*
|
||||||
|
* @param provider $provider The provider instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
/** @var provider The provider instance. */
|
||||||
|
public readonly provider $provider,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
48
ai/classes/hook/before_provider_disabled.php
Normal file
48
ai/classes/hook/before_provider_disabled.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
namespace core_ai\hook;
|
||||||
|
|
||||||
|
use core_ai\provider;
|
||||||
|
use core\hook\stoppable_trait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook before ai provider is disabled.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
* @property-read provider $provider The provider instance
|
||||||
|
*/
|
||||||
|
#[\core\attribute\label('Allows plugins or features to check the usage of an AI provider before disabling the provider.')]
|
||||||
|
#[\core\attribute\tags('ai')]
|
||||||
|
class before_provider_disabled implements
|
||||||
|
\Psr\EventDispatcher\StoppableEventInterface {
|
||||||
|
|
||||||
|
use stoppable_trait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the hook.
|
||||||
|
*
|
||||||
|
* @param provider $provider The provider instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
/** @var provider The provider instance. */
|
||||||
|
public readonly provider $provider,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,17 @@ use core_ai\aiactions\responses;
|
||||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
class manager {
|
class manager {
|
||||||
|
/**
|
||||||
|
* Create a new AI manager.
|
||||||
|
*
|
||||||
|
* @param \moodle_database $db
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
/** @var \moodle_database The database instance */
|
||||||
|
protected readonly \moodle_database $db,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get communication provider class name from the plugin name.
|
* Get communication provider class name from the plugin name.
|
||||||
*
|
*
|
||||||
|
@ -54,12 +65,11 @@ class manager {
|
||||||
*/
|
*/
|
||||||
public static function get_supported_actions(string $pluginname): array {
|
public static function get_supported_actions(string $pluginname): array {
|
||||||
$pluginclassname = static::get_ai_plugin_classname($pluginname);
|
$pluginclassname = static::get_ai_plugin_classname($pluginname);
|
||||||
$plugin = new $pluginclassname();
|
return $pluginclassname::get_action_list();
|
||||||
return $plugin->get_action_list();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a list of actions get the provider plugins that support them.
|
* Given a list of actions get the provider instances that support them.
|
||||||
*
|
*
|
||||||
* Will return an array of arrays, indexed by action name.
|
* Will return an array of arrays, indexed by action name.
|
||||||
*
|
*
|
||||||
|
@ -67,21 +77,20 @@ class manager {
|
||||||
* @param bool $enabledonly If true, only return enabled providers.
|
* @param bool $enabledonly If true, only return enabled providers.
|
||||||
* @return array An array of provider instances indexed by action name.
|
* @return array An array of provider instances indexed by action name.
|
||||||
*/
|
*/
|
||||||
public static function get_providers_for_actions(array $actions, bool $enabledonly = false): array {
|
public function get_providers_for_actions(array $actions, bool $enabledonly = false): array {
|
||||||
$providers = [];
|
$providers = [];
|
||||||
$plugins = \core_plugin_manager::instance()->get_plugins_of_type('aiprovider');
|
$instances = $this->get_provider_instances();
|
||||||
foreach ($actions as $action) {
|
foreach ($actions as $action) {
|
||||||
$providers[$action] = [];
|
$providers[$action] = [];
|
||||||
foreach ($plugins as $plugin) {
|
foreach ($instances as $instance) {
|
||||||
$pluginclassname = static::get_ai_plugin_classname($plugin->component);
|
|
||||||
$provider = new $pluginclassname();
|
|
||||||
// Check the plugin is enabled and the provider is configured before making the action available.
|
// Check the plugin is enabled and the provider is configured before making the action available.
|
||||||
if ($enabledonly && (!$plugin->is_enabled() || !static::is_action_enabled($plugin->component, $action)) ||
|
if ($enabledonly && (!$instance->enabled
|
||||||
$enabledonly && !$provider->is_provider_configured()) {
|
|| !$this->is_action_enabled($instance->provider, $action, $instance->id))
|
||||||
|
|| $enabledonly && !$instance->is_provider_configured()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (in_array($action, $provider->get_action_list())) {
|
if (in_array($action, $instance->get_action_list())) {
|
||||||
$providers[$action][] = $provider;
|
$providers[$action][] = $instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +129,7 @@ class manager {
|
||||||
$responseclassname = 'core_ai\\aiactions\\responses\\response_' . $action->get_basename();
|
$responseclassname = 'core_ai\\aiactions\\responses\\response_' . $action->get_basename();
|
||||||
|
|
||||||
// Get the providers that support the action.
|
// Get the providers that support the action.
|
||||||
$providers = self::get_providers_for_actions([$actionname], true);
|
$providers = $this->get_providers_for_actions([$actionname], true);
|
||||||
|
|
||||||
// Loop through the providers and process the action.
|
// Loop through the providers and process the action.
|
||||||
foreach ($providers[$actionname] as $provider) {
|
foreach ($providers[$actionname] as $provider) {
|
||||||
|
@ -238,19 +247,70 @@ class manager {
|
||||||
* @param string $plugin The name of the plugin.
|
* @param string $plugin The name of the plugin.
|
||||||
* @param string $actionbasename The action to be set.
|
* @param string $actionbasename The action to be set.
|
||||||
* @param int $enabled The state to be set (e.g., enabled or disabled).
|
* @param int $enabled The state to be set (e.g., enabled or disabled).
|
||||||
|
* @param int $instanceid The instance id of the instance.
|
||||||
* @return bool Returns true if the configuration was successfully set, false otherwise.
|
* @return bool Returns true if the configuration was successfully set, false otherwise.
|
||||||
*/
|
*/
|
||||||
public static function set_action_state(string $plugin, string $actionbasename, int $enabled): bool {
|
public function set_action_state(
|
||||||
|
string $plugin,
|
||||||
|
string $actionbasename,
|
||||||
|
int $enabled,
|
||||||
|
int $instanceid = 0
|
||||||
|
): bool {
|
||||||
$actionclass = 'core_ai\\aiactions\\' . $actionbasename;
|
$actionclass = 'core_ai\\aiactions\\' . $actionbasename;
|
||||||
$oldvalue = static::is_action_enabled($plugin, $actionclass);
|
$oldvalue = $this->is_action_enabled($plugin, $actionclass, $instanceid);
|
||||||
// Only set value if there is no config setting or if the value is different from the previous one.
|
|
||||||
if ($oldvalue !== $enabled) {
|
// Check if we are setting an action for a provider or placement.
|
||||||
set_config($actionbasename, $enabled, $plugin);
|
if (str_contains($plugin, 'aiprovider')) {
|
||||||
add_to_config_log('disabled', !$oldvalue, !$enabled, $plugin);
|
// Handle provider actions.
|
||||||
\core_plugin_manager::reset_caches();
|
$providers = $this->get_provider_instances(['id' => $instanceid]);
|
||||||
return true;
|
$provider = reset($providers);
|
||||||
|
|
||||||
|
// Update the enabled state of the action.
|
||||||
|
$actionconfig = $provider->actionconfig;
|
||||||
|
$actionconfig[$actionclass]['enabled'] = (bool)$enabled;
|
||||||
|
|
||||||
|
return $this->update_provider_instance(
|
||||||
|
provider: $provider,
|
||||||
|
actionconfig: $actionconfig)->actionconfig[$actionclass]['enabled'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Handle placement actions.
|
||||||
|
// Only set value if there is no config setting or if the value is different from the previous one.
|
||||||
|
if ($oldvalue !== (bool)$enabled) {
|
||||||
|
set_config($actionbasename, $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 provider.
|
||||||
|
*
|
||||||
|
* @param string $plugin The name of the plugin.
|
||||||
|
* @param string $actionclass The fully qualified action class name to be checked.
|
||||||
|
* @param int $instanceid The instance id of the plugin.
|
||||||
|
* @return bool Returns the configuration value of the action for the given plugin.
|
||||||
|
*/
|
||||||
|
private function is_provider_action_enabled(string $plugin, string $actionclass, int $instanceid): bool {
|
||||||
|
// If there is no instance id, we are checking the provider itself.
|
||||||
|
// So get the defaults.
|
||||||
|
if ($instanceid === 0) {
|
||||||
|
// Get the defaults for this provider type.
|
||||||
|
$classname = "\\{$plugin}\\provider";
|
||||||
|
$defaultconfig = $classname::initialise_action_settings();
|
||||||
|
|
||||||
|
// Return the default value.
|
||||||
|
return $defaultconfig[$actionclass]['enabled'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Get the provider instance.
|
||||||
|
$providers = $this->get_provider_instances(['id' => $instanceid]);
|
||||||
|
$provider = reset($providers);
|
||||||
|
return $provider->actionconfig[$actionclass]['enabled'];
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -258,15 +318,22 @@ class manager {
|
||||||
*
|
*
|
||||||
* @param string $plugin The name of the plugin.
|
* @param string $plugin The name of the plugin.
|
||||||
* @param string $actionclass The fully qualified action class name to be checked.
|
* @param string $actionclass The fully qualified action class name to be checked.
|
||||||
* @return mixed Returns the configuration value of the action for the given plugin.
|
* @param int $instanceid The instance id of the plugin.
|
||||||
|
* @return bool Returns the configuration value of the action for the given plugin.
|
||||||
*/
|
*/
|
||||||
public static function is_action_enabled(string $plugin, string $actionclass): bool {
|
public function is_action_enabled(string $plugin, string $actionclass, int $instanceid = 0): bool {
|
||||||
$value = get_config($plugin, $actionclass::get_basename());
|
if (str_contains($plugin, 'aiprovider')) {
|
||||||
// If not exist in DB, set it to true (enabled).
|
// Handle provider actions.
|
||||||
if ($value === false) {
|
return $this->is_provider_action_enabled($plugin, $actionclass, $instanceid);
|
||||||
return true;
|
} else {
|
||||||
|
// Handle placement actions.
|
||||||
|
$value = get_config($plugin, $actionclass::get_basename());
|
||||||
|
// If not exist in DB, set it to true (enabled).
|
||||||
|
if ($value === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (bool) $value;
|
||||||
}
|
}
|
||||||
return (bool) $value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -276,14 +343,14 @@ class manager {
|
||||||
* @param string $actionclass The fully qualified action class name to be checked.
|
* @param string $actionclass The fully qualified action class name to be checked.
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function is_action_available(string $actionclass): bool {
|
public function is_action_available(string $actionclass): bool {
|
||||||
$providers = self::get_providers_for_actions([$actionclass], true);
|
$providers = $this->get_providers_for_actions([$actionclass], true);
|
||||||
// Check if the requested action is enabled for at least one provider.
|
// Check if the requested action is enabled for at least one provider.
|
||||||
foreach ($providers as $provideractions) {
|
foreach ($providers as $provideractions) {
|
||||||
foreach ($provideractions as $provider) {
|
foreach ($provideractions as $provider) {
|
||||||
$classnamearray = explode('\\', $provider::class);
|
$classnamearray = explode('\\', $provider::class);
|
||||||
$pluginname = reset($classnamearray);
|
$pluginname = reset($classnamearray);
|
||||||
if (self::is_action_enabled($pluginname, $actionclass)) {
|
if ($this->is_action_enabled($pluginname, $actionclass)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,4 +358,169 @@ class manager {
|
||||||
// There are no providers with this action enabled.
|
// There are no providers with this action enabled.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new provider instance.
|
||||||
|
*
|
||||||
|
* @param string $classname Classname of the provider.
|
||||||
|
* @param string $name The name of the provider config.
|
||||||
|
* @param bool $enabled The enabled state of the provider.
|
||||||
|
* @param array|null $config The config json.
|
||||||
|
* @return provider
|
||||||
|
*/
|
||||||
|
public function create_provider_instance(
|
||||||
|
string $classname,
|
||||||
|
string $name,
|
||||||
|
bool $enabled = false,
|
||||||
|
?array $config = null,
|
||||||
|
): provider {
|
||||||
|
if (!class_exists($classname) || !is_a($classname, provider::class, true)) {
|
||||||
|
throw new \coding_exception("Provider class not valid: {$classname}");
|
||||||
|
}
|
||||||
|
$provider = new $classname(
|
||||||
|
enabled: $enabled,
|
||||||
|
name: $name,
|
||||||
|
config: $config ? json_encode($config) : '',
|
||||||
|
);
|
||||||
|
|
||||||
|
$id = $this->db->insert_record('ai_providers', $provider->to_record());
|
||||||
|
|
||||||
|
return $provider->with(id: $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the provider records according to the filter.
|
||||||
|
*
|
||||||
|
* @param array|null $filter The filterable elements to get the records from.
|
||||||
|
* @return array
|
||||||
|
* @throws \dml_exception
|
||||||
|
*/
|
||||||
|
public function get_provider_records(?array $filter = null): array {
|
||||||
|
return $this->db->get_records(
|
||||||
|
table: 'ai_providers',
|
||||||
|
conditions: $filter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of all provider instances.
|
||||||
|
*
|
||||||
|
* This method retrieves provider records from the database, attempts to instantiate
|
||||||
|
* each provider class, and returns an array of provider instances. It filters out
|
||||||
|
* any records where the provider class does not exist.
|
||||||
|
*
|
||||||
|
* @param null|array $filter The database filter to apply when fetching provider records.
|
||||||
|
* @return array An array of instantiated provider objects.
|
||||||
|
* @throws \dml_exception If there is a database error during record retrieval.
|
||||||
|
*/
|
||||||
|
public function get_provider_instances(?array $filter = null): array {
|
||||||
|
// Filter out any null values from the array (providers that couldn't be instantiated).
|
||||||
|
return array_filter(
|
||||||
|
// Apply a callback function to each provider record to instantiate the provider.
|
||||||
|
array_map(
|
||||||
|
function ($record): ?provider {
|
||||||
|
// Check if the provider class specified in the record exists.
|
||||||
|
if (!class_exists($record->provider)) {
|
||||||
|
// Log a debugging message if the provider class is not found.
|
||||||
|
debugging(
|
||||||
|
"Unable to find a provider class for {$record->provider}",
|
||||||
|
DEBUG_DEVELOPER,
|
||||||
|
);
|
||||||
|
// Return null to indicate that the provider could not be instantiated.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the provider class with the record's data.
|
||||||
|
return new $record->provider(
|
||||||
|
enabled: $record->enabled,
|
||||||
|
id: $record->id,
|
||||||
|
name: $record->name,
|
||||||
|
config: $record->config,
|
||||||
|
actionconfig: $record->actionconfig,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// Retrieve the provider records from the database with the optional filter.
|
||||||
|
$this->get_provider_records($filter),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update provider instance.
|
||||||
|
*
|
||||||
|
* @param provider $provider The provider instance.
|
||||||
|
* @param array|null $config the configuration of the provider instance to be updated.
|
||||||
|
* @param array|null $actionconfig the action configuration of the provider instance to be updated.
|
||||||
|
* @return provider
|
||||||
|
* @throws \dml_exception
|
||||||
|
*/
|
||||||
|
public function update_provider_instance(
|
||||||
|
provider $provider,
|
||||||
|
?array $config = null,
|
||||||
|
?array $actionconfig = null
|
||||||
|
): provider {
|
||||||
|
$provider = $provider->with(
|
||||||
|
name: $provider->name,
|
||||||
|
config: $config ?? $provider->config,
|
||||||
|
actionconfig: $actionconfig ?? $provider->actionconfig,
|
||||||
|
);
|
||||||
|
$this->db->update_record('ai_providers', $provider->to_record());
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the provider instance.
|
||||||
|
*
|
||||||
|
* @param provider $provider The provider instance.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete_provider_instance(provider $provider): bool {
|
||||||
|
// Dispatch the hook before deleting the record.
|
||||||
|
$hook = new \core_ai\hook\before_provider_deleted(
|
||||||
|
provider: $provider,
|
||||||
|
);
|
||||||
|
$hookmanager = \core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||||
|
if ($hookmanager->isPropagationStopped()) {
|
||||||
|
$deleted = false;
|
||||||
|
} else {
|
||||||
|
$deleted = $this->db->delete_records('ai_providers', ['id' => $provider->id]);
|
||||||
|
}
|
||||||
|
return $deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable a provider instance.
|
||||||
|
*
|
||||||
|
* @param provider $provider
|
||||||
|
* @return provider
|
||||||
|
*/
|
||||||
|
public function enable_provider_instance(provider $provider): provider {
|
||||||
|
if (!$provider->enabled) {
|
||||||
|
$provider = $provider->with(enabled: true);
|
||||||
|
$this->db->update_record('ai_providers', $provider->to_record());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable a provider.
|
||||||
|
*
|
||||||
|
* @param provider $provider
|
||||||
|
* @return provider
|
||||||
|
*/
|
||||||
|
public function disable_provider_instance(provider $provider): provider {
|
||||||
|
if ($provider->enabled) {
|
||||||
|
$hook = new \core_ai\hook\before_provider_disabled(
|
||||||
|
provider: $provider,
|
||||||
|
);
|
||||||
|
$hookmanager = \core\di::get(\core\hook\manager::class)->dispatch($hook);
|
||||||
|
if (!$hookmanager->isPropagationStopped()) {
|
||||||
|
$provider = $provider->with(enabled: false);
|
||||||
|
$this->db->update_record('ai_providers', $provider->to_record());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ abstract class placement {
|
||||||
*
|
*
|
||||||
* @return array An array of action class names.
|
* @return array An array of action class names.
|
||||||
*/
|
*/
|
||||||
abstract public function get_action_list(): array;
|
abstract public static function get_action_list(): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an action class name.
|
* Given an action class name.
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
namespace core_ai;
|
namespace core_ai;
|
||||||
|
|
||||||
|
use core_ai\form\action_settings_form;
|
||||||
|
use Spatie\Cloneable\Cloneable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class provider.
|
* Class provider.
|
||||||
*
|
*
|
||||||
|
@ -24,6 +27,45 @@ namespace core_ai;
|
||||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
abstract class provider {
|
abstract class provider {
|
||||||
|
use Cloneable;
|
||||||
|
|
||||||
|
/** @var string $provider The provider used to make this instance */
|
||||||
|
public readonly string $provider;
|
||||||
|
|
||||||
|
/** @var array The configuration for this instance. */
|
||||||
|
public readonly array $config;
|
||||||
|
|
||||||
|
/** @var array The action specific settings for this instance. */
|
||||||
|
public readonly array $actionconfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new provider.
|
||||||
|
*
|
||||||
|
* @param bool $enabled Whether the gateway is enabled
|
||||||
|
* @param string $name The name of the provider config.
|
||||||
|
* @param string $config The configuration for this instance.
|
||||||
|
* @param string $actionconfig The action specific settings for this instance.
|
||||||
|
* @param int|null $id The id of the provider in the database.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
/** @var bool Whether the gateway is enabled */
|
||||||
|
public readonly bool $enabled,
|
||||||
|
/** @var string The name of the provider config. */
|
||||||
|
public string $name,
|
||||||
|
string $config,
|
||||||
|
string $actionconfig = '',
|
||||||
|
/** @var null|int The ID of the provider in the database, or null if it has not been persisted yet. */
|
||||||
|
public readonly ?int $id = null,
|
||||||
|
) {
|
||||||
|
$this->provider = strstr(get_class($this), '\\', true);
|
||||||
|
$this->config = json_decode($config, true);
|
||||||
|
if ($actionconfig == '') {
|
||||||
|
$this->actionconfig = static::initialise_action_settings();
|
||||||
|
} else {
|
||||||
|
$this->actionconfig = json_decode($actionconfig, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the actions that this provider supports.
|
* Get the actions that this provider supports.
|
||||||
*
|
*
|
||||||
|
@ -31,7 +73,24 @@ abstract class provider {
|
||||||
*
|
*
|
||||||
* @return array An array of action class names.
|
* @return array An array of action class names.
|
||||||
*/
|
*/
|
||||||
abstract public function get_action_list(): array;
|
abstract public static function get_action_list(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the action settings array.
|
||||||
|
*
|
||||||
|
* @return array The initialised action settings.
|
||||||
|
*/
|
||||||
|
public static function initialise_action_settings(): array {
|
||||||
|
$actions = static::get_action_list();
|
||||||
|
$actionconfig = [];
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
$actionconfig[$action] = [
|
||||||
|
'enabled' => true,
|
||||||
|
'settings' => static::get_action_setting_defaults($action),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $actionconfig;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an action class name, return an array of sub actions
|
* Given an action class name, return an array of sub actions
|
||||||
|
@ -58,17 +117,23 @@ abstract class provider {
|
||||||
* Get any action settings for this provider.
|
* Get any action settings for this provider.
|
||||||
*
|
*
|
||||||
* @param string $action The action class name.
|
* @param string $action The action class name.
|
||||||
* @param \admin_root $ADMIN The admin root object.
|
* @param array $customdata The customdata for the form.
|
||||||
* @param string $section The section name.
|
* @return action_settings_form|bool The settings form for this action or false in no settings.
|
||||||
* @param bool $hassiteconfig Whether the current user has moodle/site:config capability.
|
|
||||||
* @return array An array of settings.
|
|
||||||
*/
|
*/
|
||||||
public function get_action_settings(
|
public static function get_action_settings(
|
||||||
string $action,
|
string $action,
|
||||||
\admin_root $ADMIN,
|
array $customdata = [],
|
||||||
string $section,
|
): action_settings_form|bool {
|
||||||
bool $hassiteconfig
|
return false;
|
||||||
): array {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default settings for an action.
|
||||||
|
*
|
||||||
|
* @param string $action The action class name.
|
||||||
|
* @return array The default settings for the action.
|
||||||
|
*/
|
||||||
|
public static function get_action_setting_defaults(string $action): array {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +143,41 @@ abstract class provider {
|
||||||
* @param aiactions\base $action The action to check.
|
* @param aiactions\base $action The action to check.
|
||||||
* @return array|bool True on success, array of error details on failure.
|
* @return array|bool True on success, array of error details on failure.
|
||||||
*/
|
*/
|
||||||
abstract public function is_request_allowed(aiactions\base $action): array|bool;
|
public function is_request_allowed(aiactions\base $action): array|bool {
|
||||||
|
$ratelimiter = \core\di::get(rate_limiter::class);
|
||||||
|
$component = \core\component::get_component_from_classname(get_class($this));
|
||||||
|
|
||||||
|
// Check the user rate limit.
|
||||||
|
if (isset($this->config['enableuserratelimit']) && $this->config['enableuserratelimit']) {
|
||||||
|
if (!$ratelimiter->check_user_rate_limit(
|
||||||
|
component: $component,
|
||||||
|
ratelimit: $this->config['userratelimit'],
|
||||||
|
userid: $action->get_configuration('userid')
|
||||||
|
)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'errorcode' => 429,
|
||||||
|
'errormessage' => 'User rate limit exceeded',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the global rate limit.
|
||||||
|
if (isset($this->config['enableglobalratelimit']) && $this->config['enableglobalratelimit']) {
|
||||||
|
if (!$ratelimiter->check_global_rate_limit(
|
||||||
|
component: $component,
|
||||||
|
ratelimit: $this->config['globalratelimit']
|
||||||
|
)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'errorcode' => 429,
|
||||||
|
'errormessage' => 'Global rate limit exceeded',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a provider has the minimal configuration to work.
|
* Check if a provider has the minimal configuration to work.
|
||||||
|
@ -88,4 +187,20 @@ abstract class provider {
|
||||||
public function is_provider_configured(): bool {
|
public function is_provider_configured(): bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert this object to a stdClass, suitable for saving to the database.
|
||||||
|
*
|
||||||
|
* @return \stdClass
|
||||||
|
*/
|
||||||
|
public function to_record(): \stdClass {
|
||||||
|
return (object) [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'provider' => get_class($this),
|
||||||
|
'enabled' => $this->enabled,
|
||||||
|
'config' => json_encode($this->config),
|
||||||
|
'actionconfig' => json_encode($this->actionconfig),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,9 @@ class aiplacement_action_management_table extends flexible_table implements dyna
|
||||||
/** @var array The list of actions this manager covers */
|
/** @var array The list of actions this manager covers */
|
||||||
protected array $actions;
|
protected array $actions;
|
||||||
|
|
||||||
|
/** @var \core_ai\manager The AI manager */
|
||||||
|
protected \core_ai\manager $manager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -54,6 +57,7 @@ class aiplacement_action_management_table extends flexible_table implements dyna
|
||||||
|
|
||||||
$this->setup_column_configuration();
|
$this->setup_column_configuration();
|
||||||
$this->set_filterset(new aiplacement_action_management_table_filterset());
|
$this->set_filterset(new aiplacement_action_management_table_filterset());
|
||||||
|
$this->manager = \core\di::get(manager::class);
|
||||||
$this->setup();
|
$this->setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +138,7 @@ class aiplacement_action_management_table extends flexible_table implements dyna
|
||||||
];
|
];
|
||||||
$output = $OUTPUT->render_from_template('core_admin/table/namedesc', $params);
|
$output = $OUTPUT->render_from_template('core_admin/table/namedesc', $params);
|
||||||
|
|
||||||
if (!manager::is_action_available($row->action)) {
|
if (!$this->manager->is_action_available($row->action)) {
|
||||||
$providerurl = new moodle_url('/admin/settings.php', ['section' => 'aiprovider']);
|
$providerurl = new moodle_url('/admin/settings.php', ['section' => 'aiprovider']);
|
||||||
$output .= $OUTPUT->render_from_template('core_ai/admin_noproviders', [
|
$output .= $OUTPUT->render_from_template('core_ai/admin_noproviders', [
|
||||||
'providerurl' => $providerurl->out(),
|
'providerurl' => $providerurl->out(),
|
||||||
|
@ -208,7 +212,7 @@ class aiplacement_action_management_table extends flexible_table implements dyna
|
||||||
// Construct the row data.
|
// Construct the row data.
|
||||||
$rowdata = (object) [
|
$rowdata = (object) [
|
||||||
'action' => $actionclass,
|
'action' => $actionclass,
|
||||||
'enabled' => manager::is_action_enabled($this->pluginname, $actionclass),
|
'enabled' => $this->manager->is_action_enabled($this->pluginname, $actionclass),
|
||||||
];
|
];
|
||||||
$this->add_data_keyed(
|
$this->add_data_keyed(
|
||||||
$this->format_row($rowdata),
|
$this->format_row($rowdata),
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
namespace core_ai\table;
|
namespace core_ai\table;
|
||||||
|
|
||||||
|
use core_ai\manager;
|
||||||
use core_table\dynamic as dynamic_table;
|
use core_table\dynamic as dynamic_table;
|
||||||
use flexible_table;
|
use flexible_table;
|
||||||
use moodle_url;
|
use moodle_url;
|
||||||
|
@ -32,26 +33,33 @@ class aiprovider_action_management_table extends flexible_table implements dynam
|
||||||
/** @var string The name of the plugin these actions related too */
|
/** @var string The name of the plugin these actions related too */
|
||||||
protected string $pluginname;
|
protected string $pluginname;
|
||||||
|
|
||||||
|
/** @var int the provider instance id */
|
||||||
|
protected int $providerid;
|
||||||
|
|
||||||
/** @var array The list of actions this manager covers */
|
/** @var array The list of actions this manager covers */
|
||||||
protected array $actions;
|
protected array $actions;
|
||||||
|
|
||||||
|
/** @var \core_ai\manager The AI manager */
|
||||||
|
protected \core_ai\manager $manager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param string $uniqueid The table unique id
|
* @param string $uniqueid The table unique id.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $uniqueid) {
|
public function __construct(string $uniqueid) {
|
||||||
$parseuniqueid = explode('-', $uniqueid);
|
$parseuniqueid = explode('-', $uniqueid);
|
||||||
$pluginname = end($parseuniqueid);
|
$this->pluginname = reset($parseuniqueid);
|
||||||
$this->pluginname = $pluginname;
|
$this->providerid = (int)end($parseuniqueid);
|
||||||
|
|
||||||
// Get the list of actions that this provider supports.
|
// Get the list of actions that this provider supports.
|
||||||
$this->actions = \core_ai\manager::get_supported_actions($this->pluginname);
|
$this->actions = \core_ai\manager::get_supported_actions($this->pluginname);
|
||||||
|
|
||||||
parent::__construct($this->get_table_id());
|
parent::__construct($uniqueid);
|
||||||
|
|
||||||
$this->setup_column_configuration();
|
$this->setup_column_configuration();
|
||||||
$this->set_filterset(new aiprovider_action_management_table_filterset());
|
$this->set_filterset(new aiprovider_action_management_table_filterset());
|
||||||
|
$this->manager = \core\di::get(manager::class);
|
||||||
$this->setup();
|
$this->setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +83,7 @@ class aiprovider_action_management_table extends flexible_table implements dynam
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function get_table_id(): string {
|
protected function get_table_id(): string {
|
||||||
return "aiprovideraction_management_table-{$this->pluginname}";
|
return "aiprovider_action_management_table-{$this->pluginname}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,14 +103,18 @@ class aiprovider_action_management_table extends flexible_table implements dynam
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function get_table_js_module(): string {
|
protected function get_table_js_module(): string {
|
||||||
return 'core_admin/plugin_management_table';
|
return 'core_ai/aiprovider_action_management_table';
|
||||||
}
|
}
|
||||||
|
|
||||||
#[\Override]
|
#[\Override]
|
||||||
protected function get_dynamic_table_html_end(): string {
|
protected function get_dynamic_table_html_end(): string {
|
||||||
global $PAGE;
|
global $PAGE;
|
||||||
|
|
||||||
$PAGE->requires->js_call_amd($this->get_table_js_module(), 'init');
|
$PAGE->requires->js_call_amd(
|
||||||
|
$this->get_table_js_module(),
|
||||||
|
'init',
|
||||||
|
[$this->providerid]
|
||||||
|
);
|
||||||
return parent::get_dynamic_table_html_end();
|
return parent::get_dynamic_table_html_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,15 +187,21 @@ class aiprovider_action_management_table extends flexible_table implements dynam
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function col_settings(stdClass $row): string {
|
protected function col_settings(stdClass $row): string {
|
||||||
global $CFG;
|
$providerclass = "\\$this->pluginname\\provider";
|
||||||
require_once($CFG->libdir . '/adminlib.php'); // Needed for the AJAX calls.
|
$mform = $providerclass::get_action_settings($row->action);
|
||||||
$tree = \admin_get_root();
|
|
||||||
$sectionname = $this->pluginname . '_' . $row->action::get_basename();
|
// Only show the per action settings options if we have a form to display.
|
||||||
$section = $tree->locate($sectionname);
|
if ($mform) {
|
||||||
if ($section) {
|
$urlparams = [
|
||||||
return \html_writer::link($section->get_settings_page_url(), get_string('settings'));
|
'provider' => $this->pluginname,
|
||||||
|
'action' => $row->action,
|
||||||
|
'providerid' => $this->providerid,
|
||||||
|
];
|
||||||
|
$url = new \moodle_url('/ai/configure_actions.php', $urlparams);
|
||||||
|
return \html_writer::link($url, get_string('settings'));
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,7 +236,10 @@ class aiprovider_action_management_table extends flexible_table implements dynam
|
||||||
// Construct the row data.
|
// Construct the row data.
|
||||||
$rowdata = (object) [
|
$rowdata = (object) [
|
||||||
'action' => $actionclass,
|
'action' => $actionclass,
|
||||||
'enabled' => \core_ai\manager::is_action_enabled($this->pluginname, $actionclass),
|
'enabled' => $this->manager->is_action_enabled(
|
||||||
|
plugin: $this->pluginname,
|
||||||
|
actionclass: $actionclass,
|
||||||
|
instanceid: $this->providerid),
|
||||||
];
|
];
|
||||||
$this->add_data_keyed(
|
$this->add_data_keyed(
|
||||||
$this->format_row($rowdata),
|
$this->format_row($rowdata),
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
namespace core_ai\table;
|
namespace core_ai\table;
|
||||||
|
|
||||||
|
use context_system;
|
||||||
|
use core_table\dynamic as dynamic_table;
|
||||||
|
use flexible_table;
|
||||||
use moodle_url;
|
use moodle_url;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,34 +28,281 @@ use moodle_url;
|
||||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
class aiprovider_management_table extends \core_admin\table\plugin_management_table {
|
class aiprovider_management_table extends flexible_table implements dynamic_table {
|
||||||
#[\Override]
|
/**
|
||||||
|
* @var array $aiproviders List of configured provider instances.
|
||||||
|
*/
|
||||||
|
protected array $aiproviders = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the AI provider table.
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct($this->get_table_id());
|
||||||
|
|
||||||
|
$this->aiproviders = $this->get_providers();
|
||||||
|
$this->setup_column_configuration();
|
||||||
|
$this->set_filterset(new aiprovider_management_table_filterset());
|
||||||
|
$this->setup();
|
||||||
|
$tableclasses = $this->attributes['class'] . ' ' . $this->get_table_id();
|
||||||
|
$this->set_attribute('class', $tableclasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the plugin type for the table.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @deprecated since 5.0
|
||||||
|
*/
|
||||||
|
#[\core\attribute\deprecated(replacement: null, since: '5.0', mdl: 'MDL-82977')]
|
||||||
protected function get_plugintype(): string {
|
protected function get_plugintype(): string {
|
||||||
|
\core\deprecation::emit_deprecation_if_present(__FUNCTION__);
|
||||||
return 'aiprovider';
|
return 'aiprovider';
|
||||||
}
|
}
|
||||||
|
|
||||||
#[\Override]
|
#[\Override]
|
||||||
|
public function get_context(): context_system {
|
||||||
|
return context_system::instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[\Override]
|
||||||
|
public function has_capability(): bool {
|
||||||
|
return has_capability('moodle/site:config', $this->get_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the table id for the table.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_table_id(): string {
|
||||||
|
return 'aiproviders_table';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the js module needed for the table.
|
||||||
|
*
|
||||||
|
* This module can include table specific ajax calls etc.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_table_js_module(): string {
|
||||||
|
return 'core_ai/aiprovider_instance_management_table';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webservice for toggle.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_toggle_service(): string {
|
||||||
|
return 'core_ai_set_provider_status';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webservice for delete.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_delete_service(): string {
|
||||||
|
return 'core_ai_delete_provider_instance';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the action URL for the table.
|
||||||
|
*
|
||||||
|
* @param array $params The params to pass to the URL.
|
||||||
|
* @return moodle_url
|
||||||
|
* @deprecated since 5.0
|
||||||
|
*/
|
||||||
|
#[\core\attribute\deprecated(replacement: null, since: '5.0', mdl: 'MDL-82977')]
|
||||||
protected function get_action_url(array $params = []): moodle_url {
|
protected function get_action_url(array $params = []): moodle_url {
|
||||||
|
\core\deprecation::emit_deprecation_if_present(__FUNCTION__);
|
||||||
return new moodle_url('/admin/ai.php', $params);
|
return new moodle_url('/admin/ai.php', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[\Override]
|
#[\Override]
|
||||||
|
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 the configured ai providers from the manager.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_providers(): array {
|
||||||
|
$providers = \core\di::get(\core_ai\manager::class)->get_provider_records();
|
||||||
|
if (!empty($providers)) {
|
||||||
|
\core_collator::asort_objects_by_property($providers, 'id');
|
||||||
|
}
|
||||||
|
return $providers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the column configs for the table.
|
||||||
|
*/
|
||||||
|
protected function setup_column_configuration(): void {
|
||||||
|
$columnlist = $this->get_column_list();
|
||||||
|
$this->define_columns(array_keys($columnlist));
|
||||||
|
$this->define_headers(array_values($columnlist));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[\Override]
|
||||||
|
public function guess_base_url(): void {
|
||||||
|
$this->define_baseurl(new moodle_url('/admin/ai.php'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the column list for the table.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
protected function get_column_list(): array {
|
protected function get_column_list(): array {
|
||||||
$columns = [
|
return [
|
||||||
'name' => get_string('provider', 'core_ai'),
|
'name' => get_string('name'),
|
||||||
|
'provider' => get_string('provider', 'core_ai'),
|
||||||
|
'enabled' => get_string('pluginenabled', 'core_plugin'),
|
||||||
|
'settings' => get_string('settings', 'core'),
|
||||||
|
'delete' => get_string('delete'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of the table.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function get_content(): string {
|
||||||
|
ob_start();
|
||||||
|
$this->out();
|
||||||
|
$content = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the row data for the table.
|
||||||
|
*/
|
||||||
|
public function out(): void {
|
||||||
|
foreach ($this->aiproviders as $provider) {
|
||||||
|
$rowdata = (object) [
|
||||||
|
'id' => $provider->id,
|
||||||
|
'name' => $provider->name,
|
||||||
|
'provider' => $provider->provider,
|
||||||
|
'enabled' => (int)$provider->enabled,
|
||||||
|
];
|
||||||
|
$this->add_data_keyed(
|
||||||
|
$this->format_row($rowdata),
|
||||||
|
$this->get_row_class($rowdata)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->finish_output(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the class for row whether is dimmed or not according to enabled or disabled.
|
||||||
|
*
|
||||||
|
* @param \stdClass $row The row object
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_row_class(\stdClass $row): string {
|
||||||
|
if ($row->enabled) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return 'dimmed_text';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column for the provider instance Name.
|
||||||
|
*
|
||||||
|
* @param \stdClass $row The row object
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_name(\stdClass $row): string {
|
||||||
|
return $row->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column for the provider plugin name.
|
||||||
|
*
|
||||||
|
* @param \stdClass $row The row object
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_provider(\stdClass $row): string {
|
||||||
|
$component = \core\component::get_component_from_classname($row->provider);
|
||||||
|
return get_string('pluginname', $component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column for enabled or disabled status of the provider instance.
|
||||||
|
*
|
||||||
|
* @param \stdClass $row The row object
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_enabled(\stdClass $row): string {
|
||||||
|
global $OUTPUT;
|
||||||
|
|
||||||
|
$enabled = $row->enabled;
|
||||||
|
if ($enabled) {
|
||||||
|
$labelstr = get_string('disableplugin', 'core_admin', $row->name);
|
||||||
|
} else {
|
||||||
|
$labelstr = get_string('enableplugin', 'core_admin', $row->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'id' => 'ai-provider-toggle-' . $row->id,
|
||||||
|
'checked' => $enabled,
|
||||||
|
'dataattributes' => [
|
||||||
|
'name' => 'id',
|
||||||
|
'value' => $row->provider,
|
||||||
|
'toggle-method' => $this->get_toggle_service(),
|
||||||
|
'action' => 'togglestate',
|
||||||
|
'plugin' => $row->id, // Set plugin attribute to provider ID.
|
||||||
|
'state' => $enabled ? 1 : 0,
|
||||||
|
],
|
||||||
|
'title' => $labelstr,
|
||||||
|
'label' => $labelstr,
|
||||||
|
'labelclasses' => 'sr-only',
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->supports_disabling()) {
|
return $OUTPUT->render_from_template('core_admin/setting_configtoggle', $params);
|
||||||
$columns['enabled'] = get_string('pluginenabled', 'core_plugin');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->supports_ordering()) {
|
/**
|
||||||
$columns['order'] = get_string('order', 'core');
|
* The column to show the settings of the provider instance.
|
||||||
}
|
*
|
||||||
|
* @param \stdClass $row The row object.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_settings(\stdClass $row): string {
|
||||||
|
$settingsurl = new moodle_url('/ai/configure.php', ['id' => $row->id]);
|
||||||
|
return \html_writer::link($settingsurl, get_string('settings'));
|
||||||
|
|
||||||
$columns['settings'] = get_string('settings', 'core');
|
}
|
||||||
$columns['uninstall'] = get_string('uninstallplugin', 'core_admin');
|
|
||||||
|
|
||||||
return $columns;
|
/**
|
||||||
|
* The column to show the delete action for the provider instance.
|
||||||
|
*
|
||||||
|
* @param \stdClass $row The row object.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function col_delete(\stdClass $row): string {
|
||||||
|
global $OUTPUT;
|
||||||
|
|
||||||
|
// Render the delete button from the template.
|
||||||
|
$component = \core\component::get_component_from_classname($row->provider);
|
||||||
|
$provider = get_string('pluginname', $component);
|
||||||
|
$params = [
|
||||||
|
'id' => $row->id,
|
||||||
|
'name' => $row->name,
|
||||||
|
'provider' => $provider,
|
||||||
|
'delete-method' => $this->get_delete_service(),
|
||||||
|
];
|
||||||
|
return $OUTPUT->render_from_template('core_ai/admin_delete_provider', $params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
ai/classes/table/aiprovider_management_table_filterset.php
Normal file
27
ai/classes/table/aiprovider_management_table_filterset.php
Normal file
|
@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Provider management table filter set.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class aiprovider_management_table_filterset extends \core_table\local\filter\filterset {
|
||||||
|
}
|
135
ai/configure.php
Normal file
135
ai/configure.php
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?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/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure provider instances.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('../config.php');
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
$context = context_system::instance();
|
||||||
|
require_capability('moodle/site:config', $context);
|
||||||
|
|
||||||
|
$id = optional_param('id', 0, PARAM_INT); // If we have an id we have existing settings.
|
||||||
|
$provider = optional_param('aiprovider', null, PARAM_PLUGIN);
|
||||||
|
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
|
||||||
|
$title = get_string('createnewprovider', 'core_ai');
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
// Handle return URL.
|
||||||
|
if (empty($returnurl)) {
|
||||||
|
$returnurl = new moodle_url(
|
||||||
|
url: '/admin/settings.php',
|
||||||
|
params: ['section' => 'aiprovider']
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$returnurl = new moodle_url($returnurl);
|
||||||
|
}
|
||||||
|
$data['returnurl'] = $returnurl;
|
||||||
|
|
||||||
|
if ($provider) {
|
||||||
|
$configs = ['aiprovider' => $provider];
|
||||||
|
$data['providerconfigs'] = $configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id !== 0) { // If we have an id we are updating an existing provider instance.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$providerrecord = $manager->get_provider_records(['id' => $id]);
|
||||||
|
$providerrecord = reset($providerrecord);
|
||||||
|
$plugin = explode('\\', $providerrecord->provider);
|
||||||
|
$plugin = $plugin[0];
|
||||||
|
|
||||||
|
$configs = json_decode($providerrecord->config, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$configs['aiprovider'] = $plugin;
|
||||||
|
$configs['id'] = $providerrecord->id;
|
||||||
|
$configs['name'] = $providerrecord->name;
|
||||||
|
|
||||||
|
$data['providerconfigs'] = $configs;
|
||||||
|
$title = get_string('configureprovider', 'core_ai');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial Page setup.
|
||||||
|
$PAGE->set_context($context);
|
||||||
|
$PAGE->set_url('/ai/configure.php', ['id' => $id]);
|
||||||
|
$PAGE->set_pagelayout('admin');
|
||||||
|
$PAGE->set_title($title);
|
||||||
|
$PAGE->set_heading($title);
|
||||||
|
|
||||||
|
// Provider instance form processing.
|
||||||
|
$mform = new \core_ai\form\ai_provider_form(customdata: $data);
|
||||||
|
if ($mform->is_cancelled()) {
|
||||||
|
$data = $mform->get_data();
|
||||||
|
if (isset($data->returnurl)) {
|
||||||
|
redirect($data->returnurl);
|
||||||
|
} else {
|
||||||
|
redirect($returnurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data = $mform->get_data()) {
|
||||||
|
$data = (array)$data;
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$aiprovider = $data['aiprovider'];
|
||||||
|
$providername = $data['name'];
|
||||||
|
unset($data->aiprovider, $data->name, $data->id, $data->returnurl, $data->updateandreturn);
|
||||||
|
if ($id !== 0) {
|
||||||
|
$providerinstance = $manager->get_provider_instances(['id' => $id]);
|
||||||
|
$providerinstance = reset($providerinstance);
|
||||||
|
$providerinstance->name = $providername;
|
||||||
|
|
||||||
|
$manager->update_provider_instance(
|
||||||
|
provider:$providerinstance,
|
||||||
|
config: $data
|
||||||
|
);
|
||||||
|
\core\notification::add(
|
||||||
|
get_string('providerinstanceupdated', 'core_ai', $providername),
|
||||||
|
\core\notification::SUCCESS
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$classname = $aiprovider . '\\' . 'provider';
|
||||||
|
$manager->create_provider_instance(
|
||||||
|
classname: $classname,
|
||||||
|
name: $providername,
|
||||||
|
config: $data,
|
||||||
|
);
|
||||||
|
\core\notification::add(
|
||||||
|
get_string('providerinstancecreated', 'core_ai', $providername),
|
||||||
|
\core\notification::SUCCESS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
redirect($returnurl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page output.
|
||||||
|
echo $OUTPUT->header();
|
||||||
|
|
||||||
|
// Add the provider instance form.
|
||||||
|
$mform->display();
|
||||||
|
|
||||||
|
// Add the per provider action settings only if we have an existing provider.
|
||||||
|
if ($id !== 0) {
|
||||||
|
echo $OUTPUT->render_from_template('core_ai/admin_action_settings', []);
|
||||||
|
$provider = $mform->get_customdata()['aiprovider'];
|
||||||
|
$tableid = $provider . '-' . $id; // This is the table id for the action settings table.
|
||||||
|
$actiontable = new \core_ai\table\aiprovider_action_management_table($tableid);
|
||||||
|
$actiontable->out();
|
||||||
|
}
|
||||||
|
echo $OUTPUT->footer();
|
108
ai/configure_actions.php
Normal file
108
ai/configure_actions.php
Normal file
|
@ -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/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure provider instance action settings.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once('../config.php');
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
$context = context_system::instance();
|
||||||
|
require_capability('moodle/site:config', $context);
|
||||||
|
|
||||||
|
$provider = required_param('provider', PARAM_PLUGIN);
|
||||||
|
$action = required_param('action', PARAM_TEXT);
|
||||||
|
$id = required_param('providerid', PARAM_INT);
|
||||||
|
$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
|
||||||
|
$data = ['providerid' => $id];
|
||||||
|
|
||||||
|
// Handle return URL.
|
||||||
|
if (empty($returnurl)) {
|
||||||
|
$returnurl = new moodle_url(
|
||||||
|
url: '/ai/configure.php',
|
||||||
|
params: ['id' => $id]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$returnurl = new moodle_url($returnurl);
|
||||||
|
}
|
||||||
|
$data['returnurl'] = $returnurl;
|
||||||
|
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$providerrecord = $manager->get_provider_records(['id' => $id]);
|
||||||
|
$providerrecord = reset($providerrecord);
|
||||||
|
|
||||||
|
$actionconfig = json_decode($providerrecord->actionconfig, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$actionconfig = $actionconfig[$action];
|
||||||
|
|
||||||
|
$data['actionconfig'] = $actionconfig;
|
||||||
|
|
||||||
|
$urlparams = [
|
||||||
|
'provider' => $provider,
|
||||||
|
'action' => $action,
|
||||||
|
'id' => $id,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Page setup.
|
||||||
|
$title = get_string('actionsettingprovider', 'core_ai', $action::get_name());
|
||||||
|
$PAGE->set_context($context);
|
||||||
|
$PAGE->set_url('/ai/configure.php_actions', $urlparams);
|
||||||
|
$PAGE->set_pagelayout('admin');
|
||||||
|
$PAGE->set_title($title);
|
||||||
|
$PAGE->set_heading($title);
|
||||||
|
|
||||||
|
$providerclass = "\\$provider\\provider";
|
||||||
|
$mform = $providerclass::get_action_settings($action, $data);
|
||||||
|
|
||||||
|
if ($mform->is_cancelled()) {
|
||||||
|
$data = $mform->get_data();
|
||||||
|
if (isset($data->returnurl)) {
|
||||||
|
redirect($data->returnurl);
|
||||||
|
} else {
|
||||||
|
redirect($returnurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data = $mform->get_data()) {
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$aiprovider = $data->provider;
|
||||||
|
unset($data->provider, $data->id, $data->action, $data->returnurl, $data->submitbutton);
|
||||||
|
$providerinstance = $manager->get_provider_instances(['id' => $id]);
|
||||||
|
$providerinstance = reset($providerinstance);
|
||||||
|
$actionconfig = $providerinstance->actionconfig;
|
||||||
|
$actionconfig[$action]['settings'] = (array)$data;
|
||||||
|
$actionconfig[$action]['enabled'] = $providerinstance->actionconfig[$action]['enabled'];
|
||||||
|
|
||||||
|
$manager->update_provider_instance(
|
||||||
|
provider: $providerinstance,
|
||||||
|
actionconfig: $actionconfig,
|
||||||
|
);
|
||||||
|
|
||||||
|
\core\notification::add(
|
||||||
|
get_string('providerinstanceactionupdated', 'core_ai', $action::get_name()),
|
||||||
|
\core\notification::SUCCESS
|
||||||
|
);
|
||||||
|
|
||||||
|
redirect($returnurl);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $OUTPUT->header();
|
||||||
|
$mform->display();
|
||||||
|
echo $OUTPUT->footer();
|
|
@ -90,7 +90,7 @@ class summarise_text extends external_api {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send the action to the AI manager.
|
// Send the action to the AI manager.
|
||||||
$manager = new \core_ai\manager();
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
$response = $manager->process_action($action);
|
$response = $manager->process_action($action);
|
||||||
// Return the response.
|
// Return the response.
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace aiplacement_courseassist;
|
||||||
class placement extends \core_ai\placement {
|
class placement extends \core_ai\placement {
|
||||||
|
|
||||||
#[\Override]
|
#[\Override]
|
||||||
public function get_action_list(): array {
|
public static function get_action_list(): array {
|
||||||
return [
|
return [
|
||||||
\core_ai\aiactions\summarise_text::class,
|
\core_ai\aiactions\summarise_text::class,
|
||||||
];
|
];
|
||||||
|
|
|
@ -35,15 +35,16 @@ class utils {
|
||||||
*/
|
*/
|
||||||
public static function is_course_assist_available(\context $context): bool {
|
public static function is_course_assist_available(\context $context): bool {
|
||||||
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_courseassist'), 2);
|
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_courseassist'), 2);
|
||||||
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
|
$pluginmanager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
|
||||||
if (!$manager::is_plugin_enabled($pluginname)) {
|
if (!$pluginmanager::is_plugin_enabled($pluginname)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$providers = manager::get_providers_for_actions([summarise_text::class], true);
|
$aimanager = \core\di::get(manager::class);
|
||||||
|
$providers = $aimanager->get_providers_for_actions([summarise_text::class], true);
|
||||||
if (!has_capability('aiplacement/courseassist:summarise_text', $context)
|
if (!has_capability('aiplacement/courseassist:summarise_text', $context)
|
||||||
|| !manager::is_action_available(summarise_text::class)
|
|| !$aimanager->is_action_available(summarise_text::class)
|
||||||
|| !manager::is_action_enabled('aiplacement_courseassist', summarise_text::class)
|
|| !$aimanager->is_action_enabled('aiplacement_courseassist', summarise_text::class)
|
||||||
|| empty($providers[summarise_text::class])
|
|| empty($providers[summarise_text::class])
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -25,9 +25,9 @@ Feature: AI Course assist summarise
|
||||||
| capability | permission | role | contextlevel | reference |
|
| capability | permission | role | contextlevel | reference |
|
||||||
| aiplacement/courseassist:summarise_text | Prohibit | custom2 | Course | C1 |
|
| aiplacement/courseassist:summarise_text | Prohibit | custom2 | Course | C1 |
|
||||||
And I log in as "admin"
|
And I log in as "admin"
|
||||||
And I enable "openai" "aiprovider" plugin
|
And the following "core_ai > ai providers" exist:
|
||||||
And the following config values are set as admin:
|
|provider | name | enabled | apikey | orgid |
|
||||||
| apikey | 123 | aiprovider_openai |
|
|aiprovider_openai| OpenAI API test | 1 | 123 | abc |
|
||||||
And I enable "courseassist" "aiplacement" plugin
|
And I enable "courseassist" "aiplacement" plugin
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
|
@ -41,10 +41,10 @@ Feature: AI Course assist summarise
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Summarise text using AI is not available if provider is not enabled
|
Scenario: Summarise text using AI is not available if provider is not enabled
|
||||||
Given I disable "openai" "aiprovider" plugin
|
Given I "disable" the ai provider with name "OpenAI API test"
|
||||||
When I am on the "PageName1" "page activity" page logged in as teacher1
|
When I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
Then "Summarise" "button" should not exist
|
Then "Summarise" "button" should not exist
|
||||||
And I enable "openai" "aiprovider" plugin
|
And I "enable" the ai provider with name "OpenAI API test"
|
||||||
And I am on the "PageName1" "page activity" page logged in as teacher1
|
And I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
And "Summarise" "button" should exist
|
And "Summarise" "button" should exist
|
||||||
|
|
||||||
|
@ -52,10 +52,14 @@ Feature: AI Course assist summarise
|
||||||
Scenario: Summarise text using AI is not available if provider action is not enabled
|
Scenario: Summarise text using AI is not available if provider action is not enabled
|
||||||
Given the following config values are set as admin:
|
Given the following config values are set as admin:
|
||||||
| summarise_text | | aiprovider_openai |
|
| summarise_text | | aiprovider_openai |
|
||||||
|
And I set the following action configuration for ai provider with name "OpenAI API test":
|
||||||
|
| action | enabled |
|
||||||
|
| summarise_text | 0 |
|
||||||
When I am on the "PageName1" "page activity" page logged in as teacher1
|
When I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
Then "Summarise" "button" should not exist
|
Then "Summarise" "button" should not exist
|
||||||
And the following config values are set as admin:
|
And I set the following action configuration for ai provider with name "OpenAI API test":
|
||||||
| summarise_text | 1 | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| summarise_text | 1 |
|
||||||
And I am on the "PageName1" "page activity" page logged in as teacher1
|
And I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
And "Summarise" "button" should exist
|
And "Summarise" "button" should exist
|
||||||
|
|
||||||
|
@ -74,15 +78,18 @@ Feature: AI Course assist summarise
|
||||||
Scenario: Summarise text using AI is not available if provider action is not enabled and placement action is enabled
|
Scenario: Summarise text using AI is not available if provider action is not enabled and placement action is enabled
|
||||||
Given the following config values are set as admin:
|
Given the following config values are set as admin:
|
||||||
| summarise_text | | aiplacement_courseassist |
|
| summarise_text | | aiplacement_courseassist |
|
||||||
| summarise_text | | aiprovider_openai |
|
And I set the following action configuration for ai provider with name "OpenAI API test":
|
||||||
|
| action | enabled |
|
||||||
|
| summarise_text | 0 |
|
||||||
When I am on the "PageName1" "page activity" page logged in as teacher1
|
When I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
Then "Summarise" "button" should not exist
|
Then "Summarise" "button" should not exist
|
||||||
And the following config values are set as admin:
|
And the following config values are set as admin:
|
||||||
| summarise_text | 1 | aiplacement_courseassist |
|
| summarise_text | 1 | aiplacement_courseassist |
|
||||||
And I am on the "PageName1" "page activity" page logged in as teacher1
|
And I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
And "Summarise" "button" should not exist
|
And "Summarise" "button" should not exist
|
||||||
And the following config values are set as admin:
|
And I set the following action configuration for ai provider with name "OpenAI API test":
|
||||||
| summarise_text | 1 | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| summarise_text | 1 |
|
||||||
And I am on the "PageName1" "page activity" page logged in as teacher1
|
And I am on the "PageName1" "page activity" page logged in as teacher1
|
||||||
And "Summarise" "button" should exist
|
And "Summarise" "button" should exist
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
|
|
||||||
namespace aiplacement_courseassist;
|
namespace aiplacement_courseassist;
|
||||||
|
|
||||||
|
use core_ai\aiactions\generate_text;
|
||||||
|
use core_ai\aiactions\summarise_text;
|
||||||
|
use core_ai\manager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI Placement course assist utils test.
|
* AI Placement course assist utils test.
|
||||||
*
|
*
|
||||||
|
@ -42,12 +46,8 @@ final class utils_test extends \advanced_testcase {
|
||||||
|
|
||||||
// Provider is not enabled.
|
// Provider is not enabled.
|
||||||
$this->setUser($user1);
|
$this->setUser($user1);
|
||||||
set_config('enabled', 0, 'aiprovider_openai');
|
|
||||||
$this->assertFalse(utils::is_course_assist_available($context));
|
$this->assertFalse(utils::is_course_assist_available($context));
|
||||||
|
|
||||||
set_config('enabled', 1, 'aiprovider_openai');
|
|
||||||
set_config('apikey', '123', 'aiprovider_openai');
|
|
||||||
|
|
||||||
// Plugin is not enabled.
|
// Plugin is not enabled.
|
||||||
$this->setUser($user1);
|
$this->setUser($user1);
|
||||||
set_config('enabled', 0, 'aiplacement_courseassist');
|
set_config('enabled', 0, 'aiplacement_courseassist');
|
||||||
|
@ -71,8 +71,18 @@ final class utils_test extends \advanced_testcase {
|
||||||
$this->assertFalse(utils::is_course_assist_available($context));
|
$this->assertFalse(utils::is_course_assist_available($context));
|
||||||
|
|
||||||
// Plugin is enabled, user has capability, placement action is available and provider action is available.
|
// Plugin is enabled, user has capability, placement action is available and provider action is available.
|
||||||
|
$mockmanager = $this->createMock(\core_ai\manager::class);
|
||||||
|
$mockmanager->method('is_action_available')->willReturn(true);
|
||||||
|
$mockmanager->method('is_action_enabled')->willReturn(true);
|
||||||
|
$mockmanager->method('get_providers_for_actions')->willReturn([
|
||||||
|
summarise_text::class => ['aiprovider_openai'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
\core\di::set(\core_ai\manager::class, function() use ($mockmanager) {
|
||||||
|
return $mockmanager;
|
||||||
|
});
|
||||||
|
|
||||||
$this->setUser($user1);
|
$this->setUser($user1);
|
||||||
set_config('summarise_text', 1, 'aiprovider_openai');
|
|
||||||
set_config('summarise_text', 1, 'aiplacement_courseassist');
|
set_config('summarise_text', 1, 'aiplacement_courseassist');
|
||||||
$this->assertTrue(utils::is_course_assist_available($context));
|
$this->assertTrue(utils::is_course_assist_available($context));
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class placement extends \core_ai\placement {
|
||||||
*
|
*
|
||||||
* @return array An array of action class names.
|
* @return array An array of action class names.
|
||||||
*/
|
*/
|
||||||
public function get_action_list(): array {
|
public static function get_action_list(): array {
|
||||||
return [
|
return [
|
||||||
\core_ai\aiactions\generate_text::class,
|
\core_ai\aiactions\generate_text::class,
|
||||||
\core_ai\aiactions\generate_image::class,
|
\core_ai\aiactions\generate_image::class,
|
||||||
|
|
|
@ -41,18 +41,19 @@ class utils {
|
||||||
string $actionclass
|
string $actionclass
|
||||||
): bool {
|
): bool {
|
||||||
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_editor'), 2);
|
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_editor'), 2);
|
||||||
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
|
$pluginmanager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
|
||||||
|
if (!$pluginmanager::is_plugin_enabled($pluginname)) {
|
||||||
if ($manager::is_plugin_enabled($pluginname)) {
|
return false;
|
||||||
if (
|
|
||||||
has_capability("aiplacement/editor:{$actionname}", $context)
|
|
||||||
&& manager::is_action_available($actionclass)
|
|
||||||
&& manager::is_action_enabled('aiplacement_editor', $actionclass)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$aimanager = \core\di::get(manager::class);
|
||||||
|
if (
|
||||||
|
has_capability("aiplacement/editor:{$actionname}", $context)
|
||||||
|
&& $aimanager->is_action_available($actionclass)
|
||||||
|
&& $aimanager->is_action_enabled('aiplacement_editor', $actionclass)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,16 +64,12 @@ final class utils_test extends \advanced_testcase {
|
||||||
): void {
|
): void {
|
||||||
// Provider is not enabled.
|
// Provider is not enabled.
|
||||||
$this->setUser($this->users[1]);
|
$this->setUser($this->users[1]);
|
||||||
set_config('enabled', 0, 'aiprovider_openai');
|
|
||||||
$this->assertFalse(utils::is_html_editor_placement_action_available(
|
$this->assertFalse(utils::is_html_editor_placement_action_available(
|
||||||
context: $this->context,
|
context: $this->context,
|
||||||
actionname: $actionname,
|
actionname: $actionname,
|
||||||
actionclass: $actionclass
|
actionclass: $actionclass
|
||||||
));
|
));
|
||||||
|
|
||||||
set_config('enabled', 1, 'aiprovider_openai');
|
|
||||||
set_config('apikey', '123', 'aiprovider_openai');
|
|
||||||
|
|
||||||
// Plugin is not enabled.
|
// Plugin is not enabled.
|
||||||
$this->setUser($this->users[1]);
|
$this->setUser($this->users[1]);
|
||||||
set_config('enabled', 0, 'aiplacement_editor');
|
set_config('enabled', 0, 'aiplacement_editor');
|
||||||
|
@ -104,7 +100,6 @@ final class utils_test extends \advanced_testcase {
|
||||||
|
|
||||||
// Plugin is enabled, user has capability and provider action is not available.
|
// Plugin is enabled, user has capability and provider action is not available.
|
||||||
$this->setUser($this->users[1]);
|
$this->setUser($this->users[1]);
|
||||||
set_config($actionname, 0, 'aiprovider_openai');
|
|
||||||
set_config($actionname, 1, 'aiplacement_editor');
|
set_config($actionname, 1, 'aiplacement_editor');
|
||||||
$this->assertFalse(utils::is_html_editor_placement_action_available(
|
$this->assertFalse(utils::is_html_editor_placement_action_available(
|
||||||
context: $this->context,
|
context: $this->context,
|
||||||
|
@ -113,9 +108,14 @@ final class utils_test extends \advanced_testcase {
|
||||||
));
|
));
|
||||||
|
|
||||||
// Plugin is enabled, user has capability, placement action is available and provider action is available.
|
// Plugin is enabled, user has capability, placement action is available and provider action is available.
|
||||||
|
$mockmanager = $this->createMock(\core_ai\manager::class);
|
||||||
|
$mockmanager->method('is_action_available')->willReturn(true);
|
||||||
|
$mockmanager->method('is_action_enabled')->willReturn(true);
|
||||||
|
|
||||||
|
\core\di::set(\core_ai\manager::class, function() use ($mockmanager) {
|
||||||
|
return $mockmanager;
|
||||||
|
});
|
||||||
$this->setUser($this->users[1]);
|
$this->setUser($this->users[1]);
|
||||||
set_config($actionname, 1, 'aiprovider_openai');
|
|
||||||
set_config($actionname, 1, 'aiplacement_editor');
|
|
||||||
$this->assertTrue(utils::is_html_editor_placement_action_available(
|
$this->assertTrue(utils::is_html_editor_placement_action_available(
|
||||||
context: $this->context,
|
context: $this->context,
|
||||||
actionname: $actionname,
|
actionname: $actionname,
|
||||||
|
|
34
ai/templates/admin_action_settings.mustache
Normal file
34
ai/templates/admin_action_settings.mustache
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{{!
|
||||||
|
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_action_settings
|
||||||
|
|
||||||
|
Template to display a headings for per provider actions.
|
||||||
|
|
||||||
|
Context variables required for this template:
|
||||||
|
* none
|
||||||
|
|
||||||
|
Example context (json):
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
<div class="mt-2 mb-2">
|
||||||
|
<hr>
|
||||||
|
<h2>{{#str}}actionsettings, core_ai{{/str}}</h2>
|
||||||
|
{{#str}}actionsettings_desc, core_ai{{/str}}
|
||||||
|
</div>
|
46
ai/templates/admin_delete_provider.mustache
Normal file
46
ai/templates/admin_delete_provider.mustache
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{{!
|
||||||
|
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_delete_provider
|
||||||
|
|
||||||
|
Template to display a call render the delete button for a provider.
|
||||||
|
|
||||||
|
Context variables required for this template:
|
||||||
|
* id - provider instance id.
|
||||||
|
* name - provider instance name.
|
||||||
|
* provider - provider name.
|
||||||
|
* delete-method - Webservice method name for delete.
|
||||||
|
|
||||||
|
Example context (json):
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Provider 1",
|
||||||
|
"provider": "aiprovider_openai",
|
||||||
|
"delete-method": "core_ai_delete_provider_instance"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
<a href="#"
|
||||||
|
id="provider-delete-{{id}}"
|
||||||
|
title="{{#str}}providerinstancedelete, core_ai{{/str}}"
|
||||||
|
class="ai-provider-delete"
|
||||||
|
data-action="provider-delete-{{id}}"
|
||||||
|
data-id="{{id}}"
|
||||||
|
data-name="{{name}}"
|
||||||
|
data-provider="{{provider}}"
|
||||||
|
data-delete-method="{{delete-method}}">
|
||||||
|
<i class="fa fa-trash"></i> {{#str}}delete, core{{/str}}
|
||||||
|
</a>
|
|
@ -1,19 +1,86 @@
|
||||||
@core @core_admin @core_ai
|
@core @core_admin @core_ai @core_ai_admin
|
||||||
Feature: An administrator can manage AI subsystem settings
|
Feature: An administrator can manage AI subsystem settings
|
||||||
In order to alter the user experience
|
In order to alter the user experience
|
||||||
As an admin
|
As an admin
|
||||||
I can manage AI subsystem settings
|
I can manage AI subsystem settings
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: An administrator can control the enabled state of AI Provider plugins using JavaScript
|
Scenario: An administrator can create AI Provider plugin instances using JavaScript
|
||||||
Given I am logged in as "admin"
|
Given I am logged in as "admin"
|
||||||
And I navigate to "AI > AI providers" in site administration
|
And I navigate to "AI > AI providers" in site administration
|
||||||
When I toggle the "Enable OpenAI API provider" admin switch "on"
|
And I should see "Nothing to display"
|
||||||
And I should see "OpenAI API provider enabled."
|
When I click on "Create a new provider instance" "link"
|
||||||
|
And I select "OpenAI API Provider" from the "Choose AI Provider plugin" singleselect
|
||||||
|
And I set the following fields to these values:
|
||||||
|
| Name for instance | OpenAI API provider test|
|
||||||
|
| OpenAI API key | 123 |
|
||||||
|
| OpenAI organization ID| abc |
|
||||||
|
And I click on "Create instance" "button"
|
||||||
|
And I should see "OpenAI API provider test AI Provider instance created"
|
||||||
|
And I should see "OpenAI API provider test"
|
||||||
|
And I click on "Create a new provider instance" "link"
|
||||||
|
And I select "Azure AI API Provider" from the "Choose AI Provider plugin" singleselect
|
||||||
|
And I set the following fields to these values:
|
||||||
|
| Name for instance | Azure AI provider test |
|
||||||
|
| Azure AI API key | 123 |
|
||||||
|
| Azure AI API endpoint| https://api.cognitive.microsofttranslator.com/ |
|
||||||
|
And I click on "Create instance" "button"
|
||||||
|
And I should see "Azure AI provider test AI Provider instance created"
|
||||||
|
And I should see "Azure AI provider test"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: An administrator can enable AI Provider plugin instances using JavaScript
|
||||||
|
Given the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | orgid |
|
||||||
|
|aiprovider_openai| OpenAI API test | 0 | 123 | abc |
|
||||||
|
And the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | endpoint |
|
||||||
|
|aiprovider_azureai | Azure AI API test| 0 | 123 | https://api.cognitive.microsofttranslator.com/ |
|
||||||
|
And I am logged in as "admin"
|
||||||
|
And I navigate to "AI > AI providers" in site administration
|
||||||
|
And I should see "OpenAI API test"
|
||||||
|
And I should see "Azure AI API test"
|
||||||
|
And I toggle the "Enable OpenAI API test" admin switch "on"
|
||||||
|
And I should see "OpenAI API test enabled."
|
||||||
|
And I toggle the "Enable Azure AI API test" admin switch "on"
|
||||||
|
And I should see "Azure AI API test enabled."
|
||||||
And I reload the page
|
And I reload the page
|
||||||
And I should see "Disable OpenAI API provider"
|
And I should see "Disable OpenAI API test"
|
||||||
And I toggle the "Disable OpenAI API provider" admin switch "off"
|
And I should see "Disable Azure AI API test"
|
||||||
Then I should see "OpenAI API provider disabled."
|
And I toggle the "Disable OpenAI API test" admin switch "off"
|
||||||
|
And I should see "OpenAI API test disabled."
|
||||||
|
And I toggle the "Disable Azure AI API test" admin switch "off"
|
||||||
|
Then I should see "Azure AI API test disabled."
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: An administrator can configure AI Provider plugin instance
|
||||||
|
action settings using JavaScript
|
||||||
|
Given the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | orgid |
|
||||||
|
|aiprovider_openai| OpenAI API test | 0 | 123 | abc |
|
||||||
|
And I am logged in as "admin"
|
||||||
|
And I navigate to "AI > AI providers" in site administration
|
||||||
|
And I click on the "Settings" link in the table row containing "OpenAI API test"
|
||||||
|
And I should see "Configure provider instance"
|
||||||
|
And I click on the "Settings" link in the table row containing "Generate text"
|
||||||
|
And I should see "Generate text action settings"
|
||||||
|
And I set the following fields to these values:
|
||||||
|
| AI model | gpt-3 |
|
||||||
|
| API endpoint | https://api.openai.com/v1/engines/gpt-3/completions |
|
||||||
|
And I click on "Save changes" "button"
|
||||||
|
Then I should see "Generate text action settings updated"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: An administrator can delete AI Provider plugin instances using JavaScript
|
||||||
|
Given the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | orgid |
|
||||||
|
|aiprovider_openai| OpenAI API test | 0 | 123 | abc |
|
||||||
|
And I am logged in as "admin"
|
||||||
|
And I navigate to "AI > AI providers" in site administration
|
||||||
|
And I click on the "Delete" link in the table row containing "OpenAI API test"
|
||||||
|
And "Delete AI Provider instance" "dialogue" should be visible
|
||||||
|
And I click on "Delete" "button" in the "Delete AI Provider instance" "dialogue"
|
||||||
|
Then I should see "OpenAI API test AI Provider instance deleted"
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: An administrator can control the enabled state of AI placement plugins using JavaScript
|
Scenario: An administrator can control the enabled state of AI placement plugins using JavaScript
|
||||||
|
@ -28,21 +95,22 @@ Feature: An administrator can manage AI subsystem settings
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Placement actions should be available when an Administrator enables AI Providers using JavaScript
|
Scenario: Placement actions should be available when an Administrator enables AI Providers using JavaScript
|
||||||
Given I am logged in as "admin"
|
Given the following "core_ai > ai providers" exist:
|
||||||
And I navigate to "AI > AI providers" in site administration
|
|provider | name | enabled | apikey | orgid |
|
||||||
When I toggle the "Enable OpenAI API provider" admin switch "on"
|
|aiprovider_openai| OpenAI API test | 1 | 123 | abc |
|
||||||
And I should see "OpenAI API provider enabled."
|
And I am logged in as "admin"
|
||||||
And the following config values are set as admin:
|
|
||||||
| apikey | 123 | aiprovider_openai |
|
|
||||||
And I navigate to "AI > AI placements" in site administration
|
And I navigate to "AI > AI placements" in site administration
|
||||||
And I click on the "Settings" link in the table row containing "Text editor placement"
|
And I click on the "Settings" link in the table row containing "Text editor placement"
|
||||||
Then I should not see "This action is unavailable."
|
Then I should not see "This action is unavailable."
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Placement actions should not be available when an Administrator disables AI Providers using JavaScript
|
Scenario: Placement actions should not be available when an Administrator disables AI Providers using JavaScript
|
||||||
Given I am logged in as "admin"
|
Given the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | orgid |
|
||||||
|
|aiprovider_openai| OpenAI API test | 1 | 123 | abc |
|
||||||
|
And I am logged in as "admin"
|
||||||
And I navigate to "AI > AI providers" in site administration
|
And I navigate to "AI > AI providers" in site administration
|
||||||
When I toggle the "Enable OpenAI API provider" admin switch "off"
|
And I toggle the "Disable OpenAI API test" admin switch "off"
|
||||||
And I navigate to "AI > AI placements" in site administration
|
And I navigate to "AI > AI placements" in site administration
|
||||||
And I click on the "Settings" link in the table row containing "Text editor placement"
|
And I click on the "Settings" link in the table row containing "Text editor placement"
|
||||||
And I should see "This action is unavailable." in the table row containing "Generate text"
|
And I should see "This action is unavailable." in the table row containing "Generate text"
|
||||||
|
@ -50,13 +118,12 @@ Feature: An administrator can manage AI subsystem settings
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Placement actions should not be available for enabled Providers when an Administrator disables an Action using JavaScript
|
Scenario: Placement actions should not be available for enabled Providers when an Administrator disables an Action using JavaScript
|
||||||
Given I am logged in as "admin"
|
Given the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | orgid |
|
||||||
|
|aiprovider_openai| OpenAI API test | 1 | 123 | abc |
|
||||||
|
And I am logged in as "admin"
|
||||||
And I navigate to "AI > AI providers" in site administration
|
And I navigate to "AI > AI providers" in site administration
|
||||||
When I toggle the "Enable OpenAI API provider" admin switch "on"
|
And I click on the "Settings" link in the table row containing "OpenAI API test"
|
||||||
And I should see "OpenAI API provider enabled."
|
|
||||||
And the following config values are set as admin:
|
|
||||||
| apikey | 123 | aiprovider_openai |
|
|
||||||
And I click on the "Settings" link in the table row containing "OpenAI API provider"
|
|
||||||
And I toggle the "Generate text" admin switch "off"
|
And I toggle the "Generate text" admin switch "off"
|
||||||
And I navigate to "AI > AI placements" in site administration
|
And I navigate to "AI > AI placements" in site administration
|
||||||
And I click on the "Settings" link in the table row containing "Text editor placement"
|
And I click on the "Settings" link in the table row containing "Text editor placement"
|
||||||
|
|
88
ai/tests/behat/behat_core_ai.php
Normal file
88
ai/tests/behat/behat_core_ai.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?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/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General use steps definitions.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\TableNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps definitions specific to the AI Subsystem.
|
||||||
|
*
|
||||||
|
* @package core_ai
|
||||||
|
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class behat_core_ai extends behat_base {
|
||||||
|
/**
|
||||||
|
* Change the enabled state of an AI provider plugin.
|
||||||
|
*
|
||||||
|
* @Then /^I "(?P<state>(?:[^"]|\\")*)" the ai provider with name "(?P<provider>(?:[^"]|\\")*)"$/
|
||||||
|
*
|
||||||
|
* @param string $state The state to set the plugin to.
|
||||||
|
* @param string $providername The name of the AI provider plugin.
|
||||||
|
*/
|
||||||
|
#[\core\attribute\example('Given I "disable" the ai provider with name "OpenAI API test"')]
|
||||||
|
public function i_change_the_ai_provider_state_with_name(string $state, string $providername): void {
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$providers = $manager->get_provider_instances(['name' => $providername]);
|
||||||
|
$provider = reset($providers);
|
||||||
|
if ($state == 'disable') {
|
||||||
|
$manager->disable_provider_instance($provider);
|
||||||
|
} else {
|
||||||
|
$manager->enable_provider_instance($provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set action configuration for AI provider instances.
|
||||||
|
*
|
||||||
|
* @Given /^I set the following action configuration for ai provider with name "(?P<providername>(?:[^"]|\\")*)":$/
|
||||||
|
*
|
||||||
|
* @param string $providername The name of the ai provider to configure actions for.
|
||||||
|
* @param TableNode $data
|
||||||
|
*/
|
||||||
|
#[\core\attribute\example('I set the following action configuration for ai provider with name "OpenAI API test":
|
||||||
|
| action | enabled | model | endpoint |
|
||||||
|
| generate_text | 1 | gpt-3 | https://api.openai.com/v1/engines/gpt-3/completions |
|
||||||
|
| summarise_text | 0 | gpt-4 | |')]
|
||||||
|
public function configure_provider_action(string $providername, TableNode $data) {
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$providers = $manager->get_provider_instances(['name' => $providername]);
|
||||||
|
$provider = reset($providers);
|
||||||
|
$rows = $data->getHash();
|
||||||
|
$actiondata = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$action = 'core_ai\\aiactions\\' . $row['action'];
|
||||||
|
$actiondata[$action]['enabled'] = $row['enabled'];
|
||||||
|
unset ($row['action'], $row['enabled']);
|
||||||
|
$actiondata[$action]['settings'] = $row;
|
||||||
|
}
|
||||||
|
$manager->update_provider_instance(
|
||||||
|
provider: $provider,
|
||||||
|
actionconfig: $actiondata
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
@report @core_ai
|
@report @core_ai @core_ai_reports
|
||||||
Feature: AI reports
|
Feature: AI reports
|
||||||
In order to view an AI report
|
In order to view an AI report
|
||||||
As an admin or system role manager
|
As an admin or system role manager
|
||||||
|
@ -11,13 +11,13 @@ Feature: AI reports
|
||||||
And the following "role assigns" exist:
|
And the following "role assigns" exist:
|
||||||
| user | role | contextlevel | reference |
|
| user | role | contextlevel | reference |
|
||||||
| manager1 | manager | System | |
|
| manager1 | manager | System | |
|
||||||
And the following config values are set as admin:
|
|
||||||
| enabled | 1 | aiprovider_openai |
|
|
||||||
| apikey | 123 | aiprovider_openai |
|
|
||||||
And the following config values are set as admin:
|
And the following config values are set as admin:
|
||||||
| enabled | 1 | aiplacement_editor |
|
| enabled | 1 | aiplacement_editor |
|
||||||
| generate_text | 1 | aiplacement_editor |
|
| generate_text | 1 | aiplacement_editor |
|
||||||
| generate_image | 0 | aiplacement_editor |
|
| generate_image | 0 | aiplacement_editor |
|
||||||
|
And the following "core_ai > ai providers" exist:
|
||||||
|
|provider | name | enabled | apikey | orgid |
|
||||||
|
|aiprovider_openai | OpenAI API test | 1 | 123 | abc |
|
||||||
|
|
||||||
@javascript @editor_tiny
|
@javascript @editor_tiny
|
||||||
Scenario: Mangers with a system role can see who has accepted the AI policy
|
Scenario: Mangers with a system role can see who has accepted the AI policy
|
||||||
|
|
|
@ -44,6 +44,15 @@ class behat_core_ai_generator extends behat_generator_base {
|
||||||
'user' => 'userid',
|
'user' => 'userid',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'ai providers' => [
|
||||||
|
'singular' => 'ai provider',
|
||||||
|
'datagenerator' => 'ai_provider',
|
||||||
|
'required' => [
|
||||||
|
'provider',
|
||||||
|
'name',
|
||||||
|
'enabled',
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,4 +87,24 @@ class core_ai_generator extends component_generator_base {
|
||||||
$action->timecompleted = time() + 1;
|
$action->timecompleted = time() + 1;
|
||||||
$DB->insert_record('ai_action_register', $action);
|
$DB->insert_record('ai_action_register', $action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates AI provider instance.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function create_ai_provider(array $data) {
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$classname = $data['provider'] . '\\' . 'provider';
|
||||||
|
$name = $data['name'];
|
||||||
|
$enabled = $data['enabled'];
|
||||||
|
unset($data['provider'], $data['name'], $data['enabled']);
|
||||||
|
$manager->create_provider_instance(
|
||||||
|
classname: $classname,
|
||||||
|
name: $name,
|
||||||
|
enabled: $enabled,
|
||||||
|
config: $data,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,20 +68,199 @@ final class manager_test extends \advanced_testcase {
|
||||||
], $actions);
|
], $actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test create_provider_instance method.
|
||||||
|
*/
|
||||||
|
public function test_create_provider_instance(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
// Create the provider instance.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsInt($provider->id);
|
||||||
|
$this->assertFalse($provider->enabled);
|
||||||
|
$this->assertEquals('goeshere', $provider->config['data']);
|
||||||
|
|
||||||
|
// Check the record was written to the DB.
|
||||||
|
$record = $DB->get_record('ai_providers', ['id' => $provider->id], '*', MUST_EXIST);
|
||||||
|
$this->assertEquals($provider->id, $record->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test create_provider_instance non provider class.
|
||||||
|
*/
|
||||||
|
public function test_create_provider_instance_non_provider_class(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
|
||||||
|
// Should throw an exception as the class is not a provider.
|
||||||
|
$this->expectException(\coding_exception::class);
|
||||||
|
$this->expectExceptionMessage(' Provider class not valid: ' . $this::class);
|
||||||
|
|
||||||
|
// Create the provider instance.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$manager->create_provider_instance(
|
||||||
|
classname: $this::class,
|
||||||
|
name: 'dummy',
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_provider_records method.
|
||||||
|
*/
|
||||||
|
public function test_get_provider_records(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
// Create some dummy provider records directly in the database.
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
$record1 = new \stdClass();
|
||||||
|
$record1->name = 'dummy1';
|
||||||
|
$record1->provider = 'dummy';
|
||||||
|
$record1->enabled = 1;
|
||||||
|
$record1->config = json_encode($config);
|
||||||
|
$record1->actionconfig = json_encode(['generate_text' => 1]);
|
||||||
|
|
||||||
|
$record2 = new \stdClass();
|
||||||
|
$record2->name = 'dummy2';
|
||||||
|
$record2->provider = 'dummy';
|
||||||
|
$record2->enabled = 1;
|
||||||
|
$record2->config = json_encode($config);
|
||||||
|
$record2->actionconfig = json_encode(['generate_text' => 1]);
|
||||||
|
|
||||||
|
$DB->insert_records('ai_providers', [
|
||||||
|
$record1,
|
||||||
|
$record2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Get the provider records.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$providers = $manager->get_provider_records(['provider' => 'dummy']);
|
||||||
|
|
||||||
|
// Assert that the records were returned.
|
||||||
|
$this->assertCount(2, $providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test get_provider_instances method.
|
||||||
|
*/
|
||||||
|
public function test_get_provider_instances(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create an instance record for a non provider class.
|
||||||
|
$record = new \stdClass();
|
||||||
|
$record->name = 'dummy';
|
||||||
|
$record->provider = 'dummy';
|
||||||
|
$record->enabled = 1;
|
||||||
|
$record->config = json_encode($config);
|
||||||
|
|
||||||
|
$DB->insert_record('ai_providers', $record);
|
||||||
|
|
||||||
|
// Get the provider instances.
|
||||||
|
$instances = $manager->get_provider_instances();
|
||||||
|
$this->assertDebuggingCalled('Unable to find a provider class for dummy');
|
||||||
|
$this->assertCount(1, $instances);
|
||||||
|
$this->assertEquals($provider->id, $instances[$provider->id]->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test update_provider_instance method.
|
||||||
|
*/
|
||||||
|
public function test_update_provider_instance(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
// Create the provider instance.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the provider instance.
|
||||||
|
$config['data'] = 'updateddata';
|
||||||
|
$manager->update_provider_instance($provider, $config);
|
||||||
|
|
||||||
|
// Check the record was updated in the DB.
|
||||||
|
$record = $DB->get_record('ai_providers', ['id' => $provider->id], '*', MUST_EXIST);
|
||||||
|
$this->assertEquals($provider->id, $record->id);
|
||||||
|
$this->assertEquals('updateddata', json_decode($record->config)->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test delete_provider_instance method.
|
||||||
|
*/
|
||||||
|
public function test_delete_provider_instance(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
|
|
||||||
|
// Create the provider instance.
|
||||||
|
$manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete the provider instance.
|
||||||
|
$manager->delete_provider_instance($provider);
|
||||||
|
|
||||||
|
// Check the record was deleted from the DB.
|
||||||
|
$record = $DB->record_exists('ai_providers', ['id' => $provider->id]);
|
||||||
|
$this->assertFalse($record);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test get_providers_for_actions.
|
* Test get_providers_for_actions.
|
||||||
*/
|
*/
|
||||||
public function test_get_providers_for_actions(): void {
|
public function test_get_providers_for_actions(): void {
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
set_config('enabled', 1, 'aiprovider_openai');
|
|
||||||
set_config('apikey', '123', 'aiprovider_openai');
|
|
||||||
|
|
||||||
$manager = \core\di::get(manager::class);
|
$manager = \core\di::get(manager::class);
|
||||||
$actions = [
|
$actions = [
|
||||||
generate_text::class,
|
generate_text::class,
|
||||||
summarise_text::class,
|
summarise_text::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Create two provider instances.
|
||||||
|
$config = [
|
||||||
|
'apikey' => 'goeshere',
|
||||||
|
'endpoint' => 'https://example.com',
|
||||||
|
];
|
||||||
|
$provider1 = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy1',
|
||||||
|
enabled: true,
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
|
$config['apiendpoint'] = 'https://example.com';
|
||||||
|
$manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_azureai\provider',
|
||||||
|
name: 'dummy2',
|
||||||
|
enabled: true,
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
// Get the providers for the actions.
|
// Get the providers for the actions.
|
||||||
$providers = $manager->get_providers_for_actions($actions);
|
$providers = $manager->get_providers_for_actions($actions);
|
||||||
|
|
||||||
|
@ -93,12 +272,19 @@ final class manager_test extends \advanced_testcase {
|
||||||
$this->assertCount(2, $providers[summarise_text::class]);
|
$this->assertCount(2, $providers[summarise_text::class]);
|
||||||
|
|
||||||
// Disable the generate text action for the Open AI provider.
|
// Disable the generate text action for the Open AI provider.
|
||||||
manager::set_action_state('aiprovider_openai', generate_text::class::get_basename(), 0);
|
$setresult = $manager->set_action_state(
|
||||||
|
plugin: $provider1->provider,
|
||||||
|
actionbasename: generate_text::class::get_basename(),
|
||||||
|
enabled: 0,
|
||||||
|
instanceid: $provider1->id);
|
||||||
|
// Assert that the action was disabled.
|
||||||
|
$this->assertFalse($setresult);
|
||||||
|
|
||||||
$providers = $manager->get_providers_for_actions($actions, true);
|
$providers = $manager->get_providers_for_actions($actions, true);
|
||||||
|
|
||||||
// Assert that there is no provider for the generate text action.
|
// Assert that there is no provider for the generate text action.
|
||||||
$this->assertCount(0, $providers[generate_text::class]);
|
$this->assertCount(1, $providers[generate_text::class]);
|
||||||
$this->assertCount(1, $providers[summarise_text::class]);
|
$this->assertCount(2, $providers[summarise_text::class]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,7 +292,9 @@ final class manager_test extends \advanced_testcase {
|
||||||
*/
|
*/
|
||||||
public function test_process_action_fail(): void {
|
public function test_process_action_fail(): void {
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
$managermock = $this->getMockBuilder(manager::class)
|
$managermock = $this->getMockBuilder(manager::class)
|
||||||
|
->setConstructorArgs([$DB])
|
||||||
->onlyMethods(['call_action_provider'])
|
->onlyMethods(['call_action_provider'])
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
|
@ -139,15 +327,28 @@ final class manager_test extends \advanced_testcase {
|
||||||
*/
|
*/
|
||||||
public function test_process_action(): void {
|
public function test_process_action(): void {
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
|
global $DB;
|
||||||
|
|
||||||
// Enable the providers.
|
// Create two provider instances.
|
||||||
set_config('enabled', 1, 'aiprovider_openai');
|
$manager = \core\di::get(manager::class);
|
||||||
set_config('apikey', '123', 'aiprovider_openai');
|
$config = ['apikey' => 'goeshere'];
|
||||||
set_config('enabled', 1, 'aiprovider_azureai');
|
$provider1 = $manager->create_provider_instance(
|
||||||
set_config('apikey', '123', 'aiprovider_azureai');
|
classname: '\aiprovider_openai\provider',
|
||||||
set_config('endpoint', 'abc', 'aiprovider_azureai');
|
name: 'dummy1',
|
||||||
|
enabled: true,
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
|
$config['apiendpoint'] = 'https://example.com';
|
||||||
|
$provider2 = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_azureai\provider',
|
||||||
|
name: 'dummy2',
|
||||||
|
enabled: true,
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
$managermock = $this->getMockBuilder(manager::class)
|
$managermock = $this->getMockBuilder(manager::class)
|
||||||
|
->setConstructorArgs([$DB])
|
||||||
->onlyMethods(['call_action_provider'])
|
->onlyMethods(['call_action_provider'])
|
||||||
->getMock();
|
->getMock();
|
||||||
|
|
||||||
|
@ -311,9 +512,14 @@ final class manager_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
|
||||||
|
|
||||||
$manager = \core\di::get(manager::class);
|
$manager = \core\di::get(manager::class);
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
// We're working with a private method here, so we need to use reflection.
|
// We're working with a private method here, so we need to use reflection.
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($manager, 'store_action_result');
|
||||||
$storeresult = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult = $method->invoke($manager, $provider, $action, $actionresponse);
|
||||||
|
@ -335,6 +541,7 @@ final class manager_test extends \advanced_testcase {
|
||||||
* Test call_action_provider.
|
* Test call_action_provider.
|
||||||
*/
|
*/
|
||||||
public function test_call_action_provider(): void {
|
public function test_call_action_provider(): void {
|
||||||
|
$this->resetAfterTest();
|
||||||
$contextid = 1;
|
$contextid = 1;
|
||||||
$userid = 1;
|
$userid = 1;
|
||||||
$prompttext = 'This is a test prompt';
|
$prompttext = 'This is a test prompt';
|
||||||
|
@ -353,9 +560,13 @@ final class manager_test extends \advanced_testcase {
|
||||||
style: $style
|
style: $style
|
||||||
);
|
);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
|
||||||
|
|
||||||
$manager = \core\di::get(manager::class);
|
$manager = \core\di::get(manager::class);
|
||||||
|
$config = ['apikey' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
// We're working with a private method here, so we need to use reflection.
|
// We're working with a private method here, so we need to use reflection.
|
||||||
$method = new \ReflectionMethod($manager, 'call_action_provider');
|
$method = new \ReflectionMethod($manager, 'call_action_provider');
|
||||||
|
@ -370,18 +581,38 @@ final class manager_test extends \advanced_testcase {
|
||||||
*/
|
*/
|
||||||
public function test_is_action_enabled(): void {
|
public function test_is_action_enabled(): void {
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
$plugin = 'aiprovider_openai';
|
|
||||||
$action = generate_image::class;
|
$action = generate_image::class;
|
||||||
|
$manager = \core\di::get(manager::class);
|
||||||
|
|
||||||
|
$config = ['apikey' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
// Should be enabled by default.
|
// Should be enabled by default.
|
||||||
$result = manager::is_action_enabled($plugin, $action);
|
$result = $manager->is_action_enabled(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionclass: $action,
|
||||||
|
instanceid: $provider->id
|
||||||
|
);
|
||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
|
|
||||||
// Disable the action.
|
// Disable the action.
|
||||||
manager::set_action_state($plugin, $action::get_basename(), 0);
|
$manager->set_action_state(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionbasename: $action::get_basename(),
|
||||||
|
enabled: 0,
|
||||||
|
instanceid: $provider->id
|
||||||
|
);
|
||||||
|
|
||||||
// Should now be disabled.
|
// Should now be disabled.
|
||||||
$result = manager::is_action_enabled($plugin, $action);
|
$result = $manager->is_action_enabled(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionclass: $action,
|
||||||
|
instanceid: $provider->id
|
||||||
|
);
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,18 +621,40 @@ final class manager_test extends \advanced_testcase {
|
||||||
*/
|
*/
|
||||||
public function test_enable_action(): void {
|
public function test_enable_action(): void {
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
$plugin = 'aiprovider_openai';
|
|
||||||
$action = generate_image::class;
|
$action = generate_image::class;
|
||||||
|
|
||||||
|
$manager = \core\di::get(manager::class);
|
||||||
|
|
||||||
|
$config = ['apikey' => 'goeshere'];
|
||||||
|
$provider = $manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
// Disable the action.
|
// Disable the action.
|
||||||
set_config('generate_image', 0, $plugin);
|
$setresult = $manager->set_action_state(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionbasename: $action::get_basename(),
|
||||||
|
enabled: 0,
|
||||||
|
instanceid: $provider->id);
|
||||||
|
|
||||||
// Should now be disabled.
|
// Should now be disabled.
|
||||||
$result = manager::is_action_enabled($plugin, $action);
|
$this->assertFalse($setresult);
|
||||||
|
$result = $manager->is_action_enabled(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionclass: $action,
|
||||||
|
instanceid: $provider->id
|
||||||
|
);
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
|
|
||||||
// Enable the action.
|
// Enable the action.
|
||||||
$result = manager::set_action_state($plugin, $action::get_basename(), 1);
|
$manager = \core\di::get(manager::class);
|
||||||
|
$result = $manager->set_action_state(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionbasename: generate_text::class::get_basename(),
|
||||||
|
enabled: 1,
|
||||||
|
instanceid: $provider->id);
|
||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,23 +666,32 @@ final class manager_test extends \advanced_testcase {
|
||||||
$action = generate_image::class;
|
$action = generate_image::class;
|
||||||
|
|
||||||
// Plugin is disabled by default, action state should not matter. Everything should be false.
|
// Plugin is disabled by default, action state should not matter. Everything should be false.
|
||||||
$result = manager::is_action_available($action);
|
$manager = \core\di::get(manager::class);
|
||||||
|
$result = $manager->is_action_available($action);
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
|
|
||||||
// Enable the plugin, actions will be enabled by default when the plugin is enabled.
|
// Create the provider instance, actions will be enabled by default when the plugin is enabled.
|
||||||
$manager = \core_plugin_manager::resolve_plugininfo_class('aiprovider');
|
$config = ['apikey' => 'goeshere'];
|
||||||
$manager::enable_plugin('openai', 1);
|
$provider = $manager->create_provider_instance(
|
||||||
set_config('apikey', '123', 'aiprovider_openai');
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
enabled: true,
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
|
||||||
// Should now be available.
|
// Should now be available.
|
||||||
$result = manager::is_action_available($action);
|
$result = $manager->is_action_available($action);
|
||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
|
|
||||||
// Disable the action.
|
// Disable the action.
|
||||||
set_config('generate_image', 0, 'aiprovider_openai');
|
$manager->set_action_state(
|
||||||
|
plugin: $provider->provider,
|
||||||
|
actionbasename: $action::get_basename(),
|
||||||
|
enabled: 0,
|
||||||
|
instanceid: $provider->id);
|
||||||
|
|
||||||
// Should now be unavailable.
|
// Should now be unavailable.
|
||||||
$result = manager::is_action_available($action);
|
$result = $manager->is_action_available($action);
|
||||||
$this->assertFalse($result);
|
$this->assertFalse($result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,27 @@ use core_privacy\tests\request\content_writer;
|
||||||
*/
|
*/
|
||||||
final class provider_test extends \advanced_testcase {
|
final class provider_test extends \advanced_testcase {
|
||||||
|
|
||||||
|
/** @var \core_ai\manager */
|
||||||
|
private $manager;
|
||||||
|
|
||||||
|
/** @var \core_ai\provider */
|
||||||
|
private $provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overriding setUp() function to always reset after tests.
|
* Overriding setUp() function to always reset after tests.
|
||||||
*/
|
*/
|
||||||
public function setUp(): void {
|
public function setUp(): void {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
$this->resetAfterTest();
|
$this->resetAfterTest();
|
||||||
|
|
||||||
|
// Create the provider instance.
|
||||||
|
$this->manager = \core\di::get(\core_ai\manager::class);
|
||||||
|
$config = ['data' => 'goeshere'];
|
||||||
|
$this->provider = $this->manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,10 +108,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
// Retrieve the user1's context ids.
|
// Retrieve the user1's context ids.
|
||||||
$contextids = provider::get_contexts_for_userid($user1->id);
|
$contextids = provider::get_contexts_for_userid($user1->id);
|
||||||
|
@ -126,10 +139,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
// Retrieve the user1's context ids.
|
// Retrieve the user1's context ids.
|
||||||
$contextids = provider::get_contexts_for_userid($user1->id);
|
$contextids = provider::get_contexts_for_userid($user1->id);
|
||||||
|
@ -166,10 +177,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
// Retrieve the user2's context ids.
|
// Retrieve the user2's context ids.
|
||||||
$contextids = provider::get_contexts_for_userid($user2->id);
|
$contextids = provider::get_contexts_for_userid($user2->id);
|
||||||
|
@ -240,10 +249,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$timecreated2 = $clock->time();
|
$timecreated2 = $clock->time();
|
||||||
|
@ -266,7 +273,7 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
// Retrieve the user's context ids.
|
// Retrieve the user's context ids.
|
||||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||||
|
@ -348,10 +355,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$timecreated2 = $clock->time();
|
$timecreated2 = $clock->time();
|
||||||
|
@ -374,7 +379,7 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
// Retrieve the user's context ids.
|
// Retrieve the user's context ids.
|
||||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||||
|
@ -460,10 +465,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$timecreated2 = $clock->time();
|
$timecreated2 = $clock->time();
|
||||||
|
@ -486,7 +489,7 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
// Retrieve the user's context ids.
|
// Retrieve the user's context ids.
|
||||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||||
|
@ -597,10 +600,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_text(
|
$action = new generate_text(
|
||||||
|
@ -622,8 +623,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
provider::delete_data_for_all_users_in_context($course1context);
|
provider::delete_data_for_all_users_in_context($course1context);
|
||||||
|
|
||||||
|
@ -675,10 +676,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_image(
|
$action = new generate_image(
|
||||||
|
@ -700,8 +699,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
provider::delete_data_for_all_users_in_context($course1context);
|
provider::delete_data_for_all_users_in_context($course1context);
|
||||||
|
|
||||||
|
@ -751,10 +750,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new summarise_text(
|
$action = new summarise_text(
|
||||||
|
@ -776,8 +773,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
provider::delete_data_for_all_users_in_context($course1context);
|
provider::delete_data_for_all_users_in_context($course1context);
|
||||||
|
|
||||||
|
@ -828,10 +825,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_text(
|
$action = new generate_text(
|
||||||
|
@ -853,8 +848,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
$contextlist = provider::get_contexts_for_userid($user1->id);
|
$contextlist = provider::get_contexts_for_userid($user1->id);
|
||||||
$approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
|
$approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
|
||||||
|
@ -907,10 +902,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_image(
|
$action = new generate_image(
|
||||||
|
@ -932,8 +925,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
$contextlist = provider::get_contexts_for_userid($user1->id);
|
$contextlist = provider::get_contexts_for_userid($user1->id);
|
||||||
$approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
|
$approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
|
||||||
|
@ -984,10 +977,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new summarise_text(
|
$action = new summarise_text(
|
||||||
|
@ -1009,8 +1000,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
$contextlist = provider::get_contexts_for_userid($user1->id);
|
$contextlist = provider::get_contexts_for_userid($user1->id);
|
||||||
$approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
|
$approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
|
||||||
|
@ -1065,6 +1056,7 @@ final class provider_test extends \advanced_testcase {
|
||||||
* Test get_users_in_context() for generate text.
|
* Test get_users_in_context() for generate text.
|
||||||
*/
|
*/
|
||||||
public function test_get_users_in_context_for_generate_text(): void {
|
public function test_get_users_in_context_for_generate_text(): void {
|
||||||
|
global $DB;
|
||||||
$user1 = $this->getDataGenerator()->create_user();
|
$user1 = $this->getDataGenerator()->create_user();
|
||||||
$user2 = $this->getDataGenerator()->create_user();
|
$user2 = $this->getDataGenerator()->create_user();
|
||||||
$course1 = $this->getDataGenerator()->create_course();
|
$course1 = $this->getDataGenerator()->create_course();
|
||||||
|
@ -1093,10 +1085,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_text(
|
$action = new generate_text(
|
||||||
|
@ -1118,8 +1108,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
// The user list for course1context should return user1.
|
// The user list for course1context should return user1.
|
||||||
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
||||||
|
@ -1168,10 +1158,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_image(
|
$action = new generate_image(
|
||||||
|
@ -1193,8 +1181,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
// The user list for course1context should return user1.
|
// The user list for course1context should return user1.
|
||||||
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
||||||
|
@ -1215,7 +1203,6 @@ final class provider_test extends \advanced_testcase {
|
||||||
* Test get_users_in_context() for summarise text.
|
* Test get_users_in_context() for summarise text.
|
||||||
*/
|
*/
|
||||||
public function test_get_users_in_context_for_summarise_text(): void {
|
public function test_get_users_in_context_for_summarise_text(): void {
|
||||||
global $DB;
|
|
||||||
$user1 = $this->getDataGenerator()->create_user();
|
$user1 = $this->getDataGenerator()->create_user();
|
||||||
$user2 = $this->getDataGenerator()->create_user();
|
$user2 = $this->getDataGenerator()->create_user();
|
||||||
$course1 = $this->getDataGenerator()->create_course();
|
$course1 = $this->getDataGenerator()->create_course();
|
||||||
|
@ -1244,10 +1231,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new summarise_text(
|
$action = new summarise_text(
|
||||||
|
@ -1269,8 +1254,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$method->invoke($manager, $provider, $action, $actionresponse);
|
$method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
// The user list for course1context should return user1.
|
// The user list for course1context should return user1.
|
||||||
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
||||||
|
@ -1351,10 +1336,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_text(
|
$action = new generate_text(
|
||||||
|
@ -1376,8 +1359,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
||||||
provider::get_users_in_context($userlist);
|
provider::get_users_in_context($userlist);
|
||||||
|
@ -1433,10 +1416,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new generate_image(
|
$action = new generate_image(
|
||||||
|
@ -1458,8 +1439,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
||||||
provider::get_users_in_context($userlist);
|
provider::get_users_in_context($userlist);
|
||||||
|
@ -1512,10 +1493,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
|
|
||||||
$provider = new \aiprovider_openai\provider();
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$manager = new manager();
|
$storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
|
||||||
$storeresult1 = $method->invoke($manager, $provider, $action, $actionresponse);
|
|
||||||
|
|
||||||
$clock->bump(2);
|
$clock->bump(2);
|
||||||
$action = new summarise_text(
|
$action = new summarise_text(
|
||||||
|
@ -1537,8 +1516,8 @@ final class provider_test extends \advanced_testcase {
|
||||||
success: true,
|
success: true,
|
||||||
);
|
);
|
||||||
$actionresponse->set_response_data($body);
|
$actionresponse->set_response_data($body);
|
||||||
$method = new \ReflectionMethod($manager, 'store_action_result');
|
$method = new \ReflectionMethod($this->manager, 'store_action_result');
|
||||||
$storeresult2 = $method->invoke($manager, $provider, $action, $actionresponse);
|
$storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
|
||||||
|
|
||||||
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
$userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
|
||||||
provider::get_users_in_context($userlist);
|
provider::get_users_in_context($userlist);
|
||||||
|
@ -1565,26 +1544,51 @@ final class provider_test extends \advanced_testcase {
|
||||||
* Test get_name.
|
* Test get_name.
|
||||||
*/
|
*/
|
||||||
public function test_get_name(): void {
|
public function test_get_name(): void {
|
||||||
$provider = new \aiprovider_openai\provider();
|
$this->assertEquals(get_string('pluginname', 'aiprovider_openai'), $this->provider->get_name());
|
||||||
|
|
||||||
$this->assertEquals(get_string('pluginname', 'aiprovider_openai'), $provider->get_name());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the is_request_allowed method of the provider abstract class.
|
* Test the is_request_allowed method of the provider abstract class.
|
||||||
*/
|
*/
|
||||||
public function test_is_request_allowed(): void {
|
public function test_is_request_allowed(): void {
|
||||||
|
// Create action.
|
||||||
|
$action1 = new summarise_text(
|
||||||
|
contextid: 1,
|
||||||
|
userid: 1,
|
||||||
|
prompttext: 'This is a test prompt 1',
|
||||||
|
);
|
||||||
|
$action2 = new summarise_text(
|
||||||
|
contextid: 1,
|
||||||
|
userid: 2,
|
||||||
|
prompttext: 'This is a test prompt 1',
|
||||||
|
);
|
||||||
|
|
||||||
$stub = $this->getMockBuilder(\core_ai\provider::class)
|
// Create the provider instance.
|
||||||
->onlyMethods(['is_request_allowed'])
|
$config = [
|
||||||
->getMockForAbstractClass();
|
'enableuserratelimit' => true,
|
||||||
|
'userratelimit' => 3,
|
||||||
|
'enableglobalratelimit' => true,
|
||||||
|
'globalratelimit' => 5,
|
||||||
|
];
|
||||||
|
$provider = $this->manager->create_provider_instance(
|
||||||
|
classname: '\aiprovider_openai\provider',
|
||||||
|
name: 'dummy',
|
||||||
|
config: $config,
|
||||||
|
);
|
||||||
|
// Make 3 requests for the first user, all should be allowed.
|
||||||
|
for ($i = 0; $i < 3; $i++) {
|
||||||
|
$this->assertTrue($provider->is_request_allowed($action1));
|
||||||
|
}
|
||||||
|
|
||||||
$stub->expects($this->once())
|
// The 4th request should be denied.
|
||||||
->method('is_request_allowed')
|
$this->assertFalse($provider->is_request_allowed($action1)['success']);
|
||||||
->willReturn(true);
|
|
||||||
|
|
||||||
$actionmock = $this->createMock(\core_ai\aiactions\base::class);
|
// Make 2 requests for the second user, all should be allowed.
|
||||||
|
for ($i = 0; $i < 2; $i++) {
|
||||||
|
$this->assertTrue($provider->is_request_allowed($action2));
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertTrue($stub->is_request_allowed($actionmock));
|
// THe final request should be denied.
|
||||||
|
$this->assertFalse($provider->is_request_allowed($action2)['success']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ $string['action_translate_text'] = 'Translate text';
|
||||||
$string['action_translate_text_desc'] = 'Translate provided text from one language to another.';
|
$string['action_translate_text_desc'] = 'Translate provided text from one language to another.';
|
||||||
$string['actionsettingprovider'] = '{$a} action settings';
|
$string['actionsettingprovider'] = '{$a} action settings';
|
||||||
$string['actionsettingprovider_desc'] = 'These settings control how the {$a->providername} performs the action {$a->actionname}.';
|
$string['actionsettingprovider_desc'] = 'These settings control how the {$a->providername} performs the action {$a->actionname}.';
|
||||||
|
$string['actionsettings'] = 'Action settings';
|
||||||
|
$string['actionsettings_desc'] = 'These settings control how the AI actions for this provider instance.';
|
||||||
$string['ai'] = 'AI';
|
$string['ai'] = 'AI';
|
||||||
$string['aiactionregister'] = 'AI action register';
|
$string['aiactionregister'] = 'AI action register';
|
||||||
$string['aiplacements'] = 'AI placements';
|
$string['aiplacements'] = 'AI placements';
|
||||||
|
@ -61,10 +63,20 @@ $string['availableplacements_desc'] = 'Placements define how and where AI action
|
||||||
$string['availableproviders'] = 'Manage the AI providers connected to your LMS';
|
$string['availableproviders'] = 'Manage the AI providers connected to your LMS';
|
||||||
$string['availableproviders_desc'] = 'AI providers add AI functionality to your site through \'actions\' like text summarisation or image generation.<br/>
|
$string['availableproviders_desc'] = 'AI providers add AI functionality to your site through \'actions\' like text summarisation or image generation.<br/>
|
||||||
You can manage the actions for each provider in their settings.';
|
You can manage the actions for each provider in their settings.';
|
||||||
|
$string['btninstancecreate'] = 'Create instance';
|
||||||
|
$string['btninstanceupdate'] = 'Update instance';
|
||||||
$string['completiontokens'] = 'Completion tokens';
|
$string['completiontokens'] = 'Completion tokens';
|
||||||
|
$string['configureprovider'] = 'Configure provider instance';
|
||||||
$string['contentwatermark'] = 'Generated by AI';
|
$string['contentwatermark'] = 'Generated by AI';
|
||||||
|
$string['createnewprovider'] = 'Create a new provider instance';
|
||||||
$string['dateaccepted'] = 'Date accepted';
|
$string['dateaccepted'] = 'Date accepted';
|
||||||
$string['declineaipolicy'] = 'Decline';
|
$string['declineaipolicy'] = 'Decline';
|
||||||
|
$string['enableglobalratelimit'] = 'Set site-wide rate limit';
|
||||||
|
$string['enableglobalratelimit_help'] = 'Limit the number of requests that the OpenAI API provider can receive across the entire site every hour.';
|
||||||
|
$string['enableuserratelimit'] = 'Set user rate limit';
|
||||||
|
$string['enableuserratelimit_help'] = 'Limit the number of requests each user can make to the OpenAI API provider every hour.';
|
||||||
|
$string['globalratelimit'] = 'Maximum number of site-wide requests';
|
||||||
|
$string['globalratelimit_help'] = 'The number of site-wide requests allowed per hour.';
|
||||||
$string['manageaiplacements'] = 'Manage AI placements';
|
$string['manageaiplacements'] = 'Manage AI placements';
|
||||||
$string['manageaiproviders'] = 'Manage AI providers';
|
$string['manageaiproviders'] = 'Manage AI providers';
|
||||||
$string['noproviders'] = 'This action is unavailable. No <a href="{$a}">AI providers</a> are configured for this action.';
|
$string['noproviders'] = 'This action is unavailable. No <a href="{$a}">AI providers</a> are configured for this action.';
|
||||||
|
@ -112,8 +124,18 @@ $string['prompttokens'] = 'Prompt tokens';
|
||||||
$string['provider'] = 'Provider';
|
$string['provider'] = 'Provider';
|
||||||
$string['provideractionsettings'] = 'Actions';
|
$string['provideractionsettings'] = 'Actions';
|
||||||
$string['provideractionsettings_desc'] = 'Choose and configure the actions that the {$a} can perform on your site.';
|
$string['provideractionsettings_desc'] = 'Choose and configure the actions that the {$a} can perform on your site.';
|
||||||
|
$string['providerinstanceactionupdated'] = '{$a} action settings updated';
|
||||||
|
$string['providerinstancecreated'] = '{$a} AI Provider instance created';
|
||||||
|
$string['providerinstancedelete'] = 'Delete AI Provider instance';
|
||||||
|
$string['providerinstancedeleteconfirm'] = 'You are about to delete the AI Provider instance: "{$a->name} ({$a->provider})". Are you sure?';
|
||||||
|
$string['providerinstancedeleted'] = '{$a} AI Provider instance deleted';
|
||||||
|
$string['providerinstancedeletefailed'] = 'Cannot delete the {$a} AI Provider instance. The provider is either in use or there\'s a database issue. Check if the private is active or contact your database administrator for help.';
|
||||||
|
$string['providerinstancedisablefailed'] = 'Cannot disable the AI Provider instance. The provider is either in use or there\'s a database issue. Check if the provider is active or contact your database administrator for help.';
|
||||||
|
$string['providerinstanceupdated'] = '{$a} AI Provider instance updated';
|
||||||
|
$string['providername'] = 'Name for instance';
|
||||||
$string['providers'] = 'Providers';
|
$string['providers'] = 'Providers';
|
||||||
$string['providersettings'] = 'Settings';
|
$string['providersettings'] = 'Settings';
|
||||||
|
$string['providertype'] = 'Choose AI Provider plugin';
|
||||||
$string['timegenerated'] = 'Time generated';
|
$string['timegenerated'] = 'Time generated';
|
||||||
$string['unknownvalue'] = '—';
|
$string['unknownvalue'] = '—';
|
||||||
$string['userpolicy'] = '<h4><strong>Welcome to the new AI feature!</strong></h4>
|
$string['userpolicy'] = '<h4><strong>Welcome to the new AI feature!</strong></h4>
|
||||||
|
@ -124,3 +146,5 @@ $string['userpolicy'] = '<h4><strong>Welcome to the new AI feature!</strong></h4
|
||||||
<p>This AI feature uses external Large Language Models (LLM). If you use this feature, any information or personal data you share will be handled according to the privacy policy of those LLMs. We recommend that you read their privacy policy to understand how they will handle your data. Additionally, a record of your interactions with the AI features may be saved in this site.</p>
|
<p>This AI feature uses external Large Language Models (LLM). If you use this feature, any information or personal data you share will be handled according to the privacy policy of those LLMs. We recommend that you read their privacy policy to understand how they will handle your data. Additionally, a record of your interactions with the AI features may be saved in this site.</p>
|
||||||
<p>If you have questions about how your data is processed, please check with your teachers or learning organisation.</p>
|
<p>If you have questions about how your data is processed, please check with your teachers or learning organisation.</p>
|
||||||
<p>By continuing, you acknowledge that you understand and agree to this policy.</p>';
|
<p>By continuing, you acknowledge that you understand and agree to this policy.</p>';
|
||||||
|
$string['userratelimit'] = 'Maximum number of requests per user';
|
||||||
|
$string['userratelimit_help'] = 'The number of requests allowed per hour, per user.';
|
||||||
|
|
|
@ -148,3 +148,35 @@ advancedelement,core_form
|
||||||
sitecommnews,core_hub
|
sitecommnews,core_hub
|
||||||
sitecommnews_help,core_hub
|
sitecommnews_help,core_hub
|
||||||
sitecommnewsyes,core_hub
|
sitecommnewsyes,core_hub
|
||||||
|
action_apiversion,aiprovider_azureai
|
||||||
|
action_deployment,aiprovider_azureai
|
||||||
|
action_deployment_desc,aiprovider_azureai
|
||||||
|
action_systeminstruction,aiprovider_azureai
|
||||||
|
action_systeminstruction_desc,aiprovider_azureai
|
||||||
|
apikey_desc,aiprovider_azureai
|
||||||
|
deployment,aiprovider_azureai
|
||||||
|
deployment_desc,aiprovider_azureai
|
||||||
|
enableglobalratelimit,aiprovider_azureai
|
||||||
|
enableglobalratelimit_desc,aiprovider_azureai
|
||||||
|
enableuserratelimit,aiprovider_azureai
|
||||||
|
enableuserratelimit_desc,aiprovider_azureai
|
||||||
|
endpoint_desc,aiprovider_azureai
|
||||||
|
globalratelimit,aiprovider_azureai
|
||||||
|
globalratelimit_desc,aiprovider_azureai
|
||||||
|
userratelimit,aiprovider_azureai
|
||||||
|
userratelimit_desc,aiprovider_azureai
|
||||||
|
action:generate_image:model_desc,aiprovider_openai
|
||||||
|
action:generate_text:model_desc,aiprovider_openai
|
||||||
|
action:generate_text:systeminstruction_desc,aiprovider_openai
|
||||||
|
action:summarise_text:model_desc,aiprovider_openai
|
||||||
|
action:summarise_text:systeminstruction_desc,aiprovider_openai
|
||||||
|
apikey_desc,aiprovider_openai
|
||||||
|
enableglobalratelimit,aiprovider_openai
|
||||||
|
enableglobalratelimit_desc,aiprovider_openai
|
||||||
|
enableuserratelimit,aiprovider_openai
|
||||||
|
enableuserratelimit_desc,aiprovider_openai
|
||||||
|
globalratelimit,aiprovider_openai
|
||||||
|
globalratelimit_desc,aiprovider_openai
|
||||||
|
orgid_desc,aiprovider_openai
|
||||||
|
userratelimit,aiprovider_openai
|
||||||
|
userratelimit_desc,aiprovider_openai
|
||||||
|
|
|
@ -228,7 +228,7 @@ abstract class behat_generator_base {
|
||||||
foreach ($entityinfo['required'] as $requiredfield) {
|
foreach ($entityinfo['required'] as $requiredfield) {
|
||||||
if (!isset($elementdata[$requiredfield])) {
|
if (!isset($elementdata[$requiredfield])) {
|
||||||
throw new Exception($this->name_for_errors($generatortype) .
|
throw new Exception($this->name_for_errors($generatortype) .
|
||||||
' requires the field ' . $requiredfield . ' to be specified');
|
' requires the field "' . $requiredfield . '" to be specified');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,161 +27,24 @@ use moodle_url;
|
||||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
class aiprovider extends base {
|
class aiprovider extends base {
|
||||||
/**
|
#[\Override]
|
||||||
* 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 {
|
public function is_uninstall_allowed(): bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[\Override]
|
||||||
* 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 {
|
public function get_settings_section_name(): string {
|
||||||
return $this->type . '_' . $this->name;
|
return $this->type . '_' . $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[\Override]
|
||||||
* 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: true,
|
|
||||||
);
|
|
||||||
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.
|
|
||||||
$providerclass = "\\{$section}\\provider";
|
|
||||||
$provider = new $providerclass();
|
|
||||||
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', $provider->get_name())));
|
|
||||||
// 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);
|
|
||||||
// Load any action settings for this provider.
|
|
||||||
$actionlist = $provider->get_action_list();
|
|
||||||
foreach ($actionlist as $action) {
|
|
||||||
$actionsettings = $provider->get_action_settings($action, $ADMIN, $section, $hassiteconfig);
|
|
||||||
if (!empty($actionsettings)) {
|
|
||||||
$actionname = substr($action, (strrpos($action, '\\') + 1));
|
|
||||||
$settings = new \admin_settingpage($section . '_' . $actionname, $action::get_name(), 'moodle/site:config', true);
|
|
||||||
$descplaceholder = [
|
|
||||||
'providername' => $provider->get_name(),
|
|
||||||
'actionname' => $action::get_name(),
|
|
||||||
];
|
|
||||||
$setting = new \admin_setting_heading("{$section}_actions/heading",
|
|
||||||
new \lang_string('actionsettingprovider', 'core_ai', $provider->get_name()),
|
|
||||||
new \lang_string('actionsettingprovider_desc', 'core_ai', $descplaceholder));
|
|
||||||
$settings->add($setting);
|
|
||||||
foreach ($actionsettings as $setting) {
|
|
||||||
$settings->add($setting);
|
|
||||||
}
|
|
||||||
$ADMIN->add('root', $settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return URL used for management of plugins of this type.
|
|
||||||
*
|
|
||||||
* @return moodle_url
|
|
||||||
*/
|
|
||||||
public static function get_manage_url(): moodle_url {
|
public static function get_manage_url(): moodle_url {
|
||||||
return new moodle_url('/admin/settings.php', [
|
return new moodle_url('/admin/settings.php', [
|
||||||
'section' => 'aiprovider',
|
'section' => 'aiprovider',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#[\Override]
|
||||||
* 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 {
|
public static function get_enabled_plugins(): ?array {
|
||||||
$pluginmanager = core_plugin_manager::instance();
|
$pluginmanager = core_plugin_manager::instance();
|
||||||
$plugins = $pluginmanager->get_installed_plugins('aiprovider');
|
$plugins = $pluginmanager->get_installed_plugins('aiprovider');
|
||||||
|
@ -195,21 +58,11 @@ class aiprovider extends base {
|
||||||
// Filter to return only enabled plugins.
|
// Filter to return only enabled plugins.
|
||||||
$enabled = [];
|
$enabled = [];
|
||||||
foreach ($plugins as $plugin) {
|
foreach ($plugins as $plugin) {
|
||||||
if (self::is_plugin_enabled($plugin)) {
|
$disabled = get_config('aiprovider_' . $plugin, 'disabled');
|
||||||
|
if (empty($disabled)) {
|
||||||
$enabled[$plugin] = $plugin;
|
$enabled[$plugin] = $plugin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $enabled;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4879,6 +4879,22 @@
|
||||||
<INDEX NAME="uid_index" UNIQUE="false" FIELDS="idnumber"/>
|
<INDEX NAME="uid_index" UNIQUE="false" FIELDS="idnumber"/>
|
||||||
</INDEXES>
|
</INDEXES>
|
||||||
</TABLE>
|
</TABLE>
|
||||||
|
<TABLE NAME="ai_providers" COMMENT="AI provider instances">
|
||||||
|
<FIELDS>
|
||||||
|
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||||
|
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The human readbale name of the provider"/>
|
||||||
|
<FIELD NAME="provider" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The provider class name this provider is an instance of"/>
|
||||||
|
<FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Is this plugin enabled"/>
|
||||||
|
<FIELD NAME="config" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Provider specific config in JSON format"/>
|
||||||
|
<FIELD NAME="actionconfig" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Stores instance specific action configuration in JSON format."/>
|
||||||
|
</FIELDS>
|
||||||
|
<KEYS>
|
||||||
|
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||||
|
</KEYS>
|
||||||
|
<INDEXES>
|
||||||
|
<INDEX NAME="provider" UNIQUE="false" FIELDS="provider" COMMENT="Index on provider class"/>
|
||||||
|
</INDEXES>
|
||||||
|
</TABLE>
|
||||||
<TABLE NAME="ai_policy_register" COMMENT="Register of users who have accepted this sites AI usage policy">
|
<TABLE NAME="ai_policy_register" COMMENT="Register of users who have accepted this sites AI usage policy">
|
||||||
<FIELDS>
|
<FIELDS>
|
||||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||||
|
|
|
@ -3260,6 +3260,12 @@ $functions = array(
|
||||||
'ajax' => true,
|
'ajax' => true,
|
||||||
'readonlysession' => true,
|
'readonlysession' => true,
|
||||||
],
|
],
|
||||||
|
'core_ai_delete_provider_instance' => [
|
||||||
|
'classname' => \core_ai\external\delete_provider_instance::class,
|
||||||
|
'description' => 'Delete a provider instance',
|
||||||
|
'type' => 'write',
|
||||||
|
'ajax' => true,
|
||||||
|
],
|
||||||
'core_ai_set_policy_status' => [
|
'core_ai_set_policy_status' => [
|
||||||
'classname' => 'core_ai\external\set_policy_status',
|
'classname' => 'core_ai\external\set_policy_status',
|
||||||
'description' => 'Set a users AI policy acceptance',
|
'description' => 'Set a users AI policy acceptance',
|
||||||
|
@ -3280,6 +3286,12 @@ $functions = array(
|
||||||
'type' => 'write',
|
'type' => 'write',
|
||||||
'ajax' => true,
|
'ajax' => true,
|
||||||
],
|
],
|
||||||
|
'core_ai_set_provider_status' => [
|
||||||
|
'classname' => \core_ai\external\set_provider_status::class,
|
||||||
|
'description' => 'Set a providers status',
|
||||||
|
'type' => 'write',
|
||||||
|
'ajax' => true,
|
||||||
|
],
|
||||||
'core_sms_set_gateway_status' => [
|
'core_sms_set_gateway_status' => [
|
||||||
'classname' => 'core_sms\external\sms_gateway_status',
|
'classname' => 'core_sms\external\sms_gateway_status',
|
||||||
'description' => 'Set the sms gateway status',
|
'description' => 'Set the sms gateway status',
|
||||||
|
|
|
@ -1115,7 +1115,6 @@ function xmldb_main_upgrade($oldversion) {
|
||||||
if (file_exists($CFG->dirroot . '/h5p/h5plib/v127/version.php')) {
|
if (file_exists($CFG->dirroot . '/h5p/h5plib/v127/version.php')) {
|
||||||
set_config('h5plibraryhandler', 'h5plib_v127');
|
set_config('h5plibraryhandler', 'h5plib_v127');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main savepoint reached.
|
// Main savepoint reached.
|
||||||
upgrade_main_savepoint(true, 2024092600.00);
|
upgrade_main_savepoint(true, 2024092600.00);
|
||||||
}
|
}
|
||||||
|
@ -1340,6 +1339,35 @@ function xmldb_main_upgrade($oldversion) {
|
||||||
// Main savepoint reached.
|
// Main savepoint reached.
|
||||||
upgrade_main_savepoint(true, 2024121800.00);
|
upgrade_main_savepoint(true, 2024121800.00);
|
||||||
}
|
}
|
||||||
|
if ($oldversion < 2024121900.00) {
|
||||||
|
// Define table ai_providers to be created.
|
||||||
|
$table = new xmldb_table('ai_providers');
|
||||||
|
|
||||||
|
// Adding fields to table ai_providers.
|
||||||
|
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||||
|
$table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||||
|
$table->add_field('provider', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||||
|
$table->add_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1');
|
||||||
|
$table->add_field('config', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
|
||||||
|
$table->add_field('actionconfig', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||||
|
|
||||||
|
// Adding keys to table ai_provider.
|
||||||
|
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||||
|
|
||||||
|
// Adding indexes to table ai_provider.
|
||||||
|
$table->add_index('provider', XMLDB_INDEX_NOTUNIQUE, ['provider']);
|
||||||
|
|
||||||
|
// Conditionally launch create table for ai_provider.
|
||||||
|
if (!$dbman->table_exists($table)) {
|
||||||
|
$dbman->create_table($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the instance table exists, migrate the existing providers.
|
||||||
|
upgrade_convert_ai_providers_to_instances();
|
||||||
|
|
||||||
|
// Main savepoint reached.
|
||||||
|
upgrade_main_savepoint(true, 2024121900.00);
|
||||||
|
}
|
||||||
|
|
||||||
if ($oldversion < 2024121900.01) {
|
if ($oldversion < 2024121900.01) {
|
||||||
// Enable mod_subsection unless 'keepsubsectiondisabled' is set.
|
// Enable mod_subsection unless 'keepsubsectiondisabled' is set.
|
||||||
|
|
|
@ -206,7 +206,7 @@ function upgrade_calculated_grade_items($courseid = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($possiblecourseids)) {
|
if (!empty($possiblecourseids)) {
|
||||||
list($sql, $params) = $DB->get_in_or_equal($possiblecourseids);
|
[$sql, $params] = $DB->get_in_or_equal($possiblecourseids);
|
||||||
// A calculated grade item grade min != 0 and grade max != 100 and the course setting is set to
|
// A calculated grade item grade min != 0 and grade max != 100 and the course setting is set to
|
||||||
// "Initial min and max grades".
|
// "Initial min and max grades".
|
||||||
$coursesql = "SELECT DISTINCT courseid
|
$coursesql = "SELECT DISTINCT courseid
|
||||||
|
@ -238,7 +238,7 @@ function upgrade_calculated_grade_items($courseid = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($categoryids)) {
|
if (!empty($categoryids)) {
|
||||||
list($sql, $params) = $DB->get_in_or_equal($categoryids);
|
[$sql, $params] = $DB->get_in_or_equal($categoryids);
|
||||||
// A category with a calculation where the raw grade min and the raw grade max don't match the grade min and grade max
|
// A category with a calculation where the raw grade min and the raw grade max don't match the grade min and grade max
|
||||||
// for the category.
|
// for the category.
|
||||||
$coursesql = "SELECT DISTINCT gi.courseid
|
$coursesql = "SELECT DISTINCT gi.courseid
|
||||||
|
@ -629,7 +629,7 @@ function upgrade_calendar_site_status(bool $output = true): bool {
|
||||||
];
|
];
|
||||||
|
|
||||||
$targetsteps = array_merge(array_values($badsteps), array_values( $fixsteps));
|
$targetsteps = array_merge(array_values($badsteps), array_values( $fixsteps));
|
||||||
list($insql, $inparams) = $DB->get_in_or_equal($targetsteps);
|
[$insql, $inparams] = $DB->get_in_or_equal($targetsteps);
|
||||||
$foundsteps = $DB->get_fieldset_sql("
|
$foundsteps = $DB->get_fieldset_sql("
|
||||||
SELECT DISTINCT version
|
SELECT DISTINCT version
|
||||||
FROM {upgrade_log}
|
FROM {upgrade_log}
|
||||||
|
@ -1898,3 +1898,131 @@ function upgrade_store_relative_url_sitehomepage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade script to convert existing AI providers to provider instances.
|
||||||
|
*/
|
||||||
|
function upgrade_convert_ai_providers_to_instances() {
|
||||||
|
global $DB;
|
||||||
|
// Start with the azureai provider.
|
||||||
|
// Only migrate the provider if it is enabled.
|
||||||
|
$azureaiconfig = get_config('aiprovider_azureai');
|
||||||
|
if (!empty($azureaiconfig->enabled) || !empty($azureaiconfig->apikey)) {
|
||||||
|
// Create the instance config. We don't want everything from the provider config.
|
||||||
|
$instanceconfig = [
|
||||||
|
'aiprovider' => \aiprovider_azureai\provider::class,
|
||||||
|
'name' => get_string('pluginname', 'aiprovider_azureai'),
|
||||||
|
'apikey' => $azureaiconfig->apikey ?? '',
|
||||||
|
'endpoint' => $azureaiconfig->endpoint ?? '',
|
||||||
|
'enableglobalratelimit' => $azureaiconfig->enableglobalratelimit ?? 0,
|
||||||
|
'globalratelimit' => $azureaiconfig->globalratelimit ?? 100,
|
||||||
|
'enableuserratelimit' => $azureaiconfig->enableuserratelimit ?? 0,
|
||||||
|
'userratelimit' => $azureaiconfig->userratelimit ?? 10,
|
||||||
|
];
|
||||||
|
$actionconfig = [
|
||||||
|
'core_ai\aiactions\generate_text' => [
|
||||||
|
'enabled' => $azureaiconfig->generate_text ?? true,
|
||||||
|
'settings' => [
|
||||||
|
'deployment' => $azureaiconfig->action_generate_text_deployment ?? '',
|
||||||
|
'apiversion' => $azureaiconfig->action_generate_text_apiversion ?? '2024-06-01',
|
||||||
|
'systeminstruction' => $azureaiconfig->action_generate_text_systeminstruction
|
||||||
|
?? get_string('action_generate_text_instruction', 'core_ai'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'core_ai\aiactions\generate_image' => [
|
||||||
|
'enabled' => $azureaiconfig->generate_image ?? true,
|
||||||
|
'settings' => [
|
||||||
|
'deployment' => $azureaiconfig->action_generate_image_deployment ?? '',
|
||||||
|
'apiversion' => $azureaiconfig->action_generate_image_apiversion ?? '2024-06-01',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'core_ai\aiactions\summarise_text' => [
|
||||||
|
'enabled' => $azureaiconfig->summarise_text ?? true,
|
||||||
|
'settings' => [
|
||||||
|
'deployment' => $azureaiconfig->action_summarise_text_deployment ?? '',
|
||||||
|
'apiversion' => $azureaiconfig->action_summarise_text_apiversion ?? '2024-06-01',
|
||||||
|
'systeminstruction' => $azureaiconfig->action_generate_text_systeminstruction
|
||||||
|
?? get_string('action_summarise_text_instruction', 'core_ai'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Because of the upgrade code restrictions we insert directly into the database and don't use the AI manager class.
|
||||||
|
$record = new stdClass();
|
||||||
|
$record->name = get_string('pluginname', 'aiprovider_azureai');
|
||||||
|
$record->provider = \aiprovider_azureai\provider::class;
|
||||||
|
$record->enabled = $azureaiconfig->enabled ?? false;
|
||||||
|
$record->config = json_encode($instanceconfig);
|
||||||
|
$record->actionconfig = json_encode($actionconfig);
|
||||||
|
|
||||||
|
$DB->insert_record('ai_providers', $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now do the same for the openai provider.
|
||||||
|
$openaiconfig = get_config('aiprovider_openai');
|
||||||
|
if (!empty($openaiconfig->enabled) || !empty($openaiconfig->apikey)) {
|
||||||
|
// Create the instance config. We don't want everything from the provider config.
|
||||||
|
$instanceconfig = [
|
||||||
|
'aiprovider' => \aiprovider_openai\provider::class,
|
||||||
|
'name' => get_string('pluginname', 'aiprovider_openai'),
|
||||||
|
'apikey' => $openaiconfig->apikey ?? '',
|
||||||
|
'orgid' => $openaiconfig->orgid ?? '',
|
||||||
|
'enableglobalratelimit' => $openaiconfig->enableglobalratelimit ?? 0,
|
||||||
|
'globalratelimit' => $openaiconfig->globalratelimit ?? 100,
|
||||||
|
'enableuserratelimit' => $openaiconfig->enableuserratelimit ?? 0,
|
||||||
|
'userratelimit' => $openaiconfig->userratelimit ?? 10,
|
||||||
|
];
|
||||||
|
$actionconfig = [
|
||||||
|
'core_ai\aiactions\generate_text' => [
|
||||||
|
'enabled' => $openaiconfig->generate_text ?? true,
|
||||||
|
'settings' => [
|
||||||
|
'model' => $openaiconfig->action_generate_text_model ?? 'gpt-4o',
|
||||||
|
'endpoint' => $openaiconfig->action_generate_text_endpoint ?? 'https://api.openai.com/v1/chat/completions',
|
||||||
|
'systeminstruction' => $openaiconfig->action_generate_text_systeminstruction
|
||||||
|
?? get_string('action_generate_text_instruction', 'core_ai'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'core_ai\aiactions\generate_image' => [
|
||||||
|
'enabled' => $openaiconfig->generate_image ?? true,
|
||||||
|
'settings' => [
|
||||||
|
'model' => $openaiconfig->action_generate_text_model ?? 'dall-e-3',
|
||||||
|
'endpoint' => $openaiconfig->action_generate_text_endpoint ?? 'https://api.openai.com/v1/images/generations',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'core_ai\aiactions\summarise_text' => [
|
||||||
|
'enabled' => $openaiconfig->summarise_text ?? true,
|
||||||
|
'settings' => [
|
||||||
|
'model' => $openaiconfig->action_generate_text_model ?? 'gpt-4o',
|
||||||
|
'endpoint' => $openaiconfig->action_generate_text_endpoint ?? 'https://api.openai.com/v1/chat/completions',
|
||||||
|
'systeminstruction' => $openaiconfig->action_generate_text_systeminstruction
|
||||||
|
?? get_string('action_summarise_text_instruction', 'core_ai'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$record = new stdClass();
|
||||||
|
$record->name = get_string('pluginname', 'aiprovider_openai');
|
||||||
|
$record->provider = \aiprovider_openai\provider::class;
|
||||||
|
$record->enabled = $openaiconfig->enabled ?? false;
|
||||||
|
$record->config = json_encode($instanceconfig);
|
||||||
|
$record->actionconfig = json_encode($actionconfig);
|
||||||
|
|
||||||
|
$DB->insert_record('ai_providers', $record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally remove the config settings from the plugin config table.
|
||||||
|
$azuresettings = ['enabled', 'apikey', 'endpoint', 'enableglobalratelimit', 'globalratelimit',
|
||||||
|
'enableuserratelimit', 'userratelimit', 'generate_text', 'action_generate_text_enabled', 'action_generate_text_deployment',
|
||||||
|
'action_generate_text_apiversion', 'action_generate_text_systeminstruction', 'generate_image',
|
||||||
|
'action_generate_image_enabled', 'action_generate_image_deployment', 'action_generate_image_apiversion',
|
||||||
|
'summarise_text', 'action_summarise_text_enabled', 'action_summarise_text_deployment', 'action_summarise_text_apiversion',
|
||||||
|
'action_summarise_text_systeminstruction'];
|
||||||
|
array_walk($azuresettings, static fn($setting) => unset_config($setting, 'aiprovider_azureai'));
|
||||||
|
$openaisettings = ['enabled', 'apikey', 'orgid', 'enableglobalratelimit', 'globalratelimit',
|
||||||
|
'enableuserratelimit', 'userratelimit', 'generate_text', 'action_generate_text_enabled', 'action_generate_text_model',
|
||||||
|
'action_generate_text_endpoint', 'action_generate_text_systeminstruction', 'generate_image',
|
||||||
|
'action_generate_image_enabled', 'action_generate_image_model', 'action_generate_image_endpoint',
|
||||||
|
'summarise_text', 'action_summarise_text_enabled', 'action_summarise_text_model', 'action_summarise_text_endpoint',
|
||||||
|
'action_summarise_text_systeminstruction'];
|
||||||
|
array_walk($openaisettings, static fn($setting) => unset_config($setting, 'aiprovider_openai'));
|
||||||
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ Feature: Generate image using AI
|
||||||
| capability | permission | role | contextlevel | reference |
|
| capability | permission | role | contextlevel | reference |
|
||||||
| aiplacement/editor:generate_text | Prohibit | user | System | |
|
| aiplacement/editor:generate_text | Prohibit | user | System | |
|
||||||
| aiplacement/editor:generate_image | Prohibit | custom2 | Course | C1 |
|
| aiplacement/editor:generate_image | Prohibit | custom2 | Course | C1 |
|
||||||
And I enable "openai" "aiprovider" plugin
|
And the following "ai providers" exist:
|
||||||
And the following config values are set as admin:
|
|provider | name | enabled | apikey | orgid |
|
||||||
| apikey | 123 | aiprovider_openai |
|
|aiprovider_openai | openai | 1 | 123 | abc |
|
||||||
And I enable "editor" "aiplacement" plugin
|
And I enable "editor" "aiplacement" plugin
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
|
@ -46,24 +46,26 @@ Feature: Generate image using AI
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Image generation using AI is not available if provider is not enabled
|
Scenario: Image generation using AI is not available if provider is not enabled
|
||||||
Given I disable "openai" "aiprovider" plugin
|
Given I "disable" the ai provider with name "openai"
|
||||||
When I am on the "PageName2" "page activity" page logged in as teacher1
|
When I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
|
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
|
||||||
And I enable "openai" "aiprovider" plugin
|
And I "enable" the ai provider with name "openai"
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate image" button should exist in the "Description" TinyMCE editor
|
And "AI generate image" button should exist in the "Description" TinyMCE editor
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Image generation using AI is not available if provider action is not enabled
|
Scenario: Image generation using AI is not available if provider action is not enabled
|
||||||
Given the following config values are set as admin:
|
Given I set the following action configuration for ai provider with name "openai":
|
||||||
| generate_image | | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| generate_image | 0 |
|
||||||
When I am on the "PageName2" "page activity" page logged in as teacher1
|
When I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
|
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
|
||||||
And the following config values are set as admin:
|
And I set the following action configuration for ai provider with name "openai":
|
||||||
| generate_image | 1 | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| generate_image | 1 |
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate image" button should exist in the "Description" TinyMCE editor
|
And "AI generate image" button should exist in the "Description" TinyMCE editor
|
||||||
|
@ -85,7 +87,9 @@ Feature: Generate image using AI
|
||||||
Scenario: Image generation using AI is not available if provider action is not enabled and placement action is enabled
|
Scenario: Image generation using AI is not available if provider action is not enabled and placement action is enabled
|
||||||
Given the following config values are set as admin:
|
Given the following config values are set as admin:
|
||||||
| generate_image | | aiplacement_editor |
|
| generate_image | | aiplacement_editor |
|
||||||
| generate_image | | aiprovider_openai |
|
And I set the following action configuration for ai provider with name "openai":
|
||||||
|
| action | enabled |
|
||||||
|
| generate_image | 0 |
|
||||||
When I am on the "PageName2" "page activity" page logged in as teacher1
|
When I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
|
Then "AI generate image" button should not exist in the "Description" TinyMCE editor
|
||||||
|
@ -94,8 +98,9 @@ Feature: Generate image using AI
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate image" button should not exist in the "Description" TinyMCE editor
|
And "AI generate image" button should not exist in the "Description" TinyMCE editor
|
||||||
And the following config values are set as admin:
|
And I set the following action configuration for ai provider with name "openai":
|
||||||
| generate_image | 1 | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| generate_image | 1 |
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate image" button should exist in the "Description" TinyMCE editor
|
And "AI generate image" button should exist in the "Description" TinyMCE editor
|
||||||
|
|
|
@ -28,11 +28,11 @@ Feature: Generate text using AI
|
||||||
| capability | permission | role | contextlevel | reference |
|
| capability | permission | role | contextlevel | reference |
|
||||||
| aiplacement/editor:generate_image | Prohibit | user | System | |
|
| aiplacement/editor:generate_image | Prohibit | user | System | |
|
||||||
| aiplacement/editor:generate_text | Prohibit | custom2 | Course | C1 |
|
| aiplacement/editor:generate_text | Prohibit | custom2 | Course | C1 |
|
||||||
And I log in as "admin"
|
And the following "ai providers" exist:
|
||||||
And I enable "openai" "aiprovider" plugin
|
|provider | name | enabled | apikey | orgid |
|
||||||
And the following config values are set as admin:
|
|aiprovider_openai | openai | 1 | 123 | abc |
|
||||||
| apikey | 123 | aiprovider_openai |
|
|
||||||
And I enable "editor" "aiplacement" plugin
|
And I enable "editor" "aiplacement" plugin
|
||||||
|
And I log in as "admin"
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Text generation using AI is not available if placement is not enabled
|
Scenario: Text generation using AI is not available if placement is not enabled
|
||||||
|
@ -47,24 +47,26 @@ Feature: Generate text using AI
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Text generation using AI is not available if provider is not enabled
|
Scenario: Text generation using AI is not available if provider is not enabled
|
||||||
Given I disable "openai" "aiprovider" plugin
|
Given I "disable" the ai provider with name "openai"
|
||||||
When I am on the "PageName2" "page activity" page logged in as teacher1
|
When I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
|
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
|
||||||
And I enable "openai" "aiprovider" plugin
|
And I "enable" the ai provider with name "openai"
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate text" button should exist in the "Description" TinyMCE editor
|
And "AI generate text" button should exist in the "Description" TinyMCE editor
|
||||||
|
|
||||||
@javascript
|
@javascript
|
||||||
Scenario: Text generation using AI is not available if provider action is not enabled
|
Scenario: Text generation using AI is not available if provider action is not enabled
|
||||||
Given the following config values are set as admin:
|
Given I set the following action configuration for ai provider with name "openai":
|
||||||
| generate_text | | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| generate_text | 0 |
|
||||||
When I am on the "PageName2" "page activity" page logged in as teacher1
|
When I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
|
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
|
||||||
And the following config values are set as admin:
|
And I set the following action configuration for ai provider with name "openai":
|
||||||
| generate_text | 1 | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| generate_text | 1 |
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate text" button should exist in the "Description" TinyMCE editor
|
And "AI generate text" button should exist in the "Description" TinyMCE editor
|
||||||
|
@ -86,7 +88,9 @@ Feature: Generate text using AI
|
||||||
Scenario: Text generation using AI is not available if provider action is not enabled and placement action is enabled
|
Scenario: Text generation using AI is not available if provider action is not enabled and placement action is enabled
|
||||||
Given the following config values are set as admin:
|
Given the following config values are set as admin:
|
||||||
| generate_text | | aiplacement_editor |
|
| generate_text | | aiplacement_editor |
|
||||||
| generate_text | | aiprovider_openai |
|
And I set the following action configuration for ai provider with name "openai":
|
||||||
|
| action | enabled |
|
||||||
|
| generate_text | 0 |
|
||||||
When I am on the "PageName2" "page activity" page logged in as teacher1
|
When I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
|
Then "AI generate text" button should not exist in the "Description" TinyMCE editor
|
||||||
|
@ -95,8 +99,9 @@ Feature: Generate text using AI
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate text" button should not exist in the "Description" TinyMCE editor
|
And "AI generate text" button should not exist in the "Description" TinyMCE editor
|
||||||
And the following config values are set as admin:
|
And I set the following action configuration for ai provider with name "openai":
|
||||||
| generate_text | 1 | aiprovider_openai |
|
| action | enabled |
|
||||||
|
| generate_text | 1 |
|
||||||
And I am on the "PageName2" "page activity" page logged in as teacher1
|
And I am on the "PageName2" "page activity" page logged in as teacher1
|
||||||
And I navigate to "Settings" in current page administration
|
And I navigate to "Settings" in current page administration
|
||||||
And "AI generate text" button should exist in the "Description" TinyMCE editor
|
And "AI generate text" button should exist in the "Description" TinyMCE editor
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue