stop button added. see #113. some info about the model are now dinamic

This commit is contained in:
mic 2024-08-20 23:52:35 +02:00
parent 4db9f0c8a5
commit 4eba5a0eaf
9 changed files with 149 additions and 65 deletions

View file

@ -432,7 +432,7 @@
"description": "" "description": ""
}, },
"chagpt_api_connecting": { "chagpt_api_connecting": {
"message": "Versucht, eine Verbindung zu OpenAI ChatGPT mit dem angegebenen API-Schlüssel herzustellen...", "message": "Versucht, eine Verbindung zu OpenAI ChatGPT mit dem angegebenen API-Schlüssel herzustellen",
"description": "" "description": ""
} }
} }

View file

@ -432,7 +432,7 @@
"description": "" "description": ""
}, },
"chagpt_api_connecting": { "chagpt_api_connecting": {
"message": "Will attempt to connect to OpenAI ChatGPT using the API key provided...", "message": "Will attempt to connect to OpenAI ChatGPT using the API key provided",
"description": "" "description": ""
}, },
"Debug": { "Debug": {
@ -478,5 +478,9 @@
"ollama_api_connecting": { "ollama_api_connecting": {
"message": "Will attempt to connect to the Ollama Local Server using the host", "message": "Will attempt to connect to the Ollama Local Server using the host",
"description": "" "description": ""
},
"andModel": {
"message": "and model",
"description": ""
} }
} }

View file

@ -432,7 +432,7 @@
"description": "" "description": ""
}, },
"chagpt_api_connecting": { "chagpt_api_connecting": {
"message": "Tentative de connexion à OpenAI ChatGPT en utilisant la clé API fournie...", "message": "Tentative de connexion à OpenAI ChatGPT en utilisant la clé API fournie",
"description": "" "description": ""
} }
} }

View file

@ -432,7 +432,7 @@
"description": "" "description": ""
}, },
"chagpt_api_connecting": { "chagpt_api_connecting": {
"message": "Tentativo di connessione a OpenAI ChatGPT utilizzando la chiave API fornita...", "message": "Tentativo di connessione a OpenAI ChatGPT utilizzando la chiave API fornita",
"description": "" "description": ""
} }
} }

View file

