v1.1.2
This commit is contained in:
+4
-1
@@ -12,7 +12,10 @@ module.exports = {
|
|||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['extension/popup/*.js', 'src/**/*.ts'],
|
files: ['extension/popup/*.js', 'src/**/*.ts'],
|
||||||
rules: {}
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'no-constant-condition': 'off'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.1.2
|
||||||
|
|
||||||
|
- Advanced settings
|
||||||
|
- Added OpenAI SDK for better support
|
||||||
|
- o1 model support
|
||||||
|
|
||||||
## v1.1.1
|
## v1.1.1
|
||||||
|
|
||||||
- Bugs correction
|
- Bugs correction
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
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 1.1.1
|
# MoodleGPT 1.1.2
|
||||||
|
|
||||||
This extension allows you to hide CHAT-GPT in a Moodle quiz. You just need to 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 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.
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ Find the extension on the Chrome Webstore right [here](https://chrome.google.com
|
|||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
- [MoodleGPT 1.1.1](#moodlegpt-111)
|
- [MoodleGPT 1.1.2](#moodlegpt-112)
|
||||||
- [Chrome Webstore](#chrome-webstore)
|
- [Chrome Webstore](#chrome-webstore)
|
||||||
- [Summary](#summary)
|
- [Summary](#summary)
|
||||||
- [Disclaimer !](#disclaimer-)
|
- [Disclaimer !](#disclaimer-)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "MoodleGPT",
|
"name": "MoodleGPT",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "Hidden chat-gpt for your moodle quiz",
|
"description": "Hidden chat-gpt for your moodle quiz",
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
"action": {
|
"action": {
|
||||||
|
|||||||
+20
-10
@@ -7,13 +7,7 @@
|
|||||||
<title>MoodleGPT</title>
|
<title>MoodleGPT</title>
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
|
||||||
<!-- Should always be at the top -->
|
<script src="./popup.js" defer></script>
|
||||||
<script src="./js/utils.js" defer></script>
|
|
||||||
|
|
||||||
<script src="./js/index.js" defer></script>
|
|
||||||
<script src="./js/modeHandler.js" defer></script>
|
|
||||||
<script src="./js/gptVersion.js" defer></script>
|
|
||||||
<script src="./js/version.js" defer></script>
|
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="../icon.png" />
|
<link rel="icon" type="image/png" href="../icon.png" />
|
||||||
<link
|
<link
|
||||||
@@ -41,6 +35,9 @@
|
|||||||
<p id="version"></p>
|
<p id="version"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SETTINGS -->
|
||||||
|
<div class="settings" id="settings">
|
||||||
<div class="line center">
|
<div class="line center">
|
||||||
<label for="apiKey" class="textLabel">Api Key<span class="required">*</span>:</label>
|
<label for="apiKey" class="textLabel">Api Key<span class="required">*</span>:</label>
|
||||||
<input id="apiKey" type="text" />
|
<input id="apiKey" type="text" />
|
||||||
@@ -49,12 +46,24 @@
|
|||||||
<label for="model" class="textLabel">GPT Model<span class="required">*</span>:</label>
|
<label for="model" class="textLabel">GPT Model<span class="required">*</span>:</label>
|
||||||
<input type="text" id="model" list="models" />
|
<input type="text" id="model" list="models" />
|
||||||
<datalist id="models"></datalist>
|
<datalist id="models"></datalist>
|
||||||
|
<i id="check-model" title="Test" style="cursor: pointer" class="fa-solid fa-play"></i>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ADVANCED SETTINGS -->
|
||||||
|
<div class="settings" id="advanced-settings" style="display: none">
|
||||||
<div class="line center">
|
<div class="line center">
|
||||||
<label for="code" class="textLabel">Code:</label>
|
<label for="code" class="textLabel">Code:</label>
|
||||||
<input id="code" type="text" />
|
<input id="code" type="text" />
|
||||||
</div>
|
</div>
|
||||||
<div class="line mt">
|
</div>
|
||||||
|
|
||||||
|
<!-- SWITCH SETTINGS MODE -->
|
||||||
|
<div class="line center mt">
|
||||||
|
<a id="switch-settings" href="#">Advanced settings</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="line center-y mt">
|
||||||
<i class="fa-solid fa-robot"></i>
|
<i class="fa-solid fa-robot"></i>
|
||||||
<p>Mode:</p>
|
<p>Mode:</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,9 +78,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="line mt">
|
<div class="line mt center-y">
|
||||||
<i class="fa-solid fa-gear"></i>
|
<i class="fa-solid fa-gear"></i>
|
||||||
<p>Settings:</p>
|
<p>Options:</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="line center" style="gap: 2rem">
|
<div class="line center" style="gap: 2rem">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@@ -122,6 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="line center">
|
<div class="line center">
|
||||||
<a
|
<a
|
||||||
|
class="donate"
|
||||||
href="https://www.buymeacoffee.com/yoannchbpro"
|
href="https://www.buymeacoffee.com/yoannchbpro"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const apiKeySelector = document.querySelector('#apiKey');
|
|
||||||
const inputModel = document.querySelector('#model');
|
|
||||||
const modelsList = document.querySelector('#models');
|
|
||||||
const imagesIntegrationLine = document.querySelector('#includeImages-line');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the gpt version is at least 4 to show the option 'Include images'
|
|
||||||
*/
|
|
||||||
function checkCanIncludeImages() {
|
|
||||||
const version = inputModel.value;
|
|
||||||
if (isCurrentVersionSupportingImages(version)) {
|
|
||||||
imagesIntegrationLine.style.display = 'flex';
|
|
||||||
} else {
|
|
||||||
imagesIntegrationLine.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inputModel.addEventListener('input', checkCanIncludeImages);
|
|
||||||
|
|
||||||
// We populate the datalist of the chatgpt model
|
|
||||||
async function populateDatalistWithGptVersions() {
|
|
||||||
const apiKey = apiKeySelector.value?.trim();
|
|
||||||
|
|
||||||
if (!apiKey) return;
|
|
||||||
|
|
||||||
inputModel.innerHTML = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const req = await fetch('https://api.openai.com/v1/models', {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const rep = await req.json();
|
|
||||||
rep.data.sort((a, b) => b.id.localeCompare(a.id)); // we sort the model to get the best chatgpt version first
|
|
||||||
const models = rep.data.filter(model => model.id.startsWith('gpt'));
|
|
||||||
|
|
||||||
for (const model of models) {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = model.id;
|
|
||||||
opt.textContent = model.id;
|
|
||||||
modelsList.appendChild(opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCanIncludeImages();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
showMessage({ msg: 'Failed to fetch last ChatGPT versions', error: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inputModel.addEventListener('focus', populateDatalistWithGptVersions);
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show message into the popup
|
|
||||||
*/
|
|
||||||
function showMessage({ msg, error, infinite }) {
|
|
||||||
const message = document.querySelector('#message');
|
|
||||||
message.style.color = error ? 'red' : 'limegreen';
|
|
||||||
message.textContent = msg;
|
|
||||||
message.style.display = 'block';
|
|
||||||
if (!infinite) setTimeout(() => (message.style.display = 'none'), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the current model support images integrations
|
|
||||||
* @param {string} version
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function isCurrentVersionSupportingImages(version) {
|
|
||||||
const versionNumber = version.match(/gpt-(\d+)/);
|
|
||||||
if (!versionNumber?.[1]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Number(versionNumber[1]) >= 4;
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -35,6 +35,15 @@ main {
|
|||||||
width: 22rem;
|
width: 22rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 5rem;
|
width: 5rem;
|
||||||
}
|
}
|
||||||
@@ -56,6 +65,10 @@ a {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center-y {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.line .textLabel {
|
.line .textLabel {
|
||||||
width: 5rem;
|
width: 5rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -148,3 +161,33 @@ a {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.donate {
|
||||||
|
color: white;
|
||||||
|
animation: infinite donate 5s linear;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes donate {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
3.57% {
|
||||||
|
transform: translateY(-9px);
|
||||||
|
}
|
||||||
|
7.14% {
|
||||||
|
transform: translateY(-9px) rotate(17deg);
|
||||||
|
}
|
||||||
|
10.78% {
|
||||||
|
transform: translateY(-9px) rotate(-17deg);
|
||||||
|
}
|
||||||
|
14% {
|
||||||
|
transform: translateY(-9px) rotate(17deg);
|
||||||
|
}
|
||||||
|
18% {
|
||||||
|
transform: translateY(-9px) rotate(-17deg);
|
||||||
|
}
|
||||||
|
22% {
|
||||||
|
transform: translateY(0) rotate(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+6
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moodlegpt",
|
"name": "moodlegpt",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "This extension allows you to hide CHAT-GPT in a Moodle quiz.",
|
"description": "This extension allows you to hide CHAT-GPT in a Moodle quiz.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run prettier && npm run lint && npm run fastBuild",
|
"build": "npm run prettier && npm run lint && npm run fastBuild",
|
||||||
@@ -26,12 +26,16 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/yoannchb-pro/MoodleGPT#readme",
|
"homepage": "https://github.com/yoannchb-pro/MoodleGPT#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.2",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/chrome": "^0.0.263",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
|
"@types/chrome": "^0.0.294",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
"@typescript-eslint/parser": "^7.2.0",
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"openai": "^4.78.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"rollup": "^4.13.0",
|
"rollup": "^4.13.0",
|
||||||
"rollup-plugin-ts": "^3.2.0",
|
"rollup-plugin-ts": "^3.2.0",
|
||||||
|
|||||||
+20
-7
@@ -1,16 +1,29 @@
|
|||||||
const ts = require('rollup-plugin-ts');
|
const ts = require('@rollup/plugin-typescript');
|
||||||
const terser = require('@rollup/plugin-terser');
|
const terser = require('@rollup/plugin-terser');
|
||||||
|
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||||
|
|
||||||
const config = require('./tsconfig.json');
|
const config = require('./tsconfig.json');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = [
|
||||||
input: './src/index.ts',
|
|
||||||
output: [
|
|
||||||
{
|
{
|
||||||
|
input: './src/background/index.ts',
|
||||||
|
output: {
|
||||||
file: './extension/MoodleGPT.js',
|
file: './extension/MoodleGPT.js',
|
||||||
format: 'umd',
|
format: 'umd',
|
||||||
sourcemap: true
|
sourcemap: true
|
||||||
|
},
|
||||||
|
onwarn() {},
|
||||||
|
plugins: [nodeResolve(), ts(config), terser()]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
input: './src/popup/index.ts',
|
||||||
|
output: {
|
||||||
|
file: './extension/popup/popup.js',
|
||||||
|
format: 'umd',
|
||||||
|
sourcemap: true
|
||||||
|
},
|
||||||
|
onwarn() {},
|
||||||
|
plugins: [nodeResolve(), ts(config), terser()]
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
plugins: [ts(config), terser()]
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../types/config';
|
||||||
import titleIndications from '@utils/title-indications';
|
import titleIndications from 'background/utils/title-indications';
|
||||||
import reply from './reply';
|
import reply from './reply';
|
||||||
|
|
||||||
type Listener = {
|
type Listener = {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import normalizeText from '@utils/normalize-text';
|
import normalizeText from 'background/utils/normalize-text';
|
||||||
import htmlTableToString from '@utils/html-table-to-string';
|
import htmlTableToString from 'background/utils/html-table-to-string';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the question as text and add sub informations
|
* Normalize the question as text and add sub informations
|
||||||
+14
-14
@@ -1,14 +1,14 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../types/config';
|
||||||
import { ROLE, CONTENT_TYPE, type MessageContent, type Message } from '@typing/message';
|
import imageToBase64 from 'background/utils/image-to-base64';
|
||||||
import imageToBase64 from '@utils/image-to-base64';
|
import isGPTModelGreaterOrEqualTo4 from 'background/utils/version-support-images';
|
||||||
import isGPTModelGreaterOrEqualTo4 from '@utils/version-support-images';
|
import { ChatCompletionMessageParam, ChatCompletionUserMessageParam } from 'openai/resources';
|
||||||
|
|
||||||
// The attempt and the cmid allow us to identify a quiz
|
// The attempt and the cmid allow us to identify a quiz
|
||||||
type History = {
|
type History = {
|
||||||
host: string;
|
host: string;
|
||||||
cmid: string; // The id of the quiz
|
cmid: string; // The id of the quiz
|
||||||
attempt: string; // The attempt of the current quiz
|
attempt: string; // The attempt of the current quiz
|
||||||
history: { role: ROLE; content: MessageContent }[];
|
history: ChatCompletionMessageParam[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const INSTRUCTION: string = `
|
const INSTRUCTION: string = `
|
||||||
@@ -26,9 +26,9 @@ Act as a quiz solver for the best notation with the following rules:
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
const SYSTEM_INSTRUCTION_MESSAGE = {
|
const SYSTEM_INSTRUCTION_MESSAGE = {
|
||||||
role: ROLE.SYSTEM,
|
role: 'system',
|
||||||
content: INSTRUCTION
|
content: INSTRUCTION
|
||||||
} as const satisfies Message;
|
} as const satisfies ChatCompletionMessageParam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the content to send to ChatGPT API (it allows to includes images if supported)
|
* Get the content to send to ChatGPT API (it allows to includes images if supported)
|
||||||
@@ -38,7 +38,7 @@ async function getContent(
|
|||||||
config: Config,
|
config: Config,
|
||||||
questionElement: HTMLElement,
|
questionElement: HTMLElement,
|
||||||
question: string
|
question: string
|
||||||
): Promise<MessageContent> {
|
): Promise<ChatCompletionUserMessageParam['content']> {
|
||||||
const imagesElements = questionElement.querySelectorAll('img');
|
const imagesElements = questionElement.querySelectorAll('img');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -49,7 +49,7 @@ async function getContent(
|
|||||||
return question;
|
return question;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentWithImages: MessageContent = [];
|
const contentWithImages: ChatCompletionUserMessageParam['content'] = [];
|
||||||
|
|
||||||
const base64Images = Array.from(imagesElements).map(imgEl => imageToBase64(imgEl));
|
const base64Images = Array.from(imagesElements).map(imgEl => imageToBase64(imgEl));
|
||||||
const base64ImagesResolved = await Promise.allSettled(base64Images);
|
const base64ImagesResolved = await Promise.allSettled(base64Images);
|
||||||
@@ -57,7 +57,7 @@ async function getContent(
|
|||||||
for (const result of base64ImagesResolved) {
|
for (const result of base64ImagesResolved) {
|
||||||
if (result.status === 'fulfilled') {
|
if (result.status === 'fulfilled') {
|
||||||
contentWithImages.push({
|
contentWithImages.push({
|
||||||
type: CONTENT_TYPE.IMAGE,
|
type: 'image_url',
|
||||||
image_url: { url: result.value }
|
image_url: { url: result.value }
|
||||||
});
|
});
|
||||||
} else if (config.logs) {
|
} else if (config.logs) {
|
||||||
@@ -66,7 +66,7 @@ async function getContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentWithImages.push({
|
contentWithImages.push({
|
||||||
type: CONTENT_TYPE.TEXT,
|
type: 'text',
|
||||||
text: question
|
text: question
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,11 +124,11 @@ async function getContentWithHistory(
|
|||||||
questionElement: HTMLElement,
|
questionElement: HTMLElement,
|
||||||
question: string
|
question: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
messages: [typeof SYSTEM_INSTRUCTION_MESSAGE, ...Message[]];
|
messages: [typeof SYSTEM_INSTRUCTION_MESSAGE, ...ChatCompletionMessageParam[]];
|
||||||
saveResponse?: (response: string) => void;
|
saveResponse?: (response: string) => void;
|
||||||
}> {
|
}> {
|
||||||
const content = await getContent(config, questionElement, question);
|
const content = await getContent(config, questionElement, question);
|
||||||
const message = { role: ROLE.USER, content };
|
const message: ChatCompletionMessageParam = { role: 'user', content };
|
||||||
|
|
||||||
if (!config.history) return { messages: [SYSTEM_INSTRUCTION_MESSAGE, message] };
|
if (!config.history) return { messages: [SYSTEM_INSTRUCTION_MESSAGE, message] };
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ async function getContentWithHistory(
|
|||||||
// Register the conversation
|
// Register the conversation
|
||||||
if (config.history) {
|
if (config.history) {
|
||||||
history.history.push(message);
|
history.history.push(message);
|
||||||
history.history.push({ role: ROLE.ASSISTANT, content: response });
|
history.history.push({ role: 'assistant', content: response });
|
||||||
sessionStorage.moodleGPTHistory = JSON.stringify(history);
|
sessionStorage.moodleGPTHistory = JSON.stringify(history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../types/gpt-answer';
|
||||||
import normalizeText from '@utils/normalize-text';
|
import normalizeText from 'background/utils/normalize-text';
|
||||||
import getContentWithHistory from './get-content-with-history';
|
import getContentWithHistory from './get-content-with-history';
|
||||||
|
import OpenAI from 'openai';
|
||||||
|
import { fixeO1 } from '../utils/fixe-o1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the response from chatGPT api
|
* Get the response from chatGPT api
|
||||||
@@ -21,28 +23,27 @@ async function getChatGPTResponse(
|
|||||||
// Including the instructions to the AI, the images as base64 if needed, the question and the past conversation if history is set to true
|
// Including the instructions to the AI, the images as base64 if needed, the question and the past conversation if history is set to true
|
||||||
const contentHandler = await getContentWithHistory(config, questionElement, question);
|
const contentHandler = await getContentWithHistory(config, questionElement, question);
|
||||||
|
|
||||||
const req = await fetch('https://api.openai.com/v1/chat/completions', {
|
const client = new OpenAI({
|
||||||
method: 'POST',
|
apiKey: config.apiKey,
|
||||||
headers: {
|
dangerouslyAllowBrowser: true
|
||||||
'Content-Type': 'application/json',
|
});
|
||||||
Authorization: `Bearer ${config.apiKey}`
|
|
||||||
},
|
const req = await client.chat.completions.create(
|
||||||
signal: config.timeout ? controller.signal : null,
|
fixeO1(config.model, {
|
||||||
body: JSON.stringify({
|
|
||||||
model: config.model,
|
model: config.model,
|
||||||
messages: contentHandler.messages,
|
messages: contentHandler.messages,
|
||||||
|
|
||||||
temperature: 0.1, // Controls the randomness of the generated responses, with lower values producing more deterministic and predictable outputs. With set to 0.1 instead of 0 for more creativity.
|
temperature: 0.1, // Controls the randomness of the generated responses, with lower values producing more deterministic and predictable outputs. With set to 0.1 instead of 0 for more creativity.
|
||||||
top_p: 0.6, // Determines the diversity of the generated responses
|
top_p: 0.6, // Determines the diversity of the generated responses
|
||||||
presence_penalty: 0, // Encourages the model to introduce new concepts by penalizing words that have already appeared in the text.
|
presence_penalty: 0, // Encourages the model to introduce new concepts by penalizing words that have already appeared in the text.
|
||||||
max_tokens: 2000 // Maximum length of the response
|
max_tokens: 2000 // Maximum length of the response,
|
||||||
})
|
}),
|
||||||
});
|
{ signal: config.timeout ? controller.signal : null }
|
||||||
|
);
|
||||||
|
|
||||||
clearTimeout(timeoutControler);
|
clearTimeout(timeoutControler);
|
||||||
|
|
||||||
const rep = await req.json();
|
const response = req.choices[0].message.content ?? '';
|
||||||
const response = rep.choices[0].message.content;
|
|
||||||
|
|
||||||
// Save the response into the history
|
// Save the response into the history
|
||||||
if (typeof contentHandler.saveResponse === 'function') contentHandler.saveResponse(response);
|
if (typeof contentHandler.saveResponse === 'function') contentHandler.saveResponse(response);
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import handleClipboard from '@core/questions/clipboard';
|
import handleClipboard from 'background/core/questions/clipboard';
|
||||||
import handleContentEditable from '@core/questions/contenteditable';
|
import handleContentEditable from 'background/core/questions/contenteditable';
|
||||||
import handleNumber from '@core/questions/number';
|
import handleNumber from 'background/core/questions/number';
|
||||||
import handleRadio from '@core/questions/radio';
|
import handleRadio from 'background/core/questions/radio';
|
||||||
import handleCheckbox from '@core/questions/checkbox';
|
import handleCheckbox from 'background/core/questions/checkbox';
|
||||||
import handleSelect from '@core/questions/select';
|
import handleSelect from 'background/core/questions/select';
|
||||||
import handleTextbox from '@core/questions/textbox';
|
import handleTextbox from 'background/core/questions/textbox';
|
||||||
import handleAtto from '@core/questions/atto';
|
import handleAtto from 'background/core/questions/atto';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: Config;
|
config: Config;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
import handleClipboard from '@core/questions/clipboard';
|
import handleClipboard from 'background/core/questions/clipboard';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: Config;
|
config: Config;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
questionElement: HTMLElement;
|
questionElement: HTMLElement;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hanlde atto editor
|
* Hanlde atto editor
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
import Logs from '@utils/logs';
|
import Logs from 'background/utils/logs';
|
||||||
import normalizeText from '@utils/normalize-text';
|
import normalizeText from 'background/utils/normalize-text';
|
||||||
import { pickBestReponse } from '@utils/pick-best-response';
|
import { pickBestReponse } from 'background/utils/pick-best-response';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle input checkbox elements
|
* Handle input checkbox elements
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
import titleIndications from '@utils/title-indications';
|
import titleIndications from 'background/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
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
|
|
||||||
function isContentEditable(element: HTMLElement) {
|
function isContentEditable(element: HTMLElement) {
|
||||||
const contenteditable = element.getAttribute('contenteditable');
|
const contenteditable = element.getAttribute('contenteditable');
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle number input
|
* Handle number input
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
import Logs from '@utils/logs';
|
import Logs from 'background/utils/logs';
|
||||||
import normalizeText from '@utils/normalize-text';
|
import normalizeText from 'background/utils/normalize-text';
|
||||||
import { pickBestReponse } from '@utils/pick-best-response';
|
import { pickBestReponse } from 'background/utils/pick-best-response';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle input radio elements
|
* Handle input radio elements
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
import Logs from '@utils/logs';
|
import Logs from 'background/utils/logs';
|
||||||
import normalizeText from '@utils/normalize-text';
|
import normalizeText from 'background/utils/normalize-text';
|
||||||
import { pickBestReponse } from '@utils/pick-best-response';
|
import { pickBestReponse } from 'background/utils/pick-best-response';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle select elements (and put in order select)
|
* Handle select elements (and put in order select)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../../types/config';
|
||||||
import type GPTAnswer from '@typing/gpt-answer';
|
import type GPTAnswer from '../../types/gpt-answer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle textbox
|
* Handle textbox
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from '../types/config';
|
||||||
import Logs from '@utils/logs';
|
import Logs from 'background/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';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type Config from '@typing/config';
|
import type Config from './types/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) {
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources/chat/completions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixe request body for "o1" model
|
||||||
|
* @param model
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function fixeO1(model: string, data: ChatCompletionCreateParamsNonStreaming) {
|
||||||
|
if (!model.startsWith('o1')) return data;
|
||||||
|
|
||||||
|
if (data.max_tokens) {
|
||||||
|
data.max_completion_tokens = data.max_tokens;
|
||||||
|
delete data.max_tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.temperature) delete data.temperature;
|
||||||
|
|
||||||
|
if (data.top_p) delete data.top_p;
|
||||||
|
|
||||||
|
for (const message of data.messages) {
|
||||||
|
if (message.role === 'system') message.role = 'user' as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import GPTAnswer from '@typing/gpt-answer';
|
import GPTAnswer from '../types/gpt-answer';
|
||||||
import { toPourcentage } from './pick-best-response';
|
import { toPourcentage } from './pick-best-response';
|
||||||
|
|
||||||
class Logs {
|
class Logs {
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const globalData = { actualMode: 'autocomplete' };
|
||||||
|
|
||||||
|
export const inputsCheckbox = [
|
||||||
|
'logs',
|
||||||
|
'title',
|
||||||
|
'cursor',
|
||||||
|
'typing',
|
||||||
|
'mouseover',
|
||||||
|
'infinite',
|
||||||
|
'timeout',
|
||||||
|
'history',
|
||||||
|
'includeImages'
|
||||||
|
];
|
||||||
|
export const mode = document.querySelector('#mode')!;
|
||||||
|
export const modes = mode.querySelectorAll('button')!;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import OpenAI from 'openai';
|
||||||
|
import { isCurrentVersionSupportingImages, showMessage } from './utils';
|
||||||
|
|
||||||
|
const apiKeySelector: HTMLInputElement = document.querySelector('#apiKey')!;
|
||||||
|
const inputModel: HTMLInputElement = document.querySelector('#model')!;
|
||||||
|
const modelsList: HTMLElement = document.querySelector('#models')!;
|
||||||
|
const imagesIntegrationLine: HTMLInputElement = document.querySelector('#includeImages-line')!;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the gpt version is at least 4 to show the option 'Include images'
|
||||||
|
*/
|
||||||
|
export function checkCanIncludeImages() {
|
||||||
|
const version = inputModel.value;
|
||||||
|
if (isCurrentVersionSupportingImages(version)) {
|
||||||
|
imagesIntegrationLine.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
imagesIntegrationLine.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputModel.addEventListener('input', checkCanIncludeImages);
|
||||||
|
|
||||||
|
// We populate the datalist of the chatgpt model
|
||||||
|
export async function populateDatalistWithGptVersions() {
|
||||||
|
const apiKey = apiKeySelector.value?.trim();
|
||||||
|
|
||||||
|
if (!apiKey) return;
|
||||||
|
|
||||||
|
inputModel.innerHTML = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = new OpenAI({
|
||||||
|
apiKey,
|
||||||
|
dangerouslyAllowBrowser: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const rep = await client.models.list();
|
||||||
|
|
||||||
|
const models = rep.data.filter(
|
||||||
|
model =>
|
||||||
|
model.id.startsWith('gpt') || model.id.startsWith('o1') || model.id.startsWith('chatgpt')
|
||||||
|
);
|
||||||
|
models.sort((a, b) => b.id.localeCompare(a.id)); // we sort the model to get the best chatgpt version first
|
||||||
|
|
||||||
|
for (const model of models) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = model.id;
|
||||||
|
opt.textContent = model.id;
|
||||||
|
modelsList.appendChild(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCanIncludeImages();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
showMessage({ msg: err, isError: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputModel.addEventListener('focus', populateDatalistWithGptVersions);
|
||||||
|
|
||||||
|
export async function checkModel() {
|
||||||
|
const model = inputModel.value?.trim();
|
||||||
|
const apiKey = apiKeySelector.value?.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = new OpenAI({ apiKey, dangerouslyAllowBrowser: true });
|
||||||
|
await client.chat.completions.create({
|
||||||
|
model,
|
||||||
|
messages: [{ role: 'user', content: 'reply just pong' }]
|
||||||
|
});
|
||||||
|
showMessage({ msg: 'The model is valid!' });
|
||||||
|
} catch (err: any) {
|
||||||
|
showMessage({ msg: err, isError: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkModelBtn: HTMLElement = document.querySelector('#check-model')!;
|
||||||
|
checkModelBtn.addEventListener('click', checkModel);
|
||||||
@@ -1,41 +1,36 @@
|
|||||||
'use strict';
|
import { globalData, inputsCheckbox, modes } from './data';
|
||||||
|
import { checkCanIncludeImages } from './gpt-version';
|
||||||
|
import { handleModeChange } from './mode-handler';
|
||||||
|
import './version';
|
||||||
|
import './settings';
|
||||||
|
|
||||||
const saveBtn = document.querySelector('.save');
|
import { showMessage } from './utils';
|
||||||
|
|
||||||
|
const saveBtn = document.querySelector('.save')!;
|
||||||
|
|
||||||
// inputs id
|
// inputs id
|
||||||
const inputsText = ['apiKey', 'code', 'model'];
|
const inputsText = ['apiKey', 'code', 'model'];
|
||||||
const inputsCheckbox = [
|
|
||||||
'logs',
|
|
||||||
'title',
|
|
||||||
'cursor',
|
|
||||||
'typing',
|
|
||||||
'mouseover',
|
|
||||||
'infinite',
|
|
||||||
'timeout',
|
|
||||||
'history',
|
|
||||||
'includeImages'
|
|
||||||
];
|
|
||||||
|
|
||||||
// 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) as HTMLInputElement).value.trim()
|
||||||
);
|
);
|
||||||
const [logs, title, cursor, typing, mouseover, infinite, timeout, history, includeImages] =
|
const [logs, title, cursor, typing, mouseover, infinite, timeout, history, includeImages] =
|
||||||
inputsCheckbox.map(selector => {
|
inputsCheckbox.map(selector => {
|
||||||
const element = document.querySelector('#' + selector);
|
const element: HTMLInputElement = document.querySelector('#' + selector)!;
|
||||||
return element.checked && element.parentElement.style.display !== 'none';
|
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', isError: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code.length > 0 && code.length < 2) {
|
if (code.length > 0 && code.length < 2) {
|
||||||
showMessage({
|
showMessage({
|
||||||
msg: 'The code should at least contain 2 characters',
|
msg: 'The code should at least contain 2 characters',
|
||||||
error: true
|
isError: true
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -54,7 +49,7 @@ saveBtn.addEventListener('click', function () {
|
|||||||
timeout,
|
timeout,
|
||||||
history,
|
history,
|
||||||
includeImages,
|
includeImages,
|
||||||
mode: actualMode
|
mode: globalData.actualMode
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,7 +62,7 @@ chrome.storage.sync.get(['moodleGPT']).then(function (storage) {
|
|||||||
|
|
||||||
if (config) {
|
if (config) {
|
||||||
if (config.mode) {
|
if (config.mode) {
|
||||||
actualMode = config.mode;
|
globalData.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');
|
||||||
@@ -78,9 +73,13 @@ chrome.storage.sync.get(['moodleGPT']).then(function (storage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputsText.forEach(key =>
|
inputsText.forEach(key =>
|
||||||
config[key] ? (document.querySelector('#' + key).value = config[key]) : null
|
config[key]
|
||||||
|
? ((document.querySelector('#' + key) as HTMLInputElement).value = config[key])
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
inputsCheckbox.forEach(
|
||||||
|
key => ((document.querySelector('#' + key) as HTMLInputElement).checked = config[key] || '')
|
||||||
);
|
);
|
||||||
inputsCheckbox.forEach(key => (document.querySelector('#' + key).checked = config[key] || ''));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModeChange();
|
handleModeChange();
|
||||||
@@ -1,15 +1,10 @@
|
|||||||
'use strict';
|
import { globalData, inputsCheckbox, modes } from './data';
|
||||||
|
|
||||||
const mode = document.querySelector('#mode');
|
|
||||||
const modes = mode.querySelectorAll('button');
|
|
||||||
|
|
||||||
let actualMode = 'autocomplete';
|
|
||||||
|
|
||||||
// input to don't take in consideration
|
// input to don't take in consideration
|
||||||
const toExcludes = ['includeImages'];
|
const toExcludes = ['includeImages'];
|
||||||
|
|
||||||
// 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: Record<string, string[]> = {
|
||||||
autocomplete: [],
|
autocomplete: [],
|
||||||
clipboard: ['typing', 'mouseover'],
|
clipboard: ['typing', 'mouseover'],
|
||||||
'question-to-answer': ['typing', 'infinite', 'mouseover']
|
'question-to-answer': ['typing', 'infinite', 'mouseover']
|
||||||
@@ -18,16 +13,16 @@ const disabledForThisMode = {
|
|||||||
/**
|
/**
|
||||||
* Handle when a mode change to show specific input or to hide them
|
* Handle when a mode change to show specific input or to hide them
|
||||||
*/
|
*/
|
||||||
function handleModeChange() {
|
export function handleModeChange() {
|
||||||
const needDisable = disabledForThisMode[actualMode];
|
const needDisable = disabledForThisMode[globalData.actualMode];
|
||||||
const dontNeedDisable = inputsCheckbox.filter(
|
const dontNeedDisable = inputsCheckbox.filter(
|
||||||
input => !needDisable.includes(input) && !toExcludes.includes(input)
|
input => !needDisable.includes(input) && !toExcludes.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 = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +30,7 @@ function handleModeChange() {
|
|||||||
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;
|
globalData.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');
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const settings: HTMLElement = document.querySelector('#settings')!;
|
||||||
|
const advencedSettings: HTMLElement = document.querySelector('#advanced-settings')!;
|
||||||
|
const switchSettings: HTMLLinkElement = document.querySelector('#switch-settings')!;
|
||||||
|
|
||||||
|
export function switchSettingsMode() {
|
||||||
|
const isAdvancedSettings = advencedSettings.style.display === 'flex';
|
||||||
|
|
||||||
|
if (isAdvancedSettings) {
|
||||||
|
settings.style.display = 'flex';
|
||||||
|
advencedSettings.style.display = 'none';
|
||||||
|
switchSettings.textContent = 'Advanced settings';
|
||||||
|
} else {
|
||||||
|
settings.style.display = 'none';
|
||||||
|
advencedSettings.style.display = 'flex';
|
||||||
|
switchSettings.textContent = 'Go back to settings';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switchSettings.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
switchSettingsMode();
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Show message into the popup
|
||||||
|
*/
|
||||||
|
export function showMessage({
|
||||||
|
msg,
|
||||||
|
isError,
|
||||||
|
isInfinite
|
||||||
|
}: {
|
||||||
|
msg: string;
|
||||||
|
isError?: boolean;
|
||||||
|
isInfinite?: boolean;
|
||||||
|
}) {
|
||||||
|
const message: HTMLElement = document.querySelector('#message')!;
|
||||||
|
message.style.color = isError ? 'red' : 'limegreen';
|
||||||
|
message.textContent = msg;
|
||||||
|
message.style.display = 'block';
|
||||||
|
if (!isInfinite) setTimeout(() => (message.style.display = 'none'), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current model support images integrations
|
||||||
|
* @param {string} version
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function isCurrentVersionSupportingImages(version: string) {
|
||||||
|
const versionNumber = version.match(/gpt-(\d+)/);
|
||||||
|
if (!versionNumber?.[1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Number(versionNumber[1]) >= 4;
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
'use strict';
|
const CURRENT_VERSION = '1.1.2';
|
||||||
|
const versionDisplay = document.querySelector('#version')!;
|
||||||
const CURRENT_VERSION = '1.1.1';
|
|
||||||
const versionDisplay = document.querySelector('#version');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the last version from the github
|
* Get the last version from the github
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function getLastVersion() {
|
export async function getLastVersion(): Promise<string> {
|
||||||
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'
|
||||||
);
|
);
|
||||||
@@ -21,7 +19,7 @@ async function getLastVersion() {
|
|||||||
* @param {boolean} isCurrent
|
* @param {boolean} isCurrent
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function setVersion(version, isCurrent = true) {
|
export function setVersion(version: string, isCurrent = true) {
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
versionDisplay.textContent = 'v' + version;
|
versionDisplay.textContent = 'v' + version;
|
||||||
return;
|
return;
|
||||||
@@ -39,7 +37,7 @@ function setVersion(version, isCurrent = true) {
|
|||||||
/**
|
/**
|
||||||
* Check if the extension neeed an update or not
|
* Check if the extension neeed an update or not
|
||||||
*/
|
*/
|
||||||
async function notifyUpdate() {
|
export 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;
|
||||||
+2
-8
@@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"module": "CommonJS",
|
"module": "ESNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
@@ -13,13 +13,7 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"types": ["node", "chrome"],
|
"types": ["node", "chrome"],
|
||||||
"typeRoots": ["node_modules/@types"],
|
"typeRoots": ["node_modules/@types"],
|
||||||
"strictBindCallApply": true,
|
"strictBindCallApply": true
|
||||||
"paths": {
|
|
||||||
"@typing/*": ["types/*"],
|
|
||||||
"@utils/*": ["utils/*"],
|
|
||||||
"@core/*": ["core/*"],
|
|
||||||
"@questions/*": ["core/question/*"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user