@seed-ship/mcp-ui-solid 1.0.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.
@@ -0,0 +1,187 @@
1
+ import { createSignal as m, onCleanup as T } from "solid-js";
2
+ const w = typeof process < "u" && process.env.NODE_ENV !== "production";
3
+ function S(n, s, o) {
4
+ const v = o ? ` ${JSON.stringify(o)}` : "";
5
+ return `[@seed-ship/mcp-ui-solid:${n}] ${s}${v}`;
6
+ }
7
+ function W(n) {
8
+ return {
9
+ info(s, o) {
10
+ w && console.info(S(n, s, o));
11
+ },
12
+ warn(s, o) {
13
+ w && console.warn(S(n, s, o));
14
+ },
15
+ error(s, o) {
16
+ console.error(S(n, s, o));
17
+ },
18
+ debug(s, o) {
19
+ w && console.debug(S(n, s, o));
20
+ }
21
+ };
22
+ }
23
+ const r = W("useStreamingUI");
24
+ function V(n) {
25
+ const [s, o] = m([]), [v, u] = m(!1), [$, d] = m(!1), [L, b] = m(null), [h, c] = m({
26
+ receivedCount: 0,
27
+ totalCount: null,
28
+ message: "Initializing...",
29
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
30
+ }), [N, R] = m(null);
31
+ let l = {}, i = 0, g = 0;
32
+ const x = 3, y = () => {
33
+ const e = [];
34
+ for (; l[i]; ) {
35
+ const { component: t } = l[i];
36
+ e.push(t), delete l[i], i++;
37
+ }
38
+ e.length > 0 && (o((t) => [...t, ...e]), c((t) => ({
39
+ ...t,
40
+ receivedCount: t.receivedCount + e.length
41
+ })), r.debug("Flushed components from buffer", {
42
+ count: e.length,
43
+ nextSequenceId: i
44
+ }));
45
+ }, U = (e) => {
46
+ r.debug("Status event received", e), c({
47
+ receivedCount: h().receivedCount,
48
+ totalCount: e.totalComponents ?? h().totalCount,
49
+ message: e.message,
50
+ timestamp: e.timestamp
51
+ });
52
+ }, A = (e) => {
53
+ r.debug("Component-start event received", e), c((t) => ({
54
+ ...t,
55
+ message: `Loading ${e.type} component...`,
56
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
57
+ }));
58
+ }, B = (e) => {
59
+ r.debug("Component event received", {
60
+ componentId: e.componentId,
61
+ sequenceId: e.sequenceId
62
+ }), l[e.sequenceId] = {
63
+ component: e.component,
64
+ position: e.position
65
+ }, y(), n.onComponentReceived && n.onComponentReceived(e.component);
66
+ }, J = (e) => {
67
+ r.info("Stream completed", e), d(!1), u(!1), R(e), y(), c((t) => ({
68
+ ...t,
69
+ message: "Dashboard loaded",
70
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
71
+ })), n.onComplete && n.onComplete(e);
72
+ }, q = (e) => {
73
+ r.error("Stream error received", e), b(e), d(!1), u(!1), c((t) => ({
74
+ ...t,
75
+ message: `Error: ${e.message}`,
76
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
77
+ })), n.onError && n.onError(e), e.recoverable && g < x && (g++, r.warn("Attempting to reconnect", { attempt: g }), setTimeout(() => C(), 1e3 * g));
78
+ }, j = (e, t) => {
79
+ try {
80
+ const a = JSON.parse(e.data);
81
+ switch (t) {
82
+ case "status":
83
+ U(a);
84
+ break;
85
+ case "component-start":
86
+ A(a);
87
+ break;
88
+ case "component":
89
+ B(a);
90
+ break;
91
+ case "complete":
92
+ J(a);
93
+ break;
94
+ case "error":
95
+ q(a);
96
+ break;
97
+ default:
98
+ r.warn("Unknown SSE event type", { eventType: t });
99
+ }
100
+ } catch (a) {
101
+ r.error("Failed to parse SSE event", {
102
+ error: a instanceof Error ? a.message : String(a),
103
+ eventType: t
104
+ });
105
+ }
106
+ }, C = () => {
107
+ o([]), b(null), u(!0), d(!0), l = {}, i = 0, c({
108
+ receivedCount: 0,
109
+ totalCount: null,
110
+ message: "Connecting to server...",
111
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
112
+ }), r.info("Starting SSE stream", {
113
+ query: n.query,
114
+ spaceIds: n.spaceIds
115
+ });
116
+ const e = {
117
+ query: n.query,
118
+ spaceIds: n.spaceIds,
119
+ sessionId: n.sessionId,
120
+ options: n.options
121
+ };
122
+ fetch("/api/mcp/generative-ui-stream", {
123
+ method: "POST",
124
+ headers: {
125
+ "Content-Type": "application/json"
126
+ },
127
+ body: JSON.stringify(e)
128
+ }).then(async (t) => {
129
+ if (!t.ok) {
130
+ const I = await t.json();
131
+ throw new Error(I.message || "Stream request failed");
132
+ }
133
+ if (!t.body)
134
+ throw new Error("Response body is null");
135
+ const a = t.body.getReader(), F = new TextDecoder();
136
+ let E = "";
137
+ const O = async () => {
138
+ const { done: I, value: M } = await a.read();
139
+ if (I) {
140
+ r.info("Stream ended");
141
+ return;
142
+ }
143
+ E += F.decode(M, { stream: !0 });
144
+ const k = E.split(`
145
+ `);
146
+ E = k.pop() || "";
147
+ let f = null;
148
+ for (const p of k)
149
+ if (p.startsWith("event: "))
150
+ f = p.slice(7);
151
+ else if (p.startsWith("data: ") && f) {
152
+ const P = p.slice(6);
153
+ j({ data: P }, f), f = null;
154
+ }
155
+ return O();
156
+ };
157
+ await O();
158
+ }).catch((t) => {
159
+ r.error("Stream fetch failed", {
160
+ error: t instanceof Error ? t.message : String(t)
161
+ }), q({
162
+ error: "Stream connection failed",
163
+ message: t instanceof Error ? t.message : "Unknown error",
164
+ recoverable: !0
165
+ });
166
+ });
167
+ }, D = () => {
168
+ d(!1), u(!1), r.info("Streaming stopped");
169
+ };
170
+ return T(() => {
171
+ D();
172
+ }), C(), {
173
+ components: s,
174
+ isLoading: v,
175
+ isStreaming: $,
176
+ error: L,
177
+ progress: h,
178
+ metadata: N,
179
+ startStreaming: C,
180
+ stopStreaming: D
181
+ };
182
+ }
183
+ export {
184
+ W as c,
185
+ V as u
186
+ };
187
+ //# sourceMappingURL=useStreamingUI-BL0nh13E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStreamingUI-BL0nh13E.js","sources":["../src/utils/logger.ts","../src/hooks/useStreamingUI.ts"],"sourcesContent":["/**\n * Simple internal logger utility\n *\n * Provides basic logging functionality for the package.\n * Consumers can disable logging by setting NODE_ENV=production\n * or by implementing their own logging solution.\n */\n\nconst isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'\n\nexport interface Logger {\n info(message: string, context?: Record<string, unknown>): void\n warn(message: string, context?: Record<string, unknown>): void\n error(message: string, context?: Record<string, unknown>): void\n debug(message: string, context?: Record<string, unknown>): void\n}\n\nfunction formatLogMessage(\n feature: string,\n message: string,\n context?: Record<string, unknown>\n): string {\n const contextStr = context ? ` ${JSON.stringify(context)}` : ''\n return `[@seed-ship/mcp-ui-solid:${feature}] ${message}${contextStr}`\n}\n\n/**\n * Creates a feature-scoped logger\n *\n * @param feature - Feature name for log prefixing\n * @returns Logger instance\n *\n * @example\n * ```typescript\n * const logger = createLogger('my-component')\n * logger.info('Component mounted', { componentId: '123' })\n * ```\n */\nexport function createLogger(feature: string): Logger {\n return {\n info(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.info(formatLogMessage(feature, message, context))\n }\n },\n\n warn(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.warn(formatLogMessage(feature, message, context))\n }\n },\n\n error(message: string, context?: Record<string, unknown>) {\n // Always log errors, even in production\n console.error(formatLogMessage(feature, message, context))\n },\n\n debug(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.debug(formatLogMessage(feature, message, context))\n }\n },\n }\n}\n\n/**\n * No-op logger for testing or when logging is disabled\n */\nexport const noopLogger: Logger = {\n info: () => {},\n warn: () => {},\n error: () => {},\n debug: () => {},\n}\n","/**\n * useStreamingUI Hook - Phase 2\n *\n * Client-side hook for consuming the streaming generative UI endpoint.\n * Handles SSE connection, component buffering, reordering, and error handling.\n *\n * Features:\n * - SSE connection with automatic reconnection\n * - Component buffering and reordering by sequenceId\n * - Progress tracking and loading states\n * - Error handling with recovery attempts\n * - Cleanup on unmount\n *\n * Usage:\n * ```tsx\n * const { components, isLoading, error, progress } = useStreamingUI({\n * query: 'Show me revenue trends',\n * spaceIds: ['uuid1', 'uuid2'],\n * onComplete: (metadata) => console.log('Done!', metadata),\n * })\n * ```\n */\n\nimport { createSignal, onCleanup } from 'solid-js'\nimport type { UIComponent } from '../types'\nimport { createLogger } from '../utils/logger'\n\nconst logger = createLogger('useStreamingUI')\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface UseStreamingUIOptions {\n query: string\n spaceIds?: string[]\n sessionId?: string\n options?: {\n useCache?: boolean\n useLLM?: boolean\n maxComponents?: number\n preferredComponents?: Array<'chart' | 'table' | 'metric' | 'text'>\n }\n onComplete?: (metadata: CompleteMetadata) => void\n onError?: (error: StreamError) => void\n onComponentReceived?: (component: UIComponent) => void\n}\n\nexport interface StreamingUIState {\n components: UIComponent[]\n isLoading: boolean\n isStreaming: boolean\n error: StreamError | null\n progress: StreamProgress\n metadata: CompleteMetadata | null\n}\n\nexport interface StreamProgress {\n receivedCount: number\n totalCount: number | null\n message: string\n timestamp: string\n}\n\nexport interface CompleteMetadata {\n layoutId: string\n componentsCount: number\n executionTimeMs: number\n firstTokenMs: number\n provider: 'groq' | 'mock'\n model: string\n tokensUsed?: number\n costUSD?: number\n cached: boolean\n}\n\nexport interface StreamError {\n error: string\n message: string\n componentId?: string\n recoverable: boolean\n}\n\ninterface ComponentBuffer {\n [sequenceId: number]: {\n component: UIComponent\n position: { colStart: number; colSpan: number; rowStart?: number; rowSpan?: number }\n }\n}\n\n// ============================================================================\n// SSE Event Types (must match server)\n// ============================================================================\n\ntype SSEEventType = 'status' | 'component-start' | 'component' | 'complete' | 'error'\n\ninterface StatusEvent {\n message: string\n timestamp: string\n totalComponents?: number\n}\n\ninterface ComponentStartEvent {\n componentId: string\n type: 'chart' | 'table' | 'metric' | 'text'\n sequenceId: number\n position: { colStart: number; colSpan: number }\n}\n\ninterface ComponentEvent {\n componentId: string\n sequenceId: number\n component: UIComponent\n position: { colStart: number; colSpan: number; rowStart?: number; rowSpan?: number }\n}\n\n// ============================================================================\n// Hook Implementation\n// ============================================================================\n\nexport function useStreamingUI(options: UseStreamingUIOptions) {\n // State\n const [components, setComponents] = createSignal<UIComponent[]>([])\n const [isLoading, setIsLoading] = createSignal(false)\n const [isStreaming, setIsStreaming] = createSignal(false)\n const [error, setError] = createSignal<StreamError | null>(null)\n const [progress, setProgress] = createSignal<StreamProgress>({\n receivedCount: 0,\n totalCount: null,\n message: 'Initializing...',\n timestamp: new Date().toISOString(),\n })\n const [metadata, setMetadata] = createSignal<CompleteMetadata | null>(null)\n\n // Component buffer for reordering\n let componentBuffer: ComponentBuffer = {}\n let nextSequenceId = 0\n let eventSource: EventSource | null = null\n let reconnectAttempts = 0\n const maxReconnectAttempts = 3\n\n /**\n * Flush components from buffer in sequence order\n */\n const flushBuffer = () => {\n const flushed: UIComponent[] = []\n\n while (componentBuffer[nextSequenceId]) {\n const { component } = componentBuffer[nextSequenceId]\n flushed.push(component)\n delete componentBuffer[nextSequenceId]\n nextSequenceId++\n }\n\n if (flushed.length > 0) {\n setComponents((prev) => [...prev, ...flushed])\n\n setProgress((prev) => ({\n ...prev,\n receivedCount: prev.receivedCount + flushed.length,\n }))\n\n logger.debug('Flushed components from buffer', {\n count: flushed.length,\n nextSequenceId,\n })\n }\n }\n\n /**\n * Handle SSE status event\n */\n const handleStatusEvent = (data: StatusEvent) => {\n logger.debug('Status event received', data as unknown as Record<string, unknown>)\n\n setProgress({\n receivedCount: progress().receivedCount,\n totalCount: data.totalComponents ?? progress().totalCount,\n message: data.message,\n timestamp: data.timestamp,\n })\n }\n\n /**\n * Handle SSE component-start event\n */\n const handleComponentStartEvent = (data: ComponentStartEvent) => {\n logger.debug('Component-start event received', data as unknown as Record<string, unknown>)\n\n setProgress((prev) => ({\n ...prev,\n message: `Loading ${data.type} component...`,\n timestamp: new Date().toISOString(),\n }))\n }\n\n /**\n * Handle SSE component event\n */\n const handleComponentEvent = (data: ComponentEvent) => {\n logger.debug('Component event received', {\n componentId: data.componentId,\n sequenceId: data.sequenceId,\n })\n\n // Add to buffer\n componentBuffer[data.sequenceId] = {\n component: data.component,\n position: data.position,\n }\n\n // Flush buffer in sequence\n flushBuffer()\n\n // Notify callback\n if (options.onComponentReceived) {\n options.onComponentReceived(data.component)\n }\n }\n\n /**\n * Handle SSE complete event\n */\n const handleCompleteEvent = (data: CompleteMetadata) => {\n logger.info('Stream completed', data as unknown as Record<string, unknown>)\n\n setIsStreaming(false)\n setIsLoading(false)\n setMetadata(data)\n\n // Flush any remaining buffered components\n flushBuffer()\n\n setProgress((prev) => ({\n ...prev,\n message: 'Dashboard loaded',\n timestamp: new Date().toISOString(),\n }))\n\n // Notify callback\n if (options.onComplete) {\n options.onComplete(data)\n }\n }\n\n /**\n * Handle SSE error event\n */\n const handleErrorEvent = (data: StreamError) => {\n logger.error('Stream error received', data as unknown as Record<string, unknown>)\n\n setError(data)\n setIsStreaming(false)\n setIsLoading(false)\n\n setProgress((prev) => ({\n ...prev,\n message: `Error: ${data.message}`,\n timestamp: new Date().toISOString(),\n }))\n\n // Notify callback\n if (options.onError) {\n options.onError(data)\n }\n\n // Try to reconnect if recoverable\n if (data.recoverable && reconnectAttempts < maxReconnectAttempts) {\n reconnectAttempts++\n logger.warn('Attempting to reconnect', { attempt: reconnectAttempts })\n setTimeout(() => startStreaming(), 1000 * reconnectAttempts)\n }\n }\n\n /**\n * Parse SSE event\n */\n const parseSSEEvent = (event: MessageEvent, eventType: SSEEventType) => {\n try {\n const data = JSON.parse(event.data)\n\n switch (eventType) {\n case 'status':\n handleStatusEvent(data as StatusEvent)\n break\n case 'component-start':\n handleComponentStartEvent(data as ComponentStartEvent)\n break\n case 'component':\n handleComponentEvent(data as ComponentEvent)\n break\n case 'complete':\n handleCompleteEvent(data as CompleteMetadata)\n break\n case 'error':\n handleErrorEvent(data as StreamError)\n break\n default:\n logger.warn('Unknown SSE event type', { eventType })\n }\n } catch (error) {\n logger.error('Failed to parse SSE event', {\n error: error instanceof Error ? error.message : String(error),\n eventType,\n })\n }\n }\n\n /**\n * Start SSE streaming\n */\n const startStreaming = () => {\n // Reset state\n setComponents([])\n setError(null)\n setIsLoading(true)\n setIsStreaming(true)\n componentBuffer = {}\n nextSequenceId = 0\n\n setProgress({\n receivedCount: 0,\n totalCount: null,\n message: 'Connecting to server...',\n timestamp: new Date().toISOString(),\n })\n\n logger.info('Starting SSE stream', {\n query: options.query,\n spaceIds: options.spaceIds,\n })\n\n // Build request body\n const requestBody = {\n query: options.query,\n spaceIds: options.spaceIds,\n sessionId: options.sessionId,\n options: options.options,\n }\n\n // Create EventSource (SSE connection)\n // Note: EventSource doesn't support POST, so we need to use fetch + ReadableStream\n fetch('/api/mcp/generative-ui-stream', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n })\n .then(async (response) => {\n if (!response.ok) {\n const errorData = await response.json()\n throw new Error(errorData.message || 'Stream request failed')\n }\n\n if (!response.body) {\n throw new Error('Response body is null')\n }\n\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n\n let buffer = ''\n\n // Read stream\n const readChunk = async (): Promise<void> => {\n const { done, value } = await reader.read()\n\n if (done) {\n logger.info('Stream ended')\n return\n }\n\n buffer += decoder.decode(value, { stream: true })\n\n // Process SSE messages\n const lines = buffer.split('\\n')\n buffer = lines.pop() || '' // Keep incomplete line in buffer\n\n let currentEvent: SSEEventType | null = null\n\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n currentEvent = line.slice(7) as SSEEventType\n } else if (line.startsWith('data: ') && currentEvent) {\n const data = line.slice(6)\n parseSSEEvent({ data } as MessageEvent, currentEvent)\n currentEvent = null\n }\n }\n\n // Continue reading\n return readChunk()\n }\n\n await readChunk()\n })\n .catch((err) => {\n logger.error('Stream fetch failed', {\n error: err instanceof Error ? err.message : String(err),\n })\n\n handleErrorEvent({\n error: 'Stream connection failed',\n message: err instanceof Error ? err.message : 'Unknown error',\n recoverable: true,\n })\n })\n }\n\n /**\n * Stop streaming\n */\n const stopStreaming = () => {\n if (eventSource) {\n eventSource.close()\n eventSource = null\n }\n\n setIsStreaming(false)\n setIsLoading(false)\n\n logger.info('Streaming stopped')\n }\n\n /**\n * Cleanup on unmount\n */\n onCleanup(() => {\n stopStreaming()\n })\n\n // Auto-start streaming\n startStreaming()\n\n // Return state accessors and controls\n return {\n components,\n isLoading,\n isStreaming,\n error,\n progress,\n metadata,\n startStreaming,\n stopStreaming,\n }\n}\n"],"names":["isDev","formatLogMessage","feature","message","context","contextStr","createLogger","logger","useStreamingUI","options","components","setComponents","createSignal","isLoading","setIsLoading","isStreaming","setIsStreaming","error","setError","progress","setProgress","metadata","setMetadata","componentBuffer","nextSequenceId","reconnectAttempts","maxReconnectAttempts","flushBuffer","flushed","component","prev","handleStatusEvent","data","handleComponentStartEvent","handleComponentEvent","handleCompleteEvent","handleErrorEvent","startStreaming","parseSSEEvent","event","eventType","requestBody","response","errorData","reader","decoder","buffer","readChunk","done","value","lines","currentEvent","line","err","stopStreaming","onCleanup"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";const c=require("solid-js"),w=typeof process<"u"&&process.env.NODE_ENV!=="production";function S(n,s,o){const v=o?` ${JSON.stringify(o)}`:"";return`[@seed-ship/mcp-ui-solid:${n}] ${s}${v}`}function L(n){return{info(s,o){w&&console.info(S(n,s,o))},warn(s,o){w&&console.warn(S(n,s,o))},error(s,o){console.error(S(n,s,o))},debug(s,o){w&&console.debug(S(n,s,o))}}}const r=L("useStreamingUI");function W(n){const[s,o]=c.createSignal([]),[v,u]=c.createSignal(!1),[$,d]=c.createSignal(!1),[N,b]=c.createSignal(null),[h,i]=c.createSignal({receivedCount:0,totalCount:null,message:"Initializing...",timestamp:new Date().toISOString()}),[R,U]=c.createSignal(null);let m={},l=0,g=0;const J=3,y=()=>{const e=[];for(;m[l];){const{component:t}=m[l];e.push(t),delete m[l],l++}e.length>0&&(o(t=>[...t,...e]),i(t=>({...t,receivedCount:t.receivedCount+e.length})),r.debug("Flushed components from buffer",{count:e.length,nextSequenceId:l}))},x=e=>{r.debug("Status event received",e),i({receivedCount:h().receivedCount,totalCount:e.totalComponents??h().totalCount,message:e.message,timestamp:e.timestamp})},A=e=>{r.debug("Component-start event received",e),i(t=>({...t,message:`Loading ${e.type} component...`,timestamp:new Date().toISOString()}))},B=e=>{r.debug("Component event received",{componentId:e.componentId,sequenceId:e.sequenceId}),m[e.sequenceId]={component:e.component,position:e.position},y(),n.onComponentReceived&&n.onComponentReceived(e.component)},j=e=>{r.info("Stream completed",e),d(!1),u(!1),U(e),y(),i(t=>({...t,message:"Dashboard loaded",timestamp:new Date().toISOString()})),n.onComplete&&n.onComplete(e)},q=e=>{r.error("Stream error received",e),b(e),d(!1),u(!1),i(t=>({...t,message:`Error: ${e.message}`,timestamp:new Date().toISOString()})),n.onError&&n.onError(e),e.recoverable&&g<J&&(g++,r.warn("Attempting to reconnect",{attempt:g}),setTimeout(()=>C(),1e3*g))},F=(e,t)=>{try{const a=JSON.parse(e.data);switch(t){case"status":x(a);break;case"component-start":A(a);break;case"component":B(a);break;case"complete":j(a);break;case"error":q(a);break;default:r.warn("Unknown SSE event type",{eventType:t})}}catch(a){r.error("Failed to parse SSE event",{error:a instanceof Error?a.message:String(a),eventType:t})}},C=()=>{o([]),b(null),u(!0),d(!0),m={},l=0,i({receivedCount:0,totalCount:null,message:"Connecting to server...",timestamp:new Date().toISOString()}),r.info("Starting SSE stream",{query:n.query,spaceIds:n.spaceIds});const e={query:n.query,spaceIds:n.spaceIds,sessionId:n.sessionId,options:n.options};fetch("/api/mcp/generative-ui-stream",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}).then(async t=>{if(!t.ok){const I=await t.json();throw new Error(I.message||"Stream request failed")}if(!t.body)throw new Error("Response body is null");const a=t.body.getReader(),M=new TextDecoder;let E="";const O=async()=>{const{done:I,value:P}=await a.read();if(I){r.info("Stream ended");return}E+=M.decode(P,{stream:!0});const k=E.split(`
2
+ `);E=k.pop()||"";let f=null;for(const p of k)if(p.startsWith("event: "))f=p.slice(7);else if(p.startsWith("data: ")&&f){const T=p.slice(6);F({data:T},f),f=null}return O()};await O()}).catch(t=>{r.error("Stream fetch failed",{error:t instanceof Error?t.message:String(t)}),q({error:"Stream connection failed",message:t instanceof Error?t.message:"Unknown error",recoverable:!0})})},D=()=>{d(!1),u(!1),r.info("Streaming stopped")};return c.onCleanup(()=>{D()}),C(),{components:s,isLoading:v,isStreaming:$,error:N,progress:h,metadata:R,startStreaming:C,stopStreaming:D}}exports.createLogger=L;exports.useStreamingUI=W;
3
+ //# sourceMappingURL=useStreamingUI-CXmvpRhz.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStreamingUI-CXmvpRhz.cjs","sources":["../src/utils/logger.ts","../src/hooks/useStreamingUI.ts"],"sourcesContent":["/**\n * Simple internal logger utility\n *\n * Provides basic logging functionality for the package.\n * Consumers can disable logging by setting NODE_ENV=production\n * or by implementing their own logging solution.\n */\n\nconst isDev = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'\n\nexport interface Logger {\n info(message: string, context?: Record<string, unknown>): void\n warn(message: string, context?: Record<string, unknown>): void\n error(message: string, context?: Record<string, unknown>): void\n debug(message: string, context?: Record<string, unknown>): void\n}\n\nfunction formatLogMessage(\n feature: string,\n message: string,\n context?: Record<string, unknown>\n): string {\n const contextStr = context ? ` ${JSON.stringify(context)}` : ''\n return `[@seed-ship/mcp-ui-solid:${feature}] ${message}${contextStr}`\n}\n\n/**\n * Creates a feature-scoped logger\n *\n * @param feature - Feature name for log prefixing\n * @returns Logger instance\n *\n * @example\n * ```typescript\n * const logger = createLogger('my-component')\n * logger.info('Component mounted', { componentId: '123' })\n * ```\n */\nexport function createLogger(feature: string): Logger {\n return {\n info(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.info(formatLogMessage(feature, message, context))\n }\n },\n\n warn(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.warn(formatLogMessage(feature, message, context))\n }\n },\n\n error(message: string, context?: Record<string, unknown>) {\n // Always log errors, even in production\n console.error(formatLogMessage(feature, message, context))\n },\n\n debug(message: string, context?: Record<string, unknown>) {\n if (isDev) {\n console.debug(formatLogMessage(feature, message, context))\n }\n },\n }\n}\n\n/**\n * No-op logger for testing or when logging is disabled\n */\nexport const noopLogger: Logger = {\n info: () => {},\n warn: () => {},\n error: () => {},\n debug: () => {},\n}\n","/**\n * useStreamingUI Hook - Phase 2\n *\n * Client-side hook for consuming the streaming generative UI endpoint.\n * Handles SSE connection, component buffering, reordering, and error handling.\n *\n * Features:\n * - SSE connection with automatic reconnection\n * - Component buffering and reordering by sequenceId\n * - Progress tracking and loading states\n * - Error handling with recovery attempts\n * - Cleanup on unmount\n *\n * Usage:\n * ```tsx\n * const { components, isLoading, error, progress } = useStreamingUI({\n * query: 'Show me revenue trends',\n * spaceIds: ['uuid1', 'uuid2'],\n * onComplete: (metadata) => console.log('Done!', metadata),\n * })\n * ```\n */\n\nimport { createSignal, onCleanup } from 'solid-js'\nimport type { UIComponent } from '../types'\nimport { createLogger } from '../utils/logger'\n\nconst logger = createLogger('useStreamingUI')\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface UseStreamingUIOptions {\n query: string\n spaceIds?: string[]\n sessionId?: string\n options?: {\n useCache?: boolean\n useLLM?: boolean\n maxComponents?: number\n preferredComponents?: Array<'chart' | 'table' | 'metric' | 'text'>\n }\n onComplete?: (metadata: CompleteMetadata) => void\n onError?: (error: StreamError) => void\n onComponentReceived?: (component: UIComponent) => void\n}\n\nexport interface StreamingUIState {\n components: UIComponent[]\n isLoading: boolean\n isStreaming: boolean\n error: StreamError | null\n progress: StreamProgress\n metadata: CompleteMetadata | null\n}\n\nexport interface StreamProgress {\n receivedCount: number\n totalCount: number | null\n message: string\n timestamp: string\n}\n\nexport interface CompleteMetadata {\n layoutId: string\n componentsCount: number\n executionTimeMs: number\n firstTokenMs: number\n provider: 'groq' | 'mock'\n model: string\n tokensUsed?: number\n costUSD?: number\n cached: boolean\n}\n\nexport interface StreamError {\n error: string\n message: string\n componentId?: string\n recoverable: boolean\n}\n\ninterface ComponentBuffer {\n [sequenceId: number]: {\n component: UIComponent\n position: { colStart: number; colSpan: number; rowStart?: number; rowSpan?: number }\n }\n}\n\n// ============================================================================\n// SSE Event Types (must match server)\n// ============================================================================\n\ntype SSEEventType = 'status' | 'component-start' | 'component' | 'complete' | 'error'\n\ninterface StatusEvent {\n message: string\n timestamp: string\n totalComponents?: number\n}\n\ninterface ComponentStartEvent {\n componentId: string\n type: 'chart' | 'table' | 'metric' | 'text'\n sequenceId: number\n position: { colStart: number; colSpan: number }\n}\n\ninterface ComponentEvent {\n componentId: string\n sequenceId: number\n component: UIComponent\n position: { colStart: number; colSpan: number; rowStart?: number; rowSpan?: number }\n}\n\n// ============================================================================\n// Hook Implementation\n// ============================================================================\n\nexport function useStreamingUI(options: UseStreamingUIOptions) {\n // State\n const [components, setComponents] = createSignal<UIComponent[]>([])\n const [isLoading, setIsLoading] = createSignal(false)\n const [isStreaming, setIsStreaming] = createSignal(false)\n const [error, setError] = createSignal<StreamError | null>(null)\n const [progress, setProgress] = createSignal<StreamProgress>({\n receivedCount: 0,\n totalCount: null,\n message: 'Initializing...',\n timestamp: new Date().toISOString(),\n })\n const [metadata, setMetadata] = createSignal<CompleteMetadata | null>(null)\n\n // Component buffer for reordering\n let componentBuffer: ComponentBuffer = {}\n let nextSequenceId = 0\n let eventSource: EventSource | null = null\n let reconnectAttempts = 0\n const maxReconnectAttempts = 3\n\n /**\n * Flush components from buffer in sequence order\n */\n const flushBuffer = () => {\n const flushed: UIComponent[] = []\n\n while (componentBuffer[nextSequenceId]) {\n const { component } = componentBuffer[nextSequenceId]\n flushed.push(component)\n delete componentBuffer[nextSequenceId]\n nextSequenceId++\n }\n\n if (flushed.length > 0) {\n setComponents((prev) => [...prev, ...flushed])\n\n setProgress((prev) => ({\n ...prev,\n receivedCount: prev.receivedCount + flushed.length,\n }))\n\n logger.debug('Flushed components from buffer', {\n count: flushed.length,\n nextSequenceId,\n })\n }\n }\n\n /**\n * Handle SSE status event\n */\n const handleStatusEvent = (data: StatusEvent) => {\n logger.debug('Status event received', data as unknown as Record<string, unknown>)\n\n setProgress({\n receivedCount: progress().receivedCount,\n totalCount: data.totalComponents ?? progress().totalCount,\n message: data.message,\n timestamp: data.timestamp,\n })\n }\n\n /**\n * Handle SSE component-start event\n */\n const handleComponentStartEvent = (data: ComponentStartEvent) => {\n logger.debug('Component-start event received', data as unknown as Record<string, unknown>)\n\n setProgress((prev) => ({\n ...prev,\n message: `Loading ${data.type} component...`,\n timestamp: new Date().toISOString(),\n }))\n }\n\n /**\n * Handle SSE component event\n */\n const handleComponentEvent = (data: ComponentEvent) => {\n logger.debug('Component event received', {\n componentId: data.componentId,\n sequenceId: data.sequenceId,\n })\n\n // Add to buffer\n componentBuffer[data.sequenceId] = {\n component: data.component,\n position: data.position,\n }\n\n // Flush buffer in sequence\n flushBuffer()\n\n // Notify callback\n if (options.onComponentReceived) {\n options.onComponentReceived(data.component)\n }\n }\n\n /**\n * Handle SSE complete event\n */\n const handleCompleteEvent = (data: CompleteMetadata) => {\n logger.info('Stream completed', data as unknown as Record<string, unknown>)\n\n setIsStreaming(false)\n setIsLoading(false)\n setMetadata(data)\n\n // Flush any remaining buffered components\n flushBuffer()\n\n setProgress((prev) => ({\n ...prev,\n message: 'Dashboard loaded',\n timestamp: new Date().toISOString(),\n }))\n\n // Notify callback\n if (options.onComplete) {\n options.onComplete(data)\n }\n }\n\n /**\n * Handle SSE error event\n */\n const handleErrorEvent = (data: StreamError) => {\n logger.error('Stream error received', data as unknown as Record<string, unknown>)\n\n setError(data)\n setIsStreaming(false)\n setIsLoading(false)\n\n setProgress((prev) => ({\n ...prev,\n message: `Error: ${data.message}`,\n timestamp: new Date().toISOString(),\n }))\n\n // Notify callback\n if (options.onError) {\n options.onError(data)\n }\n\n // Try to reconnect if recoverable\n if (data.recoverable && reconnectAttempts < maxReconnectAttempts) {\n reconnectAttempts++\n logger.warn('Attempting to reconnect', { attempt: reconnectAttempts })\n setTimeout(() => startStreaming(), 1000 * reconnectAttempts)\n }\n }\n\n /**\n * Parse SSE event\n */\n const parseSSEEvent = (event: MessageEvent, eventType: SSEEventType) => {\n try {\n const data = JSON.parse(event.data)\n\n switch (eventType) {\n case 'status':\n handleStatusEvent(data as StatusEvent)\n break\n case 'component-start':\n handleComponentStartEvent(data as ComponentStartEvent)\n break\n case 'component':\n handleComponentEvent(data as ComponentEvent)\n break\n case 'complete':\n handleCompleteEvent(data as CompleteMetadata)\n break\n case 'error':\n handleErrorEvent(data as StreamError)\n break\n default:\n logger.warn('Unknown SSE event type', { eventType })\n }\n } catch (error) {\n logger.error('Failed to parse SSE event', {\n error: error instanceof Error ? error.message : String(error),\n eventType,\n })\n }\n }\n\n /**\n * Start SSE streaming\n */\n const startStreaming = () => {\n // Reset state\n setComponents([])\n setError(null)\n setIsLoading(true)\n setIsStreaming(true)\n componentBuffer = {}\n nextSequenceId = 0\n\n setProgress({\n receivedCount: 0,\n totalCount: null,\n message: 'Connecting to server...',\n timestamp: new Date().toISOString(),\n })\n\n logger.info('Starting SSE stream', {\n query: options.query,\n spaceIds: options.spaceIds,\n })\n\n // Build request body\n const requestBody = {\n query: options.query,\n spaceIds: options.spaceIds,\n sessionId: options.sessionId,\n options: options.options,\n }\n\n // Create EventSource (SSE connection)\n // Note: EventSource doesn't support POST, so we need to use fetch + ReadableStream\n fetch('/api/mcp/generative-ui-stream', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n })\n .then(async (response) => {\n if (!response.ok) {\n const errorData = await response.json()\n throw new Error(errorData.message || 'Stream request failed')\n }\n\n if (!response.body) {\n throw new Error('Response body is null')\n }\n\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n\n let buffer = ''\n\n // Read stream\n const readChunk = async (): Promise<void> => {\n const { done, value } = await reader.read()\n\n if (done) {\n logger.info('Stream ended')\n return\n }\n\n buffer += decoder.decode(value, { stream: true })\n\n // Process SSE messages\n const lines = buffer.split('\\n')\n buffer = lines.pop() || '' // Keep incomplete line in buffer\n\n let currentEvent: SSEEventType | null = null\n\n for (const line of lines) {\n if (line.startsWith('event: ')) {\n currentEvent = line.slice(7) as SSEEventType\n } else if (line.startsWith('data: ') && currentEvent) {\n const data = line.slice(6)\n parseSSEEvent({ data } as MessageEvent, currentEvent)\n currentEvent = null\n }\n }\n\n // Continue reading\n return readChunk()\n }\n\n await readChunk()\n })\n .catch((err) => {\n logger.error('Stream fetch failed', {\n error: err instanceof Error ? err.message : String(err),\n })\n\n handleErrorEvent({\n error: 'Stream connection failed',\n message: err instanceof Error ? err.message : 'Unknown error',\n recoverable: true,\n })\n })\n }\n\n /**\n * Stop streaming\n */\n const stopStreaming = () => {\n if (eventSource) {\n eventSource.close()\n eventSource = null\n }\n\n setIsStreaming(false)\n setIsLoading(false)\n\n logger.info('Streaming stopped')\n }\n\n /**\n * Cleanup on unmount\n */\n onCleanup(() => {\n stopStreaming()\n })\n\n // Auto-start streaming\n startStreaming()\n\n // Return state accessors and controls\n return {\n components,\n isLoading,\n isStreaming,\n error,\n progress,\n metadata,\n startStreaming,\n stopStreaming,\n }\n}\n"],"names":["isDev","formatLogMessage","feature","message","context","contextStr","createLogger","logger","useStreamingUI","options","components","setComponents","createSignal","isLoading","setIsLoading","isStreaming","setIsStreaming","error","setError","progress","setProgress","metadata","setMetadata","componentBuffer","nextSequenceId","reconnectAttempts","maxReconnectAttempts","flushBuffer","flushed","component","prev","handleStatusEvent","data","handleComponentStartEvent","handleComponentEvent","handleCompleteEvent","handleErrorEvent","startStreaming","parseSSEEvent","event","eventType","requestBody","response","errorData","reader","decoder","buffer","readChunk","done","value","lines","currentEvent","line","err","stopStreaming","onCleanup"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@seed-ship/mcp-ui-solid",
3
+ "version": "1.0.2",
4
+ "description": "SolidJS components for rendering MCP-generated UI resources",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./components": {
16
+ "import": "./dist/components/index.js",
17
+ "require": "./dist/components/index.cjs",
18
+ "types": "./dist/components/index.d.ts"
19
+ },
20
+ "./hooks": {
21
+ "import": "./dist/hooks/index.js",
22
+ "require": "./dist/hooks/index.cjs",
23
+ "types": "./dist/hooks/index.d.ts"
24
+ },
25
+ "./types": {
26
+ "import": "./dist/types/index.js",
27
+ "require": "./dist/types/index.cjs",
28
+ "types": "./dist/types/index.d.ts"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "peerDependencies": {
37
+ "solid-js": "^1.8.0"
38
+ },
39
+ "dependencies": {
40
+ "zod": "^3.22.4"
41
+ },
42
+ "devDependencies": {
43
+ "@solidjs/testing-library": "^0.8.5",
44
+ "@types/node": "^20.10.0",
45
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
46
+ "@typescript-eslint/parser": "^6.15.0",
47
+ "@vitest/ui": "^4.0.9",
48
+ "eslint": "^8.56.0",
49
+ "jsdom": "^27.2.0",
50
+ "typescript": "^5.3.3",
51
+ "vite": "^5.0.10",
52
+ "vite-plugin-solid": "^2.8.2",
53
+ "vitest": "^1.1.0"
54
+ },
55
+ "keywords": [
56
+ "mcp",
57
+ "ui",
58
+ "solidjs",
59
+ "components",
60
+ "streaming",
61
+ "dashboard",
62
+ "generative-ui"
63
+ ],
64
+ "author": "Gabriel Brument",
65
+ "license": "MIT",
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "https://github.com/theseedship/mcp-ui.git",
69
+ "directory": "mcp-ui-solid"
70
+ },
71
+ "homepage": "https://github.com/theseedship/mcp-ui#readme",
72
+ "bugs": {
73
+ "url": "https://github.com/theseedship/mcp-ui/issues"
74
+ },
75
+ "scripts": {
76
+ "build": "vite build",
77
+ "build:types": "tsc --emitDeclarationOnly --outDir dist --skipLibCheck",
78
+ "dev": "vite build --watch",
79
+ "test": "vitest run",
80
+ "test:watch": "vitest",
81
+ "test:browser": "vitest --mode browser",
82
+ "lint": "eslint src",
83
+ "typecheck": "tsc --noEmit",
84
+ "clean": "rm -rf dist"
85
+ }
86
+ }