Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98728b52e7 | |||
| 78942c20dd | |||
| 5fe25d0b34 | |||
| e7a6e07856 | |||
| 0458137f0c | |||
| 7af54c40ad | |||
| 8292ae42a4 | |||
| f581bc2788 | |||
| ae9e528ac0 | |||
| d4c1e31745 | |||
| d6d6263e3d | |||
| 3dac74cf4c | |||
| 018877d907 | |||
| 76aefa56e0 | |||
| 3c03e444d1 | |||
| 1ed156363d | |||
| 79eb20bcca |
@@ -1,5 +1,15 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.0.2
|
||||
|
||||
- Added `mode`
|
||||
|
||||
## v1.0.1
|
||||
|
||||
- Removed langage
|
||||
- Added a button next to model to get the last ChatGPT version
|
||||
- Added update message
|
||||
|
||||
## v1.0.0
|
||||
|
||||
- Initial commit
|
||||
|
||||
@@ -2,14 +2,48 @@
|
||||
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>
|
||||
|
||||
# Moodle-gpt
|
||||
# MoodleGPT v1.0.2
|
||||
|
||||
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.
|
||||
|
||||
## Webstore
|
||||
|
||||
Find it on the chrome webstore "MoodleGPT"
|
||||
|
||||
## Summary
|
||||
|
||||
- [MoodleGPT v1.0.2](#moodlegpt-v102)
|
||||
- [Webstore](#webstore)
|
||||
- [Summary](#summary)
|
||||
- [Disclaimer !](#disclaimer-)
|
||||
- [Support](#support)
|
||||
- [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)
|
||||
- [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.
|
||||
|
||||
## Support
|
||||
|
||||
Will be a pleasure if you want to supprot this project :) -> Just right [here](https://www.buymeacoffee.com/yoannchbpro)
|
||||
|
||||
## Update
|
||||
|
||||
See [changelog](./CHANGELOG.md)
|
||||
@@ -20,7 +54,13 @@ If MoodleGPT cannot complete one of your moodle quiz please provide the html cod
|
||||
|
||||
## Set up
|
||||
|
||||
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 icons and enter the apiKey obtained from [openai](https://platform.openai.com/). Finally, enter a code that will activate the extension on your moodle page and click on the save button (The extension need to be configured before entering the moodle quiz).
|
||||
> 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
|
||||
|
||||
@@ -30,28 +70,38 @@ You just need to enter on the keyboard the <b>code</b> you have set into the ext
|
||||
|
||||
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>Api key\*</b>: the openai api key.
|
||||
- <b>Code\*</b>: code that you will need to inject/remove the code.
|
||||
- <b>Langage</b>: the langage you want chatgpt reply (if it's not set it will take the question langage).
|
||||
- <b>GPT Model</b>: the gpt model you want to use (by default it's "gpt-3.5-turbo").
|
||||
- <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.
|
||||
- <b>Code</b>: code that you will need to inject/remove the code.
|
||||
- <b>GPT Model</b>: the gpt model you want to use. You can click on the reload button to get the latest version of available gpt model for your account but you need to enter the api key first.
|
||||
- <b>Cursor indication</b>: show a pointer cursor and a hourglass to know when the request is finished.
|
||||
- <b>Title indication</b>: show some informations into the title to know for example if the code have been injected.
|
||||
<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 10seconds.
|
||||
- <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>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:
|
||||
|
||||
```
|
||||
| id | name | birthDate | cars |
|
||||
@@ -96,7 +146,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.
|
||||
|
||||
@@ -104,4 +154,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 with a local server on localhost. 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"</b> folder. Or a better solution is to install moodle locally.
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -13,29 +13,29 @@
|
||||
setTimeout(() => (document.title = backTitle), 3000);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
/******************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
}
|
||||
|
||||
class Logs {
|
||||
@@ -61,8 +61,8 @@
|
||||
*/
|
||||
function normalizeText(text) {
|
||||
return text
|
||||
.replace(/\n+/g, "\n")
|
||||
.replace(/[ \t]+/g, " ")
|
||||
.replace(/(\n\s*)+/gi, "\n")
|
||||
.replace(/[ \t]+/gi, " ")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/^[a-z\d]\.\s/gi, "") //a. text, b. text, c. text, 1. text, 2. text, 3.text
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
signal: config.timeout ? controller.signal : null,
|
||||
body: JSON.stringify({
|
||||
model: config.model && config.model !== "" ? config.model : "gpt-3.5-turbo",
|
||||
model: config.model,
|
||||
messages: [{ role: "user", content: question }],
|
||||
temperature: 0.8,
|
||||
top_p: 1.0,
|
||||
@@ -147,10 +147,8 @@
|
||||
question = question.replace(table.textContent, "\n" + htmlTableToString(table) + "\n");
|
||||
}
|
||||
}
|
||||
const finalQuestion = `Give a short response as possible for this question, reply in ${config.langage && config.langage !== ""
|
||||
? 'this langage "' + config.langage + '"'
|
||||
: "the following question langage"} and only show the result:
|
||||
${question}
|
||||
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);
|
||||
}
|
||||
@@ -384,6 +382,21 @@
|
||||
Logs.question(question);
|
||||
Logs.response(response);
|
||||
}
|
||||
if (config.mode === "clipboard") {
|
||||
return handleClipboard(config, response);
|
||||
}
|
||||
if (config.mode === "question-to-answer") {
|
||||
const questionBackup = form.textContent;
|
||||
const questionContainer = form.querySelector(".qtext");
|
||||
questionContainer.textContent = response;
|
||||
questionContainer.addEventListener("click", function () {
|
||||
questionContainer.textContent =
|
||||
questionContainer.textContent === questionBackup
|
||||
? response
|
||||
: questionBackup;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const handlers = [
|
||||
handleContentEditable,
|
||||
handleTextbox,
|
||||
@@ -395,6 +408,7 @@
|
||||
if (handler(config, inputList, response))
|
||||
return;
|
||||
}
|
||||
/** In the case we can't auto complete the question */
|
||||
handleClipboard(config, response);
|
||||
});
|
||||
}
|
||||
@@ -460,4 +474,4 @@
|
||||
});
|
||||
|
||||
}));
|
||||
//# sourceMappingURL=moodle-gpt.js.map
|
||||
//# sourceMappingURL=MoodleGPT.js.map
|
||||
|
After Width: | Height: | Size: 166 KiB |
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "moddle-gpt",
|
||||
"version": "1.0",
|
||||
"description": "Hidden chat-gpt for moodle cheat",
|
||||
"name": "MoodleGPT",
|
||||
"version": "1.0.2",
|
||||
"description": "Hidden chat-gpt for your moodle quiz",
|
||||
"permissions": ["activeTab", "tabs", "storage"],
|
||||
"action": {
|
||||
"default_icon": "icon.png",
|
||||
@@ -18,12 +18,8 @@
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"*://*/mod/quiz/*",
|
||||
"http://localhost:*/*",
|
||||
"http://127.0.0.1:*/*"
|
||||
],
|
||||
"js": ["moodle-gpt.js"],
|
||||
"matches": ["*://*/**/mod/quiz/*", "*://*/mod/quiz/*", "file:///*"],
|
||||
"js": ["MoodleGPT.js"],
|
||||
"run_at": "document_end"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,8 +6,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MoodleGPT</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="index.js" defer></script>
|
||||
<script src="./js/index.js" defer></script>
|
||||
<script src="./js/version.js" defer></script>
|
||||
<link rel="icon" type="image/png" href="../icon.png" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
@@ -18,24 +26,50 @@
|
||||
title="Mortarboard icons created by itim2101 - Flaticon"
|
||||
><img src="../icon.png" alt="icon"
|
||||
/></a>
|
||||
<h1>MoodleGPT</h1>
|
||||
<div class="line center">
|
||||
<label for="apiKey" class="textLabel">Api Key*</label>
|
||||
<input id="apiKey" type="password" />
|
||||
<div class="col center title">
|
||||
<h1>MoodleGPT</h1>
|
||||
<p id="version"></p>
|
||||
</div>
|
||||
<div class="line center">
|
||||
<label for="code" class="textLabel">Code*</label>
|
||||
<label for="apiKey" class="textLabel">Api Key</label>
|
||||
<input id="apiKey" type="text" />
|
||||
</div>
|
||||
<div class="line center">
|
||||
<label for="code" class="textLabel">Code</label>
|
||||
<input id="code" type="text" />
|
||||
</div>
|
||||
<div class="line center">
|
||||
<label for="langage" class="textLabel">Langage</label>
|
||||
<input id="langage" type="text" />
|
||||
</div>
|
||||
<div class="line center">
|
||||
<label for="model" class="textLabel">GPT Model</label>
|
||||
<input id="model" type="text" />
|
||||
<i
|
||||
id="reloadModel"
|
||||
class="fa-solid fa-rotate-right"
|
||||
disabled
|
||||
title="Provide an api key first"
|
||||
></i>
|
||||
</div>
|
||||
<div class="line center" style="margin-top: 1rem">
|
||||
<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="typing" type="checkbox" />
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
const saveBtn = document.querySelector(".save");
|
||||
const message = document.querySelector("#message");
|
||||
|
||||
const inputsText = ["apiKey", "code", "langage", "model"];
|
||||
const inputsCheckbox = [
|
||||
"logs",
|
||||
"title",
|
||||
"cursor",
|
||||
"typing",
|
||||
"mouseover",
|
||||
"infinite",
|
||||
"table",
|
||||
"timeout",
|
||||
];
|
||||
|
||||
function showMessage(messageTxt, valide) {
|
||||
message.style.color = valide ? "limegreen" : "red";
|
||||
message.textContent = messageTxt;
|
||||
message.style.display = "block";
|
||||
setTimeout(() => (message.style.display = "none"), 5000);
|
||||
}
|
||||
|
||||
//save the configuration
|
||||
saveBtn.addEventListener("click", function () {
|
||||
const [apiKey, code, langage, 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
|
||||
);
|
||||
|
||||
if (!apiKey || !code) {
|
||||
showMessage("Please comple all the form");
|
||||
return;
|
||||
}
|
||||
|
||||
if (code.length < 3) {
|
||||
showMessage("The code should at least contain 3 characters");
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage.sync.set({
|
||||
moodleGPT: {
|
||||
apiKey,
|
||||
code,
|
||||
langage,
|
||||
model,
|
||||
logs,
|
||||
title,
|
||||
cursor,
|
||||
typing,
|
||||
mouseover,
|
||||
infinite,
|
||||
table,
|
||||
timeout,
|
||||
},
|
||||
});
|
||||
|
||||
showMessage("Configuration saved", true);
|
||||
});
|
||||
|
||||
//we load back the configuration
|
||||
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
|
||||
if (storage.moodleGPT) {
|
||||
const config = storage.moodleGPT;
|
||||
inputsText.forEach(
|
||||
(key) => (document.querySelector("#" + key).value = config[key] || "")
|
||||
);
|
||||
inputsCheckbox.forEach(
|
||||
(key) => (document.querySelector("#" + key).checked = config[key] || "")
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
const saveBtn = document.querySelector(".save");
|
||||
const message = document.querySelector("#message");
|
||||
|
||||
/* inputs id */
|
||||
const inputsText = ["apiKey", "code", "model"];
|
||||
const inputsCheckbox = [
|
||||
"logs",
|
||||
"title",
|
||||
"cursor",
|
||||
"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;
|
||||
message.style.display = "block";
|
||||
setTimeout(() => (message.style.display = "none"), 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
const element = document.querySelector("#" + selector);
|
||||
return element.checked && element.parentElement.style.display !== "none";
|
||||
});
|
||||
|
||||
if (!apiKey || !code || !model) {
|
||||
showMessage("Please complete all the form");
|
||||
return;
|
||||
}
|
||||
|
||||
if (code.length < 3) {
|
||||
showMessage("The code should at least contain 3 characters");
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage.sync.set({
|
||||
moodleGPT: {
|
||||
apiKey,
|
||||
code,
|
||||
model,
|
||||
logs,
|
||||
title,
|
||||
cursor,
|
||||
typing,
|
||||
mouseover,
|
||||
infinite,
|
||||
table,
|
||||
timeout,
|
||||
mode: actualMode,
|
||||
},
|
||||
});
|
||||
|
||||
showMessage("Configuration saved", true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the last ChatGPT version
|
||||
*/
|
||||
function getLastChatGPTVersion() {
|
||||
const apiKeySelector = document.querySelector("#apiKey");
|
||||
const reloadModel = document.querySelector("#reloadModel");
|
||||
|
||||
let apiKey = apiKeySelector.value;
|
||||
|
||||
function checkFiledApiKey() {
|
||||
if (apiKey) {
|
||||
reloadModel.removeAttribute("disabled");
|
||||
reloadModel.setAttribute("title", "Get last ChatGPT version");
|
||||
return;
|
||||
}
|
||||
|
||||
reloadModel.setAttribute("disabled", true);
|
||||
reloadModel.setAttribute("title", "Provide an api key first");
|
||||
}
|
||||
|
||||
checkFiledApiKey();
|
||||
|
||||
apiKeySelector.addEventListener("input", function () {
|
||||
apiKey = apiKeySelector.value.trim();
|
||||
checkFiledApiKey();
|
||||
});
|
||||
|
||||
reloadModel.addEventListener("click", async function () {
|
||||
if (!apiKey) return;
|
||||
try {
|
||||
const req = await fetch("https://api.openai.com/v1/models", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
});
|
||||
const rep = await req.json();
|
||||
const model = rep.data.find((model) => model.id.includes("gpt"));
|
||||
document.querySelector("#model").value = model.root;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showMessage("Failed to fetch last ChatGPT version");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* 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();
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
const currentVersion = "1.0.2";
|
||||
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"
|
||||
);
|
||||
const rep = await req.json();
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = "https://github.com/yoannchb-pro/MoodleGPT";
|
||||
link.rel = "noopener noreferrer";
|
||||
link.target = "_blank";
|
||||
link.textContent = "v" + version;
|
||||
versionDisplay.appendChild(link);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
notifyUpdate();
|
||||
@@ -40,7 +40,7 @@ img {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
.title {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@@ -52,6 +52,7 @@ a {
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -98,8 +99,49 @@ 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;
|
||||
}
|
||||
|
||||
#message {
|
||||
display: none;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: -0.25rem;
|
||||
}
|
||||
|
||||
#reloadModel {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#reloadModel[disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moodle-gpt",
|
||||
"version": "1.0.0",
|
||||
"name": "moodlegpt",
|
||||
"version": "1.0.2",
|
||||
"description": "This extension allows you to hide CHAT-GPT in a Moodle quiz.",
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
|
||||
@@ -16,3 +16,7 @@ for (const input of document.querySelectorAll(
|
||||
for (const icon of document.querySelectorAll(".text-danger, .text-success")) {
|
||||
icon.remove();
|
||||
}
|
||||
|
||||
for (const feedback of document.querySelectorAll(".specificfeedback")) {
|
||||
feedback.remove();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = {
|
||||
input: "./src/index.ts",
|
||||
output: [
|
||||
{
|
||||
file: "./extension/moodle-gpt.js",
|
||||
file: "./extension/MoodleGPT.js",
|
||||
format: "umd",
|
||||
sourcemap: true,
|
||||
},
|
||||
|
||||
@@ -21,8 +21,7 @@ async function getChatGPTResponse(
|
||||
},
|
||||
signal: config.timeout ? controller.signal : null,
|
||||
body: JSON.stringify({
|
||||
model:
|
||||
config.model && config.model !== "" ? config.model : "gpt-3.5-turbo",
|
||||
model: config.model,
|
||||
messages: [{ role: "user", content: question }],
|
||||
temperature: 0.8,
|
||||
top_p: 1.0,
|
||||
|
||||
@@ -23,11 +23,7 @@ function normalizeQuestion(config: Config, questionContainer: HTMLElement) {
|
||||
}
|
||||
}
|
||||
|
||||
const finalQuestion = `Give a short response as possible for this question, reply in ${
|
||||
config.langage && config.langage !== ""
|
||||
? 'this langage "' + config.langage + '"'
|
||||
: "the following question langage"
|
||||
} and only show the result:
|
||||
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);
|
||||
|
||||
@@ -49,6 +49,23 @@ async function reply(
|
||||
Logs.response(response);
|
||||
}
|
||||
|
||||
if (config.mode === "clipboard") {
|
||||
return handleClipboard(config, response);
|
||||
}
|
||||
|
||||
if (config.mode === "question-to-answer") {
|
||||
const questionBackup = form.textContent;
|
||||
const questionContainer = form.querySelector(".qtext");
|
||||
questionContainer.textContent = response;
|
||||
questionContainer.addEventListener("click", function () {
|
||||
questionContainer.textContent =
|
||||
questionContainer.textContent === questionBackup
|
||||
? response
|
||||
: questionBackup;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const handlers = [
|
||||
handleContentEditable,
|
||||
handleTextbox,
|
||||
@@ -61,6 +78,7 @@ async function reply(
|
||||
if (handler(config, inputList, response)) return;
|
||||
}
|
||||
|
||||
/** In the case we can't auto complete the question */
|
||||
handleClipboard(config, response);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
type Config = {
|
||||
apiKey: string;
|
||||
code: string;
|
||||
langage?: string;
|
||||
model?: string;
|
||||
infinite?: boolean;
|
||||
typing?: boolean;
|
||||
@@ -11,6 +10,7 @@ type Config = {
|
||||
title?: boolean;
|
||||
table?: boolean;
|
||||
timeout?: boolean;
|
||||
mode?: "autocomplete" | "question-to-answer" | "clipboard";
|
||||
};
|
||||
|
||||
export default Config;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
function normalizeText(text: string) {
|
||||
return text
|
||||
.replace(/\n+/g, "\n")
|
||||
.replace(/[ \t]+/g, " ")
|
||||
.replace(/(\n\s*)+/gi, "\n")
|
||||
.replace(/[ \t]+/gi, " ")
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/^[a-z\d]\.\s/gi, "") //a. text, b. text, c. text, 1. text, 2. text, 3.text
|
||||
|
||||