13 Commits

Author SHA1 Message Date
yoannchb-pro 3591aceba1 v1.1.5 2025-10-01 09:28:48 +02:00
yoannchb-pro a2849da20c v1.1.4 2025-07-30 11:12:44 -04:00
yoannchb-pro 54c9373e33 Merge pull request #58 from LuckyForce/main
Solves #57 for o3
2025-07-30 10:52:03 -04:00
Adrian Schauer e793cbd0b2 #57 addtion of o3 2025-07-10 17:01:10 +02:00
yoannchb-pro fffc0d55d6 Merge pull request #54 from yoannchb-pro/dev
v1.1.3
2025-03-19 09:28:49 -04:00
yoannchb-pro e561227b78 updated doc 2025-03-19 09:27:19 -04:00
yoannchb-pro 677e870635 gpt version msg 2025-03-19 09:09:35 -04:00
yoannchb-pro 6e69830a2e v1.1.2 -> v1.1.3 2025-03-19 09:07:52 -04:00
yoannchb-pro f01785256c Updated dependencies 2025-03-19 09:05:08 -04:00
yoannchb-pro f2e1ec8ed6 Merge pull request #46 from dmunozv04/main
Add baseURL and max Tokens config
2025-03-19 08:53:03 -04:00
yoannchb-pro 98f22e9056 Merge pull request #53 from yoannchb-pro/main
Merge main into dev
2025-03-19 08:51:25 -04:00
yoannchb-pro 79a75fee89 Create FUNDING.yml 2025-02-03 14:29:50 -05:00
dmunozv04 476559188f Add baseURL and max Tokens config 2025-02-03 20:21:29 +01:00
22 changed files with 1502 additions and 1990 deletions
-21
View File
@@ -1,21 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
ignorePatterns: ['node_modules/'],
overrides: [
{
files: ['extension/popup/*.js', 'src/**/*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'no-constant-condition': 'off'
}
}
]
};
+1
View File
@@ -0,0 +1 @@
buy_me_a_coffee: yoannchbpro
+15
View File
@@ -1,5 +1,20 @@
# CHANGELOG # CHANGELOG
## v1.1.5
- Support for gpt-5
## v1.1.4
- Support for all `o` models
- Removed `Clear my choice` in the api call
- Code dependencies update
## v1.1.3
- Added `base url` and `max token` in config (by dmunozv04)
- Code dependencies update
## v1.1.2 ## v1.1.2
- Advanced settings - Advanced settings
+17 -4
View File
@@ -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.2 # MoodleGPT 1.1.5
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,15 +12,17 @@ Find the extension on the Chrome Webstore right [here](https://chrome.google.com
## Summary ## Summary
- [MoodleGPT 1.1.2](#moodlegpt-112) - [MoodleGPT 1.1.5](#moodlegpt-115)
- [Chrome Webstore](#chrome-webstore) - [Chrome Webstore](#chrome-webstore)
- [Summary](#summary) - [Summary](#summary)
- [Disclaimer !](#disclaimer-) - [Disclaimer !](#disclaimer-)
- [Donate](#donate) - [Donate](#donate)
- [Update](#update) - [Update](#update)
- [Set up](#set-up) - [Set up](#set-up)
- [Mode](#mode)
- [Settings](#settings) - [Settings](#settings)
- [Advanced Settings](#advanced-settings)
- [Mode](#mode)
- [Options](#options)
- [Internal other features](#internal-other-features) - [Internal other features](#internal-other-features)
- [Support table](#support-table) - [Support table](#support-table)
- [Supported questions type](#supported-questions-type) - [Supported questions type](#supported-questions-type)
@@ -61,6 +63,17 @@ See the [changelog](./CHANGELOG.md) to see every updates !
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 api](https://platform.openai.com/api-keys). Finally, select a [gpt model](https://platform.openai.com/docs/models) (ensure it work with completion api). 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 api](https://platform.openai.com/api-keys). Finally, select a [gpt model](https://platform.openai.com/docs/models) (ensure it work with completion api).
## Settings
- <b>API KEY\*</b>: Your openai [API KEY](https://platform.openai.com/api-keys)
- <b>GPT MODEL\*</b>: The [gpt model](https://platform.openai.com/docs/models) (you can click on the play button to ensure the model work with the extension)
## Advanced Settings
- <b>CODE</b>: A code you will need to type on your keyboard to inject/remove the extension code from the moodle page. It allow you to be more discret and control the injection so it's recommended.
- <b>BASE URL</b>: The API endpoint if you need to use your own llm.
- <b>MAX TOKENS</b>: The max tokens length you want the api to respond with.
## Mode ## Mode
<p align="center"> <p align="center">
@@ -72,7 +85,7 @@ Go to <b>"Manage my extensions"</b> on your browser, then click on <b>"Load unpa
- <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 ## Options
<p align="center"> <p align="center">
<img src="./assets/settings.png" alt="Popup" width="300"> <img src="./assets/settings.png" alt="Popup" width="300">
+32
View File
@@ -0,0 +1,32 @@
const js = require('@eslint/js');
const tsParser = require('@typescript-eslint/parser');
const tsPlugin = require('@typescript-eslint/eslint-plugin');
const prettierConfig = require('eslint-config-prettier');
const tseslint = require('typescript-eslint');
module.exports = [
{
ignores: ['**/node_modules/*', '**/dist/*', '**/*.js']
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: {
'@typescript-eslint': tsPlugin
},
rules: {
...tsPlugin.configs['eslint-recommended'].rules,
...tsPlugin.configs.recommended.rules,
...prettierConfig.rules,
'@typescript-eslint/no-explicit-any': 'off'
}
}
];
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "MoodleGPT", "name": "MoodleGPT",
"version": "1.1.2", "version": "1.1.5",
"description": "Hidden chat-gpt for your moodle quiz", "description": "Hidden chat-gpt for your moodle quiz",
"permissions": ["storage"], "permissions": ["storage"],
"action": { "action": {
+8
View File
@@ -56,6 +56,14 @@
<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 center">
<label for="baseURL" class="textLabel">Base URL:</label>
<input id="baseURL" type="text" />
</div>
<div class="line center">
<label for="maxTokens" class="textLabel">Max Tokens:</label>
<input id="maxTokens" type="number" />
</div>
</div> </div>
<!-- SWITCH SETTINGS MODE --> <!-- SWITCH SETTINGS MODE -->
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -82,6 +82,7 @@ a {
.line input[type='text'], .line input[type='text'],
.line input[type='password'], .line input[type='password'],
.line input[type='number'],
.line select { .line select {
flex: 1 1; flex: 1 1;
border: thin solid var(--color); border: thin solid var(--color);
+1382 -1925
View File
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "moodlegpt", "name": "moodlegpt",
"version": "1.1.2", "version": "1.1.5",
"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,19 +26,22 @@
}, },
"homepage": "https://github.com/yoannchb-pro/MoodleGPT#readme", "homepage": "https://github.com/yoannchb-pro/MoodleGPT#readme",
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^28.0.2", "@eslint/js": "^9.32.0",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2", "@rollup/plugin-typescript": "^12.1.4",
"@types/chrome": "^0.0.294", "@types/chrome": "^0.1.1",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@types/node": "^24.1.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/eslint-plugin": "^8.26.1",
"eslint": "^8.57.0", "@typescript-eslint/parser": "^8.38.0",
"eslint-config-prettier": "^9.1.0", "eslint": "^9.32.0",
"openai": "^4.78.1", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.2.5", "openai": "^5.23.2",
"rollup": "^4.13.0", "prettier": "^3.6.2",
"rollup": "^4.46.2",
"rollup-plugin-ts": "^3.2.0", "rollup-plugin-ts": "^3.2.0",
"typescript": "^5.4.2" "typescript": "^5.8.3",
"typescript-eslint": "^8.38.0"
} }
} }
+2
View File
@@ -20,6 +20,8 @@ function createAndNormalizeQuestion(questionContainer: HTMLElement) {
if (attoText) { if (attoText) {
question = question.replace((attoText as HTMLElement).innerText, ''); question = question.replace((attoText as HTMLElement).innerText, '');
} }
const clearMyChoice = questionContainer.querySelector('[role="button"]');
if (clearMyChoice) question = question.replace((clearMyChoice as HTMLElement).innerText, '');
// Make tables more readable for chat-gpt // Make tables more readable for chat-gpt
const tables: NodeListOf<HTMLTableElement> = questionContainer.querySelectorAll('.qtext table'); const tables: NodeListOf<HTMLTableElement> = questionContainer.querySelectorAll('.qtext table');
+4 -6
View File
@@ -3,7 +3,7 @@ import type GPTAnswer from '../types/gpt-answer';
import normalizeText from 'background/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 OpenAI from 'openai';
import { fixeO1 } from '../utils/fixe-o1'; import { fixeO } from '../utils/fixe-o';
/** /**
* Get the response from chatGPT api * Get the response from chatGPT api
@@ -25,18 +25,16 @@ async function getChatGPTResponse(
const client = new OpenAI({ const client = new OpenAI({
apiKey: config.apiKey, apiKey: config.apiKey,
baseURL: config.baseURL,
dangerouslyAllowBrowser: true dangerouslyAllowBrowser: true
}); });
const req = await client.chat.completions.create( const req = await client.chat.completions.create(
fixeO1(config.model, { fixeO(config.model, {
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. max_completion_tokens: config.maxTokens || 2000 // Maximum length of the response,
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.
max_tokens: 2000 // Maximum length of the response,
}), }),
{ signal: config.timeout ? controller.signal : null } { signal: config.timeout ? controller.signal : null }
); );
+2
View File
@@ -12,6 +12,8 @@ type Config = {
history?: boolean; history?: boolean;
includeImages?: boolean; includeImages?: boolean;
mode?: 'autocomplete' | 'question-to-answer' | 'clipboard'; mode?: 'autocomplete' | 'question-to-answer' | 'clipboard';
baseURL?: string;
maxTokens?: number;
}; };
export default Config; export default Config;
@@ -6,13 +6,8 @@ import { ChatCompletionCreateParamsNonStreaming } from 'openai/resources/chat/co
* @param data * @param data
* @returns * @returns
*/ */
export function fixeO1(model: string, data: ChatCompletionCreateParamsNonStreaming) { export function fixeO(model: string, data: ChatCompletionCreateParamsNonStreaming) {
if (!model.startsWith('o1')) return data; if (model.search(/^o\d+/gi) === -1) 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.temperature) delete data.temperature;
+9 -3
View File
@@ -5,7 +5,7 @@ const apiKeySelector: HTMLInputElement = document.querySelector('#apiKey')!;
const inputModel: HTMLInputElement = document.querySelector('#model')!; const inputModel: HTMLInputElement = document.querySelector('#model')!;
const modelsList: HTMLElement = document.querySelector('#models')!; const modelsList: HTMLElement = document.querySelector('#models')!;
const imagesIntegrationLine: HTMLInputElement = document.querySelector('#includeImages-line')!; const imagesIntegrationLine: HTMLInputElement = document.querySelector('#includeImages-line')!;
const baseURLSelector: HTMLInputElement = document.querySelector('#baseURL')!;
/** /**
* Check if the gpt version is at least 4 to show the option 'Include images' * Check if the gpt version is at least 4 to show the option 'Include images'
*/ */
@@ -23,6 +23,7 @@ inputModel.addEventListener('input', checkCanIncludeImages);
// We populate the datalist of the chatgpt model // We populate the datalist of the chatgpt model
export async function populateDatalistWithGptVersions() { export async function populateDatalistWithGptVersions() {
const apiKey = apiKeySelector.value?.trim(); const apiKey = apiKeySelector.value?.trim();
const baseURL = baseURLSelector.value?.trim();
if (!apiKey) return; if (!apiKey) return;
@@ -31,6 +32,7 @@ export async function populateDatalistWithGptVersions() {
try { try {
const client = new OpenAI({ const client = new OpenAI({
apiKey, apiKey,
baseURL,
dangerouslyAllowBrowser: true dangerouslyAllowBrowser: true
}); });
@@ -38,7 +40,9 @@ export async function populateDatalistWithGptVersions() {
const models = rep.data.filter( const models = rep.data.filter(
model => model =>
model.id.startsWith('gpt') || model.id.startsWith('o1') || model.id.startsWith('chatgpt') model.id.startsWith('gpt') ||
model.id.search(/^o\d+/gi) !== -1 ||
model.id.startsWith('chatgpt')
); );
models.sort((a, b) => b.id.localeCompare(a.id)); // we sort the model to get the best chatgpt version first models.sort((a, b) => b.id.localeCompare(a.id)); // we sort the model to get the best chatgpt version first
@@ -61,9 +65,11 @@ inputModel.addEventListener('focus', populateDatalistWithGptVersions);
export async function checkModel() { export async function checkModel() {
const model = inputModel.value?.trim(); const model = inputModel.value?.trim();
const apiKey = apiKeySelector.value?.trim(); const apiKey = apiKeySelector.value?.trim();
const baseURL = baseURLSelector.value?.trim();
try { try {
const client = new OpenAI({ apiKey, dangerouslyAllowBrowser: true }); showMessage({ msg: 'Checking GPT version...', isInfinite: true, isError: false });
const client = new OpenAI({ apiKey, baseURL, dangerouslyAllowBrowser: true });
await client.chat.completions.create({ await client.chat.completions.create({
model, model,
messages: [{ role: 'user', content: 'reply just pong' }] messages: [{ role: 'user', content: 'reply just pong' }]
+4 -2
View File
@@ -9,11 +9,11 @@ import { showMessage } from './utils';
const saveBtn = document.querySelector('.save')!; const saveBtn = document.querySelector('.save')!;
// inputs id // inputs id
const inputsText = ['apiKey', 'code', 'model']; const inputsText = ['apiKey', 'code', 'model', 'baseURL', 'maxTokens'];
// 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, baseURL, maxTokens] = inputsText.map(selector =>
(document.querySelector('#' + selector) as HTMLInputElement).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] =
@@ -40,6 +40,8 @@ saveBtn.addEventListener('click', function () {
apiKey, apiKey,
code, code,
model, model,
baseURL,
maxTokens: maxTokens ? parseInt(maxTokens) : undefined,
logs, logs,
title, title,
cursor, cursor,
+1 -1
View File
@@ -1,4 +1,4 @@
const CURRENT_VERSION = '1.1.2'; const CURRENT_VERSION = '1.1.5';
const versionDisplay = document.querySelector('#version')!; const versionDisplay = document.querySelector('#version')!;
/** /**
+2 -4
View File
@@ -2,15 +2,13 @@
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"baseUrl": "src", "baseUrl": "src",
"module": "ESNext", "module": "esnext",
"target": "ES6",
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"target": "ES6",
"noImplicitAny": true, "noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true, "sourceMap": true,
"outDir": "extension", "outDir": "extension",
"resolveJsonModule": true,
"types": ["node", "chrome"], "types": ["node", "chrome"],
"typeRoots": ["node_modules/@types"], "typeRoots": ["node_modules/@types"],
"strictBindCallApply": true "strictBindCallApply": true