@timbal-ai/timbal-react 0.2.2 → 0.3.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,115 @@ 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
+
326
870
  // src/components/attachment.tsx
327
871
  import { useEffect as useEffect2, useState as useState2 } from "react";
328
872
  import { XIcon as XIcon2, PlusIcon, FileText } from "lucide-react";
@@ -785,81 +1329,1123 @@ import {
785
1329
  import remarkGfm from "remark-gfm";
786
1330
  import remarkMath from "remark-math";
787
1331
  import rehypeKatex from "rehype-katex";
788
- import { memo, useState as useState4 } from "react";
789
- import { CheckIcon, CopyIcon } from "lucide-react";
1332
+ import { memo, useState as useState5 } from "react";
1333
+ import { CheckIcon as CheckIcon2, CopyIcon } from "lucide-react";
790
1334
 
791
1335
  // src/components/syntax-highlighter.tsx
792
- import { useEffect as useEffect3, useState as useState3 } from "react";
1336
+ import { useEffect as useEffect3, useState as useState4 } from "react";
793
1337
  import { createHighlighterCore } from "shiki/core";
794
1338
  import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
795
- import langJavascript from "shiki/langs/javascript.mjs";
796
- import langTypescript from "shiki/langs/typescript.mjs";
797
- import langPython from "shiki/langs/python.mjs";
798
- import langHtml from "shiki/langs/html.mjs";
799
- import langCss from "shiki/langs/css.mjs";
800
- import langJson from "shiki/langs/json.mjs";
801
- import langBash from "shiki/langs/bash.mjs";
802
- import langMarkdown from "shiki/langs/markdown.mjs";
803
- import langJsx from "shiki/langs/jsx.mjs";
804
- import langTsx from "shiki/langs/tsx.mjs";
805
- import langSql from "shiki/langs/sql.mjs";
806
- import langYaml from "shiki/langs/yaml.mjs";
807
- import langRust from "shiki/langs/rust.mjs";
808
- import langGo from "shiki/langs/go.mjs";
809
- import langJava from "shiki/langs/java.mjs";
810
- import langC from "shiki/langs/c.mjs";
811
- import langCpp from "shiki/langs/cpp.mjs";
812
- import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
813
- import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
814
- import { jsx as jsx8 } from "react/jsx-runtime";
815
- var SHIKI_THEME_DARK = "vitesse-dark";
816
- var SHIKI_THEME_LIGHT = "vitesse-light";
817
- var highlighterPromise = null;
818
- function getHighlighter() {
819
- if (!highlighterPromise) {
820
- highlighterPromise = createHighlighterCore({
821
- themes: [themeVitesseDark, themeVitesseLight],
822
- langs: [
823
- langJavascript,
824
- langTypescript,
825
- langPython,
826
- langHtml,
827
- langCss,
828
- langJson,
829
- langBash,
830
- langMarkdown,
831
- langJsx,
832
- langTsx,
833
- langSql,
834
- langYaml,
835
- langRust,
836
- langGo,
837
- langJava,
838
- langC,
839
- langCpp
840
- ],
841
- engine: createJavaScriptRegexEngine()
842
- });
843
- }
844
- return highlighterPromise;
845
- }
846
- getHighlighter();
847
- var ShikiSyntaxHighlighter = ({
848
- components: { Pre, Code: Code2 },
849
- language,
850
- code
851
- }) => {
852
- const [html, setHtml] = useState3(null);
853
- useEffect3(() => {
854
- let cancelled = false;
855
- (async () => {
856
- try {
857
- const highlighter = await getHighlighter();
858
- const loadedLangs = highlighter.getLoadedLanguages();
859
- if (!loadedLangs.includes(language)) {
860
- if (!cancelled) setHtml(null);
861
- return;
862
- }
1339
+
1340
+ // src/artifacts/registry.tsx
1341
+ import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3 } from "react";
1342
+
1343
+ // src/artifacts/chart-artifact.tsx
1344
+ import { useMemo as useMemo2 } from "react";
1345
+
1346
+ // src/artifacts/artifact-card.tsx
1347
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1348
+ var ArtifactCard = ({ title, kind, className, bodyClassName, toolbar, children }) => {
1349
+ const hasHeader = Boolean(title || toolbar);
1350
+ return /* @__PURE__ */ jsxs5(
1351
+ "div",
1352
+ {
1353
+ className: cn(
1354
+ "aui-artifact-root my-3 overflow-hidden rounded-xl border border-border/60 bg-background shadow-sm",
1355
+ className
1356
+ ),
1357
+ "data-artifact-kind": kind,
1358
+ children: [
1359
+ hasHeader && /* @__PURE__ */ jsxs5("div", { className: "aui-artifact-header flex items-center gap-2 border-b border-border/40 bg-muted/30 px-3 py-1.5", children: [
1360
+ title && /* @__PURE__ */ jsx8("span", { className: "aui-artifact-title flex-1 truncate text-xs font-semibold text-foreground/80", children: title }),
1361
+ !title && /* @__PURE__ */ jsx8("span", { className: "flex-1" }),
1362
+ toolbar
1363
+ ] }),
1364
+ /* @__PURE__ */ jsx8("div", { className: cn("aui-artifact-body", bodyClassName), children })
1365
+ ]
1366
+ }
1367
+ );
1368
+ };
1369
+
1370
+ // src/artifacts/chart-artifact.tsx
1371
+ import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1372
+ var ChartArtifactView = ({
1373
+ artifact
1374
+ }) => {
1375
+ const { type: _t, chartType = "bar", data = [] } = artifact;
1376
+ const xKey = artifact.xKey ?? inferXKey(data);
1377
+ const dataKeys = useMemo2(() => {
1378
+ if (Array.isArray(artifact.dataKey)) return artifact.dataKey;
1379
+ if (typeof artifact.dataKey === "string") return [artifact.dataKey];
1380
+ return inferDataKeys(data, xKey);
1381
+ }, [artifact.dataKey, data, xKey]);
1382
+ return /* @__PURE__ */ jsx9(ArtifactCard, { title: artifact.title, kind: "chart", children: /* @__PURE__ */ jsxs6("div", { className: "aui-artifact-chart p-3", children: [
1383
+ /* @__PURE__ */ jsx9(
1384
+ ChartSvg,
1385
+ {
1386
+ chartType,
1387
+ data,
1388
+ xKey,
1389
+ dataKeys,
1390
+ unit: artifact.unit
1391
+ }
1392
+ ),
1393
+ dataKeys.length > 1 && /* @__PURE__ */ jsx9(Legend, { dataKeys })
1394
+ ] }) });
1395
+ };
1396
+ var COLORS = [
1397
+ "var(--primary, #6366f1)",
1398
+ "#22c55e",
1399
+ "#f59e0b",
1400
+ "#ef4444",
1401
+ "#06b6d4",
1402
+ "#a855f7"
1403
+ ];
1404
+ var W = 600;
1405
+ var H = 240;
1406
+ var PAD = { top: 12, right: 16, bottom: 28, left: 36 };
1407
+ var ChartSvg = ({ chartType, data, xKey, dataKeys, unit }) => {
1408
+ if (data.length === 0 || dataKeys.length === 0) {
1409
+ return /* @__PURE__ */ jsx9(EmptyState, {});
1410
+ }
1411
+ if (chartType === "pie") {
1412
+ return /* @__PURE__ */ jsx9(PieChart, { data, xKey, dataKey: dataKeys[0] });
1413
+ }
1414
+ const innerW = W - PAD.left - PAD.right;
1415
+ const innerH = H - PAD.top - PAD.bottom;
1416
+ const all = dataKeys.flatMap((k) => data.map((d) => toNum(d[k])));
1417
+ const maxV = Math.max(0, ...all);
1418
+ const minV = Math.min(0, ...all);
1419
+ const range = maxV - minV || 1;
1420
+ const yScale = (v) => PAD.top + innerH - (v - minV) / range * innerH;
1421
+ const xCount = data.length;
1422
+ const xStep = xCount > 1 ? innerW / (xCount - 1) : innerW;
1423
+ const xPos = (i) => chartType === "bar" ? PAD.left + innerW * (i + 0.5) / xCount : PAD.left + i * xStep;
1424
+ const ticks = niceTicks(minV, maxV);
1425
+ return /* @__PURE__ */ jsxs6(
1426
+ "svg",
1427
+ {
1428
+ viewBox: `0 0 ${W} ${H}`,
1429
+ className: "aui-artifact-chart-svg w-full",
1430
+ role: "img",
1431
+ "aria-label": "Chart",
1432
+ children: [
1433
+ ticks.map((t, i) => /* @__PURE__ */ jsxs6("g", { children: [
1434
+ /* @__PURE__ */ jsx9(
1435
+ "line",
1436
+ {
1437
+ x1: PAD.left,
1438
+ x2: W - PAD.right,
1439
+ y1: yScale(t),
1440
+ y2: yScale(t),
1441
+ stroke: "currentColor",
1442
+ strokeOpacity: 0.08
1443
+ }
1444
+ ),
1445
+ /* @__PURE__ */ jsx9(
1446
+ "text",
1447
+ {
1448
+ x: PAD.left - 6,
1449
+ y: yScale(t),
1450
+ textAnchor: "end",
1451
+ dominantBaseline: "middle",
1452
+ className: "fill-muted-foreground text-[10px]",
1453
+ children: formatTick(t, unit)
1454
+ }
1455
+ )
1456
+ ] }, i)),
1457
+ data.map((d, i) => /* @__PURE__ */ jsx9(
1458
+ "text",
1459
+ {
1460
+ x: xPos(i),
1461
+ y: H - PAD.bottom + 14,
1462
+ textAnchor: "middle",
1463
+ className: "fill-muted-foreground text-[10px]",
1464
+ children: String(d[xKey] ?? i)
1465
+ },
1466
+ i
1467
+ )),
1468
+ chartType === "bar" && renderBars({ data, dataKeys, xCount, xPos, yScale, minV, innerW }),
1469
+ chartType === "line" && dataKeys.map((k, ki) => /* @__PURE__ */ jsx9(
1470
+ Polyline,
1471
+ {
1472
+ data,
1473
+ dataKey: k,
1474
+ xPos,
1475
+ yScale,
1476
+ color: COLORS[ki % COLORS.length]
1477
+ },
1478
+ k
1479
+ )),
1480
+ chartType === "area" && dataKeys.map((k, ki) => /* @__PURE__ */ jsx9(
1481
+ Area,
1482
+ {
1483
+ data,
1484
+ dataKey: k,
1485
+ xPos,
1486
+ yScale,
1487
+ baseY: yScale(Math.max(0, minV)),
1488
+ color: COLORS[ki % COLORS.length]
1489
+ },
1490
+ k
1491
+ ))
1492
+ ]
1493
+ }
1494
+ );
1495
+ };
1496
+ function renderBars(args) {
1497
+ const { data, dataKeys, xCount, xPos, yScale, minV, innerW } = args;
1498
+ const groupWidth = innerW / xCount * 0.7;
1499
+ const barWidth = groupWidth / dataKeys.length;
1500
+ const baseY = yScale(Math.max(0, minV));
1501
+ return dataKeys.flatMap(
1502
+ (k, ki) => data.map((d, i) => {
1503
+ const v = toNum(d[k]);
1504
+ const y = yScale(v);
1505
+ const x = xPos(i) - groupWidth / 2 + ki * barWidth;
1506
+ const top = Math.min(y, baseY);
1507
+ const height = Math.abs(y - baseY);
1508
+ return /* @__PURE__ */ jsx9(
1509
+ "rect",
1510
+ {
1511
+ x,
1512
+ y: top,
1513
+ width: Math.max(1, barWidth - 2),
1514
+ height: Math.max(1, height),
1515
+ rx: 2,
1516
+ fill: COLORS[ki % COLORS.length]
1517
+ },
1518
+ `${k}-${i}`
1519
+ );
1520
+ })
1521
+ );
1522
+ }
1523
+ var Polyline = ({ data, dataKey, xPos, yScale, color }) => {
1524
+ const points = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
1525
+ return /* @__PURE__ */ jsx9(
1526
+ "polyline",
1527
+ {
1528
+ points,
1529
+ fill: "none",
1530
+ stroke: color,
1531
+ strokeWidth: 2,
1532
+ strokeLinejoin: "round",
1533
+ strokeLinecap: "round"
1534
+ }
1535
+ );
1536
+ };
1537
+ var Area = ({ data, dataKey, xPos, yScale, baseY, color }) => {
1538
+ if (data.length === 0) return null;
1539
+ const top = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
1540
+ const path = `M ${xPos(0)},${baseY} L ${top} L ${xPos(data.length - 1)},${baseY} Z`;
1541
+ return /* @__PURE__ */ jsxs6(Fragment, { children: [
1542
+ /* @__PURE__ */ jsx9("path", { d: path, fill: color, fillOpacity: 0.18 }),
1543
+ /* @__PURE__ */ jsx9(Polyline, { data, dataKey, xPos, yScale, color })
1544
+ ] });
1545
+ };
1546
+ var PieChart = ({ data, xKey, dataKey }) => {
1547
+ const cx = W / 2;
1548
+ const cy = H / 2;
1549
+ const r = Math.min(W, H) / 2 - 16;
1550
+ const total = data.reduce((sum, d) => sum + toNum(d[dataKey]), 0) || 1;
1551
+ let acc = 0;
1552
+ return /* @__PURE__ */ jsx9(
1553
+ "svg",
1554
+ {
1555
+ viewBox: `0 0 ${W} ${H}`,
1556
+ className: "aui-artifact-chart-svg w-full",
1557
+ role: "img",
1558
+ "aria-label": "Pie chart",
1559
+ children: data.map((d, i) => {
1560
+ const value = toNum(d[dataKey]);
1561
+ const start = acc / total * Math.PI * 2;
1562
+ acc += value;
1563
+ const end = acc / total * Math.PI * 2;
1564
+ return /* @__PURE__ */ jsx9(
1565
+ PieSlice,
1566
+ {
1567
+ cx,
1568
+ cy,
1569
+ r,
1570
+ start,
1571
+ end,
1572
+ color: COLORS[i % COLORS.length],
1573
+ label: String(d[xKey] ?? i)
1574
+ },
1575
+ i
1576
+ );
1577
+ })
1578
+ }
1579
+ );
1580
+ };
1581
+ var PieSlice = ({ cx, cy, r, start, end, color, label }) => {
1582
+ const x1 = cx + Math.sin(start) * r;
1583
+ const y1 = cy - Math.cos(start) * r;
1584
+ const x2 = cx + Math.sin(end) * r;
1585
+ const y2 = cy - Math.cos(end) * r;
1586
+ const large = end - start > Math.PI ? 1 : 0;
1587
+ const path = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
1588
+ const mid = (start + end) / 2;
1589
+ const lx = cx + Math.sin(mid) * (r * 0.65);
1590
+ const ly = cy - Math.cos(mid) * (r * 0.65);
1591
+ return /* @__PURE__ */ jsxs6("g", { children: [
1592
+ /* @__PURE__ */ jsx9("path", { d: path, fill: color, stroke: "var(--background, #fff)", strokeWidth: 1 }),
1593
+ /* @__PURE__ */ jsx9(
1594
+ "text",
1595
+ {
1596
+ x: lx,
1597
+ y: ly,
1598
+ textAnchor: "middle",
1599
+ dominantBaseline: "middle",
1600
+ className: "fill-white text-[10px] font-semibold",
1601
+ children: label
1602
+ }
1603
+ )
1604
+ ] });
1605
+ };
1606
+ 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__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5", children: [
1607
+ /* @__PURE__ */ jsx9(
1608
+ "span",
1609
+ {
1610
+ className: "inline-block size-2 rounded-sm",
1611
+ style: { background: COLORS[i % COLORS.length] },
1612
+ "aria-hidden": true
1613
+ }
1614
+ ),
1615
+ k
1616
+ ] }, k)) });
1617
+ 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" });
1618
+ function inferXKey(data) {
1619
+ if (data.length === 0) return "x";
1620
+ for (const k of Object.keys(data[0])) {
1621
+ if (typeof data[0][k] !== "number") return k;
1622
+ }
1623
+ return Object.keys(data[0])[0] ?? "x";
1624
+ }
1625
+ function inferDataKeys(data, xKey) {
1626
+ if (data.length === 0) return [];
1627
+ return Object.keys(data[0]).filter(
1628
+ (k) => k !== xKey && typeof data[0][k] === "number"
1629
+ );
1630
+ }
1631
+ function toNum(value) {
1632
+ const n = typeof value === "number" ? value : Number(value);
1633
+ return Number.isFinite(n) ? n : 0;
1634
+ }
1635
+ function niceTicks(min, max, count = 4) {
1636
+ if (max === min) return [min];
1637
+ const range = max - min;
1638
+ const step = niceStep(range / count);
1639
+ const start = Math.floor(min / step) * step;
1640
+ const out = [];
1641
+ for (let v = start; v <= max + step / 2; v += step) {
1642
+ out.push(round(v));
1643
+ }
1644
+ return out;
1645
+ }
1646
+ function niceStep(raw) {
1647
+ const exp = Math.floor(Math.log10(Math.abs(raw))) || 0;
1648
+ const base = Math.pow(10, exp);
1649
+ const norm = raw / base;
1650
+ let nice = 1;
1651
+ if (norm >= 5) nice = 5;
1652
+ else if (norm >= 2) nice = 2;
1653
+ return nice * base;
1654
+ }
1655
+ function round(v) {
1656
+ return Math.round(v * 1e6) / 1e6;
1657
+ }
1658
+ function formatTick(v, unit) {
1659
+ const s = Math.abs(v) >= 1e3 ? `${(v / 1e3).toFixed(1)}k` : String(round(v));
1660
+ return unit ? `${s}${unit}` : s;
1661
+ }
1662
+
1663
+ // src/artifacts/question-artifact.tsx
1664
+ import { useState as useState3 } from "react";
1665
+ import { useThreadRuntime } from "@assistant-ui/react";
1666
+ import { CheckIcon } from "lucide-react";
1667
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1668
+ var QuestionArtifactView = ({
1669
+ artifact
1670
+ }) => {
1671
+ const runtime = useThreadRuntime();
1672
+ const [selected, setSelected] = useState3([]);
1673
+ const [submitted, setSubmitted] = useState3(null);
1674
+ const isMulti = artifact.multi === true;
1675
+ const send = (labels) => {
1676
+ if (labels.length === 0) return;
1677
+ const text = labels.join(", ");
1678
+ setSubmitted(text);
1679
+ runtime.append({ role: "user", content: [{ type: "text", text }] });
1680
+ };
1681
+ const onPick = (option) => {
1682
+ if (submitted) return;
1683
+ if (!isMulti) {
1684
+ send([option.label]);
1685
+ return;
1686
+ }
1687
+ setSelected(
1688
+ (prev) => prev.includes(option.id) ? prev.filter((id) => id !== option.id) : [...prev, option.id]
1689
+ );
1690
+ };
1691
+ const onConfirm = () => {
1692
+ const labels = artifact.options.filter((o) => selected.includes(o.id)).map((o) => o.label);
1693
+ send(labels);
1694
+ };
1695
+ return /* @__PURE__ */ jsx10(ArtifactCard, { kind: "question", children: /* @__PURE__ */ jsxs7("div", { className: "aui-artifact-question p-3", children: [
1696
+ artifact.prompt && /* @__PURE__ */ jsx10("p", { className: "aui-artifact-question-prompt mb-2 text-sm text-foreground/85", children: artifact.prompt }),
1697
+ /* @__PURE__ */ jsx10("div", { className: "aui-artifact-question-options flex flex-col gap-1.5", children: artifact.options.map((option) => {
1698
+ const isSelected = isMulti && selected.includes(option.id);
1699
+ const isDisabled = Boolean(submitted);
1700
+ return /* @__PURE__ */ jsxs7(
1701
+ "button",
1702
+ {
1703
+ type: "button",
1704
+ disabled: isDisabled,
1705
+ onClick: () => onPick(option),
1706
+ className: cn(
1707
+ "aui-artifact-question-option flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors",
1708
+ "border-border/60 hover:border-primary/40 hover:bg-muted/40",
1709
+ isSelected && "border-primary/60 bg-primary/5",
1710
+ isDisabled && "cursor-not-allowed opacity-60 hover:border-border/60 hover:bg-transparent"
1711
+ ),
1712
+ children: [
1713
+ /* @__PURE__ */ jsx10(
1714
+ "span",
1715
+ {
1716
+ className: cn(
1717
+ "flex size-4 shrink-0 items-center justify-center rounded-full border",
1718
+ isSelected ? "border-primary bg-primary text-primary-foreground" : "border-border"
1719
+ ),
1720
+ "aria-hidden": true,
1721
+ children: isSelected && /* @__PURE__ */ jsx10(CheckIcon, { className: "size-3" })
1722
+ }
1723
+ ),
1724
+ /* @__PURE__ */ jsxs7("div", { className: "aui-artifact-question-option-text flex-1", children: [
1725
+ /* @__PURE__ */ jsx10("div", { className: "font-medium text-foreground/90", children: option.label }),
1726
+ option.description && /* @__PURE__ */ jsx10("div", { className: "text-xs text-muted-foreground", children: option.description })
1727
+ ] })
1728
+ ]
1729
+ },
1730
+ option.id
1731
+ );
1732
+ }) }),
1733
+ isMulti && !submitted && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx10(
1734
+ Button,
1735
+ {
1736
+ type: "button",
1737
+ size: "sm",
1738
+ disabled: selected.length === 0,
1739
+ onClick: onConfirm,
1740
+ children: "Confirm"
1741
+ }
1742
+ ) }),
1743
+ submitted && /* @__PURE__ */ jsxs7("p", { className: "aui-artifact-question-submitted mt-2 text-xs text-muted-foreground", children: [
1744
+ "Sent: ",
1745
+ submitted
1746
+ ] })
1747
+ ] }) });
1748
+ };
1749
+
1750
+ // src/artifacts/html-artifact.tsx
1751
+ import { jsx as jsx11 } from "react/jsx-runtime";
1752
+ var HtmlArtifactView = ({ artifact }) => {
1753
+ const sandboxed = artifact.sandboxed !== false;
1754
+ const sandbox = sandboxed ? "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-pointer-lock" : void 0;
1755
+ const height = artifact.height ?? "320px";
1756
+ return /* @__PURE__ */ jsx11(ArtifactCard, { title: artifact.title, kind: "html", children: /* @__PURE__ */ jsx11(
1757
+ "iframe",
1758
+ {
1759
+ title: artifact.title ?? "HTML artifact",
1760
+ srcDoc: artifact.content,
1761
+ sandbox,
1762
+ className: "aui-artifact-html w-full border-0 bg-background",
1763
+ style: { height }
1764
+ }
1765
+ ) });
1766
+ };
1767
+
1768
+ // src/artifacts/json-artifact.tsx
1769
+ import { jsx as jsx12 } from "react/jsx-runtime";
1770
+ var JsonArtifactView = ({
1771
+ artifact
1772
+ }) => {
1773
+ const data = "data" in artifact ? artifact.data : artifact;
1774
+ const title = artifact.title;
1775
+ let body;
1776
+ try {
1777
+ body = JSON.stringify(data, null, 2);
1778
+ } catch {
1779
+ body = String(data);
1780
+ }
1781
+ 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 }) });
1782
+ };
1783
+
1784
+ // src/artifacts/table-artifact.tsx
1785
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1786
+ var TableArtifactView = ({ artifact }) => {
1787
+ const rows = artifact.rows ?? [];
1788
+ const columns = artifact.columns ?? deriveColumns(rows);
1789
+ return /* @__PURE__ */ jsx13(ArtifactCard, { title: artifact.title, kind: "table", children: /* @__PURE__ */ jsx13("div", { className: "aui-artifact-table-wrap overflow-x-auto", children: /* @__PURE__ */ jsxs8("table", { className: "aui-artifact-table w-full border-collapse text-sm", children: [
1790
+ /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsx13("tr", { className: "border-b border-border/40 bg-muted/20", children: columns.map((col) => /* @__PURE__ */ jsx13(
1791
+ "th",
1792
+ {
1793
+ className: "px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground",
1794
+ children: col.label ?? col.key
1795
+ },
1796
+ col.key
1797
+ )) }) }),
1798
+ /* @__PURE__ */ jsx13("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx13(
1799
+ "tr",
1800
+ {
1801
+ className: "border-b border-border/30 transition-colors last:border-b-0 hover:bg-muted/20",
1802
+ children: columns.map((col) => /* @__PURE__ */ jsx13(
1803
+ "td",
1804
+ {
1805
+ className: "px-3 py-2 align-top text-foreground/85",
1806
+ children: formatCell(row[col.key])
1807
+ },
1808
+ col.key
1809
+ ))
1810
+ },
1811
+ i
1812
+ )) })
1813
+ ] }) }) });
1814
+ };
1815
+ function deriveColumns(rows) {
1816
+ const seen = /* @__PURE__ */ new Set();
1817
+ const cols = [];
1818
+ for (const row of rows) {
1819
+ for (const key of Object.keys(row)) {
1820
+ if (!seen.has(key)) {
1821
+ seen.add(key);
1822
+ cols.push({ key });
1823
+ }
1824
+ }
1825
+ }
1826
+ return cols;
1827
+ }
1828
+ function formatCell(value) {
1829
+ if (value === null || value === void 0) return "";
1830
+ if (typeof value === "object") return JSON.stringify(value);
1831
+ return String(value);
1832
+ }
1833
+
1834
+ // src/artifacts/ui/ui-artifact.tsx
1835
+ import { useReducer } from "react";
1836
+
1837
+ // src/artifacts/ui/types.ts
1838
+ function isUiBinding(value) {
1839
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.$bind === "string";
1840
+ }
1841
+
1842
+ // src/artifacts/ui/state.ts
1843
+ function uiStateReducer(state, action) {
1844
+ switch (action.type) {
1845
+ case "set":
1846
+ return setPath(state, action.path, action.value);
1847
+ case "toggle": {
1848
+ const current = getPath(state, action.path);
1849
+ return setPath(state, action.path, !current);
1850
+ }
1851
+ case "replace":
1852
+ return action.state;
1853
+ }
1854
+ }
1855
+ function getPath(state, path) {
1856
+ if (!path) return void 0;
1857
+ const parts = path.split(".");
1858
+ let cursor = state;
1859
+ for (const part of parts) {
1860
+ if (typeof cursor !== "object" || cursor === null) return void 0;
1861
+ cursor = cursor[part];
1862
+ }
1863
+ return cursor;
1864
+ }
1865
+ function setPath(state, path, value) {
1866
+ if (!path) return state;
1867
+ const parts = path.split(".");
1868
+ const next = { ...state };
1869
+ let cursor = next;
1870
+ for (let i = 0; i < parts.length - 1; i++) {
1871
+ const key = parts[i];
1872
+ const child = cursor[key];
1873
+ const cloned = typeof child === "object" && child !== null && !Array.isArray(child) ? { ...child } : {};
1874
+ cursor[key] = cloned;
1875
+ cursor = cloned;
1876
+ }
1877
+ cursor[parts[parts.length - 1]] = value;
1878
+ return next;
1879
+ }
1880
+ function resolveBindable(value, state) {
1881
+ if (isUiBinding(value)) {
1882
+ return getPath(state, value.$bind);
1883
+ }
1884
+ return value;
1885
+ }
1886
+
1887
+ // src/artifacts/ui/registry.tsx
1888
+ import {
1889
+ createContext as createContext2,
1890
+ useContext as useContext2
1891
+ } from "react";
1892
+ import { jsx as jsx14 } from "react/jsx-runtime";
1893
+ var UiStateContext = createContext2({});
1894
+ var UiDispatchContext = createContext2(() => {
1895
+ });
1896
+ var UiStateProvider = ({ state, dispatch, children }) => /* @__PURE__ */ jsx14(UiStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx14(UiDispatchContext.Provider, { value: dispatch, children }) });
1897
+ function useUiState() {
1898
+ return useContext2(UiStateContext);
1899
+ }
1900
+ function useUiDispatch() {
1901
+ return useContext2(UiDispatchContext);
1902
+ }
1903
+ var UiEventContext = createContext2(
1904
+ null
1905
+ );
1906
+ var UiEventProvider = ({ onEvent, children }) => /* @__PURE__ */ jsx14(UiEventContext.Provider, { value: onEvent, children });
1907
+ function useUiEventEmitter() {
1908
+ return useContext2(UiEventContext);
1909
+ }
1910
+ var UiCustomNodeRegistryContext = createContext2({});
1911
+ var UiCustomNodeRegistryProvider = ({ renderers, children }) => /* @__PURE__ */ jsx14(UiCustomNodeRegistryContext.Provider, { value: renderers, children });
1912
+ function useUiCustomNodeRegistry() {
1913
+ return useContext2(UiCustomNodeRegistryContext);
1914
+ }
1915
+
1916
+ // src/artifacts/ui/nodes.tsx
1917
+ import { useCallback as useCallback2 } from "react";
1918
+ import { motion } from "motion/react";
1919
+ import { useThreadRuntime as useThreadRuntime2 } from "@assistant-ui/react";
1920
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1921
+ var UiNodeView = ({ node }) => {
1922
+ switch (node.kind) {
1923
+ case "box":
1924
+ return /* @__PURE__ */ jsx15(BoxNode, { node });
1925
+ case "text":
1926
+ return /* @__PURE__ */ jsx15(TextNode, { node });
1927
+ case "heading":
1928
+ return /* @__PURE__ */ jsx15(HeadingNode, { node });
1929
+ case "badge":
1930
+ return /* @__PURE__ */ jsx15(BadgeNode, { node });
1931
+ case "button":
1932
+ return /* @__PURE__ */ jsx15(ButtonNode, { node });
1933
+ case "toggle":
1934
+ return /* @__PURE__ */ jsx15(ToggleNode, { node });
1935
+ case "slider":
1936
+ return /* @__PURE__ */ jsx15(SliderNode, { node });
1937
+ case "tooltip":
1938
+ return /* @__PURE__ */ jsx15(TooltipNode, { node });
1939
+ case "draggable":
1940
+ return /* @__PURE__ */ jsx15(DraggableNode, { node });
1941
+ case "custom":
1942
+ return /* @__PURE__ */ jsx15(CustomNode, { node });
1943
+ default:
1944
+ return null;
1945
+ }
1946
+ };
1947
+ function useActionRunner() {
1948
+ const state = useUiState();
1949
+ const dispatch = useUiDispatch();
1950
+ const runtime = useThreadRuntime2();
1951
+ const emit = useUiEventEmitter();
1952
+ return useCallback2(
1953
+ (actions) => {
1954
+ if (!actions) return;
1955
+ const list = Array.isArray(actions) ? actions : [actions];
1956
+ for (const action of list) {
1957
+ switch (action.kind) {
1958
+ case "message": {
1959
+ const text = resolveBindable(action.text, state);
1960
+ if (typeof text === "string" && text.length > 0) {
1961
+ runtime?.append({
1962
+ role: "user",
1963
+ content: [{ type: "text", text }]
1964
+ });
1965
+ }
1966
+ break;
1967
+ }
1968
+ case "set": {
1969
+ const value = resolveBindable(action.value, state);
1970
+ dispatch({ type: "set", path: action.path, value });
1971
+ break;
1972
+ }
1973
+ case "toggle": {
1974
+ dispatch({ type: "toggle", path: action.path });
1975
+ break;
1976
+ }
1977
+ case "emit": {
1978
+ emit?.({ name: action.name, payload: action.payload });
1979
+ break;
1980
+ }
1981
+ }
1982
+ }
1983
+ },
1984
+ [state, dispatch, runtime, emit]
1985
+ );
1986
+ }
1987
+ var ALIGN_CLS = {
1988
+ start: "items-start",
1989
+ center: "items-center",
1990
+ end: "items-end",
1991
+ stretch: "items-stretch"
1992
+ };
1993
+ var JUSTIFY_CLS = {
1994
+ start: "justify-start",
1995
+ center: "justify-center",
1996
+ end: "justify-end",
1997
+ between: "justify-between",
1998
+ around: "justify-around"
1999
+ };
2000
+ var BoxNode = ({ node }) => {
2001
+ const dir = node.direction ?? "col";
2002
+ return /* @__PURE__ */ jsx15(
2003
+ "div",
2004
+ {
2005
+ className: cn(
2006
+ "aui-ui-box flex",
2007
+ dir === "col" ? "flex-col" : "flex-row",
2008
+ node.wrap && "flex-wrap",
2009
+ node.align && ALIGN_CLS[node.align],
2010
+ node.justify && JUSTIFY_CLS[node.justify],
2011
+ node.className
2012
+ ),
2013
+ style: {
2014
+ gap: node.gap !== void 0 ? `${node.gap * 0.25}rem` : void 0,
2015
+ padding: node.padding !== void 0 ? `${node.padding * 0.25}rem` : void 0
2016
+ },
2017
+ children: node.children?.map((child, i) => /* @__PURE__ */ jsx15(UiNodeView, { node: child }, child.id ?? i))
2018
+ }
2019
+ );
2020
+ };
2021
+ var TEXT_SIZE = {
2022
+ xs: "text-xs",
2023
+ sm: "text-sm",
2024
+ base: "text-base",
2025
+ lg: "text-lg"
2026
+ };
2027
+ var TEXT_WEIGHT = {
2028
+ normal: "font-normal",
2029
+ medium: "font-medium",
2030
+ semibold: "font-semibold",
2031
+ bold: "font-bold"
2032
+ };
2033
+ var TextNode = ({ node }) => {
2034
+ const state = useUiState();
2035
+ const value = resolveBindable(node.value, state);
2036
+ return /* @__PURE__ */ jsx15(
2037
+ "span",
2038
+ {
2039
+ className: cn(
2040
+ "aui-ui-text",
2041
+ node.muted && "text-muted-foreground",
2042
+ node.size && TEXT_SIZE[node.size],
2043
+ node.weight && TEXT_WEIGHT[node.weight],
2044
+ node.className
2045
+ ),
2046
+ children: value === void 0 || value === null ? "" : String(value)
2047
+ }
2048
+ );
2049
+ };
2050
+ var HEADING_CLS = {
2051
+ 1: "text-2xl",
2052
+ 2: "text-xl",
2053
+ 3: "text-lg",
2054
+ 4: "text-base"
2055
+ };
2056
+ var HeadingNode = ({ node }) => {
2057
+ const state = useUiState();
2058
+ const value = String(resolveBindable(node.value, state) ?? "");
2059
+ const level = node.level ?? 2;
2060
+ const cls = cn(
2061
+ "aui-ui-heading font-semibold text-foreground",
2062
+ HEADING_CLS[level],
2063
+ node.className
2064
+ );
2065
+ switch (level) {
2066
+ case 1:
2067
+ return /* @__PURE__ */ jsx15("h1", { className: cls, children: value });
2068
+ case 2:
2069
+ return /* @__PURE__ */ jsx15("h2", { className: cls, children: value });
2070
+ case 3:
2071
+ return /* @__PURE__ */ jsx15("h3", { className: cls, children: value });
2072
+ case 4:
2073
+ return /* @__PURE__ */ jsx15("h4", { className: cls, children: value });
2074
+ }
2075
+ };
2076
+ var BADGE_TONE = {
2077
+ default: "bg-muted text-foreground",
2078
+ primary: "bg-primary/10 text-primary",
2079
+ success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
2080
+ warn: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
2081
+ danger: "bg-destructive/10 text-destructive"
2082
+ };
2083
+ var BadgeNode = ({ node }) => {
2084
+ const state = useUiState();
2085
+ const value = String(resolveBindable(node.value, state) ?? "");
2086
+ return /* @__PURE__ */ jsx15(
2087
+ "span",
2088
+ {
2089
+ className: cn(
2090
+ "aui-ui-badge inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
2091
+ BADGE_TONE[node.tone ?? "default"],
2092
+ node.className
2093
+ ),
2094
+ children: value
2095
+ }
2096
+ );
2097
+ };
2098
+ var ButtonNode = ({ node }) => {
2099
+ const state = useUiState();
2100
+ const run = useActionRunner();
2101
+ const label = String(resolveBindable(node.label, state) ?? "");
2102
+ const disabled = node.disabled !== void 0 ? Boolean(resolveBindable(node.disabled, state)) : false;
2103
+ return /* @__PURE__ */ jsx15(
2104
+ Button,
2105
+ {
2106
+ variant: node.variant ?? "default",
2107
+ size: node.size ?? "default",
2108
+ disabled,
2109
+ className: node.className,
2110
+ onClick: () => run(node.onClick),
2111
+ children: label
2112
+ }
2113
+ );
2114
+ };
2115
+ var ToggleNode = ({ node }) => {
2116
+ const state = useUiState();
2117
+ const dispatch = useUiDispatch();
2118
+ const run = useActionRunner();
2119
+ const value = Boolean(getPath(state, node.binding));
2120
+ const label = node.label ? String(resolveBindable(node.label, state) ?? "") : null;
2121
+ const onToggle = () => {
2122
+ dispatch({ type: "toggle", path: node.binding });
2123
+ run(node.onChange);
2124
+ };
2125
+ return /* @__PURE__ */ jsxs9(
2126
+ "label",
2127
+ {
2128
+ className: cn(
2129
+ "aui-ui-toggle inline-flex cursor-pointer items-center gap-2 text-sm select-none",
2130
+ node.className
2131
+ ),
2132
+ children: [
2133
+ /* @__PURE__ */ jsx15(
2134
+ "button",
2135
+ {
2136
+ type: "button",
2137
+ role: "switch",
2138
+ "aria-checked": value,
2139
+ onClick: onToggle,
2140
+ className: cn(
2141
+ "relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors",
2142
+ value ? "border-primary bg-primary" : "border-border bg-muted hover:bg-muted/80"
2143
+ ),
2144
+ children: /* @__PURE__ */ jsx15(
2145
+ "span",
2146
+ {
2147
+ className: cn(
2148
+ "inline-block size-4 transform rounded-full bg-background shadow transition-transform",
2149
+ value ? "translate-x-4" : "translate-x-0.5"
2150
+ ),
2151
+ "aria-hidden": true
2152
+ }
2153
+ )
2154
+ }
2155
+ ),
2156
+ label && /* @__PURE__ */ jsx15("span", { className: "text-foreground/85", children: label })
2157
+ ]
2158
+ }
2159
+ );
2160
+ };
2161
+ var SliderNode = ({ node }) => {
2162
+ const state = useUiState();
2163
+ const dispatch = useUiDispatch();
2164
+ const run = useActionRunner();
2165
+ const min = node.min ?? 0;
2166
+ const max = node.max ?? 100;
2167
+ const step = node.step ?? 1;
2168
+ const raw = getPath(state, node.binding);
2169
+ const value = typeof raw === "number" ? raw : min;
2170
+ const showValue = node.showValue ?? true;
2171
+ const label = node.label ? String(resolveBindable(node.label, state) ?? "") : null;
2172
+ const onChange = (e) => {
2173
+ const next = Number(e.target.value);
2174
+ dispatch({ type: "set", path: node.binding, value: next });
2175
+ };
2176
+ return /* @__PURE__ */ jsxs9("div", { className: cn("aui-ui-slider flex flex-col gap-1", node.className), children: [
2177
+ (label || showValue) && /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
2178
+ label && /* @__PURE__ */ jsx15("span", { children: label }),
2179
+ showValue && /* @__PURE__ */ jsx15("span", { className: "font-mono", children: value })
2180
+ ] }),
2181
+ /* @__PURE__ */ jsx15(
2182
+ "input",
2183
+ {
2184
+ type: "range",
2185
+ min,
2186
+ max,
2187
+ step,
2188
+ value,
2189
+ onChange,
2190
+ onMouseUp: () => run(node.onChange),
2191
+ onKeyUp: (e) => {
2192
+ if (e.key === "Enter" || e.key === " ") run(node.onChange);
2193
+ },
2194
+ onTouchEnd: () => run(node.onChange),
2195
+ className: "aui-ui-slider-input h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary"
2196
+ }
2197
+ )
2198
+ ] });
2199
+ };
2200
+ var TooltipNode = ({ node }) => {
2201
+ const state = useUiState();
2202
+ const content = String(resolveBindable(node.content, state) ?? "");
2203
+ return /* @__PURE__ */ jsx15(TooltipProvider, { children: /* @__PURE__ */ jsxs9(Tooltip, { children: [
2204
+ /* @__PURE__ */ jsx15(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx15("span", { className: cn("aui-ui-tooltip-trigger inline-flex", node.className), children: /* @__PURE__ */ jsx15(UiNodeView, { node: node.child }) }) }),
2205
+ /* @__PURE__ */ jsx15(TooltipContent, { side: node.side ?? "top", children: content })
2206
+ ] }) });
2207
+ };
2208
+ var DraggableNode = ({ node }) => {
2209
+ const run = useActionRunner();
2210
+ const snapBack = node.snapBack ?? true;
2211
+ const axis = node.axis ?? "both";
2212
+ const dragProp = axis === "both" ? true : axis;
2213
+ return /* @__PURE__ */ jsx15(
2214
+ motion.div,
2215
+ {
2216
+ drag: dragProp,
2217
+ dragMomentum: false,
2218
+ dragSnapToOrigin: snapBack,
2219
+ whileDrag: { scale: 1.02, cursor: "grabbing" },
2220
+ onDragEnd: () => run(node.onDragEnd),
2221
+ className: cn(
2222
+ "aui-ui-draggable inline-block cursor-grab touch-none",
2223
+ node.className
2224
+ ),
2225
+ children: /* @__PURE__ */ jsx15(UiNodeView, { node: node.child })
2226
+ }
2227
+ );
2228
+ };
2229
+ var CustomNode = ({ node }) => {
2230
+ const state = useUiState();
2231
+ const registry = useUiCustomNodeRegistry();
2232
+ const Renderer = registry[node.name];
2233
+ if (!Renderer) return null;
2234
+ const resolvedProps = resolveProps(node.props ?? {}, state);
2235
+ const children = node.children?.map((child, i) => /* @__PURE__ */ jsx15(UiNodeView, { node: child }, child.id ?? i));
2236
+ return /* @__PURE__ */ jsx15(Renderer, { props: resolvedProps, children });
2237
+ };
2238
+ function resolveProps(props, state) {
2239
+ const out = {};
2240
+ for (const [k, v] of Object.entries(props)) {
2241
+ out[k] = resolveBindable(v, state);
2242
+ }
2243
+ return out;
2244
+ }
2245
+
2246
+ // src/artifacts/ui/ui-artifact.tsx
2247
+ import { jsx as jsx16 } from "react/jsx-runtime";
2248
+ var UiArtifactView = ({ artifact }) => {
2249
+ const [state, dispatch] = useReducer(
2250
+ uiStateReducer,
2251
+ artifact.initialState ?? {}
2252
+ );
2253
+ return /* @__PURE__ */ jsx16(ArtifactCard, { title: artifact.title, kind: "ui", children: /* @__PURE__ */ jsx16(UiStateProvider, { state, dispatch, children: /* @__PURE__ */ jsx16("div", { className: "aui-ui-root p-3", children: /* @__PURE__ */ jsx16(UiNodeView, { node: artifact.root }) }) }) });
2254
+ };
2255
+
2256
+ // src/artifacts/registry.tsx
2257
+ import { jsx as jsx17 } from "react/jsx-runtime";
2258
+ var defaultArtifactRenderers = {
2259
+ chart: ChartArtifactView,
2260
+ question: QuestionArtifactView,
2261
+ html: HtmlArtifactView,
2262
+ json: JsonArtifactView,
2263
+ table: TableArtifactView,
2264
+ ui: UiArtifactView
2265
+ };
2266
+ var ArtifactRegistryContext = createContext3(
2267
+ defaultArtifactRenderers
2268
+ );
2269
+ var ArtifactRegistryProvider = ({ renderers, override, children }) => {
2270
+ const merged = useMemo3(() => {
2271
+ if (!renderers) return defaultArtifactRenderers;
2272
+ if (override) return renderers;
2273
+ return { ...defaultArtifactRenderers, ...renderers };
2274
+ }, [renderers, override]);
2275
+ return /* @__PURE__ */ jsx17(ArtifactRegistryContext.Provider, { value: merged, children });
2276
+ };
2277
+ function useArtifactRegistry() {
2278
+ return useContext3(ArtifactRegistryContext);
2279
+ }
2280
+ var ArtifactView = ({ artifact }) => {
2281
+ const registry = useArtifactRegistry();
2282
+ const Renderer = registry[artifact.type] ?? registry.json;
2283
+ if (!Renderer) return null;
2284
+ return /* @__PURE__ */ jsx17(Renderer, { artifact });
2285
+ };
2286
+
2287
+ // src/artifacts/parse.ts
2288
+ var ARTIFACT_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
2289
+ "timbal-artifact",
2290
+ "timbal"
2291
+ ]);
2292
+ function isArtifactFenceLanguage(language) {
2293
+ return typeof language === "string" && ARTIFACT_FENCE_LANGUAGES.has(language);
2294
+ }
2295
+ function tryParseStructuredText(text) {
2296
+ const trimmed = text.trim();
2297
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
2298
+ try {
2299
+ return JSON.parse(trimmed);
2300
+ } catch {
2301
+ }
2302
+ try {
2303
+ const asJson = trimmed.replace(/\bTrue\b/g, "true").replace(/\bFalse\b/g, "false").replace(/\bNone\b/g, "null").replace(/'/g, '"');
2304
+ return JSON.parse(asJson);
2305
+ } catch {
2306
+ return null;
2307
+ }
2308
+ }
2309
+ function parseArtifactFromToolResult(result) {
2310
+ if (result === void 0 || result === null) return null;
2311
+ if (typeof result === "string") {
2312
+ const parsed = tryParseStructuredText(result);
2313
+ if (parsed === null) return null;
2314
+ return parseArtifactFromToolResult(parsed);
2315
+ }
2316
+ if (Array.isArray(result)) {
2317
+ for (const item of result) {
2318
+ if (typeof item === "object" && item !== null && "text" in item) {
2319
+ const text = item.text;
2320
+ if (typeof text === "string") {
2321
+ const fromText = parseArtifactFromToolResult(text);
2322
+ if (fromText) return fromText;
2323
+ }
2324
+ }
2325
+ }
2326
+ return null;
2327
+ }
2328
+ if (typeof result === "object") {
2329
+ const obj = result;
2330
+ if (obj.type === "text" && typeof obj.text === "string") {
2331
+ return parseArtifactFromToolResult(obj.text);
2332
+ }
2333
+ if (obj.type === "thinking" && typeof obj.thinking === "string") {
2334
+ return parseArtifactFromToolResult(obj.thinking);
2335
+ }
2336
+ }
2337
+ return isArtifact(result) ? result : null;
2338
+ }
2339
+ var FENCE_RE = /```(?:timbal-artifact|timbal)\s*\n([\s\S]*?)\n```/g;
2340
+ function findMarkdownArtifacts(markdown) {
2341
+ const matches = [];
2342
+ FENCE_RE.lastIndex = 0;
2343
+ let m;
2344
+ while ((m = FENCE_RE.exec(markdown)) !== null) {
2345
+ const raw = m[0];
2346
+ const body = m[1];
2347
+ try {
2348
+ const parsed = JSON.parse(body);
2349
+ if (isArtifact(parsed)) {
2350
+ matches.push({
2351
+ artifact: parsed,
2352
+ raw,
2353
+ start: m.index,
2354
+ end: m.index + raw.length
2355
+ });
2356
+ }
2357
+ } catch {
2358
+ }
2359
+ }
2360
+ return matches;
2361
+ }
2362
+ function splitMarkdownByArtifacts(markdown) {
2363
+ const matches = findMarkdownArtifacts(markdown);
2364
+ if (matches.length === 0) return [{ kind: "text", text: markdown }];
2365
+ const segments = [];
2366
+ let cursor = 0;
2367
+ for (const match of matches) {
2368
+ if (match.start > cursor) {
2369
+ segments.push({ kind: "text", text: markdown.slice(cursor, match.start) });
2370
+ }
2371
+ segments.push({ kind: "artifact", artifact: match.artifact });
2372
+ cursor = match.end;
2373
+ }
2374
+ if (cursor < markdown.length) {
2375
+ segments.push({ kind: "text", text: markdown.slice(cursor) });
2376
+ }
2377
+ return segments;
2378
+ }
2379
+
2380
+ // src/components/syntax-highlighter.tsx
2381
+ import langJavascript from "shiki/langs/javascript.mjs";
2382
+ import langTypescript from "shiki/langs/typescript.mjs";
2383
+ import langPython from "shiki/langs/python.mjs";
2384
+ import langHtml from "shiki/langs/html.mjs";
2385
+ import langCss from "shiki/langs/css.mjs";
2386
+ import langJson from "shiki/langs/json.mjs";
2387
+ import langBash from "shiki/langs/bash.mjs";
2388
+ import langMarkdown from "shiki/langs/markdown.mjs";
2389
+ import langJsx from "shiki/langs/jsx.mjs";
2390
+ import langTsx from "shiki/langs/tsx.mjs";
2391
+ import langSql from "shiki/langs/sql.mjs";
2392
+ import langYaml from "shiki/langs/yaml.mjs";
2393
+ import langRust from "shiki/langs/rust.mjs";
2394
+ import langGo from "shiki/langs/go.mjs";
2395
+ import langJava from "shiki/langs/java.mjs";
2396
+ import langC from "shiki/langs/c.mjs";
2397
+ import langCpp from "shiki/langs/cpp.mjs";
2398
+ import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
2399
+ import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
2400
+ import { jsx as jsx18 } from "react/jsx-runtime";
2401
+ var SHIKI_THEME_DARK = "vitesse-dark";
2402
+ var SHIKI_THEME_LIGHT = "vitesse-light";
2403
+ var highlighterPromise = null;
2404
+ function getHighlighter() {
2405
+ if (!highlighterPromise) {
2406
+ highlighterPromise = createHighlighterCore({
2407
+ themes: [themeVitesseDark, themeVitesseLight],
2408
+ langs: [
2409
+ langJavascript,
2410
+ langTypescript,
2411
+ langPython,
2412
+ langHtml,
2413
+ langCss,
2414
+ langJson,
2415
+ langBash,
2416
+ langMarkdown,
2417
+ langJsx,
2418
+ langTsx,
2419
+ langSql,
2420
+ langYaml,
2421
+ langRust,
2422
+ langGo,
2423
+ langJava,
2424
+ langC,
2425
+ langCpp
2426
+ ],
2427
+ engine: createJavaScriptRegexEngine()
2428
+ });
2429
+ }
2430
+ return highlighterPromise;
2431
+ }
2432
+ getHighlighter();
2433
+ var ShikiSyntaxHighlighter = ({
2434
+ components: { Pre, Code: Code2 },
2435
+ language,
2436
+ code
2437
+ }) => {
2438
+ const [html, setHtml] = useState4(null);
2439
+ useEffect3(() => {
2440
+ let cancelled = false;
2441
+ (async () => {
2442
+ try {
2443
+ const highlighter = await getHighlighter();
2444
+ const loadedLangs = highlighter.getLoadedLanguages();
2445
+ if (!loadedLangs.includes(language)) {
2446
+ if (!cancelled) setHtml(null);
2447
+ return;
2448
+ }
863
2449
  const result = highlighter.codeToHtml(code, {
864
2450
  lang: language,
865
2451
  themes: {
@@ -876,8 +2462,17 @@ var ShikiSyntaxHighlighter = ({
876
2462
  cancelled = true;
877
2463
  };
878
2464
  }, [code, language]);
2465
+ if (isArtifactFenceLanguage(language)) {
2466
+ try {
2467
+ const parsed = JSON.parse(code);
2468
+ if (isArtifact(parsed)) {
2469
+ return /* @__PURE__ */ jsx18(ArtifactView, { artifact: parsed });
2470
+ }
2471
+ } catch {
2472
+ }
2473
+ }
879
2474
  if (html) {
880
- return /* @__PURE__ */ jsx8(
2475
+ return /* @__PURE__ */ jsx18(
881
2476
  "div",
882
2477
  {
883
2478
  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 +2480,14 @@ var ShikiSyntaxHighlighter = ({
885
2480
  }
886
2481
  );
887
2482
  }
888
- return /* @__PURE__ */ jsx8(Pre, { children: /* @__PURE__ */ jsx8(Code2, { children: code }) });
2483
+ return /* @__PURE__ */ jsx18(Pre, { children: /* @__PURE__ */ jsx18(Code2, { children: code }) });
889
2484
  };
890
2485
  var syntax_highlighter_default = ShikiSyntaxHighlighter;
891
2486
 
892
2487
  // src/components/markdown-text.tsx
893
- import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
2488
+ import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
894
2489
  var MarkdownTextImpl = () => {
895
- return /* @__PURE__ */ jsx9(
2490
+ return /* @__PURE__ */ jsx19(
896
2491
  MarkdownTextPrimitive,
897
2492
  {
898
2493
  remarkPlugins: [remarkGfm, remarkMath],
@@ -908,24 +2503,25 @@ var MarkdownTextImpl = () => {
908
2503
  var MarkdownText = memo(MarkdownTextImpl);
909
2504
  var CodeHeader = ({ language, code }) => {
910
2505
  const { isCopied, copyToClipboard } = useCopyToClipboard();
2506
+ if (isArtifactFenceLanguage(language)) return null;
911
2507
  const onCopy = () => {
912
2508
  if (!code || isCopied) return;
913
2509
  copyToClipboard(code);
914
2510
  };
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" }),
2511
+ return /* @__PURE__ */ jsxs10("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: [
2512
+ /* @__PURE__ */ jsxs10("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
2513
+ /* @__PURE__ */ jsx19("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
918
2514
  language
919
2515
  ] }),
920
- /* @__PURE__ */ jsxs5(
2516
+ /* @__PURE__ */ jsxs10(
921
2517
  TooltipIconButton,
922
2518
  {
923
2519
  tooltip: isCopied ? "Copied!" : "Copy",
924
2520
  onClick: onCopy,
925
2521
  className: "transition-colors hover:text-foreground",
926
2522
  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" })
2523
+ !isCopied && /* @__PURE__ */ jsx19(CopyIcon, { className: "h-3.5 w-3.5" }),
2524
+ isCopied && /* @__PURE__ */ jsx19(CheckIcon2, { className: "h-3.5 w-3.5 text-emerald-500" })
929
2525
  ]
930
2526
  }
931
2527
  )
@@ -934,7 +2530,7 @@ var CodeHeader = ({ language, code }) => {
934
2530
  var useCopyToClipboard = ({
935
2531
  copiedDuration = 3e3
936
2532
  } = {}) => {
937
- const [isCopied, setIsCopied] = useState4(false);
2533
+ const [isCopied, setIsCopied] = useState5(false);
938
2534
  const copyToClipboard = (value) => {
939
2535
  if (!value) return;
940
2536
  navigator.clipboard.writeText(value).then(() => {
@@ -945,7 +2541,7 @@ var useCopyToClipboard = ({
945
2541
  return { isCopied, copyToClipboard };
946
2542
  };
947
2543
  var defaultComponents = memoizeMarkdownComponents({
948
- h1: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2544
+ h1: ({ className, ...props }) => /* @__PURE__ */ jsx19(
949
2545
  "h1",
950
2546
  {
951
2547
  className: cn(
@@ -955,7 +2551,7 @@ var defaultComponents = memoizeMarkdownComponents({
955
2551
  ...props
956
2552
  }
957
2553
  ),
958
- h2: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2554
+ h2: ({ className, ...props }) => /* @__PURE__ */ jsx19(
959
2555
  "h2",
960
2556
  {
961
2557
  className: cn(
@@ -965,7 +2561,7 @@ var defaultComponents = memoizeMarkdownComponents({
965
2561
  ...props
966
2562
  }
967
2563
  ),
968
- h3: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2564
+ h3: ({ className, ...props }) => /* @__PURE__ */ jsx19(
969
2565
  "h3",
970
2566
  {
971
2567
  className: cn(
@@ -975,7 +2571,7 @@ var defaultComponents = memoizeMarkdownComponents({
975
2571
  ...props
976
2572
  }
977
2573
  ),
978
- h4: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2574
+ h4: ({ className, ...props }) => /* @__PURE__ */ jsx19(
979
2575
  "h4",
980
2576
  {
981
2577
  className: cn(
@@ -985,7 +2581,7 @@ var defaultComponents = memoizeMarkdownComponents({
985
2581
  ...props
986
2582
  }
987
2583
  ),
988
- h5: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2584
+ h5: ({ className, ...props }) => /* @__PURE__ */ jsx19(
989
2585
  "h5",
990
2586
  {
991
2587
  className: cn(
@@ -995,7 +2591,7 @@ var defaultComponents = memoizeMarkdownComponents({
995
2591
  ...props
996
2592
  }
997
2593
  ),
998
- h6: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2594
+ h6: ({ className, ...props }) => /* @__PURE__ */ jsx19(
999
2595
  "h6",
1000
2596
  {
1001
2597
  className: cn(
@@ -1005,7 +2601,7 @@ var defaultComponents = memoizeMarkdownComponents({
1005
2601
  ...props
1006
2602
  }
1007
2603
  ),
1008
- p: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2604
+ p: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1009
2605
  "p",
1010
2606
  {
1011
2607
  className: cn(
@@ -1015,7 +2611,7 @@ var defaultComponents = memoizeMarkdownComponents({
1015
2611
  ...props
1016
2612
  }
1017
2613
  ),
1018
- a: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2614
+ a: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1019
2615
  "a",
1020
2616
  {
1021
2617
  className: cn(
@@ -1027,7 +2623,7 @@ var defaultComponents = memoizeMarkdownComponents({
1027
2623
  ...props
1028
2624
  }
1029
2625
  ),
1030
- blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2626
+ blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1031
2627
  "blockquote",
1032
2628
  {
1033
2629
  className: cn(
@@ -1037,7 +2633,7 @@ var defaultComponents = memoizeMarkdownComponents({
1037
2633
  ...props
1038
2634
  }
1039
2635
  ),
1040
- ul: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2636
+ ul: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1041
2637
  "ul",
1042
2638
  {
1043
2639
  className: cn(
@@ -1047,7 +2643,7 @@ var defaultComponents = memoizeMarkdownComponents({
1047
2643
  ...props
1048
2644
  }
1049
2645
  ),
1050
- ol: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2646
+ ol: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1051
2647
  "ol",
1052
2648
  {
1053
2649
  className: cn(
@@ -1057,7 +2653,7 @@ var defaultComponents = memoizeMarkdownComponents({
1057
2653
  ...props
1058
2654
  }
1059
2655
  ),
1060
- hr: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2656
+ hr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1061
2657
  "hr",
1062
2658
  {
1063
2659
  className: cn(
@@ -1067,14 +2663,14 @@ var defaultComponents = memoizeMarkdownComponents({
1067
2663
  ...props
1068
2664
  }
1069
2665
  ),
1070
- table: ({ className, ...props }) => /* @__PURE__ */ jsx9("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx9(
2666
+ table: ({ className, ...props }) => /* @__PURE__ */ jsx19("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx19(
1071
2667
  "table",
1072
2668
  {
1073
2669
  className: cn("aui-md-table w-full border-collapse text-sm", className),
1074
2670
  ...props
1075
2671
  }
1076
2672
  ) }),
1077
- th: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2673
+ th: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1078
2674
  "th",
1079
2675
  {
1080
2676
  className: cn(
@@ -1084,7 +2680,7 @@ var defaultComponents = memoizeMarkdownComponents({
1084
2680
  ...props
1085
2681
  }
1086
2682
  ),
1087
- td: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2683
+ td: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1088
2684
  "td",
1089
2685
  {
1090
2686
  className: cn(
@@ -1094,7 +2690,7 @@ var defaultComponents = memoizeMarkdownComponents({
1094
2690
  ...props
1095
2691
  }
1096
2692
  ),
1097
- tr: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2693
+ tr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1098
2694
  "tr",
1099
2695
  {
1100
2696
  className: cn(
@@ -1104,8 +2700,8 @@ var defaultComponents = memoizeMarkdownComponents({
1104
2700
  ...props
1105
2701
  }
1106
2702
  ),
1107
- li: ({ className, ...props }) => /* @__PURE__ */ jsx9("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
1108
- sup: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2703
+ li: ({ className, ...props }) => /* @__PURE__ */ jsx19("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
2704
+ sup: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1109
2705
  "sup",
1110
2706
  {
1111
2707
  className: cn(
@@ -1115,7 +2711,7 @@ var defaultComponents = memoizeMarkdownComponents({
1115
2711
  ...props
1116
2712
  }
1117
2713
  ),
1118
- pre: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2714
+ pre: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1119
2715
  "pre",
1120
2716
  {
1121
2717
  className: cn(
@@ -1124,122 +2720,394 @@ var defaultComponents = memoizeMarkdownComponents({
1124
2720
  ),
1125
2721
  ...props
1126
2722
  }
1127
- ),
1128
- code: function Code({ className, ...props }) {
1129
- const isCodeBlock = useIsMarkdownCodeBlock();
1130
- return /* @__PURE__ */ jsx9(
1131
- "code",
2723
+ ),
2724
+ code: function Code({ className, ...props }) {
2725
+ const isCodeBlock = useIsMarkdownCodeBlock();
2726
+ return /* @__PURE__ */ jsx19(
2727
+ "code",
2728
+ {
2729
+ className: cn(
2730
+ !isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90 dark:bg-muted/40",
2731
+ className
2732
+ ),
2733
+ ...props
2734
+ }
2735
+ );
2736
+ },
2737
+ strong: ({ className, ...props }) => /* @__PURE__ */ jsx19("strong", { className: cn("font-semibold text-foreground", className), ...props }),
2738
+ em: ({ className, ...props }) => /* @__PURE__ */ jsx19("em", { className: cn("italic", className), ...props }),
2739
+ CodeHeader
2740
+ });
2741
+
2742
+ // src/components/tool-fallback.tsx
2743
+ import { memo as memo3, useState as useState6 } from "react";
2744
+ import { ChevronDownIcon, WrenchIcon } from "lucide-react";
2745
+
2746
+ // src/ui/shimmer.tsx
2747
+ import { motion as motion2 } from "motion/react";
2748
+ import {
2749
+ memo as memo2,
2750
+ useMemo as useMemo4
2751
+ } from "react";
2752
+ import { jsx as jsx20 } from "react/jsx-runtime";
2753
+ var ShimmerComponent = ({
2754
+ children,
2755
+ as: Component = "p",
2756
+ className,
2757
+ duration = 2,
2758
+ spread = 2
2759
+ }) => {
2760
+ const MotionComponent = motion2.create(
2761
+ Component
2762
+ );
2763
+ const dynamicSpread = useMemo4(
2764
+ () => (children?.length ?? 0) * spread,
2765
+ [children, spread]
2766
+ );
2767
+ return /* @__PURE__ */ jsx20(
2768
+ MotionComponent,
2769
+ {
2770
+ animate: { backgroundPosition: "0% center" },
2771
+ className: cn(
2772
+ "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
2773
+ "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
2774
+ className
2775
+ ),
2776
+ initial: { backgroundPosition: "100% center" },
2777
+ style: {
2778
+ "--spread": `${dynamicSpread}px`,
2779
+ backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
2780
+ },
2781
+ transition: {
2782
+ repeat: Number.POSITIVE_INFINITY,
2783
+ duration,
2784
+ ease: "linear"
2785
+ },
2786
+ children
2787
+ }
2788
+ );
2789
+ };
2790
+ var Shimmer = memo2(ShimmerComponent);
2791
+
2792
+ // src/components/tool-fallback.tsx
2793
+ import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
2794
+ var ToolFallbackImpl = ({
2795
+ toolName,
2796
+ argsText,
2797
+ result,
2798
+ status
2799
+ }) => {
2800
+ const isRunning = status?.type === "running";
2801
+ const isError = status?.type === "incomplete" && status.reason !== "cancelled";
2802
+ if (isRunning) {
2803
+ return /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-running flex items-center gap-2 py-1 text-sm text-muted-foreground", children: [
2804
+ /* @__PURE__ */ jsx21(WrenchIcon, { className: "size-4" }),
2805
+ /* @__PURE__ */ jsx21(Shimmer, { as: "span", duration: 1.8, spread: 2.5, children: `Using tool: ${toolName}` })
2806
+ ] });
2807
+ }
2808
+ return /* @__PURE__ */ jsx21(
2809
+ ToolPanel,
2810
+ {
2811
+ toolName,
2812
+ argsText,
2813
+ result,
2814
+ isError
2815
+ }
2816
+ );
2817
+ };
2818
+ var ToolPanel = ({ toolName, argsText, result, isError }) => {
2819
+ const [open, setOpen] = useState6(false);
2820
+ return /* @__PURE__ */ jsxs11(
2821
+ "div",
2822
+ {
2823
+ className: cn(
2824
+ "aui-tool-fallback-root my-2 overflow-hidden rounded-lg border border-border/60 bg-muted/30 text-sm",
2825
+ isError && "border-destructive/50 bg-destructive/5"
2826
+ ),
2827
+ children: [
2828
+ /* @__PURE__ */ jsxs11(
2829
+ "button",
2830
+ {
2831
+ type: "button",
2832
+ onClick: () => setOpen((v) => !v),
2833
+ className: "aui-tool-fallback-header flex w-full items-center gap-2 px-3 py-2 text-left text-muted-foreground transition-colors hover:bg-muted/50",
2834
+ "aria-expanded": open,
2835
+ children: [
2836
+ /* @__PURE__ */ jsx21(WrenchIcon, { className: "size-3.5" }),
2837
+ /* @__PURE__ */ jsx21("span", { className: "aui-tool-fallback-name flex-1 truncate font-mono text-xs font-medium text-foreground/80", children: toolName }),
2838
+ isError && /* @__PURE__ */ jsx21("span", { className: "aui-tool-fallback-status text-xs font-medium text-destructive", children: "error" }),
2839
+ /* @__PURE__ */ jsx21(
2840
+ ChevronDownIcon,
2841
+ {
2842
+ className: cn(
2843
+ "size-3.5 shrink-0 transition-transform",
2844
+ open && "rotate-180"
2845
+ )
2846
+ }
2847
+ )
2848
+ ]
2849
+ }
2850
+ ),
2851
+ open && /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-body grid gap-2 border-t border-border/40 bg-background/50 px-3 py-2.5 text-xs", children: [
2852
+ argsText && argsText !== "{}" && /* @__PURE__ */ jsx21(Section, { label: "Input", value: argsText }),
2853
+ result !== void 0 && result !== null && /* @__PURE__ */ jsx21(Section, { label: "Output", value: formatResult(result) })
2854
+ ] })
2855
+ ]
2856
+ }
2857
+ );
2858
+ };
2859
+ var Section = ({ label, value }) => /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-section", children: [
2860
+ /* @__PURE__ */ jsx21("div", { className: "aui-tool-fallback-section-label mb-0.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
2861
+ /* @__PURE__ */ jsx21("pre", { className: "aui-tool-fallback-section-value overflow-x-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-foreground/85", children: value })
2862
+ ] });
2863
+ function formatResult(result) {
2864
+ if (typeof result === "string") return result;
2865
+ try {
2866
+ return JSON.stringify(result, null, 2);
2867
+ } catch {
2868
+ return String(result);
2869
+ }
2870
+ }
2871
+ var ToolFallback = memo3(
2872
+ ToolFallbackImpl
2873
+ );
2874
+ ToolFallback.displayName = "ToolFallback";
2875
+
2876
+ // src/artifacts/tool-artifact.tsx
2877
+ import { jsx as jsx22 } from "react/jsx-runtime";
2878
+ var ToolArtifactFallback = (props) => {
2879
+ const registry = useArtifactRegistry();
2880
+ const isRunning = props.status?.type === "running";
2881
+ if (!isRunning) {
2882
+ const artifact = parseArtifactFromToolResult(props.result);
2883
+ if (artifact) {
2884
+ const Renderer = registry[artifact.type];
2885
+ if (Renderer) {
2886
+ return /* @__PURE__ */ jsx22(Renderer, { artifact });
2887
+ }
2888
+ }
2889
+ }
2890
+ return /* @__PURE__ */ jsx22(ToolFallback, { ...props });
2891
+ };
2892
+
2893
+ // src/components/composer.tsx
2894
+ import {
2895
+ AuiIf,
2896
+ ComposerPrimitive as ComposerPrimitive2,
2897
+ useComposerRuntime
2898
+ } from "@assistant-ui/react";
2899
+ import { ArrowUpIcon, SquareIcon } from "lucide-react";
2900
+ import { Fragment as Fragment2, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
2901
+ var Composer = ({
2902
+ placeholder = "Send a message...",
2903
+ showAttachments = true,
2904
+ toolbar,
2905
+ sendTooltip = "Send message",
2906
+ noAutoFocus,
2907
+ className
2908
+ }) => {
2909
+ return /* @__PURE__ */ jsx23(
2910
+ ComposerPrimitive2.Root,
2911
+ {
2912
+ className: cn(
2913
+ "aui-composer-root relative mt-3 flex w-full flex-col",
2914
+ className
2915
+ ),
2916
+ children: /* @__PURE__ */ jsxs12(
2917
+ ComposerPrimitive2.AttachmentDropzone,
2918
+ {
2919
+ 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",
2920
+ children: [
2921
+ showAttachments && /* @__PURE__ */ jsx23(ComposerAttachments, {}),
2922
+ /* @__PURE__ */ jsx23(ComposerInput, { placeholder, autoFocus: !noAutoFocus }),
2923
+ /* @__PURE__ */ jsx23(
2924
+ ComposerToolbar,
2925
+ {
2926
+ showAttachments,
2927
+ toolbar,
2928
+ sendTooltip
2929
+ }
2930
+ )
2931
+ ]
2932
+ }
2933
+ )
2934
+ }
2935
+ );
2936
+ };
2937
+ var ComposerInput = ({
2938
+ placeholder,
2939
+ autoFocus
2940
+ }) => {
2941
+ const composer = useComposerRuntime();
2942
+ const onKeyDown = (e) => {
2943
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
2944
+ e.preventDefault();
2945
+ composer.send();
2946
+ }
2947
+ };
2948
+ const onInput = (e) => {
2949
+ const el = e.currentTarget;
2950
+ el.style.height = "auto";
2951
+ el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
2952
+ };
2953
+ return /* @__PURE__ */ jsx23(
2954
+ ComposerPrimitive2.Input,
2955
+ {
2956
+ placeholder,
2957
+ className: "aui-composer-input mb-1 max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0",
2958
+ rows: 1,
2959
+ autoFocus,
2960
+ "aria-label": "Message input",
2961
+ onKeyDown,
2962
+ onInput
2963
+ }
2964
+ );
2965
+ };
2966
+ var ComposerToolbar = ({ showAttachments, toolbar, sendTooltip }) => {
2967
+ return /* @__PURE__ */ jsxs12("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center gap-1", children: [
2968
+ showAttachments && /* @__PURE__ */ jsx23(ComposerAddAttachment, {}),
2969
+ toolbar,
2970
+ /* @__PURE__ */ jsx23("div", { className: "flex-1" }),
2971
+ /* @__PURE__ */ jsx23(ComposerSendOrCancel, { sendTooltip })
2972
+ ] });
2973
+ };
2974
+ var ComposerSendOrCancel = ({ sendTooltip }) => {
2975
+ return /* @__PURE__ */ jsxs12(Fragment2, { children: [
2976
+ /* @__PURE__ */ jsx23(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx23(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx23(
2977
+ TooltipIconButton,
1132
2978
  {
1133
- className: cn(
1134
- !isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90 dark:bg-muted/40",
1135
- className
1136
- ),
1137
- ...props
2979
+ tooltip: sendTooltip,
2980
+ side: "bottom",
2981
+ type: "submit",
2982
+ variant: "default",
2983
+ size: "icon",
2984
+ className: "aui-composer-send size-8 rounded-full",
2985
+ "aria-label": "Send message",
2986
+ children: /* @__PURE__ */ jsx23(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
1138
2987
  }
1139
- );
1140
- },
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 }),
1143
- CodeHeader
1144
- });
1145
-
1146
- // src/components/tool-fallback.tsx
1147
- import { memo as memo3 } from "react";
1148
- import { WrenchIcon } from "lucide-react";
2988
+ ) }) }),
2989
+ /* @__PURE__ */ jsx23(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx23(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx23(
2990
+ Button,
2991
+ {
2992
+ type: "button",
2993
+ variant: "default",
2994
+ size: "icon",
2995
+ className: "aui-composer-cancel size-8 rounded-full",
2996
+ "aria-label": "Stop generating",
2997
+ children: /* @__PURE__ */ jsx23(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
2998
+ }
2999
+ ) }) })
3000
+ ] });
3001
+ };
1149
3002
 
1150
- // src/ui/shimmer.tsx
1151
- import { motion } from "motion/react";
3003
+ // src/components/suggestions.tsx
1152
3004
  import {
1153
- memo as memo2,
1154
- useMemo
3005
+ useEffect as useEffect4,
3006
+ useMemo as useMemo5,
3007
+ useState as useState7
1155
3008
  } 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
3009
+ import { useThreadRuntime as useThreadRuntime3 } from "@assistant-ui/react";
3010
+ import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
3011
+ var Suggestions = ({
3012
+ suggestions,
3013
+ layout = "grid",
3014
+ className
1163
3015
  }) => {
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,
3016
+ const items = useResolvedSuggestions(suggestions);
3017
+ if (!items || items.length === 0) return null;
3018
+ return /* @__PURE__ */ jsx24(
3019
+ "div",
1173
3020
  {
1174
- animate: { backgroundPosition: "0% center" },
1175
3021
  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]",
3022
+ "aui-thread-suggestions w-full pb-4",
3023
+ layout === "grid" ? "grid gap-2 @md:grid-cols-2" : "flex gap-2 overflow-x-auto pb-1 [&::-webkit-scrollbar]:hidden",
1178
3024
  className
1179
3025
  ),
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
3026
+ children: items.map((s, i) => /* @__PURE__ */ jsx24(SuggestionChip, { suggestion: s, compact: layout === "row" }, s.title + i))
1191
3027
  }
1192
3028
  );
1193
3029
  };
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
3030
+ var SuggestionChip = ({
3031
+ suggestion,
3032
+ compact
1201
3033
  }) => {
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
- ] });
3034
+ const runtime = useThreadRuntime3();
3035
+ const onClick = () => {
3036
+ const text = suggestion.prompt ?? suggestion.title;
3037
+ runtime.append({ role: "user", content: [{ type: "text", text }] });
3038
+ };
3039
+ return /* @__PURE__ */ jsx24("div", { className: "aui-thread-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200", children: /* @__PURE__ */ jsxs13(
3040
+ Button,
3041
+ {
3042
+ variant: "ghost",
3043
+ onClick,
3044
+ className: cn(
3045
+ "aui-thread-suggestion h-auto rounded-2xl border text-left text-sm transition-colors hover:bg-muted",
3046
+ compact ? "shrink-0 flex-row items-center gap-2 whitespace-nowrap px-3 py-2" : "w-full flex-wrap items-start justify-start gap-1 px-4 py-3 @md:flex-col"
3047
+ ),
3048
+ children: [
3049
+ suggestion.icon && /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon }),
3050
+ /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-text-1 font-medium", children: suggestion.title }),
3051
+ suggestion.description && !compact && /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-text-2 text-muted-foreground", children: suggestion.description })
3052
+ ]
3053
+ }
3054
+ ) });
1207
3055
  };
1208
- var ToolFallback = memo3(
1209
- ToolFallbackImpl
1210
- );
1211
- ToolFallback.displayName = "ToolFallback";
3056
+ function useResolvedSuggestions(source) {
3057
+ const [resolved, setResolved] = useState7(
3058
+ () => Array.isArray(source) ? source : void 0
3059
+ );
3060
+ useEffect4(() => {
3061
+ if (!source) {
3062
+ setResolved(void 0);
3063
+ return;
3064
+ }
3065
+ if (Array.isArray(source)) {
3066
+ setResolved(source);
3067
+ return;
3068
+ }
3069
+ let cancelled = false;
3070
+ Promise.resolve().then(() => source()).then((value) => {
3071
+ if (!cancelled) setResolved(value);
3072
+ }).catch(() => {
3073
+ if (!cancelled) setResolved([]);
3074
+ });
3075
+ return () => {
3076
+ cancelled = true;
3077
+ };
3078
+ }, [source]);
3079
+ return useMemo5(() => resolved, [resolved]);
3080
+ }
1212
3081
 
1213
3082
  // src/components/thread.tsx
1214
3083
  import {
1215
3084
  ActionBarMorePrimitive,
1216
3085
  ActionBarPrimitive,
1217
- AuiIf,
1218
- ComposerPrimitive as ComposerPrimitive2,
3086
+ AuiIf as AuiIf2,
3087
+ ComposerPrimitive as ComposerPrimitive3,
1219
3088
  ErrorPrimitive,
1220
3089
  MessagePrimitive as MessagePrimitive2,
1221
- ThreadPrimitive,
1222
- useThreadRuntime
3090
+ ThreadPrimitive
1223
3091
  } from "@assistant-ui/react";
1224
3092
  import {
1225
3093
  ArrowDownIcon,
1226
- ArrowUpIcon,
1227
- CheckIcon as CheckIcon2,
3094
+ CheckIcon as CheckIcon3,
1228
3095
  CopyIcon as CopyIcon2,
1229
3096
  DownloadIcon,
1230
3097
  MoreHorizontalIcon,
1231
3098
  PencilIcon,
1232
- RefreshCwIcon,
1233
- SquareIcon
3099
+ RefreshCwIcon
1234
3100
  } from "lucide-react";
1235
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
3101
+ import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
1236
3102
  var Thread = ({
1237
3103
  className,
1238
3104
  maxWidth = "44rem",
1239
3105
  welcome,
1240
3106
  suggestions,
1241
3107
  composerPlaceholder = "Send a message...",
1242
- components
3108
+ components,
3109
+ artifacts,
3110
+ onArtifactEvent
1243
3111
  }) => {
1244
3112
  const WelcomeSlot = components?.Welcome ?? ThreadWelcome;
1245
3113
  const ComposerSlot = components?.Composer ?? Composer;
@@ -1247,59 +3115,79 @@ var Thread = ({
1247
3115
  const AssistantMessageSlot = components?.AssistantMessage ?? AssistantMessage;
1248
3116
  const EditComposerSlot = components?.EditComposer ?? EditComposer;
1249
3117
  const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
1250
- return /* @__PURE__ */ jsx12(
1251
- ThreadPrimitive.Root,
3118
+ const SuggestionsSlot = components?.Suggestions ?? Suggestions;
3119
+ return /* @__PURE__ */ jsx25(
3120
+ ArtifactRegistryProvider,
1252
3121
  {
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,
3122
+ renderers: artifacts?.renderers,
3123
+ override: artifacts?.override,
3124
+ children: /* @__PURE__ */ jsx25(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
3125
+ }), children: /* @__PURE__ */ jsx25(
3126
+ ThreadPrimitive.Root,
1260
3127
  {
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
- ]
3128
+ className: cn(
3129
+ "aui-root aui-thread-root @container flex h-full flex-col bg-background",
3130
+ className
3131
+ ),
3132
+ style: { ["--thread-max-width"]: maxWidth },
3133
+ children: /* @__PURE__ */ jsxs14(
3134
+ ThreadPrimitive.Viewport,
3135
+ {
3136
+ turnAnchor: "bottom",
3137
+ className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
3138
+ children: [
3139
+ /* @__PURE__ */ jsx25(
3140
+ WelcomeSlot,
3141
+ {
3142
+ config: welcome,
3143
+ suggestions,
3144
+ Suggestions: SuggestionsSlot
3145
+ }
3146
+ ),
3147
+ /* @__PURE__ */ jsx25(
3148
+ ThreadPrimitive.Messages,
3149
+ {
3150
+ components: {
3151
+ UserMessage: UserMessageSlot,
3152
+ EditComposer: EditComposerSlot,
3153
+ AssistantMessage: AssistantMessageSlot
3154
+ }
3155
+ }
3156
+ ),
3157
+ /* @__PURE__ */ jsxs14(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: [
3158
+ /* @__PURE__ */ jsx25(ScrollToBottomSlot, {}),
3159
+ /* @__PURE__ */ jsx25(ComposerSlot, { placeholder: composerPlaceholder })
3160
+ ] })
3161
+ ]
3162
+ }
3163
+ )
1280
3164
  }
1281
- )
3165
+ ) })
1282
3166
  }
1283
3167
  );
1284
3168
  };
1285
3169
  var ThreadScrollToBottom = () => {
1286
- return /* @__PURE__ */ jsx12(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx12(
3170
+ return /* @__PURE__ */ jsx25(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx25(
1287
3171
  TooltipIconButton,
1288
3172
  {
1289
3173
  tooltip: "Scroll to bottom",
1290
3174
  variant: "outline",
1291
3175
  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, {})
3176
+ children: /* @__PURE__ */ jsx25(ArrowDownIcon, {})
1293
3177
  }
1294
3178
  ) });
1295
3179
  };
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(
3180
+ var ThreadWelcome = ({
3181
+ config,
3182
+ suggestions,
3183
+ Suggestions: SuggestionsSlot = Suggestions
3184
+ }) => {
3185
+ return /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsxs14("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
3186
+ /* @__PURE__ */ jsx25("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs14("div", { className: "aui-thread-welcome-message flex size-full flex-col items-center justify-center px-4 text-center", children: [
3187
+ /* @__PURE__ */ jsxs14("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
3188
+ /* @__PURE__ */ jsx25("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" }),
3189
+ /* @__PURE__ */ jsx25("div", { className: "animate-ai-pulse-ring absolute inset-0" }),
3190
+ /* @__PURE__ */ jsx25(
1303
3191
  "svg",
1304
3192
  {
1305
3193
  xmlns: "http://www.w3.org/2000/svg",
@@ -1310,110 +3198,45 @@ var ThreadWelcome = ({ config, suggestions }) => {
1310
3198
  strokeLinecap: "round",
1311
3199
  strokeLinejoin: "round",
1312
3200
  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" })
3201
+ children: /* @__PURE__ */ jsx25("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
3202
  }
1315
3203
  )
1316
3204
  ] }),
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." })
3205
+ /* @__PURE__ */ jsx25("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?" }),
3206
+ /* @__PURE__ */ jsx25("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
3207
  ] }) }),
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)) });
1325
- };
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
- ) });
1343
- };
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, {})
3208
+ suggestions && /* @__PURE__ */ jsx25(SuggestionsSlot, { suggestions })
1358
3209
  ] }) });
1359
3210
  };
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,
1377
- {
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" })
1384
- }
1385
- ) }) })
1386
- ] });
1387
- };
1388
3211
  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" }) }) });
3212
+ return /* @__PURE__ */ jsx25(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx25(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__ */ jsx25(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
1390
3213
  };
1391
3214
  var AssistantMessage = () => {
1392
- return /* @__PURE__ */ jsxs7(
3215
+ return /* @__PURE__ */ jsxs14(
1393
3216
  MessagePrimitive2.Root,
1394
3217
  {
1395
3218
  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
3219
  "data-role": "assistant",
1397
3220
  children: [
1398
- /* @__PURE__ */ jsxs7("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
1399
- /* @__PURE__ */ jsx12(
3221
+ /* @__PURE__ */ jsxs14("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
3222
+ /* @__PURE__ */ jsx25(
1400
3223
  MessagePrimitive2.Parts,
1401
3224
  {
1402
3225
  components: {
1403
3226
  Text: MarkdownText,
1404
- tools: { Fallback: ToolFallback }
3227
+ tools: { Fallback: ToolArtifactFallback }
1405
3228
  }
1406
3229
  }
1407
3230
  ),
1408
- /* @__PURE__ */ jsx12(MessageError, {})
3231
+ /* @__PURE__ */ jsx25(MessageError, {})
1409
3232
  ] }),
1410
- /* @__PURE__ */ jsx12("div", { className: "aui-assistant-message-footer mt-1 ml-2 flex", children: /* @__PURE__ */ jsx12(AssistantActionBar, {}) })
3233
+ /* @__PURE__ */ jsx25("div", { className: "aui-assistant-message-footer mt-1 ml-2 flex", children: /* @__PURE__ */ jsx25(AssistantActionBar, {}) })
1411
3234
  ]
1412
3235
  }
1413
3236
  );
1414
3237
  };
1415
3238
  var AssistantActionBar = () => {
1416
- return /* @__PURE__ */ jsxs7(
3239
+ return /* @__PURE__ */ jsxs14(
1417
3240
  ActionBarPrimitive.Root,
1418
3241
  {
1419
3242
  hideWhenRunning: true,
@@ -1421,28 +3244,28 @@ var AssistantActionBar = () => {
1421
3244
  autohideFloat: "single-branch",
1422
3245
  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",
1423
3246
  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, {}) })
3247
+ /* @__PURE__ */ jsx25(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs14(TooltipIconButton, { tooltip: "Copy", children: [
3248
+ /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx25(CheckIcon3, {}) }),
3249
+ /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx25(CopyIcon2, {}) })
1427
3250
  ] }) }),
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(
3251
+ /* @__PURE__ */ jsx25(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx25(TooltipIconButton, { tooltip: "Refresh", children: /* @__PURE__ */ jsx25(RefreshCwIcon, {}) }) }),
3252
+ /* @__PURE__ */ jsxs14(ActionBarMorePrimitive.Root, { children: [
3253
+ /* @__PURE__ */ jsx25(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx25(
1431
3254
  TooltipIconButton,
1432
3255
  {
1433
3256
  tooltip: "More",
1434
3257
  className: "data-[state=open]:bg-accent",
1435
- children: /* @__PURE__ */ jsx12(MoreHorizontalIcon, {})
3258
+ children: /* @__PURE__ */ jsx25(MoreHorizontalIcon, {})
1436
3259
  }
1437
3260
  ) }),
1438
- /* @__PURE__ */ jsx12(
3261
+ /* @__PURE__ */ jsx25(
1439
3262
  ActionBarMorePrimitive.Content,
1440
3263
  {
1441
3264
  side: "bottom",
1442
3265
  align: "start",
1443
3266
  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" }),
3267
+ children: /* @__PURE__ */ jsx25(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs14(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: [
3268
+ /* @__PURE__ */ jsx25(DownloadIcon, { className: "size-4" }),
1446
3269
  "Export as Markdown"
1447
3270
  ] }) })
1448
3271
  }
@@ -1453,80 +3276,388 @@ var AssistantActionBar = () => {
1453
3276
  );
1454
3277
  };
1455
3278
  var UserMessage = () => {
1456
- return /* @__PURE__ */ jsxs7(
3279
+ return /* @__PURE__ */ jsxs14(
1457
3280
  MessagePrimitive2.Root,
1458
3281
  {
1459
3282
  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",
1460
3283
  "data-role": "user",
1461
3284
  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, {}) })
3285
+ /* @__PURE__ */ jsx25(UserMessageAttachments, {}),
3286
+ /* @__PURE__ */ jsxs14("div", { className: "aui-user-message-content-wrapper relative col-start-2 min-w-0", children: [
3287
+ /* @__PURE__ */ jsx25("div", { className: "aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground", children: /* @__PURE__ */ jsx25(MessagePrimitive2.Parts, {}) }),
3288
+ /* @__PURE__ */ jsx25("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ jsx25(UserActionBar, {}) })
1466
3289
  ] })
1467
3290
  ]
1468
3291
  }
1469
3292
  );
1470
3293
  };
1471
3294
  var UserActionBar = () => {
1472
- return /* @__PURE__ */ jsx12(
3295
+ return /* @__PURE__ */ jsx25(
1473
3296
  ActionBarPrimitive.Root,
1474
3297
  {
1475
3298
  hideWhenRunning: true,
1476
3299
  autohide: "not-last",
1477
3300
  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, {}) }) })
3301
+ children: /* @__PURE__ */ jsx25(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx25(TooltipIconButton, { tooltip: "Edit", className: "aui-user-action-edit p-4", children: /* @__PURE__ */ jsx25(PencilIcon, {}) }) })
1479
3302
  }
1480
3303
  );
1481
3304
  };
1482
3305
  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,
3306
+ return /* @__PURE__ */ jsx25(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__ */ jsxs14(ComposerPrimitive3.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
3307
+ /* @__PURE__ */ jsx25(
3308
+ ComposerPrimitive3.Input,
1486
3309
  {
1487
3310
  className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
1488
3311
  autoFocus: true
1489
3312
  }
1490
3313
  ),
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" }) })
3314
+ /* @__PURE__ */ jsxs14("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
3315
+ /* @__PURE__ */ jsx25(ComposerPrimitive3.Cancel, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
3316
+ /* @__PURE__ */ jsx25(ComposerPrimitive3.Send, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { size: "sm", children: "Update" }) })
1494
3317
  ] })
1495
3318
  ] }) });
