diff --git a/agent/pipeline_runtime.py b/agent/pipeline_runtime.py index 358d9ce..74778d4 100644 --- a/agent/pipeline_runtime.py +++ b/agent/pipeline_runtime.py @@ -200,9 +200,11 @@ def build_meeting_result(state: dict) -> dict: if pipeline_succeeded: verdict = "GO" - score = scoring.get("final_score", 0) - if not score and isinstance(code_eval_result.get("match_rate"), (int, float)): + score = scoring.get("final_score") + if score is None and isinstance(code_eval_result.get("match_rate"), (int, float)): score = code_eval_result.get("match_rate", 0) + if score is None: + score = 0 return { "score": score, diff --git a/web/src/app/error.tsx b/web/src/app/error.tsx index 3f21edb..7a673e5 100644 --- a/web/src/app/error.tsx +++ b/web/src/app/error.tsx @@ -10,7 +10,16 @@ export default function GlobalError({ error, reset }: { error: Error & { digest? const isChunkError = message.includes("ChunkLoadError") || message.includes("Failed to load chunk"); if (!retriedRef.current && isChunkError) { retriedRef.current = true; - window.location.reload(); + try { + const reloadKey = "vibedeploy_chunk_reload_count"; + const count = Number(sessionStorage.getItem(reloadKey) || "0"); + if (count < 2) { + sessionStorage.setItem(reloadKey, String(count + 1)); + window.location.reload(); + } + } catch { + // sessionStorage unavailable (e.g., Safari private mode) + } } }, [error]); diff --git a/web/src/app/result/[id]/page.tsx b/web/src/app/result/[id]/page.tsx index d3d56c2..39ad1c1 100644 --- a/web/src/app/result/[id]/page.tsx +++ b/web/src/app/result/[id]/page.tsx @@ -46,9 +46,13 @@ export default function ResultPage() { useEffect(() => { let mounted = true; - getMeetingResult(params.id).then((next) => { - if (mounted) setResult(next); - }); + getMeetingResult(params.id) + .then((next) => { + if (mounted) setResult(next); + }) + .catch(() => { + if (mounted) setResult(null); + }); return () => { mounted = false; diff --git a/web/src/components/brainstorm/brainstorm-view.tsx b/web/src/components/brainstorm/brainstorm-view.tsx index 44d1135..064b52b 100644 --- a/web/src/components/brainstorm/brainstorm-view.tsx +++ b/web/src/components/brainstorm/brainstorm-view.tsx @@ -87,22 +87,56 @@ export function BrainstormView({ sessionId }: { sessionId: string }) { ); useEffect(() => { - const stop = createSSEClient({ - url: `${DASHBOARD_API_URL}/brainstorm`, - body: { - prompt: "Run brainstorm and stream all events.", - config: { configurable: { thread_id: sessionId } }, - }, - onEvent: handleEvent, - onComplete: () => { + let stopFn: (() => void) | undefined; + let cancelled = false; + + // Check if result already exists (page refresh case) + getBrainstormResult(sessionId).then((existing) => { + if (cancelled) return; + if (existing) { + setResult(existing); setStreamCompleted(true); - getBrainstormResult(sessionId).then((res) => { - if (res) setResult(res); - }); - }, - onError: (err) => setError(err.message), + return; + } + + stopFn = createSSEClient({ + url: `${DASHBOARD_API_URL}/brainstorm`, + body: { + prompt: "Run brainstorm and stream all events.", + config: { configurable: { thread_id: sessionId } }, + }, + onEvent: handleEvent, + onComplete: () => { + setStreamCompleted(true); + getBrainstormResult(sessionId).then((res) => { + if (res) setResult(res); + }); + }, + onError: (err) => setError(err.message), + }); + }).catch(() => { + if (cancelled) return; + stopFn = createSSEClient({ + url: `${DASHBOARD_API_URL}/brainstorm`, + body: { + prompt: "Run brainstorm and stream all events.", + config: { configurable: { thread_id: sessionId } }, + }, + onEvent: handleEvent, + onComplete: () => { + setStreamCompleted(true); + getBrainstormResult(sessionId).then((res) => { + if (res) setResult(res); + }); + }, + onError: (err) => setError(err.message), + }); }); - return stop; + + return () => { + cancelled = true; + stopFn?.(); + }; }, [handleEvent, sessionId]); const synthesis = result?.synthesis; diff --git a/web/src/components/meeting/meeting-view.tsx b/web/src/components/meeting/meeting-view.tsx index ed333d1..ed11dd2 100644 --- a/web/src/components/meeting/meeting-view.tsx +++ b/web/src/components/meeting/meeting-view.tsx @@ -152,26 +152,48 @@ export function MeetingView({ meetingId }: { meetingId: string }) { }, []); useEffect(() => { - const stop = createSSEClient({ - url: `${DASHBOARD_API_URL}/run`, - body: { - prompt: "Run the council meeting and stream all events.", - config: { configurable: { thread_id: meetingId } }, - }, - onEvent: (event) => { - const tagged = { ...event, _uid: `mev-${++eventSeq.current}` }; - setEvents((prev) => [...prev, tagged]); - handleEvent(event); - }, - onComplete: () => { - setStreamCompleted(true); - setPhaseIndex(6); - setTimeout(() => router.push(`/result/${meetingId}`), 2000); - }, - onError: (streamError) => setError(streamError.message), - }); + let stopFn: (() => void) | undefined; + let cancelled = false; + + (async () => { + // Check if result already exists (page refresh case) + try { + const { getMeetingResult } = await import("@/lib/api"); + const existing = await getMeetingResult(meetingId); + if (existing && !cancelled) { + router.push(`/result/${meetingId}`); + return; + } + } catch { + // Result not found — proceed with new stream + } + + if (cancelled) return; + + stopFn = createSSEClient({ + url: `${DASHBOARD_API_URL}/run`, + body: { + prompt: "Run the council meeting and stream all events.", + config: { configurable: { thread_id: meetingId } }, + }, + onEvent: (event) => { + const tagged = { ...event, _uid: `mev-${++eventSeq.current}` }; + setEvents((prev) => [...prev, tagged]); + handleEvent(event); + }, + onComplete: () => { + setStreamCompleted(true); + setPhaseIndex(6); + setTimeout(() => router.push(`/result/${meetingId}`), 2000); + }, + onError: (streamError) => setError(streamError.message), + }); + })(); - return stop; + return () => { + cancelled = true; + stopFn?.(); + }; }, [handleEvent, meetingId, router]); useEffect(() => { @@ -316,6 +338,7 @@ export function MeetingView({ meetingId }: { meetingId: string }) { ] : ["All dimensions cleared. Proceed to build + deploy."] } + onRevise={() => router.push("/")} /> )} diff --git a/web/src/components/zero-prompt/zero-prompt-workspace.tsx b/web/src/components/zero-prompt/zero-prompt-workspace.tsx index 0ec4340..c2b0dee 100644 --- a/web/src/components/zero-prompt/zero-prompt-workspace.tsx +++ b/web/src/components/zero-prompt/zero-prompt-workspace.tsx @@ -21,6 +21,7 @@ export function ZeroPromptWorkspace({ initialSession, autostart = false }: { ini isConnected, isLoading, hasLoadedDashboard, + error, startSession, queueBuild, passCard, @@ -85,6 +86,12 @@ export function ZeroPromptWorkspace({ initialSession, autostart = false }: { ini + {error && ( +
+ {error} +
+ )} +