From 4866c112452fba7ee3e07eef1c2bf942fa98c437 Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 14 Oct 2025 12:09:57 -0700 Subject: [PATCH] feat: improve status feedback with detailed messages and colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added color-coded status indicators (success, error, streaming) - Detailed status messages for all operations - Success messages auto-reset to "Ready" after 2 seconds - Error messages show context for debugging - Status feedback for: API calls, character switching, history clearing, configuration saving, message generation, and regeneration - Smooth color transitions for better UX 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/main.js | 64 ++++++++++++++++++++++++++++++++++---------------- src/styles.css | 9 +++++++ 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/main.js b/src/main.js index efd50b5..1576098 100644 --- a/src/main.js +++ b/src/main.js @@ -485,7 +485,7 @@ async function handleRegenerateMessage(messageDiv) { // Generate a new swipe for an existing assistant message async function generateSwipe(messageDiv, userMessage) { - setStatus('Regenerating...'); + setStatus('Regenerating response...', 'default'); // Check if streaming is enabled let streamEnabled = false; @@ -518,11 +518,12 @@ async function generateSwipeNonStream(messageDiv, userMessage) { // Update swipe controls updateSwipeControls(messageDiv, swipeInfo.current, swipeInfo.total); - setStatus('Ready'); + setStatus('Regeneration complete', 'success'); + setTimeout(() => setStatus('Ready'), 2000); const regenerateBtn = messageDiv.querySelector('.message-action-btn'); if (regenerateBtn) regenerateBtn.disabled = false; } catch (error) { - setStatus('Error'); + setStatus(`Regeneration failed: ${error.substring(0, 40)}...`, 'error'); const regenerateBtn = messageDiv.querySelector('.message-action-btn'); if (regenerateBtn) regenerateBtn.disabled = false; addMessage(`Error regenerating message: ${error}`, false); @@ -531,8 +532,7 @@ async function generateSwipeNonStream(messageDiv, userMessage) { // Generate swipe using streaming async function generateSwipeStream(messageDiv, userMessage) { - setStatus('Streaming...'); - statusText.classList.add('streaming'); + setStatus('Streaming regeneration...', 'streaming'); let fullContent = ''; const contentDiv = messageDiv.querySelector('.message-content'); @@ -561,8 +561,8 @@ async function generateSwipeStream(messageDiv, userMessage) { console.error('Failed to add swipe:', error); } - setStatus('Ready'); - statusText.classList.remove('streaming'); + setStatus('Regeneration complete', 'success'); + setTimeout(() => setStatus('Ready'), 2000); const regenerateBtn = messageDiv.querySelector('.message-action-btn'); if (regenerateBtn) regenerateBtn.disabled = false; tokenUnlisten(); @@ -574,8 +574,7 @@ async function generateSwipeStream(messageDiv, userMessage) { } catch (error) { tokenUnlisten(); completeUnlisten(); - statusText.classList.remove('streaming'); - setStatus('Error'); + setStatus(`Regeneration failed: ${error.substring(0, 40)}...`, 'error'); const regenerateBtn = messageDiv.querySelector('.message-action-btn'); if (regenerateBtn) regenerateBtn.disabled = false; addMessage(`Error: ${error}`, false); @@ -620,7 +619,7 @@ async function sendMessage(message, isRegenerate = false) { sendBtn.disabled = true; messageInput.disabled = true; - setStatus('Thinking...'); + setStatus('Connecting to API...', 'default'); // Check if streaming is enabled let streamEnabled = false; @@ -633,8 +632,7 @@ async function sendMessage(message, isRegenerate = false) { if (streamEnabled) { // Use streaming - setStatus('Streaming...'); - statusText.classList.add('streaming'); + setStatus('Streaming response...', 'streaming'); let streamingMessageDiv = null; let streamingContentDiv = null; @@ -705,8 +703,8 @@ async function sendMessage(message, isRegenerate = false) { actionsDiv.appendChild(regenerateBtn); streamingMessageDiv.appendChild(actionsDiv); - setStatus('Ready'); - statusText.classList.remove('streaming'); + setStatus('Response complete', 'success'); + setTimeout(() => setStatus('Ready'), 2000); sendBtn.disabled = false; messageInput.disabled = false; messageInput.focus(); @@ -719,17 +717,17 @@ async function sendMessage(message, isRegenerate = false) { } catch (error) { tokenUnlisten(); completeUnlisten(); - statusText.classList.remove('streaming'); if (streamingMessageDiv) { streamingMessageDiv.remove(); } if (error.includes('not configured')) { addMessage('API not configured. Please configure your API settings.', false); + setStatus('API not configured', 'error'); setTimeout(showSettings, 1000); } else { addMessage(`Error: ${error}`, false); + setStatus(`Error: ${error.substring(0, 50)}...`, 'error'); } - setStatus('Error'); sendBtn.disabled = false; messageInput.disabled = false; messageInput.focus(); @@ -742,16 +740,18 @@ async function sendMessage(message, isRegenerate = false) { const response = await invoke('chat', { message }); removeTypingIndicator(); addMessage(response, false); - setStatus('Ready'); + setStatus('Response complete', 'success'); + setTimeout(() => setStatus('Ready'), 2000); } catch (error) { removeTypingIndicator(); if (error.includes('not configured')) { addMessage('API not configured. Please configure your API settings.', false); + setStatus('API not configured', 'error'); setTimeout(showSettings, 1000); } else { addMessage(`Error: ${error}`, false); + setStatus(`Error: ${error.substring(0, 50)}...`, 'error'); } - setStatus('Error'); } finally { sendBtn.disabled = false; messageInput.disabled = false; @@ -788,9 +788,21 @@ function removeTypingIndicator() { } } -// Update status -function setStatus(text) { +// Update status with optional styling +function setStatus(text, type = 'default') { statusText.textContent = text; + + // Remove all status classes + statusText.classList.remove('streaming', 'error', 'success'); + + // Add appropriate class based on type + if (type === 'streaming') { + statusText.classList.add('streaming'); + } else if (type === 'error') { + statusText.classList.add('error'); + } else if (type === 'success') { + statusText.classList.add('success'); + } } // Show/hide settings @@ -903,20 +915,24 @@ async function handleSaveSettings(e) { saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; + setStatus('Saving configuration...', 'default'); try { await invoke('save_api_config', { baseUrl, apiKey, model, stream }); validationMsg.textContent = 'Configuration saved successfully'; validationMsg.className = 'validation-message success'; + setStatus('Configuration saved', 'success'); setTimeout(() => { hideSettings(); messagesContainer.innerHTML = ''; addMessage('API configured. Ready to chat.', false, true); + setStatus('Ready'); }, 1000); } catch (error) { validationMsg.textContent = `Failed to save: ${error}`; validationMsg.className = 'validation-message error'; + setStatus('Failed to save configuration', 'error'); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Save Configuration'; @@ -1048,12 +1064,16 @@ async function loadCharacters() { // Handle character switching async function handleCharacterSwitch() { const characterId = characterSelect.value; + setStatus('Switching character...', 'default'); try { await invoke('set_active_character', { characterId }); messagesContainer.innerHTML = ''; await loadCharacters(); + setStatus('Character switched', 'success'); + setTimeout(() => setStatus('Ready'), 2000); } catch (error) { console.error('Failed to switch character:', error); + setStatus('Failed to switch character', 'error'); addMessage(`Failed to switch character: ${error}`, false); } } @@ -1175,6 +1195,7 @@ async function clearHistory() { return; } + setStatus('Clearing history...', 'default'); try { await invoke('clear_chat_history'); messagesContainer.innerHTML = ''; @@ -1183,7 +1204,10 @@ async function clearHistory() { } else { addMessage('Conversation cleared. Ready to chat.', false, true); } + setStatus('History cleared', 'success'); + setTimeout(() => setStatus('Ready'), 2000); } catch (error) { + setStatus('Failed to clear history', 'error'); addMessage(`Failed to clear history: ${error}`, false); } } diff --git a/src/styles.css b/src/styles.css index 766517b..da63d7e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -732,6 +732,7 @@ body { .status-text { font-size: 12px; color: var(--text-secondary); + transition: color 0.2s ease; } .status-text.streaming { @@ -739,6 +740,14 @@ body { animation: pulse 2s ease-in-out infinite; } +.status-text.error { + color: #ef4444; +} + +.status-text.success { + color: #22c55e; +} + @keyframes pulse { 0%, 100% { opacity: 1;