MDL-83216 AI: Provider instances ordering
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
|
||||
/**
|
||||
* Web Service to control the order of a provider instance.
|
||||
*
|
||||
* @package core_ai
|
||||
* @category external
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class set_provider_order extends external_api {
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'plugin' => new external_value(PARAM_INT, ' The provider instance ID', VALUE_REQUIRED),
|
||||
'direction' => new external_value(PARAM_INT, 'The direction to move', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the provider instance order.
|
||||
*
|
||||
* @param int $providerid The provider instance ID
|
||||
* @param int $direction The direction to move the provider instance
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(
|
||||
int $providerid,
|
||||
int $direction,
|
||||
): array {
|
||||
[
|
||||
'plugin' => $providerid,
|
||||
'direction' => $direction,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'plugin' => $providerid,
|
||||
'direction' => $direction,
|
||||
]);
|
||||
|
||||
$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) {
|
||||
$manager->change_provider_order($providerid, $direction);
|
||||
}
|
||||
|
||||
$directionstring = $direction === \core\plugininfo\aiprovider::MOVE_UP
|
||||
? \core\plugininfo\aiprovider::UP
|
||||
: \core\plugininfo\aiprovider::DOWN;
|
||||
$message = get_string('providermoved' . $directionstring, 'ai', $aiprovider->name);
|
||||
$messagetype = \core\notification::SUCCESS;
|
||||
|
||||
\core\notification::add($message, $messagetype);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the return structure of the external service.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([]);
|
||||
}
|
||||
}
|
||||
+147
-5
@@ -19,7 +19,7 @@ namespace core_ai;
|
||||
use core\exception\coding_exception;
|
||||
use core_ai\aiactions\base;
|
||||
use core_ai\aiactions\responses;
|
||||
|
||||
use core\plugininfo\aiprovider as aiproviderplugin;
|
||||
/**
|
||||
* AI subsystem manager.
|
||||
*
|
||||
@@ -79,7 +79,7 @@ class manager {
|
||||
*/
|
||||
public function get_providers_for_actions(array $actions, bool $enabledonly = false): array {
|
||||
$providers = [];
|
||||
$instances = $this->get_provider_instances();
|
||||
$instances = $this->get_sorted_providers();
|
||||
foreach ($actions as $action) {
|
||||
$providers[$action] = [];
|
||||
foreach ($instances as $instance) {
|
||||
@@ -388,6 +388,11 @@ class manager {
|
||||
|
||||
$id = $this->db->insert_record('ai_providers', $provider->to_record());
|
||||
|
||||
// Ensure the provider instance order config gets updated if the provider is enabled.
|
||||
if ($enabled) {
|
||||
$this->update_provider_order($id, \core\plugininfo\aiprovider::ENABLE);
|
||||
}
|
||||
|
||||
return $provider->with(id: $id);
|
||||
}
|
||||
|
||||
@@ -410,7 +415,7 @@ class manager {
|
||||
* Get the provider records according to the filter.
|
||||
*
|
||||
* @param array|null $filter The filterable elements to get the records from.
|
||||
* @return \stdClass[]
|
||||
* @return array
|
||||
*/
|
||||
public function get_provider_records(?array $filter = null): array {
|
||||
return $this->db->get_records(
|
||||
@@ -428,7 +433,6 @@ class manager {
|
||||
*
|
||||
* @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).
|
||||
@@ -469,7 +473,6 @@ class manager {
|
||||
* @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,
|
||||
@@ -515,6 +518,7 @@ class manager {
|
||||
if (!$provider->enabled) {
|
||||
$provider = $provider->with(enabled: true);
|
||||
$this->db->update_record('ai_providers', $provider->to_record());
|
||||
$this->update_provider_order($provider->id, aiproviderplugin::ENABLE);
|
||||
}
|
||||
|
||||
return $provider;
|
||||
@@ -536,8 +540,146 @@ class manager {
|
||||
$provider = $provider->with(enabled: false);
|
||||
$this->db->update_record('ai_providers', $provider->to_record());
|
||||
}
|
||||
$this->update_provider_order($provider->id, aiproviderplugin::DISABLE);
|
||||
}
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts provider instances by configured order.
|
||||
*
|
||||
* @param array $unsorted of provider instance objects
|
||||
* @return array of provider instance objects
|
||||
*/
|
||||
public static function sort_providers_by_order(array $unsorted): array {
|
||||
$sorted = [];
|
||||
$orderarray = explode(',', get_config('core_ai', 'provider_order'));
|
||||
|
||||
foreach ($orderarray as $notused => $providerid) {
|
||||
foreach ($unsorted as $key => $provider) {
|
||||
if ($provider->id == $providerid) {
|
||||
$sorted[] = $provider;
|
||||
unset($unsorted[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($sorted, $unsorted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured ai providers from the manager.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sorted_providers(): array {
|
||||
$unsorted = $this->get_provider_instances();
|
||||
$orders = $this->sort_providers_by_order($unsorted);
|
||||
$sortedplugins = [];
|
||||
|
||||
foreach ($orders as $order) {
|
||||
$sortedplugins[$order->id] = $unsorted[$order->id];
|
||||
}
|
||||
|
||||
return $sortedplugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the order of the provider instance relative to other provider instances.
|
||||
*
|
||||
* When possible, the change will be stored into the config_log table, to let admins check when/who has modified it.
|
||||
*
|
||||
* @param int $providerid The provider ID.
|
||||
* @param int $direction The direction to move the provider instance. Negative numbers mean up, Positive mean down.
|
||||
* @return bool Whether the provider has been updated or not.
|
||||
*/
|
||||
public function change_provider_order(int $providerid, int $direction): bool {
|
||||
$activefactors = array_keys($this->get_sorted_providers());
|
||||
$key = array_search($providerid, $activefactors);
|
||||
|
||||
if ($key === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$movedown = ($direction === aiproviderplugin::MOVE_DOWN && $key < count($activefactors) - 1);
|
||||
$moveup = ($direction === aiproviderplugin::MOVE_UP && $key >= 1);
|
||||
if ($movedown || $moveup) {
|
||||
$this->update_provider_order($providerid, $direction);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the provider instance order configuration.
|
||||
*
|
||||
* @param int $providerid The provider ID.
|
||||
* @param string|int $action
|
||||
*
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public function update_provider_order(int $providerid, string|int $action): void {
|
||||
$order = explode(',', get_config('core_ai', 'provider_order'));
|
||||
$key = array_search($providerid, $order);
|
||||
|
||||
switch ($action) {
|
||||
case aiproviderplugin::MOVE_UP:
|
||||
if ($key >= 1) {
|
||||
$fsave = $order[$key];
|
||||
$order[$key] = $order[$key - 1];
|
||||
$order[$key - 1] = $fsave;
|
||||
}
|
||||
break;
|
||||
|
||||
case aiproviderplugin::MOVE_DOWN:
|
||||
if ($key < (count($order) - 1)) {
|
||||
$fsave = $order[$key];
|
||||
$order[$key] = $order[$key + 1];
|
||||
$order[$key + 1] = $fsave;
|
||||
}
|
||||
break;
|
||||
|
||||
case aiproviderplugin::ENABLE:
|
||||
if (!$key) {
|
||||
$order[] = $providerid;
|
||||
}
|
||||
break;
|
||||
|
||||
case aiproviderplugin::DISABLE:
|
||||
if ($key) {
|
||||
unset($order[$key]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$this->set_provider_config(['provider_order' => implode(',', $order)], 'core_ai');
|
||||
|
||||
\core\session\manager::gc(); // Remove stale sessions.
|
||||
\core_plugin_manager::reset_caches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets config variable for given provider instance.
|
||||
*
|
||||
* @param array $data The data to set.
|
||||
* @param string $plugin The plugin name.
|
||||
*
|
||||
* @return bool true or exception.
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public function set_provider_config(array $data, string $plugin): bool|dml_exception {
|
||||
$providerconf = get_config($plugin);
|
||||
foreach ($data as $key => $newvalue) {
|
||||
if (empty($providerconf->$key)) {
|
||||
add_to_config_log($key, null, $newvalue, $plugin);
|
||||
set_config($key, $newvalue, $plugin);
|
||||
} else if ($providerconf->$key != $newvalue) {
|
||||
add_to_config_log($key, $providerconf->$key, $newvalue, $plugin);
|
||||
set_config($key, $newvalue, $plugin);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use context_system;
|
||||
use core_table\dynamic as dynamic_table;
|
||||
use flexible_table;
|
||||
use moodle_url;
|
||||
use html_writer;
|
||||
|
||||
/**
|
||||
* Table to manage AI provider plugins.
|
||||
@@ -34,6 +35,9 @@ class aiprovider_management_table extends flexible_table implements dynamic_tabl
|
||||
*/
|
||||
protected array $aiproviders = [];
|
||||
|
||||
/** @var int The number of enabled provider instances. */
|
||||
protected int $enabledprovidercount = 0;
|
||||
|
||||
/**
|
||||
* Constructor for the AI provider table.
|
||||
*/
|
||||
@@ -46,6 +50,10 @@ class aiprovider_management_table extends flexible_table implements dynamic_tabl
|
||||
$this->setup();
|
||||
$tableclasses = $this->attributes['class'] . ' ' . $this->get_table_id();
|
||||
$this->set_attribute('class', $tableclasses);
|
||||
|
||||
$this->enabledprovidercount = count(array_filter($this->aiproviders, function ($provider) {
|
||||
return $provider->enabled;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,11 +143,7 @@ class aiprovider_management_table extends flexible_table implements dynamic_tabl
|
||||
* @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;
|
||||
return \core\di::get(\core_ai\manager::class)->get_sorted_providers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +170,7 @@ class aiprovider_management_table extends flexible_table implements dynamic_tabl
|
||||
'name' => get_string('name'),
|
||||
'provider' => get_string('provider', 'core_ai'),
|
||||
'enabled' => get_string('pluginenabled', 'core_plugin'),
|
||||
'order' => get_string('order', 'core'),
|
||||
'settings' => get_string('settings', 'core'),
|
||||
'delete' => get_string('delete'),
|
||||
];
|
||||
@@ -305,4 +310,112 @@ class aiprovider_management_table extends flexible_table implements dynamic_tabl
|
||||
];
|
||||
return $OUTPUT->render_from_template('core_ai/admin_delete_provider', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the web service method used to order provider instances.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function get_sortorder_service(): ?string {
|
||||
return 'core_ai_set_provider_order';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the HTML for the order column with up and down controls.
|
||||
*
|
||||
* @param \stdClass $row An object representing a row of data.
|
||||
* @return string The HTML string for the order controls, or an empty string if no controls are needed.
|
||||
*/
|
||||
protected function col_order(\stdClass $row): string {
|
||||
global $OUTPUT;
|
||||
|
||||
if (!$row->enabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->enabledprovidercount <= 1) {
|
||||
// There is only one row.
|
||||
return '';
|
||||
}
|
||||
|
||||
$hasup = true;
|
||||
$hasdown = true;
|
||||
|
||||
if (empty($this->currentrow)) {
|
||||
// This is the top row.
|
||||
$hasup = false;
|
||||
}
|
||||
|
||||
if ($this->currentrow === ($this->enabledprovidercount - 1)) {
|
||||
// This is the last row.
|
||||
$hasdown = false;
|
||||
}
|
||||
|
||||
$dataattributes = [
|
||||
'data-method' => $this->get_sortorder_service(),
|
||||
'data-action' => 'move',
|
||||
'data-plugin' => $row->id,
|
||||
];
|
||||
|
||||
if ($hasup) {
|
||||
$upicon = html_writer::link(
|
||||
$this->get_base_action_url([
|
||||
'sesskey' => sesskey(),
|
||||
'action' => \core\plugininfo\aiprovider::UP,
|
||||
'id' => $row->id,
|
||||
]),
|
||||
$OUTPUT->pix_icon(
|
||||
pix: 't/up',
|
||||
alt: '',
|
||||
),
|
||||
array_merge($dataattributes, [
|
||||
'data-direction' => \core\plugininfo\aiprovider::UP,
|
||||
'role' => 'button',
|
||||
'aria-label' => get_string('moveitemup', 'core', $row->name),
|
||||
'title' => get_string('moveitemup', 'core', $row->name),
|
||||
'class' => 'btn btn-link btn-icon pt-2 pl-2',
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
$upicon = '';
|
||||
}
|
||||
|
||||
if ($hasdown) {
|
||||
$downicon = html_writer::link(
|
||||
$this->get_base_action_url([
|
||||
'sesskey' => sesskey(),
|
||||
'action' => \core\plugininfo\aiprovider::DOWN,
|
||||
'id' => $row->id,
|
||||
]),
|
||||
$OUTPUT->pix_icon(
|
||||
pix: 't/down',
|
||||
alt: '',
|
||||
),
|
||||
array_merge($dataattributes, [
|
||||
'data-direction' => \core\plugininfo\aiprovider::DOWN,
|
||||
'role' => 'button',
|
||||
'aria-label' => get_string('moveitemdown', 'core', $row->name),
|
||||
'title' => get_string('moveitemdown', 'core', $row->name),
|
||||
'class' => 'btn btn-link btn-icon pt-2 pl-2',
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
$downicon = '';
|
||||
}
|
||||
|
||||
$spacer = ($hasup && $hasdown) ? $OUTPUT->spacer() : '';
|
||||
return html_writer::div($upicon . $spacer . $downicon, '', ['class' => 'w-25 d-flex justify-content-center']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the action URL for this table.
|
||||
*
|
||||
* The action URL is used to perform all actions when JS is not available.
|
||||
*
|
||||
* @param array $params
|
||||
* @return moodle_url
|
||||
*/
|
||||
protected function get_base_action_url(array $params = []): moodle_url {
|
||||
return new moodle_url('/ai/configure_providers.php', $params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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 order settings.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '../../config.php');
|
||||
|
||||
require_login(autologinguest: false);
|
||||
require_capability('moodle/site:config', context_system::instance());
|
||||
|
||||
require_sesskey();
|
||||
|
||||
$PAGE->set_url('/ai/configure_providers.php');
|
||||
|
||||
$action = optional_param('action', '', PARAM_ALPHA);
|
||||
$id = optional_param('id', '', PARAM_INT);
|
||||
|
||||
$manager = \core\di::get(\core_ai\manager::class);
|
||||
$providerrecord = $manager->get_provider_records(filter: ['id' => $id]);
|
||||
|
||||
$returnurl = new moodle_url('/admin/settings.php?section=aiprovider');
|
||||
|
||||
if (empty($providerrecord) || !$providerrecord) {
|
||||
throw new moodle_exception('error:providernotfound', 'core_ai', $returnurl);
|
||||
}
|
||||
|
||||
if (empty($action) || !in_array($action, \core\plugininfo\aiprovider::get_provider_actions())) {
|
||||
throw new moodle_exception('error:actionnotfound', 'core_ai', $returnurl, $action);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case \core\plugininfo\aiprovider::UP:
|
||||
$manager->change_provider_order($id, \core\plugininfo\aiprovider::MOVE_UP);
|
||||
break;
|
||||
case \core\plugininfo\aiprovider::DOWN:
|
||||
$manager->change_provider_order($id, \core\plugininfo\aiprovider::MOVE_DOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
redirect($returnurl);
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_ai\external;
|
||||
|
||||
/**
|
||||
* Test provider order external api calls.
|
||||
*
|
||||
* @package core_ai
|
||||
* @copyright Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_ai\external\set_provider_order
|
||||
*/
|
||||
final class provider_order_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test set provider order.
|
||||
*/
|
||||
public function test_set_provider_order(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the provider instances.
|
||||
$manager = \core\di::get(\core_ai\manager::class);
|
||||
$openai = $manager->create_provider_instance(
|
||||
classname: '\aiprovider_openai\provider',
|
||||
name: 'openai instance',
|
||||
enabled: true, // Must be set to true to activate the sort function.
|
||||
config: ['data' => 'openai configurations'],
|
||||
);
|
||||
$azureai = $manager->create_provider_instance(
|
||||
classname: '\aiprovider_azureai\provider',
|
||||
name: 'azureai instance',
|
||||
enabled: true, // Must be set to true to activate the sort function.
|
||||
config: ['data' => 'azureai configurations'],
|
||||
);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Move the OpenAI instance to the bottom, and AzureAI will automatically move to the top.
|
||||
set_provider_order::execute($openai->id, \core\plugininfo\aiprovider::MOVE_DOWN);
|
||||
$providers = array_keys($manager->get_sorted_providers());
|
||||
$this->assertEquals($providers[0], $azureai->id);
|
||||
$this->assertEquals($providers[1], $openai->id);
|
||||
|
||||
// Move the OpenAI instance to the top, and AzureAI will automatically move to the bottom.
|
||||
set_provider_order::execute($openai->id, \core\plugininfo\aiprovider::MOVE_UP);
|
||||
$providers = array_keys($manager->get_sorted_providers());
|
||||
$this->assertEquals($providers[0], $openai->id);
|
||||
$this->assertEquals($providers[1], $azureai->id);
|
||||
}
|
||||
}
|
||||
@@ -280,7 +280,7 @@ final class manager_test extends \advanced_testcase {
|
||||
);
|
||||
|
||||
$config['apiendpoint'] = 'https://example.com';
|
||||
$manager->create_provider_instance(
|
||||
$provider2 = $manager->create_provider_instance(
|
||||
classname: '\aiprovider_azureai\provider',
|
||||
name: 'dummy2',
|
||||
enabled: true,
|
||||
@@ -312,6 +312,31 @@ final class manager_test extends \advanced_testcase {
|
||||
// Assert that there is no provider for the generate text action.
|
||||
$this->assertCount(1, $providers[generate_text::class]);
|
||||
$this->assertCount(2, $providers[summarise_text::class]);
|
||||
|
||||
// Ordering the provider instances.
|
||||
// Re-enable the generate text action for the Openai provider.
|
||||
$manager->set_action_state(
|
||||
plugin: $provider1->provider,
|
||||
actionbasename: generate_text::class::get_basename(),
|
||||
enabled: 1,
|
||||
instanceid: $provider1->id,
|
||||
);
|
||||
|
||||
// Move the $provider2 to the first provider for the generate text action.
|
||||
$manager->change_provider_order($provider2->id, \core\plugininfo\aiprovider::MOVE_UP);
|
||||
// Get the new providers for the actions.
|
||||
$providers = $manager->get_providers_for_actions($actions);
|
||||
// Assert whether provider2 is the first provider and provider1 is the last provider for the generate text action.
|
||||
$this->assertEquals($providers[generate_text::class][0], $provider2);
|
||||
$this->assertEquals($providers[generate_text::class][1], $provider1);
|
||||
|
||||
// Move the $provider2 to the last provider for the generate text action.
|
||||
$manager->change_provider_order($provider2->id, \core\plugininfo\aiprovider::MOVE_DOWN);
|
||||
// Get the new providers for the actions.
|
||||
$providers = $manager->get_providers_for_actions($actions);
|
||||
// Assert whether provider1 is the first provider and provider2 is the last provider for the generate text action.
|
||||
$this->assertEquals($providers[generate_text::class][0], $provider1);
|
||||
$this->assertEquals($providers[generate_text::class][1], $provider2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-1
@@ -88,7 +88,9 @@ $string['declineaipolicy'] = 'Decline';
|
||||
$string['enableglobalratelimit'] = 'Set site-wide rate limit';
|
||||
$string['enableglobalratelimit_help'] = 'Limit the number of requests that the AI 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 AI provider every hour.';
|
||||
$string['enableuserratelimit_help'] = 'Limit the number of requests each user can make to the OpenAI API provider every hour.';
|
||||
$string['error:actionnotfound'] = 'Action \'{$a}\' is not supported.';
|
||||
$string['error:providernotfound'] = 'The AI provider instance is not found.';
|
||||
$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';
|
||||
@@ -153,6 +155,8 @@ $string['providerinstancedeleted'] = '{$a} AI provider instance deleted.';
|
||||
$string['providerinstancedeletefailed'] = 'Cannot delete the {$a} AI provider instance. The provider is either in use or there is a database issue. Check if the provider 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 is a database issue. Check if the provider is active or contact your database administrator for help.';
|
||||
$string['providerinstanceupdated'] = '{$a} AI provider instance updated.';
|
||||
$string['providermoveddown'] = '{$a} moved down';
|
||||
$string['providermovedup'] = '{$a} moved up';
|
||||
$string['providername'] = 'Name for instance';
|
||||
$string['providers'] = 'Providers';
|
||||
$string['providersettings'] = 'Settings';
|
||||
|
||||
@@ -1446,6 +1446,8 @@ $string['movedown'] = 'Move down';
|
||||
$string['movefilestohere'] = 'Move files to here';
|
||||
$string['movefull'] = 'Move {$a} to this location';
|
||||
$string['movehere'] = 'Move to here';
|
||||
$string['moveitemdown'] = 'Move {$a} down';
|
||||
$string['moveitemup'] = 'Move {$a} up';
|
||||
$string['moveleft'] = 'Move left';
|
||||
$string['moremenu'] = 'More';
|
||||
$string['moreactions'] = 'More actions';
|
||||
|
||||
@@ -27,6 +27,18 @@ use moodle_url;
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class aiprovider extends base {
|
||||
/** @var string Enable a plugin */
|
||||
public const ENABLE = 'enable';
|
||||
|
||||
/** @var string Disable a plugin */
|
||||
public const DISABLE = 'disable';
|
||||
|
||||
/** @var string Move a plugin up in the plugin order */
|
||||
public const UP = 'up';
|
||||
|
||||
/** @var string Move a plugin down in the plugin order */
|
||||
public const DOWN = 'down';
|
||||
|
||||
#[\Override]
|
||||
public function is_uninstall_allowed(): bool {
|
||||
return true;
|
||||
@@ -65,4 +77,13 @@ class aiprovider extends base {
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available actions with provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_provider_actions(): array {
|
||||
return [self::UP, self::DOWN];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3292,6 +3292,12 @@ $functions = array(
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_ai_set_provider_order' => [
|
||||
'classname' => \core_ai\external\set_provider_order::class,
|
||||
'description' => 'Set the order of a provider',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_sms_set_gateway_status' => [
|
||||
'classname' => 'core_sms\external\sms_gateway_status',
|
||||
'description' => 'Set the sms gateway status',
|
||||
|
||||
@@ -1464,5 +1464,16 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2025022100.02);
|
||||
}
|
||||
|
||||
if ($oldversion < 2025030400.01) {
|
||||
$providers = $DB->get_records('ai_providers', ['enabled' => 1]);
|
||||
// Formatting the value.
|
||||
$value = ','. implode(',', array_column($providers, 'id'));
|
||||
// Create the order config setting.
|
||||
set_config('provider_order', $value, 'core_ai');
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2025030400.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2025030400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2025030400.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '5.0dev+ (Build: 20250304)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user