From 2da67c2f7479a32a9f67792735519a01aeb29c45 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Thu, 19 Feb 2026 15:12:41 -0800 Subject: [PATCH] =?UTF-8?q?fix(local-llm):=20Sprint=201=20=E2=80=94=20crit?= =?UTF-8?q?ical=20dashboard=20bug=20fixes=20(B1,B3-B6,B9-B11,P4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug fixes: - B4: Escape key now respects streaming state โ€” during active stream, Escape aborts the generation instead of closing the modal - B5: Auto-refresh (15s interval) pauses during streaming and pull operations to prevent background churn and UI flicker - B9: Add AbortController to streaming fetch โ€” closing modal or pressing Escape cancels the underlying HTTP request, saving CPU/bandwidth - B1: Header subtitle now dynamically shows chip name and RAM from the system API instead of hardcoded 'Apple M4 Pro ยท 48 GB' - B11: Escape handler clears promptText and promptResponse on close - B6: Toast IDs use Date.now()+random instead of incrementing ref (prevents collision on HMR remount) - B10: Brew panel distinguishes 'Loading...' (system=null) from 'No tracked packages found' (system loaded, empty array) - B3: Remove dead non-streaming generate action from Ollama API route - P4: Add 5-second AbortController timeout to all fetchOllama() calls to prevent indefinite hangs when Ollama is unresponsive --- .../dashboard/src/app/api/ollama/route.ts | 34 ++++++---------- __LOCAL_LLMs/dashboard/src/app/page.tsx | 40 ++++++++++++++----- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/__LOCAL_LLMs/dashboard/src/app/api/ollama/route.ts b/__LOCAL_LLMs/dashboard/src/app/api/ollama/route.ts index d10ac478..6007ae7f 100644 --- a/__LOCAL_LLMs/dashboard/src/app/api/ollama/route.ts +++ b/__LOCAL_LLMs/dashboard/src/app/api/ollama/route.ts @@ -3,12 +3,19 @@ import { NextResponse } from 'next/server'; const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434'; async function fetchOllama(path: string, options?: RequestInit) { - const res = await fetch(`${OLLAMA_URL}${path}`, { - ...options, - headers: { 'Content-Type': 'application/json', ...options?.headers }, - }); - if (!res.ok) throw new Error(`Ollama ${path}: ${res.status}`); - return res.json(); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + try { + const res = await fetch(`${OLLAMA_URL}${path}`, { + ...options, + signal: controller.signal, + headers: { 'Content-Type': 'application/json', ...options?.headers }, + }); + if (!res.ok) throw new Error(`Ollama ${path}: ${res.status}`); + return res.json(); + } finally { + clearTimeout(timeout); + } } export async function GET() { @@ -66,21 +73,6 @@ export async function POST(request: Request) { return NextResponse.json({ success: true, message: `Unloaded ${model}` }); } - if (action === 'generate') { - const res = await fetch(`${OLLAMA_URL}/api/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ model, prompt: body.prompt, stream: false }), - }); - const data = await res.json(); - return NextResponse.json({ - success: true, - response: data.response, - eval_count: data.eval_count, - eval_duration: data.eval_duration, - }); - } - if (action === 'pull') { const res = await fetch(`${OLLAMA_URL}/api/pull`, { method: 'POST', diff --git a/__LOCAL_LLMs/dashboard/src/app/page.tsx b/__LOCAL_LLMs/dashboard/src/app/page.tsx index 157c99e5..80dc12ae 100644 --- a/__LOCAL_LLMs/dashboard/src/app/page.tsx +++ b/__LOCAL_LLMs/dashboard/src/app/page.tsx @@ -153,10 +153,10 @@ export default function Dashboard() { const [copied, setCopied] = useState(false); const [deleteConfirm, setDeleteConfirm] = useState(null); const responseRef = useRef(null); - const toastId = useRef(0); + const abortRef = useRef(null); const addToast = useCallback((message: string, type: Toast['type'] = 'info') => { - const id = ++toastId.current; + const id = Date.now() + Math.random(); setToasts(prev => [...prev, { id, message, type }]); setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 4000); }, []); @@ -180,21 +180,29 @@ export default function Dashboard() { }, [fetchAll]); useEffect(() => { + if (promptLoading || pullLoading) return; const interval = setInterval(fetchAll, 15000); return () => clearInterval(interval); - }, [fetchAll]); + }, [fetchAll, promptLoading, pullLoading]); - // Escape key closes modals + // Escape key closes modals (respects streaming state) useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') { - setPromptModel(null); + if (promptLoading) { + abortRef.current?.abort(); + setPromptLoading(false); + } else { + setPromptModel(null); + setPromptResponse(''); + setPromptText(''); + } setDeleteConfirm(null); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); - }, []); + }, [promptLoading]); const handleModelAction = async (action: string, model: string) => { setActionLoading(`${action}-${model}`); @@ -247,11 +255,14 @@ export default function Dashboard() { if (!promptModel || !promptText.trim()) return; setPromptLoading(true); setPromptResponse(''); + const controller = new AbortController(); + abortRef.current = controller; try { const res = await fetch('/api/ollama/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: promptModel, prompt: promptText }), + signal: controller.signal, }); if (!res.ok || !res.body) { setPromptResponse('Error: Failed to connect to Ollama'); @@ -284,8 +295,13 @@ export default function Dashboard() { } if (!fullResponse) setPromptResponse('(empty response)'); } catch (err) { - setPromptResponse(`Error: ${err}`); + if (controller.signal.aborted) { + // User cancelled โ€” keep partial response + } else { + setPromptResponse(`Error: ${err}`); + } } + abortRef.current = null; setPromptLoading(false); }; @@ -314,7 +330,8 @@ export default function Dashboard() { Local LLM Mission Control

- Apple M4 Pro · 48 GB · {system?.platform || 'macOS'} + {system?.chip || 'Loading...'} · {formatBytes(system?.memory.total || 0)}{' '} + · {system?.platform || 'macOS'}

@@ -933,11 +950,16 @@ export default function Dashboard() { ))} - {(!system?.brewPackages || system.brewPackages.length === 0) && ( + {!system && (

Loading...

)} + {system && (!system.brewPackages || system.brewPackages.length === 0) && ( +

+ No tracked packages found +

+ )}