@timbal-ai/timbal-react 0.5.4 → 0.6.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/README.md +128 -4
- package/dist/app.cjs +5311 -0
- package/dist/app.d.cts +29 -0
- package/dist/app.d.ts +29 -0
- package/dist/app.esm.js +81 -0
- package/dist/chart-artifact-C71dk4xI.d.ts +329 -0
- package/dist/chart-artifact-CPEpOmtV.d.cts +329 -0
- package/dist/chat-CWtQWDtJ.d.cts +650 -0
- package/dist/chat-CWtQWDtJ.d.ts +650 -0
- package/dist/chat.cjs +4162 -0
- package/dist/chat.d.cts +13 -0
- package/dist/chat.d.ts +13 -0
- package/dist/chat.esm.js +51 -0
- package/dist/chunk-4TCJQSIX.esm.js +565 -0
- package/dist/chunk-IYENDIRY.esm.js +119 -0
- package/dist/chunk-KC5QLVUG.esm.js +22 -0
- package/dist/chunk-M4V6Q6XO.esm.js +1082 -0
- package/dist/chunk-OFHLFNJH.esm.js +138 -0
- package/dist/chunk-OVHR7J3J.esm.js +1574 -0
- package/dist/chunk-WLTW56MC.esm.js +66 -0
- package/dist/chunk-YJQLLFKP.esm.js +3672 -0
- package/dist/index.cjs +1823 -359
- package/dist/index.d.cts +15 -931
- package/dist/index.d.ts +15 -931
- package/dist/index.esm.js +187 -5578
- package/dist/layout-B9VayJhZ.d.cts +75 -0
- package/dist/layout-CQWngNQ7.d.ts +75 -0
- package/dist/studio.cjs +5734 -0
- package/dist/studio.d.cts +15 -0
- package/dist/studio.d.ts +15 -0
- package/dist/studio.esm.js +27 -0
- package/dist/styles.css +52 -2
- package/dist/timbal-v2-button-F4-z7m33.d.cts +40 -0
- package/dist/timbal-v2-button-F4-z7m33.d.ts +40 -0
- package/dist/ui.cjs +720 -0
- package/dist/ui.d.cts +74 -0
- package/dist/ui.d.ts +74 -0
- package/dist/ui.esm.js +44 -0
- package/dist/welcome--80i_O0p.d.cts +190 -0
- package/dist/welcome-BOizSp5h.d.ts +190 -0
- package/package.json +35 -3
- package/scripts/dev-linked.mjs +66 -0
- package/vite/local-dev.d.ts +4 -0
- package/vite/local-dev.mjs +71 -0
|
@@ -0,0 +1,3672 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Avatar,
|
|
3
|
+
AvatarFallback,
|
|
4
|
+
AvatarImage,
|
|
5
|
+
Button,
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
DialogTrigger,
|
|
10
|
+
Shimmer,
|
|
11
|
+
TIMBAL_V2_ELEVATED_GRADIENT,
|
|
12
|
+
TIMBAL_V2_SECONDARY_CHROME,
|
|
13
|
+
TIMBAL_V2_SWITCH_THUMB,
|
|
14
|
+
TIMBAL_V2_SWITCH_TRACK_OFF,
|
|
15
|
+
TimbalV2Button,
|
|
16
|
+
Tooltip,
|
|
17
|
+
TooltipContent,
|
|
18
|
+
TooltipProvider,
|
|
19
|
+
TooltipTrigger,
|
|
20
|
+
cn
|
|
21
|
+
} from "./chunk-4TCJQSIX.esm.js";
|
|
22
|
+
|
|
23
|
+
// src/chat/tooltip-icon-button.tsx
|
|
24
|
+
import { forwardRef } from "react";
|
|
25
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
26
|
+
var TooltipIconButton = forwardRef(function TooltipIconButton2({ tooltip, side = "bottom", variant = "secondary", children, ...props }, ref) {
|
|
27
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
28
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(TimbalV2Button, { ref, variant, size: "sm", isIconOnly: true, ...props, children: [
|
|
29
|
+
children,
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: tooltip })
|
|
31
|
+
] }) }),
|
|
32
|
+
/* @__PURE__ */ jsx(TooltipContent, { side, children: tooltip })
|
|
33
|
+
] });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// src/chat/attachment.tsx
|
|
37
|
+
import { useEffect, useState } from "react";
|
|
38
|
+
import { XIcon, PlusIcon, FileText } from "lucide-react";
|
|
39
|
+
import {
|
|
40
|
+
AttachmentPrimitive,
|
|
41
|
+
ComposerPrimitive,
|
|
42
|
+
MessagePrimitive,
|
|
43
|
+
useAuiState,
|
|
44
|
+
useAui
|
|
45
|
+
} from "@assistant-ui/react";
|
|
46
|
+
import { useShallow } from "zustand/shallow";
|
|
47
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
48
|
+
var useFileSrc = (file) => {
|
|
49
|
+
const [src, setSrc] = useState(void 0);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!file) {
|
|
52
|
+
setSrc(void 0);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const objectUrl = URL.createObjectURL(file);
|
|
56
|
+
setSrc(objectUrl);
|
|
57
|
+
return () => {
|
|
58
|
+
URL.revokeObjectURL(objectUrl);
|
|
59
|
+
};
|
|
60
|
+
}, [file]);
|
|
61
|
+
return src;
|
|
62
|
+
};
|
|
63
|
+
var useAttachmentSrc = () => {
|
|
64
|
+
const { file, src } = useAuiState(
|
|
65
|
+
useShallow((s) => {
|
|
66
|
+
if (s.attachment.type !== "image") return {};
|
|
67
|
+
if (s.attachment.file) return { file: s.attachment.file };
|
|
68
|
+
const src2 = s.attachment.content?.filter((c) => c.type === "image")[0]?.image;
|
|
69
|
+
if (!src2) return {};
|
|
70
|
+
return { src: src2 };
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
return useFileSrc(file) ?? src;
|
|
74
|
+
};
|
|
75
|
+
var AttachmentPreview = ({ src }) => {
|
|
76
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
77
|
+
return /* @__PURE__ */ jsx2(
|
|
78
|
+
"img",
|
|
79
|
+
{
|
|
80
|
+
src,
|
|
81
|
+
alt: "Image Preview",
|
|
82
|
+
className: cn(
|
|
83
|
+
"block h-auto max-h-[80vh] w-auto max-w-full object-contain",
|
|
84
|
+
isLoaded ? "aui-attachment-preview-image-loaded" : "aui-attachment-preview-image-loading invisible"
|
|
85
|
+
),
|
|
86
|
+
onLoad: () => setIsLoaded(true)
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
var AttachmentPreviewDialog = ({ children }) => {
|
|
91
|
+
const src = useAttachmentSrc();
|
|
92
|
+
if (!src) return children;
|
|
93
|
+
return /* @__PURE__ */ jsxs2(Dialog, { children: [
|
|
94
|
+
/* @__PURE__ */ jsx2(
|
|
95
|
+
DialogTrigger,
|
|
96
|
+
{
|
|
97
|
+
className: "aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50",
|
|
98
|
+
asChild: true,
|
|
99
|
+
children
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsxs2(DialogContent, { className: "aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive", children: [
|
|
103
|
+
/* @__PURE__ */ jsx2(DialogTitle, { className: "aui-sr-only sr-only", children: "Image Attachment Preview" }),
|
|
104
|
+
/* @__PURE__ */ jsx2("div", { className: "aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background", children: /* @__PURE__ */ jsx2(AttachmentPreview, { src }) })
|
|
105
|
+
] })
|
|
106
|
+
] });
|
|
107
|
+
};
|
|
108
|
+
var AttachmentThumb = () => {
|
|
109
|
+
const isImage = useAuiState((s) => s.attachment.type === "image");
|
|
110
|
+
const src = useAttachmentSrc();
|
|
111
|
+
return /* @__PURE__ */ jsxs2(Avatar, { className: "aui-attachment-tile-avatar h-full w-full rounded-none", children: [
|
|
112
|
+
/* @__PURE__ */ jsx2(
|
|
113
|
+
AvatarImage,
|
|
114
|
+
{
|
|
115
|
+
src,
|
|
116
|
+
alt: "Attachment preview",
|
|
117
|
+
className: "aui-attachment-tile-image object-cover"
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ jsx2(AvatarFallback, { delayMs: isImage ? 200 : 0, children: /* @__PURE__ */ jsx2(FileText, { className: "aui-attachment-tile-fallback-icon size-8 text-muted-foreground" }) })
|
|
121
|
+
] });
|
|
122
|
+
};
|
|
123
|
+
var AttachmentUI = () => {
|
|
124
|
+
const aui = useAui();
|
|
125
|
+
const isComposer = aui.attachment.source === "composer";
|
|
126
|
+
const isImage = useAuiState((s) => s.attachment.type === "image");
|
|
127
|
+
const typeLabel = useAuiState((s) => {
|
|
128
|
+
const type = s.attachment.type;
|
|
129
|
+
switch (type) {
|
|
130
|
+
case "image":
|
|
131
|
+
return "Image";
|
|
132
|
+
case "document":
|
|
133
|
+
return "Document";
|
|
134
|
+
case "file":
|
|
135
|
+
return "File";
|
|
136
|
+
default:
|
|
137
|
+
throw new Error(`Unknown attachment type: ${type}`);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return /* @__PURE__ */ jsxs2(Tooltip, { children: [
|
|
141
|
+
/* @__PURE__ */ jsxs2(
|
|
142
|
+
AttachmentPrimitive.Root,
|
|
143
|
+
{
|
|
144
|
+
className: cn(
|
|
145
|
+
"aui-attachment-root relative",
|
|
146
|
+
isImage && "aui-attachment-root-composer only:[&>#attachment-tile]:size-24"
|
|
147
|
+
),
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ jsx2(AttachmentPreviewDialog, { children: /* @__PURE__ */ jsx2(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(
|
|
150
|
+
"div",
|
|
151
|
+
{
|
|
152
|
+
className: cn(
|
|
153
|
+
"aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
|
|
154
|
+
isComposer && "aui-attachment-tile-composer border-foreground/20"
|
|
155
|
+
),
|
|
156
|
+
role: "button",
|
|
157
|
+
id: "attachment-tile",
|
|
158
|
+
"aria-label": `${typeLabel} attachment`,
|
|
159
|
+
children: /* @__PURE__ */ jsx2(AttachmentThumb, {})
|
|
160
|
+
}
|
|
161
|
+
) }) }),
|
|
162
|
+
isComposer && /* @__PURE__ */ jsx2(AttachmentRemove, {})
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
),
|
|
166
|
+
/* @__PURE__ */ jsx2(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx2(AttachmentPrimitive.Name, {}) })
|
|
167
|
+
] });
|
|
168
|
+
};
|
|
169
|
+
var AttachmentRemove = () => {
|
|
170
|
+
return /* @__PURE__ */ jsx2(AttachmentPrimitive.Remove, { asChild: true, children: /* @__PURE__ */ jsx2(
|
|
171
|
+
TooltipIconButton,
|
|
172
|
+
{
|
|
173
|
+
tooltip: "Remove file",
|
|
174
|
+
className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-card text-foreground opacity-100 shadow-card hover:bg-card! [&_svg]:text-foreground hover:[&_svg]:text-destructive",
|
|
175
|
+
side: "top",
|
|
176
|
+
children: /* @__PURE__ */ jsx2(XIcon, { className: "aui-attachment-remove-icon size-3" })
|
|
177
|
+
}
|
|
178
|
+
) });
|
|
179
|
+
};
|
|
180
|
+
var UserMessageAttachments = () => {
|
|
181
|
+
return /* @__PURE__ */ jsx2("div", { className: "aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2", children: /* @__PURE__ */ jsx2(MessagePrimitive.Attachments, { components: { Attachment: AttachmentUI } }) });
|
|
182
|
+
};
|
|
183
|
+
var ComposerAttachments = () => {
|
|
184
|
+
return /* @__PURE__ */ jsx2("div", { className: "aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden", children: /* @__PURE__ */ jsx2(
|
|
185
|
+
ComposerPrimitive.Attachments,
|
|
186
|
+
{
|
|
187
|
+
components: { Attachment: AttachmentUI }
|
|
188
|
+
}
|
|
189
|
+
) });
|
|
190
|
+
};
|
|
191
|
+
var ComposerAddAttachment = () => {
|
|
192
|
+
return /* @__PURE__ */ jsx2(ComposerPrimitive.AddAttachment, { asChild: true, children: /* @__PURE__ */ jsx2(
|
|
193
|
+
TooltipIconButton,
|
|
194
|
+
{
|
|
195
|
+
tooltip: "Add Attachment",
|
|
196
|
+
side: "bottom",
|
|
197
|
+
variant: "secondary",
|
|
198
|
+
className: "aui-composer-add-attachment shrink-0 text-muted-foreground",
|
|
199
|
+
"aria-label": "Add Attachment",
|
|
200
|
+
children: /* @__PURE__ */ jsx2(PlusIcon, { className: "aui-attachment-add-icon size-4 stroke-[1.5]" })
|
|
201
|
+
}
|
|
202
|
+
) });
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/artifacts/artifact-card.tsx
|
|
206
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
207
|
+
var ArtifactCard = ({ title, kind, className, bodyClassName, toolbar, children }) => {
|
|
208
|
+
const hasHeader = Boolean(title || toolbar);
|
|
209
|
+
return /* @__PURE__ */ jsxs3(
|
|
210
|
+
"div",
|
|
211
|
+
{
|
|
212
|
+
className: cn(
|
|
213
|
+
"aui-artifact-root my-3 overflow-hidden rounded-xl border border-border/60 bg-background shadow-sm",
|
|
214
|
+
className
|
|
215
|
+
),
|
|
216
|
+
"data-artifact-kind": kind,
|
|
217
|
+
children: [
|
|
218
|
+
hasHeader && /* @__PURE__ */ jsxs3("div", { className: "aui-artifact-header flex items-center gap-2 border-b border-border/40 bg-muted/30 px-3 py-1.5", children: [
|
|
219
|
+
title && /* @__PURE__ */ jsx3("span", { className: "aui-artifact-title flex-1 truncate text-xs font-semibold text-foreground/80", children: title }),
|
|
220
|
+
!title && /* @__PURE__ */ jsx3("span", { className: "flex-1" }),
|
|
221
|
+
toolbar
|
|
222
|
+
] }),
|
|
223
|
+
/* @__PURE__ */ jsx3("div", { className: cn("aui-artifact-body", bodyClassName), children })
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/artifacts/chart-artifact.tsx
|
|
230
|
+
import { useMemo } from "react";
|
|
231
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
232
|
+
var ChartArtifactView = ({
|
|
233
|
+
artifact
|
|
234
|
+
}) => {
|
|
235
|
+
const { type: _t, chartType = "bar", data = [] } = artifact;
|
|
236
|
+
const xKey = artifact.xKey ?? inferXKey(data);
|
|
237
|
+
const dataKeys = useMemo(() => {
|
|
238
|
+
if (Array.isArray(artifact.dataKey)) return artifact.dataKey;
|
|
239
|
+
if (typeof artifact.dataKey === "string") return [artifact.dataKey];
|
|
240
|
+
return inferDataKeys(data, xKey);
|
|
241
|
+
}, [artifact.dataKey, data, xKey]);
|
|
242
|
+
return /* @__PURE__ */ jsx4(ArtifactCard, { title: artifact.title, kind: "chart", children: /* @__PURE__ */ jsxs4("div", { className: "aui-artifact-chart p-3", children: [
|
|
243
|
+
/* @__PURE__ */ jsx4(
|
|
244
|
+
ChartSvg,
|
|
245
|
+
{
|
|
246
|
+
chartType,
|
|
247
|
+
data,
|
|
248
|
+
xKey,
|
|
249
|
+
dataKeys,
|
|
250
|
+
unit: artifact.unit
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
dataKeys.length > 1 && /* @__PURE__ */ jsx4(Legend, { dataKeys })
|
|
254
|
+
] }) });
|
|
255
|
+
};
|
|
256
|
+
var COLORS = [
|
|
257
|
+
"var(--primary, #6366f1)",
|
|
258
|
+
"#22c55e",
|
|
259
|
+
"#f59e0b",
|
|
260
|
+
"#ef4444",
|
|
261
|
+
"#06b6d4",
|
|
262
|
+
"#a855f7"
|
|
263
|
+
];
|
|
264
|
+
var W = 600;
|
|
265
|
+
var H = 240;
|
|
266
|
+
var PAD = { top: 12, right: 16, bottom: 28, left: 36 };
|
|
267
|
+
var ChartSvg = ({ chartType, data, xKey, dataKeys, unit }) => {
|
|
268
|
+
if (data.length === 0 || dataKeys.length === 0) {
|
|
269
|
+
return /* @__PURE__ */ jsx4(EmptyState, {});
|
|
270
|
+
}
|
|
271
|
+
if (chartType === "pie") {
|
|
272
|
+
return /* @__PURE__ */ jsx4(PieChart, { data, xKey, dataKey: dataKeys[0] });
|
|
273
|
+
}
|
|
274
|
+
const innerW = W - PAD.left - PAD.right;
|
|
275
|
+
const innerH = H - PAD.top - PAD.bottom;
|
|
276
|
+
const all = dataKeys.flatMap((k) => data.map((d) => toNum(d[k])));
|
|
277
|
+
const maxV = Math.max(0, ...all);
|
|
278
|
+
const minV = Math.min(0, ...all);
|
|
279
|
+
const range = maxV - minV || 1;
|
|
280
|
+
const yScale = (v) => PAD.top + innerH - (v - minV) / range * innerH;
|
|
281
|
+
const xCount = data.length;
|
|
282
|
+
const xStep = xCount > 1 ? innerW / (xCount - 1) : innerW;
|
|
283
|
+
const xPos = (i) => chartType === "bar" ? PAD.left + innerW * (i + 0.5) / xCount : PAD.left + i * xStep;
|
|
284
|
+
const ticks = niceTicks(minV, maxV);
|
|
285
|
+
return /* @__PURE__ */ jsxs4(
|
|
286
|
+
"svg",
|
|
287
|
+
{
|
|
288
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
289
|
+
className: "aui-artifact-chart-svg w-full",
|
|
290
|
+
role: "img",
|
|
291
|
+
"aria-label": "Chart",
|
|
292
|
+
children: [
|
|
293
|
+
ticks.map((t, i) => /* @__PURE__ */ jsxs4("g", { children: [
|
|
294
|
+
/* @__PURE__ */ jsx4(
|
|
295
|
+
"line",
|
|
296
|
+
{
|
|
297
|
+
x1: PAD.left,
|
|
298
|
+
x2: W - PAD.right,
|
|
299
|
+
y1: yScale(t),
|
|
300
|
+
y2: yScale(t),
|
|
301
|
+
stroke: "currentColor",
|
|
302
|
+
strokeOpacity: 0.08
|
|
303
|
+
}
|
|
304
|
+
),
|
|
305
|
+
/* @__PURE__ */ jsx4(
|
|
306
|
+
"text",
|
|
307
|
+
{
|
|
308
|
+
x: PAD.left - 6,
|
|
309
|
+
y: yScale(t),
|
|
310
|
+
textAnchor: "end",
|
|
311
|
+
dominantBaseline: "middle",
|
|
312
|
+
className: "fill-muted-foreground text-[10px]",
|
|
313
|
+
children: formatTick(t, unit)
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
] }, i)),
|
|
317
|
+
data.map((d, i) => /* @__PURE__ */ jsx4(
|
|
318
|
+
"text",
|
|
319
|
+
{
|
|
320
|
+
x: xPos(i),
|
|
321
|
+
y: H - PAD.bottom + 14,
|
|
322
|
+
textAnchor: "middle",
|
|
323
|
+
className: "fill-muted-foreground text-[10px]",
|
|
324
|
+
children: String(d[xKey] ?? i)
|
|
325
|
+
},
|
|
326
|
+
i
|
|
327
|
+
)),
|
|
328
|
+
chartType === "bar" && renderBars({ data, dataKeys, xCount, xPos, yScale, minV, innerW }),
|
|
329
|
+
chartType === "line" && dataKeys.map((k, ki) => /* @__PURE__ */ jsx4(
|
|
330
|
+
Polyline,
|
|
331
|
+
{
|
|
332
|
+
data,
|
|
333
|
+
dataKey: k,
|
|
334
|
+
xPos,
|
|
335
|
+
yScale,
|
|
336
|
+
color: COLORS[ki % COLORS.length]
|
|
337
|
+
},
|
|
338
|
+
k
|
|
339
|
+
)),
|
|
340
|
+
chartType === "area" && dataKeys.map((k, ki) => /* @__PURE__ */ jsx4(
|
|
341
|
+
Area,
|
|
342
|
+
{
|
|
343
|
+
data,
|
|
344
|
+
dataKey: k,
|
|
345
|
+
xPos,
|
|
346
|
+
yScale,
|
|
347
|
+
baseY: yScale(Math.max(0, minV)),
|
|
348
|
+
color: COLORS[ki % COLORS.length]
|
|
349
|
+
},
|
|
350
|
+
k
|
|
351
|
+
))
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
function renderBars(args) {
|
|
357
|
+
const { data, dataKeys, xCount, xPos, yScale, minV, innerW } = args;
|
|
358
|
+
const groupWidth = innerW / xCount * 0.7;
|
|
359
|
+
const barWidth = groupWidth / dataKeys.length;
|
|
360
|
+
const baseY = yScale(Math.max(0, minV));
|
|
361
|
+
return dataKeys.flatMap(
|
|
362
|
+
(k, ki) => data.map((d, i) => {
|
|
363
|
+
const v = toNum(d[k]);
|
|
364
|
+
const y = yScale(v);
|
|
365
|
+
const x = xPos(i) - groupWidth / 2 + ki * barWidth;
|
|
366
|
+
const top = Math.min(y, baseY);
|
|
367
|
+
const height = Math.abs(y - baseY);
|
|
368
|
+
return /* @__PURE__ */ jsx4(
|
|
369
|
+
"rect",
|
|
370
|
+
{
|
|
371
|
+
x,
|
|
372
|
+
y: top,
|
|
373
|
+
width: Math.max(1, barWidth - 2),
|
|
374
|
+
height: Math.max(1, height),
|
|
375
|
+
rx: 2,
|
|
376
|
+
fill: COLORS[ki % COLORS.length]
|
|
377
|
+
},
|
|
378
|
+
`${k}-${i}`
|
|
379
|
+
);
|
|
380
|
+
})
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
var Polyline = ({ data, dataKey, xPos, yScale, color }) => {
|
|
384
|
+
const points = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
|
|
385
|
+
return /* @__PURE__ */ jsx4(
|
|
386
|
+
"polyline",
|
|
387
|
+
{
|
|
388
|
+
points,
|
|
389
|
+
fill: "none",
|
|
390
|
+
stroke: color,
|
|
391
|
+
strokeWidth: 2,
|
|
392
|
+
strokeLinejoin: "round",
|
|
393
|
+
strokeLinecap: "round"
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
};
|
|
397
|
+
var Area = ({ data, dataKey, xPos, yScale, baseY, color }) => {
|
|
398
|
+
if (data.length === 0) return null;
|
|
399
|
+
const top = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
|
|
400
|
+
const path = `M ${xPos(0)},${baseY} L ${top} L ${xPos(data.length - 1)},${baseY} Z`;
|
|
401
|
+
return /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
402
|
+
/* @__PURE__ */ jsx4("path", { d: path, fill: color, fillOpacity: 0.18 }),
|
|
403
|
+
/* @__PURE__ */ jsx4(Polyline, { data, dataKey, xPos, yScale, color })
|
|
404
|
+
] });
|
|
405
|
+
};
|
|
406
|
+
var PieChart = ({ data, xKey, dataKey }) => {
|
|
407
|
+
const cx = W / 2;
|
|
408
|
+
const cy = H / 2;
|
|
409
|
+
const r = Math.min(W, H) / 2 - 16;
|
|
410
|
+
const total = data.reduce((sum, d) => sum + toNum(d[dataKey]), 0) || 1;
|
|
411
|
+
let acc = 0;
|
|
412
|
+
return /* @__PURE__ */ jsx4(
|
|
413
|
+
"svg",
|
|
414
|
+
{
|
|
415
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
416
|
+
className: "aui-artifact-chart-svg w-full",
|
|
417
|
+
role: "img",
|
|
418
|
+
"aria-label": "Pie chart",
|
|
419
|
+
children: data.map((d, i) => {
|
|
420
|
+
const value = toNum(d[dataKey]);
|
|
421
|
+
const start = acc / total * Math.PI * 2;
|
|
422
|
+
acc += value;
|
|
423
|
+
const end = acc / total * Math.PI * 2;
|
|
424
|
+
return /* @__PURE__ */ jsx4(
|
|
425
|
+
PieSlice,
|
|
426
|
+
{
|
|
427
|
+
cx,
|
|
428
|
+
cy,
|
|
429
|
+
r,
|
|
430
|
+
start,
|
|
431
|
+
end,
|
|
432
|
+
color: COLORS[i % COLORS.length],
|
|
433
|
+
label: String(d[xKey] ?? i)
|
|
434
|
+
},
|
|
435
|
+
i
|
|
436
|
+
);
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
};
|
|
441
|
+
var PieSlice = ({ cx, cy, r, start, end, color, label }) => {
|
|
442
|
+
const x1 = cx + Math.sin(start) * r;
|
|
443
|
+
const y1 = cy - Math.cos(start) * r;
|
|
444
|
+
const x2 = cx + Math.sin(end) * r;
|
|
445
|
+
const y2 = cy - Math.cos(end) * r;
|
|
446
|
+
const large = end - start > Math.PI ? 1 : 0;
|
|
447
|
+
const path = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
|
|
448
|
+
const mid = (start + end) / 2;
|
|
449
|
+
const lx = cx + Math.sin(mid) * (r * 0.65);
|
|
450
|
+
const ly = cy - Math.cos(mid) * (r * 0.65);
|
|
451
|
+
return /* @__PURE__ */ jsxs4("g", { children: [
|
|
452
|
+
/* @__PURE__ */ jsx4("path", { d: path, fill: color, stroke: "var(--background, #fff)", strokeWidth: 1 }),
|
|
453
|
+
/* @__PURE__ */ jsx4(
|
|
454
|
+
"text",
|
|
455
|
+
{
|
|
456
|
+
x: lx,
|
|
457
|
+
y: ly,
|
|
458
|
+
textAnchor: "middle",
|
|
459
|
+
dominantBaseline: "middle",
|
|
460
|
+
className: "fill-white text-[10px] font-semibold",
|
|
461
|
+
children: label
|
|
462
|
+
}
|
|
463
|
+
)
|
|
464
|
+
] });
|
|
465
|
+
};
|
|
466
|
+
var Legend = ({ dataKeys }) => /* @__PURE__ */ jsx4("div", { className: "aui-artifact-chart-legend mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: dataKeys.map((k, i) => /* @__PURE__ */ jsxs4("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
467
|
+
/* @__PURE__ */ jsx4(
|
|
468
|
+
"span",
|
|
469
|
+
{
|
|
470
|
+
className: "inline-block size-2 rounded-sm",
|
|
471
|
+
style: { background: COLORS[i % COLORS.length] },
|
|
472
|
+
"aria-hidden": true
|
|
473
|
+
}
|
|
474
|
+
),
|
|
475
|
+
k
|
|
476
|
+
] }, k)) });
|
|
477
|
+
var EmptyState = () => /* @__PURE__ */ jsx4("div", { className: "aui-artifact-chart-empty flex h-32 items-center justify-center text-xs text-muted-foreground", children: "No data" });
|
|
478
|
+
function inferXKey(data) {
|
|
479
|
+
if (data.length === 0) return "x";
|
|
480
|
+
for (const k of Object.keys(data[0])) {
|
|
481
|
+
if (typeof data[0][k] !== "number") return k;
|
|
482
|
+
}
|
|
483
|
+
return Object.keys(data[0])[0] ?? "x";
|
|
484
|
+
}
|
|
485
|
+
function inferDataKeys(data, xKey) {
|
|
486
|
+
if (data.length === 0) return [];
|
|
487
|
+
return Object.keys(data[0]).filter(
|
|
488
|
+
(k) => k !== xKey && typeof data[0][k] === "number"
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
function toNum(value) {
|
|
492
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
493
|
+
return Number.isFinite(n) ? n : 0;
|
|
494
|
+
}
|
|
495
|
+
function niceTicks(min, max, count = 4) {
|
|
496
|
+
if (max === min) return [min];
|
|
497
|
+
const range = max - min;
|
|
498
|
+
const step = niceStep(range / count);
|
|
499
|
+
const start = Math.floor(min / step) * step;
|
|
500
|
+
const out = [];
|
|
501
|
+
for (let v = start; v <= max + step / 2; v += step) {
|
|
502
|
+
out.push(round(v));
|
|
503
|
+
}
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
function niceStep(raw) {
|
|
507
|
+
const exp = Math.floor(Math.log10(Math.abs(raw))) || 0;
|
|
508
|
+
const base = Math.pow(10, exp);
|
|
509
|
+
const norm = raw / base;
|
|
510
|
+
let nice = 1;
|
|
511
|
+
if (norm >= 5) nice = 5;
|
|
512
|
+
else if (norm >= 2) nice = 2;
|
|
513
|
+
return nice * base;
|
|
514
|
+
}
|
|
515
|
+
function round(v) {
|
|
516
|
+
return Math.round(v * 1e6) / 1e6;
|
|
517
|
+
}
|
|
518
|
+
function formatTick(v, unit) {
|
|
519
|
+
const s = Math.abs(v) >= 1e3 ? `${(v / 1e3).toFixed(1)}k` : String(round(v));
|
|
520
|
+
return unit ? `${s}${unit}` : s;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/artifacts/question-artifact.tsx
|
|
524
|
+
import { useCallback, useState as useState2 } from "react";
|
|
525
|
+
import { useThreadRuntime } from "@assistant-ui/react";
|
|
526
|
+
import { CheckIcon } from "lucide-react";
|
|
527
|
+
|
|
528
|
+
// src/design/classes.ts
|
|
529
|
+
var studioTopbarPillHeightClass = "h-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)]";
|
|
530
|
+
var studioTopbarIconPillClass = "shrink-0 flex-none size-[var(--studio-chrome-pill-height)] min-h-[var(--studio-chrome-pill-height)] min-w-[var(--studio-chrome-pill-height)]";
|
|
531
|
+
var studioPlaygroundGradientClass = "bg-gradient-to-b from-playground-from via-playground-via to-playground-to";
|
|
532
|
+
var studioComposeInputShellClass = cn(
|
|
533
|
+
"aui-composer-shell flex w-full flex-col overflow-hidden rounded-2xl bg-composer-bg shadow-card-elevated outline-none",
|
|
534
|
+
"border border-composer-border",
|
|
535
|
+
"transition-[box-shadow,border-color]",
|
|
536
|
+
"focus-within:border-composer-border-focus focus-within:ring-2 focus-within:ring-foreground/5"
|
|
537
|
+
);
|
|
538
|
+
var studioSecondaryChromeClass = TIMBAL_V2_SECONDARY_CHROME;
|
|
539
|
+
var studioSearchChromeClass = cn(
|
|
540
|
+
studioSecondaryChromeClass,
|
|
541
|
+
studioTopbarPillHeightClass,
|
|
542
|
+
"inline-flex items-center gap-2 rounded-full px-2.5"
|
|
543
|
+
);
|
|
544
|
+
var studioIntegrationSurfaceSolid = cn(
|
|
545
|
+
TIMBAL_V2_ELEVATED_GRADIENT,
|
|
546
|
+
"shadow-card"
|
|
547
|
+
);
|
|
548
|
+
var studioIntegrationBorder = "border border-border";
|
|
549
|
+
var studioIntegrationCardClass = cn(
|
|
550
|
+
"rounded-xl",
|
|
551
|
+
studioIntegrationSurfaceSolid,
|
|
552
|
+
studioIntegrationBorder
|
|
553
|
+
);
|
|
554
|
+
var studioListRowButtonClass = cn(
|
|
555
|
+
"flex w-full cursor-pointer items-center gap-3 rounded-xl px-3 py-2.5 text-left",
|
|
556
|
+
studioIntegrationCardClass,
|
|
557
|
+
"transition-[background-color,box-shadow,border-color] duration-200 ease-in-out",
|
|
558
|
+
"hover:border-foreground/20",
|
|
559
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
560
|
+
);
|
|
561
|
+
var studioComposerIoWellClass = cn(
|
|
562
|
+
"rounded-lg",
|
|
563
|
+
studioIntegrationSurfaceSolid,
|
|
564
|
+
studioIntegrationBorder
|
|
565
|
+
);
|
|
566
|
+
var studioSidebarPanelClass = cn(
|
|
567
|
+
"bg-sidebar text-sidebar-foreground",
|
|
568
|
+
"border border-sidebar-border",
|
|
569
|
+
"shadow-card-elevated"
|
|
570
|
+
);
|
|
571
|
+
var studioSidebarNavItemClass = cn(
|
|
572
|
+
"flex items-center rounded-lg text-sm",
|
|
573
|
+
"transition-[color,background-color,box-shadow,border-color] duration-200 ease-in-out",
|
|
574
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2"
|
|
575
|
+
);
|
|
576
|
+
function studioSidebarNavItemLayout(iconOnly) {
|
|
577
|
+
return iconOnly ? "box-border size-8 min-h-8 min-w-8 shrink-0 justify-center rounded-lg p-0 focus-visible:ring-offset-0" : "w-full gap-2 px-2.5 py-2";
|
|
578
|
+
}
|
|
579
|
+
var studioSidebarNavItemSurfaceClass = cn(
|
|
580
|
+
"bg-gradient-to-b from-elevated-from to-elevated-to text-foreground",
|
|
581
|
+
"border border-border",
|
|
582
|
+
"shadow-card"
|
|
583
|
+
);
|
|
584
|
+
var studioSidebarNavItemIdleClass = cn(
|
|
585
|
+
"border border-transparent text-muted-foreground shadow-none",
|
|
586
|
+
"hover:text-foreground",
|
|
587
|
+
"hover:bg-gradient-to-b hover:from-elevated-from hover:to-elevated-to",
|
|
588
|
+
"hover:border-border hover:shadow-card"
|
|
589
|
+
);
|
|
590
|
+
var studioSidebarCollapsedRailItemClass = cn(
|
|
591
|
+
"border border-border shadow-card bg-sidebar-accent"
|
|
592
|
+
);
|
|
593
|
+
var studioSidebarCollapsedRailItemIdleClass = cn(
|
|
594
|
+
studioSidebarCollapsedRailItemClass,
|
|
595
|
+
"text-muted-foreground hover:text-foreground"
|
|
596
|
+
);
|
|
597
|
+
var studioSidebarCollapsedRailItemActiveClass = cn(
|
|
598
|
+
studioSidebarCollapsedRailItemClass,
|
|
599
|
+
studioSidebarNavItemSurfaceClass,
|
|
600
|
+
"text-foreground"
|
|
601
|
+
);
|
|
602
|
+
var studioSidebarNavItemActiveClass = studioSidebarNavItemSurfaceClass;
|
|
603
|
+
var studioTimelineRowButtonClass = "group flex w-full min-w-0 cursor-pointer items-center justify-start rounded-md border-0 bg-transparent py-1 text-left shadow-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2";
|
|
604
|
+
var studioTimelineTextClass = "text-xs font-normal leading-snug";
|
|
605
|
+
var studioTimelineActionClass = cn(
|
|
606
|
+
studioTimelineTextClass,
|
|
607
|
+
"shrink-0 text-foreground/70 transition-colors duration-150 group-hover:text-foreground/80"
|
|
608
|
+
);
|
|
609
|
+
var studioTimelineShimmerActionClass = cn(
|
|
610
|
+
studioTimelineTextClass,
|
|
611
|
+
"shrink-0"
|
|
612
|
+
);
|
|
613
|
+
var studioTimelineDetailClass = cn(
|
|
614
|
+
studioTimelineTextClass,
|
|
615
|
+
"min-w-0 truncate text-muted-foreground transition-colors duration-150"
|
|
616
|
+
);
|
|
617
|
+
function studioTimelineChevronClass(expanded) {
|
|
618
|
+
return cn(
|
|
619
|
+
"ml-0.5 size-3 min-h-3 min-w-3 shrink-0 transition-all duration-150",
|
|
620
|
+
expanded ? "rotate-90 text-foreground opacity-60" : "text-muted-foreground opacity-0 group-hover:opacity-70"
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
var studioTimelineBodyPadClass = "flex flex-col gap-2 pt-0.5 pb-0.5";
|
|
624
|
+
var studioArtifactShellClass = cn(
|
|
625
|
+
studioIntegrationCardClass,
|
|
626
|
+
"my-2 w-full min-w-0 overflow-hidden"
|
|
627
|
+
);
|
|
628
|
+
var studioQuestionOptionClass = cn(
|
|
629
|
+
"flex w-full items-center gap-2 rounded-lg border border-transparent px-2 py-1.5 text-left text-sm",
|
|
630
|
+
"transition-[background-color,border-color,box-shadow] duration-200",
|
|
631
|
+
"hover:border-border hover:bg-gradient-to-b hover:from-elevated-from hover:to-elevated-to hover:shadow-card",
|
|
632
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/15 focus-visible:ring-offset-2"
|
|
633
|
+
);
|
|
634
|
+
var studioQuestionOptionSelectedClass = cn(
|
|
635
|
+
studioQuestionOptionClass,
|
|
636
|
+
"border-border bg-gradient-to-b from-elevated-from to-elevated-to shadow-card ring-1 ring-foreground/10"
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
// src/artifacts/question-artifact.tsx
|
|
640
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
641
|
+
function optionKey(option, index) {
|
|
642
|
+
const id = option.id?.trim();
|
|
643
|
+
return id ? id : `__option-${index}`;
|
|
644
|
+
}
|
|
645
|
+
var OptionRadio = ({ selected }) => /* @__PURE__ */ jsx5(
|
|
646
|
+
"span",
|
|
647
|
+
{
|
|
648
|
+
className: cn(
|
|
649
|
+
"flex size-4 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
|
|
650
|
+
selected ? "border-foreground bg-foreground text-background" : "border-border bg-background"
|
|
651
|
+
),
|
|
652
|
+
"aria-hidden": true,
|
|
653
|
+
children: selected ? /* @__PURE__ */ jsx5(CheckIcon, { className: "size-2.5 stroke-[3]" }) : null
|
|
654
|
+
}
|
|
655
|
+
);
|
|
656
|
+
var QuestionArtifactView = ({
|
|
657
|
+
artifact
|
|
658
|
+
}) => {
|
|
659
|
+
const runtime = useThreadRuntime();
|
|
660
|
+
const [selected, setSelected] = useState2([]);
|
|
661
|
+
const [submittedIds, setSubmittedIds] = useState2(null);
|
|
662
|
+
const isMulti = artifact.multi === true;
|
|
663
|
+
const isDisabled = submittedIds !== null;
|
|
664
|
+
const send = useCallback(
|
|
665
|
+
(keys) => {
|
|
666
|
+
if (keys.length === 0) return;
|
|
667
|
+
const labels = artifact.options.map((option, index) => ({ option, key: optionKey(option, index) })).filter(({ key }) => keys.includes(key)).map(({ option }) => option.label);
|
|
668
|
+
setSubmittedIds(keys);
|
|
669
|
+
runtime.append({
|
|
670
|
+
role: "user",
|
|
671
|
+
content: [{ type: "text", text: labels.join(", ") }]
|
|
672
|
+
});
|
|
673
|
+
},
|
|
674
|
+
[artifact.options, runtime]
|
|
675
|
+
);
|
|
676
|
+
const onPick = useCallback(
|
|
677
|
+
(key) => {
|
|
678
|
+
if (isDisabled) return;
|
|
679
|
+
if (!isMulti) {
|
|
680
|
+
send([key]);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
setSelected(
|
|
684
|
+
(prev) => prev.includes(key) ? prev.filter((id) => id !== key) : [...prev, key]
|
|
685
|
+
);
|
|
686
|
+
},
|
|
687
|
+
[isDisabled, isMulti, send]
|
|
688
|
+
);
|
|
689
|
+
const onConfirm = useCallback(() => {
|
|
690
|
+
send(selected);
|
|
691
|
+
}, [selected, send]);
|
|
692
|
+
return /* @__PURE__ */ jsx5("div", { className: studioArtifactShellClass, "data-artifact-kind": "question", children: /* @__PURE__ */ jsxs5("div", { className: "px-2.5 py-2", children: [
|
|
693
|
+
artifact.prompt ? /* @__PURE__ */ jsx5("p", { className: "mb-2 text-sm font-normal leading-snug text-foreground", children: artifact.prompt }) : null,
|
|
694
|
+
/* @__PURE__ */ jsx5("div", { className: "flex flex-col gap-0.5", role: "list", children: artifact.options.map((option, index) => {
|
|
695
|
+
const key = optionKey(option, index);
|
|
696
|
+
const isSelected = submittedIds ? submittedIds.includes(key) : isMulti && selected.includes(key);
|
|
697
|
+
return /* @__PURE__ */ jsxs5(
|
|
698
|
+
"button",
|
|
699
|
+
{
|
|
700
|
+
type: "button",
|
|
701
|
+
role: "listitem",
|
|
702
|
+
disabled: isDisabled,
|
|
703
|
+
onClick: () => onPick(key),
|
|
704
|
+
className: cn(
|
|
705
|
+
isSelected ? studioQuestionOptionSelectedClass : studioQuestionOptionClass,
|
|
706
|
+
isDisabled && (isSelected ? "cursor-default" : "cursor-not-allowed opacity-50")
|
|
707
|
+
),
|
|
708
|
+
children: [
|
|
709
|
+
/* @__PURE__ */ jsx5(OptionRadio, { selected: isSelected }),
|
|
710
|
+
/* @__PURE__ */ jsxs5("span", { className: "min-w-0 flex-1 text-left", children: [
|
|
711
|
+
/* @__PURE__ */ jsx5("span", { className: "block font-normal text-foreground", children: option.label }),
|
|
712
|
+
option.description ? /* @__PURE__ */ jsx5("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: option.description }) : null
|
|
713
|
+
] })
|
|
714
|
+
]
|
|
715
|
+
},
|
|
716
|
+
key
|
|
717
|
+
);
|
|
718
|
+
}) }),
|
|
719
|
+
isMulti && !submittedIds ? /* @__PURE__ */ jsx5("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx5(
|
|
720
|
+
TimbalV2Button,
|
|
721
|
+
{
|
|
722
|
+
type: "button",
|
|
723
|
+
variant: "primary",
|
|
724
|
+
size: "sm",
|
|
725
|
+
disabled: selected.length === 0,
|
|
726
|
+
onClick: onConfirm,
|
|
727
|
+
children: "Confirm"
|
|
728
|
+
}
|
|
729
|
+
) }) : null
|
|
730
|
+
] }) });
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// src/artifacts/html-artifact.tsx
|
|
734
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
735
|
+
var HtmlArtifactView = ({ artifact }) => {
|
|
736
|
+
const sandboxed = artifact.sandboxed !== false;
|
|
737
|
+
const sandbox = sandboxed ? "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-pointer-lock" : void 0;
|
|
738
|
+
const height = artifact.height ?? "320px";
|
|
739
|
+
return /* @__PURE__ */ jsx6(ArtifactCard, { title: artifact.title, kind: "html", children: /* @__PURE__ */ jsx6(
|
|
740
|
+
"iframe",
|
|
741
|
+
{
|
|
742
|
+
title: artifact.title ?? "HTML artifact",
|
|
743
|
+
srcDoc: artifact.content,
|
|
744
|
+
sandbox,
|
|
745
|
+
className: "aui-artifact-html w-full border-0 bg-background",
|
|
746
|
+
style: { height }
|
|
747
|
+
}
|
|
748
|
+
) });
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// src/artifacts/json-artifact.tsx
|
|
752
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
753
|
+
var JsonArtifactView = ({
|
|
754
|
+
artifact
|
|
755
|
+
}) => {
|
|
756
|
+
const data = "data" in artifact ? artifact.data : artifact;
|
|
757
|
+
const title = artifact.title;
|
|
758
|
+
let body;
|
|
759
|
+
try {
|
|
760
|
+
body = JSON.stringify(data, null, 2);
|
|
761
|
+
} catch {
|
|
762
|
+
body = String(data);
|
|
763
|
+
}
|
|
764
|
+
return /* @__PURE__ */ jsx7(ArtifactCard, { title, kind: "json", children: /* @__PURE__ */ jsx7("pre", { className: "aui-artifact-json m-0 max-h-[420px] overflow-auto p-3 font-mono text-[12px] leading-relaxed text-foreground/85", children: body }) });
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// src/artifacts/table-artifact.tsx
|
|
768
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
769
|
+
var TableArtifactView = ({ artifact }) => {
|
|
770
|
+
const rows = artifact.rows ?? [];
|
|
771
|
+
const columns = artifact.columns ?? deriveColumns(rows);
|
|
772
|
+
return /* @__PURE__ */ jsx8(ArtifactCard, { title: artifact.title, kind: "table", children: /* @__PURE__ */ jsx8("div", { className: "aui-artifact-table-wrap overflow-x-auto", children: /* @__PURE__ */ jsxs6("table", { className: "aui-artifact-table w-full border-collapse text-sm", children: [
|
|
773
|
+
/* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsx8("tr", { className: "border-b border-border/40 bg-muted/20", children: columns.map((col) => /* @__PURE__ */ jsx8(
|
|
774
|
+
"th",
|
|
775
|
+
{
|
|
776
|
+
className: "px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground",
|
|
777
|
+
children: col.label ?? col.key
|
|
778
|
+
},
|
|
779
|
+
col.key
|
|
780
|
+
)) }) }),
|
|
781
|
+
/* @__PURE__ */ jsx8("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx8(
|
|
782
|
+
"tr",
|
|
783
|
+
{
|
|
784
|
+
className: "border-b border-border/30 transition-colors last:border-b-0 hover:bg-muted/20",
|
|
785
|
+
children: columns.map((col) => /* @__PURE__ */ jsx8(
|
|
786
|
+
"td",
|
|
787
|
+
{
|
|
788
|
+
className: "px-3 py-2 align-top text-foreground/85",
|
|
789
|
+
children: formatCell(row[col.key])
|
|
790
|
+
},
|
|
791
|
+
col.key
|
|
792
|
+
))
|
|
793
|
+
},
|
|
794
|
+
i
|
|
795
|
+
)) })
|
|
796
|
+
] }) }) });
|
|
797
|
+
};
|
|
798
|
+
function deriveColumns(rows) {
|
|
799
|
+
const seen = /* @__PURE__ */ new Set();
|
|
800
|
+
const cols = [];
|
|
801
|
+
for (const row of rows) {
|
|
802
|
+
for (const key of Object.keys(row)) {
|
|
803
|
+
if (!seen.has(key)) {
|
|
804
|
+
seen.add(key);
|
|
805
|
+
cols.push({ key });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return cols;
|
|
810
|
+
}
|
|
811
|
+
function formatCell(value) {
|
|
812
|
+
if (value === null || value === void 0) return "";
|
|
813
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
814
|
+
return String(value);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/artifacts/ui/types.ts
|
|
818
|
+
function isUiBinding(value) {
|
|
819
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.$bind === "string";
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/artifacts/ui/state.ts
|
|
823
|
+
function uiStateReducer(state, action) {
|
|
824
|
+
switch (action.type) {
|
|
825
|
+
case "set":
|
|
826
|
+
return setPath(state, action.path, action.value);
|
|
827
|
+
case "toggle": {
|
|
828
|
+
const current = getPath(state, action.path);
|
|
829
|
+
return setPath(state, action.path, !current);
|
|
830
|
+
}
|
|
831
|
+
case "replace":
|
|
832
|
+
return action.state;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function getPath(state, path) {
|
|
836
|
+
if (!path) return void 0;
|
|
837
|
+
const parts = path.split(".");
|
|
838
|
+
let cursor = state;
|
|
839
|
+
for (const part of parts) {
|
|
840
|
+
if (typeof cursor !== "object" || cursor === null) return void 0;
|
|
841
|
+
cursor = cursor[part];
|
|
842
|
+
}
|
|
843
|
+
return cursor;
|
|
844
|
+
}
|
|
845
|
+
function setPath(state, path, value) {
|
|
846
|
+
if (!path) return state;
|
|
847
|
+
const parts = path.split(".");
|
|
848
|
+
const next = { ...state };
|
|
849
|
+
let cursor = next;
|
|
850
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
851
|
+
const key = parts[i];
|
|
852
|
+
const child = cursor[key];
|
|
853
|
+
const cloned = typeof child === "object" && child !== null && !Array.isArray(child) ? { ...child } : {};
|
|
854
|
+
cursor[key] = cloned;
|
|
855
|
+
cursor = cloned;
|
|
856
|
+
}
|
|
857
|
+
cursor[parts[parts.length - 1]] = value;
|
|
858
|
+
return next;
|
|
859
|
+
}
|
|
860
|
+
function resolveBindable(value, state) {
|
|
861
|
+
if (isUiBinding(value)) {
|
|
862
|
+
return getPath(state, value.$bind);
|
|
863
|
+
}
|
|
864
|
+
return value;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/artifacts/ui/registry.tsx
|
|
868
|
+
import {
|
|
869
|
+
createContext,
|
|
870
|
+
useContext
|
|
871
|
+
} from "react";
|
|
872
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
873
|
+
var UiStateContext = createContext({});
|
|
874
|
+
var UiDispatchContext = createContext(() => {
|
|
875
|
+
});
|
|
876
|
+
var UiStateProvider = ({ state, dispatch, children }) => /* @__PURE__ */ jsx9(UiStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx9(UiDispatchContext.Provider, { value: dispatch, children }) });
|
|
877
|
+
function useUiState() {
|
|
878
|
+
return useContext(UiStateContext);
|
|
879
|
+
}
|
|
880
|
+
function useUiDispatch() {
|
|
881
|
+
return useContext(UiDispatchContext);
|
|
882
|
+
}
|
|
883
|
+
var UiEventContext = createContext(
|
|
884
|
+
null
|
|
885
|
+
);
|
|
886
|
+
var UiEventProvider = ({ onEvent, children }) => /* @__PURE__ */ jsx9(UiEventContext.Provider, { value: onEvent, children });
|
|
887
|
+
function useUiEventEmitter() {
|
|
888
|
+
return useContext(UiEventContext);
|
|
889
|
+
}
|
|
890
|
+
var UiCustomNodeRegistryContext = createContext({});
|
|
891
|
+
var UiCustomNodeRegistryProvider = ({ renderers, children }) => /* @__PURE__ */ jsx9(UiCustomNodeRegistryContext.Provider, { value: renderers, children });
|
|
892
|
+
function useUiCustomNodeRegistry() {
|
|
893
|
+
return useContext(UiCustomNodeRegistryContext);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/artifacts/ui/nodes.tsx
|
|
897
|
+
import { useCallback as useCallback2 } from "react";
|
|
898
|
+
import { motion } from "motion/react";
|
|
899
|
+
import { useThreadRuntime as useThreadRuntime2 } from "@assistant-ui/react";
|
|
900
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
901
|
+
var UiNodeView = ({ node }) => {
|
|
902
|
+
switch (node.kind) {
|
|
903
|
+
case "box":
|
|
904
|
+
return /* @__PURE__ */ jsx10(BoxNode, { node });
|
|
905
|
+
case "text":
|
|
906
|
+
return /* @__PURE__ */ jsx10(TextNode, { node });
|
|
907
|
+
case "heading":
|
|
908
|
+
return /* @__PURE__ */ jsx10(HeadingNode, { node });
|
|
909
|
+
case "badge":
|
|
910
|
+
return /* @__PURE__ */ jsx10(BadgeNode, { node });
|
|
911
|
+
case "button":
|
|
912
|
+
return /* @__PURE__ */ jsx10(ButtonNode, { node });
|
|
913
|
+
case "toggle":
|
|
914
|
+
return /* @__PURE__ */ jsx10(ToggleNode, { node });
|
|
915
|
+
case "slider":
|
|
916
|
+
return /* @__PURE__ */ jsx10(SliderNode, { node });
|
|
917
|
+
case "tooltip":
|
|
918
|
+
return /* @__PURE__ */ jsx10(TooltipNode, { node });
|
|
919
|
+
case "draggable":
|
|
920
|
+
return /* @__PURE__ */ jsx10(DraggableNode, { node });
|
|
921
|
+
case "custom":
|
|
922
|
+
return /* @__PURE__ */ jsx10(CustomNode, { node });
|
|
923
|
+
default:
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
function useActionRunner() {
|
|
928
|
+
const state = useUiState();
|
|
929
|
+
const dispatch = useUiDispatch();
|
|
930
|
+
const runtime = useThreadRuntime2();
|
|
931
|
+
const emit = useUiEventEmitter();
|
|
932
|
+
return useCallback2(
|
|
933
|
+
(actions) => {
|
|
934
|
+
if (!actions) return;
|
|
935
|
+
const list = Array.isArray(actions) ? actions : [actions];
|
|
936
|
+
for (const action of list) {
|
|
937
|
+
switch (action.kind) {
|
|
938
|
+
case "message": {
|
|
939
|
+
const text = resolveBindable(action.text, state);
|
|
940
|
+
if (typeof text === "string" && text.length > 0) {
|
|
941
|
+
runtime?.append({
|
|
942
|
+
role: "user",
|
|
943
|
+
content: [{ type: "text", text }]
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
case "set": {
|
|
949
|
+
const value = resolveBindable(action.value, state);
|
|
950
|
+
dispatch({ type: "set", path: action.path, value });
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case "toggle": {
|
|
954
|
+
dispatch({ type: "toggle", path: action.path });
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
case "emit": {
|
|
958
|
+
emit?.({ name: action.name, payload: action.payload });
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
[state, dispatch, runtime, emit]
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
var ALIGN_CLS = {
|
|
968
|
+
start: "items-start",
|
|
969
|
+
center: "items-center",
|
|
970
|
+
end: "items-end",
|
|
971
|
+
stretch: "items-stretch"
|
|
972
|
+
};
|
|
973
|
+
var JUSTIFY_CLS = {
|
|
974
|
+
start: "justify-start",
|
|
975
|
+
center: "justify-center",
|
|
976
|
+
end: "justify-end",
|
|
977
|
+
between: "justify-between",
|
|
978
|
+
around: "justify-around"
|
|
979
|
+
};
|
|
980
|
+
var BoxNode = ({ node }) => {
|
|
981
|
+
const dir = node.direction ?? "col";
|
|
982
|
+
return /* @__PURE__ */ jsx10(
|
|
983
|
+
"div",
|
|
984
|
+
{
|
|
985
|
+
className: cn(
|
|
986
|
+
"aui-ui-box flex",
|
|
987
|
+
dir === "col" ? "flex-col" : "flex-row",
|
|
988
|
+
node.wrap && "flex-wrap",
|
|
989
|
+
node.align && ALIGN_CLS[node.align],
|
|
990
|
+
node.justify && JUSTIFY_CLS[node.justify],
|
|
991
|
+
node.className
|
|
992
|
+
),
|
|
993
|
+
style: {
|
|
994
|
+
gap: node.gap !== void 0 ? `${node.gap * 0.25}rem` : void 0,
|
|
995
|
+
padding: node.padding !== void 0 ? `${node.padding * 0.25}rem` : void 0
|
|
996
|
+
},
|
|
997
|
+
children: node.children?.map((child, i) => /* @__PURE__ */ jsx10(UiNodeView, { node: child }, child.id ?? i))
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
};
|
|
1001
|
+
var TEXT_SIZE = {
|
|
1002
|
+
xs: "text-xs",
|
|
1003
|
+
sm: "text-sm",
|
|
1004
|
+
base: "text-base",
|
|
1005
|
+
lg: "text-lg"
|
|
1006
|
+
};
|
|
1007
|
+
var TEXT_WEIGHT = {
|
|
1008
|
+
normal: "font-normal",
|
|
1009
|
+
medium: "font-medium",
|
|
1010
|
+
semibold: "font-semibold",
|
|
1011
|
+
bold: "font-bold"
|
|
1012
|
+
};
|
|
1013
|
+
var TextNode = ({ node }) => {
|
|
1014
|
+
const state = useUiState();
|
|
1015
|
+
const value = resolveBindable(node.value, state);
|
|
1016
|
+
return /* @__PURE__ */ jsx10(
|
|
1017
|
+
"span",
|
|
1018
|
+
{
|
|
1019
|
+
className: cn(
|
|
1020
|
+
"aui-ui-text",
|
|
1021
|
+
node.muted && "text-muted-foreground",
|
|
1022
|
+
node.size && TEXT_SIZE[node.size],
|
|
1023
|
+
node.weight && TEXT_WEIGHT[node.weight],
|
|
1024
|
+
node.className
|
|
1025
|
+
),
|
|
1026
|
+
children: value === void 0 || value === null ? "" : String(value)
|
|
1027
|
+
}
|
|
1028
|
+
);
|
|
1029
|
+
};
|
|
1030
|
+
var HEADING_CLS = {
|
|
1031
|
+
1: "text-2xl",
|
|
1032
|
+
2: "text-xl",
|
|
1033
|
+
3: "text-lg",
|
|
1034
|
+
4: "text-base"
|
|
1035
|
+
};
|
|
1036
|
+
var HeadingNode = ({ node }) => {
|
|
1037
|
+
const state = useUiState();
|
|
1038
|
+
const value = String(resolveBindable(node.value, state) ?? "");
|
|
1039
|
+
const level = node.level ?? 2;
|
|
1040
|
+
const cls = cn(
|
|
1041
|
+
"aui-ui-heading font-semibold text-foreground",
|
|
1042
|
+
HEADING_CLS[level],
|
|
1043
|
+
node.className
|
|
1044
|
+
);
|
|
1045
|
+
switch (level) {
|
|
1046
|
+
case 1:
|
|
1047
|
+
return /* @__PURE__ */ jsx10("h1", { className: cls, children: value });
|
|
1048
|
+
case 2:
|
|
1049
|
+
return /* @__PURE__ */ jsx10("h2", { className: cls, children: value });
|
|
1050
|
+
case 3:
|
|
1051
|
+
return /* @__PURE__ */ jsx10("h3", { className: cls, children: value });
|
|
1052
|
+
case 4:
|
|
1053
|
+
return /* @__PURE__ */ jsx10("h4", { className: cls, children: value });
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
var BADGE_TONE = {
|
|
1057
|
+
default: "bg-muted text-foreground",
|
|
1058
|
+
primary: "bg-primary/10 text-primary",
|
|
1059
|
+
success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
|
1060
|
+
warn: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
1061
|
+
danger: "bg-destructive/10 text-destructive"
|
|
1062
|
+
};
|
|
1063
|
+
var BadgeNode = ({ node }) => {
|
|
1064
|
+
const state = useUiState();
|
|
1065
|
+
const value = String(resolveBindable(node.value, state) ?? "");
|
|
1066
|
+
return /* @__PURE__ */ jsx10(
|
|
1067
|
+
"span",
|
|
1068
|
+
{
|
|
1069
|
+
className: cn(
|
|
1070
|
+
"aui-ui-badge inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
1071
|
+
BADGE_TONE[node.tone ?? "default"],
|
|
1072
|
+
node.className
|
|
1073
|
+
),
|
|
1074
|
+
children: value
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
};
|
|
1078
|
+
var ButtonNode = ({ node }) => {
|
|
1079
|
+
const state = useUiState();
|
|
1080
|
+
const run = useActionRunner();
|
|
1081
|
+
const label = String(resolveBindable(node.label, state) ?? "");
|
|
1082
|
+
const disabled = node.disabled !== void 0 ? Boolean(resolveBindable(node.disabled, state)) : false;
|
|
1083
|
+
return /* @__PURE__ */ jsx10(
|
|
1084
|
+
Button,
|
|
1085
|
+
{
|
|
1086
|
+
variant: node.variant ?? "default",
|
|
1087
|
+
size: node.size ?? "default",
|
|
1088
|
+
disabled,
|
|
1089
|
+
className: node.className,
|
|
1090
|
+
onClick: () => run(node.onClick),
|
|
1091
|
+
children: label
|
|
1092
|
+
}
|
|
1093
|
+
);
|
|
1094
|
+
};
|
|
1095
|
+
var ToggleNode = ({ node }) => {
|
|
1096
|
+
const state = useUiState();
|
|
1097
|
+
const dispatch = useUiDispatch();
|
|
1098
|
+
const run = useActionRunner();
|
|
1099
|
+
const value = Boolean(getPath(state, node.binding));
|
|
1100
|
+
const label = node.label ? String(resolveBindable(node.label, state) ?? "") : null;
|
|
1101
|
+
const onToggle = () => {
|
|
1102
|
+
dispatch({ type: "toggle", path: node.binding });
|
|
1103
|
+
run(node.onChange);
|
|
1104
|
+
};
|
|
1105
|
+
return /* @__PURE__ */ jsxs7(
|
|
1106
|
+
"label",
|
|
1107
|
+
{
|
|
1108
|
+
className: cn(
|
|
1109
|
+
"aui-ui-toggle inline-flex cursor-pointer items-center gap-2 text-sm select-none",
|
|
1110
|
+
node.className
|
|
1111
|
+
),
|
|
1112
|
+
children: [
|
|
1113
|
+
/* @__PURE__ */ jsx10(
|
|
1114
|
+
"button",
|
|
1115
|
+
{
|
|
1116
|
+
type: "button",
|
|
1117
|
+
role: "switch",
|
|
1118
|
+
"aria-checked": value,
|
|
1119
|
+
onClick: onToggle,
|
|
1120
|
+
className: cn(
|
|
1121
|
+
"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-[background,box-shadow,border-color] duration-200",
|
|
1122
|
+
value ? "border-foreground/15 bg-gradient-to-b from-primary-fill-from to-primary-fill-to shadow-card" : cn(TIMBAL_V2_SWITCH_TRACK_OFF, "hover:from-secondary-fill-hover-from hover:to-secondary-fill-hover-to")
|
|
1123
|
+
),
|
|
1124
|
+
children: /* @__PURE__ */ jsx10(
|
|
1125
|
+
"span",
|
|
1126
|
+
{
|
|
1127
|
+
className: cn(
|
|
1128
|
+
"inline-block size-4 transform rounded-full transition-transform",
|
|
1129
|
+
TIMBAL_V2_SWITCH_THUMB,
|
|
1130
|
+
value ? "translate-x-4" : "translate-x-0.5"
|
|
1131
|
+
),
|
|
1132
|
+
"aria-hidden": true
|
|
1133
|
+
}
|
|
1134
|
+
)
|
|
1135
|
+
}
|
|
1136
|
+
),
|
|
1137
|
+
label && /* @__PURE__ */ jsx10("span", { className: "text-foreground/85", children: label })
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
);
|
|
1141
|
+
};
|
|
1142
|
+
var SliderNode = ({ node }) => {
|
|
1143
|
+
const state = useUiState();
|
|
1144
|
+
const dispatch = useUiDispatch();
|
|
1145
|
+
const run = useActionRunner();
|
|
1146
|
+
const min = node.min ?? 0;
|
|
1147
|
+
const max = node.max ?? 100;
|
|
1148
|
+
const step = node.step ?? 1;
|
|
1149
|
+
const raw = getPath(state, node.binding);
|
|
1150
|
+
const value = typeof raw === "number" ? raw : min;
|
|
1151
|
+
const showValue = node.showValue ?? true;
|
|
1152
|
+
const label = node.label ? String(resolveBindable(node.label, state) ?? "") : null;
|
|
1153
|
+
const onChange = (e) => {
|
|
1154
|
+
const next = Number(e.target.value);
|
|
1155
|
+
dispatch({ type: "set", path: node.binding, value: next });
|
|
1156
|
+
};
|
|
1157
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("aui-ui-slider flex flex-col gap-1", node.className), children: [
|
|
1158
|
+
(label || showValue) && /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
1159
|
+
label && /* @__PURE__ */ jsx10("span", { children: label }),
|
|
1160
|
+
showValue && /* @__PURE__ */ jsx10("span", { className: "font-mono", children: value })
|
|
1161
|
+
] }),
|
|
1162
|
+
/* @__PURE__ */ jsx10(
|
|
1163
|
+
"input",
|
|
1164
|
+
{
|
|
1165
|
+
type: "range",
|
|
1166
|
+
min,
|
|
1167
|
+
max,
|
|
1168
|
+
step,
|
|
1169
|
+
value,
|
|
1170
|
+
onChange,
|
|
1171
|
+
onMouseUp: () => run(node.onChange),
|
|
1172
|
+
onKeyUp: (e) => {
|
|
1173
|
+
if (e.key === "Enter" || e.key === " ") run(node.onChange);
|
|
1174
|
+
},
|
|
1175
|
+
onTouchEnd: () => run(node.onChange),
|
|
1176
|
+
className: "aui-ui-slider-input h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary"
|
|
1177
|
+
}
|
|
1178
|
+
)
|
|
1179
|
+
] });
|
|
1180
|
+
};
|
|
1181
|
+
var TooltipNode = ({ node }) => {
|
|
1182
|
+
const state = useUiState();
|
|
1183
|
+
const content = String(resolveBindable(node.content, state) ?? "");
|
|
1184
|
+
return /* @__PURE__ */ jsx10(TooltipProvider, { children: /* @__PURE__ */ jsxs7(Tooltip, { children: [
|
|
1185
|
+
/* @__PURE__ */ jsx10(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx10("span", { className: cn("aui-ui-tooltip-trigger inline-flex", node.className), children: /* @__PURE__ */ jsx10(UiNodeView, { node: node.child }) }) }),
|
|
1186
|
+
/* @__PURE__ */ jsx10(TooltipContent, { side: node.side ?? "top", children: content })
|
|
1187
|
+
] }) });
|
|
1188
|
+
};
|
|
1189
|
+
var DraggableNode = ({ node }) => {
|
|
1190
|
+
const run = useActionRunner();
|
|
1191
|
+
const snapBack = node.snapBack ?? true;
|
|
1192
|
+
const axis = node.axis ?? "both";
|
|
1193
|
+
const dragProp = axis === "both" ? true : axis;
|
|
1194
|
+
return /* @__PURE__ */ jsx10(
|
|
1195
|
+
motion.div,
|
|
1196
|
+
{
|
|
1197
|
+
drag: dragProp,
|
|
1198
|
+
dragMomentum: false,
|
|
1199
|
+
dragSnapToOrigin: snapBack,
|
|
1200
|
+
whileDrag: { scale: 1.02, cursor: "grabbing" },
|
|
1201
|
+
onDragEnd: () => run(node.onDragEnd),
|
|
1202
|
+
className: cn(
|
|
1203
|
+
"aui-ui-draggable inline-block cursor-grab touch-none",
|
|
1204
|
+
node.className
|
|
1205
|
+
),
|
|
1206
|
+
children: /* @__PURE__ */ jsx10(UiNodeView, { node: node.child })
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
};
|
|
1210
|
+
var CustomNode = ({ node }) => {
|
|
1211
|
+
const state = useUiState();
|
|
1212
|
+
const registry = useUiCustomNodeRegistry();
|
|
1213
|
+
const Renderer = registry[node.name];
|
|
1214
|
+
if (!Renderer) return null;
|
|
1215
|
+
const resolvedProps = resolveProps(node.props ?? {}, state);
|
|
1216
|
+
const children = node.children?.map((child, i) => /* @__PURE__ */ jsx10(UiNodeView, { node: child }, child.id ?? i));
|
|
1217
|
+
return /* @__PURE__ */ jsx10(Renderer, { props: resolvedProps, children });
|
|
1218
|
+
};
|
|
1219
|
+
function resolveProps(props, state) {
|
|
1220
|
+
const out = {};
|
|
1221
|
+
for (const [k, v] of Object.entries(props)) {
|
|
1222
|
+
out[k] = resolveBindable(v, state);
|
|
1223
|
+
}
|
|
1224
|
+
return out;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/artifacts/ui/ui-artifact.tsx
|
|
1228
|
+
import { useReducer } from "react";
|
|
1229
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1230
|
+
var UiArtifactView = ({ artifact }) => {
|
|
1231
|
+
const [state, dispatch] = useReducer(
|
|
1232
|
+
uiStateReducer,
|
|
1233
|
+
artifact.initialState ?? {}
|
|
1234
|
+
);
|
|
1235
|
+
return /* @__PURE__ */ jsx11(ArtifactCard, { title: artifact.title, kind: "ui", children: /* @__PURE__ */ jsx11(UiStateProvider, { state, dispatch, children: /* @__PURE__ */ jsx11("div", { className: "aui-ui-root p-3", children: /* @__PURE__ */ jsx11(UiNodeView, { node: artifact.root }) }) }) });
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
// src/artifacts/registry.tsx
|
|
1239
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
1240
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1241
|
+
var defaultArtifactRenderers = {
|
|
1242
|
+
chart: ChartArtifactView,
|
|
1243
|
+
question: QuestionArtifactView,
|
|
1244
|
+
html: HtmlArtifactView,
|
|
1245
|
+
json: JsonArtifactView,
|
|
1246
|
+
table: TableArtifactView,
|
|
1247
|
+
ui: UiArtifactView
|
|
1248
|
+
};
|
|
1249
|
+
var ArtifactRegistryContext = createContext2(
|
|
1250
|
+
defaultArtifactRenderers
|
|
1251
|
+
);
|
|
1252
|
+
var ArtifactRegistryProvider = ({ renderers, override, children }) => {
|
|
1253
|
+
const merged = useMemo2(() => {
|
|
1254
|
+
if (!renderers) return defaultArtifactRenderers;
|
|
1255
|
+
if (override) return renderers;
|
|
1256
|
+
return { ...defaultArtifactRenderers, ...renderers };
|
|
1257
|
+
}, [renderers, override]);
|
|
1258
|
+
return /* @__PURE__ */ jsx12(ArtifactRegistryContext.Provider, { value: merged, children });
|
|
1259
|
+
};
|
|
1260
|
+
function useArtifactRegistry() {
|
|
1261
|
+
return useContext2(ArtifactRegistryContext);
|
|
1262
|
+
}
|
|
1263
|
+
var ArtifactView = ({ artifact }) => {
|
|
1264
|
+
const registry = useArtifactRegistry();
|
|
1265
|
+
const Renderer = registry[artifact.type] ?? registry.json;
|
|
1266
|
+
if (!Renderer) return null;
|
|
1267
|
+
return /* @__PURE__ */ jsx12(Renderer, { artifact });
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/artifacts/types.ts
|
|
1271
|
+
function isArtifact(value) {
|
|
1272
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string";
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/artifacts/parse.ts
|
|
1276
|
+
var ARTIFACT_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
|
|
1277
|
+
"timbal-artifact",
|
|
1278
|
+
"timbal"
|
|
1279
|
+
]);
|
|
1280
|
+
function isArtifactFenceLanguage(language) {
|
|
1281
|
+
return typeof language === "string" && ARTIFACT_FENCE_LANGUAGES.has(language);
|
|
1282
|
+
}
|
|
1283
|
+
function tryParseStructuredText(text) {
|
|
1284
|
+
const trimmed = text.trim();
|
|
1285
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
1286
|
+
try {
|
|
1287
|
+
return JSON.parse(trimmed);
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
const asJson = trimmed.replace(/\bTrue\b/g, "true").replace(/\bFalse\b/g, "false").replace(/\bNone\b/g, "null").replace(/'/g, '"');
|
|
1292
|
+
return JSON.parse(asJson);
|
|
1293
|
+
} catch {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
function parseArtifactFromToolResult(result) {
|
|
1298
|
+
if (result === void 0 || result === null) return null;
|
|
1299
|
+
if (typeof result === "string") {
|
|
1300
|
+
const parsed = tryParseStructuredText(result);
|
|
1301
|
+
if (parsed === null) return null;
|
|
1302
|
+
return parseArtifactFromToolResult(parsed);
|
|
1303
|
+
}
|
|
1304
|
+
if (Array.isArray(result)) {
|
|
1305
|
+
for (const item of result) {
|
|
1306
|
+
if (typeof item === "object" && item !== null && "text" in item) {
|
|
1307
|
+
const text = item.text;
|
|
1308
|
+
if (typeof text === "string") {
|
|
1309
|
+
const fromText = parseArtifactFromToolResult(text);
|
|
1310
|
+
if (fromText) return fromText;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
if (typeof result === "object") {
|
|
1317
|
+
const obj = result;
|
|
1318
|
+
if (obj.type === "text" && typeof obj.text === "string") {
|
|
1319
|
+
return parseArtifactFromToolResult(obj.text);
|
|
1320
|
+
}
|
|
1321
|
+
if (obj.type === "thinking" && typeof obj.thinking === "string") {
|
|
1322
|
+
return parseArtifactFromToolResult(obj.thinking);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return isArtifact(result) ? result : null;
|
|
1326
|
+
}
|
|
1327
|
+
var FENCE_RE = /```(?:timbal-artifact|timbal)\s*\n([\s\S]*?)\n```/g;
|
|
1328
|
+
function findMarkdownArtifacts(markdown) {
|
|
1329
|
+
const matches = [];
|
|
1330
|
+
FENCE_RE.lastIndex = 0;
|
|
1331
|
+
let m;
|
|
1332
|
+
while ((m = FENCE_RE.exec(markdown)) !== null) {
|
|
1333
|
+
const raw = m[0];
|
|
1334
|
+
const body = m[1];
|
|
1335
|
+
try {
|
|
1336
|
+
const parsed = JSON.parse(body);
|
|
1337
|
+
if (isArtifact(parsed)) {
|
|
1338
|
+
matches.push({
|
|
1339
|
+
artifact: parsed,
|
|
1340
|
+
raw,
|
|
1341
|
+
start: m.index,
|
|
1342
|
+
end: m.index + raw.length
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return matches;
|
|
1349
|
+
}
|
|
1350
|
+
function splitMarkdownByArtifacts(markdown) {
|
|
1351
|
+
const matches = findMarkdownArtifacts(markdown);
|
|
1352
|
+
if (matches.length === 0) return [{ kind: "text", text: markdown }];
|
|
1353
|
+
const segments = [];
|
|
1354
|
+
let cursor = 0;
|
|
1355
|
+
for (const match of matches) {
|
|
1356
|
+
if (match.start > cursor) {
|
|
1357
|
+
segments.push({ kind: "text", text: markdown.slice(cursor, match.start) });
|
|
1358
|
+
}
|
|
1359
|
+
segments.push({ kind: "artifact", artifact: match.artifact });
|
|
1360
|
+
cursor = match.end;
|
|
1361
|
+
}
|
|
1362
|
+
if (cursor < markdown.length) {
|
|
1363
|
+
segments.push({ kind: "text", text: markdown.slice(cursor) });
|
|
1364
|
+
}
|
|
1365
|
+
return segments;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/chat/markdown-text.tsx
|
|
1369
|
+
import "@assistant-ui/react-markdown/styles/dot.css";
|
|
1370
|
+
import "katex/dist/katex.min.css";
|
|
1371
|
+
import {
|
|
1372
|
+
MarkdownTextPrimitive,
|
|
1373
|
+
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
|
|
1374
|
+
useIsMarkdownCodeBlock
|
|
1375
|
+
} from "@assistant-ui/react-markdown";
|
|
1376
|
+
import remarkGfm from "remark-gfm";
|
|
1377
|
+
import remarkMath from "remark-math";
|
|
1378
|
+
import rehypeKatex from "rehype-katex";
|
|
1379
|
+
import { memo, useState as useState4 } from "react";
|
|
1380
|
+
import { CheckIcon as CheckIcon2, CopyIcon } from "lucide-react";
|
|
1381
|
+
|
|
1382
|
+
// src/chat/syntax-highlighter.tsx
|
|
1383
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
1384
|
+
import { createHighlighterCore } from "shiki/core";
|
|
1385
|
+
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
|
|
1386
|
+
import langJavascript from "shiki/langs/javascript.mjs";
|
|
1387
|
+
import langTypescript from "shiki/langs/typescript.mjs";
|
|
1388
|
+
import langPython from "shiki/langs/python.mjs";
|
|
1389
|
+
import langHtml from "shiki/langs/html.mjs";
|
|
1390
|
+
import langCss from "shiki/langs/css.mjs";
|
|
1391
|
+
import langJson from "shiki/langs/json.mjs";
|
|
1392
|
+
import langBash from "shiki/langs/bash.mjs";
|
|
1393
|
+
import langMarkdown from "shiki/langs/markdown.mjs";
|
|
1394
|
+
import langJsx from "shiki/langs/jsx.mjs";
|
|
1395
|
+
import langTsx from "shiki/langs/tsx.mjs";
|
|
1396
|
+
import langSql from "shiki/langs/sql.mjs";
|
|
1397
|
+
import langYaml from "shiki/langs/yaml.mjs";
|
|
1398
|
+
import langRust from "shiki/langs/rust.mjs";
|
|
1399
|
+
import langGo from "shiki/langs/go.mjs";
|
|
1400
|
+
import langJava from "shiki/langs/java.mjs";
|
|
1401
|
+
import langC from "shiki/langs/c.mjs";
|
|
1402
|
+
import langCpp from "shiki/langs/cpp.mjs";
|
|
1403
|
+
import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
|
|
1404
|
+
import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
|
|
1405
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1406
|
+
var SHIKI_THEME_DARK = "vitesse-dark";
|
|
1407
|
+
var SHIKI_THEME_LIGHT = "vitesse-light";
|
|
1408
|
+
var highlighterPromise = null;
|
|
1409
|
+
function getHighlighter() {
|
|
1410
|
+
if (!highlighterPromise) {
|
|
1411
|
+
highlighterPromise = createHighlighterCore({
|
|
1412
|
+
themes: [themeVitesseDark, themeVitesseLight],
|
|
1413
|
+
langs: [
|
|
1414
|
+
langJavascript,
|
|
1415
|
+
langTypescript,
|
|
1416
|
+
langPython,
|
|
1417
|
+
langHtml,
|
|
1418
|
+
langCss,
|
|
1419
|
+
langJson,
|
|
1420
|
+
langBash,
|
|
1421
|
+
langMarkdown,
|
|
1422
|
+
langJsx,
|
|
1423
|
+
langTsx,
|
|
1424
|
+
langSql,
|
|
1425
|
+
langYaml,
|
|
1426
|
+
langRust,
|
|
1427
|
+
langGo,
|
|
1428
|
+
langJava,
|
|
1429
|
+
langC,
|
|
1430
|
+
langCpp
|
|
1431
|
+
],
|
|
1432
|
+
engine: createJavaScriptRegexEngine()
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
return highlighterPromise;
|
|
1436
|
+
}
|
|
1437
|
+
getHighlighter();
|
|
1438
|
+
var ShikiSyntaxHighlighter = ({
|
|
1439
|
+
components: { Pre, Code: Code2 },
|
|
1440
|
+
language,
|
|
1441
|
+
code
|
|
1442
|
+
}) => {
|
|
1443
|
+
const [html, setHtml] = useState3(null);
|
|
1444
|
+
useEffect2(() => {
|
|
1445
|
+
let cancelled = false;
|
|
1446
|
+
(async () => {
|
|
1447
|
+
try {
|
|
1448
|
+
const highlighter = await getHighlighter();
|
|
1449
|
+
const loadedLangs = highlighter.getLoadedLanguages();
|
|
1450
|
+
if (!loadedLangs.includes(language)) {
|
|
1451
|
+
if (!cancelled) setHtml(null);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const result = highlighter.codeToHtml(code, {
|
|
1455
|
+
lang: language,
|
|
1456
|
+
themes: {
|
|
1457
|
+
dark: SHIKI_THEME_DARK,
|
|
1458
|
+
light: SHIKI_THEME_LIGHT
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
if (!cancelled) setHtml(result);
|
|
1462
|
+
} catch {
|
|
1463
|
+
if (!cancelled) setHtml(null);
|
|
1464
|
+
}
|
|
1465
|
+
})();
|
|
1466
|
+
return () => {
|
|
1467
|
+
cancelled = true;
|
|
1468
|
+
};
|
|
1469
|
+
}, [code, language]);
|
|
1470
|
+
if (isArtifactFenceLanguage(language)) {
|
|
1471
|
+
try {
|
|
1472
|
+
const parsed = JSON.parse(code);
|
|
1473
|
+
if (isArtifact(parsed)) {
|
|
1474
|
+
return /* @__PURE__ */ jsx13(ArtifactView, { artifact: parsed });
|
|
1475
|
+
}
|
|
1476
|
+
} catch {
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (html) {
|
|
1480
|
+
return /* @__PURE__ */ jsx13(
|
|
1481
|
+
"div",
|
|
1482
|
+
{
|
|
1483
|
+
className: "shiki-wrapper [&>pre]:!m-0 [&>pre]:!rounded-t-none [&>pre]:!rounded-b-lg [&>pre]:!border [&>pre]:!border-t-0 [&>pre]:!border-border/50 [&>pre]:!p-3 [&>pre]:!text-xs [&>pre]:!leading-relaxed [&>pre]:overflow-x-auto",
|
|
1484
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
1485
|
+
}
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
return /* @__PURE__ */ jsx13(Pre, { children: /* @__PURE__ */ jsx13(Code2, { children: code }) });
|
|
1489
|
+
};
|
|
1490
|
+
var syntax_highlighter_default = ShikiSyntaxHighlighter;
|
|
1491
|
+
|
|
1492
|
+
// src/chat/markdown-text.tsx
|
|
1493
|
+
import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1494
|
+
var MarkdownTextImpl = () => {
|
|
1495
|
+
return /* @__PURE__ */ jsx14(
|
|
1496
|
+
MarkdownTextPrimitive,
|
|
1497
|
+
{
|
|
1498
|
+
remarkPlugins: [remarkGfm, remarkMath],
|
|
1499
|
+
rehypePlugins: [rehypeKatex],
|
|
1500
|
+
className: "aui-md",
|
|
1501
|
+
components: {
|
|
1502
|
+
...defaultComponents,
|
|
1503
|
+
SyntaxHighlighter: syntax_highlighter_default
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
);
|
|
1507
|
+
};
|
|
1508
|
+
var MarkdownText = memo(MarkdownTextImpl);
|
|
1509
|
+
var CodeHeader = ({ language, code }) => {
|
|
1510
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
1511
|
+
if (isArtifactFenceLanguage(language)) return null;
|
|
1512
|
+
const onCopy = () => {
|
|
1513
|
+
if (!code || isCopied) return;
|
|
1514
|
+
copyToClipboard(code);
|
|
1515
|
+
};
|
|
1516
|
+
return /* @__PURE__ */ jsxs8("div", { className: "aui-code-header flex items-center justify-between rounded-t-lg border border-b-0 border-border/50 bg-code-header-bg px-4 py-2", children: [
|
|
1517
|
+
/* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
|
|
1518
|
+
/* @__PURE__ */ jsx14("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
|
|
1519
|
+
language
|
|
1520
|
+
] }),
|
|
1521
|
+
/* @__PURE__ */ jsxs8(
|
|
1522
|
+
TooltipIconButton,
|
|
1523
|
+
{
|
|
1524
|
+
tooltip: isCopied ? "Copied!" : "Copy",
|
|
1525
|
+
onClick: onCopy,
|
|
1526
|
+
className: "transition-colors hover:text-foreground",
|
|
1527
|
+
children: [
|
|
1528
|
+
!isCopied && /* @__PURE__ */ jsx14(CopyIcon, { className: "h-3.5 w-3.5" }),
|
|
1529
|
+
isCopied && /* @__PURE__ */ jsx14(CheckIcon2, { className: "h-3.5 w-3.5 text-emerald-500" })
|
|
1530
|
+
]
|
|
1531
|
+
}
|
|
1532
|
+
)
|
|
1533
|
+
] });
|
|
1534
|
+
};
|
|
1535
|
+
var useCopyToClipboard = ({
|
|
1536
|
+
copiedDuration = 3e3
|
|
1537
|
+
} = {}) => {
|
|
1538
|
+
const [isCopied, setIsCopied] = useState4(false);
|
|
1539
|
+
const copyToClipboard = (value) => {
|
|
1540
|
+
if (!value) return;
|
|
1541
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
1542
|
+
setIsCopied(true);
|
|
1543
|
+
setTimeout(() => setIsCopied(false), copiedDuration);
|
|
1544
|
+
});
|
|
1545
|
+
};
|
|
1546
|
+
return { isCopied, copyToClipboard };
|
|
1547
|
+
};
|
|
1548
|
+
var defaultComponents = memoizeMarkdownComponents({
|
|
1549
|
+
h1: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1550
|
+
"h1",
|
|
1551
|
+
{
|
|
1552
|
+
className: cn(
|
|
1553
|
+
"aui-md-h1 mb-3 mt-6 scroll-m-20 text-xl font-bold tracking-tight first:mt-0 last:mb-0",
|
|
1554
|
+
className
|
|
1555
|
+
),
|
|
1556
|
+
...props
|
|
1557
|
+
}
|
|
1558
|
+
),
|
|
1559
|
+
h2: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1560
|
+
"h2",
|
|
1561
|
+
{
|
|
1562
|
+
className: cn(
|
|
1563
|
+
"aui-md-h2 mb-2.5 mt-5 scroll-m-20 border-b border-border/30 pb-1.5 text-lg font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
1564
|
+
className
|
|
1565
|
+
),
|
|
1566
|
+
...props
|
|
1567
|
+
}
|
|
1568
|
+
),
|
|
1569
|
+
h3: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1570
|
+
"h3",
|
|
1571
|
+
{
|
|
1572
|
+
className: cn(
|
|
1573
|
+
"aui-md-h3 mb-2 mt-4 scroll-m-20 text-base font-semibold tracking-tight first:mt-0 last:mb-0",
|
|
1574
|
+
className
|
|
1575
|
+
),
|
|
1576
|
+
...props
|
|
1577
|
+
}
|
|
1578
|
+
),
|
|
1579
|
+
h4: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1580
|
+
"h4",
|
|
1581
|
+
{
|
|
1582
|
+
className: cn(
|
|
1583
|
+
"aui-md-h4 mb-1.5 mt-3 scroll-m-20 text-sm font-semibold first:mt-0 last:mb-0",
|
|
1584
|
+
className
|
|
1585
|
+
),
|
|
1586
|
+
...props
|
|
1587
|
+
}
|
|
1588
|
+
),
|
|
1589
|
+
h5: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1590
|
+
"h5",
|
|
1591
|
+
{
|
|
1592
|
+
className: cn(
|
|
1593
|
+
"aui-md-h5 mb-1 mt-2.5 text-sm font-medium first:mt-0 last:mb-0",
|
|
1594
|
+
className
|
|
1595
|
+
),
|
|
1596
|
+
...props
|
|
1597
|
+
}
|
|
1598
|
+
),
|
|
1599
|
+
h6: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1600
|
+
"h6",
|
|
1601
|
+
{
|
|
1602
|
+
className: cn(
|
|
1603
|
+
"aui-md-h6 mb-1 mt-2 text-sm font-medium text-muted-foreground first:mt-0 last:mb-0",
|
|
1604
|
+
className
|
|
1605
|
+
),
|
|
1606
|
+
...props
|
|
1607
|
+
}
|
|
1608
|
+
),
|
|
1609
|
+
p: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1610
|
+
"p",
|
|
1611
|
+
{
|
|
1612
|
+
className: cn(
|
|
1613
|
+
"aui-md-p my-3 leading-[1.7] first:mt-0 last:mb-0",
|
|
1614
|
+
className
|
|
1615
|
+
),
|
|
1616
|
+
...props
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1619
|
+
a: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1620
|
+
"a",
|
|
1621
|
+
{
|
|
1622
|
+
className: cn(
|
|
1623
|
+
"aui-md-a font-medium text-primary underline decoration-primary/30 underline-offset-[3px] transition-colors hover:decoration-primary/80",
|
|
1624
|
+
className
|
|
1625
|
+
),
|
|
1626
|
+
target: "_blank",
|
|
1627
|
+
rel: "noopener noreferrer",
|
|
1628
|
+
...props
|
|
1629
|
+
}
|
|
1630
|
+
),
|
|
1631
|
+
blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1632
|
+
"blockquote",
|
|
1633
|
+
{
|
|
1634
|
+
className: cn(
|
|
1635
|
+
"aui-md-blockquote my-3 border-l-[3px] border-primary/30 bg-muted/30 py-1 pl-4 pr-2 text-muted-foreground italic [&>p]:my-1",
|
|
1636
|
+
className
|
|
1637
|
+
),
|
|
1638
|
+
...props
|
|
1639
|
+
}
|
|
1640
|
+
),
|
|
1641
|
+
ul: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1642
|
+
"ul",
|
|
1643
|
+
{
|
|
1644
|
+
className: cn(
|
|
1645
|
+
"aui-md-ul my-3 ml-1 list-none space-y-1.5 [&>li]:relative [&>li]:pl-5 [&>li]:before:absolute [&>li]:before:left-0 [&>li]:before:top-[0.6em] [&>li]:before:h-1.5 [&>li]:before:w-1.5 [&>li]:before:rounded-full [&>li]:before:bg-primary/30 [&>li]:before:content-['']",
|
|
1646
|
+
className
|
|
1647
|
+
),
|
|
1648
|
+
...props
|
|
1649
|
+
}
|
|
1650
|
+
),
|
|
1651
|
+
ol: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1652
|
+
"ol",
|
|
1653
|
+
{
|
|
1654
|
+
className: cn(
|
|
1655
|
+
"aui-md-ol my-3 ml-1 list-none space-y-1.5 [counter-reset:list-counter] [&>li]:relative [&>li]:pl-7 [&>li]:[counter-increment:list-counter] [&>li]:before:absolute [&>li]:before:left-0 [&>li]:before:top-0 [&>li]:before:flex [&>li]:before:h-[1.7em] [&>li]:before:w-5 [&>li]:before:items-center [&>li]:before:justify-center [&>li]:before:rounded-md [&>li]:before:bg-primary/[0.07] [&>li]:before:text-xs [&>li]:before:font-semibold [&>li]:before:text-primary/60 [&>li]:before:content-[counter(list-counter)]",
|
|
1656
|
+
className
|
|
1657
|
+
),
|
|
1658
|
+
...props
|
|
1659
|
+
}
|
|
1660
|
+
),
|
|
1661
|
+
hr: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1662
|
+
"hr",
|
|
1663
|
+
{
|
|
1664
|
+
className: cn(
|
|
1665
|
+
"aui-md-hr my-6 border-none h-px bg-gradient-to-r from-transparent via-border to-transparent",
|
|
1666
|
+
className
|
|
1667
|
+
),
|
|
1668
|
+
...props
|
|
1669
|
+
}
|
|
1670
|
+
),
|
|
1671
|
+
table: ({ className, ...props }) => /* @__PURE__ */ jsx14("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx14(
|
|
1672
|
+
"table",
|
|
1673
|
+
{
|
|
1674
|
+
className: cn("aui-md-table w-full border-collapse text-sm", className),
|
|
1675
|
+
...props
|
|
1676
|
+
}
|
|
1677
|
+
) }),
|
|
1678
|
+
th: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1679
|
+
"th",
|
|
1680
|
+
{
|
|
1681
|
+
className: cn(
|
|
1682
|
+
"aui-md-th border-b border-border/50 bg-muted/60 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground [[align=center]]:text-center [[align=right]]:text-right",
|
|
1683
|
+
className
|
|
1684
|
+
),
|
|
1685
|
+
...props
|
|
1686
|
+
}
|
|
1687
|
+
),
|
|
1688
|
+
td: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1689
|
+
"td",
|
|
1690
|
+
{
|
|
1691
|
+
className: cn(
|
|
1692
|
+
"aui-md-td border-b border-border/30 px-3 py-2 [[align=center]]:text-center [[align=right]]:text-right",
|
|
1693
|
+
className
|
|
1694
|
+
),
|
|
1695
|
+
...props
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
tr: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1699
|
+
"tr",
|
|
1700
|
+
{
|
|
1701
|
+
className: cn(
|
|
1702
|
+
"aui-md-tr transition-colors hover:bg-muted/30 [&:last-child>td]:border-b-0",
|
|
1703
|
+
className
|
|
1704
|
+
),
|
|
1705
|
+
...props
|
|
1706
|
+
}
|
|
1707
|
+
),
|
|
1708
|
+
li: ({ className, ...props }) => /* @__PURE__ */ jsx14("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
|
|
1709
|
+
sup: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1710
|
+
"sup",
|
|
1711
|
+
{
|
|
1712
|
+
className: cn(
|
|
1713
|
+
"aui-md-sup [&>a]:text-[0.7em] [&>a]:font-semibold [&>a]:text-primary/70 [&>a]:no-underline [&>a]:transition-colors [&>a]:hover:text-primary",
|
|
1714
|
+
className
|
|
1715
|
+
),
|
|
1716
|
+
...props
|
|
1717
|
+
}
|
|
1718
|
+
),
|
|
1719
|
+
pre: ({ className, ...props }) => /* @__PURE__ */ jsx14(
|
|
1720
|
+
"pre",
|
|
1721
|
+
{
|
|
1722
|
+
className: cn(
|
|
1723
|
+
"aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 border-border/50 bg-code-block-bg p-4 text-[13px] leading-relaxed",
|
|
1724
|
+
className
|
|
1725
|
+
),
|
|
1726
|
+
...props
|
|
1727
|
+
}
|
|
1728
|
+
),
|
|
1729
|
+
code: function Code({ className, ...props }) {
|
|
1730
|
+
const isCodeBlock = useIsMarkdownCodeBlock();
|
|
1731
|
+
return /* @__PURE__ */ jsx14(
|
|
1732
|
+
"code",
|
|
1733
|
+
{
|
|
1734
|
+
className: cn(
|
|
1735
|
+
!isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90",
|
|
1736
|
+
className
|
|
1737
|
+
),
|
|
1738
|
+
...props
|
|
1739
|
+
}
|
|
1740
|
+
);
|
|
1741
|
+
},
|
|
1742
|
+
strong: ({ className, ...props }) => /* @__PURE__ */ jsx14("strong", { className: cn("font-semibold text-foreground", className), ...props }),
|
|
1743
|
+
em: ({ className, ...props }) => /* @__PURE__ */ jsx14("em", { className: cn("italic", className), ...props }),
|
|
1744
|
+
CodeHeader
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
// src/auth/tokens.ts
|
|
1748
|
+
var ACCESS_TOKEN_KEY = "timbal_project_access_token";
|
|
1749
|
+
var REFRESH_TOKEN_KEY = "timbal_project_refresh_token";
|
|
1750
|
+
var getAccessToken = () => localStorage.getItem(ACCESS_TOKEN_KEY);
|
|
1751
|
+
var setAccessToken = (token) => localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
|
1752
|
+
var getRefreshToken = () => localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
1753
|
+
var setRefreshToken = (token) => localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
1754
|
+
var clearTokens = () => {
|
|
1755
|
+
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
1756
|
+
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
1757
|
+
};
|
|
1758
|
+
var refreshPromise = null;
|
|
1759
|
+
var refreshAccessToken = async () => {
|
|
1760
|
+
const refreshToken = getRefreshToken();
|
|
1761
|
+
if (!refreshToken) return false;
|
|
1762
|
+
if (refreshPromise) return refreshPromise;
|
|
1763
|
+
refreshPromise = (async () => {
|
|
1764
|
+
try {
|
|
1765
|
+
const res = await fetch("/api/auth/refresh", {
|
|
1766
|
+
method: "POST",
|
|
1767
|
+
headers: { "Content-Type": "application/json" },
|
|
1768
|
+
body: JSON.stringify({ refresh_token: refreshToken })
|
|
1769
|
+
});
|
|
1770
|
+
if (!res.ok) {
|
|
1771
|
+
clearTokens();
|
|
1772
|
+
return false;
|
|
1773
|
+
}
|
|
1774
|
+
const data = await res.json();
|
|
1775
|
+
if (data.access_token) {
|
|
1776
|
+
localStorage.setItem(ACCESS_TOKEN_KEY, data.access_token);
|
|
1777
|
+
}
|
|
1778
|
+
if (data.refresh_token) {
|
|
1779
|
+
localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token);
|
|
1780
|
+
}
|
|
1781
|
+
return true;
|
|
1782
|
+
} catch {
|
|
1783
|
+
clearTokens();
|
|
1784
|
+
return false;
|
|
1785
|
+
} finally {
|
|
1786
|
+
refreshPromise = null;
|
|
1787
|
+
}
|
|
1788
|
+
})();
|
|
1789
|
+
return refreshPromise;
|
|
1790
|
+
};
|
|
1791
|
+
var authFetch = async (url, options) => {
|
|
1792
|
+
const token = getAccessToken();
|
|
1793
|
+
let res = await fetch(url, {
|
|
1794
|
+
...options,
|
|
1795
|
+
headers: {
|
|
1796
|
+
...options?.headers,
|
|
1797
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
if (res.status === 401 && getRefreshToken()) {
|
|
1801
|
+
const refreshed = await refreshAccessToken();
|
|
1802
|
+
if (refreshed) {
|
|
1803
|
+
const newToken = getAccessToken();
|
|
1804
|
+
res = await fetch(url, {
|
|
1805
|
+
...options,
|
|
1806
|
+
headers: {
|
|
1807
|
+
...options?.headers,
|
|
1808
|
+
...newToken ? { Authorization: `Bearer ${newToken}` } : {}
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return res;
|
|
1814
|
+
};
|
|
1815
|
+
var fetchCurrentUser = async () => {
|
|
1816
|
+
try {
|
|
1817
|
+
const token = getAccessToken();
|
|
1818
|
+
const res = await fetch("/api/me", {
|
|
1819
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
1820
|
+
});
|
|
1821
|
+
if (!res.ok) return null;
|
|
1822
|
+
return await res.json();
|
|
1823
|
+
} catch {
|
|
1824
|
+
return null;
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1828
|
+
// src/runtime/upload-adapter.ts
|
|
1829
|
+
var DEFAULT_UPLOAD_ACCEPT = "image/*,application/pdf,text/*,.md,.json,.csv,.tsv,.xlsx,.docx";
|
|
1830
|
+
function createDefaultAttachmentAdapter({
|
|
1831
|
+
baseUrl = "",
|
|
1832
|
+
uploadUrl,
|
|
1833
|
+
fetch: fetchFn = authFetch,
|
|
1834
|
+
accept = DEFAULT_UPLOAD_ACCEPT
|
|
1835
|
+
} = {}) {
|
|
1836
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
1837
|
+
const resolvedUploadUrl = uploadUrl ?? `${base}/files/upload`;
|
|
1838
|
+
return {
|
|
1839
|
+
accept,
|
|
1840
|
+
async add({ file }) {
|
|
1841
|
+
const isImage = file.type.startsWith("image/");
|
|
1842
|
+
const pending = {
|
|
1843
|
+
id: crypto.randomUUID(),
|
|
1844
|
+
type: isImage ? "image" : "file",
|
|
1845
|
+
name: file.name,
|
|
1846
|
+
contentType: file.type || "application/octet-stream",
|
|
1847
|
+
file,
|
|
1848
|
+
status: { type: "requires-action", reason: "composer-send" }
|
|
1849
|
+
};
|
|
1850
|
+
return pending;
|
|
1851
|
+
},
|
|
1852
|
+
async send(attachment) {
|
|
1853
|
+
const fd = new FormData();
|
|
1854
|
+
fd.append("file", attachment.file);
|
|
1855
|
+
const res = await fetchFn(resolvedUploadUrl, { method: "POST", body: fd });
|
|
1856
|
+
if (!res.ok) {
|
|
1857
|
+
const detail = await res.text().catch(() => "");
|
|
1858
|
+
throw new Error(
|
|
1859
|
+
`Attachment upload failed (${res.status}): ${detail || res.statusText}`
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
const remoteUrl = await readUploadedUrl(res);
|
|
1863
|
+
const mime = attachment.contentType ?? "application/octet-stream";
|
|
1864
|
+
const filename = attachment.name;
|
|
1865
|
+
const complete = {
|
|
1866
|
+
...attachment,
|
|
1867
|
+
status: { type: "complete" },
|
|
1868
|
+
content: mime.startsWith("image/") ? [{ type: "image", image: remoteUrl, filename }] : [{ type: "file", data: remoteUrl, mimeType: mime, filename }]
|
|
1869
|
+
};
|
|
1870
|
+
return complete;
|
|
1871
|
+
},
|
|
1872
|
+
async remove() {
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
var createUploadAttachmentAdapter = createDefaultAttachmentAdapter;
|
|
1877
|
+
async function readUploadedUrl(res) {
|
|
1878
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1879
|
+
if (contentType.includes("application/json")) {
|
|
1880
|
+
const data = await res.json();
|
|
1881
|
+
const raw = data.url ?? data.signed_url ?? data.id;
|
|
1882
|
+
const candidate = typeof raw === "string" ? raw : typeof raw === "number" ? String(raw) : "";
|
|
1883
|
+
if (candidate.length > 0) {
|
|
1884
|
+
return candidate;
|
|
1885
|
+
}
|
|
1886
|
+
throw new Error(
|
|
1887
|
+
"Attachment upload response did not include a `url`, `signed_url`, or `id` field."
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
const text = (await res.text()).trim();
|
|
1891
|
+
if (!text) {
|
|
1892
|
+
throw new Error("Attachment upload response was empty.");
|
|
1893
|
+
}
|
|
1894
|
+
return text;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// src/runtime/resolve-attachments.ts
|
|
1898
|
+
function isAttachmentAdapter(value) {
|
|
1899
|
+
return typeof value === "object" && value !== null && "accept" in value && typeof value.add === "function" && typeof value.send === "function" && typeof value.remove === "function";
|
|
1900
|
+
}
|
|
1901
|
+
function resolveAttachmentAdapter(attachments, options = {}) {
|
|
1902
|
+
const baseUrl = options.baseUrl ?? "/api";
|
|
1903
|
+
const legacyUploadUrl = options.uploadUrl;
|
|
1904
|
+
const legacyAccept = options.accept;
|
|
1905
|
+
if (attachments === null) return void 0;
|
|
1906
|
+
const legacyEnables = legacyUploadUrl !== void 0 || legacyAccept !== void 0;
|
|
1907
|
+
if (attachments === void 0) {
|
|
1908
|
+
if (!legacyEnables) return void 0;
|
|
1909
|
+
return createDefaultAttachmentAdapter({
|
|
1910
|
+
baseUrl,
|
|
1911
|
+
fetch: options.fetch,
|
|
1912
|
+
uploadUrl: legacyUploadUrl,
|
|
1913
|
+
accept: legacyAccept
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
if (attachments === true) {
|
|
1917
|
+
return createDefaultAttachmentAdapter({
|
|
1918
|
+
baseUrl,
|
|
1919
|
+
fetch: options.fetch,
|
|
1920
|
+
uploadUrl: legacyUploadUrl,
|
|
1921
|
+
accept: legacyAccept
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
if (isAttachmentAdapter(attachments)) return attachments;
|
|
1925
|
+
const config = attachments;
|
|
1926
|
+
return createDefaultAttachmentAdapter({
|
|
1927
|
+
baseUrl,
|
|
1928
|
+
fetch: options.fetch,
|
|
1929
|
+
uploadUrl: config.uploadUrl ?? legacyUploadUrl,
|
|
1930
|
+
accept: config.accept ?? legacyAccept
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/runtime/provider.tsx
|
|
1935
|
+
import {
|
|
1936
|
+
createContext as createContext4,
|
|
1937
|
+
useCallback as useCallback3,
|
|
1938
|
+
useContext as useContext4,
|
|
1939
|
+
useEffect as useEffect3,
|
|
1940
|
+
useMemo as useMemo3,
|
|
1941
|
+
useRef,
|
|
1942
|
+
useState as useState5
|
|
1943
|
+
} from "react";
|
|
1944
|
+
import {
|
|
1945
|
+
useExternalStoreRuntime,
|
|
1946
|
+
AssistantRuntimeProvider
|
|
1947
|
+
} from "@assistant-ui/react";
|
|
1948
|
+
import { parseSSELine } from "@timbal-ai/timbal-sdk";
|
|
1949
|
+
|
|
1950
|
+
// src/runtime/reducer.ts
|
|
1951
|
+
function createReducerState() {
|
|
1952
|
+
return { parts: [], toolIndexById: /* @__PURE__ */ new Map() };
|
|
1953
|
+
}
|
|
1954
|
+
function reduceSseEvent(state, event) {
|
|
1955
|
+
switch (event.type) {
|
|
1956
|
+
case "DELTA":
|
|
1957
|
+
return reduceDelta(state, event.item);
|
|
1958
|
+
case "OUTPUT": {
|
|
1959
|
+
const path = event.path;
|
|
1960
|
+
const isNested = typeof path === "string" && path.includes(".");
|
|
1961
|
+
if (!isNested) {
|
|
1962
|
+
const errorMessage = readErrorMessage(event);
|
|
1963
|
+
if (errorMessage) {
|
|
1964
|
+
state.parts.push({ type: "text", text: `**Error:** ${errorMessage}` });
|
|
1965
|
+
return true;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (isNested) {
|
|
1969
|
+
return reduceNestedOutput(
|
|
1970
|
+
state,
|
|
1971
|
+
path,
|
|
1972
|
+
event.output
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
return reduceOutput(
|
|
1976
|
+
state,
|
|
1977
|
+
event.output
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
default:
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
function reduceDelta(state, item) {
|
|
1985
|
+
if (!item) return false;
|
|
1986
|
+
if (item.type === "text_delta" && typeof item.text_delta === "string") {
|
|
1987
|
+
lastTextPart(state).text += item.text_delta;
|
|
1988
|
+
return true;
|
|
1989
|
+
}
|
|
1990
|
+
if (item.type === "thinking_delta" && typeof item.thinking_delta === "string") {
|
|
1991
|
+
lastThinkingPart(state).text += item.thinking_delta;
|
|
1992
|
+
return true;
|
|
1993
|
+
}
|
|
1994
|
+
if (item.type === "tool_use") {
|
|
1995
|
+
const toolCallId = item.id || `tool-${crypto.randomUUID()}`;
|
|
1996
|
+
const inputStr = stringifyInput(item.input);
|
|
1997
|
+
const part = {
|
|
1998
|
+
type: "tool-call",
|
|
1999
|
+
toolCallId,
|
|
2000
|
+
toolName: item.name || "unknown",
|
|
2001
|
+
argsText: inputStr,
|
|
2002
|
+
status: "running"
|
|
2003
|
+
};
|
|
2004
|
+
state.parts.push(part);
|
|
2005
|
+
state.toolIndexById.set(toolCallId, state.parts.length - 1);
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
if (item.type === "tool_use_delta") {
|
|
2009
|
+
const idx = state.toolIndexById.get(item.id);
|
|
2010
|
+
if (idx !== void 0 && typeof item.input_delta === "string") {
|
|
2011
|
+
state.parts[idx].argsText += item.input_delta;
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
return false;
|
|
2016
|
+
}
|
|
2017
|
+
function reduceNestedOutput(state, path, output) {
|
|
2018
|
+
if (!output || typeof output !== "object") return false;
|
|
2019
|
+
if (!Array.isArray(output.content) && isArtifact(output)) {
|
|
2020
|
+
const toolName = toolNameFromPath(path);
|
|
2021
|
+
if (toolName && attachToolResult(state, { toolName, result: output })) {
|
|
2022
|
+
return true;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
return reduceOutput(state, output, { toolResultsOnly: true, allowOrphan: false });
|
|
2026
|
+
}
|
|
2027
|
+
function reduceOutput(state, output, options) {
|
|
2028
|
+
if (!output) return false;
|
|
2029
|
+
if (typeof output === "string") {
|
|
2030
|
+
if (state.parts.length === 0) {
|
|
2031
|
+
state.parts.push({ type: "text", text: output });
|
|
2032
|
+
return true;
|
|
2033
|
+
}
|
|
2034
|
+
return false;
|
|
2035
|
+
}
|
|
2036
|
+
if (Array.isArray(output.content)) {
|
|
2037
|
+
let changed = false;
|
|
2038
|
+
const blocks = output.content;
|
|
2039
|
+
for (const block of blocks) {
|
|
2040
|
+
if (block.type === "tool_use") {
|
|
2041
|
+
if (!options?.toolResultsOnly && recordToolUse(state, block)) {
|
|
2042
|
+
changed = true;
|
|
2043
|
+
}
|
|
2044
|
+
} else if (block.type === "tool_result") {
|
|
2045
|
+
if (recordToolResult(state, block, options)) changed = true;
|
|
2046
|
+
} else if (!options?.toolResultsOnly) {
|
|
2047
|
+
if (block.type === "text" && typeof block.text === "string" && !lastTextPart(state).text) {
|
|
2048
|
+
lastTextPart(state).text = block.text;
|
|
2049
|
+
changed = true;
|
|
2050
|
+
} else if (block.type === "thinking" && typeof block.thinking === "string" && !lastThinkingPart(state).text) {
|
|
2051
|
+
lastThinkingPart(state).text = block.thinking;
|
|
2052
|
+
changed = true;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
return changed;
|
|
2057
|
+
}
|
|
2058
|
+
if (state.parts.length === 0) {
|
|
2059
|
+
state.parts.push({ type: "text", text: JSON.stringify(output) });
|
|
2060
|
+
return true;
|
|
2061
|
+
}
|
|
2062
|
+
return false;
|
|
2063
|
+
}
|
|
2064
|
+
function recordToolUse(state, block) {
|
|
2065
|
+
const id = block.id || `tool-${crypto.randomUUID()}`;
|
|
2066
|
+
if (state.toolIndexById.has(id)) return false;
|
|
2067
|
+
const inputStr = stringifyInput(block.input);
|
|
2068
|
+
const part = {
|
|
2069
|
+
type: "tool-call",
|
|
2070
|
+
toolCallId: id,
|
|
2071
|
+
toolName: block.name || "unknown",
|
|
2072
|
+
argsText: inputStr,
|
|
2073
|
+
status: "running"
|
|
2074
|
+
};
|
|
2075
|
+
state.parts.push(part);
|
|
2076
|
+
state.toolIndexById.set(id, state.parts.length - 1);
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
function recordToolResult(state, block, options) {
|
|
2080
|
+
const allowOrphan = options?.allowOrphan !== false;
|
|
2081
|
+
const id = block.id || block.tool_use_id || "";
|
|
2082
|
+
const { result, resultText } = parseToolResultContent(block.content);
|
|
2083
|
+
const toolName = block.name || void 0;
|
|
2084
|
+
if (id) {
|
|
2085
|
+
const idx = state.toolIndexById.get(id);
|
|
2086
|
+
if (idx !== void 0) {
|
|
2087
|
+
const part2 = state.parts[idx];
|
|
2088
|
+
part2.result = result;
|
|
2089
|
+
if (resultText) part2.resultText = resultText;
|
|
2090
|
+
part2.status = "complete";
|
|
2091
|
+
return true;
|
|
2092
|
+
}
|
|
2093
|
+
if (!allowOrphan) return false;
|
|
2094
|
+
}
|
|
2095
|
+
if (!id && toolName && attachToolResult(state, {
|
|
2096
|
+
toolName,
|
|
2097
|
+
result,
|
|
2098
|
+
resultText
|
|
2099
|
+
})) {
|
|
2100
|
+
return true;
|
|
2101
|
+
}
|
|
2102
|
+
if (!id || !allowOrphan) return false;
|
|
2103
|
+
const part = {
|
|
2104
|
+
type: "tool-call",
|
|
2105
|
+
toolCallId: id,
|
|
2106
|
+
toolName: toolName || "unknown",
|
|
2107
|
+
argsText: "",
|
|
2108
|
+
result,
|
|
2109
|
+
resultText,
|
|
2110
|
+
status: "complete"
|
|
2111
|
+
};
|
|
2112
|
+
state.parts.push(part);
|
|
2113
|
+
state.toolIndexById.set(id, state.parts.length - 1);
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
function toolNameFromPath(path) {
|
|
2117
|
+
const segment = path.split(".").pop();
|
|
2118
|
+
return segment && segment !== "agent" && segment !== "llm" ? segment : null;
|
|
2119
|
+
}
|
|
2120
|
+
function attachToolResult(state, {
|
|
2121
|
+
toolCallId,
|
|
2122
|
+
toolName,
|
|
2123
|
+
result,
|
|
2124
|
+
resultText
|
|
2125
|
+
}) {
|
|
2126
|
+
if (toolCallId) {
|
|
2127
|
+
const idx = state.toolIndexById.get(toolCallId);
|
|
2128
|
+
if (idx !== void 0) {
|
|
2129
|
+
const part = state.parts[idx];
|
|
2130
|
+
part.result = result;
|
|
2131
|
+
if (resultText) part.resultText = resultText;
|
|
2132
|
+
part.status = "complete";
|
|
2133
|
+
return true;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
if (toolName) {
|
|
2137
|
+
for (let i = state.parts.length - 1; i >= 0; i--) {
|
|
2138
|
+
const part = state.parts[i];
|
|
2139
|
+
if (part.type === "tool-call" && part.toolName === toolName && part.result === void 0) {
|
|
2140
|
+
part.result = result;
|
|
2141
|
+
if (resultText) part.resultText = resultText;
|
|
2142
|
+
part.status = "complete";
|
|
2143
|
+
return true;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
return false;
|
|
2148
|
+
}
|
|
2149
|
+
function parseToolResultContent(content) {
|
|
2150
|
+
if (typeof content === "string") {
|
|
2151
|
+
return { result: content, resultText: content };
|
|
2152
|
+
}
|
|
2153
|
+
if (!Array.isArray(content)) {
|
|
2154
|
+
return { result: content };
|
|
2155
|
+
}
|
|
2156
|
+
const textChunks = [];
|
|
2157
|
+
for (const item of content) {
|
|
2158
|
+
if (typeof item === "string") {
|
|
2159
|
+
textChunks.push(item);
|
|
2160
|
+
} else if (item && typeof item === "object") {
|
|
2161
|
+
const obj = item;
|
|
2162
|
+
if (obj.type === "text" && typeof obj.text === "string") {
|
|
2163
|
+
textChunks.push(obj.text);
|
|
2164
|
+
} else if (obj.type === "thinking" && typeof obj.thinking === "string") {
|
|
2165
|
+
textChunks.push(obj.thinking);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return {
|
|
2170
|
+
result: content,
|
|
2171
|
+
resultText: textChunks.length > 0 ? textChunks.join("\n") : void 0
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
function readErrorMessage(event) {
|
|
2175
|
+
const status = event.status;
|
|
2176
|
+
const isErrorStatus = status?.code === "error";
|
|
2177
|
+
const error = event.error;
|
|
2178
|
+
let type = null;
|
|
2179
|
+
let message = null;
|
|
2180
|
+
if (isErrorStatus && typeof status?.message === "string" && status.message.length > 0) {
|
|
2181
|
+
message = status.message;
|
|
2182
|
+
}
|
|
2183
|
+
if (!message && typeof error === "string" && error.length > 0) {
|
|
2184
|
+
message = error;
|
|
2185
|
+
} else if (error && typeof error === "object") {
|
|
2186
|
+
const obj = error;
|
|
2187
|
+
if (typeof obj.type === "string" && obj.type.length > 0) {
|
|
2188
|
+
type = obj.type;
|
|
2189
|
+
}
|
|
2190
|
+
if (!message && typeof obj.message === "string" && obj.message.length > 0) {
|
|
2191
|
+
message = obj.message;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
if (!message && !isErrorStatus) return null;
|
|
2195
|
+
if (!message) return "The agent failed to generate a response.";
|
|
2196
|
+
const compact = compactError(message);
|
|
2197
|
+
return type ? `${type}: ${compact}` : compact;
|
|
2198
|
+
}
|
|
2199
|
+
var ERROR_MAX_CHARS = 480;
|
|
2200
|
+
function compactError(message) {
|
|
2201
|
+
const trimmed = message.split(/\n\s*Traceback \(most recent call last\):/u)[0].trim();
|
|
2202
|
+
if (trimmed.length <= ERROR_MAX_CHARS) return trimmed;
|
|
2203
|
+
return `${trimmed.slice(0, ERROR_MAX_CHARS).trimEnd()}\u2026`;
|
|
2204
|
+
}
|
|
2205
|
+
function stringifyInput(input) {
|
|
2206
|
+
if (input === void 0 || input === null) return "{}";
|
|
2207
|
+
return typeof input === "string" ? input : JSON.stringify(input);
|
|
2208
|
+
}
|
|
2209
|
+
function lastTextPart(state) {
|
|
2210
|
+
const last = state.parts[state.parts.length - 1];
|
|
2211
|
+
if (last?.type === "text") return last;
|
|
2212
|
+
const next = { type: "text", text: "" };
|
|
2213
|
+
state.parts.push(next);
|
|
2214
|
+
return next;
|
|
2215
|
+
}
|
|
2216
|
+
function lastThinkingPart(state) {
|
|
2217
|
+
const last = state.parts[state.parts.length - 1];
|
|
2218
|
+
if (last?.type === "thinking") return last;
|
|
2219
|
+
const next = { type: "thinking", text: "" };
|
|
2220
|
+
state.parts.push(next);
|
|
2221
|
+
return next;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
// src/runtime/attachments.ts
|
|
2225
|
+
async function extractAttachment(attachment) {
|
|
2226
|
+
const file = attachment.file;
|
|
2227
|
+
let src = null;
|
|
2228
|
+
let contentType;
|
|
2229
|
+
let name = attachment.name ?? file?.name;
|
|
2230
|
+
const content = attachment.content;
|
|
2231
|
+
if (content) {
|
|
2232
|
+
for (const block of content) {
|
|
2233
|
+
if (block.type === "image" && typeof block.image === "string") {
|
|
2234
|
+
src = block.image;
|
|
2235
|
+
if (typeof block.mimeType === "string") {
|
|
2236
|
+
contentType = block.mimeType;
|
|
2237
|
+
}
|
|
2238
|
+
break;
|
|
2239
|
+
}
|
|
2240
|
+
if (block.type === "file" && typeof block.data === "string") {
|
|
2241
|
+
src = block.data;
|
|
2242
|
+
if (typeof block.mimeType === "string") contentType = block.mimeType;
|
|
2243
|
+
break;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (src === null && file) {
|
|
2248
|
+
src = await fileToDataUrl(file);
|
|
2249
|
+
if (!contentType) contentType = file.type || void 0;
|
|
2250
|
+
if (!name) name = file.name;
|
|
2251
|
+
}
|
|
2252
|
+
if (!src) return null;
|
|
2253
|
+
if (!contentType) contentType = mimeFromDataUrl(src);
|
|
2254
|
+
const rawType = String(attachment.type ?? "file");
|
|
2255
|
+
const type = rawType === "image" || rawType === "document" ? rawType : "file";
|
|
2256
|
+
return {
|
|
2257
|
+
id: attachment.id ?? crypto.randomUUID(),
|
|
2258
|
+
type,
|
|
2259
|
+
...name !== void 0 ? { name } : {},
|
|
2260
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
2261
|
+
dataUrl: src
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
function fileToDataUrl(file) {
|
|
2265
|
+
return new Promise((resolve, reject) => {
|
|
2266
|
+
const reader = new FileReader();
|
|
2267
|
+
reader.onload = () => resolve(reader.result);
|
|
2268
|
+
reader.onerror = () => reject(reader.error);
|
|
2269
|
+
reader.readAsDataURL(file);
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
function mimeFromDataUrl(dataUrl) {
|
|
2273
|
+
const match = /^data:([^;,]+)[;,]/.exec(dataUrl);
|
|
2274
|
+
return match?.[1];
|
|
2275
|
+
}
|
|
2276
|
+
function buildPromptBody({
|
|
2277
|
+
input,
|
|
2278
|
+
attachments,
|
|
2279
|
+
parentId
|
|
2280
|
+
}) {
|
|
2281
|
+
const context = { parent_id: parentId };
|
|
2282
|
+
const files = attachments ?? [];
|
|
2283
|
+
if (files.length === 0) {
|
|
2284
|
+
return { prompt: input, context };
|
|
2285
|
+
}
|
|
2286
|
+
const parts = [];
|
|
2287
|
+
if (input) parts.push({ type: "text", text: input });
|
|
2288
|
+
for (const attachment of files) {
|
|
2289
|
+
parts.push({ type: "file", file: attachment.dataUrl });
|
|
2290
|
+
}
|
|
2291
|
+
return { prompt: parts, context };
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// src/runtime/attachments-context.tsx
|
|
2295
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
2296
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
2297
|
+
var TimbalAttachmentsEnabledContext = createContext3(false);
|
|
2298
|
+
function TimbalAttachmentsEnabledProvider({
|
|
2299
|
+
enabled,
|
|
2300
|
+
children
|
|
2301
|
+
}) {
|
|
2302
|
+
return /* @__PURE__ */ jsx15(TimbalAttachmentsEnabledContext.Provider, { value: enabled, children });
|
|
2303
|
+
}
|
|
2304
|
+
function useTimbalAttachmentsEnabled() {
|
|
2305
|
+
return useContext3(TimbalAttachmentsEnabledContext);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/runtime/provider.tsx
|
|
2309
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2310
|
+
function projectAttachment(attachment) {
|
|
2311
|
+
const filename = attachment.name ?? "attachment";
|
|
2312
|
+
const mimeType = attachment.contentType ?? "application/octet-stream";
|
|
2313
|
+
if (attachment.type === "image") {
|
|
2314
|
+
return {
|
|
2315
|
+
id: attachment.id,
|
|
2316
|
+
type: "image",
|
|
2317
|
+
name: filename,
|
|
2318
|
+
contentType: mimeType,
|
|
2319
|
+
status: { type: "complete" },
|
|
2320
|
+
content: [{ type: "image", image: attachment.dataUrl, filename }]
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
return {
|
|
2324
|
+
id: attachment.id,
|
|
2325
|
+
type: attachment.type,
|
|
2326
|
+
name: filename,
|
|
2327
|
+
contentType: mimeType,
|
|
2328
|
+
status: { type: "complete" },
|
|
2329
|
+
content: [
|
|
2330
|
+
{ type: "file", data: attachment.dataUrl, mimeType, filename }
|
|
2331
|
+
]
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
var convertMessage = (message) => {
|
|
2335
|
+
const content = message.content.map((part) => {
|
|
2336
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
2337
|
+
if (part.type === "thinking") return { type: "reasoning", text: part.text };
|
|
2338
|
+
return {
|
|
2339
|
+
type: "tool-call",
|
|
2340
|
+
toolCallId: part.toolCallId,
|
|
2341
|
+
toolName: part.toolName,
|
|
2342
|
+
argsText: part.argsText,
|
|
2343
|
+
...part.result !== void 0 ? { result: part.result } : {}
|
|
2344
|
+
};
|
|
2345
|
+
});
|
|
2346
|
+
const attachments = message.attachments && message.attachments.length > 0 ? message.attachments.map(projectAttachment) : void 0;
|
|
2347
|
+
return {
|
|
2348
|
+
role: message.role,
|
|
2349
|
+
content,
|
|
2350
|
+
id: message.id,
|
|
2351
|
+
...attachments ? { attachments } : {}
|
|
2352
|
+
};
|
|
2353
|
+
};
|
|
2354
|
+
function findParentId(messages, beforeIndex) {
|
|
2355
|
+
const slice = beforeIndex !== void 0 ? messages.slice(0, beforeIndex) : messages;
|
|
2356
|
+
for (let i = slice.length - 1; i >= 0; i--) {
|
|
2357
|
+
if (slice[i].role === "assistant" && slice[i].runId) return slice[i].runId;
|
|
2358
|
+
}
|
|
2359
|
+
return null;
|
|
2360
|
+
}
|
|
2361
|
+
function getTextFromMessage(message) {
|
|
2362
|
+
const part = message.content.find((c) => c.type === "text");
|
|
2363
|
+
return part?.type === "text" ? part.text : "";
|
|
2364
|
+
}
|
|
2365
|
+
function getAttachmentsFromMessage(message) {
|
|
2366
|
+
return message.attachments?.length ? message.attachments : void 0;
|
|
2367
|
+
}
|
|
2368
|
+
function useTimbalStream({
|
|
2369
|
+
workforceId,
|
|
2370
|
+
baseUrl = "/api",
|
|
2371
|
+
fetch: fetchFn,
|
|
2372
|
+
debug = false
|
|
2373
|
+
}) {
|
|
2374
|
+
const [messages, setMessages] = useState5([]);
|
|
2375
|
+
const [isRunning, setIsRunning] = useState5(false);
|
|
2376
|
+
const abortRef = useRef(null);
|
|
2377
|
+
const messagesRef = useRef([]);
|
|
2378
|
+
const fetchFnRef = useRef(fetchFn ?? authFetch);
|
|
2379
|
+
useEffect3(() => {
|
|
2380
|
+
fetchFnRef.current = fetchFn ?? authFetch;
|
|
2381
|
+
}, [fetchFn]);
|
|
2382
|
+
const debugRef = useRef(debug);
|
|
2383
|
+
useEffect3(() => {
|
|
2384
|
+
debugRef.current = debug;
|
|
2385
|
+
}, [debug]);
|
|
2386
|
+
useEffect3(() => {
|
|
2387
|
+
messagesRef.current = messages;
|
|
2388
|
+
}, [messages]);
|
|
2389
|
+
const streamAssistantResponse = useCallback3(
|
|
2390
|
+
async (input, attachments, userId, assistantId, parentId, signal) => {
|
|
2391
|
+
const state = createReducerState();
|
|
2392
|
+
const flush = () => {
|
|
2393
|
+
setMessages(
|
|
2394
|
+
(prev) => prev.map(
|
|
2395
|
+
(m) => m.id === assistantId ? { ...m, content: [...state.parts] } : m
|
|
2396
|
+
)
|
|
2397
|
+
);
|
|
2398
|
+
};
|
|
2399
|
+
const stampRunId = (runId) => {
|
|
2400
|
+
setMessages(
|
|
2401
|
+
(prev) => prev.map(
|
|
2402
|
+
(m) => m.id === userId || m.id === assistantId ? { ...m, runId } : m
|
|
2403
|
+
)
|
|
2404
|
+
);
|
|
2405
|
+
};
|
|
2406
|
+
try {
|
|
2407
|
+
const body = buildPromptBody({ input, attachments, parentId });
|
|
2408
|
+
const res = await fetchFnRef.current(
|
|
2409
|
+
`${baseUrl}/workforce/${workforceId}/stream`,
|
|
2410
|
+
{
|
|
2411
|
+
method: "POST",
|
|
2412
|
+
headers: { "Content-Type": "application/json" },
|
|
2413
|
+
body: JSON.stringify(body),
|
|
2414
|
+
signal
|
|
2415
|
+
}
|
|
2416
|
+
);
|
|
2417
|
+
if (!res.ok || !res.body) throw new Error(`Request failed: ${res.status}`);
|
|
2418
|
+
const reader = res.body.getReader();
|
|
2419
|
+
const decoder = new TextDecoder();
|
|
2420
|
+
let buffer = "";
|
|
2421
|
+
let capturedRunId = null;
|
|
2422
|
+
while (true) {
|
|
2423
|
+
const { done, value } = await reader.read();
|
|
2424
|
+
if (done) break;
|
|
2425
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2426
|
+
const lines = buffer.split("\n");
|
|
2427
|
+
buffer = lines.pop() ?? "";
|
|
2428
|
+
for (const line of lines) {
|
|
2429
|
+
const event = parseSSELine(line);
|
|
2430
|
+
if (!event) continue;
|
|
2431
|
+
if (debugRef.current) {
|
|
2432
|
+
console.debug("[timbal]", event.type, event);
|
|
2433
|
+
}
|
|
2434
|
+
if (!capturedRunId) {
|
|
2435
|
+
const runId = readTopLevelStartRunId(event);
|
|
2436
|
+
if (runId) {
|
|
2437
|
+
capturedRunId = runId;
|
|
2438
|
+
stampRunId(runId);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
const changed = reduceSseEvent(state, event);
|
|
2442
|
+
if (changed) flush();
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
if (buffer.trim()) {
|
|
2446
|
+
const event = parseSSELine(buffer);
|
|
2447
|
+
if (event) {
|
|
2448
|
+
if (debugRef.current) {
|
|
2449
|
+
console.debug("[timbal]", event.type, event);
|
|
2450
|
+
}
|
|
2451
|
+
if (reduceSseEvent(state, event)) flush();
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
} catch (err) {
|
|
2455
|
+
if (err.name !== "AbortError") {
|
|
2456
|
+
if (state.parts.length === 0) {
|
|
2457
|
+
state.parts.push({ type: "text", text: "Something went wrong." });
|
|
2458
|
+
}
|
|
2459
|
+
flush();
|
|
2460
|
+
}
|
|
2461
|
+
} finally {
|
|
2462
|
+
setIsRunning(false);
|
|
2463
|
+
abortRef.current = null;
|
|
2464
|
+
}
|
|
2465
|
+
},
|
|
2466
|
+
[workforceId, baseUrl]
|
|
2467
|
+
);
|
|
2468
|
+
const send = useCallback3(
|
|
2469
|
+
async (input, options) => {
|
|
2470
|
+
const userId = crypto.randomUUID();
|
|
2471
|
+
const assistantId = crypto.randomUUID();
|
|
2472
|
+
const base = messagesRef.current;
|
|
2473
|
+
const parentId = options?.parentId !== void 0 ? options.parentId : findParentId(base);
|
|
2474
|
+
const userMessage = {
|
|
2475
|
+
id: userId,
|
|
2476
|
+
role: "user",
|
|
2477
|
+
content: input ? [{ type: "text", text: input }] : [],
|
|
2478
|
+
...options?.attachments && options.attachments.length > 0 ? { attachments: options.attachments } : {}
|
|
2479
|
+
};
|
|
2480
|
+
setMessages([
|
|
2481
|
+
...base,
|
|
2482
|
+
userMessage,
|
|
2483
|
+
{ id: assistantId, role: "assistant", content: [] }
|
|
2484
|
+
]);
|
|
2485
|
+
setIsRunning(true);
|
|
2486
|
+
const controller = new AbortController();
|
|
2487
|
+
abortRef.current = controller;
|
|
2488
|
+
await streamAssistantResponse(
|
|
2489
|
+
input,
|
|
2490
|
+
options?.attachments,
|
|
2491
|
+
userId,
|
|
2492
|
+
assistantId,
|
|
2493
|
+
parentId,
|
|
2494
|
+
controller.signal
|
|
2495
|
+
);
|
|
2496
|
+
},
|
|
2497
|
+
[streamAssistantResponse]
|
|
2498
|
+
);
|
|
2499
|
+
const reload = useCallback3(
|
|
2500
|
+
async (messageId) => {
|
|
2501
|
+
const current = messagesRef.current;
|
|
2502
|
+
const idx = messageId ? current.findIndex((m) => m.id === messageId) : current.length - 2;
|
|
2503
|
+
const userMessage = idx >= 0 ? current[idx] : null;
|
|
2504
|
+
if (!userMessage || userMessage.role !== "user") return;
|
|
2505
|
+
const input = getTextFromMessage(userMessage);
|
|
2506
|
+
const messageAttachments = getAttachmentsFromMessage(userMessage);
|
|
2507
|
+
if (!input && !messageAttachments?.length) return;
|
|
2508
|
+
const assistantId = crypto.randomUUID();
|
|
2509
|
+
const parentId = findParentId(current, idx);
|
|
2510
|
+
setMessages((prev) => [
|
|
2511
|
+
...prev.slice(0, idx + 1),
|
|
2512
|
+
{ id: assistantId, role: "assistant", content: [] }
|
|
2513
|
+
]);
|
|
2514
|
+
setIsRunning(true);
|
|
2515
|
+
const controller = new AbortController();
|
|
2516
|
+
abortRef.current = controller;
|
|
2517
|
+
await streamAssistantResponse(
|
|
2518
|
+
input,
|
|
2519
|
+
messageAttachments,
|
|
2520
|
+
userMessage.id,
|
|
2521
|
+
assistantId,
|
|
2522
|
+
parentId,
|
|
2523
|
+
controller.signal
|
|
2524
|
+
);
|
|
2525
|
+
},
|
|
2526
|
+
[streamAssistantResponse]
|
|
2527
|
+
);
|
|
2528
|
+
const cancel = useCallback3(() => {
|
|
2529
|
+
abortRef.current?.abort();
|
|
2530
|
+
}, []);
|
|
2531
|
+
const clear = useCallback3(() => {
|
|
2532
|
+
abortRef.current?.abort();
|
|
2533
|
+
setMessages([]);
|
|
2534
|
+
}, []);
|
|
2535
|
+
return useMemo3(
|
|
2536
|
+
() => ({ messages, isRunning, send, reload, cancel, clear }),
|
|
2537
|
+
[messages, isRunning, send, reload, cancel, clear]
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
function readTopLevelStartRunId(event) {
|
|
2541
|
+
if (event.type === "START" && typeof event.run_id === "string" && typeof event.path === "string" && !event.path.includes(".")) {
|
|
2542
|
+
return event.run_id;
|
|
2543
|
+
}
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2546
|
+
var TimbalStreamContext = createContext4(null);
|
|
2547
|
+
function useTimbalRuntime() {
|
|
2548
|
+
const ctx = useContext4(TimbalStreamContext);
|
|
2549
|
+
if (!ctx) {
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
"useTimbalRuntime must be used inside a <TimbalRuntimeProvider>."
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
return ctx;
|
|
2555
|
+
}
|
|
2556
|
+
function TimbalRuntimeProvider({
|
|
2557
|
+
workforceId,
|
|
2558
|
+
children,
|
|
2559
|
+
baseUrl = "/api",
|
|
2560
|
+
fetch: fetchFn,
|
|
2561
|
+
attachments,
|
|
2562
|
+
attachmentsUploadUrl,
|
|
2563
|
+
attachmentsAccept,
|
|
2564
|
+
debug
|
|
2565
|
+
}) {
|
|
2566
|
+
const stream = useTimbalStream({
|
|
2567
|
+
workforceId,
|
|
2568
|
+
baseUrl,
|
|
2569
|
+
fetch: fetchFn,
|
|
2570
|
+
debug
|
|
2571
|
+
});
|
|
2572
|
+
const attachmentAdapter = useMemo3(
|
|
2573
|
+
() => resolveAttachmentAdapter(attachments, {
|
|
2574
|
+
baseUrl,
|
|
2575
|
+
fetch: fetchFn,
|
|
2576
|
+
uploadUrl: attachmentsUploadUrl,
|
|
2577
|
+
accept: attachmentsAccept
|
|
2578
|
+
}),
|
|
2579
|
+
[attachments, attachmentsUploadUrl, attachmentsAccept, baseUrl, fetchFn]
|
|
2580
|
+
);
|
|
2581
|
+
const onNew = useCallback3(
|
|
2582
|
+
async (message) => {
|
|
2583
|
+
const textPart = message.content.find((c) => c.type === "text");
|
|
2584
|
+
const input = textPart && textPart.type === "text" ? textPart.text : "";
|
|
2585
|
+
const auiAttachments = message.attachments;
|
|
2586
|
+
const attachments2 = auiAttachments ? (await Promise.all(auiAttachments.map(extractAttachment))).filter((a) => a !== null) : [];
|
|
2587
|
+
if (!input && attachments2.length === 0) return;
|
|
2588
|
+
const parentId = message.parentId !== null && message.parentId !== void 0 ? findParentIdFromAuiParent(stream.messages, message.parentId) : void 0;
|
|
2589
|
+
await stream.send(input, {
|
|
2590
|
+
attachments: attachments2.length > 0 ? attachments2 : void 0,
|
|
2591
|
+
...parentId !== void 0 ? { parentId } : {}
|
|
2592
|
+
});
|
|
2593
|
+
},
|
|
2594
|
+
[stream]
|
|
2595
|
+
);
|
|
2596
|
+
const onReload = useCallback3(
|
|
2597
|
+
async (messageId) => {
|
|
2598
|
+
await stream.reload(messageId);
|
|
2599
|
+
},
|
|
2600
|
+
[stream]
|
|
2601
|
+
);
|
|
2602
|
+
const onCancel = useCallback3(async () => {
|
|
2603
|
+
stream.cancel();
|
|
2604
|
+
}, [stream]);
|
|
2605
|
+
const runtime = useExternalStoreRuntime({
|
|
2606
|
+
isRunning: stream.isRunning,
|
|
2607
|
+
messages: stream.messages,
|
|
2608
|
+
convertMessage,
|
|
2609
|
+
onNew,
|
|
2610
|
+
onEdit: onNew,
|
|
2611
|
+
onReload,
|
|
2612
|
+
onCancel,
|
|
2613
|
+
...attachmentAdapter ? { adapters: { attachments: attachmentAdapter } } : {}
|
|
2614
|
+
});
|
|
2615
|
+
return /* @__PURE__ */ jsx16(TimbalStreamContext.Provider, { value: stream, children: /* @__PURE__ */ jsx16(TimbalAttachmentsEnabledProvider, { enabled: attachmentAdapter !== void 0, children: /* @__PURE__ */ jsx16(AssistantRuntimeProvider, { runtime, children }) }) });
|
|
2616
|
+
}
|
|
2617
|
+
function findParentIdFromAuiParent(messages, auiParentId) {
|
|
2618
|
+
const idx = messages.findIndex((m) => m.id === auiParentId);
|
|
2619
|
+
if (idx < 0) return null;
|
|
2620
|
+
return findParentId(messages.slice(0, idx + 1));
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/chat/tool-fallback.tsx
|
|
2624
|
+
import { memo as memo2, useMemo as useMemo4, useState as useState6 } from "react";
|
|
2625
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
2626
|
+
import {
|
|
2627
|
+
useAuiState as useAuiState2
|
|
2628
|
+
} from "@assistant-ui/react";
|
|
2629
|
+
|
|
2630
|
+
// src/chat/motion.tsx
|
|
2631
|
+
import { AnimatePresence, motion as motion2, useReducedMotion } from "motion/react";
|
|
2632
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2633
|
+
var luxuryEase = [0.16, 1, 0.3, 1];
|
|
2634
|
+
var TOOL_ENTER_MS = 0.78;
|
|
2635
|
+
var TOOL_EXIT_MS = 0.28;
|
|
2636
|
+
function toolPresenceTransition(reduced) {
|
|
2637
|
+
return {
|
|
2638
|
+
enter: {
|
|
2639
|
+
duration: reduced ? 0.35 : TOOL_ENTER_MS,
|
|
2640
|
+
ease: luxuryEase
|
|
2641
|
+
},
|
|
2642
|
+
exit: {
|
|
2643
|
+
duration: reduced ? 0.2 : TOOL_EXIT_MS,
|
|
2644
|
+
ease: [0.4, 0, 1, 1]
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function toolMotionState(reduced, entering, variant) {
|
|
2649
|
+
if (reduced) {
|
|
2650
|
+
return entering ? { opacity: 0, y: variant === "executing" ? 8 : 10 } : { opacity: 1, y: 0 };
|
|
2651
|
+
}
|
|
2652
|
+
if (variant === "executing") {
|
|
2653
|
+
return entering ? { opacity: 0, y: 12 } : { opacity: 1, y: 0 };
|
|
2654
|
+
}
|
|
2655
|
+
return entering ? { opacity: 0, y: 14, filter: "blur(10px)" } : { opacity: 1, y: 0, filter: "blur(0px)" };
|
|
2656
|
+
}
|
|
2657
|
+
function ToolMotion({ children, className, motionKey }) {
|
|
2658
|
+
const reduced = useReducedMotion() ?? false;
|
|
2659
|
+
const { enter, exit } = toolPresenceTransition(reduced);
|
|
2660
|
+
return /* @__PURE__ */ jsx17(
|
|
2661
|
+
motion2.div,
|
|
2662
|
+
{
|
|
2663
|
+
className: cn("aui-tool-motion w-full min-w-0", className),
|
|
2664
|
+
initial: toolMotionState(reduced, true, "settled"),
|
|
2665
|
+
animate: toolMotionState(reduced, false, "settled"),
|
|
2666
|
+
exit: reduced ? { opacity: 0, y: 6, transition: exit } : { opacity: 0, y: 8, filter: "blur(6px)", transition: exit },
|
|
2667
|
+
transition: enter,
|
|
2668
|
+
style: { willChange: "opacity, transform, filter" },
|
|
2669
|
+
children
|
|
2670
|
+
},
|
|
2671
|
+
motionKey
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
function ToolPresence({
|
|
2675
|
+
presenceKey,
|
|
2676
|
+
children,
|
|
2677
|
+
className,
|
|
2678
|
+
variant = "settled"
|
|
2679
|
+
}) {
|
|
2680
|
+
const reduced = useReducedMotion() ?? false;
|
|
2681
|
+
const { enter, exit } = toolPresenceTransition(reduced);
|
|
2682
|
+
const enterTransition = variant === "executing" ? { duration: reduced ? 0.3 : 0.52, ease: luxuryEase } : enter;
|
|
2683
|
+
return /* @__PURE__ */ jsx17(AnimatePresence, { mode: "wait", initial: true, children: /* @__PURE__ */ jsx17(
|
|
2684
|
+
motion2.div,
|
|
2685
|
+
{
|
|
2686
|
+
className: cn("aui-tool-presence w-full min-w-0", className),
|
|
2687
|
+
initial: toolMotionState(reduced, true, variant),
|
|
2688
|
+
animate: toolMotionState(reduced, false, variant),
|
|
2689
|
+
exit: reduced ? { opacity: 0, y: 6, transition: exit } : { opacity: 0, y: 8, filter: "blur(6px)", transition: exit },
|
|
2690
|
+
transition: enterTransition,
|
|
2691
|
+
style: {
|
|
2692
|
+
willChange: variant === "executing" ? "opacity, transform" : "opacity, transform, filter"
|
|
2693
|
+
},
|
|
2694
|
+
children
|
|
2695
|
+
},
|
|
2696
|
+
presenceKey
|
|
2697
|
+
) });
|
|
2698
|
+
}
|
|
2699
|
+
function ToolBodyPresence({
|
|
2700
|
+
open,
|
|
2701
|
+
children,
|
|
2702
|
+
className
|
|
2703
|
+
}) {
|
|
2704
|
+
const reduced = useReducedMotion() ?? false;
|
|
2705
|
+
return /* @__PURE__ */ jsx17(
|
|
2706
|
+
"div",
|
|
2707
|
+
{
|
|
2708
|
+
className: cn(
|
|
2709
|
+
"aui-tool-body grid min-h-0 transition-[grid-template-rows]",
|
|
2710
|
+
open ? reduced ? "duration-200 ease-out" : "duration-[340ms] ease-[cubic-bezier(0.16,1,0.3,1)]" : reduced ? "duration-150 ease-[cubic-bezier(0.4,0,0.2,1)]" : "duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]"
|
|
2711
|
+
),
|
|
2712
|
+
style: { gridTemplateRows: open ? "1fr" : "0fr" },
|
|
2713
|
+
children: /* @__PURE__ */ jsx17("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx17(
|
|
2714
|
+
"div",
|
|
2715
|
+
{
|
|
2716
|
+
className: cn(
|
|
2717
|
+
className,
|
|
2718
|
+
"transition-opacity",
|
|
2719
|
+
open ? reduced ? "opacity-100 duration-200 ease-out" : "opacity-100 duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] delay-75" : reduced ? "opacity-0 duration-100 ease-in" : "opacity-0 duration-150 ease-[cubic-bezier(0.4,0,0.2,1)]"
|
|
2720
|
+
),
|
|
2721
|
+
children
|
|
2722
|
+
}
|
|
2723
|
+
) })
|
|
2724
|
+
}
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
// src/chat/tool-fallback.tsx
|
|
2729
|
+
import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2730
|
+
function detectRunning({
|
|
2731
|
+
status,
|
|
2732
|
+
result,
|
|
2733
|
+
streamRunning
|
|
2734
|
+
}) {
|
|
2735
|
+
const isError = status?.type === "incomplete" && status.reason !== "cancelled";
|
|
2736
|
+
if (isError) return false;
|
|
2737
|
+
if (status?.type === "running") return true;
|
|
2738
|
+
if (status?.type === "complete") return false;
|
|
2739
|
+
return streamRunning && result === void 0;
|
|
2740
|
+
}
|
|
2741
|
+
function useToolRunning(props) {
|
|
2742
|
+
const { isRunning: streamRunning } = useTimbalRuntime();
|
|
2743
|
+
const partStatus = useAuiState2((s) => s.part.status);
|
|
2744
|
+
return detectRunning({
|
|
2745
|
+
status: partStatus ?? props.status,
|
|
2746
|
+
result: props.result,
|
|
2747
|
+
streamRunning
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
function formatToolLabel(toolName) {
|
|
2751
|
+
return toolName.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
|
|
2752
|
+
}
|
|
2753
|
+
function formatToolResult(result) {
|
|
2754
|
+
if (typeof result === "string") return result;
|
|
2755
|
+
try {
|
|
2756
|
+
return JSON.stringify(result, null, 2);
|
|
2757
|
+
} catch {
|
|
2758
|
+
return String(result);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
var TimelineActionLabel = ({ action, detail, shimmer = false }) => /* @__PURE__ */ jsxs9("span", { className: "inline-flex min-w-0 max-w-full items-baseline gap-1", children: [
|
|
2762
|
+
action ? shimmer ? /* @__PURE__ */ jsx18(
|
|
2763
|
+
Shimmer,
|
|
2764
|
+
{
|
|
2765
|
+
as: "span",
|
|
2766
|
+
className: cn(studioTimelineShimmerActionClass, "aui-tool-shimmer"),
|
|
2767
|
+
duration: 1.8,
|
|
2768
|
+
spread: 2.5,
|
|
2769
|
+
children: action
|
|
2770
|
+
}
|
|
2771
|
+
) : /* @__PURE__ */ jsx18("span", { className: studioTimelineActionClass, children: action }) : null,
|
|
2772
|
+
detail ? /* @__PURE__ */ jsx18("span", { className: studioTimelineDetailClass, children: detail }) : null
|
|
2773
|
+
] });
|
|
2774
|
+
var TimelineHoverChevron = ({ expanded }) => /* @__PURE__ */ jsx18(
|
|
2775
|
+
ChevronRightIcon,
|
|
2776
|
+
{
|
|
2777
|
+
className: studioTimelineChevronClass(expanded),
|
|
2778
|
+
"aria-hidden": true
|
|
2779
|
+
}
|
|
2780
|
+
);
|
|
2781
|
+
var ToolPanel = ({ toolName, argsText, result, isError }) => {
|
|
2782
|
+
const [open, setOpen] = useState6(false);
|
|
2783
|
+
const detail = formatToolLabel(toolName);
|
|
2784
|
+
const formattedArgs = useMemo4(() => {
|
|
2785
|
+
if (!argsText || argsText === "{}") return null;
|
|
2786
|
+
try {
|
|
2787
|
+
return JSON.stringify(JSON.parse(argsText), null, 2);
|
|
2788
|
+
} catch {
|
|
2789
|
+
return argsText;
|
|
2790
|
+
}
|
|
2791
|
+
}, [argsText]);
|
|
2792
|
+
const formattedResult = useMemo4(() => {
|
|
2793
|
+
if (result === void 0 || result === null) return null;
|
|
2794
|
+
return formatToolResult(result);
|
|
2795
|
+
}, [result]);
|
|
2796
|
+
const hasBody = Boolean(formattedArgs || formattedResult);
|
|
2797
|
+
const action = isError ? "Failed" : "Used";
|
|
2798
|
+
if (!hasBody) {
|
|
2799
|
+
return /* @__PURE__ */ jsx18("div", { className: "aui-tool-row w-full min-w-0", children: /* @__PURE__ */ jsx18(TimelineActionLabel, { action, detail }) });
|
|
2800
|
+
}
|
|
2801
|
+
return /* @__PURE__ */ jsxs9("div", { className: "aui-tool-row w-full min-w-0", children: [
|
|
2802
|
+
/* @__PURE__ */ jsx18(
|
|
2803
|
+
"button",
|
|
2804
|
+
{
|
|
2805
|
+
type: "button",
|
|
2806
|
+
onClick: () => setOpen((v) => !v),
|
|
2807
|
+
"aria-expanded": open,
|
|
2808
|
+
"aria-label": `${action} ${detail}`,
|
|
2809
|
+
className: studioTimelineRowButtonClass,
|
|
2810
|
+
children: /* @__PURE__ */ jsxs9(
|
|
2811
|
+
"span",
|
|
2812
|
+
{
|
|
2813
|
+
className: cn(
|
|
2814
|
+
"inline-flex min-w-0 max-w-full items-center gap-0.5",
|
|
2815
|
+
studioTimelineTextClass,
|
|
2816
|
+
"text-foreground"
|
|
2817
|
+
),
|
|
2818
|
+
children: [
|
|
2819
|
+
/* @__PURE__ */ jsx18(TimelineActionLabel, { action, detail }),
|
|
2820
|
+
/* @__PURE__ */ jsx18(TimelineHoverChevron, { expanded: open })
|
|
2821
|
+
]
|
|
2822
|
+
}
|
|
2823
|
+
)
|
|
2824
|
+
}
|
|
2825
|
+
),
|
|
2826
|
+
/* @__PURE__ */ jsxs9(
|
|
2827
|
+
ToolBodyPresence,
|
|
2828
|
+
{
|
|
2829
|
+
open,
|
|
2830
|
+
className: cn(studioTimelineBodyPadClass, "gap-2"),
|
|
2831
|
+
children: [
|
|
2832
|
+
formattedArgs ? /* @__PURE__ */ jsx18(
|
|
2833
|
+
"div",
|
|
2834
|
+
{
|
|
2835
|
+
className: cn(
|
|
2836
|
+
studioComposerIoWellClass,
|
|
2837
|
+
"max-h-48 overflow-auto px-2.5 py-2"
|
|
2838
|
+
),
|
|
2839
|
+
children: /* @__PURE__ */ jsx18("pre", { className: "whitespace-pre-wrap break-words font-mono text-[11px] font-normal leading-relaxed text-foreground", children: formattedArgs })
|
|
2840
|
+
}
|
|
2841
|
+
) : null,
|
|
2842
|
+
formattedResult ? /* @__PURE__ */ jsx18(
|
|
2843
|
+
"div",
|
|
2844
|
+
{
|
|
2845
|
+
className: cn(
|
|
2846
|
+
studioComposerIoWellClass,
|
|
2847
|
+
"max-h-48 overflow-auto px-2.5 py-2"
|
|
2848
|
+
),
|
|
2849
|
+
children: /* @__PURE__ */ jsx18("pre", { className: "whitespace-pre-wrap break-words font-mono text-[11px] font-normal leading-relaxed text-foreground", children: formattedResult })
|
|
2850
|
+
}
|
|
2851
|
+
) : null
|
|
2852
|
+
]
|
|
2853
|
+
}
|
|
2854
|
+
)
|
|
2855
|
+
] });
|
|
2856
|
+
};
|
|
2857
|
+
var ToolFallbackImpl = ({
|
|
2858
|
+
toolName,
|
|
2859
|
+
argsText,
|
|
2860
|
+
result,
|
|
2861
|
+
status
|
|
2862
|
+
}) => {
|
|
2863
|
+
const isRunning = useToolRunning({ status, result });
|
|
2864
|
+
const isError = status?.type === "incomplete" && status.reason !== "cancelled";
|
|
2865
|
+
const presenceKey = isRunning ? "running" : isError ? "error" : "complete";
|
|
2866
|
+
return /* @__PURE__ */ jsx18(
|
|
2867
|
+
ToolPresence,
|
|
2868
|
+
{
|
|
2869
|
+
presenceKey,
|
|
2870
|
+
variant: isRunning ? "executing" : "settled",
|
|
2871
|
+
className: "py-0.5",
|
|
2872
|
+
children: isRunning ? /* @__PURE__ */ jsx18("div", { className: "aui-tool-running", children: /* @__PURE__ */ jsx18(
|
|
2873
|
+
TimelineActionLabel,
|
|
2874
|
+
{
|
|
2875
|
+
action: "Using",
|
|
2876
|
+
detail: formatToolLabel(toolName),
|
|
2877
|
+
shimmer: true
|
|
2878
|
+
}
|
|
2879
|
+
) }) : /* @__PURE__ */ jsx18(
|
|
2880
|
+
ToolPanel,
|
|
2881
|
+
{
|
|
2882
|
+
toolName,
|
|
2883
|
+
argsText,
|
|
2884
|
+
result,
|
|
2885
|
+
isError
|
|
2886
|
+
}
|
|
2887
|
+
)
|
|
2888
|
+
}
|
|
2889
|
+
);
|
|
2890
|
+
};
|
|
2891
|
+
var ToolFallback = memo2(
|
|
2892
|
+
ToolFallbackImpl
|
|
2893
|
+
);
|
|
2894
|
+
ToolFallback.displayName = "ToolFallback";
|
|
2895
|
+
|
|
2896
|
+
// src/artifacts/tool-artifact.tsx
|
|
2897
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
2898
|
+
var ToolArtifactFallback = (props) => {
|
|
2899
|
+
const registry = useArtifactRegistry();
|
|
2900
|
+
const isRunning = useToolRunning({
|
|
2901
|
+
status: props.status,
|
|
2902
|
+
result: props.result
|
|
2903
|
+
});
|
|
2904
|
+
if (!isRunning) {
|
|
2905
|
+
const artifact = parseArtifactFromToolResult(props.result);
|
|
2906
|
+
if (artifact) {
|
|
2907
|
+
const Renderer = registry[artifact.type];
|
|
2908
|
+
if (Renderer) {
|
|
2909
|
+
return /* @__PURE__ */ jsx19(
|
|
2910
|
+
ToolMotion,
|
|
2911
|
+
{
|
|
2912
|
+
motionKey: `artifact-${artifact.type}`,
|
|
2913
|
+
className: "aui-tool-artifact",
|
|
2914
|
+
children: /* @__PURE__ */ jsx19(Renderer, { artifact })
|
|
2915
|
+
}
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
return /* @__PURE__ */ jsx19(ToolFallback, { ...props });
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
// src/chat/composer.tsx
|
|
2924
|
+
import {
|
|
2925
|
+
AuiIf,
|
|
2926
|
+
ComposerPrimitive as ComposerPrimitive2,
|
|
2927
|
+
useComposerRuntime
|
|
2928
|
+
} from "@assistant-ui/react";
|
|
2929
|
+
import { ArrowUpIcon, SquareIcon } from "lucide-react";
|
|
2930
|
+
import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2931
|
+
var Composer = ({
|
|
2932
|
+
placeholder = "Send a message...",
|
|
2933
|
+
showAttachments,
|
|
2934
|
+
toolbar,
|
|
2935
|
+
sendTooltip = "Send message",
|
|
2936
|
+
noAutoFocus,
|
|
2937
|
+
className
|
|
2938
|
+
}) => {
|
|
2939
|
+
const attachmentsEnabled = useTimbalAttachmentsEnabled();
|
|
2940
|
+
const attachUi = showAttachments !== false && attachmentsEnabled;
|
|
2941
|
+
const shell = /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
2942
|
+
attachUi && /* @__PURE__ */ jsx20(ComposerAttachments, {}),
|
|
2943
|
+
/* @__PURE__ */ jsx20(ComposerInput, { placeholder, autoFocus: !noAutoFocus }),
|
|
2944
|
+
/* @__PURE__ */ jsx20(
|
|
2945
|
+
ComposerToolbar,
|
|
2946
|
+
{
|
|
2947
|
+
showAttachments: attachUi,
|
|
2948
|
+
toolbar,
|
|
2949
|
+
sendTooltip
|
|
2950
|
+
}
|
|
2951
|
+
)
|
|
2952
|
+
] });
|
|
2953
|
+
return /* @__PURE__ */ jsx20(
|
|
2954
|
+
ComposerPrimitive2.Root,
|
|
2955
|
+
{
|
|
2956
|
+
className: cn(
|
|
2957
|
+
"aui-composer-root relative flex w-full flex-col",
|
|
2958
|
+
className
|
|
2959
|
+
),
|
|
2960
|
+
children: attachUi ? /* @__PURE__ */ jsx20(
|
|
2961
|
+
ComposerPrimitive2.AttachmentDropzone,
|
|
2962
|
+
{
|
|
2963
|
+
className: cn(
|
|
2964
|
+
studioComposeInputShellClass,
|
|
2965
|
+
"data-[dragging=true]:border-2 data-[dragging=true]:border-dashed data-[dragging=true]:border-primary data-[dragging=true]:bg-accent/50"
|
|
2966
|
+
),
|
|
2967
|
+
children: shell
|
|
2968
|
+
}
|
|
2969
|
+
) : /* @__PURE__ */ jsx20("div", { className: studioComposeInputShellClass, children: shell })
|
|
2970
|
+
}
|
|
2971
|
+
);
|
|
2972
|
+
};
|
|
2973
|
+
var ComposerInput = ({
|
|
2974
|
+
placeholder,
|
|
2975
|
+
autoFocus
|
|
2976
|
+
}) => {
|
|
2977
|
+
const composer = useComposerRuntime();
|
|
2978
|
+
const onKeyDown = (e) => {
|
|
2979
|
+
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
2980
|
+
e.preventDefault();
|
|
2981
|
+
composer.send();
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
const onInput = (e) => {
|
|
2985
|
+
const el = e.currentTarget;
|
|
2986
|
+
el.style.height = "auto";
|
|
2987
|
+
el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
|
|
2988
|
+
};
|
|
2989
|
+
return /* @__PURE__ */ jsx20(
|
|
2990
|
+
ComposerPrimitive2.Input,
|
|
2991
|
+
{
|
|
2992
|
+
placeholder,
|
|
2993
|
+
className: "aui-composer-input max-h-60 min-h-14 w-full resize-none bg-composer-bg px-3 pt-3 pb-1 text-sm outline-none placeholder:text-muted-foreground/70 focus-visible:ring-0",
|
|
2994
|
+
rows: 1,
|
|
2995
|
+
autoFocus,
|
|
2996
|
+
"aria-label": "Message input",
|
|
2997
|
+
onKeyDown,
|
|
2998
|
+
onInput
|
|
2999
|
+
}
|
|
3000
|
+
);
|
|
3001
|
+
};
|
|
3002
|
+
var ComposerToolbar = ({ showAttachments, toolbar, sendTooltip }) => {
|
|
3003
|
+
return /* @__PURE__ */ jsxs10("div", { className: "aui-composer-action-wrapper relative z-[1] flex items-center justify-between gap-1 bg-composer-bg px-2.5 pb-2.5", children: [
|
|
3004
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
|
|
3005
|
+
showAttachments && /* @__PURE__ */ jsx20(ComposerAddAttachment, {}),
|
|
3006
|
+
toolbar
|
|
3007
|
+
] }),
|
|
3008
|
+
/* @__PURE__ */ jsx20(ComposerSendOrCancel, { sendTooltip })
|
|
3009
|
+
] });
|
|
3010
|
+
};
|
|
3011
|
+
var ComposerSendOrCancel = ({ sendTooltip }) => {
|
|
3012
|
+
return /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
3013
|
+
/* @__PURE__ */ jsx20(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx20(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx20(
|
|
3014
|
+
TooltipIconButton,
|
|
3015
|
+
{
|
|
3016
|
+
tooltip: sendTooltip,
|
|
3017
|
+
variant: "primary",
|
|
3018
|
+
type: "submit",
|
|
3019
|
+
className: "aui-composer-send shrink-0 disabled:opacity-30",
|
|
3020
|
+
"aria-label": "Send message",
|
|
3021
|
+
children: /* @__PURE__ */ jsx20(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
|
|
3022
|
+
}
|
|
3023
|
+
) }) }),
|
|
3024
|
+
/* @__PURE__ */ jsx20(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx20(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx20(
|
|
3025
|
+
TooltipIconButton,
|
|
3026
|
+
{
|
|
3027
|
+
tooltip: "Stop generating",
|
|
3028
|
+
variant: "primary",
|
|
3029
|
+
className: "aui-composer-cancel shrink-0",
|
|
3030
|
+
"aria-label": "Stop generating",
|
|
3031
|
+
children: /* @__PURE__ */ jsx20(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
|
|
3032
|
+
}
|
|
3033
|
+
) }) })
|
|
3034
|
+
] });
|
|
3035
|
+
};
|
|
3036
|
+
|
|
3037
|
+
// src/chat/suggestions.tsx
|
|
3038
|
+
import {
|
|
3039
|
+
useEffect as useEffect4,
|
|
3040
|
+
useMemo as useMemo5,
|
|
3041
|
+
useState as useState7
|
|
3042
|
+
} from "react";
|
|
3043
|
+
import { useThreadRuntime as useThreadRuntime3 } from "@assistant-ui/react";
|
|
3044
|
+
import { ArrowUpIcon as ArrowUpIcon2 } from "lucide-react";
|
|
3045
|
+
import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3046
|
+
var Suggestions = ({
|
|
3047
|
+
suggestions,
|
|
3048
|
+
className
|
|
3049
|
+
}) => {
|
|
3050
|
+
const items = useResolvedSuggestions(suggestions);
|
|
3051
|
+
if (!items || items.length === 0) return null;
|
|
3052
|
+
return /* @__PURE__ */ jsx21(
|
|
3053
|
+
"div",
|
|
3054
|
+
{
|
|
3055
|
+
className: cn(
|
|
3056
|
+
"aui-thread-suggestions flex w-full flex-col gap-2 pb-2.5",
|
|
3057
|
+
className
|
|
3058
|
+
),
|
|
3059
|
+
role: "list",
|
|
3060
|
+
"aria-label": "Suggested prompts",
|
|
3061
|
+
children: items.map((suggestion, i) => /* @__PURE__ */ jsx21(SuggestionRow, { suggestion }, (suggestion.prompt ?? suggestion.title) + i))
|
|
3062
|
+
}
|
|
3063
|
+
);
|
|
3064
|
+
};
|
|
3065
|
+
var SuggestionRow = ({ suggestion }) => {
|
|
3066
|
+
const runtime = useThreadRuntime3();
|
|
3067
|
+
const onClick = () => {
|
|
3068
|
+
const text = suggestion.prompt ?? suggestion.title;
|
|
3069
|
+
runtime.append({ role: "user", content: [{ type: "text", text }] });
|
|
3070
|
+
};
|
|
3071
|
+
return /* @__PURE__ */ jsxs11(
|
|
3072
|
+
"button",
|
|
3073
|
+
{
|
|
3074
|
+
type: "button",
|
|
3075
|
+
role: "listitem",
|
|
3076
|
+
onClick,
|
|
3077
|
+
className: cn("aui-thread-suggestion", studioListRowButtonClass),
|
|
3078
|
+
children: [
|
|
3079
|
+
/* @__PURE__ */ jsx21("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon ?? /* @__PURE__ */ jsx21(ArrowUpIcon2, { className: "size-4", strokeWidth: 1.75, "aria-hidden": true }) }),
|
|
3080
|
+
/* @__PURE__ */ jsxs11("span", { className: "aui-thread-suggestion-text min-w-0 flex-1 text-left", children: [
|
|
3081
|
+
/* @__PURE__ */ jsx21("span", { className: "aui-thread-suggestion-text-1 block truncate text-sm font-normal text-foreground", children: suggestion.title }),
|
|
3082
|
+
suggestion.description && /* @__PURE__ */ jsx21("span", { className: "aui-thread-suggestion-text-2 mt-0.5 block truncate text-xs text-muted-foreground", children: suggestion.description })
|
|
3083
|
+
] })
|
|
3084
|
+
]
|
|
3085
|
+
}
|
|
3086
|
+
);
|
|
3087
|
+
};
|
|
3088
|
+
function useResolvedSuggestions(source) {
|
|
3089
|
+
const [resolved, setResolved] = useState7(
|
|
3090
|
+
() => Array.isArray(source) ? source : void 0
|
|
3091
|
+
);
|
|
3092
|
+
useEffect4(() => {
|
|
3093
|
+
if (!source) {
|
|
3094
|
+
setResolved(void 0);
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
if (Array.isArray(source)) {
|
|
3098
|
+
setResolved(source);
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
let cancelled = false;
|
|
3102
|
+
Promise.resolve().then(() => source()).then((value) => {
|
|
3103
|
+
if (!cancelled) setResolved(value);
|
|
3104
|
+
}).catch(() => {
|
|
3105
|
+
if (!cancelled) setResolved([]);
|
|
3106
|
+
});
|
|
3107
|
+
return () => {
|
|
3108
|
+
cancelled = true;
|
|
3109
|
+
};
|
|
3110
|
+
}, [source]);
|
|
3111
|
+
return useMemo5(() => resolved, [resolved]);
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// src/chat/thread.tsx
|
|
3115
|
+
import { useEffect as useEffect5 } from "react";
|
|
3116
|
+
import {
|
|
3117
|
+
ActionBarMorePrimitive,
|
|
3118
|
+
ActionBarPrimitive,
|
|
3119
|
+
AuiIf as AuiIf2,
|
|
3120
|
+
ComposerPrimitive as ComposerPrimitive3,
|
|
3121
|
+
ErrorPrimitive,
|
|
3122
|
+
MessagePartPrimitive,
|
|
3123
|
+
MessagePrimitive as MessagePrimitive2,
|
|
3124
|
+
ThreadPrimitive,
|
|
3125
|
+
useThread
|
|
3126
|
+
} from "@assistant-ui/react";
|
|
3127
|
+
import {
|
|
3128
|
+
ArrowDownIcon,
|
|
3129
|
+
CheckIcon as CheckIcon3,
|
|
3130
|
+
CopyIcon as CopyIcon2,
|
|
3131
|
+
DownloadIcon,
|
|
3132
|
+
MoreHorizontalIcon,
|
|
3133
|
+
PencilIcon,
|
|
3134
|
+
RefreshCwIcon
|
|
3135
|
+
} from "lucide-react";
|
|
3136
|
+
import { motion as motion3 } from "motion/react";
|
|
3137
|
+
|
|
3138
|
+
// src/design/theme-sanity.ts
|
|
3139
|
+
var scheduled = false;
|
|
3140
|
+
var warned = false;
|
|
3141
|
+
function isDev() {
|
|
3142
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") {
|
|
3143
|
+
return false;
|
|
3144
|
+
}
|
|
3145
|
+
return true;
|
|
3146
|
+
}
|
|
3147
|
+
function parseLuminance(color) {
|
|
3148
|
+
const value = color.trim();
|
|
3149
|
+
if (!value) return null;
|
|
3150
|
+
const oklch = value.match(/oklch\(\s*([0-9.]+)/i);
|
|
3151
|
+
if (oklch) {
|
|
3152
|
+
const lightness = Number.parseFloat(oklch[1]);
|
|
3153
|
+
if (Number.isFinite(lightness)) return lightness;
|
|
3154
|
+
}
|
|
3155
|
+
const rgb = value.match(/rgba?\(\s*([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)/i);
|
|
3156
|
+
if (rgb) {
|
|
3157
|
+
const r = Number.parseFloat(rgb[1]) / 255;
|
|
3158
|
+
const g = Number.parseFloat(rgb[2]) / 255;
|
|
3159
|
+
const b = Number.parseFloat(rgb[3]) / 255;
|
|
3160
|
+
if ([r, g, b].every(Number.isFinite)) {
|
|
3161
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
const hsl = value.match(/hsla?\(\s*[0-9.]+[\s,]+[0-9.]+%[\s,]+([0-9.]+)%/i);
|
|
3165
|
+
if (hsl) {
|
|
3166
|
+
const lightness = Number.parseFloat(hsl[1]) / 100;
|
|
3167
|
+
if (Number.isFinite(lightness)) return lightness;
|
|
3168
|
+
}
|
|
3169
|
+
return null;
|
|
3170
|
+
}
|
|
3171
|
+
function runCheck() {
|
|
3172
|
+
if (warned) return;
|
|
3173
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
3174
|
+
const root = document.documentElement;
|
|
3175
|
+
const styles = window.getComputedStyle(root);
|
|
3176
|
+
const background = styles.getPropertyValue("--background").trim();
|
|
3177
|
+
if (!background) {
|
|
3178
|
+
warned = true;
|
|
3179
|
+
console.warn(
|
|
3180
|
+
'[@timbal-ai/timbal-react] No `--background` CSS variable found on `<html>`. Did you `import "@timbal-ai/timbal-react/styles.css"` in your app entry? Components rely on semantic tokens (bg-background, text-foreground, \u2026) and will fall back to browser defaults.'
|
|
3181
|
+
);
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
const luminance = parseLuminance(background);
|
|
3185
|
+
if (luminance === null) return;
|
|
3186
|
+
const hasDarkClass = root.classList.contains("dark");
|
|
3187
|
+
const looksDark = luminance < 0.5;
|
|
3188
|
+
if (hasDarkClass !== looksDark) {
|
|
3189
|
+
warned = true;
|
|
3190
|
+
console.warn(
|
|
3191
|
+
`[@timbal-ai/timbal-react] Theme mismatch detected. \`<html>\` has${hasDarkClass ? "" : " no"} \`.dark\` class but the resolved \`--background\` is ${looksDark ? "dark" : "light"} (${background}). This usually means the consumer's CSS overrides \`--background\` only in one mode. Import \`@timbal-ai/timbal-react/styles.css\` for a complete light + dark token set, or declare matching \`:root\` AND \`.dark\` blocks in your own CSS.`
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
function scheduleThemeSanityCheck() {
|
|
3196
|
+
if (scheduled) return;
|
|
3197
|
+
if (!isDev()) return;
|
|
3198
|
+
if (typeof window === "undefined") return;
|
|
3199
|
+
scheduled = true;
|
|
3200
|
+
if (typeof queueMicrotask === "function") {
|
|
3201
|
+
queueMicrotask(() => setTimeout(runCheck, 0));
|
|
3202
|
+
} else {
|
|
3203
|
+
setTimeout(runCheck, 0);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
// src/chat/thread-variant.tsx
|
|
3208
|
+
import { createContext as createContext5, useContext as useContext5 } from "react";
|
|
3209
|
+
var ThreadVariantContext = createContext5("default");
|
|
3210
|
+
var ThreadVariantProvider = ThreadVariantContext.Provider;
|
|
3211
|
+
function useThreadVariant() {
|
|
3212
|
+
return useContext5(ThreadVariantContext);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
// src/chat/thread.tsx
|
|
3216
|
+
import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3217
|
+
var Thread = ({
|
|
3218
|
+
className,
|
|
3219
|
+
variant = "default",
|
|
3220
|
+
maxWidth: maxWidthProp,
|
|
3221
|
+
welcome,
|
|
3222
|
+
suggestions,
|
|
3223
|
+
composerPlaceholder,
|
|
3224
|
+
components,
|
|
3225
|
+
artifacts,
|
|
3226
|
+
onArtifactEvent
|
|
3227
|
+
}) => {
|
|
3228
|
+
const isPanel = variant === "panel";
|
|
3229
|
+
const maxWidth = maxWidthProp ?? (isPanel ? "100%" : "44rem");
|
|
3230
|
+
const placeholder = composerPlaceholder ?? (isPanel ? "Ask about this page\u2026" : "Send a message...");
|
|
3231
|
+
const WelcomeSlot = components?.Welcome ?? ThreadWelcome;
|
|
3232
|
+
const ComposerSlot = components?.Composer ?? Composer;
|
|
3233
|
+
const UserMessageSlot = components?.UserMessage ?? UserMessage;
|
|
3234
|
+
const AssistantMessageSlot = components?.AssistantMessage ?? AssistantMessage;
|
|
3235
|
+
const EditComposerSlot = components?.EditComposer ?? EditComposer;
|
|
3236
|
+
const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
|
|
3237
|
+
const SuggestionsSlot = components?.Suggestions ?? Suggestions;
|
|
3238
|
+
useEffect5(() => {
|
|
3239
|
+
scheduleThemeSanityCheck();
|
|
3240
|
+
}, []);
|
|
3241
|
+
return /* @__PURE__ */ jsx22(ThreadVariantProvider, { value: variant, children: /* @__PURE__ */ jsx22(
|
|
3242
|
+
ArtifactRegistryProvider,
|
|
3243
|
+
{
|
|
3244
|
+
renderers: artifacts?.renderers,
|
|
3245
|
+
override: artifacts?.override,
|
|
3246
|
+
children: /* @__PURE__ */ jsx22(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
|
|
3247
|
+
}), children: /* @__PURE__ */ jsx22(
|
|
3248
|
+
ThreadPrimitive.Root,
|
|
3249
|
+
{
|
|
3250
|
+
className: cn(
|
|
3251
|
+
"aui-root aui-thread-root @container flex h-full flex-col bg-transparent",
|
|
3252
|
+
isPanel && "aui-thread-root--panel",
|
|
3253
|
+
className
|
|
3254
|
+
),
|
|
3255
|
+
style: { ["--thread-max-width"]: maxWidth },
|
|
3256
|
+
"data-thread-variant": variant,
|
|
3257
|
+
children: /* @__PURE__ */ jsxs12(
|
|
3258
|
+
ThreadPrimitive.Viewport,
|
|
3259
|
+
{
|
|
3260
|
+
turnAnchor: "bottom",
|
|
3261
|
+
className: cn(
|
|
3262
|
+
"aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll scroll-pb-28",
|
|
3263
|
+
isPanel ? "px-2 pt-2" : "px-4 pt-4"
|
|
3264
|
+
),
|
|
3265
|
+
children: [
|
|
3266
|
+
/* @__PURE__ */ jsx22(
|
|
3267
|
+
WelcomeSlot,
|
|
3268
|
+
{
|
|
3269
|
+
config: welcome,
|
|
3270
|
+
suggestions,
|
|
3271
|
+
Suggestions: SuggestionsSlot
|
|
3272
|
+
}
|
|
3273
|
+
),
|
|
3274
|
+
/* @__PURE__ */ jsx22(
|
|
3275
|
+
ThreadPrimitive.Messages,
|
|
3276
|
+
{
|
|
3277
|
+
components: {
|
|
3278
|
+
UserMessage: UserMessageSlot,
|
|
3279
|
+
EditComposer: EditComposerSlot,
|
|
3280
|
+
AssistantMessage: AssistantMessageSlot
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
),
|
|
3284
|
+
/* @__PURE__ */ jsx22(
|
|
3285
|
+
ThreadPrimitive.ViewportFooter,
|
|
3286
|
+
{
|
|
3287
|
+
className: cn(
|
|
3288
|
+
"aui-thread-viewport-footer sticky bottom-0 z-10 mt-auto w-full isolate pt-2",
|
|
3289
|
+
isPanel ? "bg-card pb-2" : "bg-background pb-4 md:pb-6"
|
|
3290
|
+
),
|
|
3291
|
+
children: /* @__PURE__ */ jsxs12(
|
|
3292
|
+
"div",
|
|
3293
|
+
{
|
|
3294
|
+
className: cn(
|
|
3295
|
+
"mx-auto flex w-full max-w-(--thread-max-width) flex-col",
|
|
3296
|
+
isPanel ? "gap-2" : "gap-4"
|
|
3297
|
+
),
|
|
3298
|
+
children: [
|
|
3299
|
+
/* @__PURE__ */ jsx22(ScrollToBottomSlot, {}),
|
|
3300
|
+
/* @__PURE__ */ jsx22(ComposerSlot, { placeholder })
|
|
3301
|
+
]
|
|
3302
|
+
}
|
|
3303
|
+
)
|
|
3304
|
+
}
|
|
3305
|
+
)
|
|
3306
|
+
]
|
|
3307
|
+
}
|
|
3308
|
+
)
|
|
3309
|
+
}
|
|
3310
|
+
) })
|
|
3311
|
+
}
|
|
3312
|
+
) });
|
|
3313
|
+
};
|
|
3314
|
+
var ThreadScrollToBottom = () => {
|
|
3315
|
+
return /* @__PURE__ */ jsx22(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx22(
|
|
3316
|
+
TooltipIconButton,
|
|
3317
|
+
{
|
|
3318
|
+
tooltip: "Scroll to bottom",
|
|
3319
|
+
variant: "secondary",
|
|
3320
|
+
className: "aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center disabled:invisible",
|
|
3321
|
+
children: /* @__PURE__ */ jsx22(ArrowDownIcon, { className: "size-4" })
|
|
3322
|
+
}
|
|
3323
|
+
) });
|
|
3324
|
+
};
|
|
3325
|
+
var welcomeStagger = {
|
|
3326
|
+
initial: {},
|
|
3327
|
+
animate: {
|
|
3328
|
+
transition: { staggerChildren: 0.16, delayChildren: 0.12 }
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
var welcomeItem = {
|
|
3332
|
+
initial: { opacity: 0, y: 14 },
|
|
3333
|
+
animate: {
|
|
3334
|
+
opacity: 1,
|
|
3335
|
+
y: 0,
|
|
3336
|
+
transition: { duration: 0.9, ease: luxuryEase }
|
|
3337
|
+
}
|
|
3338
|
+
};
|
|
3339
|
+
var welcomeIcon = {
|
|
3340
|
+
initial: { opacity: 0, y: 10, scale: 0.96 },
|
|
3341
|
+
animate: {
|
|
3342
|
+
opacity: 1,
|
|
3343
|
+
y: 0,
|
|
3344
|
+
scale: 1,
|
|
3345
|
+
transition: { duration: 1.1, ease: luxuryEase }
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
var ThreadWelcome = ({
|
|
3349
|
+
config,
|
|
3350
|
+
suggestions,
|
|
3351
|
+
Suggestions: SuggestionsSlot = Suggestions
|
|
3352
|
+
}) => {
|
|
3353
|
+
const isEmpty = useThread((s) => s.messages.length === 0);
|
|
3354
|
+
const isPanel = useThreadVariant() === "panel";
|
|
3355
|
+
if (!isEmpty) return null;
|
|
3356
|
+
const defaultHeading = isPanel ? "Ask about this page" : "How can I help you today?";
|
|
3357
|
+
const defaultSubheading = isPanel ? "The assistant can use dashboard context from your app." : "Send a message to start a conversation.";
|
|
3358
|
+
return /* @__PURE__ */ jsxs12("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
|
|
3359
|
+
/* @__PURE__ */ jsx22("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs12(
|
|
3360
|
+
motion3.div,
|
|
3361
|
+
{
|
|
3362
|
+
className: cn(
|
|
3363
|
+
"aui-thread-welcome-message flex flex-col items-center justify-center text-center",
|
|
3364
|
+
isPanel ? "px-2" : "px-4"
|
|
3365
|
+
),
|
|
3366
|
+
variants: welcomeStagger,
|
|
3367
|
+
initial: "initial",
|
|
3368
|
+
animate: "animate",
|
|
3369
|
+
children: [
|
|
3370
|
+
config?.icon && /* @__PURE__ */ jsx22(motion3.div, { variants: welcomeIcon, className: isPanel ? "mb-3" : "mb-5", children: config.icon }),
|
|
3371
|
+
/* @__PURE__ */ jsx22(
|
|
3372
|
+
motion3.h1,
|
|
3373
|
+
{
|
|
3374
|
+
variants: welcomeItem,
|
|
3375
|
+
className: cn(
|
|
3376
|
+
"aui-thread-welcome-message-inner font-semibold",
|
|
3377
|
+
isPanel ? "text-base" : "text-2xl"
|
|
3378
|
+
),
|
|
3379
|
+
children: config?.heading ?? defaultHeading
|
|
3380
|
+
}
|
|
3381
|
+
),
|
|
3382
|
+
/* @__PURE__ */ jsx22(
|
|
3383
|
+
motion3.p,
|
|
3384
|
+
{
|
|
3385
|
+
variants: welcomeItem,
|
|
3386
|
+
className: "aui-thread-welcome-message-inner mt-1.5 text-muted-foreground text-sm",
|
|
3387
|
+
children: config?.subheading ?? defaultSubheading
|
|
3388
|
+
}
|
|
3389
|
+
)
|
|
3390
|
+
]
|
|
3391
|
+
}
|
|
3392
|
+
) }),
|
|
3393
|
+
suggestions && !isPanel ? /* @__PURE__ */ jsx22("div", { className: "aui-thread-welcome-suggestions mx-auto w-full max-w-(--thread-max-width) px-2", children: /* @__PURE__ */ jsx22(SuggestionsSlot, { suggestions }) }) : null
|
|
3394
|
+
] });
|
|
3395
|
+
};
|
|
3396
|
+
var MessageError = () => {
|
|
3397
|
+
return /* @__PURE__ */ jsx22(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx22(ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm", children: /* @__PURE__ */ jsx22(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
|
|
3398
|
+
};
|
|
3399
|
+
var AssistantMessage = () => {
|
|
3400
|
+
const isPanel = useThreadVariant() === "panel";
|
|
3401
|
+
return /* @__PURE__ */ jsxs12(
|
|
3402
|
+
MessagePrimitive2.Root,
|
|
3403
|
+
{
|
|
3404
|
+
className: cn(
|
|
3405
|
+
"aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in duration-150",
|
|
3406
|
+
isPanel ? "py-2" : "py-3"
|
|
3407
|
+
),
|
|
3408
|
+
"data-role": "assistant",
|
|
3409
|
+
children: [
|
|
3410
|
+
/* @__PURE__ */ jsxs12(
|
|
3411
|
+
"div",
|
|
3412
|
+
{
|
|
3413
|
+
className: cn(
|
|
3414
|
+
"aui-assistant-message-content wrap-break-word text-foreground leading-relaxed",
|
|
3415
|
+
isPanel ? "px-1 text-sm" : "px-2"
|
|
3416
|
+
),
|
|
3417
|
+
children: [
|
|
3418
|
+
/* @__PURE__ */ jsx22(
|
|
3419
|
+
MessagePrimitive2.Parts,
|
|
3420
|
+
{
|
|
3421
|
+
components: {
|
|
3422
|
+
Text: MarkdownText,
|
|
3423
|
+
// `Override` (not `Fallback`) replaces the default tool renderer
|
|
3424
|
+
// entirely so we never fall back to the assistant-ui boilerplate.
|
|
3425
|
+
tools: { Override: ToolArtifactFallback }
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
),
|
|
3429
|
+
/* @__PURE__ */ jsx22(MessageError, {})
|
|
3430
|
+
]
|
|
3431
|
+
}
|
|
3432
|
+
),
|
|
3433
|
+
/* @__PURE__ */ jsx22("div", { className: "aui-assistant-message-footer mt-1 mb-3 ml-1 flex", children: /* @__PURE__ */ jsx22(AssistantActionBar, {}) })
|
|
3434
|
+
]
|
|
3435
|
+
}
|
|
3436
|
+
);
|
|
3437
|
+
};
|
|
3438
|
+
var ASSISTANT_ACTION_ICON_CLASS = cn(
|
|
3439
|
+
"size-6 min-h-6 min-w-6 text-muted-foreground/45 hover:text-muted-foreground/80",
|
|
3440
|
+
// The v2 fill span sits inside `group/tbv2 > span:first-child`. We mute it
|
|
3441
|
+
// here so action-bar buttons read as subtle icons rather than full pills.
|
|
3442
|
+
"[&>span:first-child]:bg-transparent",
|
|
3443
|
+
"[&>span:first-child]:group-hover/tbv2:bg-ghost-fill-hover"
|
|
3444
|
+
);
|
|
3445
|
+
var AssistantActionBar = () => {
|
|
3446
|
+
return /* @__PURE__ */ jsxs12(
|
|
3447
|
+
ActionBarPrimitive.Root,
|
|
3448
|
+
{
|
|
3449
|
+
hideWhenRunning: true,
|
|
3450
|
+
autohide: "never",
|
|
3451
|
+
className: "aui-assistant-action-bar-root flex items-center gap-0 bg-transparent px-0 py-0.5 text-muted-foreground/60",
|
|
3452
|
+
children: [
|
|
3453
|
+
/* @__PURE__ */ jsx22(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs12(
|
|
3454
|
+
TooltipIconButton,
|
|
3455
|
+
{
|
|
3456
|
+
tooltip: "Copy",
|
|
3457
|
+
variant: "ghost",
|
|
3458
|
+
className: ASSISTANT_ACTION_ICON_CLASS,
|
|
3459
|
+
children: [
|
|
3460
|
+
/* @__PURE__ */ jsx22(AuiIf2, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx22(CheckIcon3, { className: "size-3" }) }),
|
|
3461
|
+
/* @__PURE__ */ jsx22(AuiIf2, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx22(CopyIcon2, { className: "size-3" }) })
|
|
3462
|
+
]
|
|
3463
|
+
}
|
|
3464
|
+
) }),
|
|
3465
|
+
/* @__PURE__ */ jsx22(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx22(
|
|
3466
|
+
TooltipIconButton,
|
|
3467
|
+
{
|
|
3468
|
+
tooltip: "Regenerate",
|
|
3469
|
+
variant: "ghost",
|
|
3470
|
+
className: ASSISTANT_ACTION_ICON_CLASS,
|
|
3471
|
+
children: /* @__PURE__ */ jsx22(RefreshCwIcon, { className: "size-3" })
|
|
3472
|
+
}
|
|
3473
|
+
) }),
|
|
3474
|
+
/* @__PURE__ */ jsxs12(ActionBarMorePrimitive.Root, { children: [
|
|
3475
|
+
/* @__PURE__ */ jsx22(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx22(
|
|
3476
|
+
TooltipIconButton,
|
|
3477
|
+
{
|
|
3478
|
+
tooltip: "More",
|
|
3479
|
+
variant: "ghost",
|
|
3480
|
+
className: cn(
|
|
3481
|
+
ASSISTANT_ACTION_ICON_CLASS,
|
|
3482
|
+
"data-[state=open]:text-muted-foreground/80"
|
|
3483
|
+
),
|
|
3484
|
+
children: /* @__PURE__ */ jsx22(MoreHorizontalIcon, { className: "size-3" })
|
|
3485
|
+
}
|
|
3486
|
+
) }),
|
|
3487
|
+
/* @__PURE__ */ jsx22(
|
|
3488
|
+
ActionBarMorePrimitive.Content,
|
|
3489
|
+
{
|
|
3490
|
+
side: "bottom",
|
|
3491
|
+
align: "start",
|
|
3492
|
+
className: "aui-action-bar-more-content z-50 min-w-36 overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-card-elevated",
|
|
3493
|
+
children: /* @__PURE__ */ jsx22(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs12(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none hover:bg-muted focus:bg-muted", children: [
|
|
3494
|
+
/* @__PURE__ */ jsx22(DownloadIcon, { className: "size-4 shrink-0" }),
|
|
3495
|
+
"Export as Markdown"
|
|
3496
|
+
] }) })
|
|
3497
|
+
}
|
|
3498
|
+
)
|
|
3499
|
+
] })
|
|
3500
|
+
]
|
|
3501
|
+
}
|
|
3502
|
+
);
|
|
3503
|
+
};
|
|
3504
|
+
var UserMessageText = () => {
|
|
3505
|
+
return /* @__PURE__ */ jsx22("span", { className: "whitespace-pre-wrap", children: /* @__PURE__ */ jsx22(MessagePartPrimitive.Text, { smooth: false }) });
|
|
3506
|
+
};
|
|
3507
|
+
var UserMessage = () => {
|
|
3508
|
+
const isPanel = useThreadVariant() === "panel";
|
|
3509
|
+
return /* @__PURE__ */ jsxs12(
|
|
3510
|
+
MessagePrimitive2.Root,
|
|
3511
|
+
{
|
|
3512
|
+
className: cn(
|
|
3513
|
+
"aui-user-message-root mx-auto flex w-full max-w-(--thread-max-width) flex-col items-end gap-2",
|
|
3514
|
+
isPanel ? "px-1 py-2" : "px-2 py-3"
|
|
3515
|
+
),
|
|
3516
|
+
"data-role": "user",
|
|
3517
|
+
children: [
|
|
3518
|
+
/* @__PURE__ */ jsx22(UserMessageAttachments, {}),
|
|
3519
|
+
/* @__PURE__ */ jsxs12(
|
|
3520
|
+
motion3.div,
|
|
3521
|
+
{
|
|
3522
|
+
className: cn(
|
|
3523
|
+
"aui-user-message-content relative inline-block max-w-[85%] rounded-2xl bg-bubble-user text-bubble-user-foreground",
|
|
3524
|
+
isPanel ? "px-3 py-2 text-sm" : "max-w-[80%] px-4 py-2.5"
|
|
3525
|
+
),
|
|
3526
|
+
initial: { opacity: 0, y: 8, scale: 0.99 },
|
|
3527
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
3528
|
+
transition: { duration: 0.65, ease: luxuryEase },
|
|
3529
|
+
children: [
|
|
3530
|
+
/* @__PURE__ */ jsx22(MessagePrimitive2.Parts, { components: { Text: UserMessageText } }),
|
|
3531
|
+
/* @__PURE__ */ jsx22("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ jsx22(UserActionBar, {}) })
|
|
3532
|
+
]
|
|
3533
|
+
}
|
|
3534
|
+
)
|
|
3535
|
+
]
|
|
3536
|
+
}
|
|
3537
|
+
);
|
|
3538
|
+
};
|
|
3539
|
+
var UserActionBar = () => {
|
|
3540
|
+
return /* @__PURE__ */ jsx22(
|
|
3541
|
+
ActionBarPrimitive.Root,
|
|
3542
|
+
{
|
|
3543
|
+
hideWhenRunning: true,
|
|
3544
|
+
autohide: "always",
|
|
3545
|
+
className: "aui-user-action-bar-root flex flex-col items-end",
|
|
3546
|
+
children: /* @__PURE__ */ jsx22(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx22(
|
|
3547
|
+
TooltipIconButton,
|
|
3548
|
+
{
|
|
3549
|
+
tooltip: "Edit",
|
|
3550
|
+
variant: "ghost",
|
|
3551
|
+
className: ASSISTANT_ACTION_ICON_CLASS,
|
|
3552
|
+
children: /* @__PURE__ */ jsx22(PencilIcon, { className: "size-3" })
|
|
3553
|
+
}
|
|
3554
|
+
) })
|
|
3555
|
+
}
|
|
3556
|
+
);
|
|
3557
|
+
};
|
|
3558
|
+
var EditComposer = () => {
|
|
3559
|
+
return /* @__PURE__ */ jsx22(MessagePrimitive2.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ jsxs12(ComposerPrimitive3.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
|
|
3560
|
+
/* @__PURE__ */ jsx22(
|
|
3561
|
+
ComposerPrimitive3.Input,
|
|
3562
|
+
{
|
|
3563
|
+
className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
|
|
3564
|
+
autoFocus: true
|
|
3565
|
+
}
|
|
3566
|
+
),
|
|
3567
|
+
/* @__PURE__ */ jsxs12("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
|
|
3568
|
+
/* @__PURE__ */ jsx22(ComposerPrimitive3.Cancel, { asChild: true, children: /* @__PURE__ */ jsx22(TimbalV2Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
|
|
3569
|
+
/* @__PURE__ */ jsx22(ComposerPrimitive3.Send, { asChild: true, children: /* @__PURE__ */ jsx22(TimbalV2Button, { variant: "primary", size: "sm", children: "Update" }) })
|
|
3570
|
+
] })
|
|
3571
|
+
] }) });
|
|
3572
|
+
};
|
|
3573
|
+
|
|
3574
|
+
// src/chat/chat.tsx
|
|
3575
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
3576
|
+
function TimbalChat({
|
|
3577
|
+
workforceId,
|
|
3578
|
+
baseUrl,
|
|
3579
|
+
fetch: fetch2,
|
|
3580
|
+
attachments,
|
|
3581
|
+
attachmentsUploadUrl,
|
|
3582
|
+
attachmentsAccept,
|
|
3583
|
+
debug,
|
|
3584
|
+
...threadProps
|
|
3585
|
+
}) {
|
|
3586
|
+
return /* @__PURE__ */ jsx23(
|
|
3587
|
+
TimbalRuntimeProvider,
|
|
3588
|
+
{
|
|
3589
|
+
workforceId,
|
|
3590
|
+
baseUrl,
|
|
3591
|
+
fetch: fetch2,
|
|
3592
|
+
attachments,
|
|
3593
|
+
attachmentsUploadUrl,
|
|
3594
|
+
attachmentsAccept,
|
|
3595
|
+
debug,
|
|
3596
|
+
children: /* @__PURE__ */ jsx23(Thread, { ...threadProps })
|
|
3597
|
+
}
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
export {
|
|
3602
|
+
studioTopbarPillHeightClass,
|
|
3603
|
+
studioTopbarIconPillClass,
|
|
3604
|
+
studioPlaygroundGradientClass,
|
|
3605
|
+
studioSecondaryChromeClass,
|
|
3606
|
+
studioSearchChromeClass,
|
|
3607
|
+
studioIntegrationCardClass,
|
|
3608
|
+
studioSidebarPanelClass,
|
|
3609
|
+
studioSidebarNavItemClass,
|
|
3610
|
+
studioSidebarNavItemLayout,
|
|
3611
|
+
studioSidebarNavItemIdleClass,
|
|
3612
|
+
studioSidebarCollapsedRailItemIdleClass,
|
|
3613
|
+
studioSidebarCollapsedRailItemActiveClass,
|
|
3614
|
+
studioSidebarNavItemActiveClass,
|
|
3615
|
+
TooltipIconButton,
|
|
3616
|
+
UserMessageAttachments,
|
|
3617
|
+
ComposerAttachments,
|
|
3618
|
+
ComposerAddAttachment,
|
|
3619
|
+
ArtifactCard,
|
|
3620
|
+
ChartArtifactView,
|
|
3621
|
+
QuestionArtifactView,
|
|
3622
|
+
HtmlArtifactView,
|
|
3623
|
+
JsonArtifactView,
|
|
3624
|
+
TableArtifactView,
|
|
3625
|
+
isUiBinding,
|
|
3626
|
+
getPath,
|
|
3627
|
+
setPath,
|
|
3628
|
+
resolveBindable,
|
|
3629
|
+
useUiState,
|
|
3630
|
+
useUiDispatch,
|
|
3631
|
+
UiEventProvider,
|
|
3632
|
+
useUiEventEmitter,
|
|
3633
|
+
UiCustomNodeRegistryProvider,
|
|
3634
|
+
useUiCustomNodeRegistry,
|
|
3635
|
+
UiNodeView,
|
|
3636
|
+
UiArtifactView,
|
|
3637
|
+
defaultArtifactRenderers,
|
|
3638
|
+
ArtifactRegistryProvider,
|
|
3639
|
+
useArtifactRegistry,
|
|
3640
|
+
ArtifactView,
|
|
3641
|
+
isArtifact,
|
|
3642
|
+
ARTIFACT_FENCE_LANGUAGES,
|
|
3643
|
+
isArtifactFenceLanguage,
|
|
3644
|
+
parseArtifactFromToolResult,
|
|
3645
|
+
findMarkdownArtifacts,
|
|
3646
|
+
splitMarkdownByArtifacts,
|
|
3647
|
+
MarkdownText,
|
|
3648
|
+
luxuryEase,
|
|
3649
|
+
getAccessToken,
|
|
3650
|
+
setAccessToken,
|
|
3651
|
+
getRefreshToken,
|
|
3652
|
+
setRefreshToken,
|
|
3653
|
+
clearTokens,
|
|
3654
|
+
refreshAccessToken,
|
|
3655
|
+
authFetch,
|
|
3656
|
+
fetchCurrentUser,
|
|
3657
|
+
DEFAULT_UPLOAD_ACCEPT,
|
|
3658
|
+
createDefaultAttachmentAdapter,
|
|
3659
|
+
createUploadAttachmentAdapter,
|
|
3660
|
+
resolveAttachmentAdapter,
|
|
3661
|
+
useTimbalStream,
|
|
3662
|
+
useTimbalRuntime,
|
|
3663
|
+
TimbalRuntimeProvider,
|
|
3664
|
+
useToolRunning,
|
|
3665
|
+
ToolFallback,
|
|
3666
|
+
ToolArtifactFallback,
|
|
3667
|
+
Composer,
|
|
3668
|
+
Suggestions,
|
|
3669
|
+
useResolvedSuggestions,
|
|
3670
|
+
Thread,
|
|
3671
|
+
TimbalChat
|
|
3672
|
+
};
|