diff --git a/vibn-frontend/app/api/chat/route.ts b/vibn-frontend/app/api/chat/route.ts index 4b180d89..9af6133e 100644 --- a/vibn-frontend/app/api/chat/route.ts +++ b/vibn-frontend/app/api/chat/route.ts @@ -1182,13 +1182,39 @@ export async function POST(request: Request) { error: undefined as string | undefined, }; + let currentTimelineKind: "thought" | "text" | null = null; + let currentTimelineText = ""; + + const flushTimeline = () => { + if (!currentTimelineKind) return; + if (currentTimelineKind === "thought") { + assistantTimeline.push({ kind: "thought", text: currentTimelineText }); + } else if (currentTimelineKind === "text") { + assistantText += (assistantText ? "\n\n" : "") + currentTimelineText; + assistantTextSegments.push(currentTimelineText); + assistantTimeline.push({ kind: "text", text: currentTimelineText }); + } + currentTimelineKind = null; + currentTimelineText = ""; + }; + for await (const chunk of stream) { if (aborted) break; if (chunk.type === "thinking_delta" && chunk.text) { + if (currentTimelineKind !== "thought") { + flushTimeline(); + currentTimelineKind = "thought"; + } + currentTimelineText += chunk.text; resp.thoughts += chunk.text; emit({ type: "thinking_delta", text: chunk.text }); } else if (chunk.type === "text_delta" && chunk.text) { + if (currentTimelineKind !== "text") { + flushTimeline(); + currentTimelineKind = "text"; + } + currentTimelineText += chunk.text; resp.text += chunk.text; emit({ type: "text_delta", text: chunk.text }); } else if (chunk.type === "tool_calls" && chunk.toolCalls) { @@ -1197,16 +1223,9 @@ export async function POST(request: Request) { resp.error = chunk.error; } } + flushTimeline(); - // If the model produced any thoughts or text, record them in the timeline once stream is complete. - // (The UI handles the delta-rendering live, but we save the complete chunk to Postgres). - if (resp.thoughts) { - assistantTimeline.push({ kind: "thought", text: resp.thoughts }); - } if (resp.text) { - assistantText += (assistantText ? "\n\n" : "") + resp.text; - assistantTextSegments.push(resp.text); - assistantTimeline.push({ kind: "text", text: resp.text }); roundsSinceText = 0; toolCallsSinceText = 0; } else if (resp.toolCalls.length) { diff --git a/vibn-frontend/components/vibn-chat/chat-panel.tsx b/vibn-frontend/components/vibn-chat/chat-panel.tsx index 5477e15d..6c9d4840 100644 --- a/vibn-frontend/components/vibn-chat/chat-panel.tsx +++ b/vibn-frontend/components/vibn-chat/chat-panel.tsx @@ -635,23 +635,7 @@ function Timeline({ entries }: { entries: TimelineEntry[] }) {