@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.
- package/CHANGELOG.md +356 -142
- package/README.md +362 -21
- package/dist/components/FooterRenderer.cjs +75 -0
- package/dist/components/FooterRenderer.cjs.map +1 -0
- package/dist/components/FooterRenderer.js +75 -0
- package/dist/components/FooterRenderer.js.map +1 -0
- package/dist/components/GridRenderer.cjs +82 -0
- package/dist/components/GridRenderer.cjs.map +1 -0
- package/dist/components/GridRenderer.d.ts +49 -0
- package/dist/components/GridRenderer.d.ts.map +1 -0
- package/dist/components/GridRenderer.js +82 -0
- package/dist/components/GridRenderer.js.map +1 -0
- package/dist/components/UIResourceRenderer.cjs +88 -36
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +90 -38
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/context/MCPActionContext.cjs +149 -0
- package/dist/context/MCPActionContext.cjs.map +1 -0
- package/dist/context/MCPActionContext.d.ts +158 -0
- package/dist/context/MCPActionContext.d.ts.map +1 -0
- package/dist/context/MCPActionContext.js +149 -0
- package/dist/context/MCPActionContext.js.map +1 -0
- package/dist/context/index.d.ts +8 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useAction.cjs +49 -0
- package/dist/hooks/useAction.cjs.map +1 -0
- package/dist/hooks/useAction.d.ts +79 -0
- package/dist/hooks/useAction.d.ts.map +1 -0
- package/dist/hooks/useAction.js +49 -0
- package/dist/hooks/useAction.js.map +1 -0
- package/dist/hooks.cjs +3 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +4 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +41 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.ts +41 -2
- package/package.json +1 -1
- package/src/components/GridRenderer.tsx +140 -0
- package/src/components/UIResourceRenderer.tsx +65 -25
- package/src/context/MCPActionContext.tsx +350 -0
- package/src/context/index.ts +19 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useAction.ts +138 -0
- package/src/index.ts +14 -1
- package/src/types/index.ts +48 -1
- 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
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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={
|
|
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
|
-
${
|
|
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.
|
|
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
|
}
|