diff --git a/extension/MoodleGPT.js b/extension/MoodleGPT.js index 73c63f4..72628e7 100644 --- a/extension/MoodleGPT.js +++ b/extension/MoodleGPT.js @@ -1,2 +1,2 @@ -!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";function e(e){const n=document.title;document.title=e,setTimeout((()=>document.title=n),3e3)}function n(e,n,t,o){return new(t||(t=Promise))((function(r,i){function s(e){try{c(o.next(e))}catch(e){i(e)}}function l(e){try{c(o.throw(e))}catch(e){i(e)}}function c(e){var n;e.done?r(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(s,l)}c((o=o.apply(e,n||[])).next())}))}function t(e,n){const t=e.length>n.length?e.length:n.length;return 0===t?1:(t-function(e,n){if(0===e.length)return n.length;if(0===n.length)return e.length;const t=[],o=e.replace(/\s+/,""),r=n.replace(/\s+/,"");for(let e=0;e<=o.length;++e){t.push([e]);for(let n=1;n<=r.length;++n)t[e][n]=0===e?n:Math.min(t[e-1][n]+1,t[e][n-1]+1,t[e-1][n-1]+(o[e-1]===r[n-1]?0:1))}return t[o.length][r.length]}(e,n))/t}function o(e,n){let o={element:null,similarity:0,value:null};for(const r of n){const n=t(r.value,e);if(1===n)return{element:r.element,value:r.value,similarity:n};n>o.similarity&&(o={element:r.element,value:r.value,similarity:n})}return o}"function"==typeof SuppressedError&&SuppressedError;class r{static question(e){console.log("%c[QUESTION]: %s","color: cyan",e)}static bestAnswer(e,n){console.log("%c[BEST ANSWER]: %s","color: green",`"${e}" with a similarity of ${function(e){return Math.round(100*e*100)/100+"%"}(n)}`)}static array(e){console.log("[CORRECTS] ",e)}static response(e){console.log("Original:\n"+e.response),console.log("Normalized:\n"+e.normalizedResponse)}}function i(e,n=!0){n&&(e=e.toLowerCase());return e.replace(/\n+/gi,"\n").replace(/(\n\s*\n)+/g,"\n").replace(/[ \t]+/gi," ").trim().replace(/^[a-z\d]\.\s/gi,"").replace(/\n[a-z\d]\.\s/gi,"\n")}var s,l;!function(e){e.SYSTEM="system",e.USER="user",e.ASSISTANT="assistant"}(s||(s={})),function(e){e.TEXT="text",e.IMAGE="image_url"}(l||(l={}));const c="\nAct as a quiz solver for the best notation with the following rules:\n- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation. \n- But for the calculation provide this format 'result: '\n- For 'put in order' questions, maintain the answer in the order as presented in the question but assocy the correct order to it by usin this format ':\n:', ignore other rules.\n- Always reply in the format: '\n\n...'.\n- Retain only the correct answer(s).\n- Maintain the same order for the answers as in the text.\n- Retain all text from the answer with its description, content or definition.\n- Only provide answers that exactly match the given answer in the text.\n- The question always has the correct answer(s), so you should always provide an answer.\n- Always respond in the same language as the user's question.\n".trim(),a={role:s.SYSTEM,content:c};function u(e,t,o){return n(this,void 0,void 0,(function*(){const n=t.querySelectorAll("img");if(!e.includeImages||!function(e){const n=e.match(/gpt-(\d+)/);return!!(null==n?void 0:n[1])&&Number(n[1])>=4}(e.model)||0===n.length)return o;const r=[],i=Array.from(n).map((e=>function(e,n=.75){return new Promise(((t,o)=>{const r=document.createElement("canvas"),i=r.getContext("2d");if(!i)return o("Can't get the canvas context, ensure your navigator support canvas"),void r.remove();const s=new Image;s.crossOrigin="Anonymous",s.onload=()=>{r.width=s.width,r.height=s.height,i.drawImage(s,0,0);const e=r.toDataURL("image/png",n);t(e),r.remove()},s.onerror=e=>{o(e),r.remove()},s.src=e.src}))}(e))),s=yield Promise.allSettled(i);for(const n of s)"fulfilled"===n.status?r.push({type:l.IMAGE,image_url:{url:n.value}}):e.logs&&console.error(n.reason);return r.push({type:l.TEXT,text:o}),r}))}function f(e,t,o){return n(this,void 0,void 0,(function*(){const n=yield u(e,t,o),r={role:s.USER,content:n};if(!e.history)return{messages:[a,r]};let i;const l=JSON.parse(null!==(c=sessionStorage.moodleGPTHistory)&&void 0!==c?c:"null");var c;const f=function(){var e,n;const t=new URLSearchParams(document.location.search);return{host:document.location.host,cmid:null!==(e=t.get("cmid"))&&void 0!==e?e:"",attempt:null!==(n=t.get("attempt"))&&void 0!==n?n:"",history:[]}}();return i=null!==l&&function(e,n){const t=["host","cmid","attempt"];for(const o of t)if(e[o]!==n[o])return!1;return!0}(l,f)?l:f,{messages:[a,...i.history,r],saveResponse(n){e.history&&(i.history.push(r),i.history.push({role:s.ASSISTANT,content:n}),sessionStorage.moodleGPTHistory=JSON.stringify(i))}}}))}function m(e){const n=[],t=Array.from(e.querySelectorAll("tr")),o=[];t.map((e=>{const t=Array.from(e.querySelectorAll("td, th")).map(((e,n)=>{var t;const r=null===(t=e.textContent)||void 0===t?void 0:t.trim();return o[n]=Math.max(o[n]||0,(null==r?void 0:r.length)||0),null!=r?r:""}));n.push(t)}));const r=n[0].length,i=o.reduce(((e,n)=>e+n),0)+3*(r-1),s="\n"+Array(i).fill("-").join("")+"\n",l=n.map((e=>e.map(((e,n)=>e.padEnd(o[n]," "))).join(" | ")));return l.shift()+s+l.join("\n")}function d(n,t){n.title&&e("Copied to clipboard"),navigator.clipboard.writeText(t.response)}function p(e,n,t){const o=n[0];if(1!==n.length||"true"!==o.getAttribute("contenteditable"))return!1;if(e.typing){let e=0;const n=function(r){if(r.preventDefault(),"Backspace"===r.key||e>t.response.length)return void o.removeEventListener("keydown",n);o.textContent=t.response.slice(0,++e),o.focus();const i=document.createRange();i.selectNodeContents(o),i.collapse(!1);const s=window.getSelection();null!==s&&(s.removeAllRanges(),s.addRange(i))};o.addEventListener("keydown",n)}else o.textContent=t.response;return!0}function v(e,n,t){var o,r;const i=n[0];if(1!==n.length||"number"!==i.type)return!1;const s=null===(r=null===(o=t.normalizedResponse.match(/\d+([,.]\d+)?/gi))||void 0===o?void 0:o[0])||void 0===r?void 0:r.replace(",",".");if(void 0===s)return!1;if(e.typing){let e=0;const n=function(t){t.preventDefault(),"Backspace"===t.key||e>s.length?i.removeEventListener("keydown",n):("."===s.slice(e,e+1)&&++e,i.value=s.slice(0,++e))};i.addEventListener("keydown",n)}else i.value=s;return!0}function h(e,n,t){const s=null==n?void 0:n[0];if(!s||"radio"!==s.type)return!1;const l=Array.from(n).map((e=>{var n,t;return{element:e,value:i(null!==(t=null===(n=null==e?void 0:e.parentElement)||void 0===n?void 0:n.textContent)&&void 0!==t?t:"")}})).filter((e=>""!==e.value)),c=o(t.normalizedResponse,l);e.logs&&c.value&&r.bestAnswer(c.value,c.similarity);const a=c.element;return e.mouseover?a.addEventListener("mouseover",(()=>a.click()),{once:!0}):a.click(),!0}function g(e,n,t){const s=null==n?void 0:n[0];if(!s||"checkbox"!==s.type)return!1;const l=t.normalizedResponse.split("\n"),c=Array.from(n).map((e=>{var n,t;return{element:e,value:i(null!==(t=null===(n=null==e?void 0:e.parentElement)||void 0===n?void 0:n.textContent)&&void 0!==t?t:"")}})).filter((e=>""!==e.value));for(const n of l){const t=o(n,c);e.logs&&t.value&&r.bestAnswer(t.value,t.similarity);const i=t.element;e.mouseover?i.addEventListener("mouseover",(()=>i.click()),{once:!0}):i.click()}return!0}function y(e,n,t){if(0===n.length||"SELECT"!==n[0].tagName)return!1;const s=t.normalizedResponse.split("\n");e.logs&&r.array(s);for(let t=0;t{var n;return{element:e,value:i(null!==(n=e.textContent)&&void 0!==n?n:"")}})).filter((e=>""!==e.value)),a=o(s[t],c);e.logs&&a.value&&r.bestAnswer(a.value,a.similarity);const u=a.element,f=u.closest("select");null!==f&&(e.mouseover?f.addEventListener("click",(()=>u.selected=!0),{once:!0}):u.selected=!0)}return!0}function w(e,n,t){const o=n[0];if(1!==n.length||"TEXTAREA"!==o.tagName&&"text"!==o.type)return!1;if(e.typing){let e=0;const n=function(r){r.preventDefault(),"Backspace"===r.key||e>t.response.length?o.removeEventListener("keydown",n):o.value=t.response.slice(0,++e)};o.addEventListener("keydown",n)}else o.value=t.response;return!0}function E(e){return n(this,void 0,void 0,(function*(){e.config.cursor&&(e.questionElement.style.cursor="wait");const t=function(e){let n=e.innerText;const t=e.querySelectorAll(".accesshide");for(const e of t)n=n.replace(e.innerText,"");const o=e.querySelectorAll(".qtext table");for(const e of o)n=n.replace(e.innerText,"\n"+m(e)+"\n");return i(n,!1)}(e.form),o=e.form.querySelectorAll(e.inputQuery),s=yield function(e,t,o){return n(this,void 0,void 0,(function*(){const n=new AbortController,r=setTimeout((()=>n.abort()),2e4),s=yield f(e,t,o),l=yield fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},signal:e.timeout?n.signal:null,body:JSON.stringify({model:e.model,messages:s.messages,temperature:.1,top_p:.6,presence_penalty:0,max_tokens:2e3})});clearTimeout(r);const c=(yield l.json()).choices[0].message.content;return"function"==typeof s.saveResponse&&s.saveResponse(c),{question:o,response:c,normalizedResponse:i(c)}}))}(e.config,e.questionElement,t).catch((e=>({error:e}))),l="object"==typeof s&&"error"in s;if(e.config.cursor&&(e.questionElement.style.cursor=e.config.infinite||l?"pointer":"initial"),l)console.error(s.error);else switch(e.config.logs&&(r.question(t),r.response(s)),e.config.mode){case"clipboard":!function(e){e.config.infinite||e.removeListener(),d(e.config,e.gptAnswer)}({config:e.config,questionElement:e.questionElement,gptAnswer:s,removeListener:e.removeListener});break;case"question-to-answer":!function(e){var n;const t=e.questionElement;e.removeListener();const o=null!==(n=t.innerHTML)&&void 0!==n?n:"";t.innerHTML=e.gptAnswer.response,t.style.whiteSpace="pre-wrap",t.addEventListener("click",(function(){const n=t.innerHTML===e.gptAnswer.response;t.style.whiteSpace=n?"initial":"pre-wrap",t.innerHTML=n?o:e.gptAnswer.response}))}({gptAnswer:s,questionElement:e.questionElement,removeListener:e.removeListener});break;case"autocomplete":!function(e){e.config.infinite||e.removeListener();const n=[p,w,v,y,h,g];for(const t of n)if(t(e.config,e.inputList,e.gptAnswer))return;d(e.config,e.gptAnswer)}({config:e.config,gptAnswer:s,inputList:o,questionElement:e.questionElement,removeListener:e.removeListener})}}))}const A=[],S=[];function T(e){const n=S.findIndex((n=>n.element===e));if(-1!==n){const e=S.splice(n,1)[0];e.element.removeEventListener("click",e.fn)}}function L(n){if(S.length>0){for(const e of S)n.cursor&&(e.element.style.cursor="initial"),e.element.removeEventListener("click",e.fn);return n.title&&e("Removed"),void(S.length=0)}const t=["checkbox","radio","text","number"].map((e=>`input[type="${e}"]`)).join(",")+", textarea, select, [contenteditable]",o=document.querySelectorAll(".formulation");for(const e of o){const o=e.querySelector(".qtext");if(null===o)continue;n.cursor&&(o.style.cursor="pointer");const r=E.bind(null,{config:n,questionElement:o,form:e,inputQuery:t,removeListener:()=>T(o)});S.push({element:o,fn:r}),o.addEventListener("click",r)}n.title&&e("Injected")}chrome.storage.sync.get(["moodleGPT"]).then((function(e){const n=e.moodleGPT;if(!n)throw new Error("Please configure MoodleGPT into the extension");n.code?function(e){document.body.addEventListener("keydown",(function(n){A.push(n.key),A.length>e.code.length&&A.shift(),A.join("")===e.code&&(A.length=0,L(e))}))}(n):L(n)}))})); +!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";function e(e){const n=document.title;document.title=e,setTimeout((()=>document.title=n),3e3)}function n(e,n,t,o){return new(t||(t=Promise))((function(r,i){function s(e){try{c(o.next(e))}catch(e){i(e)}}function l(e){try{c(o.throw(e))}catch(e){i(e)}}function c(e){var n;e.done?r(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(s,l)}c((o=o.apply(e,n||[])).next())}))}function t(e,n){const t=e.length>n.length?e.length:n.length;return 0===t?1:(t-function(e,n){if(0===e.length)return n.length;if(0===n.length)return e.length;const t=[],o=e.replace(/\s+/,""),r=n.replace(/\s+/,"");for(let e=0;e<=o.length;++e){t.push([e]);for(let n=1;n<=r.length;++n)t[e][n]=0===e?n:Math.min(t[e-1][n]+1,t[e][n-1]+1,t[e-1][n-1]+(o[e-1]===r[n-1]?0:1))}return t[o.length][r.length]}(e,n))/t}function o(e,n){let o={element:null,similarity:0,value:null};for(const r of n){const n=t(r.value,e);if(1===n)return{element:r.element,value:r.value,similarity:n};n>o.similarity&&(o={element:r.element,value:r.value,similarity:n})}return o}"function"==typeof SuppressedError&&SuppressedError;class r{static question(e){console.log("%c[QUESTION]: %s","color: cyan",e)}static bestAnswer(e,n){console.log("%c[BEST ANSWER]: %s","color: green",`"${e}" with a similarity of ${function(e){return Math.round(100*e*100)/100+"%"}(n)}`)}static array(e){console.log("[CORRECTS] ",e)}static response(e){console.log("Original:\n"+e.response),console.log("Normalized:\n"+e.normalizedResponse)}}function i(e,n=!0){n&&(e=e.toLowerCase());return e.replace(/\n+/gi,"\n").replace(/(\n\s*\n)+/g,"\n").replace(/[ \t]+/gi," ").trim().replace(/^[a-z\d]\.\s/gi,"").replace(/\n[a-z\d]\.\s/gi,"\n")}var s,l;!function(e){e.SYSTEM="system",e.USER="user",e.ASSISTANT="assistant"}(s||(s={})),function(e){e.TEXT="text",e.IMAGE="image_url"}(l||(l={}));const c="\nAct as a quiz solver for the best notation with the following rules:\n- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation. \n- But for the calculation provide this format 'result: '\n- For 'put in order' questions, maintain the answer in the order as presented in the question but assocy the correct order to it by usin this format ':\n:', ignore other rules.\n- Always reply in the format: '\n\n...'.\n- Retain only the correct answer(s).\n- Maintain the same order for the answers as in the text.\n- Retain all text from the answer with its description, content or definition.\n- Only provide answers that exactly match the given answer in the text.\n- The question always has the correct answer(s), so you should always provide an answer.\n- Always respond in the same language as the user's question.\n".trim(),a={role:s.SYSTEM,content:c};function u(e,t,o){return n(this,void 0,void 0,(function*(){const n=t.querySelectorAll("img");if(!e.includeImages||!function(e){const n=e.match(/gpt-(\d+)/);return!!(null==n?void 0:n[1])&&Number(n[1])>=4}(e.model)||0===n.length)return o;const r=[],i=Array.from(n).map((e=>function(e,n=.75){return new Promise(((t,o)=>{const r=document.createElement("canvas"),i=r.getContext("2d");if(!i)return o("Can't get the canvas context, ensure your navigator support canvas"),void r.remove();const s=new Image;s.crossOrigin="Anonymous",s.onload=()=>{r.width=s.width,r.height=s.height,i.drawImage(s,0,0);const e=r.toDataURL("image/png",n);t(e),r.remove()},s.onerror=e=>{o(e),r.remove()},s.src=e.src}))}(e))),s=yield Promise.allSettled(i);for(const n of s)"fulfilled"===n.status?r.push({type:l.IMAGE,image_url:{url:n.value}}):e.logs&&console.error(n.reason);return r.push({type:l.TEXT,text:o}),r}))}function d(e,t,o){return n(this,void 0,void 0,(function*(){const n=yield u(e,t,o),r={role:s.USER,content:n};if(!e.history)return{messages:[a,r]};let i;const l=JSON.parse(null!==(c=sessionStorage.moodleGPTHistory)&&void 0!==c?c:"null");var c;const d=function(){var e,n;const t=new URLSearchParams(document.location.search);return{host:document.location.host,cmid:null!==(e=t.get("cmid"))&&void 0!==e?e:"",attempt:null!==(n=t.get("attempt"))&&void 0!==n?n:"",history:[]}}();return i=null!==l&&function(e,n){const t=["host","cmid","attempt"];for(const o of t)if(e[o]!==n[o])return!1;return!0}(l,d)?l:d,{messages:[a,...i.history,r],saveResponse(n){e.history&&(i.history.push(r),i.history.push({role:s.ASSISTANT,content:n}),sessionStorage.moodleGPTHistory=JSON.stringify(i))}}}))}function f(e){const n=[],t=Array.from(e.querySelectorAll("tr")),o=[];t.map((e=>{const t=Array.from(e.querySelectorAll("td, th")).map(((e,n)=>{var t;const r=null===(t=e.textContent)||void 0===t?void 0:t.trim();return o[n]=Math.max(o[n]||0,(null==r?void 0:r.length)||0),null!=r?r:""}));n.push(t)}));const r=n[0].length,i=o.reduce(((e,n)=>e+n),0)+3*(r-1),s="\n"+Array(i).fill("-").join("")+"\n",l=n.map((e=>e.map(((e,n)=>e.padEnd(o[n]," "))).join(" | ")));return l.shift()+s+l.join("\n")}function m(e){let n=function(e){function n(e){const n=window.getComputedStyle(e);return"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity}return function e(t){let o="";for(const r of t.childNodes)r.nodeType===Node.TEXT_NODE?n(r.parentNode)&&(o+=r.textContent):r.nodeType===Node.ELEMENT_NODE&&(o+=e(r));return o}(e)}(e);const t=e.querySelectorAll(".qtext table");for(const e of t)n=n.replace(e.innerText,"\n"+f(e)+"\n");return i(n,!1)}function p(n,t){n.title&&e("Copied to clipboard"),navigator.clipboard.writeText(t.response)}function v(e,n,t){const o=n[0];if(1!==n.length||"string"!=typeof o.getAttribute("contenteditable"))return!1;if(e.typing){let e=0;const n=function(r){if(r.preventDefault(),"Backspace"===r.key||e>t.response.length)return void o.removeEventListener("keydown",n);o.textContent=t.response.slice(0,++e),o.focus();const i=document.createRange();i.selectNodeContents(o),i.collapse(!1);const s=window.getSelection();null!==s&&(s.removeAllRanges(),s.addRange(i))};o.addEventListener("keydown",n)}else o.textContent=t.response;return!0}function h(e,n,t){var o,r;const i=n[0];if(1!==n.length||"number"!==i.type)return!1;const s=null===(r=null===(o=t.normalizedResponse.match(/\d+([,.]\d+)?/gi))||void 0===o?void 0:o[0])||void 0===r?void 0:r.replace(",",".");if(void 0===s)return!1;if(e.typing){let e=0;const n=function(t){t.preventDefault(),"Backspace"===t.key||e>s.length?i.removeEventListener("keydown",n):("."===s.slice(e,e+1)&&++e,i.value=s.slice(0,++e))};i.addEventListener("keydown",n)}else i.value=s;return!0}function g(e,n,t){const s=null==n?void 0:n[0];if(!s||"radio"!==s.type)return!1;const l=Array.from(n).map((e=>{var n,t;return{element:e,value:i(null!==(t=null===(n=null==e?void 0:e.parentElement)||void 0===n?void 0:n.textContent)&&void 0!==t?t:"")}})).filter((e=>""!==e.value)),c=o(t.normalizedResponse,l);e.logs&&c.value&&r.bestAnswer(c.value,c.similarity);const a=c.element;return e.mouseover?a.addEventListener("mouseover",(()=>a.click()),{once:!0}):a.click(),!0}function y(e,n,t){const s=null==n?void 0:n[0];if(!s||"checkbox"!==s.type)return!1;const l=t.normalizedResponse.split("\n"),c=Array.from(n).map((e=>{var n,t;return{element:e,value:i(null!==(t=null===(n=null==e?void 0:e.parentElement)||void 0===n?void 0:n.textContent)&&void 0!==t?t:"")}})).filter((e=>""!==e.value)),a=new Set;for(const n of l){const t=o(n,c);e.logs&&t.value&&r.bestAnswer(t.value,t.similarity),a.add(t.element)}for(const n of c.map((e=>e.element))){const t=n.checked&&!a.has(n)||!n.checked&&a.has(n),o=()=>t&&n.click();e.mouseover?n.addEventListener("mouseover",o,{once:!0}):o()}return!0}function w(e,n,t){if(0===n.length||"SELECT"!==n[0].tagName)return!1;const s=t.normalizedResponse.split("\n");e.logs&&r.array(s);for(let t=0;t{var n;return{element:e,value:i(null!==(n=e.textContent)&&void 0!==n?n:"")}})).filter((e=>""!==e.value)),a=o(s[t],c);e.logs&&a.value&&r.bestAnswer(a.value,a.similarity);const u=a.element,d=u.closest("select");null!==d&&(e.mouseover?d.addEventListener("click",(()=>u.selected=!0),{once:!0}):u.selected=!0)}return!0}function E(e,n,t){const o=n[0];if(1!==n.length||"TEXTAREA"!==o.tagName&&"text"!==o.type)return!1;if(e.typing){let e=0;const n=function(r){r.preventDefault(),"Backspace"===r.key||e>t.response.length?o.removeEventListener("keydown",n):o.value=t.response.slice(0,++e)};o.addEventListener("keydown",n)}else o.value=t.response;return!0}function A(e){return n(this,void 0,void 0,(function*(){e.config.cursor&&(e.questionElement.style.cursor="wait");const t=m(e.form),o=e.form.querySelectorAll(e.inputQuery),s=yield function(e,t,o){return n(this,void 0,void 0,(function*(){const n=new AbortController,r=setTimeout((()=>n.abort()),2e4),s=yield d(e,t,o),l=yield fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},signal:e.timeout?n.signal:null,body:JSON.stringify({model:e.model,messages:s.messages,temperature:.1,top_p:.6,presence_penalty:0,max_tokens:2e3})});clearTimeout(r);const c=(yield l.json()).choices[0].message.content;return"function"==typeof s.saveResponse&&s.saveResponse(c),{question:o,response:c,normalizedResponse:i(c)}}))}(e.config,e.questionElement,t).catch((e=>({error:e}))),l="object"==typeof s&&"error"in s;if(e.config.cursor&&(e.questionElement.style.cursor=e.config.infinite||l?"pointer":"initial"),l)console.error(s.error);else switch(e.config.logs&&(r.question(t),r.response(s)),e.config.mode){case"clipboard":!function(e){e.config.infinite||e.removeListener(),p(e.config,e.gptAnswer)}({config:e.config,questionElement:e.questionElement,gptAnswer:s,removeListener:e.removeListener});break;case"question-to-answer":!function(e){var n;const t=e.questionElement;e.removeListener();const o=null!==(n=t.innerHTML)&&void 0!==n?n:"";t.innerHTML=e.gptAnswer.response,t.style.whiteSpace="pre-wrap",t.addEventListener("click",(function(){const n=t.innerHTML===e.gptAnswer.response;t.style.whiteSpace=n?"initial":"pre-wrap",t.innerHTML=n?o:e.gptAnswer.response}))}({gptAnswer:s,questionElement:e.questionElement,removeListener:e.removeListener});break;case"autocomplete":!function(e){e.config.infinite||e.removeListener();const n=[v,E,h,w,g,y];for(const t of n)if(t(e.config,e.inputList,e.gptAnswer))return;p(e.config,e.gptAnswer)}({config:e.config,gptAnswer:s,inputList:o,questionElement:e.questionElement,removeListener:e.removeListener})}}))}const S=[],T=[];function L(e){const n=T.findIndex((n=>n.element===e));if(-1!==n){const e=T.splice(n,1)[0];e.element.removeEventListener("click",e.fn)}}function q(n){if(T.length>0){for(const e of T)n.cursor&&(e.element.style.cursor="initial"),e.element.removeEventListener("click",e.fn);return n.title&&e("Removed"),void(T.length=0)}const t=["checkbox","radio","text","number"].map((e=>`input[type="${e}"]`)).join(",")+", textarea, select, [contenteditable]",o=document.querySelectorAll(".formulation");for(const e of o){const o=e.querySelector(".qtext");if(null===o)continue;n.cursor&&(o.style.cursor="pointer");const r=A.bind(null,{config:n,questionElement:o,form:e,inputQuery:t,removeListener:()=>L(o)});T.push({element:o,fn:r}),o.addEventListener("click",r)}n.title&&e("Injected")}chrome.storage.sync.get(["moodleGPT"]).then((function(e){const n=e.moodleGPT;if(!n)throw new Error("Please configure MoodleGPT into the extension");n.code?function(e){document.body.addEventListener("keydown",(function(n){S.push(n.key),S.length>e.code.length&&S.shift(),S.join("")===e.code&&(S.length=0,q(e))}))}(n):q(n)}))})); //# sourceMappingURL=MoodleGPT.js.map diff --git a/extension/MoodleGPT.js.map b/extension/MoodleGPT.js.map index 2ec1c89..e9c93d3 100644 --- a/extension/MoodleGPT.js.map +++ b/extension/MoodleGPT.js.map @@ -1 +1 @@ -{"version":3,"file":"MoodleGPT.js","sources":["../src/utils/title-indications.ts","../node_modules/tslib/tslib.es6.js","../src/utils/pick-best-response.ts","../src/utils/logs.ts","../src/utils/normalize-text.ts","../src/types/message.ts","../src/core/get-content-with-history.ts","../src/utils/version-support-images.ts","../src/utils/image-to-base64.ts","../src/utils/html-table-to-string.ts","../src/core/questions/clipboard.ts","../src/core/questions/contenteditable.ts","../src/core/questions/number.ts","../src/core/questions/radio.ts","../src/core/questions/checkbox.ts","../src/core/questions/select.ts","../src/core/questions/textbox.ts","../src/core/reply.ts","../src/core/create-question.ts","../src/core/get-response.ts","../src/core/modes/clipboard.ts","../src/core/modes/question-to-answer.ts","../src/core/modes/autocomplete.ts","../src/core/code-listener.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Show some informations into the document title and remove it after 3000ms\r\n * @param text\r\n */\r\nfunction titleIndications(text: string) {\r\n const backTitle = document.title;\r\n document.title = text;\r\n setTimeout(() => (document.title = backTitle), 3000);\r\n}\r\n\r\nexport default titleIndications;\r\n","/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n function next() {\r\n while (env.stack.length) {\r\n var rec = env.stack.pop();\r\n try {\r\n var result = rec.dispose && rec.dispose.call(rec.value);\r\n if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n};\r\n","type BestResponse = {\r\n similarity: number;\r\n value: string | null;\r\n element: HTMLElement | null;\r\n};\r\n\r\ntype ResponsesBySimilarity = {\r\n similarity: number;\r\n value: string;\r\n element: HTMLElement;\r\n};\r\n\r\n/**\r\n * Calculate the levenshtein distance between two sentence\r\n * @param str1\r\n * @param str2\r\n * @returns\r\n */\r\nfunction levenshteinDistance(str1: string, str2: string) {\r\n if (str1.length === 0) return str2.length;\r\n if (str2.length === 0) return str1.length;\r\n\r\n const matrix: number[][] = [];\r\n const str1WithoutSpaces = str1.replace(/\\s+/, '');\r\n const str2WithoutSpaces = str2.replace(/\\s+/, '');\r\n\r\n for (let i = 0; i <= str1WithoutSpaces.length; ++i) {\r\n matrix.push([i]);\r\n for (let j = 1; j <= str2WithoutSpaces.length; ++j) {\r\n matrix[i][j] =\r\n i === 0\r\n ? j\r\n : Math.min(\r\n matrix[i - 1][j] + 1,\r\n matrix[i][j - 1] + 1,\r\n matrix[i - 1][j - 1] + (str1WithoutSpaces[i - 1] === str2WithoutSpaces[j - 1] ? 0 : 1)\r\n );\r\n }\r\n }\r\n\r\n return matrix[str1WithoutSpaces.length][str2WithoutSpaces.length];\r\n}\r\n\r\n/**\r\n * Calculate the similarity between two sentences from 0 to 1 (best)\r\n * @param str1\r\n * @param str2\r\n * @returns\r\n */\r\nfunction sentenceSimilarity(str1: string, str2: string) {\r\n const longerLength = str1.length > str2.length ? str1.length : str2.length;\r\n if (longerLength === 0) return 1;\r\n return (longerLength - levenshteinDistance(str1, str2)) / longerLength;\r\n}\r\n\r\n/**\r\n * Pick the best sentence that correspond to the answer\r\n * @param arr\r\n * @param answer\r\n * @returns\r\n */\r\nexport function pickBestReponse(\r\n answer: string,\r\n arr: { element: HTMLElement; value: string }[]\r\n): BestResponse {\r\n let bestResponse: BestResponse = {\r\n element: null,\r\n similarity: 0,\r\n value: null\r\n };\r\n for (const obj of arr) {\r\n const similarity = sentenceSimilarity(obj.value, answer);\r\n if (similarity === 1) {\r\n return { element: obj.element, value: obj.value, similarity };\r\n }\r\n if (similarity > bestResponse.similarity) {\r\n bestResponse = { element: obj.element, value: obj.value, similarity };\r\n }\r\n }\r\n return bestResponse;\r\n}\r\n\r\n/**\r\n * Return the sentences sorted by score with a score superior or equal to what is asked\r\n * @param answer\r\n * @param arr\r\n * @param score\r\n * @returns\r\n */\r\nexport function pickResponsesWithSimilarityGreaterThan(\r\n answer: string,\r\n arr: { element: HTMLElement; value: string }[],\r\n score: number\r\n): ResponsesBySimilarity[] {\r\n const responses: ResponsesBySimilarity[] = [];\r\n for (const obj of arr) {\r\n const similarity = sentenceSimilarity(obj.value, answer);\r\n if (similarity >= score)\r\n responses.push({\r\n similarity,\r\n value: obj.value,\r\n element: obj.element\r\n });\r\n }\r\n return responses.sort((a, b) => a.similarity - b.similarity);\r\n}\r\n\r\n/**\r\n * Convert a number to a readable string pourcentage\r\n * @param similarity\r\n */\r\nexport function toPourcentage(similarity: number): string {\r\n return Math.round(similarity * 100 * 100) / 100 + '%';\r\n}\r\n","import GPTAnswer from '@typing/gpt-answer';\r\nimport { toPourcentage } from './pick-best-response';\r\n\r\nclass Logs {\r\n static question(text: string) {\r\n const css = 'color: cyan';\r\n console.log('%c[QUESTION]: %s', css, text);\r\n }\r\n\r\n static bestAnswer(answer: string, similarity: number) {\r\n const css = 'color: green';\r\n console.log(\r\n '%c[BEST ANSWER]: %s',\r\n css,\r\n `\"${answer}\" with a similarity of ${toPourcentage(similarity)}`\r\n );\r\n }\r\n\r\n static array(arr: unknown[]) {\r\n console.log('[CORRECTS] ', arr);\r\n }\r\n\r\n static response(gptAnswer: GPTAnswer) {\r\n console.log('Original:\\n' + gptAnswer.response);\r\n console.log('Normalized:\\n' + gptAnswer.normalizedResponse);\r\n }\r\n}\r\n\r\nexport default Logs;\r\n","/**\r\n * Normlize text\r\n * @param text\r\n */\r\nfunction normalizeText(text: string, toLowerCase: boolean = true) {\r\n if (toLowerCase) text = text.toLowerCase();\r\n\r\n const normalizedText = text\r\n .replace(/\\n+/gi, '\\n') //remove duplicate new lines\r\n .replace(/(\\n\\s*\\n)+/g, '\\n') //remove useless white space from textcontent\r\n .replace(/[ \\t]+/gi, ' ') //replace multiples space or tabs by a space\r\n .trim()\r\n // We remove the following content because sometimes ChatGPT will reply: \"answer d\"\r\n .replace(/^[a-z\\d]\\.\\s/gi, '') //a. text, b. text, c. text, 1. text, 2. text, 3.text\r\n .replace(/\\n[a-z\\d]\\.\\s/gi, '\\n'); //same but with new line\r\n\r\n return normalizedText;\r\n}\r\n\r\nexport default normalizeText;\r\n","export enum ROLE {\r\n SYSTEM = 'system',\r\n USER = 'user',\r\n ASSISTANT = 'assistant'\r\n}\r\n\r\nexport enum CONTENT_TYPE {\r\n TEXT = 'text',\r\n IMAGE = 'image_url'\r\n}\r\n\r\nexport type MessageContent =\r\n | string\r\n | Array<\r\n | {\r\n type: CONTENT_TYPE.TEXT;\r\n text: string;\r\n }\r\n | {\r\n type: CONTENT_TYPE.IMAGE;\r\n image_url: { url: string };\r\n }\r\n >;\r\n\r\nexport type Message = {\r\n role: ROLE;\r\n content: MessageContent;\r\n};\r\n","import type Config from '@typing/config';\r\nimport { ROLE, CONTENT_TYPE, type MessageContent, type Message } from '@typing/message';\r\nimport imageToBase64 from '@utils/image-to-base64';\r\nimport isGPTModelGreaterOrEqualTo4 from '@utils/version-support-images';\r\n\r\n// The attempt and the cmid allow us to identify a quiz\r\ntype History = {\r\n host: string;\r\n cmid: string; // The id of the quiz\r\n attempt: string; // The attempt of the current quiz\r\n history: { role: ROLE; content: MessageContent }[];\r\n};\r\n\r\nconst INSTRUCTION: string = `\r\nAct as a quiz solver for the best notation with the following rules:\r\n- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation. \r\n- But for the calculation provide this format 'result: '\r\n- For 'put in order' questions, maintain the answer in the order as presented in the question but assocy the correct order to it by usin this format ':\\n:', ignore other rules.\r\n- Always reply in the format: '\\n\\n...'.\r\n- Retain only the correct answer(s).\r\n- Maintain the same order for the answers as in the text.\r\n- Retain all text from the answer with its description, content or definition.\r\n- Only provide answers that exactly match the given answer in the text.\r\n- The question always has the correct answer(s), so you should always provide an answer.\r\n- Always respond in the same language as the user's question.\r\n`.trim();\r\n\r\nconst SYSTEM_INSTRUCTION_MESSAGE = {\r\n role: ROLE.SYSTEM,\r\n content: INSTRUCTION\r\n} as const satisfies Message;\r\n\r\n/**\r\n * Get the content to send to ChatGPT API (it allows to includes images if supported)\r\n * @param config\r\n */\r\nasync function getContent(\r\n config: Config,\r\n questionElement: HTMLElement,\r\n question: string\r\n): Promise {\r\n const imagesElements = questionElement.querySelectorAll('img');\r\n\r\n if (\r\n !config.includeImages ||\r\n !isGPTModelGreaterOrEqualTo4(config.model) ||\r\n imagesElements.length === 0\r\n ) {\r\n return question;\r\n }\r\n\r\n const contentWithImages: MessageContent = [];\r\n\r\n const base64Images = Array.from(imagesElements).map(imgEl => imageToBase64(imgEl));\r\n const base64ImagesResolved = await Promise.allSettled(base64Images);\r\n\r\n for (const result of base64ImagesResolved) {\r\n if (result.status === 'fulfilled') {\r\n contentWithImages.push({\r\n type: CONTENT_TYPE.IMAGE,\r\n image_url: { url: result.value }\r\n });\r\n } else if (config.logs) {\r\n console.error(result.reason);\r\n }\r\n }\r\n\r\n contentWithImages.push({\r\n type: CONTENT_TYPE.TEXT,\r\n text: question\r\n });\r\n\r\n return contentWithImages;\r\n}\r\n\r\n/**\r\n * Create a new history object from the current page\r\n * @returns\r\n */\r\nfunction createNewHistory(): History {\r\n const urlParams = new URLSearchParams(document.location.search);\r\n\r\n return {\r\n host: document.location.host,\r\n cmid: urlParams.get('cmid') ?? '',\r\n attempt: urlParams.get('attempt') ?? '',\r\n history: []\r\n };\r\n}\r\n\r\n/**\r\n * Load the past history from the session storage otherwise return the default history object\r\n * @returns\r\n */\r\nfunction loadPastHistory(): History | null {\r\n return JSON.parse(sessionStorage.moodleGPTHistory ?? 'null');\r\n}\r\n\r\n/**\r\n * Check if two history are from the same origin\r\n * @param a\r\n * @param b\r\n * @returns\r\n */\r\nfunction areHistoryFromSameQuiz(a: History, b: History): boolean {\r\n const KEYS_TO_COMPARE: (keyof History)[] = ['host', 'cmid', 'attempt'];\r\n\r\n for (const key of KEYS_TO_COMPARE) {\r\n if (a[key] !== b[key]) return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Return the content to send to chatgpt api with history if needed\r\n * @param config\r\n * @param questionElement\r\n * @param question\r\n * @returns\r\n */\r\nasync function getContentWithHistory(\r\n config: Config,\r\n questionElement: HTMLElement,\r\n question: string\r\n): Promise<{\r\n messages: [typeof SYSTEM_INSTRUCTION_MESSAGE, ...Message[]];\r\n saveResponse?: (response: string) => void;\r\n}> {\r\n const content = await getContent(config, questionElement, question);\r\n const message = { role: ROLE.USER, content };\r\n\r\n if (!config.history) return { messages: [SYSTEM_INSTRUCTION_MESSAGE, message] };\r\n\r\n let history: History;\r\n\r\n const pastHistory: History | null = loadPastHistory();\r\n const newHistory: History = createNewHistory();\r\n\r\n if (pastHistory === null || !areHistoryFromSameQuiz(pastHistory, newHistory)) {\r\n history = newHistory;\r\n } else {\r\n history = pastHistory;\r\n }\r\n\r\n return {\r\n messages: [SYSTEM_INSTRUCTION_MESSAGE, ...history.history, message],\r\n saveResponse(response: string) {\r\n // Register the conversation\r\n if (config.history) {\r\n history.history.push(message);\r\n history.history.push({ role: ROLE.ASSISTANT, content: response });\r\n sessionStorage.moodleGPTHistory = JSON.stringify(history);\r\n }\r\n }\r\n };\r\n}\r\n\r\nexport default getContentWithHistory;\r\n","/**\r\n * Check if the current ChatGPT version is greater or equal to 4\r\n * @param version\r\n * @returns\r\n */\r\nfunction isGPTModelGreaterOrEqualTo4(version: string): boolean {\r\n const versionNumber = version.match(/gpt-(\\d+)/);\r\n if (!versionNumber?.[1]) {\r\n return false;\r\n }\r\n return Number(versionNumber[1]) >= 4;\r\n}\r\n\r\nexport default isGPTModelGreaterOrEqualTo4;\r\n","/**\r\n * Convert an image html element into a base64 image string\r\n * @param imageElement\r\n * @param quality (default: 0.75 -> 75%)\r\n * @returns\r\n */\r\nfunction imageToBase64(imageElement: HTMLImageElement, quality = 0.75): Promise {\r\n return new Promise((resolve, reject) => {\r\n const canvas = document.createElement('canvas');\r\n const ctx = canvas.getContext('2d');\r\n\r\n if (!ctx) {\r\n reject(\"Can't get the canvas context, ensure your navigator support canvas\");\r\n canvas.remove();\r\n return;\r\n }\r\n\r\n const img = new Image();\r\n img.crossOrigin = 'Anonymous';\r\n img.onload = () => {\r\n canvas.width = img.width;\r\n canvas.height = img.height;\r\n ctx.drawImage(img, 0, 0);\r\n\r\n const base64 = canvas.toDataURL('image/png', quality);\r\n resolve(base64);\r\n\r\n canvas.remove();\r\n };\r\n\r\n img.onerror = err => {\r\n reject(err);\r\n canvas.remove();\r\n };\r\n\r\n img.src = imageElement.src;\r\n });\r\n}\r\n\r\nexport default imageToBase64;\r\n","/**\r\n * Convert table to representating string table\r\n * @param table\r\n * @returns\r\n */\r\nfunction htmlTableToString(table: HTMLTableElement) {\r\n const tab: string[][] = [];\r\n const lines = Array.from(table.querySelectorAll('tr'));\r\n const maxColumnsLength: number[] = [];\r\n\r\n lines.map(line => {\r\n const cells = Array.from(line.querySelectorAll('td, th'));\r\n const cellsContent = cells.map((cell, index) => {\r\n const content = cell.textContent?.trim();\r\n maxColumnsLength[index] = Math.max(maxColumnsLength[index] || 0, content?.length || 0);\r\n return content ?? '';\r\n });\r\n tab.push(cellsContent);\r\n });\r\n\r\n const jointure = ' | ';\r\n const headerLineLength = tab[0].length;\r\n const lineSeparationSize =\r\n maxColumnsLength.reduce((a, b) => a + b, 0) + (headerLineLength - 1) * jointure.length;\r\n const lineSeparation = '\\n' + Array(lineSeparationSize).fill('-').join('') + '\\n';\r\n\r\n const mappedTab = tab.map(line => {\r\n const mappedLine = line.map((content, index) =>\r\n content.padEnd(\r\n maxColumnsLength[index],\r\n '\\u00A0' // For no matching with \\s\r\n )\r\n );\r\n return mappedLine.join(jointure);\r\n });\r\n\r\n const head = mappedTab.shift();\r\n\r\n return head + lineSeparation + mappedTab.join('\\n');\r\n}\r\n\r\nexport default htmlTableToString;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport titleIndications from '@utils/title-indications';\r\n\r\n/**\r\n * Copy the response in the clipboard if we can automaticaly fill the question\r\n * @param config\r\n * @param gptAnswer\r\n */\r\nfunction handleClipboard(config: Config, gptAnswer: GPTAnswer) {\r\n if (config.title) titleIndications('Copied to clipboard');\r\n navigator.clipboard.writeText(gptAnswer.response);\r\n}\r\n\r\nexport default handleClipboard;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\n\r\n/**\r\n * Hanlde contenteditable elements\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleContentEditable(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const input = inputList[0];\r\n\r\n if (\r\n inputList.length !== 1 || // for now we don't handle many input for editable textcontent\r\n input.getAttribute('contenteditable') !== 'true'\r\n ) {\r\n return false;\r\n }\r\n\r\n if (config.typing) {\r\n let index = 0;\r\n\r\n const eventHandler = function (event: KeyboardEvent) {\r\n event.preventDefault();\r\n\r\n if (event.key === 'Backspace' || index > gptAnswer.response.length) {\r\n input.removeEventListener('keydown', eventHandler);\r\n return;\r\n }\r\n\r\n input.textContent = gptAnswer.response.slice(0, ++index);\r\n\r\n // Put the cursor at the end of the typed text\r\n input.focus();\r\n const range = document.createRange();\r\n range.selectNodeContents(input);\r\n range.collapse(false);\r\n const selection = window.getSelection();\r\n if (selection !== null) {\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n }\r\n };\r\n\r\n input.addEventListener('keydown', eventHandler);\r\n } else {\r\n input.textContent = gptAnswer.response;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleContentEditable;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\n\r\n/**\r\n * Handle number input\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleNumber(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;\r\n\r\n if (\r\n inputList.length !== 1 || // for now we don't handle many input number\r\n input.type !== 'number'\r\n ) {\r\n return false;\r\n }\r\n\r\n const number = gptAnswer.normalizedResponse.match(/\\d+([,.]\\d+)?/gi)?.[0]?.replace(',', '.');\r\n\r\n if (number === undefined) return false;\r\n\r\n if (config.typing) {\r\n let index = 0;\r\n\r\n const eventHanlder = function (event: Event) {\r\n event.preventDefault();\r\n if ((event).key === 'Backspace' || index > number.length) {\r\n input.removeEventListener('keydown', eventHanlder);\r\n return;\r\n }\r\n\r\n if (number.slice(index, index + 1) === '.') ++index;\r\n\r\n input.value = number.slice(0, ++index);\r\n };\r\n\r\n input.addEventListener('keydown', eventHanlder);\r\n } else {\r\n input.value = number;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleNumber;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport Logs from '@utils/logs';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport { pickBestReponse } from '@utils/pick-best-response';\r\n\r\n/**\r\n * Handle input radio elements\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n */\r\nfunction handleRadio(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const firstInput = inputList?.[0] as HTMLInputElement;\r\n\r\n // Handle the case the input is not a radio\r\n if (!firstInput || firstInput.type !== 'radio') {\r\n return false;\r\n }\r\n\r\n const possibleAnswers = Array.from(inputList)\r\n .map(inp => ({\r\n element: inp,\r\n value: normalizeText(inp?.parentElement?.textContent ?? '')\r\n }))\r\n .filter(obj => obj.value !== '');\r\n\r\n const bestAnswer = pickBestReponse(gptAnswer.normalizedResponse, possibleAnswers);\r\n\r\n if (config.logs && bestAnswer.value) {\r\n Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);\r\n }\r\n\r\n const correctInput = bestAnswer.element as HTMLInputElement;\r\n if (config.mouseover) {\r\n correctInput.addEventListener('mouseover', () => correctInput.click(), {\r\n once: true\r\n });\r\n } else {\r\n correctInput.click();\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleRadio;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport Logs from '@utils/logs';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport { pickBestReponse } from '@utils/pick-best-response';\r\n\r\n/**\r\n * Handle input checkbox elements\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n */\r\nfunction handleCheckbox(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const firstInput = inputList?.[0] as HTMLInputElement;\r\n\r\n // Handle the case the input is not a checkbox\r\n if (!firstInput || firstInput.type !== 'checkbox') {\r\n return false;\r\n }\r\n\r\n const corrects = gptAnswer.normalizedResponse.split('\\n');\r\n\r\n const possibleAnswers = Array.from(inputList)\r\n .map(inp => ({\r\n element: inp,\r\n value: normalizeText(inp?.parentElement?.textContent ?? '')\r\n }))\r\n .filter(obj => obj.value !== '');\r\n\r\n for (const correct of corrects) {\r\n const bestAnswer = pickBestReponse(correct, possibleAnswers);\r\n\r\n if (config.logs && bestAnswer.value) {\r\n Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);\r\n }\r\n\r\n const correctInput = bestAnswer.element as HTMLInputElement;\r\n if (config.mouseover) {\r\n correctInput.addEventListener('mouseover', () => correctInput.click(), {\r\n once: true\r\n });\r\n } else {\r\n correctInput.click();\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleCheckbox;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport Logs from '@utils/logs';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport { pickBestReponse } from '@utils/pick-best-response';\r\n\r\n/**\r\n * Handle select elements (and put in order select)\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleSelect(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n if (inputList.length === 0 || inputList[0].tagName !== 'SELECT') return false;\r\n\r\n const corrects = gptAnswer.normalizedResponse.split('\\n');\r\n\r\n if (config.logs) Logs.array(corrects);\r\n\r\n for (let i = 0; i < inputList.length; ++i) {\r\n if (!corrects[i]) break;\r\n\r\n const options = inputList[i].querySelectorAll('option');\r\n\r\n const possibleAnswers = Array.from(options)\r\n .slice(1) // We remove the first option which correspond to \"Choose...\"\r\n .map(opt => ({\r\n element: opt,\r\n value: normalizeText(opt.textContent ?? '')\r\n }))\r\n .filter(obj => obj.value !== '');\r\n\r\n const bestAnswer = pickBestReponse(corrects[i], possibleAnswers);\r\n\r\n if (config.logs && bestAnswer.value) {\r\n Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);\r\n }\r\n\r\n const correctOption = bestAnswer.element as HTMLOptionElement;\r\n const currentSelect = correctOption.closest('select');\r\n\r\n if (currentSelect === null) continue;\r\n\r\n if (config.mouseover) {\r\n currentSelect.addEventListener('click', () => (correctOption.selected = true), {\r\n once: true\r\n });\r\n } else {\r\n correctOption.selected = true;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleSelect;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\n\r\n/**\r\n * Handle textbox\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleTextbox(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;\r\n\r\n if (\r\n inputList.length !== 1 || // for now we don't handle many input text\r\n (input.tagName !== 'TEXTAREA' && input.type !== 'text')\r\n ) {\r\n return false;\r\n }\r\n\r\n if (config.typing) {\r\n let index = 0;\r\n\r\n const eventHandler = function (event: Event) {\r\n event.preventDefault();\r\n\r\n if ((event).key === 'Backspace' || index > gptAnswer.response.length) {\r\n input.removeEventListener('keydown', eventHandler);\r\n return;\r\n }\r\n\r\n input.value = gptAnswer.response.slice(0, ++index);\r\n };\r\n\r\n input.addEventListener('keydown', eventHandler);\r\n } else {\r\n input.value = gptAnswer.response;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleTextbox;\r\n","import type Config from '@typing/config';\r\nimport Logs from '@utils/logs';\r\nimport getChatGPTResponse from './get-response';\r\nimport createAndNormalizeQuestion from './create-question';\r\nimport clipboardMode from './modes/clipboard';\r\nimport questionToAnswerMode from './modes/question-to-answer';\r\nimport autoCompleteMode from './modes/autocomplete';\r\n\r\ntype Props = {\r\n config: Config;\r\n questionElement: HTMLElement;\r\n form: HTMLElement;\r\n inputQuery: string;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Reply to the question\r\n * @param props\r\n * @returns\r\n */\r\nasync function reply(props: Props): Promise {\r\n if (props.config.cursor) props.questionElement.style.cursor = 'wait';\r\n\r\n const question = createAndNormalizeQuestion(props.form);\r\n const inputList: NodeListOf = props.form.querySelectorAll(props.inputQuery);\r\n\r\n const gptAnswer = await getChatGPTResponse(props.config, props.questionElement, question).catch(\r\n error => ({\r\n error\r\n })\r\n );\r\n\r\n const haveError = typeof gptAnswer === 'object' && 'error' in gptAnswer;\r\n\r\n if (props.config.cursor) {\r\n props.questionElement.style.cursor = props.config.infinite || haveError ? 'pointer' : 'initial';\r\n }\r\n\r\n if (haveError) {\r\n console.error(gptAnswer.error);\r\n return;\r\n }\r\n\r\n if (props.config.logs) {\r\n Logs.question(question);\r\n Logs.response(gptAnswer);\r\n }\r\n\r\n switch (props.config.mode) {\r\n case 'clipboard':\r\n clipboardMode({\r\n config: props.config,\r\n questionElement: props.questionElement,\r\n gptAnswer,\r\n removeListener: props.removeListener\r\n });\r\n break;\r\n case 'question-to-answer':\r\n questionToAnswerMode({\r\n gptAnswer,\r\n questionElement: props.questionElement,\r\n removeListener: props.removeListener\r\n });\r\n break;\r\n case 'autocomplete':\r\n autoCompleteMode({\r\n config: props.config,\r\n gptAnswer,\r\n inputList,\r\n questionElement: props.questionElement,\r\n removeListener: props.removeListener\r\n });\r\n break;\r\n }\r\n}\r\n\r\nexport default reply;\r\n","import normalizeText from '@utils/normalize-text';\r\nimport htmlTableToString from '@utils/html-table-to-string';\r\n\r\n/**\r\n * Normalize the question as text and add sub informations\r\n * @param langage\r\n * @param question\r\n * @returns\r\n */\r\nfunction createAndNormalizeQuestion(questionContainer: HTMLElement) {\r\n let question = questionContainer.innerText;\r\n\r\n // We remove unnecessary information\r\n const accesshideElements: NodeListOf =\r\n questionContainer.querySelectorAll('.accesshide');\r\n for (const useless of accesshideElements) {\r\n question = question.replace(useless.innerText, '');\r\n }\r\n\r\n // Make tables more readable for chat-gpt\r\n const tables: NodeListOf = questionContainer.querySelectorAll('.qtext table');\r\n for (const table of tables) {\r\n question = question.replace(table.innerText, '\\n' + htmlTableToString(table) + '\\n');\r\n }\r\n\r\n return normalizeText(question, false);\r\n}\r\n\r\nexport default createAndNormalizeQuestion;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport getContentWithHistory from './get-content-with-history';\r\n\r\n/**\r\n * Get the response from chatGPT api\r\n * @param config\r\n * @param question\r\n * @returns\r\n */\r\nasync function getChatGPTResponse(\r\n config: Config,\r\n questionElement: HTMLElement,\r\n question: string\r\n): Promise {\r\n const controller = new AbortController();\r\n const timeoutControler = setTimeout(() => controller.abort(), 20 * 1000);\r\n\r\n // Get the content to send to chatgpt\r\n // Including the instructions to the AI, the images as base64 if needed, the question and the past conversation if history is set to true\r\n const contentHandler = await getContentWithHistory(config, questionElement, question);\r\n\r\n const req = await fetch('https://api.openai.com/v1/chat/completions', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Authorization: `Bearer ${config.apiKey}`\r\n },\r\n signal: config.timeout ? controller.signal : null,\r\n body: JSON.stringify({\r\n model: config.model,\r\n messages: contentHandler.messages,\r\n\r\n temperature: 0.1, // Controls the randomness of the generated responses, with lower values producing more deterministic and predictable outputs. With set to 0.1 instead of 0 for more creativity.\r\n top_p: 0.6, // Determines the diversity of the generated responses\r\n presence_penalty: 0, // Encourages the model to introduce new concepts by penalizing words that have already appeared in the text.\r\n max_tokens: 2000 // Maximum length of the response\r\n })\r\n });\r\n\r\n clearTimeout(timeoutControler);\r\n\r\n const rep = await req.json();\r\n const response = rep.choices[0].message.content;\r\n\r\n // Save the response into the history\r\n if (typeof contentHandler.saveResponse === 'function') contentHandler.saveResponse(response);\r\n\r\n return {\r\n question,\r\n response,\r\n normalizedResponse: normalizeText(response)\r\n };\r\n}\r\n\r\nexport default getChatGPTResponse;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport handleClipboard from '@core/questions/clipboard';\r\n\r\ntype Props = {\r\n config: Config;\r\n questionElement: HTMLElement;\r\n gptAnswer: GPTAnswer;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Clipboard mode:\r\n * Simply copy the answer into the clipboard\r\n * @param props\r\n */\r\nfunction clipboardMode(props: Props) {\r\n if (!props.config.infinite) props.removeListener();\r\n handleClipboard(props.config, props.gptAnswer);\r\n}\r\n\r\nexport default clipboardMode;\r\n","import type GPTAnswer from '@typing/gpt-answer';\r\n\r\ntype Props = {\r\n questionElement: HTMLElement;\r\n gptAnswer: GPTAnswer;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Question to answer mode:\r\n * Simply turn the question into the answer by clicking on it\r\n * @param props\r\n */\r\nfunction questionToAnswerMode(props: Props) {\r\n const questionElement = props.questionElement;\r\n\r\n props.removeListener();\r\n\r\n const questionBackup = questionElement.innerHTML ?? '';\r\n questionElement.innerHTML = props.gptAnswer.response;\r\n questionElement.style.whiteSpace = 'pre-wrap';\r\n\r\n // To go back to the question / answer\r\n questionElement.addEventListener('click', function () {\r\n const contentIsResponse = questionElement.innerHTML === props.gptAnswer.response;\r\n\r\n questionElement.style.whiteSpace = contentIsResponse ? 'initial' : 'pre-wrap';\r\n questionElement.innerHTML = contentIsResponse ? questionBackup : props.gptAnswer.response;\r\n });\r\n}\r\n\r\nexport default questionToAnswerMode;\r\n","import type GPTAnswer from '@typing/gpt-answer';\r\nimport type Config from '@typing/config';\r\nimport handleClipboard from '@core/questions/clipboard';\r\nimport handleContentEditable from '@core/questions/contenteditable';\r\nimport handleNumber from '@core/questions/number';\r\nimport handleRadio from '@core/questions/radio';\r\nimport handleCheckbox from '@core/questions/checkbox';\r\nimport handleSelect from '@core/questions/select';\r\nimport handleTextbox from '@core/questions/textbox';\r\n\r\ntype Props = {\r\n config: Config;\r\n questionElement: HTMLElement;\r\n inputList: NodeListOf;\r\n gptAnswer: GPTAnswer;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Autocomplete mode:\r\n * Autocomplete the question by checking the good answer\r\n * @param props\r\n * @returns\r\n */\r\nfunction autoCompleteMode(props: Props) {\r\n if (!props.config.infinite) props.removeListener();\r\n\r\n const handlers = [\r\n handleContentEditable,\r\n handleTextbox,\r\n handleNumber,\r\n handleSelect,\r\n handleRadio,\r\n handleCheckbox\r\n ];\r\n\r\n for (const handler of handlers) {\r\n if (handler(props.config, props.inputList, props.gptAnswer)) return;\r\n }\r\n\r\n // In the case we can't auto complete the question\r\n handleClipboard(props.config, props.gptAnswer);\r\n}\r\n\r\nexport default autoCompleteMode;\r\n","import type Config from '@typing/config';\r\nimport titleIndications from '@utils/title-indications';\r\nimport reply from './reply';\r\n\r\ntype Listener = {\r\n element: HTMLElement;\r\n fn: (this: HTMLElement, ev: MouseEvent) => void;\r\n};\r\n\r\nconst pressedKeys: string[] = [];\r\nconst listeners: Listener[] = [];\r\n\r\n/**\r\n * Create a listener on the keyboard to inject the code\r\n * @param config\r\n */\r\nfunction codeListener(config: Config) {\r\n document.body.addEventListener('keydown', function (event) {\r\n pressedKeys.push(event.key);\r\n if (pressedKeys.length > config.code!.length) pressedKeys.shift();\r\n if (pressedKeys.join('') === config.code) {\r\n pressedKeys.length = 0;\r\n setUpMoodleGpt(config);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Remove the event listener on a specific question\r\n * @param element\r\n */\r\nfunction removeListener(element: HTMLElement) {\r\n const index = listeners.findIndex(listener => listener.element === element);\r\n if (index !== -1) {\r\n const listener = listeners.splice(index, 1)[0];\r\n listener.element.removeEventListener('click', listener.fn);\r\n }\r\n}\r\n\r\n/**\r\n * Setup moodleGPT into the page (remove/injection)\r\n * @param config\r\n * @returns\r\n */\r\nfunction setUpMoodleGpt(config: Config) {\r\n // Removing events if there are already declared\r\n if (listeners.length > 0) {\r\n for (const listener of listeners) {\r\n if (config.cursor) listener.element.style.cursor = 'initial';\r\n listener.element.removeEventListener('click', listener.fn);\r\n }\r\n if (config.title) titleIndications('Removed');\r\n listeners.length = 0;\r\n return;\r\n }\r\n\r\n // Query to find inputs and forms\r\n const inputTypeQuery = ['checkbox', 'radio', 'text', 'number']\r\n .map(e => `input[type=\"${e}\"]`)\r\n .join(',');\r\n const inputQuery = inputTypeQuery + ', textarea, select, [contenteditable]';\r\n const forms = document.querySelectorAll('.formulation');\r\n\r\n // For each form we inject a function on the queqtion\r\n for (const form of forms) {\r\n const questionElement: HTMLElement | null = form.querySelector('.qtext');\r\n\r\n if (questionElement === null) continue;\r\n\r\n if (config.cursor) questionElement.style.cursor = 'pointer';\r\n\r\n const injectionFunction = reply.bind(null, {\r\n config,\r\n questionElement,\r\n form: form as HTMLElement,\r\n inputQuery,\r\n removeListener: () => removeListener(questionElement)\r\n });\r\n\r\n listeners.push({ element: questionElement, fn: injectionFunction });\r\n questionElement.addEventListener('click', injectionFunction);\r\n }\r\n\r\n if (config.title) titleIndications('Injected');\r\n}\r\n\r\nexport { codeListener, removeListener, setUpMoodleGpt };\r\n","import type Config from '@typing/config';\r\nimport { codeListener, setUpMoodleGpt } from './core/code-listener';\r\n\r\nchrome.storage.sync.get(['moodleGPT']).then(function (storage) {\r\n const config: Config = storage.moodleGPT;\r\n\r\n if (!config) throw new Error('Please configure MoodleGPT into the extension');\r\n\r\n if (config.code) {\r\n codeListener(config);\r\n } else {\r\n setUpMoodleGpt(config);\r\n }\r\n});\r\n"],"names":["titleIndications","text","backTitle","document","title","setTimeout","__awaiter","thisArg","_arguments","P","generator","Promise","resolve","reject","fulfilled","value","step","next","e","rejected","result","done","then","apply","sentenceSimilarity","str1","str2","longerLength","length","matrix","str1WithoutSpaces","replace","str2WithoutSpaces","i","push","j","Math","min","levenshteinDistance","pickBestReponse","answer","arr","bestResponse","element","similarity","obj","SuppressedError","Logs","question","console","log","bestAnswer","round","toPourcentage","array","response","gptAnswer","normalizedResponse","normalizeText","toLowerCase","trim","ROLE","CONTENT_TYPE","INSTRUCTION","SYSTEM_INSTRUCTION_MESSAGE","role","SYSTEM","content","getContent","config","questionElement","imagesElements","querySelectorAll","includeImages","version","versionNumber","match","Number","isGPTModelGreaterOrEqualTo4","model","contentWithImages","base64Images","Array","from","map","imgEl","imageElement","quality","canvas","createElement","ctx","getContext","remove","img","Image","crossOrigin","onload","width","height","drawImage","base64","toDataURL","onerror","err","src","imageToBase64","base64ImagesResolved","allSettled","status","type","IMAGE","image_url","url","logs","error","reason","TEXT","getContentWithHistory","message","USER","history","messages","pastHistory","JSON","parse","_a","sessionStorage","moodleGPTHistory","newHistory","urlParams","URLSearchParams","location","search","host","cmid","get","attempt","_b","createNewHistory","a","b","KEYS_TO_COMPARE","key","areHistoryFromSameQuiz","saveResponse","ASSISTANT","stringify","htmlTableToString","table","tab","lines","maxColumnsLength","line","cellsContent","cell","index","textContent","max","headerLineLength","lineSeparationSize","reduce","jointure","lineSeparation","fill","join","mappedTab","padEnd","shift","handleClipboard","navigator","clipboard","writeText","handleContentEditable","inputList","input","getAttribute","typing","eventHandler","event","preventDefault","removeEventListener","slice","focus","range","createRange","selectNodeContents","collapse","selection","window","getSelection","removeAllRanges","addRange","addEventListener","handleNumber","number","undefined","eventHanlder","handleRadio","firstInput","possibleAnswers","inp","parentElement","filter","correctInput","mouseover","click","once","handleCheckbox","corrects","split","correct","handleSelect","tagName","options","opt","correctOption","currentSelect","closest","selected","handleTextbox","reply","props","cursor","style","questionContainer","innerText","accesshideElements","useless","tables","createAndNormalizeQuestion","form","inputQuery","controller","AbortController","timeoutControler","abort","contentHandler","req","fetch","method","headers","Authorization","apiKey","signal","timeout","body","temperature","top_p","presence_penalty","max_tokens","clearTimeout","json","choices","getChatGPTResponse","catch","haveError","infinite","mode","removeListener","clipboardMode","questionBackup","innerHTML","whiteSpace","contentIsResponse","questionToAnswerMode","handlers","handler","autoCompleteMode","pressedKeys","listeners","findIndex","listener","splice","fn","setUpMoodleGpt","forms","querySelector","injectionFunction","bind","chrome","storage","sync","moodleGPT","Error","code","codeListener"],"mappings":"2FAIA,SAASA,EAAiBC,GACxB,MAAMC,EAAYC,SAASC,MAC3BD,SAASC,MAAQH,EACjBI,YAAW,IAAOF,SAASC,MAAQF,GAAY,IACjD,CC0GO,SAASI,EAAUC,EAASC,EAAYC,EAAGC,GAE9C,OAAO,IAAKD,IAAMA,EAAIE,WAAU,SAAUC,EAASC,GAC/C,SAASC,EAAUC,GAAS,IAAMC,EAAKN,EAAUO,KAAKF,GAAQ,CAAG,MAAOG,GAAKL,EAAOK,GAAO,CAC3F,SAASC,EAASJ,GAAS,IAAMC,EAAKN,EAAiB,MAAEK,GAAU,CAAC,MAAOG,GAAKL,EAAOK,GAAO,CAC9F,SAASF,EAAKI,GAJlB,IAAeL,EAIaK,EAAOC,KAAOT,EAAQQ,EAAOL,QAJ1CA,EAIyDK,EAAOL,MAJhDA,aAAiBN,EAAIM,EAAQ,IAAIN,GAAE,SAAUG,GAAWA,EAAQG,EAAO,KAIhBO,KAAKR,EAAWK,EAAY,CAC9GH,GAAMN,EAAYA,EAAUa,MAAMhB,EAASC,GAAc,KAAKS,OACtE,GACA,CCzEA,SAASO,EAAmBC,EAAcC,GACxC,MAAMC,EAAeF,EAAKG,OAASF,EAAKE,OAASH,EAAKG,OAASF,EAAKE,OACpE,OAAqB,IAAjBD,EAA2B,GACvBA,EAlCV,SAA6BF,EAAcC,GACzC,GAAoB,IAAhBD,EAAKG,OAAc,OAAOF,EAAKE,OACnC,GAAoB,IAAhBF,EAAKE,OAAc,OAAOH,EAAKG,OAEnC,MAAMC,EAAqB,GACrBC,EAAoBL,EAAKM,QAAQ,MAAO,IACxCC,EAAoBN,EAAKK,QAAQ,MAAO,IAE9C,IAAK,IAAIE,EAAI,EAAGA,GAAKH,EAAkBF,SAAUK,EAAG,CAClDJ,EAAOK,KAAK,CAACD,IACb,IAAK,IAAIE,EAAI,EAAGA,GAAKH,EAAkBJ,SAAUO,EAC/CN,EAAOI,GAAGE,GACF,IAANF,EACIE,EACAC,KAAKC,IACHR,EAAOI,EAAI,GAAGE,GAAK,EACnBN,EAAOI,GAAGE,EAAI,GAAK,EACnBN,EAAOI,EAAI,GAAGE,EAAI,IAAML,EAAkBG,EAAI,KAAOD,EAAkBG,EAAI,GAAK,EAAI,GAG/F,CAED,OAAON,EAAOC,EAAkBF,QAAQI,EAAkBJ,OAC5D,CAWyBU,CAAoBb,EAAMC,IAASC,CAC5D,CAQgB,SAAAY,EACdC,EACAC,GAEA,IAAIC,EAA6B,CAC/BC,QAAS,KACTC,WAAY,EACZ7B,MAAO,MAET,IAAK,MAAM8B,KAAOJ,EAAK,CACrB,MAAMG,EAAapB,EAAmBqB,EAAI9B,MAAOyB,GACjD,GAAmB,IAAfI,EACF,MAAO,CAAED,QAASE,EAAIF,QAAS5B,MAAO8B,EAAI9B,MAAO6B,cAE/CA,EAAaF,EAAaE,aAC5BF,EAAe,CAAEC,QAASE,EAAIF,QAAS5B,MAAO8B,EAAI9B,MAAO6B,cAE5D,CACD,OAAOF,CACT,CD2OkD,mBAApBI,iBAAiCA,gBExT/D,MAAMC,EACJ,eAAOC,CAAS/C,GAEdgD,QAAQC,IAAI,mBADA,cACyBjD,EACtC,CAED,iBAAOkD,CAAWX,EAAgBI,GAEhCK,QAAQC,IACN,sBAFU,eAIV,IAAIV,2BDiGJ,SAAwBI,GAC5B,OAAOR,KAAKgB,MAAmB,IAAbR,EAAmB,KAAO,IAAM,GACpD,CCnG0CS,CAAcT,KAErD,CAED,YAAOU,CAAMb,GACXQ,QAAQC,IAAI,cAAeT,EAC5B,CAED,eAAOc,CAASC,GACdP,QAAQC,IAAI,cAAgBM,EAAUD,UACtCN,QAAQC,IAAI,gBAAkBM,EAAUC,mBACzC,ECrBH,SAASC,EAAczD,EAAc0D,GAAuB,GACtDA,IAAa1D,EAAOA,EAAK0D,eAW7B,OATuB1D,EACpB8B,QAAQ,QAAS,MACjBA,QAAQ,cAAe,MACvBA,QAAQ,WAAY,KACpB6B,OAEA7B,QAAQ,iBAAkB,IAC1BA,QAAQ,kBAAmB,KAGhC,CCjBA,IAAY8B,EAMAC,GANZ,SAAYD,GACVA,EAAA,OAAA,SACAA,EAAA,KAAA,OACAA,EAAA,UAAA,WACD,CAJD,CAAYA,IAAAA,EAIX,CAAA,IAED,SAAYC,GACVA,EAAA,KAAA,OACAA,EAAA,MAAA,WACD,CAHD,CAAYA,IAAAA,EAGX,CAAA,ICID,MAAMC,EAAsB,i+BAY1BH,OAEII,EAA6B,CACjCC,KAAMJ,EAAKK,OACXC,QAASJ,GAOX,SAAeK,EACbC,EACAC,EACAtB,4CAEA,MAAMuB,EAAiBD,EAAgBE,iBAAiB,OAExD,IACGH,EAAOI,gBCvCZ,SAAqCC,GACnC,MAAMC,EAAgBD,EAAQE,MAAM,aACpC,SAAKD,aAAa,EAAbA,EAAgB,KAGdE,OAAOF,EAAc,KAAO,CACrC,CDkCKG,CAA4BT,EAAOU,QACV,IAA1BR,EAAe3C,OAEf,OAAOoB,EAGT,MAAMgC,EAAoC,GAEpCC,EAAeC,MAAMC,KAAKZ,GAAgBa,KAAIC,GE/CtD,SAAuBC,EAAgCC,EAAU,KAC/D,OAAO,IAAI5E,SAAQ,CAACC,EAASC,KAC3B,MAAM2E,EAASrF,SAASsF,cAAc,UAChCC,EAAMF,EAAOG,WAAW,MAE9B,IAAKD,EAGH,OAFA7E,EAAO,2EACP2E,EAAOI,SAIT,MAAMC,EAAM,IAAIC,MAChBD,EAAIE,YAAc,YAClBF,EAAIG,OAAS,KACXR,EAAOS,MAAQJ,EAAII,MACnBT,EAAOU,OAASL,EAAIK,OACpBR,EAAIS,UAAUN,EAAK,EAAG,GAEtB,MAAMO,EAASZ,EAAOa,UAAU,YAAad,GAC7C3E,EAAQwF,GAERZ,EAAOI,QAAQ,EAGjBC,EAAIS,QAAUC,IACZ1F,EAAO0F,GACPf,EAAOI,QAAQ,EAGjBC,EAAIW,IAAMlB,EAAakB,GAAG,GAE9B,CFgB+DC,CAAcpB,KACrEqB,QAA6B/F,QAAQgG,WAAW1B,GAEtD,IAAK,MAAM7D,KAAUsF,EACG,cAAlBtF,EAAOwF,OACT5B,EAAkB9C,KAAK,CACrB2E,KAAM/C,EAAagD,MACnBC,UAAW,CAAEC,IAAK5F,EAAOL,SAElBsD,EAAO4C,MAChBhE,QAAQiE,MAAM9F,EAAO+F,QASzB,OALAnC,EAAkB9C,KAAK,CACrB2E,KAAM/C,EAAasD,KACnBnH,KAAM+C,IAGDgC,IACR,CAgDD,SAAeqC,EACbhD,EACAC,EACAtB,4CAKA,MAAMmB,QAAgBC,EAAWC,EAAQC,EAAiBtB,GACpDsE,EAAU,CAAErD,KAAMJ,EAAK0D,KAAMpD,WAEnC,IAAKE,EAAOmD,QAAS,MAAO,CAAEC,SAAU,CAACzD,EAA4BsD,IAErE,IAAIE,EAEJ,MAAME,EAzCCC,KAAKC,MAAqC,QAA/BC,EAAAC,eAAeC,wBAAgB,IAAAF,EAAAA,EAAI,QADvD,MA2CE,MAAMG,EA1DR,mBACE,MAAMC,EAAY,IAAIC,gBAAgB/H,SAASgI,SAASC,QAExD,MAAO,CACLC,KAAMlI,SAASgI,SAASE,KACxBC,KAA2B,UAArBL,EAAUM,IAAI,eAAO,IAAAV,EAAAA,EAAI,GAC/BW,QAAiC,UAAxBP,EAAUM,IAAI,kBAAU,IAAAE,EAAAA,EAAI,GACrCjB,QAAS,GAEb,CAiD8BkB,GAQ5B,OAHElB,EAHkB,OAAhBE,GAnCN,SAAgCiB,EAAYC,GAC1C,MAAMC,EAAqC,CAAC,OAAQ,OAAQ,WAE5D,IAAK,MAAMC,KAAOD,EAChB,GAAIF,EAAEG,KAASF,EAAEE,GAAM,OAAO,EAGhC,OAAO,CACT,CA2B+BC,CAAuBrB,EAAaM,GAGrDN,EAFAM,EAKL,CACLP,SAAU,CAACzD,KAA+BwD,EAAQA,QAASF,GAC3D,YAAA0B,CAAazF,GAEPc,EAAOmD,UACTA,EAAQA,QAAQtF,KAAKoF,GACrBE,EAAQA,QAAQtF,KAAK,CAAE+B,KAAMJ,EAAKoF,UAAW9E,QAASZ,IACtDuE,eAAeC,iBAAmBJ,KAAKuB,UAAU1B,GAEpD,KAEJ,CGvJD,SAAS2B,EAAkBC,GACzB,MAAMC,EAAkB,GAClBC,EAAQpE,MAAMC,KAAKiE,EAAM5E,iBAAiB,OAC1C+E,EAA6B,GAEnCD,EAAMlE,KAAIoE,IACR,MACMC,EADQvE,MAAMC,KAAKqE,EAAKhF,iBAAiB,WACpBY,KAAI,CAACsE,EAAMC,WACpC,MAAMxF,EAA0B,QAAhB0D,EAAA6B,EAAKE,mBAAW,IAAA/B,OAAA,EAAAA,EAAEjE,OAElC,OADA2F,EAAiBI,GAASvH,KAAKyH,IAAIN,EAAiBI,IAAU,GAAGxF,aAAO,EAAPA,EAASvC,SAAU,GAC7EuC,QAAAA,EAAW,EAAE,IAEtBkF,EAAInH,KAAKuH,EAAa,IAGxB,MACMK,EAAmBT,EAAI,GAAGzH,OAC1BmI,EACJR,EAAiBS,QAAO,CAACrB,EAAGC,IAAMD,EAAIC,GAAG,GAA8BqB,GAAxBH,EAAmB,GAC9DI,EAAiB,KAAOhF,MAAM6E,GAAoBI,KAAK,KAAKC,KAAK,IAAM,KAEvEC,EAAYhB,EAAIjE,KAAIoE,GACLA,EAAKpE,KAAI,CAACjB,EAASwF,IACpCxF,EAAQmG,OACNf,EAAiBI,GACjB,OAGcS,KAbH,SAkBjB,OAFaC,EAAUE,QAETL,EAAiBG,EAAUD,KAAK,KAChD,CC9BA,SAASI,EAAgBnG,EAAgBb,GACnCa,EAAOjE,OAAOJ,EAAiB,uBACnCyK,UAAUC,UAAUC,UAAUnH,EAAUD,SAC1C,CCFA,SAASqH,EACPvG,EACAwG,EACArH,GAEA,MAAMsH,EAAQD,EAAU,GAExB,GACuB,IAArBA,EAAUjJ,QACgC,SAA1CkJ,EAAMC,aAAa,mBAEnB,OAAO,EAGT,GAAI1G,EAAO2G,OAAQ,CACjB,IAAIrB,EAAQ,EAEZ,MAAMsB,EAAe,SAAUC,GAG7B,GAFAA,EAAMC,iBAEY,cAAdD,EAAMpC,KAAuBa,EAAQnG,EAAUD,SAAS3B,OAE1D,YADAkJ,EAAMM,oBAAoB,UAAWH,GAIvCH,EAAMlB,YAAcpG,EAAUD,SAAS8H,MAAM,IAAK1B,GAGlDmB,EAAMQ,QACN,MAAMC,EAAQpL,SAASqL,cACvBD,EAAME,mBAAmBX,GACzBS,EAAMG,UAAS,GACf,MAAMC,EAAYC,OAAOC,eACP,OAAdF,IACFA,EAAUG,kBACVH,EAAUI,SAASR,GAEvB,EAEAT,EAAMkB,iBAAiB,UAAWf,EACnC,MACCH,EAAMlB,YAAcpG,EAAUD,SAGhC,OAAO,CACT,CC7CA,SAAS0I,EACP5H,EACAwG,EACArH,WAEA,MAAMsH,EAAQD,EAAU,GAExB,GACuB,IAArBA,EAAUjJ,QACK,WAAfkJ,EAAMjE,KAEN,OAAO,EAGT,MAAMqF,EAAqE,QAA5DzD,EAAwD,QAAxDZ,EAAArE,EAAUC,mBAAmBmB,MAAM,0BAAqB,IAAAiD,OAAA,EAAAA,EAAA,UAAI,IAAAY,OAAA,EAAAA,EAAA1G,QAAQ,IAAK,KAExF,QAAeoK,IAAXD,EAAsB,OAAO,EAEjC,GAAI7H,EAAO2G,OAAQ,CACjB,IAAIrB,EAAQ,EAEZ,MAAMyC,EAAe,SAAUlB,GAC7BA,EAAMC,iBAC6B,cAAfD,EAAOpC,KAAuBa,EAAQuC,EAAOtK,OAC/DkJ,EAAMM,oBAAoB,UAAWgB,IAIA,MAAnCF,EAAOb,MAAM1B,EAAOA,EAAQ,MAAcA,EAE9CmB,EAAM/J,MAAQmL,EAAOb,MAAM,IAAK1B,GAClC,EAEAmB,EAAMkB,iBAAiB,UAAWI,EACnC,MACCtB,EAAM/J,MAAQmL,EAGhB,OAAO,CACT,CCrCA,SAASG,EACPhI,EACAwG,EACArH,GAEA,MAAM8I,EAAazB,eAAAA,EAAY,GAG/B,IAAKyB,GAAkC,UAApBA,EAAWzF,KAC5B,OAAO,EAGT,MAAM0F,EAAkBrH,MAAMC,KAAK0F,GAChCzF,KAAIoH,YAAO,MAAC,CACX7J,QAAS6J,EACTzL,MAAO2C,EAA6C,QAA/B+E,EAAkB,QAAlBZ,EAAA2E,aAAA,EAAAA,EAAKC,qBAAa,IAAA5E,OAAA,EAAAA,EAAE+B,mBAAW,IAAAnB,EAAAA,EAAI,IACxD,IACDiE,QAAO7J,GAAqB,KAAdA,EAAI9B,QAEfoC,EAAaZ,EAAgBiB,EAAUC,mBAAoB8I,GAE7DlI,EAAO4C,MAAQ9D,EAAWpC,OAC5BgC,EAAKI,WAAWA,EAAWpC,MAAOoC,EAAWP,YAG/C,MAAM+J,EAAexJ,EAAWR,QAShC,OARI0B,EAAOuI,UACTD,EAAaX,iBAAiB,aAAa,IAAMW,EAAaE,SAAS,CACrEC,MAAM,IAGRH,EAAaE,SAGR,CACT,CCnCA,SAASE,EACP1I,EACAwG,EACArH,GAEA,MAAM8I,EAAazB,eAAAA,EAAY,GAG/B,IAAKyB,GAAkC,aAApBA,EAAWzF,KAC5B,OAAO,EAGT,MAAMmG,EAAWxJ,EAAUC,mBAAmBwJ,MAAM,MAE9CV,EAAkBrH,MAAMC,KAAK0F,GAChCzF,KAAIoH,YAAO,MAAC,CACX7J,QAAS6J,EACTzL,MAAO2C,EAA6C,QAA/B+E,EAAkB,QAAlBZ,EAAA2E,aAAA,EAAAA,EAAKC,qBAAa,IAAA5E,OAAA,EAAAA,EAAE+B,mBAAW,IAAAnB,EAAAA,EAAI,IACxD,IACDiE,QAAO7J,GAAqB,KAAdA,EAAI9B,QAErB,IAAK,MAAMmM,KAAWF,EAAU,CAC9B,MAAM7J,EAAaZ,EAAgB2K,EAASX,GAExClI,EAAO4C,MAAQ9D,EAAWpC,OAC5BgC,EAAKI,WAAWA,EAAWpC,MAAOoC,EAAWP,YAG/C,MAAM+J,EAAexJ,EAAWR,QAC5B0B,EAAOuI,UACTD,EAAaX,iBAAiB,aAAa,IAAMW,EAAaE,SAAS,CACrEC,MAAM,IAGRH,EAAaE,OAEhB,CAED,OAAO,CACT,CCtCA,SAASM,EACP9I,EACAwG,EACArH,GAEA,GAAyB,IAArBqH,EAAUjJ,QAAyC,WAAzBiJ,EAAU,GAAGuC,QAAsB,OAAO,EAExE,MAAMJ,EAAWxJ,EAAUC,mBAAmBwJ,MAAM,MAEhD5I,EAAO4C,MAAMlE,EAAKO,MAAM0J,GAE5B,IAAK,IAAI/K,EAAI,EAAGA,EAAI4I,EAAUjJ,QACvBoL,EAAS/K,KADwBA,EAAG,CAGzC,MAAMoL,EAAUxC,EAAU5I,GAAGuC,iBAAiB,UAExC+H,EAAkBrH,MAAMC,KAAKkI,GAChChC,MAAM,GACNjG,KAAIkI,UAAO,MAAC,CACX3K,QAAS2K,EACTvM,MAAO2C,EAAiC,QAAnBmE,EAAAyF,EAAI1D,mBAAe,IAAA/B,EAAAA,EAAA,IACxC,IACD6E,QAAO7J,GAAqB,KAAdA,EAAI9B,QAEfoC,EAAaZ,EAAgByK,EAAS/K,GAAIsK,GAE5ClI,EAAO4C,MAAQ9D,EAAWpC,OAC5BgC,EAAKI,WAAWA,EAAWpC,MAAOoC,EAAWP,YAG/C,MAAM2K,EAAgBpK,EAAWR,QAC3B6K,EAAgBD,EAAcE,QAAQ,UAEtB,OAAlBD,IAEAnJ,EAAOuI,UACTY,EAAcxB,iBAAiB,SAAS,IAAOuB,EAAcG,UAAW,GAAO,CAC7EZ,MAAM,IAGRS,EAAcG,UAAW,EAE5B,CAED,OAAO,CACT,CChDA,SAASC,EACPtJ,EACAwG,EACArH,GAEA,MAAMsH,EAAQD,EAAU,GAExB,GACuB,IAArBA,EAAUjJ,QACS,aAAlBkJ,EAAMsC,SAAyC,SAAftC,EAAMjE,KAEvC,OAAO,EAGT,GAAIxC,EAAO2G,OAAQ,CACjB,IAAIrB,EAAQ,EAEZ,MAAMsB,EAAe,SAAUC,GAC7BA,EAAMC,iBAE6B,cAAfD,EAAOpC,KAAuBa,EAAQnG,EAAUD,SAAS3B,OAC3EkJ,EAAMM,oBAAoB,UAAWH,GAIvCH,EAAM/J,MAAQyC,EAAUD,SAAS8H,MAAM,IAAK1B,EAC9C,EAEAmB,EAAMkB,iBAAiB,UAAWf,EACnC,MACCH,EAAM/J,MAAQyC,EAAUD,SAG1B,OAAO,CACT,CCvBA,SAAeqK,EAAMC,4CACfA,EAAMxJ,OAAOyJ,SAAQD,EAAMvJ,gBAAgByJ,MAAMD,OAAS,QAE9D,MAAM9K,ECfR,SAAoCgL,GAClC,IAAIhL,EAAWgL,EAAkBC,UAGjC,MAAMC,EACJF,EAAkBxJ,iBAAiB,eACrC,IAAK,MAAM2J,KAAWD,EACpBlL,EAAWA,EAASjB,QAAQoM,EAAQF,UAAW,IAIjD,MAAMG,EAAuCJ,EAAkBxJ,iBAAiB,gBAChF,IAAK,MAAM4E,KAASgF,EAClBpL,EAAWA,EAASjB,QAAQqH,EAAM6E,UAAW,KAAO9E,EAAkBC,GAAS,MAGjF,OAAO1F,EAAcV,GAAU,EACjC,CDFmBqL,CAA2BR,EAAMS,MAC5CzD,EAAqCgD,EAAMS,KAAK9J,iBAAiBqJ,EAAMU,YAEvE/K,QEhBR,SACEa,EACAC,EACAtB,4CAEA,MAAMwL,EAAa,IAAIC,gBACjBC,EAAmBrO,YAAW,IAAMmO,EAAWG,SAAS,KAIxDC,QAAuBvH,EAAsBhD,EAAQC,EAAiBtB,GAEtE6L,QAAYC,MAAM,6CAA8C,CACpEC,OAAQ,OACRC,QAAS,CACP,eAAgB,mBAChBC,cAAe,UAAU5K,EAAO6K,UAElCC,OAAQ9K,EAAO+K,QAAUZ,EAAWW,OAAS,KAC7CE,KAAM1H,KAAKuB,UAAU,CACnBnE,MAAOV,EAAOU,MACd0C,SAAUmH,EAAenH,SAEzB6H,YAAa,GACbC,MAAO,GACPC,iBAAkB,EAClBC,WAAY,QAIhBC,aAAahB,GAEb,MACMnL,SADYsL,EAAIc,QACDC,QAAQ,GAAGtI,QAAQnD,QAKxC,MAF2C,mBAAhCyK,EAAe5F,cAA6B4F,EAAe5F,aAAazF,GAE5E,CACLP,WACAO,WACAE,mBAAoBC,EAAcH,MAErC,CF3ByBsM,CAAmBhC,EAAMxJ,OAAQwJ,EAAMvJ,gBAAiBtB,GAAU8M,OACxF5I,IAAU,CACRA,YAIE6I,EAAiC,iBAAdvM,GAA0B,UAAWA,EAM9D,GAJIqK,EAAMxJ,OAAOyJ,SACfD,EAAMvJ,gBAAgByJ,MAAMD,OAASD,EAAMxJ,OAAO2L,UAAYD,EAAY,UAAY,WAGpFA,EACF9M,QAAQiE,MAAM1D,EAAU0D,YAS1B,OALI2G,EAAMxJ,OAAO4C,OACflE,EAAKC,SAASA,GACdD,EAAKQ,SAASC,IAGRqK,EAAMxJ,OAAO4L,MACnB,IAAK,aGlCT,SAAuBpC,GAChBA,EAAMxJ,OAAO2L,UAAUnC,EAAMqC,iBAClC1F,EAAgBqD,EAAMxJ,OAAQwJ,EAAMrK,UACtC,CHgCM2M,CAAc,CACZ9L,OAAQwJ,EAAMxJ,OACdC,gBAAiBuJ,EAAMvJ,gBACvBd,YACA0M,eAAgBrC,EAAMqC,iBAExB,MACF,IAAK,sBI7CT,SAA8BrC,SAC5B,MAAMvJ,EAAkBuJ,EAAMvJ,gBAE9BuJ,EAAMqC,iBAEN,MAAME,EAA0C,QAAzBvI,EAAAvD,EAAgB+L,iBAAS,IAAAxI,EAAAA,EAAI,GACpDvD,EAAgB+L,UAAYxC,EAAMrK,UAAUD,SAC5Ce,EAAgByJ,MAAMuC,WAAa,WAGnChM,EAAgB0H,iBAAiB,SAAS,WACxC,MAAMuE,EAAoBjM,EAAgB+L,YAAcxC,EAAMrK,UAAUD,SAExEe,EAAgByJ,MAAMuC,WAAaC,EAAoB,UAAY,WACnEjM,EAAgB+L,UAAYE,EAAoBH,EAAiBvC,EAAMrK,UAAUD,QACnF,GACF,CJ8BMiN,CAAqB,CACnBhN,YACAc,gBAAiBuJ,EAAMvJ,gBACvB4L,eAAgBrC,EAAMqC,iBAExB,MACF,IAAK,gBKzCT,SAA0BrC,GACnBA,EAAMxJ,OAAO2L,UAAUnC,EAAMqC,iBAElC,MAAMO,EAAW,CACf7F,EACA+C,EACA1B,EACAkB,EACAd,EACAU,GAGF,IAAK,MAAM2D,KAAWD,EACpB,GAAIC,EAAQ7C,EAAMxJ,OAAQwJ,EAAMhD,UAAWgD,EAAMrK,WAAY,OAI/DgH,EAAgBqD,EAAMxJ,OAAQwJ,EAAMrK,UACtC,CLwBMmN,CAAiB,CACftM,OAAQwJ,EAAMxJ,OACdb,YACAqH,YACAvG,gBAAiBuJ,EAAMvJ,gBACvB4L,eAAgBrC,EAAMqC,oBAI7B,CMlED,MAAMU,EAAwB,GACxBC,EAAwB,GAqB9B,SAASX,EAAevN,GACtB,MAAMgH,EAAQkH,EAAUC,WAAUC,GAAYA,EAASpO,UAAYA,IACnE,IAAe,IAAXgH,EAAc,CAChB,MAAMoH,EAAWF,EAAUG,OAAOrH,EAAO,GAAG,GAC5CoH,EAASpO,QAAQyI,oBAAoB,QAAS2F,EAASE,GACxD,CACH,CAOA,SAASC,EAAe7M,GAEtB,GAAIwM,EAAUjP,OAAS,EAAG,CACxB,IAAK,MAAMmP,KAAYF,EACjBxM,EAAOyJ,SAAQiD,EAASpO,QAAQoL,MAAMD,OAAS,WACnDiD,EAASpO,QAAQyI,oBAAoB,QAAS2F,EAASE,IAIzD,OAFI5M,EAAOjE,OAAOJ,EAAiB,gBACnC6Q,EAAUjP,OAAS,EAEpB,CAGD,MAGM2M,EAHiB,CAAC,WAAY,QAAS,OAAQ,UAClDnJ,KAAIlE,GAAK,eAAeA,QACxBkJ,KAAK,KAC4B,wCAC9B+G,EAAQhR,SAASqE,iBAAiB,gBAGxC,IAAK,MAAM8J,KAAQ6C,EAAO,CACxB,MAAM7M,EAAsCgK,EAAK8C,cAAc,UAE/D,GAAwB,OAApB9M,EAA0B,SAE1BD,EAAOyJ,SAAQxJ,EAAgByJ,MAAMD,OAAS,WAElD,MAAMuD,EAAoBzD,EAAM0D,KAAK,KAAM,CACzCjN,SACAC,kBACAgK,KAAMA,EACNC,aACA2B,eAAgB,IAAMA,EAAe5L,KAGvCuM,EAAU3O,KAAK,CAAES,QAAS2B,EAAiB2M,GAAII,IAC/C/M,EAAgB0H,iBAAiB,QAASqF,EAC3C,CAEGhN,EAAOjE,OAAOJ,EAAiB,WACrC,CCjFAuR,OAAOC,QAAQC,KAAKlJ,IAAI,CAAC,cAAcjH,MAAK,SAAUkQ,GACpD,MAAMnN,EAAiBmN,EAAQE,UAE/B,IAAKrN,EAAQ,MAAM,IAAIsN,MAAM,iDAEzBtN,EAAOuN,KDQb,SAAsBvN,GACpBlE,SAASkP,KAAKrD,iBAAiB,WAAW,SAAUd,GAClD0F,EAAY1O,KAAKgJ,EAAMpC,KACnB8H,EAAYhP,OAASyC,EAAOuN,KAAMhQ,QAAQgP,EAAYrG,QACtDqG,EAAYxG,KAAK,MAAQ/F,EAAOuN,OAClChB,EAAYhP,OAAS,EACrBsP,EAAe7M,GAEnB,GACF,CChBIwN,CAAaxN,GAEb6M,EAAe7M,EAEnB","x_google_ignoreList":[1]} \ No newline at end of file +{"version":3,"file":"MoodleGPT.js","sources":["../src/utils/title-indications.ts","../node_modules/tslib/tslib.es6.js","../src/utils/pick-best-response.ts","../src/utils/logs.ts","../src/utils/normalize-text.ts","../src/types/message.ts","../src/core/get-content-with-history.ts","../src/utils/version-support-images.ts","../src/utils/image-to-base64.ts","../src/utils/html-table-to-string.ts","../src/core/create-question.ts","../src/utils/get-visible-text.ts","../src/core/questions/clipboard.ts","../src/core/questions/contenteditable.ts","../src/core/questions/number.ts","../src/core/questions/radio.ts","../src/core/questions/checkbox.ts","../src/core/questions/select.ts","../src/core/questions/textbox.ts","../src/core/reply.ts","../src/core/get-response.ts","../src/core/modes/clipboard.ts","../src/core/modes/question-to-answer.ts","../src/core/modes/autocomplete.ts","../src/core/code-listener.ts","../src/index.ts"],"sourcesContent":["/**\r\n * Show some informations into the document title and remove it after 3000ms\r\n * @param text\r\n */\r\nfunction titleIndications(text: string) {\r\n const backTitle = document.title;\r\n document.title = text;\r\n setTimeout(() => (document.title = backTitle), 3000);\r\n}\r\n\r\nexport default titleIndications;\r\n","/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n function next() {\r\n while (env.stack.length) {\r\n var rec = env.stack.pop();\r\n try {\r\n var result = rec.dispose && rec.dispose.call(rec.value);\r\n if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n};\r\n","type BestResponse = {\r\n similarity: number;\r\n value: string | null;\r\n element: HTMLElement | null;\r\n};\r\n\r\ntype ResponsesBySimilarity = {\r\n similarity: number;\r\n value: string;\r\n element: HTMLElement;\r\n};\r\n\r\n/**\r\n * Calculate the levenshtein distance between two sentence\r\n * @param str1\r\n * @param str2\r\n * @returns\r\n */\r\nfunction levenshteinDistance(str1: string, str2: string) {\r\n if (str1.length === 0) return str2.length;\r\n if (str2.length === 0) return str1.length;\r\n\r\n const matrix: number[][] = [];\r\n const str1WithoutSpaces = str1.replace(/\\s+/, '');\r\n const str2WithoutSpaces = str2.replace(/\\s+/, '');\r\n\r\n for (let i = 0; i <= str1WithoutSpaces.length; ++i) {\r\n matrix.push([i]);\r\n for (let j = 1; j <= str2WithoutSpaces.length; ++j) {\r\n matrix[i][j] =\r\n i === 0\r\n ? j\r\n : Math.min(\r\n matrix[i - 1][j] + 1,\r\n matrix[i][j - 1] + 1,\r\n matrix[i - 1][j - 1] + (str1WithoutSpaces[i - 1] === str2WithoutSpaces[j - 1] ? 0 : 1)\r\n );\r\n }\r\n }\r\n\r\n return matrix[str1WithoutSpaces.length][str2WithoutSpaces.length];\r\n}\r\n\r\n/**\r\n * Calculate the similarity between two sentences from 0 to 1 (best)\r\n * @param str1\r\n * @param str2\r\n * @returns\r\n */\r\nfunction sentenceSimilarity(str1: string, str2: string) {\r\n const longerLength = str1.length > str2.length ? str1.length : str2.length;\r\n if (longerLength === 0) return 1;\r\n return (longerLength - levenshteinDistance(str1, str2)) / longerLength;\r\n}\r\n\r\n/**\r\n * Pick the best sentence that correspond to the answer\r\n * @param arr\r\n * @param answer\r\n * @returns\r\n */\r\nexport function pickBestReponse(\r\n answer: string,\r\n arr: { element: HTMLElement; value: string }[]\r\n): BestResponse {\r\n let bestResponse: BestResponse = {\r\n element: null,\r\n similarity: 0,\r\n value: null\r\n };\r\n for (const obj of arr) {\r\n const similarity = sentenceSimilarity(obj.value, answer);\r\n if (similarity === 1) {\r\n return { element: obj.element, value: obj.value, similarity };\r\n }\r\n if (similarity > bestResponse.similarity) {\r\n bestResponse = { element: obj.element, value: obj.value, similarity };\r\n }\r\n }\r\n return bestResponse;\r\n}\r\n\r\n/**\r\n * Return the sentences sorted by score with a score superior or equal to what is asked\r\n * @param answer\r\n * @param arr\r\n * @param score\r\n * @returns\r\n */\r\nexport function pickResponsesWithSimilarityGreaterThan(\r\n answer: string,\r\n arr: { element: HTMLElement; value: string }[],\r\n score: number\r\n): ResponsesBySimilarity[] {\r\n const responses: ResponsesBySimilarity[] = [];\r\n for (const obj of arr) {\r\n const similarity = sentenceSimilarity(obj.value, answer);\r\n if (similarity >= score)\r\n responses.push({\r\n similarity,\r\n value: obj.value,\r\n element: obj.element\r\n });\r\n }\r\n return responses.sort((a, b) => a.similarity - b.similarity);\r\n}\r\n\r\n/**\r\n * Convert a number to a readable string pourcentage\r\n * @param similarity\r\n */\r\nexport function toPourcentage(similarity: number): string {\r\n return Math.round(similarity * 100 * 100) / 100 + '%';\r\n}\r\n","import GPTAnswer from '@typing/gpt-answer';\r\nimport { toPourcentage } from './pick-best-response';\r\n\r\nclass Logs {\r\n static question(text: string) {\r\n const css = 'color: cyan';\r\n console.log('%c[QUESTION]: %s', css, text);\r\n }\r\n\r\n static bestAnswer(answer: string, similarity: number) {\r\n const css = 'color: green';\r\n console.log(\r\n '%c[BEST ANSWER]: %s',\r\n css,\r\n `\"${answer}\" with a similarity of ${toPourcentage(similarity)}`\r\n );\r\n }\r\n\r\n static array(arr: unknown[]) {\r\n console.log('[CORRECTS] ', arr);\r\n }\r\n\r\n static response(gptAnswer: GPTAnswer) {\r\n console.log('Original:\\n' + gptAnswer.response);\r\n console.log('Normalized:\\n' + gptAnswer.normalizedResponse);\r\n }\r\n}\r\n\r\nexport default Logs;\r\n","/**\r\n * Normlize text\r\n * @param text\r\n */\r\nfunction normalizeText(text: string, toLowerCase: boolean = true) {\r\n if (toLowerCase) text = text.toLowerCase();\r\n\r\n const normalizedText = text\r\n .replace(/\\n+/gi, '\\n') //remove duplicate new lines\r\n .replace(/(\\n\\s*\\n)+/g, '\\n') //remove useless white space from textcontent\r\n .replace(/[ \\t]+/gi, ' ') //replace multiples space or tabs by a space\r\n .trim()\r\n // We remove the following content because sometimes ChatGPT will reply: \"answer d\"\r\n .replace(/^[a-z\\d]\\.\\s/gi, '') //a. text, b. text, c. text, 1. text, 2. text, 3.text\r\n .replace(/\\n[a-z\\d]\\.\\s/gi, '\\n'); //same but with new line\r\n\r\n return normalizedText;\r\n}\r\n\r\nexport default normalizeText;\r\n","export enum ROLE {\r\n SYSTEM = 'system',\r\n USER = 'user',\r\n ASSISTANT = 'assistant'\r\n}\r\n\r\nexport enum CONTENT_TYPE {\r\n TEXT = 'text',\r\n IMAGE = 'image_url'\r\n}\r\n\r\nexport type MessageContent =\r\n | string\r\n | Array<\r\n | {\r\n type: CONTENT_TYPE.TEXT;\r\n text: string;\r\n }\r\n | {\r\n type: CONTENT_TYPE.IMAGE;\r\n image_url: { url: string };\r\n }\r\n >;\r\n\r\nexport type Message = {\r\n role: ROLE;\r\n content: MessageContent;\r\n};\r\n","import type Config from '@typing/config';\r\nimport { ROLE, CONTENT_TYPE, type MessageContent, type Message } from '@typing/message';\r\nimport imageToBase64 from '@utils/image-to-base64';\r\nimport isGPTModelGreaterOrEqualTo4 from '@utils/version-support-images';\r\n\r\n// The attempt and the cmid allow us to identify a quiz\r\ntype History = {\r\n host: string;\r\n cmid: string; // The id of the quiz\r\n attempt: string; // The attempt of the current quiz\r\n history: { role: ROLE; content: MessageContent }[];\r\n};\r\n\r\nconst INSTRUCTION: string = `\r\nAct as a quiz solver for the best notation with the following rules:\r\n- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation. \r\n- But for the calculation provide this format 'result: '\r\n- For 'put in order' questions, maintain the answer in the order as presented in the question but assocy the correct order to it by usin this format ':\\n:', ignore other rules.\r\n- Always reply in the format: '\\n\\n...'.\r\n- Retain only the correct answer(s).\r\n- Maintain the same order for the answers as in the text.\r\n- Retain all text from the answer with its description, content or definition.\r\n- Only provide answers that exactly match the given answer in the text.\r\n- The question always has the correct answer(s), so you should always provide an answer.\r\n- Always respond in the same language as the user's question.\r\n`.trim();\r\n\r\nconst SYSTEM_INSTRUCTION_MESSAGE = {\r\n role: ROLE.SYSTEM,\r\n content: INSTRUCTION\r\n} as const satisfies Message;\r\n\r\n/**\r\n * Get the content to send to ChatGPT API (it allows to includes images if supported)\r\n * @param config\r\n */\r\nasync function getContent(\r\n config: Config,\r\n questionElement: HTMLElement,\r\n question: string\r\n): Promise {\r\n const imagesElements = questionElement.querySelectorAll('img');\r\n\r\n if (\r\n !config.includeImages ||\r\n !isGPTModelGreaterOrEqualTo4(config.model) ||\r\n imagesElements.length === 0\r\n ) {\r\n return question;\r\n }\r\n\r\n const contentWithImages: MessageContent = [];\r\n\r\n const base64Images = Array.from(imagesElements).map(imgEl => imageToBase64(imgEl));\r\n const base64ImagesResolved = await Promise.allSettled(base64Images);\r\n\r\n for (const result of base64ImagesResolved) {\r\n if (result.status === 'fulfilled') {\r\n contentWithImages.push({\r\n type: CONTENT_TYPE.IMAGE,\r\n image_url: { url: result.value }\r\n });\r\n } else if (config.logs) {\r\n console.error(result.reason);\r\n }\r\n }\r\n\r\n contentWithImages.push({\r\n type: CONTENT_TYPE.TEXT,\r\n text: question\r\n });\r\n\r\n return contentWithImages;\r\n}\r\n\r\n/**\r\n * Create a new history object from the current page\r\n * @returns\r\n */\r\nfunction createNewHistory(): History {\r\n const urlParams = new URLSearchParams(document.location.search);\r\n\r\n return {\r\n host: document.location.host,\r\n cmid: urlParams.get('cmid') ?? '',\r\n attempt: urlParams.get('attempt') ?? '',\r\n history: []\r\n };\r\n}\r\n\r\n/**\r\n * Load the past history from the session storage otherwise return the default history object\r\n * @returns\r\n */\r\nfunction loadPastHistory(): History | null {\r\n return JSON.parse(sessionStorage.moodleGPTHistory ?? 'null');\r\n}\r\n\r\n/**\r\n * Check if two history are from the same origin\r\n * @param a\r\n * @param b\r\n * @returns\r\n */\r\nfunction areHistoryFromSameQuiz(a: History, b: History): boolean {\r\n const KEYS_TO_COMPARE: (keyof History)[] = ['host', 'cmid', 'attempt'];\r\n\r\n for (const key of KEYS_TO_COMPARE) {\r\n if (a[key] !== b[key]) return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Return the content to send to chatgpt api with history if needed\r\n * @param config\r\n * @param questionElement\r\n * @param question\r\n * @returns\r\n */\r\nasync function getContentWithHistory(\r\n config: Config,\r\n questionElement: HTMLElement,\r\n question: string\r\n): Promise<{\r\n messages: [typeof SYSTEM_INSTRUCTION_MESSAGE, ...Message[]];\r\n saveResponse?: (response: string) => void;\r\n}> {\r\n const content = await getContent(config, questionElement, question);\r\n const message = { role: ROLE.USER, content };\r\n\r\n if (!config.history) return { messages: [SYSTEM_INSTRUCTION_MESSAGE, message] };\r\n\r\n let history: History;\r\n\r\n const pastHistory: History | null = loadPastHistory();\r\n const newHistory: History = createNewHistory();\r\n\r\n if (pastHistory === null || !areHistoryFromSameQuiz(pastHistory, newHistory)) {\r\n history = newHistory;\r\n } else {\r\n history = pastHistory;\r\n }\r\n\r\n return {\r\n messages: [SYSTEM_INSTRUCTION_MESSAGE, ...history.history, message],\r\n saveResponse(response: string) {\r\n // Register the conversation\r\n if (config.history) {\r\n history.history.push(message);\r\n history.history.push({ role: ROLE.ASSISTANT, content: response });\r\n sessionStorage.moodleGPTHistory = JSON.stringify(history);\r\n }\r\n }\r\n };\r\n}\r\n\r\nexport default getContentWithHistory;\r\n","/**\r\n * Check if the current ChatGPT version is greater or equal to 4\r\n * @param version\r\n * @returns\r\n */\r\nfunction isGPTModelGreaterOrEqualTo4(version: string): boolean {\r\n const versionNumber = version.match(/gpt-(\\d+)/);\r\n if (!versionNumber?.[1]) {\r\n return false;\r\n }\r\n return Number(versionNumber[1]) >= 4;\r\n}\r\n\r\nexport default isGPTModelGreaterOrEqualTo4;\r\n","/**\r\n * Convert an image html element into a base64 image string\r\n * @param imageElement\r\n * @param quality (default: 0.75 -> 75%)\r\n * @returns\r\n */\r\nfunction imageToBase64(imageElement: HTMLImageElement, quality = 0.75): Promise {\r\n return new Promise((resolve, reject) => {\r\n const canvas = document.createElement('canvas');\r\n const ctx = canvas.getContext('2d');\r\n\r\n if (!ctx) {\r\n reject(\"Can't get the canvas context, ensure your navigator support canvas\");\r\n canvas.remove();\r\n return;\r\n }\r\n\r\n const img = new Image();\r\n img.crossOrigin = 'Anonymous';\r\n img.onload = () => {\r\n canvas.width = img.width;\r\n canvas.height = img.height;\r\n ctx.drawImage(img, 0, 0);\r\n\r\n const base64 = canvas.toDataURL('image/png', quality);\r\n resolve(base64);\r\n\r\n canvas.remove();\r\n };\r\n\r\n img.onerror = err => {\r\n reject(err);\r\n canvas.remove();\r\n };\r\n\r\n img.src = imageElement.src;\r\n });\r\n}\r\n\r\nexport default imageToBase64;\r\n","/**\r\n * Convert table to representating string table\r\n * @param table\r\n * @returns\r\n */\r\nfunction htmlTableToString(table: HTMLTableElement) {\r\n const tab: string[][] = [];\r\n const lines = Array.from(table.querySelectorAll('tr'));\r\n const maxColumnsLength: number[] = [];\r\n\r\n lines.map(line => {\r\n const cells = Array.from(line.querySelectorAll('td, th'));\r\n const cellsContent = cells.map((cell, index) => {\r\n const content = cell.textContent?.trim();\r\n maxColumnsLength[index] = Math.max(maxColumnsLength[index] || 0, content?.length || 0);\r\n return content ?? '';\r\n });\r\n tab.push(cellsContent);\r\n });\r\n\r\n const jointure = ' | ';\r\n const headerLineLength = tab[0].length;\r\n const lineSeparationSize =\r\n maxColumnsLength.reduce((a, b) => a + b, 0) + (headerLineLength - 1) * jointure.length;\r\n const lineSeparation = '\\n' + Array(lineSeparationSize).fill('-').join('') + '\\n';\r\n\r\n const mappedTab = tab.map(line => {\r\n const mappedLine = line.map((content, index) =>\r\n content.padEnd(\r\n maxColumnsLength[index],\r\n '\\u00A0' // For no matching with \\s\r\n )\r\n );\r\n return mappedLine.join(jointure);\r\n });\r\n\r\n const head = mappedTab.shift();\r\n\r\n return head + lineSeparation + mappedTab.join('\\n');\r\n}\r\n\r\nexport default htmlTableToString;\r\n","import normalizeText from '@utils/normalize-text';\r\nimport htmlTableToString from '@utils/html-table-to-string';\r\nimport getVisibleText from '@utils/get-visible-text';\r\n\r\n/**\r\n * Normalize the question as text and add sub informations\r\n * @param langage\r\n * @param question\r\n * @returns\r\n */\r\nfunction createAndNormalizeQuestion(questionContainer: HTMLElement) {\r\n let question = getVisibleText(questionContainer);\r\n\r\n // Make tables more readable for chat-gpt\r\n const tables: NodeListOf = questionContainer.querySelectorAll('.qtext table');\r\n for (const table of tables) {\r\n question = question.replace(table.innerText, '\\n' + htmlTableToString(table) + '\\n');\r\n }\r\n\r\n return normalizeText(question, false);\r\n}\r\n\r\nexport default createAndNormalizeQuestion;\r\n","/**\r\n * Get only the visible text of an element\r\n * @param element\r\n * @returns\r\n */\r\nfunction getVisibleText(element: HTMLElement): string {\r\n function traverse(node: Node): string {\r\n let text = '';\r\n\r\n for (const child of node.childNodes) {\r\n if (child.nodeType === Node.TEXT_NODE) {\r\n if (isVisible(child.parentNode as HTMLElement)) {\r\n text += (child as Text).textContent;\r\n }\r\n } else if (child.nodeType === Node.ELEMENT_NODE) {\r\n text += traverse(child);\r\n }\r\n }\r\n return text;\r\n }\r\n\r\n function isVisible(el: HTMLElement): boolean {\r\n const style = window.getComputedStyle(el);\r\n return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';\r\n }\r\n\r\n return traverse(element);\r\n}\r\n\r\nexport default getVisibleText;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport titleIndications from '@utils/title-indications';\r\n\r\n/**\r\n * Copy the response in the clipboard if we can automaticaly fill the question\r\n * @param config\r\n * @param gptAnswer\r\n */\r\nfunction handleClipboard(config: Config, gptAnswer: GPTAnswer) {\r\n if (config.title) titleIndications('Copied to clipboard');\r\n navigator.clipboard.writeText(gptAnswer.response);\r\n}\r\n\r\nexport default handleClipboard;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\n\r\n/**\r\n * Hanlde contenteditable elements\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleContentEditable(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const input = inputList[0];\r\n\r\n if (\r\n inputList.length !== 1 || // for now we don't handle many input for editable textcontent\r\n typeof input.getAttribute('contenteditable') !== 'string'\r\n ) {\r\n return false;\r\n }\r\n\r\n if (config.typing) {\r\n let index = 0;\r\n\r\n const eventHandler = function (event: KeyboardEvent) {\r\n event.preventDefault();\r\n\r\n if (event.key === 'Backspace' || index > gptAnswer.response.length) {\r\n input.removeEventListener('keydown', eventHandler);\r\n return;\r\n }\r\n\r\n input.textContent = gptAnswer.response.slice(0, ++index);\r\n\r\n // Put the cursor at the end of the typed text\r\n input.focus();\r\n const range = document.createRange();\r\n range.selectNodeContents(input);\r\n range.collapse(false);\r\n const selection = window.getSelection();\r\n if (selection !== null) {\r\n selection.removeAllRanges();\r\n selection.addRange(range);\r\n }\r\n };\r\n\r\n input.addEventListener('keydown', eventHandler);\r\n } else {\r\n input.textContent = gptAnswer.response;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleContentEditable;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\n\r\n/**\r\n * Handle number input\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleNumber(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;\r\n\r\n if (\r\n inputList.length !== 1 || // for now we don't handle many input number\r\n input.type !== 'number'\r\n ) {\r\n return false;\r\n }\r\n\r\n const number = gptAnswer.normalizedResponse.match(/\\d+([,.]\\d+)?/gi)?.[0]?.replace(',', '.');\r\n\r\n if (number === undefined) return false;\r\n\r\n if (config.typing) {\r\n let index = 0;\r\n\r\n const eventHanlder = function (event: Event) {\r\n event.preventDefault();\r\n if ((event).key === 'Backspace' || index > number.length) {\r\n input.removeEventListener('keydown', eventHanlder);\r\n return;\r\n }\r\n\r\n if (number.slice(index, index + 1) === '.') ++index;\r\n\r\n input.value = number.slice(0, ++index);\r\n };\r\n\r\n input.addEventListener('keydown', eventHanlder);\r\n } else {\r\n input.value = number;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleNumber;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport Logs from '@utils/logs';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport { pickBestReponse } from '@utils/pick-best-response';\r\n\r\n/**\r\n * Handle input radio elements\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n */\r\nfunction handleRadio(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const firstInput = inputList?.[0] as HTMLInputElement;\r\n\r\n // Handle the case the input is not a radio\r\n if (!firstInput || firstInput.type !== 'radio') {\r\n return false;\r\n }\r\n\r\n const possibleAnswers = Array.from(inputList)\r\n .map(inp => ({\r\n element: inp,\r\n value: normalizeText(inp?.parentElement?.textContent ?? '')\r\n }))\r\n .filter(obj => obj.value !== '');\r\n\r\n const bestAnswer = pickBestReponse(gptAnswer.normalizedResponse, possibleAnswers);\r\n\r\n if (config.logs && bestAnswer.value) {\r\n Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);\r\n }\r\n\r\n const correctInput = bestAnswer.element as HTMLInputElement;\r\n if (config.mouseover) {\r\n correctInput.addEventListener('mouseover', () => correctInput.click(), {\r\n once: true\r\n });\r\n } else {\r\n correctInput.click();\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleRadio;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport Logs from '@utils/logs';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport { pickBestReponse } from '@utils/pick-best-response';\r\n\r\n/**\r\n * Handle input checkbox elements\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n */\r\nfunction handleCheckbox(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const firstInput = inputList?.[0] as HTMLInputElement;\r\n\r\n // Handle the case the input is not a checkbox\r\n if (!firstInput || firstInput.type !== 'checkbox') {\r\n return false;\r\n }\r\n\r\n const corrects = gptAnswer.normalizedResponse.split('\\n');\r\n\r\n const possibleAnswers = Array.from(inputList)\r\n .map(inp => ({\r\n element: inp as HTMLInputElement,\r\n value: normalizeText(inp?.parentElement?.textContent ?? '')\r\n }))\r\n .filter(obj => obj.value !== '');\r\n\r\n // Find the best answers elements\r\n const correctElements: Set = new Set();\r\n for (const correct of corrects) {\r\n const bestAnswer = pickBestReponse(correct, possibleAnswers);\r\n\r\n if (config.logs && bestAnswer.value) {\r\n Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);\r\n }\r\n\r\n correctElements.add(bestAnswer.element as HTMLInputElement);\r\n }\r\n\r\n // Check if it should be checked or not\r\n for (const element of possibleAnswers.map(e => e.element)) {\r\n const needAction =\r\n (element.checked && !correctElements.has(element)) ||\r\n (!element.checked && correctElements.has(element));\r\n\r\n const action = () => needAction && element.click();\r\n\r\n if (config.mouseover) {\r\n element.addEventListener('mouseover', action, {\r\n once: true\r\n });\r\n } else {\r\n action();\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleCheckbox;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport Logs from '@utils/logs';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport { pickBestReponse } from '@utils/pick-best-response';\r\n\r\n/**\r\n * Handle select elements (and put in order select)\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleSelect(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n if (inputList.length === 0 || inputList[0].tagName !== 'SELECT') return false;\r\n\r\n const corrects = gptAnswer.normalizedResponse.split('\\n');\r\n\r\n if (config.logs) Logs.array(corrects);\r\n\r\n for (let i = 0; i < inputList.length; ++i) {\r\n if (!corrects[i]) break;\r\n\r\n const options = inputList[i].querySelectorAll('option');\r\n\r\n const possibleAnswers = Array.from(options)\r\n .slice(1) // We remove the first option which correspond to \"Choose...\"\r\n .map(opt => ({\r\n element: opt,\r\n value: normalizeText(opt.textContent ?? '')\r\n }))\r\n .filter(obj => obj.value !== '');\r\n\r\n const bestAnswer = pickBestReponse(corrects[i], possibleAnswers);\r\n\r\n if (config.logs && bestAnswer.value) {\r\n Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);\r\n }\r\n\r\n const correctOption = bestAnswer.element as HTMLOptionElement;\r\n const currentSelect = correctOption.closest('select');\r\n\r\n if (currentSelect === null) continue;\r\n\r\n if (config.mouseover) {\r\n currentSelect.addEventListener('click', () => (correctOption.selected = true), {\r\n once: true\r\n });\r\n } else {\r\n correctOption.selected = true;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleSelect;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\n\r\n/**\r\n * Handle textbox\r\n * @param config\r\n * @param inputList\r\n * @param gptAnswer\r\n * @returns\r\n */\r\nfunction handleTextbox(\r\n config: Config,\r\n inputList: NodeListOf,\r\n gptAnswer: GPTAnswer\r\n): boolean {\r\n const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;\r\n\r\n if (\r\n inputList.length !== 1 || // for now we don't handle many input text\r\n (input.tagName !== 'TEXTAREA' && input.type !== 'text')\r\n ) {\r\n return false;\r\n }\r\n\r\n if (config.typing) {\r\n let index = 0;\r\n\r\n const eventHandler = function (event: Event) {\r\n event.preventDefault();\r\n\r\n if ((event).key === 'Backspace' || index > gptAnswer.response.length) {\r\n input.removeEventListener('keydown', eventHandler);\r\n return;\r\n }\r\n\r\n input.value = gptAnswer.response.slice(0, ++index);\r\n };\r\n\r\n input.addEventListener('keydown', eventHandler);\r\n } else {\r\n input.value = gptAnswer.response;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport default handleTextbox;\r\n","import type Config from '@typing/config';\r\nimport Logs from '@utils/logs';\r\nimport getChatGPTResponse from './get-response';\r\nimport createAndNormalizeQuestion from './create-question';\r\nimport clipboardMode from './modes/clipboard';\r\nimport questionToAnswerMode from './modes/question-to-answer';\r\nimport autoCompleteMode from './modes/autocomplete';\r\n\r\ntype Props = {\r\n config: Config;\r\n questionElement: HTMLElement;\r\n form: HTMLElement;\r\n inputQuery: string;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Reply to the question\r\n * @param props\r\n * @returns\r\n */\r\nasync function reply(props: Props): Promise {\r\n if (props.config.cursor) props.questionElement.style.cursor = 'wait';\r\n\r\n const question = createAndNormalizeQuestion(props.form);\r\n const inputList: NodeListOf = props.form.querySelectorAll(props.inputQuery);\r\n\r\n const gptAnswer = await getChatGPTResponse(props.config, props.questionElement, question).catch(\r\n error => ({\r\n error\r\n })\r\n );\r\n\r\n const haveError = typeof gptAnswer === 'object' && 'error' in gptAnswer;\r\n\r\n if (props.config.cursor) {\r\n props.questionElement.style.cursor = props.config.infinite || haveError ? 'pointer' : 'initial';\r\n }\r\n\r\n if (haveError) {\r\n console.error(gptAnswer.error);\r\n return;\r\n }\r\n\r\n if (props.config.logs) {\r\n Logs.question(question);\r\n Logs.response(gptAnswer);\r\n }\r\n\r\n switch (props.config.mode) {\r\n case 'clipboard':\r\n clipboardMode({\r\n config: props.config,\r\n questionElement: props.questionElement,\r\n gptAnswer,\r\n removeListener: props.removeListener\r\n });\r\n break;\r\n case 'question-to-answer':\r\n questionToAnswerMode({\r\n gptAnswer,\r\n questionElement: props.questionElement,\r\n removeListener: props.removeListener\r\n });\r\n break;\r\n case 'autocomplete':\r\n autoCompleteMode({\r\n config: props.config,\r\n gptAnswer,\r\n inputList,\r\n questionElement: props.questionElement,\r\n removeListener: props.removeListener\r\n });\r\n break;\r\n }\r\n}\r\n\r\nexport default reply;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport normalizeText from '@utils/normalize-text';\r\nimport getContentWithHistory from './get-content-with-history';\r\n\r\n/**\r\n * Get the response from chatGPT api\r\n * @param config\r\n * @param question\r\n * @returns\r\n */\r\nasync function getChatGPTResponse(\r\n config: Config,\r\n questionElement: HTMLElement,\r\n question: string\r\n): Promise {\r\n const controller = new AbortController();\r\n const timeoutControler = setTimeout(() => controller.abort(), 20 * 1000);\r\n\r\n // Get the content to send to chatgpt\r\n // Including the instructions to the AI, the images as base64 if needed, the question and the past conversation if history is set to true\r\n const contentHandler = await getContentWithHistory(config, questionElement, question);\r\n\r\n const req = await fetch('https://api.openai.com/v1/chat/completions', {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n Authorization: `Bearer ${config.apiKey}`\r\n },\r\n signal: config.timeout ? controller.signal : null,\r\n body: JSON.stringify({\r\n model: config.model,\r\n messages: contentHandler.messages,\r\n\r\n temperature: 0.1, // Controls the randomness of the generated responses, with lower values producing more deterministic and predictable outputs. With set to 0.1 instead of 0 for more creativity.\r\n top_p: 0.6, // Determines the diversity of the generated responses\r\n presence_penalty: 0, // Encourages the model to introduce new concepts by penalizing words that have already appeared in the text.\r\n max_tokens: 2000 // Maximum length of the response\r\n })\r\n });\r\n\r\n clearTimeout(timeoutControler);\r\n\r\n const rep = await req.json();\r\n const response = rep.choices[0].message.content;\r\n\r\n // Save the response into the history\r\n if (typeof contentHandler.saveResponse === 'function') contentHandler.saveResponse(response);\r\n\r\n return {\r\n question,\r\n response,\r\n normalizedResponse: normalizeText(response)\r\n };\r\n}\r\n\r\nexport default getChatGPTResponse;\r\n","import type Config from '@typing/config';\r\nimport type GPTAnswer from '@typing/gpt-answer';\r\nimport handleClipboard from '@core/questions/clipboard';\r\n\r\ntype Props = {\r\n config: Config;\r\n questionElement: HTMLElement;\r\n gptAnswer: GPTAnswer;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Clipboard mode:\r\n * Simply copy the answer into the clipboard\r\n * @param props\r\n */\r\nfunction clipboardMode(props: Props) {\r\n if (!props.config.infinite) props.removeListener();\r\n handleClipboard(props.config, props.gptAnswer);\r\n}\r\n\r\nexport default clipboardMode;\r\n","import type GPTAnswer from '@typing/gpt-answer';\r\n\r\ntype Props = {\r\n questionElement: HTMLElement;\r\n gptAnswer: GPTAnswer;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Question to answer mode:\r\n * Simply turn the question into the answer by clicking on it\r\n * @param props\r\n */\r\nfunction questionToAnswerMode(props: Props) {\r\n const questionElement = props.questionElement;\r\n\r\n props.removeListener();\r\n\r\n const questionBackup = questionElement.innerHTML ?? '';\r\n questionElement.innerHTML = props.gptAnswer.response;\r\n questionElement.style.whiteSpace = 'pre-wrap';\r\n\r\n // To go back to the question / answer\r\n questionElement.addEventListener('click', function () {\r\n const contentIsResponse = questionElement.innerHTML === props.gptAnswer.response;\r\n\r\n questionElement.style.whiteSpace = contentIsResponse ? 'initial' : 'pre-wrap';\r\n questionElement.innerHTML = contentIsResponse ? questionBackup : props.gptAnswer.response;\r\n });\r\n}\r\n\r\nexport default questionToAnswerMode;\r\n","import type GPTAnswer from '@typing/gpt-answer';\r\nimport type Config from '@typing/config';\r\nimport handleClipboard from '@core/questions/clipboard';\r\nimport handleContentEditable from '@core/questions/contenteditable';\r\nimport handleNumber from '@core/questions/number';\r\nimport handleRadio from '@core/questions/radio';\r\nimport handleCheckbox from '@core/questions/checkbox';\r\nimport handleSelect from '@core/questions/select';\r\nimport handleTextbox from '@core/questions/textbox';\r\n\r\ntype Props = {\r\n config: Config;\r\n questionElement: HTMLElement;\r\n inputList: NodeListOf;\r\n gptAnswer: GPTAnswer;\r\n removeListener: () => void;\r\n};\r\n\r\n/**\r\n * Autocomplete mode:\r\n * Autocomplete the question by checking the good answer\r\n * @param props\r\n * @returns\r\n */\r\nfunction autoCompleteMode(props: Props) {\r\n if (!props.config.infinite) props.removeListener();\r\n\r\n const handlers = [\r\n handleContentEditable,\r\n handleTextbox,\r\n handleNumber,\r\n handleSelect,\r\n handleRadio,\r\n handleCheckbox\r\n ];\r\n\r\n for (const handler of handlers) {\r\n if (handler(props.config, props.inputList, props.gptAnswer)) return;\r\n }\r\n\r\n // In the case we can't auto complete the question\r\n handleClipboard(props.config, props.gptAnswer);\r\n}\r\n\r\nexport default autoCompleteMode;\r\n","import type Config from '@typing/config';\r\nimport titleIndications from '@utils/title-indications';\r\nimport reply from './reply';\r\n\r\ntype Listener = {\r\n element: HTMLElement;\r\n fn: (this: HTMLElement, ev: MouseEvent) => void;\r\n};\r\n\r\nconst pressedKeys: string[] = [];\r\nconst listeners: Listener[] = [];\r\n\r\n/**\r\n * Create a listener on the keyboard to inject the code\r\n * @param config\r\n */\r\nfunction codeListener(config: Config) {\r\n document.body.addEventListener('keydown', function (event) {\r\n pressedKeys.push(event.key);\r\n if (pressedKeys.length > config.code!.length) pressedKeys.shift();\r\n if (pressedKeys.join('') === config.code) {\r\n pressedKeys.length = 0;\r\n setUpMoodleGpt(config);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Remove the event listener on a specific question\r\n * @param element\r\n */\r\nfunction removeListener(element: HTMLElement) {\r\n const index = listeners.findIndex(listener => listener.element === element);\r\n if (index !== -1) {\r\n const listener = listeners.splice(index, 1)[0];\r\n listener.element.removeEventListener('click', listener.fn);\r\n }\r\n}\r\n\r\n/**\r\n * Setup moodleGPT into the page (remove/injection)\r\n * @param config\r\n * @returns\r\n */\r\nfunction setUpMoodleGpt(config: Config) {\r\n // Removing events if there are already declared\r\n if (listeners.length > 0) {\r\n for (const listener of listeners) {\r\n if (config.cursor) listener.element.style.cursor = 'initial';\r\n listener.element.removeEventListener('click', listener.fn);\r\n }\r\n if (config.title) titleIndications('Removed');\r\n listeners.length = 0;\r\n return;\r\n }\r\n\r\n // Query to find inputs and forms\r\n const inputTypeQuery = ['checkbox', 'radio', 'text', 'number']\r\n .map(e => `input[type=\"${e}\"]`)\r\n .join(',');\r\n const inputQuery = inputTypeQuery + ', textarea, select, [contenteditable]';\r\n const forms = document.querySelectorAll('.formulation');\r\n\r\n // For each form we inject a function on the queqtion\r\n for (const form of forms) {\r\n const questionElement: HTMLElement | null = form.querySelector('.qtext');\r\n\r\n if (questionElement === null) continue;\r\n\r\n if (config.cursor) questionElement.style.cursor = 'pointer';\r\n\r\n const injectionFunction = reply.bind(null, {\r\n config,\r\n questionElement,\r\n form: form as HTMLElement,\r\n inputQuery,\r\n removeListener: () => removeListener(questionElement)\r\n });\r\n\r\n listeners.push({ element: questionElement, fn: injectionFunction });\r\n questionElement.addEventListener('click', injectionFunction);\r\n }\r\n\r\n if (config.title) titleIndications('Injected');\r\n}\r\n\r\nexport { codeListener, removeListener, setUpMoodleGpt };\r\n","import type Config from '@typing/config';\r\nimport { codeListener, setUpMoodleGpt } from './core/code-listener';\r\n\r\nchrome.storage.sync.get(['moodleGPT']).then(function (storage) {\r\n const config: Config = storage.moodleGPT;\r\n\r\n if (!config) throw new Error('Please configure MoodleGPT into the extension');\r\n\r\n if (config.code) {\r\n codeListener(config);\r\n } else {\r\n setUpMoodleGpt(config);\r\n }\r\n});\r\n"],"names":["titleIndications","text","backTitle","document","title","setTimeout","__awaiter","thisArg","_arguments","P","generator","Promise","resolve","reject","fulfilled","value","step","next","e","rejected","result","done","then","apply","sentenceSimilarity","str1","str2","longerLength","length","matrix","str1WithoutSpaces","replace","str2WithoutSpaces","i","push","j","Math","min","levenshteinDistance","pickBestReponse","answer","arr","bestResponse","element","similarity","obj","SuppressedError","Logs","question","console","log","bestAnswer","round","toPourcentage","array","response","gptAnswer","normalizedResponse","normalizeText","toLowerCase","trim","ROLE","CONTENT_TYPE","INSTRUCTION","SYSTEM_INSTRUCTION_MESSAGE","role","SYSTEM","content","getContent","config","questionElement","imagesElements","querySelectorAll","includeImages","version","versionNumber","match","Number","isGPTModelGreaterOrEqualTo4","model","contentWithImages","base64Images","Array","from","map","imgEl","imageElement","quality","canvas","createElement","ctx","getContext","remove","img","Image","crossOrigin","onload","width","height","drawImage","base64","toDataURL","onerror","err","src","imageToBase64","base64ImagesResolved","allSettled","status","type","IMAGE","image_url","url","logs","error","reason","TEXT","getContentWithHistory","message","USER","history","messages","pastHistory","JSON","parse","_a","sessionStorage","moodleGPTHistory","newHistory","urlParams","URLSearchParams","location","search","host","cmid","get","attempt","_b","createNewHistory","a","b","KEYS_TO_COMPARE","key","areHistoryFromSameQuiz","saveResponse","ASSISTANT","stringify","htmlTableToString","table","tab","lines","maxColumnsLength","line","cellsContent","cell","index","textContent","max","headerLineLength","lineSeparationSize","reduce","jointure","lineSeparation","fill","join","mappedTab","padEnd","shift","createAndNormalizeQuestion","questionContainer","isVisible","el","style","window","getComputedStyle","display","visibility","opacity","traverse","node","child","childNodes","nodeType","Node","TEXT_NODE","parentNode","ELEMENT_NODE","getVisibleText","tables","innerText","handleClipboard","navigator","clipboard","writeText","handleContentEditable","inputList","input","getAttribute","typing","eventHandler","event","preventDefault","removeEventListener","slice","focus","range","createRange","selectNodeContents","collapse","selection","getSelection","removeAllRanges","addRange","addEventListener","handleNumber","number","undefined","eventHanlder","handleRadio","firstInput","possibleAnswers","inp","parentElement","filter","correctInput","mouseover","click","once","handleCheckbox","corrects","split","correctElements","Set","correct","add","needAction","checked","has","action","handleSelect","tagName","options","opt","correctOption","currentSelect","closest","selected","handleTextbox","reply","props","cursor","form","inputQuery","controller","AbortController","timeoutControler","abort","contentHandler","req","fetch","method","headers","Authorization","apiKey","signal","timeout","body","temperature","top_p","presence_penalty","max_tokens","clearTimeout","json","choices","getChatGPTResponse","catch","haveError","infinite","mode","removeListener","clipboardMode","questionBackup","innerHTML","whiteSpace","contentIsResponse","questionToAnswerMode","handlers","handler","autoCompleteMode","pressedKeys","listeners","findIndex","listener","splice","fn","setUpMoodleGpt","forms","querySelector","injectionFunction","bind","chrome","storage","sync","moodleGPT","Error","code","codeListener"],"mappings":"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","x_google_ignoreList":[1]} \ No newline at end of file diff --git a/extension/popup/js/index.js b/extension/popup/js/index.js index 5b69638..dd70c83 100644 --- a/extension/popup/js/index.js +++ b/extension/popup/js/index.js @@ -32,9 +32,9 @@ saveBtn.addEventListener('click', function () { return; } - if (code.length > 0 && code.length < 3) { + if (code.length > 0 && code.length < 2) { showMessage({ - msg: 'The code should at least contain 3 characters', + msg: 'The code should at least contain 2 characters', error: true }); return; diff --git a/src/core/create-question.ts b/src/core/create-question.ts index a765410..1fe0aec 100644 --- a/src/core/create-question.ts +++ b/src/core/create-question.ts @@ -1,5 +1,6 @@ import normalizeText from '@utils/normalize-text'; import htmlTableToString from '@utils/html-table-to-string'; +import getVisibleText from '@utils/get-visible-text'; /** * Normalize the question as text and add sub informations @@ -8,14 +9,7 @@ import htmlTableToString from '@utils/html-table-to-string'; * @returns */ function createAndNormalizeQuestion(questionContainer: HTMLElement) { - let question = questionContainer.innerText; - - // We remove unnecessary information - const accesshideElements: NodeListOf = - questionContainer.querySelectorAll('.accesshide'); - for (const useless of accesshideElements) { - question = question.replace(useless.innerText, ''); - } + let question = getVisibleText(questionContainer); // Make tables more readable for chat-gpt const tables: NodeListOf = questionContainer.querySelectorAll('.qtext table'); diff --git a/src/core/questions/checkbox.ts b/src/core/questions/checkbox.ts index f6c7ed3..74ffd1d 100644 --- a/src/core/questions/checkbox.ts +++ b/src/core/questions/checkbox.ts @@ -26,11 +26,13 @@ function handleCheckbox( const possibleAnswers = Array.from(inputList) .map(inp => ({ - element: inp, + element: inp as HTMLInputElement, value: normalizeText(inp?.parentElement?.textContent ?? '') })) .filter(obj => obj.value !== ''); + // Find the best answers elements + const correctElements: Set = new Set(); for (const correct of corrects) { const bestAnswer = pickBestReponse(correct, possibleAnswers); @@ -38,13 +40,23 @@ function handleCheckbox( Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity); } - const correctInput = bestAnswer.element as HTMLInputElement; + correctElements.add(bestAnswer.element as HTMLInputElement); + } + + // Check if it should be checked or not + for (const element of possibleAnswers.map(e => e.element)) { + const needAction = + (element.checked && !correctElements.has(element)) || + (!element.checked && correctElements.has(element)); + + const action = () => needAction && element.click(); + if (config.mouseover) { - correctInput.addEventListener('mouseover', () => correctInput.click(), { + element.addEventListener('mouseover', action, { once: true }); } else { - correctInput.click(); + action(); } } diff --git a/src/core/questions/contenteditable.ts b/src/core/questions/contenteditable.ts index 3cdc4a7..609677d 100644 --- a/src/core/questions/contenteditable.ts +++ b/src/core/questions/contenteditable.ts @@ -17,7 +17,7 @@ function handleContentEditable( if ( inputList.length !== 1 || // for now we don't handle many input for editable textcontent - input.getAttribute('contenteditable') !== 'true' + typeof input.getAttribute('contenteditable') !== 'string' ) { return false; } diff --git a/src/utils/get-visible-text.ts b/src/utils/get-visible-text.ts new file mode 100644 index 0000000..308eefd --- /dev/null +++ b/src/utils/get-visible-text.ts @@ -0,0 +1,30 @@ +/** + * Get only the visible text of an element + * @param element + * @returns + */ +function getVisibleText(element: HTMLElement): string { + function traverse(node: Node): string { + let text = ''; + + for (const child of node.childNodes) { + if (child.nodeType === Node.TEXT_NODE) { + if (isVisible(child.parentNode as HTMLElement)) { + text += (child as Text).textContent; + } + } else if (child.nodeType === Node.ELEMENT_NODE) { + text += traverse(child); + } + } + return text; + } + + function isVisible(el: HTMLElement): boolean { + const style = window.getComputedStyle(el); + return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; + } + + return traverse(element); +} + +export default getVisibleText;