From 7781b2caabccb7185ad3725153029dc726b8f96f Mon Sep 17 00:00:00 2001 From: Hikari Date: Sat, 7 Feb 2026 12:42:34 -0800 Subject: [PATCH] feat: add extended thinking blocks display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements support for displaying Claude's extended thinking blocks in the conversation UI with a collapsible, visually distinct component. Changes: - Backend: Update wsl_bridge.rs to emit thinking blocks with dedicated line_type instead of system messages - Types: Add "thinking" to TerminalLine type union - Config: Add show_thinking_blocks toggle (default: true) - UI: Create ThinkingBlock.svelte component with collapsible interface - Terminal: Update to conditionally render thinking blocks - Settings: Add toggle in Appearance section of ConfigSidebar The ThinkingBlock component features: - Lightbulb icon to indicate extended thinking - Collapsible/expandable with animated chevron - Distinct styling: dimmed, italic, monospace - Timestamp display - Respects global show_thinking_blocks config setting Note: Extended thinking support in Claude Code CLI appears to be in development. This implementation is ready and will automatically display thinking blocks once the CLI begins emitting them. Issue: #120 ✨ Implemented by Hikari~ 🌸 --- src-tauri/src/wsl_bridge.rs | 4 +- src/lib/components/ConfigSidebar.svelte | 17 +++ src/lib/components/StatusBar.svelte | 1 + src/lib/components/Terminal.svelte | 154 +++++++++++++----------- src/lib/components/ThinkingBlock.svelte | 130 ++++++++++++++++++++ src/lib/stores/config.test.ts | 3 + src/lib/stores/config.ts | 7 ++ src/lib/tauri.ts | 4 +- src/lib/types/messages.ts | 2 +- 9 files changed, 247 insertions(+), 75 deletions(-) create mode 100644 src/lib/components/ThinkingBlock.svelte diff --git a/src-tauri/src/wsl_bridge.rs b/src-tauri/src/wsl_bridge.rs index dac8689..ebac570 100644 --- a/src-tauri/src/wsl_bridge.rs +++ b/src-tauri/src/wsl_bridge.rs @@ -974,8 +974,8 @@ fn process_json_line( let _ = app.emit( "claude:output", OutputEvent { - line_type: "system".to_string(), - content: format!("[Thinking] {}", thinking), + line_type: "thinking".to_string(), + content: thinking.clone(), tool_name: None, conversation_id: conversation_id.clone(), cost: None, diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte index 2f3c94b..7274373 100644 --- a/src/lib/components/ConfigSidebar.svelte +++ b/src/lib/components/ConfigSidebar.svelte @@ -51,6 +51,7 @@ budget_action: "warn", budget_warning_threshold: 0.8, discord_rpc_enabled: true, + show_thinking_blocks: true, }); let showCustomThemeEditor = $state(false); @@ -703,6 +704,22 @@ Use Ctrl++ / Ctrl+- to quickly adjust, Ctrl+0 to reset

+ + +
+ +

+ Display Claude's extended thinking process in the conversation. Thinking blocks can be + expanded/collapsed to see reasoning details. +

