mirror of
https://github.com/moodle/moodle.git
synced 2025-04-24 09:55:33 +02:00
MDL-80890 AI: Course Assistance Placement
Co-authored-by: Matt Porritt <pozze30@gmail.com>
This commit is contained in:
parent
f6141a67d8
commit
cb4e621ce9
.upgradenotes
ai
amd
classes
placement
templates
lang/en
lib
8
.upgradenotes/MDL-80890-2024082311294389.yml
Normal file
8
.upgradenotes/MDL-80890-2024082311294389.yml
Normal file
@ -0,0 +1,8 @@
|
||||
issueNumber: MDL-80890
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
A new hook called `core\hook\output\after_http_headers` has been
|
||||
created. This hook allow plugins to modify the content after headers are
|
||||
sent.
|
||||
type: improved
|
11
ai/amd/build/helper.min.js
vendored
Normal file
11
ai/amd/build/helper.min.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
define("core_ai/helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default=
|
||||
/**
|
||||
* The helper module or AI Subsystem.
|
||||
*
|
||||
* @module core_ai/helper
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class{static replaceLineBreaks(text){const textWithBreaks=text.replace(/\n{2,}|\r\n/g,"<br/><br/>").replace(/\n/g,"<br/>");return"<p>".concat(textWithBreaks,"</p>")}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=helper.min.js.map
|
1
ai/amd/build/helper.min.js.map
Normal file
1
ai/amd/build/helper.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * The helper module or AI Subsystem.\n *\n * @module core_ai/helper\n * @copyright 2024 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class AIHelper {\n /**\n * Replace double line breaks with <br> and with </p><p> for paragraphs.\n * This is to handle the difference in response from the AI to what is expected by the editor.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceLineBreaks(text) {\n // Replace double line breaks with </p><p> for paragraphs\n const textWithParagraphs = text.replace(/\\n{2,}|\\r\\n/g, '<br/><br/>');\n\n // Replace remaining single line breaks with <br> tags\n const textWithBreaks = textWithParagraphs.replace(/\\n/g, '<br/>');\n\n // Add opening and closing <p> tags to wrap the entire content\n return `<p>${textWithBreaks}</p>`;\n }\n}\n"],"names":["text","textWithBreaks","replace"],"mappings":";;;;;;;;+BA8B6BA,YAKfC,eAHqBD,KAAKE,QAAQ,eAAgB,cAGdA,QAAQ,MAAO,4BAG5CD"}
|
41
ai/amd/src/helper.js
Normal file
41
ai/amd/src/helper.js
Normal file
@ -0,0 +1,41 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* The helper module or AI Subsystem.
|
||||
*
|
||||
* @module core_ai/helper
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
export default class AIHelper {
|
||||
/**
|
||||
* Replace double line breaks with <br> and with </p><p> for paragraphs.
|
||||
* This is to handle the difference in response from the AI to what is expected by the editor.
|
||||
*
|
||||
* @param {String} text The text to replace.
|
||||
* @returns {String}
|
||||
*/
|
||||
static replaceLineBreaks(text) {
|
||||
// Replace double line breaks with </p><p> for paragraphs
|
||||
const textWithParagraphs = text.replace(/\n{2,}|\r\n/g, '<br/><br/>');
|
||||
|
||||
// Replace remaining single line breaks with <br> tags
|
||||
const textWithBreaks = textWithParagraphs.replace(/\n/g, '<br/>');
|
||||
|
||||
// Add opening and closing <p> tags to wrap the entire content
|
||||
return `<p>${textWithBreaks}</p>`;
|
||||
}
|
||||
}
|
@ -233,7 +233,7 @@ class ai_image {
|
||||
): static {
|
||||
global $CFG;
|
||||
if (empty($watermark)) {
|
||||
$watermark = get_string('imagewatermark', 'core_ai');
|
||||
$watermark = get_string('contentwatermark', 'core_ai');
|
||||
}
|
||||
if (empty($options)) {
|
||||
$options = [
|
||||
|
3
ai/placement/courseassist/amd/build/placement.min.js
vendored
Normal file
3
ai/placement/courseassist/amd/build/placement.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ai/placement/courseassist/amd/build/placement.min.js.map
Normal file
1
ai/placement/courseassist/amd/build/placement.min.js.map
Normal file
File diff suppressed because one or more lines are too long
3
ai/placement/courseassist/amd/build/selectors.min.js
vendored
Normal file
3
ai/placement/courseassist/amd/build/selectors.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
define("aiplacement_courseassist/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={ELEMENTS:{AIDRAWER:"#ai-drawer",AIDRAWER_BODY:"#ai-drawer .ai-drawer-body",PAGE:"#page",MAIN_REGION:'[role="main"]'},ACTIONS:{SUMMARY:'[data-action="course-summarise"]',RETRY:'[data-action="course-summarise-retry"]',DECLINE:'[data-action="course-summarise-policy-decline"]',ACCEPT:'.ai-policy-block [data-action="accept"]',REGENERATE:'[data-action="course-summarise-regenerate"]',CANCEL:'.ai-policy-block [data-action="decline"]'}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=selectors.min.js.map
|
1
ai/placement/courseassist/amd/build/selectors.min.js.map
Normal file
1
ai/placement/courseassist/amd/build/selectors.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Define all of the selectors we will be using on the AI Course assistant.\n *\n * @module aiplacement_courseassist/selectors\n * @copyright 2024 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n ELEMENTS: {\n AIDRAWER: '#ai-drawer',\n AIDRAWER_BODY: '#ai-drawer .ai-drawer-body',\n PAGE: '#page',\n MAIN_REGION: '[role=\"main\"]',\n },\n ACTIONS: {\n SUMMARY: '[data-action=\"course-summarise\"]',\n RETRY: '[data-action=\"course-summarise-retry\"]',\n DECLINE: '[data-action=\"course-summarise-policy-decline\"]',\n ACCEPT: '.ai-policy-block [data-action=\"accept\"]',\n REGENERATE: '[data-action=\"course-summarise-regenerate\"]',\n CANCEL: '.ai-policy-block [data-action=\"decline\"]',\n }\n};\n"],"names":["ELEMENTS","AIDRAWER","AIDRAWER_BODY","PAGE","MAIN_REGION","ACTIONS","SUMMARY","RETRY","DECLINE","ACCEPT","REGENERATE","CANCEL"],"mappings":"oLAsBe,CACXA,SAAU,CACNC,SAAU,aACVC,cAAe,6BACfC,KAAM,QACNC,YAAa,iBAEjBC,QAAS,CACLC,QAAS,mCACTC,MAAO,yCACPC,QAAS,kDACTC,OAAQ,0CACRC,WAAY,8CACZC,OAAQ"}
|
356
ai/placement/courseassist/amd/src/placement.js
Normal file
356
ai/placement/courseassist/amd/src/placement.js
Normal file
@ -0,0 +1,356 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Module to load and render the tools for the AI assist plugin.
|
||||
*
|
||||
* @module aiplacement_courseassist/placement
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Templates from 'core/templates';
|
||||
import Ajax from 'core/ajax';
|
||||
import 'core/copy_to_clipboard';
|
||||
import Notification from 'core/notification';
|
||||
import Selectors from 'aiplacement_courseassist/selectors';
|
||||
import Policy from 'core_ai/policy';
|
||||
import AIHelper from 'core_ai/helper';
|
||||
|
||||
const AICourseAssist = class {
|
||||
|
||||
/**
|
||||
* The user ID.
|
||||
* @type {Integer}
|
||||
*/
|
||||
userId;
|
||||
/**
|
||||
* The context ID.
|
||||
* @type {Integer}
|
||||
*/
|
||||
contextId;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {Integer} userId The user ID.
|
||||
* @param {Integer} contextId The context ID.
|
||||
*/
|
||||
constructor(userId, contextId) {
|
||||
this.userId = userId;
|
||||
this.contextId = contextId;
|
||||
|
||||
this.aiDrawerElement = document.querySelector(Selectors.ELEMENTS.AIDRAWER);
|
||||
this.aiDrawerBodyElement = document.querySelector(Selectors.ELEMENTS.AIDRAWER_BODY);
|
||||
this.pageElement = document.querySelector(Selectors.ELEMENTS.PAGE);
|
||||
|
||||
this.registerEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
registerEventListeners() {
|
||||
document.addEventListener('click', async(e) => {
|
||||
const summariseAction = e.target.closest(Selectors.ACTIONS.SUMMARY);
|
||||
if (summariseAction) {
|
||||
e.preventDefault();
|
||||
this.toggleAIDrawer();
|
||||
const isPolicyAccepted = await this.isPolicyAccepted();
|
||||
if (!isPolicyAccepted) {
|
||||
// Display policy.
|
||||
this.displayPolicy();
|
||||
return;
|
||||
}
|
||||
// Display summary.
|
||||
this.displaySummary();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners for the policy.
|
||||
*/
|
||||
registerPolicyEventListeners() {
|
||||
const acceptAction = document.querySelector(Selectors.ACTIONS.ACCEPT);
|
||||
const declineAction = document.querySelector(Selectors.ACTIONS.DECLINE);
|
||||
if (acceptAction) {
|
||||
acceptAction.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.acceptPolicy().then(() => {
|
||||
return this.displaySummary();
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
}
|
||||
if (declineAction) {
|
||||
declineAction.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.closeAIDrawer();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners for the error.
|
||||
*/
|
||||
registerErrorEventListeners() {
|
||||
const retryAction = document.querySelector(Selectors.ACTIONS.RETRY);
|
||||
if (retryAction) {
|
||||
retryAction.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.aiDrawerBodyElement.dataset.hasdata = '0';
|
||||
this.displaySummary();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners for the response.
|
||||
*/
|
||||
registerResponseEventListeners() {
|
||||
const regenerateAction = document.querySelector(Selectors.ACTIONS.REGENERATE);
|
||||
if (regenerateAction) {
|
||||
regenerateAction.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.aiDrawerBodyElement.dataset.hasdata = '0';
|
||||
this.displaySummary();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerLoadingEventListeners() {
|
||||
const cancelAction = document.querySelector(Selectors.ACTIONS.CANCEL);
|
||||
if (cancelAction) {
|
||||
cancelAction.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.setRequestCancelled();
|
||||
this.toggleAIDrawer();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the AI drawer is open.
|
||||
* @return {boolean} True if the AI drawer is open, false otherwise.
|
||||
*/
|
||||
isAIDrawerOpen() {
|
||||
return this.aiDrawerElement.classList.contains('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request is cancelled.
|
||||
* @return {boolean} True if the request is cancelled, false otherwise.
|
||||
*/
|
||||
isRequestCancelled() {
|
||||
return this.aiDrawerBodyElement.dataset.cancelled === '1';
|
||||
}
|
||||
|
||||
setRequestCancelled() {
|
||||
this.aiDrawerBodyElement.dataset.cancelled = '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the AI drawer.
|
||||
*/
|
||||
openAIDrawer() {
|
||||
this.aiDrawerElement.classList.add('show');
|
||||
this.aiDrawerBodyElement.setAttribute('aria-live', 'polite');
|
||||
if (!this.pageElement.classList.contains('show-drawer-right')) {
|
||||
this.addPadding();
|
||||
}
|
||||
// Disable the summary button.
|
||||
this.disableSummaryButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the AI drawer.
|
||||
*/
|
||||
closeAIDrawer() {
|
||||
this.aiDrawerElement.classList.remove('show');
|
||||
this.aiDrawerBodyElement.removeAttribute('aria-live');
|
||||
if (this.pageElement.classList.contains('show-drawer-right') && this.aiDrawerBodyElement.dataset.removepadding === '1') {
|
||||
this.removePadding();
|
||||
}
|
||||
// Enable the summary button.
|
||||
this.enableSummaryButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the AI drawer.
|
||||
*/
|
||||
toggleAIDrawer() {
|
||||
if (this.isAIDrawerOpen()) {
|
||||
this.closeAIDrawer();
|
||||
} else {
|
||||
this.openAIDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add padding to the page to make space for the AI drawer.
|
||||
*/
|
||||
addPadding() {
|
||||
this.pageElement.classList.add('show-drawer-right');
|
||||
this.aiDrawerBodyElement.dataset.removepadding = '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove padding from the page.
|
||||
*/
|
||||
removePadding() {
|
||||
this.pageElement.classList.remove('show-drawer-right');
|
||||
this.aiDrawerBodyElement.dataset.removepadding = '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the summary button.
|
||||
*/
|
||||
disableSummaryButton() {
|
||||
const summaryButton = document.querySelector(Selectors.ACTIONS.SUMMARY);
|
||||
if (summaryButton) {
|
||||
summaryButton.setAttribute('disabled', 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the summary button and focus on it.
|
||||
*/
|
||||
enableSummaryButton() {
|
||||
const summaryButton = document.querySelector(Selectors.ACTIONS.SUMMARY);
|
||||
if (summaryButton) {
|
||||
summaryButton.removeAttribute('disabled');
|
||||
summaryButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the policy is accepted.
|
||||
* @return {bool} True if the policy is accepted, false otherwise.
|
||||
*/
|
||||
async isPolicyAccepted() {
|
||||
return await Policy.getPolicyStatus(this.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the policy.
|
||||
* @return {Promise<Object>}
|
||||
*/
|
||||
acceptPolicy() {
|
||||
return Policy.acceptPolicy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the AI drawer has generated content or not.
|
||||
* @return {boolean} True if the AI drawer has generated content, false otherwise.
|
||||
*/
|
||||
hasGeneratedContent() {
|
||||
return this.aiDrawerBodyElement.dataset.hasdata === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the policy.
|
||||
*/
|
||||
displayPolicy() {
|
||||
Templates.render('core_ai/policyblock', {}).then((html) => {
|
||||
this.aiDrawerBodyElement.innerHTML = html;
|
||||
this.registerPolicyEventListeners();
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the loading spinner.
|
||||
*/
|
||||
displayLoading() {
|
||||
Templates.render('aiplacement_courseassist/loading', {}).then((html) => {
|
||||
this.aiDrawerBodyElement.innerHTML = html;
|
||||
this.registerLoadingEventListeners();
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the summary.
|
||||
*/
|
||||
async displaySummary() {
|
||||
if (!this.hasGeneratedContent()) {
|
||||
// Display loading spinner.
|
||||
this.displayLoading();
|
||||
// Clear the drawer content to prevent sending some unnecessary content.
|
||||
this.aiDrawerBodyElement.innerHTML = '';
|
||||
const request = {
|
||||
methodname: 'aiplacement_courseassist_summarise_text',
|
||||
args: {
|
||||
contextid: this.contextId,
|
||||
prompttext: this.getTextContent(),
|
||||
}
|
||||
};
|
||||
try {
|
||||
const responseObj = await Ajax.call([request])[0];
|
||||
if (responseObj.error) {
|
||||
window.console.log(responseObj.error);
|
||||
this.displayError();
|
||||
return;
|
||||
} else {
|
||||
if (!this.isRequestCancelled()) {
|
||||
window.console.log(responseObj);
|
||||
// Replace double line breaks with <br> and with </p><p> for paragraphs.
|
||||
const generatedContent = AIHelper.replaceLineBreaks(responseObj.generatedcontent);
|
||||
this.displayResponse(generatedContent);
|
||||
return;
|
||||
} else {
|
||||
this.aiDrawerBodyElement.dataset.cancelled = '0';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the response.
|
||||
* @param {String} content The content to display.
|
||||
*/
|
||||
displayResponse(content) {
|
||||
Templates.render('aiplacement_courseassist/response', {content: content}).then((html) => {
|
||||
this.aiDrawerBodyElement.innerHTML = html;
|
||||
this.aiDrawerBodyElement.dataset.hasdata = '1';
|
||||
this.registerResponseEventListeners();
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the error.
|
||||
*/
|
||||
displayError() {
|
||||
Templates.render('aiplacement_courseassist/error', {}).then((html) => {
|
||||
this.aiDrawerBodyElement.innerHTML = html;
|
||||
this.registerErrorEventListeners();
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of the main region.
|
||||
* @return {String} The text content.
|
||||
*/
|
||||
getTextContent() {
|
||||
const mainRegion = document.querySelector(Selectors.ELEMENTS.MAIN_REGION);
|
||||
return mainRegion.innerText || mainRegion.textContent;
|
||||
}
|
||||
};
|
||||
|
||||
export default AICourseAssist;
|
38
ai/placement/courseassist/amd/src/selectors.js
Normal file
38
ai/placement/courseassist/amd/src/selectors.js
Normal file
@ -0,0 +1,38 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Define all of the selectors we will be using on the AI Course assistant.
|
||||
*
|
||||
* @module aiplacement_courseassist/selectors
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
export default {
|
||||
ELEMENTS: {
|
||||
AIDRAWER: '#ai-drawer',
|
||||
AIDRAWER_BODY: '#ai-drawer .ai-drawer-body',
|
||||
PAGE: '#page',
|
||||
MAIN_REGION: '[role="main"]',
|
||||
},
|
||||
ACTIONS: {
|
||||
SUMMARY: '[data-action="course-summarise"]',
|
||||
RETRY: '[data-action="course-summarise-retry"]',
|
||||
DECLINE: '[data-action="course-summarise-policy-decline"]',
|
||||
ACCEPT: '.ai-policy-block [data-action="accept"]',
|
||||
REGENERATE: '[data-action="course-summarise-regenerate"]',
|
||||
CANCEL: '.ai-policy-block [data-action="decline"]',
|
||||
}
|
||||
};
|
152
ai/placement/courseassist/classes/external/summarise_text.php
vendored
Normal file
152
ai/placement/courseassist/classes/external/summarise_text.php
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
<?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 aiplacement_courseassist\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_value;
|
||||
|
||||
/**
|
||||
* External API to call summarise text action for this placement.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class summarise_text extends external_api {
|
||||
|
||||
/**
|
||||
* Summarise text parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
* @since Moodle 4.5
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'contextid' => new external_value(
|
||||
PARAM_INT,
|
||||
'The context ID',
|
||||
VALUE_REQUIRED,
|
||||
),
|
||||
'prompttext' => new external_value(
|
||||
PARAM_RAW,
|
||||
'The prompt text for the AI service',
|
||||
VALUE_REQUIRED,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarise text from the AI placement.
|
||||
*
|
||||
* @param int $contextid The context ID.
|
||||
* @param string $prompttext The data encoded as a json array.
|
||||
* @return array The generated content.
|
||||
* @since Moodle 4.5
|
||||
*/
|
||||
public static function execute(
|
||||
int $contextid,
|
||||
string $prompttext
|
||||
): array {
|
||||
global $USER;
|
||||
// Parameter validation.
|
||||
[
|
||||
'contextid' => $contextid,
|
||||
'prompttext' => $prompttext,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'contextid' => $contextid,
|
||||
'prompttext' => $prompttext,
|
||||
]);
|
||||
// Context validation and permission check.
|
||||
// Get the context from the passed in ID.
|
||||
$context = \context::instance_by_id($contextid);
|
||||
|
||||
// Check the user has permission to use the AI service.
|
||||
self::validate_context($context);
|
||||
require_capability('aiplacement/courseassist:summarise_text', $context);
|
||||
|
||||
// Prepare the action.
|
||||
$action = new \core_ai\aiactions\summarise_text(
|
||||
contextid: $contextid,
|
||||
userid: $USER->id,
|
||||
prompttext: $prompttext,
|
||||
);
|
||||
|
||||
// Send the action to the AI manager.
|
||||
$manager = new \core_ai\manager();
|
||||
$response = $manager->process_action($action);
|
||||
// Return the response.
|
||||
return [
|
||||
'success' => $response->get_success(),
|
||||
'generatedcontent' => $response->get_response_data()['generatedcontent'] ?? '',
|
||||
'finishreason' => $response->get_response_data()['finishreason'] ?? '',
|
||||
'errorcode' => $response->get_errorcode(),
|
||||
'error' => $response->get_errormessage(),
|
||||
'timecreated' => $response->get_timecreated(),
|
||||
'prompttext' => $prompttext,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate content return value.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
* @since Moodle 4.5
|
||||
*/
|
||||
public static function execute_returns(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'success' => new external_value(
|
||||
PARAM_BOOL,
|
||||
'Was the request successful',
|
||||
VALUE_REQUIRED
|
||||
),
|
||||
'timecreated' => new external_value(
|
||||
PARAM_INT,
|
||||
'The time the request was created',
|
||||
VALUE_REQUIRED,
|
||||
),
|
||||
'prompttext' => new external_value(
|
||||
PARAM_RAW,
|
||||
'The prompt text for the AI service',
|
||||
VALUE_REQUIRED,
|
||||
),
|
||||
'generatedcontent' => new external_value(
|
||||
PARAM_RAW,
|
||||
'The text generated by AI.',
|
||||
VALUE_DEFAULT,
|
||||
),
|
||||
'finishreason' => new external_value(
|
||||
PARAM_ALPHA,
|
||||
'The reason generation was stopped',
|
||||
VALUE_DEFAULT,
|
||||
'stop',
|
||||
),
|
||||
'errorcode' => new external_value(
|
||||
PARAM_INT,
|
||||
'Error code if any',
|
||||
VALUE_DEFAULT,
|
||||
0,
|
||||
),
|
||||
'error' => new external_value(
|
||||
PARAM_TEXT,
|
||||
'Error message if any',
|
||||
VALUE_DEFAULT,
|
||||
'',
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
47
ai/placement/courseassist/classes/hook_callbacks.php
Normal file
47
ai/placement/courseassist/classes/hook_callbacks.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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 aiplacement_courseassist;
|
||||
|
||||
use core\hook\output\after_http_headers;
|
||||
use core\hook\output\before_footer_html_generation;
|
||||
|
||||
/**
|
||||
* Hook callbacks for the course assist AI Placement.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class hook_callbacks {
|
||||
/**
|
||||
* Bootstrap the course assist UI.
|
||||
*
|
||||
* @param before_footer_html_generation $hook
|
||||
*/
|
||||
public static function before_footer_html_generation(before_footer_html_generation $hook): void {
|
||||
\aiplacement_courseassist\output\assist_ui::load_assist_ui($hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap the summarise button.
|
||||
*
|
||||
* @param after_http_headers $hook
|
||||
*/
|
||||
public static function after_http_headers(after_http_headers $hook): void {
|
||||
\aiplacement_courseassist\output\assist_ui::load_summarise_button($hook);
|
||||
}
|
||||
}
|
107
ai/placement/courseassist/classes/output/assist_ui.php
Normal file
107
ai/placement/courseassist/classes/output/assist_ui.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?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 aiplacement_courseassist\output;
|
||||
|
||||
use core\hook\output\after_http_headers;
|
||||
use core\hook\output\before_footer_html_generation;
|
||||
use core_ai\aiactions\summarise_text;
|
||||
use core_ai\manager;
|
||||
|
||||
/**
|
||||
* Output handler for the course assist AI Placement.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class assist_ui {
|
||||
/**
|
||||
* Bootstrap the course assist UI.
|
||||
*
|
||||
* @param before_footer_html_generation $hook
|
||||
*/
|
||||
public static function load_assist_ui(before_footer_html_generation $hook): void {
|
||||
global $PAGE, $OUTPUT, $USER;
|
||||
|
||||
// Preflight checks.
|
||||
if (!self::preflight_checks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the markup for the assist interface.
|
||||
$params = [
|
||||
'userid' => $USER->id,
|
||||
'contextid' => $PAGE->context->id,
|
||||
];
|
||||
$html = $OUTPUT->render_from_template('aiplacement_courseassist/drawer', $params);
|
||||
$hook->add_html($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap the summarise button.
|
||||
*
|
||||
* @param after_http_headers $hook
|
||||
*/
|
||||
public static function load_summarise_button(after_http_headers $hook): void {
|
||||
global $OUTPUT;
|
||||
|
||||
// Preflight checks.
|
||||
if (!self::preflight_checks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$html = $OUTPUT->render_from_template('aiplacement_courseassist/summarise_button', []);
|
||||
$hook->add_html($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preflight checks to determine if the assist UI should be loaded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function preflight_checks(): bool {
|
||||
global $PAGE;
|
||||
if (during_initial_install()) {
|
||||
return false;
|
||||
}
|
||||
if (!get_config('aiplacement_courseassist', 'version')) {
|
||||
return false;
|
||||
}
|
||||
if (in_array($PAGE->pagelayout, ['maintenance', 'print', 'redirect', 'embedded'])) {
|
||||
// Do not try to show assist UI inside iframe, in maintenance mode,
|
||||
// when printing, or during redirects.
|
||||
return false;
|
||||
}
|
||||
// Check we are in the right context, exit if not activity.
|
||||
if ($PAGE->context->contextlevel != CONTEXT_MODULE) {
|
||||
return false;
|
||||
}
|
||||
[$plugintype, $pluginname] = explode('_', \core_component::normalize_componentname('aiplacement_courseassist'), 2);
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class($plugintype);
|
||||
if (!$manager::is_plugin_enabled($pluginname)) {
|
||||
return false;
|
||||
}
|
||||
$providers = manager::get_providers_for_actions([summarise_text::class], true);
|
||||
if (!has_capability('aiplacement/courseassist:summarise_text', $PAGE->context)
|
||||
|| !manager::is_action_enabled('aiplacement_courseassist', 'summarise_text')
|
||||
|| empty($providers[summarise_text::class])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
35
ai/placement/courseassist/classes/placement.php
Normal file
35
ai/placement/courseassist/classes/placement.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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 aiplacement_courseassist;
|
||||
|
||||
/**
|
||||
* Class placement.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class placement extends \core_ai\placement {
|
||||
|
||||
#[\Override]
|
||||
public function get_action_list(): array {
|
||||
return [
|
||||
\core_ai\aiactions\summarise_text::class,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
35
ai/placement/courseassist/classes/privacy/provider.php
Normal file
35
ai/placement/courseassist/classes/privacy/provider.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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 aiplacement_courseassist\privacy;
|
||||
|
||||
use core_privacy\local\metadata\null_provider;
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for course assistance placement implementing null_provider.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class provider implements null_provider {
|
||||
|
||||
#[\Override]
|
||||
public static function get_reason(): string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
37
ai/placement/courseassist/db/access.php
Normal file
37
ai/placement/courseassist/db/access.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Capabilities for the aiplacement_courseassist plugin.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$capabilities = [
|
||||
'aiplacement/courseassist:summarise_text' => [
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => [
|
||||
'manager' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'teacher' => CAP_ALLOW,
|
||||
'student' => CAP_ALLOW,
|
||||
],
|
||||
],
|
||||
];
|
38
ai/placement/courseassist/db/hooks.php
Normal file
38
ai/placement/courseassist/db/hooks.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Hook callbacks for the course assist placement
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$callbacks = [
|
||||
[
|
||||
'hook' => \core\hook\output\before_footer_html_generation::class,
|
||||
'callback' => \aiplacement_courseassist\hook_callbacks::class . '::before_footer_html_generation',
|
||||
'priority' => 0,
|
||||
],
|
||||
[
|
||||
'hook' => \core\hook\output\after_http_headers::class,
|
||||
'callback' => \aiplacement_courseassist\hook_callbacks::class . '::after_http_headers',
|
||||
'priority' => 0,
|
||||
],
|
||||
];
|
35
ai/placement/courseassist/db/services.php
Normal file
35
ai/placement/courseassist/db/services.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Course Assistance Placement webservice definitions.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$functions = [
|
||||
'aiplacement_courseassist_summarise_text' => [
|
||||
'classname' => 'aiplacement_courseassist\external\summarise_text',
|
||||
'description' => 'Summarise text for the Course Assistance Placement',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
];
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Strings for component aiplacement_courseassist, language 'en'.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['aisummary'] = 'AI summary';
|
||||
$string['courseassist:summarise_text'] = 'Summarise text';
|
||||
$string['copy'] = 'Copy';
|
||||
$string['generatefailtitle'] = 'Something went wrong';
|
||||
$string['generating'] = 'Generating your response';
|
||||
$string['pluginname'] = 'Course Assistance Placement';
|
||||
$string['privacy:metadata'] = 'The Course Assistance placement plugin does not store any personal data.';
|
||||
$string['regenerate'] = 'Regenerate';
|
||||
$string['summarise'] = 'Summarise';
|
||||
$string['summarise_tooltips'] = 'Create an AI-generated summary of the page content';
|
||||
$string['tryagain'] = 'Try again';
|
8
ai/placement/courseassist/pix/sparkles-white.svg
Normal file
8
ai/placement/courseassist/pix/sparkles-white.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMid meet">
|
||||
<path d="M6.5 2C6.5 2 6.74829 4.31747 7.71541 5.28459C8.68253 6.25171 11 6.5 11 6.5C11 6.5 8.68253 6.74829 7.71541 7.71541C6.74829 8.68253 6.5 11 6.5 11C6.5 11 6.25171 8.68253 5.28459 7.71541C4.31747 6.74829 2 6.5 2 6.5C2 6.5 4.31747 6.25171 5.28459 5.28459C6.25171 4.31747 6.5 2 6.5 2Z" fill="#ffffff"/>
|
||||
<path d="M13 10C13 10 13.1103 11.03 13.5402 11.4598C13.97 11.8897 15 12 15 12C15 12 13.97 12.1103 13.5402 12.5402C13.1103 12.97 13 14 13 14C13 14 12.8896 12.97 12.4598 12.5402C12.03 12.1103 11 12 11 12C11 12 12.03 11.8897 12.4598 11.4598C12.8896 11.03 13 10 13 10Z" fill="#ffffff"/>
|
||||
<path d="M16 2C16 2 16.1103 3.02999 16.5402 3.45982C16.97 3.88965 18 4 18 4C18 4 16.97 4.11035 16.5402 4.54018C16.1103 4.97001 16 6 16 6C16 6 15.8896 4.97001 15.4598 4.54018C15.03 4.11035 14 4 14 4C14 4 15.03 3.88965 15.4598 3.45982C15.8896 3.02999 16 2 16 2Z" fill="#ffffff"/>
|
||||
<path d="M6.5 2C6.5 2 6.74829 4.31747 7.71541 5.28459C8.68253 6.25171 11 6.5 11 6.5C11 6.5 8.68253 6.74829 7.71541 7.71541C6.74829 8.68253 6.5 11 6.5 11C6.5 11 6.25171 8.68253 5.28459 7.71541C4.31747 6.74829 2 6.5 2 6.5C2 6.5 4.31747 6.25171 5.28459 5.28459C6.25171 4.31747 6.5 2 6.5 2Z" stroke="#ffffff" stroke-linejoin="round"/>
|
||||
<path d="M13 10C13 10 13.1103 11.03 13.5402 11.4598C13.97 11.8897 15 12 15 12C15 12 13.97 12.1103 13.5402 12.5402C13.1103 12.97 13 14 13 14C13 14 12.8896 12.97 12.4598 12.5402C12.03 12.1103 11 12 11 12C11 12 12.03 11.8897 12.4598 11.4598C12.8896 11.03 13 10 13 10Z" stroke="#ffffff" stroke-linejoin="round"/>
|
||||
<path d="M16 2C16 2 16.1103 3.02999 16.5402 3.45982C16.97 3.88965 18 4 18 4C18 4 16.97 4.11035 16.5402 4.54018C16.1103 4.97001 16 6 16 6C16 6 15.8896 4.97001 15.4598 4.54018C15.03 4.11035 14 4 14 4C14 4 15.03 3.88965 15.4598 3.45982C15.8896 3.02999 16 2 16 2Z" stroke="#ffffff" stroke-linejoin="round"/>
|
||||
</svg>
|
After (image error) Size: 1.9 KiB |
8
ai/placement/courseassist/pix/sparkles.svg
Normal file
8
ai/placement/courseassist/pix/sparkles.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 2C6.5 2 6.74829 4.31747 7.71541 5.28459C8.68253 6.25171 11 6.5 11 6.5C11 6.5 8.68253 6.74829 7.71541 7.71541C6.74829 8.68253 6.5 11 6.5 11C6.5 11 6.25171 8.68253 5.28459 7.71541C4.31747 6.74829 2 6.5 2 6.5C2 6.5 4.31747 6.25171 5.28459 5.28459C6.25171 4.31747 6.5 2 6.5 2Z" fill="#343A40"/>
|
||||
<path d="M13 10C13 10 13.1103 11.03 13.5402 11.4598C13.97 11.8897 15 12 15 12C15 12 13.97 12.1103 13.5402 12.5402C13.1103 12.97 13 14 13 14C13 14 12.8896 12.97 12.4598 12.5402C12.03 12.1103 11 12 11 12C11 12 12.03 11.8897 12.4598 11.4598C12.8896 11.03 13 10 13 10Z" fill="#343A40"/>
|
||||
<path d="M16 2C16 2 16.1103 3.02999 16.5402 3.45982C16.97 3.88965 18 4 18 4C18 4 16.97 4.11035 16.5402 4.54018C16.1103 4.97001 16 6 16 6C16 6 15.8896 4.97001 15.4598 4.54018C15.03 4.11035 14 4 14 4C14 4 15.03 3.88965 15.4598 3.45982C15.8896 3.02999 16 2 16 2Z" fill="#343A40"/>
|
||||
<path d="M6.5 2C6.5 2 6.74829 4.31747 7.71541 5.28459C8.68253 6.25171 11 6.5 11 6.5C11 6.5 8.68253 6.74829 7.71541 7.71541C6.74829 8.68253 6.5 11 6.5 11C6.5 11 6.25171 8.68253 5.28459 7.71541C4.31747 6.74829 2 6.5 2 6.5C2 6.5 4.31747 6.25171 5.28459 5.28459C6.25171 4.31747 6.5 2 6.5 2Z" stroke="#343A40" stroke-linejoin="round"/>
|
||||
<path d="M13 10C13 10 13.1103 11.03 13.5402 11.4598C13.97 11.8897 15 12 15 12C15 12 13.97 12.1103 13.5402 12.5402C13.1103 12.97 13 14 13 14C13 14 12.8896 12.97 12.4598 12.5402C12.03 12.1103 11 12 11 12C11 12 12.03 11.8897 12.4598 11.4598C12.8896 11.03 13 10 13 10Z" stroke="#343A40" stroke-linejoin="round"/>
|
||||
<path d="M16 2C16 2 16.1103 3.02999 16.5402 3.45982C16.97 3.88965 18 4 18 4C18 4 16.97 4.11035 16.5402 4.54018C16.1103 4.97001 16 6 16 6C16 6 15.8896 4.97001 15.4598 4.54018C15.03 4.11035 14 4 14 4C14 4 15.03 3.88965 15.4598 3.45982C15.8896 3.02999 16 2 16 2Z" stroke="#343A40" stroke-linejoin="round"/>
|
||||
</svg>
|
After (image error) Size: 1.9 KiB |
77
ai/placement/courseassist/styles.css
Normal file
77
ai/placement/courseassist/styles.css
Normal file
@ -0,0 +1,77 @@
|
||||
.ai-drawer {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
bottom: 0;
|
||||
right: calc(-315px + -10px);
|
||||
width: 315px;
|
||||
background-color: #f8f9fa;
|
||||
z-index: 2001;
|
||||
transition: right 0.2s ease, top 0.2s ease, bottom 0.2s ease, visibility 0.2s ease, transform 0.5s ease;
|
||||
visibility: hidden;
|
||||
}
|
||||
.ai-drawer.show {
|
||||
right: 0;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.ai-drawer-header {
|
||||
padding: 0;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ai-drawer-header .ai-drawer-button {
|
||||
margin-left: auto;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ai-drawer-body {
|
||||
position: relative;
|
||||
height: calc(100vh - 120px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
padding: 0.4rem;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #6a737b #f8f9fa;
|
||||
}
|
||||
|
||||
#course-summarise-response {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.course-summarise-response-controls button .icon,
|
||||
.course-summarise-response-watermark img.icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button.btn.btn-outline-secondary {
|
||||
color: unset;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button.btn.btn-outline-secondary span.ai-course-summarise-sparkles-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button.btn.btn-outline-secondary span.ai-course-summarise-sparkles-icon.white {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button.btn.btn-outline-secondary:not([disabled]):hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button.btn.btn-outline-secondary:not([disabled]):hover span.ai-course-summarise-sparkles-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button.btn.btn-outline-secondary:not([disabled]):hover span.ai-course-summarise-sparkles-icon.white {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ai-course-summarise-controls button img.icon {
|
||||
width: auto;
|
||||
vertical-align: sub;
|
||||
}
|
49
ai/placement/courseassist/templates/drawer.mustache
Normal file
49
ai/placement/courseassist/templates/drawer.mustache
Normal file
@ -0,0 +1,49 @@
|
||||
{{!
|
||||
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 aiplacement_courseassist/drawer
|
||||
|
||||
Template to display the AI drawer for the course assist.
|
||||
|
||||
Context variables required for this template:
|
||||
* userid - User ID
|
||||
* contextid - Context ID
|
||||
* content - Content to display
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"userid": "1",
|
||||
"contextid": "1",
|
||||
"content": "<p>Content to display</p>"
|
||||
}
|
||||
}}
|
||||
<div class="ai-drawer" id="ai-drawer">
|
||||
<div class="ai-drawer-header">
|
||||
<button id="ai-drawer-close" class="btn ai-drawer-button" type="button" data-action="course-summarise">
|
||||
{{#pix}} e/cancel, core {{/pix}}
|
||||
<span class="sr-only">{{#str}} closedrawer, core {{/str}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ai-drawer-body" data-hasdata="0" data-cancelled="0">
|
||||
{{{content}}}
|
||||
</div>
|
||||
</div>
|
||||
{{#js}}
|
||||
require(['aiplacement_courseassist/placement'], function(AICourseAssist) {
|
||||
const AI = new AICourseAssist({{userid}}, {{contextid}});
|
||||
});
|
||||
{{/js}}
|
43
ai/placement/courseassist/templates/error.mustache
Normal file
43
ai/placement/courseassist/templates/error.mustache
Normal file
@ -0,0 +1,43 @@
|
||||
{{!
|
||||
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 aiplacement_courseassist/error
|
||||
|
||||
Template to display error message when AI placement fails.
|
||||
|
||||
Context variables required for this template:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<section class="mb-3">
|
||||
<div class="card-body p-3">
|
||||
<h3 id="instance-9-header" class="h6 card-title d-inline">
|
||||
{{#pix}} req, core {{/pix}}
|
||||
{{#str}} generatefailtitle, aiplacement_courseassist {{/str}}
|
||||
</h3>
|
||||
<div class="card-text content mt-3">
|
||||
<div class="d-block pt-3">
|
||||
<button class="btn btn-sm btn-outline-secondary" data-action="course-summarise-retry">
|
||||
{{#str}} tryagain, aiplacement_courseassist {{/str}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
43
ai/placement/courseassist/templates/loading.mustache
Normal file
43
ai/placement/courseassist/templates/loading.mustache
Normal file
@ -0,0 +1,43 @@
|
||||
{{!
|
||||
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 aiplacement_courseassist/loading
|
||||
|
||||
Template to display loading message when AI placement is in progress.
|
||||
|
||||
Context variables required for this template:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<section class="card mb-3">
|
||||
<div class="card-body p-3">
|
||||
<h3 id="instance-9-header" class="h6 card-title d-inline">
|
||||
{{#pix}} i/loading, core {{/pix}}
|
||||
{{#str}} generating, aiplacement_courseassist {{/str}}
|
||||
</h3>
|
||||
<div class="card-text content mt-3">
|
||||
<div class="d-block pt-3">
|
||||
<button class="btn btn-sm btn-outline-secondary" data-action="course-summarise-cancel">
|
||||
{{#str}} cancel, core {{/str}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
57
ai/placement/courseassist/templates/response.mustache
Normal file
57
ai/placement/courseassist/templates/response.mustache
Normal file
@ -0,0 +1,57 @@
|
||||
{{!
|
||||
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 aiplacement_courseassist/response
|
||||
|
||||
Template to display reponse content for AI placement.
|
||||
|
||||
Context variables required for this template:
|
||||
* content - Content to display
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"content": "<p>Content to display</p>"
|
||||
}
|
||||
}}
|
||||
<section class="card mb-3">
|
||||
<div class="card-body p-3">
|
||||
<h3 id="instance-9-header" class="h5 card-title d-inline">
|
||||
{{#str}} aisummary, aiplacement_courseassist {{/str}}
|
||||
</h3>
|
||||
<div class="card-text content mt-3">
|
||||
<div id="course-summarise-response" class="mb-3">
|
||||
{{{content}}}
|
||||
</div>
|
||||
<div class="course-summarise-response-watermark">
|
||||
<small class="text-muted">
|
||||
{{#pix}} sparkles, aiplacement_courseassist {{/pix}}
|
||||
{{#str}} contentwatermark, core_ai {{/str}}
|
||||
</small>
|
||||
</div>
|
||||
<div class="course-summarise-response-controls d-block pt-3">
|
||||
<button class="btn btn-sm btn-outline-secondary" data-action="course-summarise-regenerate">
|
||||
{{#pix}} a/refresh, core {{/pix}}
|
||||
{{#str}} regenerate, aiplacement_courseassist {{/str}}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-action="copytoclipboard" data-clipboard-target="#course-summarise-response">
|
||||
{{#pix}} e/copy, core {{/pix}}
|
||||
{{#str}} copy, aiplacement_courseassist {{/str}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -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 aiplacement_courseassist/summarise_button
|
||||
|
||||
Template to display summarise button for the course assist.
|
||||
|
||||
Context variables required for this template:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div class="ai-course-summarise-controls pt-3 pb-3">
|
||||
<button class="btn btn-outline-secondary"
|
||||
aria-controls="ai-drawer"
|
||||
type="button"
|
||||
data-action="course-summarise"
|
||||
data-toggle="tooltip"
|
||||
data-html="true"
|
||||
title="{{#str}} summarise_tooltips, aiplacement_courseassist {{/str}}"
|
||||
>
|
||||
<span class="ai-course-summarise-sparkles-icon">
|
||||
{{#pix}} sparkles, aiplacement_courseassist {{/pix}}
|
||||
</span>
|
||||
<span class="ai-course-summarise-sparkles-icon white">
|
||||
{{#pix}} sparkles-white, aiplacement_courseassist {{/pix}}
|
||||
</span>
|
||||
{{#str}} summarise, aiplacement_courseassist {{/str}}
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,56 @@
|
||||
@core_ai @aiplacement_courseassist
|
||||
Feature: AI Course assist summarise
|
||||
In order to generate a summary of a module using AI, as a teacher, I need to be able to use the AI course assist summarise feature
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | t1@example.com |
|
||||
| teacher2 | Teacher | 2 | t2@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | topics |
|
||||
And the following "roles" exist:
|
||||
| name | shortname | description | archetype |
|
||||
| Custom editing teacher | custom1 | My custom role 1 | editingteacher |
|
||||
| Custom teacher | custom2 | My custom role 2 | editingteacher |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | custom1 |
|
||||
| teacher2 | C1 | custom2 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | introformat | course | content | contentformat | idnumber |
|
||||
| page | PageName1 | PageDesc1 | 1 | C1 | PageContent | 1 | 1 |
|
||||
And the following "permission overrides" exist:
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| aiplacement/courseassist:summarise_text | Prohibit | custom2 | Course | C1 |
|
||||
And I log in as "admin"
|
||||
And I enable "openai" "aiprovider" plugin
|
||||
And I enable "courseassist" "aiplacement" plugin
|
||||
|
||||
@javascript
|
||||
Scenario: Summarise text using AI is not available if placement is not enabled
|
||||
Given I disable "courseassist" "aiplacement" plugin
|
||||
When I am on the "PageName1" "page activity" page logged in as teacher1
|
||||
Then "Summarise" "button" should not exist
|
||||
And I enable "courseassist" "aiplacement" plugin
|
||||
And I am on the "PageName1" "page activity" page logged in as teacher1
|
||||
And "Summarise" "button" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: Summarise text using AI is not available if provider is not enabled
|
||||
Given I disable "openai" "aiprovider" plugin
|
||||
When I am on the "PageName1" "page activity" page logged in as teacher1
|
||||
Then "Summarise" "button" should not exist
|
||||
And I enable "openai" "aiprovider" plugin
|
||||
And I am on the "PageName1" "page activity" page logged in as teacher1
|
||||
And "Summarise" "button" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: Summarise text using AI is not available if the user does not have permission
|
||||
When I am on the "PageName1" "page activity" page logged in as teacher2
|
||||
Then "Summarise" "button" should not exist
|
||||
When I am on the "PageName1" "page activity" page logged in as teacher1
|
||||
And "Summarise" "button" should exist
|
||||
And I click on "Summarise" "button"
|
||||
And I should see "Welcome to the new AI feature!" in the ".ai-drawer" "css_element"
|
30
ai/placement/courseassist/version.php
Normal file
30
ai/placement/courseassist/version.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Version information for aiplacement_courseassist.
|
||||
*
|
||||
* @package aiplacement_courseassist
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'aiplacement_courseassist';
|
||||
$plugin->version = 2024061401;
|
||||
$plugin->requires = 2024041600;
|
||||
$plugin->maturity = MATURITY_ALPHA;
|
@ -22,8 +22,6 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['accept'] = 'Accept and continue';
|
||||
$string['cancelai'] = 'Decline';
|
||||
$string['generatecontent'] = 'Generate AI Content';
|
||||
$string['generateimage'] = 'AI generate image';
|
||||
$string['generateimagesetting'] = 'Enable generate image';
|
||||
|
46
ai/templates/policyblock.mustache
Normal file
46
ai/templates/policyblock.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/policyblock
|
||||
|
||||
Template to display AI policy in the block drawer.
|
||||
|
||||
Context variables required for this template:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<section class="card mb-3">
|
||||
<div class="card-body p-3 ai-policy-block">
|
||||
<h3 id="instance-9-header" class="h5 card-title d-inline">
|
||||
{{#str}} aiusagepolicy, core_ai {{/str}}
|
||||
</h3>
|
||||
<div class="card-text content mt-3">
|
||||
{{#str}} userpolicy, core_ai {{/str}}
|
||||
<div class="d-block pt-3">
|
||||
<button class="btn btn-sm btn-secondary" data-action="decline">
|
||||
{{#str}} declineaipolicy, core_ai {{/str}}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" data-action="accept">
|
||||
{{#str}} acceptai, core_ai {{/str}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -51,8 +51,8 @@
|
||||
{{/body}}
|
||||
|
||||
{{$footer}}
|
||||
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancelai, aiplacement_editor {{/str}}</button>
|
||||
<button type="button" class="btn btn-primary" data-action="save">{{#str}} accept, aiplacement_editor {{/str}}</button>
|
||||
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} declineaipolicy, core_ai {{/str}}</button>
|
||||
<button type="button" class="btn btn-primary" data-action="save">{{#str}} acceptai, core_ai {{/str}}</button>
|
||||
{{/footer}}
|
||||
|
||||
{{/ core/modal }}
|
||||
|
@ -22,6 +22,7 @@
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
$string['acceptai'] = 'Accept and continue';
|
||||
$string['action'] = 'Action';
|
||||
$string['action_generate_image'] = 'Generate image';
|
||||
$string['action_generate_image_desc'] = 'Generates an image based on a text prompt.';
|
||||
@ -59,7 +60,8 @@ $string['availableproviders'] = 'Available AI providers';
|
||||
$string['availableproviders_desc'] = 'Select an AI provider to manage its settings.<br/>
|
||||
AI providers are responsible for providing the AI services used by the AI subsystem. <br/>
|
||||
Each enabled provider makes available one or more "AI Actions". Actions can be enabled or disabled for each provider in the provider plugin settings.';
|
||||
$string['imagewatermark'] = 'Generated by AI';
|
||||
$string['contentwatermark'] = 'Generated by AI';
|
||||
$string['declineaipolicy'] = 'Decline';
|
||||
$string['manageaiplacements'] = 'Manage AI placements';
|
||||
$string['manageaiproviders'] = 'Manage AI providers';
|
||||
$string['noproviders'] = 'This action is unavailable. No <a href="{$a}">providers</a> are configured for this action';
|
||||
@ -114,11 +116,11 @@ They control how the provider connects to the AI service, and related operations
|
||||
$string['userpolicy'] = '<b>Welcome to the new AI feature!</b>
|
||||
<br>
|
||||
This Artificial Intelligence (AI) feature is based solely on external Large Language Models (LLM) to improve your learning and teaching experience. Before you start using these AI services, please read this usage policy.
|
||||
<br>
|
||||
<br><br>
|
||||
<b>Accuracy of AI-generated content</b>
|
||||
<br>
|
||||
AI can provide useful suggestions and information, but its accuracy may vary. You should always double-check the information provided to make sure it\'s accurate, complete, and suitable for your specific situation.
|
||||
<br>
|
||||
<br><br>
|
||||
<b>How your data is processed</b>
|
||||
<br>
|
||||
This AI feature is provided by external, third-party LLMs. If you choose to use this feature, any inputs or personal data you submit will be processed in accordance with the privacy policy of the third-party service. We recommend that you review the LLM\'s privacy to understand how your personal data will be handled.
|
||||
|
65
lib/classes/hook/output/after_http_headers.php
Normal file
65
lib/classes/hook/output/after_http_headers.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\hook\output;
|
||||
|
||||
/**
|
||||
* Class after_http_headers
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @property-read \renderer_base $renderer The page renderer object
|
||||
*/
|
||||
#[\core\attribute\tags('output')]
|
||||
#[\core\attribute\label('Allows plugins to make changes after headers are sent')]
|
||||
class after_http_headers {
|
||||
/**
|
||||
* Hook to allow subscribers to modify the process after headers are sent.
|
||||
*
|
||||
* @param \renderer_base $renderer
|
||||
* @param string $output
|
||||
*/
|
||||
public function __construct(
|
||||
/** @var \renderer_base The page renderer object */
|
||||
public readonly \renderer_base $renderer,
|
||||
/** @var string The collected output */
|
||||
private string $output = '',
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugins implementing callback can add any HTML after headers content.
|
||||
*
|
||||
* Must be a string containing valid html content.
|
||||
*
|
||||
* @param null|string $output
|
||||
*/
|
||||
public function add_html(?string $output): void {
|
||||
if ($output) {
|
||||
$this->output .= $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all HTML added by the plugins
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_output(): string {
|
||||
return $this->output;
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ namespace core\output;
|
||||
|
||||
use breadcrumb_navigation_node;
|
||||
use cm_info;
|
||||
use core\hook\output\after_http_headers;
|
||||
use core_block\output\block_contents;
|
||||
use core_block\output\block_move_target;
|
||||
use core_completion\cm_completion_details;
|
||||
@ -937,7 +938,14 @@ class core_renderer extends renderer_base {
|
||||
if (!$this->page->cm || !empty($this->page->layout_options['noactivityheader'])) {
|
||||
$header .= $this->skip_link_target('maincontent');
|
||||
}
|
||||
return $header;
|
||||
|
||||
$hook = new after_http_headers(
|
||||
renderer: $this,
|
||||
output: $header,
|
||||
);
|
||||
di::get(hook_manager::class)->dispatch($hook);
|
||||
|
||||
return $hook->get_output();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,3 +1,3 @@
|
||||
define("tiny_aiplacement/generatetext",["exports","./textmodal","core/ajax","core/str","core/templates","./options","./textmark","./generatebase"],(function(_exports,_textmodal,_ajax,_str,_templates,_options,_textmark,_generatebase){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_textmodal=_interopRequireDefault(_textmodal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_textmark=_interopRequireDefault(_textmark),_generatebase=_interopRequireDefault(_generatebase);class GenerateText extends _generatebase.default{constructor(){var obj,key,value;super(...arguments),value={GENERATEBUTTON:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generatebutton"]'),PROMPTAREA:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textprompt"]'),RESPONSEWRAPPER:".tiny_aiplacement_textresponse",RESPONSEPLACEHOLDER:".tiny_aiplacement_textresponse_placeholder",GENERATEDRESPONSE:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textresponse"]'),INSERTBTN:'[data-action="inserter"]',BACKTBTN:'[data-action="back"]'},(key="SELECTORS")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}getModalClass(){return _textmodal.default}handleContentModalClick(e,root){const actions={generate:()=>this.handleSubmit(root,e.target),inserter:()=>this.handleInsert(root,e.target),cancel:()=>this.modalObject.destroy(),back:()=>{this.modalObject.destroy(),this.displayContentModal()}},actionKey=Object.keys(actions).find((key=>e.target.closest('[data-action="'.concat(key,'"]'))));actionKey&&(e.preventDefault(),actions[actionKey]())}setupPromptArea(root){const generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON()),promptArea=root.querySelector(this.SELECTORS.PROMPTAREA());promptArea.addEventListener("input",(()=>{generateBtn.disabled=""===promptArea.value.trim()}))}async handleSubmit(root,submitBtn){await this.displayLoading(root,submitBtn);const request={methodname:"aiplacement_editor_generate_text",args:this.getRequestArgs(root)};try{this.responseObj=await _ajax.default.call([request])[0],this.responseObj.error?this.handleGenerationError(root,submitBtn,""):(await this.displayGeneratedText(root),this.hideLoading(root,submitBtn))}catch(error){this.handleGenerationError(root,submitBtn,"")}}async handleInsert(root,submitBtn){await this.displayLoading(root,submitBtn);const generatedResponseDiv=root.querySelector(this.SELECTORS.GENERATEDRESPONSE()),wrappedEditedResponse=await _textmark.default.wrapEditedSections(this.responseObj.generatedcontent,generatedResponseDiv.value);this.responseObj.editedtext=this.replaceLineBreaks(wrappedEditedResponse);const formattedResponse=await _templates.default.render("tiny_aiplacement/textinsert",this.responseObj);this.editor.insertContent(formattedResponse),this.editor.execCommand("mceRepaint"),this.editor.windowManager.close(),this.modalObject.hide()}async handleGenerationError(root,submitBtn){let errorMessage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";errorMessage||(errorMessage=await(0,_str.getString)("errorgeneral","tiny_aiplacement")),this.modalObject.setBody(_templates.default.render("tiny_aiplacement/modalbodyerror",{errorMessage:errorMessage}));const backBtn=root.querySelector(this.SELECTORS.BACKTBTN),generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON());backBtn.classList.remove("hidden"),generateBtn.classList.add("hidden"),this.hideLoading(root,submitBtn)}async displayGeneratedText(root){root.querySelector(this.SELECTORS.INSERTBTN).classList.remove("hidden");root.querySelector(this.SELECTORS.GENERATEDRESPONSE()).value=this.responseObj.generatedcontent;root.querySelector(this.SELECTORS.RESPONSEWRAPPER).classList.remove("hidden");root.querySelector(this.SELECTORS.RESPONSEPLACEHOLDER).classList.add("hidden")}getRequestArgs(root){return{contextid:(0,_options.getContextId)(this.editor),prompttext:root.querySelector(this.SELECTORS.PROMPTAREA()).value}}replaceLineBreaks(text){const textWithBreaks=text.replace(/\n{2,}|\r\n/g,"<br/><br/>").replace(/\n/g,"<br/>");return"<p>".concat(textWithBreaks,"</p>")}}return _exports.default=GenerateText,_exports.default}));
|
||||
define("tiny_aiplacement/generatetext",["exports","./textmodal","core/ajax","core/str","core/templates","core_ai/helper","./options","./textmark","./generatebase"],(function(_exports,_textmodal,_ajax,_str,_templates,_helper,_options,_textmark,_generatebase){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_textmodal=_interopRequireDefault(_textmodal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_helper=_interopRequireDefault(_helper),_textmark=_interopRequireDefault(_textmark),_generatebase=_interopRequireDefault(_generatebase);class GenerateText extends _generatebase.default{constructor(){var obj,key,value;super(...arguments),value={GENERATEBUTTON:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generatebutton"]'),PROMPTAREA:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textprompt"]'),RESPONSEWRAPPER:".tiny_aiplacement_textresponse",RESPONSEPLACEHOLDER:".tiny_aiplacement_textresponse_placeholder",GENERATEDRESPONSE:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textresponse"]'),INSERTBTN:'[data-action="inserter"]',BACKTBTN:'[data-action="back"]'},(key="SELECTORS")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}getModalClass(){return _textmodal.default}handleContentModalClick(e,root){const actions={generate:()=>this.handleSubmit(root,e.target),inserter:()=>this.handleInsert(root,e.target),cancel:()=>this.modalObject.destroy(),back:()=>{this.modalObject.destroy(),this.displayContentModal()}},actionKey=Object.keys(actions).find((key=>e.target.closest('[data-action="'.concat(key,'"]'))));actionKey&&(e.preventDefault(),actions[actionKey]())}setupPromptArea(root){const generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON()),promptArea=root.querySelector(this.SELECTORS.PROMPTAREA());promptArea.addEventListener("input",(()=>{generateBtn.disabled=""===promptArea.value.trim()}))}async handleSubmit(root,submitBtn){await this.displayLoading(root,submitBtn);const request={methodname:"aiplacement_editor_generate_text",args:this.getRequestArgs(root)};try{this.responseObj=await _ajax.default.call([request])[0],this.responseObj.error?this.handleGenerationError(root,submitBtn,""):(await this.displayGeneratedText(root),this.hideLoading(root,submitBtn))}catch(error){this.handleGenerationError(root,submitBtn,"")}}async handleInsert(root,submitBtn){await this.displayLoading(root,submitBtn);const generatedResponseDiv=root.querySelector(this.SELECTORS.GENERATEDRESPONSE()),wrappedEditedResponse=await _textmark.default.wrapEditedSections(this.responseObj.generatedcontent,generatedResponseDiv.value);this.responseObj.editedtext=_helper.default.replaceLineBreaks(wrappedEditedResponse);const formattedResponse=await _templates.default.render("tiny_aiplacement/textinsert",this.responseObj);this.editor.insertContent(formattedResponse),this.editor.execCommand("mceRepaint"),this.editor.windowManager.close(),this.modalObject.hide()}async handleGenerationError(root,submitBtn){let errorMessage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";errorMessage||(errorMessage=await(0,_str.getString)("errorgeneral","tiny_aiplacement")),this.modalObject.setBody(_templates.default.render("tiny_aiplacement/modalbodyerror",{errorMessage:errorMessage}));const backBtn=root.querySelector(this.SELECTORS.BACKTBTN),generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON());backBtn.classList.remove("hidden"),generateBtn.classList.add("hidden"),this.hideLoading(root,submitBtn)}async displayGeneratedText(root){root.querySelector(this.SELECTORS.INSERTBTN).classList.remove("hidden");root.querySelector(this.SELECTORS.GENERATEDRESPONSE()).value=this.responseObj.generatedcontent;root.querySelector(this.SELECTORS.RESPONSEWRAPPER).classList.remove("hidden");root.querySelector(this.SELECTORS.RESPONSEPLACEHOLDER).classList.add("hidden")}getRequestArgs(root){return{contextid:(0,_options.getContextId)(this.editor),prompttext:root.querySelector(this.SELECTORS.PROMPTAREA()).value}}}return _exports.default=GenerateText,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=generatetext.min.js.map
|
File diff suppressed because one or more lines are too long
@ -5,6 +5,6 @@ define("tiny_aiplacement/mediaimage",["exports","core/str","tiny_media/image","c
|
||||
* @module tiny_aiplacement/mediaimage
|
||||
* @copyright 2024 Matt Porritt <matt.porritt@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_image=_interopRequireDefault(_image),_notification=_interopRequireDefault(_notification),(0,_prefetch.prefetchStrings)("core_ai",["imagewatermark"]);class AiMediaImage extends _image.default{constructor(editor,url,alt){super(editor),this.generatedImageUrl=url,this.altText=alt,(0,_str.getString)("imagewatermark","core_ai").then((watermark=>{this.watermark=watermark})).catch(_notification.default.exception)}getSelectedImage(){const imgElement=document.createElement("img");return imgElement.src=this.generatedImageUrl,imgElement.alt=this.truncateAltText(this.altText),imgElement}truncateAltText(altText){const watermark=" - "+this.watermark;if(altText.length+watermark.length<=125)altText+=watermark;else{const remainingLength=125-watermark.length-"...".length;altText=altText.substring(0,remainingLength)+"..."+watermark}return altText}}return _exports.default=AiMediaImage,_exports.default}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_image=_interopRequireDefault(_image),_notification=_interopRequireDefault(_notification),(0,_prefetch.prefetchStrings)("core_ai",["imagewatermark"]);class AiMediaImage extends _image.default{constructor(editor,url,alt){super(editor),this.generatedImageUrl=url,this.altText=alt,(0,_str.getString)("contentwatermark","core_ai").then((watermark=>{this.watermark=watermark})).catch(_notification.default.exception)}getSelectedImage(){const imgElement=document.createElement("img");return imgElement.src=this.generatedImageUrl,imgElement.alt=this.truncateAltText(this.altText),imgElement}truncateAltText(altText){const watermark=" - "+this.watermark;if(altText.length+watermark.length<=125)altText+=watermark;else{const remainingLength=125-watermark.length-"...".length;altText=altText.substring(0,remainingLength)+"..."+watermark}return altText}}return _exports.default=AiMediaImage,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=mediaimage.min.js.map
|
@ -1 +1 @@
|
||||
{"version":3,"file":"mediaimage.min.js","sources":["../src/mediaimage.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * AI Modal for Tiny.\n *\n * @module tiny_aiplacement/mediaimage\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\nimport {getString} from 'core/str';\nimport MediaImage from 'tiny_media/image';\nimport Notification from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\n\nprefetchStrings('core_ai', [\n 'imagewatermark',\n]);\n\nexport default class AiMediaImage extends MediaImage {\n constructor(editor, url, alt) {\n super(editor); // Call the parent class constructor\n this.generatedImageUrl = url;\n this.altText = alt;\n getString('imagewatermark', 'core_ai').then((watermark) => {\n this.watermark = watermark;\n return;\n }).catch(Notification.exception);\n }\n\n getSelectedImage() {\n const imgElement = document.createElement('img');\n\n // Set attributes for the img element\n imgElement.src = this.generatedImageUrl;\n imgElement.alt = this.truncateAltText(this.altText);\n\n return imgElement;\n }\n\n /**\n * Truncate the alt text if it is longer than the maximum length.\n * @param {String} altText The alt text\n * @return {string} The truncated alt text\n */\n truncateAltText(altText) {\n const maximumAltTextLength = 125;\n const watermark = ' - ' + this.watermark;\n const ellipsis = '...';\n\n // Append the watermark to the alt text.\n if (altText.length + watermark.length <= maximumAltTextLength) {\n altText = altText + watermark;\n } else {\n const remainingLength = maximumAltTextLength - watermark.length - ellipsis.length;\n altText = altText.substring(0, remainingLength) + ellipsis + watermark;\n }\n return altText;\n }\n}\n"],"names":["AiMediaImage","MediaImage","constructor","editor","url","alt","generatedImageUrl","altText","then","watermark","catch","Notification","exception","getSelectedImage","imgElement","document","createElement","src","this","truncateAltText","length","remainingLength","substring"],"mappings":";;;;;;;6MA4BgB,UAAW,CACvB,yBAGiBA,qBAAqBC,eACtCC,YAAYC,OAAQC,IAAKC,WACfF,aACDG,kBAAoBF,SACpBG,QAAUF,uBACL,iBAAkB,WAAWG,MAAMC,iBACpCA,UAAYA,aAElBC,MAAMC,sBAAaC,WAG1BC,yBACUC,WAAaC,SAASC,cAAc,cAG1CF,WAAWG,IAAMC,KAAKZ,kBACtBQ,WAAWT,IAAMa,KAAKC,gBAAgBD,KAAKX,SAEpCO,WAQXK,gBAAgBZ,eAENE,UAAY,MAAQS,KAAKT,aAI3BF,QAAQa,OAASX,UAAUW,QALF,IAMzBb,SAAoBE,cACjB,OACGY,gBARmB,IAQsBZ,UAAUW,OAN5C,MAM8DA,OAC3Eb,QAAUA,QAAQe,UAAU,EAAGD,iBAPlB,MAOgDZ,iBAE1DF"}
|
||||
{"version":3,"file":"mediaimage.min.js","sources":["../src/mediaimage.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * AI Modal for Tiny.\n *\n * @module tiny_aiplacement/mediaimage\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\nimport {getString} from 'core/str';\nimport MediaImage from 'tiny_media/image';\nimport Notification from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\n\nprefetchStrings('core_ai', [\n 'imagewatermark',\n]);\n\nexport default class AiMediaImage extends MediaImage {\n constructor(editor, url, alt) {\n super(editor); // Call the parent class constructor\n this.generatedImageUrl = url;\n this.altText = alt;\n getString('contentwatermark', 'core_ai').then((watermark) => {\n this.watermark = watermark;\n return;\n }).catch(Notification.exception);\n }\n\n getSelectedImage() {\n const imgElement = document.createElement('img');\n\n // Set attributes for the img element\n imgElement.src = this.generatedImageUrl;\n imgElement.alt = this.truncateAltText(this.altText);\n\n return imgElement;\n }\n\n /**\n * Truncate the alt text if it is longer than the maximum length.\n * @param {String} altText The alt text\n * @return {string} The truncated alt text\n */\n truncateAltText(altText) {\n const maximumAltTextLength = 125;\n const watermark = ' - ' + this.watermark;\n const ellipsis = '...';\n\n // Append the watermark to the alt text.\n if (altText.length + watermark.length <= maximumAltTextLength) {\n altText = altText + watermark;\n } else {\n const remainingLength = maximumAltTextLength - watermark.length - ellipsis.length;\n altText = altText.substring(0, remainingLength) + ellipsis + watermark;\n }\n return altText;\n }\n}\n"],"names":["AiMediaImage","MediaImage","constructor","editor","url","alt","generatedImageUrl","altText","then","watermark","catch","Notification","exception","getSelectedImage","imgElement","document","createElement","src","this","truncateAltText","length","remainingLength","substring"],"mappings":";;;;;;;6MA4BgB,UAAW,CACvB,yBAGiBA,qBAAqBC,eACtCC,YAAYC,OAAQC,IAAKC,WACfF,aACDG,kBAAoBF,SACpBG,QAAUF,uBACL,mBAAoB,WAAWG,MAAMC,iBACtCA,UAAYA,aAElBC,MAAMC,sBAAaC,WAG1BC,yBACUC,WAAaC,SAASC,cAAc,cAG1CF,WAAWG,IAAMC,KAAKZ,kBACtBQ,WAAWT,IAAMa,KAAKC,gBAAgBD,KAAKX,SAEpCO,WAQXK,gBAAgBZ,eAENE,UAAY,MAAQS,KAAKT,aAI3BF,QAAQa,OAASX,UAAUW,QALF,IAMzBb,SAAoBE,cACjB,OACGY,gBARmB,IAQsBZ,UAAUW,OAN5C,MAM8DA,OAC3Eb,QAAUA,QAAQe,UAAU,EAAGD,iBAPlB,MAOgDZ,iBAE1DF"}
|
@ -25,6 +25,7 @@ import TextModal from './textmodal';
|
||||
import Ajax from 'core/ajax';
|
||||
import {getString} from 'core/str';
|
||||
import Templates from 'core/templates';
|
||||
import AIHelper from 'core_ai/helper';
|
||||
import {getContextId} from './options';
|
||||
import TinyAiTextMarker from './textmark';
|
||||
import GenerateBase from './generatebase';
|
||||
@ -131,7 +132,7 @@ export default class GenerateText extends GenerateBase {
|
||||
;
|
||||
|
||||
// Replace double line breaks with <br> and with </p><p> for paragraphs.
|
||||
this.responseObj.editedtext = this.replaceLineBreaks(wrappedEditedResponse);
|
||||
this.responseObj.editedtext = AIHelper.replaceLineBreaks(wrappedEditedResponse);
|
||||
|
||||
// Generate the HTML for the response.
|
||||
const formattedResponse = await Templates.render('tiny_aiplacement/textinsert', this.responseObj);
|
||||
@ -197,22 +198,4 @@ export default class GenerateText extends GenerateBase {
|
||||
prompttext: promptText
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace double line breaks with <br> and with </p><p> for paragraphs.
|
||||
* This is to handle the difference in response from the AI to what is expected by the editor.
|
||||
*
|
||||
* @param {String} text The text to replace.
|
||||
* @returns {String}
|
||||
*/
|
||||
replaceLineBreaks(text) {
|
||||
// Replace double line breaks with </p><p> for paragraphs
|
||||
const textWithParagraphs = text.replace(/\n{2,}|\r\n/g, '<br/><br/>');
|
||||
|
||||
// Replace remaining single line breaks with <br> tags
|
||||
const textWithBreaks = textWithParagraphs.replace(/\n/g, '<br/>');
|
||||
|
||||
// Add opening and closing <p> tags to wrap the entire content
|
||||
return `<p>${textWithBreaks}</p>`;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export default class AiMediaImage extends MediaImage {
|
||||
super(editor); // Call the parent class constructor
|
||||
this.generatedImageUrl = url;
|
||||
this.altText = alt;
|
||||
getString('imagewatermark', 'core_ai').then((watermark) => {
|
||||
getString('contentwatermark', 'core_ai').then((watermark) => {
|
||||
this.watermark = watermark;
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"standard": {
|
||||
"aiplacement": [
|
||||
"courseassist",
|
||||
"editor"
|
||||
],
|
||||
"aiprovider": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user