From 4e470380d3c53fa138dfd59e8c9d2249f58e0ce2 Mon Sep 17 00:00:00 2001 From: Tobias Garske Date: Thu, 14 Nov 2024 13:57:28 +0000 Subject: [PATCH] MBS-9493: Fix failing checks --- amd/build/ai_manager.min.js | 2 +- amd/build/ai_manager.min.js.map | 2 +- amd/build/dialog.min.js | 2 +- amd/build/dialog.min.js.map | 2 +- amd/build/helper.min.js | 2 +- amd/build/helper.min.js.map | 2 +- amd/src/ai_manager.js | 2 -- amd/src/dialog.js | 18 ++---------------- amd/src/helper.js | 3 +-- classes/external/delete_conversation.php | 2 ++ classes/local/hook_callbacks.php | 4 +++- classes/output/renderer.php | 1 + styles.css | 14 ++++++++------ styles.scss | 20 +++++++++++--------- templates/dialog_modal.mustache | 6 +++--- templates/floatingbutton.mustache | 4 ++-- templates/history.mustache | 4 ++-- templates/message.mustache | 12 +++++++++--- 18 files changed, 50 insertions(+), 52 deletions(-) diff --git a/amd/build/ai_manager.min.js b/amd/build/ai_manager.min.js index fca32a7..5908b82 100644 --- a/amd/build/ai_manager.min.js +++ b/amd/build/ai_manager.min.js @@ -1,3 +1,3 @@ -define("block_ai_chat/ai_manager",["exports","local_ai_manager/make_request","core/notification"],(function(_exports,_make_request,_notification){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.askLocalAiManager=void 0;_exports.askLocalAiManager=async function(purpose,prompt){let options=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],result={};try{result=await(0,_make_request.makeRequest)(purpose,prompt,options)}catch(error){console.log(error),result.code="aiconnector",result.result=error.error+" "+error.message,result.result+=error.backtrace}return result}})); +define("block_ai_chat/ai_manager",["exports","local_ai_manager/make_request"],(function(_exports,_make_request){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.askLocalAiManager=void 0;_exports.askLocalAiManager=async function(purpose,prompt){let options=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],result={};try{result=await(0,_make_request.makeRequest)(purpose,prompt,options)}catch(error){result.code="aiconnector",result.result=error.error+" "+error.message,result.result+=error.backtrace}return result}})); //# sourceMappingURL=ai_manager.min.js.map \ No newline at end of file diff --git a/amd/build/ai_manager.min.js.map b/amd/build/ai_manager.min.js.map index f58b68c..8acf45e 100644 --- a/amd/build/ai_manager.min.js.map +++ b/amd/build/ai_manager.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ai_manager.min.js","sources":["../src/ai_manager.js"],"sourcesContent":["import {makeRequest} from 'local_ai_manager/make_request';\nimport {exception as displayException} from 'core/notification';\n\n/**\n * Get the async answer from the local_ai_manager.\n *\n * @param {string} purpose\n * @param {string} prompt\n * @param {array} options\n * @returns {string}\n */\nexport const askLocalAiManager = async(purpose, prompt, options = []) => {\n let result = {};\n try {\n result = await makeRequest(purpose, prompt, options);\n } catch (error) {\n console.log(error);\n result.code = 'aiconnector';\n result.result = error.error + \" \" + error.message;\n // For devs.\n result.result += error.backtrace;\n }\n return result;\n};\n"],"names":["async","purpose","prompt","options","result","error","console","log","code","message","backtrace"],"mappings":"uQAWiCA,eAAMC,QAASC,YAAQC,+DAAU,GAC1DC,OAAS,OAETA,aAAe,6BAAYH,QAASC,OAAQC,SAC9C,MAAOE,OACLC,QAAQC,IAAIF,OACZD,OAAOI,KAAO,cACdJ,OAAOA,OAASC,MAAMA,MAAQ,IAAMA,MAAMI,QAE1CL,OAAOA,QAAUC,MAAMK,iBAEpBN"} \ No newline at end of file +{"version":3,"file":"ai_manager.min.js","sources":["../src/ai_manager.js"],"sourcesContent":["import {makeRequest} from 'local_ai_manager/make_request';\n\n/**\n * Get the async answer from the local_ai_manager.\n *\n * @param {string} purpose\n * @param {string} prompt\n * @param {array} options\n * @returns {string}\n */\nexport const askLocalAiManager = async(purpose, prompt, options = []) => {\n let result = {};\n try {\n result = await makeRequest(purpose, prompt, options);\n } catch (error) {\n result.code = 'aiconnector';\n result.result = error.error + \" \" + error.message;\n // For devs.\n result.result += error.backtrace;\n }\n return result;\n};\n"],"names":["async","purpose","prompt","options","result","error","code","message","backtrace"],"mappings":"qOAUiCA,eAAMC,QAASC,YAAQC,+DAAU,GAC1DC,OAAS,OAETA,aAAe,6BAAYH,QAASC,OAAQC,SAC9C,MAAOE,OACLD,OAAOE,KAAO,cACdF,OAAOA,OAASC,MAAMA,MAAQ,IAAMA,MAAME,QAE1CH,OAAOA,QAAUC,MAAMG,iBAEpBJ"} \ No newline at end of file diff --git a/amd/build/dialog.min.js b/amd/build/dialog.min.js index ef6f0d7..555a6cf 100644 --- a/amd/build/dialog.min.js +++ b/amd/build/dialog.min.js @@ -1,3 +1,3 @@ -define("block_ai_chat/dialog",["exports","core/modal","block_ai_chat/webservices","core/templates","core/notification","core/modal_events","block_ai_chat/helper","block_ai_chat/ai_manager","core/str","local_ai_manager/infobox","local_ai_manager/userquota","local_ai_manager/config","core/localstorage","./helper","core/config"],(function(_exports,_modal,externalServices,_templates,_notification,_modal_events,helper,manager,_str,_infobox,_userquota,_config,_localstorage,_helper2,_config2){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=_interopRequireDefault(_modal),externalServices=_interopRequireWildcard(externalServices),_templates=_interopRequireDefault(_templates),_modal_events=_interopRequireDefault(_modal_events),helper=_interopRequireWildcard(helper),manager=_interopRequireWildcard(manager),_localstorage=_interopRequireDefault(_localstorage),_config2=_interopRequireDefault(_config2);const VIEW_CHATWINDOW="block_ai_chat_chatwindow",VIEW_OPENFULL="block_ai_chat_openfull",VIEW_DOCKRIGHT="block_ai_chat_dockright";let strHistory,strNewDialog,strToday,strYesterday,badge,viewmode,modal={},modalopen=!1,conversation={id:0,messages:[]},allConversations=[],userid=0,contextid=0,firstLoad=!0,aiAtWork=!1,maxHistory=5,maxHistoryWarnings=new Set,tenantConfig={},chatConfig={};class DialogModal extends _modal.default{configure(modalConfig){modalConfig.show=!1,modalConfig.removeOnClose=!1,modalConfig.isVerticallyCentered=!1,super.configure(modalConfig),modalConfig.titletest&&this.setTitletest(modalConfig.titletest)}setTitletest(value){this.titletest=value}hide(){super.hide(),modalopen=!1;document.querySelector("body").classList.remove("block_ai_chat_open")}}_defineProperty(DialogModal,"TYPE","block_ai_chat/dialog_modal"),_defineProperty(DialogModal,"TEMPLATE","block_ai_chat/dialog_modal");_exports.init=async params=>{userid=params.userid,contextid=params.contextid,strNewDialog=params.new,strHistory=params.history,badge=params.badge,badge=!1;const aiConfig=await(0,_config.getAiConfig)();tenantConfig=aiConfig,chatConfig=aiConfig.purposes.find((p=>"chat"===p.purpose)),modal=await DialogModal.create({templateContext:{title:strNewDialog,badge:false}}),modal.getRoot().on("modal:shown",(function(e){e.target.classList.add("ai_chat_modal")})),modal.getRoot().on(_modal_events.default.outsideClick,(event=>{checkOutsideClick(event)})),setView(),document.getElementById("ai_chat_button").addEventListener("mousedown",(async()=>{!async function(){if(modalopen)return void modal.hide();await modal.show(),modalopen=!0;document.querySelector("body").classList.add("block_ai_chat_open");const textarea=document.getElementById("block_ai_chat-input-id");addTextareaListener(textarea);if(document.getElementById("block_ai_chat-submit-id").addEventListener("click",(event=>{clickSubmitButton(event)})),firstLoad){await getConversations(),showConversation();let conversationcontextLimit=await externalServices.getConversationcontextLimit(contextid);maxHistory=conversationcontextLimit.limit;document.getElementById("block_ai_chat_new_dialog").addEventListener("click",(()=>{newDialog()}));document.getElementById("block_ai_chat_delete_dialog").addEventListener("click",(()=>{deleteCurrentDialog()}));document.getElementById("block_ai_chat_show_history").addEventListener("click",(()=>{showHistory()}));document.getElementById(VIEW_CHATWINDOW).addEventListener("click",(()=>{setView(VIEW_CHATWINDOW)}));document.getElementById(VIEW_OPENFULL).addEventListener("click",(()=>{setView(VIEW_OPENFULL)}));document.getElementById(VIEW_DOCKRIGHT).addEventListener("click",(()=>{setView(VIEW_DOCKRIGHT)})),await(0,_userquota.renderUserQuota)("#block_ai_chat_userquota",["chat"]),await(0,_infobox.renderInfoBox)("block_ai_chat",userid,'.ai_chat_modal_body [data-content="local_ai_manager_infobox"]',["chat"]);const message=await userAllowed();if(""!==message){const notice=await(0,_str.getString)("notice","block_ai_chat");await(0,_notification.alert)(notice,message)}firstLoad=!1}helper.focustextarea()}()})),strToday=await(0,_str.getString)("today","core"),strYesterday=await(0,_str.getString)("yesterday","block_ai_chat");window.matchMedia("(max-width: 576px)").addEventListener("change",handleScreenWidthChange),window.innerWidth<=576&&setView(VIEW_OPENFULL)};const getConversations=async()=>{console.log("allConversations called");try{allConversations=await externalServices.getAllConversations(userid,contextid),console.log(allConversations)}catch(error){console.log(allConversations),(0,_notification.exception)(error)}},showConversation=function(){let id=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;console.log("showConversation called"),aiAtWork||(0!==id?conversation=allConversations.find((x=>x.id===id)):void 0!==allConversations[0]?conversation=allConversations.at(allConversations.length-1):0===allConversations.length&&newDialog(!0),clearMessages(),setModalHeader(),showMessages())};document.showConversation=showConversation;const enterQuestion=async question=>{if(""==question)return void(aiAtWork=!1);const message=await userAllowed();if(""!==message){console.log("User not allowed");const notice=await(0,_str.getString)("noticenewquestion","block_ai_chat");return await(0,_notification.alert)(notice,message),void(aiAtWork=!1)}if(showMessage(question,"self",!1),0===conversation.messages.length){const currentUserLanguage=_config2.default.language.substring(0,2),LangNames=new Intl.DisplayNames("en",{type:"language"});conversation.messages.push({message:"Answer in "+LangNames.of(currentUserLanguage),sender:"system"})}const convHistory=await checkMessageHistoryLengthLimit(conversation.messages),options={component:"block_ai_chat",contextid:contextid,conversationcontext:convHistory};if(0===conversation.id){try{let idresult=await externalServices.getNewConversationId(contextid);conversation.id=idresult.id,conversation.timecreated=Math.floor(Date.now()/1e3),setModalHeader((0,_helper2.escapeHTML)(question))}catch(error){(0,_notification.exception)(error)}options.forcenewitemid=!0}options.itemid=conversation.id;let requestresult=await manager.askLocalAiManager("chat",question,options);200!=requestresult.code&&(requestresult=await errorHandling(requestresult,question,options));let copy=document.querySelector(".ai_chat_modal .awaitanswer .copy");copy.addEventListener("mousedown",(()=>{helper.copyToClipboard(copy)})),showReply(requestresult.result),aiAtWork=!1,saveConversationLocally(question,requestresult.result);document.getElementById("block_ai_chat_userquota").innerHTML="",(0,_userquota.renderUserQuota)("#block_ai_chat_userquota",["chat"])},showReply=async text=>{let fields=document.querySelectorAll(".ai_chat_modal .awaitanswer .text");const field=fields[fields.length-1];field.innerHTML=text,field.classList.remove("small");let awaitdivs=document.querySelectorAll(".ai_chat_modal .awaitanswer");awaitdivs[awaitdivs.length-1].classList.remove("awaitanswer")},showMessages=()=>{console.log("showMessages called"),conversation.messages.forEach((val=>{showMessage(val.message,val.sender)}))},showMessage=async function(text){let sender=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",answer=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if("system"===sender)return;"ai"===sender&&(sender=""),answer||(text=(0,_helper2.escapeHTML)(text));const templateData={sender:sender,content:text,answer:answer},{html:html,js:js}=await _templates.default.renderForPromise("block_ai_chat/message",templateData);_templates.default.appendNodeContents(".block_ai_chat-output",html,js),""===sender&&helper.attachCopyListenerLast(),helper.scrollToBottom()},newDialog=async function(){let deleted=arguments.length>0&&void 0!==arguments[0]&&arguments[0];console.log("newDialog called"),aiAtWork||(void 0!==allConversations.find((x=>x.id===conversation.id))||deleted||allConversations.push(conversation),conversation={id:0,messages:[]},clearMessages(),setModalHeader(strNewDialog),helper.focustextarea())},deleteCurrentDialog=()=>{console.log("deleteCurrentDialog called"),(0,_notification.deleteCancelPromise)((0,_str.getString)("delete","block_ai_chat"),(0,_str.getString)("deletewarning","block_ai_chat")).then((async()=>{if(0!==conversation.id)try{await externalServices.deleteConversation(contextid,userid,conversation.id)&&(removeFromHistory(),showConversation())}catch(error){(0,_notification.exception)(error)}})).catch((()=>{}))},showHistory=async()=>{console.log("showHistory called"),void 0===allConversations.find((x=>x.id===conversation.id))&&allConversations.push(conversation);let title=''+strHistory+"";clearMessages(!0),setModalHeader(title);document.getElementById("block_ai_chat_backlink").addEventListener("click",(()=>{0!==conversation.id?showConversation(conversation.id):newDialog(),clearMessages(),setModalHeader()})),document.querySelector(".ai_chat_modal").classList.add("onhistorypage");let groupedByDate={};allConversations.forEach((convo=>{if(void 0!==convo.messages[1]){let title=convo.messages[1].message;const now=new Date,date=new Date(1e3*convo.timecreated),today=new Date(now.getFullYear(),now.getMonth(),now.getDate()),yesterday=new Date(now.getFullYear(),now.getMonth(),now.getDate()-1),twoWeeksAgo=new Date(now);twoWeeksAgo.setDate(now.getDate()-14);const options={weekday:"long",day:"2-digit",month:"2-digit"},monthOptions={month:"long",year:"2-digit"};let dateString="";dateString=date>=today?strToday:date>=yesterday?strYesterday:date>=twoWeeksAgo?date.toLocaleDateString(void 0,options):date.toLocaleDateString(void 0,monthOptions);const hours=date.getHours(),minutes=date.getMinutes().toString().padStart(2,"0");let convItem={title:title,conversationid:convo.id,time:hours+":"+minutes};groupedByDate[dateString]||(groupedByDate[dateString]=[]),groupedByDate[dateString].push(convItem)}}));const templateData={dates:{groups:Object.keys(groupedByDate).map((key=>({key:key,objects:groupedByDate[key]})))}.groups},{html:html,js:js}=await _templates.default.renderForPromise("block_ai_chat/history",templateData);_templates.default.appendNodeContents(".ai_chat_modal .block_ai_chat-output",html,js);document.getElementById("ai_chat_history_new_dialog").addEventListener("mousedown",(()=>{newDialog()}))},removeFromHistory=()=>{0!==conversation.id&&void 0!==allConversations.find((x=>x.id===conversation.id))&&(allConversations=allConversations.filter((obj=>obj.id!==conversation.id)))},saveConversationLocally=(question,reply)=>{let message={message:question,sender:"user"};conversation.messages.push(message),message={message:reply,sender:"ai"},conversation.messages.push(message)},clearMessages=function(){let hideinput=arguments.length>0&&void 0!==arguments[0]&&arguments[0];console.log("clearMessages called");const output=document.querySelector(".block_ai_chat-output");output.innerHTML="";let input=document.querySelector(".block_ai_chat-input");input.style.display=hideinput?"none":"flex"},setModalHeader=function(){let setTitle=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",modalheader=document.querySelector(".ai_chat_modal .modal-title div"),title="";null!==modalheader&&(conversation.messages.length>0||setTitle.length)&&(title=setTitle.length?setTitle:conversation.messages[1].message,modalheader.innerHTML=title);let modal=document.querySelector(".ai_chat_modal");modal.classList.remove("onhistorypage")},addTextareaListener=textarea=>{textarea.addEventListener("keydown",(event=>{textareaOnKeydown(event),textarea.style.height="auto";const computedStyles=window.getComputedStyle(textarea),lineHeight=parseFloat(computedStyles.lineHeight),paddingTop=parseFloat(computedStyles.paddingTop),paddingBottom=parseFloat(computedStyles.paddingBottom),borderTop=parseFloat(computedStyles.borderTopWidth),borderBottom=parseFloat(computedStyles.borderBottomWidth),maxHeight=4*lineHeight+paddingTop+paddingBottom+borderTop+borderBottom,newHeight=Math.min(textarea.scrollHeight+borderTop+borderBottom,maxHeight);textarea.style.height=newHeight+"px"}))},textareaOnKeydown=event=>{"Enter"!==event.key||aiAtWork||event.shiftKey||(aiAtWork=!0,enterQuestion(event.target.value),event.preventDefault(),event.target.value="")},clickSubmitButton=()=>{if(!aiAtWork){aiAtWork=!0;const textarea=document.getElementById("block_ai_chat-input-id");enterQuestion(textarea.value),textarea.value=""}},errorHandling=async(requestresult,question,options)=>{if(409==requestresult.code)for(;409==requestresult.code;){try{let idresult=await externalServices.getNewConversationId(contextid);conversation.id=idresult.id,options.itemid=conversation.id}catch(error){(0,_notification.exception)(error)}return requestresult=await manager.askLocalAiManager("chat",question,options)}const errorString=await(0,_str.getString)("errorwithcode","block_ai_chat",requestresult.code),result=JSON.parse(requestresult.result);await(0,_notification.alert)(errorString,result.message);const answerdivs=document.querySelectorAll(".awaitanswer");return answerdivs[answerdivs.length-1].closest(".message").classList.add("text-danger"),requestresult.result=await(0,_str.getString)("error","block_ai_chat"),console.log(requestresult),requestresult},checkMessageHistoryLengthLimit=async messages=>{const length=messages.length;if(console.log("checkHistoryLengthLimit called"),length>maxHistory){let shortenedMessages=[messages[0],...messages.slice(-maxHistory)];if(console.log(shortenedMessages),!maxHistoryWarnings.has(conversation.id)){const maxHistoryString=await(0,_str.getString)("maxhistory","block_ai_chat",maxHistory),warningErrorString=await(0,_str.getString)("maxhistoryreached","block_ai_chat",maxHistory);await(0,_notification.alert)(maxHistoryString,warningErrorString),maxHistoryWarnings.add(conversation.id)}return shortenedMessages}return messages},checkOutsideClick=event=>{viewmode!=VIEW_OPENFULL&&event.preventDefault()},setView=async function(){let mode=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";const key=await(0,_helper2.hash)("chatmode"+userid);let savedmode=_localstorage.default.get(key);""==mode&&(mode=savedmode||VIEW_CHATWINDOW),_localstorage.default.set(key,mode),viewmode=mode;const body=document.querySelector("body");body.classList.remove(VIEW_CHATWINDOW,VIEW_OPENFULL,VIEW_DOCKRIGHT),body.classList.add(mode)},userAllowed=async()=>{if(!1===tenantConfig.tenantenabled)return message=await(0,_str.getString)("error_http403disabled","local_ai_manager"),message;if(!1===tenantConfig.userconfirmed){message=await(0,_str.getString)("error_http403notconfirmed","local_ai_manager"),message+=". ";const link=window.location.origin+"/local/ai_manager/confirm_ai_usage.php";return message+=await(0,_str.getString)("confirm_ai_usage","block_ai_chat",link),message}return!0===tenantConfig.userlocked?(message=await(0,_str.getString)("error_http403blocked","local_ai_manager"),message):!1===chatConfig.isconfigured?(message=await(0,_str.getString)("error_purposenotconfigured","local_ai_manager"),message):!0===chatConfig.lockedforrole?(message=await(0,_str.getString)("error_http403blocked","local_ai_manager"),message):!0===chatConfig.limitreached?(message=await(0,_str.getString)("error_limitreached","local_ai_manager"),message):""},handleScreenWidthChange=e=>{const body=document.querySelector("body");e.matches?(body.classList.remove(VIEW_CHATWINDOW,VIEW_OPENFULL,VIEW_DOCKRIGHT),body.classList.add(VIEW_OPENFULL)):(body.classList.remove(VIEW_CHATWINDOW,VIEW_OPENFULL,VIEW_DOCKRIGHT),body.classList.add(viewmode))}})); +define("block_ai_chat/dialog",["exports","core/modal","block_ai_chat/webservices","core/templates","core/notification","core/modal_events","block_ai_chat/helper","block_ai_chat/ai_manager","core/str","local_ai_manager/infobox","local_ai_manager/userquota","local_ai_manager/config","core/localstorage","./helper","core/config"],(function(_exports,_modal,externalServices,_templates,_notification,_modal_events,helper,manager,_str,_infobox,_userquota,_config,_localstorage,_helper2,_config2){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=_interopRequireDefault(_modal),externalServices=_interopRequireWildcard(externalServices),_templates=_interopRequireDefault(_templates),_modal_events=_interopRequireDefault(_modal_events),helper=_interopRequireWildcard(helper),manager=_interopRequireWildcard(manager),_localstorage=_interopRequireDefault(_localstorage),_config2=_interopRequireDefault(_config2);const VIEW_CHATWINDOW="block_ai_chat_chatwindow",VIEW_OPENFULL="block_ai_chat_openfull",VIEW_DOCKRIGHT="block_ai_chat_dockright";let strHistory,strNewDialog,strToday,strYesterday,badge,viewmode,modal={},modalopen=!1,conversation={id:0,messages:[]},allConversations=[],userid=0,contextid=0,firstLoad=!0,aiAtWork=!1,maxHistory=5,maxHistoryWarnings=new Set,tenantConfig={},chatConfig={};class DialogModal extends _modal.default{configure(modalConfig){modalConfig.show=!1,modalConfig.removeOnClose=!1,modalConfig.isVerticallyCentered=!1,super.configure(modalConfig),modalConfig.titletest&&this.setTitletest(modalConfig.titletest)}setTitletest(value){this.titletest=value}hide(){super.hide(),modalopen=!1;document.querySelector("body").classList.remove("block_ai_chat_open")}}_defineProperty(DialogModal,"TYPE","block_ai_chat/dialog_modal"),_defineProperty(DialogModal,"TEMPLATE","block_ai_chat/dialog_modal");_exports.init=async params=>{userid=params.userid,contextid=params.contextid,strNewDialog=params.new,strHistory=params.history,badge=params.badge,badge=!1;const aiConfig=await(0,_config.getAiConfig)();tenantConfig=aiConfig,chatConfig=aiConfig.purposes.find((p=>"chat"===p.purpose)),modal=await DialogModal.create({templateContext:{title:strNewDialog,badge:false}}),modal.getRoot().on("modal:shown",(function(e){e.target.classList.add("ai_chat_modal")})),modal.getRoot().on(_modal_events.default.outsideClick,(event=>{checkOutsideClick(event)})),setView(),document.getElementById("ai_chat_button").addEventListener("mousedown",(async()=>{!async function(){if(modalopen)return void modal.hide();await modal.show(),modalopen=!0;document.querySelector("body").classList.add("block_ai_chat_open");const textarea=document.getElementById("block_ai_chat-input-id");addTextareaListener(textarea);if(document.getElementById("block_ai_chat-submit-id").addEventListener("click",(event=>{clickSubmitButton(event)})),firstLoad){await getConversations(),showConversation();let conversationcontextLimit=await externalServices.getConversationcontextLimit(contextid);maxHistory=conversationcontextLimit.limit;document.getElementById("block_ai_chat_new_dialog").addEventListener("click",(()=>{newDialog()}));document.getElementById("block_ai_chat_delete_dialog").addEventListener("click",(()=>{deleteCurrentDialog()}));document.getElementById("block_ai_chat_show_history").addEventListener("click",(()=>{showHistory()}));document.getElementById(VIEW_CHATWINDOW).addEventListener("click",(()=>{setView(VIEW_CHATWINDOW)}));document.getElementById(VIEW_OPENFULL).addEventListener("click",(()=>{setView(VIEW_OPENFULL)}));document.getElementById(VIEW_DOCKRIGHT).addEventListener("click",(()=>{setView(VIEW_DOCKRIGHT)})),await(0,_userquota.renderUserQuota)("#block_ai_chat_userquota",["chat"]),await(0,_infobox.renderInfoBox)("block_ai_chat",userid,'.ai_chat_modal_body [data-content="local_ai_manager_infobox"]',["chat"]);const message=await userAllowed();if(""!==message){const notice=await(0,_str.getString)("notice","block_ai_chat");await(0,_notification.alert)(notice,message)}firstLoad=!1}helper.focustextarea()}()})),strToday=await(0,_str.getString)("today","core"),strYesterday=await(0,_str.getString)("yesterday","block_ai_chat");window.matchMedia("(max-width: 576px)").addEventListener("change",handleScreenWidthChange),window.innerWidth<=576&&setView(VIEW_OPENFULL)};const getConversations=async()=>{try{allConversations=await externalServices.getAllConversations(userid,contextid)}catch(error){(0,_notification.exception)(error)}},showConversation=function(){let id=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;aiAtWork||(0!==id?conversation=allConversations.find((x=>x.id===id)):void 0!==allConversations[0]?conversation=allConversations.at(allConversations.length-1):0===allConversations.length&&newDialog(!0),clearMessages(),setModalHeader(),showMessages())};document.showConversation=showConversation;const enterQuestion=async question=>{if(""==question)return void(aiAtWork=!1);const message=await userAllowed();if(""!==message){const notice=await(0,_str.getString)("noticenewquestion","block_ai_chat");return await(0,_notification.alert)(notice,message),void(aiAtWork=!1)}if(showMessage(question,"self",!1),0===conversation.messages.length){const currentUserLanguage=_config2.default.language.substring(0,2),LangNames=new Intl.DisplayNames("en",{type:"language"});conversation.messages.push({message:"Answer in "+LangNames.of(currentUserLanguage),sender:"system"})}const convHistory=await checkMessageHistoryLengthLimit(conversation.messages),options={component:"block_ai_chat",contextid:contextid,conversationcontext:convHistory};if(0===conversation.id){try{let idresult=await externalServices.getNewConversationId(contextid);conversation.id=idresult.id,conversation.timecreated=Math.floor(Date.now()/1e3),setModalHeader((0,_helper2.escapeHTML)(question))}catch(error){(0,_notification.exception)(error)}options.forcenewitemid=!0}options.itemid=conversation.id;let requestresult=await manager.askLocalAiManager("chat",question,options);200!=requestresult.code&&(requestresult=await errorHandling(requestresult,question,options));let copy=document.querySelector(".ai_chat_modal .awaitanswer .copy");copy.addEventListener("mousedown",(()=>{helper.copyToClipboard(copy)})),showReply(requestresult.result),aiAtWork=!1,saveConversationLocally(question,requestresult.result);document.getElementById("block_ai_chat_userquota").innerHTML="",(0,_userquota.renderUserQuota)("#block_ai_chat_userquota",["chat"])},showReply=async text=>{let fields=document.querySelectorAll(".ai_chat_modal .awaitanswer .text");const field=fields[fields.length-1];field.innerHTML=text,field.classList.remove("small");let awaitdivs=document.querySelectorAll(".ai_chat_modal .awaitanswer");awaitdivs[awaitdivs.length-1].classList.remove("awaitanswer")},showMessages=()=>{conversation.messages.forEach((val=>{showMessage(val.message,val.sender)}))},showMessage=async function(text){let sender=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",answer=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if("system"===sender)return;"ai"===sender&&(sender=""),answer||(text=(0,_helper2.escapeHTML)(text));const templateData={sender:sender,content:text,answer:answer},{html:html,js:js}=await _templates.default.renderForPromise("block_ai_chat/message",templateData);_templates.default.appendNodeContents(".block_ai_chat-output",html,js),""===sender&&helper.attachCopyListenerLast(),helper.scrollToBottom()},newDialog=async function(){let deleted=arguments.length>0&&void 0!==arguments[0]&&arguments[0];aiAtWork||(void 0!==allConversations.find((x=>x.id===conversation.id))||deleted||allConversations.push(conversation),conversation={id:0,messages:[]},clearMessages(),setModalHeader(strNewDialog),helper.focustextarea())},deleteCurrentDialog=()=>{(0,_notification.deleteCancelPromise)((0,_str.getString)("delete","block_ai_chat"),(0,_str.getString)("deletewarning","block_ai_chat")).then((async()=>{if(0!==conversation.id)try{await externalServices.deleteConversation(contextid,userid,conversation.id)&&(removeFromHistory(),showConversation())}catch(error){(0,_notification.exception)(error)}})).catch((()=>{}))},showHistory=async()=>{void 0===allConversations.find((x=>x.id===conversation.id))&&allConversations.push(conversation);let title=''+strHistory+"";clearMessages(!0),setModalHeader(title);document.getElementById("block_ai_chat_backlink").addEventListener("click",(()=>{0!==conversation.id?showConversation(conversation.id):newDialog(),clearMessages(),setModalHeader()})),document.querySelector(".ai_chat_modal").classList.add("onhistorypage");let groupedByDate={};allConversations.forEach((convo=>{if(void 0!==convo.messages[1]){let title=convo.messages[1].message;const now=new Date,date=new Date(1e3*convo.timecreated),today=new Date(now.getFullYear(),now.getMonth(),now.getDate()),yesterday=new Date(now.getFullYear(),now.getMonth(),now.getDate()-1),twoWeeksAgo=new Date(now);twoWeeksAgo.setDate(now.getDate()-14);const options={weekday:"long",day:"2-digit",month:"2-digit"},monthOptions={month:"long",year:"2-digit"};let dateString="";dateString=date>=today?strToday:date>=yesterday?strYesterday:date>=twoWeeksAgo?date.toLocaleDateString(void 0,options):date.toLocaleDateString(void 0,monthOptions);const hours=date.getHours(),minutes=date.getMinutes().toString().padStart(2,"0");let convItem={title:title,conversationid:convo.id,time:hours+":"+minutes};groupedByDate[dateString]||(groupedByDate[dateString]=[]),groupedByDate[dateString].push(convItem)}}));const templateData={dates:{groups:Object.keys(groupedByDate).map((key=>({key:key,objects:groupedByDate[key]})))}.groups},{html:html,js:js}=await _templates.default.renderForPromise("block_ai_chat/history",templateData);_templates.default.appendNodeContents(".ai_chat_modal .block_ai_chat-output",html,js);document.getElementById("ai_chat_history_new_dialog").addEventListener("mousedown",(()=>{newDialog()}))},removeFromHistory=()=>{0!==conversation.id&&void 0!==allConversations.find((x=>x.id===conversation.id))&&(allConversations=allConversations.filter((obj=>obj.id!==conversation.id)))},saveConversationLocally=(question,reply)=>{let message={message:question,sender:"user"};conversation.messages.push(message),message={message:reply,sender:"ai"},conversation.messages.push(message)},clearMessages=function(){let hideinput=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const output=document.querySelector(".block_ai_chat-output");output.innerHTML="";let input=document.querySelector(".block_ai_chat-input");input.style.display=hideinput?"none":"flex"},setModalHeader=function(){let setTitle=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",modalheader=document.querySelector(".ai_chat_modal .modal-title div"),title="";null!==modalheader&&(conversation.messages.length>0||setTitle.length)&&(title=setTitle.length?setTitle:conversation.messages[1].message,modalheader.innerHTML=title);let modal=document.querySelector(".ai_chat_modal");modal.classList.remove("onhistorypage")},addTextareaListener=textarea=>{textarea.addEventListener("keydown",(event=>{textareaOnKeydown(event),textarea.style.height="auto";const computedStyles=window.getComputedStyle(textarea),lineHeight=parseFloat(computedStyles.lineHeight),paddingTop=parseFloat(computedStyles.paddingTop),paddingBottom=parseFloat(computedStyles.paddingBottom),borderTop=parseFloat(computedStyles.borderTopWidth),borderBottom=parseFloat(computedStyles.borderBottomWidth),maxHeight=4*lineHeight+paddingTop+paddingBottom+borderTop+borderBottom,newHeight=Math.min(textarea.scrollHeight+borderTop+borderBottom,maxHeight);textarea.style.height=newHeight+"px"}))},textareaOnKeydown=event=>{"Enter"!==event.key||aiAtWork||event.shiftKey||(aiAtWork=!0,enterQuestion(event.target.value),event.preventDefault(),event.target.value="")},clickSubmitButton=()=>{if(!aiAtWork){aiAtWork=!0;const textarea=document.getElementById("block_ai_chat-input-id");enterQuestion(textarea.value),textarea.value=""}},errorHandling=async(requestresult,question,options)=>{if(409==requestresult.code)for(;409==requestresult.code;){try{let idresult=await externalServices.getNewConversationId(contextid);conversation.id=idresult.id,options.itemid=conversation.id}catch(error){(0,_notification.exception)(error)}return requestresult=await manager.askLocalAiManager("chat",question,options)}const errorString=await(0,_str.getString)("errorwithcode","block_ai_chat",requestresult.code),result=JSON.parse(requestresult.result);await(0,_notification.alert)(errorString,result.message);const answerdivs=document.querySelectorAll(".awaitanswer");return answerdivs[answerdivs.length-1].closest(".message").classList.add("text-danger"),requestresult.result=await(0,_str.getString)("error","block_ai_chat"),requestresult},checkMessageHistoryLengthLimit=async messages=>{if(messages.length>maxHistory){let shortenedMessages=[messages[0],...messages.slice(-maxHistory)];if(!maxHistoryWarnings.has(conversation.id)){const maxHistoryString=await(0,_str.getString)("maxhistory","block_ai_chat",maxHistory),warningErrorString=await(0,_str.getString)("maxhistoryreached","block_ai_chat",maxHistory);await(0,_notification.alert)(maxHistoryString,warningErrorString),maxHistoryWarnings.add(conversation.id)}return shortenedMessages}return messages},checkOutsideClick=event=>{viewmode!=VIEW_OPENFULL&&event.preventDefault()},setView=async function(){let mode=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";const key=await(0,_helper2.hash)("chatmode"+userid);let savedmode=_localstorage.default.get(key);""==mode&&(mode=savedmode||VIEW_CHATWINDOW),_localstorage.default.set(key,mode),viewmode=mode;const body=document.querySelector("body");body.classList.remove(VIEW_CHATWINDOW,VIEW_OPENFULL,VIEW_DOCKRIGHT),body.classList.add(mode)},userAllowed=async()=>{let message;if(!1===tenantConfig.tenantenabled)return message=await(0,_str.getString)("error_http403disabled","local_ai_manager"),message;if(!1===tenantConfig.userconfirmed){message=await(0,_str.getString)("error_http403notconfirmed","local_ai_manager"),message+=". ";const link=window.location.origin+"/local/ai_manager/confirm_ai_usage.php";return message+=await(0,_str.getString)("confirm_ai_usage","block_ai_chat",link),message}return!0===tenantConfig.userlocked?(message=await(0,_str.getString)("error_http403blocked","local_ai_manager"),message):!1===chatConfig.isconfigured?(message=await(0,_str.getString)("error_purposenotconfigured","local_ai_manager"),message):!0===chatConfig.lockedforrole?(message=await(0,_str.getString)("error_http403blocked","local_ai_manager"),message):!0===chatConfig.limitreached?(message=await(0,_str.getString)("error_limitreached","local_ai_manager"),message):""},handleScreenWidthChange=e=>{const body=document.querySelector("body");e.matches?(body.classList.remove(VIEW_CHATWINDOW,VIEW_OPENFULL,VIEW_DOCKRIGHT),body.classList.add(VIEW_OPENFULL)):(body.classList.remove(VIEW_CHATWINDOW,VIEW_OPENFULL,VIEW_DOCKRIGHT),body.classList.add(viewmode))}})); //# sourceMappingURL=dialog.min.js.map \ No newline at end of file diff --git a/amd/build/dialog.min.js.map b/amd/build/dialog.min.js.map index d98c112..f03a690 100644 --- a/amd/build/dialog.min.js.map +++ b/amd/build/dialog.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dialog.min.js","sources":["../src/dialog.js"],"sourcesContent":["import Modal from 'core/modal';\nimport * as externalServices from 'block_ai_chat/webservices';\nimport Templates from 'core/templates';\nimport {alert as displayAlert, exception as displayException, deleteCancelPromise} from 'core/notification';\nimport ModalEvents from 'core/modal_events';\nimport * as helper from 'block_ai_chat/helper';\nimport * as manager from 'block_ai_chat/ai_manager';\nimport {getString} from 'core/str';\nimport {renderInfoBox} from 'local_ai_manager/infobox';\nimport {renderUserQuota} from 'local_ai_manager/userquota';\nimport {getAiConfig} from 'local_ai_manager/config';\nimport LocalStorage from 'core/localstorage';\nimport {escapeHTML, hash} from './helper';\nimport Config from 'core/config';\n\n// Declare variables.\nconst VIEW_CHATWINDOW = 'block_ai_chat_chatwindow';\nconst VIEW_OPENFULL = 'block_ai_chat_openfull';\nconst VIEW_DOCKRIGHT = 'block_ai_chat_dockright';\nconst MODAL_OPEN = 'block_ai_chat_open';\n\n// Modal.\nlet modal = {};\nlet strHistory;\nlet strNewDialog;\nlet strToday;\nlet strYesterday;\nlet badge;\nlet viewmode;\nlet modalopen = false;\n\n// Current conversation.\nlet conversation = {\n id: 0,\n messages: [],\n};\n// All conversations.\nlet allConversations = [];\n// Userid.\nlet userid = 0;\n// Course context id.\nlet contextid = 0;\n// First load.\nlet firstLoad = true;\n// AI in process of answering.\nlet aiAtWork = false;\n// Maximum history included in query.\nlet maxHistory = 5;\n// Remember warnings for maximum history in this session.\nlet maxHistoryWarnings = new Set();\n// Tenantconfig.\nlet tenantConfig = {};\nlet chatConfig = {};\n\nclass DialogModal extends Modal {\n static TYPE = \"block_ai_chat/dialog_modal\";\n static TEMPLATE = \"block_ai_chat/dialog_modal\";\n\n configure(modalConfig) {\n // Show this modal on instantiation.\n modalConfig.show = false;\n\n // Remove from the DOM on close.\n modalConfig.removeOnClose = false;\n\n modalConfig.isVerticallyCentered = false;\n // returnFocus: target,\n\n super.configure(modalConfig);\n\n // Accept our own custom arguments too.\n if (modalConfig.titletest) {\n this.setTitletest(modalConfig.titletest);\n }\n }\n\n setTitletest(value) {\n this.titletest = value;\n }\n\n hide() {\n super.hide();\n // Keep track of state, to restrict changes to block_ai_chat modal.\n modalopen = false;\n const body = document.querySelector('body');\n body.classList.remove(MODAL_OPEN);\n }\n}\n\nexport const init = async(params) => {\n // Read params.\n userid = params.userid;\n contextid = params.contextid;\n strNewDialog = params.new;\n strHistory = params.history;\n badge = params.badge;\n // Disable bdage.\n badge = false;\n\n // Get configuration.\n const aiConfig = await getAiConfig();\n tenantConfig = aiConfig;\n chatConfig = aiConfig.purposes.find(p => p.purpose === \"chat\");\n\n // Build modal.\n modal = await DialogModal.create({\n templateContext: {\n title: strNewDialog,\n badge: badge,\n // history: history, // history dynamically added.\n },\n });\n\n // Add class for styling when modal is displayed.\n modal.getRoot().on('modal:shown', function(e) {\n e.target.classList.add(\"ai_chat_modal\");\n });\n\n // Conditionally prevent outside click event.\n modal.getRoot().on(ModalEvents.outsideClick, event => {\n checkOutsideClick(event);\n });\n\n // Check and set viewmode.\n setView();\n\n // Attach listener to the ai button to call modal.\n let button = document.getElementById('ai_chat_button');\n button.addEventListener('mousedown', async() => {\n showModal(params);\n });\n\n // Get strings.\n strToday = await getString('today', 'core');\n strYesterday = await getString('yesterday', 'block_ai_chat');\n\n // Create a MediaQueryList object to check for small screens.\n const mediaQuery = window.matchMedia(\"(max-width: 576px)\");\n\n // Attach the event listener to handle changes.\n mediaQuery.addEventListener('change', handleScreenWidthChange);\n\n // Initial check for screenwidth.\n if (window.innerWidth <= 576) {\n setView(VIEW_OPENFULL);\n }\n};\n\n/**\n * Show ai_chat modal.\n */\nasync function showModal() {\n // Switch for repeated clicking.\n if (modalopen) {\n modal.hide();\n return;\n }\n\n // Show modal.\n await modal.show();\n modalopen = true;\n const body = document.querySelector('body');\n body.classList.add(MODAL_OPEN);\n\n // Add listener for input submission.\n const textarea = document.getElementById('block_ai_chat-input-id');\n addTextareaListener(textarea);\n const button = document.getElementById('block_ai_chat-submit-id');\n button.addEventListener(\"click\", (event) => {\n clickSubmitButton(event);\n });\n\n if (firstLoad) {\n // Load conversations.\n await getConversations();\n\n // Show conversation.\n // Todo - Evtl. noch firstload verschönern, spinner für header und content z.b.\n showConversation();\n\n // Get conversationcontext message limit.\n let conversationcontextLimit = await externalServices.getConversationcontextLimit(contextid);\n maxHistory = conversationcontextLimit.limit;\n\n // Add listeners for dropdownmenus.\n // Actions.\n const btnNewDialog = document.getElementById('block_ai_chat_new_dialog');\n btnNewDialog.addEventListener('click', () => {\n newDialog();\n });\n const btnDeleteDialog = document.getElementById('block_ai_chat_delete_dialog');\n btnDeleteDialog.addEventListener('click', () => {\n deleteCurrentDialog();\n });\n const btnShowHistory = document.getElementById('block_ai_chat_show_history');\n btnShowHistory.addEventListener('click', () => {\n showHistory();\n });\n // Views.\n const btnChatwindow = document.getElementById(VIEW_CHATWINDOW);\n btnChatwindow.addEventListener('click', () => {\n setView(VIEW_CHATWINDOW);\n });\n const btnFullWidth = document.getElementById(VIEW_OPENFULL);\n btnFullWidth.addEventListener('click', () => {\n setView(VIEW_OPENFULL);\n });\n const btnDockRight = document.getElementById(VIEW_DOCKRIGHT);\n btnDockRight.addEventListener('click', () => {\n setView(VIEW_DOCKRIGHT);\n });\n\n // Show userquota.\n await renderUserQuota('#block_ai_chat_userquota', ['chat']);\n // Show infobox.\n await renderInfoBox('block_ai_chat', userid, '.ai_chat_modal_body [data-content=\"local_ai_manager_infobox\"]', ['chat']);\n\n // Check if all permissions and settings are correct.\n const message = await userAllowed();\n if (message !== '') {\n const notice = await getString('notice', 'block_ai_chat');\n await displayAlert(notice, message);\n }\n firstLoad = false;\n }\n\n helper.focustextarea();\n}\n\n\n/**\n * Webservice Get all conversations.\n */\nconst getConversations = async() => {\n console.log(\"allConversations called\");\n try {\n allConversations = await externalServices.getAllConversations(userid, contextid);\n console.log(allConversations);\n } catch (error) {\n console.log(allConversations);\n displayException(error);\n }\n};\n\n/**\n * Function to set conversation.\n * @param {*} id\n */\nconst showConversation = (id = 0) => {\n console.log(\"showConversation called\");\n // Dissallow changing conversations when question running.\n if (aiAtWork) {\n return;\n }\n // Change conversation or get last conversation.\n if (id !== 0) {\n // Set selected conversation.\n conversation = allConversations.find(x => x.id === id);\n } else if (typeof allConversations[0] !== 'undefined') {\n // Set last conversation.\n conversation = allConversations.at(allConversations.length - 1);\n } else if (allConversations.length === 0) {\n // Last conversation has been deleted.\n newDialog(true);\n }\n clearMessages();\n setModalHeader();\n showMessages();\n};\n// Make globally accessible since it is used to show history in dropdownmenuitem.mustache.\ndocument.showConversation = showConversation;\n\n\n/**\n * Send input to ai connector.\n * @param {*} question\n */\nconst enterQuestion = async(question) => {\n\n // Deny changing dialogs until answer present?\n if (question == '') {\n aiAtWork = false;\n return;\n }\n const message = await userAllowed();\n if (message !== '') {\n console.log(\"User not allowed\");\n const notice = await getString('noticenewquestion', 'block_ai_chat');\n await displayAlert(notice, message);\n aiAtWork = false;\n return;\n }\n\n // Add to conversation, answer not yet available.\n showMessage(question, 'self', false);\n\n // For first message, add a system message.\n if (conversation.messages.length === 0) {\n const currentUserLanguage = Config.language.substring(0, 2);\n const LangNames = new Intl.DisplayNames('en', {type: 'language'});\n conversation.messages.push({\n 'message': 'Answer in ' + LangNames.of(currentUserLanguage),\n 'sender': 'system',\n });\n }\n\n // Ceck history for length limit.\n const convHistory = await checkMessageHistoryLengthLimit(conversation.messages);\n\n // Options, with conversation history.\n const options = {\n 'component': 'block_ai_chat',\n 'contextid': contextid,\n 'conversationcontext': convHistory,\n };\n\n // For a new conversation, get an id.\n if (conversation.id === 0) {\n try {\n let idresult = await externalServices.getNewConversationId(contextid);\n conversation.id = idresult.id;\n conversation.timecreated = Math.floor(Date.now() / 1000);\n setModalHeader(escapeHTML(question));\n } catch (error) {\n displayException(error);\n }\n options.forcenewitemid = true;\n }\n\n // Pass itemid / conversationid.\n options.itemid = conversation.id;\n\n // Send to local_ai_manager.\n let requestresult = await manager.askLocalAiManager('chat', question, options);\n\n // Handle errors.\n if (requestresult.code != 200) {\n requestresult = await errorHandling(requestresult, question, options);\n }\n\n // Attach copy listener.\n let copy = document.querySelector('.ai_chat_modal .awaitanswer .copy');\n copy.addEventListener('mousedown', () => {\n helper.copyToClipboard(copy);\n });\n\n // Write back answer.\n showReply(requestresult.result);\n\n // Ai is done.\n aiAtWork = false;\n\n // Save new question and answer.\n saveConversationLocally(question, requestresult.result);\n\n // Update userquota.\n const userquota = document.getElementById('block_ai_chat_userquota');\n userquota.innerHTML = '';\n renderUserQuota('#block_ai_chat_userquota', ['chat']);\n};\n\n/**\n * Render reply.\n * @param {string} text\n */\nconst showReply = async (text) => {\n // Get textblock.\n let fields = document.querySelectorAll('.ai_chat_modal .awaitanswer .text');\n const field = fields[fields.length - 1];\n // Render the reply.\n field.innerHTML = text;\n field.classList.remove('small');\n\n // Remove awaitanswer class.\n let awaitdivs = document.querySelectorAll('.ai_chat_modal .awaitanswer');\n const awaitdiv = awaitdivs[awaitdivs.length - 1];\n awaitdiv.classList.remove('awaitanswer');\n};\n\nconst showMessages = () => {\n console.log(\"showMessages called\");\n conversation.messages.forEach((val) => {\n showMessage(val.message, val.sender);\n });\n};\n\n/**\n * Show answer from local_ai_manager.\n * @param {*} text\n * @param {*} sender User or Ai\n * @param {*} answer Is answer in history\n */\nconst showMessage = async(text, sender = '', answer = true) => {\n // Skip if sender is system.\n if (sender === 'system') {\n return;\n }\n // Imitate bool for message.mustache logic {{#sender}}.\n if (sender === 'ai') {\n sender = '';\n }\n // Escape chars for immediate rendering.\n if (!answer) {\n text = escapeHTML(text);\n }\n\n const templateData = {\n \"sender\": sender,\n \"content\": text,\n \"answer\": answer,\n };\n // Call the function to load and render our template.\n const {html, js} = await Templates.renderForPromise('block_ai_chat/message', templateData);\n Templates.appendNodeContents('.block_ai_chat-output', html, js);\n\n // Add copy listener for replys.\n if (sender === '') {\n helper.attachCopyListenerLast();\n }\n\n // Scroll the modal content to the bottom.\n helper.scrollToBottom();\n};\n\n/**\n * Create new / Reset dialog.\n * @param {bool} deleted\n */\nconst newDialog = async(deleted = false) => {\n console.log(\"newDialog called\");\n if (aiAtWork) {\n return;\n }\n // Add current convo local representation, if not already there.\n if (allConversations.find(x => x.id === conversation.id) === undefined && !deleted) {\n allConversations.push(conversation);\n }\n // Reset local conservation.\n conversation = {\n id: 0,\n messages: [],\n };\n clearMessages();\n setModalHeader(strNewDialog);\n helper.focustextarea();\n};\n\n/**\n * Delete /hide current dialog.\n */\nconst deleteCurrentDialog = () => {\n console.log(\"deleteCurrentDialog called\");\n deleteCancelPromise(\n getString('delete', 'block_ai_chat'),\n getString('deletewarning', 'block_ai_chat'),\n ).then(async() => {\n if (conversation.id !== 0) {\n try {\n const deleted = await externalServices.deleteConversation(contextid, userid, conversation.id);\n if (deleted) {\n removeFromHistory();\n showConversation();\n }\n } catch (error) {\n displayException(error);\n }\n }\n return;\n }).catch(() => {\n return;\n });\n};\n\n/**\n * Show conversation history.\n */\nconst showHistory = async() => {\n console.log(\"showHistory called\");\n // Add current convo local representation, if not already there.\n if (allConversations.find(x => x.id === conversation.id) === undefined) {\n allConversations.push(conversation);\n }\n // Change title and add backlink.\n let title = '' + strHistory + '';\n clearMessages(true);\n setModalHeader(title);\n const btnBacklink = document.getElementById('block_ai_chat_backlink');\n btnBacklink.addEventListener('click', () => {\n if (conversation.id !== 0) {\n showConversation(conversation.id);\n } else {\n newDialog();\n }\n clearMessages();\n setModalHeader();\n });\n\n // Set modal class to hide info about ratelimits and infobox.\n let modal = document.querySelector('.ai_chat_modal');\n modal.classList.add('onhistorypage');\n\n // Iterate over conversations and group by date.\n let groupedByDate = {};\n allConversations.forEach((convo) => {\n if (typeof convo.messages[1] !== 'undefined') {\n // Get first prompt.\n let title = convo.messages[1].message;\n\n // Get date and sort convos into a date array.\n const now = new Date();\n const date = new Date(convo.timecreated * 1000);\n const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);\n const twoWeeksAgo = new Date(now);\n twoWeeksAgo.setDate(now.getDate() - 14);\n\n const options = {weekday: 'long', day: '2-digit', month: '2-digit'};\n const monthOptions = {month: 'long', year: '2-digit'};\n\n // Create a date string.\n let dateString = '';\n if (date >= today) {\n dateString = strToday;\n } else if (date >= yesterday) {\n dateString = strYesterday;\n } else if (date >= twoWeeksAgo) {\n dateString = date.toLocaleDateString(undefined, options);\n } else {\n dateString = date.toLocaleDateString(undefined, monthOptions);\n }\n\n // Create a time string.\n const hours = date.getHours();\n const minutes = date.getMinutes().toString().padStart(2, '0');\n\n let convItem = {\n \"title\": title,\n \"conversationid\": convo.id,\n \"time\": hours + ':' + minutes,\n };\n\n // Save entry under the date.\n if (!groupedByDate[dateString]) {\n groupedByDate[dateString] = [];\n }\n groupedByDate[dateString].push(convItem);\n }\n });\n\n // Convert the grouped objects into an array format that Mustache can iterate over.\n let convert = {\n groups: Object.keys(groupedByDate).map(key => ({\n key: key,\n objects: groupedByDate[key]\n }))\n };\n\n // Render history.\n const templateData = {\n \"dates\": convert.groups,\n };\n const {html, js} = await Templates.renderForPromise('block_ai_chat/history', templateData);\n Templates.appendNodeContents('.ai_chat_modal .block_ai_chat-output', html, js);\n\n // Add a listener for the new dialog button.\n const btnNewDialog = document.getElementById('ai_chat_history_new_dialog');\n btnNewDialog.addEventListener('mousedown', () => {\n newDialog();\n });\n};\n\n/**\n * Remove currrent conversation from history.\n */\nconst removeFromHistory = () => {\n // Cant remove if new or not yet in history.\n if (conversation.id !== 0 && allConversations.find(x => x.id === conversation.id) !== undefined) {\n // Build new allConversations array without deleted one.\n allConversations = allConversations.filter(obj => obj.id !== conversation.id);\n }\n};\n\n/**\n * Webservice Save conversation.\n * @param {*} question\n * @param {*} reply\n */\nconst saveConversationLocally = (question, reply) => {\n // Add to local representation.\n let message = {'message': question, 'sender': 'user'};\n conversation.messages.push(message);\n message = {'message': reply, 'sender': 'ai'};\n conversation.messages.push(message);\n};\n\n/**\n * Clear output div.\n * @param {*} hideinput\n */\nconst clearMessages = (hideinput = false) => {\n console.log(\"clearMessages called\");\n const output = document.querySelector('.block_ai_chat-output');\n output.innerHTML = '';\n // For showing history.\n let input = document.querySelector('.block_ai_chat-input');\n if (hideinput) {\n input.style.display = 'none';\n } else {\n input.style.display = 'flex';\n }\n};\n\n/**\n * Set modal header title.\n * @param {*} setTitle\n */\nconst setModalHeader = (setTitle = '') => {\n let modalheader = document.querySelector('.ai_chat_modal .modal-title div');\n let title = '';\n if (modalheader !== null && (conversation.messages.length > 0 || setTitle.length)) {\n if (!setTitle.length) {\n title = conversation.messages[1].message;\n } else {\n title = setTitle;\n }\n modalheader.innerHTML = title;\n }\n // Remove onhistorypage, since history page is setting it.\n let modal = document.querySelector('.ai_chat_modal');\n modal.classList.remove('onhistorypage');\n};\n\n/**\n * Attach event listener.\n * @param {*} textarea\n */\nconst addTextareaListener = (textarea) => {\n textarea.addEventListener('keydown', (event) => {\n // Handle submission.\n textareaOnKeydown(event);\n\n // Handle autgrow.\n // Reset the height to auto to get the correct scrollHeight.\n textarea.style.height = 'auto';\n\n // Fetch the computed styles.\n const computedStyles = window.getComputedStyle(textarea);\n const lineHeight = parseFloat(computedStyles.lineHeight);\n const paddingTop = parseFloat(computedStyles.paddingTop);\n const paddingBottom = parseFloat(computedStyles.paddingBottom);\n const borderTop = parseFloat(computedStyles.borderTopWidth);\n const borderBottom = parseFloat(computedStyles.borderBottomWidth);\n\n // Calculate the maximum height for four rows plus padding and borders.\n const maxHeight = (lineHeight * 4) + paddingTop + paddingBottom + borderTop + borderBottom;\n\n // Calculate the new height based on the scrollHeight.\n const newHeight = Math.min(textarea.scrollHeight + borderTop + borderBottom, maxHeight);\n\n // Set the new height.\n textarea.style.height = newHeight + 'px';\n });\n};\n\n/**\n * Action for textarea submission.\n * @param {*} event\n */\nconst textareaOnKeydown = (event) => {\n // TODO check for mobile devices.\n if (event.key === 'Enter' && !aiAtWork && !event.shiftKey) {\n aiAtWork = true;\n enterQuestion(event.target.value);\n event.preventDefault();\n event.target.value = '';\n }\n};\n\n/**\n * Submit form.\n */\nconst clickSubmitButton = () => {\n // Var aiAtWork to make it impossible to submit multiple questions at once.\n if (!aiAtWork) {\n aiAtWork = true;\n const textarea = document.getElementById('block_ai_chat-input-id');\n enterQuestion(textarea.value);\n textarea.value = '';\n }\n};\n\n/**\n * Handle error from local_ai_manager.\n * @param {*} requestresult\n * @param {*} question\n * @param {*} options\n * @returns {object}\n */\nconst errorHandling = async(requestresult, question, options) => {\n\n // If code 409, conversationid is already taken, try get new a one.\n if (requestresult.code == 409) {\n while (requestresult.code == 409) {\n try {\n let idresult = await externalServices.getNewConversationId(contextid);\n conversation.id = idresult.id;\n options.itemid = conversation.id;\n } catch (error) {\n displayException(error);\n }\n // Retry with new id.\n requestresult = await manager.askLocalAiManager('chat', question, options);\n return requestresult;\n }\n }\n\n // If any other errorcode, alert with errormessage.\n const errorString = await getString('errorwithcode', 'block_ai_chat', requestresult.code);\n const result = JSON.parse(requestresult.result);\n await displayAlert(errorString, result.message);\n\n // Change answer styling to differentiate from ai.\n const answerdivs = document.querySelectorAll('.awaitanswer');\n const answerdiv = answerdivs[answerdivs.length - 1];\n const messagediv = answerdiv.closest('.message');\n messagediv.classList.add('text-danger');\n\n // And write generic error message in chatbot.\n requestresult.result = await getString('error', 'block_ai_chat');\n console.log(requestresult);\n return requestresult;\n};\n\n/**\n * Check historic messages for max length.\n * @param {array} messages\n * @returns {array}\n */\nconst checkMessageHistoryLengthLimit = async(messages) => {\n const length = messages.length;\n console.log(\"checkHistoryLengthLimit called\");\n if (length > maxHistory) {\n // Cut history.\n let shortenedMessages = [messages[0], ...messages.slice(-maxHistory)];\n console.log(shortenedMessages);\n\n // Show warning once per session.\n if (!maxHistoryWarnings.has(conversation.id)) {\n const maxHistoryString = await getString('maxhistory', 'block_ai_chat', maxHistory);\n const warningErrorString = await getString('maxhistoryreached', 'block_ai_chat', maxHistory);\n await displayAlert(maxHistoryString, warningErrorString);\n // Remember warning.\n maxHistoryWarnings.add(conversation.id);\n }\n return shortenedMessages;\n }\n // Limit not reached, return messages.\n return messages;\n};\n\n/**\n * Check if modal should close on outside click.\n * @param {*} event\n */\nconst checkOutsideClick = (event) => {\n // View openfull acts like a normal modal.\n if (viewmode != VIEW_OPENFULL) {\n event.preventDefault();\n }\n};\n\n/**\n * Set different viewmodes and save in local storage.\n * @param {string} mode\n */\nconst setView = async(mode = '') => {\n const key = await hash('chatmode' + userid);\n // Check for saved viewmode.\n let savedmode = LocalStorage.get(key);\n if (mode == '') {\n if (!savedmode) {\n // Set default.\n mode = VIEW_CHATWINDOW;\n } else {\n mode = savedmode;\n }\n }\n // Save viewmode and set global var.\n LocalStorage.set(key, mode);\n viewmode = mode;\n\n // Set viewmode as bodyclass.\n const body = document.querySelector('body');\n body.classList.remove(VIEW_CHATWINDOW, VIEW_OPENFULL, VIEW_DOCKRIGHT);\n body.classList.add(mode);\n};\n\n/**\n * Is user allowed new queries.\n * @returns {message}\n */\nconst userAllowed = async() => {\n if (tenantConfig.tenantenabled === false) {\n message = await getString('error_http403disabled', 'local_ai_manager');\n return message;\n }\n if (tenantConfig.userconfirmed === false) {\n message = await getString('error_http403notconfirmed', 'local_ai_manager');\n message += \". \";\n const link = window.location.origin + '/local/ai_manager/confirm_ai_usage.php';\n message += await getString('confirm_ai_usage', 'block_ai_chat', link);\n return message;\n }\n if (tenantConfig.userlocked === true) {\n message = await getString('error_http403blocked', 'local_ai_manager');\n return message;\n }\n if (chatConfig.isconfigured === false) {\n message = await getString('error_purposenotconfigured', 'local_ai_manager');\n return message;\n }\n if (chatConfig.lockedforrole === true) {\n message = await getString('error_http403blocked', 'local_ai_manager');\n return message;\n }\n if (chatConfig.limitreached === true) {\n message = await getString('error_limitreached', 'local_ai_manager');\n return message;\n }\n return '';\n};\n\n/**\n * Change to openfull view when screen is small.\n * @param {*} e\n */\nconst handleScreenWidthChange = (e) => {\n const body = document.querySelector('body');\n if (e.matches) {\n // Screen width is less than 576px\n body.classList.remove(VIEW_CHATWINDOW, VIEW_OPENFULL, VIEW_DOCKRIGHT);\n body.classList.add(VIEW_OPENFULL);\n } else {\n body.classList.remove(VIEW_CHATWINDOW, VIEW_OPENFULL, VIEW_DOCKRIGHT);\n body.classList.add(viewmode);\n }\n};\n"],"names":["VIEW_CHATWINDOW","VIEW_OPENFULL","VIEW_DOCKRIGHT","strHistory","strNewDialog","strToday","strYesterday","badge","viewmode","modal","modalopen","conversation","id","messages","allConversations","userid","contextid","firstLoad","aiAtWork","maxHistory","maxHistoryWarnings","Set","tenantConfig","chatConfig","DialogModal","Modal","configure","modalConfig","show","removeOnClose","isVerticallyCentered","titletest","setTitletest","value","hide","document","querySelector","classList","remove","async","params","new","history","aiConfig","purposes","find","p","purpose","create","templateContext","title","getRoot","on","e","target","add","ModalEvents","outsideClick","event","checkOutsideClick","setView","getElementById","addEventListener","textarea","addTextareaListener","clickSubmitButton","getConversations","showConversation","conversationcontextLimit","externalServices","getConversationcontextLimit","limit","newDialog","deleteCurrentDialog","showHistory","message","userAllowed","notice","helper","focustextarea","showModal","window","matchMedia","handleScreenWidthChange","innerWidth","console","log","getAllConversations","error","x","at","length","clearMessages","setModalHeader","showMessages","enterQuestion","question","showMessage","currentUserLanguage","Config","language","substring","LangNames","Intl","DisplayNames","type","push","of","convHistory","checkMessageHistoryLengthLimit","options","idresult","getNewConversationId","timecreated","Math","floor","Date","now","forcenewitemid","itemid","requestresult","manager","askLocalAiManager","code","errorHandling","copy","copyToClipboard","showReply","result","saveConversationLocally","innerHTML","fields","querySelectorAll","field","text","awaitdivs","forEach","val","sender","answer","templateData","html","js","Templates","renderForPromise","appendNodeContents","attachCopyListenerLast","scrollToBottom","deleted","undefined","then","deleteConversation","removeFromHistory","catch","groupedByDate","convo","date","today","getFullYear","getMonth","getDate","yesterday","twoWeeksAgo","setDate","weekday","day","month","monthOptions","year","dateString","toLocaleDateString","hours","getHours","minutes","getMinutes","toString","padStart","convItem","groups","Object","keys","map","key","objects","filter","obj","reply","hideinput","output","input","style","display","setTitle","modalheader","textareaOnKeydown","height","computedStyles","getComputedStyle","lineHeight","parseFloat","paddingTop","paddingBottom","borderTop","borderTopWidth","borderBottom","borderBottomWidth","maxHeight","newHeight","min","scrollHeight","shiftKey","preventDefault","errorString","JSON","parse","answerdivs","closest","shortenedMessages","slice","has","maxHistoryString","warningErrorString","mode","savedmode","LocalStorage","get","set","body","tenantenabled","userconfirmed","link","location","origin","userlocked","isconfigured","lockedforrole","limitreached","matches"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dialog.min.js","sources":["../src/dialog.js"],"sourcesContent":["import Modal from 'core/modal';\nimport * as externalServices from 'block_ai_chat/webservices';\nimport Templates from 'core/templates';\nimport {alert as displayAlert, exception as displayException, deleteCancelPromise} from 'core/notification';\nimport ModalEvents from 'core/modal_events';\nimport * as helper from 'block_ai_chat/helper';\nimport * as manager from 'block_ai_chat/ai_manager';\nimport {getString} from 'core/str';\nimport {renderInfoBox} from 'local_ai_manager/infobox';\nimport {renderUserQuota} from 'local_ai_manager/userquota';\nimport {getAiConfig} from 'local_ai_manager/config';\nimport LocalStorage from 'core/localstorage';\nimport {escapeHTML, hash} from './helper';\nimport Config from 'core/config';\n\n// Declare variables.\nconst VIEW_CHATWINDOW = 'block_ai_chat_chatwindow';\nconst VIEW_OPENFULL = 'block_ai_chat_openfull';\nconst VIEW_DOCKRIGHT = 'block_ai_chat_dockright';\nconst MODAL_OPEN = 'block_ai_chat_open';\n\n// Modal.\nlet modal = {};\nlet strHistory;\nlet strNewDialog;\nlet strToday;\nlet strYesterday;\nlet badge;\nlet viewmode;\nlet modalopen = false;\n\n// Current conversation.\nlet conversation = {\n id: 0,\n messages: [],\n};\n// All conversations.\nlet allConversations = [];\n// Userid.\nlet userid = 0;\n// Course context id.\nlet contextid = 0;\n// First load.\nlet firstLoad = true;\n// AI in process of answering.\nlet aiAtWork = false;\n// Maximum history included in query.\nlet maxHistory = 5;\n// Remember warnings for maximum history in this session.\nlet maxHistoryWarnings = new Set();\n// Tenantconfig.\nlet tenantConfig = {};\nlet chatConfig = {};\n\nclass DialogModal extends Modal {\n static TYPE = \"block_ai_chat/dialog_modal\";\n static TEMPLATE = \"block_ai_chat/dialog_modal\";\n\n configure(modalConfig) {\n // Show this modal on instantiation.\n modalConfig.show = false;\n\n // Remove from the DOM on close.\n modalConfig.removeOnClose = false;\n\n modalConfig.isVerticallyCentered = false;\n\n super.configure(modalConfig);\n\n // Accept our own custom arguments too.\n if (modalConfig.titletest) {\n this.setTitletest(modalConfig.titletest);\n }\n }\n\n setTitletest(value) {\n this.titletest = value;\n }\n\n hide() {\n super.hide();\n // Keep track of state, to restrict changes to block_ai_chat modal.\n modalopen = false;\n const body = document.querySelector('body');\n body.classList.remove(MODAL_OPEN);\n }\n}\n\nexport const init = async(params) => {\n // Read params.\n userid = params.userid;\n contextid = params.contextid;\n strNewDialog = params.new;\n strHistory = params.history;\n badge = params.badge;\n // Disable bdage.\n badge = false;\n\n // Get configuration.\n const aiConfig = await getAiConfig();\n tenantConfig = aiConfig;\n chatConfig = aiConfig.purposes.find(p => p.purpose === \"chat\");\n\n // Build modal.\n modal = await DialogModal.create({\n templateContext: {\n title: strNewDialog,\n badge: badge,\n },\n });\n\n // Add class for styling when modal is displayed.\n modal.getRoot().on('modal:shown', function(e) {\n e.target.classList.add(\"ai_chat_modal\");\n });\n\n // Conditionally prevent outside click event.\n modal.getRoot().on(ModalEvents.outsideClick, event => {\n checkOutsideClick(event);\n });\n\n // Check and set viewmode.\n setView();\n\n // Attach listener to the ai button to call modal.\n let button = document.getElementById('ai_chat_button');\n button.addEventListener('mousedown', async() => {\n showModal(params);\n });\n\n // Get strings.\n strToday = await getString('today', 'core');\n strYesterday = await getString('yesterday', 'block_ai_chat');\n\n // Create a MediaQueryList object to check for small screens.\n const mediaQuery = window.matchMedia(\"(max-width: 576px)\");\n\n // Attach the event listener to handle changes.\n mediaQuery.addEventListener('change', handleScreenWidthChange);\n\n // Initial check for screenwidth.\n if (window.innerWidth <= 576) {\n setView(VIEW_OPENFULL);\n }\n};\n\n/**\n * Show ai_chat modal.\n */\nasync function showModal() {\n // Switch for repeated clicking.\n if (modalopen) {\n modal.hide();\n return;\n }\n\n // Show modal.\n await modal.show();\n modalopen = true;\n const body = document.querySelector('body');\n body.classList.add(MODAL_OPEN);\n\n // Add listener for input submission.\n const textarea = document.getElementById('block_ai_chat-input-id');\n addTextareaListener(textarea);\n const button = document.getElementById('block_ai_chat-submit-id');\n button.addEventListener(\"click\", (event) => {\n clickSubmitButton(event);\n });\n\n if (firstLoad) {\n // Load conversations.\n await getConversations();\n\n // Show conversation.\n // Todo - Evtl. noch firstload verschönern, spinner für header und content z.b.\n showConversation();\n\n // Get conversationcontext message limit.\n let conversationcontextLimit = await externalServices.getConversationcontextLimit(contextid);\n maxHistory = conversationcontextLimit.limit;\n\n // Add listeners for dropdownmenus.\n // Actions.\n const btnNewDialog = document.getElementById('block_ai_chat_new_dialog');\n btnNewDialog.addEventListener('click', () => {\n newDialog();\n });\n const btnDeleteDialog = document.getElementById('block_ai_chat_delete_dialog');\n btnDeleteDialog.addEventListener('click', () => {\n deleteCurrentDialog();\n });\n const btnShowHistory = document.getElementById('block_ai_chat_show_history');\n btnShowHistory.addEventListener('click', () => {\n showHistory();\n });\n // Views.\n const btnChatwindow = document.getElementById(VIEW_CHATWINDOW);\n btnChatwindow.addEventListener('click', () => {\n setView(VIEW_CHATWINDOW);\n });\n const btnFullWidth = document.getElementById(VIEW_OPENFULL);\n btnFullWidth.addEventListener('click', () => {\n setView(VIEW_OPENFULL);\n });\n const btnDockRight = document.getElementById(VIEW_DOCKRIGHT);\n btnDockRight.addEventListener('click', () => {\n setView(VIEW_DOCKRIGHT);\n });\n\n // Show userquota.\n await renderUserQuota('#block_ai_chat_userquota', ['chat']);\n // Show infobox.\n await renderInfoBox('block_ai_chat', userid, '.ai_chat_modal_body [data-content=\"local_ai_manager_infobox\"]', ['chat']);\n\n // Check if all permissions and settings are correct.\n const message = await userAllowed();\n if (message !== '') {\n const notice = await getString('notice', 'block_ai_chat');\n await displayAlert(notice, message);\n }\n firstLoad = false;\n }\n\n helper.focustextarea();\n}\n\n\n/**\n * Webservice Get all conversations.\n */\nconst getConversations = async() => {\n try {\n allConversations = await externalServices.getAllConversations(userid, contextid);\n } catch (error) {\n displayException(error);\n }\n};\n\n/**\n * Function to set conversation.\n * @param {*} id\n */\nconst showConversation = (id = 0) => {\n // Dissallow changing conversations when question running.\n if (aiAtWork) {\n return;\n }\n // Change conversation or get last conversation.\n if (id !== 0) {\n // Set selected conversation.\n conversation = allConversations.find(x => x.id === id);\n } else if (typeof allConversations[0] !== 'undefined') {\n // Set last conversation.\n conversation = allConversations.at(allConversations.length - 1);\n } else if (allConversations.length === 0) {\n // Last conversation has been deleted.\n newDialog(true);\n }\n clearMessages();\n setModalHeader();\n showMessages();\n};\n// Make globally accessible since it is used to show history in dropdownmenuitem.mustache.\ndocument.showConversation = showConversation;\n\n\n/**\n * Send input to ai connector.\n * @param {*} question\n */\nconst enterQuestion = async(question) => {\n\n // Deny changing dialogs until answer present?\n if (question == '') {\n aiAtWork = false;\n return;\n }\n const message = await userAllowed();\n if (message !== '') {\n const notice = await getString('noticenewquestion', 'block_ai_chat');\n await displayAlert(notice, message);\n aiAtWork = false;\n return;\n }\n\n // Add to conversation, answer not yet available.\n showMessage(question, 'self', false);\n\n // For first message, add a system message.\n if (conversation.messages.length === 0) {\n const currentUserLanguage = Config.language.substring(0, 2);\n const LangNames = new Intl.DisplayNames('en', {type: 'language'});\n conversation.messages.push({\n 'message': 'Answer in ' + LangNames.of(currentUserLanguage),\n 'sender': 'system',\n });\n }\n\n // Ceck history for length limit.\n const convHistory = await checkMessageHistoryLengthLimit(conversation.messages);\n\n // Options, with conversation history.\n const options = {\n 'component': 'block_ai_chat',\n 'contextid': contextid,\n 'conversationcontext': convHistory,\n };\n\n // For a new conversation, get an id.\n if (conversation.id === 0) {\n try {\n let idresult = await externalServices.getNewConversationId(contextid);\n conversation.id = idresult.id;\n conversation.timecreated = Math.floor(Date.now() / 1000);\n setModalHeader(escapeHTML(question));\n } catch (error) {\n displayException(error);\n }\n options.forcenewitemid = true;\n }\n\n // Pass itemid / conversationid.\n options.itemid = conversation.id;\n\n // Send to local_ai_manager.\n let requestresult = await manager.askLocalAiManager('chat', question, options);\n\n // Handle errors.\n if (requestresult.code != 200) {\n requestresult = await errorHandling(requestresult, question, options);\n }\n\n // Attach copy listener.\n let copy = document.querySelector('.ai_chat_modal .awaitanswer .copy');\n copy.addEventListener('mousedown', () => {\n helper.copyToClipboard(copy);\n });\n\n // Write back answer.\n showReply(requestresult.result);\n\n // Ai is done.\n aiAtWork = false;\n\n // Save new question and answer.\n saveConversationLocally(question, requestresult.result);\n\n // Update userquota.\n const userquota = document.getElementById('block_ai_chat_userquota');\n userquota.innerHTML = '';\n renderUserQuota('#block_ai_chat_userquota', ['chat']);\n};\n\n/**\n * Render reply.\n * @param {string} text\n */\nconst showReply = async(text) => {\n // Get textblock.\n let fields = document.querySelectorAll('.ai_chat_modal .awaitanswer .text');\n const field = fields[fields.length - 1];\n // Render the reply.\n field.innerHTML = text;\n field.classList.remove('small');\n\n // Remove awaitanswer class.\n let awaitdivs = document.querySelectorAll('.ai_chat_modal .awaitanswer');\n const awaitdiv = awaitdivs[awaitdivs.length - 1];\n awaitdiv.classList.remove('awaitanswer');\n};\n\nconst showMessages = () => {\n conversation.messages.forEach((val) => {\n showMessage(val.message, val.sender);\n });\n};\n\n/**\n * Show answer from local_ai_manager.\n * @param {*} text\n * @param {*} sender User or Ai\n * @param {*} answer Is answer in history\n */\nconst showMessage = async(text, sender = '', answer = true) => {\n // Skip if sender is system.\n if (sender === 'system') {\n return;\n }\n // Imitate bool for message.mustache logic {{#sender}}.\n if (sender === 'ai') {\n sender = '';\n }\n // Escape chars for immediate rendering.\n if (!answer) {\n text = escapeHTML(text);\n }\n\n const templateData = {\n \"sender\": sender,\n \"content\": text,\n \"answer\": answer,\n };\n // Call the function to load and render our template.\n const {html, js} = await Templates.renderForPromise('block_ai_chat/message', templateData);\n Templates.appendNodeContents('.block_ai_chat-output', html, js);\n\n // Add copy listener for replys.\n if (sender === '') {\n helper.attachCopyListenerLast();\n }\n\n // Scroll the modal content to the bottom.\n helper.scrollToBottom();\n};\n\n/**\n * Create new / Reset dialog.\n * @param {bool} deleted\n */\nconst newDialog = async(deleted = false) => {\n if (aiAtWork) {\n return;\n }\n // Add current convo local representation, if not already there.\n if (allConversations.find(x => x.id === conversation.id) === undefined && !deleted) {\n allConversations.push(conversation);\n }\n // Reset local conservation.\n conversation = {\n id: 0,\n messages: [],\n };\n clearMessages();\n setModalHeader(strNewDialog);\n helper.focustextarea();\n};\n\n/**\n * Delete /hide current dialog.\n */\nconst deleteCurrentDialog = () => {\n deleteCancelPromise(\n getString('delete', 'block_ai_chat'),\n getString('deletewarning', 'block_ai_chat'),\n ).then(async() => {\n if (conversation.id !== 0) {\n try {\n const deleted = await externalServices.deleteConversation(contextid, userid, conversation.id);\n if (deleted) {\n removeFromHistory();\n showConversation();\n }\n } catch (error) {\n displayException(error);\n }\n }\n return;\n }).catch(() => {\n return;\n });\n};\n\n/**\n * Show conversation history.\n */\nconst showHistory = async() => {\n // Add current convo local representation, if not already there.\n if (allConversations.find(x => x.id === conversation.id) === undefined) {\n allConversations.push(conversation);\n }\n // Change title and add backlink.\n let title = '' + strHistory + '';\n clearMessages(true);\n setModalHeader(title);\n const btnBacklink = document.getElementById('block_ai_chat_backlink');\n btnBacklink.addEventListener('click', () => {\n if (conversation.id !== 0) {\n showConversation(conversation.id);\n } else {\n newDialog();\n }\n clearMessages();\n setModalHeader();\n });\n\n // Set modal class to hide info about ratelimits and infobox.\n let modal = document.querySelector('.ai_chat_modal');\n modal.classList.add('onhistorypage');\n\n // Iterate over conversations and group by date.\n let groupedByDate = {};\n allConversations.forEach((convo) => {\n if (typeof convo.messages[1] !== 'undefined') {\n // Get first prompt.\n let title = convo.messages[1].message;\n\n // Get date and sort convos into a date array.\n const now = new Date();\n const date = new Date(convo.timecreated * 1000);\n const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);\n const twoWeeksAgo = new Date(now);\n twoWeeksAgo.setDate(now.getDate() - 14);\n\n const options = {weekday: 'long', day: '2-digit', month: '2-digit'};\n const monthOptions = {month: 'long', year: '2-digit'};\n\n // Create a date string.\n let dateString = '';\n if (date >= today) {\n dateString = strToday;\n } else if (date >= yesterday) {\n dateString = strYesterday;\n } else if (date >= twoWeeksAgo) {\n dateString = date.toLocaleDateString(undefined, options);\n } else {\n dateString = date.toLocaleDateString(undefined, monthOptions);\n }\n\n // Create a time string.\n const hours = date.getHours();\n const minutes = date.getMinutes().toString().padStart(2, '0');\n\n let convItem = {\n \"title\": title,\n \"conversationid\": convo.id,\n \"time\": hours + ':' + minutes,\n };\n\n // Save entry under the date.\n if (!groupedByDate[dateString]) {\n groupedByDate[dateString] = [];\n }\n groupedByDate[dateString].push(convItem);\n }\n });\n\n // Convert the grouped objects into an array format that Mustache can iterate over.\n let convert = {\n groups: Object.keys(groupedByDate).map(key => ({\n key: key,\n objects: groupedByDate[key]\n }))\n };\n\n // Render history.\n const templateData = {\n \"dates\": convert.groups,\n };\n const {html, js} = await Templates.renderForPromise('block_ai_chat/history', templateData);\n Templates.appendNodeContents('.ai_chat_modal .block_ai_chat-output', html, js);\n\n // Add a listener for the new dialog button.\n const btnNewDialog = document.getElementById('ai_chat_history_new_dialog');\n btnNewDialog.addEventListener('mousedown', () => {\n newDialog();\n });\n};\n\n/**\n * Remove currrent conversation from history.\n */\nconst removeFromHistory = () => {\n // Cant remove if new or not yet in history.\n if (conversation.id !== 0 && allConversations.find(x => x.id === conversation.id) !== undefined) {\n // Build new allConversations array without deleted one.\n allConversations = allConversations.filter(obj => obj.id !== conversation.id);\n }\n};\n\n/**\n * Webservice Save conversation.\n * @param {*} question\n * @param {*} reply\n */\nconst saveConversationLocally = (question, reply) => {\n // Add to local representation.\n let message = {'message': question, 'sender': 'user'};\n conversation.messages.push(message);\n message = {'message': reply, 'sender': 'ai'};\n conversation.messages.push(message);\n};\n\n/**\n * Clear output div.\n * @param {*} hideinput\n */\nconst clearMessages = (hideinput = false) => {\n const output = document.querySelector('.block_ai_chat-output');\n output.innerHTML = '';\n // For showing history.\n let input = document.querySelector('.block_ai_chat-input');\n if (hideinput) {\n input.style.display = 'none';\n } else {\n input.style.display = 'flex';\n }\n};\n\n/**\n * Set modal header title.\n * @param {*} setTitle\n */\nconst setModalHeader = (setTitle = '') => {\n let modalheader = document.querySelector('.ai_chat_modal .modal-title div');\n let title = '';\n if (modalheader !== null && (conversation.messages.length > 0 || setTitle.length)) {\n if (!setTitle.length) {\n title = conversation.messages[1].message;\n } else {\n title = setTitle;\n }\n modalheader.innerHTML = title;\n }\n // Remove onhistorypage, since history page is setting it.\n let modal = document.querySelector('.ai_chat_modal');\n modal.classList.remove('onhistorypage');\n};\n\n/**\n * Attach event listener.\n * @param {*} textarea\n */\nconst addTextareaListener = (textarea) => {\n textarea.addEventListener('keydown', (event) => {\n // Handle submission.\n textareaOnKeydown(event);\n\n // Handle autgrow.\n // Reset the height to auto to get the correct scrollHeight.\n textarea.style.height = 'auto';\n\n // Fetch the computed styles.\n const computedStyles = window.getComputedStyle(textarea);\n const lineHeight = parseFloat(computedStyles.lineHeight);\n const paddingTop = parseFloat(computedStyles.paddingTop);\n const paddingBottom = parseFloat(computedStyles.paddingBottom);\n const borderTop = parseFloat(computedStyles.borderTopWidth);\n const borderBottom = parseFloat(computedStyles.borderBottomWidth);\n\n // Calculate the maximum height for four rows plus padding and borders.\n const maxHeight = (lineHeight * 4) + paddingTop + paddingBottom + borderTop + borderBottom;\n\n // Calculate the new height based on the scrollHeight.\n const newHeight = Math.min(textarea.scrollHeight + borderTop + borderBottom, maxHeight);\n\n // Set the new height.\n textarea.style.height = newHeight + 'px';\n });\n};\n\n/**\n * Action for textarea submission.\n * @param {*} event\n */\nconst textareaOnKeydown = (event) => {\n // TODO check for mobile devices.\n if (event.key === 'Enter' && !aiAtWork && !event.shiftKey) {\n aiAtWork = true;\n enterQuestion(event.target.value);\n event.preventDefault();\n event.target.value = '';\n }\n};\n\n/**\n * Submit form.\n */\nconst clickSubmitButton = () => {\n // Var aiAtWork to make it impossible to submit multiple questions at once.\n if (!aiAtWork) {\n aiAtWork = true;\n const textarea = document.getElementById('block_ai_chat-input-id');\n enterQuestion(textarea.value);\n textarea.value = '';\n }\n};\n\n/**\n * Handle error from local_ai_manager.\n * @param {*} requestresult\n * @param {*} question\n * @param {*} options\n * @returns {object}\n */\nconst errorHandling = async(requestresult, question, options) => {\n\n // If code 409, conversationid is already taken, try get new a one.\n if (requestresult.code == 409) {\n while (requestresult.code == 409) {\n try {\n let idresult = await externalServices.getNewConversationId(contextid);\n conversation.id = idresult.id;\n options.itemid = conversation.id;\n } catch (error) {\n displayException(error);\n }\n // Retry with new id.\n requestresult = await manager.askLocalAiManager('chat', question, options);\n return requestresult;\n }\n }\n\n // If any other errorcode, alert with errormessage.\n const errorString = await getString('errorwithcode', 'block_ai_chat', requestresult.code);\n const result = JSON.parse(requestresult.result);\n await displayAlert(errorString, result.message);\n\n // Change answer styling to differentiate from ai.\n const answerdivs = document.querySelectorAll('.awaitanswer');\n const answerdiv = answerdivs[answerdivs.length - 1];\n const messagediv = answerdiv.closest('.message');\n messagediv.classList.add('text-danger');\n\n // And write generic error message in chatbot.\n requestresult.result = await getString('error', 'block_ai_chat');\n return requestresult;\n};\n\n/**\n * Check historic messages for max length.\n * @param {array} messages\n * @returns {array}\n */\nconst checkMessageHistoryLengthLimit = async(messages) => {\n const length = messages.length;\n if (length > maxHistory) {\n // Cut history.\n let shortenedMessages = [messages[0], ...messages.slice(-maxHistory)];\n\n // Show warning once per session.\n if (!maxHistoryWarnings.has(conversation.id)) {\n const maxHistoryString = await getString('maxhistory', 'block_ai_chat', maxHistory);\n const warningErrorString = await getString('maxhistoryreached', 'block_ai_chat', maxHistory);\n await displayAlert(maxHistoryString, warningErrorString);\n // Remember warning.\n maxHistoryWarnings.add(conversation.id);\n }\n return shortenedMessages;\n }\n // Limit not reached, return messages.\n return messages;\n};\n\n/**\n * Check if modal should close on outside click.\n * @param {*} event\n */\nconst checkOutsideClick = (event) => {\n // View openfull acts like a normal modal.\n if (viewmode != VIEW_OPENFULL) {\n event.preventDefault();\n }\n};\n\n/**\n * Set different viewmodes and save in local storage.\n * @param {string} mode\n */\nconst setView = async(mode = '') => {\n const key = await hash('chatmode' + userid);\n // Check for saved viewmode.\n let savedmode = LocalStorage.get(key);\n if (mode == '') {\n if (!savedmode) {\n // Set default.\n mode = VIEW_CHATWINDOW;\n } else {\n mode = savedmode;\n }\n }\n // Save viewmode and set global var.\n LocalStorage.set(key, mode);\n viewmode = mode;\n\n // Set viewmode as bodyclass.\n const body = document.querySelector('body');\n body.classList.remove(VIEW_CHATWINDOW, VIEW_OPENFULL, VIEW_DOCKRIGHT);\n body.classList.add(mode);\n};\n\n/**\n * Is user allowed new queries.\n * @returns {message}\n */\nconst userAllowed = async() => {\n let message;\n if (tenantConfig.tenantenabled === false) {\n message = await getString('error_http403disabled', 'local_ai_manager');\n return message;\n }\n if (tenantConfig.userconfirmed === false) {\n message = await getString('error_http403notconfirmed', 'local_ai_manager');\n message += \". \";\n const link = window.location.origin + '/local/ai_manager/confirm_ai_usage.php';\n message += await getString('confirm_ai_usage', 'block_ai_chat', link);\n return message;\n }\n if (tenantConfig.userlocked === true) {\n message = await getString('error_http403blocked', 'local_ai_manager');\n return message;\n }\n if (chatConfig.isconfigured === false) {\n message = await getString('error_purposenotconfigured', 'local_ai_manager');\n return message;\n }\n if (chatConfig.lockedforrole === true) {\n message = await getString('error_http403blocked', 'local_ai_manager');\n return message;\n }\n if (chatConfig.limitreached === true) {\n message = await getString('error_limitreached', 'local_ai_manager');\n return message;\n }\n return '';\n};\n\n/**\n * Change to openfull view when screen is small.\n * @param {*} e\n */\nconst handleScreenWidthChange = (e) => {\n const body = document.querySelector('body');\n if (e.matches) {\n // Screen width is less than 576px\n body.classList.remove(VIEW_CHATWINDOW, VIEW_OPENFULL, VIEW_DOCKRIGHT);\n body.classList.add(VIEW_OPENFULL);\n } else {\n body.classList.remove(VIEW_CHATWINDOW, VIEW_OPENFULL, VIEW_DOCKRIGHT);\n body.classList.add(viewmode);\n }\n};\n"],"names":["VIEW_CHATWINDOW","VIEW_OPENFULL","VIEW_DOCKRIGHT","strHistory","strNewDialog","strToday","strYesterday","badge","viewmode","modal","modalopen","conversation","id","messages","allConversations","userid","contextid","firstLoad","aiAtWork","maxHistory","maxHistoryWarnings","Set","tenantConfig","chatConfig","DialogModal","Modal","configure","modalConfig","show","removeOnClose","isVerticallyCentered","titletest","setTitletest","value","hide","document","querySelector","classList","remove","async","params","new","history","aiConfig","purposes","find","p","purpose","create","templateContext","title","getRoot","on","e","target","add","ModalEvents","outsideClick","event","checkOutsideClick","setView","getElementById","addEventListener","textarea","addTextareaListener","clickSubmitButton","getConversations","showConversation","conversationcontextLimit","externalServices","getConversationcontextLimit","limit","newDialog","deleteCurrentDialog","showHistory","message","userAllowed","notice","helper","focustextarea","showModal","window","matchMedia","handleScreenWidthChange","innerWidth","getAllConversations","error","x","at","length","clearMessages","setModalHeader","showMessages","enterQuestion","question","showMessage","currentUserLanguage","Config","language","substring","LangNames","Intl","DisplayNames","type","push","of","convHistory","checkMessageHistoryLengthLimit","options","idresult","getNewConversationId","timecreated","Math","floor","Date","now","forcenewitemid","itemid","requestresult","manager","askLocalAiManager","code","errorHandling","copy","copyToClipboard","showReply","result","saveConversationLocally","innerHTML","fields","querySelectorAll","field","text","awaitdivs","forEach","val","sender","answer","templateData","html","js","Templates","renderForPromise","appendNodeContents","attachCopyListenerLast","scrollToBottom","deleted","undefined","then","deleteConversation","removeFromHistory","catch","groupedByDate","convo","date","today","getFullYear","getMonth","getDate","yesterday","twoWeeksAgo","setDate","weekday","day","month","monthOptions","year","dateString","toLocaleDateString","hours","getHours","minutes","getMinutes","toString","padStart","convItem","groups","Object","keys","map","key","objects","filter","obj","reply","hideinput","output","input","style","display","setTitle","modalheader","textareaOnKeydown","height","computedStyles","getComputedStyle","lineHeight","parseFloat","paddingTop","paddingBottom","borderTop","borderTopWidth","borderBottom","borderBottomWidth","maxHeight","newHeight","min","scrollHeight","shiftKey","preventDefault","errorString","JSON","parse","answerdivs","closest","shortenedMessages","slice","has","maxHistoryString","warningErrorString","mode","savedmode","LocalStorage","get","set","body","tenantenabled","userconfirmed","link","location","origin","userlocked","isconfigured","lockedforrole","limitreached","matches"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/helper.min.js b/amd/build/helper.min.js index 460aca8..acf1467 100644 --- a/amd/build/helper.min.js +++ b/amd/build/helper.min.js @@ -1,3 +1,3 @@ -define("block_ai_chat/helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.scrollToBottom=_exports.hash=_exports.focustextarea=_exports.escapeHTML=_exports.copyToClipboard=_exports.attachCopyListenerLast=void 0;const copyToClipboard=element=>{const textElement=element.nextElementSibling,textToCopy=textElement.innerText||textElement.textContent;navigator.clipboard.writeText(textToCopy);const toast=element.previousElementSibling;toast.style.visibility="visible",setTimeout((()=>{toast.style.visibility="hidden"}),750)};_exports.copyToClipboard=copyToClipboard;_exports.attachCopyListenerLast=()=>{const elements=document.querySelectorAll(".ai_chat_modal .copy"),last=elements[elements.length-1];last.addEventListener("click",(function(){copyToClipboard(last)}))};_exports.focustextarea=()=>{document.getElementById("block_ai_chat-input-id").focus()};_exports.scrollToBottom=()=>{console.log("scroll to bottom called");const modalContent=document.querySelector(".ai_chat_modal .modal-body .block_ai_chat-output-wrapper");modalContent.scrollTop=modalContent.scrollHeight};_exports.escapeHTML=str=>{if(null==str)return"";const escapeMap={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","/":"/"};return String(str).replace(/[&<>"'`\/]/g,(function(match){return escapeMap[match]}))};_exports.hash=async stringToHash=>{const data=(new TextEncoder).encode(stringToHash),hashAsArrayBuffer=await window.crypto.subtle.digest("SHA-256",data),uint8ViewOfHash=new Uint8Array(hashAsArrayBuffer);return Array.from(uint8ViewOfHash).map((b=>b.toString(16).padStart(2,"0"))).join("")}})); +define("block_ai_chat/helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.scrollToBottom=_exports.hash=_exports.focustextarea=_exports.escapeHTML=_exports.copyToClipboard=_exports.attachCopyListenerLast=void 0;const copyToClipboard=element=>{const textElement=element.nextElementSibling,textToCopy=textElement.innerText||textElement.textContent;navigator.clipboard.writeText(textToCopy);const toast=element.previousElementSibling;toast.style.visibility="visible",setTimeout((()=>{toast.style.visibility="hidden"}),750)};_exports.copyToClipboard=copyToClipboard;_exports.attachCopyListenerLast=()=>{const elements=document.querySelectorAll(".ai_chat_modal .copy"),last=elements[elements.length-1];last.addEventListener("click",(function(){copyToClipboard(last)}))};_exports.focustextarea=()=>{document.getElementById("block_ai_chat-input-id").focus()};_exports.scrollToBottom=()=>{const modalContent=document.querySelector(".ai_chat_modal .modal-body .block_ai_chat-output-wrapper");modalContent.scrollTop=modalContent.scrollHeight};_exports.escapeHTML=str=>{if(null==str)return"";const escapeMap={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","/":"/"};return String(str).replace(/[&<>"'`/]/g,(function(match){return escapeMap[match]}))};_exports.hash=async stringToHash=>{const data=(new TextEncoder).encode(stringToHash),hashAsArrayBuffer=await window.crypto.subtle.digest("SHA-256",data),uint8ViewOfHash=new Uint8Array(hashAsArrayBuffer);return Array.from(uint8ViewOfHash).map((b=>b.toString(16).padStart(2,"0"))).join("")}})); //# sourceMappingURL=helper.min.js.map \ No newline at end of file diff --git a/amd/build/helper.min.js.map b/amd/build/helper.min.js.map index ce3b10b..8bbdfc3 100644 --- a/amd/build/helper.min.js.map +++ b/amd/build/helper.min.js.map @@ -1 +1 @@ -{"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["/**\n * Copy ai reply to clipboard.\n * @param {*} element\n */\nexport const copyToClipboard = (element) => {\n\n // Find the adjacent text container.\n const textElement = element.nextElementSibling;\n\n // Get the text content.\n const textToCopy = textElement.innerText || textElement.textContent;\n\n // Copy to clipboard using the Clipboard API.\n navigator.clipboard.writeText(textToCopy);\n\n // Briefly show toast.\n const toast = element.previousElementSibling;\n toast.style.visibility = 'visible';\n setTimeout(() => {\n toast.style.visibility = 'hidden';\n }, 750);\n\n};\n\n/**\n * Attach copy listener to all elements.\n */\nexport const attachCopyListenerLast = () => {\n const elements = document.querySelectorAll(\".ai_chat_modal .copy\");\n const last = elements[elements.length - 1];\n last.addEventListener('click', function() {\n copyToClipboard(last);\n });\n};\n\n\n/**\n * Focus textarea.\n */\nexport const focustextarea = () => {\n const textarea = document.getElementById('block_ai_chat-input-id');\n textarea.focus();\n};\n\n\n/**\n * Scroll to bottom of modal body.\n */\nexport const scrollToBottom = () => {\n console.log(\"scroll to bottom called\");\n const modalContent = document.querySelector('.ai_chat_modal .modal-body .block_ai_chat-output-wrapper');\n modalContent.scrollTop = modalContent.scrollHeight;\n};\n\n\n/**\n * Escape html.\n * @param {*} str\n */\nexport const escapeHTML = (str) => {\n if (str === null || str === undefined) {\n return '';\n }\n const escapeMap = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '`': '`',\n '/': '/',\n };\n\n return String(str).replace(/[&<>\"'`\\/]/g, function(match) {\n return escapeMap[match];\n });\n};\n\n/**\n * Hash function to get a hash of a string.\n *\n * @param {string} stringToHash the string to hash\n * @returns {Promise} the promise containing a hex representation of the string encoded by SHA-256\n */\nexport const hash = async(stringToHash) => {\n const encoder = new TextEncoder();\n const data = encoder.encode(stringToHash);\n const hashAsArrayBuffer = await window.crypto.subtle.digest(\"SHA-256\", data);\n const uint8ViewOfHash = new Uint8Array(hashAsArrayBuffer);\n return Array.from(uint8ViewOfHash)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n};\n"],"names":["copyToClipboard","element","textElement","nextElementSibling","textToCopy","innerText","textContent","navigator","clipboard","writeText","toast","previousElementSibling","style","visibility","setTimeout","elements","document","querySelectorAll","last","length","addEventListener","getElementById","focus","console","log","modalContent","querySelector","scrollTop","scrollHeight","str","escapeMap","String","replace","match","async","data","TextEncoder","encode","stringToHash","hashAsArrayBuffer","window","crypto","subtle","digest","uint8ViewOfHash","Uint8Array","Array","from","map","b","toString","padStart","join"],"mappings":"6QAIaA,gBAAmBC,gBAGtBC,YAAcD,QAAQE,mBAGtBC,WAAaF,YAAYG,WAAaH,YAAYI,YAGxDC,UAAUC,UAAUC,UAAUL,kBAGxBM,MAAQT,QAAQU,uBACtBD,MAAME,MAAMC,WAAa,UACzBC,YAAW,KACRJ,MAAME,MAAMC,WAAa,WACzB,+EAO+B,WAC5BE,SAAWC,SAASC,iBAAiB,wBACrCC,KAAOH,SAASA,SAASI,OAAS,GACxCD,KAAKE,iBAAiB,SAAS,WAC3BpB,gBAAgBkB,iCAQK,KACRF,SAASK,eAAe,0BAChCC,iCAOiB,KAC1BC,QAAQC,IAAI,iCACNC,aAAeT,SAASU,cAAc,4DAC5CD,aAAaE,UAAYF,aAAaG,kCAQfC,SACnBA,MAAAA,UACO,SAELC,UAAY,KACT,YACA,WACA,WACA,aACA,YACA,aACA,iBAGFC,OAAOF,KAAKG,QAAQ,eAAe,SAASC,cACxCH,UAAUG,yBAULC,MAAAA,qBAEVC,MADU,IAAIC,aACCC,OAAOC,cACtBC,wBAA0BC,OAAOC,OAAOC,OAAOC,OAAO,UAAWR,MACjES,gBAAkB,IAAIC,WAAWN,0BAChCO,MAAMC,KAAKH,iBACbI,KAAKC,GAAMA,EAAEC,SAAS,IAAIC,SAAS,EAAG,OACtCC,KAAK"} \ No newline at end of file +{"version":3,"file":"helper.min.js","sources":["../src/helper.js"],"sourcesContent":["/**\n * Copy ai reply to clipboard.\n * @param {*} element\n */\nexport const copyToClipboard = (element) => {\n\n // Find the adjacent text container.\n const textElement = element.nextElementSibling;\n\n // Get the text content.\n const textToCopy = textElement.innerText || textElement.textContent;\n\n // Copy to clipboard using the Clipboard API.\n navigator.clipboard.writeText(textToCopy);\n\n // Briefly show toast.\n const toast = element.previousElementSibling;\n toast.style.visibility = 'visible';\n setTimeout(() => {\n toast.style.visibility = 'hidden';\n }, 750);\n\n};\n\n/**\n * Attach copy listener to all elements.\n */\nexport const attachCopyListenerLast = () => {\n const elements = document.querySelectorAll(\".ai_chat_modal .copy\");\n const last = elements[elements.length - 1];\n last.addEventListener('click', function() {\n copyToClipboard(last);\n });\n};\n\n\n/**\n * Focus textarea.\n */\nexport const focustextarea = () => {\n const textarea = document.getElementById('block_ai_chat-input-id');\n textarea.focus();\n};\n\n\n/**\n * Scroll to bottom of modal body.\n */\nexport const scrollToBottom = () => {\n const modalContent = document.querySelector('.ai_chat_modal .modal-body .block_ai_chat-output-wrapper');\n modalContent.scrollTop = modalContent.scrollHeight;\n};\n\n\n/**\n * Escape html.\n * @param {*} str\n */\nexport const escapeHTML = (str) => {\n if (str === null || str === undefined) {\n return '';\n }\n const escapeMap = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '`': '`',\n '/': '/',\n };\n\n return String(str).replace(/[&<>\"'`/]/g, function(match) {\n return escapeMap[match];\n });\n};\n\n/**\n * Hash function to get a hash of a string.\n *\n * @param {string} stringToHash the string to hash\n * @returns {Promise} the promise containing a hex representation of the string encoded by SHA-256\n */\nexport const hash = async(stringToHash) => {\n const encoder = new TextEncoder();\n const data = encoder.encode(stringToHash);\n const hashAsArrayBuffer = await window.crypto.subtle.digest(\"SHA-256\", data);\n const uint8ViewOfHash = new Uint8Array(hashAsArrayBuffer);\n return Array.from(uint8ViewOfHash)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n};\n"],"names":["copyToClipboard","element","textElement","nextElementSibling","textToCopy","innerText","textContent","navigator","clipboard","writeText","toast","previousElementSibling","style","visibility","setTimeout","elements","document","querySelectorAll","last","length","addEventListener","getElementById","focus","modalContent","querySelector","scrollTop","scrollHeight","str","escapeMap","String","replace","match","async","data","TextEncoder","encode","stringToHash","hashAsArrayBuffer","window","crypto","subtle","digest","uint8ViewOfHash","Uint8Array","Array","from","map","b","toString","padStart","join"],"mappings":"6QAIaA,gBAAmBC,gBAGtBC,YAAcD,QAAQE,mBAGtBC,WAAaF,YAAYG,WAAaH,YAAYI,YAGxDC,UAAUC,UAAUC,UAAUL,kBAGxBM,MAAQT,QAAQU,uBACtBD,MAAME,MAAMC,WAAa,UACzBC,YAAW,KACRJ,MAAME,MAAMC,WAAa,WACzB,+EAO+B,WAC5BE,SAAWC,SAASC,iBAAiB,wBACrCC,KAAOH,SAASA,SAASI,OAAS,GACxCD,KAAKE,iBAAiB,SAAS,WAC3BpB,gBAAgBkB,iCAQK,KACRF,SAASK,eAAe,0BAChCC,iCAOiB,WACpBC,aAAeP,SAASQ,cAAc,4DAC5CD,aAAaE,UAAYF,aAAaG,kCAQfC,SACnBA,MAAAA,UACO,SAELC,UAAY,KACT,YACA,WACA,WACA,aACA,YACA,aACA,iBAGFC,OAAOF,KAAKG,QAAQ,cAAc,SAASC,cACvCH,UAAUG,yBAULC,MAAAA,qBAEVC,MADU,IAAIC,aACCC,OAAOC,cACtBC,wBAA0BC,OAAOC,OAAOC,OAAOC,OAAO,UAAWR,MACjES,gBAAkB,IAAIC,WAAWN,0BAChCO,MAAMC,KAAKH,iBACbI,KAAKC,GAAMA,EAAEC,SAAS,IAAIC,SAAS,EAAG,OACtCC,KAAK"} \ No newline at end of file diff --git a/amd/src/ai_manager.js b/amd/src/ai_manager.js index d224eba..27ea7c2 100644 --- a/amd/src/ai_manager.js +++ b/amd/src/ai_manager.js @@ -1,5 +1,4 @@ import {makeRequest} from 'local_ai_manager/make_request'; -import {exception as displayException} from 'core/notification'; /** * Get the async answer from the local_ai_manager. @@ -14,7 +13,6 @@ export const askLocalAiManager = async(purpose, prompt, options = []) => { try { result = await makeRequest(purpose, prompt, options); } catch (error) { - console.log(error); result.code = 'aiconnector'; result.result = error.error + " " + error.message; // For devs. diff --git a/amd/src/dialog.js b/amd/src/dialog.js index 1660064..f9fe907 100644 --- a/amd/src/dialog.js +++ b/amd/src/dialog.js @@ -64,7 +64,6 @@ class DialogModal extends Modal { modalConfig.removeOnClose = false; modalConfig.isVerticallyCentered = false; - // returnFocus: target, super.configure(modalConfig); @@ -107,7 +106,6 @@ export const init = async(params) => { templateContext: { title: strNewDialog, badge: badge, - // history: history, // history dynamically added. }, }); @@ -232,12 +230,9 @@ async function showModal() { * Webservice Get all conversations. */ const getConversations = async() => { - console.log("allConversations called"); try { allConversations = await externalServices.getAllConversations(userid, contextid); - console.log(allConversations); } catch (error) { - console.log(allConversations); displayException(error); } }; @@ -247,7 +242,6 @@ const getConversations = async() => { * @param {*} id */ const showConversation = (id = 0) => { - console.log("showConversation called"); // Dissallow changing conversations when question running. if (aiAtWork) { return; @@ -284,7 +278,6 @@ const enterQuestion = async(question) => { } const message = await userAllowed(); if (message !== '') { - console.log("User not allowed"); const notice = await getString('noticenewquestion', 'block_ai_chat'); await displayAlert(notice, message); aiAtWork = false; @@ -363,7 +356,7 @@ const enterQuestion = async(question) => { * Render reply. * @param {string} text */ -const showReply = async (text) => { +const showReply = async(text) => { // Get textblock. let fields = document.querySelectorAll('.ai_chat_modal .awaitanswer .text'); const field = fields[fields.length - 1]; @@ -378,7 +371,6 @@ const showReply = async (text) => { }; const showMessages = () => { - console.log("showMessages called"); conversation.messages.forEach((val) => { showMessage(val.message, val.sender); }); @@ -427,7 +419,6 @@ const showMessage = async(text, sender = '', answer = true) => { * @param {bool} deleted */ const newDialog = async(deleted = false) => { - console.log("newDialog called"); if (aiAtWork) { return; } @@ -449,7 +440,6 @@ const newDialog = async(deleted = false) => { * Delete /hide current dialog. */ const deleteCurrentDialog = () => { - console.log("deleteCurrentDialog called"); deleteCancelPromise( getString('delete', 'block_ai_chat'), getString('deletewarning', 'block_ai_chat'), @@ -475,7 +465,6 @@ const deleteCurrentDialog = () => { * Show conversation history. */ const showHistory = async() => { - console.log("showHistory called"); // Add current convo local representation, if not already there. if (allConversations.find(x => x.id === conversation.id) === undefined) { allConversations.push(conversation); @@ -598,7 +587,6 @@ const saveConversationLocally = (question, reply) => { * @param {*} hideinput */ const clearMessages = (hideinput = false) => { - console.log("clearMessages called"); const output = document.querySelector('.block_ai_chat-output'); output.innerHTML = ''; // For showing history. @@ -727,7 +715,6 @@ const errorHandling = async(requestresult, question, options) => { // And write generic error message in chatbot. requestresult.result = await getString('error', 'block_ai_chat'); - console.log(requestresult); return requestresult; }; @@ -738,11 +725,9 @@ const errorHandling = async(requestresult, question, options) => { */ const checkMessageHistoryLengthLimit = async(messages) => { const length = messages.length; - console.log("checkHistoryLengthLimit called"); if (length > maxHistory) { // Cut history. let shortenedMessages = [messages[0], ...messages.slice(-maxHistory)]; - console.log(shortenedMessages); // Show warning once per session. if (!maxHistoryWarnings.has(conversation.id)) { @@ -800,6 +785,7 @@ const setView = async(mode = '') => { * @returns {message} */ const userAllowed = async() => { + let message; if (tenantConfig.tenantenabled === false) { message = await getString('error_http403disabled', 'local_ai_manager'); return message; diff --git a/amd/src/helper.js b/amd/src/helper.js index ea9cc88..d82e1eb 100644 --- a/amd/src/helper.js +++ b/amd/src/helper.js @@ -47,7 +47,6 @@ export const focustextarea = () => { * Scroll to bottom of modal body. */ export const scrollToBottom = () => { - console.log("scroll to bottom called"); const modalContent = document.querySelector('.ai_chat_modal .modal-body .block_ai_chat-output-wrapper'); modalContent.scrollTop = modalContent.scrollHeight; }; @@ -71,7 +70,7 @@ export const escapeHTML = (str) => { '/': '/', }; - return String(str).replace(/[&<>"'`\/]/g, function(match) { + return String(str).replace(/[&<>"'`/]/g, function(match) { return escapeMap[match]; }); }; diff --git a/classes/external/delete_conversation.php b/classes/external/delete_conversation.php index 594dd8d..3ee147f 100644 --- a/classes/external/delete_conversation.php +++ b/classes/external/delete_conversation.php @@ -47,6 +47,8 @@ public static function execute_parameters(): external_function_parameters { * Execute the service. * * @param int $contextid + * @param int $userid + * @param int $conversationid * @return array * @throws invalid_parameter_exception * @throws dml_exception diff --git a/classes/local/hook_callbacks.php b/classes/local/hook_callbacks.php index fd1e0dd..25c0130 100644 --- a/classes/local/hook_callbacks.php +++ b/classes/local/hook_callbacks.php @@ -85,7 +85,9 @@ public static function handle_after_form_submission(\core_course\hook\after_form /** * Check if block instance is present and set addaichat form setting. * - * @param after_form_submission $hook + * @param after_form_definition_after_data $hook + * @return void + * @throws \dml_exception */ public static function handle_after_form_definition_after_data(\core_course\hook\after_form_definition_after_data $hook): void { // Get form data. diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 3e8e768..33eda6d 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -33,6 +33,7 @@ class renderer extends plugin_renderer_base { /** * Defer to template. * + * @param block_ai_chat $block * @return string html for the page */ public function render_ai_chat_content(\block_ai_chat $block): string { diff --git a/styles.css b/styles.css index dd90c58..0504a0b 100644 --- a/styles.css +++ b/styles.css @@ -1,3 +1,4 @@ +/* stylelint-disable */ #ai_chat_button { visibility: visible; position: fixed; @@ -256,6 +257,11 @@ display: none; } +@media (min-width: 497px) { + .ai_chat_modal.onhistorypage .modal-body { + min-width: 497px; + } +} .ai_fake_block { visibility: hidden; height: 0; @@ -277,7 +283,8 @@ body.block_ai_chat_chatwindow, body.block_ai_chat_dockright { overflow: initial; } -body.block_ai_chat_chatwindow .modal-backdrop.show, body.block_ai_chat_chatwindow .ai_chat_modal, +body.block_ai_chat_chatwindow .modal-backdrop.show, +body.block_ai_chat_chatwindow .ai_chat_modal, body.block_ai_chat_dockright .modal-backdrop.show, body.block_ai_chat_dockright .ai_chat_modal { width: 0; @@ -330,11 +337,6 @@ body.block_ai_chat_openfull .block_floatingbutton-floatingicons { display: none; } } -@media (min-width: 497px) { - .ai_chat_modal.onhistorypage .modal-body { - min-width: 497px; - } -} body.block_ai_chat_replacehelp #page-footer [data-region=footer-container-popover] .btn-footer-popover[data-action=footer-popover] { display: none; } diff --git a/styles.scss b/styles.scss index affaf93..fcb4dc0 100644 --- a/styles.scss +++ b/styles.scss @@ -1,3 +1,4 @@ +/* stylelint-disable */ #ai_chat_button { visibility: visible; position: fixed; @@ -269,7 +270,13 @@ } } } - +@media (min-width: 497px) { + .ai_chat_modal.onhistorypage { + .modal-body { + min-width: 497px; + } + } +} .ai_fake_block { visibility: hidden; @@ -292,7 +299,8 @@ body.block_ai_chat_chatwindow { body.block_ai_chat_chatwindow, body.block_ai_chat_dockright { overflow: initial; - .modal-backdrop.show, .ai_chat_modal { + .modal-backdrop.show, + .ai_chat_modal { width: 0; height: 0; } @@ -349,13 +357,7 @@ body.block_ai_chat_openfull { display: none; } } -@media (min-width: 497px) { - .ai_chat_modal.onhistorypage { - .modal-body { - min-width: 497px; - } - } -} + body.block_ai_chat_replacehelp { // Replace question mark popover on the bottom right corner with ai_chat. diff --git a/templates/dialog_modal.mustache b/templates/dialog_modal.mustache index 4cc00a6..235e664 100644 --- a/templates/dialog_modal.mustache +++ b/templates/dialog_modal.mustache @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! - @template block_ai_chat/iconpicker + @template block_ai_chat/dialog_modal Template for iconpicker. @@ -32,7 +32,7 @@ along with Moodle. If not, see . { "history": { "message":"content", - "sender":"user", + "sender":"user" } } }} @@ -40,7 +40,7 @@ along with Moodle. If not, see . {{< core/modal }} {{$title}} -
{{title}}
+ {{title}} {{> block_ai_chat/headeroptions }} {{/title}} {{$body}} diff --git a/templates/floatingbutton.mustache b/templates/floatingbutton.mustache index bda2723..50c04a2 100644 --- a/templates/floatingbutton.mustache +++ b/templates/floatingbutton.mustache @@ -22,10 +22,10 @@ Example context (json): { "title":"AI Companion", - "arialabel":"Open AI Companion", + "arialabel":"Open AI Companion" } }} \ No newline at end of file diff --git a/templates/history.mustache b/templates/history.mustache index c1c68cc..2064da7 100644 --- a/templates/history.mustache +++ b/templates/history.mustache @@ -24,8 +24,8 @@ "key": "Heute", "objects": [{ "title": "Erste Frage", - "conversationid": 9, - },] + "conversationid": 9 + }] } }} diff --git a/templates/message.mustache b/templates/message.mustache index e0427d2..62fcdfa 100644 --- a/templates/message.mustache +++ b/templates/message.mustache @@ -15,14 +15,14 @@ along with Moodle. If not, see . }} {{! - @template block_ai_chat/floatingbutton + @template block_ai_chat/message Button to call ai_chat modal Example context (json): { "message":"content", - "sender":"", + "sender":"" } }} {{! Show message from user or ai }} @@ -40,7 +40,13 @@ {{{content}}} - +{{! Use 2 closing divs to satisfy linter}} +{{#sender}} + +{{/sender}} +{{^sender}} +
+{{/sender}} {{! Insert placeholder in case of pending question }} {{^answer}}