+
diff --git a/src/lib/components/StatusBar.svelte b/src/lib/components/StatusBar.svelte index af4a37b..aef4f74 100644 --- a/src/lib/components/StatusBar.svelte +++ b/src/lib/components/StatusBar.svelte @@ -92,6 +92,7 @@ budget_action: "warn", budget_warning_threshold: 0.8, discord_rpc_enabled: true, + show_thinking_blocks: true, }); let streamerModeActive = $state(false); diff --git a/src/lib/components/Terminal.svelte b/src/lib/components/Terminal.svelte index ee473e8..616231c 100644 --- a/src/lib/components/Terminal.svelte +++ b/src/lib/components/Terminal.svelte @@ -4,9 +4,10 @@ import ConversationTabs from "./ConversationTabs.svelte"; import Markdown from "./Markdown.svelte"; import HighlightedText from "./HighlightedText.svelte"; + import ThinkingBlock from "./ThinkingBlock.svelte"; import { searchState, searchQuery } from "$lib/stores/search"; import { clipboardStore } from "$lib/stores/clipboard"; - import { shouldHidePaths, maskPaths } from "$lib/stores/config"; + import { shouldHidePaths, maskPaths, showThinkingBlocks } from "$lib/stores/config"; let terminalElement: HTMLDivElement; let shouldAutoScroll = true; @@ -24,6 +25,11 @@ hidePaths = value; }); + let showThinking = true; + showThinkingBlocks.subscribe((value) => { + showThinking = value; + }); + claudeStore.terminalLines.subscribe((value) => { lines = value; }); @@ -84,6 +90,8 @@ return "terminal-tool"; case "error": return "terminal-error"; + case "thinking": + return "terminal-thinking"; default: return "terminal-default"; } @@ -209,80 +217,86 @@ {:else} {#each lines as line (line.id)} -
- {formatTime(line.timestamp)} - {#if line.parentToolUseId} - - + {/if} + {:else} +
+ {formatTime(line.timestamp)} + {#if line.parentToolUseId} + + + + + + {/if} + {#if line.cost && line.cost.costUsd > 0} + - + {/if} + {#if getLinePrefix(line.type)} + {getLinePrefix(line.type)} + {/if} + {#if line.toolName} + [{line.toolName}] + {/if} + {#if line.type === "assistant" || line.type === "user"} +
+ - - - {/if} - {#if line.cost && line.cost.costUsd > 0} - - ${line.cost.costUsd < 0.01 - ? line.cost.costUsd.toFixed(4) - : line.cost.costUsd.toFixed(3)} - - {/if} - {#if getLinePrefix(line.type)} - {getLinePrefix(line.type)} - {/if} - {#if line.toolName} - [{line.toolName}] - {/if} - {#if line.type === "assistant" || line.type === "user"} -
- handleCopyMessage(line.id, line.content)} + title="Copy message" + > + + + + + {copiedMessageId === line.id ? "Copied!" : "Copy"} + +
+ {:else} + - -
- {:else} - - {/if} -
+ {/if} +
+ {/if} {/each} {/if} diff --git a/src/lib/components/ThinkingBlock.svelte b/src/lib/components/ThinkingBlock.svelte new file mode 100644 index 0000000..369b4fc --- /dev/null +++ b/src/lib/components/ThinkingBlock.svelte @@ -0,0 +1,130 @@ + + +
+ + + {#if isExpanded} +
+ {content} +
+ {/if} +
+ + diff --git a/src/lib/stores/config.test.ts b/src/lib/stores/config.test.ts index 40db753..db65871 100644 --- a/src/lib/stores/config.test.ts +++ b/src/lib/stores/config.test.ts @@ -193,6 +193,7 @@ describe("config store", () => { budget_action: "warn", budget_warning_threshold: 0.8, discord_rpc_enabled: true, + show_thinking_blocks: true, }; expect(config.model).toBe("claude-sonnet-4"); @@ -238,6 +239,7 @@ describe("config store", () => { budget_action: "warn", budget_warning_threshold: 0.8, discord_rpc_enabled: true, + show_thinking_blocks: true, }; expect(config.model).toBeNull(); @@ -773,6 +775,7 @@ describe("config store", () => { budget_action: "block", budget_warning_threshold: 0.9, discord_rpc_enabled: false, + show_thinking_blocks: true, }; const mockInvokeImpl = vi.mocked(invoke); diff --git a/src/lib/stores/config.ts b/src/lib/stores/config.ts index a19d5bd..b904379 100644 --- a/src/lib/stores/config.ts +++ b/src/lib/stores/config.ts @@ -45,6 +45,8 @@ export interface HikariConfig { budget_warning_threshold: number; // Discord RPC settings discord_rpc_enabled: boolean; + // Thinking blocks settings + show_thinking_blocks: boolean; } const defaultConfig: HikariConfig = { @@ -84,6 +86,7 @@ const defaultConfig: HikariConfig = { budget_action: "warn", budget_warning_threshold: 0.8, discord_rpc_enabled: true, + show_thinking_blocks: true, }; function createConfigStore() { @@ -297,6 +300,10 @@ export const shouldHidePaths = derived( configStore.config, ($config) => $config.streamer_mode && $config.streamer_hide_paths ); +export const showThinkingBlocks = derived( + configStore.config, + ($config) => $config.show_thinking_blocks +); /** * Masks file paths in text when streamer mode with hide paths is enabled. diff --git a/src/lib/tauri.ts b/src/lib/tauri.ts index e51cc75..e854caf 100644 --- a/src/lib/tauri.ts +++ b/src/lib/tauri.ts @@ -272,7 +272,7 @@ export async function initializeTauriListeners() { if (conversation_id) { claudeStore.addLineToConversation( conversation_id, - line_type as "user" | "assistant" | "system" | "tool" | "error", + line_type as "user" | "assistant" | "system" | "tool" | "error" | "thinking", content, tool_name || undefined, costData, @@ -281,7 +281,7 @@ export async function initializeTauriListeners() { } else { // Fallback to active conversation if no conversation_id provided claudeStore.addLine( - line_type as "user" | "assistant" | "system" | "tool" | "error", + line_type as "user" | "assistant" | "system" | "tool" | "error" | "thinking", content, tool_name || undefined, costData, diff --git a/src/lib/types/messages.ts b/src/lib/types/messages.ts index 4826cfa..1a9e7cd 100644 --- a/src/lib/types/messages.ts +++ b/src/lib/types/messages.ts @@ -1,6 +1,6 @@ export interface TerminalLine { id: string; - type: "user" | "assistant" | "system" | "tool" | "error"; + type: "user" | "assistant" | "system" | "tool" | "error" | "thinking"; content: string; timestamp: Date; toolName?: string;