feat: thinking blocks collapsed by default [appimage]
Some checks failed
Build AppImage / build (push) Has been cancelled

- Extended Thinking (SDK thinking blocks) rendered as collapsible
  <details> 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 <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-04-20 22:32:55 +02:00
parent 8a7e0d87f3
commit 8336ac6eb1
2 changed files with 63 additions and 1 deletions

View file

@ -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 = `<details class="thinking-block"><summary>💭 Überlegung (${thinkLines} Zeilen)</summary>\n\n${block.thinking}\n\n</details>\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

View file

@ -42,12 +42,38 @@
function renderMarkdown(text: string): string {
try {
return marked.parse(text) as string;
// Thinking-Blöcke erkennen und in <details> 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-<details>.
* 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 `<details class="thinking-block">\n<summary>💭 Überlegung (${lines} Zeilen)</summary>\n\n${thinkingPart}\n\n</details>\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;