@epicbagel/saas-kit 1.21.0 → 1.22.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.
@@ -1,1215 +0,0 @@
1
- "use client";
2
- import {
3
- Trace,
4
- TraceLine,
5
- TraceTitle
6
- } from "./chunk-Z45YKVAM.js";
7
- import {
8
- VoiceDictationButton
9
- } from "./chunk-ZGTPNSR7.js";
10
- import {
11
- VoiceSessionControls
12
- } from "./chunk-KF4X37DP.js";
13
- import {
14
- Suggestion,
15
- Suggestions
16
- } from "./chunk-IJG2B3Y2.js";
17
- import {
18
- Marker,
19
- MarkerContent
20
- } from "./chunk-IW4ZIXXG.js";
21
- import {
22
- MessageScroller,
23
- MessageScrollerButton,
24
- MessageScrollerContent,
25
- MessageScrollerItem,
26
- MessageScrollerProvider,
27
- MessageScrollerViewport
28
- } from "./chunk-OUZSIA5G.js";
29
- import {
30
- Message,
31
- MessageAction,
32
- MessageActions,
33
- MessageContent
34
- } from "./chunk-H4XH2PPU.js";
35
- import {
36
- InputGroup,
37
- InputGroupAddon,
38
- InputGroupTextarea
39
- } from "./chunk-QTPPNK6F.js";
40
- import {
41
- FileDropArea,
42
- FileDropzone
43
- } from "./chunk-WHDHUYS6.js";
44
- import {
45
- LazyMarkdown
46
- } from "./chunk-VY5U4VGV.js";
47
- import {
48
- Drawer,
49
- DrawerClose,
50
- DrawerContent,
51
- DrawerHeader,
52
- DrawerTitle,
53
- DrawerTrigger
54
- } from "./chunk-4HWCNRK4.js";
55
- import {
56
- DropdownMenu,
57
- DropdownMenuContent,
58
- DropdownMenuItem,
59
- DropdownMenuTrigger
60
- } from "./chunk-V4LE2HWP.js";
61
- import {
62
- Bubble,
63
- BubbleContent
64
- } from "./chunk-F366BHWH.js";
65
- import {
66
- Attachment,
67
- AttachmentAction,
68
- AttachmentActions,
69
- AttachmentContent,
70
- AttachmentDescription,
71
- AttachmentGroup,
72
- AttachmentMedia,
73
- AttachmentTitle,
74
- AttachmentTrigger
75
- } from "./chunk-CYDICVH2.js";
76
- import {
77
- ActionDock
78
- } from "./chunk-42RTTHND.js";
79
- import {
80
- Button
81
- } from "./chunk-W326GKML.js";
82
- import {
83
- cn
84
- } from "./chunk-G6SRSWXB.js";
85
- import {
86
- useCopyToClipboard
87
- } from "./chunk-ZWZAAGQF.js";
88
- import {
89
- useIsMobile
90
- } from "./chunk-PIU5SSUV.js";
91
- import {
92
- formatRelativeLong
93
- } from "./chunk-CS7DM3NV.js";
94
-
95
- // src/primitives/chat.tsx
96
- import {
97
- forwardRef,
98
- useCallback,
99
- useEffect,
100
- useId,
101
- useImperativeHandle,
102
- useRef,
103
- useState
104
- } from "react";
105
- import {
106
- ArrowDown,
107
- ArrowRight,
108
- ArrowUp,
109
- ArrowUpRight,
110
- Check,
111
- ChevronUp,
112
- Copy,
113
- Download,
114
- FileText,
115
- LockKeyhole,
116
- Paperclip,
117
- Plus,
118
- X
119
- } from "lucide-react";
120
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
121
- function offersActions(message) {
122
- if (message.status) return false;
123
- return message.actions ?? message.role === "assistant";
124
- }
125
- function useArrivingUserTurnId(messages) {
126
- const seen = useRef(null);
127
- seen.current ??= new Set(messages.map((m) => m.id));
128
- useEffect(() => {
129
- seen.current = new Set(messages.map((m) => m.id));
130
- }, [messages]);
131
- for (let i = messages.length - 1; i >= 0; i--) {
132
- const message = messages[i];
133
- if (message.role === "user" && message.status !== "running") {
134
- return seen.current.has(message.id) ? void 0 : message.id;
135
- }
136
- }
137
- return void 0;
138
- }
139
- function timelineMarkers(messages, now) {
140
- let previous = null;
141
- return messages.map((message) => {
142
- if (message.status === "running") return null;
143
- const relativeTime = formatRelativeLong(message.createdAt, now);
144
- const opens = relativeTime && relativeTime !== previous ? relativeTime : null;
145
- previous = relativeTime;
146
- return opens;
147
- });
148
- }
149
- function TimelineMarker({ label }) {
150
- return /* @__PURE__ */ jsx(Marker, { variant: "separator", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx(MarkerContent, { children: label }) });
151
- }
152
- function ChatRunningLine({
153
- label,
154
- align = "start",
155
- workspace
156
- }) {
157
- return /* @__PURE__ */ jsx(Message, { align, children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Trace, { className: workspace ? void 0 : "px-3.5 py-2", children: /* @__PURE__ */ jsx(TraceLine, { status: "running", children: /* @__PURE__ */ jsx(TraceTitle, { children: label }) }) }) }) });
158
- }
159
- var ChatThread = forwardRef(
160
- function ChatThread2({
161
- messages,
162
- pending = false,
163
- pendingLabel = "Thinking\u2026",
164
- scrollButtonLabel = "Scroll to latest message",
165
- uploadingLabel,
166
- maxHeight = "46vh",
167
- appearance = "bubbles",
168
- footer,
169
- markdownComponents,
170
- onOpenAttachment,
171
- attachmentAction = "open",
172
- messageActions,
173
- className
174
- }, ref) {
175
- const markers = timelineMarkers(messages, /* @__PURE__ */ new Date());
176
- const anchorId = useArrivingUserTurnId(messages);
177
- const workspace = appearance === "workspace";
178
- return /* @__PURE__ */ jsx(MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ jsxs(
179
- MessageScroller,
180
- {
181
- ref,
182
- style: { maxHeight },
183
- className: cn("min-h-0", className),
184
- children: [
185
- /* @__PURE__ */ jsx(MessageScrollerViewport, { children: /* @__PURE__ */ jsxs(
186
- MessageScrollerContent,
187
- {
188
- className: cn(
189
- workspace ? "mx-auto w-full max-w-3xl gap-10 px-4 py-6 md:px-6 md:py-8" : "gap-2.5 px-px py-px"
190
- ),
191
- children: [
192
- messages.map((m, index) => {
193
- const isUser = m.role === "user";
194
- if (m.status === "running") {
195
- return /* @__PURE__ */ jsx(MessageScrollerItem, { children: /* @__PURE__ */ jsx(
196
- ChatRunningLine,
197
- {
198
- label: m.text || pendingLabel,
199
- align: isUser ? "end" : "start",
200
- workspace
201
- }
202
- ) }, m.id);
203
- }
204
- const isFullWidth = m.width === "full";
205
- const marker = markers[index];
206
- const actions = messageActions && offersActions(m) ? messageActions(m) : null;
207
- return /* @__PURE__ */ jsxs(
208
- MessageScrollerItem,
209
- {
210
- messageId: m.id,
211
- scrollAnchor: m.id === anchorId,
212
- className: cn("flex flex-col", workspace ? "gap-5" : "gap-3"),
213
- children: [
214
- marker ? /* @__PURE__ */ jsx(TimelineMarker, { label: marker }) : null,
215
- /* @__PURE__ */ jsx(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
216
- (m.content || m.text) && /* @__PURE__ */ jsx(
217
- Bubble,
218
- {
219
- variant: workspace ? isUser ? "muted" : "ghost" : isUser ? "default" : "muted",
220
- align: isUser ? "end" : "start",
221
- className: isFullWidth ? "w-full" : void 0,
222
- children: /* @__PURE__ */ jsx(
223
- BubbleContent,
224
- {
225
- className: cn(
226
- isFullWidth && "w-full",
227
- workspace ? isUser ? "rounded-2xl px-4 py-2.5 text-[15px] leading-7" : "px-0 py-0 text-[15px] leading-7" : "px-3.5 py-2 text-sm leading-relaxed"
228
- ),
229
- children: m.content ?? (m.text ? /* @__PURE__ */ jsx(LazyMarkdown, { size: workspace ? "base" : "sm", components: markdownComponents, children: m.text }) : null)
230
- }
231
- )
232
- }
233
- ),
234
- m.attachments && m.attachments.length > 0 && /* @__PURE__ */ jsx(
235
- ChatAttachmentList,
236
- {
237
- attachments: m.attachments,
238
- onOpen: onOpenAttachment,
239
- openAction: attachmentAction,
240
- uploadingLabel,
241
- className: "px-0 pt-0"
242
- }
243
- ),
244
- actions != null && actions !== false ? (
245
- // Unboxed workspace turns pull the row out by the
246
- // button's inset, so the first icon lines up with
247
- // the text above it.
248
- /* @__PURE__ */ jsx(
249
- MessageActions,
250
- {
251
- className: cn(
252
- workspace && (isUser ? "-me-2 pointer-coarse:-me-3.5" : "-ms-2 pointer-coarse:-ms-3.5")
253
- ),
254
- children: actions
255
- }
256
- )
257
- ) : null
258
- ] }) })
259
- ]
260
- },
261
- m.id
262
- );
263
- }),
264
- pending && /* @__PURE__ */ jsx(MessageScrollerItem, { children: /* @__PURE__ */ jsx(ChatRunningLine, { label: pendingLabel, workspace }) }),
265
- footer && // Always painted: content-visibility:auto would contain the
266
- // footer's paint and layout, clipping a ring or shadow at its
267
- // edge and re-parenting anything fixed inside it.
268
- /* @__PURE__ */ jsx(MessageScrollerItem, { className: "[content-visibility:visible]", children: /* @__PURE__ */ jsx(Message, { align: "start", children: /* @__PURE__ */ jsx(MessageContent, { children: footer }) }) })
269
- ]
270
- }
271
- ) }),
272
- /* @__PURE__ */ jsxs(MessageScrollerButton, { variant: "outline", className: "rounded-full shadow-sm", children: [
273
- /* @__PURE__ */ jsx(ArrowDown, { className: "h-4 w-4" }),
274
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: scrollButtonLabel })
275
- ] })
276
- ]
277
- }
278
- ) });
279
- }
280
- );
281
- function ChatCopyAction({
282
- message,
283
- label = "Copy",
284
- copiedLabel = "Copied",
285
- onCopy,
286
- resetAfter = 2e3
287
- }) {
288
- const { copied, copy } = useCopyToClipboard({ resetAfter });
289
- const text = message.text;
290
- if (!text) return null;
291
- return /* @__PURE__ */ jsx(
292
- MessageAction,
293
- {
294
- tooltip: copied ? copiedLabel : label,
295
- "data-copied": copied || void 0,
296
- onClick: async () => {
297
- const ok = await copy(text);
298
- onCopy?.(ok);
299
- },
300
- children: copied ? /* @__PURE__ */ jsx(Check, {}) : /* @__PURE__ */ jsx(Copy, {})
301
- }
302
- );
303
- }
304
- function formatFileSize(size) {
305
- if (size === void 0) return "File";
306
- if (size < 1024) return `${size} B`;
307
- if (size < 1048576) return `${Math.round(size / 1024)} KB`;
308
- return `${(size / 1048576).toFixed(1)} MB`;
309
- }
310
- var ATTACHMENT_ACTIONS = {
311
- open: { verb: "Open", icon: ArrowUpRight },
312
- download: { verb: "Download", icon: Download }
313
- };
314
- var COMPOSER_SIZES = {
315
- sm: { minHeight: "min-h-12", rows: 2 },
316
- md: { minHeight: "min-h-[4.5rem]", rows: 2 },
317
- lg: { minHeight: "min-h-32", rows: 3 }
318
- };
319
- function ChatAttachmentList({
320
- attachments,
321
- onRemove,
322
- onOpen,
323
- openAction = "open",
324
- uploadingLabel = "Uploading\u2026",
325
- className
326
- }) {
327
- const { verb, icon: ActionIcon } = ATTACHMENT_ACTIONS[openAction];
328
- return /* @__PURE__ */ jsx(AttachmentGroup, { className: cn("gap-2 px-3 pt-3", className), children: attachments.map((attachment) => {
329
- const state = attachment.state ?? "done";
330
- const opens = Boolean(onOpen) && state === "done";
331
- return /* @__PURE__ */ jsxs(
332
- Attachment,
333
- {
334
- state,
335
- size: "sm",
336
- className: "min-w-[12rem] rounded-xl p-2",
337
- children: [
338
- opens && /* @__PURE__ */ jsx(
339
- AttachmentTrigger,
340
- {
341
- "aria-label": `${verb} ${attachment.name}`,
342
- title: `${verb} ${attachment.name}`,
343
- onClick: () => onOpen?.(attachment),
344
- className: "rounded-xl focus-visible:ring-2 focus-visible:ring-ring/50"
345
- }
346
- ),
347
- /* @__PURE__ */ jsx(AttachmentMedia, { variant: "icon", className: "h-9 w-9 rounded-lg bg-muted", children: /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }) }),
348
- /* @__PURE__ */ jsxs(AttachmentContent, { className: "px-2", children: [
349
- /* @__PURE__ */ jsx(AttachmentTitle, { className: "text-xs font-medium", children: attachment.name }),
350
- /* @__PURE__ */ jsx(AttachmentDescription, { className: "text-[11px]", children: state === "uploading" ? uploadingLabel : opens ? `${formatFileSize(attachment.size)} \xB7 ${verb}` : formatFileSize(attachment.size) })
351
- ] }),
352
- onRemove ? /* @__PURE__ */ jsx(AttachmentActions, { children: /* @__PURE__ */ jsx(
353
- AttachmentAction,
354
- {
355
- type: "button",
356
- "aria-label": `Remove ${attachment.name}`,
357
- onClick: () => onRemove(attachment),
358
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
359
- }
360
- ) }) : opens ? (
361
- // The trigger covers the whole file; the icon only says what it does.
362
- /* @__PURE__ */ jsx(AttachmentActions, { "aria-hidden": true, className: "pointer-events-none pr-1 text-muted-foreground", children: /* @__PURE__ */ jsx(ActionIcon, { className: "h-4 w-4" }) })
363
- ) : null
364
- ]
365
- },
366
- attachment.id
367
- );
368
- }) });
369
- }
370
- function ChatInput({
371
- value,
372
- onChange,
373
- onSend,
374
- pending = false,
375
- disabled = false,
376
- placeholder = "Type your message\u2026",
377
- size = "md",
378
- uploadingLabel,
379
- voice = true,
380
- voiceSession,
381
- voiceTitle,
382
- voiceLang,
383
- submitLabel,
384
- autoFocus = false,
385
- className,
386
- inputRef,
387
- attachments = [],
388
- onFilesSelected,
389
- onRemoveAttachment,
390
- accept,
391
- multiple = true,
392
- addFilesLabel = "Add files",
393
- appearance = "default",
394
- compactOnBlur = false,
395
- header,
396
- helperText,
397
- privacyNote
398
- }) {
399
- const internalRef = useRef(null);
400
- const ref = inputRef ?? internalRef;
401
- const fileInputRef = useRef(null);
402
- const [editing, setEditing] = useState(false);
403
- const focusVersion = useRef(0);
404
- const isDisabled = pending || disabled;
405
- const hasText = Boolean(value.trim());
406
- const attachmentsReady = attachments.length > 0 && attachments.every((item) => item.state !== "uploading");
407
- const canSubmit = !isDisabled && (hasText || attachmentsReady);
408
- const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
409
- const submit = () => {
410
- const trimmed = value.trim();
411
- if (!trimmed && !attachmentsReady || isDisabled) return;
412
- onSend(trimmed, attachments);
413
- };
414
- const spacious = appearance === "spacious";
415
- const composerSize = COMPOSER_SIZES[size];
416
- const compact = compactOnBlur && !editing && value.length === 0 && attachments.length === 0;
417
- return /* @__PURE__ */ jsxs("div", { "data-slot": "chat-input-container", className: "w-full", children: [
418
- /* @__PURE__ */ jsxs(
419
- "form",
420
- {
421
- "data-slot": "chat-input",
422
- "data-appearance": appearance,
423
- "data-compact": compact || void 0,
424
- className: "w-full",
425
- onFocusCapture: (event) => {
426
- focusVersion.current += 1;
427
- if (event.nativeEvent.target === ref.current) setEditing(true);
428
- },
429
- onBlurCapture: (event) => {
430
- if (event.currentTarget.contains(event.relatedTarget)) return;
431
- const form = event.currentTarget;
432
- const blurredAt = ++focusVersion.current;
433
- queueMicrotask(() => {
434
- if (form.isConnected && focusVersion.current === blurredAt) setEditing(false);
435
- });
436
- },
437
- onSubmit: (event) => {
438
- if (event.target !== event.currentTarget) return;
439
- event.preventDefault();
440
- submit();
441
- },
442
- children: [
443
- onFilesSelected && /* @__PURE__ */ jsx(
444
- "input",
445
- {
446
- ref: fileInputRef,
447
- type: "file",
448
- accept,
449
- multiple,
450
- hidden: true,
451
- onChange: (event) => {
452
- const files = Array.from(event.currentTarget.files ?? []);
453
- if (files.length > 0) void onFilesSelected(files);
454
- event.currentTarget.value = "";
455
- }
456
- }
457
- ),
458
- /* @__PURE__ */ jsxs(
459
- InputGroup,
460
- {
461
- className: cn(
462
- // Opaque in both modes: the input group's own dark:bg-input/30
463
- // would let the thread show through the composer.
464
- "overflow-hidden bg-background dark:bg-background",
465
- spacious ? "rounded-3xl" : "rounded-lg",
466
- className
467
- ),
468
- children: [
469
- header && /* @__PURE__ */ jsx(
470
- InputGroupAddon,
471
- {
472
- align: "block-start",
473
- className: "w-full cursor-default border-b border-border bg-muted/50 p-0 [.border-b]:pb-0",
474
- onClick: () => {
475
- },
476
- children: header
477
- }
478
- ),
479
- liveVoiceActive && /* @__PURE__ */ jsx(InputGroupAddon, { align: "block-start", className: "w-full cursor-default border-b border-border p-0 [.border-b]:pb-0", children: /* @__PURE__ */ jsx(VoiceSessionControls, { ...voiceSession }) }),
480
- attachments.length > 0 && /* @__PURE__ */ jsx(
481
- ChatAttachmentList,
482
- {
483
- attachments,
484
- onRemove: onRemoveAttachment,
485
- uploadingLabel,
486
- className: "w-full"
487
- }
488
- ),
489
- /* @__PURE__ */ jsx(
490
- InputGroupTextarea,
491
- {
492
- ref,
493
- value,
494
- onChange: (e) => onChange(e.target.value),
495
- onKeyDown: (e) => {
496
- if (e.key !== "Enter" || e.shiftKey) return;
497
- if (e.nativeEvent.isComposing || e.keyCode === 229) return;
498
- e.preventDefault();
499
- submit();
500
- },
501
- placeholder,
502
- rows: compact ? 1 : composerSize.rows,
503
- disabled: isDisabled,
504
- autoFocus,
505
- className: cn(
506
- compact ? "min-h-[44px] max-h-24 min-w-0" : composerSize.minHeight,
507
- // Grows with the text, then scrolls rather than pushing the thread away.
508
- "leading-relaxed disabled:opacity-70 placeholder:text-sm",
509
- !compact && "max-h-48",
510
- compact ? "px-4 py-2" : spacious ? "px-6 pb-2 pt-6" : "px-4 py-3.5"
511
- )
512
- }
513
- ),
514
- /* @__PURE__ */ jsxs(
515
- InputGroupAddon,
516
- {
517
- align: "block-end",
518
- "data-slot": "chat-input-actions",
519
- onClick: (event) => {
520
- if (!event.currentTarget.contains(event.target)) return;
521
- if (!event.target.closest("button")) ref.current?.focus();
522
- },
523
- className: cn(
524
- compact ? "gap-1 px-2 pb-2 pt-0" : "gap-2",
525
- !compact && (spacious ? "px-5 pb-5 pt-1" : "border-t border-border px-3 py-2.5")
526
- ),
527
- children: [
528
- onFilesSelected && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
529
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
530
- Button,
531
- {
532
- type: "button",
533
- size: spacious && !compact ? "icon-lg" : "icon",
534
- variant: "ghost",
535
- "aria-label": "Add to conversation",
536
- className: compactOnBlur ? "min-h-[44px] min-w-[44px] shrink-0" : void 0,
537
- children: /* @__PURE__ */ jsx(Plus, { className: cn(spacious && "h-5 w-5") })
538
- }
539
- ) }),
540
- /* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => fileInputRef.current?.click(), children: [
541
- /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" }),
542
- addFilesLabel
543
- ] }) })
544
- ] }),
545
- helperText ? /* @__PURE__ */ jsx(
546
- "span",
547
- {
548
- "data-slot": "chat-input-helper",
549
- className: cn("min-w-0 flex-1 truncate px-1 text-center text-xs font-normal text-muted-foreground sm:px-2 sm:text-sm", compact && "hidden"),
550
- children: helperText
551
- }
552
- ) : null,
553
- (!helperText || compact) && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
554
- voice && /* @__PURE__ */ jsx(
555
- VoiceDictationButton,
556
- {
557
- size: "icon",
558
- title: voiceTitle,
559
- lang: voiceLang,
560
- disabled: Boolean(isDisabled || liveVoiceActive),
561
- onCommit: (text) => onSend(text),
562
- className: cn(
563
- spacious && "border-transparent bg-transparent shadow-none hover:bg-muted",
564
- spacious && !compact && "h-12 w-12",
565
- compactOnBlur && "min-h-[44px] min-w-[44px] shrink-0"
566
- )
567
- }
568
- ),
569
- voiceSession && !liveVoiceActive && !hasText && attachments.length === 0 ? /* @__PURE__ */ jsx(VoiceSessionControls, { ...voiceSession, disabled: voiceSession.disabled || isDisabled }) : submitLabel ? /* @__PURE__ */ jsxs(
570
- Button,
571
- {
572
- type: "submit",
573
- disabled: !canSubmit,
574
- "data-chat-action": "submit",
575
- className: cn(spacious && "rounded-xl", compactOnBlur && "min-h-[44px] min-w-[44px] shrink-0"),
576
- children: [
577
- submitLabel,
578
- /* @__PURE__ */ jsx(ArrowRight, { className: "ml-2 h-4 w-4" })
579
- ]
580
- }
581
- ) : /* @__PURE__ */ jsx(
582
- Button,
583
- {
584
- type: "submit",
585
- size: !voiceSession && spacious && !compact ? "icon-lg" : "icon",
586
- shape: voiceSession ? "circle" : void 0,
587
- disabled: !canSubmit,
588
- "aria-label": "Send",
589
- "data-chat-action": "submit",
590
- className: cn(
591
- spacious && !voiceSession && "rounded-xl",
592
- (voiceSession || compactOnBlur) && "min-h-[44px] min-w-[44px] shrink-0"
593
- ),
594
- children: /* @__PURE__ */ jsx(ArrowUp, { className: cn(spacious && "h-5 w-5") })
595
- }
596
- )
597
- ]
598
- }
599
- )
600
- ]
601
- }
602
- )
603
- ]
604
- }
605
- ),
606
- privacyNote ? /* @__PURE__ */ jsxs(
607
- "div",
608
- {
609
- "data-slot": "chat-input-privacy",
610
- className: "mt-3 flex items-center justify-center gap-2 px-4 text-center text-xs text-muted-foreground sm:text-sm",
611
- children: [
612
- /* @__PURE__ */ jsx(LockKeyhole, { "aria-hidden": true, className: "h-4 w-4 shrink-0" }),
613
- /* @__PURE__ */ jsx("span", { children: privacyNote })
614
- ]
615
- }
616
- ) : null
617
- ] });
618
- }
619
- function VoiceHero({
620
- onSubmit,
621
- voice,
622
- voiceSession,
623
- voiceTitle,
624
- voiceLang,
625
- onSwitchToText,
626
- label,
627
- switchLabel
628
- }) {
629
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-5 py-6", children: [
630
- voiceSession ? /* @__PURE__ */ jsxs(Fragment, { children: [
631
- /* @__PURE__ */ jsx(VoiceSessionControls, { ...voiceSession, size: "hero" }),
632
- voice && /* @__PURE__ */ jsx(
633
- VoiceDictationButton,
634
- {
635
- size: "icon",
636
- title: voiceTitle,
637
- lang: voiceLang,
638
- onCommit: onSubmit,
639
- disabled: Boolean(voiceSession.disabled || voiceSession.state !== "disconnected" && voiceSession.state !== "error")
640
- }
641
- )
642
- ] }) : /* @__PURE__ */ jsx(
643
- VoiceDictationButton,
644
- {
645
- size: "lg",
646
- title: voiceTitle,
647
- lang: voiceLang,
648
- onCommit: onSubmit,
649
- className: "h-24 w-24 rounded-full p-0 shadow-lg transition-transform hover:scale-105 active:scale-95 [&_svg]:size-7"
650
- }
651
- ),
652
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1.5", children: [
653
- /* @__PURE__ */ jsx("span", { className: "text-base font-medium text-foreground", children: label }),
654
- /* @__PURE__ */ jsx(
655
- "button",
656
- {
657
- type: "button",
658
- onClick: onSwitchToText,
659
- className: "text-xs text-muted-foreground hover:text-foreground transition-colors underline underline-offset-4 decoration-muted-foreground/40",
660
- children: switchLabel
661
- }
662
- )
663
- ] })
664
- ] });
665
- }
666
- var EXAMPLE_PROMPT_LAYOUTS = {
667
- chips: "wrap",
668
- row: "row",
669
- cards: "grid"
670
- };
671
- function ChatSuggestions({
672
- slot,
673
- items,
674
- onSelect,
675
- label,
676
- showLabel = true,
677
- variant = "chips",
678
- disabled = false,
679
- className
680
- }) {
681
- const labelId = useId();
682
- if (items.length === 0) return null;
683
- const cards = variant === "cards";
684
- return /* @__PURE__ */ jsxs("div", { "data-slot": slot, "data-variant": variant, className, children: [
685
- showLabel && /* @__PURE__ */ jsx("p", { id: labelId, "data-slot": `${slot}-label`, className: "mb-2 text-left text-xs text-muted-foreground", children: label }),
686
- /* @__PURE__ */ jsx(
687
- Suggestions,
688
- {
689
- role: "group",
690
- "aria-labelledby": showLabel ? labelId : void 0,
691
- "aria-label": showLabel ? void 0 : label,
692
- layout: EXAMPLE_PROMPT_LAYOUTS[variant],
693
- children: items.map((item) => /* @__PURE__ */ jsx(
694
- Suggestion,
695
- {
696
- suggestion: item,
697
- onClick: onSelect,
698
- disabled,
699
- className: cards ? "h-auto min-h-12 px-3 py-2.5 text-xs" : void 0
700
- },
701
- item
702
- ))
703
- }
704
- )
705
- ] });
706
- }
707
- function MobileReplySuggestions({
708
- items,
709
- onSelect,
710
- label,
711
- moreLabel,
712
- closeLabel,
713
- disabled
714
- }) {
715
- const [open, setOpen] = useState(false);
716
- const triggerRef = useRef(null);
717
- const closeRef = useRef(null);
718
- useEffect(() => {
719
- if (disabled) setOpen(false);
720
- }, [disabled]);
721
- const select = (item) => {
722
- if (disabled) return;
723
- setOpen(false);
724
- onSelect(item);
725
- };
726
- const primary = items[0];
727
- if (primary === void 0) return null;
728
- return /* @__PURE__ */ jsxs(Drawer, { open, onOpenChange: setOpen, shouldScaleBackground: false, repositionInputs: false, children: [
729
- /* @__PURE__ */ jsxs(
730
- "div",
731
- {
732
- "data-slot": "chat-replies",
733
- "data-variant": "compact",
734
- role: "group",
735
- "aria-label": label,
736
- className: "flex w-full min-w-0 items-center text-foreground",
737
- children: [
738
- /* @__PURE__ */ jsxs(
739
- Button,
740
- {
741
- type: "button",
742
- variant: "ghost",
743
- size: "bare",
744
- disabled,
745
- className: "h-[44px] min-h-[44px] min-w-0 flex-1 justify-start gap-2 px-3 text-left focus-visible:-ring-offset-2",
746
- onClick: () => select(primary),
747
- children: [
748
- /* @__PURE__ */ jsx(ArrowUpRight, { "aria-hidden": "true" }),
749
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: primary })
750
- ]
751
- }
752
- ),
753
- items.length > 1 && /* @__PURE__ */ jsx(DrawerTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
754
- Button,
755
- {
756
- ref: triggerRef,
757
- type: "button",
758
- variant: "ghost",
759
- size: "bare",
760
- disabled,
761
- className: "h-[44px] min-h-[44px] shrink-0 gap-1 px-3 text-muted-foreground focus-visible:-ring-offset-2",
762
- children: [
763
- moreLabel(items.length - 1),
764
- /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" })
765
- ]
766
- }
767
- ) })
768
- ]
769
- }
770
- ),
771
- /* @__PURE__ */ jsxs(
772
- DrawerContent,
773
- {
774
- "aria-describedby": void 0,
775
- className: "max-h-[min(85dvh,40rem)] overflow-hidden pb-[env(safe-area-inset-bottom)]",
776
- onOpenAutoFocus: (event) => {
777
- event.preventDefault();
778
- closeRef.current?.focus({ preventScroll: true });
779
- },
780
- onCloseAutoFocus: (event) => {
781
- event.preventDefault();
782
- if (triggerRef.current?.isConnected && !triggerRef.current.disabled) {
783
- triggerRef.current.focus({ preventScroll: true });
784
- }
785
- },
786
- children: [
787
- /* @__PURE__ */ jsxs(DrawerHeader, { align: "start", className: "flex shrink-0 items-center justify-between gap-3 border-b px-4 py-2", children: [
788
- /* @__PURE__ */ jsx(DrawerTitle, { className: "min-w-0 break-words leading-snug", children: label }),
789
- /* @__PURE__ */ jsx(DrawerClose, { asChild: true, children: /* @__PURE__ */ jsx(
790
- Button,
791
- {
792
- ref: closeRef,
793
- type: "button",
794
- variant: "ghost",
795
- size: "icon",
796
- "aria-label": closeLabel,
797
- className: "h-[44px] w-[44px] shrink-0",
798
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
799
- }
800
- ) })
801
- ] }),
802
- /* @__PURE__ */ jsx("div", { className: "min-h-0 overflow-y-auto overscroll-contain", role: "group", "aria-label": label, children: items.map((item) => /* @__PURE__ */ jsxs(
803
- Button,
804
- {
805
- type: "button",
806
- variant: "ghost",
807
- size: "bare",
808
- disabled,
809
- "data-slot": "chat-reply-option",
810
- className: "min-h-[56px] w-full justify-between gap-4 rounded-none border-b px-4 py-4 text-left text-base font-normal whitespace-normal last:border-b-0 focus-visible:-ring-offset-2",
811
- onClick: () => select(item),
812
- children: [
813
- /* @__PURE__ */ jsx("span", { className: "min-w-0 break-words", children: item }),
814
- /* @__PURE__ */ jsx(ArrowUpRight, { "aria-hidden": "true" })
815
- ]
816
- },
817
- item
818
- )) })
819
- ]
820
- }
821
- )
822
- ] });
823
- }
824
- function ExamplePromptsLoading({ label, className }) {
825
- const list = Array.isArray(label) ? label : [label];
826
- const [idx, setIdx] = useState(0);
827
- useEffect(() => {
828
- if (list.length <= 1) return;
829
- const id = setInterval(() => setIdx((i) => (i + 1) % list.length), 1800);
830
- return () => clearInterval(id);
831
- }, [list.length]);
832
- return /* @__PURE__ */ jsxs("div", { "data-slot": "chat-examples", "data-state": "loading", "aria-busy": "true", className: cn("mt-3", className), children: [
833
- /* @__PURE__ */ jsx(Trace, { className: "mb-2", children: /* @__PURE__ */ jsx(TraceLine, { status: "running", className: "text-xs", children: /* @__PURE__ */ jsx(TraceTitle, { className: "animate-in fade-in duration-500", children: list[idx] }, idx) }) }),
834
- /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "flex gap-2 overflow-hidden", children: [36, 44, 28, 40].map((w, i) => /* @__PURE__ */ jsx(
835
- "span",
836
- {
837
- className: "h-11 shrink-0 rounded-md bg-muted animate-pulse",
838
- style: {
839
- width: `${w * 4}px`,
840
- animationDelay: `${i * 120}ms`
841
- }
842
- },
843
- i
844
- )) })
845
- ] });
846
- }
847
- function ChatError({ children, className }) {
848
- return /* @__PURE__ */ jsx("p", { "data-slot": "chat-error", role: "alert", className: cn("mt-2 text-xs text-destructive", className), children });
849
- }
850
- var Chat = forwardRef(function Chat2({
851
- messages,
852
- onSend,
853
- onSendError,
854
- pending = false,
855
- pendingLabel,
856
- emptyPendingContent,
857
- error = null,
858
- emptyTitle,
859
- emptySubtitle,
860
- examplePrompts = [],
861
- examplePromptsLabel = "Try",
862
- examplePromptsVariant = "chips",
863
- examplePromptsLoading = false,
864
- examplePromptsLoadingLabel = "Loading suggestions\u2026",
865
- replySuggestions = [],
866
- replySuggestionsLabel = "Suggested replies",
867
- mobileCompactSuggestions = false,
868
- replySuggestionsMoreLabel = (count) => `+${count} more`,
869
- replySuggestionsCloseLabel = "Close suggested replies",
870
- compactEmptyState = false,
871
- defaultMode,
872
- placeholder = "Type your message\u2026",
873
- activePlaceholder = "Reply\u2026",
874
- submitLabel,
875
- attachments = [],
876
- onFilesSelected,
877
- onRemoveAttachment,
878
- attachmentAccept,
879
- addFilesLabel,
880
- uploadingLabel,
881
- composerAppearance = "default",
882
- mobileCompactComposer = false,
883
- composerHelperText,
884
- composerPrivacyNote,
885
- emptyFileDropzone = false,
886
- emptyFileDropzoneLabel = "Drop files here, or choose them",
887
- emptyFileDropzoneDescription,
888
- fileDrop = "conversation",
889
- fileDropLabel,
890
- fileDropDescription,
891
- voice = true,
892
- voiceSession,
893
- voiceTitle,
894
- activeVoiceTitle,
895
- voiceLang,
896
- voiceHeroLabel = "Tap to speak",
897
- voiceHeroSwitchLabel = "or type instead",
898
- threadMaxHeight = "46vh",
899
- scrollButtonLabel,
900
- emptyAlign = "start",
901
- emptyIcon,
902
- emptyFooter,
903
- layout = "embedded",
904
- threadFooter,
905
- markdownComponents,
906
- onOpenAttachment,
907
- attachmentAction,
908
- messageActions,
909
- className
910
- }, ref) {
911
- const isMobile = useIsMobile();
912
- const [input, setInput] = useState("");
913
- const inputRef = useRef(null);
914
- const conversationStarted = messages.length > 0;
915
- const [userMode, setUserMode] = useState(null);
916
- const mode = !voice && !voiceSession ? "text" : userMode ?? defaultMode ?? (isMobile ? "voice" : "text");
917
- const reportSendError = useCallback(
918
- (cause) => {
919
- if (onSendError) onSendError(cause);
920
- else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
921
- else console.error(cause);
922
- },
923
- [onSendError]
924
- );
925
- const send = useCallback(
926
- async (text, sentAttachments = attachments) => {
927
- const trimmed = text.trim();
928
- if (!trimmed && sentAttachments.length === 0 || pending) return;
929
- setInput("");
930
- try {
931
- await onSend(trimmed, sentAttachments);
932
- } catch (cause) {
933
- if (trimmed) {
934
- setInput((current) => current ? current : trimmed);
935
- if (mode === "voice") setUserMode("text");
936
- }
937
- reportSendError(cause);
938
- }
939
- window.setTimeout(() => inputRef.current?.focus(), 0);
940
- },
941
- [attachments, mode, onSend, pending, reportSendError]
942
- );
943
- const sendSuggestion = useCallback(
944
- async (text, focusInput = true) => {
945
- const trimmed = text.trim();
946
- if (!trimmed || pending) return;
947
- try {
948
- await onSend(trimmed, []);
949
- } catch (cause) {
950
- reportSendError(cause);
951
- }
952
- if (focusInput) window.setTimeout(() => inputRef.current?.focus(), 0);
953
- },
954
- [onSend, pending, reportSendError]
955
- );
956
- useImperativeHandle(
957
- ref,
958
- () => ({
959
- focusInput: () => inputRef.current?.focus(),
960
- clearInput: () => setInput("")
961
- }),
962
- []
963
- );
964
- const dropFiles = useCallback(
965
- (files) => {
966
- if (!conversationStarted && mode === "voice") setUserMode("text");
967
- return onFilesSelected?.(files);
968
- },
969
- [conversationStarted, mode, onFilesSelected]
970
- );
971
- const application = layout === "application";
972
- const workspace = layout === "workspace";
973
- const compactSuggestions = mobileCompactSuggestions && isMobile;
974
- const showEmptyFileDropzone = emptyFileDropzone && Boolean(onFilesSelected) && attachments.length === 0;
975
- const liveVoiceActive = voiceSession && voiceSession.state !== "disconnected" && voiceSession.state !== "error";
976
- const showEmptyPending = !conversationStarted && pending && Boolean(emptyPendingContent) && !liveVoiceActive;
977
- return /* @__PURE__ */ jsx(
978
- FileDropArea,
979
- {
980
- "data-slot": "chat",
981
- "data-layout": layout,
982
- onFiles: onFilesSelected ? dropFiles : void 0,
983
- disabled: fileDrop === false || showEmptyPending,
984
- scope: fileDrop === "page" ? "page" : "area",
985
- label: fileDropLabel,
986
- description: fileDropDescription,
987
- className: cn(
988
- "w-full",
989
- application && "flex min-h-[calc(100svh-4rem)] flex-1 flex-col md:block md:min-h-0",
990
- workspace && "flex h-full min-h-0 flex-1 flex-col overflow-hidden",
991
- className
992
- ),
993
- children: showEmptyPending ? /* @__PURE__ */ jsx(
994
- "div",
995
- {
996
- "data-slot": "chat-empty-pending",
997
- className: "flex min-h-0 flex-1 items-center justify-center px-4 py-8",
998
- children: emptyPendingContent
999
- }
1000
- ) : !conversationStarted ? /* @__PURE__ */ jsxs(
1001
- "div",
1002
- {
1003
- "data-slot": "chat-empty",
1004
- className: cn(
1005
- emptyAlign === "center" && "mx-auto flex w-full max-w-3xl flex-col items-center text-center",
1006
- application && "min-h-0 max-w-none flex-1 md:max-w-3xl",
1007
- workspace && "min-h-0 max-w-none flex-1 overflow-y-auto overscroll-contain px-4 pb-20"
1008
- ),
1009
- children: [
1010
- /* @__PURE__ */ jsxs(
1011
- "div",
1012
- {
1013
- "data-slot": "chat-empty-intro",
1014
- className: cn(
1015
- application && "flex flex-1 flex-col items-center justify-center px-6 py-8 md:contents",
1016
- workspace && "mt-auto w-full flex-none px-0 py-0"
1017
- ),
1018
- children: [
1019
- !compactEmptyState && emptyIcon && /* @__PURE__ */ jsx("div", { "data-slot": "chat-empty-icon", className: "mb-4", children: emptyIcon }),
1020
- emptyTitle && !compactEmptyState && /* @__PURE__ */ jsx("h2", { "data-slot": "chat-empty-title", className: "text-3xl md:text-4xl font-medium tracking-tight leading-[1.1] mt-2 mb-3", children: emptyTitle }),
1021
- emptySubtitle && !compactEmptyState && /* @__PURE__ */ jsx(
1022
- "p",
1023
- {
1024
- "data-slot": "chat-empty-subtitle",
1025
- className: cn(
1026
- "text-base text-muted-foreground leading-relaxed mb-6 max-w-md",
1027
- emptyAlign === "center" && "mx-auto"
1028
- ),
1029
- children: emptySubtitle
1030
- }
1031
- )
1032
- ]
1033
- }
1034
- ),
1035
- /* @__PURE__ */ jsxs(
1036
- ActionDock,
1037
- {
1038
- shrink: false,
1039
- className: cn(
1040
- application && "px-4 py-3 md:border-0 md:bg-transparent md:p-0 md:backdrop-blur-none md:supports-backdrop-filter:bg-transparent",
1041
- !application && "border-0 bg-transparent p-0 backdrop-blur-none",
1042
- workspace && "mb-auto mt-2 w-full max-w-3xl border-0 bg-transparent p-0 backdrop-blur-none supports-[backdrop-filter]:bg-transparent"
1043
- ),
1044
- children: [
1045
- showEmptyFileDropzone && onFilesSelected && /* @__PURE__ */ jsx(
1046
- FileDropzone,
1047
- {
1048
- multiple: true,
1049
- onFiles: onFilesSelected,
1050
- accept: attachmentAccept,
1051
- label: emptyFileDropzoneLabel,
1052
- description: emptyFileDropzoneDescription,
1053
- icon: /* @__PURE__ */ jsx(FileText, {}),
1054
- className: "mb-4 rounded-lg bg-muted/20 py-8"
1055
- }
1056
- ),
1057
- mode === "voice" ? /* @__PURE__ */ jsx(
1058
- VoiceHero,
1059
- {
1060
- onSubmit: (text) => void send(text),
1061
- voice,
1062
- voiceSession: voiceSession ? { ...voiceSession, disabled: voiceSession.disabled || pending } : void 0,
1063
- voiceTitle,
1064
- voiceLang,
1065
- label: voiceHeroLabel,
1066
- switchLabel: voiceHeroSwitchLabel,
1067
- onSwitchToText: () => setUserMode("text")
1068
- }
1069
- ) : /* @__PURE__ */ jsx(
1070
- ChatInput,
1071
- {
1072
- value: input,
1073
- onChange: setInput,
1074
- onSend: (text) => void send(text),
1075
- pending,
1076
- placeholder,
1077
- size: showEmptyFileDropzone || application && isMobile ? "md" : "lg",
1078
- voice,
1079
- voiceSession,
1080
- voiceTitle,
1081
- voiceLang,
1082
- submitLabel,
1083
- autoFocus: !isMobile,
1084
- inputRef,
1085
- attachments,
1086
- onFilesSelected,
1087
- onRemoveAttachment,
1088
- accept: attachmentAccept,
1089
- addFilesLabel,
1090
- uploadingLabel,
1091
- appearance: composerAppearance,
1092
- helperText: composerHelperText,
1093
- privacyNote: composerPrivacyNote,
1094
- className: workspace ? "rounded-3xl shadow-sm" : void 0
1095
- }
1096
- ),
1097
- error && /* @__PURE__ */ jsx(ChatError, { children: error }),
1098
- !pending && examplePromptsLoading && /* @__PURE__ */ jsx(ExamplePromptsLoading, { label: examplePromptsLoadingLabel }),
1099
- !pending && !examplePromptsLoading && examplePrompts.length > 0 && /* @__PURE__ */ jsx(
1100
- ChatSuggestions,
1101
- {
1102
- slot: "chat-examples",
1103
- items: examplePrompts,
1104
- onSelect: (prompt) => void sendSuggestion(prompt),
1105
- label: examplePromptsLabel,
1106
- variant: examplePromptsVariant,
1107
- className: examplePromptsVariant === "cards" ? "mt-4" : "mt-3"
1108
- }
1109
- ),
1110
- emptyFooter && /* @__PURE__ */ jsx("div", { "data-slot": "chat-empty-footer", className: "mt-5", children: emptyFooter })
1111
- ]
1112
- }
1113
- )
1114
- ]
1115
- }
1116
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1117
- /* @__PURE__ */ jsx(
1118
- ChatThread,
1119
- {
1120
- messages,
1121
- pending,
1122
- pendingLabel,
1123
- scrollButtonLabel,
1124
- uploadingLabel,
1125
- maxHeight: workspace || application && isMobile ? "none" : threadMaxHeight,
1126
- appearance: workspace ? "workspace" : "bubbles",
1127
- footer: threadFooter,
1128
- markdownComponents,
1129
- onOpenAttachment,
1130
- attachmentAction,
1131
- messageActions,
1132
- className: cn(
1133
- "mb-3",
1134
- application && "min-h-0 flex-1 px-4 py-4 md:px-0 md:py-0",
1135
- workspace && "mb-0 min-h-0 flex-1"
1136
- )
1137
- }
1138
- ),
1139
- /* @__PURE__ */ jsxs(
1140
- ActionDock,
1141
- {
1142
- shrink: false,
1143
- className: cn(
1144
- "pt-3",
1145
- application && "px-4 py-3 md:bg-transparent md:px-0 md:pb-0 md:backdrop-blur-none md:supports-backdrop-filter:bg-transparent",
1146
- workspace && "border-0 bg-gradient-to-t from-background via-background to-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-8 backdrop-blur-none supports-[backdrop-filter]:bg-gradient-to-t",
1147
- workspace && mobileCompactComposer && "max-md:pb-[max(0.75rem,env(safe-area-inset-bottom))] max-md:pt-3"
1148
- ),
1149
- children: [
1150
- !compactSuggestions && /* @__PURE__ */ jsx(
1151
- ChatSuggestions,
1152
- {
1153
- slot: "chat-replies",
1154
- items: replySuggestions,
1155
- onSelect: (reply) => void sendSuggestion(reply),
1156
- label: replySuggestionsLabel,
1157
- showLabel: false,
1158
- disabled: pending,
1159
- className: cn("mb-3", workspace && "mx-auto max-w-3xl")
1160
- }
1161
- ),
1162
- /* @__PURE__ */ jsx(
1163
- ChatInput,
1164
- {
1165
- header: compactSuggestions && replySuggestions.length > 0 ? /* @__PURE__ */ jsx(
1166
- MobileReplySuggestions,
1167
- {
1168
- items: replySuggestions,
1169
- onSelect: (reply) => void sendSuggestion(reply, false),
1170
- label: replySuggestionsLabel,
1171
- moreLabel: replySuggestionsMoreLabel,
1172
- closeLabel: replySuggestionsCloseLabel,
1173
- disabled: pending
1174
- },
1175
- JSON.stringify([messages.at(-1)?.id, replySuggestions])
1176
- ) : void 0,
1177
- value: input,
1178
- onChange: setInput,
1179
- onSend: (text) => void send(text),
1180
- pending,
1181
- placeholder: activePlaceholder,
1182
- size: composerAppearance === "spacious" ? "lg" : "sm",
1183
- voice,
1184
- voiceSession,
1185
- voiceTitle: activeVoiceTitle ?? voiceTitle,
1186
- voiceLang,
1187
- inputRef,
1188
- attachments,
1189
- onFilesSelected,
1190
- onRemoveAttachment,
1191
- accept: attachmentAccept,
1192
- addFilesLabel,
1193
- uploadingLabel,
1194
- appearance: composerAppearance,
1195
- compactOnBlur: mobileCompactComposer && isMobile,
1196
- helperText: composerHelperText,
1197
- privacyNote: composerPrivacyNote,
1198
- className: workspace ? "mx-auto max-w-3xl rounded-3xl shadow-sm" : void 0
1199
- }
1200
- ),
1201
- error && /* @__PURE__ */ jsx(ChatError, { className: workspace ? "mx-auto max-w-3xl px-2" : void 0, children: error })
1202
- ]
1203
- }
1204
- )
1205
- ] })
1206
- }
1207
- );
1208
- });
1209
-
1210
- export {
1211
- ChatThread,
1212
- ChatCopyAction,
1213
- ChatInput,
1214
- Chat
1215
- };