@yourgpt/copilot-sdk 2.1.7 → 2.1.9-alpha.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 (33) hide show
  1. package/dist/{chunk-ISOMZAYN.js → chunk-RBULQ6EJ.js} +207 -62
  2. package/dist/chunk-RBULQ6EJ.js.map +1 -0
  3. package/dist/chunk-RPR5GMWF.js +52 -0
  4. package/dist/chunk-RPR5GMWF.js.map +1 -0
  5. package/dist/{chunk-IDAQU3FP.cjs → chunk-TD7NF6OE.cjs} +207 -62
  6. package/dist/chunk-TD7NF6OE.cjs.map +1 -0
  7. package/dist/{chunk-JFVTY757.cjs → chunk-WYFJZNFT.cjs} +16 -16
  8. package/dist/{chunk-JFVTY757.cjs.map → chunk-WYFJZNFT.cjs.map} +1 -1
  9. package/dist/{chunk-H3LX6FTP.js → chunk-XVKKLLKW.js} +3 -3
  10. package/dist/{chunk-H3LX6FTP.js.map → chunk-XVKKLLKW.js.map} +1 -1
  11. package/dist/chunk-YBLAHX2Z.cjs +55 -0
  12. package/dist/chunk-YBLAHX2Z.cjs.map +1 -0
  13. package/dist/experimental/index.cjs +416 -536
  14. package/dist/experimental/index.cjs.map +1 -1
  15. package/dist/experimental/index.d.cts +189 -853
  16. package/dist/experimental/index.d.ts +189 -853
  17. package/dist/experimental/index.js +415 -530
  18. package/dist/experimental/index.js.map +1 -1
  19. package/dist/react/index.cjs +62 -62
  20. package/dist/react/index.d.cts +18 -0
  21. package/dist/react/index.d.ts +18 -0
  22. package/dist/react/index.js +2 -2
  23. package/dist/ui/index.cjs +521 -263
  24. package/dist/ui/index.cjs.map +1 -1
  25. package/dist/ui/index.js +352 -94
  26. package/dist/ui/index.js.map +1 -1
  27. package/package.json +2 -1
  28. package/dist/chunk-5EGBIQYS.cjs +0 -292
  29. package/dist/chunk-5EGBIQYS.cjs.map +0 -1
  30. package/dist/chunk-IDAQU3FP.cjs.map +0 -1
  31. package/dist/chunk-ISOMZAYN.js.map +0 -1
  32. package/dist/chunk-TXQ37MAO.js +0 -287
  33. package/dist/chunk-TXQ37MAO.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { cn, DotsLoader } from '../chunk-TXQ37MAO.js';
2
- import { useTool } from '../chunk-ISOMZAYN.js';
1
+ import { Markdown, cn } from '../chunk-RPR5GMWF.js';
2
+ import { useCopilot } from '../chunk-RBULQ6EJ.js';
3
3
  import '../chunk-J5D3AZF6.js';
4
4
  import '../chunk-EWVQWTNV.js';
5
5
  import '../chunk-VNLLW3ZI.js';
@@ -16,136 +16,442 @@ import '../chunk-MNDGIW47.js';
16
16
  import '../chunk-VD74IPKB.js';
17
17
  import '../chunk-DGUM43GV.js';
18
18
  import * as React from 'react';
19
- import { jsxs, jsx } from 'react/jsx-runtime';
19
+ import { jsx, jsxs } from 'react/jsx-runtime';
20
20
  import { z } from 'zod';
21
21
 
