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