@timbal-ai/timbal-react 0.2.2 → 0.4.0

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