@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.
@@ -10,12 +10,18 @@ import {
10
10
  import {
11
11
  agentChatRuntimeErrorMessage
12
12
  } from "@agents24/chat-react/runtime";
13
- import { CircleAlert, LoaderCircle as LoaderCircle3, SquarePen as SquarePen2, X as X2 } from "lucide-react";
13
+ import { CircleAlert, LoaderCircle as LoaderCircle3, SquarePen as SquarePen2, X } from "lucide-react";
14
14
 
15
15
  // scaffold/src/components/chat/chat-composer.tsx
16
- import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square, X } from "lucide-react";
16
+ import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square } from "lucide-react";
17
17
  import * as React2 from "react";
18
- import { AgentChatContextStatus, useAudioRecorder } from "@agents24/chat-react/ui";
18
+ import {
19
+ AgentChatContextStatus,
20
+ ChatAttachmentRows,
21
+ createAgentChatComposerFile,
22
+ revokeAgentChatComposerFiles,
23
+ useAudioRecorder
24
+ } from "@agents24/chat-react/ui";
19
25
  import { canonicalInputMimeType, inputMimeTypes, validateAgentChatSubmission } from "@agents24/chat-react/runtime";
20
26
 
21
27
  // scaffold/src/components/ui/button.tsx
@@ -491,11 +497,20 @@ function ChatComposer({
491
497
  const [attachmentError, setAttachmentError] = React2.useState(null);
492
498
  const inputRef = React2.useRef(null);
493
499
  const textareaRef = React2.useRef(null);
500
+ const filesRef = React2.useRef([]);
501
+ const inFlightFilesRef = React2.useRef([]);
494
502
  const submittingRef = React2.useRef(false);
495
503
  const draftRevisionRef = React2.useRef(0);
496
504
  const attachmentMimeTypes = inputMimeTypes(inputContract);
497
505
  const allowAttachments = attachmentMimeTypes.length > 0;
498
506
  const recordingAllowed = inputContract.modalities.audio.recording_enabled;
507
+ React2.useEffect(() => {
508
+ filesRef.current = files;
509
+ }, [files]);
510
+ React2.useEffect(() => () => {
511
+ revokeAgentChatComposerFiles(filesRef.current);
512
+ revokeAgentChatComposerFiles(inFlightFilesRef.current);
513
+ }, []);
499
514
  React2.useEffect(() => {
500
515
  if (disabled || isRunning) return;
501
516
  const frame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }));
@@ -503,14 +518,14 @@ function ChatComposer({
503
518
  }, [disabled, focusKey, isRunning]);
504
519
  const addRecording = React2.useCallback((file) => {
505
520
  draftRevisionRef.current += 1;
506
- setFiles((current) => [...current, file]);
521
+ setFiles((current) => [...current, createAgentChatComposerFile(file)]);
507
522
  }, []);
508
523
  const recorder = useAudioRecorder(addRecording);
509
524
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
510
525
  const validationError = validateAgentChatSubmission(
511
526
  inputContract,
512
527
  text,
513
- files.map((file) => ({ data: file, mediaType: file.type }))
528
+ files.map((file) => ({ data: file.source, mediaType: file.mediaType }))
514
529
  );
515
530
  const selectedAgent = agents.find((item) => item.id === agentId);
516
531
  const selectedModel = models.find((item) => item.id === modelId);
@@ -558,16 +573,23 @@ function ChatComposer({
558
573
  const clearedRevision = draftRevisionRef.current + 1;
559
574
  draftRevisionRef.current = clearedRevision;
560
575
  submittingRef.current = true;
576
+ inFlightFilesRef.current = selectedFiles;
561
577
  setText("");
562
578
  setFiles([]);
563
579
  textareaRef.current?.focus();
564
580
  try {
565
581
  await onSubmit(value, selectedFiles);
582
+ revokeAgentChatComposerFiles(selectedFiles);
583
+ inFlightFilesRef.current = [];
566
584
  } catch {
567
585
  if (draftRevisionRef.current === clearedRevision) {
568
586
  draftRevisionRef.current += 1;
569
587
  setText(value);
570
588
  setFiles(selectedFiles);
589
+ inFlightFilesRef.current = [];
590
+ } else {
591
+ revokeAgentChatComposerFiles(selectedFiles);
592
+ inFlightFilesRef.current = [];
571
593
  }
572
594
  } finally {
573
595
  submittingRef.current = false;
@@ -579,13 +601,21 @@ function ChatComposer({
579
601
  className: "mx-auto w-full max-w-3xl px-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:px-6",
580
602
  "data-agents24-chat-composer": "",
581
603
  children: [
582
- allowAttachments && files.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-2 flex min-w-0 gap-2 overflow-x-auto", children: files.map((file, index) => /* @__PURE__ */ jsxs3("div", { className: "flex max-w-48 items-center gap-2 border bg-card px-2 py-1 text-xs", children: [
583
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: file.name }),
584
- /* @__PURE__ */ jsx7(Button, { variant: "ghost", size: "icon-xs", "aria-label": `Remove ${file.name}`, onClick: () => {
585
- draftRevisionRef.current += 1;
586
- setFiles((current) => current.filter((_, itemIndex) => itemIndex !== index));
587
- }, children: /* @__PURE__ */ jsx7(X, {}) })
588
- ] }, `${file.name}-${index}`)) }),
604
+ allowAttachments && files.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-2 min-w-0", children: /* @__PURE__ */ jsx7(
605
+ ChatAttachmentRows,
606
+ {
607
+ attachments: files,
608
+ onRemove: (attachment) => {
609
+ if (!attachment.id) return;
610
+ draftRevisionRef.current += 1;
611
+ setFiles((current) => {
612
+ const removed = current.find((file) => file.id === attachment.id);
613
+ if (removed) revokeAgentChatComposerFiles([removed]);
614
+ return current.filter((file) => file.id !== attachment.id);
615
+ });
616
+ }
617
+ }
618
+ ) }),
589
619
  attachmentError || recorder.error ? /* @__PURE__ */ jsx7("p", { className: "mb-2 text-xs text-destructive", role: "alert", children: attachmentError || recorder.error }) : null,
