Files
Lumotia/src/lib/components/LlmStatusChip.svelte
Jake ad311d278f
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
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>
2026-04-21 16:40:02 +01:00

61 lines
1.7 KiB
Svelte

<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}