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