From 2b875cfa2726c8aefb479a6ba5a1ebd9c84b2a1d Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:56:21 +0000 Subject: [PATCH] feat: show thinking above response with expand/collapse, first line preview --- .../src/lib/components/ChatInterface.svelte | 145 ++++++++++-------- src/frontend/src/lib/stores/chatStore.ts | 2 + src/frontend/src/routes/bot/[id]/+page.svelte | 13 +- 3 files changed, 88 insertions(+), 72 deletions(-) diff --git a/src/frontend/src/lib/components/ChatInterface.svelte b/src/frontend/src/lib/components/ChatInterface.svelte index 63d06e6..072369b 100644 --- a/src/frontend/src/lib/components/ChatInterface.svelte +++ b/src/frontend/src/lib/components/ChatInterface.svelte @@ -6,8 +6,6 @@ interface Props { bot: Bot | null; messages: ChatMessage[]; - isThinking?: boolean; - thinkingContent?: string; onSendMessage: (message: string) => void; onSelectBot?: (botId: string) => void; availableBots?: Bot[]; @@ -17,8 +15,6 @@ let { bot, messages, - isThinking = false, - thinkingContent = '', onSendMessage, onSelectBot, availableBots = [], @@ -27,13 +23,12 @@ let messageInput = $state(''); let chatContainer: HTMLDivElement; - let showThinking = $state(false); + let expandedThinking: Record = $state({}); function handleSend() { if (!messageInput.trim()) return; onSendMessage(messageInput); messageInput = ''; - showThinking = false; } function handleKeydown(e: KeyboardEvent) { @@ -50,8 +45,8 @@ } } - function toggleThinking() { - showThinking = !showThinking; + function toggleThinkingExpand(messageId: string) { + expandedThinking[messageId] = !expandedThinking[messageId]; } $effect(() => { @@ -62,13 +57,6 @@ } }); - // Watch for thinking state changes - $effect(() => { - if (isThinking && thinkingContent) { - showThinking = true; - } - }); - function renderContent(content: string) { return parseMarkdown(content); } @@ -89,7 +77,7 @@ {/if}
- {#if messages.length === 0 && !isThinking} + {#if messages.length === 0}

Welcome to {bot?.name || 'your bot'}! Describe your trading strategy in plain English.

Example: "Buy PEPE when the price drops by 5% within 1 hour"

@@ -98,6 +86,24 @@ {#each messages as message}
+ {#if message.role === 'assistant' && message.thinking} + {@const firstLine = message.thinking.split('\n')[0]} + {@const isExpanded = expandedThinking[message.id] ?? false} +
+ + {#if isExpanded} +
+ {message.thinking} +
+ {/if} +
+ {/if}
{#each renderContent(message.content) as segment} {#if segment.type === 'bold'} @@ -125,51 +131,7 @@ {message.timestamp.toLocaleTimeString()}
- {/each} - - {#if isThinking} -
-
- {#if thinkingContent} -
- -
- {#if showThinking} -
- {#each renderContent(thinkingContent) as segment} - {#if segment.type === 'bold'} - {segment.content} - {:else if segment.type === 'italic'} - {segment.content} - {:else if segment.type === 'code'} - {segment.content} - {:else if segment.type === 'codeBlock'} -
{segment.content}
- {:else if segment.type === 'list' && segment.items} -
    - {#each segment.items as item} -
  • {item}
  • - {/each} -
- {:else} - {segment.content} - {/if} - {/each} -
- {/if} - {:else} -
- - - -
- {/if} -
-
- {/if} + {/each}
{#if bot} @@ -179,9 +141,8 @@ onkeydown={handleKeydown} placeholder="Describe your trading strategy..." rows="1" - disabled={isThinking} > -
@@ -280,6 +241,64 @@ border-bottom-left-radius: 4px; } + .thinking-section { + margin-bottom: 0.5rem; + padding: 0.5rem 0.75rem; + background: rgba(255, 255, 255, 0.03); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .thinking-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + background: none; + border: none; + color: #888; + cursor: pointer; + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.8rem; + transition: background 0.2s; + width: 100%; + text-align: left; + } + + .thinking-toggle:hover { + background: rgba(255, 255, 255, 0.1); + } + + .thinking-icon { + font-size: 0.6rem; + color: #667eea; + } + + .thinking-label { + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #667eea; + } + + .thinking-preview { + color: #666; + font-style: italic; + font-weight: normal; + text-transform: none; + letter-spacing: normal; + } + + .thinking-content { + color: #888; + font-size: 0.85rem; + padding: 0.75rem 0.5rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + margin-top: 0.5rem; + white-space: pre-wrap; + line-height: 1.6; + } + .message.system .message-content { background: rgba(251, 191, 36, 0.1); color: #fbbf24; diff --git a/src/frontend/src/lib/stores/chatStore.ts b/src/frontend/src/lib/stores/chatStore.ts index ffb2d63..42c5182 100644 --- a/src/frontend/src/lib/stores/chatStore.ts +++ b/src/frontend/src/lib/stores/chatStore.ts @@ -5,6 +5,7 @@ export interface ChatMessage { id: string; role: 'user' | 'assistant' | 'system'; content: string; + thinking: string | null; timestamp: Date; } @@ -37,6 +38,7 @@ export function setMessages(messages: BotConversation[]) { id: m.id, role: m.role, content: m.content, + thinking: null, timestamp: new Date(m.created_at) }))); } diff --git a/src/frontend/src/routes/bot/[id]/+page.svelte b/src/frontend/src/routes/bot/[id]/+page.svelte index 7f04f5e..4a2378f 100644 --- a/src/frontend/src/routes/bot/[id]/+page.svelte +++ b/src/frontend/src/routes/bot/[id]/+page.svelte @@ -9,7 +9,6 @@ let botId = $derived($page.params.id); let isSending = $state(false); let showStrategy = $state(false); - let thinkingContent = $state(''); onMount(async () => { if (!$isAuthenticated && !$isLoading) { @@ -44,7 +43,6 @@ if (isSending) return; isSending = true; - thinkingContent = ''; // Add user's message immediately so it shows even before API response addMessage({ role: 'user', content: message }); @@ -57,9 +55,8 @@ const response = await api.bots.chat(botId, message, controller.signal); clearTimeout(timeoutId); - // Set thinking content for display - thinkingContent = response.thinking || ''; - addMessage({ role: 'assistant', content: response.response }); + // Add assistant response with thinking + addMessage({ role: 'assistant', content: response.response, thinking: response.thinking || null }); if (response.strategy_config) { const bot = await api.bots.get(botId); @@ -67,9 +64,9 @@ } } catch (e) { if (e instanceof Error && e.name === 'AbortError') { - addMessage({ role: 'assistant', content: 'Request timed out. Please try again.' }); + addMessage({ role: 'assistant', content: 'Request timed out. Please try again.', thinking: null }); } else { - addMessage({ role: 'assistant', content: 'Sorry, I encountered an error. Please try again.' }); + addMessage({ role: 'assistant', content: 'Sorry, I encountered an error. Please try again.', thinking: null }); } } finally { isSending = false; @@ -112,8 +109,6 @@