feat(sidebar B.1 #31): visible LLM status chip with live state
The LLM runtime has been quiet since it shipped in Phase 3 — users had no surface-level signal that cleanup was loaded, warming, or actively generating. Settings has verbose status text internally, but a dictation-flow user never opens Settings during a run. New: a shared $state store drives a small chip in the sidebar that reflects the true LLM state in ≤500 ms of any transition (brief item #31 acceptance). Five states: off → hidden (user has aiTier === "off") warming → model download or first load in flight; amber pulse ready → loaded + idle; green dot generating → cleanup_transcript_text_cmd or extract_tasks_from_transcript_cmd in flight; accent pulse with Sparkles icon error → last operation failed; red dot with AlertTriangle The store exposes three calls: refreshLlmStatus(aiTier) (polls the backend), markGenerating(detail) / markGenerationDone(success). DictationPage wraps its cleanup + extract calls in mark-generating pairs. SettingsPage's LLM load / unload / delete / download paths also refresh the global store so Settings-initiated transitions surface in the sidebar immediately. The chip collapses to a dot-only compact form when the sidebar is collapsed. No backend changes — everything wires onto the existing `get_llm_status` Tauri command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
60
src/lib/components/LlmStatusChip.svelte
Normal file
60
src/lib/components/LlmStatusChip.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { Sparkles, AlertTriangle } from "lucide-svelte";
|
||||
import { llmStatus } from "$lib/stores/llmStatus.svelte.js";
|
||||
|
||||
let { compact = false } = $props<{ compact?: boolean }>();
|
||||
|
||||
let label = $derived.by(() => {
|
||||
switch (llmStatus.kind) {
|
||||
case "warming":
|
||||
return llmStatus.detail ?? "AI warming";
|
||||
case "ready":
|
||||
return "AI ready";
|
||||
case "generating":
|
||||
return llmStatus.detail ?? "Cleaning…";
|
||||
case "error":
|
||||
return "AI error";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
let dotClass = $derived.by(() => {
|
||||
switch (llmStatus.kind) {
|
||||
case "warming":
|
||||
return "bg-warning animate-pulse";
|
||||
case "ready":
|
||||
return "bg-success";
|
||||
case "generating":
|
||||
return "bg-accent animate-pulse";
|
||||
case "error":
|
||||
return "bg-danger";
|
||||
default:
|
||||
return "bg-text-tertiary";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if llmStatus.kind !== "off"}
|
||||
{#if compact}
|
||||
<span
|
||||
class="inline-flex items-center justify-center w-[10px] h-[10px] rounded-full {dotClass}"
|
||||
title={label}
|
||||
aria-label={label}
|
||||
></span>
|
||||
{:else}
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 px-2 py-[3px] rounded-full bg-bg-elevated border border-border-subtle text-[10px] text-text-secondary whitespace-nowrap"
|
||||
title={llmStatus.detail ?? label}
|
||||
>
|
||||
<span class="inline-block w-[6px] h-[6px] rounded-full {dotClass}"></span>
|
||||
{#if llmStatus.kind === "error"}
|
||||
<AlertTriangle size={10} class="text-danger" />
|
||||
{:else if llmStatus.kind === "generating"}
|
||||
<Sparkles size={10} class="text-accent" />
|
||||
{/if}
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
Reference in New Issue
Block a user