@mieweb/ui 0.9.0-dev.4 → 0.9.0-dev.6

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 (50) hide show
  1. package/dist/{chunk-DG6LIEUJ.js → chunk-22CYDCXA.js} +13 -1183
  2. package/dist/chunk-22CYDCXA.js.map +1 -0
  3. package/dist/chunk-3SD7PDCV.js +1878 -0
  4. package/dist/chunk-3SD7PDCV.js.map +1 -0
  5. package/dist/{chunk-USZ3VEPE.js → chunk-NORSXAXL.js} +853 -779
  6. package/dist/chunk-NORSXAXL.js.map +1 -0
  7. package/dist/{chunk-Z3RYJWSV.cjs → chunk-PY7XAG4D.cjs} +3 -4
  8. package/dist/chunk-PY7XAG4D.cjs.map +1 -0
  9. package/dist/chunk-T6PTKWM6.cjs +1912 -0
  10. package/dist/chunk-T6PTKWM6.cjs.map +1 -0
  11. package/dist/{chunk-DH7I5HMK.cjs → chunk-WIAQQNFU.cjs} +12 -1184
  12. package/dist/chunk-WIAQQNFU.cjs.map +1 -0
  13. package/dist/{chunk-XEZCHQHB.js → chunk-X5YM3KXY.js} +3 -4
  14. package/dist/chunk-X5YM3KXY.js.map +1 -0
  15. package/dist/{chunk-3MHG4HYB.cjs → chunk-XCNDZNY7.cjs} +855 -780
  16. package/dist/chunk-XCNDZNY7.cjs.map +1 -0
  17. package/dist/components/SuperChat/index.cjs +8 -8
  18. package/dist/components/SuperChat/index.d.cts +4 -4
  19. package/dist/components/SuperChat/index.d.ts +4 -4
  20. package/dist/components/SuperChat/index.js +2 -2
  21. package/dist/components/SuperChat/plugins/index.cjs +10 -3
  22. package/dist/components/SuperChat/plugins/index.cjs.map +1 -1
  23. package/dist/components/SuperChat/plugins/index.d.cts +3 -3
  24. package/dist/components/SuperChat/plugins/index.d.ts +3 -3
  25. package/dist/components/SuperChat/plugins/index.js +9 -2
  26. package/dist/components/SuperChat/plugins/index.js.map +1 -1
  27. package/dist/index.cjs +171 -114
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +471 -410
  30. package/dist/index.d.ts +471 -410
  31. package/dist/index.js +95 -41
  32. package/dist/index.js.map +1 -1
  33. package/dist/styles.css +1 -1
  34. package/dist/tailwind-preset.cjs +4 -4
  35. package/dist/tailwind-preset.js +1 -1
  36. package/dist/{types-DZDroUW8.d.cts → types-B97JNRNQ.d.cts} +1 -1
  37. package/dist/{types-B2RLmdVd.d.cts → types-CnPEwzlp.d.cts} +6 -2
  38. package/dist/{types-B2RLmdVd.d.ts → types-CnPEwzlp.d.ts} +6 -2
  39. package/dist/{types-9vK_YdEb.d.ts → types-gZXrfLj6.d.ts} +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunk-3MHG4HYB.cjs.map +0 -1
  42. package/dist/chunk-5VXRKOPR.cjs +0 -735
  43. package/dist/chunk-5VXRKOPR.cjs.map +0 -1
  44. package/dist/chunk-DG6LIEUJ.js.map +0 -1
  45. package/dist/chunk-DH7I5HMK.cjs.map +0 -1
  46. package/dist/chunk-IF3X2WZP.js +0 -705
  47. package/dist/chunk-IF3X2WZP.js.map +0 -1
  48. package/dist/chunk-USZ3VEPE.js.map +0 -1
  49. package/dist/chunk-XEZCHQHB.js.map +0 -1
  50. package/dist/chunk-Z3RYJWSV.cjs.map +0 -1
