prettier formatage
This commit is contained in:
+11
-11
@@ -1,18 +1,18 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: "@typescript-eslint/parser",
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: ["@typescript-eslint"],
|
plugins: ['@typescript-eslint'],
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
'eslint:recommended',
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
"plugin:@typescript-eslint/recommended",
|
'plugin:@typescript-eslint/recommended',
|
||||||
"prettier",
|
'prettier'
|
||||||
],
|
],
|
||||||
ignorePatterns: ["node_modules/"],
|
ignorePatterns: ['node_modules/'],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["extension/popup/*.js", "src/**/*.ts"],
|
files: ['extension/popup/*.js', 'src/**/*.ts'],
|
||||||
rules: {},
|
rules: {}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
+33
-33
@@ -1,33 +1,33 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## v1.0.4
|
## v1.0.4
|
||||||
|
|
||||||
- `code` is not required anymore
|
- `code` is not required anymore
|
||||||
- Issue [#9](https://github.com/yoannchb-pro/MoodleGPT/issues/9) resolved
|
- Issue [#9](https://github.com/yoannchb-pro/MoodleGPT/issues/9) resolved
|
||||||
- GPT version button fixed
|
- GPT version button fixed
|
||||||
- Better algorithm to find the correct answer (levenshtein distance)
|
- Better algorithm to find the correct answer (levenshtein distance)
|
||||||
- Better ChatGPT prompt
|
- Better ChatGPT prompt
|
||||||
- Added `history` to the options/configuration
|
- Added `history` to the options/configuration
|
||||||
|
|
||||||
## v1.0.3
|
## v1.0.3
|
||||||
|
|
||||||
- Removed the option `table formating` because it will now set to true by default
|
- Removed the option `table formating` because it will now set to true by default
|
||||||
- Adjusted the abort timeout to 15seconds
|
- Adjusted the abort timeout to 15seconds
|
||||||
- If an error occur the user can now click back on the question
|
- 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
|
- `Textbox, question to answser mode and clipboard mode` is not formatted anymore
|
||||||
- Fixed many bugs
|
- Fixed many bugs
|
||||||
- Write AI system instructions
|
- Write AI system instructions
|
||||||
|
|
||||||
## v1.0.2
|
## v1.0.2
|
||||||
|
|
||||||
- Added `mode`
|
- Added `mode`
|
||||||
|
|
||||||
## v1.0.1
|
## v1.0.1
|
||||||
|
|
||||||
- Removed langage
|
- Removed langage
|
||||||
- Added a button next to model to get the last ChatGPT version
|
- Added a button next to model to get the last ChatGPT version
|
||||||
- Added update message
|
- Added update message
|
||||||
|
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
|
|
||||||
- Initial commit
|
- Initial commit
|
||||||
|
|||||||
@@ -1,167 +1,167 @@
|
|||||||
<p align="center"><a
|
<p align="center"><a
|
||||||
href="https://www.flaticon.com/free-icons/mortarboard" target="_blank" rel="noopener noreferrer"
|
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>
|
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
|
# 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.
|
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
|
## Chrome Webstore
|
||||||
|
|
||||||
Find the extension on the Chrome Webstore right [here](https://chrome.google.com/webstore/detail/moodlegpt/fgiepdkoifhpcgdhbiikpgdapjdoemko)
|
Find the extension on the Chrome Webstore right [here](https://chrome.google.com/webstore/detail/moodlegpt/fgiepdkoifhpcgdhbiikpgdapjdoemko)
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- [MoodleGPT](#moodlegpt)
|
- [MoodleGPT](#moodlegpt)
|
||||||
- [Chrome Webstore](#chrome-webstore)
|
- [Chrome Webstore](#chrome-webstore)
|
||||||
- [Summary](#summary)
|
- [Summary](#summary)
|
||||||
- [Disclaimer !](#disclaimer-)
|
- [Disclaimer !](#disclaimer-)
|
||||||
- [Donate](#donate)
|
- [Donate](#donate)
|
||||||
- [Update](#update)
|
- [Update](#update)
|
||||||
- [MoodleGPT don't complete my quiz ?](#moodlegpt-dont-complete-my-quiz-)
|
- [MoodleGPT don't complete my quiz ?](#moodlegpt-dont-complete-my-quiz-)
|
||||||
- [Set up](#set-up)
|
- [Set up](#set-up)
|
||||||
- [Inject the code into the moodle](#inject-the-code-into-the-moodle)
|
- [Inject the code into the moodle](#inject-the-code-into-the-moodle)
|
||||||
- [Remove injection](#remove-injection)
|
- [Remove injection](#remove-injection)
|
||||||
- [Mode](#mode)
|
- [Mode](#mode)
|
||||||
- [Settings](#settings)
|
- [Settings](#settings)
|
||||||
- [Internal Features](#internal-features)
|
- [Internal Features](#internal-features)
|
||||||
- [Support table](#support-table)
|
- [Support table](#support-table)
|
||||||
- [Supported questions type](#supported-questions-type)
|
- [Supported questions type](#supported-questions-type)
|
||||||
- [Select](#select)
|
- [Select](#select)
|
||||||
- [Put in order question](#put-in-order-question)
|
- [Put in order question](#put-in-order-question)
|
||||||
- [Resolve equation](#resolve-equation)
|
- [Resolve equation](#resolve-equation)
|
||||||
- [One response (radio button)](#one-response-radio-button)
|
- [One response (radio button)](#one-response-radio-button)
|
||||||
- [Multiples responses (checkbox)](#multiples-responses-checkbox)
|
- [Multiples responses (checkbox)](#multiples-responses-checkbox)
|
||||||
- [True or false](#true-or-false)
|
- [True or false](#true-or-false)
|
||||||
- [Number](#number)
|
- [Number](#number)
|
||||||
- [Text](#text)
|
- [Text](#text)
|
||||||
- [What about if the question can't be completed ?](#what-about-if-the-question-cant-be-completed-)
|
- [What about if the question can't be completed ?](#what-about-if-the-question-cant-be-completed-)
|
||||||
- [Test](#test)
|
- [Test](#test)
|
||||||
|
|
||||||
## Disclaimer !
|
## 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.
|
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
|
## Donate
|
||||||
|
|
||||||
Will be a pleasure if you want to support this project :)
|
Will be a pleasure if you want to support this project :)
|
||||||
<br/>
|
<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>
|
<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
|
## Update
|
||||||
|
|
||||||
See [changelog](./CHANGELOG.md)
|
See [changelog](./CHANGELOG.md)
|
||||||
|
|
||||||
## MoodleGPT don't complete my quiz ?
|
## 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.
|
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
|
## 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.
|
> 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">
|
<p align="center">
|
||||||
<img src="./assets/setup.png" alt="Popup" width="300">
|
<img src="./assets/setup.png" alt="Popup" width="300">
|
||||||
</p>
|
</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).
|
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
|
## 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.
|
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
|
## Remove injection
|
||||||
|
|
||||||
Type back the <b>code</b> on the keyboard and the code will be removed from the current page.
|
Type back the <b>code</b> on the keyboard and the code will be removed from the current page.
|
||||||
|
|
||||||
## Mode
|
## Mode
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./assets/mode.png" alt="Popup" width="300">
|
<img src="./assets/mode.png" alt="Popup" width="300">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- <b>Autocomplete:</b> The extension will complete the question for you.
|
- <b>Autocomplete:</b> The extension will complete the question for you.
|
||||||
- <b>Clipboard:</b> The response is copied into the clipboard.
|
- <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).
|
- <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">
|
<br/><img src="./assets/question-to-answer.gif" alt="Question to Answer">
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./assets/settings.png" alt="Popup" width="300">
|
<img src="./assets/settings.png" alt="Popup" width="300">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- <b>Api key</b>: the openai api key.
|
- <b>Api key</b>: the openai api key.
|
||||||
- <b>Code</b>: code that you will need to inject/remove the code.
|
- <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>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>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.
|
- <b>Title indication</b>: show some informations into the title to know for example if the code have been injected.
|
||||||
<br/> 
|
<br/> 
|
||||||
- <b>Console logs</b>: show logs into the console.
|
- <b>Console logs</b>: show logs into the console.
|
||||||
<br/><img src="./assets/logs.png" alt="Logs" width="250">
|
<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>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.
|
- <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/> 
|
<br/> 
|
||||||
- <b>Mouseover effect</b>: you will need to hover (or click for select) the question response to complete it automaticaly.
|
- <b>Mouseover effect</b>: you will need to hover (or click for select) the question response to complete it automaticaly.
|
||||||
<br/> 
|
<br/> 
|
||||||
<br/> 
|
<br/> 
|
||||||
|
|
||||||
- <b>Infinite try</b>: click as much as you want on the question (don't forget to reset the question).
|
- <b>Infinite try</b>: click as much as you want on the question (don't forget to reset the question).
|
||||||
|
|
||||||
## Internal Features
|
## Internal Features
|
||||||
|
|
||||||
### Support table
|
### Support table
|
||||||
|
|
||||||
Table are formated from the question to make it more readable for CHAT-GPT. Example of formatted table output:
|
Table are formated from the question to make it more readable for CHAT-GPT. Example of formatted table output:
|
||||||
|
|
||||||
```
|
```
|
||||||
| id | name | birthDate | cars |
|
| id | name | birthDate | cars |
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
| Person 1 | Yvick | 15/08/1999 | yes |
|
| Person 1 | Yvick | 15/08/1999 | yes |
|
||||||
| Person 2 | Yann | 19/01/2000 | no |
|
| Person 2 | Yann | 19/01/2000 | no |
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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 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.
|
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.
|
||||||
|
|
||||||

|

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