/* Copyright 2026 Marimo. All rights reserved. */ import type { DataUIPart, ToolUIPart, UIMessage } from "ai"; import React from "react"; import { renderHTML } from "@/plugins/core/RenderHTML"; import { logNever } from "@/utils/assertNever"; import { Logger } from "@/utils/Logger"; import { MarkdownRenderer } from "../markdown/markdown-renderer"; import { AttachmentRenderer } from "./chat-components"; import { ReasoningAccordion } from "./reasoning-accordion"; import { ToolCallAccordion } from "./tool-call-accordion"; export const renderUIMessage = ({ message, isStreamingReasoning, isLast, }: { message: UIMessage; isStreamingReasoning: boolean; isLast: boolean; }) => { return ( <>{message.parts.map((part, index) => renderUIMessagePart(part, index))} ); function renderUIMessagePart( part: UIMessage["parts"][number], index: number, ) { if (isToolPart(part)) { return ( ); } if (isDataPart(part)) { Logger.debug("Found data part", part); return null; } switch (part.type) { case "text": // Streamdown sanitizes the HTML which strips out marimo elements // So instead, we render the HTML with our custom renderer. if (part.text.includes(" {renderHTML({ html: part.text, })} ); } return ; case "reasoning": return ( ); case "file": return ; case "dynamic-tool": return ( ); case "source-document": case "source-url": case "step-start": Logger.debug("Found non-renderable part", part); return null; default: logNever(part); return null; } } }; function isToolPart(part: UIMessage["parts"][number]): part is ToolUIPart { return part.type.startsWith("tool-"); } function isDataPart( part: UIMessage["parts"][number], ): part is DataUIPart> { return part.type.startsWith("data-"); }