@seed-ship/mcp-ui-solid 1.0.43 → 1.2.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +356 -142
  2. package/README.md +362 -21
  3. package/dist/components/FooterRenderer.cjs +75 -0
  4. package/dist/components/FooterRenderer.cjs.map +1 -0
  5. package/dist/components/FooterRenderer.js +75 -0
  6. package/dist/components/FooterRenderer.js.map +1 -0
  7. package/dist/components/GridRenderer.cjs +82 -0
  8. package/dist/components/GridRenderer.cjs.map +1 -0
  9. package/dist/components/GridRenderer.d.ts +49 -0
  10. package/dist/components/GridRenderer.d.ts.map +1 -0
  11. package/dist/components/GridRenderer.js +82 -0
  12. package/dist/components/GridRenderer.js.map +1 -0
  13. package/dist/components/UIResourceRenderer.cjs +88 -36
  14. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  15. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  16. package/dist/components/UIResourceRenderer.js +90 -38
  17. package/dist/components/UIResourceRenderer.js.map +1 -1
  18. package/dist/context/MCPActionContext.cjs +149 -0
  19. package/dist/context/MCPActionContext.cjs.map +1 -0
  20. package/dist/context/MCPActionContext.d.ts +158 -0
  21. package/dist/context/MCPActionContext.d.ts.map +1 -0
  22. package/dist/context/MCPActionContext.js +149 -0
  23. package/dist/context/MCPActionContext.js.map +1 -0
  24. package/dist/context/index.d.ts +8 -0
  25. package/dist/context/index.d.ts.map +1 -0
  26. package/dist/hooks/index.d.ts +2 -0
  27. package/dist/hooks/index.d.ts.map +1 -1
  28. package/dist/hooks/useAction.cjs +49 -0
  29. package/dist/hooks/useAction.cjs.map +1 -0
  30. package/dist/hooks/useAction.d.ts +79 -0
  31. package/dist/hooks/useAction.d.ts.map +1 -0
  32. package/dist/hooks/useAction.js +49 -0
  33. package/dist/hooks/useAction.js.map +1 -0
  34. package/dist/hooks.cjs +3 -0
  35. package/dist/hooks.cjs.map +1 -1
  36. package/dist/hooks.d.ts +2 -0
  37. package/dist/hooks.js +4 -1
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.cjs +8 -0
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +5 -3
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +8 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/types/index.d.ts +41 -2
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/types.d.ts +41 -2
  48. package/package.json +1 -1
  49. package/src/components/GridRenderer.tsx +140 -0
  50. package/src/components/UIResourceRenderer.tsx +65 -25
  51. package/src/context/MCPActionContext.tsx +350 -0
  52. package/src/context/index.ts +19 -0
  53. package/src/hooks/index.ts +4 -0
  54. package/src/hooks/useAction.ts +138 -0
  55. package/src/index.ts +14 -1
  56. package/src/types/index.ts +48 -1
  57. package/tsconfig.tsbuildinfo +1 -1
package/dist/types.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * Component types supported by the renderer
9
9
  */
10
- export type ComponentType = 'chart' | 'table' | 'metric' | 'text' | 'grid' | 'iframe' | 'image' | 'link' | 'action';
10
+ export type ComponentType = 'chart' | 'table' | 'metric' | 'text' | 'grid' | 'iframe' | 'image' | 'link' | 'action' | 'footer' | 'carousel' | 'artifact';
11
11
  /**
12
12
  * Chart types (powered by Quickchart)
13
13
  */
@@ -133,6 +133,33 @@ export interface ActionComponentParams {
133
133
  icon?: string;
134
134
  disabled?: boolean;
135
135
  }
136
+ /**
137
+ * Grid component parameters (Phase 5.0)
138
+ * Enables nested CSS Grid layouts for template builder
139
+ */
140
+ export interface GridComponentParams {
141
+ /**
142
+ * Number of columns (default: 12)
143
+ */
144
+ columns?: number;
145
+ /**
146
+ * Gap between grid items (default: '1rem')
147
+ */
148
+ gap?: string;
149
+ /**
150
+ * Minimum row height (optional)
151
+ */
152
+ minRowHeight?: string;
153
+ /**
154
+ * CSS Grid template areas for named regions
155
+ * Example: [['header', 'header'], ['sidebar', 'main'], ['footer', 'footer']]
156
+ */
157
+ areas?: string[][];
158
+ /**
159
+ * Child components to render within the grid
160
+ */
161
+ children: UIComponent[];
162
+ }
136
163
  /**
137
164
  * UI Component definition (generated by LLM)
138
165
  */