@@ -0,0 +1,1878 @@
1
+ import { Dropdown, DropdownItem, DropdownSeparator } from './chunk-TZX76ZNZ.js';
2
+ import { AlertTriangle, X2, Paperclip, Plus, Mic, Square, Loader2, ArrowUp, Check, Bot, ChevronUp } from './chunk-BKSPDTX4.js';
3
+ import { cn } from './chunk-TQONQSJO.js';
4
+ import { useEscapeKey } from './chunk-T4ME7QCT.js';
5
+ import { useClickOutside } from './chunk-62ZOA6PX.js';
6
+ import { useAnchoredPosition } from './chunk-WSNMQQ4P.js';
7
+ import * as React from 'react';
8
+ import { createPortal } from 'react-dom';
9
+ import { ChevronUp as ChevronUp$1, Check as Check$1, ChevronRight, ChevronLeft } from 'lucide-react';
10
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
11
+
12
+ var MENU_OFFSET_PX = 6;
13
+ var MENU_MAX_HEIGHT_PX = 320;
14
+ function optionKey(option) {
15
+ return `${option.provider}\0${option.model}`;
16
+ }
17
+ function renderedOptionKey(option, index) {
18
+ return `${option.id ?? optionKey(option)}\0${index}`;
19
+ }
20
+ function groupByProvider(models) {
21
+ const groups = /* @__PURE__ */ new Map();
22
+ for (const model of models) {
23
+ const group = groups.get(model.provider);
24
+ if (group) {
25
+ group.push(model);
26
+ } else {
27
+ groups.set(model.provider, [model]);
28
+ }
29
+ }
30
+ return Array.from(groups, ([provider, options]) => ({ provider, options }));
31
+ }
32
+ function getProviderLabel(models, provider) {
33
+ return models.find((model) => model.provider === provider && model.providerLabel)?.providerLabel ?? provider;
34
+ }
35
+ function ComposerModelSelector({
36
+ models,
37
+ value,
38
+ providerFilter,
39
+ onProviderFilterChange,
40
+ onChange,
41
+ disabled = false,
42
+ variant = "default",
43
+ className,
44
+ boundaryRef,
45
+ placeholder = "Model",
46
+ anyLabel = "Any",
47
+ emptyLabel = "No models",
48
+ ariaLabel = "Model",
49
+ effortOptions,
50
+ effort = null,
51
+ defaultEffort,
52
+ onEffortChange,
53
+ effortLabel = "Effort",
54
+ effortHint,
55
+ defaultBadgeLabel = "Default",
56
+ backLabel = "Back"
57
+ }) {
58
+ const [open, setOpen] = React.useState(false);
59
+ const [view, setView] = React.useState("models");
60
+ const [effortHighlight, setEffortHighlight] = React.useState(0);
61
+ const [internalProviderFilter, setInternalProviderFilter] = React.useState(null);
62
+ const [highlightedIndex, setHighlightedIndex] = React.useState(0);
63
+ const listRef = React.useRef(null);
64
+ const effortListRef = React.useRef(null);
65
+ const menuId = React.useId();
66
+ const {
67
+ anchorRef: triggerRef,
68
+ floatingRef: menuRef,
69
+ style: menuStyle
70
+ } = useAnchoredPosition({
71
+ open,
72
+ placement: "top-start",
73
+ offset: MENU_OFFSET_PX,
74
+ matchMinWidth: true,
75
+ maxHeight: MENU_MAX_HEIGHT_PX,
76
+ allowFlip: false,
77
+ boundaryRef
78
+ });
79
+ const activeProviderFilter = providerFilter === void 0 ? internalProviderFilter : providerFilter;
80
+ const selectedKey = value ? optionKey(value) : null;
81
+ const selectedOption = models.find(
82
+ (model) => optionKey(model) === selectedKey
83
+ );
84
+ const providers = React.useMemo(
85
+ () => Array.from(new Set(models.map((model) => model.provider))),
86
+ [models]
87
+ );
88
+ const providerLabels = React.useMemo(
89
+ () => new Map(
90
+ providers.map((provider) => [
91
+ provider,
92
+ getProviderLabel(models, provider)
93
+ ])
94
+ ),
95
+ [models, providers]
96
+ );
97
+ const filteredModels = React.useMemo(
98
+ () => activeProviderFilter === null ? models : models.filter((model) => model.provider === activeProviderFilter),
99
+ [activeProviderFilter, models]
100
+ );
101
+ const groupedModels = React.useMemo(
102
+ () => groupByProvider(filteredModels),
103
+ [filteredModels]
104
+ );
105
+ const groupedRenderedModels = React.useMemo(() => {
106
+ let index = 0;
107
+ return groupedModels.map((group) => ({
108
+ provider: group.provider,
109
+ options: group.options.map((option) => {
110
+ const rendered = {
111
+ option,
112
+ index,
113
+ key: renderedOptionKey(option, index)
114
+ };
115
+ index += 1;
116
+ return rendered;
117
+ })
118
+ }));
119
+ }, [groupedModels]);
120
+ const renderedModels = React.useMemo(
121
+ () => groupedRenderedModels.flatMap((group) => group.options),
122
+ [groupedRenderedModels]
123
+ );
124
+ const getOptionId = React.useCallback(
125
+ (index) => `${menuId}-option-${index}`,
126
+ [menuId]
127
+ );
128
+ const getEffortOptionId = React.useCallback(
129
+ (index) => `${menuId}-effort-option-${index}`,
130
+ [menuId]
131
+ );
132
+ const activeOptionId = open && renderedModels[highlightedIndex] ? getOptionId(highlightedIndex) : void 0;
133
+ const selectedRenderedIndex = React.useMemo(
134
+ () => selectedKey ? renderedModels.findIndex(
135
+ (renderedModel) => optionKey(renderedModel.option) === selectedKey
136
+ ) : -1,
137
+ [renderedModels, selectedKey]
138
+ );
139
+ const setProvider = React.useCallback(
140
+ (provider) => {
141
+ if (providerFilter === void 0) {
142
+ setInternalProviderFilter(provider);
143
+ }
144
+ onProviderFilterChange?.(provider);
145
+ },
146
+ [onProviderFilterChange, providerFilter]
147
+ );
148
+ const close = React.useCallback(
149
+ (restoreFocus = true) => {
150
+ setOpen(false);
151
+ if (restoreFocus) {
152
+ triggerRef.current?.focus({ preventScroll: true });
153
+ }
154
+ },
155
+ [triggerRef]
156
+ );
157
+ const selectModel = React.useCallback(
158
+ (model) => {
159
+ onChange({ provider: model.provider, model: model.model });
160
+ close();
161
+ },
162
+ [close, onChange]
163
+ );
164
+ const efforts = React.useMemo(() => effortOptions ?? [], [effortOptions]);
165
+ const activeEffortOptionId = open && efforts[effortHighlight] ? getEffortOptionId(effortHighlight) : void 0;
166
+ const selectedEffortOption = efforts.find(
167
+ (option) => option.value === effort
168
+ );
169
+ const selectEffort = React.useCallback(
170
+ (value2) => {
171
+ onEffortChange?.(value2);
172
+ close();
173
+ },
174
+ [close, onEffortChange]
175
+ );
176
+ const handleEffortKeyDown = (event) => {
177
+ if (efforts.length === 0) return;
178
+ if (event.key === "ArrowDown") {
179
+ event.preventDefault();
180
+ setEffortHighlight((current) => (current + 1) % efforts.length);
181
+ }
182
+ if (event.key === "ArrowUp") {
183
+ event.preventDefault();
184
+ setEffortHighlight(
185
+ (current) => (current - 1 + efforts.length) % efforts.length
186
+ );
187
+ }
188
+ if (event.key === "Home") {
189
+ event.preventDefault();
190
+ setEffortHighlight(0);
191
+ }
192
+ if (event.key === "End") {
193
+ event.preventDefault();
194
+ setEffortHighlight(efforts.length - 1);
195
+ }
196
+ if (event.key === "ArrowLeft" || event.key === "Backspace") {
197
+ event.preventDefault();
198
+ setView("models");
199
+ }
200
+ if (event.key === "Enter" || event.key === " ") {
201
+ event.preventDefault();
202
+ const highlighted = efforts[effortHighlight];
203
+ if (highlighted) selectEffort(highlighted.value);
204
+ }
205
+ };
206
+ const outsideRefs = React.useMemo(
207
+ () => [triggerRef, menuRef],
208
+ [triggerRef, menuRef]
209
+ );
210
+ useClickOutside(outsideRefs, () => close(false), open);
211
+ useEscapeKey(close, open);
212
+ React.useEffect(() => {
213
+ if (!open) return;
214
+ setHighlightedIndex(selectedRenderedIndex >= 0 ? selectedRenderedIndex : 0);
215
+ }, [activeProviderFilter, open, selectedRenderedIndex]);
216
+ React.useEffect(() => {
217
+ if (!open) return;
218
+ setView("models");
219
+ }, [open]);
220
+ React.useEffect(() => {
221
+ if (!open) return;
222
+ const current = efforts.findIndex((option) => option.value === effort);
223
+ setEffortHighlight(current >= 0 ? current : 0);
224
+ }, [effort, efforts, open]);
225
+ React.useEffect(() => {
226
+ if (!open) return;
227
+ setHighlightedIndex(
228
+ (current) => Math.min(current, Math.max(renderedModels.length - 1, 0))
229
+ );
230
+ }, [open, renderedModels.length]);
231
+ React.useEffect(() => {
232
+ if (!open) return;
233
+ requestAnimationFrame(() => {
234
+ const target = view === "effort" ? effortListRef.current : listRef.current;
235
+ target?.focus({ preventScroll: true });
236
+ });
237
+ }, [open, view]);
238
+ const handleTriggerKeyDown = (event) => {
239
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
240
+ event.preventDefault();
241
+ setOpen(true);
242
+ }
243
+ };
244
+ const handleMenuKeyDown = (event) => {
245
+ if (renderedModels.length === 0) return;
246
+ if (event.key === "ArrowDown") {
247
+ event.preventDefault();
248
+ setHighlightedIndex((current) => (current + 1) % renderedModels.length);
249
+ }
250
+ if (event.key === "ArrowUp") {
251
+ event.preventDefault();
252
+ setHighlightedIndex(
253
+ (current) => (current - 1 + renderedModels.length) % renderedModels.length
254
+ );
255
+ }
256
+ if (event.key === "Home") {
257
+ event.preventDefault();
258
+ setHighlightedIndex(0);
259
+ }
260
+ if (event.key === "End") {
261
+ event.preventDefault();
262
+ setHighlightedIndex(renderedModels.length - 1);
263
+ }
264
+ if (event.key === "Enter" || event.key === " ") {
265
+ event.preventDefault();
266
+ const highlightedModel = renderedModels[highlightedIndex]?.option;
267
+ if (highlightedModel) {
268
+ selectModel(highlightedModel);
269
+ }
270
+ }
271
+ };
272
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
273
+ /* @__PURE__ */ jsxs(
274
+ "button",
275
+ {
276
+ ref: triggerRef,
277
+ type: "button",
278
+ disabled,
279
+ "aria-haspopup": "listbox",
280
+ "aria-expanded": open,
281
+ "aria-controls": open ? menuId : void 0,
282
+ onClick: () => setOpen((current) => !current),
283
+ onKeyDown: handleTriggerKeyDown,
284
+ "data-slot": "composer-model-selector-trigger",
285
+ className: cn(
286
+ variant === "ghost" ? [
287
+ "inline-flex max-w-full min-w-0 items-center gap-1 rounded-md px-2 py-1 text-xs",
288
+ "text-neutral-600 dark:text-neutral-400",
289
+ "transition-colors duration-150",
290
+ "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-700 dark:hover:text-white",
291
+ "focus-visible:ring-primary-500 focus-visible:ring-2 focus-visible:outline-none",
292
+ "disabled:pointer-events-none disabled:opacity-40"
293
+ ] : [
294
+ "inline-flex h-8 max-w-full items-center gap-1.5 rounded-full border px-2.5 text-sm font-medium",
295
+ "border-border bg-background text-foreground shadow-sm",
296
+ "hover:bg-muted/50",
297
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
298
+ "disabled:cursor-not-allowed disabled:opacity-50"
299
+ ],
300
+ className
301
+ ),
302
+ children: [
303
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: selectedOption?.label ?? selectedOption?.model ?? placeholder }),
304
+ selectedEffortOption && /* @__PURE__ */ jsx(
305
+ "span",
306
+ {
307
+ "data-slot": "composer-model-selector-trigger-effort",
308
+ className: "text-muted-foreground min-w-0 truncate font-normal",
309
+ children: selectedEffortOption.label
310
+ }
311
+ ),
312
+ /* @__PURE__ */ jsx(
313
+ ChevronUp$1,
314
+ {
315
+ "aria-hidden": "true",
316
+ className: cn(
317
+ "shrink-0",
318
+ variant === "ghost" ? "h-3 w-3" : "h-3.5 w-3.5"
319
+ )
320
+ }
321
+ )
322
+ ]
323
+ }
324
+ ),
325
+ open && createPortal(
326
+ /* @__PURE__ */ jsxs(
327
+ "div",
328
+ {
329
+ ref: menuRef,
330
+ style: menuStyle,
331
+ "data-slot": "composer-model-selector-menu",
332
+ className: cn(
333
+ "border-border bg-card text-card-foreground flex w-80 max-w-full flex-col rounded-lg border shadow-lg",
334
+ "animate-in fade-in overflow-hidden duration-100"
335
+ ),
336
+ children: [
337
+ view === "models" && /* @__PURE__ */ jsxs(Fragment, { children: [
338
+ /* @__PURE__ */ jsx(
339
+ "div",
340
+ {
341
+ "data-slot": "composer-model-selector-provider-filter",
342
+ className: "border-border flex min-h-9 items-center gap-1 overflow-x-auto overflow-y-hidden border-b p-2",
343
+ children: [null, ...providers].map((provider) => {
344
+ const selected = activeProviderFilter === provider;
345
+ return /* @__PURE__ */ jsx(
346
+ "button",
347
+ {
348
+ type: "button",
349
+ "aria-pressed": selected,
350
+ onMouseDown: (event) => event.preventDefault(),
351
+ onClick: () => setProvider(provider),
352
+ className: cn(
353
+ "inline-flex h-6 shrink-0 items-center justify-center rounded-full px-2.5 text-xs font-medium whitespace-nowrap transition-colors",
354
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
355
+ selected ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
356
+ ),
357
+ children: provider === null ? anyLabel : providerLabels.get(provider) ?? provider
358
+ },
359
+ provider ?? "all-providers"
360
+ );
361
+ })
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx(
365
+ "div",
366
+ {
367
+ ref: listRef,
368
+ id: menuId,
369
+ role: "listbox",
370
+ "aria-label": ariaLabel,
371
+ "aria-activedescendant": activeOptionId,
372
+ tabIndex: -1,
373
+ onKeyDown: handleMenuKeyDown,
374
+ className: "min-h-0 flex-1 overflow-y-auto p-1",
375
+ children: groupedModels.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground px-3 py-4 text-center text-sm", children: emptyLabel }) : groupedRenderedModels.map((group) => /* @__PURE__ */ jsxs(
376
+ "div",
377
+ {
378
+ role: "group",
379
+ "aria-label": providerLabels.get(group.provider) ?? group.provider,
380
+ children: [
381
+ /* @__PURE__ */ jsx("div", { className: "text-muted-foreground px-3 py-1.5 text-xs font-semibold uppercase", children: providerLabels.get(group.provider) ?? group.provider }),
382
+ group.options.map((renderedModel) => {
383
+ const { index, key, option: model } = renderedModel;
384
+ const selected = optionKey(model) === selectedKey;
385
+ const highlighted = index === highlightedIndex;
386
+ return /* @__PURE__ */ jsxs(
387
+ "button",
388
+ {
389
+ id: getOptionId(index),
390
+ type: "button",
391
+ role: "option",
392
+ "aria-selected": selected,
393
+ onMouseDown: (event) => event.preventDefault(),
394
+ onClick: () => selectModel(model),
395
+ onMouseEnter: () => setHighlightedIndex(index),
396
+ className: cn(
397
+ "flex w-full items-center gap-2 rounded-md px-3 py-2 text-start text-sm",
398
+ "focus-visible:ring-ring transition-colors focus-visible:ring-2 focus-visible:outline-none",
399
+ highlighted && "bg-muted",
400
+ selected && "bg-primary/10 text-primary"
401
+ ),
402
+ children: [
403
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: model.label ?? model.model }),
404
+ selected && /* @__PURE__ */ jsx(
405
+ Check$1,
406
+ {
407
+ "aria-hidden": "true",
408
+ className: "text-primary h-4 w-4 shrink-0"
409
+ }
410
+ )
411
+ ]
412
+ },
413
+ key
414
+ );
415
+ })
416
+ ]
417
+ },
418
+ group.provider
419
+ ))
420
+ }
421
+ ),
422
+ efforts.length > 0 && /* @__PURE__ */ jsx("div", { className: "border-border border-t p-1", children: /* @__PURE__ */ jsxs(
423
+ "button",
424
+ {
425
+ type: "button",
426
+ "data-slot": "composer-model-selector-effort-row",
427
+ "aria-haspopup": "listbox",
428
+ onMouseDown: (event) => event.preventDefault(),
429
+ onClick: () => setView("effort"),
430
+ className: cn(
431
+ "flex w-full items-center gap-2 rounded-md px-3 py-2 text-start text-sm",
432
+ "hover:bg-muted transition-colors",
433
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none"
434
+ ),
435
+ children: [
436
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: effortLabel }),
437
+ selectedEffortOption && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground min-w-0 truncate", children: selectedEffortOption.label }),
438
+ /* @__PURE__ */ jsx(
439
+ ChevronRight,
440
+ {
441
+ "aria-hidden": "true",
442
+ className: "text-muted-foreground h-4 w-4 shrink-0 rtl:-scale-x-100"
443
+ }
444
+ )
445
+ ]
446
+ }
447
+ ) })
448
+ ] }),
449
+ view === "effort" && /* @__PURE__ */ jsxs(Fragment, { children: [
450
+ /* @__PURE__ */ jsx("div", { className: "border-border flex items-center gap-1 border-b p-2", children: /* @__PURE__ */ jsxs(
451
+ "button",
452
+ {
453
+ type: "button",
454
+ "data-slot": "composer-model-selector-effort-back",
455
+ "aria-label": backLabel,
456
+ onMouseDown: (event) => event.preventDefault(),
457
+ onClick: () => setView("models"),
458
+ className: cn(
459
+ "inline-flex h-6 shrink-0 items-center gap-1 rounded-full px-2 text-xs font-medium",
460
+ "text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
461
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none"
462
+ ),
463
+ children: [
464
+ /* @__PURE__ */ jsx(
465
+ ChevronLeft,
466
+ {
467
+ "aria-hidden": "true",
468
+ className: "h-3.5 w-3.5 rtl:-scale-x-100"
469
+ }
470
+ ),
471
+ effortLabel
472
+ ]
473
+ }
474
+ ) }),
475
+ effortHint && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground px-3 pt-2 text-xs", children: effortHint }),
476
+ /* @__PURE__ */ jsx(
477
+ "div",
478
+ {
479
+ ref: effortListRef,
480
+ id: menuId,
481
+ role: "listbox",
482
+ "aria-label": effortLabel,
483
+ "aria-activedescendant": activeEffortOptionId,
484
+ tabIndex: -1,
485
+ onKeyDown: handleEffortKeyDown,
486
+ className: "min-h-0 flex-1 overflow-y-auto p-1",
487
+ children: efforts.map((option, index) => {
488
+ const selected = option.value === effort;
489
+ const highlighted = index === effortHighlight;
490
+ return /* @__PURE__ */ jsxs(
491
+ "button",
492
+ {
493
+ id: getEffortOptionId(index),
494
+ type: "button",
495
+ role: "option",
496
+ "aria-selected": selected,
497
+ onMouseDown: (event) => event.preventDefault(),
498
+ onClick: () => selectEffort(option.value),
499
+ onMouseEnter: () => setEffortHighlight(index),
500
+ className: cn(
501
+ "flex w-full items-center gap-2 rounded-md px-3 py-2 text-start text-sm",
502
+ "focus-visible:ring-ring transition-colors focus-visible:ring-2 focus-visible:outline-none",
503
+ highlighted && "bg-muted",
504
+ selected && "bg-primary/10 text-primary"
505
+ ),
506
+ children: [
507
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
508
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: option.label }),
509
+ option.description && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground block truncate text-xs", children: option.description })
510
+ ] }),
511
+ option.value === defaultEffort && /* @__PURE__ */ jsx("span", { className: "bg-muted text-muted-foreground shrink-0 rounded px-1.5 py-0.5 text-xs", children: defaultBadgeLabel }),
512
+ selected && /* @__PURE__ */ jsx(
513
+ Check$1,
514
+ {
515
+ "aria-hidden": "true",
516
+ className: "text-primary h-4 w-4 shrink-0"
517
+ }
518
+ )
519
+ ]
520
+ },
521
+ option.value
522
+ );
523
+ })
524
+ }
525
+ )
526
+ ] })
527
+ ]
528
+ }
529
+ ),
530
+ document.body
531
+ )
532
+ ] });
533
+ }
534
+ ComposerModelSelector.displayName = "ComposerModelSelector";
535
+ function getFileType(mimeType) {
536
+ if (mimeType.startsWith("image/")) return "image";
537
+ if (mimeType.startsWith("video/")) return "video";
538
+ if (mimeType.startsWith("audio/")) return "audio";
539
+ if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("text")) {
540
+ return "document";
541
+ }
542
+ return "file";
543
+ }
544
+ function formatFileSize(bytes) {
545
+ if (bytes < 1024) return `${bytes} B`;
546
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
547
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
548
+ }
549
+ function validateFile(file, acceptedTypes, maxSize) {
550
+ if (acceptedTypes && acceptedTypes.length > 0) {
551
+ const isAccepted = acceptedTypes.some((type) => {
552
+ if (type.startsWith(".")) {
553
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
554
+ }
555
+ if (type.endsWith("/*")) {
556
+ return file.type.startsWith(type.replace("/*", "/"));
557
+ }
558
+ return file.type === type;
559
+ });
560
+ if (!isAccepted) {
561
+ return { valid: false, error: "File type not supported" };
562
+ }
563
+ }
564
+ if (maxSize && file.size > maxSize) {
565
+ return {
566
+ valid: false,
567
+ error: `File too large (max ${formatFileSize(maxSize)})`
568
+ };
569
+ }
570
+ return { valid: true };
571
+ }
572
+ function generateAttachmentId() {
573
+ return `attachment-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
574
+ }
575
+ var DEFAULT_ACCEPTED_FILE_TYPES = [
576
+ "image/*",
577
+ "video/*",
578
+ ".pdf",
579
+ ".doc",
580
+ ".docx"
581
+ ];
582
+ var DEFAULT_MAX_FILE_SIZE = 25 * 1024 * 1024;
583
+ function AttachmentPreviewItem({
584
+ attachment,
585
+ onRemove,
586
+ onRetry,
587
+ disabled = false,
588
+ className
589
+ }) {
590
+ const { file, previewUrl, type, state, progress } = attachment;
591
+ const isImage = type === "image";
592
+ const isVideo = type === "video";
593
+ const isUploading = state === "uploading";
594
+ const isFailed = state === "failed";
595
+ return /* @__PURE__ */ jsxs(
596
+ "div",
597
+ {
598
+ className: cn(
599
+ "group relative overflow-hidden rounded-lg",
600
+ "bg-neutral-100 dark:bg-neutral-800",
601
+ "border border-neutral-200 dark:border-neutral-700",
602
+ isFailed && "border-red-500",
603
+ className
604
+ ),
605
+ children: [
606
+ (isImage || isVideo) && previewUrl ? /* @__PURE__ */ jsxs("div", { className: "relative h-20 w-20", children: [
607
+ isVideo ? /* @__PURE__ */ jsx(
608
+ "video",
609
+ {
610
+ src: previewUrl,
611
+ "aria-label": file.name,
612
+ muted: true,
613
+ playsInline: true,
614
+ preload: "metadata",
615
+ className: cn(
616
+ "h-full w-full object-cover",
617
+ (isUploading || isFailed) && "opacity-50"
618
+ )
619
+ }
620
+ ) : /* @__PURE__ */ jsx(
621
+ "img",
622
+ {
623
+ src: previewUrl,
624
+ alt: file.name,
625
+ className: cn(
626
+ "h-full w-full object-cover",
627
+ (isUploading || isFailed) && "opacity-50"
628
+ )
629
+ }
630
+ ),
631
+ isVideo && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(
632
+ "svg",
633
+ {
634
+ "aria-hidden": "true",
635
+ className: "h-6 w-6 text-white drop-shadow",
636
+ fill: "currentColor",
637
+ viewBox: "0 0 24 24",
638
+ children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
639
+ }
640
+ ) })
641
+ ] }) : (
642
+ /* File preview */
643
+ /* @__PURE__ */ jsxs("div", { className: "flex h-20 w-20 flex-col items-center justify-center p-2", children: [
644
+ /* @__PURE__ */ jsx(
645
+ "svg",
646
+ {
647
+ "aria-hidden": "true",
648
+ className: "h-8 w-8 text-neutral-500",
649
+ fill: "none",
650
+ viewBox: "0 0 24 24",
651
+ stroke: "currentColor",
652
+ children: /* @__PURE__ */ jsx(
653
+ "path",
654
+ {
655
+ strokeLinecap: "round",
656
+ strokeLinejoin: "round",
657
+ strokeWidth: 2,
658
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
659
+ }
660
+ )
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsx("span", { className: "mt-1 max-w-full truncate px-1 text-xs text-neutral-600 dark:text-neutral-400", children: file.name.split(".").pop()?.toUpperCase() })
664
+ ] })
665
+ ),
666
+ isUploading && progress !== void 0 && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "text-center text-white", children: [
667
+ /* @__PURE__ */ jsxs(
668
+ "svg",
669
+ {
670
+ "aria-hidden": "true",
671
+ className: "mx-auto h-6 w-6 animate-spin",
672
+ fill: "none",
673
+ viewBox: "0 0 24 24",
674
+ children: [
675
+ /* @__PURE__ */ jsx(
676
+ "circle",
677
+ {
678
+ className: "opacity-25",
679
+ cx: "12",
680
+ cy: "12",
681
+ r: "10",
682
+ stroke: "currentColor",
683
+ strokeWidth: "4"
684
+ }
685
+ ),
686
+ /* @__PURE__ */ jsx(
687
+ "path",
688
+ {
689
+ className: "opacity-75",
690
+ fill: "currentColor",
691
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
692
+ }
693
+ )
694
+ ]
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsxs("span", { className: "mt-1 text-xs", children: [
698
+ progress,
699
+ "%"
700
+ ] })
701
+ ] }) }),
702
+ isFailed && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
703
+ /* @__PURE__ */ jsx(
704
+ "svg",
705
+ {
706
+ "aria-hidden": "true",
707
+ className: "mx-auto h-6 w-6 text-red-400",
708
+ fill: "none",
709
+ viewBox: "0 0 24 24",
710
+ stroke: "currentColor",
711
+ children: /* @__PURE__ */ jsx(
712
+ "path",
713
+ {
714
+ strokeLinecap: "round",
715
+ strokeLinejoin: "round",
716
+ strokeWidth: 2,
717
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
718
+ }
719
+ )
720
+ }
721
+ ),
722
+ onRetry && /* @__PURE__ */ jsx(
723
+ "button",
724
+ {
725
+ type: "button",
726
+ onClick: onRetry,
727
+ disabled,
728
+ className: "mt-1 text-xs text-white underline hover:no-underline disabled:no-underline disabled:opacity-50",
729
+ children: "Retry"
730
+ }
731
+ )
732
+ ] }) }),
733
+ /* @__PURE__ */ jsx(
734
+ "button",
735
+ {
736
+ type: "button",
737
+ onClick: onRemove,
738
+ disabled,
739
+ className: cn(
740
+ "absolute -end-1 -top-1 z-10",
741
+ "rounded-full p-1",
742
+ "bg-neutral-900 text-white",
743
+ "opacity-0 group-hover:opacity-100",
744
+ "focus:ring-primary-500 focus:opacity-100 focus:ring-2 focus:outline-none",
745
+ "transition-opacity",
746
+ "disabled:cursor-not-allowed disabled:group-hover:opacity-0"
747
+ ),
748
+ "aria-label": `Remove ${file.name}`,
749
+ children: /* @__PURE__ */ jsx(
750
+ "svg",
751
+ {
752
+ "aria-hidden": "true",
753
+ className: "h-3 w-3",
754
+ fill: "none",
755
+ viewBox: "0 0 24 24",
756
+ stroke: "currentColor",
757
+ children: /* @__PURE__ */ jsx(
758
+ "path",
759
+ {
760
+ strokeLinecap: "round",
761
+ strokeLinejoin: "round",
762
+ strokeWidth: 2,
763
+ d: "M6 18L18 6M6 6l12 12"
764
+ }
765
+ )
766
+ }
767
+ )
768
+ }
769
+ ),
770
+ /* @__PURE__ */ jsx(
771
+ "div",
772
+ {
773
+ className: cn(
774
+ "absolute start-0 end-0 bottom-0",
775
+ "bg-black/70 px-1 py-0.5",
776
+ "opacity-0 group-hover:opacity-100",
777
+ "transition-opacity"
778
+ ),
779
+ children: /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-white", children: file.name })
780
+ }
781
+ )
782
+ ]
783
+ }
784
+ );
785
+ }
786
+ AttachmentPreviewItem.displayName = "AttachmentPreviewItem";
787
+ var AttachmentPicker = React.forwardRef(
788
+ ({
789
+ onFilesSelected,
790
+ acceptedTypes = DEFAULT_ACCEPTED_FILE_TYPES,
791
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
792
+ maxFiles = 10,
793
+ multiple = true,
794
+ disabled = false,
795
+ onError,
796
+ className,
797
+ children
798
+ }, ref) => {
799
+ const inputRef = React.useRef(null);
800
+ React.useImperativeHandle(ref, () => inputRef.current);
801
+ const handleClick = () => {
802
+ inputRef.current?.click();
803
+ };
804
+ const handleChange = (event) => {
805
+ const files = Array.from(event.target.files || []);
806
+ if (files.length === 0) return;
807
+ const validFiles = [];
808
+ for (const file of files.slice(0, maxFiles)) {
809
+ const validation = validateFile(file, acceptedTypes, maxFileSize);
810
+ if (validation.valid) {
811
+ validFiles.push(file);
812
+ } else if (onError) {
813
+ onError(`${file.name}: ${validation.error}`);
814
+ }
815
+ }
816
+ if (files.length > maxFiles && onError) {
817
+ onError(`Maximum ${maxFiles} files allowed`);
818
+ }
819
+ if (validFiles.length > 0) {
820
+ onFilesSelected(validFiles);
821
+ }
822
+ event.target.value = "";
823
+ };
824
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
825
+ /* @__PURE__ */ jsx(
826
+ "input",
827
+ {
828
+ ref: inputRef,
829
+ type: "file",
830
+ accept: acceptedTypes.join(","),
831
+ multiple,
832
+ onChange: handleChange,
833
+ disabled,
834
+ className: "sr-only",
835
+ "aria-label": "Select files to attach"
836
+ }
837
+ ),
838
+ /* @__PURE__ */ jsx(
839
+ "button",
840
+ {
841
+ type: "button",
842
+ onClick: handleClick,
843
+ disabled,
844
+ "data-slot": "attachment-picker-button",
845
+ className: cn(
846
+ "inline-flex items-center justify-center",
847
+ "rounded-full p-2",
848
+ "text-neutral-500 hover:text-neutral-700",
849
+ "dark:text-neutral-400 dark:hover:text-neutral-200",
850
+ "hover:bg-neutral-100 dark:hover:bg-neutral-800",
851
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none",
852
+ "disabled:cursor-not-allowed disabled:opacity-50",
853
+ "transition-colors",
854
+ className
855
+ ),
856
+ "aria-label": "Attach files",
857
+ children: children || /* @__PURE__ */ jsx(
858
+ "svg",
859
+ {
860
+ "aria-hidden": "true",
861
+ className: "h-5 w-5",
862
+ fill: "none",
863
+ viewBox: "0 0 24 24",
864
+ stroke: "currentColor",
865
+ children: /* @__PURE__ */ jsx(
866
+ "path",
867
+ {
868
+ strokeLinecap: "round",
869
+ strokeLinejoin: "round",
870
+ strokeWidth: 2,
871
+ d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
872
+ }
873
+ )
874
+ }
875
+ )
876
+ }
877
+ )
878
+ ] });
879
+ }
880
+ );
881
+ AttachmentPicker.displayName = "AttachmentPicker";
882
+ function DragDropZone({
883
+ onFilesDropped,
884
+ acceptedTypes,
885
+ maxFileSize,
886
+ maxFiles = 10,
887
+ disabled = false,
888
+ onError,
889
+ overlayLabel = "Drop files here",
890
+ children,
891
+ className
892
+ }) {
893
+ const [isDragging, setIsDragging] = React.useState(false);
894
+ const dragCounterRef = React.useRef(0);
895
+ const handleDragEnter = (event) => {
896
+ event.preventDefault();
897
+ event.stopPropagation();
898
+ if (disabled) return;
899
+ dragCounterRef.current++;
900
+ if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
901
+ setIsDragging(true);
902
+ }
903
+ };
904
+ const handleDragLeave = (event) => {
905
+ event.preventDefault();
906
+ event.stopPropagation();
907
+ if (disabled) return;
908
+ dragCounterRef.current--;
909
+ if (dragCounterRef.current === 0) {
910
+ setIsDragging(false);
911
+ }
912
+ };
913
+ const handleDragOver = (event) => {
914
+ event.preventDefault();
915
+ event.stopPropagation();
916
+ };
917
+ const handleDrop = (event) => {
918
+ event.preventDefault();
919
+ event.stopPropagation();
920
+ if (disabled) return;
921
+ setIsDragging(false);
922
+ dragCounterRef.current = 0;
923
+ const files = Array.from(event.dataTransfer.files);
924
+ if (files.length === 0) return;
925
+ const validFiles = [];
926
+ for (const file of files.slice(0, maxFiles)) {
927
+ const validation = validateFile(file, acceptedTypes, maxFileSize);
928
+ if (validation.valid) {
929
+ validFiles.push(file);
930
+ } else if (onError) {
931
+ onError(`${file.name}: ${validation.error}`);
932
+ }
933
+ }
934
+ if (files.length > maxFiles && onError) {
935
+ onError(`Maximum ${maxFiles} files allowed`);
936
+ }
937
+ if (validFiles.length > 0) {
938
+ onFilesDropped(validFiles);
939
+ }
940
+ };
941
+ return /* @__PURE__ */ jsxs(
942
+ "div",
943
+ {
944
+ onDragEnter: handleDragEnter,
945
+ onDragLeave: handleDragLeave,
946
+ onDragOver: handleDragOver,
947
+ onDrop: handleDrop,
948
+ className: cn("relative", className),
949
+ children: [
950
+ children,
951
+ isDragging && /* @__PURE__ */ jsx(
952
+ "div",
953
+ {
954
+ className: cn(
955
+ "absolute inset-0 z-50",
956
+ "flex items-center justify-center",
957
+ "bg-primary-50/90 dark:bg-primary-900/90",
958
+ "border-primary-500 border-2 border-dashed",
959
+ "rounded-lg"
960
+ ),
961
+ children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
962
+ /* @__PURE__ */ jsx(
963
+ "svg",
964
+ {
965
+ "aria-hidden": "true",
966
+ className: "text-primary-800 mx-auto h-12 w-12",
967
+ fill: "none",
968
+ viewBox: "0 0 24 24",
969
+ stroke: "currentColor",
970
+ children: /* @__PURE__ */ jsx(
971
+ "path",
972
+ {
973
+ strokeLinecap: "round",
974
+ strokeLinejoin: "round",
975
+ strokeWidth: 2,
976
+ d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
977
+ }
978
+ )
979
+ }
980
+ ),
981
+ /* @__PURE__ */ jsx("p", { className: "text-primary-700 dark:text-primary-300 mt-2 text-sm font-medium", children: overlayLabel })
982
+ ] })
983
+ }
984
+ )
985
+ ]
986
+ }
987
+ );
988
+ }
989
+ DragDropZone.displayName = "DragDropZone";
990
+ function CameraButton({
991
+ onCapture,
992
+ useFrontCamera = false,
993
+ disabled = false,
994
+ className
995
+ }) {
996
+ const inputRef = React.useRef(null);
997
+ const handleClick = () => {
998
+ inputRef.current?.click();
999
+ };
1000
+ const handleChange = (event) => {
1001
+ const file = event.target.files?.[0];
1002
+ if (file) {
1003
+ onCapture(file);
1004
+ }
1005
+ event.target.value = "";
1006
+ };
1007
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1008
+ /* @__PURE__ */ jsx(
1009
+ "input",
1010
+ {
1011
+ ref: inputRef,
1012
+ type: "file",
1013
+ accept: "image/*",
1014
+ capture: useFrontCamera ? "user" : "environment",
1015
+ onChange: handleChange,
1016
+ disabled,
1017
+ className: "sr-only",
1018
+ "aria-label": "Take a photo"
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsx(
1022
+ "button",
1023
+ {
1024
+ type: "button",
1025
+ onClick: handleClick,
1026
+ disabled,
1027
+ className: cn(
1028
+ "inline-flex items-center justify-center",
1029
+ "rounded-full p-2",
1030
+ "text-neutral-500 hover:text-neutral-700",
1031
+ "dark:text-neutral-400 dark:hover:text-neutral-200",
1032
+ "hover:bg-neutral-100 dark:hover:bg-neutral-800",
1033
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1034
+ "disabled:cursor-not-allowed disabled:opacity-50",
1035
+ "transition-colors",
1036
+ className
1037
+ ),
1038
+ "aria-label": "Take a photo",
1039
+ children: /* @__PURE__ */ jsxs(
1040
+ "svg",
1041
+ {
1042
+ "aria-hidden": "true",
1043
+ className: "h-5 w-5",
1044
+ fill: "none",
1045
+ viewBox: "0 0 24 24",
1046
+ stroke: "currentColor",
1047
+ children: [
1048
+ /* @__PURE__ */ jsx(
1049
+ "path",
1050
+ {
1051
+ strokeLinecap: "round",
1052
+ strokeLinejoin: "round",
1053
+ strokeWidth: 2,
1054
+ d: "M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
1055
+ }
1056
+ ),
1057
+ /* @__PURE__ */ jsx(
1058
+ "path",
1059
+ {
1060
+ strokeLinecap: "round",
1061
+ strokeLinejoin: "round",
1062
+ strokeWidth: 2,
1063
+ d: "M15 13a3 3 0 11-6 0 3 3 0 016 0z"
1064
+ }
1065
+ )
1066
+ ]
1067
+ }
1068
+ )
1069
+ }
1070
+ )
1071
+ ] });
1072
+ }
1073
+ CameraButton.displayName = "CameraButton";
1074
+ function activeMentionQuery(value, caret) {
1075
+ const upToCaret = value.slice(0, caret);
1076
+ const match = /(^|\s)@([^\s@]*)$/.exec(upToCaret);
1077
+ if (!match) return null;
1078
+ const query = match[2];
1079
+ return { query, start: caret - query.length - 1 };
1080
+ }
1081
+ function useMentionAutocomplete({
1082
+ options,
1083
+ value,
1084
+ setValue,
1085
+ textareaRef
1086
+ }) {
1087
+ const enabled = !!options && options.length > 0;
1088
+ const [mention, setMention] = React.useState(null);
1089
+ const [highlight, setHighlight] = React.useState(0);
1090
+ const listId = React.useId();
1091
+ const optionId = React.useCallback(
1092
+ (index) => `${listId}-option-${index}`,
1093
+ [listId]
1094
+ );
1095
+ const suggestions = React.useMemo(() => {
1096
+ if (!mention || !options) return [];
1097
+ const q = mention.query.toLowerCase();
1098
+ return options.filter((o) => o.label.toLowerCase().includes(q));
1099
+ }, [mention, options]);
1100
+ const menuOpen = enabled && mention !== null && suggestions.length > 0;
1101
+ const {
1102
+ anchorRef,
1103
+ floatingRef,
1104
+ style: menuStyle
1105
+ } = useAnchoredPosition({
1106
+ open: menuOpen,
1107
+ placement: "top-start",
1108
+ maxHeight: 224
1109
+ });
1110
+ const clampedHighlight = menuOpen ? Math.min(highlight, suggestions.length - 1) : -1;
1111
+ const activeOptionId = menuOpen ? optionId(clampedHighlight) : void 0;
1112
+ const sync = React.useCallback(
1113
+ (nextValue, caret) => {
1114
+ if (!enabled) return;
1115
+ setMention(activeMentionQuery(nextValue, caret));
1116
+ setHighlight(0);
1117
+ },
1118
+ [enabled]
1119
+ );
1120
+ const insert = (option) => {
1121
+ if (!mention) return;
1122
+ const insertValue = option.value ?? option.label.split(" ")[0];
1123
+ const before = value.slice(0, mention.start);
1124
+ const after = value.slice(mention.start + 1 + mention.query.length);
1125
+ const insertText = `@${insertValue} `;
1126
+ setValue(before + insertText + after);
1127
+ setMention(null);
1128
+ const caret = before.length + insertText.length;
1129
+ requestAnimationFrame(() => {
1130
+ const el = textareaRef.current;
1131
+ if (el) {
1132
+ el.focus();
1133
+ el.setSelectionRange(caret, caret);
1134
+ }
1135
+ });
1136
+ };
1137
+ const handleKeyDown = (event) => {
1138
+ if (!menuOpen) return false;
1139
+ if (event.key === "ArrowDown") {
1140
+ event.preventDefault();
1141
+ setHighlight(
1142
+ (h) => (Math.min(h, suggestions.length - 1) + 1) % suggestions.length
1143
+ );
1144
+ return true;
1145
+ }
1146
+ if (event.key === "ArrowUp") {
1147
+ event.preventDefault();
1148
+ setHighlight(
1149
+ (h) => (Math.min(h, suggestions.length - 1) - 1 + suggestions.length) % suggestions.length
1150
+ );
1151
+ return true;
1152
+ }
1153
+ if (event.key === "Enter" || event.key === "Tab") {
1154
+ event.preventDefault();
1155
+ const chosen = suggestions[clampedHighlight];
1156
+ if (chosen) insert(chosen);
1157
+ return true;
1158
+ }
1159
+ if (event.key === "Escape") {
1160
+ event.preventDefault();
1161
+ setMention(null);
1162
+ return true;
1163
+ }
1164
+ return false;
1165
+ };
1166
+ const inputProps = enabled ? {
1167
+ "aria-controls": menuOpen ? listId : void 0,
1168
+ "aria-activedescendant": activeOptionId,
1169
+ "aria-autocomplete": "list"
1170
+ } : {};
1171
+ return {
1172
+ enabled,
1173
+ menuOpen,
1174
+ suggestions,
1175
+ highlight: clampedHighlight,
1176
+ listId,
1177
+ optionId,
1178
+ anchorRef,
1179
+ floatingRef,
1180
+ menuStyle,
1181
+ sync,
1182
+ insert,
1183
+ setHighlight,
1184
+ handleKeyDown,
1185
+ inputProps
1186
+ };
1187
+ }
1188
+ function MentionMenu({
1189
+ mention,
1190
+ label = "Mention",
1191
+ dataSlot = "composer-mention-list"
1192
+ }) {
1193
+ if (!mention.menuOpen) return null;
1194
+ return createPortal(
1195
+ /* @__PURE__ */ jsx(
1196
+ "ul",
1197
+ {
1198
+ ref: mention.floatingRef,
1199
+ style: mention.menuStyle,
1200
+ id: mention.listId,
1201
+ role: "listbox",
1202
+ "aria-label": label,
1203
+ "data-slot": dataSlot,
1204
+ className: "w-64 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-800",
1205
+ children: mention.suggestions.map((option, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
1206
+ "button",
1207
+ {
1208
+ type: "button",
1209
+ id: mention.optionId(i),
1210
+ role: "option",
1211
+ "aria-selected": i === mention.highlight,
1212
+ onMouseDown: (e) => {
1213
+ e.preventDefault();
1214
+ mention.insert(option);
1215
+ },
1216
+ onMouseEnter: () => mention.setHighlight(i),
1217
+ className: cn(
1218
+ "flex w-full items-center gap-2 px-3 py-1.5 text-start text-sm",
1219
+ i === mention.highlight ? "bg-primary-100 text-primary-900 dark:bg-primary-900/40 dark:text-primary-100" : "text-neutral-700 dark:text-neutral-200"
1220
+ ),
1221
+ children: [
1222
+ option.icon,
1223
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 truncate", children: [
1224
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: option.label }),
1225
+ option.description && /* @__PURE__ */ jsx("span", { className: "ms-1 text-xs text-neutral-400", children: option.description })
1226
+ ] }),
1227
+ option.meta && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-400", children: option.meta })
1228
+ ]
1229
+ }
1230
+ ) }, option.id))
1231
+ }
1232
+ ),
1233
+ document.body
1234
+ );
1235
+ }
1236
+ MentionMenu.displayName = "MentionMenu";
1237
+ var iconButtonClasses = cn(
1238
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg",
1239
+ "text-neutral-500 dark:text-neutral-400",
1240
+ "transition-colors duration-150",
1241
+ "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-700 dark:hover:text-white",
1242
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
1243
+ "disabled:pointer-events-none disabled:opacity-40"
1244
+ );
1245
+ var selectorTriggerClasses = cn(
1246
+ "flex min-w-0 items-center gap-1 rounded-md px-2 py-1 text-xs",
1247
+ "text-neutral-600 dark:text-neutral-400",
1248
+ "transition-colors duration-150",
1249
+ "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-700 dark:hover:text-white",
1250
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
1251
+ "disabled:pointer-events-none disabled:opacity-40"
1252
+ );
1253
+ var MAX_INPUT_HEIGHT = 160;
1254
+ var ChatComposer = React.forwardRef(function ChatComposer2({
1255
+ onSend,
1256
+ value: controlledValue,
1257
+ onValueChange,
1258
+ placeholder = "Type a message\u2026",
1259
+ disabled = false,
1260
+ isSending = false,
1261
+ autoFocus = false,
1262
+ maxLength,
1263
+ showCharacterCount = false,
1264
+ canSendWhenEmpty = false,
1265
+ maxHeight = MAX_INPUT_HEIGHT,
1266
+ textareaProps,
1267
+ addMenuItems,
1268
+ allowAttachments = true,
1269
+ acceptedFileTypes,
1270
+ maxFileSize,
1271
+ maxAttachments = 10,
1272
+ mentionOptions,
1273
+ replyTo = null,
1274
+ onCancelReply,
1275
+ showMic = false,
1276
+ onMicClick,
1277
+ micSlot,
1278
+ micBehavior = "always",
1279
+ isStreaming = false,
1280
+ onStop,
1281
+ showAgentSelector = false,
1282
+ agents = [],
1283
+ selectedAgent = null,
1284
+ onAgentChange,
1285
+ showModelSelector = false,
1286
+ modelSelectorProps,
1287
+ readOnly = false,
1288
+ readOnlyMessage = "You have read-only access and cannot send messages.",
1289
+ onError,
1290
+ attachmentLimitLabel,
1291
+ sendFailedLabel = "Failed to send message",
1292
+ sendingLabel = "Sending message\u2026",
1293
+ inputLabel = "Message input",
1294
+ addMenuLabel = "Add to message",
1295
+ attachFilesLabel = "Attach files",
1296
+ micLabel = "Start voice input",
1297
+ sendLabel = "Send message",
1298
+ stopLabel = "Stop generating",
1299
+ agentSelectorLabel = "Select agent",
1300
+ mentionListLabel = "Mention",
1301
+ dropFilesLabel = "Drop files here",
1302
+ replyingToLabel = "Replying to",
1303
+ cancelReplyLabel = "Cancel reply",
1304
+ className
1305
+ }, ref) {
1306
+ const [internalValue, setInternalValue] = React.useState("");
1307
+ const isControlled = controlledValue !== void 0;
1308
+ const value = isControlled ? controlledValue : internalValue;
1309
+ const [attachments, setAttachments] = React.useState([]);
1310
+ const attachmentsRef = React.useRef(attachments);
1311
+ attachmentsRef.current = attachments;
1312
+ const textareaRef = React.useRef(null);
1313
+ const fileInputRef = React.useRef(null);
1314
+ const [addMenuOpen, setAddMenuOpen] = React.useState(false);
1315
+ const [agentMenuOpen, setAgentMenuOpen] = React.useState(false);
1316
+ const replyToId = replyTo?.id;
1317
+ React.useEffect(() => {
1318
+ if (replyToId !== void 0) {
1319
+ textareaRef.current?.focus();
1320
+ }
1321
+ }, [replyToId]);
1322
+ const hasText = value.trim().length > 0;
1323
+ const hasContent = hasText || attachments.length > 0;
1324
+ const isOverLimit = maxLength !== void 0 && value.length > maxLength;
1325
+ const canSend = (hasContent || canSendWhenEmpty) && onSend !== void 0 && !disabled && !isSending && !isStreaming && !isOverLimit;
1326
+ const setValue = React.useCallback(
1327
+ (next) => {
1328
+ if (!isControlled) setInternalValue(next);
1329
+ onValueChange?.(next);
1330
+ },
1331
+ [isControlled, onValueChange]
1332
+ );
1333
+ const mention = useMentionAutocomplete({
1334
+ options: mentionOptions,
1335
+ value,
1336
+ setValue,
1337
+ textareaRef
1338
+ });
1339
+ const resizeTextarea = React.useCallback(() => {
1340
+ const textarea = textareaRef.current;
1341
+ if (!textarea) return;
1342
+ textarea.style.height = "auto";
1343
+ if (textarea.scrollHeight === 0) return;
1344
+ textarea.style.height = `${textarea.scrollHeight}px`;
1345
+ }, []);
1346
+ React.useLayoutEffect(resizeTextarea, [resizeTextarea, value]);
1347
+ React.useEffect(() => {
1348
+ const textarea = textareaRef.current;
1349
+ if (!textarea || typeof ResizeObserver === "undefined") return;
1350
+ let frame = 0;
1351
+ const observer = new ResizeObserver(() => {
1352
+ cancelAnimationFrame(frame);
1353
+ frame = requestAnimationFrame(resizeTextarea);
1354
+ });
1355
+ observer.observe(textarea);
1356
+ return () => {
1357
+ cancelAnimationFrame(frame);
1358
+ observer.disconnect();
1359
+ };
1360
+ }, [resizeTextarea]);
1361
+ const addFiles = React.useCallback(
1362
+ (files) => {
1363
+ if (files.length === 0) return;
1364
+ const limitMessage = attachmentLimitLabel ?? `Attachment limit reached (max ${maxAttachments})`;
1365
+ const room = maxAttachments - attachmentsRef.current.length;
1366
+ if (files.length > room) {
1367
+ onError?.(limitMessage, { reason: "attachment-limit" });
1368
+ if (room <= 0) return;
1369
+ }
1370
+ const staged = [];
1371
+ for (const file of files.slice(0, Math.max(room, 0))) {
1372
+ const typeCheck = validateFile(file, acceptedFileTypes, void 0);
1373
+ if (!typeCheck.valid) {
1374
+ onError?.(`${file.name}: ${typeCheck.error}`, {
1375
+ reason: "file-type",
1376
+ file
1377
+ });
1378
+ continue;
1379
+ }
1380
+ const sizeCheck = validateFile(file, void 0, maxFileSize);
1381
+ if (!sizeCheck.valid) {
1382
+ onError?.(`${file.name}: ${sizeCheck.error}`, {
1383
+ reason: "file-size",
1384
+ file
1385
+ });
1386
+ continue;
1387
+ }
1388
+ const type = getFileType(file.type);
1389
+ staged.push({
1390
+ id: generateAttachmentId(),
1391
+ file,
1392
+ type,
1393
+ previewUrl: type === "image" || type === "video" ? URL.createObjectURL(file) : void 0
1394
+ });
1395
+ }
1396
+ if (staged.length > 0) {
1397
+ attachmentsRef.current = [...attachmentsRef.current, ...staged];
1398
+ setAttachments((current) => [...current, ...staged]);
1399
+ }
1400
+ },
1401
+ [
1402
+ maxAttachments,
1403
+ acceptedFileTypes,
1404
+ maxFileSize,
1405
+ onError,
1406
+ attachmentLimitLabel
1407
+ ]
1408
+ );
1409
+ const removeAttachment = React.useCallback((id) => {
1410
+ const target = attachmentsRef.current.find(
1411
+ (attachment) => attachment.id === id
1412
+ );
1413
+ if (target?.previewUrl) URL.revokeObjectURL(target.previewUrl);
1414
+ attachmentsRef.current = attachmentsRef.current.filter(
1415
+ (attachment) => attachment.id !== id
1416
+ );
1417
+ setAttachments(
1418
+ (current) => current.filter((attachment) => attachment.id !== id)
1419
+ );
1420
+ }, []);
1421
+ React.useEffect(() => {
1422
+ return () => {
1423
+ for (const attachment of attachmentsRef.current) {
1424
+ if (attachment.previewUrl) URL.revokeObjectURL(attachment.previewUrl);
1425
+ }
1426
+ };
1427
+ }, []);
1428
+ const handleFileInputChange = (event) => {
1429
+ const files = Array.from(event.target.files ?? []);
1430
+ addFiles(files);
1431
+ event.target.value = "";
1432
+ };
1433
+ const handlePaste = (event) => {
1434
+ if (!allowAttachments) return;
1435
+ const files = Array.from(event.clipboardData.files);
1436
+ if (files.length > 0) {
1437
+ event.preventDefault();
1438
+ addFiles(files);
1439
+ }
1440
+ };
1441
+ React.useImperativeHandle(
1442
+ ref,
1443
+ () => ({
1444
+ addFiles,
1445
+ focus: () => textareaRef.current?.focus(),
1446
+ getTextarea: () => textareaRef.current
1447
+ }),
1448
+ [addFiles]
1449
+ );
1450
+ const handleSend = () => {
1451
+ if (!canSend || !onSend) return;
1452
+ const message = {
1453
+ content: value.trim(),
1454
+ attachments: attachments.map((attachment) => attachment.file),
1455
+ // Only add the key when replying — hosts may key-check the payload.
1456
+ ...replyTo ? { replyToId: replyTo.id } : {}
1457
+ };
1458
+ for (const attachment of attachments) {
1459
+ if (attachment.previewUrl) URL.revokeObjectURL(attachment.previewUrl);
1460
+ }
1461
+ attachmentsRef.current = [];
1462
+ setAttachments([]);
1463
+ setValue("");
1464
+ try {
1465
+ Promise.resolve(onSend(message)).catch(() => {
1466
+ onError?.(sendFailedLabel, { reason: "send-failed" });
1467
+ });
1468
+ } catch {
1469
+ onError?.(sendFailedLabel, { reason: "send-failed" });
1470
+ }
1471
+ textareaRef.current?.focus();
1472
+ };
1473
+ const handleKeyDown = (event) => {
1474
+ if (mention.handleKeyDown(event)) return;
1475
+ if (event.key === "Enter" && !event.shiftKey) {
1476
+ event.preventDefault();
1477
+ handleSend();
1478
+ }
1479
+ };
1480
+ if (readOnly) {
1481
+ return /* @__PURE__ */ jsxs(
1482
+ "div",
1483
+ {
1484
+ "data-slot": "chat-composer",
1485
+ role: "status",
1486
+ className: cn(
1487
+ "flex items-center gap-2 rounded-xl border px-4 py-3 text-sm",
1488
+ "border-amber-300 bg-amber-50 text-amber-900",
1489
+ "dark:border-amber-700 dark:bg-amber-950 dark:text-amber-200",
1490
+ className
1491
+ ),
1492
+ children: [
1493
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1494
+ /* @__PURE__ */ jsx("span", { "data-slot": "chat-composer-readonly", children: readOnlyMessage })
1495
+ ]
1496
+ }
1497
+ );
1498
+ }
1499
+ const showAddMenu = allowAttachments || addMenuItems && addMenuItems.length > 0;
1500
+ const micVisible = (showMic || onMicClick !== void 0 || micSlot !== void 0) && (micBehavior === "always" || !hasContent);
1501
+ const showSelectorRow = showAgentSelector || showModelSelector;
1502
+ const selectedAgentOption = agents.find((agent) => agent.id === selectedAgent) ?? null;
1503
+ const cells = {
1504
+ input: "col-span-3 col-start-1 row-start-1 px-1 pt-1 md:col-span-1 md:col-start-2 md:self-end md:px-0 md:py-1.5",
1505
+ textarea: "px-2 pt-2 pb-1 md:py-1.5",
1506
+ add: "col-start-1 row-start-2 pb-2 ps-2 md:row-start-1 md:self-end md:p-1.5 md:pe-0.5",
1507
+ actions: "col-start-3 row-start-2 flex items-center gap-1 pb-2 pe-2 md:row-start-1 md:self-end md:gap-0.5 md:p-1.5 md:ps-0.5"
1508
+ };
1509
+ const composerCard = /* @__PURE__ */ jsxs(Fragment, { children: [
1510
+ replyTo && /* @__PURE__ */ jsxs(
1511
+ "div",
1512
+ {
1513
+ "data-slot": "chat-composer-reply-preview",
1514
+ className: cn(
1515
+ "flex items-center gap-2 rounded-t-2xl px-4 py-2",
1516
+ "bg-neutral-50 dark:bg-neutral-800/50",
1517
+ "border-b border-neutral-200 dark:border-neutral-700",
1518
+ "border-s-primary-500 border-s-4"
1519
+ ),
1520
+ children: [
1521
+ /* @__PURE__ */ jsxs("div", { role: "status", className: "min-w-0 flex-1", children: [
1522
+ /* @__PURE__ */ jsxs("span", { className: "text-primary-800 dark:text-primary-400 block text-xs font-medium", children: [
1523
+ replyingToLabel,
1524
+ " ",
1525
+ replyTo.senderName
1526
+ ] }),
1527
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
1528
+ ] }),
1529
+ /* @__PURE__ */ jsx(
1530
+ "button",
1531
+ {
1532
+ type: "button",
1533
+ "data-slot": "chat-composer-cancel-reply",
1534
+ "aria-label": cancelReplyLabel,
1535
+ disabled,
1536
+ onClick: onCancelReply,
1537
+ className: iconButtonClasses,
1538
+ children: /* @__PURE__ */ jsx(X2, { className: "h-4 w-4", "aria-hidden": "true" })
1539
+ }
1540
+ )
1541
+ ]
1542
+ }
1543
+ ),
1544
+ attachments.length > 0 && /* @__PURE__ */ jsx(
1545
+ "div",
1546
+ {
1547
+ "data-slot": "chat-composer-attachments",
1548
+ className: "flex flex-wrap gap-2 px-3 pt-3",
1549
+ children: attachments.map((attachment) => /* @__PURE__ */ jsx(
1550
+ AttachmentPreviewItem,
1551
+ {
1552
+ attachment: { ...attachment, state: "pending" },
1553
+ onRemove: () => removeAttachment(attachment.id),
1554
+ disabled
1555
+ },
1556
+ attachment.id
1557
+ ))
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto]", children: [
1561
+ /* @__PURE__ */ jsxs("div", { className: cells.input, ref: mention.anchorRef, children: [
1562
+ /* @__PURE__ */ jsx(
1563
+ MentionMenu,
1564
+ {
1565
+ mention,
1566
+ label: mentionListLabel,
1567
+ dataSlot: "chat-composer-mention-list"
1568
+ }
1569
+ ),
1570
+ /* @__PURE__ */ jsx(
1571
+ "textarea",
1572
+ {
1573
+ ...textareaProps,
1574
+ ref: textareaRef,
1575
+ "data-slot": "chat-composer-input",
1576
+ value,
1577
+ onChange: (event) => {
1578
+ textareaProps?.onChange?.(event);
1579
+ setValue(event.target.value);
1580
+ mention.sync(
1581
+ event.target.value,
1582
+ event.target.selectionStart ?? event.target.value.length
1583
+ );
1584
+ },
1585
+ onKeyDown: (event) => {
1586
+ textareaProps?.onKeyDown?.(event);
1587
+ if (event.defaultPrevented) return;
1588
+ handleKeyDown(event);
1589
+ },
1590
+ onClick: (event) => {
1591
+ textareaProps?.onClick?.(event);
1592
+ if (event.defaultPrevented) return;
1593
+ const el = event.currentTarget;
1594
+ mention.sync(el.value, el.selectionStart ?? el.value.length);
1595
+ },
1596
+ onPaste: (event) => {
1597
+ textareaProps?.onPaste?.(event);
1598
+ if (event.defaultPrevented) return;
1599
+ handlePaste(event);
1600
+ },
1601
+ placeholder,
1602
+ disabled,
1603
+ autoFocus,
1604
+ rows: 1,
1605
+ "aria-label": inputLabel,
1606
+ ...mention.inputProps,
1607
+ style: {
1608
+ ...textareaProps?.style,
1609
+ maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight
1610
+ },
1611
+ className: cn(
1612
+ "block w-full resize-none bg-transparent",
1613
+ "rounded-lg text-sm",
1614
+ cells.textarea,
1615
+ "text-neutral-900 placeholder:text-neutral-400 dark:text-white dark:placeholder:text-neutral-500",
1616
+ // Ring the input itself on focus rather than the whole shell.
1617
+ "focus:ring-primary-500 focus:ring-1 focus:outline-none",
1618
+ "disabled:cursor-not-allowed",
1619
+ textareaProps?.className
1620
+ )
1621
+ }
1622
+ )
1623
+ ] }),
1624
+ showAddMenu && /* @__PURE__ */ jsx("div", { className: cells.add, children: /* @__PURE__ */ jsxs(
1625
+ Dropdown,
1626
+ {
1627
+ placement: "top-start",
1628
+ open: addMenuOpen,
1629
+ onOpenChange: setAddMenuOpen,
1630
+ trigger: /* @__PURE__ */ jsx(
1631
+ "button",
1632
+ {
1633
+ type: "button",
1634
+ "data-slot": "chat-composer-add-button",
1635
+ "aria-label": addMenuLabel,
1636
+ disabled,
1637
+ className: iconButtonClasses,
1638
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4", "aria-hidden": "true" })
1639
+ }
1640
+ ),
1641
+ children: [
1642
+ allowAttachments && /* @__PURE__ */ jsx(
1643
+ DropdownItem,
1644
+ {
1645
+ icon: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4", "aria-hidden": "true" }),
1646
+ onClick: () => {
1647
+ setAddMenuOpen(false);
1648
+ fileInputRef.current?.click();
1649
+ },
1650
+ children: attachFilesLabel
1651
+ }
1652
+ ),
1653
+ allowAttachments && addMenuItems && addMenuItems.length > 0 && /* @__PURE__ */ jsx(DropdownSeparator, {}),
1654
+ addMenuItems?.map((item) => /* @__PURE__ */ jsx(
1655
+ DropdownItem,
1656
+ {
1657
+ icon: item.icon,
1658
+ disabled: item.disabled,
1659
+ variant: item.variant,
1660
+ checked: item.checked,
1661
+ onClick: () => {
1662
+ setAddMenuOpen(false);
1663
+ item.onSelect?.();
1664
+ },
1665
+ children: /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: item.label })
1666
+ },
1667
+ item.id
1668
+ ))
1669
+ ]
1670
+ }
1671
+ ) }),
1672
+ allowAttachments && /* @__PURE__ */ jsx(
1673
+ "input",
1674
+ {
1675
+ ref: fileInputRef,
1676
+ type: "file",
1677
+ multiple: true,
1678
+ accept: acceptedFileTypes?.join(","),
1679
+ onChange: handleFileInputChange,
1680
+ className: "hidden",
1681
+ "aria-hidden": "true",
1682
+ tabIndex: -1
1683
+ }
1684
+ ),
1685
+ /* @__PURE__ */ jsxs("div", { className: cells.actions, children: [
1686
+ showCharacterCount && maxLength !== void 0 && /* @__PURE__ */ jsxs(
1687
+ "span",
1688
+ {
1689
+ "data-slot": "chat-composer-char-count",
1690
+ "aria-live": "polite",
1691
+ className: cn(
1692
+ "pe-1 text-xs tabular-nums",
1693
+ isOverLimit ? "font-medium text-red-700 dark:text-red-400" : "text-neutral-500 dark:text-neutral-400"
1694
+ ),
1695
+ children: [
1696
+ value.length,
1697
+ "/",
1698
+ maxLength
1699
+ ]
1700
+ }
1701
+ ),
1702
+ micVisible && (micSlot != null ? (
1703
+ // Normalize slot content (e.g. a RecordButton) to the 32px
1704
+ // control row: taller content overflow-centers instead of
1705
+ // inflating the pill and pushing the other cells apart. A div
1706
+ // so block-level slot content stays valid HTML.
1707
+ /* @__PURE__ */ jsx(
1708
+ "div",
1709
+ {
1710
+ "data-slot": "chat-composer-mic-slot",
1711
+ className: "flex h-8 shrink-0 items-center",
1712
+ children: micSlot
1713
+ }
1714
+ )
1715
+ ) : /* @__PURE__ */ jsx(
1716
+ "button",
1717
+ {
1718
+ type: "button",
1719
+ "data-slot": "chat-composer-mic-button",
1720
+ "aria-label": micLabel,
1721
+ disabled,
1722
+ onClick: onMicClick,
1723
+ className: iconButtonClasses,
1724
+ children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4", "aria-hidden": "true" })
1725
+ }
1726
+ )),
1727
+ isStreaming && onStop ? /* @__PURE__ */ jsx(
1728
+ "button",
1729
+ {
1730
+ type: "button",
1731
+ "data-slot": "chat-composer-stop-button",
1732
+ "aria-label": stopLabel,
1733
+ disabled,
1734
+ onClick: onStop,
1735
+ className: cn(
1736
+ iconButtonClasses,
1737
+ "text-neutral-700 dark:text-neutral-200"
1738
+ ),
1739
+ children: /* @__PURE__ */ jsx(
1740
+ Square,
1741
+ {
1742
+ className: "h-3.5 w-3.5 fill-current",
1743
+ "aria-hidden": "true"
1744
+ }
1745
+ )
1746
+ }
1747
+ ) : /* @__PURE__ */ jsx(
1748
+ "button",
1749
+ {
1750
+ type: "button",
1751
+ "data-slot": "chat-composer-send-button",
1752
+ "aria-label": isSending ? sendingLabel : sendLabel,
1753
+ "aria-busy": isSending || void 0,
1754
+ disabled: !canSend,
1755
+ onClick: handleSend,
1756
+ className: cn(
1757
+ iconButtonClasses,
1758
+ canSend && "bg-primary-800 hover:bg-primary-700 dark:bg-primary-600 dark:hover:bg-primary-500 text-white hover:text-white dark:hover:text-white"
1759
+ ),
1760
+ children: isSending ? /* @__PURE__ */ jsx(
1761
+ Loader2,
1762
+ {
1763
+ className: "h-4 w-4 animate-spin",
1764
+ "aria-hidden": "true"
1765
+ }
1766
+ ) : /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4", "aria-hidden": "true" })
1767
+ }
1768
+ )
1769
+ ] })
1770
+ ] })
1771
+ ] });
1772
+ const selectorsRow = showSelectorRow && /* @__PURE__ */ jsxs(
1773
+ "div",
1774
+ {
1775
+ "data-slot": "chat-composer-selectors",
1776
+ className: "flex items-center justify-between gap-2 px-2 pt-1",
1777
+ children: [
1778
+ showAgentSelector ? /* @__PURE__ */ jsx(
1779
+ Dropdown,
1780
+ {
1781
+ placement: "top-start",
1782
+ open: agentMenuOpen,
1783
+ onOpenChange: setAgentMenuOpen,
1784
+ trigger: /* @__PURE__ */ jsxs(
1785
+ "button",
1786
+ {
1787
+ type: "button",
1788
+ "data-slot": "chat-composer-agent-trigger",
1789
+ "aria-label": selectedAgentOption ? `${agentSelectorLabel}: ${selectedAgentOption.label}` : agentSelectorLabel,
1790
+ disabled,
1791
+ className: selectorTriggerClasses,
1792
+ children: [
1793
+ selectedAgentOption?.icon ?? /* @__PURE__ */ jsx(Bot, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1794
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: selectedAgentOption?.label ?? agentSelectorLabel }),
1795
+ /* @__PURE__ */ jsx(ChevronUp, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" })
1796
+ ]
1797
+ }
1798
+ ),
1799
+ children: agents.map((agent) => /* @__PURE__ */ jsx(
1800
+ DropdownItem,
1801
+ {
1802
+ icon: agent.icon,
1803
+ role: "menuitemradio",
1804
+ "aria-checked": agent.id === selectedAgent,
1805
+ onClick: () => {
1806
+ setAgentMenuOpen(false);
1807
+ onAgentChange?.(agent.id);
1808
+ },
1809
+ children: /* @__PURE__ */ jsxs("span", { className: "flex w-full items-center justify-between gap-2", children: [
1810
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
1811
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: agent.label }),
1812
+ agent.description && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-neutral-500 dark:text-neutral-400", children: agent.description })
1813
+ ] }),
1814
+ agent.id === selectedAgent && /* @__PURE__ */ jsx(
1815
+ Check,
1816
+ {
1817
+ className: "text-primary-600 dark:text-primary-400 h-4 w-4 shrink-0",
1818
+ "aria-hidden": "true"
1819
+ }
1820
+ )
1821
+ ] })
1822
+ },
1823
+ agent.id
1824
+ ))
1825
+ }
1826
+ ) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
1827
+ showModelSelector && modelSelectorProps && /* @__PURE__ */ jsx(
1828
+ ComposerModelSelector,
1829
+ {
1830
+ variant: "ghost",
1831
+ ...modelSelectorProps,
1832
+ disabled: disabled || modelSelectorProps.disabled
1833
+ }
1834
+ )
1835
+ ]
1836
+ }
1837
+ );
1838
+ const card = /* @__PURE__ */ jsx(
1839
+ "div",
1840
+ {
1841
+ "data-slot": "chat-composer-card",
1842
+ className: cn(
1843
+ "rounded-2xl border border-neutral-200 bg-white shadow-sm",
1844
+ "dark:border-[#2e2e30] dark:bg-[#1c1c1e]"
1845
+ ),
1846
+ children: composerCard
1847
+ }
1848
+ );
1849
+ return /* @__PURE__ */ jsxs(
1850
+ "div",
1851
+ {
1852
+ "data-slot": "chat-composer",
1853
+ className: cn(
1854
+ "flex w-full flex-col",
1855
+ disabled && "opacity-60",
1856
+ className
1857
+ ),
1858
+ children: [
1859
+ allowAttachments ? /* @__PURE__ */ jsx(
1860
+ DragDropZone,
1861
+ {
1862
+ onFilesDropped: addFiles,
1863
+ maxFiles: Number.MAX_SAFE_INTEGER,
1864
+ disabled,
1865
+ overlayLabel: dropFilesLabel,
1866
+ children: card
1867
+ }
1868
+ ) : card,
1869
+ selectorsRow
1870
+ ]
1871
+ }
1872
+ );
1873
+ });
1874
+ ChatComposer.displayName = "ChatComposer";
1875
+
1876
+ export { AttachmentPicker, AttachmentPreviewItem, CameraButton, ChatComposer, ComposerModelSelector, DEFAULT_ACCEPTED_FILE_TYPES, DEFAULT_MAX_FILE_SIZE, DragDropZone, MentionMenu, generateAttachmentId, getFileType, useMentionAutocomplete, validateFile };
1877
+ //# sourceMappingURL=chunk-3SD7PDCV.js.map
1878
+ //# sourceMappingURL=chunk-3SD7PDCV.js.map