@agents24/chat-react 0.5.15 → 0.5.17

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 (36) hide show
  1. package/compatibility.json +8 -8
  2. package/dist/index.cjs +8 -0
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +7 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/latest-thread-scroller.d.ts +5 -0
  7. package/dist/runtime/index.cjs +16 -2
  8. package/dist/runtime/index.cjs.map +1 -1
  9. package/dist/runtime/index.js +16 -2
  10. package/dist/runtime/index.js.map +1 -1
  11. package/dist/runtime/use-agent-chat-runtime.d.ts +1 -0
  12. package/dist/scaffold/components/chat/chat-composer.d.ts +6 -1
  13. package/dist/scaffold/components/chat/chat-message.d.ts +2 -1
  14. package/dist/scaffold/index.cjs +185 -286
  15. package/dist/scaffold/index.cjs.map +1 -1
  16. package/dist/scaffold/index.js +190 -291
  17. package/dist/scaffold/index.js.map +1 -1
  18. package/dist/styles.css +34 -1
  19. package/dist/types.d.ts +1 -1
  20. package/dist/ui/agent-chat-actions.d.ts +4 -1
  21. package/dist/ui/agent-chat-composer.d.ts +9 -1
  22. package/dist/ui/agent-chat-queue.d.ts +12 -0
  23. package/dist/ui/composer-attachments.d.ts +5 -1
  24. package/dist/ui/composer-interactions.d.ts +17 -0
  25. package/dist/ui/index.cjs +531 -331
  26. package/dist/ui/index.cjs.map +1 -1
  27. package/dist/ui/index.d.ts +2 -0
  28. package/dist/ui/index.js +516 -322
  29. package/dist/ui/index.js.map +1 -1
  30. package/dist/ui/tool-sessions.d.ts +4 -1
  31. package/package.json +4 -4
  32. package/scaffold/src/components/chat/chat-composer.tsx +25 -26
  33. package/scaffold/src/components/chat/chat-message.tsx +7 -2
  34. package/scaffold/src/components/chat/chat-shell.tsx +52 -69
  35. package/dist/scaffold/components/ui/message-scroller.d.ts +0 -10
  36. package/scaffold/src/components/ui/message-scroller.tsx +0 -129
@@ -10,6 +10,7 @@ import {
10
10
  import {
11
11
  agentChatRuntimeErrorMessage
12
12
  } from "@agents24/chat-react/runtime";
13
+ import { AgentChatQueue, queuedMessageComposerFiles, useLiveCompletionRuns } from "@agents24/chat-react/ui";
13
14
  import { CircleAlert, LoaderCircle as LoaderCircle3, SquarePen as SquarePen2, X } from "lucide-react";
14
15
  import * as React8 from "react";
15
16
 
@@ -19,9 +20,11 @@ import * as React2 from "react";
19
20
  import {
20
21
  AgentChatContextStatus,
21
22
  ChatAttachmentRows,
23
+ chatComposerAction,
22
24
  createAgentChatComposerFile,
23
25
  revokeAgentChatComposerFiles,
24
- useAudioRecorder
26
+ useAudioRecorder,
27
+ useChatComposerFocus
25
28
  } from "@agents24/chat-react/ui";
26
29
  import { canonicalInputMimeType, inputMimeTypes, validateAgentChatSubmission } from "@agents24/chat-react/runtime";
27
30
 
@@ -491,6 +494,7 @@ function ChatComposer({
491
494
  onAgentChange,
492
495
  onStop,
493
496
  onSubmit,
497
+ restoredDraft,
494
498
  utilityRow = "inline"
495
499
  }) {
496
500
  const [text, setText] = React2.useState("");
@@ -512,11 +516,18 @@ function ChatComposer({
512
516
  revokeAgentChatComposerFiles(filesRef.current);
513
517
  revokeAgentChatComposerFiles(inFlightFilesRef.current);
514
518
  }, []);
515
- React2.useEffect(() => {
516
- if (disabled || isRunning) return;
517
- const frame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }));
518
- return () => window.cancelAnimationFrame(frame);
519
- }, [disabled, focusKey, isRunning]);
519
+ const focusComposer = useChatComposerFocus({
520
+ textareaRef,
521
+ disabled: Boolean(disabled),
522
+ focusKey,
523
+ restoredDraft,
524
+ onRestore: (draft) => {
525
+ draftRevisionRef.current += 1;
526
+ revokeAgentChatComposerFiles(filesRef.current);
527
+ setText(draft.text);
528
+ setFiles(draft.files);
529
+ }
530
+ });
520
531
  const addRecording = React2.useCallback((file) => {
521
532
  draftRevisionRef.current += 1;
522
533
  setFiles((current) => [...current, createAgentChatComposerFile(file)]);
@@ -526,8 +537,9 @@ function ChatComposer({
526
537
  const validationError = validateAgentChatSubmission(
527
538
  inputContract,
528
539
  text,
529
- files.map((file) => ({ data: file.source, mediaType: file.mediaType }))
540
+ files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType }))
530
541
  );
542
+ const action = chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning, isCancelling });
531
543
  const selectedAgent = agents.find((item) => item.id === agentId);
532
544
  const selectedModel = models.find((item) => item.id === modelId);
