import { useEffect, useRef, useState } from 'react'; import { Button, Callout, Intent, Tag } from '@blueprintjs/core'; import { getImageStatus, type ImageBuildStatus } from '../api/imageApi'; const BASE_URL = import.meta.env.VITE_API_URL ?? ''; export default function ImageBuildPanel() { const [status, setStatus] = useState(null); const [building, setBuilding] = useState(false); const [logs, setLogs] = useState([]); const [open, setOpen] = useState(false); const [error, setError] = useState(null); const logRef = useRef(null); useEffect(() => { getImageStatus().then(setStatus).catch(() => {}); }, []); // Auto-scroll log panel useEffect(() => { if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight; }, [logs]); const handleBuild = async () => { if (building) return; setBuilding(true); setOpen(true); setLogs([]); setError(null); try { // POST /api/image/build — the response body IS the SSE stream const res = await fetch(`${BASE_URL}/api/image/build`, { method: 'POST' }); if (!res.ok || !res.body) { setError(`Build failed to start: ${res.statusText}`); setBuilding(false); return; } const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const parts = buffer.split('\n\n'); buffer = parts.pop() ?? ''; for (const chunk of parts) { const dataLine = chunk.replace(/^data:\s*/m, '').trim(); if (!dataLine) continue; try { const msg = JSON.parse(dataLine); if (msg.done) { // Build finished — refresh status getImageStatus().then(setStatus).catch(() => {}); } else if (typeof msg.line === 'string') { setLogs((prev) => [...prev.slice(-1000), msg.line]); } } catch { /* ignore non-JSON */ } } } } catch (e) { setError(e instanceof Error ? e.message : 'Unknown error during build'); } finally { setBuilding(false); } }; const lastBuilt = status?.builtAt ? new Date(status.builtAt).toLocaleString() : 'Never'; return (
{error && ( {error} )} {open && logs.length > 0 && (
{logs.map((l, i) =>
{l}
)}
)}
); }