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