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>
65 lines
2.4 KiB
TypeScript
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;
|
|
}
|
|
}
|