diff --git a/api_webchat/controller.js b/api_webchat/controller.js index d4a9cc52..5f29526a 100644 --- a/api_webchat/controller.js +++ b/api_webchat/controller.js @@ -49,7 +49,7 @@ let worker = null; switch (llm) { case "chatgpt_api": - worker = new Worker('../js/workers/model-worker-openai.js', { type: 'module' }); + worker = new Worker('../js/workers/model-worker-openai_responses.js', { type: 'module' }); break; case "google_gemini_api": worker = new Worker('../js/workers/model-worker-google_gemini.js', { type: 'module' }); diff --git a/js/api/openai.js b/js/api/openai_responses.js similarity index 76% rename from js/api/openai.js rename to js/api/openai_responses.js index daadaea0..f4f4977a 100644 --- a/js/api/openai.js +++ b/js/api/openai_responses.js @@ -27,13 +27,13 @@ export class OpenAI { stream = false; store = false; - constructor({ + constructor( apiKey = '', model = '', developer_messages = '', stream = false, store = false - } = {}) { + ) { this.apiKey = apiKey; this.model = model; this.developer_messages = developer_messages; @@ -78,52 +78,46 @@ export class OpenAI { } } - fetchResponse = async (messages, maxTokens = 0) => { + fetchResponse = async (messages, maxTokens = 0, previous_response_id = null) => { + + const input = messages.map(msg => ({ + role: msg.role, + content: [{ type: "input_text", text: msg.content }] + })); + + let request_body = { + model: this.model, + input: input, + stream: this.stream, + store: this.store, + ...(maxTokens > 0 ? { 'max_output_tokens': parseInt(maxTokens) } : {}), + ...(previous_response_id && this.store ? { 'previous_response_id': previous_response_id } : {}) + } if(this.developer_messages !== ''){ - messages.push({role: "developer", content: [{"type": "text", "text": this.developer_messages}]}); + request_body.instructions = this.developer_messages; } // console.log(">>>>>>>>>>> OpenAI API request: " + JSON.stringify(messages)); try { - const response = await fetch("https://api.openai.com/v1/chat/completions", { + const response = await fetch("https://api.openai.com/v1/responses", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer "+ this.apiKey }, - body: JSON.stringify({ - model: this.model, - messages: messages, - stream: this.stream, - store: this.store, - ...(maxTokens > 0 ? { 'max_tokens': parseInt(maxTokens) } : {}) - }), + body: JSON.stringify(request_body), }); return response; }catch (error) { - console.error("[ThunderAI] OpenAI API request failed: " + error); + console.error("[ThunderAI] OpenAI Responses API request failed: " + error); let output = {}; output.is_exception = true; output.ok = false; - output.error = "OpenAI API request failed: " + error; + output.error = "OpenAI Responses API request failed: " + error; return output; } } - async countTokensUsingAPI(model, text) { - const response = await fetch('https://api.openai.com/v1/engines/'+model+'/tokenizer', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + this.apiKey - }, - body: JSON.stringify({ text }) - }); - - const data = await response.json(); - return data.token_count; - } - -} +} \ No newline at end of file diff --git a/js/mzta-special-commands.js b/js/mzta-special-commands.js index 0bbab812..a6ddaad3 100644 --- a/js/mzta-special-commands.js +++ b/js/mzta-special-commands.js @@ -44,7 +44,7 @@ this.do_debug = do_debug; switch (this.llm) { case "chatgpt_api": - this.worker = new Worker(new URL('./workers/model-worker-openai.js', import.meta.url), { type: 'module' }); + this.worker = new Worker(new URL('./workers/model-worker-openai_responses.js', import.meta.url), { type: 'module' }); break; case "google_gemini_api": this.worker = new Worker(new URL('./workers/model-worker-google_gemini.js', import.meta.url), { type: 'module' }); diff --git a/js/workers/model-worker-openai.js b/js/workers/model-worker-openai_responses.js similarity index 77% rename from js/workers/model-worker-openai.js rename to js/workers/model-worker-openai_responses.js index 1cd9055b..cb1562d5 100644 --- a/js/workers/model-worker-openai.js +++ b/js/workers/model-worker-openai_responses.js @@ -20,7 +20,7 @@ * The original code has been released under the Apache License, Version 2.0. */ -import { OpenAI } from '../api/openai.js'; +import { OpenAI } from '../api/openai_responses.js'; import { taLogger } from '../mzta-logger.js'; let chatgpt_api_key = null; @@ -33,25 +33,29 @@ let taLog = null; let conversationHistory = []; let assistantResponseAccumulator = ''; +let previous_response_id = null; self.onmessage = async function(event) { if (event.data.type === 'init') { chatgpt_api_key = event.data.chatgpt_api_key; chatgpt_model = event.data.chatgpt_model; - openai = new OpenAI({ - apiKey: chatgpt_api_key, - model: chatgpt_model, - developer_messages: event.data.chatgpt_developer_messages, - stream: true, - store: event.data.chatgpt_api_store - }); + openai = new OpenAI(chatgpt_api_key, chatgpt_model, event.data.chatgpt_developer_messages, true, event.data.chatgpt_api_store); do_debug = event.data.do_debug; i18nStrings = event.data.i18nStrings; - taLog = new taLogger('model-worker-openai', do_debug); + taLog = new taLogger('model-worker-openai_responses', do_debug); + previous_response_id = null; } else if (event.data.type === 'chatMessage') { conversationHistory.push({ role: 'user', content: event.data.message }); - const response = await openai.fetchResponse(conversationHistory); //4096); + let messagesToSend = conversationHistory; + if (previous_response_id) { + messagesToSend = [conversationHistory[conversationHistory.length - 1]]; + taLog.log("previous_response_id: " + previous_response_id); + } else { + taLog.log("no previous_response_id"); + } + + const response = await openai.fetchResponse(messagesToSend, 0, previous_response_id); postMessage({ type: 'messageSent' }); if (!response.ok) { @@ -102,6 +106,8 @@ self.onmessage = async function(event) { let parsedLines = []; try{ parsedLines = lines + .map((line) => line.trim()) + .filter((line) => line.startsWith("data:")) .map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix .filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]" // .map((line) => JSON.parse(line)); // Parse the JSON string @@ -120,13 +126,13 @@ self.onmessage = async function(event) { } for (const parsedLine of parsedLines) { - const { choices } = parsedLine; - const { delta } = choices[0]; - const { content } = delta; - // Update the UI with the new content - if (content) { - assistantResponseAccumulator += content; - postMessage({ type: 'newToken', payload: { token: content } }); + if (parsedLine.type === 'response.created' && parsedLine.response && parsedLine.response.id){ + previous_response_id = parsedLine.response.id; + } else if (parsedLine.type === 'response.output_text.delta' && parsedLine.delta) { + assistantResponseAccumulator += parsedLine.delta; + postMessage({ type: 'newToken', payload: { token: parsedLine.delta } }); + // } else if (parsedLine.type === 'response.completed' && parsedLine.response && parsedLine.response.id) { + // previous_response_id = parsedLine.response.id; } } } diff --git a/pages/_lib/connection-ui.js b/pages/_lib/connection-ui.js index ef285b3a..9bbcf41a 100644 --- a/pages/_lib/connection-ui.js +++ b/pages/_lib/connection-ui.js @@ -17,7 +17,7 @@ */ import { prefs_default } from '../../options/mzta-options-default.js'; -import { OpenAI } from '../../js/api/openai.js'; +import { OpenAI } from '../../js/api/openai_responses.js'; import { Ollama } from '../../js/api/ollama.js'; import { OpenAIComp } from '../../js/api/openai_comp.js' import { GoogleGemini } from '../../js/api/google_gemini.js';