diff --git a/js/api/openai.js b/js/api/openai.js deleted file mode 100644 index 6d589c4c..00000000 --- a/js/api/openai.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] - * Copyright (C) 2024 - 2025 Mic (m@micz.it) - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// Some original methods derived from https://github.com/ali-raheem/Aify/blob/4ece286095ea7a6cf89d696902e6b81b5d1c3a4b/plugin/html/API.js - - -export class OpenAI { - - apiKey = ''; - model = ''; - developer_messages = ''; - stream = false; - store = false; - - constructor({ - apiKey = '', - model = '', - developer_messages = '', - stream = false, - store = false - } = {}) { - this.apiKey = apiKey; - this.model = model; - this.developer_messages = developer_messages; - this.stream = stream; - this.store = store; - } - - - fetchModels = async () => { - try{ - const response = await fetch("https://api.openai.com/v1/models", { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer "+ this.apiKey - }, - }); - - if (!response.ok) { - const errorDetail = await response.text(); - let err_msg = "[ThunderAI] OpenAI API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail; - console.error(err_msg); - let output = {}; - output.ok = false; - output.error = errorDetail; - return output; - } - - let output = {}; - output.ok = true; - let output_response = await response.json(); - output.response = output_response.data.filter(item => item.id.startsWith('gpt-')).sort((a, b) => b.id.localeCompare(a.id)); - - return output; - }catch (error) { - console.error("[ThunderAI] OpenAI API request failed: " + error); - let output = {}; - output.is_exception = true; - output.ok = false; - output.error = "OpenAI API request failed: " + error; - return output; - } - } - - fetchResponse = async (messages, maxTokens = 0) => { - - if(this.developer_messages !== ''){ - messages.push({role: "developer", content: [{"type": "text", "text": this.developer_messages}]}); - } - - // console.log(">>>>>>>>>>> OpenAI API request: " + JSON.stringify(messages)); - - try { - const response = await fetch("https://api.openai.com/v1/chat/completions", { - 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) } : {}) - }), - }); - return response; - }catch (error) { - console.error("[ThunderAI] OpenAI API request failed: " + error); - let output = {}; - output.is_exception = true; - output.ok = false; - output.error = "OpenAI 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; - } - -} diff --git a/js/workers/model-worker-openai.js b/js/workers/model-worker-openai.js deleted file mode 100644 index 1cd9055b..00000000 --- a/js/workers/model-worker-openai.js +++ /dev/null @@ -1,136 +0,0 @@ -/* - * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] - * Copyright (C) 2024 - 2025 Mic (m@micz.it) - - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - * This file contains a modified version of the code from the project at https://github.com/boxabirds/chatgpt-frontend-nobuild - * The original code has been released under the Apache License, Version 2.0. - */ - -import { OpenAI } from '../api/openai.js'; -import { taLogger } from '../mzta-logger.js'; - -let chatgpt_api_key = null; -let chatgpt_model = ''; -let openai = null; -let stopStreaming = false; -let i18nStrings = null; -let do_debug = false; -let taLog = null; - -let conversationHistory = []; -let assistantResponseAccumulator = ''; - -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 - }); - do_debug = event.data.do_debug; - i18nStrings = event.data.i18nStrings; - taLog = new taLogger('model-worker-openai', do_debug); - } else if (event.data.type === 'chatMessage') { - conversationHistory.push({ role: 'user', content: event.data.message }); - - const response = await openai.fetchResponse(conversationHistory); //4096); - postMessage({ type: 'messageSent' }); - - if (!response.ok) { - let error_message = ''; - let errorDetail = ''; - if(response.is_exception === true){ - error_message = response.error; - }else{ - try{ - const errorJSON = await response.json(); - errorDetail = JSON.stringify(errorJSON); - error_message = errorJSON.error.message; - }catch(e){ - error_message = response.statusText; - } - taLog.log("error_message: " + JSON.stringify(error_message)); - } - postMessage({ type: 'error', payload: i18nStrings["chatgpt_api_request_failed"] + ": " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail }); - throw new Error("[ThunderAI] OpenAI ChatGPT API request failed: " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail); - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder("utf-8"); - let buffer = ''; - - while (true) { - if (stopStreaming) { - stopStreaming = false; - reader.cancel(); - conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); - assistantResponseAccumulator = ''; - postMessage({ type: 'tokensDone' }); - break; - } - const { done, value } = await reader.read(); - if (done) { - conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); - assistantResponseAccumulator = ''; - postMessage({ type: 'tokensDone' }); - break; - } - // lots of low-level OpenAI response parsing stuff - const chunk = decoder.decode(value); - buffer += chunk; - taLog.log("buffer " + buffer); - const lines = buffer.split("\n"); - buffer = lines.pop(); - let parsedLines = []; - try{ - parsedLines = lines - .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 - .map((line) => { - try { - taLog.log("line: " + JSON.stringify(line)); - return JSON.parse(line); - } catch (e) { - taLog.warn("JSON parse warning, skipped line: " + line + " - " + e.message); - return null; - } - }) - .filter((parsed) => parsed !== null); - }catch(e){ - taLog.error("Error parsing lines: " + e); - } - - 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 } }); - } - } - } - } else if (event.data.type === 'stop') { - stopStreaming = true; - } -};