@@ -152,7 +179,7 @@ export interface UIComponent {
152
179
  /**
153
180
  * Component parameters (type-specific)
154
181
  */
155
- params: ChartComponentParams | TableComponentParams | MetricComponentParams | TextComponentParams | ActionComponentParams;
182
+ params: ChartComponentParams | TableComponentParams | MetricComponentParams | TextComponentParams | ActionComponentParams | GridComponentParams;
156
183
  /**
157
184
  * Metadata for observability
158
185
  */
@@ -191,6 +218,18 @@ export interface UILayout {
191
218
  generatedAt: string;
192
219
  llmModel?: string;
193
220
  totalComponents: number;
221
+ /**
222
+ * Execution time in milliseconds (Phase 5.0)
223
+ */
224
+ executionTime?: number;
225
+ /**
226
+ * Number of sources used (Phase 5.0)
227
+ */
228
+ sourceCount?: number;
229
+ /**
230
+ * Hide auto-injected footer (Phase 5.0)
231
+ */
232
+ hideFooter?: boolean;
194
233
  };
195
234
  }
196
235
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-ship/mcp-ui-solid",
3
- "version": "1.0.43",
3
+ "version": "1.2.0",
4
4
  "description": "SolidJS components for rendering MCP-generated UI resources",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -0,0 +1,140 @@
1
+ /**
2
+ * GridRenderer - CSS Grid layout component for nested layouts
3
+ * Phase 5.0: Quick Wins - Enables template builder layouts
4
+ */
5
+
6
+ import { Component, For, createMemo } from 'solid-js'
7
+ import type { UIComponent, GridPosition } from '../types'
8
+ import { UIResourceRenderer } from './UIResourceRenderer'
9
+
10
+ /**
11
+ * Parameters for GridRenderer component
12
+ */
13
+ export interface GridComponentParams {
14
+ /**
15
+ * Number of columns (default: 12)
16
+ */
17
+ columns?: number
18
+
19
+ /**
20
+ * Gap between grid items (default: '1rem')
21
+ */
22
+ gap?: string
23
+
24
+ /**
25
+ * Minimum row height (optional)
26
+ */
27
+ minRowHeight?: string
28
+
29
+ /**
30
+ * CSS Grid template areas for named regions
31
+ * Example: [['header', 'header'], ['sidebar', 'main'], ['footer', 'footer']]
32
+ */
33
+ areas?: string[][]
34
+
35
+ /**
36
+ * Child components to render within the grid
37
+ */
38
+ children: UIComponent[]
39
+ }
40
+
41
+ export interface GridRendererProps {
42
+ /**
43
+ * Grid component with params
44
+ */
45
+ component: UIComponent
46
+
47
+ /**
48
+ * Error callback
49
+ */
50
+ onError?: (error: any) => void
51
+ }
52
+
53
+ /**
54
+ * Convert grid position to CSS style string
55
+ */
56
+ function getGridItemStyle(position: GridPosition | undefined, areas?: string[][]): string {
57
+ // Default to full width if no position specified
58
+ if (!position) {
59
+ return 'grid-column: 1 / -1; grid-row: auto'
60
+ }
61
+
62
+ const { colStart, colSpan, rowStart, rowSpan = 1 } = position
63
+
64
+ // If using named areas and component has area name, use grid-area
65
+ // Otherwise use explicit grid-column/grid-row
66
+ let style = `grid-column: ${colStart} / span ${colSpan}`
67
+
68
+ if (rowStart) {
69
+ style += `; grid-row: ${rowStart} / span ${rowSpan}`
70
+ } else {
71
+ style += '; grid-row: auto'
72
+ }
73
+
74
+ return style
75
+ }
76
+
77
+ /**
78
+ * Build CSS grid-template-areas string from areas array
79
+ */
80
+ function buildGridTemplateAreas(areas: string[][]): string {
81
+ return areas.map((row) => `"${row.join(' ')}"`).join(' ')
82
+ }
83
+
84
+ /**
85
+ * GridRenderer Component
86
+ * Renders a CSS Grid container with nested UIComponents
87
+ */
88
+ export const GridRenderer: Component<GridRendererProps> = (props) => {
89
+ // Extract params with defaults
90
+ const params = createMemo(() => {
91
+ const p = props.component.params as GridComponentParams
92
+ return {
93
+ columns: p.columns ?? 12,
94
+ gap: p.gap ?? '1rem',
95
+ minRowHeight: p.minRowHeight,
96
+ areas: p.areas,
97
+ children: p.children ?? [],
98
+ }
99
+ })
100
+
101
+ // Build grid container style
102
+ const gridContainerStyle = createMemo(() => {
103
+ const p = params()
104
+ let style = `display: grid; grid-template-columns: repeat(${p.columns}, 1fr); gap: ${p.gap}`
105
+
106
+ if (p.minRowHeight) {
107
+ style += `; grid-auto-rows: minmax(${p.minRowHeight}, auto)`
108
+ }
109
+
110
+ if (p.areas && p.areas.length > 0) {
111
+ style += `; grid-template-areas: ${buildGridTemplateAreas(p.areas)}`
112
+ }
113
+
114
+ return style
115
+ })
116
+
117
+ return (
118
+ <div
119
+ 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"
120
+ data-component-type="grid"
121
+ data-component-id={props.component.id}
122
+ >
123
+ <div class="p-4 h-full" style={gridContainerStyle()}>
124
+ <For each={params().children}>
125
+ {(child) => (
126
+ <div
127
+ style={getGridItemStyle(child.position, params().areas)}
128
+ class="min-w-0 h-full"
129
+ >
130
+ {/* Use UIResourceRenderer for recursive rendering */}
131
+ <UIResourceRenderer content={child} onError={props.onError} />
132
+ </div>
133
+ )}
134
+ </For>
135
+ </div>
136
+ </div>
137
+ )
138
+ }
139
+
140
+ export default GridRenderer
@@ -9,6 +9,9 @@ import { isServer } from 'solid-js/web'
9
9
  import type { UIComponent, UILayout, RendererError, ComponentType } from '../types'
10
10
  import { validateComponent, DEFAULT_RESOURCE_LIMITS } from '../services/validation'
11
11
  import { GenerativeUIErrorBoundary } from './GenerativeUIErrorBoundary'
12
+ import { GridRenderer } from './GridRenderer'
13
+ import { FooterRenderer } from './FooterRenderer'
14
+ import { useAction } from '../hooks/useAction'
12
15
  import { marked } from 'marked'
13
16
 
14
17
  /**
@@ -554,43 +557,32 @@ function ComponentRenderer(props: {
554
557
  <Show when={props.component.type === 'action'}>
555
558
  <ActionRenderer component={props.component} />
556
559
  </Show>
560
+ <Show when={props.component.type === 'grid'}>
561
+ <GridRenderer component={props.component} onError={props.onError} />
562
+ </Show>
557
563
  </GenerativeUIErrorBoundary>
558
564
  )
559
565
  }
560
566
 
561
567
  /**
562
568
  * Render an action component (button or link)
569
+ * Refactored in Phase 5.0 to use useAction hook for Context-based execution
563
570
  */
564
571
  function ActionRenderer(props: { component: UIComponent }) {
565
572
  const params = props.component.params as any
566
- let dispatchAction: ((toolName: string, toolParams: any) => void) | null = null
567
-
568
- // Initialize CustomEvent dispatcher only on client-side
569
- // Use createEffect instead of onMount for SSR compatibility
570
- createEffect(() => {
571
- if (typeof window !== 'undefined') {
572
- dispatchAction = (toolName: string, toolParams: any) => {
573
- const event = new CustomEvent('mcp-action', {
574
- detail: {
575
- toolName,
576
- params: toolParams,
577
- },
578
- bubbles: true,
579
- })
580
- window.dispatchEvent(event)
581
- }
582
- }
583
- })
573
+ const { execute, isExecuting } = useAction()
584
574
 
585
- // Handle click to execute tool via window event
586
- const handleClick = (e: MouseEvent) => {
575
+ // Handle click to execute tool via Context (falls back to CustomEvent if no provider)
576
+ const handleClick = async (e: MouseEvent) => {
587
577
  if (params.action === 'tool-call' && params.toolName) {
588
578
  e.preventDefault()
589
- // SSR-safe: Only call if dispatcher was initialized client-side
590
- dispatchAction?.(params.toolName, params.params || {})
579
+ await execute(params.toolName, params.params || {})
591
580
  }
592
581
  }
593
582
 
583
+ // Determine if button should be disabled (explicit disable or currently executing)
584
+ const isDisabled = () => params.disabled || (params.action === 'tool-call' && isExecuting())
585
+
594
586
  if (params.type === 'link' || params.action === 'link') {
595
587
  return (
596
588
  <a
@@ -614,18 +606,21 @@ function ActionRenderer(props: { component: UIComponent }) {
614
606
  return (
615
607
  <button
616
608
  type={params.action === 'submit' ? 'submit' : 'button'}
617
- disabled={params.disabled}
609
+ disabled={isDisabled()}
618
610
  class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
619
611
  ${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm' :
620
612
  params.variant === 'secondary' ? 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600' :
621
613
  params.variant === 'outline' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800' :
622
614
  params.variant === 'danger' ? 'bg-red-600 text-white hover:bg-red-700' :
623
615
  'bg-transparent text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'}
624
- ${params.disabled ? 'opacity-50 cursor-not-allowed' : ''}
616
+ ${isDisabled() ? 'opacity-50 cursor-not-allowed' : ''}
625
617
  ${params.size === 'sm' ? 'px-3 py-1.5 text-xs' : params.size === 'lg' ? 'px-6 py-3 text-base' : ''}`}
626
618
  onClick={handleClick}
627
619
  >
628
- <Show when={params.icon}>
620
+ <Show when={isExecuting() && params.action === 'tool-call'}>
621
+ <span class="animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full" />
622
+ </Show>
623
+ <Show when={params.icon && !(isExecuting() && params.action === 'tool-call')}>
629
624
  <span>{params.icon}</span>
630
625
  </Show>
631
626
  {params.label}
@@ -668,6 +663,46 @@ export const UIResourceRenderer: Component<UIResourceRendererProps> = (props) =>
668
663
  return `grid-column: ${colStart} / span ${colSpan}; grid-row: ${rowStart ? `${rowStart} / span ${rowSpan}` : 'auto'}`
669
664
  }
670
665
 
666
+ // Auto-footer logic (Phase 5.0)
667
+ // Automatically inject footer when metadata is present and no explicit footer exists
668
+ const shouldShowAutoFooter = createMemo(() => {
669
+ const layoutData = layout()
670
+
671
+ // Don't show if explicitly hidden
672
+ if (layoutData.metadata?.hideFooter) {
673
+ return false
674
+ }
675
+
676
+ // Don't show if no metadata (nothing to display)
677
+ if (!layoutData.metadata) {
678
+ return false
679
+ }
680
+
681
+ // Don't show if explicit footer component exists
682
+ const hasExplicitFooter = layoutData.components.some((c) => c.type === 'footer')
683
+ if (hasExplicitFooter) {
684
+ return false
685
+ }
686
+
687
+ // Show auto-footer if metadata has relevant info
688
+ return !!(
689
+ layoutData.metadata.executionTime ||
690
+ layoutData.metadata.sourceCount ||
691
+ layoutData.metadata.llmModel
692
+ )
693
+ })
694
+
695
+ // Build auto-footer params from metadata
696
+ const autoFooterParams = createMemo(() => {
697
+ const layoutData = layout()
698
+ return {
699
+ poweredBy: 'Deposium',
700
+ executionTime: layoutData.metadata?.executionTime,
701
+ model: layoutData.metadata?.llmModel,
702
+ sourceCount: layoutData.metadata?.sourceCount,
703
+ }
704
+ })
705
+
671
706
  const layoutData = layout()
672
707
 
673
708
  return (
@@ -681,6 +716,11 @@ export const UIResourceRenderer: Component<UIResourceRendererProps> = (props) =>
681
716
  )}
682
717
  </For>
683
718
  </div>
719
+
720
+ {/* Auto-injected footer (Phase 5.0) */}
721
+ <Show when={shouldShowAutoFooter()}>
722
+ <FooterRenderer params={autoFooterParams()} />
723
+ </Show>
684
724
  </div>
685
725
  )
686
726
  }