Skip to content

Commit

Permalink
MDL-83503 mod_glossary: AI Glossary assistant
Browse files Browse the repository at this point in the history
  • Loading branch information
laurentdavid committed Oct 23, 2024
1 parent 3dcd58b commit fcd4a9d
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 27 deletions.
160 changes: 160 additions & 0 deletions mod/glossary/classes/ai/mod_assist_info.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?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 mod_glossary\ai;

use aiplacement_modassist\action_process_response;
use context;
use core_ai\aiactions\base as action_base;

/**
* Class mod_assist_info.
*
* @package mod_glossary
* @copyright 2024 Laurent David <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_assist_info extends \aiplacement_modassist\mod_assist_info {
/**
* Get the list of actions that are available for this placement.
*
* @return array
*/
public function get_base_action_list(): array {
return [
\core_ai\aiactions\generate_text::class,
];
}

/**
* Add the form definitions for the action.
*
* @param \MoodleQuickForm $mform
* @param string $action
* @return void
* @throws \coding_exception
*/
public function add_action_form_definitions(\MoodleQuickForm $mform, string $action): void {
switch ($action) {
case 'glossary-add-entry':
$this->add_glossary_add_entry_form_definitions($mform);
break;
}
}

/**
* Add the form definitions for the glossary add entry action.
*
* @param \MoodleQuickForm $mform
* @return void
* @throws \coding_exception
*/
protected function add_glossary_add_entry_form_definitions(\MoodleQuickForm $mform): void {
$mform->addElement('text',
'topic',
get_string('aiaction:addentries:topic', 'mod_glossary'), ['size' => 50]
);
$mform->setType('topic', PARAM_TEXT);
$options = array_combine(range(1, 20), range(1, 20));
$mform->addElement('select', 'itemcount', get_string('aiaction:addentries:itemcount', 'mod_glossary'), $options);
$mform->setDefault('itemcount', 10);
$mform->setType('itemscount', PARAM_INT);
}

/**
* Get relevant AI action to retrieve content
*
* @param context $context
* @param string $action
* @param object $actiondata
* @return action_base|null
*/
public function get_ai_action(context $context, string $action, object $actiondata): ?action_base {
global $DB;
$cm = get_coursemodule_from_id('glossary', $context->instanceid);
$entries = $DB->get_records_sql("SELECT e.*, u.firstname, u.lastname, u.email, u.picture
FROM {glossary} g, {glossary_entries} e, {user} u
WHERE g.id = ?
AND e.glossaryid = g.id
ORDER BY e.timemodified ASC", [$cm->instance]);
switch ($action) {
case 'glossary-add-entry':
$prompttext = $this->get_system_instructions($action) . "\n";
$existingentries = join(",", array_column($entries, 'concept'));
$prompttext .= "Existing entries: $existingentries\n";
$prompttext .= "Number of entries to create: " . $actiondata->itemcount . "\n";
$prompttext .= "Topic is:" . $actiondata->topic . "\n";
$action = new \core_ai\aiactions\generate_text(
contextid: $context->id,
userid: $actiondata->userid,
prompttext: $prompttext,
);
return $action;
}
return null;
}

/**
* Get system instructions for the action.
*
* @param string $action
* @return string
*/
protected function get_system_instructions(string $action): string {
switch ($action) {
case 'glossary-add-entry':
return get_string('aiaction:addentries:instructions', 'mod_glossary');
}
return '';
}

/**
* Process action response
*
* @param context $context
* @param string $action
* @param string $generatedcontent
* @return action_process_response|null
*/
public function process_response(context $context, string $action,
string $generatedcontent): ?action_process_response {
global $DB, $USER;
$cm = get_coursemodule_from_id('glossary', $context->instanceid);
switch ($action) {
case 'glossary-add-entry':
$newentries = explode("\n", $generatedcontent);
// Parse as csv.
foreach ($newentries as $entry) {
$entry = str_getcsv($entry, ";");
$entryobj = new \stdClass();
$entryobj->concept = trim($entry[0]);
$entryobj->definition = trim($entry[1]);
$entryobj->glossaryid = $cm->instance;
$entryobj->userid = $USER->id;
$entryobj->timecreated = time();
$entryobj->timemodified = time();
$DB->insert_record('glossary_entries', $entryobj);
}
return new action_process_response(
success:true,
actionname: $action,
successmessage: get_string('aiaction:addentries:success', 'mod_glossary', count($newentries))
);
}
return null;
}

}
40 changes: 40 additions & 0 deletions mod/glossary/classes/ai/output/assist_actions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace mod_glossary\ai\output;

use core\output\renderer_base;