533
545
  const modelSelector = models.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "w-fit max-w-40", children: /* @__PURE__ */ jsxs3(Select, { disabled: disabled || isRunning, onValueChange: onModelChange, value: modelId || void 0, children: [
@@ -568,7 +580,7 @@ function ChatComposer({
568
580
  const hasContextWindow = Number.isFinite(Number(contextWindow?.max_tokens)) && Number(contextWindow?.max_tokens) > 0;
569
581
  const showUtilityRow = utilityRow === "below";
570
582
  const submit = async () => {
571
- if (disabled || isRunning || submittingRef.current || validationError) return;
583
+ if (disabled || submittingRef.current || validationError) return;
572
584
  const value = text;
573
585
  const selectedFiles = files;
574
586
  const clearedRevision = draftRevisionRef.current + 1;
@@ -577,7 +589,7 @@ function ChatComposer({
577
589
  inFlightFilesRef.current = selectedFiles;
578
590
  setText("");
579
591
  setFiles([]);
580
- textareaRef.current?.focus();
592
+ focusComposer();
581
593
  try {
582
594
  await onSubmit(value, selectedFiles);
583
595
  revokeAgentChatComposerFiles(selectedFiles);
@@ -599,7 +611,7 @@ function ChatComposer({
599
611
  return /* @__PURE__ */ jsxs3(
600
612
  "div",
601
613
  {
602
- className: "mx-auto w-full max-w-3xl px-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:px-6",
614
+ className: "relative z-10 w-full pb-[max(1rem,env(safe-area-inset-bottom))]",
603
615
  "data-agents24-chat-composer": "",
604
616
  children: [
605
617
  allowAttachments && files.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-2 min-w-0", children: /* @__PURE__ */ jsx7(
@@ -648,7 +660,7 @@ function ChatComposer({
648
660
  }
649
661
  }
650
662
  ),
651
- /* @__PURE__ */ jsx7("div", { className: "agents24-composer-attach flex min-w-0 items-center", children: /* @__PURE__ */ jsx7(Button, { className: "bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground", variant: "ghost", size: "icon", "aria-label": "Attach files", disabled: disabled || isRunning, onClick: () => inputRef.current?.click(), children: /* @__PURE__ */ jsx7("span", { "aria-hidden": true, children: "+" }) }) })
663
+ /* @__PURE__ */ jsx7("div", { className: "agents24-composer-attach flex min-w-0 items-center", children: /* @__PURE__ */ jsx7(Button, { className: "bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground", variant: "ghost", size: "icon", "aria-label": "Attach files", disabled, onClick: () => inputRef.current?.click(), children: /* @__PURE__ */ jsx7("span", { "aria-hidden": true, children: "+" }) }) })
652
664
  ] }) : /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", className: "agents24-composer-attach" }),
653
665
  /* @__PURE__ */ jsx7(
654
666
  Textarea,
@@ -692,7 +704,7 @@ function ChatComposer({
692
704
  children: recorder.isRecording ? /* @__PURE__ */ jsx7(Square, { className: "fill-current" }) : /* @__PURE__ */ jsx7(Mic, {})
693
705
  }
694
706
  ) : null,
695
- isRunning ? /* @__PURE__ */ jsx7(
707
+ action === "stop" ? /* @__PURE__ */ jsx7(
696
708
  Button,
697
709
  {
698
710
  size: "icon",
@@ -702,9 +714,9 @@ function ChatComposer({
702
714
  children: isCancelling ? /* @__PURE__ */ jsx7(LoaderCircle, { className: "animate-spin" }) : /* @__PURE__ */ jsx7(Square, { className: "fill-current" })
703
715
  }
704
716
  ) : validationError ? /* @__PURE__ */ jsxs3(Tooltip, { children: [
705
- /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7("span", { tabIndex: 0, "aria-label": validationError, children: /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": "Send message", disabled: true, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) }) }) }),
717
+ /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7("span", { tabIndex: 0, "aria-label": validationError, children: /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": isRunning ? "Queue message" : "Send message", disabled: true, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) }) }) }),
706
718
  /* @__PURE__ */ jsx7(TooltipContent, { children: validationError })
707
- ] }) : /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": "Send message", disabled, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) })
719
+ ] }) : /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": isRunning ? "Queue message" : "Send message", disabled, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) })
708
720
  ] })
709
721
  ]
710
722
  }
@@ -727,7 +739,7 @@ function ChatComposer({
727
739
  }
728
740
 
729
741
  // scaffold/src/components/chat/chat-message.tsx
730
- import { AgentChatDefaultActions, AgentChatUserMessageActionRow, ChatAttachmentRows as ChatAttachmentRows2 } from "@agents24/chat-react/ui";
742
+ import { AgentChatDefaultActions, AgentChatUserMessageActionRow, ChatAttachmentRows as ChatAttachmentRows2, useCompletionActionEntrance } from "@agents24/chat-react/ui";
731
743
 
732
744
  // scaffold/src/components/chat/message-parts.tsx
733
745
  import {
@@ -1128,6 +1140,7 @@ function MessageFooter({ className, ...props }) {
1128
1140
  // scaffold/src/components/chat/chat-message.tsx
1129
1141
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1130
1142
  function ChatMessage({
1143
+ animateCompletion,
1131
1144
  allowFeedback,
1132
1145
  getHitlActionState,
1133
1146
  isLoading,
@@ -1139,6 +1152,8 @@ function ChatMessage({
1139
1152
  Timeline
1140
1153
  }) {
1141
1154
  const isUser = message.role === "user";
1155
+ const showCompletionActions = !isUser && message.isFinal !== false && !isLoading;
1156
+ const animateCompletionActions = useCompletionActionEntrance(showCompletionActions, message.runId, animateCompletion);
1142
1157
  return /* @__PURE__ */ jsx14(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ jsxs8(MessageContent, { className: isUser ? "gap-0" : void 0, children: [
1143
1158
  isUser && message.attachments?.length ? /* @__PURE__ */ jsx14(
1144
1159
  ChatAttachmentRows2,
@@ -1169,9 +1184,10 @@ function ChatMessage({
1169
1184
  message
1170
1185
  }
1171
1186
  ) }),
1172
- !isUser && message.isFinal !== false && /* @__PURE__ */ jsx14(MessageFooter, { className: "gap-1 opacity-70 transition-opacity group-hover/message:opacity-100", children: /* @__PURE__ */ jsx14(
1187
+ showCompletionActions && /* @__PURE__ */ jsx14(MessageFooter, { className: "gap-1 opacity-70 transition-opacity group-hover/message:opacity-100", children: /* @__PURE__ */ jsx14(
1173
1188
  AgentChatDefaultActions,
1174
1189
  {
1190
+ animateCompletion: animateCompletionActions,
1175
1191
  handlers: {
1176
1192
  feedback: message.feedback,
1177
1193
  onCopy: (content) => {
@@ -2012,117 +2028,12 @@ function isScaffoldTimelineLoading({
2012
2028
  return message.id === streamingMessageId || Boolean(message.runId && message.runId === activeRunId);
2013
2029
  }
2014
2030
 
2015
- // scaffold/src/components/ui/message-scroller.tsx
2031
+ // scaffold/src/components/chat/chat-shell.tsx
2016
2032
  import {
2017
- MessageScroller as MessageScrollerPrimitive,
2018
- useMessageScroller,
2019
- useMessageScrollerScrollable,
2020
- useMessageScrollerVisibility
2021
- } from "@shadcn/react/message-scroller";
2022
- import { ArrowDownIcon } from "lucide-react";
2033
+ LatestThreadScroller as LatestThreadScroller2,
2034
+ latestThreadAnchorId
2035
+ } from "@agents24/chat-react";
2023
2036
  import { Fragment as Fragment3, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
2024
- function MessageScrollerProvider(props) {
2025
- return /* @__PURE__ */ jsx24(MessageScrollerPrimitive.Provider, { ...props });
2026
- }
2027
- function MessageScroller({
2028
- className,
2029
- ...props
2030
- }) {
2031
- return /* @__PURE__ */ jsx24(
2032
- MessageScrollerPrimitive.Root,
2033
- {
2034
- "data-slot": "message-scroller",
2035
- className: cn(
2036
- "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
2037
- className
2038
- ),
2039
- ...props
2040
- }
2041
- );
2042
- }
2043
- function MessageScrollerViewport({
2044
- className,
2045
- ...props
2046
- }) {
2047
- return /* @__PURE__ */ jsx24(
2048
- MessageScrollerPrimitive.Viewport,
2049
- {
2050
- "data-slot": "message-scroller-viewport",
2051
- className: cn(
2052
- "size-full min-h-0 min-w-0 scroll-fade-b overflow-y-auto overscroll-contain contain-content [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
2053
- className
2054
- ),
2055
- ...props
2056
- }
2057
- );
2058
- }
2059
- function MessageScrollerContent({
2060
- className,
2061
- ...props
2062
- }) {
2063
- return /* @__PURE__ */ jsx24(
2064
- MessageScrollerPrimitive.Content,
2065
- {
2066
- "data-slot": "message-scroller-content",
2067
- className: cn("flex h-max min-h-full flex-col gap-3", className),
2068
- ...props
2069
- }
2070
- );
2071
- }
2072
- function MessageScrollerItem({
2073
- className,
2074
- scrollAnchor = false,
2075
- ...props
2076
- }) {
2077
- return /* @__PURE__ */ jsx24(
2078
- MessageScrollerPrimitive.Item,
2079
- {
2080
- "data-slot": "message-scroller-item",
2081
- scrollAnchor,
2082
- className: cn(
2083
- "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
2084
- className
2085
- ),
2086
- ...props
2087
- }
2088
- );
2089
- }
2090
- function MessageScrollerButton({
2091
- direction = "end",
2092
- className,
2093
- children,
2094
- render,
2095
- variant = "secondary",
2096
- size = "icon-sm",
2097
- ...props
2098
- }) {
2099
- return /* @__PURE__ */ jsx24(
2100
- MessageScrollerPrimitive.Button,
2101
- {
2102
- "data-slot": "message-scroller-button",
2103
- "data-direction": direction,
2104
- "data-variant": variant,
2105
- "data-size": size,
2106
- direction,
2107
- className: cn(
2108
- "absolute inset-s-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180",
2109
- className
2110
- ),
2111
- render: render ?? /* @__PURE__ */ jsx24(Button, { variant, size }),
2112
- ...props,
2113
- children: children ?? /* @__PURE__ */ jsxs14(Fragment3, { children: [
2114
- /* @__PURE__ */ jsx24(
2115
- ArrowDownIcon,
2116
- {}
2117
- ),
2118
- /* @__PURE__ */ jsx24("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
2119
- ] })
2120
- }
2121
- );
2122
- }
2123
-
2124
- // scaffold/src/components/chat/chat-shell.tsx
2125
- import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
2126
2037
  var LOADING_INPUT_CONTRACT = {
2127
2038
  version: "agents24.runtime-inputs.v2",
2128
2039
  modalities: {
@@ -2135,31 +2046,31 @@ var LOADING_INPUT_CONTRACT = {
2135
2046
  retention: { mode: "thread_lifetime" }
2136
2047
  };
2137
2048
  function PersistentSidebarTrigger() {
2138
- return /* @__PURE__ */ jsx25("div", { className: "pointer-events-none fixed start-3 top-0 z-[60] flex h-12 items-center", children: /* @__PURE__ */ jsx25(SidebarTrigger, { className: "pointer-events-auto [&_svg]:size-[18px]" }) });
2049
+ return /* @__PURE__ */ jsx24("div", { className: "pointer-events-none fixed start-3 top-0 z-[60] flex h-12 items-center", children: /* @__PURE__ */ jsx24(SidebarTrigger, { className: "pointer-events-auto [&_svg]:size-[18px]" }) });
2139
2050
  }
2140
2051
  function CollapsedHeaderActions({ onNew }) {
2141
2052
  const { isMobile, openMobile, state } = useSidebar();
2142
2053
  const sidebarVisible = isMobile ? openMobile : state === "expanded";
2143
2054
  if (sidebarVisible) return null;
2144
- return /* @__PURE__ */ jsxs15(Fragment4, { children: [
2145
- /* @__PURE__ */ jsx25("div", { className: "h-8 w-17 shrink-0", "aria-hidden": "true" }),
2146
- /* @__PURE__ */ jsx25("div", { className: "pointer-events-none fixed start-12 top-0 z-[60] flex h-12 items-center", children: /* @__PURE__ */ jsx25(Button, { className: "pointer-events-auto", variant: "ghost", size: "icon-sm", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx25(SquarePen2, {}) }) })
2055
+ return /* @__PURE__ */ jsxs14(Fragment3, { children: [
2056
+ /* @__PURE__ */ jsx24("div", { className: "h-8 w-17 shrink-0", "aria-hidden": "true" }),
2057
+ /* @__PURE__ */ jsx24("div", { className: "pointer-events-none fixed start-12 top-0 z-[60] flex h-12 items-center", children: /* @__PURE__ */ jsx24(Button, { className: "pointer-events-auto", variant: "ghost", size: "icon-sm", "aria-label": "New chat", onClick: onNew, children: /* @__PURE__ */ jsx24(SquarePen2, {}) }) })
2147
2058
  ] });
2148
2059
  }
2149
2060
  function OperationErrorNotice({ runtime }) {
2150
2061
  const error = runtime.operationError || runtime.backgroundError;
2151
2062
  if (!error) return null;
2152
- return /* @__PURE__ */ jsx25("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-40 flex justify-center px-4", children: /* @__PURE__ */ jsxs15("div", { className: "pointer-events-auto flex max-w-lg items-start gap-2 rounded-lg border border-destructive/20 bg-background px-3 py-2 text-sm shadow-md", role: "alert", children: [
2153
- /* @__PURE__ */ jsx25(CircleAlert, { className: "mt-0.5 size-4 shrink-0 text-destructive" }),
2154
- /* @__PURE__ */ jsx25("span", { className: "min-w-0 flex-1 wrap-break-word", children: agentChatRuntimeErrorMessage(error) || "The chat could not complete that action." }),
2155
- /* @__PURE__ */ jsx25(
2063
+ return /* @__PURE__ */ jsx24("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-40 flex justify-center px-4", children: /* @__PURE__ */ jsxs14("div", { className: "pointer-events-auto flex max-w-lg items-start gap-2 rounded-lg border border-destructive/20 bg-background px-3 py-2 text-sm shadow-md", role: "alert", children: [
2064
+ /* @__PURE__ */ jsx24(CircleAlert, { className: "mt-0.5 size-4 shrink-0 text-destructive" }),
2065
+ /* @__PURE__ */ jsx24("span", { className: "min-w-0 flex-1 wrap-break-word", children: agentChatRuntimeErrorMessage(error) || "The chat could not complete that action." }),
2066
+ /* @__PURE__ */ jsx24(
2156
2067
  Button,
2157
2068
  {
2158
2069
  "aria-label": "Dismiss error",
2159
2070
  onClick: runtime.operationError ? runtime.clearOperationError : runtime.controller.clearBackgroundError,
2160
2071
  size: "icon-xs",
2161
2072
  variant: "ghost",
2162
- children: /* @__PURE__ */ jsx25(X, {})
2073
+ children: /* @__PURE__ */ jsx24(X, {})
2163
2074
  }
2164
2075
  )
2165
2076
  ] }) });
@@ -2171,15 +2082,27 @@ function ChatShell({
2171
2082
  sidebar
2172
2083
  }) {
2173
2084
  const { controller } = runtime;
2085
+ const isUploading = controller.runState === "uploading";
2174
2086
  const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling";
2175
- const isUploading = controller.runState === "uploading" || runtime.isUploading;
2176
2087
  const isCancelling = controller.runState === "cancelling";
2088
+ const wasLiveRun = useLiveCompletionRuns(controller.activeRunId, isRunning);
2177
2089
  const activeTitle = controller.threads.find((thread) => thread.id === controller.activeThreadId)?.title;
2178
2090
  const hasBlockingError = Boolean(runtime.fatalError && controller.messages.length === 0);
2179
2091
  const isEmptyState = controller.activeThreadId === null && controller.messages.length === 0 && !hasBlockingError;
2092
+ const latestAnchorId = latestThreadAnchorId(controller.messages);
2180
2093
  const composerOverlayRef = React8.useRef(null);
2181
2094
  const [composerOverlayHeight, setComposerOverlayHeight] = React8.useState(192);
2182
- React8.useEffect(() => {
2095
+ const [restoredDraft, setRestoredDraft] = React8.useState(null);
2096
+ const draftRevisionRef = React8.useRef(0);
2097
+ const retractForEdit = React8.useCallback(async (message) => {
2098
+ const draft = await controller.retractPendingMessage(message);
2099
+ setRestoredDraft({
2100
+ revision: ++draftRevisionRef.current,
2101
+ text: draft.text,
2102
+ files: queuedMessageComposerFiles(draft)
2103
+ });
2104
+ }, [controller]);
2105
+ React8.useLayoutEffect(() => {
2183
2106
  const overlay = composerOverlayRef.current;
2184
2107
  if (!overlay || isEmptyState) return;
2185
2108
  const updateHeight = () => {
@@ -2190,15 +2113,15 @@ function ChatShell({
2190
2113
  resizeObserver.observe(overlay);
2191
2114
  return () => resizeObserver.disconnect();
2192
2115
  }, [isEmptyState]);
2193
- return /* @__PURE__ */ jsxs15(
2116
+ return /* @__PURE__ */ jsxs14(
2194
2117
  SidebarProvider,
2195
2118
  {
2196
2119
  className: "h-svh min-h-0 overflow-hidden",
2197
2120
  "data-agents24-chat": "",
2198
2121
  "data-agents24-chat-layout": "",
2199
2122
  children: [
2200
- /* @__PURE__ */ jsx25(PersistentSidebarTrigger, {}),
2201
- sidebar || /* @__PURE__ */ jsx25(
2123
+ /* @__PURE__ */ jsx24(PersistentSidebarTrigger, {}),
2124
+ sidebar || /* @__PURE__ */ jsx24(
2202
2125
  ThreadSidebar,
2203
2126
  {
2204
2127
  activeThreadId: controller.activeThreadId,
@@ -2213,171 +2136,147 @@ function ChatShell({
2213
2136
  onOpen: runtime.openThread
2214
2137
  }
2215
2138
  ),
2216
- /* @__PURE__ */ jsxs15(SidebarInset, { className: "h-svh min-h-0 overflow-hidden bg-background", children: [
2217
- /* @__PURE__ */ jsxs15(
2139
+ /* @__PURE__ */ jsxs14(SidebarInset, { className: "h-svh min-h-0 overflow-hidden bg-background", children: [
2140
+ /* @__PURE__ */ jsxs14(
2218
2141
  "header",
2219
2142
  {
2220
2143
  className: "flex h-12 shrink-0 items-center gap-2 border-b border-border/50 px-3 sm:px-4",
2221
2144
  "data-agents24-chat-header": "",
2222
2145
  children: [
2223
- /* @__PURE__ */ jsx25(CollapsedHeaderActions, { onNew: runtime.newThread }),
2224
- /* @__PURE__ */ jsx25("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx25("h1", { className: "truncate text-sm font-medium", children: String(activeTitle || "New chat") }) }),
2225
- /* @__PURE__ */ jsx25(ThemeMenu, {})
2146
+ /* @__PURE__ */ jsx24(CollapsedHeaderActions, { onNew: runtime.newThread }),
2147
+ /* @__PURE__ */ jsx24("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx24("h1", { className: "truncate text-sm font-medium", children: String(activeTitle || "New chat") }) }),
2148
+ /* @__PURE__ */ jsx24(ThemeMenu, {})
2226
2149
  ]
2227
2150
  }
2228
2151
  ),
2229
- /* @__PURE__ */ jsxs15(
2152
+ /* @__PURE__ */ jsxs14(
2230
2153
  "section",
2231
2154
  {
2232
2155
  "aria-label": "Conversation",
2233
2156
  className: "relative flex min-h-0 flex-1 flex-col overflow-hidden",
2234
2157
  "data-agents24-chat-transcript": "",
2235
2158
  children: [
2236
- /* @__PURE__ */ jsx25(OperationErrorNotice, { runtime }),
2237
- isEmptyState ? /* @__PURE__ */ jsxs15("div", { className: "absolute inset-0 flex min-h-0 flex-col justify-center pb-20", children: [
2238
- /* @__PURE__ */ jsx25("div", { className: "mx-auto mb-8 w-full max-w-3xl px-4 text-center sm:px-6", children: /* @__PURE__ */ jsx25("h2", { className: "text-3xl font-semibold tracking-tight sm:text-4xl", children: "Ready when you are." }) }),
2239
- /* @__PURE__ */ jsx25(
2240
- ChatComposer,
2159
+ /* @__PURE__ */ jsx24(OperationErrorNotice, { runtime }),
2160
+ /* @__PURE__ */ jsxs14(LatestThreadScroller2.Provider, { autoScroll: true, defaultScrollPosition: "last-anchor", scrollPreviousItemPeek: 64, children: [
2161
+ !isEmptyState && /* @__PURE__ */ jsxs14(LatestThreadScroller2.Root, { className: "relative min-h-0 flex-1", children: [
2162
+ /* @__PURE__ */ jsx24(LatestThreadScroller2.Viewport, { preserveScrollOnPrepend: true, children: /* @__PURE__ */ jsxs14(
2163
+ LatestThreadScroller2.Content,
2164
+ {
2165
+ className: "mx-auto w-full max-w-3xl gap-3 px-4 pt-8 pb-0 sm:px-6 sm:pt-12",
2166
+ style: { paddingBottom: composerOverlayHeight + 72 },
2167
+ children: [
2168
+ controller.hasOlderTurns && /* @__PURE__ */ jsxs14(Button, { className: "self-center", variant: "ghost", size: "sm", disabled: controller.isLoadingOlder, onClick: () => {
2169
+ void controller.loadOlder().catch(() => void 0);
2170
+ }, children: [
2171
+ controller.isLoadingOlder && /* @__PURE__ */ jsx24(LoaderCircle3, { className: "animate-spin" }),
2172
+ " Load earlier messages"
2173
+ ] }),
2174
+ runtime.isBootstrapping && /* @__PURE__ */ jsx24("div", { "aria-label": "Loading conversation", className: "flex flex-1 items-center justify-center py-24 text-muted-foreground", role: "status", children: /* @__PURE__ */ jsx24(LoaderCircle3, { className: "size-4 animate-spin" }) }),
2175
+ hasBlockingError && /* @__PURE__ */ jsxs14("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 py-24 text-center", children: [
2176
+ /* @__PURE__ */ jsx24(CircleAlert, { className: "size-5 text-destructive" }),
2177
+ /* @__PURE__ */ jsxs14("div", { children: [
2178
+ /* @__PURE__ */ jsx24("h2", { className: "font-medium", children: "Couldn\u2019t connect" }),
2179
+ /* @__PURE__ */ jsx24("p", { className: "mt-1 max-w-md text-sm text-muted-foreground", children: runtime.errorMessage })
2180
+ ] }),
2181
+ /* @__PURE__ */ jsx24(Button, { variant: "outline", onClick: () => void runtime.retryBootstrap(), children: "Try again" })
2182
+ ] }),
2183
+ controller.messages.map((message) => /* @__PURE__ */ jsx24(
2184
+ LatestThreadScroller2.Item,
2185
+ {
2186
+ messageId: message.id,
2187
+ scrollAnchor: isRunning && message.id === latestAnchorId,
2188
+ className: "overflow-visible [content-visibility:visible]",
2189
+ children: /* @__PURE__ */ jsx24(
2190
+ ChatMessage,
2191
+ {
2192
+ animateCompletion: wasLiveRun(message.runId),
2193
+ allowFeedback: runtime.allowFeedback,
2194
+ getHitlActionState: runtime.getHitlActionState,
2195
+ isLoading: isScaffoldTimelineLoading({
2196
+ activeRunId: controller.activeRunId,
2197
+ message,
2198
+ runState: controller.runState,
2199
+ streamingMessageId: controller.streamingMessageId
2200
+ }),
2201
+ message,
2202
+ onHitlAction: runtime.onHitlAction,
2203
+ onRegenerate: () => controller.regenerate(message),
2204
+ onSetFeedback: (input) => controller.setFeedback(message, input),
2205
+ resolveAttachmentContent: runtime.resolveAttachmentContent,
2206
+ Timeline: components?.Timeline
2207
+ }
2208
+ )
2209
+ },
2210
+ message.id
2211
+ ))
2212
+ ]
2213
+ }
2214
+ ) }),
2215
+ /* @__PURE__ */ jsx24(TurnOutline, { messages: controller.messages })
2216
+ ] }),
2217
+ /* @__PURE__ */ jsxs14(
2218
+ "div",
2241
2219
  {
2242
- inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2243
- contextWindow: runtime.contextWindow,
2244
- disabled: runtime.isBootstrapping || runtime.isChangingAgent || isUploading,
2245
- focusKey: controller.activeThreadId,
2246
- forceExpanded: true,
2247
- isCancelling,
2248
- isRunning,
2249
- agentId: runtime.agentId,
2250
- agents: runtime.agents,
2251
- onAgentChange: (agentId) => {
2252
- void runtime.changeAgent(agentId);
2220
+ ref: composerOverlayRef,
2221
+ className: isEmptyState ? "pointer-events-none absolute inset-0 z-30 flex flex-col justify-center overflow-visible pb-20" : "pointer-events-none absolute inset-x-0 bottom-0 z-30 overflow-visible pt-10",
2222
+ style: isEmptyState ? void 0 : {
2223
+ background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--background) 85%, transparent) 45%, var(--background) 65%, var(--background) 100%)"
2253
2224
  },
2254
- onStop: () => {
2255
- void controller.cancelRun().catch(() => void 0);
2256
- },
2257
- modelId: runtime.modelId,
2258
- models: runtime.models,
2259
- onModelChange: runtime.changeModel,
2260
- onSubmit: (text, files) => runtime.submit({
2261
- text,
2262
- files: files.map((file) => ({
2263
- data: file.source,
2264
- mediaType: file.mediaType,
2265
- name: file.filename
2266
- })),
2267
- attachments: files
2268
- })
2225
+ children: [
2226
+ isEmptyState ? /* @__PURE__ */ jsx24("div", { className: "mx-auto mb-8 w-full max-w-3xl px-4 text-center sm:px-6", children: /* @__PURE__ */ jsx24("h2", { className: "text-3xl font-semibold tracking-tight sm:text-4xl", children: "Ready when you are." }) }) : /* @__PURE__ */ jsx24("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ jsx24(LatestThreadScroller2.Button, { className: "!relative !inset-auto !translate-x-0 rtl:!translate-x-0" }) }),
2227
+ /* @__PURE__ */ jsxs14("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4 sm:px-6", children: [
2228
+ /* @__PURE__ */ jsx24(
2229
+ AgentChatQueue,
2230
+ {
2231
+ pending: controller.pendingMessages,
2232
+ queue: controller.messageQueue,
2233
+ canSteer: controller.runState === "streaming",
2234
+ onSteer: controller.steerPendingMessage,
2235
+ onRemove: controller.removePendingMessage,
2236
+ onEdit: retractForEdit,
2237
+ onResume: controller.resumeQueue
2238
+ }
2239
+ ),
2240
+ /* @__PURE__ */ jsx24(
2241
+ ChatComposer,
2242
+ {
2243
+ restoredDraft,
2244
+ inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2245
+ contextWindow: runtime.contextWindow,
2246
+ disabled: runtime.isBootstrapping || runtime.isChangingAgent || isUploading || hasBlockingError,
2247
+ focusKey: controller.activeThreadId,
2248
+ forceExpanded: isEmptyState,
2249
+ isCancelling,
2250
+ isRunning,
2251
+ agentId: runtime.agentId,
2252
+ agents: runtime.agents,
2253
+ onAgentChange: (agentId) => {
2254
+ void runtime.changeAgent(agentId);
2255
+ },
2256
+ onStop: () => {
2257
+ void controller.cancelRun().catch(() => void 0);
2258
+ },
2259
+ modelId: runtime.modelId,
2260
+ models: runtime.models,
2261
+ onModelChange: runtime.changeModel,
2262
+ onSubmit: (text, files) => runtime.submit({
2263
+ text,
2264
+ files: files.filter((file) => file.source).map((file) => ({
2265
+ data: file.source,
2266
+ mediaType: file.mediaType,
2267
+ name: file.filename
2268
+ })),
2269
+ attachmentIds: files.flatMap((file) => file.persistedAttachmentId ? [file.persistedAttachmentId] : []),
2270
+ attachments: files
2271
+ }),
2272
+ utilityRow: isEmptyState ? "inline" : "below"
2273
+ }
2274
+ )
2275
+ ] })
2276
+ ]
2269
2277
  }
2270
2278
  )
2271
- ] }) : /* @__PURE__ */ jsx25(Fragment4, { children: /* @__PURE__ */ jsxs15(
2272
- MessageScrollerProvider,
2273
- {
2274
- autoScroll: true,
2275
- defaultScrollPosition: "last-anchor",
2276
- scrollPreviousItemPeek: 64,
2277
- children: [
2278
- /* @__PURE__ */ jsxs15(MessageScroller, { className: "relative min-h-0 flex-1", children: [
2279
- /* @__PURE__ */ jsx25(MessageScrollerViewport, { preserveScrollOnPrepend: true, children: /* @__PURE__ */ jsxs15(
2280
- MessageScrollerContent,
2281
- {
2282
- className: "mx-auto w-full max-w-3xl gap-3 px-4 pt-8 pb-0 sm:px-6 sm:pt-12",
2283
- style: { paddingBottom: composerOverlayHeight + 72 },
2284
- children: [
2285
- controller.hasOlderTurns && /* @__PURE__ */ jsxs15(Button, { className: "self-center", variant: "ghost", size: "sm", disabled: controller.isLoadingOlder, onClick: () => {
2286
- void controller.loadOlder().catch(() => void 0);
2287
- }, children: [
2288
- controller.isLoadingOlder && /* @__PURE__ */ jsx25(LoaderCircle3, { className: "animate-spin" }),
2289
- " Load earlier messages"
2290
- ] }),
2291
- runtime.isBootstrapping && /* @__PURE__ */ jsx25("div", { "aria-label": "Loading conversation", className: "flex flex-1 items-center justify-center py-24 text-muted-foreground", role: "status", children: /* @__PURE__ */ jsx25(LoaderCircle3, { className: "size-4 animate-spin" }) }),
2292
- hasBlockingError && /* @__PURE__ */ jsxs15("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 py-24 text-center", children: [
2293
- /* @__PURE__ */ jsx25(CircleAlert, { className: "size-5 text-destructive" }),
2294
- /* @__PURE__ */ jsxs15("div", { children: [
2295
- /* @__PURE__ */ jsx25("h2", { className: "font-medium", children: "Couldn\u2019t connect" }),
2296
- /* @__PURE__ */ jsx25("p", { className: "mt-1 max-w-md text-sm text-muted-foreground", children: runtime.errorMessage })
2297
- ] }),
2298
- /* @__PURE__ */ jsx25(Button, { variant: "outline", onClick: () => void runtime.retryBootstrap(), children: "Try again" })
2299
- ] }),
2300
- controller.messages.map((message) => /* @__PURE__ */ jsx25(
2301
- MessageScrollerItem,
2302
- {
2303
- messageId: message.id,
2304
- scrollAnchor: message.role === "user",
2305
- className: "overflow-visible [content-visibility:visible]",
2306
- children: /* @__PURE__ */ jsx25(
2307
- ChatMessage,
2308
- {
2309
- allowFeedback: runtime.allowFeedback,
2310
- getHitlActionState: runtime.getHitlActionState,
2311
- isLoading: isScaffoldTimelineLoading({
2312
- activeRunId: controller.activeRunId,
2313
- message,
2314
- runState: controller.runState,
2315
- streamingMessageId: controller.streamingMessageId
2316
- }),
2317
- message,
2318
- onHitlAction: runtime.onHitlAction,
2319
- onRegenerate: () => controller.regenerate(message),
2320
- onSetFeedback: (input) => controller.setFeedback(message, input),
2321
- resolveAttachmentContent: runtime.resolveAttachmentContent,
2322
- Timeline: components?.Timeline
2323
- }
2324
- )
2325
- },
2326
- message.id
2327
- ))
2328
- ]
2329
- }
2330
- ) }),
2331
- /* @__PURE__ */ jsx25(TurnOutline, { messages: controller.messages })
2332
- ] }),
2333
- /* @__PURE__ */ jsxs15(
2334
- "div",
2335
- {
2336
- ref: composerOverlayRef,
2337
- className: "pointer-events-none absolute inset-x-0 bottom-0 z-30 overflow-visible pt-10",
2338
- style: {
2339
- background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--background) 85%, transparent) 45%, var(--background) 65%, var(--background) 100%)"
2340
- },
2341
- children: [
2342
- /* @__PURE__ */ jsx25("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ jsx25(MessageScrollerButton, { className: "!relative !inset-auto !translate-x-0 rtl:!translate-x-0" }) }),
2343
- /* @__PURE__ */ jsx25("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx25(
2344
- ChatComposer,
2345
- {
2346
- inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2347
- contextWindow: runtime.contextWindow,
2348
- disabled: runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError || isUploading,
2349
- focusKey: controller.activeThreadId,
2350
- isCancelling,
2351
- isRunning,
2352
- agentId: runtime.agentId,
2353
- agents: runtime.agents,
2354
- onAgentChange: (agentId) => {
2355
- void runtime.changeAgent(agentId);
2356
- },
2357
- onStop: () => {
2358
- void controller.cancelRun().catch(() => void 0);
2359
- },
2360
- modelId: runtime.modelId,
2361
- models: runtime.models,
2362
- onModelChange: runtime.changeModel,
2363
- onSubmit: (text, files) => runtime.submit({
2364
- text,
2365
- files: files.map((file) => ({
2366
- data: file.source,
2367
- mediaType: file.mediaType,
2368
- name: file.filename
2369
- })),
2370
- attachments: files
2371
- }),
2372
- utilityRow: "below"
2373
- }
2374
- ) })
2375
- ]
2376
- }
2377
- )
2378
- ]
2379
- }
2380
- ) })
2279
+ ] })
2381
2280
  ]
2382
2281
  }
2383
2282
  )
@@ -2391,7 +2290,7 @@ function ChatShell({
2391
2290
  var GUEST_CHAT_IDENTITY = { state: "guest" };
2392
2291
 
2393
2292
  // scaffold/src/app.tsx
2394
- import { jsx as jsx26 } from "react/jsx-runtime";
2293
+ import { jsx as jsx25 } from "react/jsx-runtime";
2395
2294
  var createBrowserAbortController = () => new AbortController();
2396
2295
  function RuntimeChat({
2397
2296
  components,
@@ -2402,17 +2301,17 @@ function RuntimeChat({
2402
2301
  ...props,
2403
2302
  mcpRedirectUri: props.mcpRedirectUri || (typeof window === "undefined" ? void 0 : window.location.href)
2404
2303
  });
2405
- return /* @__PURE__ */ jsx26(ChatShell, { components, identity, runtime, sidebar: props.sidebar });
2304
+ return /* @__PURE__ */ jsx25(ChatShell, { components, identity, runtime, sidebar: props.sidebar });
2406
2305
  }
2407
2306
  function App(props) {
2408
2307
  const identity = props.identity || GUEST_CHAT_IDENTITY;
2409
- return /* @__PURE__ */ jsx26(Agents24Provider, { client: props.client, createAbortController: createBrowserAbortController, children: /* @__PURE__ */ jsx26(RuntimeChat, { ...props }, identity.state) });
2308
+ return /* @__PURE__ */ jsx25(Agents24Provider, { client: props.client, createAbortController: createBrowserAbortController, children: /* @__PURE__ */ jsx25(RuntimeChat, { ...props }, identity.state) });
2410
2309
  }
2411
2310
 
2412
2311
  // scaffold/src/index.tsx
2413
- import { jsx as jsx27 } from "react/jsx-runtime";
2312
+ import { jsx as jsx26 } from "react/jsx-runtime";
2414
2313
  function Agents24ChatScaffold({ appearanceController, theme, ...props }) {
2415
- return /* @__PURE__ */ jsx27(ThemeProvider, { controller: appearanceController, theme, children: /* @__PURE__ */ jsx27(TooltipProvider, { children: /* @__PURE__ */ jsx27(App, { ...props }) }) });
2314
+ return /* @__PURE__ */ jsx26(ThemeProvider, { controller: appearanceController, theme, children: /* @__PURE__ */ jsx26(TooltipProvider, { children: /* @__PURE__ */ jsx26(App, { ...props }) }) });
2416
2315
  }
2417
2316
  export {
2418
2317
  Agents24ChatScaffold,