@djangocfg/ui-tools 2.1.385 → 2.1.389

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.
Files changed (80) hide show
  1. package/README.md +25 -11
  2. package/dist/ChatRoot-EFNXQXXN.cjs +15 -0
  3. package/dist/{ChatRoot-JVR3M3H2.mjs.map → ChatRoot-EFNXQXXN.cjs.map} +1 -1
  4. package/dist/ChatRoot-FITF5RVP.mjs +6 -0
  5. package/dist/{ChatRoot-LXIUBOXF.cjs.map → ChatRoot-FITF5RVP.mjs.map} +1 -1
  6. package/dist/{DocsLayout-2P3ONDWJ.mjs → DocsLayout-EKASBSP7.mjs} +3 -3
  7. package/dist/{DocsLayout-2P3ONDWJ.mjs.map → DocsLayout-EKASBSP7.mjs.map} +1 -1
  8. package/dist/{DocsLayout-2YZNS5VK.cjs → DocsLayout-OURFYWQE.cjs} +8 -8
  9. package/dist/{DocsLayout-2YZNS5VK.cjs.map → DocsLayout-OURFYWQE.cjs.map} +1 -1
  10. package/dist/MapContainer-AKIPABJK.mjs +4 -0
  11. package/dist/MapContainer-AKIPABJK.mjs.map +1 -0
  12. package/dist/MapContainer-STVDMC36.cjs +17 -0
  13. package/dist/MapContainer-STVDMC36.cjs.map +1 -0
  14. package/dist/{chunk-HIK6BPL7.mjs → chunk-2NG4SXEP.mjs} +6 -5
  15. package/dist/chunk-2NG4SXEP.mjs.map +1 -0
  16. package/dist/chunk-4LFB7I5K.cjs +1387 -0
  17. package/dist/chunk-4LFB7I5K.cjs.map +1 -0
  18. package/dist/{MapContainer-76YL2JXL.cjs → chunk-5D2OCOPQ.cjs} +3 -2
  19. package/dist/chunk-5D2OCOPQ.cjs.map +1 -0
  20. package/dist/chunk-6ZX2G25W.mjs +1361 -0
  21. package/dist/chunk-6ZX2G25W.mjs.map +1 -0
  22. package/dist/{MapContainer-7HXBI3OH.mjs → chunk-7CWGZPO3.mjs} +3 -3
  23. package/dist/chunk-7CWGZPO3.mjs.map +1 -0
  24. package/dist/{chunk-FIRK5CEH.cjs → chunk-7IYXZUJO.cjs} +8 -4
  25. package/dist/chunk-7IYXZUJO.cjs.map +1 -0
  26. package/dist/{chunk-PEKBT75W.mjs → chunk-DMX7W4XZ.mjs} +53 -1387
  27. package/dist/chunk-DMX7W4XZ.mjs.map +1 -0
  28. package/dist/chunk-NTVBIIUD.mjs +1439 -0
  29. package/dist/chunk-NTVBIIUD.mjs.map +1 -0
  30. package/dist/{chunk-HPK3EWBF.cjs → chunk-TBSHZO5R.cjs} +50 -1409
  31. package/dist/chunk-TBSHZO5R.cjs.map +1 -0
  32. package/dist/chunk-W75B7Y6C.cjs +1478 -0
  33. package/dist/chunk-W75B7Y6C.cjs.map +1 -0
  34. package/dist/index.cjs +1269 -1790
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +660 -623
  37. package/dist/index.d.ts +660 -623
  38. package/dist/index.mjs +856 -1427
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/launcher-5Y42OBSN.mjs +6 -0
  41. package/dist/launcher-5Y42OBSN.mjs.map +1 -0
  42. package/dist/launcher-PMW2YB24.cjs +59 -0
  43. package/dist/launcher-PMW2YB24.cjs.map +1 -0
  44. package/package.json +23 -18
  45. package/src/components/index.ts +2 -2
  46. package/src/index.ts +20 -2
  47. package/src/tools/AudioPlayer/lazy.tsx +100 -0
  48. package/src/tools/Chat/README.md +85 -1
  49. package/src/tools/Chat/components/MessageBubble.tsx +1 -1
  50. package/src/tools/Chat/context/ChatProvider.tsx +42 -0
  51. package/src/tools/Chat/index.ts +1 -1
  52. package/src/tools/Chat/lazy.tsx +300 -1
  53. package/src/tools/CodeEditor/lazy.tsx +70 -0
  54. package/src/tools/Map/lazy.tsx +38 -1
  55. package/src/tools/MarkdownEditor/lazy.tsx +42 -0
  56. package/src/{components/markdown → tools}/MarkdownMessage/CodeBlock.tsx +1 -1
  57. package/src/{components/markdown → tools}/MarkdownMessage/CollapseToggle.tsx +1 -1
  58. package/src/{components/markdown → tools}/MarkdownMessage/MarkdownMessage.tsx +1 -1
  59. package/src/{components/markdown → tools}/MarkdownMessage/components.tsx +2 -2
  60. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +1 -1
  61. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +1 -1
  62. package/src/tools/SpeechRecognition/README.md +48 -0
  63. package/dist/ChatRoot-JVR3M3H2.mjs +0 -5
  64. package/dist/ChatRoot-LXIUBOXF.cjs +0 -14
  65. package/dist/MapContainer-76YL2JXL.cjs.map +0 -1
  66. package/dist/MapContainer-7HXBI3OH.mjs.map +0 -1
  67. package/dist/chunk-FIRK5CEH.cjs.map +0 -1
  68. package/dist/chunk-HIK6BPL7.mjs.map +0 -1
  69. package/dist/chunk-HPK3EWBF.cjs.map +0 -1
  70. package/dist/chunk-PEKBT75W.mjs.map +0 -1
  71. package/src/components/markdown/index.ts +0 -19
  72. /package/src/{components/markdown → hooks}/useCollapsibleContent.ts +0 -0
  73. /package/src/{components/markdown → tools}/MarkdownMessage/ActionRow.tsx +0 -0
  74. /package/src/{components/markdown → tools}/MarkdownMessage/ChatMessageRow.tsx +0 -0
  75. /package/src/{components/markdown → tools}/MarkdownMessage/README.md +0 -0
  76. /package/src/{components/markdown → tools}/MarkdownMessage/index.ts +0 -0
  77. /package/src/{components/markdown → tools}/MarkdownMessage/linkRules.ts +0 -0
  78. /package/src/{components/markdown → tools}/MarkdownMessage/plainText.ts +0 -0
  79. /package/src/{components/markdown → tools}/MarkdownMessage/sanitize.ts +0 -0
  80. /package/src/{components/markdown → tools}/MarkdownMessage/types.ts +0 -0
@@ -0,0 +1,1361 @@
1
+ import { useChatContextOptional, LIMITS, ChatProvider, useChatContext } from './chunk-DMX7W4XZ.mjs';
2
+ import { TOOL_CALL, DESTRUCTIVE_SURFACE, TOGGLE, ANCHOR, BUBBLE_SURFACE, MarkdownMessage } from './chunk-2NG4SXEP.mjs';
3
+ import { __name } from './chunk-N2XQF2OL.mjs';
4
+ import { forwardRef, useRef, useEffect, memo, useState, useCallback, useImperativeHandle, useMemo } from 'react';
5
+ import { cn } from '@djangocfg/ui-core/lib';
6
+ import { Paperclip, Square, Send, ChevronDown, ChevronRight, Loader2, File, X, Sparkles, AlertCircle, RefreshCw, ArrowDown, ExternalLink, Copy, Pencil, Trash } from 'lucide-react';
7
+ import { Button, Textarea, Spinner, Avatar, AvatarImage, AvatarFallback } from '@djangocfg/ui-core/components';
8
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import { Virtuoso } from 'react-virtuoso';
10
+ import { useCopy } from '@djangocfg/ui-core/hooks';
11
+
12
+ function useAutoFocusOnStreamEnd(options = {}) {
13
+ const { isStreaming: isStreamingProp, targetRef, enabled = true, delayMs = 0 } = options;
14
+ const ctx = useChatContextOptional();
15
+ const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;
16
+ const composerHandleRef = useRef(null);
17
+ composerHandleRef.current = ctx?.composer ?? null;
18
+ const prevStreamingRef = useRef(isStreaming);
19
+ useEffect(() => {
20
+ const wasStreaming = prevStreamingRef.current;
21
+ prevStreamingRef.current = isStreaming;
22
+ if (!enabled) return;
23
+ if (!(wasStreaming && !isStreaming)) return;
24
+ const focusNow = /* @__PURE__ */ __name(() => {
25
+ const explicit = targetRef?.current;
26
+ const target = explicit ?? composerHandleRef.current;
27
+ target?.focus();
28
+ }, "focusNow");
29
+ if (delayMs > 0) {
30
+ const id = window.setTimeout(focusNow, delayMs);
31
+ return () => window.clearTimeout(id);
32
+ }
33
+ const raf = requestAnimationFrame(focusNow);
34
+ return () => cancelAnimationFrame(raf);
35
+ }, [isStreaming, enabled, delayMs, targetRef]);
36
+ }
37
+ __name(useAutoFocusOnStreamEnd, "useAutoFocusOnStreamEnd");
38
+ function useRegisterComposer(handle) {
39
+ const ctx = useChatContextOptional();
40
+ const register = ctx?.registerComposer;
41
+ const focus = handle.focus;
42
+ const moveCursorToEnd = handle.moveCursorToEnd;
43
+ useEffect(() => {
44
+ if (!register) return;
45
+ register({ focus, moveCursorToEnd });
46
+ return () => register(null);
47
+ }, [register, focus, moveCursorToEnd]);
48
+ }
49
+ __name(useRegisterComposer, "useRegisterComposer");
50
+
51
+ // src/tools/Chat/utils/sanitizeDraft.ts
52
+ function sanitizeDraft(input) {
53
+ if (!input) return "";
54
+ let s = input.replace(/[​‌‍]/g, "");
55
+ s = s.replace(/\r\n?/g, "\n");
56
+ s = s.trim();
57
+ return s;
58
+ }
59
+ __name(sanitizeDraft, "sanitizeDraft");
60
+ function isSubmittableDraft(input) {
61
+ return sanitizeDraft(input).length > 0;
62
+ }
63
+ __name(isSubmittableDraft, "isSubmittableDraft");
64
+
65
+ // src/tools/Chat/hooks/useChatComposer.ts
66
+ var MAX_TEXTAREA_HEIGHT = 240;
67
+ function useChatComposer(options) {
68
+ const {
69
+ onSubmit,
70
+ initialValue = "",
71
+ maxLength = LIMITS.messageMaxLength,
72
+ maxAttachments = LIMITS.attachmentsMax,
73
+ disabled = false,
74
+ submitOn = "enter",
75
+ history = { enabled: true, size: LIMITS.composerHistorySize },
76
+ onPasteFiles,
77
+ persistKey,
78
+ preserveExactValue = false
79
+ } = options;
80
+ const initialFromStorage = (() => {
81
+ if (!persistKey || typeof window === "undefined") return initialValue;
82
+ try {
83
+ const stored = window.sessionStorage.getItem(`chat:draft:${persistKey}`);
84
+ return stored && stored.length > 0 ? stored : initialValue;
85
+ } catch {
86
+ return initialValue;
87
+ }
88
+ })();
89
+ const [value, setValueState] = useState(initialFromStorage);
90
+ useEffect(() => {
91
+ if (!persistKey || typeof window === "undefined") return;
92
+ try {
93
+ const k = `chat:draft:${persistKey}`;
94
+ if (value.length > 0) window.sessionStorage.setItem(k, value);
95
+ else window.sessionStorage.removeItem(k);
96
+ } catch {
97
+ }
98
+ }, [value, persistKey]);
99
+ const [attachments, setAttachments] = useState([]);
100
+ const [isSubmitting, setIsSubmitting] = useState(false);
101
+ const textareaRef = useRef(null);
102
+ const historyRef = useRef({ items: [], index: -1 });
103
+ const setValue = useCallback(
104
+ (next) => {
105
+ setValueState(next.length > maxLength ? next.slice(0, maxLength) : next);
106
+ },
107
+ [maxLength]
108
+ );
109
+ useEffect(() => {
110
+ const el = textareaRef.current;
111
+ if (!el) return;
112
+ el.style.height = "auto";
113
+ el.style.height = `${Math.min(el.scrollHeight, MAX_TEXTAREA_HEIGHT)}px`;
114
+ }, [value]);
115
+ const reset = useCallback(() => {
116
+ setValueState("");
117
+ setAttachments([]);
118
+ historyRef.current.index = -1;
119
+ }, []);
120
+ const focus = useCallback(() => {
121
+ requestAnimationFrame(() => textareaRef.current?.focus());
122
+ }, []);
123
+ const submit = useCallback(async () => {
124
+ const cleaned = preserveExactValue ? value : sanitizeDraft(value);
125
+ if (!cleaned && attachments.length === 0 || isSubmitting || disabled) return;
126
+ setIsSubmitting(true);
127
+ try {
128
+ if (history.enabled !== false && cleaned) {
129
+ const buf = historyRef.current.items;
130
+ if (buf[buf.length - 1] !== cleaned) {
131
+ buf.push(cleaned);
132
+ if (buf.length > (history.size ?? LIMITS.composerHistorySize)) buf.shift();
133
+ }
134
+ historyRef.current.index = -1;
135
+ }
136
+ const snapshot = [...attachments];
137
+ reset();
138
+ await onSubmit(cleaned, snapshot);
139
+ } finally {
140
+ setIsSubmitting(false);
141
+ }
142
+ }, [value, attachments, isSubmitting, disabled, history, onSubmit, reset, preserveExactValue]);
143
+ const addAttachment = useCallback(
144
+ (a) => {
145
+ setAttachments((prev) => {
146
+ if (prev.some((p) => p.id === a.id)) {
147
+ return prev.map((p) => p.id === a.id ? a : p);
148
+ }
149
+ if (prev.length >= maxAttachments) return prev;
150
+ return [...prev, a];
151
+ });
152
+ },
153
+ [maxAttachments]
154
+ );
155
+ const removeAttachment = useCallback((id) => {
156
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
157
+ }, []);
158
+ const recallPrevious = useCallback(() => {
159
+ const { items } = historyRef.current;
160
+ if (!items.length) return;
161
+ const next = historyRef.current.index < 0 ? items.length - 1 : Math.max(0, historyRef.current.index - 1);
162
+ historyRef.current.index = next;
163
+ setValueState(items[next]);
164
+ }, []);
165
+ const recallNext = useCallback(() => {
166
+ const { items } = historyRef.current;
167
+ if (!items.length || historyRef.current.index < 0) return;
168
+ const next = historyRef.current.index + 1;
169
+ if (next >= items.length) {
170
+ historyRef.current.index = -1;
171
+ setValueState("");
172
+ return;
173
+ }
174
+ historyRef.current.index = next;
175
+ setValueState(items[next]);
176
+ }, []);
177
+ const onChange = useCallback(
178
+ (e) => {
179
+ setValue(e.target.value);
180
+ },
181
+ [setValue]
182
+ );
183
+ const onKeyDown = useCallback(
184
+ (e) => {
185
+ if (e.key === "Enter") {
186
+ const isCmd = e.metaKey || e.ctrlKey;
187
+ const shouldSend = submitOn === "cmd+enter" ? isCmd : !e.shiftKey;
188
+ if (shouldSend) {
189
+ e.preventDefault();
190
+ void submit();
191
+ }
192
+ return;
193
+ }
194
+ if (history.enabled !== false && value === "" && attachments.length === 0) {
195
+ if (e.key === "ArrowUp") {
196
+ e.preventDefault();
197
+ recallPrevious();
198
+ } else if (e.key === "ArrowDown" && historyRef.current.index >= 0) {
199
+ e.preventDefault();
200
+ recallNext();
201
+ }
202
+ }
203
+ },
204
+ [submitOn, submit, history, value, attachments.length, recallPrevious, recallNext]
205
+ );
206
+ const onPaste = useCallback(
207
+ (e) => {
208
+ const files = Array.from(e.clipboardData?.files ?? []);
209
+ if (files.length && onPasteFiles) {
210
+ e.preventDefault();
211
+ onPasteFiles(files);
212
+ }
213
+ },
214
+ [onPasteFiles]
215
+ );
216
+ const canSubmit = !disabled && !isSubmitting && ((preserveExactValue ? value.trim().length : sanitizeDraft(value).length) > 0 || attachments.length > 0);
217
+ return {
218
+ value,
219
+ setValue,
220
+ attachments,
221
+ addAttachment,
222
+ removeAttachment,
223
+ isSubmitting,
224
+ canSubmit,
225
+ submit,
226
+ reset,
227
+ focus,
228
+ textareaRef,
229
+ textareaProps: {
230
+ ref: textareaRef,
231
+ value,
232
+ disabled,
233
+ onChange,
234
+ onKeyDown,
235
+ onPaste
236
+ },
237
+ recallPrevious,
238
+ recallNext
239
+ };
240
+ }
241
+ __name(useChatComposer, "useChatComposer");
242
+ function useFocusOnEmptyClick(options = {}) {
243
+ const { targetRef, enabled = true, skipWhileStreaming = true } = options;
244
+ const ctx = useChatContextOptional();
245
+ const composerHandleRef = useRef(null);
246
+ composerHandleRef.current = ctx?.composer ?? null;
247
+ const isStreamingRef = useRef(false);
248
+ isStreamingRef.current = ctx?.isStreaming ?? false;
249
+ return useCallback(
250
+ (event) => {
251
+ if (!enabled) return;
252
+ if (skipWhileStreaming && isStreamingRef.current) return;
253
+ const pointerType = event.nativeEvent.pointerType;
254
+ if (pointerType && pointerType !== "mouse") return;
255
+ const selection = typeof window !== "undefined" ? window.getSelection?.() : null;
256
+ if (selection && !selection.isCollapsed && selection.toString().length > 0) {
257
+ return;
258
+ }
259
+ const target = event.target;
260
+ if (!target) return;
261
+ if (isInteractive(target)) return;
262
+ const explicit = targetRef?.current;
263
+ if (explicit) {
264
+ explicit.focus?.();
265
+ return;
266
+ }
267
+ composerHandleRef.current?.focus?.();
268
+ },
269
+ [enabled, skipWhileStreaming, targetRef]
270
+ );
271
+ }
272
+ __name(useFocusOnEmptyClick, "useFocusOnEmptyClick");
273
+ var INTERACTIVE_SELECTORS = [
274
+ "a[href]",
275
+ "button",
276
+ "input",
277
+ "textarea",
278
+ "select",
279
+ "label",
280
+ "summary",
281
+ '[role="button"]',
282
+ '[role="link"]',
283
+ '[role="menuitem"]',
284
+ '[role="tab"]',
285
+ '[contenteditable]:not([contenteditable="false"])',
286
+ "[data-no-autofocus]"
287
+ ].join(",");
288
+ function isInteractive(el) {
289
+ if (!el) return false;
290
+ return !!el.closest(INTERACTIVE_SELECTORS);
291
+ }
292
+ __name(isInteractive, "isInteractive");
293
+ function useChatBubbleStyles(role, isError) {
294
+ return useMemo(() => {
295
+ const isUser = role === "user";
296
+ const variant = isUser ? "user" : isError ? "error" : "assistant";
297
+ return {
298
+ surface: BUBBLE_SURFACE[variant],
299
+ anchor: isUser ? ANCHOR.user : ANCHOR.assistant,
300
+ toggle: isUser ? TOGGLE.user : TOGGLE.assistant
301
+ };
302
+ }, [role, isError]);
303
+ }
304
+ __name(useChatBubbleStyles, "useChatBubbleStyles");
305
+ function useChatRoleStyles(isUser) {
306
+ return useMemo(
307
+ () => ({
308
+ anchor: isUser ? ANCHOR.user : ANCHOR.assistant,
309
+ toggle: isUser ? TOGGLE.user : TOGGLE.assistant
310
+ }),
311
+ [isUser]
312
+ );
313
+ }
314
+ __name(useChatRoleStyles, "useChatRoleStyles");
315
+ function useChatDestructiveStyles() {
316
+ return DESTRUCTIVE_STYLES;
317
+ }
318
+ __name(useChatDestructiveStyles, "useChatDestructiveStyles");
319
+ var DESTRUCTIVE_STYLES = {
320
+ banner: DESTRUCTIVE_SURFACE.banner,
321
+ hover: DESTRUCTIVE_SURFACE.hover,
322
+ hoverStrong: DESTRUCTIVE_SURFACE.hoverStrong,
323
+ text: DESTRUCTIVE_SURFACE.text,
324
+ menuItem: DESTRUCTIVE_SURFACE.menuItem,
325
+ toolErrorText: TOOL_CALL.errorText
326
+ };
327
+ function AttachmentsGrid({
328
+ attachments,
329
+ maxVisible,
330
+ onClick,
331
+ onRemove,
332
+ isInComposer = false,
333
+ layout = "wrap",
334
+ className
335
+ }) {
336
+ if (!attachments?.length) return null;
337
+ const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
338
+ return /* @__PURE__ */ jsx(
339
+ "div",
340
+ {
341
+ className: cn(
342
+ layout === "grid" ? "grid grid-cols-3 gap-2" : "flex flex-wrap gap-2",
343
+ className
344
+ ),
345
+ children: visible.map((a) => /* @__PURE__ */ jsx(
346
+ AttachmentTile,
347
+ {
348
+ attachment: a,
349
+ isInComposer,
350
+ onClick: onClick ? () => onClick(a) : void 0,
351
+ onRemove: onRemove ? () => onRemove(a) : void 0
352
+ },
353
+ a.id
354
+ ))
355
+ }
356
+ );
357
+ }
358
+ __name(AttachmentsGrid, "AttachmentsGrid");
359
+ function AttachmentsList({
360
+ attachments,
361
+ maxVisible,
362
+ onClick,
363
+ onRemove,
364
+ renderers,
365
+ isInComposer = false,
366
+ className
367
+ }) {
368
+ if (!attachments?.length) return null;
369
+ const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
370
+ return /* @__PURE__ */ jsx("div", { className: cn("flex w-full flex-col gap-2", className), children: visible.map((a) => {
371
+ const renderer = renderers?.[a.type] ?? renderers?.default;
372
+ const args = {
373
+ attachment: a,
374
+ isInComposer,
375
+ onClick: onClick ? () => onClick(a) : void 0,
376
+ onRemove: onRemove ? () => onRemove(a) : void 0
377
+ };
378
+ if (renderer) {
379
+ return /* @__PURE__ */ jsxs("div", { className: "relative w-full min-w-0", children: [
380
+ renderer(args),
381
+ args.onRemove ? /* @__PURE__ */ jsx(RemoveBtn, { onRemove: args.onRemove }) : null
382
+ ] }, a.id);
383
+ }
384
+ return /* @__PURE__ */ jsx(AttachmentTile, { ...args }, a.id);
385
+ }) });
386
+ }
387
+ __name(AttachmentsList, "AttachmentsList");
388
+ function Attachments(props) {
389
+ const { renderers, layout, ...rest } = props;
390
+ if (renderers) {
391
+ return /* @__PURE__ */ jsx(AttachmentsList, { ...rest, renderers });
392
+ }
393
+ return /* @__PURE__ */ jsx(AttachmentsGrid, { ...rest, layout: layout === "grid" ? "grid" : "wrap" });
394
+ }
395
+ __name(Attachments, "Attachments");
396
+ function AttachmentTile({ attachment, onClick, onRemove }) {
397
+ const isImage = attachment.type === "image";
398
+ const isUploading = attachment.status === "uploading";
399
+ const inner = isImage ? /* @__PURE__ */ jsx(
400
+ "img",
401
+ {
402
+ src: attachment.thumbnailUrl ?? attachment.url,
403
+ alt: attachment.name ?? "attachment",
404
+ className: "h-16 w-16 rounded-md object-cover",
405
+ loading: "lazy"
406
+ }
407
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex max-w-44 items-center gap-2 rounded-md border border-border bg-background/60 px-2 py-1.5 text-xs", children: [
408
+ /* @__PURE__ */ jsx(File, { "aria-hidden": true, className: "size-4 shrink-0 text-muted-foreground" }),
409
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: attachment.name ?? "file" })
410
+ ] });
411
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
412
+ onClick ? /* @__PURE__ */ jsx("button", { type: "button", onClick, className: "block", children: inner }) : inner,
413
+ isUploading ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center rounded-md bg-background/70 text-[10px] font-medium", children: attachment.progress != null ? `${Math.round(attachment.progress * 100)}%` : "\u2026" }) : null,
414
+ onRemove ? /* @__PURE__ */ jsx(RemoveBtn, { onRemove }) : null
415
+ ] });
416
+ }
417
+ __name(AttachmentTile, "AttachmentTile");
418
+ function RemoveBtn({ onRemove }) {
419
+ const styles = useChatDestructiveStyles();
420
+ return /* @__PURE__ */ jsx(
421
+ "button",
422
+ {
423
+ type: "button",
424
+ "aria-label": "Remove attachment",
425
+ onClick: onRemove,
426
+ className: cn(
427
+ "absolute -right-1.5 -top-1.5 grid h-4 w-4 place-items-center rounded-full border border-border bg-background text-muted-foreground",
428
+ styles.hoverStrong
429
+ ),
430
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-2.5" })
431
+ }
432
+ );
433
+ }
434
+ __name(RemoveBtn, "RemoveBtn");
435
+ var SIZE_CLASSES = {
436
+ sm: {
437
+ slot: "[&>:not(textarea)]:h-8",
438
+ button: "h-8 w-8",
439
+ iconButton: "size-3.5",
440
+ textarea: "min-h-8 max-h-48 px-3 py-1.5",
441
+ text: "text-sm",
442
+ padding: "gap-1.5",
443
+ containerPadding: "px-2 pt-1.5 pb-[max(0.375rem,env(safe-area-inset-bottom))]"
444
+ },
445
+ md: {
446
+ slot: "[&>:not(textarea)]:h-9",
447
+ button: "h-9 w-9",
448
+ iconButton: "size-4",
449
+ textarea: "min-h-9 max-h-60 px-3.5 py-2",
450
+ text: "text-base sm:text-sm",
451
+ padding: "gap-1.5",
452
+ containerPadding: "px-2.5 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))]"
453
+ },
454
+ lg: {
455
+ slot: "[&>:not(textarea)]:h-12",
456
+ button: "h-12 w-12",
457
+ iconButton: "size-5",
458
+ textarea: "min-h-12 max-h-72 px-4 py-3",
459
+ text: "text-base",
460
+ padding: "gap-2",
461
+ containerPadding: "px-3.5 pt-3 pb-[max(0.875rem,env(safe-area-inset-bottom))]"
462
+ }
463
+ };
464
+ var Composer = forwardRef(/* @__PURE__ */ __name(function Composer2({
465
+ composer,
466
+ placeholder = "Type a message...",
467
+ disabled,
468
+ showAttachmentButton = false,
469
+ onPickFiles,
470
+ toolbarStart,
471
+ toolbarEnd,
472
+ attachmentTray,
473
+ className,
474
+ textareaClassName,
475
+ size = "md",
476
+ isStreaming: isStreamingProp,
477
+ onCancel: onCancelProp
478
+ }, ref) {
479
+ const ctx = useChatContextOptional();
480
+ const isStreaming = isStreamingProp ?? ctx?.isStreaming ?? false;
481
+ const onCancel = onCancelProp ?? ctx?.cancelStream;
482
+ const isDisabled = disabled ?? isStreaming;
483
+ const sz = SIZE_CLASSES[size];
484
+ const register = ctx?.registerComposer;
485
+ const composerFocus = composer.focus;
486
+ const composerSetValue = composer.setValue;
487
+ const textareaRef = composer.textareaRef;
488
+ const getValueRef = useRef(() => composer.value);
489
+ getValueRef.current = () => composer.value;
490
+ useEffect(() => {
491
+ if (!register) return;
492
+ register({
493
+ focus: composerFocus,
494
+ moveCursorToEnd: /* @__PURE__ */ __name(() => {
495
+ const el = textareaRef.current;
496
+ if (!el) return;
497
+ const end = el.value.length;
498
+ el.setSelectionRange(end, end);
499
+ }, "moveCursorToEnd"),
500
+ getValue: /* @__PURE__ */ __name(() => getValueRef.current(), "getValue"),
501
+ setValue: composerSetValue
502
+ });
503
+ return () => register(null);
504
+ }, [register, composerFocus, composerSetValue, textareaRef]);
505
+ return /* @__PURE__ */ jsxs(
506
+ "div",
507
+ {
508
+ ref,
509
+ className: cn(
510
+ "border-t border-border bg-background/95",
511
+ sz.containerPadding,
512
+ className
513
+ ),
514
+ children: [
515
+ composer.attachments.length > 0 ? /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: attachmentTray ?? /* @__PURE__ */ jsx(
516
+ Attachments,
517
+ {
518
+ attachments: composer.attachments,
519
+ onRemove: (a) => composer.removeAttachment(a.id)
520
+ }
521
+ ) }) : null,
522
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-end [&>:not(textarea)]:shrink-0", sz.padding, sz.slot), children: [
523
+ showAttachmentButton ? /* @__PURE__ */ jsx(
524
+ Button,
525
+ {
526
+ type: "button",
527
+ variant: "ghost",
528
+ size: "icon",
529
+ onClick: onPickFiles,
530
+ "aria-label": "Attach files",
531
+ disabled: isDisabled,
532
+ className: sz.button,
533
+ children: /* @__PURE__ */ jsx(Paperclip, { "aria-hidden": true, className: sz.iconButton })
534
+ }
535
+ ) : null,
536
+ toolbarStart,
537
+ /* @__PURE__ */ jsx(
538
+ Textarea,
539
+ {
540
+ ...composer.textareaProps,
541
+ rows: 1,
542
+ placeholder,
543
+ "aria-label": placeholder,
544
+ "aria-multiline": "true",
545
+ disabled: isDisabled,
546
+ className: cn(
547
+ "flex-1 resize-none rounded-2xl",
548
+ sz.textarea,
549
+ sz.text,
550
+ textareaClassName
551
+ )
552
+ }
553
+ ),
554
+ toolbarEnd,
555
+ isStreaming ? /* @__PURE__ */ jsx(
556
+ Button,
557
+ {
558
+ type: "button",
559
+ variant: "secondary",
560
+ size: "icon",
561
+ onClick: onCancel,
562
+ "aria-label": "Stop",
563
+ "aria-keyshortcuts": "Escape",
564
+ className: sz.button,
565
+ children: /* @__PURE__ */ jsx(Square, { "aria-hidden": true, className: sz.iconButton })
566
+ }
567
+ ) : /* @__PURE__ */ jsx(
568
+ Button,
569
+ {
570
+ type: "button",
571
+ size: "icon",
572
+ onClick: () => void composer.submit(),
573
+ disabled: !composer.canSubmit,
574
+ "aria-label": "Send",
575
+ "aria-keyshortcuts": "Enter",
576
+ className: sz.button,
577
+ children: /* @__PURE__ */ jsx(Send, { "aria-hidden": true, className: sz.iconButton })
578
+ }
579
+ )
580
+ ] })
581
+ ]
582
+ }
583
+ );
584
+ }, "Composer"));
585
+ function EmptyState({
586
+ greeting,
587
+ description,
588
+ suggestions,
589
+ onPickSuggestion,
590
+ className
591
+ }) {
592
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-3 px-4 py-12 text-center", className), children: [
593
+ /* @__PURE__ */ jsx("div", { className: "grid size-10 place-items-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Sparkles, { "aria-hidden": true, className: "size-5 text-muted-foreground" }) }),
594
+ greeting ? /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: greeting }) : null,
595
+ description ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: description }) : null,
596
+ suggestions?.length ? /* @__PURE__ */ jsx("div", { className: "mt-2 grid w-full max-w-md grid-cols-1 gap-2 sm:grid-cols-2", children: suggestions.map((s) => /* @__PURE__ */ jsx(
597
+ "button",
598
+ {
599
+ type: "button",
600
+ onClick: () => onPickSuggestion?.(s.prompt),
601
+ className: "rounded-lg border border-border bg-background/60 px-3 py-2 text-left text-xs hover:bg-accent",
602
+ children: s.label
603
+ },
604
+ s.prompt
605
+ )) }) : null
606
+ ] });
607
+ }
608
+ __name(EmptyState, "EmptyState");
609
+ function ErrorBanner({ error, onDismiss, onRetry, className }) {
610
+ const styles = useChatDestructiveStyles();
611
+ if (!error) return null;
612
+ return /* @__PURE__ */ jsxs(
613
+ "div",
614
+ {
615
+ role: "alert",
616
+ className: cn(
617
+ "mx-2.5 my-2 flex items-start gap-2 rounded-md px-3 py-2 text-xs",
618
+ styles.banner,
619
+ className
620
+ ),
621
+ children: [
622
+ /* @__PURE__ */ jsx(AlertCircle, { "aria-hidden": true, className: "mt-0.5 size-3.5 shrink-0" }),
623
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 flex-1 break-words", children: error }),
624
+ onRetry ? /* @__PURE__ */ jsxs(
625
+ "button",
626
+ {
627
+ type: "button",
628
+ onClick: onRetry,
629
+ className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5", styles.hover),
630
+ children: [
631
+ /* @__PURE__ */ jsx(RefreshCw, { "aria-hidden": true, className: "size-3" }),
632
+ " Retry"
633
+ ]
634
+ }
635
+ ) : null,
636
+ onDismiss ? /* @__PURE__ */ jsx(
637
+ "button",
638
+ {
639
+ type: "button",
640
+ "aria-label": "Dismiss",
641
+ onClick: onDismiss,
642
+ className: cn("rounded p-0.5", styles.hover),
643
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-3" })
644
+ }
645
+ ) : null
646
+ ]
647
+ }
648
+ );
649
+ }
650
+ __name(ErrorBanner, "ErrorBanner");
651
+ function JumpToLatest({ visible, unreadCount = 0, onClick, className }) {
652
+ if (!visible) return null;
653
+ return /* @__PURE__ */ jsxs(
654
+ "button",
655
+ {
656
+ type: "button",
657
+ onClick,
658
+ "aria-live": "polite",
659
+ className: cn(
660
+ "pointer-events-auto inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-md hover:bg-accent",
661
+ className
662
+ ),
663
+ children: [
664
+ /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": true, className: "size-3.5" }),
665
+ unreadCount > 0 ? `${unreadCount} new` : "Jump to latest"
666
+ ]
667
+ }
668
+ );
669
+ }
670
+ __name(JumpToLatest, "JumpToLatest");
671
+
672
+ // src/tools/Chat/core/persona.ts
673
+ var FALLBACK_USER = { name: "You", initials: "You" };
674
+ var FALLBACK_ASSISTANT = { name: "AI", initials: "AI" };
675
+ function resolvePersona(message, user, assistant) {
676
+ if (message.sender) return message.sender;
677
+ if (message.role === "user") return user ?? FALLBACK_USER;
678
+ if (message.role === "assistant") return assistant ?? FALLBACK_ASSISTANT;
679
+ return { name: message.role };
680
+ }
681
+ __name(resolvePersona, "resolvePersona");
682
+ function deriveInitials(persona, role) {
683
+ if (persona.initials) return persona.initials;
684
+ if (persona.name) {
685
+ const parts = persona.name.trim().split(/\s+/);
686
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
687
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
688
+ }
689
+ if (role === "user") return "You";
690
+ if (role === "assistant") return "AI";
691
+ return "?";
692
+ }
693
+ __name(deriveInitials, "deriveInitials");
694
+ function StreamingIndicatorRaw({ variant = "dots", label, className }) {
695
+ return /* @__PURE__ */ jsxs(
696
+ "span",
697
+ {
698
+ className: cn("inline-flex items-center gap-1.5 text-xs text-muted-foreground", className),
699
+ "aria-live": "off",
700
+ children: [
701
+ variant === "dots" ? /* @__PURE__ */ jsxs("span", { className: "inline-flex gap-0.5", "aria-hidden": true, children: [
702
+ /* @__PURE__ */ jsx("span", { className: "size-1 animate-bounce rounded-full bg-current [animation-delay:-0.2s]" }),
703
+ /* @__PURE__ */ jsx("span", { className: "size-1 animate-bounce rounded-full bg-current [animation-delay:-0.1s]" }),
704
+ /* @__PURE__ */ jsx("span", { className: "size-1 animate-bounce rounded-full bg-current" })
705
+ ] }) : /* @__PURE__ */ jsx("span", { className: "inline-block size-1.5 animate-pulse rounded-full bg-current", "aria-hidden": true }),
706
+ label ? /* @__PURE__ */ jsx("span", { className: "italic", children: label }) : null
707
+ ]
708
+ }
709
+ );
710
+ }
711
+ __name(StreamingIndicatorRaw, "StreamingIndicatorRaw");
712
+ var StreamingIndicator = memo(StreamingIndicatorRaw);
713
+ function Sources({ sources, layout = "inline", maxVisible, onClick, className }) {
714
+ if (!sources?.length) return null;
715
+ const visible = maxVisible ? sources.slice(0, maxVisible) : sources;
716
+ const remaining = maxVisible ? Math.max(0, sources.length - maxVisible) : 0;
717
+ return /* @__PURE__ */ jsxs(
718
+ "div",
719
+ {
720
+ className: cn(
721
+ "mt-2 flex flex-wrap gap-1.5",
722
+ layout === "grid" && "grid grid-cols-2",
723
+ className
724
+ ),
725
+ children: [
726
+ visible.map((s, i) => {
727
+ const handle = onClick ? () => onClick(s) : void 0;
728
+ const Tag = handle ? "button" : "a";
729
+ const props = handle ? { type: "button", onClick: handle } : { href: s.url, target: "_blank", rel: "noopener noreferrer" };
730
+ return /* @__PURE__ */ jsxs(
731
+ Tag,
732
+ {
733
+ ...props,
734
+ className: "inline-flex max-w-full items-center gap-1 rounded-md border border-border bg-background/60 px-2 py-1 text-xs text-foreground/80 hover:bg-accent hover:text-foreground",
735
+ title: s.snippet ?? s.title,
736
+ children: [
737
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: s.title || s.url }),
738
+ /* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": true, className: "size-3 shrink-0 opacity-60" })
739
+ ]
740
+ },
741
+ `${s.url}-${i}`
742
+ );
743
+ }),
744
+ remaining > 0 ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-dashed border-border px-2 py-1 text-xs text-muted-foreground", children: [
745
+ "+",
746
+ remaining
747
+ ] }) : null
748
+ ]
749
+ }
750
+ );
751
+ }
752
+ __name(Sources, "Sources");
753
+ function ToolCalls({
754
+ calls,
755
+ defaultExpanded = false,
756
+ expandWhileStreaming = true,
757
+ renderInput,
758
+ renderOutput,
759
+ renderStreaming,
760
+ renderPayload,
761
+ renderAfterCalls,
762
+ renderToolCall,
763
+ hideToolCalls = false,
764
+ className
765
+ }) {
766
+ if (!calls?.length) return null;
767
+ return /* @__PURE__ */ jsxs("div", { className: cn("mt-2 space-y-1.5", className), children: [
768
+ !hideToolCalls && calls.map(
769
+ (call) => renderToolCall ? /* @__PURE__ */ jsx("div", { children: renderToolCall(call) }, call.id) : /* @__PURE__ */ jsx(
770
+ ToolCallItem,
771
+ {
772
+ call,
773
+ defaultExpanded,
774
+ expandWhileStreaming,
775
+ renderInput,
776
+ renderOutput,
777
+ renderStreaming,
778
+ renderPayload
779
+ },
780
+ call.id
781
+ )
782
+ ),
783
+ renderAfterCalls ? renderAfterCalls(calls) : null
784
+ ] });
785
+ }
786
+ __name(ToolCalls, "ToolCalls");
787
+ var ToolCallItem = memo(/* @__PURE__ */ __name(function ToolCallItem2({
788
+ call,
789
+ defaultExpanded,
790
+ expandWhileStreaming,
791
+ renderInput,
792
+ renderOutput,
793
+ renderStreaming,
794
+ renderPayload
795
+ }) {
796
+ const isRunning = call.status === "running";
797
+ const initialOpen = defaultExpanded || expandWhileStreaming && isRunning;
798
+ const [open, setOpen] = useState(initialOpen);
799
+ const userToggledRef = useRef(false);
800
+ const wasRunningRef = useRef(isRunning);
801
+ useEffect(() => {
802
+ if (wasRunningRef.current && !isRunning) {
803
+ if (!userToggledRef.current && !defaultExpanded) {
804
+ setOpen(false);
805
+ }
806
+ }
807
+ wasRunningRef.current = isRunning;
808
+ }, [isRunning, defaultExpanded]);
809
+ const handleToggle = /* @__PURE__ */ __name(() => {
810
+ userToggledRef.current = true;
811
+ setOpen((v) => !v);
812
+ }, "handleToggle");
813
+ const Icon = open ? ChevronDown : ChevronRight;
814
+ const statusColor = call.status === "success" ? "text-emerald-500" : call.status === "error" ? "text-destructive" : call.status === "cancelled" ? "text-muted-foreground" : "text-amber-500";
815
+ const renderValue = /* @__PURE__ */ __name((value, kind) => {
816
+ if (kind === "input" && renderInput) return renderInput(value, call);
817
+ if (kind === "output" && renderOutput) return renderOutput(value, call);
818
+ if (kind === "streaming" && renderStreaming)
819
+ return renderStreaming(typeof value === "string" ? value : String(value), call);
820
+ if (renderPayload) return renderPayload(value, kind, call);
821
+ return /* @__PURE__ */ jsx(DefaultPayload, { value, kind });
822
+ }, "renderValue");
823
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border bg-muted/30", children: [
824
+ /* @__PURE__ */ jsxs(
825
+ "button",
826
+ {
827
+ type: "button",
828
+ onClick: handleToggle,
829
+ "aria-expanded": open,
830
+ className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs hover:bg-muted/60",
831
+ children: [
832
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }),
833
+ isRunning ? /* @__PURE__ */ jsx(Loader2, { "aria-hidden": true, className: "size-3 shrink-0 animate-spin text-amber-500" }) : /* @__PURE__ */ jsx("span", { className: cn("size-2 shrink-0 rounded-full", statusColor.replace("text-", "bg-")) }),
834
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground", children: call.name }),
835
+ /* @__PURE__ */ jsx("span", { className: cn("ml-auto", statusColor), children: call.status })
836
+ ]
837
+ }
838
+ ),
839
+ open ? /* @__PURE__ */ jsxs("div", { className: "space-y-1 border-t border-border px-2 py-1.5 text-[11px]", children: [
840
+ call.input != null ? renderValue(call.input, "input") : null,
841
+ call.streamingText != null ? renderValue(call.streamingText, "streaming") : call.output !== void 0 ? renderValue(call.output, "output") : null
842
+ ] }) : null
843
+ ] });
844
+ }, "ToolCallItem"), (prev, next) => {
845
+ const a = prev.call;
846
+ const b = next.call;
847
+ return a.id === b.id && a.status === b.status && a.output === b.output && a.streamingText === b.streamingText && prev.defaultExpanded === next.defaultExpanded && prev.expandWhileStreaming === next.expandWhileStreaming && prev.renderInput === next.renderInput && prev.renderOutput === next.renderOutput && prev.renderStreaming === next.renderStreaming && prev.renderPayload === next.renderPayload;
848
+ });
849
+ function DefaultPayload({ value, kind }) {
850
+ const isStreamingOrString = kind === "streaming" || typeof value === "string";
851
+ const muted = kind === "input";
852
+ return /* @__PURE__ */ jsx(
853
+ "pre",
854
+ {
855
+ className: cn(
856
+ "overflow-auto rounded bg-background/60 p-1.5 font-mono",
857
+ kind === "input" ? "max-h-32" : "max-h-48",
858
+ muted ? "text-muted-foreground" : "text-foreground/90"
859
+ ),
860
+ children: isStreamingOrString ? String(value) : safeStringify(value)
861
+ }
862
+ );
863
+ }
864
+ __name(DefaultPayload, "DefaultPayload");
865
+ function safeStringify(value) {
866
+ try {
867
+ return JSON.stringify(value, null, 2);
868
+ } catch {
869
+ return String(value);
870
+ }
871
+ }
872
+ __name(safeStringify, "safeStringify");
873
+ function MessageActionsRaw({
874
+ role,
875
+ onCopy,
876
+ onRegenerate,
877
+ onEdit,
878
+ onDelete,
879
+ hideOn,
880
+ className
881
+ }) {
882
+ if (hideOn?.includes(role)) return null;
883
+ return /* @__PURE__ */ jsxs(
884
+ "div",
885
+ {
886
+ className: cn(
887
+ "mt-1 flex items-center gap-0.5 opacity-0 transition-opacity group-hover/msg:opacity-100 focus-within:opacity-100",
888
+ className
889
+ ),
890
+ children: [
891
+ onCopy ? /* @__PURE__ */ jsx(ActionButton, { onClick: onCopy, label: "Copy", icon: Copy }) : null,
892
+ onRegenerate && role === "assistant" ? /* @__PURE__ */ jsx(ActionButton, { onClick: onRegenerate, label: "Regenerate", icon: RefreshCw }) : null,
893
+ onEdit && role === "user" ? /* @__PURE__ */ jsx(ActionButton, { onClick: onEdit, label: "Edit", icon: Pencil }) : null,
894
+ onDelete ? /* @__PURE__ */ jsx(ActionButton, { onClick: onDelete, label: "Delete", icon: Trash, destructive: true }) : null
895
+ ]
896
+ }
897
+ );
898
+ }
899
+ __name(MessageActionsRaw, "MessageActionsRaw");
900
+ var ActionButton = memo(/* @__PURE__ */ __name(function ActionButton2({
901
+ onClick,
902
+ label,
903
+ icon: Icon,
904
+ destructive
905
+ }) {
906
+ const styles = useChatDestructiveStyles();
907
+ return /* @__PURE__ */ jsx(
908
+ "button",
909
+ {
910
+ type: "button",
911
+ onClick,
912
+ "aria-label": label,
913
+ className: cn(
914
+ "rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
915
+ destructive && cn(styles.hover, "hover:text-destructive")
916
+ ),
917
+ children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3" })
918
+ }
919
+ );
920
+ }, "ActionButton"));
921
+ var MessageActions = memo(MessageActionsRaw);
922
+ var MessageBubbleInner = /* @__PURE__ */ __name(({
923
+ message,
924
+ isUser: isUserProp,
925
+ showAvatar = true,
926
+ avatarSrc,
927
+ avatarFallback,
928
+ user,
929
+ assistant,
930
+ showTimestamp = false,
931
+ showActions = true,
932
+ isCompact = false,
933
+ className,
934
+ beforeContent,
935
+ afterContent,
936
+ toolCallsRenderer,
937
+ toolCallsProps,
938
+ sourcesRenderer,
939
+ attachmentsRenderer,
940
+ attachmentRenderers,
941
+ onAttachmentOpen,
942
+ onCopy,
943
+ onRegenerate,
944
+ onEdit,
945
+ onDelete,
946
+ messageActionsExtra,
947
+ streamingIndicator
948
+ }) => {
949
+ const isUser = isUserProp ?? message.role === "user";
950
+ const isStreaming = !!message.isStreaming;
951
+ const isErr = !!message.isError;
952
+ const { surface: bubbleSurface } = useChatBubbleStyles(
953
+ isUser ? "user" : "assistant",
954
+ isErr
955
+ );
956
+ const ctx = useChatContextOptional();
957
+ const persona = resolvePersona(
958
+ message,
959
+ user ?? ctx?.config.user,
960
+ assistant ?? ctx?.config.assistant
961
+ );
962
+ const initials = deriveInitials(persona, message.role);
963
+ const personaName = persona.name ?? (isUser ? "You" : "Assistant");
964
+ return /* @__PURE__ */ jsxs(
965
+ "div",
966
+ {
967
+ role: "article",
968
+ "aria-label": `${personaName} said: ${message.content.slice(0, 80)}`,
969
+ "aria-busy": isStreaming || void 0,
970
+ "data-role": message.role,
971
+ className: cn(
972
+ "group/msg flex gap-2.5 px-2.5 py-2",
973
+ isUser ? "flex-row-reverse" : "flex-row",
974
+ className
975
+ ),
976
+ children: [
977
+ showAvatar ? /* @__PURE__ */ jsxs(
978
+ Avatar,
979
+ {
980
+ className: "size-7 shrink-0",
981
+ title: persona.description ?? personaName,
982
+ children: [
983
+ avatarSrc || persona.avatarUrl ? /* @__PURE__ */ jsx(AvatarImage, { src: avatarSrc ?? persona.avatarUrl, alt: personaName }) : null,
984
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "text-[10px]", children: avatarFallback ?? initials })
985
+ ]
986
+ }
987
+ ) : null,
988
+ /* @__PURE__ */ jsxs("div", { className: cn("min-w-0 flex-1", isUser && "flex flex-col items-end"), children: [
989
+ beforeContent,
990
+ message.attachments?.length ? attachmentsRenderer ? attachmentsRenderer(message.attachments) : /* @__PURE__ */ jsx("div", { className: "mb-1.5 w-full", children: attachmentRenderers ? /* @__PURE__ */ jsx(
991
+ AttachmentsList,
992
+ {
993
+ attachments: message.attachments,
994
+ renderers: attachmentRenderers,
995
+ onClick: onAttachmentOpen,
996
+ className: isUser ? "items-end" : void 0
997
+ }
998
+ ) : /* @__PURE__ */ jsx(
999
+ AttachmentsGrid,
1000
+ {
1001
+ attachments: message.attachments,
1002
+ onClick: onAttachmentOpen,
1003
+ className: isUser ? "justify-end" : void 0
1004
+ }
1005
+ ) }) : null,
1006
+ /* @__PURE__ */ jsxs(
1007
+ "div",
1008
+ {
1009
+ className: cn(
1010
+ "inline-block max-w-full rounded-2xl px-3.5 py-2 text-sm",
1011
+ bubbleSurface
1012
+ ),
1013
+ children: [
1014
+ isStreaming && message.toolActivity ? /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: streamingIndicator ? streamingIndicator(message) : /* @__PURE__ */ jsx(StreamingIndicator, { label: message.toolActivity }) }) : null,
1015
+ message.content || !isStreaming ? /* @__PURE__ */ jsx(
1016
+ MarkdownMessage,
1017
+ {
1018
+ content: message.content || (isErr ? "*Failed to generate a response.*" : ""),
1019
+ isUser,
1020
+ isCompact,
1021
+ plainText: isStreaming
1022
+ }
1023
+ ) : streamingIndicator ? streamingIndicator(message) : /* @__PURE__ */ jsx(StreamingIndicator, {})
1024
+ ]
1025
+ }
1026
+ ),
1027
+ message.toolCalls?.length ? toolCallsRenderer ? toolCallsRenderer(message.toolCalls) : /* @__PURE__ */ jsx(ToolCalls, { calls: message.toolCalls, ...toolCallsProps }) : null,
1028
+ message.sources?.length && !isStreaming ? sourcesRenderer ? sourcesRenderer(message.sources) : /* @__PURE__ */ jsx(Sources, { sources: message.sources }) : null,
1029
+ showActions && !isStreaming ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
1030
+ /* @__PURE__ */ jsx(
1031
+ MessageActions,
1032
+ {
1033
+ role: message.role,
1034
+ onCopy,
1035
+ onRegenerate,
1036
+ onEdit,
1037
+ onDelete
1038
+ }
1039
+ ),
1040
+ messageActionsExtra ? messageActionsExtra(message) : null
1041
+ ] }) : null,
1042
+ showTimestamp ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-[10px] text-muted-foreground", children: new Date(message.createdAt).toLocaleTimeString() }) : null,
1043
+ afterContent
1044
+ ] })
1045
+ ]
1046
+ }
1047
+ );
1048
+ }, "MessageBubbleInner");
1049
+ var MessageBubble = memo(MessageBubbleInner, (prev, next) => {
1050
+ const a = prev.message;
1051
+ const b = next.message;
1052
+ return a.id === b.id && a.content === b.content && a.isStreaming === b.isStreaming && a.isError === b.isError && (a.version ?? 0) === (b.version ?? 0) && a.toolActivity === b.toolActivity && a.toolCalls === b.toolCalls && a.sources === b.sources && a.attachments === b.attachments;
1053
+ });
1054
+ MessageBubble.displayName = "MessageBubble";
1055
+ var MessageList = forwardRef(/* @__PURE__ */ __name(function MessageList2({
1056
+ messages: messagesProp,
1057
+ renderItem,
1058
+ renderEmpty,
1059
+ isLoadingMore: isLoadingMoreProp,
1060
+ onStartReached,
1061
+ className,
1062
+ itemClassName,
1063
+ noVirtualize = false,
1064
+ defaultItemHeight: _deprecatedDefaultItemHeight,
1065
+ onAtBottomChange,
1066
+ atBottomThreshold = 120,
1067
+ scrollAnchorId
1068
+ }, ref) {
1069
+ const ctx = useChatContextOptional();
1070
+ const messages = messagesProp ?? ctx?.messages ?? [];
1071
+ const isLoadingMore = isLoadingMoreProp ?? ctx?.isLoadingMore ?? false;
1072
+ const { copyToClipboard } = useCopy();
1073
+ const virtuosoRef = useRef(null);
1074
+ const scrollerRef = useRef(null);
1075
+ const [isScrollable, setIsScrollable] = useState(false);
1076
+ const lastReportedAtBottomRef = useRef(null);
1077
+ const reportAtBottom = useCallback(
1078
+ (value) => {
1079
+ if (lastReportedAtBottomRef.current === value) return;
1080
+ lastReportedAtBottomRef.current = value;
1081
+ onAtBottomChange?.(value);
1082
+ },
1083
+ [onAtBottomChange]
1084
+ );
1085
+ const didInitialScrollRef = useRef(false);
1086
+ useImperativeHandle(
1087
+ ref,
1088
+ () => ({
1089
+ scrollToBottom: /* @__PURE__ */ __name((smooth = false) => {
1090
+ virtuosoRef.current?.scrollToIndex({
1091
+ index: "LAST",
1092
+ behavior: smooth ? "smooth" : "auto",
1093
+ align: "end"
1094
+ });
1095
+ }, "scrollToBottom"),
1096
+ scrollToIndex: /* @__PURE__ */ __name((index, smooth = false) => {
1097
+ virtuosoRef.current?.scrollToIndex({
1098
+ index,
1099
+ behavior: smooth ? "smooth" : "auto"
1100
+ });
1101
+ }, "scrollToIndex")
1102
+ }),
1103
+ []
1104
+ );
1105
+ const defaultRenderItem = useCallback(
1106
+ (m) => /* @__PURE__ */ jsx("div", { className: itemClassName, children: /* @__PURE__ */ jsx(
1107
+ MessageBubble,
1108
+ {
1109
+ message: m,
1110
+ onCopy: () => void copyToClipboard(m.content),
1111
+ onRegenerate: ctx ? () => void ctx.regenerate(m.id) : void 0,
1112
+ onDelete: ctx ? () => ctx.deleteMessage(m.id) : void 0
1113
+ }
1114
+ ) }),
1115
+ [itemClassName, ctx, copyToClipboard]
1116
+ );
1117
+ const itemRenderer = renderItem ?? defaultRenderItem;
1118
+ useEffect(() => {
1119
+ if (didInitialScrollRef.current) return;
1120
+ if (messages.length === 0) return;
1121
+ didInitialScrollRef.current = true;
1122
+ const id = requestAnimationFrame(() => {
1123
+ virtuosoRef.current?.scrollToIndex({
1124
+ index: "LAST",
1125
+ align: "end",
1126
+ behavior: "auto"
1127
+ });
1128
+ });
1129
+ return () => cancelAnimationFrame(id);
1130
+ }, [messages.length]);
1131
+ useEffect(() => {
1132
+ if (scrollAnchorId == null) return;
1133
+ if (!didInitialScrollRef.current) return;
1134
+ let raf1 = 0;
1135
+ let raf2 = 0;
1136
+ raf1 = requestAnimationFrame(() => {
1137
+ raf2 = requestAnimationFrame(() => {
1138
+ virtuosoRef.current?.scrollToIndex({
1139
+ index: "LAST",
1140
+ align: "end",
1141
+ behavior: "smooth"
1142
+ });
1143
+ });
1144
+ });
1145
+ return () => {
1146
+ cancelAnimationFrame(raf1);
1147
+ cancelAnimationFrame(raf2);
1148
+ };
1149
+ }, [scrollAnchorId]);
1150
+ useEffect(() => {
1151
+ const el = scrollerRef.current;
1152
+ if (!el || el === window || !(el instanceof HTMLElement)) return;
1153
+ const probe = /* @__PURE__ */ __name(() => {
1154
+ const scrollable = el.scrollHeight > el.clientHeight + 1;
1155
+ setIsScrollable(scrollable);
1156
+ if (!scrollable) reportAtBottom(true);
1157
+ }, "probe");
1158
+ probe();
1159
+ const ro = new ResizeObserver(probe);
1160
+ ro.observe(el);
1161
+ return () => ro.disconnect();
1162
+ }, [reportAtBottom, messages.length]);
1163
+ const computeItemKey = useCallback(
1164
+ (index, m) => m?.id ?? index,
1165
+ []
1166
+ );
1167
+ const startReachedHandler = useMemo(() => {
1168
+ if (!onStartReached) return void 0;
1169
+ let inFlight = false;
1170
+ return () => {
1171
+ if (inFlight || isLoadingMore) return;
1172
+ inFlight = true;
1173
+ try {
1174
+ onStartReached();
1175
+ } finally {
1176
+ queueMicrotask(() => {
1177
+ inFlight = false;
1178
+ });
1179
+ }
1180
+ };
1181
+ }, [onStartReached, isLoadingMore]);
1182
+ if (messages.length === 0) {
1183
+ return /* @__PURE__ */ jsx(
1184
+ "div",
1185
+ {
1186
+ role: "log",
1187
+ "aria-live": "polite",
1188
+ "aria-atomic": "false",
1189
+ className: cn("flex-1 overflow-y-auto", className),
1190
+ children: renderEmpty?.() ?? null
1191
+ }
1192
+ );
1193
+ }
1194
+ if (noVirtualize) {
1195
+ return /* @__PURE__ */ jsxs(
1196
+ "div",
1197
+ {
1198
+ role: "log",
1199
+ "aria-live": "polite",
1200
+ "aria-atomic": "false",
1201
+ className: cn("flex-1 overflow-y-auto", className),
1202
+ children: [
1203
+ isLoadingMore ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx(Spinner, { className: "size-4 text-muted-foreground" }) }) : null,
1204
+ messages.map((m, i) => /* @__PURE__ */ jsx("div", { children: itemRenderer(m, i) }, m.id ?? i))
1205
+ ]
1206
+ }
1207
+ );
1208
+ }
1209
+ return /* @__PURE__ */ jsx(
1210
+ Virtuoso,
1211
+ {
1212
+ ref: virtuosoRef,
1213
+ role: "log",
1214
+ "aria-live": "polite",
1215
+ "aria-atomic": "false",
1216
+ className: cn("flex-1", className),
1217
+ data: messages,
1218
+ computeItemKey,
1219
+ itemContent: (index, m) => m ? itemRenderer(m, index) : null,
1220
+ initialTopMostItemIndex: messages.length > 0 ? messages.length - 1 : 0,
1221
+ atBottomThreshold,
1222
+ followOutput: (isAtBottom) => isAtBottom ? "auto" : false,
1223
+ scrollerRef: (el) => {
1224
+ scrollerRef.current = el;
1225
+ },
1226
+ atBottomStateChange: (atBottom) => {
1227
+ reportAtBottom(!isScrollable ? true : atBottom);
1228
+ },
1229
+ startReached: startReachedHandler,
1230
+ components: isLoadingMore ? {
1231
+ Header: /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx(Spinner, { className: "size-4 text-muted-foreground" }) }), "Header")
1232
+ } : EMPTY_COMPONENTS
1233
+ }
1234
+ );
1235
+ }, "MessageList"));
1236
+ var EMPTY_COMPONENTS = {};
1237
+ function ChatRoot(props) {
1238
+ const { transport, config, initialSessionId, autoCreateSession, streaming, audio, debug, className, listClassName, ...slots } = props;
1239
+ return /* @__PURE__ */ jsx(
1240
+ ChatProvider,
1241
+ {
1242
+ transport,
1243
+ config,
1244
+ initialSessionId,
1245
+ autoCreateSession,
1246
+ streaming,
1247
+ audio,
1248
+ debug,
1249
+ children: /* @__PURE__ */ jsx(ChatRootShell, { className, listClassName, slots })
1250
+ }
1251
+ );
1252
+ }
1253
+ __name(ChatRoot, "ChatRoot");
1254
+ function ChatRootShell({ className, listClassName, slots }) {
1255
+ const chat = useChatContext();
1256
+ const composer = useChatComposer({
1257
+ onSubmit: /* @__PURE__ */ __name((content, attachments) => chat.sendMessage(content, attachments), "onSubmit"),
1258
+ disabled: chat.isStreaming
1259
+ });
1260
+ const onMessagesMouseUp = useFocusOnEmptyClick({
1261
+ enabled: slots.focusOnEmptyClick !== false
1262
+ });
1263
+ useAutoFocusOnStreamEnd();
1264
+ const listRef = useRef(null);
1265
+ const [isAtBottom, setIsAtBottom] = useState(true);
1266
+ const lastUserMessageId = useMemo(() => {
1267
+ const msgs = chat.messages;
1268
+ for (let i = msgs.length - 1; i >= 0; i -= 1) {
1269
+ if (msgs[i].role === "user") return msgs[i].id;
1270
+ }
1271
+ return null;
1272
+ }, [chat.messages]);
1273
+ const handleStartReached = chat.hasMore && !chat.isLoadingMore ? () => void chat.loadMore() : void 0;
1274
+ const greeting = chat.config.greeting ?? "How can I help?";
1275
+ const description = chat.config.description;
1276
+ const suggestions = chat.config.suggestions;
1277
+ const headerNode = slots.renderHeader ? slots.renderHeader(chat) : slots.header;
1278
+ const emptyNode = slots.empty ?? (slots.renderEmpty ? slots.renderEmpty({ setValue: composer.setValue, focus: composer.focus }) : /* @__PURE__ */ jsx(
1279
+ EmptyState,
1280
+ {
1281
+ greeting,
1282
+ description,
1283
+ suggestions,
1284
+ onPickSuggestion: (prompt) => {
1285
+ composer.setValue(prompt);
1286
+ composer.focus();
1287
+ }
1288
+ }
1289
+ ));
1290
+ const renderItem = slots.renderMessage ?? ((m) => /* @__PURE__ */ jsx(
1291
+ MessageBubble,
1292
+ {
1293
+ message: m,
1294
+ toolCallsProps: slots.toolCallsProps,
1295
+ attachmentRenderers: slots.attachmentRenderers,
1296
+ onAttachmentOpen: slots.onAttachmentOpen,
1297
+ onCopy: () => copy(m.content),
1298
+ onRegenerate: () => void chat.regenerate(m.id),
1299
+ onDelete: () => chat.deleteMessage(m.id)
1300
+ },
1301
+ m.id
1302
+ ));
1303
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative flex h-full min-h-0 flex-col overflow-hidden", className), children: [
1304
+ slots.banner ?? null,
1305
+ headerNode ?? null,
1306
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col", onMouseUp: onMessagesMouseUp, children: [
1307
+ /* @__PURE__ */ jsx(
1308
+ ErrorBanner,
1309
+ {
1310
+ error: chat.error,
1311
+ onDismiss: chat.error ? () => chat.clearMessages() : void 0,
1312
+ onRetry: chat.error ? () => void chat.regenerate() : void 0
1313
+ }
1314
+ ),
1315
+ /* @__PURE__ */ jsx(
1316
+ MessageList,
1317
+ {
1318
+ ref: listRef,
1319
+ renderItem,
1320
+ renderEmpty: () => /* @__PURE__ */ jsx(Fragment, { children: emptyNode }),
1321
+ className: listClassName,
1322
+ onStartReached: handleStartReached,
1323
+ onAtBottomChange: setIsAtBottom,
1324
+ scrollAnchorId: lastUserMessageId
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-2 flex justify-center", children: slots.jumpToLatest ?? /* @__PURE__ */ jsx(
1328
+ JumpToLatest,
1329
+ {
1330
+ visible: !isAtBottom,
1331
+ onClick: () => listRef.current?.scrollToBottom(true)
1332
+ }
1333
+ ) })
1334
+ ] }),
1335
+ !slots.hideComposer && /* @__PURE__ */ jsx(
1336
+ Composer,
1337
+ {
1338
+ composer,
1339
+ placeholder: chat.config.placeholder,
1340
+ showAttachmentButton: slots.showAttachmentButton,
1341
+ onPickFiles: slots.onPickFiles,
1342
+ toolbarStart: slots.composerToolbarStart,
1343
+ toolbarEnd: slots.composerToolbarEnd,
1344
+ attachmentTray: slots.composerAttachmentTray,
1345
+ size: slots.composerSize
1346
+ }
1347
+ ),
1348
+ slots.footer ?? null
1349
+ ] });
1350
+ }
1351
+ __name(ChatRootShell, "ChatRootShell");
1352
+ function copy(text) {
1353
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
1354
+ void navigator.clipboard.writeText(text);
1355
+ }
1356
+ }
1357
+ __name(copy, "copy");
1358
+
1359
+ export { Attachments, AttachmentsGrid, AttachmentsList, ChatRoot, Composer, EmptyState, ErrorBanner, JumpToLatest, MessageActions, MessageBubble, MessageList, Sources, StreamingIndicator, ToolCalls, deriveInitials, isSubmittableDraft, resolvePersona, sanitizeDraft, useAutoFocusOnStreamEnd, useChatBubbleStyles, useChatComposer, useChatDestructiveStyles, useChatRoleStyles, useFocusOnEmptyClick, useRegisterComposer };
1360
+ //# sourceMappingURL=chunk-6ZX2G25W.mjs.map
1361
+ //# sourceMappingURL=chunk-6ZX2G25W.mjs.map