Feat: Add Configurable Timeout, internal Project ID fields, and patch model testing payload

This commit is contained in:
2026-04-12 19:20:47 +02:00
parent a3e828a00e
commit 9cab0155b1
11 changed files with 132 additions and 11 deletions
+19 -1
View File
@@ -1 +1,19 @@
# TODO # MoodleGPT TODO List
Based on the open GitHub issues, the following features and improvements are planned:
## Configuration & Settings
- [x] **Configurable Request Timeout** (#70): Add a setting to manually configure the API request timeout, preventing hangups on deeply nested or slow LLM requests.
- [x] **OpenAI Project ID Support** (#55): Add a field under Advanced Settings to provide an OpenAI `project_id`, enabling compatibility with organizational level API keys.
- [ ] **Toggleable Visual Indications** (#71): Add an option to completely disable the cursor hijack and loading animations to prevent unwanted visual behaviors during strictly proctored quizzes.
- [ ] **Custom LMS Hostname Whitelisting** (#71): Implement internal configuration logic that seamlessly whitelists custom university domains (e.g., `*.oes.kz`) to ensure extension logic runs on rebranded Moodle portals.
## Context & Advanced AI Integration
- [ ] **Document Context Injection** (#39, #42): Allow users to upload or attach PDFs/lecture notes to be embedded in the AI's prompt resolution context to strictly enforce the correct syllabus answers.
- [ ] **ChatGPT Assistant Integrations** (#37): Implement support for routing requests to customized pre-trained ChatGPT Assistants using specific Assistant IDs instead of relying entirely on standard ChatCompletion routes.
## Cross-Platform
- [ ] **Firefox Port** (#50): Migrate and package the Chrome extension codebase for native Firefox compatibility, adapting to standard `browser.*` APIs.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+8
View File
@@ -60,10 +60,18 @@
<label for="baseURL" class="textLabel">Base URL:</label> <label for="baseURL" class="textLabel">Base URL:</label>
<input id="baseURL" type="text" /> <input id="baseURL" type="text" />
</div> </div>
<div class="line center">
<label for="projectId" class="textLabel">Project ID:</label>
<input id="projectId" type="text" placeholder="proj_..." />
</div>
<div class="line center"> <div class="line center">
<label for="maxTokens" class="textLabel">Max Tokens:</label> <label for="maxTokens" class="textLabel">Max Tokens:</label>
<input id="maxTokens" type="number" /> <input id="maxTokens" type="number" />
</div> </div>
<div class="line center">
<label for="timeoutValue" class="textLabel">Timeout (s):</label>
<input id="timeoutValue" type="number" placeholder="20" />
</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
+78
View File
@@ -0,0 +1,78 @@
[HELP] Add host and desactivate the cursor indication #71
Help Request
Issue Summary
Host name change due to private proctoring system integration and unwanted visual behavior in tests.
Describe the problem
Hello!
Our university is integrating a private proctoring system, and because of this, the host name is changing — it now ends with .oes.kz.
How can we properly add or register this new host in the LMS so that everything continues to work correctly?
Additionally, wed like to remove the visual behavior when hovering over a test question. Currently, when you hover, the cursor changes, and when you click, a loading animation appears. Is there a way to disable this effect?
Environment
• Operating System: Windows 10
• Browser: Google Chrome
• Version: [e.g. 130.0.6723.70]
• Platform: LMS
[FEATURE] Add "Request timeout" configuration option #70
Help Request
Issue Summary
[Request was abort.]
Describe the problem
[I have no idea what happen maybe request is take too long time.it happen after using gpt 5 mode which not heppen on gpt4 before. can we have timeout setting feature so we can manually set it]
Environment
Operating System: [W11]
Browser: [Chrome]
Version: [1.1.5]
Any other relevant information
Additional Information
[Any additional information that may be helpful in diagnosing the issue.]
[FEATURE] Add Project ID under Advanced Settings #55
Add Project ID under Advanced Settings
To use organization API keys it is required to provide the project id in the request. I never used openai APIs before and I was getting 401 - Incorrect API key provided.
I fixed changing this line in MoodleGPT.js :
project: s = Be("OPENAI_PROJECT_ID") ?? <my-project-id>
and then loading the extension manually.
It would be very nice to add an optional project id under the advanced settings section.
[FEATURE] Firefox port? #50
Feature Request
Description of the Feature
Would be really good if the extension was available for Firefox too.
Additional Information
It wouldn't require too much code modification, since Firefox supports chrome. APIs natively too.
[FEATURE] Preprompt for test with existing documents #42
Hello, could there be an option to maybe choose a chatgpt chat you have made already or do I need to make a separate API and enter the files in there maybe?
TLDR: I have specific exams for what correct answers only come from there. For this I would usually make a chatgpt chat that had all the documents pre uploaded in there. But if I want to use the plugin, it takes new chats. Any way to work this in or do I need to generate a specific API for this?
[FEATURE] Add a document with the extension #39
Hello, quick question: is it possible to provide my PDF of lecture notes in advance, to prepare and optimise ChatGPTs answers to the multiple-choice questions?
For example, for my Strategic Communication exam, I provide my PDF of about 30 pages of notes, so that the answers are faithful to what we covered in class.
Thanks and have a good day
Leo
[FEATURE] ChatGPT Assistants #37
Feature Request
It would be really helpful if ChatGPT's assistants would also be supported in the extension. It should be only an option to select and use assistants which you have made for a specific topic to give more exact results.
+2 -1
View File
@@ -18,7 +18,7 @@ async function getChatGPTResponse(
question: string question: string
): Promise<GPTAnswer> { ): Promise<GPTAnswer> {
const controller = new AbortController(); const controller = new AbortController();
const timeoutControler = setTimeout(() => controller.abort(), 20 * 1000); const timeoutControler = setTimeout(() => controller.abort(), (config.timeoutValue || 20) * 1000);
// Get the content to send to chatgpt // Get the content to send to chatgpt
// 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
@@ -27,6 +27,7 @@ async function getChatGPTResponse(
const client = new OpenAI({ const client = new OpenAI({
apiKey: config.apiKey, apiKey: config.apiKey,
baseURL: config.baseURL, baseURL: config.baseURL,
project: config.projectId,
dangerouslyAllowBrowser: true dangerouslyAllowBrowser: true
}); });
+2
View File
@@ -13,7 +13,9 @@ type Config = {
includeImages?: boolean; includeImages?: boolean;
mode?: 'autocomplete' | 'question-to-answer' | 'clipboard'; mode?: 'autocomplete' | 'question-to-answer' | 'clipboard';
baseURL?: string; baseURL?: string;
projectId?: string;
maxTokens?: number; maxTokens?: number;
timeoutValue?: number;
}; };
export default Config; export default Config;
+14 -2
View File
@@ -6,6 +6,8 @@ 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')!; const baseURLSelector: HTMLInputElement = document.querySelector('#baseURL')!;
const projectIdSelector: HTMLInputElement = document.querySelector('#projectId')!;
const maxTokensSelector: HTMLInputElement = document.querySelector('#maxTokens')!;
/** /**
* 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'
*/ */
@@ -24,6 +26,7 @@ inputModel.addEventListener('input', checkCanIncludeImages);
export async function populateDatalistWithGptVersions() { export async function populateDatalistWithGptVersions() {
const apiKey = apiKeySelector.value?.trim(); const apiKey = apiKeySelector.value?.trim();
const baseURL = baseURLSelector.value?.trim(); const baseURL = baseURLSelector.value?.trim();
const projectId = projectIdSelector.value?.trim();
if (!apiKey) return; if (!apiKey) return;
@@ -33,6 +36,7 @@ export async function populateDatalistWithGptVersions() {
const client = new OpenAI({ const client = new OpenAI({
apiKey, apiKey,
baseURL, baseURL,
project: projectId,
dangerouslyAllowBrowser: true dangerouslyAllowBrowser: true
}); });
@@ -66,13 +70,21 @@ 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(); const baseURL = baseURLSelector.value?.trim();
const projectId = projectIdSelector.value?.trim();
const maxTokens = maxTokensSelector.value ? parseInt(maxTokensSelector.value) : undefined;
try { try {
showMessage({ msg: 'Checking GPT version...', isInfinite: true, isError: false }); showMessage({ msg: 'Checking GPT version...', isInfinite: true, isError: false });
const client = new OpenAI({ apiKey, baseURL, dangerouslyAllowBrowser: true }); const client = new OpenAI({
apiKey,
baseURL,
project: projectId,
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' }],
max_completion_tokens: maxTokens || 2000
}); });
showMessage({ msg: 'The model is valid!' }); showMessage({ msg: 'The model is valid!' });
} catch (err: any) { } catch (err: any) {
+5 -3
View File
@@ -9,12 +9,12 @@ import { showMessage } from './utils';
const saveBtn = document.querySelector('.save')!; const saveBtn = document.querySelector('.save')!;
// inputs id // inputs id
const inputsText = ['apiKey', 'code', 'model', 'baseURL', 'maxTokens']; const inputsText = ['apiKey', 'code', 'model', 'baseURL', 'maxTokens', 'projectId', 'timeoutValue'];
// Save the configuration // Save the configuration
saveBtn.addEventListener('click', function () { saveBtn.addEventListener('click', function () {
const [apiKey, code, model, baseURL, maxTokens] = inputsText.map(selector => const [apiKey, code, model, baseURL, maxTokens, projectId, timeoutValue] = inputsText.map(
(document.querySelector('#' + selector) as HTMLInputElement).value.trim() selector => (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 => {
@@ -42,6 +42,8 @@ saveBtn.addEventListener('click', function () {
model, model,
baseURL, baseURL,
maxTokens: maxTokens ? parseInt(maxTokens) : undefined, maxTokens: maxTokens ? parseInt(maxTokens) : undefined,
projectId,
timeoutValue: timeoutValue ? parseInt(timeoutValue) : undefined,
logs, logs,
title, title,
cursor, cursor,