@timbal-ai/timbal-react 0.1.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.
@@ -0,0 +1,1700 @@
1
+ // src/runtime/provider.tsx
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import {
4
+ useExternalStoreRuntime,
5
+ AssistantRuntimeProvider
6
+ } from "@assistant-ui/react";
7
+ import { parseSSELine } from "@timbal-ai/timbal-sdk";
8
+
9
+ // src/auth/tokens.ts
10
+ var ACCESS_TOKEN_KEY = "timbal_project_access_token";
11
+ var REFRESH_TOKEN_KEY = "timbal_project_refresh_token";
12
+ var getAccessToken = () => localStorage.getItem(ACCESS_TOKEN_KEY);
13
+ var getRefreshToken = () => localStorage.getItem(REFRESH_TOKEN_KEY);
14
+ var clearTokens = () => {
15
+ localStorage.removeItem(ACCESS_TOKEN_KEY);
16
+ localStorage.removeItem(REFRESH_TOKEN_KEY);
17
+ };
18
+ var refreshPromise = null;
19
+ var refreshAccessToken = async () => {
20
+ const refreshToken = getRefreshToken();
21
+ if (!refreshToken) return false;
22
+ if (refreshPromise) return refreshPromise;
23
+ refreshPromise = (async () => {
24
+ try {
25
+ const res = await fetch("/api/auth/refresh", {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json" },
28
+ body: JSON.stringify({ refresh_token: refreshToken })
29
+ });
30
+ if (!res.ok) {
31
+ clearTokens();
32
+ return false;
33
+ }
34
+ const data = await res.json();
35
+ if (data.access_token) {
36
+ localStorage.setItem(ACCESS_TOKEN_KEY, data.access_token);
37
+ }
38
+ if (data.refresh_token) {
39
+ localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token);
40
+ }
41
+ return true;
42
+ } catch {
43
+ clearTokens();
44
+ return false;
45
+ } finally {
46
+ refreshPromise = null;
47
+ }
48
+ })();
49
+ return refreshPromise;
50
+ };
51
+ var authFetch = async (url, options) => {
52
+ const token = getAccessToken();
53
+ let res = await fetch(url, {
54
+ ...options,
55
+ headers: {
56
+ ...options?.headers,
57
+ ...token ? { Authorization: `Bearer ${token}` } : {}
58
+ }
59
+ });
60
+ if (res.status === 401 && getRefreshToken()) {
61
+ const refreshed = await refreshAccessToken();
62
+ if (refreshed) {
63
+ const newToken = getAccessToken();
64
+ res = await fetch(url, {
65
+ ...options,
66
+ headers: {
67
+ ...options?.headers,
68
+ ...newToken ? { Authorization: `Bearer ${newToken}` } : {}
69
+ }
70
+ });
71
+ }
72
+ }
73
+ return res;
74
+ };
75
+ var fetchCurrentUser = async () => {
76
+ try {
77
+ const token = getAccessToken();
78
+ const res = await fetch("/api/me", {
79
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
80
+ });
81
+ if (!res.ok) return null;
82
+ return await res.json();
83
+ } catch {
84
+ return null;
85
+ }
86
+ };
87
+
88
+ // src/runtime/provider.tsx
89
+ import { jsx } from "react/jsx-runtime";
90
+ var parseLine = parseSSELine;
91
+ var convertMessage = (message) => ({
92
+ role: message.role,
93
+ content: message.content,
94
+ id: message.id
95
+ });
96
+ function findParentId(messages, beforeIndex) {
97
+ const slice = beforeIndex !== void 0 ? messages.slice(0, beforeIndex) : messages;
98
+ for (let i = slice.length - 1; i >= 0; i--) {
99
+ if (slice[i].role === "assistant" && slice[i].runId) return slice[i].runId;
100
+ }
101
+ return null;
102
+ }
103
+ function isTopLevelStart(event) {
104
+ return event.type === "START" && typeof event.run_id === "string" && typeof event.path === "string" && !event.path.includes(".");
105
+ }
106
+ function getTextFromMessage(message) {
107
+ const part = message.content.find((c) => c.type === "text");
108
+ return part?.type === "text" ? part.text : null;
109
+ }
110
+ function waitWithAbort(ms, signal) {
111
+ if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
112
+ return new Promise((resolve, reject) => {
113
+ const timeoutId = setTimeout(() => {
114
+ signal.removeEventListener("abort", onAbort);
115
+ resolve();
116
+ }, ms);
117
+ const onAbort = () => {
118
+ clearTimeout(timeoutId);
119
+ reject(new DOMException("The operation was aborted.", "AbortError"));
120
+ };
121
+ signal.addEventListener("abort", onAbort, { once: true });
122
+ });
123
+ }
124
+ function buildFakeLongResponse(input) {
125
+ const safeInput = input.trim() || "your request";
126
+ const base = [
127
+ `Fake streaming fallback enabled. You asked: "${safeInput}".`,
128
+ "",
129
+ "This is a deliberately long response used to test rendering, scrolling, cancellation, and streaming UX behavior.",
130
+ "",
131
+ "What this stream is exercising:",
132
+ "- Frequent tiny token updates",
133
+ "- Long markdown paragraphs",
134
+ "- Bullet list rendering",
135
+ "- UI action bar behavior while running",
136
+ "- Stop button and abort flow",
137
+ "",
138
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vitae mi at augue pulvinar porta. Praesent ullamcorper felis at nibh tincidunt, id sagittis mauris interdum. Integer nec semper dui. Curabitur sed fermentum libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.",
139
+ "",
140
+ "Aliquam luctus purus non bibendum faucibus. Donec at elit eget massa feugiat ultricies. Quisque condimentum, libero in egestas varius, purus justo aliquam sem, vitae feugiat nunc lorem a justo. Sed non tempor est. In hac habitasse platea dictumst.",
141
+ "",
142
+ "If you can read this arriving progressively, the fallback is working as intended."
143
+ ].join("\n");
144
+ return `${base}
145
+
146
+ ---
147
+
148
+ ${base}`;
149
+ }
150
+ async function streamFakeLongResponse(input, delayMs, signal, onDelta) {
151
+ const fullResponse = buildFakeLongResponse(input);
152
+ let cursor = 0;
153
+ while (cursor < fullResponse.length) {
154
+ if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
155
+ const chunkSize = Math.min(fullResponse.length - cursor, Math.floor(Math.random() * 12) + 2);
156
+ const delta = fullResponse.slice(cursor, cursor + chunkSize);
157
+ cursor += chunkSize;
158
+ onDelta(delta);
159
+ await waitWithAbort(delayMs, signal);
160
+ }
161
+ }
162
+ function TimbalRuntimeProvider({
163
+ workforceId,
164
+ children,
165
+ baseUrl = "/api",
166
+ fetch: fetchFn,
167
+ devFakeStream = false,
168
+ devFakeStreamDelayMs = 75
169
+ }) {
170
+ const [messages, setMessages] = useState([]);
171
+ const [isRunning, setIsRunning] = useState(false);
172
+ const abortRef = useRef(null);
173
+ const messagesRef = useRef([]);
174
+ const fetchFnRef = useRef(fetchFn ?? authFetch);
175
+ useEffect(() => {
176
+ fetchFnRef.current = fetchFn ?? authFetch;
177
+ }, [fetchFn]);
178
+ useEffect(() => {
179
+ messagesRef.current = messages;
180
+ }, [messages]);
181
+ const streamAssistantResponse = useCallback(
182
+ async (input, userId, assistantId, parentId, signal) => {
183
+ const parts = [];
184
+ const toolIndexById = /* @__PURE__ */ new Map();
185
+ const lastTextPart = () => {
186
+ const last = parts[parts.length - 1];
187
+ if (last?.type === "text") return last;
188
+ const next = { type: "text", text: "" };
189
+ parts.push(next);
190
+ return next;
191
+ };
192
+ const flush = () => {
193
+ setMessages(
194
+ (prev) => prev.map((m) => m.id === assistantId ? { ...m, content: [...parts] } : m)
195
+ );
196
+ };
197
+ const stampRunId = (runId) => {
198
+ setMessages(
199
+ (prev) => prev.map((m) => m.id === userId || m.id === assistantId ? { ...m, runId } : m)
200
+ );
201
+ };
202
+ try {
203
+ if (devFakeStream) {
204
+ const fakeId = `call_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
205
+ parts.push({ type: "tool-call", toolCallId: fakeId, toolName: "get_datetime", argsText: "{}" });
206
+ flush();
207
+ await waitWithAbort(2e3, signal);
208
+ parts[0].result = `Current datetime (from tool): ${(/* @__PURE__ */ new Date()).toISOString()}`;
209
+ flush();
210
+ await waitWithAbort(300, signal);
211
+ await streamFakeLongResponse(input, devFakeStreamDelayMs, signal, (delta) => {
212
+ lastTextPart().text += delta;
213
+ flush();
214
+ });
215
+ return;
216
+ }
217
+ const res = await fetchFnRef.current(`${baseUrl}/workforce/${workforceId}/stream`, {
218
+ method: "POST",
219
+ headers: { "Content-Type": "application/json" },
220
+ body: JSON.stringify({
221
+ prompt: input,
222
+ context: { parent_id: parentId }
223
+ }),
224
+ signal
225
+ });
226
+ if (!res.ok || !res.body) throw new Error(`Request failed: ${res.status}`);
227
+ const reader = res.body.getReader();
228
+ const decoder = new TextDecoder();
229
+ let buffer = "";
230
+ let capturedRunId = null;
231
+ while (true) {
232
+ const { done, value } = await reader.read();
233
+ if (done) break;
234
+ buffer += decoder.decode(value, { stream: true });
235
+ const lines = buffer.split("\n");
236
+ buffer = lines.pop() ?? "";
237
+ for (const line of lines) {
238
+ const event = parseLine(line);
239
+ if (!event) continue;
240
+ if (!capturedRunId && isTopLevelStart(event)) {
241
+ capturedRunId = event.run_id;
242
+ stampRunId(capturedRunId);
243
+ }
244
+ switch (event.type) {
245
+ case "DELTA": {
246
+ const item = event.item;
247
+ if (!item) break;
248
+ if (item.type === "text_delta" && typeof item.text_delta === "string") {
249
+ lastTextPart().text += item.text_delta;
250
+ flush();
251
+ } else if (item.type === "tool_use") {
252
+ const toolCallId = item.id || `tool-${crypto.randomUUID()}`;
253
+ const inputStr = typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? {});
254
+ parts.push({
255
+ type: "tool-call",
256
+ toolCallId,
257
+ toolName: item.name || "unknown",
258
+ argsText: inputStr
259
+ });
260
+ toolIndexById.set(toolCallId, parts.length - 1);
261
+ flush();
262
+ } else if (item.type === "tool_use_delta") {
263
+ const idx = toolIndexById.get(item.id);
264
+ if (idx !== void 0 && typeof item.input_delta === "string") {
265
+ parts[idx].argsText += item.input_delta;
266
+ flush();
267
+ }
268
+ }
269
+ break;
270
+ }
271
+ case "OUTPUT": {
272
+ const output = event.output;
273
+ if (!output) break;
274
+ if (typeof output === "object" && Array.isArray(output.content)) {
275
+ for (const block of output.content) {
276
+ if (block.type === "tool_use") {
277
+ const id = block.id || `tool-${crypto.randomUUID()}`;
278
+ const idx = toolIndexById.get(id);
279
+ if (idx !== void 0) {
280
+ parts[idx].result = "Tool executed";
281
+ } else {
282
+ const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input ?? {});
283
+ parts.push({
284
+ type: "tool-call",
285
+ toolCallId: id,
286
+ toolName: block.name || "unknown",
287
+ argsText: inputStr,
288
+ result: "Tool executed"
289
+ });
290
+ toolIndexById.set(id, parts.length - 1);
291
+ }
292
+ } else if (block.type === "text" && typeof block.text === "string" && !lastTextPart().text) {
293
+ lastTextPart().text = block.text;
294
+ }
295
+ }
296
+ flush();
297
+ } else if (parts.length === 0) {
298
+ const text = typeof output === "string" ? output : JSON.stringify(output);
299
+ parts.push({ type: "text", text });
300
+ flush();
301
+ }
302
+ break;
303
+ }
304
+ }
305
+ }
306
+ }
307
+ if (buffer.trim()) {
308
+ const event = parseLine(buffer);
309
+ if (event?.type === "OUTPUT" && parts.length === 0 && event.output) {
310
+ const text = typeof event.output === "string" ? event.output : JSON.stringify(event.output);
311
+ parts.push({ type: "text", text });
312
+ flush();
313
+ }
314
+ }
315
+ } catch (err) {
316
+ if (err.name !== "AbortError") {
317
+ if (parts.length === 0) parts.push({ type: "text", text: "Something went wrong." });
318
+ flush();
319
+ }
320
+ } finally {
321
+ setIsRunning(false);
322
+ abortRef.current = null;
323
+ }
324
+ },
325
+ [workforceId, baseUrl, devFakeStream, devFakeStreamDelayMs]
326
+ );
327
+ const onNew = useCallback(
328
+ async (message) => {
329
+ const textPart = message.content.find((c) => c.type === "text");
330
+ if (!textPart || textPart.type !== "text") return;
331
+ const input = textPart.text;
332
+ const userId = crypto.randomUUID();
333
+ const assistantId = crypto.randomUUID();
334
+ let base = messagesRef.current;
335
+ if (message.parentId !== null) {
336
+ const parentIdx = base.findIndex((m) => m.id === message.parentId);
337
+ if (parentIdx >= 0) {
338
+ base = base.slice(0, parentIdx + 1);
339
+ }
340
+ }
341
+ const parentId = findParentId(base);
342
+ setMessages([
343
+ ...base,
344
+ { id: userId, role: "user", content: [{ type: "text", text: input }] }
345
+ ]);
346
+ setIsRunning(true);
347
+ setMessages((prev) => [
348
+ ...prev,
349
+ { id: assistantId, role: "assistant", content: [] }
350
+ ]);
351
+ const controller = new AbortController();
352
+ abortRef.current = controller;
353
+ await streamAssistantResponse(input, userId, assistantId, parentId, controller.signal);
354
+ },
355
+ [streamAssistantResponse]
356
+ );
357
+ const onReload = useCallback(
358
+ async (messageId) => {
359
+ const current = messagesRef.current;
360
+ const idx = messageId ? current.findIndex((m) => m.id === messageId) : current.length - 2;
361
+ const userMessage = idx >= 0 ? current[idx] : null;
362
+ if (!userMessage || userMessage.role !== "user") return;
363
+ const input = getTextFromMessage(userMessage);
364
+ if (!input) return;
365
+ const assistantId = crypto.randomUUID();
366
+ const parentId = findParentId(current, idx);
367
+ setMessages((prev) => [
368
+ ...prev.slice(0, idx + 1),
369
+ { id: assistantId, role: "assistant", content: [] }
370
+ ]);
371
+ setIsRunning(true);
372
+ const controller = new AbortController();
373
+ abortRef.current = controller;
374
+ await streamAssistantResponse(input, userMessage.id, assistantId, parentId, controller.signal);
375
+ },
376
+ [streamAssistantResponse]
377
+ );
378
+ const onCancel = useCallback(async () => {
379
+ abortRef.current?.abort();
380
+ }, []);
381
+ const runtime = useExternalStoreRuntime({
382
+ isRunning,
383
+ messages,
384
+ convertMessage,
385
+ onNew,
386
+ onEdit: onNew,
387
+ onReload,
388
+ onCancel
389
+ });
390
+ return /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children });
391
+ }
392
+
393
+ // src/components/attachment.tsx
394
+ import { useEffect as useEffect2, useState as useState2 } from "react";
395
+ import { XIcon as XIcon2, PlusIcon, FileText } from "lucide-react";
396
+ import {
397
+ AttachmentPrimitive,
398
+ ComposerPrimitive,
399
+ MessagePrimitive,
400
+ useAuiState,
401
+ useAui
402
+ } from "@assistant-ui/react";
403
+ import { useShallow } from "zustand/shallow";
404
+
405
+ // src/ui/tooltip.tsx
406
+ import { Tooltip as TooltipPrimitive } from "radix-ui";
407
+
408
+ // src/utils.ts
409
+ import { clsx } from "clsx";
410
+ import { twMerge } from "tailwind-merge";
411
+ function cn(...inputs) {
412
+ return twMerge(clsx(inputs));
413
+ }
414
+
415
+ // src/ui/tooltip.tsx
416
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
417
+ function TooltipProvider({
418
+ delayDuration = 0,
419
+ ...props
420
+ }) {
421
+ return /* @__PURE__ */ jsx2(
422
+ TooltipPrimitive.Provider,
423
+ {
424
+ "data-slot": "tooltip-provider",
425
+ delayDuration,
426
+ ...props
427
+ }
428
+ );
429
+ }
430
+ function Tooltip({
431
+ ...props
432
+ }) {
433
+ return /* @__PURE__ */ jsx2(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props });
434
+ }
435
+ function TooltipTrigger({
436
+ ...props
437
+ }) {
438
+ return /* @__PURE__ */ jsx2(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
439
+ }
440
+ function TooltipContent({
441
+ className,
442
+ sideOffset = 0,
443
+ children,
444
+ ...props
445
+ }) {
446
+ return /* @__PURE__ */ jsx2(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
447
+ TooltipPrimitive.Content,
448
+ {
449
+ "data-slot": "tooltip-content",
450
+ sideOffset,
451
+ className: cn(
452
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
453
+ className
454
+ ),
455
+ ...props,
456
+ children: [
457
+ children,
458
+ /* @__PURE__ */ jsx2(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
459
+ ]
460
+ }
461
+ ) });
462
+ }
463
+
464
+ // src/ui/dialog.tsx
465
+ import { XIcon } from "lucide-react";
466
+ import { Dialog as DialogPrimitive } from "radix-ui";
467
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
468
+ function Dialog({
469
+ ...props
470
+ }) {
471
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
472
+ }
473
+ function DialogTrigger({
474
+ ...props
475
+ }) {
476
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
477
+ }
478
+ function DialogPortal({
479
+ ...props
480
+ }) {
481
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
482
+ }
483
+ function DialogClose({
484
+ ...props
485
+ }) {
486
+ return /* @__PURE__ */ jsx3(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
487
+ }
488
+ function DialogOverlay({
489
+ className,
490
+ ...props
491
+ }) {
492
+ return /* @__PURE__ */ jsx3(
493
+ DialogPrimitive.Overlay,
494
+ {
495
+ "data-slot": "dialog-overlay",
496
+ className: cn(
497
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
498
+ className
499
+ ),
500
+ ...props
501
+ }
502
+ );
503
+ }
504
+ function DialogContent({
505
+ className,
506
+ children,
507
+ showCloseButton = true,
508
+ ...props
509
+ }) {
510
+ return /* @__PURE__ */ jsxs2(DialogPortal, { "data-slot": "dialog-portal", children: [
511
+ /* @__PURE__ */ jsx3(DialogOverlay, {}),
512
+ /* @__PURE__ */ jsxs2(
513
+ DialogPrimitive.Content,
514
+ {
515
+ "data-slot": "dialog-content",
516
+ className: cn(
517
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
518
+ className
519
+ ),
520
+ ...props,
521
+ children: [
522
+ children,
523
+ showCloseButton && /* @__PURE__ */ jsxs2(
524
+ DialogPrimitive.Close,
525
+ {
526
+ "data-slot": "dialog-close",
527
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
528
+ children: [
529
+ /* @__PURE__ */ jsx3(XIcon, {}),
530
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: "Close" })
531
+ ]
532
+ }
533
+ )
534
+ ]
535
+ }
536
+ )
537
+ ] });
538
+ }
539
+ function DialogTitle({
540
+ className,
541
+ ...props
542
+ }) {
543
+ return /* @__PURE__ */ jsx3(
544
+ DialogPrimitive.Title,
545
+ {
546
+ "data-slot": "dialog-title",
547
+ className: cn("text-lg leading-none font-semibold", className),
548
+ ...props
549
+ }
550
+ );
551
+ }
552
+
553
+ // src/ui/avatar.tsx
554
+ import { Avatar as AvatarPrimitive } from "radix-ui";
555
+ import { jsx as jsx4 } from "react/jsx-runtime";
556
+ function Avatar({
557
+ className,
558
+ size = "default",
559
+ ...props
560
+ }) {
561
+ return /* @__PURE__ */ jsx4(
562
+ AvatarPrimitive.Root,
563
+ {
564
+ "data-slot": "avatar",
565
+ "data-size": size,
566
+ className: cn(
567
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
568
+ className
569
+ ),
570
+ ...props
571
+ }
572
+ );
573
+ }
574
+ function AvatarImage({
575
+ className,
576
+ ...props
577
+ }) {
578
+ return /* @__PURE__ */ jsx4(
579
+ AvatarPrimitive.Image,
580
+ {
581
+ "data-slot": "avatar-image",
582
+ className: cn("aspect-square size-full", className),
583
+ ...props
584
+ }
585
+ );
586
+ }
587
+ function AvatarFallback({
588
+ className,
589
+ ...props
590
+ }) {
591
+ return /* @__PURE__ */ jsx4(
592
+ AvatarPrimitive.Fallback,
593
+ {
594
+ "data-slot": "avatar-fallback",
595
+ className: cn(
596
+ "bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
597
+ className
598
+ ),
599
+ ...props
600
+ }
601
+ );
602
+ }
603
+
604
+ // src/components/tooltip-icon-button.tsx
605
+ import { forwardRef } from "react";
606
+ import { Slottable } from "@radix-ui/react-slot";
607
+
608
+ // src/ui/button.tsx
609
+ import { cva } from "class-variance-authority";
610
+ import { Slot } from "radix-ui";
611
+ import { jsx as jsx5 } from "react/jsx-runtime";
612
+ var buttonVariants = cva(
613
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
614
+ {
615
+ variants: {
616
+ variant: {
617
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
618
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
619
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
620
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
621
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
622
+ link: "text-primary underline-offset-4 hover:underline"
623
+ },
624
+ size: {
625
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
626
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
627
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
628
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
629
+ icon: "size-9",
630
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
631
+ "icon-sm": "size-8",
632
+ "icon-lg": "size-10"
633
+ }
634
+ },
635
+ defaultVariants: {
636
+ variant: "default",
637
+ size: "default"
638
+ }
639
+ }
640
+ );
641
+ function Button({
642
+ className,
643
+ variant = "default",
644
+ size = "default",
645
+ asChild = false,
646
+ ...props
647
+ }) {
648
+ const Comp = asChild ? Slot.Root : "button";
649
+ return /* @__PURE__ */ jsx5(
650
+ Comp,
651
+ {
652
+ "data-slot": "button",
653
+ "data-variant": variant,
654
+ "data-size": size,
655
+ className: cn(buttonVariants({ variant, size, className })),
656
+ ...props
657
+ }
658
+ );
659
+ }
660
+
661
+ // src/components/tooltip-icon-button.tsx
662
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
663
+ var TooltipIconButton = forwardRef(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
664
+ return /* @__PURE__ */ jsxs3(Tooltip, { children: [
665
+ /* @__PURE__ */ jsx6(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
666
+ Button,
667
+ {
668
+ variant: "ghost",
669
+ size: "icon",
670
+ ...rest,
671
+ className: cn("aui-button-icon size-6 p-1", className),
672
+ ref,
673
+ children: [
674
+ /* @__PURE__ */ jsx6(Slottable, { children }),
675
+ /* @__PURE__ */ jsx6("span", { className: "aui-sr-only sr-only", children: tooltip })
676
+ ]
677
+ }
678
+ ) }),
679
+ /* @__PURE__ */ jsx6(TooltipContent, { side, children: tooltip })
680
+ ] });
681
+ });
682
+ TooltipIconButton.displayName = "TooltipIconButton";
683
+
684
+ // src/components/attachment.tsx
685
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
686
+ var useFileSrc = (file) => {
687
+ const [src, setSrc] = useState2(void 0);
688
+ useEffect2(() => {
689
+ if (!file) {
690
+ setSrc(void 0);
691
+ return;
692
+ }
693
+ const objectUrl = URL.createObjectURL(file);
694
+ setSrc(objectUrl);
695
+ return () => {
696
+ URL.revokeObjectURL(objectUrl);
697
+ };
698
+ }, [file]);
699
+ return src;
700
+ };
701
+ var useAttachmentSrc = () => {
702
+ const { file, src } = useAuiState(
703
+ useShallow((s) => {
704
+ if (s.attachment.type !== "image") return {};
705
+ if (s.attachment.file) return { file: s.attachment.file };
706
+ const src2 = s.attachment.content?.filter((c) => c.type === "image")[0]?.image;
707
+ if (!src2) return {};
708
+ return { src: src2 };
709
+ })
710
+ );
711
+ return useFileSrc(file) ?? src;
712
+ };
713
+ var AttachmentPreview = ({ src }) => {
714
+ const [isLoaded, setIsLoaded] = useState2(false);
715
+ return /* @__PURE__ */ jsx7(
716
+ "img",
717
+ {
718
+ src,
719
+ alt: "Image Preview",
720
+ className: cn(
721
+ "block h-auto max-h-[80vh] w-auto max-w-full object-contain",
722
+ isLoaded ? "aui-attachment-preview-image-loaded" : "aui-attachment-preview-image-loading invisible"
723
+ ),
724
+ onLoad: () => setIsLoaded(true)
725
+ }
726
+ );
727
+ };
728
+ var AttachmentPreviewDialog = ({ children }) => {
729
+ const src = useAttachmentSrc();
730
+ if (!src) return children;
731
+ return /* @__PURE__ */ jsxs4(Dialog, { children: [
732
+ /* @__PURE__ */ jsx7(
733
+ DialogTrigger,
734
+ {
735
+ className: "aui-attachment-preview-trigger cursor-pointer transition-colors hover:bg-accent/50",
736
+ asChild: true,
737
+ children
738
+ }
739
+ ),
740
+ /* @__PURE__ */ jsxs4(DialogContent, { className: "aui-attachment-preview-dialog-content p-2 sm:max-w-3xl [&>button]:rounded-full [&>button]:bg-foreground/60 [&>button]:p-1 [&>button]:opacity-100 [&>button]:ring-0! [&_svg]:text-background [&>button]:hover:[&_svg]:text-destructive", children: [
741
+ /* @__PURE__ */ jsx7(DialogTitle, { className: "aui-sr-only sr-only", children: "Image Attachment Preview" }),
742
+ /* @__PURE__ */ jsx7("div", { className: "aui-attachment-preview relative mx-auto flex max-h-[80dvh] w-full items-center justify-center overflow-hidden bg-background", children: /* @__PURE__ */ jsx7(AttachmentPreview, { src }) })
743
+ ] })
744
+ ] });
745
+ };
746
+ var AttachmentThumb = () => {
747
+ const isImage = useAuiState((s) => s.attachment.type === "image");
748
+ const src = useAttachmentSrc();
749
+ return /* @__PURE__ */ jsxs4(Avatar, { className: "aui-attachment-tile-avatar h-full w-full rounded-none", children: [
750
+ /* @__PURE__ */ jsx7(
751
+ AvatarImage,
752
+ {
753
+ src,
754
+ alt: "Attachment preview",
755
+ className: "aui-attachment-tile-image object-cover"
756
+ }
757
+ ),
758
+ /* @__PURE__ */ jsx7(AvatarFallback, { delayMs: isImage ? 200 : 0, children: /* @__PURE__ */ jsx7(FileText, { className: "aui-attachment-tile-fallback-icon size-8 text-muted-foreground" }) })
759
+ ] });
760
+ };
761
+ var AttachmentUI = () => {
762
+ const aui = useAui();
763
+ const isComposer = aui.attachment.source === "composer";
764
+ const isImage = useAuiState((s) => s.attachment.type === "image");
765
+ const typeLabel = useAuiState((s) => {
766
+ const type = s.attachment.type;
767
+ switch (type) {
768
+ case "image":
769
+ return "Image";
770
+ case "document":
771
+ return "Document";
772
+ case "file":
773
+ return "File";
774
+ default:
775
+ throw new Error(`Unknown attachment type: ${type}`);
776
+ }
777
+ });
778
+ return /* @__PURE__ */ jsxs4(Tooltip, { children: [
779
+ /* @__PURE__ */ jsxs4(
780
+ AttachmentPrimitive.Root,
781
+ {
782
+ className: cn(
783
+ "aui-attachment-root relative",
784
+ isImage && "aui-attachment-root-composer only:[&>#attachment-tile]:size-24"
785
+ ),
786
+ children: [
787
+ /* @__PURE__ */ jsx7(AttachmentPreviewDialog, { children: /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
788
+ "div",
789
+ {
790
+ className: cn(
791
+ "aui-attachment-tile size-14 cursor-pointer overflow-hidden rounded-[14px] border bg-muted transition-opacity hover:opacity-75",
792
+ isComposer && "aui-attachment-tile-composer border-foreground/20"
793
+ ),
794
+ role: "button",
795
+ id: "attachment-tile",
796
+ "aria-label": `${typeLabel} attachment`,
797
+ children: /* @__PURE__ */ jsx7(AttachmentThumb, {})
798
+ }
799
+ ) }) }),
800
+ isComposer && /* @__PURE__ */ jsx7(AttachmentRemove, {})
801
+ ]
802
+ }
803
+ ),
804
+ /* @__PURE__ */ jsx7(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx7(AttachmentPrimitive.Name, {}) })
805
+ ] });
806
+ };
807
+ var AttachmentRemove = () => {
808
+ return /* @__PURE__ */ jsx7(AttachmentPrimitive.Remove, { asChild: true, children: /* @__PURE__ */ jsx7(
809
+ TooltipIconButton,
810
+ {
811
+ tooltip: "Remove file",
812
+ className: "aui-attachment-tile-remove absolute top-1.5 right-1.5 size-3.5 rounded-full bg-white text-muted-foreground opacity-100 shadow-sm hover:bg-white! [&_svg]:text-black hover:[&_svg]:text-destructive",
813
+ side: "top",
814
+ children: /* @__PURE__ */ jsx7(XIcon2, { className: "aui-attachment-remove-icon size-3 dark:stroke-[2.5px]" })
815
+ }
816
+ ) });
817
+ };
818
+ var UserMessageAttachments = () => {
819
+ return /* @__PURE__ */ jsx7("div", { className: "aui-user-message-attachments-end col-span-full col-start-1 row-start-1 flex w-full flex-row justify-end gap-2", children: /* @__PURE__ */ jsx7(MessagePrimitive.Attachments, { components: { Attachment: AttachmentUI } }) });
820
+ };
821
+ var ComposerAttachments = () => {
822
+ return /* @__PURE__ */ jsx7("div", { className: "aui-composer-attachments mb-2 flex w-full flex-row items-center gap-2 overflow-x-auto px-1.5 pt-0.5 pb-1 empty:hidden", children: /* @__PURE__ */ jsx7(
823
+ ComposerPrimitive.Attachments,
824
+ {
825
+ components: { Attachment: AttachmentUI }
826
+ }
827
+ ) });
828
+ };
829
+ var ComposerAddAttachment = () => {
830
+ return /* @__PURE__ */ jsx7(ComposerPrimitive.AddAttachment, { asChild: true, children: /* @__PURE__ */ jsx7(
831
+ TooltipIconButton,
832
+ {
833
+ tooltip: "Add Attachment",
834
+ side: "bottom",
835
+ variant: "ghost",
836
+ size: "icon",
837
+ className: "aui-composer-add-attachment size-8.5 rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30",
838
+ "aria-label": "Add Attachment",
839
+ children: /* @__PURE__ */ jsx7(PlusIcon, { className: "aui-attachment-add-icon size-5 stroke-[1.5px]" })
840
+ }
841
+ ) });
842
+ };
843
+
844
+ // src/components/markdown-text.tsx
845
+ import "@assistant-ui/react-markdown/styles/dot.css";
846
+ import "katex/dist/katex.min.css";
847
+ import {
848
+ MarkdownTextPrimitive,
849
+ unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
850
+ useIsMarkdownCodeBlock
851
+ } from "@assistant-ui/react-markdown";
852
+ import remarkGfm from "remark-gfm";
853
+ import remarkMath from "remark-math";
854
+ import rehypeKatex from "rehype-katex";
855
+ import { memo, useState as useState4 } from "react";
856
+ import { CheckIcon, CopyIcon } from "lucide-react";
857
+
858
+ // src/components/syntax-highlighter.tsx
859
+ import { useEffect as useEffect3, useState as useState3 } from "react";
860
+ import { createHighlighterCore } from "shiki/core";
861
+ import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
862
+ import langJavascript from "shiki/langs/javascript.mjs";
863
+ import langTypescript from "shiki/langs/typescript.mjs";
864
+ import langPython from "shiki/langs/python.mjs";
865
+ import langHtml from "shiki/langs/html.mjs";
866
+ import langCss from "shiki/langs/css.mjs";
867
+ import langJson from "shiki/langs/json.mjs";
868
+ import langBash from "shiki/langs/bash.mjs";
869
+ import langMarkdown from "shiki/langs/markdown.mjs";
870
+ import langJsx from "shiki/langs/jsx.mjs";
871
+ import langTsx from "shiki/langs/tsx.mjs";
872
+ import langSql from "shiki/langs/sql.mjs";
873
+ import langYaml from "shiki/langs/yaml.mjs";
874
+ import langRust from "shiki/langs/rust.mjs";
875
+ import langGo from "shiki/langs/go.mjs";
876
+ import langJava from "shiki/langs/java.mjs";
877
+ import langC from "shiki/langs/c.mjs";
878
+ import langCpp from "shiki/langs/cpp.mjs";
879
+ import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
880
+ import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
881
+ import { jsx as jsx8 } from "react/jsx-runtime";
882
+ var SHIKI_THEME_DARK = "vitesse-dark";
883
+ var SHIKI_THEME_LIGHT = "vitesse-light";
884
+ var highlighterPromise = null;
885
+ function getHighlighter() {
886
+ if (!highlighterPromise) {
887
+ highlighterPromise = createHighlighterCore({
888
+ themes: [themeVitesseDark, themeVitesseLight],
889
+ langs: [
890
+ langJavascript,
891
+ langTypescript,
892
+ langPython,
893
+ langHtml,
894
+ langCss,
895
+ langJson,
896
+ langBash,
897
+ langMarkdown,
898
+ langJsx,
899
+ langTsx,
900
+ langSql,
901
+ langYaml,
902
+ langRust,
903
+ langGo,
904
+ langJava,
905
+ langC,
906
+ langCpp
907
+ ],
908
+ engine: createJavaScriptRegexEngine()
909
+ });
910
+ }
911
+ return highlighterPromise;
912
+ }
913
+ getHighlighter();
914
+ var ShikiSyntaxHighlighter = ({
915
+ components: { Pre, Code: Code2 },
916
+ language,
917
+ code
918
+ }) => {
919
+ const [html, setHtml] = useState3(null);
920
+ useEffect3(() => {
921
+ let cancelled = false;
922
+ (async () => {
923
+ try {
924
+ const highlighter = await getHighlighter();
925
+ const loadedLangs = highlighter.getLoadedLanguages();
926
+ if (!loadedLangs.includes(language)) {
927
+ if (!cancelled) setHtml(null);
928
+ return;
929
+ }
930
+ const result = highlighter.codeToHtml(code, {
931
+ lang: language,
932
+ themes: {
933
+ dark: SHIKI_THEME_DARK,
934
+ light: SHIKI_THEME_LIGHT
935
+ }
936
+ });
937
+ if (!cancelled) setHtml(result);
938
+ } catch {
939
+ if (!cancelled) setHtml(null);
940
+ }
941
+ })();
942
+ return () => {
943
+ cancelled = true;
944
+ };
945
+ }, [code, language]);
946
+ if (html) {
947
+ return /* @__PURE__ */ jsx8(
948
+ "div",
949
+ {
950
+ 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",
951
+ dangerouslySetInnerHTML: { __html: html }
952
+ }
953
+ );
954
+ }
955
+ return /* @__PURE__ */ jsx8(Pre, { children: /* @__PURE__ */ jsx8(Code2, { children: code }) });
956
+ };
957
+ var syntax_highlighter_default = ShikiSyntaxHighlighter;
958
+
959
+ // src/components/markdown-text.tsx
960
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
961
+ var MarkdownTextImpl = () => {
962
+ return /* @__PURE__ */ jsx9(
963
+ MarkdownTextPrimitive,
964
+ {
965
+ remarkPlugins: [remarkGfm, remarkMath],
966
+ rehypePlugins: [rehypeKatex],
967
+ className: "aui-md",
968
+ components: {
969
+ ...defaultComponents,
970
+ SyntaxHighlighter: syntax_highlighter_default
971
+ }
972
+ }
973
+ );
974
+ };
975
+ var MarkdownText = memo(MarkdownTextImpl);
976
+ var CodeHeader = ({ language, code }) => {
977
+ const { isCopied, copyToClipboard } = useCopyToClipboard();
978
+ const onCopy = () => {
979
+ if (!code || isCopied) return;
980
+ copyToClipboard(code);
981
+ };
982
+ 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: [
983
+ /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
984
+ /* @__PURE__ */ jsx9("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
985
+ language
986
+ ] }),
987
+ /* @__PURE__ */ jsxs5(
988
+ TooltipIconButton,
989
+ {
990
+ tooltip: isCopied ? "Copied!" : "Copy",
991
+ onClick: onCopy,
992
+ className: "transition-colors hover:text-foreground",
993
+ children: [
994
+ !isCopied && /* @__PURE__ */ jsx9(CopyIcon, { className: "h-3.5 w-3.5" }),
995
+ isCopied && /* @__PURE__ */ jsx9(CheckIcon, { className: "h-3.5 w-3.5 text-emerald-500" })
996
+ ]
997
+ }
998
+ )
999
+ ] });
1000
+ };
1001
+ var useCopyToClipboard = ({
1002
+ copiedDuration = 3e3
1003
+ } = {}) => {
1004
+ const [isCopied, setIsCopied] = useState4(false);
1005
+ const copyToClipboard = (value) => {
1006
+ if (!value) return;
1007
+ navigator.clipboard.writeText(value).then(() => {
1008
+ setIsCopied(true);
1009
+ setTimeout(() => setIsCopied(false), copiedDuration);
1010
+ });
1011
+ };
1012
+ return { isCopied, copyToClipboard };
1013
+ };
1014
+ var defaultComponents = memoizeMarkdownComponents({
1015
+ h1: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1016
+ "h1",
1017
+ {
1018
+ className: cn(
1019
+ "aui-md-h1 mb-3 mt-6 scroll-m-20 text-xl font-bold tracking-tight first:mt-0 last:mb-0",
1020
+ className
1021
+ ),
1022
+ ...props
1023
+ }
1024
+ ),
1025
+ h2: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1026
+ "h2",
1027
+ {
1028
+ className: cn(
1029
+ "aui-md-h2 mb-2.5 mt-5 scroll-m-20 border-b border-border/30 pb-1.5 text-lg font-semibold tracking-tight first:mt-0 last:mb-0",
1030
+ className
1031
+ ),
1032
+ ...props
1033
+ }
1034
+ ),
1035
+ h3: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1036
+ "h3",
1037
+ {
1038
+ className: cn(
1039
+ "aui-md-h3 mb-2 mt-4 scroll-m-20 text-base font-semibold tracking-tight first:mt-0 last:mb-0",
1040
+ className
1041
+ ),
1042
+ ...props
1043
+ }
1044
+ ),
1045
+ h4: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1046
+ "h4",
1047
+ {
1048
+ className: cn(
1049
+ "aui-md-h4 mb-1.5 mt-3 scroll-m-20 text-sm font-semibold first:mt-0 last:mb-0",
1050
+ className
1051
+ ),
1052
+ ...props
1053
+ }
1054
+ ),
1055
+ h5: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1056
+ "h5",
1057
+ {
1058
+ className: cn(
1059
+ "aui-md-h5 mb-1 mt-2.5 text-sm font-medium first:mt-0 last:mb-0",
1060
+ className
1061
+ ),
1062
+ ...props
1063
+ }
1064
+ ),
1065
+ h6: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1066
+ "h6",
1067
+ {
1068
+ className: cn(
1069
+ "aui-md-h6 mb-1 mt-2 text-sm font-medium text-muted-foreground first:mt-0 last:mb-0",
1070
+ className
1071
+ ),
1072
+ ...props
1073
+ }
1074
+ ),
1075
+ p: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1076
+ "p",
1077
+ {
1078
+ className: cn(
1079
+ "aui-md-p my-3 leading-[1.7] first:mt-0 last:mb-0",
1080
+ className
1081
+ ),
1082
+ ...props
1083
+ }
1084
+ ),
1085
+ a: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1086
+ "a",
1087
+ {
1088
+ className: cn(
1089
+ "aui-md-a font-medium text-primary underline decoration-primary/30 underline-offset-[3px] transition-colors hover:decoration-primary/80",
1090
+ className
1091
+ ),
1092
+ target: "_blank",
1093
+ rel: "noopener noreferrer",
1094
+ ...props
1095
+ }
1096
+ ),
1097
+ blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1098
+ "blockquote",
1099
+ {
1100
+ className: cn(
1101
+ "aui-md-blockquote my-3 border-l-[3px] border-primary/30 bg-muted/30 py-1 pl-4 pr-2 text-muted-foreground italic [&>p]:my-1",
1102
+ className
1103
+ ),
1104
+ ...props
1105
+ }
1106
+ ),
1107
+ ul: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1108
+ "ul",
1109
+ {
1110
+ className: cn(
1111
+ "aui-md-ul my-3 ml-1 list-none space-y-1.5 [&>li]:relative [&>li]:pl-5 [&>li]:before:absolute [&>li]:before:left-0 [&>li]:before:top-[0.6em] [&>li]:before:h-1.5 [&>li]:before:w-1.5 [&>li]:before:rounded-full [&>li]:before:bg-primary/30 [&>li]:before:content-['']",
1112
+ className
1113
+ ),
1114
+ ...props
1115
+ }
1116
+ ),
1117
+ ol: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1118
+ "ol",
1119
+ {
1120
+ className: cn(
1121
+ "aui-md-ol my-3 ml-1 list-none space-y-1.5 [counter-reset:list-counter] [&>li]:relative [&>li]:pl-7 [&>li]:[counter-increment:list-counter] [&>li]:before:absolute [&>li]:before:left-0 [&>li]:before:top-0 [&>li]:before:flex [&>li]:before:h-[1.7em] [&>li]:before:w-5 [&>li]:before:items-center [&>li]:before:justify-center [&>li]:before:rounded-md [&>li]:before:bg-primary/[0.07] [&>li]:before:text-xs [&>li]:before:font-semibold [&>li]:before:text-primary/60 [&>li]:before:content-[counter(list-counter)]",
1122
+ className
1123
+ ),
1124
+ ...props
1125
+ }
1126
+ ),
1127
+ hr: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1128
+ "hr",
1129
+ {
1130
+ className: cn(
1131
+ "aui-md-hr my-6 border-none h-px bg-gradient-to-r from-transparent via-border to-transparent",
1132
+ className
1133
+ ),
1134
+ ...props
1135
+ }
1136
+ ),
1137
+ table: ({ className, ...props }) => /* @__PURE__ */ jsx9("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx9(
1138
+ "table",
1139
+ {
1140
+ className: cn("aui-md-table w-full border-collapse text-sm", className),
1141
+ ...props
1142
+ }
1143
+ ) }),
1144
+ th: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1145
+ "th",
1146
+ {
1147
+ className: cn(
1148
+ "aui-md-th border-b border-border/50 bg-muted/60 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground [[align=center]]:text-center [[align=right]]:text-right",
1149
+ className
1150
+ ),
1151
+ ...props
1152
+ }
1153
+ ),
1154
+ td: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1155
+ "td",
1156
+ {
1157
+ className: cn(
1158
+ "aui-md-td border-b border-border/30 px-3 py-2 [[align=center]]:text-center [[align=right]]:text-right",
1159
+ className
1160
+ ),
1161
+ ...props
1162
+ }
1163
+ ),
1164
+ tr: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1165
+ "tr",
1166
+ {
1167
+ className: cn(
1168
+ "aui-md-tr transition-colors hover:bg-muted/30 [&:last-child>td]:border-b-0",
1169
+ className
1170
+ ),
1171
+ ...props
1172
+ }
1173
+ ),
1174
+ li: ({ className, ...props }) => /* @__PURE__ */ jsx9("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
1175
+ sup: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1176
+ "sup",
1177
+ {
1178
+ className: cn(
1179
+ "aui-md-sup [&>a]:text-[0.7em] [&>a]:font-semibold [&>a]:text-primary/70 [&>a]:no-underline [&>a]:transition-colors [&>a]:hover:text-primary",
1180
+ className
1181
+ ),
1182
+ ...props
1183
+ }
1184
+ ),
1185
+ pre: ({ className, ...props }) => /* @__PURE__ */ jsx9(
1186
+ "pre",
1187
+ {
1188
+ className: cn(
1189
+ "aui-md-pre overflow-x-auto rounded-t-none rounded-b-lg border border-t-0 border-border/50 bg-zinc-50 p-4 text-[13px] leading-relaxed dark:bg-zinc-900/80",
1190
+ className
1191
+ ),
1192
+ ...props
1193
+ }
1194
+ ),
1195
+ code: function Code({ className, ...props }) {
1196
+ const isCodeBlock = useIsMarkdownCodeBlock();
1197
+ return /* @__PURE__ */ jsx9(
1198
+ "code",
1199
+ {
1200
+ className: cn(
1201
+ !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",
1202
+ className
1203
+ ),
1204
+ ...props
1205
+ }
1206
+ );
1207
+ },
1208
+ strong: ({ className, ...props }) => /* @__PURE__ */ jsx9("strong", { className: cn("font-semibold text-foreground", className), ...props }),
1209
+ em: ({ className, ...props }) => /* @__PURE__ */ jsx9("em", { className: cn("italic", className), ...props }),
1210
+ CodeHeader
1211
+ });
1212
+
1213
+ // src/components/tool-fallback.tsx
1214
+ import { memo as memo3 } from "react";
1215
+ import { WrenchIcon } from "lucide-react";
1216
+
1217
+ // src/ui/shimmer.tsx
1218
+ import { motion } from "motion/react";
1219
+ import {
1220
+ memo as memo2,
1221
+ useMemo
1222
+ } from "react";
1223
+ import { jsx as jsx10 } from "react/jsx-runtime";
1224
+ var ShimmerComponent = ({
1225
+ children,
1226
+ as: Component = "p",
1227
+ className,
1228
+ duration = 2,
1229
+ spread = 2
1230
+ }) => {
1231
+ const MotionComponent = motion.create(
1232
+ Component
1233
+ );
1234
+ const dynamicSpread = useMemo(
1235
+ () => (children?.length ?? 0) * spread,
1236
+ [children, spread]
1237
+ );
1238
+ return /* @__PURE__ */ jsx10(
1239
+ MotionComponent,
1240
+ {
1241
+ animate: { backgroundPosition: "0% center" },
1242
+ className: cn(
1243
+ "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
1244
+ "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
1245
+ className
1246
+ ),
1247
+ initial: { backgroundPosition: "100% center" },
1248
+ style: {
1249
+ "--spread": `${dynamicSpread}px`,
1250
+ backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
1251
+ },
1252
+ transition: {
1253
+ repeat: Number.POSITIVE_INFINITY,
1254
+ duration,
1255
+ ease: "linear"
1256
+ },
1257
+ children
1258
+ }
1259
+ );
1260
+ };
1261
+ var Shimmer = memo2(ShimmerComponent);
1262
+
1263
+ // src/components/tool-fallback.tsx
1264
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1265
+ var ToolFallbackImpl = ({
1266
+ toolName,
1267
+ status
1268
+ }) => {
1269
+ if (status?.type !== "running") return null;
1270
+ return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 py-1 text-sm text-muted-foreground", children: [
1271
+ /* @__PURE__ */ jsx11(WrenchIcon, { className: "size-4" }),
1272
+ /* @__PURE__ */ jsx11(Shimmer, { as: "span", duration: 1.8, spread: 2.5, children: `Using tool: ${toolName}` })
1273
+ ] });
1274
+ };
1275
+ var ToolFallback = memo3(
1276
+ ToolFallbackImpl
1277
+ );
1278
+ ToolFallback.displayName = "ToolFallback";
1279
+
1280
+ // src/components/thread.tsx
1281
+ import {
1282
+ ActionBarMorePrimitive,
1283
+ ActionBarPrimitive,
1284
+ AuiIf,
1285
+ ComposerPrimitive as ComposerPrimitive2,
1286
+ ErrorPrimitive,
1287
+ MessagePrimitive as MessagePrimitive2,
1288
+ SuggestionPrimitive,
1289
+ ThreadPrimitive
1290
+ } from "@assistant-ui/react";
1291
+ import {
1292
+ ArrowDownIcon,
1293
+ ArrowUpIcon,
1294
+ CheckIcon as CheckIcon2,
1295
+ CopyIcon as CopyIcon2,
1296
+ DownloadIcon,
1297
+ MoreHorizontalIcon,
1298
+ PencilIcon,
1299
+ RefreshCwIcon,
1300
+ SquareIcon
1301
+ } from "lucide-react";
1302
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1303
+ var Thread = () => {
1304
+ return /* @__PURE__ */ jsx12(
1305
+ ThreadPrimitive.Root,
1306
+ {
1307
+ className: "aui-root aui-thread-root @container flex h-full flex-col bg-background",
1308
+ style: {
1309
+ ["--thread-max-width"]: "44rem"
1310
+ },
1311
+ children: /* @__PURE__ */ jsxs7(
1312
+ ThreadPrimitive.Viewport,
1313
+ {
1314
+ turnAnchor: "bottom",
1315
+ className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
1316
+ children: [
1317
+ /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsx12(ThreadWelcome, {}) }),
1318
+ /* @__PURE__ */ jsx12(
1319
+ ThreadPrimitive.Messages,
1320
+ {
1321
+ components: {
1322
+ UserMessage,
1323
+ EditComposer,
1324
+ AssistantMessage
1325
+ }
1326
+ }
1327
+ ),
1328
+ /* @__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: [
1329
+ /* @__PURE__ */ jsx12(ThreadScrollToBottom, {}),
1330
+ /* @__PURE__ */ jsx12(Composer, {})
1331
+ ] })
1332
+ ]
1333
+ }
1334
+ )
1335
+ }
1336
+ );
1337
+ };
1338
+ var ThreadScrollToBottom = () => {
1339
+ return /* @__PURE__ */ jsx12(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx12(
1340
+ TooltipIconButton,
1341
+ {
1342
+ tooltip: "Scroll to bottom",
1343
+ variant: "outline",
1344
+ 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",
1345
+ children: /* @__PURE__ */ jsx12(ArrowDownIcon, {})
1346
+ }
1347
+ ) });
1348
+ };
1349
+ var ThreadWelcome = () => {
1350
+ return /* @__PURE__ */ jsxs7("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
1351
+ /* @__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: [
1352
+ /* @__PURE__ */ jsxs7("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
1353
+ /* @__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" }),
1354
+ /* @__PURE__ */ jsx12("div", { className: "animate-ai-pulse-ring absolute inset-0" }),
1355
+ /* @__PURE__ */ jsx12(
1356
+ "svg",
1357
+ {
1358
+ xmlns: "http://www.w3.org/2000/svg",
1359
+ viewBox: "0 0 24 24",
1360
+ fill: "none",
1361
+ stroke: "currentColor",
1362
+ strokeWidth: "1.5",
1363
+ strokeLinecap: "round",
1364
+ strokeLinejoin: "round",
1365
+ className: "animate-ai-breathe relative size-7 text-primary/75",
1366
+ 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" })
1367
+ }
1368
+ )
1369
+ ] }),
1370
+ /* @__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: "How can I help you today?" }),
1371
+ /* @__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: "Send a message to start a conversation." })
1372
+ ] }) }),
1373
+ /* @__PURE__ */ jsx12(ThreadSuggestions, {})
1374
+ ] });
1375
+ };
1376
+ var ThreadSuggestions = () => {
1377
+ return /* @__PURE__ */ jsx12("div", { className: "aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4", children: /* @__PURE__ */ jsx12(
1378
+ ThreadPrimitive.Suggestions,
1379
+ {
1380
+ components: {
1381
+ Suggestion: ThreadSuggestionItem
1382
+ }
1383
+ }
1384
+ ) });
1385
+ };
1386
+ var ThreadSuggestionItem = () => {
1387
+ return /* @__PURE__ */ jsx12("div", { className: "aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200", children: /* @__PURE__ */ jsx12(SuggestionPrimitive.Trigger, { send: true, asChild: true, children: /* @__PURE__ */ jsxs7(
1388
+ Button,
1389
+ {
1390
+ variant: "ghost",
1391
+ 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",
1392
+ children: [
1393
+ /* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children: /* @__PURE__ */ jsx12(SuggestionPrimitive.Title, {}) }),
1394
+ /* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children: /* @__PURE__ */ jsx12(SuggestionPrimitive.Description, {}) })
1395
+ ]
1396
+ }
1397
+ ) }) });
1398
+ };
1399
+ var Composer = () => {
1400
+ 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: [
1401
+ /* @__PURE__ */ jsx12(ComposerAttachments, {}),
1402
+ /* @__PURE__ */ jsx12(
1403
+ ComposerPrimitive2.Input,
1404
+ {
1405
+ placeholder: "Send a message...",
1406
+ 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",
1407
+ rows: 1,
1408
+ autoFocus: true,
1409
+ "aria-label": "Message input"
1410
+ }
1411
+ ),
1412
+ /* @__PURE__ */ jsx12(ComposerAction, {})
1413
+ ] }) });
1414
+ };
1415
+ var ComposerAction = () => {
1416
+ return /* @__PURE__ */ jsxs7("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-end", children: [
1417
+ /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx12(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx12(
1418
+ TooltipIconButton,
1419
+ {
1420
+ tooltip: "Send message",
1421
+ side: "bottom",
1422
+ type: "submit",
1423
+ variant: "default",
1424
+ size: "icon",
1425
+ className: "aui-composer-send size-8 rounded-full",
1426
+ "aria-label": "Send message",
1427
+ children: /* @__PURE__ */ jsx12(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
1428
+ }
1429
+ ) }) }),
1430
+ /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx12(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx12(
1431
+ Button,
1432
+ {
1433
+ type: "button",
1434
+ variant: "default",
1435
+ size: "icon",
1436
+ className: "aui-composer-cancel size-8 rounded-full",
1437
+ "aria-label": "Stop generating",
1438
+ children: /* @__PURE__ */ jsx12(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
1439
+ }
1440
+ ) }) })
1441
+ ] });
1442
+ };
1443
+ var MessageError = () => {
1444
+ 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" }) }) });
1445
+ };
1446
+ var AssistantMessage = () => {
1447
+ return /* @__PURE__ */ jsxs7(
1448
+ MessagePrimitive2.Root,
1449
+ {
1450
+ 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",
1451
+ "data-role": "assistant",
1452
+ children: [
1453
+ /* @__PURE__ */ jsxs7("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
1454
+ /* @__PURE__ */ jsx12(
1455
+ MessagePrimitive2.Parts,
1456
+ {
1457
+ components: {
1458
+ Text: MarkdownText,
1459
+ tools: { Fallback: ToolFallback }
1460
+ }
1461
+ }
1462
+ ),
1463
+ /* @__PURE__ */ jsx12(MessageError, {})
1464
+ ] }),
1465
+ /* @__PURE__ */ jsx12("div", { className: "aui-assistant-message-footer mt-1 ml-2 flex", children: /* @__PURE__ */ jsx12(AssistantActionBar, {}) })
1466
+ ]
1467
+ }
1468
+ );
1469
+ };
1470
+ var AssistantActionBar = () => {
1471
+ return /* @__PURE__ */ jsxs7(
1472
+ ActionBarPrimitive.Root,
1473
+ {
1474
+ hideWhenRunning: true,
1475
+ autohide: "not-last",
1476
+ autohideFloat: "single-branch",
1477
+ 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",
1478
+ children: [
1479
+ /* @__PURE__ */ jsx12(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs7(TooltipIconButton, { tooltip: "Copy", children: [
1480
+ /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx12(CheckIcon2, {}) }),
1481
+ /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx12(CopyIcon2, {}) })
1482
+ ] }) }),
1483
+ /* @__PURE__ */ jsx12(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx12(TooltipIconButton, { tooltip: "Refresh", children: /* @__PURE__ */ jsx12(RefreshCwIcon, {}) }) }),
1484
+ /* @__PURE__ */ jsxs7(ActionBarMorePrimitive.Root, { children: [
1485
+ /* @__PURE__ */ jsx12(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx12(
1486
+ TooltipIconButton,
1487
+ {
1488
+ tooltip: "More",
1489
+ className: "data-[state=open]:bg-accent",
1490
+ children: /* @__PURE__ */ jsx12(MoreHorizontalIcon, {})
1491
+ }
1492
+ ) }),
1493
+ /* @__PURE__ */ jsx12(
1494
+ ActionBarMorePrimitive.Content,
1495
+ {
1496
+ side: "bottom",
1497
+ align: "start",
1498
+ 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",
1499
+ 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: [
1500
+ /* @__PURE__ */ jsx12(DownloadIcon, { className: "size-4" }),
1501
+ "Export as Markdown"
1502
+ ] }) })
1503
+ }
1504
+ )
1505
+ ] })
1506
+ ]
1507
+ }
1508
+ );
1509
+ };
1510
+ var UserMessage = () => {
1511
+ return /* @__PURE__ */ jsxs7(
1512
+ MessagePrimitive2.Root,
1513
+ {
1514
+ 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",
1515
+ "data-role": "user",
1516
+ children: [
1517
+ /* @__PURE__ */ jsx12(UserMessageAttachments, {}),
1518
+ /* @__PURE__ */ jsxs7("div", { className: "aui-user-message-content-wrapper relative col-start-2 min-w-0", children: [
1519
+ /* @__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, {}) }),
1520
+ /* @__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, {}) })
1521
+ ] })
1522
+ ]
1523
+ }
1524
+ );
1525
+ };
1526
+ var UserActionBar = () => {
1527
+ return /* @__PURE__ */ jsx12(
1528
+ ActionBarPrimitive.Root,
1529
+ {
1530
+ hideWhenRunning: true,
1531
+ autohide: "not-last",
1532
+ className: "aui-user-action-bar-root flex flex-col items-end",
1533
+ children: /* @__PURE__ */ jsx12(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx12(TooltipIconButton, { tooltip: "Edit", className: "aui-user-action-edit p-4", children: /* @__PURE__ */ jsx12(PencilIcon, {}) }) })
1534
+ }
1535
+ );
1536
+ };
1537
+ var EditComposer = () => {
1538
+ 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: [
1539
+ /* @__PURE__ */ jsx12(
1540
+ ComposerPrimitive2.Input,
1541
+ {
1542
+ className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
1543
+ autoFocus: true
1544
+ }
1545
+ ),
1546
+ /* @__PURE__ */ jsxs7("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
1547
+ /* @__PURE__ */ jsx12(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx12(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
1548
+ /* @__PURE__ */ jsx12(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx12(Button, { size: "sm", children: "Update" }) })
1549
+ ] })
1550
+ ] }) });
1551
+ };
1552
+
1553
+ // src/auth/provider.tsx
1554
+ import {
1555
+ createContext,
1556
+ useCallback as useCallback2,
1557
+ useContext,
1558
+ useEffect as useEffect4,
1559
+ useState as useState5
1560
+ } from "react";
1561
+ import { jsx as jsx13 } from "react/jsx-runtime";
1562
+ var SessionContext = createContext(void 0);
1563
+ var useSession = () => {
1564
+ const context = useContext(SessionContext);
1565
+ if (context === void 0) {
1566
+ throw new Error("useSession must be used within a SessionProvider");
1567
+ }
1568
+ return context;
1569
+ };
1570
+ var SessionProvider = ({
1571
+ children,
1572
+ enabled = true
1573
+ }) => {
1574
+ const [user, setUser] = useState5(null);
1575
+ const [loading, setLoading] = useState5(enabled);
1576
+ useEffect4(() => {
1577
+ if (!enabled) {
1578
+ setLoading(false);
1579
+ return;
1580
+ }
1581
+ let ignore = false;
1582
+ const restoreSession = async () => {
1583
+ try {
1584
+ const u = await fetchCurrentUser();
1585
+ if (ignore) return;
1586
+ if (u) {
1587
+ setUser(u);
1588
+ setLoading(false);
1589
+ return;
1590
+ }
1591
+ if (getRefreshToken()) {
1592
+ const ok = await refreshAccessToken();
1593
+ if (ignore) return;
1594
+ if (ok) {
1595
+ const refreshedUser = await fetchCurrentUser();
1596
+ if (ignore) return;
1597
+ if (refreshedUser) {
1598
+ setUser(refreshedUser);
1599
+ setLoading(false);
1600
+ return;
1601
+ }
1602
+ }
1603
+ }
1604
+ } catch {
1605
+ if (ignore) return;
1606
+ clearTokens();
1607
+ }
1608
+ setLoading(false);
1609
+ };
1610
+ restoreSession();
1611
+ return () => {
1612
+ ignore = true;
1613
+ };
1614
+ }, [enabled]);
1615
+ const logout = useCallback2(() => {
1616
+ clearTokens();
1617
+ setUser(null);
1618
+ const returnTo = encodeURIComponent(
1619
+ window.location.pathname + window.location.search
1620
+ );
1621
+ fetch("/api/auth/logout", { method: "POST" }).finally(
1622
+ () => window.location.href = `/api/auth/login?return_to=${returnTo}`
1623
+ );
1624
+ }, []);
1625
+ return /* @__PURE__ */ jsx13(
1626
+ SessionContext.Provider,
1627
+ {
1628
+ value: {
1629
+ user,
1630
+ loading,
1631
+ isAuthenticated: !!user,
1632
+ logout
1633
+ },
1634
+ children
1635
+ }
1636
+ );
1637
+ };
1638
+
1639
+ // src/auth/guard.tsx
1640
+ import { Loader2 } from "lucide-react";
1641
+ import { jsx as jsx14 } from "react/jsx-runtime";
1642
+ var AuthGuard = ({
1643
+ children,
1644
+ requireAuth = false,
1645
+ enabled = true
1646
+ }) => {
1647
+ const { isAuthenticated, loading } = useSession();
1648
+ if (!enabled) {
1649
+ return children;
1650
+ }
1651
+ if (loading) {
1652
+ return /* @__PURE__ */ jsx14("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx14(Loader2, { className: "w-8 h-8 animate-spin" }) });
1653
+ }
1654
+ if (requireAuth && !isAuthenticated) {
1655
+ const returnTo = encodeURIComponent(
1656
+ window.location.pathname + window.location.search
1657
+ );
1658
+ window.location.href = `/api/auth/login?return_to=${returnTo}`;
1659
+ return null;
1660
+ }
1661
+ return children;
1662
+ };
1663
+ export {
1664
+ AuthGuard,
1665
+ Avatar,
1666
+ AvatarFallback,
1667
+ AvatarImage,
1668
+ Button,
1669
+ ComposerAddAttachment,
1670
+ ComposerAttachments,
1671
+ Dialog,
1672
+ DialogClose,
1673
+ DialogContent,
1674
+ DialogOverlay,
1675
+ DialogPortal,
1676
+ DialogTitle,
1677
+ DialogTrigger,
1678
+ MarkdownText,
1679
+ SessionProvider,
1680
+ Shimmer,
1681
+ syntax_highlighter_default as SyntaxHighlighter,
1682
+ Thread,
1683
+ TimbalRuntimeProvider,
1684
+ ToolFallback,
1685
+ Tooltip,
1686
+ TooltipContent,
1687
+ TooltipIconButton,
1688
+ TooltipProvider,
1689
+ TooltipTrigger,
1690
+ UserMessageAttachments,
1691
+ authFetch,
1692
+ buttonVariants,
1693
+ clearTokens,
1694
+ cn,
1695
+ fetchCurrentUser,
1696
+ getAccessToken,
1697
+ getRefreshToken,
1698
+ refreshAccessToken,
1699
+ useSession
1700
+ };