@timbal-ai/timbal-react 0.2.1 → 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
@@ -10,7 +18,9 @@ import { parseSSELine } from "@timbal-ai/timbal-sdk";
10
18
  var ACCESS_TOKEN_KEY = "timbal_project_access_token";
11
19
  var REFRESH_TOKEN_KEY = "timbal_project_refresh_token";
12
20
  var getAccessToken = () => localStorage.getItem(ACCESS_TOKEN_KEY);
21
+ var setAccessToken = (token) => localStorage.setItem(ACCESS_TOKEN_KEY, token);
13
22
  var getRefreshToken = () => localStorage.getItem(REFRESH_TOKEN_KEY);
23
+ var setRefreshToken = (token) => localStorage.setItem(REFRESH_TOKEN_KEY, token);
14
24
  var clearTokens = () => {
15
25
  localStorage.removeItem(ACCESS_TOKEN_KEY);
16
26
  localStorage.removeItem(REFRESH_TOKEN_KEY);
@@ -85,13 +95,506 @@ var fetchCurrentUser = async () => {
85
95
  }
86
96
  };
87
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
+
88
552
  // src/runtime/provider.tsx
89
553
  import { jsx } from "react/jsx-runtime";
90
- var convertMessage = (message) => ({
91
- role: message.role,
92
- content: message.content,
93
- id: message.id
94
- });
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
+ };
95
598
  function findParentId(messages, beforeIndex) {
96
599
  const slice = beforeIndex !== void 0 ? messages.slice(0, beforeIndex) : messages;
97
600
  for (let i = slice.length - 1; i >= 0; i--) {
@@ -99,18 +602,18 @@ function findParentId(messages, beforeIndex) {
99
602
  }
100
603
  return null;
101
604
  }
102
- function isTopLevelStart(event) {
103
- return event.type === "START" && typeof event.run_id === "string" && typeof event.path === "string" && !event.path.includes(".");
104
- }
105
605
  function getTextFromMessage(message) {
106
606
  const part = message.content.find((c) => c.type === "text");
107
- return part?.type === "text" ? part.text : null;
607
+ return part?.type === "text" ? part.text : "";
108
608
  }
109
- function TimbalRuntimeProvider({
609
+ function getAttachmentsFromMessage(message) {
610
+ return message.attachments?.length ? message.attachments : void 0;
611
+ }
612
+ function useTimbalStream({
110
613
  workforceId,
111
- children,
112
614
  baseUrl = "/api",
113
- fetch: fetchFn
615
+ fetch: fetchFn,
616
+ debug = false
114
617
  }) {
115
618
  const [messages, setMessages] = useState([]);
116
619
  const [isRunning, setIsRunning] = useState(false);
@@ -120,40 +623,41 @@ function TimbalRuntimeProvider({
120
623
  useEffect(() => {
121
624
  fetchFnRef.current = fetchFn ?? authFetch;
122
625
  }, [fetchFn]);
626
+ const debugRef = useRef(debug);
627
+ useEffect(() => {
628
+ debugRef.current = debug;
629
+ }, [debug]);
123
630
  useEffect(() => {
124
631
  messagesRef.current = messages;
125
632
  }, [messages]);
126
633
  const streamAssistantResponse = useCallback(
127
- async (input, userId, assistantId, parentId, signal) => {
128
- const parts = [];
129
- const toolIndexById = /* @__PURE__ */ new Map();
130
- const lastTextPart = () => {
131
- const last = parts[parts.length - 1];
132
- if (last?.type === "text") return last;
133
- const next = { type: "text", text: "" };
134
- parts.push(next);
135
- return next;
136
- };
634
+ async (input, attachments, userId, assistantId, parentId, signal) => {
635
+ const state = createReducerState();
137
636
  const flush = () => {
138
637
  setMessages(
139
- (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
+ )
140
641
  );
141
642
  };
142
643
  const stampRunId = (runId) => {
143
644
  setMessages(
144
- (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
+ )
145
648
  );
146
649
  };
147
650
  try {
148
- const res = await fetchFnRef.current(`${baseUrl}/workforce/${workforceId}/stream`, {
149
- method: "POST",
150
- headers: { "Content-Type": "application/json" },
151
- body: JSON.stringify({
152
- prompt: input,
153
- context: { parent_id: parentId }
154
- }),
155
- signal
156
- });
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
+ );
157
661
  if (!res.ok || !res.body) throw new Error(`Request failed: ${res.status}`);
158
662
  const reader = res.body.getReader();
159
663
  const decoder = new TextDecoder();
@@ -168,84 +672,34 @@ function TimbalRuntimeProvider({
168
672
  for (const line of lines) {
169
673
  const event = parseSSELine(line);
170
674
  if (!event) continue;
171
- if (!capturedRunId && isTopLevelStart(event)) {
172
- capturedRunId = event.run_id;
173
- stampRunId(capturedRunId);
675
+ if (debugRef.current) {
676
+ console.debug("[timbal]", event.type, event);
174
677
  }
175
- switch (event.type) {
176
- case "DELTA": {
177
- const item = event.item;
178
- if (!item) break;
179
- if (item.type === "text_delta" && typeof item.text_delta === "string") {
180
- lastTextPart().text += item.text_delta;
181
- flush();
182
- } else if (item.type === "tool_use") {
183
- const toolCallId = item.id || `tool-${crypto.randomUUID()}`;
184
- const inputStr = typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? {});
185
- parts.push({
186
- type: "tool-call",
187
- toolCallId,
188
- toolName: item.name || "unknown",
189
- argsText: inputStr
190
- });
191
- toolIndexById.set(toolCallId, parts.length - 1);
192
- flush();
193
- } else if (item.type === "tool_use_delta") {
194
- const idx = toolIndexById.get(item.id);
195
- if (idx !== void 0 && typeof item.input_delta === "string") {
196
- parts[idx].argsText += item.input_delta;
197
- flush();
198
- }
199
- }
200
- break;
201
- }
202
- case "OUTPUT": {
203
- const output = event.output;
204
- if (!output) break;
205
- if (typeof output === "object" && Array.isArray(output.content)) {
206
- for (const block of output.content) {
207
- if (block.type === "tool_use") {
208
- const id = block.id || `tool-${crypto.randomUUID()}`;
209
- const idx = toolIndexById.get(id);
210
- if (idx !== void 0) {
211
- parts[idx].result = "Tool executed";
212
- } else {
213
- const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input ?? {});
214
- parts.push({
215
- type: "tool-call",
216
- toolCallId: id,
217
- toolName: block.name || "unknown",
218
- argsText: inputStr,
219
- result: "Tool executed"
220
- });
221
- toolIndexById.set(id, parts.length - 1);
222
- }
223
- } else if (block.type === "text" && typeof block.text === "string" && !lastTextPart().text) {
224
- lastTextPart().text = block.text;
225
- }
226
- }
227
- flush();
228
- } else if (parts.length === 0) {
229
- const text = typeof output === "string" ? output : JSON.stringify(output);
230
- parts.push({ type: "text", text });
231
- flush();
232
- }
233
- break;
678
+ if (!capturedRunId) {
679
+ const runId = readTopLevelStartRunId(event);
680
+ if (runId) {
681
+ capturedRunId = runId;
682
+ stampRunId(runId);
234
683
  }
235
684
  }
685
+ const changed = reduceSseEvent(state, event);
686
+ if (changed) flush();
236
687
  }
237
688
  }
238
689
  if (buffer.trim()) {
239
690
  const event = parseSSELine(buffer);
240
- if (event?.type === "OUTPUT" && parts.length === 0 && event.output) {
241
- const text = typeof event.output === "string" ? event.output : JSON.stringify(event.output);
242
- parts.push({ type: "text", text });
243
- flush();
691
+ if (event) {
692
+ if (debugRef.current) {
693
+ console.debug("[timbal]", event.type, event);
694
+ }
695
+ if (reduceSseEvent(state, event)) flush();
244
696
  }
245
697
  }
246
698
  } catch (err) {
247
699
  if (err.name !== "AbortError") {
248
- 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
+ }
249
703
  flush();
250
704
  }
251
705
  } finally {
@@ -255,44 +709,46 @@ function TimbalRuntimeProvider({
255
709
  },
256
710
  [workforceId, baseUrl]
257
711
  );
258
- const onNew = useCallback(
259
- async (message) => {
260
- const textPart = message.content.find((c) => c.type === "text");
261
- if (!textPart || textPart.type !== "text") return;
262
- const input = textPart.text;
712
+ const send = useCallback(
713
+ async (input, options) => {
263
714
  const userId = crypto.randomUUID();
264
715
  const assistantId = crypto.randomUUID();
265
- let base = messagesRef.current;
266
- if (message.parentId !== null) {
267
- const parentIdx = base.findIndex((m) => m.id === message.parentId);
268
- if (parentIdx >= 0) {
269
- base = base.slice(0, parentIdx + 1);
270
- }
271
- }
272
- 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
+ };
273
724
  setMessages([
274
725
  ...base,
275
- { id: userId, role: "user", content: [{ type: "text", text: input }] }
276
- ]);
277
- setIsRunning(true);
278
- setMessages((prev) => [
279
- ...prev,
726
+ userMessage,
280
727
  { id: assistantId, role: "assistant", content: [] }
281
728
  ]);
729
+ setIsRunning(true);
282
730
  const controller = new AbortController();
283
731
  abortRef.current = controller;
284
- 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
+ );
285
740
  },
286
741
  [streamAssistantResponse]
287
742
  );
288
- const onReload = useCallback(
743
+ const reload = useCallback(
289
744
  async (messageId) => {
290
745
  const current = messagesRef.current;
291
746
  const idx = messageId ? current.findIndex((m) => m.id === messageId) : current.length - 2;
292
747
  const userMessage = idx >= 0 ? current[idx] : null;
293
748
  if (!userMessage || userMessage.role !== "user") return;
294
749
  const input = getTextFromMessage(userMessage);
295
- if (!input) return;
750
+ const messageAttachments = getAttachmentsFromMessage(userMessage);
751
+ if (!input && !messageAttachments?.length) return;
296
752
  const assistantId = crypto.randomUUID();
297
753
  const parentId = findParentId(current, idx);
298
754
  setMessages((prev) => [
@@ -302,24 +758,114 @@ function TimbalRuntimeProvider({
302
758
  setIsRunning(true);
303
759
  const controller = new AbortController();
304
760
  abortRef.current = controller;
305
- 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
+ );
306
769
  },
307
770
  [streamAssistantResponse]
308
771
  );
309
- const onCancel = useCallback(async () => {
772
+ const cancel = useCallback(() => {
773
+ abortRef.current?.abort();
774
+ }, []);
775
+ const clear = useCallback(() => {
310
776
  abortRef.current?.abort();
777
+ setMessages([]);
311
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]);
312
849
  const runtime = useExternalStoreRuntime({
313
- isRunning,
314
- messages,
850
+ isRunning: stream.isRunning,
851
+ messages: stream.messages,
315
852
  convertMessage,
316
853
  onNew,
317
854
  onEdit: onNew,
318
855
  onReload,
319
- onCancel
856
+ onCancel,
857
+ ...attachmentAdapter ? { adapters: { attachments: attachmentAdapter } } : {}
320
858
  });
321
- return /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children });
859
+ return /* @__PURE__ */ jsx(TimbalStreamContext.Provider, { value: stream, children: /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children }) });
322
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));
865
+ }
866
+
867
+ // src/index.ts
868
+ import { parseSSELine as parseSSELine2 } from "@timbal-ai/timbal-sdk";
323
869
 
324
870
  // src/components/attachment.tsx
325
871
  import { useEffect as useEffect2, useState as useState2 } from "react";
@@ -783,73 +1329,1115 @@ import {
783
1329
  import remarkGfm from "remark-gfm";
784
1330
  import remarkMath from "remark-math";
785
1331
  import rehypeKatex from "rehype-katex";
786
- import { memo, useState as useState4 } from "react";
787
- import { CheckIcon, CopyIcon } from "lucide-react";
1332
+ import { memo, useState as useState5 } from "react";
1333
+ import { CheckIcon as CheckIcon2, CopyIcon } from "lucide-react";
788
1334
 
789
1335
  // src/components/syntax-highlighter.tsx
790
- import { useEffect as useEffect3, useState as useState3 } from "react";
1336
+ import { useEffect as useEffect3, useState as useState4 } from "react";
791
1337
  import { createHighlighterCore } from "shiki/core";
792
1338
  import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
793
- import langJavascript from "shiki/langs/javascript.mjs";
794
- import langTypescript from "shiki/langs/typescript.mjs";
795
- import langPython from "shiki/langs/python.mjs";
796
- import langHtml from "shiki/langs/html.mjs";
797
- import langCss from "shiki/langs/css.mjs";
798
- import langJson from "shiki/langs/json.mjs";
799
- import langBash from "shiki/langs/bash.mjs";
800
- import langMarkdown from "shiki/langs/markdown.mjs";
801
- import langJsx from "shiki/langs/jsx.mjs";
802
- import langTsx from "shiki/langs/tsx.mjs";
803
- import langSql from "shiki/langs/sql.mjs";
804
- import langYaml from "shiki/langs/yaml.mjs";
805
- import langRust from "shiki/langs/rust.mjs";
806
- import langGo from "shiki/langs/go.mjs";
807
- import langJava from "shiki/langs/java.mjs";
808
- import langC from "shiki/langs/c.mjs";
809
- import langCpp from "shiki/langs/cpp.mjs";
810
- import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
811
- import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
812
- import { jsx as jsx8 } from "react/jsx-runtime";
813
- var SHIKI_THEME_DARK = "vitesse-dark";
814
- var SHIKI_THEME_LIGHT = "vitesse-light";
815
- var highlighterPromise = null;
816
- function getHighlighter() {
817
- if (!highlighterPromise) {
818
- highlighterPromise = createHighlighterCore({
819
- themes: [themeVitesseDark, themeVitesseLight],
820
- langs: [
821
- langJavascript,
822
- langTypescript,
823
- langPython,
824
- langHtml,
825
- langCss,
826
- langJson,
827
- langBash,
828
- langMarkdown,
829
- langJsx,
830
- langTsx,
831
- langSql,
832
- langYaml,
833
- langRust,
834
- langGo,
835
- langJava,
836
- langC,
837
- langCpp
838
- ],
839
- engine: createJavaScriptRegexEngine()
840
- });
841
- }
842
- return highlighterPromise;
843
- }
844
- getHighlighter();
845
- var ShikiSyntaxHighlighter = ({
846
- components: { Pre, Code: Code2 },
847
- language,
848
- code
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
849
1374
  }) => {
850
- const [html, setHtml] = useState3(null);
851
- useEffect3(() => {
852
- let cancelled = false;
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;
853
2441
  (async () => {
854
2442
  try {
855
2443
  const highlighter = await getHighlighter();
@@ -874,8 +2462,17 @@ var ShikiSyntaxHighlighter = ({
874
2462
  cancelled = true;
875
2463
  };
876
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
+ }
877
2474
  if (html) {
878
- return /* @__PURE__ */ jsx8(
2475
+ return /* @__PURE__ */ jsx18(
879
2476
  "div",
880
2477
  {
881
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",
@@ -883,14 +2480,14 @@ var ShikiSyntaxHighlighter = ({
883
2480
  }
884
2481
  );
885
2482
  }
886
- return /* @__PURE__ */ jsx8(Pre, { children: /* @__PURE__ */ jsx8(Code2, { children: code }) });
2483
+ return /* @__PURE__ */ jsx18(Pre, { children: /* @__PURE__ */ jsx18(Code2, { children: code }) });
887
2484
  };
888
2485
  var syntax_highlighter_default = ShikiSyntaxHighlighter;
889
2486
 
890
2487
  // src/components/markdown-text.tsx
891
- import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
2488
+ import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
892
2489
  var MarkdownTextImpl = () => {
893
- return /* @__PURE__ */ jsx9(
2490
+ return /* @__PURE__ */ jsx19(
894
2491
  MarkdownTextPrimitive,
895
2492
  {
896
2493
  remarkPlugins: [remarkGfm, remarkMath],
@@ -906,24 +2503,25 @@ var MarkdownTextImpl = () => {
906
2503
  var MarkdownText = memo(MarkdownTextImpl);
907
2504
  var CodeHeader = ({ language, code }) => {
908
2505
  const { isCopied, copyToClipboard } = useCopyToClipboard();
2506
+ if (isArtifactFenceLanguage(language)) return null;
909
2507
  const onCopy = () => {
910
2508
  if (!code || isCopied) return;
911
2509
  copyToClipboard(code);
912
2510
  };
913
- 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: [
914
- /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
915
- /* @__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" }),
916
2514
  language
917
2515
  ] }),
918
- /* @__PURE__ */ jsxs5(
2516
+ /* @__PURE__ */ jsxs10(
919
2517
  TooltipIconButton,
920
2518
  {
921
2519
  tooltip: isCopied ? "Copied!" : "Copy",
922
2520
  onClick: onCopy,
923
2521
  className: "transition-colors hover:text-foreground",
924
2522
  children: [
925
- !isCopied && /* @__PURE__ */ jsx9(CopyIcon, { className: "h-3.5 w-3.5" }),
926
- 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" })
927
2525
  ]
928
2526
  }
929
2527
  )
@@ -932,7 +2530,7 @@ var CodeHeader = ({ language, code }) => {
932
2530
  var useCopyToClipboard = ({
933
2531
  copiedDuration = 3e3
934
2532
  } = {}) => {
935
- const [isCopied, setIsCopied] = useState4(false);
2533
+ const [isCopied, setIsCopied] = useState5(false);
936
2534
  const copyToClipboard = (value) => {
937
2535
  if (!value) return;
938
2536
  navigator.clipboard.writeText(value).then(() => {
@@ -943,7 +2541,7 @@ var useCopyToClipboard = ({
943
2541
  return { isCopied, copyToClipboard };
944
2542
  };
945
2543
  var defaultComponents = memoizeMarkdownComponents({
946
- h1: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2544
+ h1: ({ className, ...props }) => /* @__PURE__ */ jsx19(
947
2545
  "h1",
948
2546
  {
949
2547
  className: cn(
@@ -953,7 +2551,7 @@ var defaultComponents = memoizeMarkdownComponents({
953
2551
  ...props
954
2552
  }
955
2553
  ),
956
- h2: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2554
+ h2: ({ className, ...props }) => /* @__PURE__ */ jsx19(
957
2555
  "h2",
958
2556
  {
959
2557
  className: cn(
@@ -963,7 +2561,7 @@ var defaultComponents = memoizeMarkdownComponents({
963
2561
  ...props
964
2562
  }
965
2563
  ),
966
- h3: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2564
+ h3: ({ className, ...props }) => /* @__PURE__ */ jsx19(
967
2565
  "h3",
968
2566
  {
969
2567
  className: cn(
@@ -973,7 +2571,7 @@ var defaultComponents = memoizeMarkdownComponents({
973
2571
  ...props
974
2572
  }
975
2573
  ),
976
- h4: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2574
+ h4: ({ className, ...props }) => /* @__PURE__ */ jsx19(
977
2575
  "h4",
978
2576
  {
979
2577
  className: cn(
@@ -983,7 +2581,7 @@ var defaultComponents = memoizeMarkdownComponents({
983
2581
  ...props
984
2582
  }
985
2583
  ),
986
- h5: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2584
+ h5: ({ className, ...props }) => /* @__PURE__ */ jsx19(
987
2585
  "h5",
988
2586
  {
989
2587
  className: cn(
@@ -993,7 +2591,7 @@ var defaultComponents = memoizeMarkdownComponents({
993
2591
  ...props
994
2592
  }
995
2593
  ),
996
- h6: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2594
+ h6: ({ className, ...props }) => /* @__PURE__ */ jsx19(
997
2595
  "h6",
998
2596
  {
999
2597
  className: cn(
@@ -1003,7 +2601,7 @@ var defaultComponents = memoizeMarkdownComponents({
1003
2601
  ...props
1004
2602
  }
1005
2603
  ),
1006
- p: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2604
+ p: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1007
2605
  "p",
1008
2606
  {
1009
2607
  className: cn(
@@ -1013,7 +2611,7 @@ var defaultComponents = memoizeMarkdownComponents({
1013
2611
  ...props
1014
2612
  }
1015
2613
  ),
1016
- a: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2614
+ a: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1017
2615
  "a",
1018
2616
  {
1019
2617
  className: cn(
@@ -1025,7 +2623,7 @@ var defaultComponents = memoizeMarkdownComponents({
1025
2623
  ...props
1026
2624
  }
1027
2625
  ),
1028
- blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2626
+ blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1029
2627
  "blockquote",
1030
2628
  {
1031
2629
  className: cn(
@@ -1035,7 +2633,7 @@ var defaultComponents = memoizeMarkdownComponents({
1035
2633
  ...props
1036
2634
  }
1037
2635
  ),
1038
- ul: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2636
+ ul: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1039
2637
  "ul",
1040
2638
  {
1041
2639
  className: cn(
@@ -1045,7 +2643,7 @@ var defaultComponents = memoizeMarkdownComponents({
1045
2643
  ...props
1046
2644
  }
1047
2645
  ),
1048
- ol: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2646
+ ol: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1049
2647
  "ol",
1050
2648
  {
1051
2649
  className: cn(
@@ -1055,7 +2653,7 @@ var defaultComponents = memoizeMarkdownComponents({
1055
2653
  ...props
1056
2654
  }
1057
2655
  ),
1058
- hr: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2656
+ hr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1059
2657
  "hr",
1060
2658
  {
1061
2659
  className: cn(
@@ -1065,14 +2663,14 @@ var defaultComponents = memoizeMarkdownComponents({
1065
2663
  ...props
1066
2664
  }
1067
2665
  ),
1068
- 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(
1069
2667
  "table",
1070
2668
  {
1071
2669
  className: cn("aui-md-table w-full border-collapse text-sm", className),
1072
2670
  ...props
1073
2671
  }
1074
2672
  ) }),
1075
- th: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2673
+ th: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1076
2674
  "th",
1077
2675
  {
1078
2676
  className: cn(
@@ -1082,7 +2680,7 @@ var defaultComponents = memoizeMarkdownComponents({
1082
2680
  ...props
1083
2681
  }
1084
2682
  ),
1085
- td: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2683
+ td: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1086
2684
  "td",
1087
2685
  {
1088
2686
  className: cn(
@@ -1092,7 +2690,7 @@ var defaultComponents = memoizeMarkdownComponents({
1092
2690
  ...props
1093
2691
  }
1094
2692
  ),
1095
- tr: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2693
+ tr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1096
2694
  "tr",
1097
2695
  {
1098
2696
  className: cn(
@@ -1102,8 +2700,8 @@ var defaultComponents = memoizeMarkdownComponents({
1102
2700
  ...props
1103
2701
  }
1104
2702
  ),
1105
- li: ({ className, ...props }) => /* @__PURE__ */ jsx9("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
1106
- 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(
1107
2705
  "sup",
1108
2706
  {
1109
2707
  className: cn(
@@ -1113,7 +2711,7 @@ var defaultComponents = memoizeMarkdownComponents({
1113
2711
  ...props
1114
2712
  }
1115
2713
  ),
1116
- pre: ({ className, ...props }) => /* @__PURE__ */ jsx9(
2714
+ pre: ({ className, ...props }) => /* @__PURE__ */ jsx19(
1117
2715
  "pre",
1118
2716
  {
1119
2717
  className: cn(
@@ -1125,119 +2723,391 @@ var defaultComponents = memoizeMarkdownComponents({
1125
2723
  ),
1126
2724
  code: function Code({ className, ...props }) {
1127
2725
  const isCodeBlock = useIsMarkdownCodeBlock();
1128
- return /* @__PURE__ */ jsx9(
2726
+ return /* @__PURE__ */ jsx19(
1129
2727
  "code",
1130
2728
  {
1131
- className: cn(
1132
- !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",
1133
- className
1134
- ),
1135
- ...props
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,
2978
+ {
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" })
2987
+ }
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" })
1136
2998
  }
1137
- );
1138
- },
1139
- strong: ({ className, ...props }) => /* @__PURE__ */ jsx9("strong", { className: cn("font-semibold text-foreground", className), ...props }),
1140
- em: ({ className, ...props }) => /* @__PURE__ */ jsx9("em", { className: cn("italic", className), ...props }),
1141
- CodeHeader
1142
- });
1143
-
1144
- // src/components/tool-fallback.tsx
1145
- import { memo as memo3 } from "react";
1146
- import { WrenchIcon } from "lucide-react";
2999
+ ) }) })
3000
+ ] });
3001
+ };
1147
3002
 
1148
- // src/ui/shimmer.tsx
1149
- import { motion } from "motion/react";
3003
+ // src/components/suggestions.tsx
1150
3004
  import {
1151
- memo as memo2,
1152
- useMemo
3005
+ useEffect as useEffect4,
3006
+ useMemo as useMemo5,
3007
+ useState as useState7
1153
3008
  } from "react";
1154
- import { jsx as jsx10 } from "react/jsx-runtime";
1155
- var ShimmerComponent = ({
1156
- children,
1157
- as: Component = "p",
1158
- className,
1159
- duration = 2,
1160
- 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
1161
3015
  }) => {
1162
- const MotionComponent = motion.create(
1163
- Component
1164
- );
1165
- const dynamicSpread = useMemo(
1166
- () => (children?.length ?? 0) * spread,
1167
- [children, spread]
1168
- );
1169
- return /* @__PURE__ */ jsx10(
1170
- MotionComponent,
3016
+ const items = useResolvedSuggestions(suggestions);
3017
+ if (!items || items.length === 0) return null;
3018
+ return /* @__PURE__ */ jsx24(
3019
+ "div",
1171
3020
  {
1172
- animate: { backgroundPosition: "0% center" },
1173
3021
  className: cn(
1174
- "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
1175
- "[--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",
1176
3024
  className
1177
3025
  ),
1178
- initial: { backgroundPosition: "100% center" },
1179
- style: {
1180
- "--spread": `${dynamicSpread}px`,
1181
- backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
1182
- },
1183
- transition: {
1184
- repeat: Number.POSITIVE_INFINITY,
1185
- duration,
1186
- ease: "linear"
1187
- },
1188
- children
3026
+ children: items.map((s, i) => /* @__PURE__ */ jsx24(SuggestionChip, { suggestion: s, compact: layout === "row" }, s.title + i))
1189
3027
  }
1190
3028
  );
1191
3029
  };
1192
- var Shimmer = memo2(ShimmerComponent);
1193
-
1194
- // src/components/tool-fallback.tsx
1195
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1196
- var ToolFallbackImpl = ({
1197
- toolName,
1198
- status
3030
+ var SuggestionChip = ({
3031
+ suggestion,
3032
+ compact
1199
3033
  }) => {
1200
- if (status?.type !== "running") return null;
1201
- return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 py-1 text-sm text-muted-foreground", children: [
1202
- /* @__PURE__ */ jsx11(WrenchIcon, { className: "size-4" }),
1203
- /* @__PURE__ */ jsx11(Shimmer, { as: "span", duration: 1.8, spread: 2.5, children: `Using tool: ${toolName}` })
1204
- ] });
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
+ ) });
1205
3055
  };
1206
- var ToolFallback = memo3(
1207
- ToolFallbackImpl
1208
- );
1209
- 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
+ }
1210
3081
 
1211
3082
  // src/components/thread.tsx
1212
3083
  import {
1213
3084
  ActionBarMorePrimitive,
1214
3085
  ActionBarPrimitive,
1215
- AuiIf,
1216
- ComposerPrimitive as ComposerPrimitive2,
3086
+ AuiIf as AuiIf2,
3087
+ ComposerPrimitive as ComposerPrimitive3,
1217
3088
  ErrorPrimitive,
1218
3089
  MessagePrimitive as MessagePrimitive2,
1219
- ThreadPrimitive,
1220
- useThreadRuntime
3090
+ ThreadPrimitive
1221
3091
  } from "@assistant-ui/react";
1222
3092
  import {
1223
3093
  ArrowDownIcon,
1224
- ArrowUpIcon,
1225
- CheckIcon as CheckIcon2,
3094
+ CheckIcon as CheckIcon3,
1226
3095
  CopyIcon as CopyIcon2,
1227
3096
  DownloadIcon,
1228
3097
  MoreHorizontalIcon,
1229
3098
  PencilIcon,
1230
- RefreshCwIcon,
1231
- SquareIcon
3099
+ RefreshCwIcon
1232
3100
  } from "lucide-react";
1233
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
3101
+ import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
1234
3102
  var Thread = ({
1235
3103
  className,
1236
3104
  maxWidth = "44rem",
1237
3105
  welcome,
1238
3106
  suggestions,
1239
3107
  composerPlaceholder = "Send a message...",
1240
- components
3108
+ components,
3109
+ artifacts,
3110
+ onArtifactEvent
1241
3111
  }) => {
1242
3112
  const WelcomeSlot = components?.Welcome ?? ThreadWelcome;
1243
3113
  const ComposerSlot = components?.Composer ?? Composer;
@@ -1245,59 +3115,79 @@ var Thread = ({
1245
3115
  const AssistantMessageSlot = components?.AssistantMessage ?? AssistantMessage;
1246
3116
  const EditComposerSlot = components?.EditComposer ?? EditComposer;
1247
3117
  const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
1248
- return /* @__PURE__ */ jsx12(
1249
- ThreadPrimitive.Root,
3118
+ const SuggestionsSlot = components?.Suggestions ?? Suggestions;
3119
+ return /* @__PURE__ */ jsx25(
3120
+ ArtifactRegistryProvider,
1250
3121
  {
1251
- className: cn(
1252
- "aui-root aui-thread-root @container flex h-full flex-col bg-background",
1253
- className
1254
- ),
1255
- style: { ["--thread-max-width"]: maxWidth },
1256
- children: /* @__PURE__ */ jsxs7(
1257
- ThreadPrimitive.Viewport,
3122
+ renderers: artifacts?.renderers,
3123
+ override: artifacts?.override,
3124
+ children: /* @__PURE__ */ jsx25(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
3125
+ }), children: /* @__PURE__ */ jsx25(
3126
+ ThreadPrimitive.Root,
1258
3127
  {
1259
- turnAnchor: "bottom",
1260
- className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
1261
- children: [
1262
- /* @__PURE__ */ jsx12(WelcomeSlot, { config: welcome, suggestions }),
1263
- /* @__PURE__ */ jsx12(
1264
- ThreadPrimitive.Messages,
1265
- {
1266
- components: {
1267
- UserMessage: UserMessageSlot,
1268
- EditComposer: EditComposerSlot,
1269
- AssistantMessage: AssistantMessageSlot
1270
- }
1271
- }
1272
- ),
1273
- /* @__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: [
1274
- /* @__PURE__ */ jsx12(ScrollToBottomSlot, {}),
1275
- /* @__PURE__ */ jsx12(ComposerSlot, { placeholder: composerPlaceholder })
1276
- ] })
1277
- ]
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
+ )
1278
3164
  }
1279
- )
3165
+ ) })
1280
3166
  }
1281
3167
  );
1282
3168
  };
1283
3169
  var ThreadScrollToBottom = () => {
1284
- return /* @__PURE__ */ jsx12(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx12(
3170
+ return /* @__PURE__ */ jsx25(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx25(
1285
3171
  TooltipIconButton,
1286
3172
  {
1287
3173
  tooltip: "Scroll to bottom",
1288
3174
  variant: "outline",
1289
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",
1290
- children: /* @__PURE__ */ jsx12(ArrowDownIcon, {})
3176
+ children: /* @__PURE__ */ jsx25(ArrowDownIcon, {})
1291
3177
  }
1292
3178
  ) });
1293
3179
  };
1294
- var ThreadWelcome = ({ config, suggestions }) => {
1295
- 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: [
1296
- /* @__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: [
1297
- /* @__PURE__ */ jsxs7("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
1298
- /* @__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" }),
1299
- /* @__PURE__ */ jsx12("div", { className: "animate-ai-pulse-ring absolute inset-0" }),
1300
- /* @__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(
1301
3191
  "svg",
1302
3192
  {
1303
3193
  xmlns: "http://www.w3.org/2000/svg",
@@ -1308,110 +3198,45 @@ var ThreadWelcome = ({ config, suggestions }) => {
1308
3198
  strokeLinecap: "round",
1309
3199
  strokeLinejoin: "round",
1310
3200
  className: "animate-ai-breathe relative size-7 text-primary/75",
1311
- 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" })
1312
3202
  }
1313
3203
  )
1314
3204
  ] }),
1315
- /* @__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?" }),
1316
- /* @__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." })
1317
3207
  ] }) }),
1318
- suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx12(ThreadSuggestions, { suggestions })
1319
- ] }) });
1320
- };
1321
- var ThreadSuggestions = ({ suggestions }) => {
1322
- 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)) });
1323
- };
1324
- var ThreadSuggestionItem = ({ title, description }) => {
1325
- const runtime = useThreadRuntime();
1326
- 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(
1327
- Button,
1328
- {
1329
- variant: "ghost",
1330
- 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",
1331
- onClick: () => runtime.append({
1332
- role: "user",
1333
- content: [{ type: "text", text: title }]
1334
- }),
1335
- children: [
1336
- /* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children: title }),
1337
- description && /* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children: description })
1338
- ]
1339
- }
1340
- ) });
1341
- };
1342
- var Composer = ({ placeholder }) => {
1343
- 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: [
1344
- /* @__PURE__ */ jsx12(ComposerAttachments, {}),
1345
- /* @__PURE__ */ jsx12(
1346
- ComposerPrimitive2.Input,
1347
- {
1348
- placeholder: placeholder ?? "Send a message...",
1349
- 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",
1350
- rows: 1,
1351
- autoFocus: true,
1352
- "aria-label": "Message input"
1353
- }
1354
- ),
1355
- /* @__PURE__ */ jsx12(ComposerAction, {})
3208
+ suggestions && /* @__PURE__ */ jsx25(SuggestionsSlot, { suggestions })
1356
3209
  ] }) });
1357
3210
  };
1358
- var ComposerAction = () => {
1359
- return /* @__PURE__ */ jsxs7("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-end", children: [
1360
- /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx12(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx12(
1361
- TooltipIconButton,
1362
- {
1363
- tooltip: "Send message",
1364
- side: "bottom",
1365
- type: "submit",
1366
- variant: "default",
1367
- size: "icon",
1368
- className: "aui-composer-send size-8 rounded-full",
1369
- "aria-label": "Send message",
1370
- children: /* @__PURE__ */ jsx12(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
1371
- }
1372
- ) }) }),
1373
- /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx12(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx12(
1374
- Button,
1375
- {
1376
- type: "button",
1377
- variant: "default",
1378
- size: "icon",
1379
- className: "aui-composer-cancel size-8 rounded-full",
1380
- "aria-label": "Stop generating",
1381
- children: /* @__PURE__ */ jsx12(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
1382
- }
1383
- ) }) })
1384
- ] });
1385
- };
1386
3211
  var MessageError = () => {
1387
- 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" }) }) });
1388
3213
  };
1389
3214
  var AssistantMessage = () => {
1390
- return /* @__PURE__ */ jsxs7(
3215
+ return /* @__PURE__ */ jsxs14(
1391
3216
  MessagePrimitive2.Root,
1392
3217
  {
1393
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",
1394
3219
  "data-role": "assistant",
1395
3220
  children: [
1396
- /* @__PURE__ */ jsxs7("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
1397
- /* @__PURE__ */ jsx12(
3221
+ /* @__PURE__ */ jsxs14("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
3222
+ /* @__PURE__ */ jsx25(
1398
3223
  MessagePrimitive2.Parts,
1399
3224
  {
1400
3225
  components: {
1401
3226
  Text: MarkdownText,
1402
- tools: { Fallback: ToolFallback }
3227
+ tools: { Fallback: ToolArtifactFallback }
1403
3228
  }
1404
3229
  }
1405
3230
  ),
1406
- /* @__PURE__ */ jsx12(MessageError, {})
3231
+ /* @__PURE__ */ jsx25(MessageError, {})
1407
3232
  ] }),
1408
- /* @__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, {}) })
1409
3234
  ]
1410
3235
  }
1411
3236
  );
1412
3237
  };
1413
3238
  var AssistantActionBar = () => {
1414
- return /* @__PURE__ */ jsxs7(
3239
+ return /* @__PURE__ */ jsxs14(
1415
3240
  ActionBarPrimitive.Root,
1416
3241
  {
1417
3242
  hideWhenRunning: true,
@@ -1419,28 +3244,28 @@ var AssistantActionBar = () => {
1419
3244
  autohideFloat: "single-branch",
1420
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",
1421
3246
  children: [
1422
- /* @__PURE__ */ jsx12(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs7(TooltipIconButton, { tooltip: "Copy", children: [
1423
- /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx12(CheckIcon2, {}) }),
1424
- /* @__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, {}) })
1425
3250
  ] }) }),
1426
- /* @__PURE__ */ jsx12(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx12(TooltipIconButton, { tooltip: "Refresh", children: /* @__PURE__ */ jsx12(RefreshCwIcon, {}) }) }),
1427
- /* @__PURE__ */ jsxs7(ActionBarMorePrimitive.Root, { children: [
1428
- /* @__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(
1429
3254
  TooltipIconButton,
1430
3255
  {
1431
3256
  tooltip: "More",
1432
3257
  className: "data-[state=open]:bg-accent",
1433
- children: /* @__PURE__ */ jsx12(MoreHorizontalIcon, {})
3258
+ children: /* @__PURE__ */ jsx25(MoreHorizontalIcon, {})
1434
3259
  }
1435
3260
  ) }),
1436
- /* @__PURE__ */ jsx12(
3261
+ /* @__PURE__ */ jsx25(
1437
3262
  ActionBarMorePrimitive.Content,
1438
3263
  {
1439
3264
  side: "bottom",
1440
3265
  align: "start",
1441
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",
1442
- 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: [
1443
- /* @__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" }),
1444
3269
  "Export as Markdown"
1445
3270
  ] }) })
1446
3271
  }
@@ -1451,83 +3276,398 @@ var AssistantActionBar = () => {
1451
3276
  );
1452
3277
  };
1453
3278
  var UserMessage = () => {
1454
- return /* @__PURE__ */ jsxs7(
3279
+ return /* @__PURE__ */ jsxs14(
1455
3280
  MessagePrimitive2.Root,
1456
3281
  {
1457
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",
1458
3283
  "data-role": "user",
1459
3284
  children: [
1460
- /* @__PURE__ */ jsx12(UserMessageAttachments, {}),
1461
- /* @__PURE__ */ jsxs7("div", { className: "aui-user-message-content-wrapper relative col-start-2 min-w-0", children: [
1462
- /* @__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, {}) }),
1463
- /* @__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, {}) })
1464
3289
  ] })
1465
3290
  ]
1466
3291
  }
1467
3292
  );
1468
3293
  };
1469
3294
  var UserActionBar = () => {
1470
- return /* @__PURE__ */ jsx12(
3295
+ return /* @__PURE__ */ jsx25(
1471
3296
  ActionBarPrimitive.Root,
1472
3297
  {
1473
3298
  hideWhenRunning: true,
1474
3299
  autohide: "not-last",
1475
3300
  className: "aui-user-action-bar-root flex flex-col items-end",
1476
- 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, {}) }) })
1477
3302
  }
1478
3303
  );
1479
3304
  };
1480
3305
  var EditComposer = () => {
1481
- 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: [
1482
- /* @__PURE__ */ jsx12(
1483
- 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,
1484
3309
  {
1485
3310
  className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
1486
3311
  autoFocus: true
1487
3312
  }
1488
3313
  ),
1489
- /* @__PURE__ */ jsxs7("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
1490
- /* @__PURE__ */ jsx12(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
1491
- /* @__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" }) })
1492
3317
  ] })
1493
3318
  ] }) });
1494
3319
  };
1495
3320
 
1496
3321
  // src/components/chat.tsx
1497
- import { jsx as jsx13 } from "react/jsx-runtime";
3322
+ import { jsx as jsx26 } from "react/jsx-runtime";
1498
3323
  function TimbalChat({
1499
3324
  workforceId,
1500
3325
  baseUrl,
1501
3326
  fetch: fetch2,
3327
+ attachments,
3328
+ attachmentsUploadUrl,
3329
+ attachmentsAccept,
3330
+ debug,
1502
3331
  ...threadProps
1503
3332
  }) {
1504
- 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
+ }
1505
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();
1506
3473
 
1507
3474
  // src/index.ts
1508
3475
  import {
1509
3476
  ThreadPrimitive as ThreadPrimitive2,
1510
3477
  MessagePrimitive as MessagePrimitive3,
1511
- ComposerPrimitive as ComposerPrimitive3,
3478
+ ComposerPrimitive as ComposerPrimitive4,
1512
3479
  ActionBarPrimitive as ActionBarPrimitive2,
3480
+ AssistantRuntimeProvider as AssistantRuntimeProvider2,
1513
3481
  useThread,
1514
- useThreadRuntime as useThreadRuntime2,
3482
+ useThreadRuntime as useThreadRuntime4,
1515
3483
  useMessageRuntime,
1516
- useComposerRuntime
3484
+ useComposerRuntime as useComposerRuntime2
1517
3485
  } from "@assistant-ui/react";
1518
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
+
1519
3652
  // src/auth/provider.tsx
1520
3653
  import {
1521
- createContext,
1522
- useCallback as useCallback2,
1523
- useContext,
1524
- useEffect as useEffect4,
1525
- useState as useState5
3654
+ createContext as createContext4,
3655
+ useCallback as useCallback3,
3656
+ useContext as useContext4,
3657
+ useEffect as useEffect6,
3658
+ useState as useState9
1526
3659
  } from "react";
1527
- import { jsx as jsx14 } from "react/jsx-runtime";
1528
- var SessionContext = createContext(void 0);
3660
+ import { jsx as jsx29 } from "react/jsx-runtime";
3661
+ function isInsideIframe() {
3662
+ try {
3663
+ return typeof window !== "undefined" && window.self !== window.top;
3664
+ } catch {
3665
+ return true;
3666
+ }
3667
+ }
3668
+ var SessionContext = createContext4(void 0);
1529
3669
  var useSession = () => {
1530
- const context = useContext(SessionContext);
3670
+ const context = useContext4(SessionContext);
1531
3671
  if (context === void 0) {
1532
3672
  throw new Error("useSession must be used within a SessionProvider");
1533
3673
  }
@@ -1537,9 +3677,10 @@ var SessionProvider = ({
1537
3677
  children,
1538
3678
  enabled = true
1539
3679
  }) => {
1540
- const [user, setUser] = useState5(null);
1541
- const [loading, setLoading] = useState5(enabled);
1542
- useEffect4(() => {
3680
+ const [user, setUser] = useState9(null);
3681
+ const [loading, setLoading] = useState9(enabled);
3682
+ const [embedded] = useState9(isInsideIframe);
3683
+ useEffect6(() => {
1543
3684
  if (!enabled) {
1544
3685
  setLoading(false);
1545
3686
  return;
@@ -1571,14 +3712,36 @@ var SessionProvider = ({
1571
3712
  if (ignore) return;
1572
3713
  clearTokens();
1573
3714
  }
1574
- setLoading(false);
3715
+ if (!ignore && !embedded) {
3716
+ setLoading(false);
3717
+ }
1575
3718
  };
1576
3719
  restoreSession();
3720
+ let messageCleanup;
3721
+ if (embedded) {
3722
+ const handleMessage = async (event) => {
3723
+ if (ignore) return;
3724
+ if (event.data?.type !== "timbal:auth" || !event.data.token) return;
3725
+ setAccessToken(event.data.token);
3726
+ if (event.data.refreshToken) {
3727
+ setRefreshToken(event.data.refreshToken);
3728
+ }
3729
+ const u = await fetchCurrentUser();
3730
+ if (!ignore) {
3731
+ setUser(u);
3732
+ setLoading(false);
3733
+ }
3734
+ };
3735
+ window.addEventListener("message", handleMessage);
3736
+ window.parent.postMessage({ type: "timbal:request-session" }, "*");
3737
+ messageCleanup = () => window.removeEventListener("message", handleMessage);
3738
+ }
1577
3739
  return () => {
1578
3740
  ignore = true;
3741
+ messageCleanup?.();
1579
3742
  };
1580
- }, [enabled]);
1581
- const logout = useCallback2(() => {
3743
+ }, [enabled, embedded]);
3744
+ const logout = useCallback3(() => {
1582
3745
  clearTokens();
1583
3746
  setUser(null);
1584
3747
  const returnTo = encodeURIComponent(
@@ -1588,13 +3751,14 @@ var SessionProvider = ({
1588
3751
  () => window.location.href = `/api/auth/login?return_to=${returnTo}`
1589
3752
  );
1590
3753
  }, []);
1591
- return /* @__PURE__ */ jsx14(
3754
+ return /* @__PURE__ */ jsx29(
1592
3755
  SessionContext.Provider,
1593
3756
  {
1594
3757
  value: {
1595
3758
  user,
1596
3759
  loading,
1597
3760
  isAuthenticated: !!user,
3761
+ isEmbedded: embedded,
1598
3762
  logout
1599
3763
  },
1600
3764
  children
@@ -1604,20 +3768,20 @@ var SessionProvider = ({
1604
3768
 
1605
3769
  // src/auth/guard.tsx
1606
3770
  import { Loader2 } from "lucide-react";
1607
- import { jsx as jsx15 } from "react/jsx-runtime";
3771
+ import { jsx as jsx30 } from "react/jsx-runtime";
1608
3772
  var AuthGuard = ({
1609
3773
  children,
1610
3774
  requireAuth = false,
1611
3775
  enabled = true
1612
3776
  }) => {
1613
- const { isAuthenticated, loading } = useSession();
3777
+ const { isAuthenticated, loading, isEmbedded } = useSession();
1614
3778
  if (!enabled) {
1615
3779
  return children;
1616
3780
  }
1617
3781
  if (loading) {
1618
- 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" }) });
1619
3783
  }
1620
- if (requireAuth && !isAuthenticated) {
3784
+ if (requireAuth && !isAuthenticated && !isEmbedded) {
1621
3785
  const returnTo = encodeURIComponent(
1622
3786
  window.location.pathname + window.location.search
1623
3787
  );
@@ -1627,15 +3791,24 @@ var AuthGuard = ({
1627
3791
  return children;
1628
3792
  };
1629
3793
  export {
3794
+ ARTIFACT_AGENT_INSTRUCTIONS,
3795
+ ARTIFACT_FENCE_LANGUAGES,
1630
3796
  ActionBarPrimitive2 as ActionBarPrimitive,
3797
+ ArtifactCard,
3798
+ ArtifactRegistryProvider,
3799
+ ArtifactView,
3800
+ AssistantRuntimeProvider2 as AssistantRuntimeProvider,
1631
3801
  AuthGuard,
1632
3802
  Avatar,
1633
3803
  AvatarFallback,
1634
3804
  AvatarImage,
1635
3805
  Button,
3806
+ ChartArtifactView,
3807
+ Composer,
1636
3808
  ComposerAddAttachment,
1637
3809
  ComposerAttachments,
1638
- ComposerPrimitive3 as ComposerPrimitive,
3810
+ ComposerPrimitive4 as ComposerPrimitive,
3811
+ DEFAULT_UPLOAD_ACCEPT,
1639
3812
  Dialog,
1640
3813
  DialogClose,
1641
3814
  DialogContent,
@@ -1643,33 +3816,70 @@ export {
1643
3816
  DialogPortal,
1644
3817
  DialogTitle,
1645
3818
  DialogTrigger,
3819
+ HtmlArtifactView,
3820
+ JsonArtifactView,
1646
3821
  MarkdownText,
1647
3822
  MessagePrimitive3 as MessagePrimitive,
3823
+ QuestionArtifactView,
1648
3824
  SessionProvider,
1649
3825
  Shimmer,
3826
+ Suggestions,
1650
3827
  syntax_highlighter_default as SyntaxHighlighter,
3828
+ TableArtifactView,
1651
3829
  Thread,
1652
3830
  ThreadPrimitive2 as ThreadPrimitive,
1653
3831
  TimbalChat,
3832
+ TimbalChatShell,
1654
3833
  TimbalRuntimeProvider,
3834
+ ToolArtifactFallback,
1655
3835
  ToolFallback,
1656
3836
  Tooltip,
1657
3837
  TooltipContent,
1658
3838
  TooltipIconButton,
1659
3839
  TooltipProvider,
1660
3840
  TooltipTrigger,
3841
+ UiArtifactView,
3842
+ UiCustomNodeRegistryProvider,
3843
+ UiEventProvider,
3844
+ UiNodeView,
1661
3845
  UserMessageAttachments,
3846
+ WorkforceSelector,
1662
3847
  authFetch,
1663
3848
  buttonVariants,
1664
3849
  clearTokens,
1665
3850
  cn,
3851
+ createDefaultAttachmentAdapter,
3852
+ createUploadAttachmentAdapter,
3853
+ defaultArtifactRenderers,
1666
3854
  fetchCurrentUser,
3855
+ findMarkdownArtifacts,
1667
3856
  getAccessToken,
3857
+ getPath,
1668
3858
  getRefreshToken,
3859
+ isArtifact,
3860
+ isArtifactFenceLanguage,
3861
+ isUiBinding,
3862
+ parseArtifactFromToolResult,
3863
+ parseSSELine2 as parseSSELine,
1669
3864
  refreshAccessToken,
1670
- useComposerRuntime,
3865
+ resolveAttachmentAdapter,
3866
+ resolveBindable,
3867
+ setAccessToken,
3868
+ setPath,
3869
+ setRefreshToken,
3870
+ splitMarkdownByArtifacts,
3871
+ useArtifactRegistry,
3872
+ useComposerRuntime2 as useComposerRuntime,
1671
3873
  useMessageRuntime,
3874
+ useResolvedSuggestions,
1672
3875
  useSession,
1673
3876
  useThread,
1674
- useThreadRuntime2 as useThreadRuntime
3877
+ useThreadRuntime4 as useThreadRuntime,
3878
+ useTimbalRuntime,
3879
+ useTimbalStream,
3880
+ useUiCustomNodeRegistry,
3881
+ useUiDispatch,
3882
+ useUiEventEmitter,
3883
+ useUiState,
3884
+ useWorkforces
1675
3885
  };