48 Commits

Author SHA1 Message Date
yoannchb-pro 820baecc29 Merge pull request #6 from yoannchb-pro/dev
v1.0.3
2023-06-20 21:36:46 -04:00
yoannchb-pro d6575bc399 build 2023-06-20 21:35:21 -04:00
yoannchb-pro 145cad1874 removed double check causing error 2023-06-20 21:35:09 -04:00
yoannchb-pro 05670745d8 fiex question to answer 2023-06-20 21:29:27 -04:00
yoannchb-pro 69883870f6 better ratio to complete the question 2023-06-20 21:26:31 -04:00
yoannchb-pro 4d18aaf1b2 fixed put in order questions 2023-06-20 21:17:58 -04:00
yoannchb-pro 46b4ebb7b7 minified extension file 2023-06-20 20:31:08 -04:00
yoannchb-pro 68bac8b432 AI instructions 2023-06-20 18:50:15 -04:00
yoannchb-pro e33cc5b8af Update TODO.md 2023-06-20 18:50:06 -04:00
yoannchb-pro 3b22ffdd5f harder test 2023-06-20 18:42:52 -04:00
yoannchb-pro 1a6d5a63e0 added some informations for equations 2023-06-20 18:26:50 -04:00
yoannchb-pro a8dd6e2388 Update CHANGELOG.md 2023-06-20 18:22:32 -04:00
yoannchb-pro 54d05fedec fixed put in order 2023-06-20 18:22:07 -04:00
yoannchb-pro b0faf4c92f AI system instructions 2023-06-20 18:16:54 -04:00
yoannchb-pro b24a40aa65 description: version removed 2023-06-20 16:14:58 -04:00
yoannchb-pro 9745301815 Update TODO.md 2023-06-20 16:07:38 -04:00
yoannchb-pro 0eac796a5b Better logs for response 2023-06-20 15:02:38 -04:00
yoannchb-pro b67727a4ff Update TODO.md 2023-06-20 14:45:53 -04:00
yoannchb-pro e48e5e6785 fixed question infinite click 2023-06-20 14:45:49 -04:00
yoannchb-pro e26422a4a3 jsDoc for removeListener 2023-06-20 14:40:25 -04:00
yoannchb-pro a290733bf7 doc update 2023-06-20 14:34:27 -04:00
yoannchb-pro 305f3e452d Textbox, question to answser mode and clipboard mode is not formatted anymore 2023-06-20 14:29:29 -04:00
yoannchb-pro 6f40acd45f adjusted the abort to 15s and fixed can't click back after error 2023-06-20 13:57:36 -04:00
yoannchb-pro 68e7c07d4f Update README.md 2023-06-20 13:54:34 -04:00
yoannchb-pro 5b1608c5e8 added donate and need help link 2023-06-20 13:39:00 -04:00
yoannchb-pro 0e3ddf566b Update README.md 2023-06-20 13:38:48 -04:00
yoannchb-pro 9ad9d87c38 Update README.md 2023-06-20 13:33:08 -04:00
yoannchb-pro 7e521a50c2 buymeacoffee icon 2023-06-20 13:30:59 -04:00
yoannchb-pro 706dc315f8 webstore description text 2023-06-20 13:27:25 -04:00
yoannchb-pro 3232bcc548 webstore new assets 2023-06-20 13:24:19 -04:00
yoannchb-pro 9c6068df36 v1.0.3 assets mode/settings/popup 2023-06-20 12:47:20 -04:00
yoannchb-pro 00edfb5d29 better update checking
We now check if the new version is upper
2023-06-20 12:44:45 -04:00
yoannchb-pro 5c4581bf2d logo and title/version same line 2023-06-20 12:40:43 -04:00
yoannchb-pro 8574b73001 v1.0.3 2023-06-19 23:08:48 -04:00
yoannchb-pro 9dfe7acc58 v1.0.3 2023-06-19 23:08:04 -04:00
yoannchb-pro 47b239ae54 v1.0.3 2023-06-19 22:08:27 -04:00
yoannchb-pro 88088c0acf Merge pull request #5 from yoannchb-pro/main
main merge
2023-06-18 18:45:38 -04:00
yoannchb-pro f31252bf80 Update TODO.md 2023-06-14 22:45:50 -04:00
yoannchb-pro 656b84e4a9 Update TODO.md 2023-06-14 22:44:23 -04:00
yoannchb-pro 29364756c7 some docs update 2023-06-14 22:35:19 -04:00
yoannchb-pro f8ce415cdc donation message and chrome webstore message 2023-06-13 13:24:45 -04:00
yoannchb-pro 98728b52e7 Update README.md 2023-06-12 23:37:54 -04:00
yoannchb-pro 78942c20dd Merge pull request #3 from yoannchb-pro/dev
v1.0.2
2023-06-12 23:36:40 -04:00
yoannchb-pro 5fe25d0b34 Update README.md 2023-06-12 23:35:40 -04:00
yoannchb-pro e7a6e07856 extension assets 2023-06-12 23:34:54 -04:00
yoannchb-pro 0458137f0c v1.0.2 2023-06-12 23:13:47 -04:00
yoannchb-pro 7af54c40ad v1.0.2 2023-06-12 22:49:46 -04:00
yoannchb-pro 8292ae42a4 v1.0.2 2023-06-12 22:36:34 -04:00
45 changed files with 924 additions and 1031 deletions
+13
View File
@@ -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
+68 -10
View File
@@ -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/> ![Injected](./assets/title-injected.png)
- <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/> ![Typing](./assets/typing.gif)
- <b>Mouseover effect</b>: you will need to hover (or click for select) the question response to complete it automaticaly.
<br/> ![Mouseover](./assets/mouseover.gif)
<br/> ![Mouseover2](./assets/mouseover2.gif)
- <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
![Text](./assets/text.gif)
## 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.
+12
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+1 -460
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -9
View File
@@ -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"
}
+72 -36
View File
@@ -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>
+88 -26
View File
@@ -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();
});
+27 -5
View File
@@ -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();
+29 -7
View File
@@ -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;
}
+3 -4
View File
@@ -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"
}
}
+2 -1
View File
@@ -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()],
};
+26 -7
View File
@@ -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 };
+34
View File
@@ -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;
+24 -4
View File
@@ -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;
-32
View File
@@ -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;
+4 -6
View File
@@ -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;
+14 -6
View File
@@ -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;
+6 -3
View File
@@ -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;
+4 -3
View File
@@ -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) {
+39 -19
View File
@@ -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);
+7 -6
View File
@@ -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;
+44 -12
View File
@@ -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 -1
View File
@@ -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;
+15 -15
View File
@@ -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;
+6
View File
@@ -0,0 +1,6 @@
type GPTAnswer = {
response: string;
normalizedResponse: string;
};
export default GPTAnswer;
+5 -2
View File
@@ -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(" | ") + " |";
});
+4 -2
View File
@@ -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);
}
}
+15 -8
View File
@@ -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;
}
+258 -261
View File
@@ -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();
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

+11
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB