Spaces:
Paused
Paused
| /** | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β SMART COMPONENT RENDERER β | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β Dynamic renderer that maps JSON component specifications to React β | |
| * β Part of the Liquid UI Arsenal β | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| */ | |
| import { Suspense, lazy, ComponentType, useMemo } from 'react'; | |
| import { Loader2, AlertTriangle } from 'lucide-react'; | |
| import { cn } from '@/lib/utils'; | |
| // Import all smart components | |
| import { LiveMetricGauge, LiveMetricGaugeProps } from './LiveMetricGauge'; | |
| import { CodeDiffViewer, CodeDiffViewerProps } from './CodeDiffViewer'; | |
| import { LogStreamViewer, LogStreamViewerProps } from './LogStreamViewer'; | |
| import { InteractiveForceGraph, InteractiveForceGraphProps } from './InteractiveForceGraph'; | |
| import { KnowledgeGapCard, KnowledgeGapCardProps } from './KnowledgeGapCard'; | |
| import { AgentProcessTracker, AgentProcessTrackerProps } from './AgentProcessTracker'; | |
| import { IdeaStickyNote, IdeaStickyNoteProps } from './IdeaStickyNote'; | |
| // Visual Cortex Components (Diagram Engine) | |
| import { MermaidDiagram, MermaidDiagramProps } from './MermaidDiagram'; | |
| import { ArchitectureView, ArchitectureViewProps } from './ArchitectureView'; | |
| import { SequenceDiagram, SequenceDiagramProps } from './SequenceDiagram'; | |
| import { FlowchartView, FlowchartViewProps } from './FlowchartView'; | |
| import { MindMapView, MindMapViewProps } from './MindMapView'; | |
| // Component type definitions | |
| export type SmartComponentName = | |
| | 'LiveMetricGauge' | |
| | 'CodeDiffViewer' | |
| | 'LogStreamViewer' | |
| | 'InteractiveForceGraph' | |
| | 'KnowledgeGapCard' | |
| | 'AgentProcessTracker' | |
| | 'IdeaStickyNote' | |
| // Visual Cortex (Diagram Engine) | |
| | 'MermaidDiagram' | |
| | 'ArchitectureView' | |
| | 'SequenceDiagram' | |
| | 'FlowchartView' | |
| | 'MindMapView'; | |
| // Props type mapping | |
| export type SmartComponentProps = { | |
| LiveMetricGauge: LiveMetricGaugeProps; | |
| CodeDiffViewer: CodeDiffViewerProps; | |
| LogStreamViewer: LogStreamViewerProps; | |
| InteractiveForceGraph: InteractiveForceGraphProps; | |
| KnowledgeGapCard: KnowledgeGapCardProps; | |
| AgentProcessTracker: AgentProcessTrackerProps; | |
| IdeaStickyNote: IdeaStickyNoteProps; | |
| // Visual Cortex | |
| MermaidDiagram: MermaidDiagramProps; | |
| ArchitectureView: ArchitectureViewProps; | |
| SequenceDiagram: SequenceDiagramProps; | |
| FlowchartView: FlowchartViewProps; | |
| MindMapView: MindMapViewProps; | |
| }; | |
| // Component registry | |
| const componentRegistry: Record<SmartComponentName, ComponentType<any>> = { | |
| LiveMetricGauge, | |
| CodeDiffViewer, | |
| LogStreamViewer, | |
| InteractiveForceGraph, | |
| KnowledgeGapCard, | |
| AgentProcessTracker, | |
| IdeaStickyNote, | |
| // Visual Cortex | |
| MermaidDiagram, | |
| ArchitectureView, | |
| SequenceDiagram, | |
| FlowchartView, | |
| MindMapView, | |
| }; | |
| // UI_COMPONENT message format from backend | |
| export interface UIComponentMessage { | |
| type: 'UI_COMPONENT'; | |
| component: SmartComponentName; | |
| props: Record<string, unknown>; | |
| id?: string; | |
| } | |
| export interface SmartComponentRendererProps { | |
| message: UIComponentMessage; | |
| onError?: (error: Error, componentName: string) => void; | |
| className?: string; | |
| } | |
| // Error boundary fallback | |
| function ComponentError({ componentName, error }: { componentName: string; error?: Error }) { | |
| return ( | |
| <div className="flex items-center gap-2 p-4 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400"> | |
| <AlertTriangle className="w-4 h-4 flex-shrink-0" /> | |
| <div> | |
| <p className="text-sm font-medium">Failed to render: {componentName}</p> | |
| {error && <p className="text-xs text-red-400/70 mt-1">{error.message}</p>} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Loading state | |
| function ComponentLoading({ componentName }: { componentName: string }) { | |
| return ( | |
| <div className="flex items-center gap-2 p-4 rounded-lg bg-muted/30 border border-border/30"> | |
| <Loader2 className="w-4 h-4 animate-spin text-primary" /> | |
| <span className="text-sm text-muted-foreground">Loading {componentName}...</span> | |
| </div> | |
| ); | |
| } | |
| /** | |
| * SmartComponentRenderer - Renders UI components based on JSON specification | |
| * | |
| * Usage: | |
| * ```tsx | |
| * <SmartComponentRenderer | |
| * message={{ | |
| * type: 'UI_COMPONENT', | |
| * component: 'LiveMetricGauge', | |
| * props: { label: 'CPU', value: 75, max: 100 } | |
| * }} | |
| * /> | |
| * ``` | |
| */ | |
| export function SmartComponentRenderer({ | |
| message, | |
| onError, | |
| className, | |
| }: SmartComponentRendererProps) { | |
| const { component: componentName, props, id } = message; | |
| // Validate component exists | |
| const Component = componentRegistry[componentName]; | |
| if (!Component) { | |
| const error = new Error(`Unknown component: ${componentName}`); | |
| onError?.(error, componentName); | |
| return <ComponentError componentName={componentName} error={error} />; | |
| } | |
| // Render with error boundary | |
| try { | |
| return ( | |
| <div | |
| className={cn('smart-component', className)} | |
| data-component={componentName} | |
| data-component-id={id} | |
| > | |
| <Suspense fallback={<ComponentLoading componentName={componentName} />}> | |
| <Component {...props} /> | |
| </Suspense> | |
| </div> | |
| ); | |
| } catch (error) { | |
| const err = error instanceof Error ? error : new Error(String(error)); | |
| onError?.(err, componentName); | |
| return <ComponentError componentName={componentName} error={err} />; | |
| } | |
| } | |
| /** | |
| * Helper to check if a message is a UI_COMPONENT message | |
| */ | |
| export function isUIComponentMessage(message: unknown): message is UIComponentMessage { | |
| if (!message || typeof message !== 'object') return false; | |
| const msg = message as Record<string, unknown>; | |
| return msg.type === 'UI_COMPONENT' && typeof msg.component === 'string'; | |
| } | |
| /** | |
| * Parse assistant message content for embedded UI components | |
| * Supports format: ```ui:ComponentName {...props}``` | |
| */ | |
| export function parseMessageForComponents(content: string): Array<string | UIComponentMessage> { | |
| const parts: Array<string | UIComponentMessage> = []; | |
| const regex = /```ui:(\w+)\s*([\s\S]*?)```/g; | |
| let lastIndex = 0; | |
| let match; | |
| while ((match = regex.exec(content)) !== null) { | |
| // Add text before the component | |
| if (match.index > lastIndex) { | |
| parts.push(content.slice(lastIndex, match.index)); | |
| } | |
| // Parse the component | |
| const componentName = match[1] as SmartComponentName; | |
| let props = {}; | |
| try { | |
| if (match[2].trim()) { | |
| props = JSON.parse(match[2].trim()); | |
| } | |
| } catch { | |
| // If JSON parsing fails, try to interpret as simple key-value pairs | |
| console.warn(`Failed to parse props for ${componentName}:`, match[2]); | |
| } | |
| parts.push({ | |
| type: 'UI_COMPONENT', | |
| component: componentName, | |
| props, | |
| id: `${componentName}-${match.index}`, | |
| }); | |
| lastIndex = regex.lastIndex; | |
| } | |
| // Add remaining text | |
| if (lastIndex < content.length) { | |
| parts.push(content.slice(lastIndex)); | |
| } | |
| return parts.length > 0 ? parts : [content]; | |
| } | |
| /** | |
| * List all available component names | |
| */ | |
| export function getAvailableComponents(): SmartComponentName[] { | |
| return Object.keys(componentRegistry) as SmartComponentName[]; | |
| } | |
| /** | |
| * Component metadata for documentation | |
| */ | |
| export const componentMetadata: Record<SmartComponentName, { description: string; category: string }> = { | |
| LiveMetricGauge: { | |
| description: 'Animated gauge for real-time metrics with thresholds', | |
| category: 'Observability', | |
| }, | |
| CodeDiffViewer: { | |
| description: 'Side-by-side or unified diff viewer for code changes', | |
| category: 'Builder', | |
| }, | |
| LogStreamViewer: { | |
| description: 'Real-time log streaming with filtering and levels', | |
| category: 'Observability', | |
| }, | |
| InteractiveForceGraph: { | |
| description: 'Force-directed knowledge graph visualization', | |
| category: 'Knowledge', | |
| }, | |
| KnowledgeGapCard: { | |
| description: 'Visual card for knowledge gaps and their status', | |
| category: 'Knowledge', | |
| }, | |
| AgentProcessTracker: { | |
| description: 'Real-time visualization of agent task execution', | |
| category: 'Builder', | |
| }, | |
| IdeaStickyNote: { | |
| description: 'Playful sticky note for incubated ideas', | |
| category: 'Creative', | |
| }, | |
| // Visual Cortex (Diagram Engine) | |
| MermaidDiagram: { | |
| description: 'Render any Mermaid diagram from DSL syntax', | |
| category: 'Visual', | |
| }, | |
| ArchitectureView: { | |
| description: 'System architecture visualization with typed nodes', | |
| category: 'Visual', | |
| }, | |
| SequenceDiagram: { | |
| description: 'Interaction sequences between actors/systems', | |
| category: 'Visual', | |
| }, | |
| FlowchartView: { | |
| description: 'Business process and decision flow visualization', | |
| category: 'Visual', | |
| }, | |
| MindMapView: { | |
| description: 'Hierarchical idea visualization and brainstorming', | |
| category: 'Visual', | |
| }, | |
| }; | |
| export default SmartComponentRenderer; | |