@agents24/chat-react 0.5.5 → 0.5.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.
package/dist/ui/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(ui_exports, {
61
61
  BubbleGroup: () => BubbleGroup,
62
62
  BubbleReactions: () => BubbleReactions,
63
63
  ChatAttachment: () => ChatAttachment,
64
+ ChatAttachmentRows: () => ChatAttachmentRows,
64
65
  ChatAttachments: () => ChatAttachments,
65
66
  ChatMessage: () => ChatMessage,
66
67
  ChatMessageAction: () => ChatMessageAction,
@@ -74,6 +75,7 @@ __export(ui_exports, {
74
75
  MessageAction: () => ChatMessageAction,
75
76
  MessageActions: () => ChatMessageActions,
76
77
  MessageAttachment: () => ChatAttachment,
78
+ MessageAttachmentRows: () => ChatAttachmentRows,
77
79
  MessageAttachments: () => ChatAttachments,
78
80
  MessageAvatar: () => MessageAvatar,
79
81
  MessageContent: () => ChatMessageContent,
@@ -89,6 +91,7 @@ __export(ui_exports, {
89
91
  bubbleVariants: () => bubbleVariants,
90
92
  cn: () => cn,
91
93
  contextStatusMetrics: () => contextStatusMetrics,
94
+ createAgentChatComposerFile: () => createAgentChatComposerFile,
92
95
  hitlActionLabel: () => hitlActionLabel,
93
96
  hitlTitle: () => hitlTitle,
94
97
  isAgentChatSubagent: () => isAgentChatSubagent,
@@ -97,15 +100,25 @@ __export(ui_exports, {
97
100
  markerVariants: () => markerVariants,
98
101
  partitionAgentChatToolTimeline: () => partitionAgentChatToolTimeline,
99
102
  resolvedHitlLabel: () => resolvedHitlLabel,
103
+ revokeAgentChatComposerFiles: () => revokeAgentChatComposerFiles,
100
104
  shouldCollapseUserMessage: () => shouldCollapseUserMessage,
101
105
  useAudioRecorder: () => useAudioRecorder
102
106
  });
103
107
  module.exports = __toCommonJS(ui_exports);
104
108
 
105
109
  // src/ui/agent-chat-composer.tsx
110
+ var React2 = __toESM(require("react"), 1);
111
+ var import_lucide_react2 = require("lucide-react");
112
+
113
+ // src/ui/adapters.ts
106
114
  var React = __toESM(require("react"), 1);
115
+ var import_file_icons = require("@untitledui/file-icons");
107
116
  var import_lucide_react = require("lucide-react");
108
117
 
118
+ // src/ui/attachment.tsx
119
+ var import_class_variance_authority = require("class-variance-authority");
120
+ var import_react_slot = require("@radix-ui/react-slot");
121
+
109
122
  // src/ui/utils.ts
110
123
  var import_clsx = require("clsx");
111
124
  var import_tailwind_merge = require("tailwind-merge");
@@ -113,266 +126,8 @@ function cn(...values) {
113
126
  return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(values));
114
127
  }
115
128
 
116
- // src/ui/agent-chat-composer.tsx
117
- var import_jsx_runtime = require("react/jsx-runtime");
118
- var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
119
- function createComposerFile(file) {
120
- return {
121
- id: createId(),
122
- type: "file",
123
- url: URL.createObjectURL(file),
124
- filename: file.name || "attachment",
125
- mediaType: file.type || "application/octet-stream",
126
- source: file
127
- };
128
- }
129
- function revokeComposerFiles(files) {
130
- files.forEach((file) => {
131
- try {
132
- URL.revokeObjectURL(file.url);
133
- } catch {
134
- }
135
- });
136
- }
137
- function formatFileCount(files) {
138
- if (files.length === 1) return files[0].filename;
139
- return `${files.length} files`;
140
- }
141
- var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
142
- var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
143
- function AgentChatComposer({
144
- accept,
145
- allowAttachments = true,
146
- attachmentLayout = "flow",
147
- className,
148
- disabled = false,
149
- forceExpanded = false,
150
- inputToolbarContent,
151
- isRunning = false,
152
- onAttachmentCountChange,
153
- onStop,
154
- onSubmit,
155
- placeholder = "Message the agent",
156
- textareaRef
157
- }) {
158
- const fileInputRef = React.useRef(null);
159
- const filesRef = React.useRef([]);
160
- const localTextareaRef = React.useRef(null);
161
- const [text, setText] = React.useState("");
162
- const [files, setFiles] = React.useState([]);
163
- const [isSubmitting, setIsSubmitting] = React.useState(false);
164
- const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
165
- const setTextareaRef = React.useCallback(
166
- (node) => {
167
- localTextareaRef.current = node;
168
- if (typeof textareaRef === "function") {
169
- textareaRef(node);
170
- } else if (textareaRef) {
171
- textareaRef.current = node;
172
- }
173
- },
174
- [textareaRef]
175
- );
176
- React.useEffect(() => {
177
- filesRef.current = files;
178
- onAttachmentCountChange?.(files.length);
179
- }, [files, onAttachmentCountChange]);
180
- React.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
181
- React.useLayoutEffect(() => {
182
- const textarea = localTextareaRef.current;
183
- if (!textarea) return;
184
- if (isExpanded) {
185
- textarea.style.height = "auto";
186
- const scrollHeight = textarea.scrollHeight;
187
- textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
188
- textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
189
- return;
190
- }
191
- textarea.style.height = "";
192
- textarea.style.overflowY = "hidden";
193
- }, [isExpanded, text]);
194
- const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
195
- const handleFilesChange = (event) => {
196
- const selected = Array.from(event.target.files || []).map(createComposerFile);
197
- if (selected.length > 0) {
198
- setFiles((current) => [...current, ...selected]);
199
- }
200
- event.currentTarget.value = "";
201
- };
202
- const removeFile = (fileId) => {
203
- setFiles((current) => {
204
- const target = current.find((file) => file.id === fileId);
205
- if (target) revokeComposerFiles([target]);
206
- return current.filter((file) => file.id !== fileId);
207
- });
208
- };
209
- const submit = async () => {
210
- if (!canSubmit) return;
211
- const submittedText = text.trim();
212
- const submittedFiles = files;
213
- setIsSubmitting(true);
214
- try {
215
- await onSubmit({ text: submittedText, files: submittedFiles });
216
- setText("");
217
- setFiles([]);
218
- revokeComposerFiles(submittedFiles);
219
- } catch {
220
- } finally {
221
- setIsSubmitting(false);
222
- }
223
- };
224
- const handleKeyDown = (event) => {
225
- if (event.key === "Enter" && !event.shiftKey) {
226
- event.preventDefault();
227
- void submit();
228
- }
229
- };
230
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
231
- files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
232
- "div",
233
- {
234
- "data-testid": "agent-chat-composer-attachments",
235
- "data-layout": attachmentLayout,
236
- className: cn(
237
- "a24-composer__attachments flex min-w-0 gap-2 py-1",
238
- attachmentLayout === "overlay" ? "absolute bottom-full left-0 right-0 mb-1.5 flex-nowrap overflow-x-auto overflow-y-hidden overscroll-x-contain px-2" : "mb-2 flex-wrap px-1"
239
- ),
240
- children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
241
- "div",
242
- {
243
- className: "a24-composer__attachment group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-border bg-card px-1.5 text-sm font-medium text-card-foreground transition-colors hover:bg-accent",
244
- children: [
245
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Paperclip, { className: "size-3" }) }),
246
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
247
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
248
- "button",
249
- {
250
- "aria-label": `Remove ${file.filename}`,
251
- className: "a24-composer__attachment-remove inline-flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground opacity-70 transition-colors hover:bg-muted hover:text-foreground hover:opacity-100 focus-visible:outline-none",
252
- onClick: () => removeFile(file.id),
253
- type: "button",
254
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "size-3" })
255
- }
256
- )
257
- ]
258
- },
259
- file.id
260
- ))
261
- }
262
- ) : null,
263
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
264
- "div",
265
- {
266
- "data-expanded": isExpanded ? "" : void 0,
267
- className: cn(
268
- "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
269
- isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
270
- ),
271
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
272
- "div",
273
- {
274
- className: "a24-composer__grid w-full",
275
- style: {
276
- alignItems: "center",
277
- display: "grid",
278
- gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
279
- gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
280
- gap: isExpanded ? "3px 10px" : "8px"
281
- },
282
- children: [
283
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
284
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
285
- "input",
286
- {
287
- ref: fileInputRef,
288
- accept,
289
- "aria-label": "Attach files",
290
- className: "hidden",
291
- multiple: true,
292
- name: "attachments",
293
- onChange: handleFilesChange,
294
- type: "file"
295
- }
296
- ),
297
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
298
- "button",
299
- {
300
- "aria-label": "Attach files",
301
- className: cn("a24-composer__attach", pillButtonClass),
302
- disabled: disabled || isSubmitting || isRunning || !allowAttachments,
303
- onClick: () => fileInputRef.current?.click(),
304
- title: "Attach files",
305
- type: "button",
306
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Plus, { className: "size-4" })
307
- }
308
- )
309
- ] }),
310
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
311
- "textarea",
312
- {
313
- ref: setTextareaRef,
314
- "aria-label": "Message",
315
- autoComplete: "off",
316
- className: cn(
317
- "a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0",
318
- isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
319
- ),
320
- disabled: disabled || isSubmitting || isRunning,
321
- name: "message",
322
- onChange: (event) => setText(event.target.value),
323
- onKeyDown: handleKeyDown,
324
- placeholder,
325
- rows: isExpanded ? 2 : 1,
326
- value: text
327
- }
328
- ) }),
329
- isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
330
- !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
331
- !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
333
- "button",
334
- {
335
- "aria-label": "Stop generating",
336
- className: cn("a24-composer__submit", submitButtonClass),
337
- disabled: disabled || !onStop,
338
- onClick: onStop,
339
- title: "Stop",
340
- type: "button",
341
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Square, { className: "size-3.5 fill-current" })
342
- }
343
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
344
- "button",
345
- {
346
- "aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
347
- className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
348
- disabled: !canSubmit,
349
- onClick: () => void submit(),
350
- title: "Send",
351
- type: "button",
352
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
353
- }
354
- ) })
355
- ]
356
- }
357
- )
358
- }
359
- ),
360
- !isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
361
- ] });
362
- }
363
-
364
- // src/ui/agent-chat-actions.tsx
365
- var React3 = __toESM(require("react"), 1);
366
- var import_lucide_react2 = require("lucide-react");
367
- var import_radix_ui = require("radix-ui");
368
-
369
- // src/ui/adapters.ts
370
- var React2 = __toESM(require("react"), 1);
371
-
372
129
  // src/ui/attachment.tsx