590
620
  /* @__PURE__ */ jsxs3(
591
621
  "div",
@@ -609,7 +639,7 @@ function ChatComposer({
609
639
  const selected = Array.from(event.target.files ?? []);
610
640
  const accepted = selected.filter((file) => attachmentMimeTypes.includes(canonicalInputMimeType(file.type)));
611
641
  draftRevisionRef.current += 1;
612
- setFiles((current) => [...current, ...accepted]);
642
+ setFiles((current) => [...current, ...accepted.map(createAgentChatComposerFile)]);
613
643
  setAttachmentError(
614
644
  accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent."
615
645
  );
@@ -696,7 +726,7 @@ function ChatComposer({
696
726
  }
697
727
 
698
728
  // scaffold/src/components/chat/chat-message.tsx
699
- import { AgentChatDefaultActions, ChatAttachment, ChatAttachments } from "@agents24/chat-react/ui";
729
+ import { AgentChatDefaultActions, ChatAttachmentRows as ChatAttachmentRows2 } from "@agents24/chat-react/ui";
700
730
 
701
731
  // scaffold/src/components/chat/message-parts.tsx
702
732
  import {
@@ -1104,17 +1134,19 @@ function ChatMessage({
1104
1134
  onHitlAction,
1105
1135
  onRegenerate,
1106
1136
  onSetFeedback,
1137
+ resolveAttachmentContent,
1107
1138
  Timeline
1108
1139
  }) {
1109
1140
  const isUser = message.role === "user";
1110
1141
  return /* @__PURE__ */ jsx14(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ jsxs8(MessageContent, { children: [
1111
- isUser && message.attachments?.length ? /* @__PURE__ */ jsx14(ChatAttachments, { className: "mb-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx14(
1112
- ChatAttachment,
1142
+ isUser && message.attachments?.length ? /* @__PURE__ */ jsx14(
1143
+ ChatAttachmentRows2,
1113
1144
  {
1114
- data: attachment
1115
- },
1116
- String(attachment.id || attachment.url || attachment.filename || index)
1117
- )) }) : null,
1145
+ attachments: message.attachments,
1146
+ className: "mb-2",
1147
+ resolveContent: resolveAttachmentContent
1148
+ }
1149
+ ) : null,
1118
1150
  /* @__PURE__ */ jsx14(Bubble, { variant: isUser ? "secondary" : "ghost", align: isUser ? "end" : "start", className: isUser ? "max-w-[min(75%,42rem)]" : "max-w-full overflow-visible", children: /* @__PURE__ */ jsx14(BubbleContent, { className: isUser ? "px-4 py-2.5" : "w-full overflow-visible", children: /* @__PURE__ */ jsx14(
1119
1151
  MessageParts,
1120
1152
  {
@@ -2142,7 +2174,7 @@ function OperationErrorNotice({ runtime }) {
2142
2174
  onClick: runtime.operationError ? runtime.clearOperationError : runtime.controller.clearBackgroundError,
2143
2175
  size: "icon-xs",
2144
2176
  variant: "ghost",
2145
- children: /* @__PURE__ */ jsx25(X2, {})
2177
+ children: /* @__PURE__ */ jsx25(X, {})
2146
2178
  }
2147
2179
  )
2148
2180
  ] }) });
@@ -2154,7 +2186,8 @@ function ChatShell({
2154
2186
  sidebar
2155
2187
  }) {
2156
2188
  const { controller } = runtime;
2157
- const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling" || runtime.isSubmitting;
2189
+ const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling";
2190
+ const isUploading = controller.runState === "uploading" || runtime.isUploading;
2158
2191
  const isCancelling = controller.runState === "cancelling";
2159
2192
  const activeTitle = controller.threads.find((thread) => thread.id === controller.activeThreadId)?.title;
2160
2193
  const hasBlockingError = Boolean(runtime.fatalError && controller.messages.length === 0);
@@ -2210,7 +2243,7 @@ function ChatShell({
2210
2243
  {
2211
2244
  inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2212
2245
  contextWindow: runtime.contextWindow,
2213
- disabled: runtime.isBootstrapping || runtime.isChangingAgent,
2246
+ disabled: runtime.isBootstrapping || runtime.isChangingAgent || isUploading,
2214
2247
  focusKey: controller.activeThreadId,
2215
2248
  forceExpanded: true,
2216
2249
  isCancelling,
@@ -2229,10 +2262,11 @@ function ChatShell({
2229
2262
  onSubmit: (text, files) => runtime.submit({
2230
2263
  text,
2231
2264
  files: files.map((file) => ({
2232
- data: file,
2233
- mediaType: file.type || "application/octet-stream",
2234
- name: file.name
2235
- }))
2265
+ data: file.source,
2266
+ mediaType: file.mediaType,
2267
+ name: file.filename
2268
+ })),
2269
+ attachments: files
2236
2270
  })
2237
2271
  }
2238
2272
  )
@@ -2285,6 +2319,7 @@ function ChatShell({
2285
2319
  onHitlAction: runtime.onHitlAction,
2286
2320
  onRegenerate: () => controller.regenerate(message),
2287
2321
  onSetFeedback: (input) => controller.setFeedback(message, input),
2322
+ resolveAttachmentContent: runtime.resolveAttachmentContent,
2288
2323
  Timeline: components?.Timeline
2289
2324
  }
2290
2325
  )
@@ -2304,7 +2339,7 @@ function ChatShell({
2304
2339
  {
2305
2340
  inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2306
2341
  contextWindow: runtime.contextWindow,
2307
- disabled: runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError,
2342
+ disabled: runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError || isUploading,
2308
2343
  focusKey: controller.activeThreadId,
2309
2344
  isCancelling,
2310
2345
  isRunning,
@@ -2322,10 +2357,11 @@ function ChatShell({
2322
2357
  onSubmit: (text, files) => runtime.submit({
2323
2358
  text,
2324
2359
  files: files.map((file) => ({
2325
- data: file,
2326
- mediaType: file.type || "application/octet-stream",
2327
- name: file.name
2328
- }))
2360
+ data: file.source,
2361
+ mediaType: file.mediaType,
2362
+ name: file.filename
2363
+ })),
2364
+ attachments: files
2329
2365
  }),
2330
2366
  utilityRow: "below"
2331
2367
  }