Files
Lumotia/src/lib/stores/llmStatus.svelte.ts
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

65 lines
2.4 KiB
TypeScript

import { invoke } from "@tauri-apps/api/core";
import { hasTauriRuntime } from "$lib/utils/runtime.js";
/// Visible LLM state for the status chip (brief item #31).
/// off — user has settings.aiTier === "off"; chip hidden.
/// warming — model download or load in progress.
/// ready — model loaded, idle.
/// generating — an active cleanup_transcript_text_cmd call is in flight.
/// error — last health check or generation failed.
export type LlmStatusKind = "off" | "warming" | "ready" | "generating" | "error";
export interface LlmStatusState {
kind: LlmStatusKind;
/// Optional short phrase for the chip to surface (e.g. "Loading Qwen3 4B").
detail: string | null;
}
export const llmStatus = $state<LlmStatusState>({ kind: "off", detail: null });
/// Poll `get_llm_status` once. Cheap enough to call on layout mount, on
/// recording start, and on Settings-panel open. Keeps the chip in sync
/// with loads / unloads that happen outside the frontend's observation
/// path (first-run, background reload). The `aiTier` input short-circuits
/// to "off" so we don't show a chip when the user has opted out.
export async function refreshLlmStatus(aiTier: string): Promise<void> {
if (aiTier === "off") {
llmStatus.kind = "off";
llmStatus.detail = null;
return;
}
if (!hasTauriRuntime()) {
// Running in a pure-browser preview (vite dev without tauri) — leave
// the chip off rather than asserting a loaded state we can't verify.
llmStatus.kind = "off";
llmStatus.detail = null;
return;
}
try {
const loaded = await invoke<boolean>("get_llm_status");
// Don't clobber a "generating" state with "ready" — a parallel
// cleanup call in flight is a truer signal than the load status.
if (llmStatus.kind === "generating") return;
llmStatus.kind = loaded ? "ready" : "warming";
llmStatus.detail = null;
} catch (err) {
llmStatus.kind = "error";
llmStatus.detail = typeof err === "string" ? err : (err as Error)?.message ?? "Unknown error";
}
}
export function markGenerating(detail: string | null = null): void {
llmStatus.kind = "generating";
llmStatus.detail = detail;
}
export function markGenerationDone(success: boolean, detail: string | null = null): void {
if (success) {
llmStatus.kind = "ready";
llmStatus.detail = null;
} else {
llmStatus.kind = "error";
llmStatus.detail = detail;
}
}