Spaces:
Paused
Paused
File size: 9,412 Bytes
5a81b95 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | /**
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β 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;
|