import { Fragment, type ReactNode } from "react"; import { Minus } from "lucide-react"; import { cn } from "../lib/utils"; import { Badge } from "../primitives/badge"; import { Button } from "../primitives/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../primitives/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../primitives/table"; import { CodeBlock } from "../primitives/code-block"; import { Markdown } from "../markdown/markdown"; export type OpenUIPrimitive = string | number | boolean | null | undefined; export interface OpenUIAction { id: string; label: string; tone?: "default" | "secondary" | "outline" | "ghost" | "destructive"; disabled?: boolean; onPress?: () => void; } interface OpenUIBaseNode { id?: string; } export interface OpenUIHeadingNode extends OpenUIBaseNode { type: "heading"; text: string; level?: 1 | 2 | 3 | 4; kicker?: string; meta?: string; } export interface OpenUITextNode extends OpenUIBaseNode { type: "text"; text: string; tone?: "default" | "muted" | "success" | "warning" | "error"; mono?: boolean; } export interface OpenUIBadgeNode extends OpenUIBaseNode { type: "badge"; label: string; tone?: "default" | "secondary" | "success" | "warning" | "error" | "info" | "sandbox"; } export interface OpenUIStatNode extends OpenUIBaseNode { type: "stat"; label: string; value: string; change?: string; tone?: "default" | "success" | "warning" | "error" | "info"; } export interface OpenUIKeyValueNode extends OpenUIBaseNode { type: "key_value"; items: Array<{ id?: string; label: string; value: ReactNode | OpenUIPrimitive; tone?: "default" | "muted"; }>; } export interface OpenUICodeNode extends OpenUIBaseNode { type: "code"; code: string; language?: string; title?: string; showLineNumbers?: boolean; } export interface OpenUIMarkdownNode extends OpenUIBaseNode { type: "markdown"; content: string; } export interface OpenUITableNode extends OpenUIBaseNode { type: "table"; columns: Array<{ key: string; header: string; align?: "left" | "right"; }>; rows: Array>; caption?: string; } export interface OpenUIActionsNode extends OpenUIBaseNode { type: "actions"; actions: OpenUIAction[]; } export interface OpenUISeparatorNode extends OpenUIBaseNode { type: "separator"; label?: string; } export interface OpenUIStackNode extends OpenUIBaseNode { type: "stack"; direction?: "row" | "column"; gap?: "sm" | "md" | "lg"; align?: "start" | "center" | "end" | "stretch"; wrap?: boolean; children: OpenUIComponentNode[]; } export interface OpenUIGridNode extends OpenUIBaseNode { type: "grid"; columns?: 1 | 2 | 3 | 4; gap?: "sm" | "md" | "lg"; children: OpenUIComponentNode[]; } export interface OpenUICardNode extends OpenUIBaseNode { type: "card"; title?: string; description?: string; eyebrow?: string; badge?: OpenUIBadgeNode; actions?: OpenUIAction[]; children?: OpenUIComponentNode[]; } export type OpenUIComponentNode = | OpenUIActionsNode | OpenUIBadgeNode | OpenUICardNode | OpenUICodeNode | OpenUIGridNode | OpenUIHeadingNode | OpenUIKeyValueNode | OpenUIMarkdownNode | OpenUISeparatorNode | OpenUIStackNode | OpenUIStatNode | OpenUITableNode | OpenUITextNode; export interface OpenUIArtifactRendererProps { schema: OpenUIComponentNode | OpenUIComponentNode[]; onAction?: (action: OpenUIAction) => void; className?: string; } const NODE_TYPES = new Set([ "heading", "text", "badge", "stat", "key_value", "code", "markdown", "table", "actions", "separator", "stack", "grid", "card", ]); const GAP_STYLES = { sm: "gap-2", md: "gap-4", lg: "gap-6", } as const; const GRID_STYLES = { 1: "grid-cols-1", 2: "grid-cols-1 md:grid-cols-2", 3: "grid-cols-1 md:grid-cols-3", 4: "grid-cols-1 md:grid-cols-2 xl:grid-cols-4", } as const; const ALIGN_STYLES = { start: "items-start", center: "items-center", end: "items-end", stretch: "items-stretch", } as const; function formatValue(value: ReactNode | OpenUIPrimitive) { if (value === null || value === undefined) { return ; } if (typeof value === "boolean") { return value ? "Yes" : "No"; } return value; } function asArray(value: unknown): T[] { return Array.isArray(value) ? (value as T[]) : []; } function isOpenUIComponentNode(value: unknown): value is OpenUIComponentNode { return ( typeof value === "object" && value !== null && "type" in value && typeof (value as { type?: unknown }).type === "string" && NODE_TYPES.has((value as { type: string }).type) ); } function renderActions(actions: OpenUIAction[], onAction?: (action: OpenUIAction) => void) { if (actions.length === 0) return null; return (
{actions.map((action) => ( ))}
); } function renderNode(node: OpenUIComponentNode, onAction?: (action: OpenUIAction) => void): ReactNode { switch (node.type) { case "heading": { const level = node.level ?? 2; const HeadingTag = `h${level}` as const; return (
{node.kicker && (
{node.kicker}
)} {node.text} {node.meta &&

{node.meta}

}
); } case "text": return (

{node.text}

); case "badge": return {node.label}; case "stat": return (
{node.label}
{node.value}
{node.change && (
{node.change}
)}
); case "key_value": if (asArray(node.items).length === 0) { return null; } return (
{asArray(node.items).map((item, index) => (
{item.label}
{formatValue(item.value)}
))}
); case "code": return (
{node.title && (
{node.title}
)}
); case "markdown": return (
{node.content}
); case "table": if (asArray(node.columns).length === 0) { return null; } return (
{asArray(node.columns).map((column) => ( {column.header} ))} {asArray(node.rows).map((row, rowIndex) => ( {asArray(node.columns).map((column) => ( {formatValue(row[column.key])} ))} ))}
{node.caption && (
{node.caption}
)}
); case "actions": return renderActions(asArray(node.actions), onAction); case "separator": return (
{node.label && ( {node.label} )}
); case "stack": if (asArray(node.children).length === 0) { return null; } return (
{asArray(node.children).map((child, index) => ( {renderNode(child, onAction)} ))}
); case "grid": if (asArray(node.children).length === 0) { return null; } return (
{asArray(node.children).map((child, index) => ( {renderNode(child, onAction)} ))}
); case "card": return ( {(node.eyebrow || node.title || node.description || node.badge || asArray(node.actions).length > 0) && (
{node.eyebrow && (
{node.eyebrow}
)} {node.title && {node.title}} {node.description && ( {node.description} )}
{node.badge && {node.badge.label}}
{asArray(node.actions).length > 0 && renderActions(asArray(node.actions), onAction)}
)} {asArray(node.children).length > 0 && ( {asArray(node.children).map((child, index) => ( {renderNode(child, onAction)} ))} )}
); } } /** * OpenUIArtifactRenderer — contained renderer for OpenUI-like structured * artifact payloads using sandbox-ui primitives and theme tokens. This is an * extension surface for generated inspectors/results, not a replacement for * the sandbox shell. */ export function OpenUIArtifactRenderer({ schema, onAction, className, }: OpenUIArtifactRendererProps) { const nodes = (Array.isArray(schema) ? schema : [schema]).filter(isOpenUIComponentNode); if (nodes.length === 0) { return (
No view was generated.
); } return (
{nodes.map((node, index) => ( {renderNode(node, onAction)} ))}
); }