diff --git a/gui/css/styles.css b/gui/css/styles.css index b0b185c..66bd07e 100644 --- a/gui/css/styles.css +++ b/gui/css/styles.css @@ -38,6 +38,25 @@ body { background-color: #6c757d; cursor: not-allowed; } +#stopButton { + padding: 10px 20px; + font-size: 1rem; + color: white; + background-color: #c0392b; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.2s; +} + +#stopButton:hover { + background-color: #a93226; +} + +#stopButton:disabled { + background-color: #6c757d; + cursor: not-allowed; +} .message { font-size: 0.9em; padding: 10px 15px; diff --git a/gui/index.html b/gui/index.html index bb5534f..ca6bd49 100644 --- a/gui/index.html +++ b/gui/index.html @@ -23,6 +23,7 @@
+
diff --git a/gui/kuzco-gui.js b/gui/kuzco-gui.js index cf1e342..66a1f3e 100644 --- a/gui/kuzco-gui.js +++ b/gui/kuzco-gui.js @@ -75,4 +75,8 @@ const kuzcoCore = new KuzcoCore(); ipcMain.handle('send-prompt', async (event, { prompt, model }) => { console.log("Received model in main process:", model); return await kuzcoCore.sendPrompt(prompt, model); +}); + +ipcMain.on('abort-prompt', () => { + kuzcoCore.abortFetch(); }); \ No newline at end of file diff --git a/gui/kuzcoCore.js b/gui/kuzcoCore.js index 1c4a18d..86c5edb 100644 --- a/gui/kuzcoCore.js +++ b/gui/kuzcoCore.js @@ -14,6 +14,8 @@ class KuzcoCore { constructor() { this.configPath = path.join(os.homedir(), '.kuzco-cli', 'config.json'); this.API_KEY = this.loadApiKey(); + this.controller = new AbortController(); + this.isAborted = false; } loadApiKey() { @@ -36,12 +38,17 @@ class KuzcoCore { return fs.existsSync(this.configPath) && this.API_KEY !== ''; } + abortFetch() { + this.isAborted = true; + this.controller.abort(); + } + async sendPrompt(prompt, model) { console.log("Model received in sendPrompt:", model) - const controller = new AbortController(); - const signal = controller.signal; + this.controller = new AbortController(); + const signal = this.controller.signal; - const timeoutId = setTimeout(() => controller.abort(), 25000); + const timeoutId = setTimeout(() => this.controller.abort(), 25000); try { const response = await fetch('https://relay.kuzco.xyz/v1/chat/completions', { @@ -65,15 +72,19 @@ class KuzcoCore { } return await response.json(); + } catch (error) { clearTimeout(timeoutId); - if (error.name === 'AbortError') { - console.error('Request was aborted due to timeout.'); - return { error: 'Request timed out. Please try again.' }; - } else { - console.error(`An error occurred: ${error.message}`); - return { error: error.message }; - } + const errorMessage = this.isAborted + ? 'Request aborted by the user. Please try again.' + : 'Request timed out. Please try again.'; + + this.isAborted = false; + this.controller = new AbortController(); + + return error.name === 'AbortError' + ? { error: errorMessage } + : { error: error.message }; } } } diff --git a/gui/preload.js b/gui/preload.js index 8435c4f..7105cef 100644 --- a/gui/preload.js +++ b/gui/preload.js @@ -3,4 +3,5 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { sendPrompt: (prompt, model) => ipcRenderer.invoke('send-prompt', { prompt, model }), onApiKeySaved: (callback) => ipcRenderer.on('api-key-saved', callback), + abortPrompt: () => ipcRenderer.send('abort-prompt') }); \ No newline at end of file diff --git a/gui/renderer.js b/gui/renderer.js index 8c3715f..4c8a4fa 100644 --- a/gui/renderer.js +++ b/gui/renderer.js @@ -19,6 +19,7 @@ document.addEventListener('DOMContentLoaded', () => { const promptInput = document.getElementById('promptInput'); const modelSelect = document.getElementById('modelSelect'); const sendButton = document.getElementById('sendButton'); + const stopButton = document.getElementById('stopButton'); const modelSelectionContainer = document.getElementById('modelSelectionContainer'); if (chatForm && promptInput && modelSelect) { @@ -32,6 +33,7 @@ document.addEventListener('DOMContentLoaded', () => { sendButton.disabled = true; promptInput.disabled = true; + stopButton.disabled = false; displayMessage(userInput, 'user'); @@ -49,10 +51,10 @@ document.addEventListener('DOMContentLoaded', () => { displayMessage(`Error: ${error.message}`, 'assistant'); } finally { typingIndicator.remove(); - sendButton.disabled = false; promptInput.disabled = false; promptInput.focus(); + stopButton.disabled = true; } }); } else { @@ -60,6 +62,11 @@ document.addEventListener('DOMContentLoaded', () => { } }); +document.getElementById('stopButton').addEventListener('click', () => { + window.electronAPI.abortPrompt(); + document.getElementById('stopButton').disabled = true; +}); + function displayTypingIndicator() { const chatHistory = document.getElementById('chatHistory'); const typingIndicator = document.createElement('div');