All checks were successful
Build AppImage / build (push) Successful in 8m8s
- 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>
141 lines
3.3 KiB
Svelte
141 lines
3.3 KiB
Svelte
<script lang="ts">
|
|
// Animierter "Claude arbeitet"-Indikator unter der letzten Message.
|
|
// Rotiert ein zufaelliges deutsches Verb alle ~2.5s, daneben laeuft ein
|
|
// Braille-Spinner und ein Sekunden-Counter. Stil orientiert sich an
|
|
// Claude Code.
|
|
|
|
import { onMount, onDestroy } from 'svelte';
|
|
|
|
const VERBS = [
|
|
'Denke nach',
|
|
'Gruebele',
|
|
'Bruete',
|
|
'Sinniere',
|
|
'Tueftle',
|
|
'Werkle',
|
|
'Stoebere',
|
|
'Knirsche',
|
|
'Bastle',
|
|
'Mische',
|
|
'Brodelt',
|
|
'Kluegele',
|
|
'Wuehle',
|
|
'Krame',
|
|
'Spinne',
|
|
'Schmiede',
|
|
'Pruefe',
|
|
'Lese',
|
|
'Verdaue',
|
|
'Sortiere',
|
|
'Sammle',
|
|
'Suche',
|
|
'Forsche',
|
|
'Faedele ein',
|
|
'Knete',
|
|
'Falte die Stirn',
|
|
'Klopfe ab',
|
|
'Lege Hand an',
|
|
];
|
|
|
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
|
|
let verb = $state(pickVerb());
|
|
let spinnerIdx = $state(0);
|
|
let elapsed = $state(0);
|
|
|
|
let verbTimer: ReturnType<typeof setInterval> | null = null;
|
|
let spinTimer: ReturnType<typeof setInterval> | null = null;
|
|
let secondsTimer: ReturnType<typeof setInterval> | null = null;
|
|
let lastVerb = '';
|
|
|
|
function pickVerb(): string {
|
|
// Nicht zweimal hintereinander dasselbe Wort
|
|
let next = VERBS[Math.floor(Math.random() * VERBS.length)];
|
|
if (next === lastVerb && VERBS.length > 1) {
|
|
next = VERBS[(VERBS.indexOf(next) + 1) % VERBS.length];
|
|
}
|
|
lastVerb = next;
|
|
return next;
|
|
}
|
|
|
|
onMount(() => {
|
|
const start = Date.now();
|
|
verbTimer = setInterval(() => { verb = pickVerb(); }, 2500);
|
|
spinTimer = setInterval(() => {
|
|
spinnerIdx = (spinnerIdx + 1) % SPINNER_FRAMES.length;
|
|
}, 90);
|
|
secondsTimer = setInterval(() => {
|
|
elapsed = Math.floor((Date.now() - start) / 1000);
|
|
}, 1000);
|
|
});
|
|
|
|
onDestroy(() => {
|
|
if (verbTimer) clearInterval(verbTimer);
|
|
if (spinTimer) clearInterval(spinTimer);
|
|
if (secondsTimer) clearInterval(secondsTimer);
|
|
});
|
|
</script>
|
|
|
|
<div class="working" role="status" aria-live="polite">
|
|
<span class="spinner">{SPINNER_FRAMES[spinnerIdx]}</span>
|
|
<span class="verb">{verb}</span>
|
|
<span class="dots"><span>.</span><span>.</span><span>.</span></span>
|
|
{#if elapsed > 1}
|
|
<span class="elapsed">({elapsed}s)</span>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.working {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 16px 14px 52px;
|
|
font-size: 13px;
|
|
color: var(--vscode-descriptionForeground, #888);
|
|
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
opacity: 0;
|
|
animation: fade-in 200ms ease-out forwards;
|
|
}
|
|
|
|
.spinner {
|
|
color: var(--accent, var(--vscode-button-background, #007acc));
|
|
font-size: 15px;
|
|
line-height: 1;
|
|
display: inline-block;
|
|
min-width: 14px;
|
|
text-align: center;
|
|
}
|
|
|
|
.verb {
|
|
color: var(--text-primary, var(--vscode-foreground, #ddd));
|
|
font-style: italic;
|
|
min-width: 100px;
|
|
transition: opacity 200ms ease;
|
|
}
|
|
|
|
.dots {
|
|
display: inline-flex;
|
|
gap: 2px;
|
|
color: var(--text-primary, var(--vscode-foreground, #ddd));
|
|
}
|
|
.dots span {
|
|
animation: dot-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
.dots span:nth-child(2) { animation-delay: 200ms; }
|
|
.dots span:nth-child(3) { animation-delay: 400ms; }
|
|
|
|
.elapsed {
|
|
color: var(--text-muted, #888);
|
|
font-size: 11px;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
@keyframes fade-in {
|
|
to { opacity: 1; }
|
|
}
|
|
@keyframes dot-pulse {
|
|
0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
|
|
30% { opacity: 1; transform: translateY(-2px); }
|
|
}
|
|
</style>
|