/** * InlineSurface component. * * Wraps A2UIRenderer with expand/collapse and dismiss controls. * Renders inline within the chat transcript, anchored to the * assistant message turn that created the surface. */ import React, { useEffect, useState } from 'react'; import type { A2UIResolvedComponent, A2UIClientAction } from '../../main/a2ui/types'; import { A2UIRenderer } from './A2UIRenderer'; import './InlineSurface.css'; interface InlineSurfaceProps { surfaceId: string; tree: A2UIResolvedComponent[]; isExpanded: boolean; onDismiss?: (surfaceId: string) => void; onAction?: (action: A2UIClientAction) => void; onDataChange?: (surfaceId: string, path: string, value: unknown) => void; } /** * Derive a display title from the surface's component tree. * Tries the root component's `title` or `label` property, * then falls back to the capitalized component type. */ export function deriveSurfaceTitle(tree: A2UIResolvedComponent[]): string { if (tree.length === 0) { return 'Surface'; } const root = tree[0]; const title = root.properties?.title as string | undefined; if (title) { return title; } const label = root.properties?.label as string | undefined; if (label) { return label; } return root.type.charAt(0).toUpperCase() + root.type.slice(1); } /** * Get an icon character for the surface based on the root component type. */ export function getSurfaceIcon(tree: A2UIResolvedComponent[]): string { if (tree.length === 0) { return '\u25A0'; } const type = tree[0].type; const icons: Record = { chart: '\u{1F4CA}', table: '\u{1F4CB}', form: '\u{1F4DD}', card: '\u{1F4C4}', metric: '\u{1F4CF}', list: '\u{1F4CB}', tabs: '\u{1F4C2}', }; return icons[type] || '\u25A0'; } export const InlineSurface: React.FC = ({ surfaceId, tree, isExpanded: defaultExpanded, onDismiss, onAction, onDataChange, }) => { const [expanded, setExpanded] = useState(defaultExpanded); // Auto-collapse/expand when the parent changes which surface is latest useEffect(() => { setExpanded(defaultExpanded); }, [defaultExpanded]); const surfaceTitle = deriveSurfaceTitle(tree); const surfaceIcon = getSurfaceIcon(tree); if (!expanded) { return (
setExpanded(true)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { setExpanded(true); } }} > {surfaceIcon} {surfaceTitle} {onDismiss && ( )}
); } return (
{surfaceIcon} {surfaceTitle} {onDismiss && ( )}
); };