diff --git a/src/components/ToolMessage.tsx b/src/components/ToolMessage.tsx index e508fde..3635430 100644 --- a/src/components/ToolMessage.tsx +++ b/src/components/ToolMessage.tsx @@ -2,7 +2,15 @@ import React, { useState } from "react"; import type { MessageResponse } from "@/types/message"; import { ChevronDownIcon, ChevronRightIcon, CopyIcon, CheckIcon } from "lucide-react"; import { getToolName } from "@/services/messageUtils"; -import { ToolResult } from "./toolRenderers"; +import { ToolResult, renderersFor } from "./toolRenderers"; +import { Chart } from "./charts/Chart"; +import type { ChartPayload } from "./charts/types"; + +/** The artifact is `unknown` off the wire, so check the shape before drawing it. */ +const isChartPayload = (v: unknown): v is ChartPayload => { + const p = v as ChartPayload | undefined; + return !!p && typeof p === "object" && !!p.spec && Array.isArray(p.rows); +}; interface ToolMessageProps { message: MessageResponse; @@ -46,6 +54,11 @@ export const ToolMessage = ({ message }: ToolMessageProps) => { const toolName = getToolName(message); const content = getContentAsString(message.data?.content); const summary = summarize(content); + // A chart is the answer, not a detail of it, so it renders outside the collapsed disclosure — + // the receipt stays inside for inspection. + const artifact = (message.data as { artifact?: unknown })?.artifact; + const chart = + renderersFor(toolName).result === "chart" && isChartPayload(artifact) ? artifact : null; const handleCopy = async (e: React.MouseEvent) => { e.stopPropagation(); @@ -58,6 +71,22 @@ export const ToolMessage = ({ message }: ToolMessageProps) => { } }; + if (chart) { + return ( +
+ +
+ + {toolName ?? "tool"} · result + +
+ +
+
+
+ ); + } + return (