@ -68,26 +68,27 @@ let promptData = null;
// } // }
// ============================== TESTING - END // ============================== TESTING - END
const params = new URLSearchParams(window.location.search);
switch (llm) { switch (llm) {
case "chatgpt_api": case "chatgpt_api":
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: ''}); let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: ''});
//console.log(">>>>>>>>>>> chatgpt_api_key: " + prefs_api_key.chatgpt_api_key); //console.log(">>>>>>>>>>> chatgpt_api_key: " + prefs_api_key.chatgpt_api_key);
messageInput.setModel(prefs_api.chatgpt_model);
messagesArea.setLLMName("ChatGPT");
worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model}); worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model});
messagesArea.appendUserMessage(browser.i18n.getMessage("chagpt_api_connecting"), "info"); messagesArea.appendUserMessage(browser.i18n.getMessage("chagpt_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " " + prefs_api.chatgpt_model + " ...", "info");
break; break;
case "ollama_api": { case "ollama_api": {
let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: ''}); let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: ''});
//console.log(">>>>>>>>>>> ollama_host: " + prefs_api_key.ollama_host); //console.log(">>>>>>>>>>> ollama_host: " + prefs_api_key.ollama_host);
messageInput.setModel(prefs_api.ollama_model);
messagesArea.setLLMName("Ollama Local");
worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model}); worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model});
messagesArea.appendUserMessage(browser.i18n.getMessage("ollama_api_connecting") + " " + prefs_api.ollama_host + " ...", "info"); messagesArea.appendUserMessage(browser.i18n.getMessage("ollama_api_connecting") + " " + prefs_api.ollama_host + " " +browser.i18n.getMessage("AndModel") + " " + prefs_api.ollama_model + " ...", "info");
break; break;
} }
} }
// Event listeners for worker messages // Event listeners for worker messages
worker.onmessage = function(event) { worker.onmessage = function(event) {
const { type, payload } = event.data; const { type, payload } = event.data;

View file

@ -50,6 +50,12 @@ messagesInputStyle .textContent = `
cursor: pointer; cursor: pointer;
border-radius: 10px; border-radius: 10px;
} }
#stopButton {
width: 44px;
height: 36px;
cursor: pointer;
border-radius: 10px;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
#messageInputField { #messageInputField {
background-color: #303030; background-color: #303030;
@ -87,7 +93,32 @@ sendIcon.appendChild(sendPath);
sendButton.appendChild(sendIcon); sendButton.appendChild(sendIcon);
messageInputTemplate.content.appendChild(sendButton); messageInputTemplate.content.appendChild(sendButton);
const stopButton = document.createElement('button');
stopButton.id = 'stopButton';
stopButton.style.display = 'none';
const stopIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
stopIcon.setAttribute('width', '24');
stopIcon.setAttribute('height', '24');
stopIcon.setAttribute('viewBox', '0 0 24 24');
stopIcon.setAttribute('fill', 'none');
stopIcon.classList.add('text-white', 'dark:text-black');
const stopRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
stopRect.setAttribute('x', '6');
stopRect.setAttribute('y', '6');
stopRect.setAttribute('width', '12');
stopRect.setAttribute('height', '12');
stopRect.setAttribute('fill', 'currentColor');
stopIcon.appendChild(stopRect);
stopButton.appendChild(stopIcon);
messageInputTemplate.content.appendChild(stopButton);
class MessageInput extends HTMLElement { class MessageInput extends HTMLElement {
model = '';
constructor() { constructor() {
super(); super();
const shadowRoot = this.attachShadow({ mode: 'open' }); const shadowRoot = this.attachShadow({ mode: 'open' });
@ -95,9 +126,11 @@ class MessageInput extends HTMLElement {
this._messageInputField = shadowRoot.querySelector('#messageInputField'); this._messageInputField = shadowRoot.querySelector('#messageInputField');
this._sendButton = shadowRoot.querySelector('#sendButton'); this._sendButton = shadowRoot.querySelector('#sendButton');
this._stopButton = shadowRoot.querySelector('#stopButton');
this._messageInputField.addEventListener('keydown', this._handleKeyDown.bind(this)); this._messageInputField.addEventListener('keydown', this._handleKeyDown.bind(this));
this._sendButton.addEventListener('click', this._handleClick.bind(this)); this._sendButton.addEventListener('click', this._handleClick.bind(this));
this._stopButton.addEventListener('click', this._handleStopClick.bind(this));
} }
connectedCallback() { connectedCallback() {
@ -107,14 +140,18 @@ class MessageInput extends HTMLElement {
async init(worker) { async init(worker) {
this.worker = worker; this.worker = worker;
let prefs_api = await browser.storage.sync.get({chatgpt_model: ''});
this._sendButton.title = await browser.i18n.getMessage("chagtp_api_send_button") + ": " + prefs_api.chatgpt_model;
} }
setMessagesArea(messagesAreaComponent) { setMessagesArea(messagesAreaComponent) {
this.messagesAreaComponent = messagesAreaComponent; this.messagesAreaComponent = messagesAreaComponent;
} }
setModel(model){
this.model = model;
this._sendButton.title = browser.i18n.getMessage("chagtp_api_send_button") + ": " + this.model;
this._stopButton.title = browser.i18n.getMessage("chagtp_api_send_button") + ": " + this.model;
}
handleMessageSent() { handleMessageSent() {
// console.log("[ThunderAI] handleMessageSent"); // console.log("[ThunderAI] handleMessageSent");
this._messageInputField.value = ''; this._messageInputField.value = '';
@ -123,8 +160,12 @@ class MessageInput extends HTMLElement {
enableInput() { enableInput() {
// console.log("[ThunderAI] enableInput"); // console.log("[ThunderAI] enableInput");
this._messageInputField.value = ''; this._messageInputField.value = '';
this._sendButton.removeAttribute('disabled');
this._messageInputField.removeAttribute('disabled'); this._messageInputField.removeAttribute('disabled');
this._sendButton.removeAttribute('disabled');
this._sendButton.style.display = 'block';
this._stopButton.setAttribute('disabled', 'disabled');
this._stopButton.style.display = 'none';
this._stopButton.title = browser.i18n.getMessage("chagtp_api_send_button") + ": " + this.model;
} }
_handleKeyDown(event) { _handleKeyDown(event) {
@ -137,6 +178,12 @@ class MessageInput extends HTMLElement {
this._handleNewChatMessage(); this._handleNewChatMessage();
} }
_handleStopClick() {
this.worker.postMessage({ type: 'stop' });
this._stopButton.setAttribute('disabled', 'disabled');
this._stopButton.title = 'Stopping...';
}
_handleNewChatMessage() { _handleNewChatMessage() {
//do nothing if input is empty //do nothing if input is empty
if ((!this._messageInputField.value)||(this._messageInputField.value.trim().length === 0)) { if ((!this._messageInputField.value)||(this._messageInputField.value.trim().length === 0)) {
@ -144,6 +191,9 @@ class MessageInput extends HTMLElement {
} }
// prevent user from interacting while we're waiting // prevent user from interacting while we're waiting
this._sendButton.setAttribute('disabled', 'disabled'); this._sendButton.setAttribute('disabled', 'disabled');
this._sendButton.style.display = 'none';
this._stopButton.removeAttribute('disabled');
this._stopButton.style.display = 'block';
this._messageInputField.setAttribute('disabled', 'disabled'); this._messageInputField.setAttribute('disabled', 'disabled');
let messageContent = this._messageInputField.value; let messageContent = this._messageInputField.value;
this._messageInputField.value = ''; this._messageInputField.value = '';

View file

@ -100,6 +100,7 @@ messagesAreaTemplate.content.appendChild(messagesDiv);
class MessagesArea extends HTMLElement { class MessagesArea extends HTMLElement {
fullTextHTML = ""; fullTextHTML = "";
llmName = "LLM";
constructor() { constructor() {
super(); super();
@ -117,7 +118,7 @@ class MessagesArea extends HTMLElement {
if (isLastMessageFromUser) { if (isLastMessageFromUser) {
const header = document.createElement('h2'); const header = document.createElement('h2');
header.textContent = "ChatGTP"; header.textContent = this.llmName;
this.messages.appendChild(header); this.messages.appendChild(header);
} }
@ -130,6 +131,10 @@ class MessagesArea extends HTMLElement {
this.worker = worker; this.worker = worker;
} }
setLLMName(llmName) {
this.llmName = llmName;
}
handleTokensDone(promptData = null) { handleTokensDone(promptData = null) {
this.flushAccumulatingMessage(); this.flushAccumulatingMessage();
this.addActionButtons(promptData); this.addActionButtons(promptData);

View file

@ -25,66 +25,80 @@ import { Ollama } from '../js/api/ollama.js';
let ollama_host = null; let ollama_host = null;
let ollama_model = ''; let ollama_model = '';
let ollama = null; let ollama = null;
let stopStreaming = false;
let conversationHistory = []; let conversationHistory = [];
let assistantResponseAccumulator = ''; let assistantResponseAccumulator = '';
self.onmessage = async function(event) { self.onmessage = async function(event) {
if (event.data.type === 'init') { switch (event.data.type) {
ollama_host = event.data.ollama_host; case 'init':
ollama_model = event.data.ollama_model; ollama_host = event.data.ollama_host;
//console.log(">>>>>>>>>>> ollama_host: " + ollama_host); ollama_model = event.data.ollama_model;
ollama = new Ollama(ollama_host, ollama_model, true); //console.log(">>>>>>>>>>> ollama_host: " + ollama_host);
} else if (event.data.type === 'chatMessage') { ollama = new Ollama(ollama_host, ollama_model, true);
conversationHistory.push({ role: 'user', content: event.data.message }); break; // init
//console.log(">>>>>>>>>>> conversationHistory: " + JSON.stringify(conversationHistory)); case 'chatMessage':
const response = await ollama.fetchResponse(conversationHistory); //4096); conversationHistory.push({ role: 'user', content: event.data.message });
postMessage({ type: 'messageSent' }); //console.log(">>>>>>>>>>> conversationHistory: " + JSON.stringify(conversationHistory));
const response = await ollama.fetchResponse(conversationHistory); //4096);
postMessage({ type: 'messageSent' });
if (!response.ok) { if (!response.ok) {
let error_message = ''; let error_message = '';
let errorDetail = ''; let errorDetail = '';
if(response.is_exception === true){ if(response.is_exception === true){
error_message = response.error; error_message = response.error;
}else{ }else{
const errorJSON = await response.json(); const errorJSON = await response.json();
errorDetail = JSON.stringify(errorJSON); errorDetail = JSON.stringify(errorJSON);
error_message = errorJSON.error; error_message = errorJSON.error;
//console.log(">>>>>>>>>>>>> errorJSON.error.message: " + JSON.stringify(errorJSON.error.message)); //console.log(">>>>>>>>>>>>> errorJSON.error.message: " + JSON.stringify(errorJSON.error.message));
}
postMessage({ type: 'error', payload: "Ollama API request failed: " + error_message });
throw new Error("[ThunderAI] Ollama API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail);
} }
postMessage({ type: 'error', payload: "Ollama API request failed: " + error_message });
throw new Error("[ThunderAI] Ollama API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail);
}
const reader = response.body.getReader(); const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder("utf-8");
while (true) { while (true) {
const { done, value } = await reader.read(); if (stopStreaming) {
if (done) { stopStreaming = false;
conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
assistantResponseAccumulator = ''; assistantResponseAccumulator = '';
postMessage({ type: 'tokensDone' }); postMessage({ type: 'tokensDone' });
break; break;
} }
// lots of low-level Ollama response parsing stuff const { done, value } = await reader.read();
const chunk = decoder.decode(value); if (done) {
//console.log(">>>>>>>>>>>>> chunk: " + chunk); conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
const lines = chunk.split("\n"); assistantResponseAccumulator = '';
const parsedLines = lines postMessage({ type: 'tokensDone' });
.map((line) => line.replace(/^chunk: /, "").trim()) // Remove the "chunk: " prefix break;
.filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]" }
.map((line) => JSON.parse(line)); // Parse the JSON string // lots of low-level Ollama response parsing stuff
const chunk = decoder.decode(value);
for (const parsedLine of parsedLines) { //console.log(">>>>>>>>>>>>> chunk: " + chunk);
const { message } = parsedLine; const lines = chunk.split("\n");
const { content } = message; const parsedLines = lines
// Update the UI with the new content .map((line) => line.replace(/^chunk: /, "").trim()) // Remove the "chunk: " prefix
if (content) { .filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
assistantResponseAccumulator += content; .map((line) => JSON.parse(line)); // Parse the JSON string
postMessage({ type: 'newToken', payload: { token: content } });
for (const parsedLine of parsedLines) {
const { message } = parsedLine;
const { content } = message;
// Update the UI with the new content
if (content) {
assistantResponseAccumulator += content;
postMessage({ type: 'newToken', payload: { token: content } });
}
} }
} }
} break; //chatMessage
} case 'stop':
stopStreaming = true;
break; //stop
}
}; };

View file

@ -42,6 +42,7 @@ import { OpenAI } from '../js/api/openai.js';
let chatgpt_api_key = null; let chatgpt_api_key = null;
let chatgpt_model = ''; let chatgpt_model = '';
let openai = null; let openai = null;
let stopStreaming = false;
let conversationHistory = []; let conversationHistory = [];
let assistantResponseAccumulator = ''; let assistantResponseAccumulator = '';
@ -107,6 +108,13 @@ self.onmessage = async function(event) {
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder("utf-8");
while (true) { while (true) {
if (stopStreaming) {
stopStreaming = false;
conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
assistantResponseAccumulator = '';
postMessage({ type: 'tokensDone' });
break;
}
const { done, value } = await reader.read(); const { done, value } = await reader.read();
if (done) { if (done) {
conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
@ -133,5 +141,7 @@ self.onmessage = async function(event) {
} }
} }
} }
} else if (event.data.type === 'stop') {
stopStreaming = true;
} }
}; };