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": ""
},
"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": ""
}
}

View file

@ -432,7 +432,7 @@
"description": ""
},
"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": ""
},
"Debug": {
@ -478,5 +478,9 @@
"ollama_api_connecting": {
"message": "Will attempt to connect to the Ollama Local Server using the host",
"description": ""
},
"andModel": {
"message": "and model",
"description": ""
}
}

View file

@ -432,7 +432,7 @@
"description": ""
},
"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": ""
}
}

View file

@ -432,7 +432,7 @@
"description": ""
},
"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": ""
}
}

View file

@ -68,26 +68,27 @@ let promptData = null;
// }
// ============================== TESTING - END
const params = new URLSearchParams(window.location.search);
switch (llm) {
case "chatgpt_api":
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: ''});
//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});
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;
case "ollama_api": {
let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: ''});
//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});
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;
}
}
// Event listeners for worker messages
worker.onmessage = function(event) {
const { type, payload } = event.data;

View file

@ -50,6 +50,12 @@ messagesInputStyle .textContent = `
cursor: pointer;
border-radius: 10px;
}
#stopButton {
width: 44px;
height: 36px;
cursor: pointer;
border-radius: 10px;
}
@media (prefers-color-scheme: dark) {
#messageInputField {
background-color: #303030;
@ -87,7 +93,32 @@ sendIcon.appendChild(sendPath);
sendButton.appendChild(sendIcon);
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 {
model = '';
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
@ -95,9 +126,11 @@ class MessageInput extends HTMLElement {
this._messageInputField = shadowRoot.querySelector('#messageInputField');
this._sendButton = shadowRoot.querySelector('#sendButton');
this._stopButton = shadowRoot.querySelector('#stopButton');
this._messageInputField.addEventListener('keydown', this._handleKeyDown.bind(this));
this._sendButton.addEventListener('click', this._handleClick.bind(this));
this._stopButton.addEventListener('click', this._handleStopClick.bind(this));
}
connectedCallback() {
@ -107,14 +140,18 @@ class MessageInput extends HTMLElement {
async init(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) {
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() {
// console.log("[ThunderAI] handleMessageSent");
this._messageInputField.value = '';
@ -123,8 +160,12 @@ class MessageInput extends HTMLElement {
enableInput() {
// console.log("[ThunderAI] enableInput");
this._messageInputField.value = '';
this._sendButton.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) {
@ -137,6 +178,12 @@ class MessageInput extends HTMLElement {
this._handleNewChatMessage();
}
_handleStopClick() {
this.worker.postMessage({ type: 'stop' });
this._stopButton.setAttribute('disabled', 'disabled');
this._stopButton.title = 'Stopping...';
}
_handleNewChatMessage() {
//do nothing if input is empty
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
this._sendButton.setAttribute('disabled', 'disabled');
this._sendButton.style.display = 'none';
this._stopButton.removeAttribute('disabled');
this._stopButton.style.display = 'block';
this._messageInputField.setAttribute('disabled', 'disabled');
let messageContent = this._messageInputField.value;
this._messageInputField.value = '';

View file

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

View file

@ -25,66 +25,80 @@ import { Ollama } from '../js/api/ollama.js';
let ollama_host = null;
let ollama_model = '';
let ollama = null;
let stopStreaming = false;
let conversationHistory = [];
let assistantResponseAccumulator = '';
self.onmessage = async function(event) {
if (event.data.type === 'init') {
ollama_host = event.data.ollama_host;
ollama_model = event.data.ollama_model;
//console.log(">>>>>>>>>>> ollama_host: " + ollama_host);
ollama = new Ollama(ollama_host, ollama_model, true);
} else if (event.data.type === 'chatMessage') {
conversationHistory.push({ role: 'user', content: event.data.message });
//console.log(">>>>>>>>>>> conversationHistory: " + JSON.stringify(conversationHistory));
const response = await ollama.fetchResponse(conversationHistory); //4096);
postMessage({ type: 'messageSent' });
switch (event.data.type) {
case 'init':
ollama_host = event.data.ollama_host;
ollama_model = event.data.ollama_model;
//console.log(">>>>>>>>>>> ollama_host: " + ollama_host);
ollama = new Ollama(ollama_host, ollama_model, true);
break; // init
case 'chatMessage':
conversationHistory.push({ role: 'user', content: event.data.message });
//console.log(">>>>>>>>>>> conversationHistory: " + JSON.stringify(conversationHistory));
const response = await ollama.fetchResponse(conversationHistory); //4096);
postMessage({ type: 'messageSent' });
if (!response.ok) {
let error_message = '';
let errorDetail = '';
if(response.is_exception === true){
error_message = response.error;
}else{
const errorJSON = await response.json();
errorDetail = JSON.stringify(errorJSON);
error_message = errorJSON.error;
//console.log(">>>>>>>>>>>>> errorJSON.error.message: " + JSON.stringify(errorJSON.error.message));
if (!response.ok) {
let error_message = '';
let errorDetail = '';
if(response.is_exception === true){
error_message = response.error;
}else{
const errorJSON = await response.json();
errorDetail = JSON.stringify(errorJSON);
error_message = errorJSON.error;
//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 decoder = new TextDecoder("utf-8");
while (true) {
const { done, value } = await reader.read();
if (done) {
conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
assistantResponseAccumulator = '';
postMessage({ type: 'tokensDone' });
break;
}
// lots of low-level Ollama response parsing stuff
const chunk = decoder.decode(value);
//console.log(">>>>>>>>>>>>> chunk: " + chunk);
const lines = chunk.split("\n");
const parsedLines = lines
.map((line) => line.replace(/^chunk: /, "").trim()) // Remove the "chunk: " prefix
.filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
.map((line) => JSON.parse(line)); // Parse the JSON string
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 } });
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
while (true) {
if (stopStreaming) {
stopStreaming = false;
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 Ollama response parsing stuff
const chunk = decoder.decode(value);
//console.log(">>>>>>>>>>>>> chunk: " + chunk);
const lines = chunk.split("\n");
const parsedLines = lines
.map((line) => line.replace(/^chunk: /, "").trim()) // Remove the "chunk: " prefix
.filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
.map((line) => JSON.parse(line)); // Parse the JSON string
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_model = '';
let openai = null;
let stopStreaming = false;
let conversationHistory = [];
let assistantResponseAccumulator = '';
@ -107,6 +108,13 @@ self.onmessage = async function(event) {
const decoder = new TextDecoder("utf-8");
while (true) {
if (stopStreaming) {
stopStreaming = false;
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 });
@ -133,5 +141,7 @@ self.onmessage = async function(event) {
}
}
}
} else if (event.data.type === 'stop') {
stopStreaming = true;
}
};