MDL-80890 AI: Course Assistance Placement

Co-authored-by: Matt Porritt <[email protected]>
This commit is contained in:
Huong Nguyen
2024-09-19 09:30:10 +07:00
co-authored by Matt Porritt
parent f6141a67d8
commit cb4e621ce9
43 changed files with 1537 additions and 33 deletions
@@ -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 <[email protected]>\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 <[email protected]>\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);