From 8336ac6eb11cf55fd331c7c08713548acff978e3 Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 20 Apr 2026 22:32:55 +0200 Subject: [PATCH] feat: thinking blocks collapsed by default [appimage] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extended Thinking (SDK thinking blocks) rendered as collapsible
elements — closed by default, click to expand - Text-based thinking patterns (Lass mich..., Ich analysiere...) auto-detected and wrapped in collapsible block - Clean styling: subtle border, smaller font, 💭 icon - Bridge now forwards thinking content blocks (previously ignored) Co-Authored-By: Claude Opus 4.6 --- scripts/claude-bridge.js | 6 +++ src/lib/components/ChatPanel.svelte | 58 ++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/scripts/claude-bridge.js b/scripts/claude-bridge.js index dff7356..da7b2b4 100644 --- a/scripts/claude-bridge.js +++ b/scripts/claude-bridge.js @@ -542,6 +542,12 @@ async function sendMessage(message, requestId, model = null, contextOverride = n if (block.type === 'text' && block.text) { fullText += block.text; sendEvent('text', { text: block.text }); + } else if (block.type === 'thinking' && block.thinking) { + // Extended Thinking — als kollabierbaren Block senden + const thinkLines = block.thinking.split('\n').length; + const collapsed = `
💭 Überlegung (${thinkLines} Zeilen)\n\n${block.thinking}\n\n
\n\n`; + fullText += collapsed; + sendEvent('text', { text: collapsed }); } else if (block.type === 'tool_use') { // Tool-Call von Main-Agent — manuell weiterreichen, damit // der tool_use-Case weiter unten greift diff --git a/src/lib/components/ChatPanel.svelte b/src/lib/components/ChatPanel.svelte index edbc62d..45f694e 100644 --- a/src/lib/components/ChatPanel.svelte +++ b/src/lib/components/ChatPanel.svelte @@ -42,12 +42,38 @@ function renderMarkdown(text: string): string { try { - return marked.parse(text) as string; + // Thinking-Blöcke erkennen und in
packen + const processed = collapseThinkingBlocks(text); + return marked.parse(processed) as string; } catch { return text; } } + /** + * Erkennt "Denk-Blöcke" im Text und packt sie in Markdown-
. + * Patterns: Absätze die mit typischen Überlegungs-Phrasen starten, + * gefolgt von der eigentlichen Antwort. + */ + function collapseThinkingBlocks(text: string): string { + // Pattern: Text beginnt mit Analyse/Überlegungs-Block, dann kommt die Antwort + const thinkingPatterns = [ + /^((?:(?:Lass mich|Ich (?:schaue|prüfe|analysiere|untersuche|überleg)|OK,? (?:lass|ich)|Gut,? (?:lass|ich)|Hmm|Also|Zunächst|Zuerst).*?\n(?:.*\n)*?))((?:\n(?:#{1,3} |(?:\*\*|Die |Das |Hier |Zusammen|Fertig|Erledigt|✅)).*[\s\S]*))/m, + ]; + + for (const pattern of thinkingPatterns) { + const match = text.match(pattern); + if (match && match[1] && match[1].split('\n').length > 5) { + const thinkingPart = match[1].trim(); + const answerPart = match[2].trim(); + const lines = thinkingPart.split('\n').length; + return `
\n💭 Überlegung (${lines} Zeilen)\n\n${thinkingPart}\n\n
\n\n${answerPart}`; + } + } + + return text; + } + // Svelte Action: Copy-Buttons zu Code-Blöcken hinzufügen function addCopyButtons(node: HTMLElement) { function processCodeBlocks() { @@ -1324,6 +1350,36 @@ background: var(--bg-hover, #333); } + /* Thinking-Block (details/summary) */ + .message-content :global(details.thinking-block) { + margin: 0.4rem 0; + border: 1px solid var(--border, #3a3a3a); + border-radius: 4px; + background: var(--bg-tertiary, #1e1e1e); + } + + .message-content :global(details.thinking-block summary) { + padding: 0.4rem 0.6rem; + cursor: pointer; + font-size: 0.8rem; + color: var(--text-secondary, #888); + user-select: none; + } + + .message-content :global(details.thinking-block summary:hover) { + color: var(--text-primary, #ccc); + } + + .message-content :global(details.thinking-block[open]) { + padding-bottom: 0.4rem; + } + + .message-content :global(details.thinking-block[open] > :not(summary)) { + padding: 0 0.6rem; + font-size: 0.8rem; + opacity: 0.8; + } + /* Markdown-Styles innerhalb von Nachrichten */ .message-content :global(p) { margin: 0.3em 0;