diff --git a/vibn-frontend/components/vibn-chat/chat-panel.tsx b/vibn-frontend/components/vibn-chat/chat-panel.tsx index eec2cda4..cadca706 100644 --- a/vibn-frontend/components/vibn-chat/chat-panel.tsx +++ b/vibn-frontend/components/vibn-chat/chat-panel.tsx @@ -65,6 +65,8 @@ interface Message { type TimelineEntry = | { kind: "thought"; text: string } + | { kind: "phase"; phase: string; label: string } + | { kind: "checkpoint"; goal: string; findings: string } | { kind: "tool"; name: string; @@ -604,6 +606,8 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) { type Item = | { kind: "thought"; text: string } | { kind: "text"; text: string } + | { kind: "phase"; phase: string; label: string } + | { kind: "checkpoint"; goal: string; findings: string } | { kind: "toolGroup"; category: string; @@ -615,6 +619,10 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) { items.push({ kind: "thought", text: e.text }); } else if (e.kind === "text") { items.push({ kind: "text", text: e.text }); + } else if (e.kind === "phase") { + items.push({ kind: "phase", phase: e.phase, label: e.label }); + } else if (e.kind === "checkpoint") { + items.push({ kind: "checkpoint", goal: e.goal, findings: e.findings }); } else { const last = items[items.length - 1]; const category = getFriendlyCategory(e.name); @@ -634,6 +642,69 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) { if (item.kind === "text") { return ; } + if (item.kind === "phase") { + return ( +
+
+ + {item.label} + +
+ ); + } + if (item.kind === "checkpoint") { + return ( +
+
+ [Checkpoint Logged] +
+
{item.goal}
+
+ ); + } return ( { + const next = [...prev]; + if (msgIndex >= 0 && next[msgIndex]) { + const tl = next[msgIndex].timeline ?? []; + next[msgIndex] = { + ...next[msgIndex], + timeline: [ + ...tl, + { kind: "phase", phase: ev.phase!, label: ev.label! }, + ], + }; + } + return next; + }); + } else if (ev.type === "checkpoint" && ev.goal) { + setMessages((prev) => { + const next = [...prev]; + if (msgIndex >= 0 && next[msgIndex]) { + const tl = next[msgIndex].timeline ?? []; + next[msgIndex] = { + ...next[msgIndex], + timeline: [ + ...tl, + { + kind: "checkpoint", + goal: ev.goal!, + findings: ev.findings!, + }, + ], + }; + } + return next; + }); + } else if (ev.type === "text" && ev.text) { // Each text SSE event = one round of the model's text // output. Push a new "text" timeline entry so the // renderer can show multi-round turns as separate