diff --git a/amd/build/dialog.min.js b/amd/build/dialog.min.js
index 555a6cf..254da35 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()=>{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))}}));
+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("block_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,'.block_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(".block_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(".block_ai_chat_modal .awaitanswer .text");const field=fields[fields.length-1];field.innerHTML=text,field.classList.remove("small");let awaitdivs=document.querySelectorAll(".block_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(".block_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(".block_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(".block_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(".block_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 f03a690..24a061b 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\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
+{"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(\"block_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(\n 'block_ai_chat', userid, '.block_ai_chat_modal_body [data-content=\"local_ai_manager_infobox\"]', ['chat']\n );\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('.block_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('.block_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('.block_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('.block_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('.block_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('.block_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('.block_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 acf1467..8f90271 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=()=>{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(".block_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(".block_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 8bbdfc3..f9fdc6d 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 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
+{"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(\".block_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('.block_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,8BACrCC,KAAOH,SAASA,SAASI,OAAS,GACxCD,KAAKE,iBAAiB,SAAS,WAC3BpB,gBAAgBkB,iCAQK,KACRF,SAASK,eAAe,0BAChCC,iCAOiB,WACpBC,aAAeP,SAASQ,cAAc,kEAC5CD,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/dialog.js b/amd/src/dialog.js
index f9fe907..2e2136a 100644
--- a/amd/src/dialog.js
+++ b/amd/src/dialog.js
@@ -111,7 +111,7 @@ export const init = async(params) => {
// Add class for styling when modal is displayed.
modal.getRoot().on('modal:shown', function(e) {
- e.target.classList.add("ai_chat_modal");
+ e.target.classList.add("block_ai_chat_modal");
});
// Conditionally prevent outside click event.
@@ -211,7 +211,9 @@ async function showModal() {
// Show userquota.
await renderUserQuota('#block_ai_chat_userquota', ['chat']);
// Show infobox.
- await renderInfoBox('block_ai_chat', userid, '.ai_chat_modal_body [data-content="local_ai_manager_infobox"]', ['chat']);
+ await renderInfoBox(
+ 'block_ai_chat', userid, '.block_ai_chat_modal_body [data-content="local_ai_manager_infobox"]', ['chat']
+ );
// Check if all permissions and settings are correct.
const message = await userAllowed();
@@ -332,7 +334,7 @@ const enterQuestion = async(question) => {
}
// Attach copy listener.
- let copy = document.querySelector('.ai_chat_modal .awaitanswer .copy');
+ let copy = document.querySelector('.block_ai_chat_modal .awaitanswer .copy');
copy.addEventListener('mousedown', () => {
helper.copyToClipboard(copy);
});
@@ -358,14 +360,14 @@ const enterQuestion = async(question) => {
*/
const showReply = async(text) => {
// Get textblock.
- let fields = document.querySelectorAll('.ai_chat_modal .awaitanswer .text');
+ let fields = document.querySelectorAll('.block_ai_chat_modal .awaitanswer .text');
const field = fields[fields.length - 1];
// Render the reply.
field.innerHTML = text;
field.classList.remove('small');
// Remove awaitanswer class.
- let awaitdivs = document.querySelectorAll('.ai_chat_modal .awaitanswer');
+ let awaitdivs = document.querySelectorAll('.block_ai_chat_modal .awaitanswer');
const awaitdiv = awaitdivs[awaitdivs.length - 1];
awaitdiv.classList.remove('awaitanswer');
};
@@ -485,7 +487,7 @@ const showHistory = async() => {
});
// Set modal class to hide info about ratelimits and infobox.
- let modal = document.querySelector('.ai_chat_modal');
+ let modal = document.querySelector('.block_ai_chat_modal');
modal.classList.add('onhistorypage');
// Iterate over conversations and group by date.
@@ -549,7 +551,7 @@ const showHistory = async() => {
"dates": convert.groups,
};
const {html, js} = await Templates.renderForPromise('block_ai_chat/history', templateData);
- Templates.appendNodeContents('.ai_chat_modal .block_ai_chat-output', html, js);
+ Templates.appendNodeContents('.block_ai_chat_modal .block_ai_chat-output', html, js);
// Add a listener for the new dialog button.
const btnNewDialog = document.getElementById('ai_chat_history_new_dialog');
@@ -603,7 +605,7 @@ const clearMessages = (hideinput = false) => {
* @param {*} setTitle
*/
const setModalHeader = (setTitle = '') => {
- let modalheader = document.querySelector('.ai_chat_modal .modal-title div');
+ let modalheader = document.querySelector('.block_ai_chat_modal .modal-title div');
let title = '';
if (modalheader !== null && (conversation.messages.length > 0 || setTitle.length)) {
if (!setTitle.length) {
@@ -614,7 +616,7 @@ const setModalHeader = (setTitle = '') => {
modalheader.innerHTML = title;
}
// Remove onhistorypage, since history page is setting it.
- let modal = document.querySelector('.ai_chat_modal');
+ let modal = document.querySelector('.block_ai_chat_modal');
modal.classList.remove('onhistorypage');
};
diff --git a/amd/src/helper.js b/amd/src/helper.js
index d82e1eb..e4c0d30 100644
--- a/amd/src/helper.js
+++ b/amd/src/helper.js
@@ -26,7 +26,7 @@ export const copyToClipboard = (element) => {
* Attach copy listener to all elements.
*/
export const attachCopyListenerLast = () => {
- const elements = document.querySelectorAll(".ai_chat_modal .copy");
+ const elements = document.querySelectorAll(".block_ai_chat_modal .copy");
const last = elements[elements.length - 1];
last.addEventListener('click', function() {
copyToClipboard(last);
@@ -47,7 +47,7 @@ export const focustextarea = () => {
* Scroll to bottom of modal body.
*/
export const scrollToBottom = () => {
- const modalContent = document.querySelector('.ai_chat_modal .modal-body .block_ai_chat-output-wrapper');
+ const modalContent = document.querySelector('.block_ai_chat_modal .modal-body .block_ai_chat-output-wrapper');
modalContent.scrollTop = modalContent.scrollHeight;
};
diff --git a/lang/de/block_ai_chat.php b/lang/de/block_ai_chat.php
index ca7d54c..e05dc45 100644
--- a/lang/de/block_ai_chat.php
+++ b/lang/de/block_ai_chat.php
@@ -53,7 +53,7 @@
$string['noticenewquestion'] = 'Aktuell können sie keine Frage mehr übermitteln';
$string['openfull'] = 'Vollflächig öffnen';
$string['pluginname'] = 'KI-Chat';
-$string['privacy:metadata'] = 'Dieses Plugin speichert keine Daten, Konversationen werden im AI Manager gespeichert.';
+$string['privacy:metadata'] = 'Konversationen werden im AI Manager gespeichert.';
$string['private'] = 'Privat';
$string['public'] = 'Öffentlich';
$string['replacehelp'] = 'Ai Chat-Button anstelle des Hilfe-Buttons anzeigen.';
diff --git a/lang/en/block_ai_chat.php b/lang/en/block_ai_chat.php
index 4882f1d..99c9d36 100644
--- a/lang/en/block_ai_chat.php
+++ b/lang/en/block_ai_chat.php
@@ -53,7 +53,7 @@
$string['noticenewquestion'] = 'Currently you cant submit a new question';
$string['openfull'] = 'Use full width';
$string['pluginname'] = 'AI Chat';
-$string['privacy:metadata'] = 'This plugin doesn\'t store personal data.';
+$string['privacy:metadata'] = 'Conversations are saved by local_ai_manager.';
$string['private'] = 'Private';
$string['public'] = 'Public';
$string['replacehelp'] = 'Replace helpbutton with block_ai_chat button';
diff --git a/styles.css b/styles.css
index 0504a0b..3c8af3b 100644
--- a/styles.css
+++ b/styles.css
@@ -27,85 +27,85 @@
right: calc(315px + 2rem);
}
-.ai_chat_modal .modal-dialog {
+.block_ai_chat_modal .modal-dialog {
min-height: 500px;
}
-.ai_chat_modal .modal-header {
+.block_ai_chat_modal .modal-header {
width: 100%;
border-bottom: none;
}
-.ai_chat_modal .modal-header .modal-title {
+.block_ai_chat_modal .modal-header .modal-title {
display: flex;
justify-content: space-between;
align-items: center;
width: calc(100% - 2rem);
font-size: 1rem;
}
-.ai_chat_modal .modal-header .modal-title .ai_chat_title {
+.block_ai_chat_modal .modal-header .modal-title .block_ai_chat_title {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
-.ai_chat_modal .modal-body {
+.block_ai_chat_modal .modal-body {
display: flex;
padding: 0 0 0 1rem;
}
-.ai_chat_modal .modal-body .block_ai_chat-dialog {
+.block_ai_chat_modal .modal-body .block_ai_chat-dialog {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 100%;
}
-.ai_chat_modal .content {
+.block_ai_chat_modal .content {
display: flex;
align-items: flex-start;
padding: 0.5rem 1rem 0.5rem 1rem;
position: relative;
}
-.ai_chat_modal .message .copy {
+.block_ai_chat_modal .message .copy {
visibility: hidden;
position: absolute;
top: 0;
right: 0;
padding: 0.5rem 0.75rem 2.5rem 2.75rem;
}
-.ai_chat_modal .message.ai:hover .copy {
+.block_ai_chat_modal .message.ai:hover .copy {
visibility: visible;
cursor: pointer;
}
-.ai_chat_modal .message.ai .content {
+.block_ai_chat_modal .message.ai .content {
padding-left: 0;
}
-.ai_chat_modal .message.ai .content .text {
+.block_ai_chat_modal .message.ai .content .text {
margin-top: 0.4rem;
}
-.ai_chat_modal .message.ai .content .text p {
+.block_ai_chat_modal .message.ai .content .text p {
margin-bottom: 0.3rem;
}
-.ai_chat_modal .message.ai .content .ai_chat_icon {
+.block_ai_chat_modal .message.ai .content .ai_chat_icon {
margin-top: 0.8rem;
}
-.ai_chat_modal .message.ai .content .spinner-border {
+.block_ai_chat_modal .message.ai .content .spinner-border {
width: 1.5rem;
height: 1.5rem;
}
-.ai_chat_modal .message.ai .content.awaitanswer .ai_chat_icon,
-.ai_chat_modal .message.ai .content.awaitanswer .copy {
+.block_ai_chat_modal .message.ai .content.awaitanswer .ai_chat_icon,
+.block_ai_chat_modal .message.ai .content.awaitanswer .copy {
display: none;
}
-.ai_chat_modal .message.agent .content {
+.block_ai_chat_modal .message.agent .content {
background-color: #dee2e6;
margin-left: 4rem;
border-radius: 0.5rem;
}
-.ai_chat_modal .message.agent .content .ai_chat_icon {
+.block_ai_chat_modal .message.agent .content .ai_chat_icon {
display: none;
}
-.ai_chat_modal .message.agent .content p {
+.block_ai_chat_modal .message.agent .content p {
margin-bottom: 0.1rem;
}
-.ai_chat_modal .copiedtoast {
+.block_ai_chat_modal .copiedtoast {
position: absolute;
top: -15px;
right: 0;
@@ -115,24 +115,24 @@
background-color: #fff;
visibility: hidden;
}
-.ai_chat_modal .block_ai_chat-input-wrapper {
+.block_ai_chat_modal .block_ai_chat-input-wrapper {
position: sticky;
bottom: 0;
background-color: #fff;
padding-right: 1rem;
}
-.ai_chat_modal .block_ai_chat-input {
+.block_ai_chat_modal .block_ai_chat-input {
display: flex;
margin-top: 0.15rem;
padding: 0 0.1rem 0 0.1rem;
position: relative;
}
-.ai_chat_modal .block_ai_chat-input .ai_chat_icon {
+.block_ai_chat_modal .block_ai_chat-input .ai_chat_icon {
position: absolute;
top: 1.2rem;
left: 0.5rem;
}
-.ai_chat_modal .block_ai_chat-input textarea {
+.block_ai_chat_modal .block_ai_chat-input textarea {
height: inherit;
padding: 1rem 4.1rem 1rem 2.2rem;
resize: none;
@@ -143,44 +143,44 @@
box-sizing: border-box;
margin-left: 2px;
}
-.ai_chat_modal .block_ai_chat-input textarea::placeholder {
+.block_ai_chat_modal .block_ai_chat-input textarea::placeholder {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
-.ai_chat_modal .block_ai_chat-input button {
+.block_ai_chat_modal .block_ai_chat-input button {
position: absolute;
display: flex;
bottom: 0.5rem;
right: 0.7rem;
}
-.ai_chat_modal .block_ai_chat-input button i {
+.block_ai_chat_modal .block_ai_chat-input button i {
padding: 6.5px 0;
}
-.ai_chat_modal .headeroptions {
+.block_ai_chat_modal .headeroptions {
display: flex;
align-items: center;
}
-.ai_chat_modal .headeroptions .badge-pill {
+.block_ai_chat_modal .headeroptions .badge-pill {
display: flex;
align-items: center;
height: 1.3rem;
font-size: 50%;
}
-.ai_chat_modal .headeroptions .dropdown-menu {
+.block_ai_chat_modal .headeroptions .dropdown-menu {
left: 36px !important;
}
-.ai_chat_modal .headeroptions .dropdown-menu #block_ai_chat_delete_dialog {
+.block_ai_chat_modal .headeroptions .dropdown-menu #block_ai_chat_delete_dialog {
color: #ff3333;
}
-.ai_chat_modal .headeroptions .dropdown-menu #block_ai_chat_delete_dialog:hover {
+.block_ai_chat_modal .headeroptions .dropdown-menu #block_ai_chat_delete_dialog:hover {
background-color: #ffcccc;
}
-.ai_chat_modal #block_ai_chat_userquota {
+.block_ai_chat_modal #block_ai_chat_userquota {
text-align: right;
min-height: 1rem;
}
-.ai_chat_modal #block_ai_chat_userquota .local_ai_manager_userquota_infobox {
+.block_ai_chat_modal #block_ai_chat_userquota .local_ai_manager_userquota_infobox {
color: #6c757d;
background-color: inherit;
font-size: 0.6rem;
@@ -188,7 +188,7 @@
margin: 0;
text-align: right;
}
-.ai_chat_modal .gradient-overlay {
+.block_ai_chat_modal .gradient-overlay {
content: "";
position: sticky;
top: 0;
@@ -198,11 +198,11 @@
background: linear-gradient(to bottom, #fff 10%, transparent);
z-index: 33;
}
-.ai_chat_modal .ai_chat_modal_body {
+.block_ai_chat_modal .block_ai_chat_modal_body {
width: 100%;
overflow: hidden;
}
-.ai_chat_modal .ai_chat_modal_body .infobox {
+.block_ai_chat_modal .block_ai_chat_modal_body .infobox {
display: flex;
align-items: center;
flex-direction: row-reverse;
@@ -211,64 +211,58 @@
z-index: 1;
min-height: 2rem;
}
-.ai_chat_modal .ai_chat_modal_body .infobox .local_ai_manager-infobox.alert.alert-success {
+.block_ai_chat_modal .block_ai_chat_modal_body .infobox .local_ai_manager-infobox.alert.alert-success {
color: #6c757d;
background-color: inherit;
font-size: 0.6rem;
padding: 0;
margin: 0;
}
-.ai_chat_modal .block_ai_chat-output-wrapper {
+.block_ai_chat_modal .block_ai_chat-output-wrapper {
overflow-y: auto;
height: 100%;
padding-right: 1rem;
}
-.ai_chat_modal .block_ai-history-items {
+.block_ai_chat_modal .block_ai-history-items {
padding: 0.5rem;
}
-.ai_chat_modal .block_ai-history-items a {
+.block_ai_chat_modal .block_ai-history-items a {
display: flex;
align-items: baseline;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
-.ai_chat_modal .block_ai-history-items a p {
+.block_ai_chat_modal .block_ai-history-items a p {
margin: 0;
}
-.ai_chat_modal .block_ai-history-items > div {
+.block_ai_chat_modal .block_ai-history-items > div {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
gap: 0.2rem;
}
-.ai_chat_modal .block_ai-history-items.card {
+.block_ai_chat_modal .block_ai-history-items.card {
flex-direction: column-reverse;
}
-.ai_chat_modal.onhistorypage .block_ai_chat-output {
+.block_ai_chat_modal.onhistorypage .block_ai_chat-output {
display: flex;
flex-direction: column-reverse;
}
-.ai_chat_modal.onhistorypage .infobox,
-.ai_chat_modal.onhistorypage #block_ai_chat_userquota {
+.block_ai_chat_modal.onhistorypage .infobox,
+.block_ai_chat_modal.onhistorypage #block_ai_chat_userquota {
display: none;
}
-.ai_chat_modal.onhistorypage .gradient-overlay {
+.block_ai_chat_modal.onhistorypage .gradient-overlay {
display: none;
}
@media (min-width: 497px) {
- .ai_chat_modal.onhistorypage .modal-body {
+ .block_ai_chat_modal.onhistorypage .modal-body {
min-width: 497px;
}
}
-.ai_fake_block {
- visibility: hidden;
- height: 0;
- margin: 0;
-}
-
-body.block_ai_chat_chatwindow .ai_chat_modal .modal-dialog {
+body.block_ai_chat_chatwindow .block_ai_chat_modal .modal-dialog {
position: fixed;
max-height: calc(100vh - 6rem);
right: 2rem;
@@ -284,18 +278,18 @@ 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 .block_ai_chat_modal,
body.block_ai_chat_dockright .modal-backdrop.show,
-body.block_ai_chat_dockright .ai_chat_modal {
+body.block_ai_chat_dockright .block_ai_chat_modal {
width: 0;
height: 0;
}
-body.block_ai_chat_openfull .ai_chat_modal .modal-dialog {
+body.block_ai_chat_openfull .block_ai_chat_modal .modal-dialog {
max-width: 95%;
min-height: calc(100vh - 3.5rem);
}
-body.block_ai_chat_openfull .ai_chat_modal .ai_chat_modal_body {
+body.block_ai_chat_openfull .block_ai_chat_modal .block_ai_chat_modal_body {
max-width: 1080px;
margin: 0 auto;
}
@@ -306,7 +300,7 @@ body.block_ai_chat_openfull a#block_ai_chat_openfull {
body.block_ai_chat_dockright.block_ai_chat_open #page-wrapper #page {
margin-right: 50%;
}
-body.block_ai_chat_dockright .ai_chat_modal .modal-dialog {
+body.block_ai_chat_dockright .block_ai_chat_modal .modal-dialog {
position: fixed;
width: 50%;
max-width: 50%;
@@ -316,7 +310,7 @@ body.block_ai_chat_dockright .ai_chat_modal .modal-dialog {
margin-top: 0;
margin-bottom: 0;
}
-body.block_ai_chat_dockright .ai_chat_modal .modal-dialog .modal-content {
+body.block_ai_chat_dockright .block_ai_chat_modal .modal-dialog .modal-content {
border-radius: 0;
border-top: none;
border-bottom: none;
diff --git a/styles.scss b/styles.scss
index fcb4dc0..4fcc533 100644
--- a/styles.scss
+++ b/styles.scss
@@ -31,7 +31,7 @@
}
-.ai_chat_modal{
+.block_ai_chat_modal{
.modal-dialog {
min-height: 500px;
}
@@ -44,7 +44,7 @@
align-items: center;
width: calc(100% - 2rem);
font-size: 1rem;
- .ai_chat_title {
+ .block_ai_chat_title {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@@ -209,7 +209,7 @@
background: linear-gradient(to bottom, #fff 10%, transparent);
z-index: 33;
}
- .ai_chat_modal_body {
+ .block_ai_chat_modal_body {
width: 100%;
overflow: hidden;
.infobox {
@@ -271,21 +271,15 @@
}
}
@media (min-width: 497px) {
- .ai_chat_modal.onhistorypage {
+ .block_ai_chat_modal.onhistorypage {
.modal-body {
min-width: 497px;
}
}
}
-.ai_fake_block {
- visibility: hidden;
- height: 0;
- margin: 0;
-}
-
body.block_ai_chat_chatwindow {
- .ai_chat_modal .modal-dialog {
+ .block_ai_chat_modal .modal-dialog {
position: fixed;
max-height: calc(100vh - 6rem);
right: 2rem;
@@ -300,19 +294,19 @@ body.block_ai_chat_chatwindow,
body.block_ai_chat_dockright {
overflow: initial;
.modal-backdrop.show,
- .ai_chat_modal {
+ .block_ai_chat_modal {
width: 0;
height: 0;
}
}
body.block_ai_chat_openfull {
- .ai_chat_modal {
+ .block_ai_chat_modal {
.modal-dialog {
max-width: 95%;
min-height: calc(100vh - 3.5rem);
}
- .ai_chat_modal_body {
+ .block_ai_chat_modal_body {
max-width: 1080px;
margin: 0 auto;
}
@@ -325,7 +319,7 @@ body.block_ai_chat_dockright {
&.block_ai_chat_open #page-wrapper #page {
margin-right: 50%;
}
- .ai_chat_modal .modal-dialog {
+ .block_ai_chat_modal .modal-dialog {
position: fixed;
width: 50%;
max-width: 50%;
diff --git a/templates/dialog_modal.mustache b/templates/dialog_modal.mustache
index cc7b5b9..ed01c56 100644
--- a/templates/dialog_modal.mustache
+++ b/templates/dialog_modal.mustache
@@ -40,11 +40,11 @@ along with Moodle. If not, see .
{{< core/modal }}
{{$title}}
- {{title}}
+ {{title}}
{{> block_ai_chat/headeroptions }}
{{/title}}
{{$body}}
-