/**
* Class mod_assist_widget.
*
* @package aiplacement_modassist
* @copyright 2024 Laurent David <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assist_actions implements \renderable, \templatable {
public function export_for_template(renderer_base $output) {
return [
'actions' => [
[
'action' => 'glossary-add-entry',
'title' => get_string('aiaction:addentries', 'mod_glossary'),
'description' => get_string('aiaction:addentries:description', 'mod_glossary'),
],
],
];
}
}
55 changes: 29 additions & 26 deletions mod/glossary/classes/output/standard_action_bar.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

namespace mod_glossary\output;

use moodle_url;
use context_module;
use moodle_url;
use renderable;
use renderer_base;
use single_button;
Expand Down Expand Up @@ -75,8 +75,8 @@ class standard_action_bar implements renderable, templatable {
* @throws \coding_exception
*/
public function __construct(object $cm, object $module, object $displayformat, string $mode, string $hook,
string $sortkey, string $sortorder, int $offset, int $pagelimit, int $fullsearch,
string $tab, string $defaulttab) {
string $sortkey, string $sortorder, int $offset, int $pagelimit, int $fullsearch,
string $tab, string $defaulttab) {
$this->cm = $cm;
$this->module = $module;
$this->displayformat = $displayformat;
Expand Down Expand Up @@ -105,12 +105,31 @@ public function __construct(object $cm, object $module, object $displayformat, s
public function export_for_template(renderer_base $output) {
return [
'addnewbutton' => $this->create_add_button($output),
'aimodassistinfo' => \aiplacement_modassist\output\assist_ui::get_action_buttons(
context_module::instance($this->cm->id)
),
'searchbox' => $this->create_search_box(),
'tools' => $this->get_additional_tools($output),
'tabjumps' => $this->generate_tab_jumps($output)
];
}

/**
* Render the add entry button
*
* @param renderer_base $output
* @return \stdClass|null
*/
private function create_add_button(renderer_base $output): ?\stdClass {
if (!has_capability('mod/glossary:write', $this->context)) {
return null;
}
$btn = new single_button(new moodle_url('/mod/glossary/edit.php', ['cmid' => $this->cm->id]),
get_string('addsingleentry', 'glossary'), 'post', single_button::BUTTON_PRIMARY);

return $btn->export_for_template($output);
}

/**
* Render the search box with the checkbox
*
Expand Down Expand Up @@ -149,22 +168,6 @@ private function create_search_box(): array {
return $data;
}

/**
* Render the add entry button
*
* @param renderer_base $output
* @return \stdClass|null
*/
private function create_add_button(renderer_base $output): ?\stdClass {
if (!has_capability('mod/glossary:write', $this->context)) {
return null;
}
$btn = new single_button(new moodle_url('/mod/glossary/edit.php', ['cmid' => $this->cm->id]),
get_string('addsingleentry', 'glossary'), 'post', single_button::BUTTON_PRIMARY);

return $btn->export_for_template($output);
}

/**
* Render the additional tools required by the glossary
*
Expand Down Expand Up @@ -195,12 +198,12 @@ private function get_additional_tools(renderer_base $output): array {

if (has_capability('mod/glossary:manageentries', $this->context) or $this->module->allowprintview) {
$params = array(
'id' => $this->cm->id,
'mode' => $this->mode,
'hook' => $this->hook,
'sortkey' => $this->sortkey,
'id' => $this->cm->id,
'mode' => $this->mode,
'hook' => $this->hook,
'sortkey' => $this->sortkey,
'sortorder' => $this->sortorder,
'offset' => $this->offset,
'offset' => $this->offset,
'pagelimit' => $this->pagelimit
);
$printurl = new moodle_url('/mod/glossary/print.php', $params);
Expand All @@ -209,8 +212,8 @@ private function get_additional_tools(renderer_base $output): array {
}

if (!empty($CFG->enablerssfeeds) && !empty($CFG->glossary_enablerssfeeds)
&& $this->module->rsstype && $this->module->rssarticles
&& has_capability('mod/glossary:view', $this->context)) {
&& $this->module->rsstype && $this->module->rssarticles
&& has_capability('mod/glossary:view', $this->context)) {
require_once("$CFG->libdir/rsslib.php");
$string = get_string('rssfeed', 'glossary');
$url = new moodle_url(rss_get_url($this->context->id, $USER->id, 'mod_glossary', $this->cm->instance));
Expand Down
10 changes: 10 additions & 0 deletions mod/glossary/lang/en/glossary.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
$string['addentry'] = 'Add a new entry';
$string['addsingleentry'] = 'Add entry';
$string['addingcomment'] = 'Add a comment';
$string['aiaction:addentries'] = 'Add entries';
$string['aiaction:addentries:description'] = 'Add entries to the glossary';
$string['aiaction:addentries:instructions'] = 'You should provide a set of new entries to the glossary on the specified topic.
The format of the entries should be as follows (csv): "term";"definition". Each entry should be separated by a new line.
You should ignore existing entries.';
$string['aiaction:addentries:itemcount'] = 'Number of entries';
$string['aiaction:addentries:success'] = '{$a} entries have been added successfully';
$string['aiaction:addentries:topic'] = 'Topic of the entries';
$string['aiaction:addentries:topic_helper'] = 'Write a simple description of the topic the entries should be about...';;
$string['alias'] = 'Keyword';
$string['aliases'] = 'Keyword(s)';
$string['aliases_help'] = 'Each glossary entry can have an associated list of keywords (or aliases). If the entry is auto-linked, then any keywords will also be auto-linked.
Expand Down Expand Up @@ -343,3 +352,4 @@

// Deprecated since 4.5.
$string['tagsdeleted'] = 'Glossary tags have been deleted';

4 changes: 3 additions & 1 deletion mod/glossary/templates/standard_action_menu.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@
{{> core/single_button }}
</div>
{{/addnewbutton}}

{{#aimodassistinfo}}
{{> aiplacement_modassist/action_buttons}}
{{/aimodassistinfo}}
<div class="d-flex ms-sm-auto">
{{#tools}}
{{#button}}
Expand Down

0 comments on commit fcd4a9d

Please sign in to comment.