prettier formatage

This commit is contained in:
yoannchb-pro
2024-02-28 19:07:19 -05:00
parent 8c364e286b
commit 6a5e1eb8c3
40 changed files with 1666 additions and 1295 deletions
+11 -11
View File
@@ -1,18 +1,18 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
ignorePatterns: ["node_modules/"],
ignorePatterns: ['node_modules/'],
overrides: [
{
files: ["extension/popup/*.js", "src/**/*.ts"],
rules: {},
},
],
files: ['extension/popup/*.js', 'src/**/*.ts'],
rules: {}
}
]
};
+10
View File
@@ -0,0 +1,10 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"printWidth": 100,
"trailingComma": "none",
"arrowParens": "avoid",
"endOfLine": "auto"
}
+33 -33
View File
@@ -1,33 +1,33 @@
# CHANGELOG
## v1.0.4
- `code` is not required anymore
- Issue [#9](https://github.com/yoannchb-pro/MoodleGPT/issues/9) resolved
- GPT version button fixed
- Better algorithm to find the correct answer (levenshtein distance)
- Better ChatGPT prompt
- Added `history` to the options/configuration
## 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
- Added a button next to model to get the last ChatGPT version
- Added update message
## v1.0.0
- Initial commit
# CHANGELOG
## v1.0.4
- `code` is not required anymore
- Issue [#9](https://github.com/yoannchb-pro/MoodleGPT/issues/9) resolved
- GPT version button fixed
- Better algorithm to find the correct answer (levenshtein distance)
- Better ChatGPT prompt
- Added `history` to the options/configuration
## 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
- Added a button next to model to get the last ChatGPT version
- Added update message
## v1.0.0
- Initial commit
+167 -167
View File
@@ -1,167 +1,167 @@
<p align="center"><a
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
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
Find the extension on the Chrome Webstore right [here](https://chrome.google.com/webstore/detail/moodlegpt/fgiepdkoifhpcgdhbiikpgdapjdoemko)
## Summary
- [MoodleGPT](#moodlegpt)
- [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)
## MoodleGPT don't complete my quiz ?
If MoodleGPT cannot complete one of your moodle quiz please provide the html code of the page. It will help us to add it in the futur version of MoodleGPT ! Check the [TODO](./TODO.md) to see what is comming in the futur version.
## Set up
> 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 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.
## Mode
<p align="center">
<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.
- <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/> ![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 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>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 |
----------------------------------------
| Person 1 | Yvick | 15/08/1999 | yes |
| Person 2 | Yann | 19/01/2000 | no |
```
## Supported questions type
### Select
![Select](./assets/select.gif)
### Put in order question
![Order](./assets/order.gif)
### Resolve equation
![Equations](./assets/equations.gif)
### One response (radio button)
![Radio](./assets/radio.gif)
### Multiples responses (checkbox)
![Checkbox](./assets/checkbox.gif)
### True or false
![True-false](./assets/true-false.gif)
### Number
![Number](./assets/number.gif)
### Text
![Text](./assets/text.gif)
## 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.
![Clipboard](./assets/clipboard.gif)
## Test
- <b>Solution 1</b>: Go on [this moodle test page](https://school.moodledemo.net/login/index.php) (username: `student`, password: `moodle`) and choose any quiz.
- <b>Solution 2</b>: Run the `index.html` file located in the `test/fake-moodle` folder.
<p align="center"><a
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
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
Find the extension on the Chrome Webstore right [here](https://chrome.google.com/webstore/detail/moodlegpt/fgiepdkoifhpcgdhbiikpgdapjdoemko)
## Summary
- [MoodleGPT](#moodlegpt)
- [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)
## MoodleGPT don't complete my quiz ?
If MoodleGPT cannot complete one of your moodle quiz please provide the html code of the page. It will help us to add it in the futur version of MoodleGPT ! Check the [TODO](./TODO.md) to see what is comming in the futur version.
## Set up
> 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 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.
## Mode
<p align="center">
<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.
- <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/> ![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 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>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 |
----------------------------------------
| Person 1 | Yvick | 15/08/1999 | yes |
| Person 2 | Yann | 19/01/2000 | no |
```
## Supported questions type
### Select
![Select](./assets/select.gif)
### Put in order question
![Order](./assets/order.gif)
### Resolve equation
![Equations](./assets/equations.gif)
### One response (radio button)
![Radio](./assets/radio.gif)
### Multiples responses (checkbox)
![Checkbox](./assets/checkbox.gif)
### True or false
![True-false](./assets/true-false.gif)
### Number
![Number](./assets/number.gif)
### Text
![Text](./assets/text.gif)
## 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.
![Clipboard](./assets/clipboard.gif)
## Test
- <b>Solution 1</b>: Go on [this moodle test page](https://school.moodledemo.net/login/index.php) (username: `student`, password: `moodle`) and choose any quiz.
- <b>Solution 2</b>: Run the `index.html` file located in the `test/fake-moodle` folder.
+7 -7
View File
@@ -1,7 +1,7 @@
# TODO
- Historic for questions (implemented but need testing)
- Contributing.md
- Better prompt (Fixe put in order question, Fixe calculation question)
- Support math equation from image stocked in the `data-mathml` attribute
- Better assets
# TODO
- Historic for questions (implemented but need testing)
- Contributing.md / Fixe readme.md 'MoodleGPT don't complete my quiz ?'
- Better prompt (Fixe put in order question, Fixe calculation question)
- Support math equation from image stocked in the `data-mathml` attribute
- Better assets
+418 -1
View File
File diff suppressed because one or more lines are too long
+26 -26
View File
@@ -1,26 +1,26 @@
{
"manifest_version": 3,
"name": "MoodleGPT",
"version": "1.0.4",
"description": "Hidden chat-gpt for your moodle quiz",
"permissions": ["storage"],
"action": {
"default_icon": "icon.png",
"default_popup": "./popup/index.html"
},
"icons": {
"16": "icon.png",
"32": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"content_scripts": [
{
"matches": ["*://*/**/mod/quiz/*", "*://*/mod/quiz/*", "file:///*"],
"js": ["MoodleGPT.js"],
"run_at": "document_end"
}
]
}
{
"manifest_version": 3,
"name": "MoodleGPT",
"version": "1.0.4",
"description": "Hidden chat-gpt for your moodle quiz",
"permissions": ["storage"],
"action": {
"default_icon": "icon.png",
"default_popup": "./popup/index.html"
},
"icons": {
"16": "icon.png",
"32": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"content_scripts": [
{
"matches": ["*://*/**/mod/quiz/*", "*://*/mod/quiz/*", "file:///*"],
"js": ["MoodleGPT.js"],
"run_at": "document_end"
}
]
}
+148 -154
View File
@@ -1,154 +1,148 @@
<!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>MoodleGPT</title>
<link rel="stylesheet" href="style.css" />
<!-- Should always be at the top -->
<script src="./js/utils.js" defer></script>
<script src="./js/index.js" defer></script>
<script src="./js/modeHandler.js" defer></script>
<script src="./js/gptVersion.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>
<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<span class="required">*</span>:</label
>
<input id="apiKey" type="text" />
</div>
<div class="line center">
<label for="model" class="textLabel"
>GPT Model<span class="required">*</span>:</label
>
<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">
<label for="code" class="textLabel">Code:</label>
<input id="code" type="text" />
</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" style="gap: 2rem">
<div class="col">
<div class="line">
<input id="logs" type="checkbox" />
<label for="logs">Console logs</label>
</div>
<div class="line">
<input id="title" type="checkbox" checked />
<label for="title">Title indication</label>
</div>
<div class="line">
<input id="cursor" type="checkbox" checked />
<label for="cursor">Cursor indication</label>
</div>
<div class="line">
<input id="timeout" type="checkbox" checked />
<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 class="line">
<input id="history" type="checkbox" />
<label for="history">Save history</label>
</div>
</div>
</div>
<p id="message">Message</p>
<div class="line center">
<button class="save">Save</button>
</div>
<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>
<!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>MoodleGPT</title>
<link rel="stylesheet" href="style.css" />
<!-- Should always be at the top -->
<script src="./js/utils.js" defer></script>
<script src="./js/index.js" defer></script>
<script src="./js/modeHandler.js" defer></script>
<script src="./js/gptVersion.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>
<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<span class="required">*</span>:</label>
<input id="apiKey" type="text" />
</div>
<div class="line center">
<label for="model" class="textLabel">GPT Model<span class="required">*</span>:</label>
<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">
<label for="code" class="textLabel">Code:</label>
<input id="code" type="text" />
</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" style="gap: 2rem">
<div class="col">
<div class="line">
<input id="logs" type="checkbox" />
<label for="logs">Console logs</label>
</div>
<div class="line">
<input id="title" type="checkbox" checked />
<label for="title">Title indication</label>
</div>
<div class="line">
<input id="cursor" type="checkbox" checked />
<label for="cursor">Cursor indication</label>
</div>
<div class="line">
<input id="timeout" type="checkbox" checked />
<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 class="line">
<input id="history" type="checkbox" />
<label for="history">Save history</label>
</div>
</div>
</div>
<p id="message">Message</p>
<div class="line center">
<button class="save">Save</button>
</div>
<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>
+15 -15
View File
@@ -1,49 +1,49 @@
"use strict";
'use strict';
/**
* Get the last ChatGPT version
*/
function getLastChatGPTVersion() {
const apiKeySelector = document.querySelector("#apiKey");
const reloadModel = document.querySelector("#reloadModel");
const apiKeySelector = document.querySelector('#apiKey');
const reloadModel = document.querySelector('#reloadModel');
let apiKey = apiKeySelector.value;
// If the api key is set we enable the button to get the last chatgpt version
function checkFiledApiKey() {
if (apiKey) {
reloadModel.removeAttribute("disabled");
reloadModel.setAttribute("title", "Get last ChatGPT version");
reloadModel.removeAttribute('disabled');
reloadModel.setAttribute('title', 'Get last ChatGPT version');
return;
}
reloadModel.setAttribute("disabled", true);
reloadModel.setAttribute("title", "Provide an api key first");
reloadModel.setAttribute('disabled', true);
reloadModel.setAttribute('title', 'Provide an api key first');
}
checkFiledApiKey();
// Check if the api key is set
apiKeySelector.addEventListener("input", function () {
apiKeySelector.addEventListener('input', function () {
apiKey = apiKeySelector.value.trim();
checkFiledApiKey();
});
// Event listener to handle a click on the relaod icon button
reloadModel.addEventListener("click", async function () {
reloadModel.addEventListener('click', async function () {
if (!apiKey) return;
try {
const req = await fetch("https://api.openai.com/v1/models", {
const req = await fetch('https://api.openai.com/v1/models', {
headers: {
Authorization: `Bearer ${apiKey}`,
},
Authorization: `Bearer ${apiKey}`
}
});
const rep = await req.json();
const model = rep.data.find((model) => model.id.startsWith("gpt"));
document.querySelector("#model").value = model.id;
const model = rep.data.find(model => model.id.startsWith('gpt'));
document.querySelector('#model').value = model.id;
} catch (err) {
console.error(err);
showMessage({ msg: "Failed to fetch last ChatGPT version", error: true });
showMessage({ msg: 'Failed to fetch last ChatGPT version', error: true });
}
});
}
+31 -34
View File
@@ -1,38 +1,39 @@
const saveBtn = document.querySelector(".save");
const saveBtn = document.querySelector('.save');
/* inputs id */
const inputsText = ["apiKey", "code", "model"];
const inputsText = ['apiKey', 'code', 'model'];
const inputsCheckbox = [
"logs",
"title",
"cursor",
"typing",
"mouseover",
"infinite",
"timeout",
"history",
'logs',
'title',
'cursor',
'typing',
'mouseover',
'infinite',
'timeout',
'history'
];
/* Save the configuration */
saveBtn.addEventListener("click", function () {
const [apiKey, code, model] = inputsText.map((selector) =>
document.querySelector("#" + selector).value.trim()
saveBtn.addEventListener('click', function () {
const [apiKey, code, model] = inputsText.map(selector =>
document.querySelector('#' + selector).value.trim()
);
const [logs, title, cursor, typing, mouseover, infinite, timeout, history] = inputsCheckbox.map(
selector => {
const element = document.querySelector('#' + selector);
return element.checked && element.parentElement.style.display !== 'none';
}
);
const [logs, title, cursor, typing, mouseover, infinite, timeout, history] =
inputsCheckbox.map((selector) => {
const element = document.querySelector("#" + selector);
return element.checked && element.parentElement.style.display !== "none";
});
if (!apiKey || !model) {
showMessage({ msg: "Please complete all the form", error: true });
showMessage({ msg: 'Please complete all the form', error: true });
return;
}
if (code.length > 0 && code.length < 3) {
showMessage({
msg: "The code should at least contain 3 characters",
error: true,
msg: 'The code should at least contain 3 characters',
error: true
});
return;
}
@@ -50,15 +51,15 @@ saveBtn.addEventListener("click", function () {
infinite,
timeout,
history,
mode: actualMode,
},
mode: actualMode
}
});
showMessage({ msg: "Configuration saved" });
showMessage({ msg: 'Configuration saved' });
});
/* we load back the configuration */
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
chrome.storage.sync.get(['moodleGPT']).then(function (storage) {
const config = storage.moodleGPT;
if (config) {
@@ -66,21 +67,17 @@ chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
actualMode = config.mode;
for (const mode of modes) {
if (mode.value === config.mode) {
mode.classList.remove("not-selected");
mode.classList.remove('not-selected');
} else {
mode.classList.add("not-selected");
mode.classList.add('not-selected');
}
}
}
inputsText.forEach((key) =>
config[key]
? (document.querySelector("#" + key).value = config[key])
: null
);
inputsCheckbox.forEach(
(key) => (document.querySelector("#" + key).checked = config[key] || "")
inputsText.forEach(key =>
config[key] ? (document.querySelector('#' + key).value = config[key]) : null
);
inputsCheckbox.forEach(key => (document.querySelector('#' + key).checked = config[key] || ''));
}
handleModeChange();
+12 -14
View File
@@ -1,15 +1,15 @@
"use strict";
'use strict';
const mode = document.querySelector("#mode");
const modes = mode.querySelectorAll("button");
const mode = document.querySelector('#mode');
const modes = mode.querySelectorAll('button');
let actualMode = "autocomplete";
let actualMode = 'autocomplete';
/* inputs id that need to be disabled for a specific mode */
const disabledForThisMode = {
autocomplete: [],
clipboard: ["typing", "mouseover"],
"question-to-answer": ["typing", "infinite", "mouseover"],
clipboard: ['typing', 'mouseover'],
'question-to-answer': ['typing', 'infinite', 'mouseover']
};
/**
@@ -17,27 +17,25 @@ const disabledForThisMode = {
*/
function handleModeChange() {
const needDisable = disabledForThisMode[actualMode];
const dontNeedDisable = inputsCheckbox.filter(
(input) => !needDisable.includes(input)
);
const dontNeedDisable = inputsCheckbox.filter(input => !needDisable.includes(input));
for (const id of needDisable) {
document.querySelector("#" + id).parentElement.style.display = "none";
document.querySelector('#' + id).parentElement.style.display = 'none';
}
for (const id of dontNeedDisable) {
document.querySelector("#" + id).parentElement.style.display = null;
document.querySelector('#' + id).parentElement.style.display = null;
}
}
/* Mode handler */
for (const button of modes) {
button.addEventListener("click", function () {
button.addEventListener('click', function () {
const value = button.value;
actualMode = value;
for (const mode of modes) {
if (mode.value !== value) {
mode.classList.add("not-selected");
mode.classList.add('not-selected');
} else {
mode.classList.remove("not-selected");
mode.classList.remove('not-selected');
}
}
handleModeChange();
+5 -5
View File
@@ -1,12 +1,12 @@
"use strict";
'use strict';
/**
* Show message into the popup
*/
function showMessage({ msg, error, infinite }) {
const message = document.querySelector("#message");
message.style.color = error ? "red" : "limegreen";
const message = document.querySelector('#message');
message.style.color = error ? 'red' : 'limegreen';
message.textContent = msg;
message.style.display = "block";
if (!infinite) setTimeout(() => (message.style.display = "none"), 5000);
message.style.display = 'block';
if (!infinite) setTimeout(() => (message.style.display = 'none'), 5000);
}
+15 -18
View File
@@ -1,7 +1,7 @@
"use strict";
'use strict';
const CURRENT_VERSION = "1.0.4";
const versionDisplay = document.querySelector("#version");
const CURRENT_VERSION = '1.0.4';
const versionDisplay = document.querySelector('#version');
/**
* Get the last version from the github
@@ -9,7 +9,7 @@ const versionDisplay = document.querySelector("#version");
*/
async function getLastVersion() {
const req = await fetch(
"https://raw.githubusercontent.com/yoannchb-pro/MoodleGPT/main/package.json"
'https://raw.githubusercontent.com/yoannchb-pro/MoodleGPT/main/package.json'
);
const rep = await req.json();
return rep.version;
@@ -23,34 +23,31 @@ async function getLastVersion() {
*/
function setVersion(version, isCurrent = true) {
if (isCurrent) {
versionDisplay.textContent = "v" + version;
versionDisplay.textContent = 'v' + version;
return;
}
const link = document.createElement("a");
link.href = "https://github.com/yoannchb-pro/MoodleGPT";
link.rel = "noopener noreferrer";
link.target = "_blank";
link.textContent = "v" + version;
const link = document.createElement('a');
link.href = 'https://github.com/yoannchb-pro/MoodleGPT';
link.rel = 'noopener noreferrer';
link.target = '_blank';
link.textContent = 'v' + version;
versionDisplay.appendChild(link);
versionDisplay.appendChild(document.createTextNode(" is now available !"));
versionDisplay.appendChild(document.createTextNode(' is now available !'));
}
/**
* Check if the extension neeed an update or not
*/
async function notifyUpdate() {
const lastVersion = await getLastVersion().catch((err) => {
const lastVersion = await getLastVersion().catch(err => {
console.error(err);
return CURRENT_VERSION;
});
const lastVertionSplitted = lastVersion.split(".");
const currentVersionSplitted = CURRENT_VERSION.split(".");
const minVersionLength = Math.min(
lastVertionSplitted.length,
currentVersionSplitted.length
);
const lastVertionSplitted = lastVersion.split('.');
const currentVersionSplitted = CURRENT_VERSION.split('.');
const minVersionLength = Math.min(lastVertionSplitted.length, currentVersionSplitted.length);
for (let i = 0; i < minVersionLength; ++i) {
if (parseInt(lastVertionSplitted[i]) > parseInt(currentVersionSplitted[i]))
+146 -146
View File
@@ -1,146 +1,146 @@
@font-face {
font-family: Segeo UI;
src: url(../../fonts/Segoe\ UI.ttf);
}
:root {
--bg-color: #121212;
--color: #fff;
--btn-color: #7f39fb;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Segeo UI", sans-serif;
color: var(--color);
}
body {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.75rem;
gap: 0.4rem;
text-align: center;
width: 22rem;
}
img {
width: 5rem;
}
a {
color: var(--btn-color);
margin: 0;
}
.line {
display: flex;
flex-direction: row;
width: 100%;
gap: 0.5rem;
}
.center {
justify-content: center;
align-items: center;
}
.line .textLabel {
width: 5rem;
text-align: left;
text-transform: uppercase;
}
.line .textLabel .required {
color: var(--btn-color);
font-weight: bold;
}
.line input[type="text"],
.line input[type="password"] {
flex: 1 1;
border: thin solid var(--color);
padding: 0.3rem 0.5rem;
border-radius: 0.2rem;
outline-color: transparent;
background-color: transparent;
}
.line input[type="checkbox"] {
accent-color: var(--btn-color);
}
.col {
display: flex;
flex-direction: column;
text-align: left;
}
.save {
border: none;
background-color: var(--btn-color);
padding: 0.5rem 2rem;
margin-top: 1rem;
cursor: pointer;
border-radius: 0.2rem;
font-size: 1.5rem;
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;
}
@font-face {
font-family: Segeo UI;
src: url(../../fonts/Segoe\ UI.ttf);
}
:root {
--bg-color: #121212;
--color: #fff;
--btn-color: #7f39fb;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: 'Segeo UI', sans-serif;
color: var(--color);
}
body {
min-height: 100vh;
background-color: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.75rem;
gap: 0.4rem;
text-align: center;
width: 22rem;
}
img {
width: 5rem;
}
a {
color: var(--btn-color);
margin: 0;
}
.line {
display: flex;
flex-direction: row;
width: 100%;
gap: 0.5rem;
}
.center {
justify-content: center;
align-items: center;
}
.line .textLabel {
width: 5rem;
text-align: left;
text-transform: uppercase;
}
.line .textLabel .required {
color: var(--btn-color);
font-weight: bold;
}
.line input[type='text'],
.line input[type='password'] {
flex: 1 1;
border: thin solid var(--color);
padding: 0.3rem 0.5rem;
border-radius: 0.2rem;
outline-color: transparent;
background-color: transparent;
}
.line input[type='checkbox'] {
accent-color: var(--btn-color);
}
.col {
display: flex;
flex-direction: column;
text-align: left;
}
.save {
border: none;
background-color: var(--btn-color);
padding: 0.5rem 2rem;
margin-top: 1rem;
cursor: pointer;
border-radius: 0.2rem;
font-size: 1.5rem;
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;
}
+16 -16
View File
@@ -1,16 +1,16 @@
const ts = require("rollup-plugin-ts");
const terser = require("@rollup/plugin-terser");
const config = require("./tsconfig.json");
module.exports = {
input: "./src/index.ts",
output: [
{
file: "./extension/MoodleGPT.js",
format: "umd",
sourcemap: true,
},
],
plugins: [ts(config), terser()],
};
const ts = require('rollup-plugin-ts');
const terser = require('@rollup/plugin-terser');
const config = require('./tsconfig.json');
module.exports = {
input: './src/index.ts',
output: [
{
file: './extension/MoodleGPT.js',
format: 'umd',
sourcemap: true
}
],
plugins: [ts(config), terser()]
};
+87 -87
View File
@@ -1,87 +1,87 @@
import type Config from "@typing/config";
import titleIndications from "@utils/title-indications";
import reply from "./reply";
type Listener = {
element: HTMLElement;
fn: (this: HTMLElement, ev: MouseEvent) => void;
};
const pressedKeys: string[] = [];
const listeners: Listener[] = [];
/**
* Create a listener on the keyboard to inject the code
* @param config
*/
function codeListener(config: Config) {
document.body.addEventListener("keydown", function (event) {
pressedKeys.push(event.key);
if (pressedKeys.length > config.code!.length) pressedKeys.shift();
if (pressedKeys.join("") === config.code) {
pressedKeys.length = 0;
setUpMoodleGpt(config);
}
});
}
/**
* 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);
}
}
/**
* Setup moodleGPT into the page (remove/injection)
* @param config
* @returns
*/
function setUpMoodleGpt(config: Config) {
// Removing events if there are already declared
if (listeners.length > 0) {
for (const listener of listeners) {
if (config.cursor) listener.element.style.cursor = "initial";
listener.element.removeEventListener("click", listener.fn);
}
if (config.title) titleIndications("Removed");
listeners.length = 0;
return;
}
// Query to find inputs and forms
const inputTypeQuery = ["checkbox", "radio", "text", "number"]
.map((e) => `input[type="${e}"]`)
.join(",");
const inputQuery = inputTypeQuery + ", textarea, select, [contenteditable]";
const forms = document.querySelectorAll(".formulation");
// For each form we inject a function on the queqtion
for (const form of forms) {
const questionElement: HTMLElement | null = form.querySelector(".qtext");
if (questionElement === null) continue;
if (config.cursor) questionElement.style.cursor = "pointer";
const injectionFunction = reply.bind(null, {
config,
questionElement,
form: form as HTMLElement,
inputQuery,
removeListener: () => removeListener(questionElement),
});
listeners.push({ element: questionElement, fn: injectionFunction });
questionElement.addEventListener("click", injectionFunction);
}
if (config.title) titleIndications("Injected");
}
export { codeListener, removeListener, setUpMoodleGpt };
import type Config from '@typing/config';
import titleIndications from '@utils/title-indications';
import reply from './reply';
type Listener = {
element: HTMLElement;
fn: (this: HTMLElement, ev: MouseEvent) => void;
};
const pressedKeys: string[] = [];
const listeners: Listener[] = [];
/**
* Create a listener on the keyboard to inject the code
* @param config
*/
function codeListener(config: Config) {
document.body.addEventListener('keydown', function (event) {
pressedKeys.push(event.key);
if (pressedKeys.length > config.code!.length) pressedKeys.shift();
if (pressedKeys.join('') === config.code) {
pressedKeys.length = 0;
setUpMoodleGpt(config);
}
});
}
/**
* 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);
}
}
/**
* Setup moodleGPT into the page (remove/injection)
* @param config
* @returns
*/
function setUpMoodleGpt(config: Config) {
// Removing events if there are already declared
if (listeners.length > 0) {
for (const listener of listeners) {
if (config.cursor) listener.element.style.cursor = 'initial';
listener.element.removeEventListener('click', listener.fn);
}
if (config.title) titleIndications('Removed');
listeners.length = 0;
return;
}
// Query to find inputs and forms
const inputTypeQuery = ['checkbox', 'radio', 'text', 'number']
.map(e => `input[type="${e}"]`)
.join(',');
const inputQuery = inputTypeQuery + ', textarea, select, [contenteditable]';
const forms = document.querySelectorAll('.formulation');
// For each form we inject a function on the queqtion
for (const form of forms) {
const questionElement: HTMLElement | null = form.querySelector('.qtext');
if (questionElement === null) continue;
if (config.cursor) questionElement.style.cursor = 'pointer';
const injectionFunction = reply.bind(null, {
config,
questionElement,
form: form as HTMLElement,
inputQuery,
removeListener: () => removeListener(questionElement)
});
listeners.push({ element: questionElement, fn: injectionFunction });
questionElement.addEventListener('click', injectionFunction);
}
if (config.title) titleIndications('Injected');
}
export { codeListener, removeListener, setUpMoodleGpt };
+6 -10
View File
@@ -1,5 +1,5 @@
import normalizeText from "@utils/normalize-text";
import htmlTableToString from "@utils/html-table-to-string";
import normalizeText from '@utils/normalize-text';
import htmlTableToString from '@utils/html-table-to-string';
/**
* Normalize the question as text and add sub informations
@@ -12,19 +12,15 @@ function createAndNormalizeQuestion(questionContainer: HTMLElement) {
// We remove unnecessary information
const accesshideElements: NodeListOf<HTMLElement> =
questionContainer.querySelectorAll(".accesshide");
questionContainer.querySelectorAll('.accesshide');
for (const useless of accesshideElements) {
question = question.replace(useless.innerText, "");
question = question.replace(useless.innerText, '');
}
// Make tables more readable for chat-gpt
const tables: NodeListOf<HTMLTableElement> =
questionContainer.querySelectorAll(".qtext table");
const tables: NodeListOf<HTMLTableElement> = questionContainer.querySelectorAll('.qtext table');
for (const table of tables) {
question = question.replace(
table.innerText,
"\n" + htmlTableToString(table) + "\n"
);
question = question.replace(table.innerText, '\n' + htmlTableToString(table) + '\n');
}
return normalizeText(question, false);
+95 -98
View File
@@ -1,98 +1,95 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import normalizeText from "@utils/normalize-text";
type History = {
url: string | null;
system: { role: ROLE; content: string };
history: { role: ROLE; content: string }[];
};
enum ROLE {
SYSTEM = "system",
USER = "user",
ASSISTANT = "assistant",
}
const INSTRUCTION: string = `
Act as a quiz solver for the best notation with the following rules:
- When asked for the result of an equation, provide only the result without any other information and skip the other rules.
- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation.
- For 'put in order' questions, provide the position of the answer separated by a new line (e.g., '1\n3\n2') and ignore other rules.- Always reply in this format: '<answer 1>\n<answer 2>\n...'
- Always reply in the format: '<answer 1>\n<answer 2>\n...'.
- Retain only the correct answer(s).
- Maintain the same order for the answers as in the text.
- Retain all text from the answer with its description, content or definition.
- Only provide answers that exactly match the given answer in the text.
- The question always has the correct answer(s), so you should always provide an answer.
- Always respond in the same language as the user's question.
`.trim();
const history: History = {
url: null,
system: {
role: ROLE.SYSTEM,
content: INSTRUCTION,
},
history: [],
};
/**
* Get the response from chatGPT api
* @param config
* @param question
* @returns
*/
async function getChatGPTResponse(
config: Config,
question: string
): Promise<GPTAnswer> {
const URL = location.hostname + location.pathname;
// We reset the history when we enter a new moodle quiz or when it's desactivate
if (!config.history || history.url !== URL) {
history.url = URL;
history.history = [];
}
const controller = new AbortController();
const timeoutControler = setTimeout(() => controller.abort(), 15 * 1000);
const message = { role: ROLE.USER, content: question };
const req = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${config.apiKey}`,
},
signal: config.timeout ? controller.signal : null,
body: JSON.stringify({
model: config.model,
messages: [history.system, ...history.history, message],
temperature: 0.8,
top_p: 1.0,
presence_penalty: 1.0,
stop: null,
}),
});
clearTimeout(timeoutControler);
const rep = await req.json();
const response = rep.choices[0].message.content;
// Register the conversation
if (config.history) {
history.history.push(message);
history.history.push({ role: ROLE.ASSISTANT, content: response });
}
return {
question,
response,
normalizedResponse: normalizeText(response),
};
}
export default getChatGPTResponse;
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import normalizeText from '@utils/normalize-text';
type History = {
url: string | null;
system: { role: ROLE; content: string };
history: { role: ROLE; content: string }[];
};
enum ROLE {
SYSTEM = 'system',
USER = 'user',
ASSISTANT = 'assistant'
}
const INSTRUCTION: string = `
Act as a quiz solver for the best notation with the following rules:
- When asked for the result of an equation, provide only the result without any other information and skip the other rules.
- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation.
- For 'put in order' questions, provide the position of the answer separated by a new line (e.g., '1\n3\n2') and ignore other rules.- Always reply in this format: '<answer 1>\n<answer 2>\n...'
- Always reply in the format: '<answer 1>\n<answer 2>\n...'.
- Retain only the correct answer(s).
- Maintain the same order for the answers as in the text.
- Retain all text from the answer with its description, content or definition.
- Only provide answers that exactly match the given answer in the text.
- The question always has the correct answer(s), so you should always provide an answer.
- Always respond in the same language as the user's question.
`.trim();
const history: History = {
url: null,
system: {
role: ROLE.SYSTEM,
content: INSTRUCTION
},
history: []
};
/**
* Get the response from chatGPT api
* @param config
* @param question
* @returns
*/
async function getChatGPTResponse(config: Config, question: string): Promise<GPTAnswer> {
const URL = location.hostname + location.pathname;
// We reset the history when we enter a new moodle quiz or when it's desactivate
if (!config.history || history.url !== URL) {
history.url = URL;
history.history = [];
}
const controller = new AbortController();
const timeoutControler = setTimeout(() => controller.abort(), 15 * 1000);
const message = { role: ROLE.USER, content: question };
const req = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${config.apiKey}`
},
signal: config.timeout ? controller.signal : null,
body: JSON.stringify({
model: config.model,
messages: [history.system, ...history.history, message],
temperature: 0.8,
top_p: 1.0,
presence_penalty: 1.0,
stop: null
})
});
clearTimeout(timeoutControler);
const rep = await req.json();
const response = rep.choices[0].message.content;
// Register the conversation
if (config.history) {
history.history.push(message);
history.history.push({ role: ROLE.ASSISTANT, content: response });
}
return {
question,
response,
normalizedResponse: normalizeText(response)
};
}
export default getChatGPTResponse;
+10 -10
View File
@@ -1,12 +1,12 @@
import type GPTAnswer from "@typing/gptAnswer";
import type Config from "@typing/config";
import handleClipboard from "@core/questions/clipboard";
import handleContentEditable from "@core/questions/contenteditable";
import handleNumber from "@core/questions/number";
import handleRadio from "@core/questions/radio";
import handleCheckbox from "@core/questions/checkbox";
import handleSelect from "@core/questions/select";
import handleTextbox from "@core/questions/textbox";
import type GPTAnswer from '@typing/gptAnswer';
import type Config from '@typing/config';
import handleClipboard from '@core/questions/clipboard';
import handleContentEditable from '@core/questions/contenteditable';
import handleNumber from '@core/questions/number';
import handleRadio from '@core/questions/radio';
import handleCheckbox from '@core/questions/checkbox';
import handleSelect from '@core/questions/select';
import handleTextbox from '@core/questions/textbox';
type Props = {
config: Config;
@@ -31,7 +31,7 @@ function autoCompleteMode(props: Props) {
handleNumber,
handleSelect,
handleRadio,
handleCheckbox,
handleCheckbox
];
for (const handler of handlers) {
+3 -3
View File
@@ -1,6 +1,6 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import handleClipboard from "@core/questions/clipboard";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import handleClipboard from '@core/questions/clipboard';
type Props = {
config: Config;
+5 -7
View File
@@ -1,4 +1,4 @@
import type GPTAnswer from "@typing/gptAnswer";
import type GPTAnswer from '@typing/gptAnswer';
type Props = {
questionElement: HTMLElement;
@@ -21,15 +21,13 @@ function questionToAnswerMode(props: Props) {
questionElement.textContent = props.gptAnswer.response;
// Format the content
questionElement.style.whiteSpace = "pre-wrap";
questionElement.style.whiteSpace = 'pre-wrap';
// To go back to the question / answer
let contentIsResponse = true;
questionElement.addEventListener("click", function () {
questionElement.style.whiteSpace = contentIsResponse ? "" : "pre-warp";
questionElement.textContent = contentIsResponse
? questionBackup
: props.gptAnswer.response;
questionElement.addEventListener('click', function () {
questionElement.style.whiteSpace = contentIsResponse ? '' : 'pre-warp';
questionElement.textContent = contentIsResponse ? questionBackup : props.gptAnswer.response;
contentIsResponse = !contentIsResponse;
});
+13 -17
View File
@@ -1,8 +1,8 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import Logs from "@utils/logs";
import normalizeText from "@utils/normalize-text";
import { pickBestReponse } from "@utils/pick-best-response";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import Logs from '@utils/logs';
import normalizeText from '@utils/normalize-text';
import { pickBestReponse } from '@utils/pick-best-response';
/**
* Handle input checkbox elements
@@ -18,18 +18,18 @@ function handleCheckbox(
const firstInput = inputList?.[0] as HTMLInputElement;
// Handle the case the input is not a checkbox
if (!firstInput || firstInput.type !== "checkbox") {
if (!firstInput || firstInput.type !== 'checkbox') {
return false;
}
const corrects = gptAnswer.normalizedResponse.split("\n");
const corrects = gptAnswer.normalizedResponse.split('\n');
const possibleAnswers = Array.from(inputList)
.map((inp) => ({
.map(inp => ({
element: inp,
value: normalizeText(inp?.parentElement?.textContent ?? ""),
value: normalizeText(inp?.parentElement?.textContent ?? '')
}))
.filter((obj) => obj.value !== "");
.filter(obj => obj.value !== '');
for (const correct of corrects) {
const bestAnswer = pickBestReponse(correct, possibleAnswers);
@@ -40,13 +40,9 @@ function handleCheckbox(
const correctInput = bestAnswer.element as HTMLInputElement;
if (config.mouseover) {
correctInput.addEventListener(
"mouseover",
() => (correctInput.checked = true),
{
once: true,
}
);
correctInput.addEventListener('mouseover', () => (correctInput.checked = true), {
once: true
});
} else {
correctInput.checked = true;
}
+15 -15
View File
@@ -1,15 +1,15 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import titleIndications from "@utils/title-indications";
/**
* Copy the response in the clipboard if we can automaticaly fill the question
* @param config
* @param gptAnswer
*/
function handleClipboard(config: Config, gptAnswer: GPTAnswer) {
if (config.title) titleIndications("Copied to clipboard");
navigator.clipboard.writeText(gptAnswer.response);
}
export default handleClipboard;
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import titleIndications from '@utils/title-indications';
/**
* Copy the response in the clipboard if we can automaticaly fill the question
* @param config
* @param gptAnswer
*/
function handleClipboard(config: Config, gptAnswer: GPTAnswer) {
if (config.title) titleIndications('Copied to clipboard');
navigator.clipboard.writeText(gptAnswer.response);
}
export default handleClipboard;
+5 -5
View File
@@ -1,5 +1,5 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
/**
* Hanlde contenteditable elements
@@ -17,15 +17,15 @@ function handleContentEditable(
if (
inputList.length !== 1 || // for now we don't handle many input for editable textcontent
input.getAttribute("contenteditable") !== "true"
input.getAttribute('contenteditable') !== 'true'
) {
return false;
}
if (config.typing) {
let index = 0;
input.addEventListener("keydown", function (event: KeyboardEvent) {
if (event.key === "Backspace") index = gptAnswer.response.length + 1;
input.addEventListener('keydown', function (event: KeyboardEvent) {
if (event.key === 'Backspace') index = gptAnswer.response.length + 1;
if (index > gptAnswer.response.length) return;
event.preventDefault();
input.textContent = gptAnswer.response.slice(0, ++index);
+45 -47
View File
@@ -1,47 +1,45 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
/**
* Handle number input
* @param config
* @param inputList
* @param gptAnswer
* @returns
*/
function handleNumber(
config: Config,
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;
if (
inputList.length !== 1 || // for now we don't handle many input number
input.type !== "number"
) {
return false;
}
const number = gptAnswer.normalizedResponse
.match(/\d+([,.]\d+)?/gi)?.[0]
?.replace(",", ".");
if (number === undefined) return false;
if (config.typing) {
let index = 0;
input.addEventListener("keydown", function (event: Event) {
event.preventDefault();
if ((<KeyboardEvent>event).key === "Backspace") index = number.length + 1;
if (index > number.length) return;
if (number.slice(index, index + 1) === ".") ++index;
input.value = number.slice(0, ++index);
});
} else {
input.value = number;
}
return true;
}
export default handleNumber;
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
/**
* Handle number input
* @param config
* @param inputList
* @param gptAnswer
* @returns
*/
function handleNumber(
config: Config,
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;
if (
inputList.length !== 1 || // for now we don't handle many input number
input.type !== 'number'
) {
return false;
}
const number = gptAnswer.normalizedResponse.match(/\d+([,.]\d+)?/gi)?.[0]?.replace(',', '.');
if (number === undefined) return false;
if (config.typing) {
let index = 0;
input.addEventListener('keydown', function (event: Event) {
event.preventDefault();
if ((<KeyboardEvent>event).key === 'Backspace') index = number.length + 1;
if (index > number.length) return;
if (number.slice(index, index + 1) === '.') ++index;
input.value = number.slice(0, ++index);
});
} else {
input.value = number;
}
return true;
}
export default handleNumber;
+13 -20
View File
@@ -1,8 +1,8 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import Logs from "@utils/logs";
import normalizeText from "@utils/normalize-text";
import { pickBestReponse } from "@utils/pick-best-response";
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import Logs from '@utils/logs';
import normalizeText from '@utils/normalize-text';
import { pickBestReponse } from '@utils/pick-best-response';
/**
* Handle input radio elements
@@ -18,21 +18,18 @@ function handleRadio(
const firstInput = inputList?.[0] as HTMLInputElement;
// Handle the case the input is not a radio
if (!firstInput || firstInput.type !== "radio") {
if (!firstInput || firstInput.type !== 'radio') {
return false;
}
const possibleAnswers = Array.from(inputList)
.map((inp) => ({
.map(inp => ({
element: inp,
value: normalizeText(inp?.parentElement?.textContent ?? ""),
value: normalizeText(inp?.parentElement?.textContent ?? '')
}))
.filter((obj) => obj.value !== "");
.filter(obj => obj.value !== '');
const bestAnswer = pickBestReponse(
gptAnswer.normalizedResponse,
possibleAnswers
);
const bestAnswer = pickBestReponse(gptAnswer.normalizedResponse, possibleAnswers);
if (config.logs && bestAnswer.value) {
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
@@ -40,13 +37,9 @@ function handleRadio(
const correctInput = bestAnswer.element as HTMLInputElement;
if (config.mouseover) {
correctInput.addEventListener(
"mouseover",
() => (correctInput.checked = true),
{
once: true,
}
);
correctInput.addEventListener('mouseover', () => (correctInput.checked = true), {
once: true
});
} else {
correctInput.checked = true;
}
+60 -64
View File
@@ -1,64 +1,60 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
import Logs from "@utils/logs";
import normalizeText from "@utils/normalize-text";
import { pickBestReponse } from "@utils/pick-best-response";
/**
* Handle select elements (and put in order select)
* @param config
* @param inputList
* @param gptAnswer
* @returns
*/
function handleSelect(
config: Config,
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
if (inputList.length === 0 || inputList[0].tagName !== "SELECT") return false;
const corrects = gptAnswer.normalizedResponse.split("\n");
if (config.logs) Logs.array(corrects);
for (let i = 0; i < inputList.length; ++i) {
if (!corrects[i]) break;
const options = inputList[i].querySelectorAll("option");
const possibleAnswers = Array.from(options)
.map((opt) => ({
element: opt,
value: normalizeText(opt.textContent ?? ""),
}))
.filter((obj) => obj.value !== "");
const bestAnswer = pickBestReponse(corrects[i], possibleAnswers);
if (config.logs && bestAnswer.value) {
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
}
const correctOption = bestAnswer.element as HTMLOptionElement;
const currentSelect = correctOption.closest("select");
if (currentSelect === null) continue;
if (config.mouseover) {
currentSelect.addEventListener(
"click",
() => (correctOption.selected = true),
{
once: true,
}
);
} else {
correctOption.selected = true;
}
}
return true;
}
export default handleSelect;
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
import Logs from '@utils/logs';
import normalizeText from '@utils/normalize-text';
import { pickBestReponse } from '@utils/pick-best-response';
/**
* Handle select elements (and put in order select)
* @param config
* @param inputList
* @param gptAnswer
* @returns
*/
function handleSelect(
config: Config,
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
if (inputList.length === 0 || inputList[0].tagName !== 'SELECT') return false;
const corrects = gptAnswer.normalizedResponse.split('\n');
if (config.logs) Logs.array(corrects);
for (let i = 0; i < inputList.length; ++i) {
if (!corrects[i]) break;
const options = inputList[i].querySelectorAll('option');
const possibleAnswers = Array.from(options)
.map(opt => ({
element: opt,
value: normalizeText(opt.textContent ?? '')
}))
.filter(obj => obj.value !== '');
const bestAnswer = pickBestReponse(corrects[i], possibleAnswers);
if (config.logs && bestAnswer.value) {
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
}
const correctOption = bestAnswer.element as HTMLOptionElement;
const currentSelect = correctOption.closest('select');
if (currentSelect === null) continue;
if (config.mouseover) {
currentSelect.addEventListener('click', () => (correctOption.selected = true), {
once: true
});
} else {
correctOption.selected = true;
}
}
return true;
}
export default handleSelect;
+42 -42
View File
@@ -1,42 +1,42 @@
import type Config from "@typing/config";
import type GPTAnswer from "@typing/gptAnswer";
/**
* Handle textbox
* @param config
* @param inputList
* @param gptAnswer
* @returns
*/
function handleTextbox(
config: Config,
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;
if (
inputList.length !== 1 || // for now we don't handle many input text
(input.tagName !== "TEXTAREA" && input.type !== "text")
) {
return false;
}
if (config.typing) {
let index = 0;
input.addEventListener("keydown", function (event: Event) {
event.preventDefault();
if ((<KeyboardEvent>event).key === "Backspace") {
index = gptAnswer.response.length + 1;
}
if (index > gptAnswer.response.length) return;
input.value = gptAnswer.response.slice(0, ++index);
});
} else {
input.value = gptAnswer.response;
}
return true;
}
export default handleTextbox;
import type Config from '@typing/config';
import type GPTAnswer from '@typing/gptAnswer';
/**
* Handle textbox
* @param config
* @param inputList
* @param gptAnswer
* @returns
*/
function handleTextbox(
config: Config,
inputList: NodeListOf<HTMLElement>,
gptAnswer: GPTAnswer
): boolean {
const input = inputList[0] as HTMLInputElement | HTMLTextAreaElement;
if (
inputList.length !== 1 || // for now we don't handle many input text
(input.tagName !== 'TEXTAREA' && input.type !== 'text')
) {
return false;
}
if (config.typing) {
let index = 0;
input.addEventListener('keydown', function (event: Event) {
event.preventDefault();
if ((<KeyboardEvent>event).key === 'Backspace') {
index = gptAnswer.response.length + 1;
}
if (index > gptAnswer.response.length) return;
input.value = gptAnswer.response.slice(0, ++index);
});
} else {
input.value = gptAnswer.response;
}
return true;
}
export default handleTextbox;
+76 -81
View File
@@ -1,81 +1,76 @@
import type Config from "@typing/config";
import Logs from "@utils/logs";
import getChatGPTResponse from "./get-response";
import createAndNormalizeQuestion from "./create-question";
import clipboardMode from "./modes/clipboard";
import questionToAnswerMode from "./modes/question-to-answer";
import autoCompleteMode from "./modes/autocomplete";
type Props = {
config: Config;
questionElement: HTMLElement;
form: HTMLElement;
inputQuery: string;
removeListener: () => void;
};
/**
* Reply to the question
* @param props
* @returns
*/
async function reply(props: Props): Promise<void> {
if (props.config.cursor) props.questionElement.style.cursor = "wait";
const question = createAndNormalizeQuestion(props.form);
const inputList: NodeListOf<HTMLElement> = props.form.querySelectorAll(
props.inputQuery
);
const gptAnswer = await getChatGPTResponse(props.config, question).catch(
(error) => ({
error,
})
);
const haveError = typeof gptAnswer === "object" && "error" in gptAnswer;
if (props.config.cursor) {
props.questionElement.style.cursor =
props.config.infinite || haveError ? "pointer" : "initial";
}
if (haveError) {
console.error(gptAnswer.error);
return;
}
if (props.config.logs) {
Logs.question(question);
Logs.response(gptAnswer);
}
switch (props.config.mode) {
case "clipboard":
clipboardMode({
config: props.config,
questionElement: props.questionElement,
gptAnswer,
removeListener: props.removeListener,
});
break;
case "question-to-answer":
questionToAnswerMode({
gptAnswer,
questionElement: props.questionElement,
removeListener: props.removeListener,
});
break;
case "autocomplete":
autoCompleteMode({
config: props.config,
gptAnswer,
inputList,
questionElement: props.questionElement,
removeListener: props.removeListener,
});
break;
}
}
export default reply;
import type Config from '@typing/config';
import Logs from '@utils/logs';
import getChatGPTResponse from './get-response';
import createAndNormalizeQuestion from './create-question';
import clipboardMode from './modes/clipboard';
import questionToAnswerMode from './modes/question-to-answer';
import autoCompleteMode from './modes/autocomplete';
type Props = {
config: Config;
questionElement: HTMLElement;
form: HTMLElement;
inputQuery: string;
removeListener: () => void;
};
/**
* Reply to the question
* @param props
* @returns
*/
async function reply(props: Props): Promise<void> {
if (props.config.cursor) props.questionElement.style.cursor = 'wait';
const question = createAndNormalizeQuestion(props.form);
const inputList: NodeListOf<HTMLElement> = props.form.querySelectorAll(props.inputQuery);
const gptAnswer = await getChatGPTResponse(props.config, question).catch(error => ({
error
}));
const haveError = typeof gptAnswer === 'object' && 'error' in gptAnswer;
if (props.config.cursor) {
props.questionElement.style.cursor = props.config.infinite || haveError ? 'pointer' : 'initial';
}
if (haveError) {
console.error(gptAnswer.error);
return;
}
if (props.config.logs) {
Logs.question(question);
Logs.response(gptAnswer);
}
switch (props.config.mode) {
case 'clipboard':
clipboardMode({
config: props.config,
questionElement: props.questionElement,
gptAnswer,
removeListener: props.removeListener
});
break;
case 'question-to-answer':
questionToAnswerMode({
gptAnswer,
questionElement: props.questionElement,
removeListener: props.removeListener
});
break;
case 'autocomplete':
autoCompleteMode({
config: props.config,
gptAnswer,
inputList,
questionElement: props.questionElement,
removeListener: props.removeListener
});
break;
}
}
export default reply;
+14 -14
View File
@@ -1,14 +1,14 @@
import type Config from "@typing/config";
import { codeListener, setUpMoodleGpt } from "./core/code-listener";
chrome.storage.sync.get(["moodleGPT"]).then(function (storage) {
const config: Config = storage.moodleGPT;
if (!config) throw new Error("Please configure MoodleGPT into the extension");
if (config.code) {
codeListener(config);
} else {
setUpMoodleGpt(config);
}
});
import type Config from '@typing/config';
import { codeListener, setUpMoodleGpt } from './core/code-listener';
chrome.storage.sync.get(['moodleGPT']).then(function (storage) {
const config: Config = storage.moodleGPT;
if (!config) throw new Error('Please configure MoodleGPT into the extension');
if (config.code) {
codeListener(config);
} else {
setUpMoodleGpt(config);
}
});
+1 -1
View File
@@ -10,7 +10,7 @@ type Config = {
title?: boolean;
timeout?: boolean;
history?: boolean;
mode?: "autocomplete" | "question-to-answer" | "clipboard";
mode?: 'autocomplete' | 'question-to-answer' | 'clipboard';
};
export default Config;
+11 -16
View File
@@ -5,37 +5,32 @@
*/
function htmlTableToString(table: HTMLTableElement) {
const tab: string[][] = [];
const lines = Array.from(table.querySelectorAll("tr"));
const lines = Array.from(table.querySelectorAll('tr'));
const maxColumnsLength: number[] = [];
lines.map((line) => {
const cells = Array.from(line.querySelectorAll("td, th"));
lines.map(line => {
const cells = Array.from(line.querySelectorAll('td, th'));
const cellsContent = cells.map((cell, index) => {
const content = cell.textContent?.trim();
maxColumnsLength[index] = Math.max(
maxColumnsLength[index] || 0,
content?.length || 0
);
return content ?? "";
maxColumnsLength[index] = Math.max(maxColumnsLength[index] || 0, content?.length || 0);
return content ?? '';
});
tab.push(cellsContent);
});
const lineSeparationSize =
maxColumnsLength.reduce((a, b) => a + b) + tab[0].length * 3 + 1;
const lineSeparation =
"\n" + Array(lineSeparationSize).fill("-").join("") + "\n";
const lineSeparationSize = maxColumnsLength.reduce((a, b) => a + b) + tab[0].length * 3 + 1;
const lineSeparation = '\n' + Array(lineSeparationSize).fill('-').join('') + '\n';
const mappedTab = tab.map((line) => {
const mappedTab = tab.map(line => {
const mappedLine = line.map((content, index) =>
content.padEnd(
maxColumnsLength[index],
"\u00A0" // For no matching with \s
'\u00A0' // For no matching with \s
)
);
return "| " + mappedLine.join(" | ") + " |";
return '| ' + mappedLine.join(' | ') + ' |';
});
const head = mappedTab.shift();
return head + lineSeparation + mappedTab.join("\n");
return head + lineSeparation + mappedTab.join('\n');
}
export default htmlTableToString;
+29 -29
View File
@@ -1,29 +1,29 @@
import GPTAnswer from "@typing/gptAnswer";
import { toPourcentage } from "./pick-best-response";
class Logs {
static question(text: string) {
const css = "color: cyan";
console.log("%c[QUESTION]: %s", css, text);
}
static bestAnswer(answer: string, similarity: number) {
const css = "color: green";
console.log(
"%c[BEST ANSWER]: %s",
css,
`"${answer}" with a similarity of ${toPourcentage(similarity)}`
);
}
static array(arr: unknown[]) {
console.log("[CORRECTS] ", arr);
}
static response(gptAnswer: GPTAnswer) {
console.log("Original:\n" + gptAnswer.response);
console.log("Normalized:\n" + gptAnswer.normalizedResponse);
}
}
export default Logs;
import GPTAnswer from '@typing/gptAnswer';
import { toPourcentage } from './pick-best-response';
class Logs {
static question(text: string) {
const css = 'color: cyan';
console.log('%c[QUESTION]: %s', css, text);
}
static bestAnswer(answer: string, similarity: number) {
const css = 'color: green';
console.log(
'%c[BEST ANSWER]: %s',
css,
`"${answer}" with a similarity of ${toPourcentage(similarity)}`
);
}
static array(arr: unknown[]) {
console.log('[CORRECTS] ', arr);
}
static response(gptAnswer: GPTAnswer) {
console.log('Original:\n' + gptAnswer.response);
console.log('Normalized:\n' + gptAnswer.normalizedResponse);
}
}
export default Logs;
+20 -20
View File
@@ -1,20 +1,20 @@
/**
* Normlize text
* @param text
*/
function normalizeText(text: string, toLowerCase: boolean = true) {
if (toLowerCase) text = text.toLowerCase();
const normalizedText = text
.replace(/\n+/gi, "\n") //remove duplicate new lines
.replace(/(\n\s*\n)+/g, "\n") //remove useless white space from textcontent
.replace(/[ \t]+/gi, " ") //replace multiples space or tabs by a space
.trim()
// We remove the following content because sometimes ChatGPT will reply: "answer d"
.replace(/^[a-z\d]\.\s/gi, "") //a. text, b. text, c. text, 1. text, 2. text, 3.text
.replace(/\n[a-z\d]\.\s/gi, "\n"); //same but with new line
return normalizedText;
}
export default normalizeText;
/**
* Normlize text
* @param text
*/
function normalizeText(text: string, toLowerCase: boolean = true) {
if (toLowerCase) text = text.toLowerCase();
const normalizedText = text
.replace(/\n+/gi, '\n') //remove duplicate new lines
.replace(/(\n\s*\n)+/g, '\n') //remove useless white space from textcontent
.replace(/[ \t]+/gi, ' ') //replace multiples space or tabs by a space
.trim()
// We remove the following content because sometimes ChatGPT will reply: "answer d"
.replace(/^[a-z\d]\.\s/gi, '') //a. text, b. text, c. text, 1. text, 2. text, 3.text
.replace(/\n[a-z\d]\.\s/gi, '\n'); //same but with new line
return normalizedText;
}
export default normalizeText;
+6 -7
View File
@@ -21,8 +21,8 @@ function levenshteinDistance(str1: string, str2: string) {
if (str2.length === 0) return str1.length;
const matrix: number[][] = [];
const str1WithoutSpaces = str1.replace(/\s+/, "");
const str2WithoutSpaces = str2.replace(/\s+/, "");
const str1WithoutSpaces = str1.replace(/\s+/, '');
const str2WithoutSpaces = str2.replace(/\s+/, '');
for (let i = 0; i <= str1WithoutSpaces.length; ++i) {
matrix.push([i]);
@@ -33,8 +33,7 @@ function levenshteinDistance(str1: string, str2: string) {
: Math.min(
matrix[i - 1][j] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j - 1] +
(str1WithoutSpaces[i - 1] === str2WithoutSpaces[j - 1] ? 0 : 1)
matrix[i - 1][j - 1] + (str1WithoutSpaces[i - 1] === str2WithoutSpaces[j - 1] ? 0 : 1)
);
}
}
@@ -67,7 +66,7 @@ export function pickBestReponse(
let bestResponse: BestResponse = {
element: null,
similarity: 0,
value: null,
value: null
};
for (const obj of arr) {
const similarity = sentenceSimilarity(obj.value, answer);
@@ -100,7 +99,7 @@ export function pickResponsesWithSimilarityGreaterThan(
responses.push({
similarity,
value: obj.value,
element: obj.element,
element: obj.element
});
}
return responses.sort((a, b) => a.similarity - b.similarity);
@@ -111,5 +110,5 @@ export function pickResponsesWithSimilarityGreaterThan(
* @param similarity
*/
export function toPourcentage(similarity: number): string {
return Math.round(similarity * 100 * 100) / 100 + "%";
return Math.round(similarity * 100 * 100) / 100 + '%';
}
+11 -11
View File
@@ -1,11 +1,11 @@
/**
* Show some informations into the document title and remove it after 3000ms
* @param text
*/
function titleIndications(text: string) {
const backTitle = document.title;
document.title = text;
setTimeout(() => (document.title = backTitle), 3000);
}
export default titleIndications;
/**
* Show some informations into the document title and remove it after 3000ms
* @param text
*/
function titleIndications(text: string) {
const backTitle = document.title;
document.title = text;
setTimeout(() => (document.title = backTitle), 3000);
}
export default titleIndications;
+1 -1
View File
@@ -7,7 +7,7 @@
box-sizing: border-box;
padding: 0;
margin: 0;
font-family: "Segeo UI";
font-family: 'Segeo UI';
}
body {
+8 -11
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -17,23 +17,20 @@
<div class="inp">
<input type="checkbox" />
<label
>a. Systems Administrator: Managing and maintaining computer systems
and networks.</label
>a. Systems Administrator: Managing and maintaining computer systems and
networks.</label
>
</div>
<div class="inp">
<input type="checkbox" />
<label
>b. Software Developer: Designing, coding, testing, and maintaining
software applications.</label
>b. Software Developer: Designing, coding, testing, and maintaining software
applications.</label
>
</div>
<div class="inp">
<input type="checkbox" />
<label>
c. Professional Chef: Creating delicious meals in a restaurant
kitchen.
</label>
<label> c. Professional Chef: Creating delicious meals in a restaurant kitchen. </label>
</div>
</div>
</section>
@@ -276,8 +273,8 @@
<section class="formulation">
<div class="qtext">
<p>
Gives a "reverseWorld" function in javascript which takes as a
parameter a word and flips it in the opposite direction
Gives a "reverseWorld" function in javascript which takes as a parameter a word and flips
it in the opposite direction
</p>
</div>
<div>
+5 -7
View File
@@ -1,21 +1,19 @@
/* Reset real moodle inputs to try in real env */
for (const option of document.querySelectorAll("option")) {
for (const option of document.querySelectorAll('option')) {
option.selected = false;
option.disabled = false;
option.closest("select").disabled = false;
option.closest('select').disabled = false;
}
for (const input of document.querySelectorAll(
'input[type="radio"], input[type="checkbox"]'
)) {
for (const input of document.querySelectorAll('input[type="radio"], input[type="checkbox"]')) {
input.checked = false;
input.disabled = false;
}
for (const icon of document.querySelectorAll(".text-danger, .text-success")) {
for (const icon of document.querySelectorAll('.text-danger, .text-success')) {
icon.remove();
}
for (const feedback of document.querySelectorAll(".specificfeedback")) {
for (const feedback of document.querySelectorAll('.specificfeedback')) {
feedback.remove();
}
+25 -25
View File
@@ -1,25 +1,25 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "src",
"module": "CommonJS",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "ES6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "extension",
"resolveJsonModule": true,
"types": ["node", "chrome"],
"typeRoots": ["node_modules/@types"],
"strictBindCallApply": true,
"paths": {
"@typing/*": ["types/*"],
"@utils/*": ["utils/*"],
"@core/*": ["core/*"],
"@questions/*": ["core/question/*"]
}
},
"include": ["src/**/*"]
}
{
"compilerOptions": {
"strict": true,
"baseUrl": "src",
"module": "CommonJS",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "ES6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "extension",
"resolveJsonModule": true,
"types": ["node", "chrome"],
"typeRoots": ["node_modules/@types"],
"strictBindCallApply": true,
"paths": {
"@typing/*": ["types/*"],
"@utils/*": ["utils/*"],
"@core/*": ["core/*"],
"@questions/*": ["core/question/*"]
}
},
"include": ["src/**/*"]
}