claude-desktop/src/lib/components/ToolCallCard.svelte
Eddy fec8aea22c
All checks were successful
Build AppImage / build (push) Successful in 8m8s
feat: KB-Hints, Voice-Konversation, Chat-Darstellung, Cross-Session-Recall [appimage]
- Block A: KB-Hint-Pillen im Chat (💡) über Tool-Cards, Klick öffnet KB-Browser
- Block B: KB-Usage-Tracking (usage_count/last_used), Sortier-Boost für bewährte Einträge
- Block C: Cross-Session-Recall per SQLite-FTS5 (🕒 Pille "Schon mal beantwortet")
- Block D: Voice-Konversationsmodus (Langes Halten = Loop mit Barge-In-Unterbrechung)
- Block F: Select-Button im Audit-Log (appearance:none + SVG-Chevron, WebKitGTK-Fix)
- Block G: Chat-Darstellungseinstellungen (Schriftart, -größe, Zeilenhöhe, Code-Größe)
- WorkingIndicator: Deutsche Animationstexte beim Verarbeiten

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:54:58 +02:00

193 lines
5 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
// Inline Tool-Call-Karte fuer Assistant-Messages (Phase 8)
//
// Sieht aus wie die Tool-Karten in der Claude-Code-Extension fuer VS Code:
// [▾ 📖 Read · src/app.ts:45-80]
// <Inhalt>
//
// Inhalt wird per <slot /> von Spezialisierungen gefuellt
// (ToolCardRead/Edit/Bash/...). Die Basis kuemmert sich um Header,
// Collapse, Status-Animation, Akzentleiste links.
import { getToolMeta, getToolSubtitle } from '$lib/utils/toolCards';
import type { InlineToolCall } from '$lib/stores';
interface Props {
call: InlineToolCall;
// Wenn der aufrufende Component bewusst aufgeklappt starten will:
defaultOpen?: boolean;
}
let { call, defaultOpen }: Props = $props();
const meta = $derived(getToolMeta(call.tool));
const subtitle = $derived(getToolSubtitle(call.tool, call.input));
// Manueller Override des Auto-Collapse-Verhaltens
let userOverride = $state<boolean | null>(null);
function autoOpen(): boolean {
if (defaultOpen !== undefined) return defaultOpen;
if (call.status === 'running') return true;
if (call.status === 'error') return true;
return !meta.collapseWhenDone;
}
const isOpen = $derived(userOverride !== null ? userOverride : autoOpen());
function toggle() {
userOverride = !isOpen;
}
function statusClass(s: string): string {
if (s === 'running') return 'running';
if (s === 'error') return 'error';
return 'success';
}
</script>
<!-- Tool-Card: kompakte „Background-Aktion"-Pille. Bewusst dezenter als
Chat-Messages — kleinerer Font, monospaced, gedämpfte Farben.
Sobald done und nichts Spannendes drin → bleibt collapsed. -->
<div class="tool-card {statusClass(call.status)}" class:open={isOpen}>
<button class="card-header" onclick={toggle} aria-expanded={isOpen}>
<span class="chevron" class:open={isOpen}></span>
<span class="icon">{meta.icon}</span>
<span class="tool-name">{meta.label}</span>
{#if subtitle}
<span class="subtitle">{subtitle}</span>
{/if}
<span class="status-spacer"></span>
{#if call.status === 'running'}
<span class="status-dots" aria-label="laeuft">
<span></span><span></span><span></span>
</span>
{:else if call.status === 'error'}
<span class="status-icon error" title="Fehler"></span>
{:else}
<span class="status-icon ok" title="erledigt"></span>
{/if}
</button>
{#if isOpen}
<div class="card-body">
<slot {call} />
</div>
{/if}
</div>
<style>
.tool-card {
margin: 3px 0;
font-size: 11.5px;
overflow: hidden;
border-radius: 4px;
border-left: 2px solid var(--vscode-input-border, #3c3c3c);
opacity: 0.78;
transition: opacity 0.15s ease;
}
.tool-card.running {
opacity: 1;
border-left-color: var(--vscode-progressBar-background, #3794ff);
}
.tool-card.error {
opacity: 1;
border-left-color: var(--vscode-errorForeground, #f48771);
}
.tool-card.open {
opacity: 1;
}
.tool-card:hover {
opacity: 1;
}
.card-header {
display: flex;
align-items: center;
width: 100%;
gap: 6px;
padding: 3px 8px;
background: transparent;
color: var(--vscode-descriptionForeground, #888);
text-align: left;
font-size: 11.5px;
line-height: 1.5;
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
min-height: 22px;
}
.card-header:hover {
background: var(--vscode-list-hoverBackground, rgba(255,255,255,0.04));
color: var(--vscode-editor-foreground, #ddd);
}
.chevron {
display: inline-block;
font-size: 12px;
width: 8px;
color: var(--vscode-descriptionForeground);
transition: transform 0.12s ease;
flex-shrink: 0;
}
.chevron.open {
transform: rotate(90deg);
}
.icon {
font-size: 11px;
line-height: 1;
opacity: 0.85;
}
.tool-name {
font-weight: 500;
color: var(--vscode-foreground, #ccc);
font-size: 11.5px;
}
.subtitle {
color: var(--vscode-descriptionForeground);
font-family: var(--font-mono);
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 55%;
opacity: 0.85;
}
.status-spacer { flex: 1; }
.status-icon {
font-size: 10px;
font-weight: 700;
}
.status-icon.ok { color: var(--vscode-successForeground, #89d185); opacity: 0.7; }
.status-icon.error { color: var(--vscode-errorForeground, #f48771); }
.status-dots {
display: inline-flex;
gap: 3px;
}
.status-dots span {
width: 3px;
height: 3px;
background: var(--vscode-progressBar-background, #3794ff);
border-radius: 50%;
animation: dotPulse 1.2s ease-in-out infinite;
}
.status-dots span:nth-child(2) { animation-delay: 0.15s; }
.status-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes dotPulse {
0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }
40% { opacity: 1; transform: scale(1); }
}
.card-body {
padding: 6px 10px 8px 18px;
font-size: 11.5px;
font-family: var(--font-mono, monospace);
color: var(--vscode-descriptionForeground);
background: var(--vscode-input-background, rgba(0,0,0,0.15));
border-top: 1px solid var(--vscode-input-border, #3c3c3c);
}
</style>