Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 820baecc29 | |||
| d6575bc399 | |||
| 145cad1874 | |||
| 05670745d8 | |||
| 69883870f6 | |||
| 4d18aaf1b2 | |||
| 46b4ebb7b7 | |||
| 68bac8b432 | |||
| e33cc5b8af | |||
| 3b22ffdd5f | |||
| 1a6d5a63e0 | |||
| a8dd6e2388 | |||
| 54d05fedec | |||
| b0faf4c92f | |||
| b24a40aa65 | |||
| 9745301815 | |||
| 0eac796a5b | |||
| b67727a4ff | |||
| e48e5e6785 | |||
| e26422a4a3 | |||
| a290733bf7 | |||
| 305f3e452d | |||
| 6f40acd45f | |||
| 68e7c07d4f | |||
| 5b1608c5e8 | |||
| 0e3ddf566b | |||
| 9ad9d87c38 | |||
| 7e521a50c2 | |||
| 706dc315f8 | |||
| 3232bcc548 | |||
| 9c6068df36 | |||
| 00edfb5d29 | |||
| 5c4581bf2d | |||
| 8574b73001 | |||
| 9dfe7acc58 | |||
| 47b239ae54 | |||
| 88088c0acf | |||
| f31252bf80 | |||
| 656b84e4a9 | |||
| 29364756c7 | |||
| f8ce415cdc | |||
| 98728b52e7 | |||
| 78942c20dd | |||
| 5fe25d0b34 | |||
| e7a6e07856 | |||
| 0458137f0c | |||
| 7af54c40ad | |||
| 8292ae42a4 |
@@ -1,5 +1,18 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.0.3
|
||||
|
||||
- Removed the option `table formating` because it will now set to true by default
|
||||
- Adjusted the abort timeout to 15seconds
|
||||
- If an error occur the user can now click back on the question
|
||||
- `Textbox, question to answser mode and clipboard mode` is not formatted anymore
|
||||
- Fixed many bugs
|
||||
- Write AI system instructions
|
||||
|
||||
## v1.0.2
|
||||
|
||||
- Added `mode`
|
||||
|
||||
## v1.0.1
|
||||
|
||||
- Removed langage
|
||||
|
||||
@@ -2,14 +2,52 @@
|
||||
href="https://www.flaticon.com/free-icons/mortarboard" target="_blank" rel="noopener noreferrer"
|
||||
title="Mortarboard icons created by itim2101 - Flaticon" ><img src="./extension/icon.png" alt="Mortarboard icons created by itim2101 - Flaticon" width="150" style="display:block; margin:auto;"></a></p>
|
||||
|
||||
# MoodleGPT v1.0.1
|
||||
# MoodleGPT v1.0.3
|
||||
|
||||
This extension allows you to hide CHAT-GPT in a Moodle quiz. You just need to enter <b>the code configured in the extension</b> on the keyboard and then click on the question you want to solve, and CHAT-GPT will automatically provide the answer. However, one needs to be careful because as we know, CHAT-GPT can make errors especially in calculations.
|
||||
|
||||
## Chrome Webstore
|
||||
|
||||
I'm actually waiting for a review of my extension. It should be available in some days.
|
||||
|
||||
## Summary
|
||||
|
||||
- [MoodleGPT v1.0.3](#moodlegpt-v103)
|
||||
- [Chrome Webstore](#chrome-webstore)
|
||||
- [Summary](#summary)
|
||||
- [Disclaimer !](#disclaimer-)
|
||||
- [Donate](#donate)
|
||||
- [Update](#update)
|
||||
- [MoodleGPT don't complete my quiz ?](#moodlegpt-dont-complete-my-quiz-)
|
||||
- [Set up](#set-up)
|
||||
- [Inject the code into the moodle](#inject-the-code-into-the-moodle)
|
||||
- [Remove injection](#remove-injection)
|
||||
- [Mode](#mode)
|
||||
- [Settings](#settings)
|
||||
- [Internal Features](#internal-features)
|
||||
- [Support table](#support-table)
|
||||
- [Supported questions type](#supported-questions-type)
|
||||
- [Select](#select)
|
||||
- [Put in order question](#put-in-order-question)
|
||||
- [Resolve equation](#resolve-equation)
|
||||
- [One response (radio button)](#one-response-radio-button)
|
||||
- [Multiples responses (checkbox)](#multiples-responses-checkbox)
|
||||
- [True or false](#true-or-false)
|
||||
- [Number](#number)
|
||||
- [Text](#text)
|
||||
- [What about if the question can't be completed ?](#what-about-if-the-question-cant-be-completed-)
|
||||
- [Test](#test)
|
||||
|
||||
## Disclaimer !
|
||||
|
||||
I hereby declare that I am not responsible for any misuse or illegal activities carried out using my program. The code is provided for educational and research purposes only, and any use of it outside of these purposes is at the user's own risk.
|
||||
|
||||
## Donate
|
||||
|
||||
Will be a pleasure if you want to support this project :)
|
||||
<br/>
|
||||
<a href="https://www.buymeacoffee.com/yoannchbpro" target="_blank" rel="noopener noreferrer"><img src="./assets/bmc-button.png" alt="Mortarboard icons created by itim2101 - Flaticon" width="150"></a>
|
||||
|
||||
## Update
|
||||
|
||||
See [changelog](./CHANGELOG.md)
|
||||
@@ -22,20 +60,35 @@ If MoodleGPT cannot complete one of your moodle quiz please provide the html cod
|
||||
|
||||
> NOTE: This extension only works on Chromium-based browsers like Edge, Chrome, etc. Unfortunately, Firefox requires a click on the extension, which is not very discreet.
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/setup.png" alt="Popup" width="300">
|
||||
</p>
|
||||
|
||||
Go to <b>"Manage my extensions"</b> on your browser, then click on <b>"Load unpacked extension"</b> and select the <b>"extension"</b> folder. Afterwards, click on the extension icon and enter the apiKey obtained from [openai](https://platform.openai.com/) and enter a <b>code</b> that will activate the extension on your moodle page. Finally, click on the <b>reload button</b> next to model (it should give you the last ChatGPT version, otherwise enter it by your self) and click on the save button (The extension need to be configured before entering the moodle quiz).
|
||||
|
||||
## Inject the code into the moodle
|
||||
|
||||
You just need to enter on the keyboard the <b>code</b> you have set into the extension and clique on the question you want to solve.
|
||||
You just need to enter on the keyboard the <b>code</b> you have set into the extension and click on the question you want to solve.
|
||||
|
||||
## Remove injection
|
||||
|
||||
Type back the <b>code</b> on the keyboard and the code will be removed from the current page.
|
||||
|
||||
## Options
|
||||
## Mode
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/popup.png" alt="Popup" height="300">
|
||||
<img src="./assets/mode.png" alt="Popup" width="300">
|
||||
</p>
|
||||
|
||||
- <b>Autocomplete:</b> The extension will complete the question for you.
|
||||
- <b>Clipboard:</b> The response is copied into the clipboard.
|
||||
- <b>Question to answer:</b> The question is converted to the answer and you can click on it to show back the question (or show back the answer).
|
||||
<br/><img src="./assets/question-to-answer.gif" alt="Question to Answer">
|
||||
|
||||
## Settings
|
||||
|
||||
<p align="center">
|
||||
<img src="./assets/settings.png" alt="Popup" width="300">
|
||||
</p>
|
||||
|
||||
- <b>Api key</b>: the openai api key.
|
||||
@@ -46,13 +99,20 @@ Type back the <b>code</b> on the keyboard and the code will be removed from the
|
||||
<br/> 
|
||||
- <b>Console logs</b>: show logs into the console.
|
||||
<br/><img src="./assets/logs.png" alt="Logs" width="250">
|
||||
- <b>Request timeout</b>: if the request is too long it will be abort after 10seconds.
|
||||
- <b>Request timeout</b>: if the request is too long it will be abort after 15seconds.
|
||||
- <b>Typing effect</b>: create a typing effect for text. Type any text and it will be replaced by the correct one. If you want to stop it press <b>Backspace</b> key.
|
||||
<br/> 
|
||||
- <b>Mouseover effect</b>: you will need to hover (or click for select) the question response to complete it automaticaly.
|
||||
<br/> 
|
||||
<br/> 
|
||||
- <b>Table formatting</b>: format table from the question to make it more readable for CHAT-GPT but cost most tokens (so if the question is too large it will make an error). Example of formatted table:
|
||||
|
||||
- <b>Infinite try</b>: click as much as you want on the question (don't forget to reset the question).
|
||||
|
||||
## Internal Features
|
||||
|
||||
### Support table
|
||||
|
||||
Table are formated from the question to make it more readable for CHAT-GPT. Example of formatted table output:
|
||||
|
||||
```
|
||||
| id | name | birthDate | cars |
|
||||
@@ -61,8 +121,6 @@ Type back the <b>code</b> on the keyboard and the code will be removed from the
|
||||
| Person 2 | Yann | 19/01/2000 | no |
|
||||
```
|
||||
|
||||
- <b>Infinite try</b>: click as much as you want on the question (don't forget to reset the question).
|
||||
|
||||
## Supported questions type
|
||||
|
||||
### Select
|
||||
@@ -97,7 +155,7 @@ Type back the <b>code</b> on the keyboard and the code will be removed from the
|
||||
|
||||

|
||||
|
||||
## If it can't complete the question, the answer will be copied to your clipboard
|
||||
## What about if the question can't be completed ?
|
||||
|
||||
To know if the answer has been copied to the clipboard, you can look at the title of the page which will become <b>"Copied to clipboard"</b> for 3 seconds.
|
||||
|
||||
@@ -105,4 +163,4 @@ To know if the answer has been copied to the clipboard, you can look at the titl
|
||||
|
||||
## Test
|
||||
|
||||
To test the code, you can run the index.html file located in the <b>"test"</b> folder. Or a better solution is to install moodle locally.
|
||||
To test the code, you can run the index.html file located in the <b>"test/fake-moodle"</b> folder. Or a better solution is to install moodle locally.
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
# TODO
|
||||
|
||||
## Priority: 1
|
||||
|
||||
- [ ] Fixe put in order
|
||||
- [ ] Make some tests
|
||||
|
||||
## Priority: 2
|
||||
|
||||
## Priority: 3 (because hard to make)
|
||||
|
||||
- [ ] Increment question when there is statement (hard because it is often on another page)
|
||||
- [ ] Support math equation from image stocked in the `data-mathml` attribute
|
||||
- [ ] Try something to understand images like (image -> ascii or may be using other AI ?)
|
||||
- [ ] Support multiple input type in a question
|
||||
- [ ] Support drag and drop quiz
|
||||
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "MoodleGPT",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "Hidden chat-gpt for your moodle quiz",
|
||||
"permissions": ["activeTab", "tabs", "storage"],
|
||||
"permissions": ["storage"],
|
||||
"action": {
|
||||
"default_icon": "icon.png",
|
||||
"default_popup": "./popup/index.html"
|
||||
@@ -18,13 +18,7 @@
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"*://*/**/mod/quiz/*",
|
||||
"*://*/mod/quiz/*",
|
||||
"http://localhost:*/*",
|
||||
"http://127.0.0.1:*/*",
|
||||
"file:///*"
|
||||
],
|
||||
"matches": ["*://*/**/mod/quiz/*", "*://*/mod/quiz/*", "file:///*"],
|
||||
"js": ["MoodleGPT.js"],
|
||||
"run_at": "document_end"
|
||||
}
|
||||
|
||||
@@ -19,16 +19,18 @@
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://www.flaticon.com/free-icons/mortarboard"
|
||||
title="Mortarboard icons created by itim2101 - Flaticon"
|
||||
><img src="../icon.png" alt="icon"
|
||||
/></a>
|
||||
<div class="col center title">
|
||||
<h1>MoodleGPT</h1>
|
||||
<p id="version"></p>
|
||||
<div class="line center" style="margin-top: 1rem; margin-bottom: 1rem">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://www.flaticon.com/free-icons/mortarboard"
|
||||
title="Mortarboard icons created by itim2101 - Flaticon"
|
||||
><img src="../icon.png" alt="icon"
|
||||
/></a>
|
||||
<div class="col center title">
|
||||
<h1>MoodleGPT</h1>
|
||||
<p id="version"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line center">
|
||||
<label for="apiKey" class="textLabel">Api Key</label>
|
||||
@@ -48,25 +50,28 @@
|
||||
title="Provide an api key first"
|
||||
></i>
|
||||
</div>
|
||||
<div class="line center" style="margin-top: 1rem">
|
||||
<div class="col">
|
||||
<div class="line">
|
||||
<input id="typing" type="checkbox" />
|
||||
<label for="typing">Typing effect</label>
|
||||
</div>
|
||||
<div class="line">
|
||||
<input id="mouseover" type="checkbox" />
|
||||
<label for="mouseover">Mouseover effect</label>
|
||||
</div>
|
||||
<div class="line">
|
||||
<input id="table" type="checkbox" checked />
|
||||
<label for="table">Table formatting</label>
|
||||
</div>
|
||||
<div class="line">
|
||||
<input id="infinite" type="checkbox" />
|
||||
<label for="infinite">Infinite try</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line mt">
|
||||
<i class="fa-solid fa-robot"></i>
|
||||
<p>Mode:</p>
|
||||
</div>
|
||||
<div class="line">
|
||||
<ul id="mode" class="line center">
|
||||
<li><button value="autocomplete">autocomplete</button></li>
|
||||
<li>
|
||||
<button value="clipboard" class="not-selected">clipboard</button>
|
||||
</li>
|
||||
<li>
|
||||
<button value="question-to-answer" class="not-selected">
|
||||
question to answer
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="line mt">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<p>Settings:</p>
|
||||
</div>
|
||||
<div class="line center">
|
||||
<div class="col">
|
||||
<div class="line">
|
||||
<input id="logs" type="checkbox" />
|
||||
@@ -85,18 +90,49 @@
|
||||
<label for="timeout">Request timeout</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="line">
|
||||
<input id="typing" type="checkbox" />
|
||||
<label for="typing">Typing effect</label>
|
||||
</div>
|
||||
<div class="line">
|
||||
<input id="mouseover" type="checkbox" />
|
||||
<label for="mouseover">Mouseover effect</label>
|
||||
</div>
|
||||
<div class="line">
|
||||
<input id="infinite" type="checkbox" />
|
||||
<label for="infinite">Infinite try</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p id="message">Message</p>
|
||||
<div class="line center">
|
||||
<button class="save">Save</button>
|
||||
</div>
|
||||
<a
|
||||
href="https://github.com/yoannchb-pro/MoodleGPT"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
See the documentation
|
||||
</a>
|
||||
<div class="line center">
|
||||
<a
|
||||
href="https://www.buymeacoffee.com/yoannchbpro"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Donate
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/yoannchb-pro/MoodleGPT"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
See the documentation
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/yoannchb-pro/MoodleGPT/issues"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Need Help
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const saveBtn = document.querySelector(".save");
|
||||
const message = document.querySelector("#message");
|
||||
|
||||
/* inputs id */
|
||||
const inputsText = ["apiKey", "code", "model"];
|
||||
const inputsCheckbox = [
|
||||
"logs",
|
||||
@@ -9,10 +10,24 @@ const inputsCheckbox = [
|
||||
"typing",
|
||||
"mouseover",
|
||||
"infinite",
|
||||
"table",
|
||||
"timeout",
|
||||
];
|
||||
|
||||
const mode = document.querySelector("#mode");
|
||||
const modes = mode.querySelectorAll("button");
|
||||
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"],
|
||||
};
|
||||
|
||||
/**
|
||||
* Show message into the popup
|
||||
* @param {string} messageTxt
|
||||
* @param {boolean} valide
|
||||
*/
|
||||
function showMessage(messageTxt, valide) {
|
||||
message.style.color = valide ? "limegreen" : "red";
|
||||
message.textContent = messageTxt;
|
||||
@@ -20,15 +35,48 @@ function showMessage(messageTxt, valide) {
|
||||
setTimeout(() => (message.style.display = "none"), 5000);
|
||||
}
|
||||
|
||||
//save the configuration
|
||||
/**
|
||||
* Handle when a mode change to show specific input
|
||||
*/
|
||||
function handleModeChange() {
|
||||
const needDisable = disabledForThisMode[actualMode];
|
||||
const dontNeedDisable = inputsCheckbox.filter(
|
||||
(input) => !needDisable.includes(input)
|
||||
);
|
||||
for (const id of needDisable) {
|
||||
document.querySelector("#" + id).parentElement.style.display = "none";
|
||||
}
|
||||
for (const id of dontNeedDisable) {
|
||||
document.querySelector("#" + id).parentElement.style.display = null;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mode handler */
|
||||
modes.forEach((button) => {
|
||||
button.addEventListener("click", function () {
|
||||
const value = button.value;
|
||||
actualMode = value;
|
||||
for (const mode of modes) {
|
||||
if (mode.value !== value) {
|
||||
mode.classList.add("not-selected");
|
||||
} else {
|
||||
mode.classList.remove("not-selected");
|
||||
}
|
||||
}
|
||||
handleModeChange();
|
||||
});
|
||||
});
|
||||
|
||||
/* Save the configuration */
|
||||
saveBtn.addEventListener("click", function () {
|
||||
const [apiKey, code, model] = inputsText.map((selector) =>
|
||||
document.querySelector("#" + selector).value.trim()
|
||||
);
|
||||
const [logs, title, cursor, typing, mouseover, infinite, table, timeout] =
|
||||
inputsCheckbox.map(
|
||||
(selector) => document.querySelector("#" + selector).checked
|
||||
);
|
||||
const [logs, title, cursor, typing, mouseover, infinite, timeout] =
|
||||
inputsCheckbox.map((selector) => {
|
||||
const element = document.querySelector("#" + selector);
|
||||
return element.checked && element.parentElement.style.display !== "none";
|
||||
});
|
||||
|
||||
if (!apiKey || !code || !model) {
|
||||
showMessage("Please complete all the form");
|
||||
@@ -51,33 +99,17 @@ saveBtn.addEventListener("click", function () {
|
||||
typing,
|
||||
mouseover,
|
||||
infinite,
|
||||
table,
|
||||
timeout,
|
||||
mode: actualMode,
|
||||
},
|
||||
});
|
||||
|
||||
showMessage("Configuration saved", true);
|
||||
});
|
||||
|
||||
//we load back the configuration
|
||||
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
|
||||
const config = storage.moodleGPT;
|
||||
|
||||
if (config) {
|
||||
inputsText.forEach((key) =>
|
||||
config[key]
|
||||
? (document.querySelector("#" + key).value = config[key])
|
||||
: null
|
||||
);
|
||||
inputsCheckbox.forEach(
|
||||
(key) => (document.querySelector("#" + key).checked = config[key] || "")
|
||||
);
|
||||
}
|
||||
|
||||
getLastChatGPTVersion();
|
||||
});
|
||||
|
||||
//getting the last chatgpt version
|
||||
/**
|
||||
* Get the last ChatGPT version
|
||||
*/
|
||||
function getLastChatGPTVersion() {
|
||||
const apiKeySelector = document.querySelector("#apiKey");
|
||||
const reloadModel = document.querySelector("#reloadModel");
|
||||
@@ -119,3 +151,33 @@ function getLastChatGPTVersion() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* we load back the configuration */
|
||||
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
|
||||
const config = storage.moodleGPT;
|
||||
|
||||
if (config) {
|
||||
if (config.mode) {
|
||||
actualMode = config.mode;
|
||||
for (const mode of modes) {
|
||||
if (mode.value === config.mode) {
|
||||
mode.classList.remove("not-selected");
|
||||
} else {
|
||||
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] || "")
|
||||
);
|
||||
}
|
||||
|
||||
handleModeChange();
|
||||
getLastChatGPTVersion();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
const currentVersion = "1.0.1";
|
||||
const currentVersion = "1.0.3";
|
||||
const versionDisplay = document.querySelector("#version");
|
||||
|
||||
/**
|
||||
* Get the last version from the github
|
||||
* @returns
|
||||
*/
|
||||
async function getLastVersion() {
|
||||
const req = await fetch(
|
||||
"https://raw.githubusercontent.com/yoannchb-pro/MoodleGPT/main/package.json"
|
||||
@@ -9,6 +13,12 @@ async function getLastVersion() {
|
||||
return rep.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the version or an update message
|
||||
* @param {string} version
|
||||
* @param {boolean} isCurrent
|
||||
* @returns
|
||||
*/
|
||||
function setVersion(version, isCurrent = true) {
|
||||
if (isCurrent) {
|
||||
versionDisplay.textContent = "v" + version;
|
||||
@@ -24,16 +34,28 @@ function setVersion(version, isCurrent = true) {
|
||||
versionDisplay.appendChild(document.createTextNode(" is now available !"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the extension neeed an update or no
|
||||
*/
|
||||
async function notifyUpdate() {
|
||||
const lastVersion = await getLastVersion().catch((err) => {
|
||||
console.error(err);
|
||||
return currentVersion;
|
||||
});
|
||||
if (currentVersion !== lastVersion) {
|
||||
setVersion(lastVersion, false);
|
||||
} else {
|
||||
setVersion(currentVersion);
|
||||
|
||||
const lastVertionSplitted = lastVersion.split(".");
|
||||
const currentVersionSplitted = currentVersion.split(".");
|
||||
const minVersionLength = Math.min(
|
||||
lastVertionSplitted.length,
|
||||
currentVersionSplitted.length
|
||||
);
|
||||
|
||||
for (let i = 0; i < minVersionLength; ++i) {
|
||||
if (parseInt(lastVertionSplitted[i]) > parseInt(currentVersionSplitted[i]))
|
||||
return setVersion(lastVersion, false);
|
||||
}
|
||||
|
||||
setVersion(currentVersion);
|
||||
}
|
||||
|
||||
notifyUpdate();
|
||||
|
||||
@@ -37,12 +37,6 @@ main {
|
||||
|
||||
img {
|
||||
width: 5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -52,6 +46,7 @@ a {
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -78,7 +73,6 @@ a {
|
||||
|
||||
.line input[type="checkbox"] {
|
||||
accent-color: var(--btn-color);
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
.col {
|
||||
@@ -98,6 +92,34 @@ a {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mt {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.not-selected {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
#mode li {
|
||||
list-style: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#mode {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#mode button {
|
||||
background-color: var(--btn-color);
|
||||
border: none;
|
||||
text-align: center;
|
||||
padding: 0.3rem 0.75rem;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#version {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moodlegpt",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "This extension allows you to hide CHAT-GPT in a Moodle quiz.",
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
@@ -23,11 +23,10 @@
|
||||
},
|
||||
"homepage": "https://github.com/yoannchb-pro/MoodleGPT#readme",
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.224",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"rollup": "^3.20.0",
|
||||
"rollup-plugin-ts": "^3.2.0",
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/chrome": "^0.0.224"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const ts = require("rollup-plugin-ts");
|
||||
const terser = require("@rollup/plugin-terser");
|
||||
|
||||
const config = require("./tsconfig.json");
|
||||
|
||||
@@ -11,5 +12,5 @@ module.exports = {
|
||||
sourcemap: true,
|
||||
},
|
||||
],
|
||||
plugins: [ts(config)],
|
||||
plugins: [ts(config), terser()],
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ function codeListener(config: Config) {
|
||||
* @returns
|
||||
*/
|
||||
function setUpMoodleGpt(config: Config) {
|
||||
//removing events
|
||||
/* Removing events */
|
||||
if (listeners.length > 0) {
|
||||
for (const listener of listeners) {
|
||||
if (config.cursor) listener.element.style.cursor = "initial";
|
||||
@@ -40,23 +40,42 @@ function setUpMoodleGpt(config: Config) {
|
||||
return;
|
||||
}
|
||||
|
||||
//injection
|
||||
/* Code injection */
|
||||
const inputQuery = ["checkbox", "radio", "text", "number"]
|
||||
.map((e) => `input[type="${e}"]`)
|
||||
.join(",");
|
||||
const query = inputQuery + ", textarea, select, [contenteditable]";
|
||||
const forms = Array.from(document.querySelectorAll(".formulation"));
|
||||
const forms = document.querySelectorAll(".formulation");
|
||||
|
||||
for (const form of forms) {
|
||||
const hiddenButton: HTMLElement = form.querySelector(".qtext");
|
||||
|
||||
if (config.cursor) hiddenButton.style.cursor = "pointer";
|
||||
const fn = reply.bind(null, config, hiddenButton, form, query);
|
||||
listeners.push({ element: hiddenButton, fn });
|
||||
hiddenButton.addEventListener("click", fn, { once: !config.infinite });
|
||||
|
||||
const injectionFunction = reply.bind(
|
||||
null,
|
||||
config,
|
||||
hiddenButton,
|
||||
form,
|
||||
query
|
||||
);
|
||||
listeners.push({ element: hiddenButton, fn: injectionFunction });
|
||||
hiddenButton.addEventListener("click", injectionFunction);
|
||||
}
|
||||
|
||||
if (config.title) titleIndications("Injected");
|
||||
}
|
||||
|
||||
export default codeListener;
|
||||
/**
|
||||
* Remove the event listener on a specific question
|
||||
* @param element
|
||||
*/
|
||||
function removeListener(element: HTMLElement) {
|
||||
const index = listeners.findIndex((listener) => listener.element === element);
|
||||
if (index !== -1) {
|
||||
const listener = listeners.splice(index, 1)[0];
|
||||
listener.element.removeEventListener("click", listener.fn);
|
||||
}
|
||||
}
|
||||
|
||||
export { codeListener, removeListener };
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import Config from "../types/config";
|
||||
import normalizeText from "../utils/normalize-text";
|
||||
import htmlTableToString from "../utils/html-table-to-string";
|
||||
|
||||
/**
|
||||
* Normalize the question and add sub informations
|
||||
* @param langage
|
||||
* @param question
|
||||
* @returns
|
||||
*/
|
||||
function createQuestion(config: Config, questionContainer: HTMLElement) {
|
||||
let question = questionContainer.innerText;
|
||||
|
||||
/* We remove unnecessary information */
|
||||
const accesshideElements: NodeListOf<HTMLElement> =
|
||||
questionContainer.querySelectorAll(".accesshide");
|
||||
for (const useless of accesshideElements) {
|
||||
question = question.replace(useless.innerText, "");
|
||||
}
|
||||
|
||||
/* Make tables more readable for chat-gpt */
|
||||
const tables: NodeListOf<HTMLTableElement> =
|
||||
questionContainer.querySelectorAll(".qtext table");
|
||||
for (const table of tables) {
|
||||
question = question.replace(
|
||||
table.innerText,
|
||||
"\n" + htmlTableToString(table) + "\n"
|
||||
);
|
||||
}
|
||||
|
||||
return normalizeText(question, false);
|
||||
}
|
||||
|
||||
export default createQuestion;
|
||||
@@ -1,4 +1,5 @@
|
||||
import Config from "../types/config";
|
||||
import GPTAnswer from "../types/gptAnswer";
|
||||
import normalizeText from "../utils/normalize-text";
|
||||
|
||||
/**
|
||||
@@ -10,9 +11,9 @@ import normalizeText from "../utils/normalize-text";
|
||||
async function getChatGPTResponse(
|
||||
config: Config,
|
||||
question: string
|
||||
): Promise<string> {
|
||||
): Promise<GPTAnswer> {
|
||||
const controller = new AbortController();
|
||||
const timeoutControler = setTimeout(() => controller.abort(), 10000);
|
||||
const timeoutControler = setTimeout(() => controller.abort(), 15000);
|
||||
const req = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -22,7 +23,23 @@ async function getChatGPTResponse(
|
||||
signal: config.timeout ? controller.signal : null,
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
messages: [{ role: "user", content: question }],
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `
|
||||
Follow those rules:
|
||||
- Sometimes there won't be a question, so just answer the statement as you normally would without following the other rules and give the most detailled and complete answer with explication.
|
||||
- For put in order question just give the good order separate by new line
|
||||
- Your goal is to understand the statement and to reply to each question by giving only the answer.
|
||||
- You will keep the same order for the answers like in the text.
|
||||
- You will separate all the answer with new lines and only show the correctes one.
|
||||
- You will only give the answers for each question and omit the questions, statement, title or other informations from the response.
|
||||
- You will only give answer with exactly the same text as the gived answers.
|
||||
- The question always have the good answer so you should always give an answer to the question.
|
||||
- You will always respond in the same langage as the user question.`,
|
||||
},
|
||||
{ role: "user", content: question },
|
||||
],
|
||||
temperature: 0.8,
|
||||
top_p: 1.0,
|
||||
presence_penalty: 1.0,
|
||||
@@ -32,7 +49,10 @@ async function getChatGPTResponse(
|
||||
clearTimeout(timeoutControler);
|
||||
const rep = await req.json();
|
||||
const response = rep.choices[0].message.content;
|
||||
return normalizeText(response);
|
||||
return {
|
||||
response,
|
||||
normalizedResponse: normalizeText(response),
|
||||
};
|
||||
}
|
||||
|
||||
export default getChatGPTResponse;
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import Config from "../types/config";
|
||||
import normalizeText from "../utils/normalize-text";
|
||||
import htmlTableToString from "../utils/html-table-to-string";
|
||||
|
||||
/**
|
||||
* Normalize the question and add sub informations
|
||||
* @param langage
|
||||
* @param question
|
||||
* @returns
|
||||
*/
|
||||
function normalizeQuestion(config: Config, questionContainer: HTMLElement) {
|
||||
let question = questionContainer.textContent;
|
||||
|
||||
if (config.table) {
|
||||
//make table more readable for chat-gpt
|
||||
const tables: NodeListOf<HTMLTableElement> =
|
||||
questionContainer.querySelectorAll(".qtext table");
|
||||
for (const table of tables) {
|
||||
question = question.replace(
|
||||
table.textContent,
|
||||
"\n" + htmlTableToString(table) + "\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const finalQuestion = `Give a short response as possible for this question, reply in the following question langage and only show the result:
|
||||
${question}
|
||||
(If you have to choose between multiple results only show the corrects one, separate them with new line and take the same text as the question)`;
|
||||
return normalizeText(finalQuestion);
|
||||
}
|
||||
|
||||
export default normalizeQuestion;
|
||||
@@ -1,17 +1,15 @@
|
||||
import Config from "../../types/config";
|
||||
import GPTAnswer from "../../types/gptAnswer";
|
||||
import titleIndications from "../../utils/title-indications";
|
||||
|
||||
/**
|
||||
* Copy the response in the clipboard if we can automaticaly fill the question
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param response
|
||||
* @param force Force the copy to clipboard
|
||||
* @returns
|
||||
* @param gptAnswer
|
||||
*/
|
||||
function handleClipboard(config: Config, response: string) {
|
||||
function handleClipboard(config: Config, gptAnswer: GPTAnswer) {
|
||||
if (config.title) titleIndications("Copied to clipboard");
|
||||
navigator.clipboard.writeText(response);
|
||||
navigator.clipboard.writeText(gptAnswer.response);
|
||||
}
|
||||
|
||||
export default handleClipboard;
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import Config from "../../types/config";
|
||||
import GPTAnswer from "../../types/gptAnswer";
|
||||
|
||||
/**
|
||||
* Hanlde contenteditable elements
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param gptAnswer
|
||||
* @returns
|
||||
*/
|
||||
function handleContentEditable(
|
||||
config: Config,
|
||||
inputList: NodeListOf<HTMLElement>,
|
||||
response: string
|
||||
gptAnswer: GPTAnswer
|
||||
): boolean {
|
||||
const input = inputList[0];
|
||||
|
||||
@@ -16,12 +24,12 @@ function handleContentEditable(
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
input.addEventListener("keydown", function (event: KeyboardEvent) {
|
||||
if (event.key === "Backspace") index = response.length + 1;
|
||||
if (index > response.length) return;
|
||||
if (event.key === "Backspace") index = gptAnswer.response.length + 1;
|
||||
if (index > gptAnswer.response.length) return;
|
||||
event.preventDefault();
|
||||
input.textContent = response.slice(0, ++index);
|
||||
input.textContent = gptAnswer.response.slice(0, ++index);
|
||||
|
||||
//put the cursor at the end
|
||||
/* Put the cursor at the end of the typed text */
|
||||
input.focus();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(input);
|
||||
@@ -31,7 +39,7 @@ function handleContentEditable(
|
||||
selection.addRange(range);
|
||||
});
|
||||
} else {
|
||||
input.textContent = response;
|
||||
input.textContent = gptAnswer.response;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import Config from "../../types/config";
|
||||
import GPTAnswer from "../../types/gptAnswer";
|
||||
|
||||
/**
|
||||
* Handle number input
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param response
|
||||
* @param gptAnswer
|
||||
* @returns
|
||||
*/
|
||||
function handleNumber(
|
||||
config: Config,
|
||||
inputList: NodeListOf<HTMLElement>,
|
||||
response: string
|
||||
gptAnswer: GPTAnswer
|
||||
): boolean {
|
||||
const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;
|
||||
|
||||
if (inputList.length !== 1 || input.type !== "number") return false;
|
||||
|
||||
const number = response.match(/\d+([,\.]\d+)?/gi)?.[0]?.replace(",", ".");
|
||||
const number = gptAnswer.normalizedResponse
|
||||
.match(/\d+([,\.]\d+)?/gi)?.[0]
|
||||
?.replace(",", ".");
|
||||
|
||||
if (!number) return false;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Config from "../../types/config";
|
||||
import GPTAnswer from "../../types/gptAnswer";
|
||||
import Logs from "../../utils/logs";
|
||||
import normalizeText from "../../utils/normalize-text";
|
||||
|
||||
@@ -6,12 +7,12 @@ import normalizeText from "../../utils/normalize-text";
|
||||
* Handle checkbox and input elements
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param response
|
||||
* @param gptAnswer
|
||||
*/
|
||||
function handleRadioAndCheckbox(
|
||||
config: Config,
|
||||
inputList: NodeListOf<HTMLElement>,
|
||||
response: string
|
||||
gptAnswer: GPTAnswer
|
||||
): boolean {
|
||||
const input = inputList?.[0] as HTMLInputElement;
|
||||
|
||||
@@ -20,7 +21,7 @@ function handleRadioAndCheckbox(
|
||||
|
||||
for (const input of inputList as NodeListOf<HTMLInputElement>) {
|
||||
const content = normalizeText(input.parentNode.textContent);
|
||||
const valide = response.includes(content);
|
||||
const valide = gptAnswer.normalizedResponse.includes(content);
|
||||
if (config.logs) Logs.responseTry(content, valide);
|
||||
if (valide) {
|
||||
if (config.mouseover) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Config from "../../types/config";
|
||||
import GPTAnswer from "../../types/gptAnswer";
|
||||
import Logs from "../../utils/logs";
|
||||
import normalizeText from "../../utils/normalize-text";
|
||||
|
||||
@@ -6,22 +7,39 @@ import normalizeText from "../../utils/normalize-text";
|
||||
* Handle select elements (and put in order select)
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param response
|
||||
* @param gptAnswer
|
||||
* @returns
|
||||
*/
|
||||
function handleSelect(
|
||||
config: Config,
|
||||
inputList: NodeListOf<HTMLElement>,
|
||||
response: string
|
||||
gptAnswer: GPTAnswer
|
||||
): boolean {
|
||||
if (inputList.length === 0 || inputList[0].tagName !== "SELECT") return false;
|
||||
|
||||
let correct = response.split("\n");
|
||||
if (correct.length === 1 && correct.length !== inputList.length)
|
||||
correct = response.split(",");
|
||||
let correct = gptAnswer.normalizedResponse.split("\n");
|
||||
|
||||
if (config.logs) Logs.array(correct);
|
||||
|
||||
/**
|
||||
* Sometimes ChatGPT give the question so we should remove them
|
||||
* Example:
|
||||
* 5*5
|
||||
* 25
|
||||
* 10+10
|
||||
* 20
|
||||
* 20-10
|
||||
* 10
|
||||
*
|
||||
* And we only want to keep answers
|
||||
* 25
|
||||
* 20
|
||||
* 10
|
||||
*/
|
||||
if (correct.length === inputList.length * 2) {
|
||||
correct = correct.filter((answer, index) => index % 2 === 1);
|
||||
}
|
||||
|
||||
for (let j = 0; j < inputList.length; ++j) {
|
||||
const options = inputList[j].querySelectorAll("option");
|
||||
|
||||
@@ -29,34 +47,36 @@ function handleSelect(
|
||||
const content = normalizeText(option.textContent);
|
||||
const valide = correct[j].includes(content);
|
||||
|
||||
//if it's a put in order
|
||||
if (!isNaN(parseInt(content))) {
|
||||
const content = normalizeText(
|
||||
(option.parentNode as HTMLElement)
|
||||
.closest("tr")
|
||||
.querySelector(".text").textContent
|
||||
);
|
||||
const index = correct.findIndex((c) => {
|
||||
const valide = c.includes(content);
|
||||
/* Handle put in order question */
|
||||
if (!/[^\d]+/gi.test(content)) {
|
||||
const elementTitle = (option.parentNode as HTMLElement)
|
||||
.closest("tr")
|
||||
.querySelector(".text");
|
||||
const content = normalizeText(elementTitle.textContent);
|
||||
|
||||
const indexCorrectAnswer = correct.findIndex((answer) => {
|
||||
const valide = answer.includes(content);
|
||||
if (config.logs) Logs.responseTry(content, valide);
|
||||
return valide;
|
||||
});
|
||||
if (index !== -1) {
|
||||
|
||||
if (indexCorrectAnswer !== -1) {
|
||||
//we do + 1 because we skip the first option: Choose...
|
||||
if (config.mouseover) {
|
||||
options[index + 1].closest("select").addEventListener(
|
||||
options[indexCorrectAnswer + 1].closest("select").addEventListener(
|
||||
"click",
|
||||
function () {
|
||||
options[index + 1].selected = "selected" as any;
|
||||
options[indexCorrectAnswer + 1].selected = "selected" as any;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
} else {
|
||||
options[index + 1].selected = "selected" as any;
|
||||
options[indexCorrectAnswer + 1].selected = "selected" as any;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//end put in order
|
||||
/* End */
|
||||
|
||||
if (config.logs) Logs.responseTry(content, valide);
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import Config from "../../types/config";
|
||||
import GPTAnswer from "../../types/gptAnswer";
|
||||
|
||||
/**
|
||||
* Handle textbox
|
||||
* @param config
|
||||
* @param inputList
|
||||
* @param response
|
||||
* @param gptAnswer
|
||||
* @returns
|
||||
*/
|
||||
function handleTextbox(
|
||||
config: Config,
|
||||
inputList: NodeListOf<HTMLElement>,
|
||||
response: string
|
||||
gptAnswer: GPTAnswer
|
||||
): boolean {
|
||||
const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;
|
||||
|
||||
@@ -23,13 +24,13 @@ function handleTextbox(
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
input.addEventListener("keydown", function (event: KeyboardEvent) {
|
||||
if (event.key === "Backspace") index = response.length + 1;
|
||||
if (index > response.length) return;
|
||||
if (event.key === "Backspace") index = gptAnswer.response.length + 1;
|
||||
if (index > gptAnswer.response.length) return;
|
||||
event.preventDefault();
|
||||
input.value = response.slice(0, ++index);
|
||||
input.value = gptAnswer.response.slice(0, ++index);
|
||||
});
|
||||
} else {
|
||||
input.value = response;
|
||||
input.value = gptAnswer.response;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import Config from "../types/config";
|
||||
import Logs from "../utils/logs";
|
||||
import getChatGPTResponse from "./get-response";
|
||||
import normalizeQuestion from "./normalize-question";
|
||||
import createQuestion from "./create-question";
|
||||
import handleRadioAndCheckbox from "./questions/radio-checkbox";
|
||||
import handleSelect from "./questions/select";
|
||||
import handleTextbox from "./questions/textbox";
|
||||
import handleClipboard from "./questions/clipboard";
|
||||
import handleNumber from "./questions/number";
|
||||
import handleContentEditable from "./questions/contenteditable";
|
||||
import { removeListener } from "./code-listener";
|
||||
|
||||
/**
|
||||
* Reply to the question
|
||||
@@ -25,30 +26,60 @@ async function reply(
|
||||
) {
|
||||
if (config.cursor) hiddenButton.style.cursor = "wait";
|
||||
|
||||
form.querySelector(".accesshide")?.remove();
|
||||
|
||||
const question = normalizeQuestion(config, form);
|
||||
const question = createQuestion(config, form);
|
||||
const inputList: NodeListOf<HTMLElement> = form.querySelectorAll(query);
|
||||
|
||||
const response = await getChatGPTResponse(config, question).catch(
|
||||
const gptAnswer = await getChatGPTResponse(config, question).catch(
|
||||
(error) => ({
|
||||
error,
|
||||
})
|
||||
);
|
||||
|
||||
if (config.cursor)
|
||||
hiddenButton.style.cursor = config.infinite ? "pointer" : "initial";
|
||||
const haveError = typeof gptAnswer === "object" && "error" in gptAnswer;
|
||||
|
||||
if (typeof response === "object" && "error" in response) {
|
||||
console.error(response.error);
|
||||
if (config.cursor)
|
||||
hiddenButton.style.cursor =
|
||||
config.infinite || haveError ? "pointer" : "initial";
|
||||
|
||||
if (haveError) {
|
||||
console.error(gptAnswer.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.logs) {
|
||||
Logs.question(question);
|
||||
Logs.response(response);
|
||||
Logs.response(gptAnswer);
|
||||
}
|
||||
|
||||
/* Handle clipboard mode */
|
||||
if (config.mode === "clipboard") {
|
||||
if (!config.infinite) removeListener(hiddenButton);
|
||||
return handleClipboard(config, gptAnswer);
|
||||
}
|
||||
|
||||
/* Handle question to answer mode */
|
||||
if (config.mode === "question-to-answer") {
|
||||
removeListener(hiddenButton);
|
||||
|
||||
const questionContainer = form.querySelector<HTMLElement>(".qtext");
|
||||
const questionBackup = questionContainer.textContent;
|
||||
|
||||
questionContainer.textContent = gptAnswer.response;
|
||||
questionContainer.style.whiteSpace = "pre-wrap";
|
||||
|
||||
questionContainer.addEventListener("click", function () {
|
||||
const isNotResponse = questionContainer.textContent === questionBackup;
|
||||
questionContainer.style.whiteSpace = isNotResponse ? "pre-wrap" : null;
|
||||
questionContainer.textContent = isNotResponse
|
||||
? gptAnswer.response
|
||||
: questionBackup;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/* Better then set once on the event because if there is an error the user can click an other time on the question */
|
||||
if (!config.infinite) removeListener(hiddenButton);
|
||||
|
||||
const handlers = [
|
||||
handleContentEditable,
|
||||
handleTextbox,
|
||||
@@ -58,10 +89,11 @@ async function reply(
|
||||
];
|
||||
|
||||
for (const handler of handlers) {
|
||||
if (handler(config, inputList, response)) return;
|
||||
if (handler(config, inputList, gptAnswer)) return;
|
||||
}
|
||||
|
||||
handleClipboard(config, response);
|
||||
/* In the case we can't auto complete the question */
|
||||
handleClipboard(config, gptAnswer);
|
||||
}
|
||||
|
||||
export default reply;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import codeListener from "./core/code-listener";
|
||||
import { codeListener } from "./core/code-listener";
|
||||
|
||||
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
|
||||
const config = storage.moodleGPT;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
type Config = {
|
||||
apiKey: string;
|
||||
code: string;
|
||||
model?: string;
|
||||
infinite?: boolean;
|
||||
typing?: boolean;
|
||||
mouseover?: boolean;
|
||||
cursor?: boolean;
|
||||
logs?: boolean;
|
||||
title?: boolean;
|
||||
table?: boolean;
|
||||
timeout?: boolean;
|
||||
};
|
||||
|
||||
export default Config;
|
||||
type Config = {
|
||||
apiKey: string;
|
||||
code: string;
|
||||
model?: string;
|
||||
infinite?: boolean;
|
||||
typing?: boolean;
|
||||
mouseover?: boolean;
|
||||
cursor?: boolean;
|
||||
logs?: boolean;
|
||||
title?: boolean;
|
||||
timeout?: boolean;
|
||||
mode?: "autocomplete" | "question-to-answer" | "clipboard";
|
||||
};
|
||||
|
||||
export default Config;
|
||||
@@ -0,0 +1,6 @@
|
||||
type GPTAnswer = {
|
||||
response: string;
|
||||
normalizedResponse: string;
|
||||
};
|
||||
|
||||
export default GPTAnswer;
|
||||
@@ -26,8 +26,11 @@ function htmlTableToString(table: HTMLTableElement) {
|
||||
"\n" + Array(lineSeparationSize).fill("-").join("") + "\n";
|
||||
|
||||
const mappedTab = tab.map((line) => {
|
||||
const mappedLine = line.map(
|
||||
(content, index) => content.padEnd(maxColumnsLength[index], "\u00A0") //for no matching with \s
|
||||
const mappedLine = line.map((content, index) =>
|
||||
content.padEnd(
|
||||
maxColumnsLength[index],
|
||||
"\u00A0" /* For no matching with \s */
|
||||
)
|
||||
);
|
||||
return "| " + mappedLine.join(" | ") + " |";
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import GPTAnswer from "../types/gptAnswer";
|
||||
class Logs {
|
||||
static question(text: string) {
|
||||
const css = "color: cyan";
|
||||
@@ -13,8 +14,9 @@ class Logs {
|
||||
console.log("[CORRECTS] ", arr);
|
||||
}
|
||||
|
||||
static response(text: string) {
|
||||
console.log(text);
|
||||
static response(gptAnswer: GPTAnswer) {
|
||||
console.log("Original:\n" + gptAnswer.response);
|
||||
console.log("Normalized:\n" + gptAnswer.normalizedResponse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
* Normlize text
|
||||
* @param text
|
||||
*/
|
||||
function normalizeText(text: string) {
|
||||
return text
|
||||
.replace(/\n+/g, "\n")
|
||||
.replace(/[ \t]+/g, " ")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.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
|
||||
function normalizeText(text: string, toLowerCase: boolean = true) {
|
||||
let normalizedText = text
|
||||
.replace(/\n+/gi, "\n") //remove duplicate new lines
|
||||
.replace(/(\n\s*\n)+/g, "\n") //remove useless white sapce from textcontent
|
||||
.replace(/[ \t]+/gi, " "); //replace multiples space or tabs by a space
|
||||
|
||||
if (toLowerCase) normalizedText = normalizedText.toLowerCase();
|
||||
|
||||
return (
|
||||
normalizedText
|
||||
.trim()
|
||||
/* We remove that 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
|
||||
);
|
||||
}
|
||||
|
||||
export default normalizeText;
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
@font-face {
|
||||
font-family: Segeo UI;
|
||||
src: url(../../extension/fonts/Segoe\ UI.ttf);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Segeo UI";
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.formulation {
|
||||
width: 60%;
|
||||
background-color: #e7f3f5;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.inp {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
select {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.editable {
|
||||
background-color: #fff;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
padding: 0.5rem;
|
||||
outline: none;
|
||||
border: thin solid #000;
|
||||
}
|
||||
|
||||
textarea {
|
||||
outline: none;
|
||||
padding: 0.5rem;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Segeo UI;
|
||||
src: url(../../../extension/fonts/Segoe\ UI.ttf);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Segeo UI";
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.formulation {
|
||||
width: 60%;
|
||||
background-color: #e7f3f5;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.inp {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
select {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.editable {
|
||||
background-color: #fff;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
padding: 0.5rem;
|
||||
outline: none;
|
||||
border: thin solid #000;
|
||||
}
|
||||
|
||||
textarea {
|
||||
outline: none;
|
||||
padding: 0.5rem;
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -1,261 +1,258 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Moodle test</title>
|
||||
<link rel="stylesheet" href="./css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- checkbox -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Which words are animals ?</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<input type="checkbox" />
|
||||
<label>a. Cat</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="checkbox" />
|
||||
<label>b. Dog</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="checkbox" />
|
||||
<label>c. Computer</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Radio -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>What is the french president name ?</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>Emmanuel Macron</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>Jean Macron</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>Yves Macron</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- True or false -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>The cat sometimes drink milk?</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>True</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>False</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Number -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>What is the result of 17/20</p>
|
||||
</div>
|
||||
<div>
|
||||
<input type="number" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Choose the correct answer</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>I am a feline</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>Cat</option>
|
||||
<option>Dog</option>
|
||||
<option>Cow</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>I am a descendant of the wolf</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>Cat</option>
|
||||
<option>Dog</option>
|
||||
<option>Cow</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="text"><p>I produce milk</p></td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>Cat</option>
|
||||
<option>Dog</option>
|
||||
<option>Cow</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select Number -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>
|
||||
Put the three steps needed in a general sense for a computer program
|
||||
to solve the problem in the correct order
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>Understand the problem</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>Carry out the plan and write the actual code</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>Create a step-by-step plan for how you'll solve it</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select Calc -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Solve those equations:</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>1. 5*5</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>25</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>2. 20 - 10</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>25</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="text"><p>3. 10+10</p></td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>25</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Text -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Give me five diferences between a dog and a cat</p>
|
||||
</div>
|
||||
<div>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Clipboard -->
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div contenteditable="true" class="editable"></div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Moodle test</title>
|
||||
<link rel="stylesheet" href="./css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- checkbox -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Which words are animals ?</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<input type="checkbox" />
|
||||
<label>a. Cat</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="checkbox" />
|
||||
<label>b. Dog</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="checkbox" />
|
||||
<label>c. Computer</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Radio -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>What is the french president name ?</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>Emmanuel Macron</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>Jean Macron</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>Yves Macron</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- True or false -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>The cat sometimes drink milk?</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>True</label>
|
||||
</div>
|
||||
<div class="inp">
|
||||
<input type="radio" />
|
||||
<label>False</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Number -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>What is the result of 17/20</p>
|
||||
</div>
|
||||
<div>
|
||||
<input type="number" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Choose the correct answer</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>I am a feline</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>Cat</option>
|
||||
<option>Cow</option>
|
||||
<option>Dog</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>I am a descendant of the wolf</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>Cat</option>
|
||||
<option>Cow</option>
|
||||
<option>Dog</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="text"><p>I produce milk</p></td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>Cat</option>
|
||||
<option>Cow</option>
|
||||
<option>Dog</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select Number -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Put in order the step to create a java program.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>Write java code</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>Execute the java executable file</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>Compile the java code</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Select Calc -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Solve those equations:</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inp">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>1. 5*5 = ?</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>25</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text">
|
||||
<p>2. 20 - 10 = ?</p>
|
||||
</td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>25</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="text"><p>3. 10+10 = ?</p></td>
|
||||
<td>
|
||||
<select>
|
||||
<option>Choose...</option>
|
||||
<option>10</option>
|
||||
<option>20</option>
|
||||
<option>25</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Text -->
|
||||
<section class="formulation">
|
||||
<div class="qtext">
|
||||
<p>Give me five diferences between a dog and a cat</p>
|
||||
</div>
|
||||
<div>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Clipboard -->
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div contenteditable="true" class="editable"></div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,18 +1,21 @@
|
||||
//to try in real moodle env
|
||||
|
||||
for (const option of document.querySelectorAll("option")) {
|
||||
option.selected = false;
|
||||
option.disabled = false;
|
||||
option.closest("select").disabled = false;
|
||||
}
|
||||
|
||||
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")) {
|
||||
icon.remove();
|
||||
}
|
||||
/* Reset real moodle inputs to try in real env */
|
||||
for (const option of document.querySelectorAll("option")) {
|
||||
option.selected = false;
|
||||
option.disabled = false;
|
||||
option.closest("select").disabled = false;
|
||||
}
|
||||
|
||||
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")) {
|
||||
icon.remove();
|
||||
}
|
||||
|
||||
for (const feedback of document.querySelectorAll(".specificfeedback")) {
|
||||
feedback.remove();
|
||||
}
|
||||
|
After Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 87 KiB |
@@ -0,0 +1,11 @@
|
||||
MoodleGPT is a Chrome extension that allows you to enhance your Moodle quiz experience. With this extension, you can hide the CHAT-GPT feature and effortlessly solve quiz questions. Simply enter the code provided by the extension and click on the question you want to solve, and CHAT-GPT will automatically provide the answer.
|
||||
|
||||
This extension supports various question types, including Select, Put in Order, Resolve Equation, One Response (radio button), Multiple Responses (checkbox), True or False, Number, and Text. It also provides different modes, such as Autocomplete, Clipboard, and Question to Answer, giving you flexibility in how you interact with the answers.
|
||||
|
||||
To use MoodleGPT, you need to set it up by loading the unpacked extension in your browser's extension management page. Enter the API key obtained from OpenAI and a unique code to activate the extension on your Moodle page. You can customize the settings, including the GPT model, cursor indication, title indication, and more.
|
||||
|
||||
MoodleGPT simplifies and accelerates the process of completing Moodle quizzes, making it easier for users to obtain accurate answers. Download the extension now and enhance your Moodle quiz experience!
|
||||
|
||||
Github: https://github.com/yoannchb-pro/MoodleGPT
|
||||
Donation: https://www.buymeacoffee.com/yoannchbpro
|
||||
Icon credits: Mortarboard icons created by itim2101 - Flaticon
|
||||
|
After Width: | Height: | Size: 19 KiB |