@mieweb/ui 0.9.0 → 0.10.0

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