@seed-ship/mcp-ui-solid 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/components/ActionRenderer.cjs +34 -0
- package/dist/components/ActionRenderer.cjs.map +1 -0
- package/dist/components/ActionRenderer.js +34 -0
- package/dist/components/ActionRenderer.js.map +1 -0
- package/dist/components/ArtifactRenderer.cjs +37 -0
- package/dist/components/ArtifactRenderer.cjs.map +1 -0
- package/dist/components/ArtifactRenderer.js +37 -0
- package/dist/components/ArtifactRenderer.js.map +1 -0
- package/dist/components/CarouselRenderer.cjs +58 -0
- package/dist/components/CarouselRenderer.cjs.map +1 -0
- package/dist/components/CarouselRenderer.js +58 -0
- package/dist/components/CarouselRenderer.js.map +1 -0
- package/dist/components/UIResourceRenderer.cjs +238 -160
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +238 -160
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components.cjs +10 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +10 -0
- package/dist/components.js +10 -0
- package/dist/components.js.map +1 -1
- package/dist/hooks/useStreamingUI.cjs +4 -1
- package/dist/hooks/useStreamingUI.cjs.map +1 -1
- package/dist/hooks/useStreamingUI.d.ts +2 -0
- package/dist/hooks/useStreamingUI.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.js +4 -1
- package/dist/hooks/useStreamingUI.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/services/component-registry.cjs +211 -1
- package/dist/services/component-registry.cjs.map +1 -1
- package/dist/services/component-registry.d.ts +25 -0
- package/dist/services/component-registry.d.ts.map +1 -1
- package/dist/services/component-registry.js +211 -1
- package/dist/services/component-registry.js.map +1 -1
- package/package.json +1 -1
- package/src/components/UIResourceRenderer.tsx +79 -3
- package/src/components/index.ts +16 -0
- package/src/hooks/useStreamingUI.ts +7 -1
- package/src/services/component-registry.ts +239 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -279,14 +279,224 @@ const TextRegistry = {
|
|
|
279
279
|
// 1s
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
|
+
const GridRegistry = {
|
|
283
|
+
type: "grid",
|
|
284
|
+
name: "GridLayout",
|
|
285
|
+
description: "Nested CSS Grid layout for organizing multiple components. Supports named areas, responsive columns (1-12), and custom gap spacing. Best for complex dashboard layouts and template builder.",
|
|
286
|
+
schema: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
columns: {
|
|
290
|
+
type: "number",
|
|
291
|
+
description: "Number of columns (default: 12)"
|
|
292
|
+
},
|
|
293
|
+
gap: {
|
|
294
|
+
type: "string",
|
|
295
|
+
description: 'Gap between items (e.g., "1rem")'
|
|
296
|
+
},
|
|
297
|
+
minRowHeight: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "Minimum row height (optional)"
|
|
300
|
+
},
|
|
301
|
+
areas: {
|
|
302
|
+
type: "array",
|
|
303
|
+
items: {
|
|
304
|
+
type: "array",
|
|
305
|
+
items: { type: "string" }
|
|
306
|
+
},
|
|
307
|
+
description: "CSS Grid template areas for named regions"
|
|
308
|
+
},
|
|
309
|
+
children: {
|
|
310
|
+
type: "array",
|
|
311
|
+
items: { type: "object" },
|
|
312
|
+
description: "Child UIComponents to render within the grid"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
required: ["children"]
|
|
316
|
+
},
|
|
317
|
+
examples: [],
|
|
318
|
+
limits: DEFAULT_RESOURCE_LIMITS
|
|
319
|
+
};
|
|
320
|
+
const ActionRegistry = {
|
|
321
|
+
type: "action",
|
|
322
|
+
name: "ActionButton",
|
|
323
|
+
description: "Interactive button or link that triggers tool calls or navigation. Best for user interactions, form submissions, and workflow triggers.",
|
|
324
|
+
schema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
label: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "Button text"
|
|
330
|
+
},
|
|
331
|
+
type: {
|
|
332
|
+
type: "string",
|
|
333
|
+
enum: ["button", "link"],
|
|
334
|
+
description: "Render as button or link"
|
|
335
|
+
},
|
|
336
|
+
action: {
|
|
337
|
+
type: "string",
|
|
338
|
+
enum: ["tool-call", "link", "submit"],
|
|
339
|
+
description: "Action type to perform"
|
|
340
|
+
},
|
|
341
|
+
toolName: {
|
|
342
|
+
type: "string",
|
|
343
|
+
description: "Tool name to call (for tool-call action)"
|
|
344
|
+
},
|
|
345
|
+
params: {
|
|
346
|
+
type: "object",
|
|
347
|
+
description: "Parameters to pass to the tool"
|
|
348
|
+
},
|
|
349
|
+
url: {
|
|
350
|
+
type: "string",
|
|
351
|
+
description: "URL for link action"
|
|
352
|
+
},
|
|
353
|
+
variant: {
|
|
354
|
+
type: "string",
|
|
355
|
+
enum: ["primary", "secondary", "outline", "ghost", "danger"],
|
|
356
|
+
description: "Visual style variant"
|
|
357
|
+
},
|
|
358
|
+
size: {
|
|
359
|
+
type: "string",
|
|
360
|
+
enum: ["sm", "md", "lg"],
|
|
361
|
+
description: "Button size"
|
|
362
|
+
},
|
|
363
|
+
disabled: {
|
|
364
|
+
type: "boolean",
|
|
365
|
+
description: "Whether the action is disabled"
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
required: ["label", "type", "action"]
|
|
369
|
+
},
|
|
370
|
+
examples: [],
|
|
371
|
+
limits: DEFAULT_RESOURCE_LIMITS
|
|
372
|
+
};
|
|
373
|
+
const FooterRegistry = {
|
|
374
|
+
type: "footer",
|
|
375
|
+
name: "FooterSection",
|
|
376
|
+
description: "Footer section displaying execution metadata. Best for showing timing, model info, and source counts. Auto-injected by layouts when metadata is provided.",
|
|
377
|
+
schema: {
|
|
378
|
+
type: "object",
|
|
379
|
+
properties: {
|
|
380
|
+
poweredBy: {
|
|
381
|
+
type: "string",
|
|
382
|
+
description: "Powered by text (optional)"
|
|
383
|
+
},
|
|
384
|
+
executionTime: {
|
|
385
|
+
type: "number",
|
|
386
|
+
description: "Execution time in milliseconds"
|
|
387
|
+
},
|
|
388
|
+
model: {
|
|
389
|
+
type: "string",
|
|
390
|
+
description: "LLM model used"
|
|
391
|
+
},
|
|
392
|
+
sourceCount: {
|
|
393
|
+
type: "number",
|
|
394
|
+
description: "Number of sources used"
|
|
395
|
+
},
|
|
396
|
+
customText: {
|
|
397
|
+
type: "string",
|
|
398
|
+
description: "Custom footer text"
|
|
399
|
+
},
|
|
400
|
+
links: {
|
|
401
|
+
type: "array",
|
|
402
|
+
items: {
|
|
403
|
+
type: "object",
|
|
404
|
+
properties: {
|
|
405
|
+
label: { type: "string" },
|
|
406
|
+
url: { type: "string" }
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
description: "Footer links"
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
examples: [],
|
|
414
|
+
limits: {
|
|
415
|
+
maxDataPoints: 1,
|
|
416
|
+
maxTableRows: 1,
|
|
417
|
+
maxPayloadSize: 5 * 1024,
|
|
418
|
+
renderTimeout: 1e3
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
const CarouselRegistry = {
|
|
422
|
+
type: "carousel",
|
|
423
|
+
name: "Carousel",
|
|
424
|
+
description: "Horizontal carousel for displaying multiple items with snap scrolling and navigation buttons. Best for showcasing related content, image galleries, or card collections.",
|
|
425
|
+
schema: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
items: {
|
|
429
|
+
type: "array",
|
|
430
|
+
items: { type: "object" },
|
|
431
|
+
description: "Array of UIComponents to display in carousel"
|
|
432
|
+
},
|
|
433
|
+
height: {
|
|
434
|
+
type: "string",
|
|
435
|
+
description: "Carousel height (optional)"
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
required: ["items"]
|
|
439
|
+
},
|
|
440
|
+
examples: [],
|
|
441
|
+
limits: DEFAULT_RESOURCE_LIMITS
|
|
442
|
+
};
|
|
443
|
+
const ArtifactRegistry = {
|
|
444
|
+
type: "artifact",
|
|
445
|
+
name: "Artifact",
|
|
446
|
+
description: "Display downloadable artifacts like generated files or exports. Shows filename, size, and download button. Best for CSV exports, PDF reports, and generated documents.",
|
|
447
|
+
schema: {
|
|
448
|
+
type: "object",
|
|
449
|
+
properties: {
|
|
450
|
+
url: {
|
|
451
|
+
type: "string",
|
|
452
|
+
description: "Download URL for the artifact"
|
|
453
|
+
},
|
|
454
|
+
filename: {
|
|
455
|
+
type: "string",
|
|
456
|
+
description: "Display filename"
|
|
457
|
+
},
|
|
458
|
+
mimeType: {
|
|
459
|
+
type: "string",
|
|
460
|
+
description: 'MIME type (e.g., "text/csv", "application/pdf")'
|
|
461
|
+
},
|
|
462
|
+
size: {
|
|
463
|
+
type: "number",
|
|
464
|
+
description: "File size in bytes"
|
|
465
|
+
},
|
|
466
|
+
description: {
|
|
467
|
+
type: "string",
|
|
468
|
+
description: "Description of the artifact"
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
required: ["url", "filename", "mimeType"]
|
|
472
|
+
},
|
|
473
|
+
examples: [],
|
|
474
|
+
limits: {
|
|
475
|
+
maxDataPoints: 1,
|
|
476
|
+
maxTableRows: 1,
|
|
477
|
+
maxPayloadSize: 5 * 1024,
|
|
478
|
+
renderTimeout: 1e3
|
|
479
|
+
}
|
|
480
|
+
};
|
|
282
481
|
const ComponentRegistry = /* @__PURE__ */ new Map([
|
|
283
482
|
["chart", QuickchartRegistry],
|
|
284
483
|
["table", TableRegistry],
|
|
285
484
|
["metric", MetricRegistry],
|
|
286
|
-
["text", TextRegistry]
|
|
485
|
+
["text", TextRegistry],
|
|
486
|
+
// Sprint 4 additions
|
|
487
|
+
["grid", GridRegistry],
|
|
488
|
+
["action", ActionRegistry],
|
|
489
|
+
["footer", FooterRegistry],
|
|
490
|
+
["carousel", CarouselRegistry],
|
|
491
|
+
["artifact", ArtifactRegistry]
|
|
287
492
|
]);
|
|
288
493
|
export {
|
|
494
|
+
ActionRegistry,
|
|
495
|
+
ArtifactRegistry,
|
|
496
|
+
CarouselRegistry,
|
|
289
497
|
ComponentRegistry,
|
|
498
|
+
FooterRegistry,
|
|
499
|
+
GridRegistry,
|
|
290
500
|
MetricRegistry,
|
|
291
501
|
QuickchartRegistry,
|
|
292
502
|
TableRegistry,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-registry.js","sources":["../../src/services/component-registry.ts"],"sourcesContent":["/**\n * Component Registry Service\n * Phase 0: Static registry with Quickchart and Table definitions\n * Phase 1: Dynamic registry populated from /api/mcp/tools/list\n *\n * Provides component schemas for LLM prompt engineering\n */\n\nimport type { ComponentRegistryEntry, ComponentType } from '../types'\nimport { DEFAULT_RESOURCE_LIMITS } from './validation'\n\n/**\n * Quickchart Component Registry Entry\n * Based on Quickchart API documentation\n */\nexport const QuickchartRegistry: ComponentRegistryEntry = {\n type: 'chart',\n name: 'Quickchart',\n description:\n 'Render charts using Quickchart.io API. Supports bar, line, pie, doughnut, radar, and scatter charts. Best for visualizing numerical data with 2-10 data series and up to 1000 data points.',\n schema: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['bar', 'line', 'pie', 'doughnut', 'radar', 'scatter'],\n description: 'Chart type',\n },\n title: {\n type: 'string',\n description: 'Chart title (optional)',\n },\n data: {\n type: 'object',\n properties: {\n labels: {\n type: 'array',\n items: { type: 'string' },\n description: 'X-axis labels',\n },\n datasets: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n label: { type: 'string' },\n data: {\n type: 'array',\n items: { type: 'number' },\n },\n backgroundColor: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n },\n borderColor: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n },\n borderWidth: { type: 'number' },\n },\n required: ['label', 'data'],\n },\n },\n },\n required: ['labels', 'datasets'],\n },\n options: {\n type: 'object',\n description: 'Chart.js options for customization',\n },\n },\n required: ['type', 'data'],\n },\n examples: [\n {\n query: 'Show me document types distribution',\n component: {\n id: 'example-bar-1',\n type: 'chart',\n position: { colStart: 1, colSpan: 6 },\n params: {\n type: 'bar',\n title: 'Document Types',\n data: {\n labels: ['PDF', 'DOCX', 'TXT', 'XLSX'],\n datasets: [\n {\n label: 'Count',\n data: [245, 189, 123, 98],\n backgroundColor: ['rgba(59, 130, 246, 0.8)'],\n },\n ],\n },\n },\n },\n },\n {\n query: 'Display upload trends over the last week',\n component: {\n id: 'example-line-1',\n type: 'chart',\n position: { colStart: 1, colSpan: 6 },\n params: {\n type: 'line',\n title: 'Upload Trends',\n data: {\n labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],\n datasets: [\n {\n label: 'Uploads',\n data: [42, 38, 51, 47, 63, 29, 15],\n borderColor: 'rgb(59, 130, 246)',\n },\n ],\n },\n options: {\n tension: 0.4,\n },\n },\n },\n },\n ],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Table Component Registry Entry\n */\nexport const TableRegistry: ComponentRegistryEntry = {\n type: 'table',\n name: 'DataTable',\n description:\n 'Render tabular data with sortable columns and pagination. Best for displaying structured records with up to 100 rows. Supports column width customization and cell formatting.',\n schema: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n description: 'Table title (optional)',\n },\n columns: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'Data key for this column' },\n label: { type: 'string', description: 'Column header label' },\n sortable: { type: 'boolean', description: 'Whether column is sortable' },\n width: { type: 'string', description: 'CSS width (e.g., \"30%\")' },\n },\n required: ['key', 'label'],\n },\n minItems: 1,\n },\n rows: {\n type: 'array',\n items: {\n type: 'object',\n description: 'Row data matching column keys',\n },\n maxItems: 100,\n },\n pagination: {\n type: 'object',\n properties: {\n currentPage: { type: 'number' },\n pageSize: { type: 'number' },\n totalRows: { type: 'number' },\n },\n },\n },\n required: ['columns', 'rows'],\n },\n examples: [\n {\n query: 'Show me the most recent documents',\n component: {\n id: 'example-table-1',\n type: 'table',\n position: { colStart: 1, colSpan: 8 },\n params: {\n title: 'Recent Documents',\n columns: [\n { key: 'name', label: 'Name', sortable: true, width: '40%' },\n { key: 'type', label: 'Type', sortable: true, width: '15%' },\n { key: 'size', label: 'Size', width: '15%' },\n { key: 'modified', label: 'Modified', sortable: true, width: '30%' },\n ],\n rows: [\n { name: 'Report.pdf', type: 'PDF', size: '2.4 MB', modified: '2 hours ago' },\n { name: 'Slides.pptx', type: 'PPTX', size: '8.7 MB', modified: '1 day ago' },\n ],\n },\n },\n },\n ],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Metric Card Component Registry Entry\n */\nexport const MetricRegistry: ComponentRegistryEntry = {\n type: 'metric',\n name: 'MetricCard',\n description:\n 'Display a single metric with optional trend indicator. Best for KPIs, statistics, and summary numbers. Supports trend direction (up/down/neutral) and subtitles.',\n schema: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n description: 'Metric title',\n },\n value: {\n oneOf: [{ type: 'string' }, { type: 'number' }],\n description: 'Metric value',\n },\n unit: {\n type: 'string',\n description: 'Unit of measurement (optional)',\n },\n trend: {\n type: 'object',\n properties: {\n value: { type: 'number', description: 'Percentage change' },\n direction: { type: 'string', enum: ['up', 'down', 'neutral'] },\n },\n },\n subtitle: {\n type: 'string',\n description: 'Additional context (optional)',\n },\n },\n required: ['title', 'value'],\n },\n examples: [\n {\n query: 'Show total document count',\n component: {\n id: 'example-metric-1',\n type: 'metric',\n position: { colStart: 1, colSpan: 3 },\n params: {\n title: 'Total Documents',\n value: '1,247',\n trend: {\n value: 12.5,\n direction: 'up',\n },\n subtitle: '+142 this month',\n },\n },\n },\n ],\n limits: {\n maxDataPoints: 1,\n maxTableRows: 1,\n maxPayloadSize: 5 * 1024, // 5KB\n renderTimeout: 1000, // 1s\n },\n}\n\n/**\n * Text Component Registry Entry\n */\nexport const TextRegistry: ComponentRegistryEntry = {\n type: 'text',\n name: 'TextBlock',\n description:\n 'Render text content with optional markdown support. Best for explanations, summaries, and context. Supports basic HTML formatting.',\n schema: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description: 'Text content (HTML allowed, will be sanitized)',\n },\n markdown: {\n type: 'boolean',\n description: 'Whether content is markdown (not yet implemented)',\n },\n className: {\n type: 'string',\n description: 'Custom CSS classes',\n },\n },\n required: ['content'],\n },\n examples: [\n {\n query: 'Explain the document distribution',\n component: {\n id: 'example-text-1',\n type: 'text',\n position: { colStart: 1, colSpan: 12 },\n params: {\n content:\n '<p>Your document library contains <strong>1,247 files</strong> across 5 different formats. PDFs represent the largest category at 35% of total storage.</p>',\n },\n },\n },\n ],\n limits: {\n maxDataPoints: 1,\n maxTableRows: 1,\n maxPayloadSize: 10 * 1024, // 10KB\n renderTimeout: 1000, // 1s\n },\n}\n\n/**\n * Component Registry - All components indexed by type\n */\nexport const ComponentRegistry: Map<ComponentType, ComponentRegistryEntry> = new Map([\n ['chart', QuickchartRegistry],\n ['table', TableRegistry],\n ['metric', MetricRegistry],\n ['text', TextRegistry],\n])\n\n/**\n * Get component registry entry by type\n */\nexport function getComponentEntry(type: ComponentType): ComponentRegistryEntry | undefined {\n return ComponentRegistry.get(type)\n}\n\n/**\n * Get all component types\n */\nexport function getAllComponentTypes(): ComponentType[] {\n return Array.from(ComponentRegistry.keys())\n}\n\n/**\n * Get registry as JSON for LLM context\n */\nexport function getRegistryForLLM(): string {\n const entries = Array.from(ComponentRegistry.values()).map((entry) => ({\n type: entry.type,\n name: entry.name,\n description: entry.description,\n schema: entry.schema,\n examples: entry.examples.map((ex) => ({\n query: ex.query,\n component: ex.component,\n })),\n limits: entry.limits,\n }))\n\n return JSON.stringify(entries, null, 2)\n}\n\n/**\n * Validate component against registry schema\n * (Future: Use Zod for runtime validation)\n */\nexport function validateAgainstRegistry(\n componentType: ComponentType,\n params: any\n): { valid: boolean; errors?: string[] } {\n const entry = getComponentEntry(componentType)\n if (!entry) {\n return { valid: false, errors: [`Unknown component type: ${componentType}`] }\n }\n\n // Basic validation (Phase 1 will add Zod schema validation)\n const required = entry.schema.required || []\n const missing = required.filter((key: string) => !(key in params))\n\n if (missing.length > 0) {\n return {\n valid: false,\n errors: missing.map((key: string) => `Missing required field: ${key}`),\n }\n }\n\n return { valid: true }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"component-registry.js","sources":["../../src/services/component-registry.ts"],"sourcesContent":["/**\n * Component Registry Service\n * Phase 0: Static registry with Quickchart and Table definitions\n * Phase 1: Dynamic registry populated from /api/mcp/tools/list\n *\n * Provides component schemas for LLM prompt engineering\n */\n\nimport type { ComponentRegistryEntry, ComponentType } from '../types'\nimport { DEFAULT_RESOURCE_LIMITS } from './validation'\n\n/**\n * Quickchart Component Registry Entry\n * Based on Quickchart API documentation\n */\nexport const QuickchartRegistry: ComponentRegistryEntry = {\n type: 'chart',\n name: 'Quickchart',\n description:\n 'Render charts using Quickchart.io API. Supports bar, line, pie, doughnut, radar, and scatter charts. Best for visualizing numerical data with 2-10 data series and up to 1000 data points.',\n schema: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['bar', 'line', 'pie', 'doughnut', 'radar', 'scatter'],\n description: 'Chart type',\n },\n title: {\n type: 'string',\n description: 'Chart title (optional)',\n },\n data: {\n type: 'object',\n properties: {\n labels: {\n type: 'array',\n items: { type: 'string' },\n description: 'X-axis labels',\n },\n datasets: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n label: { type: 'string' },\n data: {\n type: 'array',\n items: { type: 'number' },\n },\n backgroundColor: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n },\n borderColor: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n },\n borderWidth: { type: 'number' },\n },\n required: ['label', 'data'],\n },\n },\n },\n required: ['labels', 'datasets'],\n },\n options: {\n type: 'object',\n description: 'Chart.js options for customization',\n },\n },\n required: ['type', 'data'],\n },\n examples: [\n {\n query: 'Show me document types distribution',\n component: {\n id: 'example-bar-1',\n type: 'chart',\n position: { colStart: 1, colSpan: 6 },\n params: {\n type: 'bar',\n title: 'Document Types',\n data: {\n labels: ['PDF', 'DOCX', 'TXT', 'XLSX'],\n datasets: [\n {\n label: 'Count',\n data: [245, 189, 123, 98],\n backgroundColor: ['rgba(59, 130, 246, 0.8)'],\n },\n ],\n },\n },\n },\n },\n {\n query: 'Display upload trends over the last week',\n component: {\n id: 'example-line-1',\n type: 'chart',\n position: { colStart: 1, colSpan: 6 },\n params: {\n type: 'line',\n title: 'Upload Trends',\n data: {\n labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],\n datasets: [\n {\n label: 'Uploads',\n data: [42, 38, 51, 47, 63, 29, 15],\n borderColor: 'rgb(59, 130, 246)',\n },\n ],\n },\n options: {\n tension: 0.4,\n },\n },\n },\n },\n ],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Table Component Registry Entry\n */\nexport const TableRegistry: ComponentRegistryEntry = {\n type: 'table',\n name: 'DataTable',\n description:\n 'Render tabular data with sortable columns and pagination. Best for displaying structured records with up to 100 rows. Supports column width customization and cell formatting.',\n schema: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n description: 'Table title (optional)',\n },\n columns: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'Data key for this column' },\n label: { type: 'string', description: 'Column header label' },\n sortable: { type: 'boolean', description: 'Whether column is sortable' },\n width: { type: 'string', description: 'CSS width (e.g., \"30%\")' },\n },\n required: ['key', 'label'],\n },\n minItems: 1,\n },\n rows: {\n type: 'array',\n items: {\n type: 'object',\n description: 'Row data matching column keys',\n },\n maxItems: 100,\n },\n pagination: {\n type: 'object',\n properties: {\n currentPage: { type: 'number' },\n pageSize: { type: 'number' },\n totalRows: { type: 'number' },\n },\n },\n },\n required: ['columns', 'rows'],\n },\n examples: [\n {\n query: 'Show me the most recent documents',\n component: {\n id: 'example-table-1',\n type: 'table',\n position: { colStart: 1, colSpan: 8 },\n params: {\n title: 'Recent Documents',\n columns: [\n { key: 'name', label: 'Name', sortable: true, width: '40%' },\n { key: 'type', label: 'Type', sortable: true, width: '15%' },\n { key: 'size', label: 'Size', width: '15%' },\n { key: 'modified', label: 'Modified', sortable: true, width: '30%' },\n ],\n rows: [\n { name: 'Report.pdf', type: 'PDF', size: '2.4 MB', modified: '2 hours ago' },\n { name: 'Slides.pptx', type: 'PPTX', size: '8.7 MB', modified: '1 day ago' },\n ],\n },\n },\n },\n ],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Metric Card Component Registry Entry\n */\nexport const MetricRegistry: ComponentRegistryEntry = {\n type: 'metric',\n name: 'MetricCard',\n description:\n 'Display a single metric with optional trend indicator. Best for KPIs, statistics, and summary numbers. Supports trend direction (up/down/neutral) and subtitles.',\n schema: {\n type: 'object',\n properties: {\n title: {\n type: 'string',\n description: 'Metric title',\n },\n value: {\n oneOf: [{ type: 'string' }, { type: 'number' }],\n description: 'Metric value',\n },\n unit: {\n type: 'string',\n description: 'Unit of measurement (optional)',\n },\n trend: {\n type: 'object',\n properties: {\n value: { type: 'number', description: 'Percentage change' },\n direction: { type: 'string', enum: ['up', 'down', 'neutral'] },\n },\n },\n subtitle: {\n type: 'string',\n description: 'Additional context (optional)',\n },\n },\n required: ['title', 'value'],\n },\n examples: [\n {\n query: 'Show total document count',\n component: {\n id: 'example-metric-1',\n type: 'metric',\n position: { colStart: 1, colSpan: 3 },\n params: {\n title: 'Total Documents',\n value: '1,247',\n trend: {\n value: 12.5,\n direction: 'up',\n },\n subtitle: '+142 this month',\n },\n },\n },\n ],\n limits: {\n maxDataPoints: 1,\n maxTableRows: 1,\n maxPayloadSize: 5 * 1024, // 5KB\n renderTimeout: 1000, // 1s\n },\n}\n\n/**\n * Text Component Registry Entry\n */\nexport const TextRegistry: ComponentRegistryEntry = {\n type: 'text',\n name: 'TextBlock',\n description:\n 'Render text content with optional markdown support. Best for explanations, summaries, and context. Supports basic HTML formatting.',\n schema: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description: 'Text content (HTML allowed, will be sanitized)',\n },\n markdown: {\n type: 'boolean',\n description: 'Whether content is markdown (not yet implemented)',\n },\n className: {\n type: 'string',\n description: 'Custom CSS classes',\n },\n },\n required: ['content'],\n },\n examples: [\n {\n query: 'Explain the document distribution',\n component: {\n id: 'example-text-1',\n type: 'text',\n position: { colStart: 1, colSpan: 12 },\n params: {\n content:\n '<p>Your document library contains <strong>1,247 files</strong> across 5 different formats. PDFs represent the largest category at 35% of total storage.</p>',\n },\n },\n },\n ],\n limits: {\n maxDataPoints: 1,\n maxTableRows: 1,\n maxPayloadSize: 10 * 1024, // 10KB\n renderTimeout: 1000, // 1s\n },\n}\n\n// ============================================================================\n// Sprint 4: Additional Component Registry Entries\n// ============================================================================\n\n/**\n * Grid Component Registry Entry\n * Nested CSS Grid layout for organizing multiple components\n */\nexport const GridRegistry: ComponentRegistryEntry = {\n type: 'grid',\n name: 'GridLayout',\n description:\n 'Nested CSS Grid layout for organizing multiple components. Supports named areas, responsive columns (1-12), and custom gap spacing. Best for complex dashboard layouts and template builder.',\n schema: {\n type: 'object',\n properties: {\n columns: {\n type: 'number',\n description: 'Number of columns (default: 12)',\n },\n gap: {\n type: 'string',\n description: 'Gap between items (e.g., \"1rem\")',\n },\n minRowHeight: {\n type: 'string',\n description: 'Minimum row height (optional)',\n },\n areas: {\n type: 'array',\n items: {\n type: 'array',\n items: { type: 'string' },\n },\n description: 'CSS Grid template areas for named regions',\n },\n children: {\n type: 'array',\n items: { type: 'object' },\n description: 'Child UIComponents to render within the grid',\n },\n },\n required: ['children'],\n },\n examples: [],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Action Component Registry Entry\n * Interactive button or link that triggers tool calls\n */\nexport const ActionRegistry: ComponentRegistryEntry = {\n type: 'action',\n name: 'ActionButton',\n description:\n 'Interactive button or link that triggers tool calls or navigation. Best for user interactions, form submissions, and workflow triggers.',\n schema: {\n type: 'object',\n properties: {\n label: {\n type: 'string',\n description: 'Button text',\n },\n type: {\n type: 'string',\n enum: ['button', 'link'],\n description: 'Render as button or link',\n },\n action: {\n type: 'string',\n enum: ['tool-call', 'link', 'submit'],\n description: 'Action type to perform',\n },\n toolName: {\n type: 'string',\n description: 'Tool name to call (for tool-call action)',\n },\n params: {\n type: 'object',\n description: 'Parameters to pass to the tool',\n },\n url: {\n type: 'string',\n description: 'URL for link action',\n },\n variant: {\n type: 'string',\n enum: ['primary', 'secondary', 'outline', 'ghost', 'danger'],\n description: 'Visual style variant',\n },\n size: {\n type: 'string',\n enum: ['sm', 'md', 'lg'],\n description: 'Button size',\n },\n disabled: {\n type: 'boolean',\n description: 'Whether the action is disabled',\n },\n },\n required: ['label', 'type', 'action'],\n },\n examples: [],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Footer Component Registry Entry\n * Display execution metadata like timing and source count\n */\nexport const FooterRegistry: ComponentRegistryEntry = {\n type: 'footer',\n name: 'FooterSection',\n description:\n 'Footer section displaying execution metadata. Best for showing timing, model info, and source counts. Auto-injected by layouts when metadata is provided.',\n schema: {\n type: 'object',\n properties: {\n poweredBy: {\n type: 'string',\n description: 'Powered by text (optional)',\n },\n executionTime: {\n type: 'number',\n description: 'Execution time in milliseconds',\n },\n model: {\n type: 'string',\n description: 'LLM model used',\n },\n sourceCount: {\n type: 'number',\n description: 'Number of sources used',\n },\n customText: {\n type: 'string',\n description: 'Custom footer text',\n },\n links: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n label: { type: 'string' },\n url: { type: 'string' },\n },\n },\n description: 'Footer links',\n },\n },\n },\n examples: [],\n limits: {\n maxDataPoints: 1,\n maxTableRows: 1,\n maxPayloadSize: 5 * 1024,\n renderTimeout: 1000,\n },\n}\n\n/**\n * Carousel Component Registry Entry\n * Display multiple items with horizontal scrolling\n */\nexport const CarouselRegistry: ComponentRegistryEntry = {\n type: 'carousel',\n name: 'Carousel',\n description:\n 'Horizontal carousel for displaying multiple items with snap scrolling and navigation buttons. Best for showcasing related content, image galleries, or card collections.',\n schema: {\n type: 'object',\n properties: {\n items: {\n type: 'array',\n items: { type: 'object' },\n description: 'Array of UIComponents to display in carousel',\n },\n height: {\n type: 'string',\n description: 'Carousel height (optional)',\n },\n },\n required: ['items'],\n },\n examples: [],\n limits: DEFAULT_RESOURCE_LIMITS,\n}\n\n/**\n * Artifact Component Registry Entry\n * Display downloadable artifacts like generated files\n */\nexport const ArtifactRegistry: ComponentRegistryEntry = {\n type: 'artifact',\n name: 'Artifact',\n description:\n 'Display downloadable artifacts like generated files or exports. Shows filename, size, and download button. Best for CSV exports, PDF reports, and generated documents.',\n schema: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'Download URL for the artifact',\n },\n filename: {\n type: 'string',\n description: 'Display filename',\n },\n mimeType: {\n type: 'string',\n description: 'MIME type (e.g., \"text/csv\", \"application/pdf\")',\n },\n size: {\n type: 'number',\n description: 'File size in bytes',\n },\n description: {\n type: 'string',\n description: 'Description of the artifact',\n },\n },\n required: ['url', 'filename', 'mimeType'],\n },\n examples: [],\n limits: {\n maxDataPoints: 1,\n maxTableRows: 1,\n maxPayloadSize: 5 * 1024,\n renderTimeout: 1000,\n },\n}\n\n/**\n * Component Registry - All components indexed by type\n */\nexport const ComponentRegistry: Map<ComponentType, ComponentRegistryEntry> = new Map([\n ['chart', QuickchartRegistry],\n ['table', TableRegistry],\n ['metric', MetricRegistry],\n ['text', TextRegistry],\n // Sprint 4 additions\n ['grid', GridRegistry],\n ['action', ActionRegistry],\n ['footer', FooterRegistry],\n ['carousel', CarouselRegistry],\n ['artifact', ArtifactRegistry],\n])\n\n/**\n * Get component registry entry by type\n */\nexport function getComponentEntry(type: ComponentType): ComponentRegistryEntry | undefined {\n return ComponentRegistry.get(type)\n}\n\n/**\n * Get all component types\n */\nexport function getAllComponentTypes(): ComponentType[] {\n return Array.from(ComponentRegistry.keys())\n}\n\n/**\n * Get registry as JSON for LLM context\n */\nexport function getRegistryForLLM(): string {\n const entries = Array.from(ComponentRegistry.values()).map((entry) => ({\n type: entry.type,\n name: entry.name,\n description: entry.description,\n schema: entry.schema,\n examples: entry.examples.map((ex) => ({\n query: ex.query,\n component: ex.component,\n })),\n limits: entry.limits,\n }))\n\n return JSON.stringify(entries, null, 2)\n}\n\n/**\n * Validate component against registry schema\n * (Future: Use Zod for runtime validation)\n */\nexport function validateAgainstRegistry(\n componentType: ComponentType,\n params: any\n): { valid: boolean; errors?: string[] } {\n const entry = getComponentEntry(componentType)\n if (!entry) {\n return { valid: false, errors: [`Unknown component type: ${componentType}`] }\n }\n\n // Basic validation (Phase 1 will add Zod schema validation)\n const required = entry.schema.required || []\n const missing = required.filter((key: string) => !(key in params))\n\n if (missing.length > 0) {\n return {\n valid: false,\n errors: missing.map((key: string) => `Missing required field: ${key}`),\n }\n }\n\n return { valid: true }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -14,6 +14,50 @@ import { FooterRenderer } from './FooterRenderer'
|
|
|
14
14
|
import { useAction } from '../hooks/useAction'
|
|
15
15
|
import { marked } from 'marked'
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Copy button component with visual feedback
|
|
19
|
+
*/
|
|
20
|
+
function CopyButton(props: { getText: () => string; title?: string; position?: 'top-right' | 'bottom-right' }) {
|
|
21
|
+
const [copied, setCopied] = createSignal(false)
|
|
22
|
+
|
|
23
|
+
const handleCopy = async () => {
|
|
24
|
+
try {
|
|
25
|
+
await navigator.clipboard.writeText(props.getText())
|
|
26
|
+
setCopied(true)
|
|
27
|
+
setTimeout(() => setCopied(false), 2000)
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('Failed to copy:', err)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const positionClasses = () => {
|
|
34
|
+
return props.position === 'bottom-right'
|
|
35
|
+
? 'absolute -right-2 -bottom-3'
|
|
36
|
+
: 'absolute right-2 top-2'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<button
|
|
41
|
+
onClick={handleCopy}
|
|
42
|
+
class={`${positionClasses()} opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm z-10`}
|
|
43
|
+
title={props.title || 'Copy'}
|
|
44
|
+
>
|
|
45
|
+
<Show
|
|
46
|
+
when={!copied()}
|
|
47
|
+
fallback={
|
|
48
|
+
<svg class="w-3 h-3 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
49
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
50
|
+
</svg>
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
<svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
54
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
55
|
+
</svg>
|
|
56
|
+
</Show>
|
|
57
|
+
</button>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
17
61
|
/**
|
|
18
62
|
* Props for UIResourceRenderer
|
|
19
63
|
*/
|
|
@@ -200,8 +244,25 @@ function TableRenderer(props: {
|
|
|
200
244
|
}) {
|
|
201
245
|
const tableParams = props.component.params as any
|
|
202
246
|
|
|
247
|
+
// Generate copyable text from table data (TSV format for spreadsheet compatibility)
|
|
248
|
+
const getTableText = () => {
|
|
249
|
+
const columns = tableParams.columns || []
|
|
250
|
+
const rows = tableParams.rows || []
|
|
251
|
+
const header = columns.map((c: any) => c.label).join('\t')
|
|
252
|
+
const dataRows = rows.map((row: any) =>
|
|
253
|
+
columns.map((c: any) => {
|
|
254
|
+
const value = row[c.key]
|
|
255
|
+
if (value === null || value === undefined) return ''
|
|
256
|
+
if (typeof value === 'object') return value.name || value.label || JSON.stringify(value)
|
|
257
|
+
return String(value)
|
|
258
|
+
}).join('\t')
|
|
259
|
+
).join('\n')
|
|
260
|
+
return `${header}\n${dataRows}`
|
|
261
|
+
}
|
|
262
|
+
|
|
203
263
|
return (
|
|
204
|
-
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
264
|
+
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden group">
|
|
265
|
+
<CopyButton getText={getTableText} title="Copy table data" position="top-right" />
|
|
205
266
|
<div class="p-4">
|
|
206
267
|
<Show when={tableParams.title}>
|
|
207
268
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
|
|
@@ -267,8 +328,17 @@ function TableRenderer(props: {
|
|
|
267
328
|
function MetricRenderer(props: { component: UIComponent }) {
|
|
268
329
|
const metricParams = props.component.params as any
|
|
269
330
|
|
|
331
|
+
// Generate copyable text for metric
|
|
332
|
+
const getMetricText = () => {
|
|
333
|
+
const title = metricParams.title || metricParams.label || ''
|
|
334
|
+
const value = metricParams.value
|
|
335
|
+
const unit = metricParams.unit || ''
|
|
336
|
+
return `${title}: ${value}${unit ? ' ' + unit : ''}`
|
|
337
|
+
}
|
|
338
|
+
|
|
270
339
|
return (
|
|
271
|
-
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
|
340
|
+
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
|
|
341
|
+
<CopyButton getText={getMetricText} title="Copy metric" position="top-right" />
|
|
272
342
|
<div class="flex flex-col h-full justify-between">
|
|
273
343
|
<div>
|
|
274
344
|
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
|
@@ -355,12 +425,18 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
355
425
|
return textParams.content
|
|
356
426
|
})
|
|
357
427
|
|
|
428
|
+
// Get plain text content for copying (strip markdown/HTML)
|
|
429
|
+
const getTextContent = () => {
|
|
430
|
+
return textParams.content || ''
|
|
431
|
+
}
|
|
432
|
+
|
|
358
433
|
// Render as image component if we extracted image data
|
|
359
434
|
return (
|
|
360
435
|
<Show
|
|
361
436
|
when={imageData()}
|
|
362
437
|
fallback={
|
|
363
|
-
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
|
438
|
+
<div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
|
|
439
|
+
<CopyButton getText={getTextContent} title="Copy text" position="top-right" />
|
|
364
440
|
<div
|
|
365
441
|
class={`prose prose-sm dark:prose-invert max-w-none ${textParams.className || ''}`}
|
|
366
442
|
innerHTML={htmlContent()}
|
package/src/components/index.ts
CHANGED
|
@@ -13,5 +13,21 @@ export type { StreamingUIRendererProps } from './StreamingUIRenderer'
|
|
|
13
13
|
export { GenerativeUIErrorBoundary } from './GenerativeUIErrorBoundary'
|
|
14
14
|
export type { GenerativeUIErrorBoundaryProps } from './GenerativeUIErrorBoundary'
|
|
15
15
|
|
|
16
|
+
// Sprint 4: Export additional renderers
|
|
17
|
+
export { FooterRenderer } from './FooterRenderer'
|
|
18
|
+
export type { FooterComponentParams } from './FooterRenderer'
|
|
19
|
+
|
|
20
|
+
export { ActionRenderer } from './ActionRenderer'
|
|
21
|
+
export type { ActionRendererProps } from './ActionRenderer'
|
|
22
|
+
|
|
23
|
+
export { ArtifactRenderer } from './ArtifactRenderer'
|
|
24
|
+
export type { ArtifactComponentParams } from './ArtifactRenderer'
|
|
25
|
+
|
|
26
|
+
export { CarouselRenderer } from './CarouselRenderer'
|
|
27
|
+
export type { CarouselRendererProps } from './CarouselRenderer'
|
|
28
|
+
|
|
29
|
+
export { GridRenderer } from './GridRenderer'
|
|
30
|
+
export type { GridRendererProps, GridComponentParams } from './GridRenderer'
|
|
31
|
+
|
|
16
32
|
// Default exports for lazy loading compatibility
|
|
17
33
|
export { UIResourceRenderer as default } from './UIResourceRenderer'
|
|
@@ -69,6 +69,8 @@ export interface CompleteMetadata {
|
|
|
69
69
|
layoutId: string
|
|
70
70
|
componentsCount: number
|
|
71
71
|
executionTimeMs: number
|
|
72
|
+
/** Alias for executionTimeMs (for compatibility with FooterRenderer) */
|
|
73
|
+
executionTime?: number
|
|
72
74
|
firstTokenMs: number
|
|
73
75
|
provider: 'groq' | 'mock'
|
|
74
76
|
model: string
|
|
@@ -229,7 +231,11 @@ export function useStreamingUI(options: UseStreamingUIOptions) {
|
|
|
229
231
|
|
|
230
232
|
setIsStreaming(false)
|
|
231
233
|
setIsLoading(false)
|
|
232
|
-
|
|
234
|
+
// Normalize executionTimeMs to executionTime for compatibility with FooterRenderer
|
|
235
|
+
setMetadata({
|
|
236
|
+
...data,
|
|
237
|
+
executionTime: data.executionTimeMs || data.executionTime,
|
|
238
|
+
})
|
|
233
239
|
|
|
234
240
|
// Flush any remaining buffered components
|
|
235
241
|
flushBuffer()
|