import type { UIMessage } from "@ai-sdk/react"; import { memo } from "react"; import { Spinner } from "~/components/primitives/Spinner"; import { MessageBubble, renderPart } from "~/components/runs/v3/agent/AgentMessageView"; import { useAutoScrollToBottom } from "~/hooks/useAutoScrollToBottom"; import { ViewBlocks } from "./view-catalog"; // The shared MessageBubble renders `step-start` parts as a dashed "step" // separator — useful in the run inspector / playground, just noise in this // simple chat. Drop them before rendering (reference preserved when there are // none, so memoization still holds for those messages). function stripStepParts(message: UIMessage): UIMessage { if (!message.parts?.some((p) => p.type === "step-start")) return message; return { ...message, parts: message.parts.filter((p) => p.type !== "step-start") }; } // A completed render_view tool part carries a `{ blocks }` view spec the agent // composed (see the dashboard-agent view catalog). We render those blocks as // rich cards instead of the generic tool row. function viewSpecFor(part: UIMessage["parts"][number]): { blocks: unknown[] } | null { const p = part as { type: string; output?: { blocks?: unknown[] } }; if (p.type !== "tool-render_view") return null; return Array.isArray(p.output?.blocks) ? { blocks: p.output!.blocks! } : null; } // Renders one message. Assistant messages that include a completed render_view // part get the catalog cards (plus the gather tool rows / lead-in text for // transparency); everything else uses the shared MessageBubble unchanged, so // its streaming memoization is preserved for the common case. const DashboardAgentMessageBubble = memo(function DashboardAgentMessageBubble({ message, }: { message: UIMessage; }) { if (message.role !== "assistant" || !message.parts?.some((p) => viewSpecFor(p))) { return ; } return (
{message.parts.map((part, i) => { const spec = viewSpecFor(part); if (spec) return ; return renderPart(part, i); })}
); }); // Renders the conversation with the shared agent message renderer — the same // MessageBubble the run inspector and playground use, so agent output looks // identical everywhere — except where the agent emits a view-catalog block, // which renders as a rich card. export function DashboardAgentMessages({ messages, isThinking, error, }: { messages: UIMessage[]; isThinking: boolean; error?: Error; }) { const rootRef = useAutoScrollToBottom([messages, isThinking]); return (
{messages.map((message) => ( ))} {isThinking && (
Thinking…
)} {error && (
{error.message}
)}
); }