373
- var import_class_variance_authority = require("class-variance-authority");
374
- var import_react_slot = require("@radix-ui/react-slot");
375
- var import_jsx_runtime2 = require("react/jsx-runtime");
130
+ var import_jsx_runtime = require("react/jsx-runtime");
376
131
  var attachmentVariants = (0, import_class_variance_authority.cva)(
377
132
  "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:border-neutral-300 focus-within:ring-0 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
378
133
  {
@@ -396,7 +151,7 @@ function Attachment({
396
151
  orientation = "horizontal",
397
152
  ...props
398
153
  }) {
399
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
154
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
400
155
  "div",
401
156
  {
402
157
  "data-slot": "attachment",
@@ -409,12 +164,12 @@ function Attachment({
409
164
  );
410
165
  }
411
166
  var attachmentMediaVariants = (0, import_class_variance_authority.cva)(
412
- "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
167
+ "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
413
168
  {
414
169
  variants: {
415
170
  variant: {
416
171
  icon: "",
417
- image: "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover"
172
+ image: "bg-muted opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover"
418
173
  }
419
174
  },
420
175
  defaultVariants: {
@@ -427,7 +182,7 @@ function AttachmentMedia({
427
182
  variant = "icon",
428
183
  ...props
429
184
  }) {
430
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
185
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
431
186
  "div",
432
187
  {
433
188
  "data-slot": "attachment-media",
@@ -441,7 +196,7 @@ function AttachmentContent({
441
196
  className,
442
197
  ...props
443
198
  }) {
444
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
199
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
445
200
  "div",
446
201
  {
447
202
  "data-slot": "attachment-content",
@@ -457,7 +212,7 @@ function AttachmentTitle({
457
212
  className,
458
213
  ...props
459
214
  }) {
460
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
215
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
461
216
  "span",
462
217
  {
463
218
  "data-slot": "attachment-title",
@@ -473,7 +228,7 @@ function AttachmentDescription({
473
228
  className,
474
229
  ...props
475
230
  }) {
476
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
231
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
477
232
  "span",
478
233
  {
479
234
  "data-slot": "attachment-description",
@@ -490,7 +245,7 @@ function AttachmentActions({
490
245
  className,
491
246
  ...props
492
247
  }) {
493
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
248
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
494
249
  "div",
495
250
  {
496
251
  "data-slot": "attachment-actions",
@@ -507,7 +262,7 @@ function AttachmentAction({
507
262
  type = "button",
508
263
  ...props
509
264
  }) {
510
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
265
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
511
266
  "button",
512
267
  {
513
268
  "data-slot": "attachment-action",
@@ -527,7 +282,7 @@ function AttachmentTrigger({
527
282
  ...props
528
283
  }) {
529
284
  const Comp = asChild ? import_react_slot.Slot : "button";
530
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
285
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
531
286
  Comp,
532
287
  {
533
288
  "data-slot": "attachment-trigger",
@@ -538,7 +293,7 @@ function AttachmentTrigger({
538
293
  );
539
294
  }
540
295
  function AttachmentGroup({ className, ...props }) {
541
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
296
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
542
297
  "div",
543
298
  {
544
299
  "data-slot": "attachment-group",
@@ -552,9 +307,9 @@ function AttachmentGroup({ className, ...props }) {
552
307
  }
553
308
 
554
309
  // src/ui/message.tsx
555
- var import_jsx_runtime3 = require("react/jsx-runtime");
310
+ var import_jsx_runtime2 = require("react/jsx-runtime");
556
311
  function MessageGroup({ className, ...props }) {
557
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
312
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
558
313
  "div",
559
314
  {
560
315
  "data-slot": "message-group",
@@ -568,7 +323,7 @@ function Message({
568
323
  align = "start",
569
324
  ...props
570
325
  }) {
571
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
326
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
572
327
  "div",
573
328
  {
574
329
  "data-slot": "message",
@@ -582,7 +337,7 @@ function Message({
582
337
  );
583
338
  }
584
339
  function MessageAvatar({ className, ...props }) {
585
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
340
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
586
341
  "div",
587
342
  {
588
343
  "data-slot": "message-avatar",
@@ -595,7 +350,7 @@ function MessageAvatar({ className, ...props }) {
595
350
  );
596
351
  }
597
352
  function MessageContent({ className, ...props }) {
598
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
353
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
599
354
  "div",
600
355
  {
601
356
  "data-slot": "message-content",
@@ -608,7 +363,7 @@ function MessageContent({ className, ...props }) {
608
363
  );
609
364
  }
610
365
  function MessageHeader({ className, ...props }) {
611
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
366
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
612
367
  "div",
613
368
  {
614
369
  "data-slot": "message-header",
@@ -621,7 +376,7 @@ function MessageHeader({ className, ...props }) {
621
376
  );
622
377
  }
623
378
  function MessageFooter({ className, ...props }) {
624
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
379
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
625
380
  "div",
626
381
  {
627
382
  "data-slot": "message-footer",
@@ -635,23 +390,22 @@ function MessageFooter({ className, ...props }) {
635
390
  }
636
391
 
637
392
  // src/ui/adapters.ts
638
- var ChatMessageRoleContext = React2.createContext("assistant");
393
+ var ChatMessageRoleContext = React.createContext("assistant");
639
394
  function ChatMessage(input) {
640
395
  const { className, from, align, ...props } = input;
641
396
  const isUser = from === "user";
642
- const isRtl = props.dir === "rtl";
643
397
  const resolvedAlign = align || "start";
644
398
  const messageProps = Object.assign({}, props);
645
399
  messageProps.align = resolvedAlign;
646
400
  messageProps.className = cn(
647
401
  "a24-chat-message group flex w-full flex-col gap-2",
648
- isUser ? cn("a24-chat-message--user is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "a24-chat-message--assistant is-assistant max-w-full",
402
+ isUser ? "a24-chat-message--user is-user max-w-full justify-end" : "a24-chat-message--assistant is-assistant max-w-full",
649
403
  className
650
404
  );
651
- return React2.createElement(
405
+ return React.createElement(
652
406
  ChatMessageRoleContext.Provider,
653
407
  { value: from },
654
- React2.createElement(Message, messageProps)
408
+ React.createElement(Message, messageProps)
655
409
  );
656
410
  }
657
411
  function ChatMessageContent({
@@ -659,9 +413,9 @@ function ChatMessageContent({
659
413
  from,
660
414
  ...props
661
415
  }) {
662
- const inheritedRole = React2.useContext(ChatMessageRoleContext);
416
+ const inheritedRole = React.useContext(ChatMessageRoleContext);
663
417
  const isUser = (from ?? inheritedRole) === "user";
664
- return React2.createElement("div", {
418
+ return React.createElement("div", {
665
419
  className: cn(
666
420
  "a24-chat-message-content flex max-w-full min-w-0 flex-col gap-2 text-sm",
667
421
  isUser ? "a24-chat-message-content--user is-user:dark w-fit overflow-visible rounded-lg bg-secondary px-4 py-3 text-foreground" : "a24-chat-message-content--assistant w-full overflow-visible text-foreground",
@@ -675,7 +429,7 @@ function ChatMessageActions({
675
429
  className,
676
430
  ...props
677
431
  }) {
678
- return React2.createElement("div", {
432
+ return React.createElement("div", {
679
433
  className: cn("flex items-center gap-1", className),
680
434
  "data-slot": "message-actions",
681
435
  ...props
@@ -690,7 +444,7 @@ function ChatMessageAction({
690
444
  ...props
691
445
  }) {
692
446
  const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
693
- return React2.createElement(
447
+ return React.createElement(
694
448
  "button",
695
449
  {
696
450
  "aria-label": label || tooltipLabel,
@@ -702,60 +456,310 @@ function ChatMessageAction({
702
456
  type,
703
457
  ...props
704
458
  },
705
- children,
706
- React2.createElement(
707
- "span",
708
- { className: "sr-only" },
709
- label || tooltipLabel
710
- )
459
+ children,
460
+ React.createElement(
461
+ "span",
462
+ { className: "sr-only" },
463
+ label || tooltipLabel
464
+ )
465
+ );
466
+ }
467
+ var resolvedContentCache = /* @__PURE__ */ new WeakMap();
468
+ var CONTENT_CACHE_EXPIRY_BUFFER_MS = 15e3;
469
+ var CONTENT_CACHE_MAX_ENTRIES = 100;
470
+ function attachmentCacheKey(data) {
471
+ const id = typeof data.id === "string" ? data.id.trim() : "";
472
+ return id || null;
473
+ }
474
+ function cachedAttachmentContent(resolver, data) {
475
+ const key = attachmentCacheKey(data);
476
+ if (!key) return null;
477
+ const cached = resolvedContentCache.get(resolver)?.get(key);
478
+ if (!cached) return null;
479
+ if (!cached.validUntil) return cached;
480
+ const validUntil = Date.parse(cached.validUntil);
481
+ if (Number.isFinite(validUntil) && validUntil - CONTENT_CACHE_EXPIRY_BUFFER_MS > Date.now()) {
482
+ return cached;
483
+ }
484
+ resolvedContentCache.get(resolver)?.delete(key);
485
+ return null;
486
+ }
487
+ function cacheAttachmentContent(resolver, data, result) {
488
+ const key = attachmentCacheKey(data);
489
+ if (!key) return;
490
+ const cache = resolvedContentCache.get(resolver) || /* @__PURE__ */ new Map();
491
+ cache.delete(key);
492
+ cache.set(key, result);
493
+ while (cache.size > CONTENT_CACHE_MAX_ENTRIES) {
494
+ const oldestKey = cache.keys().next().value;
495
+ if (typeof oldestKey !== "string") break;
496
+ cache.delete(oldestKey);
497
+ }
498
+ resolvedContentCache.set(resolver, cache);
499
+ }
500
+ function invalidateAttachmentContent(resolver, data) {
501
+ const key = attachmentCacheKey(data);
502
+ if (key) resolvedContentCache.get(resolver)?.delete(key);
503
+ }
504
+ function attachmentMediaType(data) {
505
+ return data.mediaType || data.contentType || String(data.type || "application/octet-stream");
506
+ }
507
+ function attachmentFilename(data) {
508
+ return data.filename || data.name || "Attachment";
509
+ }
510
+ function attachmentExtension(filename) {
511
+ const extension = filename.split(".").pop();
512
+ return extension && extension !== filename ? extension.toLowerCase() : "";
513
+ }
514
+ function resolveAttachmentVisual(data) {
515
+ const mediaType = attachmentMediaType(data).toLowerCase();
516
+ const extension = attachmentExtension(attachmentFilename(data));
517
+ if (mediaType.startsWith("image/") || ["avif", "gif", "heic", "jpeg", "jpg", "png", "svg", "webp"].includes(extension)) {
518
+ return { fileIconType: extension || "image", kind: "image" };
519
+ }
520
+ if (mediaType === "application/pdf" || extension === "pdf") {
521
+ return { fileIconType: "pdf", kind: "pdf" };
522
+ }
523
+ if (mediaType.includes("spreadsheet") || mediaType.includes("csv") || ["csv", "numbers", "ods", "xls", "xlsm", "xlsx"].includes(extension)) {
524
+ if (["csv", "xls", "xlsx"].includes(extension)) {
525
+ return { fileIconType: extension, kind: "spreadsheet" };
526
+ }
527
+ return { fileIconType: "spreadsheets", kind: "spreadsheet" };
528
+ }
529
+ if (mediaType.includes("presentation") || ["key", "odp", "ppt", "pptx"].includes(extension)) {
530
+ return { fileIconType: extension === "ppt" ? "ppt" : "pptx", kind: "presentation" };
531
+ }
532
+ if (mediaType.includes("word") || mediaType.includes("rtf") || ["doc", "docx", "odt", "rtf"].includes(extension)) {
533
+ if (extension === "doc" || extension === "docx") {
534
+ return { fileIconType: extension, kind: "document" };
535
+ }
536
+ return { fileIconType: "document", kind: "document" };
537
+ }
538
+ if (mediaType.startsWith("audio/") || ["aac", "flac", "m4a", "mp3", "ogg", "wav"].includes(extension)) {
539
+ if (extension === "mp3" || extension === "wav") {
540
+ return { fileIconType: extension, kind: "audio" };
541
+ }
542
+ return { fileIconType: "audio", kind: "audio" };
543
+ }
544
+ if (mediaType.startsWith("video/") || ["avi", "m4v", "mkv", "mov", "mp4", "webm"].includes(extension)) {
545
+ if (["avi", "mkv", "mp4", "mpeg"].includes(extension)) {
546
+ return { fileIconType: extension, kind: "video" };
547
+ }
548
+ return { fileIconType: "video", kind: "video" };
549
+ }
550
+ if (mediaType.includes("zip") || mediaType.includes("compressed") || ["7z", "bz2", "gz", "rar", "tar", "tgz", "zip"].includes(extension)) {
551
+ return { fileIconType: extension === "rar" ? "rar" : "zip", kind: "archive" };
552
+ }
553
+ if (mediaType.includes("json") || mediaType.includes("javascript") || mediaType.includes("typescript") || mediaType.includes("xml") || ["css", "html", "js", "jsx", "json", "py", "sql", "ts", "tsx", "xml", "yaml", "yml"].includes(extension)) {
554
+ if (["css", "html", "java", "js", "json", "sql", "xml"].includes(extension)) {
555
+ return { fileIconType: extension, kind: "code" };
556
+ }
557
+ return { fileIconType: "code", kind: "code" };
558
+ }
559
+ if (mediaType.startsWith("text/") || ["md", "markdown", "txt"].includes(extension)) {
560
+ return { fileIconType: "txt", kind: "file" };
561
+ }
562
+ return { fileIconType: "empty", kind: "file" };
563
+ }
564
+ function solidFileIconBackground(fileIconType, kind) {
565
+ if (["txt", "zip", "rar"].includes(fileIconType)) return "#344054";
566
+ if (kind === "pdf") return "#D92D20";
567
+ if (kind === "spreadsheet") return "#079455";
568
+ if (kind === "document" || kind === "video") return "#155EEF";
569
+ if (kind === "presentation") return "#E62E05";
570
+ if (kind === "audio") return "#DD2590";
571
+ if (kind === "code") return "#444CE7";
572
+ return "#7F56D9";
573
+ }
574
+ function isImageAttachment(data) {
575
+ return resolveAttachmentVisual(data).kind === "image";
576
+ }
577
+ function attachmentRenderKey(data, index, rowKind) {
578
+ const id = typeof data.id === "string" ? data.id.trim() : "";
579
+ const identity = id || `${attachmentFilename(data)}:${attachmentMediaType(data)}`;
580
+ return `${rowKind}:${identity}:${index}`;
581
+ }
582
+ function ChatImageAttachment({
583
+ className,
584
+ data,
585
+ onRemove,
586
+ resolveContent,
587
+ ...props
588
+ }) {
589
+ const filename = attachmentFilename(data);
590
+ const initialUrl = typeof data.url === "string" ? data.url.trim() : "";
591
+ const contentStatus = String(data.contentStatus || data.content_status || "available");
592
+ const attachmentKey = attachmentCacheKey(data);
593
+ const dataRef = React.useRef(data);
594
+ dataRef.current = data;
595
+ const rootRef = React.useRef(null);
596
+ const retryCountRef = React.useRef(0);
597
+ const [isNearViewport, setIsNearViewport] = React.useState(Boolean(initialUrl));
598
+ const [sourceUrl, setSourceUrl] = React.useState(initialUrl);
599
+ const [status, setStatus] = React.useState(
600
+ initialUrl ? "loading" : contentStatus === "expired" || !resolveContent ? "error" : "idle"
601
+ );
602
+ const [resolutionAttempt, setResolutionAttempt] = React.useState(0);
603
+ React.useEffect(() => {
604
+ if (initialUrl && resolutionAttempt === 0 || contentStatus === "expired" || !resolveContent) return;
605
+ const node = rootRef.current;
606
+ if (!node || typeof IntersectionObserver === "undefined") {
607
+ setIsNearViewport(true);
608
+ return;
609
+ }
610
+ const observer = new IntersectionObserver(
611
+ (entries) => {
612
+ if (entries.some((entry) => entry.isIntersecting)) {
613
+ setIsNearViewport(true);
614
+ observer.disconnect();
615
+ }
616
+ },
617
+ { rootMargin: "240px" }
618
+ );
619
+ observer.observe(node);
620
+ return () => observer.disconnect();
621
+ }, [contentStatus, initialUrl, resolutionAttempt, resolveContent]);
622
+ React.useEffect(() => {
623
+ if (initialUrl && resolutionAttempt === 0) {
624
+ setSourceUrl(initialUrl);
625
+ setStatus("loading");
626
+ return;
627
+ }
628
+ if (!isNearViewport || !resolveContent || contentStatus === "expired") return;
629
+ const attachment = dataRef.current;
630
+ const cached = cachedAttachmentContent(resolveContent, attachment);
631
+ if (cached) {
632
+ setSourceUrl(cached.url);
633
+ setStatus("loading");
634
+ return;
635
+ }
636
+ const controller = new AbortController();
637
+ setStatus("resolving");
638
+ void resolveContent(attachment, controller.signal).then((result) => {
639
+ if (controller.signal.aborted) return;
640
+ if (!result.url?.trim()) throw new Error("Attachment content resolver returned an empty URL");
641
+ cacheAttachmentContent(resolveContent, attachment, result);
642
+ setSourceUrl(result.url);
643
+ setStatus("loading");
644
+ }).catch(() => {
645
+ if (!controller.signal.aborted) setStatus("error");
646
+ });
647
+ return () => controller.abort();
648
+ }, [attachmentKey, contentStatus, initialUrl, isNearViewport, resolutionAttempt, resolveContent]);
649
+ const handleImageError = () => {
650
+ if (resolveContent && retryCountRef.current === 0) {
651
+ retryCountRef.current = 1;
652
+ invalidateAttachmentContent(resolveContent, dataRef.current);
653
+ setSourceUrl("");
654
+ setStatus("resolving");
655
+ setResolutionAttempt((current) => current + 1);
656
+ return;
657
+ }
658
+ setStatus("error");
659
+ };
660
+ return React.createElement(
661
+ Attachment,
662
+ {
663
+ ...props,
664
+ className: cn("a24-chat-image-attachment", className),
665
+ orientation: "vertical",
666
+ ref: rootRef,
667
+ state: status === "error" ? "error" : status === "done" ? "done" : "processing",
668
+ title: filename
669
+ },
670
+ React.createElement(
671
+ AttachmentMedia,
672
+ { className: "a24-chat-image-media", variant: "image" },
673
+ sourceUrl ? React.createElement("img", {
674
+ alt: filename,
675
+ decoding: "async",
676
+ loading: "lazy",
677
+ onError: handleImageError,
678
+ onLoad: () => setStatus("done"),
679
+ src: sourceUrl
680
+ }) : status === "error" ? React.createElement(import_lucide_react.ImageOff, { "aria-hidden": "true", className: "size-5" }) : React.createElement(import_lucide_react.LoaderCircle, {
681
+ "aria-hidden": "true",
682
+ className: "a24-chat-image-spinner size-5 animate-spin"
683
+ }),
684
+ status !== "done" && status !== "error" ? React.createElement("span", { className: "sr-only", role: "status" }, `Loading ${filename}`) : null,
685
+ status === "error" ? React.createElement("span", { className: "sr-only" }, `${filename} preview unavailable`) : null
686
+ ),
687
+ onRemove ? React.createElement(
688
+ "button",
689
+ {
690
+ "aria-label": `Remove ${filename}`,
691
+ className: "a24-chat-image-remove",
692
+ onClick: (event) => {
693
+ event.stopPropagation();
694
+ onRemove();
695
+ },
696
+ type: "button"
697
+ },
698
+ React.createElement(import_lucide_react.X, { "aria-hidden": "true", className: "size-3.5" })
699
+ ) : null
711
700
  );
712
701
  }
713
702
  function ChatAttachment({
703
+ className,
714
704
  data,
715
705
  onRemove,
716
706
  orientation,
707
+ resolveContent,
717
708
  ...props
718
709
  }) {
719
- const mediaType = data.mediaType || data.contentType || String(data.type || "application/octet-stream");
720
- const filename = data.filename || data.name || "Attachment";
721
- const isImage = mediaType.startsWith("image/") && Boolean(data.url);
722
- return React2.createElement(
710
+ const filename = attachmentFilename(data);
711
+ const { fileIconType, kind } = resolveAttachmentVisual(data);
712
+ if (kind === "image") {
713
+ return React.createElement(ChatImageAttachment, {
714
+ ...props,
715
+ className,
716
+ data,
717
+ onRemove,
718
+ resolveContent
719
+ });
720
+ }
721
+ return React.createElement(
723
722
  Attachment,
724
723
  {
725
- orientation: orientation || (isImage ? "vertical" : "horizontal"),
724
+ className: cn("a24-chat-attachment", className),
725
+ orientation: orientation || "horizontal",
726
+ size: "sm",
726
727
  ...props
727
728
  },
728
- React2.createElement(
729
+ React.createElement(
729
730
  AttachmentMedia,
730
- { variant: isImage ? "image" : "icon" },
731
- isImage ? React2.createElement("img", {
732
- alt: filename,
733
- className: "size-full object-cover",
734
- src: data.url
735
- }) : React2.createElement(
736
- "span",
737
- { "aria-hidden": "true", className: "text-sm" },
738
- filename.slice(0, 1).toUpperCase()
739
- )
731
+ Object.assign(
732
+ {
733
+ className: "a24-chat-attachment-icon",
734
+ style: { backgroundColor: solidFileIconBackground(fileIconType, kind) },
735
+ variant: "icon"
736
+ },
737
+ { "data-file-kind": kind }
738
+ ),
739
+ React.createElement(import_file_icons.FileIcon, {
740
+ "aria-hidden": "true",
741
+ size: 28,
742
+ type: fileIconType,
743
+ variant: "solid"
744
+ })
740
745
  ),
741
- React2.createElement(
746
+ React.createElement(
742
747
  AttachmentContent,
743
- null,
744
- React2.createElement(AttachmentTitle, null, filename),
745
- React2.createElement(AttachmentDescription, null, mediaType)
748
+ { className: "a24-chat-attachment-content" },
749
+ React.createElement(AttachmentTitle, { title: filename }, filename)
746
750
  ),
747
- onRemove ? React2.createElement(
751
+ onRemove ? React.createElement(
748
752
  "button",
749
753
  {
750
754
  "aria-label": "Remove attachment",
751
- className: "relative z-20 mr-1 inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
755
+ className: "relative z-20 inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
752
756
  onClick: (event) => {
753
757
  event.stopPropagation();
754
758
  onRemove();
755
759
  },
756
760
  type: "button"
757
761
  },
758
- React2.createElement("span", { "aria-hidden": "true" }, "x")
762
+ React.createElement(import_lucide_react.X, { "aria-hidden": "true", className: "size-3.5" })
759
763
  ) : null
760
764
  );
761
765
  }
@@ -765,12 +769,11 @@ function ChatAttachments({
765
769
  ...props
766
770
  }) {
767
771
  if (!children) return null;
768
- return React2.createElement(
772
+ return React.createElement(
769
773
  AttachmentGroup,
770
774
  {
771
775
  className: cn(
772
- props.dir === "rtl" ? "mr-auto" : "ml-auto",
773
- "w-fit flex-wrap overflow-visible",
776
+ "a24-chat-attachments w-full flex-nowrap",
774
777
  className
775
778
  ),
776
779
  ...props
@@ -778,8 +781,305 @@ function ChatAttachments({
778
781
  children
779
782
  );
780
783
  }
784
+ function ChatAttachmentRows({
785
+ attachments,
786
+ className,
787
+ dir,
788
+ onRemove,
789
+ resolveContent,
790
+ ...props
791
+ }) {
792
+ if (attachments.length === 0) return null;
793
+ const imageAttachments = attachments.filter(isImageAttachment);
794
+ const fileAttachments = attachments.filter((attachment) => !isImageAttachment(attachment));
795
+ const renderRow = (items, kind) => items.length > 0 ? React.createElement(
796
+ ChatAttachments,
797
+ Object.assign({ dir, key: kind }, { "data-attachment-row": kind }),
798
+ items.map(
799
+ (attachment, index) => React.createElement(ChatAttachment, {
800
+ data: attachment,
801
+ dir: "ltr",
802
+ key: attachmentRenderKey(attachment, index, kind),
803
+ onRemove: onRemove ? () => onRemove(attachment) : void 0,
804
+ resolveContent
805
+ })
806
+ )
807
+ ) : null;
808
+ return React.createElement(
809
+ "div",
810
+ {
811
+ className: cn("a24-chat-attachment-rows", className),
812
+ dir,
813
+ ...props
814
+ },
815
+ renderRow(imageAttachments, "images"),
816
+ renderRow(fileAttachments, "files")
817
+ );
818
+ }
819
+
820
+ // src/ui/composer-attachments.ts
821
+ var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
822
+ function createAgentChatComposerFile(file) {
823
+ return {
824
+ id: createId(),
825
+ type: "file",
826
+ url: URL.createObjectURL(file),
827
+ filename: file.name || "attachment",
828
+ mediaType: file.type || "application/octet-stream",
829
+ source: file
830
+ };
831
+ }
832
+ function revokeAgentChatComposerFiles(files) {
833
+ files.forEach((file) => {
834
+ try {
835
+ URL.revokeObjectURL(file.url);
836
+ } catch {
837
+ }
838
+ });
839
+ }
840
+
841
+ // src/ui/agent-chat-composer.tsx
842
+ var import_jsx_runtime3 = require("react/jsx-runtime");
843
+ function formatFileCount(files) {
844
+ if (files.length === 1) return files[0].filename;
845
+ return `${files.length} files`;
846
+ }
847
+ var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
848
+ var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
849
+ function AgentChatComposer({
850
+ accept,
851
+ allowAttachments = true,
852
+ attachmentLayout = "flow",
853
+ className,
854
+ disabled = false,
855
+ forceExpanded = false,
856
+ inputToolbarContent,
857
+ isRunning = false,
858
+ onAttachmentCountChange,
859
+ onStop,
860
+ onSubmit,
861
+ placeholder = "Message the agent",
862
+ textareaRef
863
+ }) {
864
+ const fileInputRef = React2.useRef(null);
865
+ const filesRef = React2.useRef([]);
866
+ const inFlightFilesRef = React2.useRef([]);
867
+ const localTextareaRef = React2.useRef(null);
868
+ const draftRevisionRef = React2.useRef(0);
869
+ const [text, setText] = React2.useState("");
870
+ const [files, setFiles] = React2.useState([]);
871
+ const [isSubmitting, setIsSubmitting] = React2.useState(false);
872
+ const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
873
+ const setTextareaRef = React2.useCallback(
874
+ (node) => {
875
+ localTextareaRef.current = node;
876
+ if (typeof textareaRef === "function") {
877
+ textareaRef(node);
878
+ } else if (textareaRef) {
879
+ textareaRef.current = node;
880
+ }
881
+ },
882
+ [textareaRef]
883
+ );
884
+ React2.useEffect(() => {
885
+ filesRef.current = files;
886
+ onAttachmentCountChange?.(files.length);
887
+ }, [files, onAttachmentCountChange]);
888
+ React2.useEffect(() => () => {
889
+ revokeAgentChatComposerFiles(filesRef.current);
890
+ revokeAgentChatComposerFiles(inFlightFilesRef.current);
891
+ }, []);
892
+ React2.useLayoutEffect(() => {
893
+ const textarea = localTextareaRef.current;
894
+ if (!textarea) return;
895
+ if (isExpanded) {
896
+ textarea.style.height = "auto";
897
+ const scrollHeight = textarea.scrollHeight;
898
+ textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
899
+ textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
900
+ return;
901
+ }
902
+ textarea.style.height = "";
903
+ textarea.style.overflowY = "hidden";
904
+ }, [isExpanded, text]);
905
+ const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
906
+ const handleFilesChange = (event) => {
907
+ const selected = Array.from(event.target.files || []).map(createAgentChatComposerFile);
908
+ if (selected.length > 0) {
909
+ draftRevisionRef.current += 1;
910
+ setFiles((current) => [...current, ...selected]);
911
+ }
912
+ event.currentTarget.value = "";
913
+ };
914
+ const removeFile = (fileId) => {
915
+ draftRevisionRef.current += 1;
916
+ setFiles((current) => {
917
+ const target = current.find((file) => file.id === fileId);
918
+ if (target) revokeAgentChatComposerFiles([target]);
919
+ return current.filter((file) => file.id !== fileId);
920
+ });
921
+ };
922
+ const submit = async () => {
923
+ if (!canSubmit) return;
924
+ const submittedText = text.trim();
925
+ const submittedFiles = files;
926
+ const clearedRevision = draftRevisionRef.current + 1;
927
+ draftRevisionRef.current = clearedRevision;
928
+ inFlightFilesRef.current = submittedFiles;
929
+ setIsSubmitting(true);
930
+ setText("");
931
+ setFiles([]);
932
+ try {
933
+ await onSubmit({ text: submittedText, files: submittedFiles });
934
+ revokeAgentChatComposerFiles(submittedFiles);
935
+ inFlightFilesRef.current = [];
936
+ } catch {
937
+ if (draftRevisionRef.current === clearedRevision) {
938
+ draftRevisionRef.current += 1;
939
+ setText(submittedText);
940
+ setFiles(submittedFiles);
941
+ inFlightFilesRef.current = [];
942
+ } else {
943
+ revokeAgentChatComposerFiles(submittedFiles);
944
+ inFlightFilesRef.current = [];
945
+ }
946
+ } finally {
947
+ setIsSubmitting(false);
948
+ }
949
+ };
950
+ const handleKeyDown = (event) => {
951
+ if (event.key === "Enter" && !event.shiftKey) {
952
+ event.preventDefault();
953
+ void submit();
954
+ }
955
+ };
956
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
957
+ files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
958
+ "div",
959
+ {
960
+ "data-testid": "agent-chat-composer-attachments",
961
+ "data-layout": attachmentLayout,
962
+ className: cn(
963
+ "a24-composer__attachments min-w-0 py-1",
964
+ attachmentLayout === "overlay" ? "absolute bottom-full left-0 right-0 mb-1.5 flex-nowrap overflow-x-auto overflow-y-hidden overscroll-x-contain px-2" : "mb-2 flex-wrap px-1"
965
+ ),
966
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
967
+ ChatAttachmentRows,
968
+ {
969
+ attachments: files,
970
+ onRemove: (attachment) => attachment.id && removeFile(attachment.id)
971
+ }
972
+ )
973
+ }
974
+ ) : null,
975
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
976
+ "div",
977
+ {
978
+ "data-expanded": isExpanded ? "" : void 0,
979
+ className: cn(
980
+ "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
981
+ isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
982
+ ),
983
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
984
+ "div",
985
+ {
986
+ className: "a24-composer__grid w-full",
987
+ style: {
988
+ alignItems: "center",
989
+ display: "grid",
990
+ gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
991
+ gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
992
+ gap: isExpanded ? "3px 10px" : "8px"
993
+ },
994
+ children: [
995
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
997
+ "input",
998
+ {
999
+ ref: fileInputRef,
1000
+ accept,
1001
+ "aria-label": "Attach files",
1002
+ className: "hidden",
1003
+ multiple: true,
1004
+ name: "attachments",
1005
+ onChange: handleFilesChange,
1006
+ type: "file"
1007
+ }
1008
+ ),
1009
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1010
+ "button",
1011
+ {
1012
+ "aria-label": "Attach files",
1013
+ className: cn("a24-composer__attach", pillButtonClass),
1014
+ disabled: disabled || isSubmitting || isRunning || !allowAttachments,
1015
+ onClick: () => fileInputRef.current?.click(),
1016
+ title: "Attach files",
1017
+ type: "button",
1018
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Plus, { className: "size-4" })
1019
+ }
1020
+ )
1021
+ ] }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1023
+ "textarea",
1024
+ {
1025
+ ref: setTextareaRef,
1026
+ "aria-label": "Message",
1027
+ autoComplete: "off",
1028
+ className: cn(
1029
+ "a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0",
1030
+ isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
1031
+ ),
1032
+ disabled: disabled || isSubmitting || isRunning,
1033
+ name: "message",
1034
+ onChange: (event) => {
1035
+ draftRevisionRef.current += 1;
1036
+ setText(event.target.value);
1037
+ },
1038
+ onKeyDown: handleKeyDown,
1039
+ placeholder,
1040
+ rows: isExpanded ? 2 : 1,
1041
+ value: text
1042
+ }
1043
+ ) }),
1044
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
1045
+ !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
1046
+ !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
1047
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1048
+ "button",
1049
+ {
1050
+ "aria-label": "Stop generating",
1051
+ className: cn("a24-composer__submit", submitButtonClass),
1052
+ disabled: disabled || !onStop,
1053
+ onClick: onStop,
1054
+ title: "Stop",
1055
+ type: "button",
1056
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Square, { className: "size-3.5 fill-current" })
1057
+ }
1058
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1059
+ "button",
1060
+ {
1061
+ "aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
1062
+ className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
1063
+ disabled: !canSubmit,
1064
+ onClick: () => void submit(),
1065
+ title: "Send",
1066
+ type: "button",
1067
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
1068
+ }
1069
+ ) })
1070
+ ]
1071
+ }
1072
+ )
1073
+ }
1074
+ ),
1075
+ !isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
1076
+ ] });
1077
+ }
781
1078
 
782
1079
  // src/ui/agent-chat-actions.tsx
1080
+ var React3 = __toESM(require("react"), 1);
1081
+ var import_lucide_react3 = require("lucide-react");
1082
+ var import_radix_ui = require("radix-ui");
783
1083
  var import_jsx_runtime4 = require("react/jsx-runtime");
784
1084
  var FEEDBACK_REASONS = [
785
1085
  ["incorrect", "Incorrect"],
@@ -812,7 +1112,7 @@ function AgentChatDefaultActions({
812
1112
  label: "Retry",
813
1113
  onClick: () => void handlers.onRetry?.(message),
814
1114
  tooltip: "Regenerate response",
815
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.RefreshCcw, { className: "size-4" })
1115
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.RefreshCcw, { className: "size-4" })
816
1116
  }
817
1117
  ) : null,
818
1118
  canWriteFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -823,7 +1123,7 @@ function AgentChatDefaultActions({
823
1123
  rating: handlers.feedback?.rating === "like" ? null : "like"
824
1124
  }),
825
1125
  tooltip: "Like this response",
826
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
1126
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
827
1127
  }
828
1128
  ) : null,
829
1129
  canWriteFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_radix_ui.Popover.Root, { onOpenChange: setFeedbackOpen, open: feedbackOpen, children: [
@@ -839,7 +1139,7 @@ function AgentChatDefaultActions({
839
1139
  },
840
1140
  ref: setFeedbackTrigger,
841
1141
  tooltip: "Dislike this response",
842
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
1142
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
843
1143
  }
844
1144
  ) }),
845
1145
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -916,7 +1216,7 @@ function AgentChatDefaultActions({
916
1216
  label: "Copy",
917
1217
  onClick: () => handlers.onCopy?.(message.content || "", message.id),
918
1218
  tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
919
- children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.Copy, { className: "size-4" })
1219
+ children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Copy, { className: "size-4" })
920
1220
  }
921
1221
  ) : null
922
1222
  ] });
@@ -924,7 +1224,7 @@ function AgentChatDefaultActions({
924
1224
 
925
1225
  // src/ui/agent-chat-message.tsx
926
1226
  var React8 = __toESM(require("react"), 1);
927
- var import_lucide_react5 = require("lucide-react");
1227
+ var import_lucide_react6 = require("lucide-react");
928
1228
 
929
1229
  // src/ui/agent-response-timeline.tsx
930
1230
  var React7 = __toESM(require("react"), 1);
@@ -1113,7 +1413,7 @@ function useStreamingText({
1113
1413
 
1114
1414
  // src/ui/ask-user-interaction.tsx
1115
1415
  var React4 = __toESM(require("react"), 1);
1116
- var import_lucide_react3 = require("lucide-react");
1416
+ var import_lucide_react4 = require("lucide-react");
1117
1417
 
1118
1418
  // src/ui/ask-user-interaction-state.ts
1119
1419
  function emptyAskUserDrafts(questions) {
@@ -1207,8 +1507,8 @@ function ResolvedQuestions({
1207
1507
  " question",
1208
1508
  count === 1 ? "" : "s"
1209
1509
  ] }),
1210
- open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1211
- import_lucide_react3.ChevronRight,
1510
+ open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1511
+ import_lucide_react4.ChevronRight,
1212
1512
  {
1213
1513
  "aria-hidden": "true",
1214
1514
  className: "size-3.5 opacity-70 rtl:rotate-180"
@@ -1323,7 +1623,7 @@ function AskUserInteraction({
1323
1623
  onClick: () => setStep((current) => Math.max(0, current - 1)),
1324
1624
  type: "button",
1325
1625
  children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1326
- import_lucide_react3.ChevronLeft,
1626
+ import_lucide_react4.ChevronLeft,
1327
1627
  {
1328
1628
  "aria-hidden": "true",
1329
1629
  className: "size-4 rtl:rotate-180"
@@ -1352,7 +1652,7 @@ function AskUserInteraction({
1352
1652
  onClick: () => completeStep(drafts),
1353
1653
  type: "button",
1354
1654
  children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1355
- import_lucide_react3.ChevronRight,
1655
+ import_lucide_react4.ChevronRight,
1356
1656
  {
1357
1657
  "aria-hidden": "true",
1358
1658
  className: "size-4 rtl:rotate-180"
@@ -1389,7 +1689,7 @@ function AskUserInteraction({
1389
1689
  ),
1390
1690
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "min-w-0 flex-1 font-medium text-foreground", children: option.label }),
1391
1691
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1392
- import_lucide_react3.ChevronRight,
1692
+ import_lucide_react4.ChevronRight,
1393
1693
  {
1394
1694
  "aria-hidden": "true",
1395
1695
  className: "size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 rtl:rotate-180"
@@ -1401,7 +1701,7 @@ function AskUserInteraction({
1401
1701
  );
1402
1702
  }),
1403
1703
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-h-11 items-center gap-2 rounded-xl bg-muted px-2.5 py-1.5 focus-within:ring-1 focus-within:ring-ring", children: [
1404
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/70 text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Pencil, { "aria-hidden": "true", className: "size-4" }) }),
1704
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/70 text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.Pencil, { "aria-hidden": "true", className: "size-4" }) }),
1405
1705
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1406
1706
  "input",
1407
1707
  {
@@ -1653,7 +1953,7 @@ function resolvedHitlLabel(part) {
1653
1953
 
1654
1954
  // src/ui/tool-sessions.tsx
1655
1955
  var React6 = __toESM(require("react"), 1);
1656
- var import_lucide_react4 = require("lucide-react");
1956
+ var import_lucide_react5 = require("lucide-react");
1657
1957
  var import_thinking_orbs = require("thinking-orbs");
1658
1958
  var import_jsx_runtime8 = require("react/jsx-runtime");
1659
1959
  function agentChatToolLabel(part) {
@@ -1821,7 +2121,7 @@ function AgentChatNodeActivityRow({
1821
2121
  type: "button",
1822
2122
  children: [
1823
2123
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
1824
- open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2124
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
1825
2125
  ]
1826
2126
  }
1827
2127
  ) }),
@@ -1881,7 +2181,7 @@ function ToolDisclosure({
1881
2181
  children: [
1882
2182
  showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
1883
2183
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
1884
- open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
2184
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
1885
2185
  ]
1886
2186
  }
1887
2187
  ),
@@ -2342,7 +2642,7 @@ function AgentChatUserMessageContent({
2342
2642
  const [isExpanded, setIsExpanded] = React8.useState(false);
2343
2643
  const contentId = React8.useId();
2344
2644
  const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
2345
- const Icon = isExpanded ? import_lucide_react5.ChevronUp : import_lucide_react5.ChevronDown;
2645
+ const Icon = isExpanded ? import_lucide_react6.ChevronUp : import_lucide_react6.ChevronDown;
2346
2646
  const clampedStyle = shouldCollapse && !isExpanded ? {
2347
2647
  WebkitBoxOrient: "vertical",
2348
2648
  WebkitLineClamp: Math.max(1, collapsedLines),
@@ -2375,6 +2675,7 @@ function AgentChatMessage({
2375
2675
  additionalActions,
2376
2676
  actions,
2377
2677
  actionsClassName,
2678
+ attachmentContentResolver,
2378
2679
  className,
2379
2680
  contentClassName,
2380
2681
  dir,
@@ -2408,14 +2709,15 @@ function AgentChatMessage({
2408
2709
  }
2409
2710
  ) : null);
2410
2711
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
2411
- showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2412
- ChatAttachment,
2712
+ showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2713
+ ChatAttachmentRows,
2413
2714
  {
2414
- data: attachment,
2415
- dir: "ltr"
2416
- },
2417
- String(attachment.url || attachment.filename || index)
2418
- )) }) : null,
2715
+ attachments: message.attachments || [],
2716
+ className: "mb-2",
2717
+ dir,
2718
+ resolveContent: attachmentContentResolver
2719
+ }
2720
+ ) : null,
2419
2721
  shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2420
2722
  ChatMessageContent,
2421
2723
  {
@@ -2871,6 +3173,7 @@ function useAudioRecorder(onRecorded, options = {}) {
2871
3173
  BubbleGroup,
2872
3174
  BubbleReactions,
2873
3175
  ChatAttachment,
3176
+ ChatAttachmentRows,
2874
3177
  ChatAttachments,
2875
3178
  ChatMessage,
2876
3179
  ChatMessageAction,
@@ -2884,6 +3187,7 @@ function useAudioRecorder(onRecorded, options = {}) {
2884
3187
  MessageAction,
2885
3188
  MessageActions,
2886
3189
  MessageAttachment,
3190
+ MessageAttachmentRows,
2887
3191
  MessageAttachments,
2888
3192
  MessageAvatar,
2889
3193
  MessageContent,
@@ -2899,6 +3203,7 @@ function useAudioRecorder(onRecorded, options = {}) {
2899
3203
  bubbleVariants,
2900
3204
  cn,
2901
3205
  contextStatusMetrics,
3206
+ createAgentChatComposerFile,
2902
3207
  hitlActionLabel,
2903
3208
  hitlTitle,
2904
3209
  isAgentChatSubagent,
@@ -2907,6 +3212,7 @@ function useAudioRecorder(onRecorded, options = {}) {
2907
3212
  markerVariants,
2908
3213
  partitionAgentChatToolTimeline,
2909
3214
  resolvedHitlLabel,
3215
+ revokeAgentChatComposerFiles,
2910
3216
  shouldCollapseUserMessage,
2911
3217
  useAudioRecorder
2912
3218
  });