1496
3319
  };
1497
3320
 
1498
3321
  // src/components/chat.tsx
1499
- import { jsx as jsx13 } from "react/jsx-runtime";
3322
+ import { jsx as jsx26 } from "react/jsx-runtime";
1500
3323
  function TimbalChat({
1501
3324
  workforceId,
1502
3325
  baseUrl,
1503
3326
  fetch: fetch2,
3327
+ attachments,
3328
+ attachmentsUploadUrl,
3329
+ attachmentsAccept,
3330
+ debug,
1504
3331
  ...threadProps
1505
3332
  }) {
1506
- return /* @__PURE__ */ jsx13(TimbalRuntimeProvider, { workforceId, baseUrl, fetch: fetch2, children: /* @__PURE__ */ jsx13(Thread, { ...threadProps }) });
3333
+ return /* @__PURE__ */ jsx26(
3334
+ TimbalRuntimeProvider,
3335
+ {
3336
+ workforceId,
3337
+ baseUrl,
3338
+ fetch: fetch2,
3339
+ attachments,
3340
+ attachmentsUploadUrl,
3341
+ attachmentsAccept,
3342
+ debug,
3343
+ children: /* @__PURE__ */ jsx26(Thread, { ...threadProps })
3344
+ }
3345
+ );
3346
+ }
3347
+
3348
+ // src/artifacts/agent-instructions.ts
3349
+ var ARTIFACT_AGENT_INSTRUCTIONS = `
3350
+ ## Rich artifacts (Timbal chat UI)
3351
+
3352
+ 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.
3353
+
3354
+ ### Delivery channels (either works)
3355
+
3356
+ 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.
3357
+ 2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
3358
+
3359
+ \`\`\`timbal-artifact
3360
+ {"type":"chart","data":[{"month":"Jan","sales":120}]}
3361
+ \`\`\`
3362
+
3363
+ The alias \`\`\`timbal\`\`\` is also accepted.
3364
+
3365
+ ### Built-in artifact types
3366
+
3367
+ | \`type\` | Use for |
3368
+ |---|---|
3369
+ | \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
3370
+ | \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
3371
+ | \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
3372
+ | \`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). |
3373
+ | \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
3374
+ | \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
3375
+
3376
+ ### When to use \`type: "html"\`
3377
+
3378
+ 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\`.
3379
+
3380
+ - Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
3381
+ - Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
3382
+ - Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
3383
+ - Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
3384
+
3385
+ ### When to use \`type: "ui"\`
3386
+
3387
+ 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.
3388
+
3389
+ Each \`ui\` artifact has:
3390
+
3391
+ - \`initialState\` \u2014 optional object seeding local state (per widget instance).
3392
+ - \`root\` \u2014 a single node tree (see node kinds below).
3393
+ - optional \`title\` \u2014 card heading.
3394
+
3395
+ **Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
3396
+
3397
+ **Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
3398
+
3399
+ | Action | Shape | Effect |
3400
+ |---|---|---|
3401
+ | User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
3402
+ | Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
3403
+ | Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
3404
+ | Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
3405
+
3406
+ ### \`ui\` node palette (\`root.kind\`)
3407
+
3408
+ | \`kind\` | Purpose | Key fields |
3409
+ |---|---|---|
3410
+ | \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
3411
+ | \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
3412
+ | \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
3413
+ | \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
3414
+ | \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
3415
+ | \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
3416
+ | \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
3417
+ | \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
3418
+ | \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
3419
+ | \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
3420
+
3421
+ ### Example \`ui\` artifact
3422
+
3423
+ \`\`\`json
3424
+ {
3425
+ "type": "ui",
3426
+ "title": "Configure plan",
3427
+ "initialState": { "qty": 1, "premium": false },
3428
+ "root": {
3429
+ "kind": "box",
3430
+ "direction": "col",
3431
+ "gap": 3,
3432
+ "children": [
3433
+ { "kind": "heading", "value": "Choose quantity", "level": 3 },
3434
+ {
3435
+ "kind": "tooltip",
3436
+ "content": "Drag to adjust quantity",
3437
+ "child": {
3438
+ "kind": "slider",
3439
+ "binding": "qty",
3440
+ "min": 1,
3441
+ "max": 50,
3442
+ "label": "Quantity",
3443
+ "onChange": { "kind": "emit", "name": "qty-changed" }
3444
+ }
3445
+ },
3446
+ { "kind": "toggle", "binding": "premium", "label": "Premium support" },
3447
+ {
3448
+ "kind": "button",
3449
+ "label": "Confirm",
3450
+ "onClick": { "kind": "message", "text": { "$bind": "qty" } }
3451
+ }
3452
+ ]
3453
+ }
1507
3454
  }
3455
+ \`\`\`
3456
+
3457
+ ### Rules
3458
+
3459
+ - Always set \`type\` to a built-in value above unless the app documented a custom type.
3460
+ - Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
3461
+ - Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
3462
+ - Keep \`data\` arrays reasonably small (charts/tables).
3463
+
3464
+ ### After calling an artifact tool (critical)
3465
+
3466
+ When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
3467
+
3468
+ 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.
3469
+ 2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
3470
+ 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.
3471
+ 4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
3472
+ `.trim();
1508
3473
 