22
+ var _frameIdCounter = 0;
23
+ function GenUIFrame({
24
+ html,
25
+ streaming = false,
26
+ className,
27
+ maxWidth,
28
+ themeVars,
29
+ onSendMessage,
30
+ onAction
31
+ }) {
32
+ const iframeRef = React.useRef(null);
33
+ const readyRef = React.useRef(false);
34
+ const themeVarsRef = React.useRef(themeVars);
35
+ const [height, setHeight] = React.useState(0);
36
+ const frameId = React.useRef(`genui_${++_frameIdCounter}`);
37
+ React.useEffect(() => {
38
+ themeVarsRef.current = themeVars;
39
+ });
40
+ const displayHtml = React.useMemo(() => {
41
+ if (!streaming) return html;
42
+ const lines = html.split("\n");
43
+ if (lines.length > 1) lines.pop();
44
+ return lines.join("\n").replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
45
+ }, [html, streaming]);
46
+ const shell = React.useMemo(() => {
47
+ const id = frameId.current;
48
+ return `<!DOCTYPE html>
49
+ <html><head>
50
+ <meta charset="utf-8"/>
51
+ <script src="https://cdn.tailwindcss.com"></script>
52
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
53
+ <style>
54
+ html,body{margin:0;padding:0;font-family:ui-sans-serif,system-ui,sans-serif;overflow:hidden;background:var(--background,transparent)!important}
55
+ *{box-sizing:border-box}
56
+ #root{padding:0}
57
+ canvas{background:transparent!important}
58
+ </style>
59
+ <script>
60
+ var FRAME_ID='${id}';
61
+ var _callId=0,_pending={};
62
+
63
+ // \u2500\u2500 Auto-height reporting \u2500\u2500
64
+ function reportHeight(){
65
+ var h=document.getElementById('root').scrollHeight;
66
+ if(h>0) window.parent.postMessage({action:'resize',id:FRAME_ID,height:h},'*');
67
+ }
68
+
69
+ // \u2500\u2500 Copilot bridge API (available to AI-generated HTML) \u2500\u2500
70
+ window.copilot={
71
+ sendMessage:function(msg){
72
+ window.parent.postMessage({action:'copilot:sendMessage',id:FRAME_ID,message:msg},'*');
73
+ },
74
+ action:function(name,data){
75
+ var cid=++_callId;
76
+ window.parent.postMessage({action:'copilot:action',id:FRAME_ID,name:name,data:data,callId:cid},'*');
77
+ return new Promise(function(resolve){_pending[cid]=resolve});
78
+ }
79
+ };
80
+
81
+ // \u2500\u2500 Message handler \u2500\u2500
82
+ window.addEventListener('message',function(e){
83
+ if(!e.data||e.data.id!==FRAME_ID)return;
84
+ if(e.data.action==='theme'){
85
+ var css=':root{';
86
+ var vars=e.data.vars||{};
87
+ for(var k in vars){if(vars.hasOwnProperty(k))css+=k+':'+vars[k]+';'}
88
+ css+='}';
89
+ var el=document.getElementById('theme-vars');
90
+ if(!el){el=document.createElement('style');el.id='theme-vars';document.head.appendChild(el);}
91
+ el.textContent=css;
92
+ // Resolve CSS vars via a temporary element so oklch/hsl/etc are computed by the browser
93
+ var probe=document.createElement('div');
94
+ probe.style.position='absolute';probe.style.visibility='hidden';
95
+ document.body.appendChild(probe);
96
+ // Normalize any CSS color (oklch, hsl, etc.) to rgb() via canvas so Chart.js can always use it
97
+ var normCanvas=document.createElement('canvas');
98
+ normCanvas.width=1;normCanvas.height=1;
99
+ var normCtx=normCanvas.getContext('2d');
100
+ function resolveColor(varName){
101
+ probe.style.color='var('+varName+')';
102
+ var computed=getComputedStyle(probe).color||'';
103
+ if(!computed)return'';
104
+ // Pass through canvas to guarantee rgb() output regardless of input color space
105
+ normCtx.fillStyle=computed;
106
+ var hex=normCtx.fillStyle;
107
+ if(hex&&hex.startsWith('#')&&hex.length===7){
108
+ var r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16);
109
+ return'rgb('+r+','+g+','+b+')';
110
+ }
111
+ return hex||computed;
112
+ }
113
+ var chartColors=[
114
+ resolveColor('--chart-1')||'rgb(224,85,34)',
115
+ resolveColor('--chart-2')||'rgb(34,170,197)',
116
+ resolveColor('--chart-3')||'rgb(80,120,180)',
117
+ resolveColor('--chart-4')||'rgb(200,170,34)',
118
+ resolveColor('--chart-5')||'rgb(170,80,200)',
119
+ ];
120
+ var fg=resolveColor('--foreground')||'#888';
121
+ var border=resolveColor('--border')||'rgba(128,128,128,0.2)';
122
+ document.body.removeChild(probe);
123
+ if(window.Chart){
124
+ Chart.defaults.color=fg;
125
+ Chart.defaults.borderColor=border;
126
+ if(Chart.defaults.plugins&&Chart.defaults.plugins.legend)
127
+ Chart.defaults.plugins.legend.labels.color=fg;
128
+ // Set chart color palette so AI-generated charts using var(--chart-N) get real colors
129
+ Chart.defaults.datasets=Chart.defaults.datasets||{};
130
+ var ds=Chart.defaults.datasets;
131
+ // line/radar: semi-transparent fill using first chart color
132
+ ['line','radar'].forEach(function(t){
133
+ ds[t]=ds[t]||{};
134
+ ds[t].backgroundColor=chartColors[0].replace(')',', 0.5)').replace('rgb(','rgba(');
135
+ });
136
+ // pie/doughnut: solid resolved palette so canvas segments get real colors
137
+ ['pie','doughnut'].forEach(function(t){
138
+ ds[t]=ds[t]||{};
139
+ ds[t].backgroundColor=chartColors;
140
+ ds[t].borderColor=chartColors;
141
+ });
142
+ }
143
+ // Expose resolved colors so AI scripts can use window._chartColors[n] directly
144
+ window._chartColors=chartColors;
145
+ // Semi-transparent versions: rgb(r,g,b) \u2192 rgba(r,g,b,0.5) \u2014 for bar/line backgrounds only
146
+ window._chartColorsA=chartColors.map(function(c){return c.replace('rgb(','rgba(').replace(')',',0.5)');});
147
+ // Store ALL resolved vars for full script replacement in hydrate
148
+ window._themeVars=vars;
149
+ }
150
+ if(e.data.action==='hydrate'){
151
+ var rawHtml=e.data.html;
152
+ // Replace ALL var(--*) with resolved rgb values so Chart.js canvas can use them
153
+ rawHtml=rawHtml.replace(/var(--[w-]+)/g,function(ref){
154
+ var name=ref.slice(4,-1);
155
+ return (window._themeVars&&window._themeVars[name])||ref;
156
+ });
157
+ document.getElementById('root').innerHTML=rawHtml;
158
+ if(!e.data.streaming){
159
+ document.querySelectorAll('#root script').forEach(function(s){
160
+ var n=document.createElement('script');n.text=s.text;s.parentNode.replaceChild(n,s);
161
+ });
162
+ }
163
+ // Poll at 30ms, 150ms, 400ms, 800ms to catch async renders (e.g. Chart.js)
164
+ [30,150,400,800].forEach(function(ms){setTimeout(reportHeight,ms)});
165
+ }
166
+ if(e.data.action==='copilot:actionResult'&&_pending[e.data.callId]){
167
+ _pending[e.data.callId](e.data.result);
168
+ delete _pending[e.data.callId];
169
+ }
170
+ });
171
+
172
+ new ResizeObserver(function(){reportHeight()}).observe(document.getElementById('root'));
173
+ </script>
174
+ </head><body><div id="root"></div></body></html>`;
175
+ }, []);
176
+ React.useEffect(() => {
177
+ const id = frameId.current;
178
+ const handleMessage = (e) => {
179
+ if (!e.data || e.data.id !== id) return;
180
+ if (e.data.action === "resize" && typeof e.data.height === "number") {
181
+ setHeight(e.data.height);
182
+ }
183
+ if (e.data.action === "copilot:sendMessage" && onSendMessage) {
184
+ onSendMessage(e.data.message);
185
+ }
186
+ if (e.data.action === "copilot:action" && onAction) {
187
+ const result = onAction(e.data.name, e.data.data);
188
+ Promise.resolve(result).then((res) => {
189
+ iframeRef.current?.contentWindow?.postMessage(
190
+ {
191
+ action: "copilot:actionResult",
192
+ id,
193
+ callId: e.data.callId,
194
+ result: res
195
+ },
196
+ "*"
197
+ );
198
+ });
199
+ }
200
+ };
201
+ window.addEventListener("message", handleMessage);
202
+ return () => window.removeEventListener("message", handleMessage);
203
+ }, [onSendMessage, onAction]);
204
+ const sendTheme = React.useCallback(
205
+ (vars) => {
206
+ if (!vars || !iframeRef.current?.contentWindow) return;
207
+ iframeRef.current.contentWindow.postMessage(
208
+ { action: "theme", id: frameId.current, vars },
209
+ "*"
210
+ );
211
+ },
212
+ []
213
+ );
214
+ React.useEffect(() => {
215
+ const iframe = iframeRef.current;
216
+ if (!iframe) return;
217
+ const id = frameId.current;
218
+ const onLoad = () => {
219
+ readyRef.current = true;
220
+ const vars = themeVarsRef.current;
221
+ if (vars && Object.keys(vars).length > 0) {
222
+ iframe.contentWindow?.postMessage({ action: "theme", id, vars }, "*");
223
+ }
224
+ iframe.contentWindow?.postMessage(
225
+ { action: "hydrate", id, html: displayHtml, streaming },
226
+ "*"
227
+ );
228
+ };
229
+ iframe.addEventListener("load", onLoad);
230
+ return () => iframe.removeEventListener("load", onLoad);
231
+ }, []);
232
+ React.useEffect(() => {
233
+ if (!displayHtml || !readyRef.current) return;
234
+ iframeRef.current?.contentWindow?.postMessage(
235
+ { action: "hydrate", id: frameId.current, html: displayHtml, streaming },
236
+ "*"
237
+ );
238
+ }, [displayHtml, streaming]);
239
+ React.useEffect(() => {
240
+ if (!readyRef.current) return;
241
+ sendTheme(themeVars);
242
+ }, [themeVars, sendTheme]);
243
+ return /* @__PURE__ */ jsx(
244
+ "iframe",
245
+ {
246
+ ref: iframeRef,
247
+ srcDoc: shell,
248
+ sandbox: "allow-scripts allow-same-origin",
249
+ style: {
250
+ height: height > 0 ? `${height}px` : "40px",
251
+ width: "100%",
252
+ maxWidth: maxWidth ?? "100%",
253
+ transition: "height 0.15s ease",
254
+ border: "none",
255
+ display: "block"
256
+ },
257
+ className: className ?? "bg-transparent overflow-hidden",
258
+ title: "Rendered HTML"
259
+ }
260
+ );
261
+ }
262
+ function useGenerativeUI(config = {}) {
263
+ const { messages, isLoading, sendMessage } = useCopilot();
264
+ const configRef = React.useRef(config);
265
+ configRef.current = config;
266
+ const genuiMessages = React.useMemo(() => {
267
+ return messages.map((msg) => {
268
+ if (msg.role !== "assistant" || !msg.content)
269
+ return msg;
270
+ const content = msg.content;
271
+ const match = content.match(/<GENUI>([\s\S]*?)(<\/GENUI>|$)/);
272
+ if (!match) return msg;
273
+ const htmlContent = match[1];
274
+ const isComplete = content.includes("</GENUI>");
275
+ const textBefore = content.slice(0, match.index).trim();
276
+ const textAfter = isComplete ? content.slice(match.index + match[0].length).trim() : "";
277
+ return {
278
+ ...msg,
279
+ _genui: {
280
+ html: htmlContent,
281
+ streaming: !isComplete,
282
+ textBefore,
283
+ textAfter
284
+ }
285
+ };
286
+ });
287
+ }, [messages, isLoading]);
288
+ const handleAction = React.useCallback((name, data) => {
289
+ const handler = configRef.current.actions?.[name];
290
+ if (handler) return handler(data);
291
+ return void 0;
292
+ }, []);
293
+ const handleSendMessage = React.useCallback(
294
+ (msg) => {
295
+ sendMessage(msg);
296
+ },
297
+ [sendMessage]
298
+ );
299
+ const wrapMessage = React.useCallback(
300
+ (content, message) => {
301
+ const processed = genuiMessages.find((m) => m.id === message.id);
302
+ const genui = processed?._genui;
303
+ if (!genui) return content;
304
+ const { html, streaming, textBefore, textAfter } = genui;
305
+ return /* @__PURE__ */ jsxs("div", { className: "csdk-genui-wrap [&_.csdk-message-content>*]:!hidden", children: [
306
+ content,
307
+ /* @__PURE__ */ jsx("style", { children: `.csdk-genui-wrap .csdk-message-content { padding: 0 !important; }
308
+ .csdk-genui-wrap .csdk-message-content > * { display: none !important; }
309
+ .csdk-genui-injected { margin-top: -4px; padding-left: 36px; }` }),
310
+ /* @__PURE__ */ jsxs("div", { className: "csdk-genui-injected space-y-2", children: [
311
+ textBefore && /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: textBefore }) }),
312
+ /* @__PURE__ */ jsx(
313
+ GenUIFrame,
314
+ {
315
+ html,
316
+ streaming,
317
+ maxWidth: configRef.current.maxWidth,
318
+ onSendMessage: handleSendMessage,
319
+ onAction: handleAction
320
+ }
321
+ ),
322
+ textAfter && /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: textAfter }) }),
323
+ streaming && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground animate-pulse", children: "Rendering\u2026" })
324
+ ] })
325
+ ] });
326
+ },
327
+ [genuiMessages, handleSendMessage, handleAction]
328
+ );
329
+ return { wrapMessage };
330
+ }
331
+
332
+ // src/experimental/generativeUIPrompt.ts
333
+ function generativeUISystemPrompt(options) {
334
+ const actionsSection = options?.actions ? `
335
+ INTERACTIVE ACTIONS \u2014 available inside onclick handlers:
336
+ - copilot.sendMessage(text): Send a message in the chat as the user
337
+ ${Object.entries(options.actions).map(([name, desc]) => `- copilot.action('${name}', data): ${desc}`).join("\n")}
338
+
339
+ Example:
340
+ <button onclick="copilot.action('addToCart', {itemId: '123'})">Add to Cart</button>
341
+ <button onclick="copilot.sendMessage('Tell me more about this')">Ask AI</button>
342
+ ` : "";
343
+ const customGuidelines = options?.designGuidelines ? `
344
+ ${options.designGuidelines}` : "";
345
+ return `You are an intelligent SaaS dashboard copilot. You help users understand their data, answer questions, and provide insights through conversation.
346
+
347
+ RESPONSE STYLE:
348
+ - Be conversational and helpful \u2014 most responses should be plain text with markdown formatting
349
+ - Use bullet points, bold text, and clear structure for readability
350
+ - Keep responses concise and actionable
351
+
352
+ VISUAL COMPONENTS \u2014 use only when it genuinely helps:
353
+ - Dashboards, KPI summaries, metric overviews \u2192 visual
354
+ - Data tables, comparisons, rankings \u2192 visual
355
+ - Charts, trends, distributions \u2192 visual
356
+ - Simple answers, explanations, suggestions \u2192 plain text (no visual)
357
+ - Greetings, follow-up questions \u2192 plain text
358
+
359
+ When rendering visual components, wrap HTML in <GENUI> tags:
360
+
361
+ <GENUI>
362
+ your html here using Tailwind CSS classes
363
+ </GENUI>
364
+
365
+ You can mix text and visual \u2014 add context before or after the <GENUI> block.
366
+
367
+ VISUAL RULES:
368
+ 1. Wrap HTML in <GENUI> and </GENUI> tags \u2014 no markdown code blocks
369
+ 2. Use ONLY Tailwind CSS utility classes \u2014 no custom CSS or <style> tags
370
+ 3. For charts: ALWAYS wrap canvas in a fixed-height div: <div style="height:200px"><canvas id="c"></canvas></div><script>new Chart(document.getElementById('c'), {options:{responsive:true,maintainAspectRatio:false}})</script>
371
+ 4. Left-align content \u2014 never use mx-auto or centered max-w containers
372
+ 5. Keep it compact \u2014 you're inside a chat widget (~500-600px wide)
373
+
374
+ DESIGN SYSTEM (shadcn/ui):
375
+ - Cards: rounded-xl border border-gray-200 bg-white shadow-sm p-4
376
+ - Headings: text-gray-900 font-semibold text-sm
377
+ - Labels: text-xs text-gray-500 font-medium uppercase tracking-wider
378
+ - Body: text-sm text-gray-700
379
+ - Stats: text-2xl font-bold text-gray-900, label text-xs text-gray-500
380
+ - Badges: px-2 py-0.5 rounded-full text-xs font-medium (bg-emerald-50 text-emerald-700 for positive, bg-red-50 text-red-500 for negative)
381
+ - Tables: text-sm w-full, th border-b py-2 text-xs text-gray-500 uppercase, td py-2
382
+ - Grid: grid grid-cols-2 gap-3
383
+ - Charts: max 200px height, colors #4f46e5 #10b981 #f59e0b #ef4444 #8b5cf6${customGuidelines}
384
+ ${actionsSection}`;
385
+ }
386
+
22
387
  // src/experimental/generativeUITool.ts
