diff --git a/frontend/src/components/ModelSelector.vue b/frontend/src/components/ModelSelector.vue index 64c3178..38b2f2e 100644 --- a/frontend/src/components/ModelSelector.vue +++ b/frontend/src/components/ModelSelector.vue @@ -49,7 +49,7 @@ onMounted(async () => { :key="m.name" :value="m.name" > - {{ isHot(m.name) ? "\u2B24 " : "\u25CB " }}{{ m.name }} + {{ isHot(m.name) ? "🟢 " : "🔴 " }}{{ m.name }} diff --git a/frontend/src/stores/chat.ts b/frontend/src/stores/chat.ts index a038e85..6687843 100644 --- a/frontend/src/stores/chat.ts +++ b/frontend/src/stores/chat.ts @@ -239,6 +239,10 @@ export const useChatStore = defineStore("chat", () => { // Recovery fallback: re-fetch conversation from DB if (!gotDone && currentConversation.value?.id === convId) { + useToastStore().show( + "Connection lost — response may be incomplete", + "error", + ); try { await fetchConversation(convId); } catch { diff --git a/src/fabledassistant/routes/chat.py b/src/fabledassistant/routes/chat.py index dc41e01..63321af 100644 --- a/src/fabledassistant/routes/chat.py +++ b/src/fabledassistant/routes/chat.py @@ -269,7 +269,7 @@ async def warm_model_route(): async def _warm(): try: - async with httpx.AsyncClient(timeout=120.0) as client: + async with httpx.AsyncClient(timeout=300.0) as client: await client.post( f"{Config.OLLAMA_URL}/api/generate", json={"model": model, "prompt": "", "keep_alive": "30m"}, diff --git a/src/fabledassistant/services/llm.py b/src/fabledassistant/services/llm.py index a3bb415..028e8a5 100644 --- a/src/fabledassistant/services/llm.py +++ b/src/fabledassistant/services/llm.py @@ -54,7 +54,7 @@ async def ensure_model(model: str) -> None: logger.info("Pulling model '%s' from Ollama...", model) try: - async with httpx.AsyncClient(timeout=600.0) as client: + async with httpx.AsyncClient(timeout=1800.0) as client: async with client.stream( "POST", f"{Config.OLLAMA_URL}/api/pull", @@ -80,7 +80,7 @@ async def stream_chat( payload: dict = {"model": model, "messages": messages, "stream": True} if options: payload["options"] = options - async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=10.0, read=300.0)) as client: + async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=30.0, read=300.0)) as client: async with client.stream( "POST", f"{Config.OLLAMA_URL}/api/chat", @@ -100,7 +100,7 @@ async def stream_chat( async def generate_completion(messages: list[dict], model: str) -> str: """Non-streaming chat completion, returns full response text.""" - async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=10.0, read=300.0)) as client: + async with httpx.AsyncClient(timeout=httpx.Timeout(1800.0, connect=30.0, read=300.0)) as client: resp = await client.post( f"{Config.OLLAMA_URL}/api/chat", json={"model": model, "messages": messages, "stream": False}, diff --git a/summary.md b/summary.md index ddbb965..8b3ec5b 100644 --- a/summary.md +++ b/summary.md @@ -12,7 +12,7 @@ > Include file-level details in the commit body when the change is non-trivial. ## Last Updated -2026-02-14 — Phase 6.0: Model selection, dashboard chat input, model warming +2026-02-14 — Phase 6.1: Model status colors, timeout tuning, SSE error feedback ## Project Overview Fabled Assistant is a self-hosted note-taking and task-tracking application with @@ -527,8 +527,10 @@ When adding a new migration, follow these conventions: - Per-conversation model selection via ModelSelector dropdown in chat header (persisted via PATCH) - Dashboard inline chat input: model selector + note picker + textarea; creates conversation and navigates - Model warming: default model pre-loaded into Ollama on dashboard mount via fire-and-forget POST -- Hot/cold model indicators: `/api/chat/ps` proxies Ollama `/api/ps`; ModelSelector shows filled/empty circles +- Hot/cold model indicators: `/api/chat/ps` proxies Ollama `/api/ps`; ModelSelector shows green/red emoji circles - Ollama configured with `OLLAMA_MAX_LOADED_MODELS=2` and `OLLAMA_KEEP_ALIVE=30m` +- Timeout tuning: connect timeout 30s (cold model loading), warm timeout 300s, pull timeout 1800s +- SSE reconnection failure shows error toast instead of silently recovering ### Authentication & User Management - Session cookie auth with bcrypt, first-user-is-admin, orphaned data claiming