1509
3474
  // src/index.ts
1510
3475
  import {
1511
3476
  ThreadPrimitive as ThreadPrimitive2,
1512
3477
  MessagePrimitive as MessagePrimitive3,
1513
- ComposerPrimitive as ComposerPrimitive3,
3478
+ ComposerPrimitive as ComposerPrimitive4,
1514
3479
  ActionBarPrimitive as ActionBarPrimitive2,
3480
+ AssistantRuntimeProvider as AssistantRuntimeProvider2,
1515
3481
  useThread,
1516
- useThreadRuntime as useThreadRuntime2,
3482
+ useThreadRuntime as useThreadRuntime4,
1517
3483
  useMessageRuntime,
1518
- useComposerRuntime
3484
+ useComposerRuntime as useComposerRuntime2
1519
3485
  } from "@assistant-ui/react";
1520
3486
 
3487
+ // src/hooks/use-workforces.ts
3488
+ import { useEffect as useEffect5, useMemo as useMemo6, useRef as useRef2, useState as useState8 } from "react";
3489
+ function useWorkforces(options = {}) {
3490
+ const { baseUrl = "/api", fetch: fetchFn, pickInitial } = options;
3491
+ const [workforces, setWorkforces] = useState8([]);
3492
+ const [selectedId, setSelectedId] = useState8("");
3493
+ const [isLoading, setIsLoading] = useState8(true);
3494
+ const [error, setError] = useState8(null);
3495
+ const fetchFnRef = useRef2(fetchFn ?? authFetch);
3496
+ useEffect5(() => {
3497
+ fetchFnRef.current = fetchFn ?? authFetch;
3498
+ }, [fetchFn]);
3499
+ const pickInitialRef = useRef2(pickInitial);
3500
+ useEffect5(() => {
3501
+ pickInitialRef.current = pickInitial;
3502
+ }, [pickInitial]);
3503
+ const load = useMemo6(() => {
3504
+ return async () => {
3505
+ setIsLoading(true);
3506
+ setError(null);
3507
+ try {
3508
+ const res = await fetchFnRef.current(`${baseUrl}/workforce`);
3509
+ if (!res.ok) throw new Error(`Failed to load workforces (${res.status})`);
3510
+ const data = await res.json();
3511
+ setWorkforces(data);
3512
+ setSelectedId((current) => {
3513
+ if (current && data.some((w) => idOf(w) === current)) return current;
3514
+ const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
3515
+ return initial ? idOf(initial) : "";
3516
+ });
3517
+ } catch (err) {
3518
+ setError(err instanceof Error ? err : new Error(String(err)));
3519
+ } finally {
3520
+ setIsLoading(false);
3521
+ }
3522
+ };
3523
+ }, [baseUrl]);
3524
+ useEffect5(() => {
3525
+ load();
3526
+ }, [load]);
3527
+ const selected = useMemo6(
3528
+ () => workforces.find((w) => idOf(w) === selectedId),
3529
+ [workforces, selectedId]
3530
+ );
3531
+ return {
3532
+ workforces,
3533
+ selectedId,
3534
+ setSelectedId,
3535
+ selected,
3536
+ isLoading,
3537
+ error,
3538
+ refresh: load
3539
+ };
3540
+ }
3541
+ function idOf(item) {
3542
+ return item.id ?? item.uid ?? item.name ?? "";
3543
+ }
3544
+
3545
+ // src/components/workforce-selector.tsx
3546
+ import { ChevronDownIcon as ChevronDownIcon2 } from "lucide-react";
3547
+ import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
3548
+ var WorkforceSelector = ({
3549
+ workforces,
3550
+ value,
3551
+ onChange,
3552
+ hideWhenSingle = true,
3553
+ className,
3554
+ placeholder = "Select agent"
3555
+ }) => {
3556
+ if (workforces.length === 0) return null;
3557
+ if (hideWhenSingle && workforces.length === 1) return null;
3558
+ return /* @__PURE__ */ jsxs15("div", { className: cn("aui-workforce-selector relative inline-flex items-center", className), children: [
3559
+ /* @__PURE__ */ jsxs15(
3560
+ "select",
3561
+ {
3562
+ className: "aui-workforce-selector-input h-7 cursor-pointer appearance-none rounded-md border-none bg-transparent pr-5 pl-1.5 text-xs font-medium text-muted-foreground shadow-none outline-none ring-0 transition-colors hover:text-foreground focus:ring-0",
3563
+ value,
3564
+ onChange: (e) => onChange(e.target.value),
3565
+ "aria-label": placeholder,
3566
+ children: [
3567
+ !value && /* @__PURE__ */ jsx27("option", { value: "", children: placeholder }),
3568
+ workforces.map((w) => {
3569
+ const id = idOf2(w);
3570
+ return /* @__PURE__ */ jsx27("option", { value: id, children: w.name ?? id }, id);
3571
+ })
3572
+ ]
3573
+ }
3574
+ ),
3575
+ /* @__PURE__ */ jsx27(ChevronDownIcon2, { className: "aui-workforce-selector-icon pointer-events-none absolute right-1 size-3 text-muted-foreground" })
3576
+ ] });
3577
+ };
3578
+ function idOf2(item) {
3579
+ return item.id ?? item.uid ?? item.name ?? "";
3580
+ }
3581
+
3582
+ // src/components/chat-shell.tsx
3583
+ import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
3584
+ var TimbalChatShell = ({
3585
+ workforceId,
3586
+ brand,
3587
+ headerActions,
3588
+ hideWorkforceSelector,
3589
+ className,
3590
+ headerClassName,
3591
+ baseUrl,
3592
+ fetch: fetch2,
3593
+ ...chatProps
3594
+ }) => {
3595
+ const {
3596
+ workforces,
3597
+ selectedId,
3598
+ setSelectedId
3599
+ } = useWorkforces({ baseUrl, fetch: fetch2 });
3600
+ const effectiveId = workforceId ?? selectedId;
3601
+ const showSelector = !hideWorkforceSelector && !workforceId && workforces.length > 0;
3602
+ return /* @__PURE__ */ jsxs16(
3603
+ "div",
3604
+ {
3605
+ className: cn(
3606
+ "aui-chat-shell flex h-screen flex-col overflow-hidden",
3607
+ className
3608
+ ),
3609
+ children: [
3610
+ /* @__PURE__ */ jsxs16(
3611
+ "header",
3612
+ {
3613
+ className: cn(
3614
+ "aui-chat-shell-header flex shrink-0 items-center justify-between border-b border-border/50 bg-background/90 px-5 py-2 backdrop-blur-md",
3615
+ headerClassName
3616
+ ),
3617
+ children: [
3618
+ /* @__PURE__ */ jsxs16("div", { className: "flex items-center", children: [
3619
+ brand,
3620
+ showSelector && /* @__PURE__ */ jsxs16(Fragment3, { children: [
3621
+ /* @__PURE__ */ jsx28("div", { className: "mx-3.5 h-3.5 w-px bg-border" }),
3622
+ /* @__PURE__ */ jsx28(
3623
+ WorkforceSelector,
3624
+ {
3625
+ workforces,
3626
+ value: selectedId,
3627
+ onChange: setSelectedId
3628
+ }
3629
+ )
3630
+ ] })
3631
+ ] }),
3632
+ /* @__PURE__ */ jsx28("div", { className: "flex items-center gap-0.5", children: headerActions })
3633
+ ]
3634
+ }
3635
+ ),
3636
+ /* @__PURE__ */ jsx28(
3637
+ TimbalChat,
3638
+ {
3639
+ workforceId: effectiveId,
3640
+ baseUrl,
3641
+ fetch: fetch2,
3642
+ className: "min-h-0 flex-1",
3643
+ ...chatProps
3644
+ },
3645
+ effectiveId
3646
+ )
3647
+ ]
3648
+ }
3649
+ );
3650
+ };
3651
+
1521
3652
  // src/auth/provider.tsx