23
388
  var RENDER_UI_PARAMETERS = {
24
389
  type: "object",
25
390
  properties: {
26
- type: {
27
- type: "string",
28
- enum: ["html", "chart", "table", "stat", "card"],
29
- description: "UI component type to render"
30
- },
31
391
  html: {
32
392
  type: "string",
33
- description: "Raw HTML with Tailwind classes (type=html)"
34
- },
35
- height: { type: "string" },
36
- chartType: {
37
- type: "string",
38
- enum: ["bar", "line", "pie", "area", "scatter"],
39
- description: "Chart variant (type=chart)"
40
- },
41
- labels: {
42
- type: "array",
43
- items: { type: "string" },
44
- description: "X-axis category labels (type=chart)"
45
- },
46
- datasets: {
47
- type: "array",
48
- items: {
49
- type: "object",
50
- properties: {
51
- label: { type: "string" },
52
- data: { type: "array", items: { type: "number" } }
53
- },
54
- required: ["label", "data"]
55
- },
56
- description: "Data series (type=chart)"
57
- },
58
- xLabel: { type: "string" },
59
- yLabel: { type: "string" },
60
- columns: {
61
- type: "array",
62
- items: {
63
- type: "object",
64
- properties: {
65
- key: { type: "string" },
66
- label: { type: "string" },
67
- align: { type: "string" }
68
- },
69
- required: ["key", "label"]
70
- },
71
- description: "Column definitions \u2014 key matches row property names (type=table)"
72
- },
73
- rows: {
74
- type: "array",
75
- items: { type: "object" },
76
- description: "Array of row objects keyed by column.key (type=table)"
77
- },
78
- caption: { type: "string" },
79
- stats: {
80
- type: "array",
81
- items: {
82
- type: "object",
83
- properties: {
84
- label: { type: "string" },
85
- value: { type: "string" },
86
- change: { type: "string" },
87
- changeDirection: {
88
- type: "string",
89
- enum: ["positive", "negative", "neutral"]
90
- },
91
- description: { type: "string" }
92
- },
93
- required: ["label", "value"]
94
- },
95
- description: "KPI stats array (type=stat)"
393
+ description: "Raw HTML string with Tailwind CSS classes. Chart.js is available for charts via <canvas> + inline <script>."
96
394
  },
97
395
  title: {
98
396
  type: "string",
99
- description: "Title shown above the component"
397
+ description: "Optional title shown above the rendered component"
100
398
  },
101
- subtitle: { type: "string" },
102
- body: { type: "string" },
103
- fields: {
104
- type: "array",
105
- items: {
106
- type: "object",
107
- properties: {
108
- label: { type: "string" },
109
- value: { type: "string" },
110
- badge: { type: "boolean" }
111
- },
112
- required: ["label", "value"]
113
- },
114
- description: "Key-value fields (type=card)"
115
- },
116
- cta: {
117
- type: "object",
118
- properties: {
119
- label: { type: "string" },
120
- url: { type: "string" }
121
- },
122
- required: ["label", "url"],
123
- description: "Call-to-action link (type=card)"
399
+ height: {
400
+ type: "string",
401
+ description: "CSS height for the iframe, e.g. '400px'. Defaults to '520px'."
124
402
  }
125
403
  },
126
- required: ["type"]
404
+ required: ["html"]
127
405
  };
128
406
  function generativeUITool(config = {}) {
129
407
  return {
130
- description: config.description ?? `Render a rich visual UI component directly in the chat. Use this tool whenever the user's request is best answered with a visual instead of text.
408
+ description: config.description ?? `Render a rich visual UI component directly in the chat using HTML with Tailwind CSS and Chart.js.
409
+
410
+ Use this tool whenever the user's request is best answered with a visual instead of plain text.
131
411
 
132
- Choose the type based on what fits best:
133
- - "table": structured rows of data (comparisons, lists, records)
134
- - "stat": KPI metrics, numbers with labels, dashboards with change deltas
135
- - "card": entity summaries \u2014 profiles, products, results, structured key-value info
136
- - "chart": graphs and visualizations \u2014 bar, line, pie, area, scatter \u2014 pass raw data only, not markup
137
- - "html": anything that requires custom layout, rich formatting, or doesn't fit the above types \u2014 use Tailwind CSS classes freely
412
+ The HTML is rendered in an iframe with two libraries pre-loaded:
413
+ 1. Tailwind CSS (Play CDN) \u2014 use any utility class
414
+ 2. Chart.js \u2014 create charts with <canvas> + new Chart(...)
138
415
 
139
- Always prefer a structured type (table, stat, card) over html when the data fits.
140
- Only use html as a last resort for truly freeform content.`,
416
+ Design in a clean, modern style:
417
+ - Cards: bg-white rounded-xl border border-gray-200 shadow-sm p-6
418
+ - Headings: text-gray-900 font-semibold text-lg
419
+ - Muted: text-gray-500 text-sm
420
+ - Badges: bg-blue-50 text-blue-700 px-2.5 py-0.5 rounded-full text-xs font-medium
421
+ - Grid: grid grid-cols-3 gap-4
422
+ - Tables, stats, cards, charts \u2014 build them all with HTML + Tailwind + Chart.js.
423
+
424
+ Set the "height" field to fit the content \u2014 e.g. "600px" for dashboards, "320px" for a small card.`,
141
425
  parameters: RENDER_UI_PARAMETERS
142
426
  };
143
427
  }
428
+ var HtmlPayloadSchema = z.object({
429
+ type: z.literal("html"),
430
+ html: z.string().describe(
431
+ "Raw HTML string rendered in an isolated iframe with Tailwind CSS and Chart.js"
432
+ ),
433
+ title: z.string().optional().describe("Optional label shown above the component"),
434
+ height: z.string().optional().describe("CSS height value e.g. '300px'. Defaults to auto.")
435
+ }).passthrough();
436
+ var GenerativeUIPayloadSchema = HtmlPayloadSchema;
144
437
  function sanitizeHtml(html) {
145
438
  return html.replace(/<script\b[^>]*\bsrc=["'][^"']*["'][^>]*><\/script>/gi, "").replace(/\s*on\w+="[^"]*"/gi, "").replace(/\s*on\w+='[^']*'/gi, "");
146
439
  }
147
- function HtmlRenderer({ payload, className }) {
148
- const clean = sanitizeHtml(payload.html);
440
+ function prepareStreamingHtml(html) {
441
+ const lines = html.split("\n");
442
+ if (lines.length > 1) lines.pop();
443
+ let result = lines.join("\n");
444
+ result = result.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
445
+ return result;
446
+ }
447
+ function HtmlRenderer({
448
+ payload,
449
+ className,
450
+ streaming = false
451
+ }) {
452
+ const rawHtml = payload.html ?? "";
453
+ const clean = sanitizeHtml(rawHtml);
454
+ const displayHtml = streaming ? prepareStreamingHtml(clean) : clean;
149
455
  const srcdoc = `<!DOCTYPE html>
150
456
  <html>
151
457
  <head>
@@ -157,7 +463,7 @@ function HtmlRenderer({ payload, className }) {
157
463
  * { box-sizing: border-box; }
158
464
  </style>
159
465
  </head>
160
- <body>${clean}</body>
466
+ <body>${displayHtml}</body>
161
467
  </html>`;
162
468
  return /* @__PURE__ */ jsxs(
163
469
  "div",
@@ -184,428 +490,7 @@ function HtmlRenderer({ payload, className }) {
184
490
  }
185
491
  );
186
492
  }
187
- function TableRenderer({ payload, className }) {
188
- const { columns, rows, caption } = payload;
189
- return /* @__PURE__ */ jsx(
190
- "div",
191
- {
192
- className: cn(
193
- "csdk-genui-table w-full overflow-x-auto rounded-md border border-border",
194
- className
195
- ),
196
- children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-full text-sm", children: [
197
- caption && /* @__PURE__ */ jsx("caption", { className: "px-3 pt-2 pb-1 text-left text-xs text-muted-foreground", children: caption }),
198
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "border-b border-border bg-muted/40", children: columns.map((col) => /* @__PURE__ */ jsx(
199
- "th",
200
- {
201
- className: cn(
202
- "whitespace-nowrap px-3 py-2 font-medium text-foreground",
203
- col.align === "right" && "text-right",
204
- col.align === "center" && "text-center",
205
- (!col.align || col.align === "left") && "text-left"
206
- ),
207
- children: col.label
208
- },
209
- col.key
210
- )) }) }),
211
- /* @__PURE__ */ jsx("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
212
- "td",
213
- {
214
- colSpan: columns.length,
215
- className: "px-3 py-4 text-center text-sm text-muted-foreground",
216
- children: "No data"
217
- }
218
- ) }) : rows.map((row, i) => /* @__PURE__ */ jsx(
219
- "tr",
220
- {
221
- className: cn(
222
- "border-b border-border last:border-0",
223
- i % 2 === 1 && "bg-muted/20"
224
- ),
225
- children: columns.map((col) => /* @__PURE__ */ jsx(
226
- "td",
227
- {
228
- className: cn(
229
- "px-3 py-2 text-foreground/90",
230
- col.align === "right" && "text-right tabular-nums",
231
- col.align === "center" && "text-center",
232
- (!col.align || col.align === "left") && "text-left"
233
- ),
234
- children: row[col.key] === null || row[col.key] === void 0 ? "\u2014" : String(row[col.key])
235
- },
236
- col.key
237
- ))
238
- },
239
- i
240
- )) })
241
- ] })
242
- }
243
- );
244
- }
245
- function StatRenderer({ payload, className }) {
246
- const { stats, title } = payload;
247
- return /* @__PURE__ */ jsxs("div", { className: cn("csdk-genui-stat", className), children: [
248
- title && /* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-semibold text-foreground", children: title }),
249
- /* @__PURE__ */ jsx(
250
- "div",
251
- {
252
- className: cn(
253
- "grid gap-2",
254
- stats.length === 1 && "grid-cols-1",
255
- stats.length === 2 && "grid-cols-2",
256
- stats.length >= 3 && "grid-cols-2 sm:grid-cols-3"
257
- ),
258
- children: stats.map((stat, i) => /* @__PURE__ */ jsxs(
259
- "div",
260
- {
261
- className: "flex flex-col gap-0.5 rounded-lg border border-border bg-card p-3",
262
- children: [
263
- /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: stat.label }),
264
- /* @__PURE__ */ jsx("span", { className: "text-xl font-bold tabular-nums leading-tight text-foreground", children: stat.value }),
265
- stat.change && /* @__PURE__ */ jsx(
266
- "span",
267
- {
268
- className: cn(
269
- "text-xs font-medium",
270
- stat.changeDirection === "positive" && "text-green-600 dark:text-green-400",
271
- stat.changeDirection === "negative" && "text-red-500 dark:text-red-400",
272
- (!stat.changeDirection || stat.changeDirection === "neutral") && "text-muted-foreground"
273
- ),
274
- children: stat.change
275
- }
276
- ),
277
- stat.description && /* @__PURE__ */ jsx("span", { className: "text-xs leading-snug text-muted-foreground", children: stat.description })
278
- ]
279
- },
280
- i
281
- ))
282
- }
283
- )
284
- ] });
285
- }
286
- function CardRenderer({ payload, className }) {
287
- const { title, subtitle, fields, body, cta } = payload;
288
- return /* @__PURE__ */ jsxs(
289
- "div",
290
- {
291
- className: cn(
292
- "csdk-genui-card flex flex-col gap-3 rounded-lg border border-border bg-card p-4",
293
- className
294
- ),
295
- children: [
296
- /* @__PURE__ */ jsxs("div", { children: [
297
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold leading-snug text-foreground", children: title }),
298
- subtitle && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: subtitle })
299
- ] }),
300
- fields && fields.length > 0 && /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5", children: fields.map((field, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
301
- /* @__PURE__ */ jsx("dt", { className: "self-center whitespace-nowrap text-xs text-muted-foreground", children: field.label }),
302
- /* @__PURE__ */ jsx("dd", { className: "text-xs text-foreground", children: field.badge ? /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full border border-border bg-muted px-2 py-0.5 font-medium text-foreground", children: String(field.value) }) : String(field.value) })
303
- ] }, i)) }),
304
- body && /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: body }),
305
- cta && /* @__PURE__ */ jsxs(
306
- "a",
307
- {
308
- href: cta.url,
309
- target: "_blank",
310
- rel: "noopener noreferrer",
311
- className: "mt-1 inline-flex items-center gap-1 text-xs font-medium text-primary underline-offset-2 hover:underline",
312
- children: [
313
- cta.label,
314
- " \u2197"
315
- ]
316
- }
317
- )
318
- ]
319
- }
320
- );
321
- }
322
- var HtmlPayloadSchema = z.object({
323
- type: z.literal("html"),
324
- html: z.string().describe(
325
- "Raw HTML string rendered in an isolated Shadow DOM with Tailwind CSS"
326
- ),
327
- title: z.string().optional().describe("Optional label shown above the component"),
328
- height: z.string().optional().describe("CSS height value e.g. '300px'. Defaults to auto.")
329
- }).passthrough();
330
- var ChartPayloadSchema = z.object({
331
- type: z.literal("chart"),
332
- chartType: z.enum(["bar", "line", "pie", "area", "scatter"]).describe("Chart visualization type"),
333
- title: z.string().optional(),
334
- labels: z.array(z.string()).describe("X-axis labels or category names"),
335
- datasets: z.array(
336
- z.object({
337
- label: z.string(),
338
- data: z.array(z.number()),
339
- color: z.string().optional().describe("Hex or CSS color for this series")
340
- })
341
- ).describe("One or more data series"),
342
- xLabel: z.string().optional().describe("X-axis label"),
343
- yLabel: z.string().optional().describe("Y-axis label")
344
- }).passthrough();
345
- var TablePayloadSchema = z.object({
346
- type: z.literal("table"),
347
- title: z.string().optional(),
348
- columns: z.array(
349
- z.object({
350
- key: z.string(),
351
- label: z.string(),
352
- align: z.enum(["left", "right", "center"]).optional()
353
- }).passthrough()
354
- ).describe("Column definitions \u2014 order controls render order"),
355
- rows: z.array(z.record(z.string(), z.unknown())).describe("Row data keyed by column.key"),
356
- caption: z.string().optional()
357
- }).passthrough();
358
- var StatPayloadSchema = z.object({
359
- type: z.literal("stat"),
360
- title: z.string().optional(),
361
- stats: z.array(
362
- z.object({
363
- label: z.string(),
364
- value: z.union([z.string(), z.number()]),
365
- change: z.string().optional().describe("e.g. '+12%' or '-3.4%'"),
366
- changeDirection: z.enum(["positive", "negative", "neutral"]).optional(),
367
- description: z.string().optional().describe("Sub-text below value")
368
- }).passthrough()
369
- )
370
- }).passthrough();
371
- var CardPayloadSchema = z.object({
372
- type: z.literal("card"),
373
- title: z.string(),
374
- subtitle: z.string().optional(),
375
- body: z.string().optional(),
376
- fields: z.array(
377
- z.object({
378
- label: z.string(),
379
- value: z.union([z.string(), z.number(), z.boolean()]),
380
- badge: z.boolean().optional().describe("Render value as a badge pill")
381
- })
382
- ).optional(),
383
- cta: z.object({
384
- label: z.string().optional(),
385
- text: z.string().optional(),
386
- // LLMs sometimes use "text" instead of "label"
387
- url: z.string().describe("URL for the call-to-action link")
388
- }).transform((c) => ({ ...c, label: c.label ?? c.text ?? "" })).optional()
389
- }).passthrough();
390
- var GenerativeUIPayloadSchema = z.discriminatedUnion("type", [
391
- HtmlPayloadSchema,
392
- ChartPayloadSchema,
393
- TablePayloadSchema,
394
- StatPayloadSchema,
395
- CardPayloadSchema
396
- ]);
397
- var RENDER_UI_SCHEMA = {
398
- type: "object",
399
- properties: {
400
- type: {
401
- type: "string",
402
- enum: ["html", "chart", "table", "stat", "card"],
403
- description: "The UI component type to render"
404
- },
405
- // html
406
- html: {
407
- type: "string",
408
- description: "Raw HTML with Tailwind classes (type=html only)"
409
- },
410
- height: {
411
- type: "string",
412
- description: "Optional CSS height (type=html only)"
413
- },
414
- // chart
415
- chartType: {
416
- type: "string",
417
- enum: ["bar", "line", "pie", "area", "scatter"],
418
- description: "Chart variant (type=chart only)"
419
- },
420
- labels: {
421
- type: "array",
422
- items: { type: "string" },
423
- description: "X-axis labels (type=chart only)"
424
- },
425
- datasets: {
426
- type: "array",
427
- items: {
428
- type: "object",
429
- properties: {
430
- label: { type: "string" },
431
- data: { type: "array", items: { type: "number" } }
432
- },
433
- required: ["label", "data"]
434
- },
435
- description: "Data series array (type=chart only)"
436
- },
437
- xLabel: { type: "string" },
438
- yLabel: { type: "string" },
439
- // table
440
- columns: {
441
- type: "array",
442
- items: {
443
- type: "object",
444
- properties: {
445
- key: { type: "string" },
446
- label: { type: "string" },
447
- align: { type: "string", enum: ["left", "right", "center"] }
448
- },
449
- required: ["key", "label"]
450
- },
451
- description: "Column definitions \u2014 key matches row property names (type=table only)"
452
- },
453
- rows: {
454
- type: "array",
455
- items: { type: "object" },
456
- description: "Row objects keyed by column.key values (type=table only)"
457
- },
458
- caption: { type: "string" },
459
- // stat
460
- stats: {
461
- type: "array",
462
- items: {
463
- type: "object",
464
- properties: {
465
- label: { type: "string" },
466
- value: { type: "string" },
467
- change: { type: "string" },
468
- changeDirection: {
469
- type: "string",
470
- enum: ["positive", "negative", "neutral"]
471
- },
472
- description: { type: "string" }
473
- },
474
- required: ["label", "value"]
475
- },
476
- description: "KPI stats array (type=stat only)"
477
- },
478
- // card
479
- title: { type: "string", description: "Card or table title" },
480
- subtitle: { type: "string" },
481
- body: { type: "string" },
482
- fields: {
483
- type: "array",
484
- items: {
485
- type: "object",
486
- properties: {
487
- label: { type: "string" },
488
- value: { type: "string" },
489
- badge: { type: "boolean" }
490
- },
491
- required: ["label", "value"]
492
- },
493
- description: "Key-value fields (type=card only)"
494
- },
495
- cta: {
496
- type: "object",
497
- properties: {
498
- label: { type: "string" },
499
- url: { type: "string" }
500
- },
501
- required: ["label", "url"],
502
- description: "Call-to-action link (type=card only)"
503
- }
504
- },
505
- required: ["type"]
506
- };
507
- function useGenerativeUI(config = {}) {
508
- const toolName = config.name ?? "render_ui";
509
- const configRef = React.useRef(config);
510
- configRef.current = config;
511
- useTool({
512
- name: toolName,
513
- description: "Renders a rich UI component inline in the chat. Handled automatically by the SDK.",
514
- inputSchema: RENDER_UI_SCHEMA,
515
- hidden: false,
516
- aiResponseMode: "none",
517
- aiContext: (_, args) => {
518
- const type = args?.type ?? "ui";
519
- return `[Rendered ${type} component to user]`;
520
- },
521
- handler: async (params) => {
522
- const raw = params && typeof params === "object" && "data" in params ? params.data : params;
523
- const parsed = GenerativeUIPayloadSchema.safeParse(raw);
524
- if (!parsed.success) {
525
- return {
526
- success: false,
527
- error: `Invalid generative UI payload: ${parsed.error.message}`
528
- };
529
- }
530
- return {
531
- success: true,
532
- data: parsed.data,
533
- _aiContext: `[Rendered ${parsed.data.type} component to user]`,
534
- _aiResponseMode: "none"
535
- };
536
- },
537
- render: (props) => /* @__PURE__ */ jsx(GenerativeUIRenderer, { props, configRef })
538
- });
539
- }
540
- function GenerativeUIRenderer({
541
- props: renderProps,
542
- configRef
543
- }) {
544
- const { status, result, error } = renderProps;
545
- const config = configRef.current;
546
- if (status === "pending" || status === "executing") {
547
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-1.5 text-sm text-muted-foreground", children: [
548
- /* @__PURE__ */ jsx(DotsLoader, { size: "sm" }),
549
- /* @__PURE__ */ jsx("span", { children: "Preparing response\u2026" })
550
- ] });
551
- }
552
- if (status === "error") {
553
- return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error ?? "Failed to render UI component." });
554
- }
555
- if (status !== "completed") return null;
556
- const rawPayload = result?.data;
557
- const parsed = GenerativeUIPayloadSchema.safeParse(rawPayload);
558
- if (!parsed.success) {
559
- return /* @__PURE__ */ jsx("div", { className: "overflow-auto rounded-md border border-border bg-muted/30 px-3 py-2", children: /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap text-xs text-muted-foreground", children: JSON.stringify(rawPayload, null, 2) }) });
560
- }
561
- const payload = parsed.data;
562
- switch (payload.type) {
563
- case "html": {
564
- const Override = config.overrideRenderers?.html;
565
- return Override ? /* @__PURE__ */ jsx(Override, { payload }) : /* @__PURE__ */ jsx(HtmlRenderer, { payload });
566
- }
567
- case "chart": {
568
- const ChartComp = config.overrideRenderers?.chart ?? config.chartRenderer;
569
- return ChartComp ? /* @__PURE__ */ jsx(ChartComp, { payload }) : /* @__PURE__ */ jsx(ChartFallback, { payload });
570
- }
571
- case "table": {
572
- const Override = config.overrideRenderers?.table;
573
- return Override ? /* @__PURE__ */ jsx(Override, { payload }) : /* @__PURE__ */ jsx(TableRenderer, { payload });
574
- }
575
- case "stat": {
576
- const Override = config.overrideRenderers?.stat;
577
- return Override ? /* @__PURE__ */ jsx(Override, { payload }) : /* @__PURE__ */ jsx(StatRenderer, { payload });
578
- }
579
- case "card": {
580
- const Override = config.overrideRenderers?.card;
581
- return Override ? /* @__PURE__ */ jsx(Override, { payload }) : /* @__PURE__ */ jsx(CardRenderer, { payload });
582
- }
583
- default:
584
- return null;
585
- }
586
- }
587
- function ChartFallback({ payload }) {
588
- return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/20 p-3", children: [
589
- payload.title && /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-sm font-medium text-foreground", children: payload.title }),
590
- /* @__PURE__ */ jsxs("p", { className: "mb-2 text-xs text-muted-foreground", children: [
591
- "Chart type:",
592
- " ",
593
- /* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5 font-mono text-xs", children: payload.chartType }),
594
- ". Pass a ",
595
- /* @__PURE__ */ jsx("code", { className: "font-mono", children: "chartRenderer" }),
596
- " prop to",
597
- " ",
598
- /* @__PURE__ */ jsx("code", { className: "font-mono", children: "useGenerativeUI()" }),
599
- " to render this."
600
- ] }),
601
- /* @__PURE__ */ jsx("pre", { className: "max-h-40 overflow-auto whitespace-pre-wrap text-xs text-muted-foreground font-mono", children: JSON.stringify(
602
- { labels: payload.labels, datasets: payload.datasets },
603
- null,
604
- 2
605
- ) })
606
- ] });
607
- }
608
493
 
609
- export { CardPayloadSchema, CardRenderer, ChartPayloadSchema, GenerativeUIPayloadSchema, HtmlPayloadSchema, HtmlRenderer, StatPayloadSchema, StatRenderer, TablePayloadSchema, TableRenderer, generativeUITool, useGenerativeUI };
494
+ export { GenUIFrame, GenerativeUIPayloadSchema, HtmlPayloadSchema, HtmlRenderer, generativeUISystemPrompt, generativeUITool, useGenerativeUI };
610
495
  //# sourceMappingURL=index.js.map
611
496
  //# sourceMappingURL=index.js.map