prettier formatage

This commit is contained in:
yoannchb-pro
2024-02-28 19:07:19 -05:00
parent 8c364e286b
commit 6a5e1eb8c3
40 changed files with 1666 additions and 1295 deletions
+11 -11
View File
@@ -1,18 +1,18 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
ignorePatterns: ["node_modules/"],
ignorePatterns: ['node_modules/'],
overrides: [
{
files: ["extension/popup/*.js", "src/**/*.ts"],
rules: {},
},
],
files: ['extension/popup/*.js', 'src/**/*.ts'],
rules: {}
}
]
};
+10
View File
@@ -0,0 +1,10 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"printWidth": 100,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto"
}
+1 -1
View File
@@ -1,7 +1,7 @@
# TODO
- Historic for questions (implemented but need testing)
- Contributing.md
- Contributing.md / Fixe readme.md 'MoodleGPT don't complete my quiz ?'
- Better prompt (Fixe put in order question, Fixe calculation question)
- Support math equation from image stocked in the `data-mathml` attribute
- Better assets
+418 -1
View File
File diff suppressed because one or more lines are too long
+4 -10
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -41,15 +41,11 @@
</div>
</div>
<div class="line center">
<label for="apiKey" class="textLabel"
>Api Key<span class="required">*</span>:</label
>
<label for="apiKey" class="textLabel">Api Key<span class="required">*</span>:</label>
<input id="apiKey" type="text" />
</div>
<div class="line center">
<label for="model" class="textLabel"
>GPT Model<span class="required">*</span>:</label
>
<label for="model" class="textLabel">GPT Model<span class="required">*</span>:</label>
<input id="model" type="text" />
<i
id="reloadModel"
@@ -73,9 +69,7 @@
<button value="clipboard" class="not-selected">clipboard</button>
</li>
<li>
<button value="question-to-answer" class="not-selected">
question to answer
</button>
<button value="question-to-answer" class="not-selected">question to answer</button>
</li>
</ul>
</div>
+15 -15
View File
@@ -1,49 +1,49 @@
"use strict";
'use strict';
/**
* Get the last ChatGPT version
*/
function getLastChatGPTVersion() {
const apiKeySelector = document.querySelector("#apiKey");
const reloadModel = document.querySelector("#reloadModel");
const apiKeySelector = document.querySelector('#apiKey');
const reloadModel = document.querySelector('#reloadModel');
let apiKey = apiKeySelector.value;
// If the api key is set we enable the button to get the last chatgpt version
function checkFiledApiKey() {
if (apiKey) {
reloadModel.removeAttribute("disabled");
reloadModel.setAttribute("title", "Get last ChatGPT version");
reloadModel.removeAttribute('disabled');
reloadModel.setAttribute('title', 'Get last ChatGPT version');
return;
}
reloadModel.setAttribute("disabled", true);
reloadModel.setAttribute("title", "Provide an api key first");
reloadModel.setAttribute('disabled', true);
reloadModel.setAttribute('title', 'Provide an api key first');
}
checkFiledApiKey();
// Check if the api key is set
apiKeySelector.addEventListener("input", function () {
apiKeySelector.addEventListener('input', function () {
apiKey = apiKeySelector.value.trim();
checkFiledApiKey();
});
// Event listener to handle a click on the relaod icon button
reloadModel.addEventListener("click", async function () {
reloadModel.addEventListener('click', async function () {
if (!apiKey) return;
try {
const req = await fetch("https://api.openai.com/v1/models", {
const req = await fetch('https://api.openai.com/v1/models', {
headers: {
Authorization: `Bearer ${apiKey}`,
},
Authorization: `Bearer ${apiKey}`
}
});
const rep = await req.json();
const model = rep.data.find((model) => model.id.startsWith("gpt"));
document.querySelector("#model").value = model.id;
const model = rep.data.find(model => model.id.startsWith('gpt'));
document.querySelector('#model').value = model.id;
} catch (err) {
console.error(err);
showMessage({ msg: "Failed to fetch last ChatGPT version", error: true });
showMessage({ msg: 'Failed to fetch last ChatGPT version', error: true });
}
});
}
+31 -34
View File
@@ -1,38 +1,39 @@
const saveBtn = document.querySelector(".save");
const saveBtn = document.querySelector('.save');
/* inputs id */
const inputsText = ["apiKey", "code", "model"];
const inputsText = ['apiKey', 'code', 'model'];
const inputsCheckbox = [
"logs",
"title",
"cursor",
"typing",
"mouseover",
"infinite",
"timeout",
"history",
'logs',
'title',
'cursor',
'typing',
'mouseover',
'infinite',
'timeout',
'history'
];
/* Save the configuration */
saveBtn.addEventListener("click", function () {
const [apiKey, code, model] = inputsText.map((selector) =>
document.querySelector("#" + selector).value.trim()
saveBtn.addEventListener('click', function () {
const [apiKey, code, model] = inputsText.map(selector =>
document.querySelector('#' + selector).value.trim()
);
const [logs, title, cursor, typing, mouseover, infinite, timeout, history] = inputsCheckbox.map(
selector => {
const element = document.querySelector('#' + selector);
return element.checked && element.parentElement.style.display !== 'none';
}
);
const [logs, title, cursor, typing, mouseover, infinite, timeout, history] =
inputsCheckbox.map((selector) => {
const element = document.querySelector("#" + selector);
return element.checked && element.parentElement.style.display !== "none";
});
if (!apiKey || !model) {
showMessage({ msg: "Please complete all the form", error: true });
showMessage({ msg: 'Please complete all the form', error: true });
return;
}
if (code.length > 0 && code.length < 3) {
showMessage({
msg: "The code should at least contain 3 characters",
error: true,
msg: 'The code should at least contain 3 characters',
error: true
});
return;
}
@@ -50,15 +51,15 @@ saveBtn.addEventListener("click", function () {
infinite,
timeout,
history,
mode: actualMode,
},
mode: actualMode
}
});
showMessage({ msg: "Configuration saved" });
showMessage({ msg: 'Configuration saved' });
});
/* we load back the configuration */
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
chrome.storage.sync.get(['moodleGPT']).then(function (storage) {
const config = storage.moodleGPT;
if (config) {
@@ -66,21 +67,17 @@ chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
actualMode = config.mode;
for (const mode of modes) {
if (mode.value === config.mode) {
mode.classList.remove("not-selected");
mode.classList.remove('not-selected');
} else {
mode.classList.add("not-selected");
mode.classList.add('not-selected');
}
}
}
inputsText.forEach((key) =>
config[key]
? (document.querySelector("#" + key).value = config[key])
: null
);
inputsCheckbox.forEach(
(key) => (document.querySelector("#" + key).checked = config[key] || "")
inputsText.forEach(key =>
config[key] ? (document.querySelector('#' + key).value = config[key]) : null
);
inputsCheckbox.forEach(key => (document.querySelector('#' + key).checked = config[key] || ''));
}
handleModeChange();
+12 -14
View File
@@ -1,15 +1,15 @@
"use strict";
'use strict';
const mode = document.querySelector("#mode");
const modes = mode.querySelectorAll("button");
const mode = document.querySelector('#mode');
const modes = mode.querySelectorAll('button');
let actualMode = "autocomplete";
let actualMode = 'autocomplete';
/* inputs id that need to be disabled for a specific mode */
const disabledForThisMode = {
autocomplete: [],
clipboard: ["typing", "mouseover"],
"question-to-answer": ["typing", "infinite", "mouseover"],
clipboard: ['typing', 'mouseover'],
'question-to-answer': ['typing', 'infinite', 'mouseover']
};
/**
@@ -17,27 +17,25 @@ const disabledForThisMode = {
*/
function handleModeChange() {
const needDisable = disabledForThisMode[actualMode];
const dontNeedDisable = inputsCheckbox.filter(
(input) => !needDisable.includes(input)
);
const dontNeedDisable = inputsCheckbox.filter(input => !needDisable.includes(input));
for (const id of needDisable) {
document.querySelector("#" + id).parentElement.style.display = "none";
document.querySelector('#' + id).parentElement.style.display = 'none';
}
for (const id of dontNeedDisable) {
document.querySelector("#" + id).parentElement.style.display = null;
document.querySelector('#' + id).parentElement.style.display = null;
}
}
/* Mode handler */
for (const button of modes) {
button.addEventListener("click", function () {
button.addEventListener('click', function () {
const value = button.value;
actualMode = value;
for (const mode of modes) {
if (mode.value !== value) {
mode.classList.add("not-selected");
mode.classList.add('not-selected');
} else {
mode.classList.remove("not-selected");
mode.classList.remove('not-selected');
}
}
handleModeChange();
+5 -5
View File
@@ -1,12 +1,12 @@
"use strict";
'use strict';
/**
* Show message into the popup
*/
function showMessage({ msg, error, infinite }) {
const message = document.querySelector("#message");
message.style.color = error ? "red" : "limegreen";
const message = document.querySelector('#message');
message.style.color = error ? 'red' : 'limegreen';
message.textContent = msg;
message.style.display = "block";
if (!infinite) setTimeout(() => (message.style.display = "none"), 5000);
message.style.display = 'block';
if (!infinite) setTimeout(() => (message.style.display = 'none'), 5000);
}
+15 -18
View File
@@ -1,7 +1,7 @@
"use strict";
'use strict';
const CURRENT_VERSION = "1.0.4";
const versionDisplay = document.querySelector("#version");
const CURRENT_VERSION = '1.0.4';
const versionDisplay = document.querySelector('#version');
/**
* Get the last version from the github
@@ -9,7 +9,7 @@ const versionDisplay = document.querySelector("#version");
*/
async function getLastVersion() {
const req = await fetch(
"https://raw.githubusercontent.com/yoannchb-pro/MoodleGPT/main/package.json"
'https://raw.githubusercontent.com/yoannchb-pro/MoodleGPT/main/package.json'
);
const rep = await req.json();
return rep.version;
@@ -23,34 +23,31 @@ async function getLastVersion() {
*/
function setVersion(version, isCurrent = true) {
if (isCurrent) {
versionDisplay.textContent = "v" + version;
versionDisplay.textContent = 'v' + version;
return;
}
const link = document.createElement("a");
link.href = "https://github.com/yoannchb-pro/MoodleGPT";
link.rel = "noopener noreferrer";
link.target = "_blank";
link.textContent = "v" + version;
const link = document.createElement('a');
link.href = 'https://github.com/yoannchb-pro/MoodleGPT';
link.rel = 'noopener noreferrer';
link.target = '_blank';
link.textContent = 'v' + version;
versionDisplay.appendChild(link);
versionDisplay.appendChild(document.createTextNode(" is now available !"));
versionDisplay.appendChild(document.createTextNode(' is now available !'));
}
/**
* Check if the extension neeed an update or not
*/
async function notifyUpdate() {
const lastVersion = await getLastVersion().catch((err) => {
const lastVersion = await getLastVersion().catch(err => {
console.error(err);
return CURRENT_VERSION;
});
const lastVertionSplitted = lastVersion.split(".");
const currentVersionSplitted = CURRENT_VERSION.split(".");
const minVersionLength = Math.min(
lastVertionSplitted.length,
currentVersionSplitted.length
);
const lastVertionSplitted = lastVersion.split('.');
const currentVersionSplitted = CURRENT_VERSION.split('.');
const minVersionLength = Math.min(lastVertionSplitted.length, currentVersionSplitted.length);
for (let i = 0; i < minVersionLength; ++i) {
if (parseInt(lastVertionSplitted[i]) > parseInt(currentVersionSplitted[i]))
+4 -4
View File
@@ -13,7 +13,7 @@
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Segeo UI", sans-serif;
font-family: 'Segeo UI', sans-serif;
color: var(--color);
}
@@ -67,8 +67,8 @@ a {
font-weight: bold;
}
.line input[type="text"],
.line input[type="password"] {
.line input[type='text'],
.line input[type='password'] {
flex: 1 1;
border: thin solid var(--color);
padding: 0.3rem 0.5rem;
@@ -77,7 +77,7 @@ a {
background-color: transparent;
}
.line input[type="checkbox"] {
.line input[type='checkbox'] {
accent-color: var(--btn-color);
}
+9 -9
View File
@@ -1,16 +1,16 @@
const ts = require("rollup-plugin-ts");
const terser = require("@rollup/plugin-terser");
const ts = require('rollup-plugin-ts');
const terser = require('@rollup/plugin-terser');
const config = require("./tsconfig.json");
const config = require('./tsconfig.json');
module.exports = {
input: "./src/index.ts",
input: './src/index.ts',
output: [
{
file: "./extension/MoodleGPT.js",
format: "umd",
sourcemap: true,
},
file: './extension/MoodleGPT.js',
format: 'umd',
sourcemap: true
}
],
plugins: [ts(config), terser()],
plugins: [ts(config), terser()]
};
+20 -20
View File
@@ -1,6 +1,6 @@
import type Config from "@typing/config";
import titleIndications from "@utils/title-indications";
import reply from "./reply";
import type Config from '@typing/config';
import titleIndications from '@utils/title-indications';
import reply from './reply';
type Listener = {
element: HTMLElement;
@@ -15,10 +15,10 @@ const listeners: Listener[] = [];
* @param config
*/
function codeListener(config: Config) {
document.body.addEventListener("keydown", function (event) {
document.body.addEventListener('keydown', function (event) {
pressedKeys.push(event.key);
if (pressedKeys.length > config.code!.length) pressedKeys.shift();
if (pressedKeys.join("") === config.code) {
if (pressedKeys.join('') === config.code) {
pressedKeys.length = 0;
setUpMoodleGpt(config);
}
@@ -30,10 +30,10 @@ function codeListener(config: Config) {
* @param element
*/
function removeListener(element: HTMLElement) {
const index = listeners.findIndex((listener) => listener.element === element);
const index = listeners.findIndex(listener => listener.element === element);
if (index !== -1) {
const listener = listeners.splice(index, 1)[0];
listener.element.removeEventListener("click", listener.fn);
listener.element.removeEventListener('click', listener.fn);
}
}
@@ -46,42 +46,42 @@ function setUpMoodleGpt(config: Config) {
// Removing events if there are already declared
if (listeners.length > 0) {
for (const listener of listeners) {
if (config.cursor) listener.element.style.cursor = "initial";
listener.element.removeEventListener("click", listener.fn);
if (config.cursor) listener.element.style.cursor = 'initial';
listener.element.removeEventListener('click', listener.fn);
}
if (config.title) titleIndications("Removed");
if (config.title) titleIndications('Removed');
listeners.length = 0;
return;
}
// Query to find inputs and forms
const inputTypeQuery = ["checkbox", "radio", "text", "number"]
.map((e) => `input[type="${e}"]`)
.join(",");
const inputQuery = inputTypeQuery + ", textarea, select, [contenteditable]";
const forms = document.querySelectorAll(".formulation");
const inputTypeQuery = ['checkbox', 'radio', 'text', 'number']
.map(e => `input[type="${e}"]`)
.join(',');
const inputQuery = inputTypeQuery + ', textarea, select, [contenteditable]';
const forms = document.querySelectorAll('.formulation');
// For each form we inject a function on the queqtion
for (const form of forms) {
const questionElement: HTMLElement | null = form.querySelector(".qtext");
const questionElement: HTMLElement | null = form.querySelector('.qtext');
if (questionElement === null) continue;
if (config.cursor) questionElement.style.cursor = "pointer";
if (config.cursor) questionElement.style.cursor = 'pointer';
const injectionFunction = reply.bind(null, {
config,
questionElement,
form: form as HTMLElement,
inputQuery,
removeListener: () => removeListener(questionElement),
removeListener: () => removeListener(questionElement)
});
listeners.push({ element: questionElement, fn: injectionFunction });
questionElement.addEventListener("click", injectionFunction);
questionElement.addEventListener('click', injectionFunction);
}
if (config.title) titleIndications("Injected");
if (config.title) titleIndications('Injected');
}
export { codeListener, removeListener, setUpMoodleGpt };
+6 -10
View File
@@ -1,5 +1,5 @@
import normalizeText from "@utils/normalize-text";
import htmlTableToString from "@utils/html-table-to-string";
import normalizeText from '@utils/normalize-text';
import htmlTableToString from '@utils/html-table-to-string';
/**
* Normalize the question as text and add sub informations
@@ -12,19 +12,15 @@ function createAndNormalizeQuestion(questionContainer: HTMLElement) {
// We remove unnecessary information
const accesshideElements: NodeListOf<HTMLElement> =
questionContainer.querySelectorAll(".accesshide");
questionContainer.querySelectorAll('.accesshide');
for (const useless of accesshideElements) {
question = question.replace(useless.innerText, "");
question = question.replace(useless.innerText, '');
}
// Make tables more readable for chat-gpt
const tables: NodeListOf<HTMLTableElement> =
questionContainer.querySelectorAll(".qtext table");
const tables: NodeListOf<HTMLTableElement> = questionContainer.querySelectorAll('.qtext table');
for (const table of tables) {
question = question.replace(
table.innerText,
"\n" + htmlTableToString(table) + "\n"
);
question = question.replace(table.innerText, '\n' + htmlTableToString(table) + '\n');
}
return normalizeText(question, false);
+16 -19
View File
@@ -1,6 +1,6 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import normalizeText from "@utils/normalize-text";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import normalizeText from '@utils/normalize-text';
type History = {
url: string | null;
@@ -9,9 +9,9 @@ type History = {
};
enum ROLE {
SYSTEM = "system",
USER = "user",
ASSISTANT = "assistant",
SYSTEM = 'system',
USER = 'user',
ASSISTANT = 'assistant'
}
const INSTRUCTION: string = `
@@ -32,9 +32,9 @@ const history: History = {
url: null,
system: {
role: ROLE.SYSTEM,
content: INSTRUCTION,
content: INSTRUCTION
},
history: [],
history: []
};
/**
@@ -43,10 +43,7 @@ const history: History = {
* @param question
* @returns
*/
async function getChatGPTResponse(
config: Config,
question: string
): Promise<GPTAnswer> {
async function getChatGPTResponse(config: Config, question: string): Promise<GPTAnswer> {
const URL = location.hostname + location.pathname;
// We reset the history when we enter a new moodle quiz or when it's desactivate
@@ -60,11 +57,11 @@ async function getChatGPTResponse(
const message = { role: ROLE.USER, content: question };
const req = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
const req = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
Authorization: `Bearer ${config.apiKey}`
},
signal: config.timeout ? controller.signal : null,
body: JSON.stringify({
@@ -73,8 +70,8 @@ async function getChatGPTResponse(
temperature: 0.8,
top_p: 1.0,
presence_penalty: 1.0,
stop: null,
}),
stop: null
})
});
clearTimeout(timeoutControler);
@@ -91,7 +88,7 @@ async function getChatGPTResponse(
return {
question,
response,
normalizedResponse: normalizeText(response),
normalizedResponse: normalizeText(response)
};
}
+10 -10
View File
@@ -1,12 +1,12 @@
import type GPTAnswer from "@typing/gptAnswer";
import type Config from "@typing/config";
import handleClipboard from "@core/questions/clipboard";
import handleContentEditable from "@core/questions/contenteditable";
import handleNumber from "@core/questions/number";
import handleRadio from "@core/questions/radio";
import handleCheckbox from "@core/questions/checkbox";
import handleSelect from "@core/questions/select";
import handleTextbox from "@core/questions/textbox";
import type GPTAnswer from '@typing/gptAnswer';
import type Config from '@typing/config';
import handleClipboard from '@core/questions/clipboard';
import handleContentEditable from '@core/questions/contenteditable';
import handleNumber from '@core/questions/number';
import handleRadio from '@core/questions/radio';
import handleCheckbox from '@core/questions/checkbox';
import handleSelect from '@core/questions/select';
import handleTextbox from '@core/questions/textbox';
type Props = {
config: Config;
@@ -31,7 +31,7 @@ function autoCompleteMode(props: Props) {
handleNumber,
handleSelect,
handleRadio,
handleCheckbox,
handleCheckbox
];
for (const handler of handlers) {
+3 -3
View File
@@ -1,6 +1,6 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import handleClipboard from "@core/questions/clipboard";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import handleClipboard from '@core/questions/clipboard';
type Props = {
config: Config;
+5 -7
View File
@@ -1,4 +1,4 @@
import type GPTAnswer from "@typing/gptAnswer";
import type GPTAnswer from '@typing/gptAnswer';
type Props = {
questionElement: HTMLElement;
@@ -21,15 +21,13 @@ function questionToAnswerMode(props: Props) {
questionElement.textContent = props.gptAnswer.response;
// Format the content
questionElement.style.whiteSpace = "pre-wrap";
questionElement.style.whiteSpace = 'pre-wrap';
// To go back to the question / answer
let contentIsResponse = true;
questionElement.addEventListener("click", function () {
questionElement.style.whiteSpace = contentIsResponse ? "" : "pre-warp";
questionElement.textContent = contentIsResponse
? questionBackup
: props.gptAnswer.response;
questionElement.addEventListener('click', function () {
questionElement.style.whiteSpace = contentIsResponse ? '' : 'pre-warp';
questionElement.textContent = contentIsResponse ? questionBackup : props.gptAnswer.response;
contentIsResponse = !contentIsResponse;
});
+13 -17
View File
@@ -1,8 +1,8 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import Logs from "@utils/logs";
import normalizeText from "@utils/normalize-text";
import { pickBestReponse } from "@utils/pick-best-response";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import Logs from '@utils/logs';
import normalizeText from '@utils/normalize-text';
import { pickBestReponse } from '@utils/pick-best-response';
/**
* Handle input checkbox elements
@@ -18,18 +18,18 @@ function handleCheckbox(
const firstInput = inputList?.[0] as HTMLInputElement;
// Handle the case the input is not a checkbox
if (!firstInput || firstInput.type !== "checkbox") {
if (!firstInput || firstInput.type !== 'checkbox') {
return false;
}
const corrects = gptAnswer.normalizedResponse.split("\n");
const corrects = gptAnswer.normalizedResponse.split('\n');
const possibleAnswers = Array.from(inputList)
.map((inp) => ({
.map(inp => ({
element: inp,
value: normalizeText(inp?.parentElement?.textContent ?? ""),
value: normalizeText(inp?.parentElement?.textContent ?? '')
}))
.filter((obj) => obj.value !== "");
.filter(obj => obj.value !== '');
for (const correct of corrects) {
const bestAnswer = pickBestReponse(correct, possibleAnswers);
@@ -40,13 +40,9 @@ function handleCheckbox(
const correctInput = bestAnswer.element as HTMLInputElement;
if (config.mouseover) {
correctInput.addEventListener(
"mouseover",
() => (correctInput.checked = true),
{
once: true,
}
);
correctInput.addEventListener('mouseover', () => (correctInput.checked = true), {
once: true
});
} else {
correctInput.checked = true;
}
+4 -4
View File
@@ -1,6 +1,6 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import titleIndications from "@utils/title-indications";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import titleIndications from '@utils/title-indications';
/**
* Copy the response in the clipboard if we can automaticaly fill the question
@@ -8,7 +8,7 @@ import titleIndications from "@utils/title-indications";
* @param gptAnswer
*/
function handleClipboard(config: Config, gptAnswer: GPTAnswer) {
if (config.title) titleIndications("Copied to clipboard");
if (config.title) titleIndications('Copied to clipboard');
navigator.clipboard.writeText(gptAnswer.response);
}
+5 -5
View File
@@ -1,5 +1,5 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
/**
* Hanlde contenteditable elements
@@ -17,15 +17,15 @@ function handleContentEditable(
if (
inputList.length !== 1 || // for now we don't handle many input for editable textcontent
input.getAttribute("contenteditable") !== "true"
input.getAttribute('contenteditable') !== 'true'
) {
return false;
}
if (config.typing) {
let index = 0;
input.addEventListener("keydown", function (event: KeyboardEvent) {
if (event.key === "Backspace") index = gptAnswer.response.length + 1;
input.addEventListener('keydown', function (event: KeyboardEvent) {
if (event.key === 'Backspace') index = gptAnswer.response.length + 1;
if (index > gptAnswer.response.length) return;
event.preventDefault();
input.textContent = gptAnswer.response.slice(0, ++index);
+7 -9
View File
@@ -1,5 +1,5 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
/**
* Handle number input
@@ -17,24 +17,22 @@ function handleNumber(
if (
inputList.length !== 1 || // for now we don't handle many input number
input.type !== "number"
input.type !== 'number'
) {
return false;
}
const number = gptAnswer.normalizedResponse
.match(/\d+([,.]\d+)?/gi)?.[0]
?.replace(",", ".");
const number = gptAnswer.normalizedResponse.match(/\d+([,.]\d+)?/gi)?.[0]?.replace(',', '.');
if (number === undefined) return false;
if (config.typing) {
let index = 0;
input.addEventListener("keydown", function (event: Event) {
input.addEventListener('keydown', function (event: Event) {
event.preventDefault();
if ((<KeyboardEvent>event).key === "Backspace") index = number.length + 1;
if ((<KeyboardEvent>event).key === 'Backspace') index = number.length + 1;
if (index > number.length) return;
if (number.slice(index, index + 1) === ".") ++index;
if (number.slice(index, index + 1) === '.') ++index;
input.value = number.slice(0, ++index);
});
} else {
+13 -20
View File
@@ -1,8 +1,8 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import Logs from "@utils/logs";
import normalizeText from "@utils/normalize-text";
import { pickBestReponse } from "@utils/pick-best-response";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import Logs from '@utils/logs';
import normalizeText from '@utils/normalize-text';
import { pickBestReponse } from '@utils/pick-best-response';
/**
* Handle input radio elements
@@ -18,21 +18,18 @@ function handleRadio(
const firstInput = inputList?.[0] as HTMLInputElement;
// Handle the case the input is not a radio
if (!firstInput || firstInput.type !== "radio") {
if (!firstInput || firstInput.type !== 'radio') {
return false;
}
const possibleAnswers = Array.from(inputList)
.map((inp) => ({
.map(inp => ({
element: inp,
value: normalizeText(inp?.parentElement?.textContent ?? ""),
value: normalizeText(inp?.parentElement?.textContent ?? '')
}))
.filter((obj) => obj.value !== "");
.filter(obj => obj.value !== '');
const bestAnswer = pickBestReponse(
gptAnswer.normalizedResponse,
possibleAnswers
);
const bestAnswer = pickBestReponse(gptAnswer.normalizedResponse, possibleAnswers);
if (config.logs && bestAnswer.value) {
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
@@ -40,13 +37,9 @@ function handleRadio(
const correctInput = bestAnswer.element as HTMLInputElement;
if (config.mouseover) {
correctInput.addEventListener(
"mouseover",
() => (correctInput.checked = true),
{
once: true,
}
);
correctInput.addEventListener('mouseover', () => (correctInput.checked = true), {
once: true
});
} else {
correctInput.checked = true;
}
+15 -19
View File
@@ -1,8 +1,8 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import Logs from "@utils/logs";
import normalizeText from "@utils/normalize-text";
import { pickBestReponse } from "@utils/pick-best-response";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import Logs from '@utils/logs';
import normalizeText from '@utils/normalize-text';
import { pickBestReponse } from '@utils/pick-best-response';
/**
* Handle select elements (and put in order select)
@@ -16,23 +16,23 @@ function handleSelect(
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
if (inputList.length === 0 || inputList[0].tagName !== "SELECT") return false;
if (inputList.length === 0 || inputList[0].tagName !== 'SELECT') return false;
const corrects = gptAnswer.normalizedResponse.split("\n");
const corrects = gptAnswer.normalizedResponse.split('\n');
if (config.logs) Logs.array(corrects);
for (let i = 0; i < inputList.length; ++i) {
if (!corrects[i]) break;
const options = inputList[i].querySelectorAll("option");
const options = inputList[i].querySelectorAll('option');
const possibleAnswers = Array.from(options)
.map((opt) => ({
.map(opt => ({
element: opt,
value: normalizeText(opt.textContent ?? ""),
value: normalizeText(opt.textContent ?? '')
}))
.filter((obj) => obj.value !== "");
.filter(obj => obj.value !== '');
const bestAnswer = pickBestReponse(corrects[i], possibleAnswers);
@@ -41,18 +41,14 @@ function handleSelect(
}
const correctOption = bestAnswer.element as HTMLOptionElement;
const currentSelect = correctOption.closest("select");
const currentSelect = correctOption.closest('select');
if (currentSelect === null) continue;
if (config.mouseover) {
currentSelect.addEventListener(
"click",
() => (correctOption.selected = true),
{
once: true,
}
);
currentSelect.addEventListener('click', () => (correctOption.selected = true), {
once: true
});
} else {
correctOption.selected = true;
}
+5 -5
View File
@@ -1,5 +1,5 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
/**
* Handle textbox
@@ -17,16 +17,16 @@ function handleTextbox(
if (
inputList.length !== 1 || // for now we don't handle many input text
(input.tagName !== "TEXTAREA" && input.type !== "text")
(input.tagName !== 'TEXTAREA' && input.type !== 'text')
) {
return false;
}
if (config.typing) {
let index = 0;
input.addEventListener("keydown", function (event: Event) {
input.addEventListener('keydown', function (event: Event) {
event.preventDefault();
if ((<KeyboardEvent>event).key === "Backspace") {
if ((<KeyboardEvent>event).key === 'Backspace') {
index = gptAnswer.response.length + 1;
}
if (index > gptAnswer.response.length) return;
+20 -25
View File
@@ -1,10 +1,10 @@
import type Config from "@typing/config";
import Logs from "@utils/logs";
import getChatGPTResponse from "./get-response";
import createAndNormalizeQuestion from "./create-question";
import clipboardMode from "./modes/clipboard";
import questionToAnswerMode from "./modes/question-to-answer";
import autoCompleteMode from "./modes/autocomplete";
import type Config from '@typing/config';
import Logs from '@utils/logs';
import getChatGPTResponse from './get-response';
import createAndNormalizeQuestion from './create-question';
import clipboardMode from './modes/clipboard';
import questionToAnswerMode from './modes/question-to-answer';
import autoCompleteMode from './modes/autocomplete';
type Props = {
config: Config;
@@ -20,24 +20,19 @@ type Props = {
* @returns
*/
async function reply(props: Props): Promise<void> {
if (props.config.cursor) props.questionElement.style.cursor = "wait";
if (props.config.cursor) props.questionElement.style.cursor = 'wait';
const question = createAndNormalizeQuestion(props.form);
const inputList: NodeListOf<HTMLElement> = props.form.querySelectorAll(
props.inputQuery
);
const inputList: NodeListOf<HTMLElement> = props.form.querySelectorAll(props.inputQuery);
const gptAnswer = await getChatGPTResponse(props.config, question).catch(
(error) => ({
error,
})
);
const gptAnswer = await getChatGPTResponse(props.config, question).catch(error => ({
error
}));
const haveError = typeof gptAnswer === "object" && "error" in gptAnswer;
const haveError = typeof gptAnswer === 'object' && 'error' in gptAnswer;
if (props.config.cursor) {
props.questionElement.style.cursor =
props.config.infinite || haveError ? "pointer" : "initial";
props.questionElement.style.cursor = props.config.infinite || haveError ? 'pointer' : 'initial';
}
if (haveError) {
@@ -51,28 +46,28 @@ async function reply(props: Props): Promise<void> {
}
switch (props.config.mode) {
case "clipboard":
case 'clipboard':
clipboardMode({
config: props.config,
questionElement: props.questionElement,
gptAnswer,
removeListener: props.removeListener,
removeListener: props.removeListener
});
break;
case "question-to-answer":
case 'question-to-answer':
questionToAnswerMode({
gptAnswer,
questionElement: props.questionElement,
removeListener: props.removeListener,
removeListener: props.removeListener
});
break;
case "autocomplete":
case 'autocomplete':
autoCompleteMode({
config: props.config,
gptAnswer,
inputList,
questionElement: props.questionElement,
removeListener: props.removeListener,
removeListener: props.removeListener
});
break;
}
+4 -4
View File
@@ -1,10 +1,10 @@
import type Config from "@typing/config";
import { codeListener, setUpMoodleGpt } from "./core/code-listener";
import type Config from '@typing/config';
import { codeListener, setUpMoodleGpt } from './core/code-listener';
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
chrome.storage.sync.get(['moodleGPT']).then(function (storage) {
const config: Config = storage.moodleGPT;
if (!config) throw new Error("Please configure MoodleGPT into the extension");
if (!config) throw new Error('Please configure MoodleGPT into the extension');
if (config.code) {
codeListener(config);
+1 -1
View File
@@ -10,7 +10,7 @@ type Config = {
title?: boolean;
timeout?: boolean;
history?: boolean;
mode?: "autocomplete" | "question-to-answer" | "clipboard";
mode?: 'autocomplete' | 'question-to-answer' | 'clipboard';
};
export default Config;
+11 -16
View File
@@ -5,37 +5,32 @@
*/
function htmlTableToString(table: HTMLTableElement) {
const tab: string[][] = [];
const lines = Array.from(table.querySelectorAll("tr"));
const lines = Array.from(table.querySelectorAll('tr'));
const maxColumnsLength: number[] = [];
lines.map((line) => {
const cells = Array.from(line.querySelectorAll("td, th"));
lines.map(line => {
const cells = Array.from(line.querySelectorAll('td, th'));
const cellsContent = cells.map((cell, index) => {
const content = cell.textContent?.trim();
maxColumnsLength[index] = Math.max(
maxColumnsLength[index] || 0,
content?.length || 0
);
return content ?? "";
maxColumnsLength[index] = Math.max(maxColumnsLength[index] || 0, content?.length || 0);
return content ?? '';
});
tab.push(cellsContent);
});
const lineSeparationSize =
maxColumnsLength.reduce((a, b) => a + b) + tab[0].length * 3 + 1;
const lineSeparation =
"\n" + Array(lineSeparationSize).fill("-").join("") + "\n";
const lineSeparationSize = maxColumnsLength.reduce((a, b) => a + b) + tab[0].length * 3 + 1;
const lineSeparation = '\n' + Array(lineSeparationSize).fill('-').join('') + '\n';
const mappedTab = tab.map((line) => {
const mappedTab = tab.map(line => {
const mappedLine = line.map((content, index) =>
content.padEnd(
maxColumnsLength[index],
"\u00A0" // For no matching with \s
'\u00A0' // For no matching with \s
)
);
return "| " + mappedLine.join(" | ") + " |";
return '| ' + mappedLine.join(' | ') + ' |';
});
const head = mappedTab.shift();
return head + lineSeparation + mappedTab.join("\n");
return head + lineSeparation + mappedTab.join('\n');
}
export default htmlTableToString;
+9 -9
View File
@@ -1,28 +1,28 @@
import GPTAnswer from "@typing/gptAnswer";
import { toPourcentage } from "./pick-best-response";
import GPTAnswer from '@typing/gptAnswer';
import { toPourcentage } from './pick-best-response';
class Logs {
static question(text: string) {
const css = "color: cyan";
console.log("%c[QUESTION]: %s", css, text);
const css = 'color: cyan';
console.log('%c[QUESTION]: %s', css, text);
}
static bestAnswer(answer: string, similarity: number) {
const css = "color: green";
const css = 'color: green';
console.log(
"%c[BEST ANSWER]: %s",
'%c[BEST ANSWER]: %s',
css,
`"${answer}" with a similarity of ${toPourcentage(similarity)}`
);
}
static array(arr: unknown[]) {
console.log("[CORRECTS] ", arr);
console.log('[CORRECTS] ', arr);
}
static response(gptAnswer: GPTAnswer) {
console.log("Original:\n" + gptAnswer.response);
console.log("Normalized:\n" + gptAnswer.normalizedResponse);
console.log('Original:\n' + gptAnswer.response);
console.log('Normalized:\n' + gptAnswer.normalizedResponse);
}
}
+5 -5
View File
@@ -6,13 +6,13 @@ function normalizeText(text: string, toLowerCase: boolean = true) {
if (toLowerCase) text = text.toLowerCase();
const normalizedText = text
.replace(/\n+/gi, "\n") //remove duplicate new lines
.replace(/(\n\s*\n)+/g, "\n") //remove useless white space from textcontent
.replace(/[ \t]+/gi, " ") //replace multiples space or tabs by a space
.replace(/\n+/gi, '\n') //remove duplicate new lines
.replace(/(\n\s*\n)+/g, '\n') //remove useless white space from textcontent
.replace(/[ \t]+/gi, ' ') //replace multiples space or tabs by a space
.trim()
// We remove the following content because sometimes ChatGPT will reply: "answer d"
.replace(/^[a-z\d]\.\s/gi, "") //a. text, b. text, c. text, 1. text, 2. text, 3.text
.replace(/\n[a-z\d]\.\s/gi, "\n"); //same but with new line
.replace(/^[a-z\d]\.\s/gi, '') //a. text, b. text, c. text, 1. text, 2. text, 3.text
.replace(/\n[a-z\d]\.\s/gi, '\n'); //same but with new line
return normalizedText;
}
+6 -7
View File
@@ -21,8 +21,8 @@ function levenshteinDistance(str1: string, str2: string) {
if (str2.length === 0) return str1.length;
const matrix: number[][] = [];
const str1WithoutSpaces = str1.replace(/\s+/, "");
const str2WithoutSpaces = str2.replace(/\s+/, "");
const str1WithoutSpaces = str1.replace(/\s+/, '');
const str2WithoutSpaces = str2.replace(/\s+/, '');
for (let i = 0; i <= str1WithoutSpaces.length; ++i) {
matrix.push([i]);
@@ -33,8 +33,7 @@ function levenshteinDistance(str1: string, str2: string) {
: Math.min(
matrix[i - 1][j] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j - 1] +
(str1WithoutSpaces[i - 1] === str2WithoutSpaces[j - 1] ? 0 : 1)
matrix[i - 1][j - 1] + (str1WithoutSpaces[i - 1] === str2WithoutSpaces[j - 1] ? 0 : 1)
);
}
}
@@ -67,7 +66,7 @@ export function pickBestReponse(
let bestResponse: BestResponse = {
element: null,
similarity: 0,
value: null,
value: null
};
for (const obj of arr) {
const similarity = sentenceSimilarity(obj.value, answer);
@@ -100,7 +99,7 @@ export function pickResponsesWithSimilarityGreaterThan(
responses.push({
similarity,
value: obj.value,
element: obj.element,
element: obj.element
});
}
return responses.sort((a, b) => a.similarity - b.similarity);
@@ -111,5 +110,5 @@ export function pickResponsesWithSimilarityGreaterThan(
* @param similarity
*/
export function toPourcentage(similarity: number): string {
return Math.round(similarity * 100 * 100) / 100 + "%";
return Math.round(similarity * 100 * 100) / 100 + '%';
}
+1 -1
View File
@@ -7,7 +7,7 @@
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Segeo UI";
font-family: 'Segeo UI';
}
body {
+8 -11
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -17,23 +17,20 @@
<div class="inp">
<input type="checkbox" />
<label
>a. Systems Administrator: Managing and maintaining computer systems
and networks.</label
>a. Systems Administrator: Managing and maintaining computer systems and
networks.</label
>
</div>
<div class="inp">
<input type="checkbox" />
<label
>b. Software Developer: Designing, coding, testing, and maintaining
software applications.</label
>b. Software Developer: Designing, coding, testing, and maintaining software
applications.</label
>
</div>
<div class="inp">
<input type="checkbox" />
<label>
c. Professional Chef: Creating delicious meals in a restaurant
kitchen.
</label>
<label> c. Professional Chef: Creating delicious meals in a restaurant kitchen. </label>
</div>
</div>
</section>
@@ -276,8 +273,8 @@
<section class="formulation">
<div class="qtext">
<p>
Gives a "reverseWorld" function in javascript which takes as a
parameter a word and flips it in the opposite direction
Gives a "reverseWorld" function in javascript which takes as a parameter a word and flips
it in the opposite direction
</p>
</div>
<div>
+5 -7
View File
@@ -1,21 +1,19 @@
/* Reset real moodle inputs to try in real env */
for (const option of document.querySelectorAll("option")) {
for (const option of document.querySelectorAll('option')) {
option.selected = false;
option.disabled = false;
option.closest("select").disabled = false;
option.closest('select').disabled = false;
}
for (const input of document.querySelectorAll(
'input[type="radio"], input[type="checkbox"]'
)) {
for (const input of document.querySelectorAll('input[type="radio"], input[type="checkbox"]')) {
input.checked = false;
input.disabled = false;
}
for (const icon of document.querySelectorAll(".text-danger, .text-success")) {
for (const icon of document.querySelectorAll('.text-danger, .text-success')) {
icon.remove();
}
for (const feedback of document.querySelectorAll(".specificfeedback")) {
for (const feedback of document.querySelectorAll('.specificfeedback')) {
feedback.remove();
}