1522
3653
  import {
1523
- createContext,
1524
- useCallback as useCallback2,
1525
- useContext,
1526
- useEffect as useEffect4,
1527
- useState as useState5
3654
+ createContext as createContext4,
3655
+ useCallback as useCallback3,
3656
+ useContext as useContext4,
3657
+ useEffect as useEffect6,
3658
+ useState as useState9
1528
3659
  } from "react";
1529
- import { jsx as jsx14 } from "react/jsx-runtime";
3660
+ import { jsx as jsx29 } from "react/jsx-runtime";
1530
3661
  function isInsideIframe() {
1531
3662
  try {
1532
3663
  return typeof window !== "undefined" && window.self !== window.top;
@@ -1534,9 +3665,9 @@ function isInsideIframe() {
1534
3665
  return true;
1535
3666
  }
1536
3667
  }
1537
- var SessionContext = createContext(void 0);
3668
+ var SessionContext = createContext4(void 0);
1538
3669
  var useSession = () => {
1539
- const context = useContext(SessionContext);
3670
+ const context = useContext4(SessionContext);
1540
3671
  if (context === void 0) {
1541
3672
  throw new Error("useSession must be used within a SessionProvider");
1542
3673
  }
@@ -1546,10 +3677,10 @@ var SessionProvider = ({
1546
3677
  children,
1547
3678
  enabled = true
1548
3679
  }) => {
1549
- const [user, setUser] = useState5(null);
1550
- const [loading, setLoading] = useState5(enabled);
1551
- const [embedded] = useState5(isInsideIframe);
1552
- useEffect4(() => {
3680
+ const [user, setUser] = useState9(null);
3681
+ const [loading, setLoading] = useState9(enabled);
3682
+ const [embedded] = useState9(isInsideIframe);
3683
+ useEffect6(() => {
1553
3684
  if (!enabled) {
1554
3685
  setLoading(false);
1555
3686
  return;
@@ -1610,7 +3741,7 @@ var SessionProvider = ({
1610
3741
  messageCleanup?.();
1611
3742
  };
1612
3743
  }, [enabled, embedded]);
1613
- const logout = useCallback2(() => {
3744
+ const logout = useCallback3(() => {
1614
3745
  clearTokens();
1615
3746
  setUser(null);
1616
3747
  const returnTo = encodeURIComponent(
@@ -1620,7 +3751,7 @@ var SessionProvider = ({
1620
3751
  () => window.location.href = `/api/auth/login?return_to=${returnTo}`
1621
3752
  );
1622
3753
  }, []);
1623
- return /* @__PURE__ */ jsx14(
3754
+ return /* @__PURE__ */ jsx29(
1624
3755
  SessionContext.Provider,
1625
3756
  {
1626
3757
  value: {
@@ -1637,7 +3768,7 @@ var SessionProvider = ({
1637
3768
 
1638
3769
  // src/auth/guard.tsx
1639
3770
  import { Loader2 } from "lucide-react";
1640
- import { jsx as jsx15 } from "react/jsx-runtime";
3771
+ import { jsx as jsx30 } from "react/jsx-runtime";
1641
3772
  var AuthGuard = ({
1642
3773
  children,
1643
3774
  requireAuth = false,
@@ -1648,7 +3779,7 @@ var AuthGuard = ({
1648
3779
  return children;
1649
3780
  }
1650
3781
  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" }) });
3782
+ return /* @__PURE__ */ jsx30("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx30(Loader2, { className: "w-8 h-8 animate-spin" }) });
1652
3783
  }
1653
3784
  if (requireAuth && !isAuthenticated && !isEmbedded) {
1654
3785
  const returnTo = encodeURIComponent(
@@ -1660,15 +3791,24 @@ var AuthGuard = ({
1660
3791
  return children;
1661
3792
  };
1662
3793
  export {
3794
+ ARTIFACT_AGENT_INSTRUCTIONS,
3795
+ ARTIFACT_FENCE_LANGUAGES,
1663
3796
  ActionBarPrimitive2 as ActionBarPrimitive,
3797
+ ArtifactCard,
3798
+ ArtifactRegistryProvider,
3799
+ ArtifactView,
3800
+ AssistantRuntimeProvider2 as AssistantRuntimeProvider,
1664
3801
  AuthGuard,
1665
3802
  Avatar,
1666
3803
  AvatarFallback,
1667
3804
  AvatarImage,
1668
3805
  Button,
3806
+ ChartArtifactView,
3807
+ Composer,
1669
3808
  ComposerAddAttachment,
1670
3809
  ComposerAttachments,
1671
- ComposerPrimitive3 as ComposerPrimitive,
3810
+ ComposerPrimitive4 as ComposerPrimitive,
3811
+ DEFAULT_UPLOAD_ACCEPT,
1672
3812
  Dialog,
1673
3813
  DialogClose,
1674
3814
  DialogContent,
@@ -1676,35 +3816,70 @@ export {
1676
3816
  DialogPortal,
1677
3817
  DialogTitle,
1678
3818
  DialogTrigger,
3819
+ HtmlArtifactView,
3820
+ JsonArtifactView,
1679
3821
  MarkdownText,
1680
3822
  MessagePrimitive3 as MessagePrimitive,
3823
+ QuestionArtifactView,
1681
3824
  SessionProvider,
1682
3825
  Shimmer,
3826
+ Suggestions,
1683
3827
  syntax_highlighter_default as SyntaxHighlighter,
3828
+ TableArtifactView,
1684
3829
  Thread,
1685
3830
  ThreadPrimitive2 as ThreadPrimitive,
1686
3831
  TimbalChat,
3832
+ TimbalChatShell,
1687
3833
  TimbalRuntimeProvider,
3834
+ ToolArtifactFallback,
1688
3835
  ToolFallback,
1689
3836
  Tooltip,
1690
3837
  TooltipContent,
1691
3838
  TooltipIconButton,
1692
3839
  TooltipProvider,
1693
3840
  TooltipTrigger,
3841
+ UiArtifactView,
3842
+ UiCustomNodeRegistryProvider,
3843
+ UiEventProvider,
3844
+ UiNodeView,
1694
3845
  UserMessageAttachments,
3846
+ WorkforceSelector,
1695
3847
  authFetch,
1696
3848
  buttonVariants,
1697
3849
  clearTokens,
1698
3850
  cn,
3851
+ createDefaultAttachmentAdapter,
3852
+ createUploadAttachmentAdapter,
3853
+ defaultArtifactRenderers,
1699
3854
  fetchCurrentUser,
3855
+ findMarkdownArtifacts,
1700
3856
  getAccessToken,
3857
+ getPath,
1701
3858
  getRefreshToken,
3859
+ isArtifact,
3860
+ isArtifactFenceLanguage,
3861
+ isUiBinding,
3862
+ parseArtifactFromToolResult,
3863
+ parseSSELine2 as parseSSELine,
1702
3864
  refreshAccessToken,
3865
+ resolveAttachmentAdapter,
3866
+ resolveBindable,
1703
3867
  setAccessToken,
3868
+ setPath,
1704
3869
  setRefreshToken,
1705
- useComposerRuntime,
3870
+ splitMarkdownByArtifacts,
3871
+ useArtifactRegistry,
3872
+ useComposerRuntime2 as useComposerRuntime,
1706
3873
  useMessageRuntime,
3874
+ useResolvedSuggestions,
1707
3875
  useSession,
1708
3876
  useThread,
1709
- useThreadRuntime2 as useThreadRuntime
3877
+ useThreadRuntime4 as useThreadRuntime,
3878
+ useTimbalRuntime,
3879
+ useTimbalStream,
3880
+ useUiCustomNodeRegistry,
3881
+ useUiDispatch,
3882
+ useUiEventEmitter,
3883
+ useUiState,
3884
+ useWorkforces
1710
3885
  };