From fbce540638bd9696c5a88af6acc459004836c825 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 18 Feb 2026 17:54:37 -0500 Subject: [PATCH] Add streaming status UX and model load state indicator Streaming status transparency: - generation_task.py emits 'status' SSE events at each pipeline stage: "Analyzing your request..." before intent classification, tool label before each tool execution, "Generating/Composing response..." before each LLM streaming round - chat.ts adds streamingStatus ref; cleared on first chunk or done/error; includes fast 5s poll loop after warmModel() until model shows as loaded - ChatView.vue shows pulsing dot + italic status label above content area; falls back to blinking cursor once content arrives - HomeView.vue shows status label in dashboard panel instead of '...' Model load state indicator: - /api/chat/status now queries /api/tags and /api/ps in parallel to distinguish installed-but-cold vs loaded-in-VRAM model states - New model status values: 'not_found' | 'cold' | 'loaded' (was 'ready') - chatReady true for both 'cold' and 'loaded' (cold models still work) - AppHeader shows 5 states: gray pulse (checking), red (Ollama down), orange (not installed), yellow pulse (cold), green (loaded) - Inline short label ("Cold", "Ready", "Offline", etc.) visible without hovering; detailed tooltip on hover Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/AppHeader.vue | 46 +++++++++++++++---- frontend/src/stores/chat.ts | 28 ++++++++++- frontend/src/types/chat.ts | 2 +- frontend/src/views/ChatView.vue | 24 +++++++++- frontend/src/views/HomeView.vue | 21 ++++++++- src/fabledassistant/routes/chat.py | 30 ++++++++---- .../services/generation_task.py | 24 ++++++++++ summary.md | 20 +++++++- 8 files changed, 171 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue index 4c7c998..6c5a707 100644 --- a/frontend/src/components/AppHeader.vue +++ b/frontend/src/components/AppHeader.vue @@ -14,18 +14,31 @@ const chatStore = useChatStore(); const router = useRouter(); const mobileMenuOpen = ref(false); +const statusShortLabel = computed(() => { + if (chatStore.ollamaStatus === "unavailable") return "Offline"; + if (chatStore.ollamaStatus === "checking") return "Checking"; + if (chatStore.modelStatus === "not_found") return "No model"; + if (chatStore.modelStatus === "cold") return "Cold"; + if (chatStore.modelStatus === "loaded") return "Ready"; + return "Checking"; +}); + const statusLabel = computed(() => { if (chatStore.ollamaStatus === "unavailable") return "Ollama unavailable"; - if (chatStore.modelStatus === "not_found") return "Model downloading..."; - if (chatStore.chatReady) return "Connected"; + if (chatStore.ollamaStatus === "checking") return "Checking..."; + if (chatStore.modelStatus === "not_found") return "Model not installed"; + if (chatStore.modelStatus === "cold") return "Model cold — first response may be slow"; + if (chatStore.modelStatus === "loaded") return "Model loaded · ready"; return "Checking..."; }); const statusClass = computed(() => { if (chatStore.ollamaStatus === "unavailable") return "status-red"; - if (chatStore.modelStatus === "not_found") return "status-yellow"; - if (chatStore.chatReady) return "status-green"; - return "status-yellow"; + if (chatStore.ollamaStatus === "checking") return "status-gray"; + if (chatStore.modelStatus === "not_found") return "status-orange"; + if (chatStore.modelStatus === "cold") return "status-yellow"; + if (chatStore.modelStatus === "loaded") return "status-green"; + return "status-gray"; }); function toggleMobileMenu() { @@ -67,6 +80,7 @@ router.afterEach(() => { Logs + {{ statusShortLabel }}