@agents24/chat-react 0.5.5 → 0.5.7

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 (35) hide show
  1. package/dist/index.cjs +11 -6
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +11 -6
  4. package/dist/index.js.map +1 -1
  5. package/dist/latest-thread-scroller.d.ts +2 -1
  6. package/dist/runtime/index.cjs +17 -11
  7. package/dist/runtime/index.cjs.map +1 -1
  8. package/dist/runtime/index.js +17 -11
  9. package/dist/runtime/index.js.map +1 -1
  10. package/dist/runtime/use-agent-chat-runtime.d.ts +7 -2
  11. package/dist/scaffold/components/chat/chat-composer.d.ts +2 -1
  12. package/dist/scaffold/components/chat/chat-message.d.ts +3 -1
  13. package/dist/scaffold/components/chat/turn-outline.d.ts +1 -1
  14. package/dist/scaffold/index.cjs +195 -154
  15. package/dist/scaffold/index.cjs.map +1 -1
  16. package/dist/scaffold/index.js +215 -168
  17. package/dist/scaffold/index.js.map +1 -1
  18. package/dist/styles.css +25 -6
  19. package/dist/ui/adapters.d.ts +14 -2
  20. package/dist/ui/agent-chat-composer.d.ts +2 -9
  21. package/dist/ui/agent-chat-message.d.ts +7 -1
  22. package/dist/ui/composer-attachments.d.ts +11 -0
  23. package/dist/ui/index.cjs +781 -389
  24. package/dist/ui/index.cjs.map +1 -1
  25. package/dist/ui/index.d.ts +1 -0
  26. package/dist/ui/index.js +766 -375
  27. package/dist/ui/index.js.map +1 -1
  28. package/package.json +4 -3
  29. package/scaffold/src/components/chat/chat-composer.tsx +43 -19
  30. package/scaffold/src/components/chat/chat-message.tsx +19 -10
  31. package/scaffold/src/components/chat/chat-shell.tsx +65 -34
  32. package/scaffold/src/components/chat/turn-outline.tsx +17 -59
  33. package/scaffold/src/components/ui/message-scroller.tsx +1 -1
  34. package/scaffold/src/components/ui/message.tsx +1 -1
  35. package/scaffold/src/components/ui/sidebar.tsx +1 -1
@@ -10,12 +10,19 @@ 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
+ import * as React8 from "react";
14
15
 
15
16
  // scaffold/src/components/chat/chat-composer.tsx
16
- import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square, X } from "lucide-react";
17
+ import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square } from "lucide-react";
17
18
  import * as React2 from "react";
18
- import { AgentChatContextStatus, useAudioRecorder } from "@agents24/chat-react/ui";
19
+ import {
20
+ AgentChatContextStatus,
21
+ ChatAttachmentRows,
22
+ createAgentChatComposerFile,
23
+ revokeAgentChatComposerFiles,
24
+ useAudioRecorder
25
+ } from "@agents24/chat-react/ui";
19
26
  import { canonicalInputMimeType, inputMimeTypes, validateAgentChatSubmission } from "@agents24/chat-react/runtime";
20
27
 
21
28
  // scaffold/src/components/ui/button.tsx
@@ -491,11 +498,20 @@ function ChatComposer({
491
498
  const [attachmentError, setAttachmentError] = React2.useState(null);
492
499
  const inputRef = React2.useRef(null);
493
500
  const textareaRef = React2.useRef(null);
501
+ const filesRef = React2.useRef([]);
502
+ const inFlightFilesRef = React2.useRef([]);
494
503
  const submittingRef = React2.useRef(false);
495
504
  const draftRevisionRef = React2.useRef(0);
496
505
  const attachmentMimeTypes = inputMimeTypes(inputContract);
497
506
  const allowAttachments = attachmentMimeTypes.length > 0;
498
507
  const recordingAllowed = inputContract.modalities.audio.recording_enabled;
508
+ React2.useEffect(() => {
509
+ filesRef.current = files;
510
+ }, [files]);
511
+ React2.useEffect(() => () => {
512
+ revokeAgentChatComposerFiles(filesRef.current);
513
+ revokeAgentChatComposerFiles(inFlightFilesRef.current);
514
+ }, []);
499
515
  React2.useEffect(() => {
500
516
  if (disabled || isRunning) return;
501
517
  const frame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }));
@@ -503,14 +519,14 @@ function ChatComposer({
503
519
  }, [disabled, focusKey, isRunning]);
504
520
  const addRecording = React2.useCallback((file) => {
505
521
  draftRevisionRef.current += 1;
506
- setFiles((current) => [...current, file]);
522
+ setFiles((current) => [...current, createAgentChatComposerFile(file)]);
507
523
  }, []);
508
524
  const recorder = useAudioRecorder(addRecording);
509
525
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
510
526
  const validationError = validateAgentChatSubmission(
511
527
  inputContract,
512
528
  text,
513
- files.map((file) => ({ data: file, mediaType: file.type }))
529
+ files.map((file) => ({ data: file.source, mediaType: file.mediaType }))
514
530
  );
515
531
  const selectedAgent = agents.find((item) => item.id === agentId);
516
532
  const selectedModel = models.find((item) => item.id === modelId);
@@ -558,16 +574,23 @@ function ChatComposer({
558
574
  const clearedRevision = draftRevisionRef.current + 1;
559
575
  draftRevisionRef.current = clearedRevision;
560
576
  submittingRef.current = true;
577
+ inFlightFilesRef.current = selectedFiles;
561
578
  setText("");
562
579
  setFiles([]);
563
580
  textareaRef.current?.focus();
564
581
  try {
565
582
  await onSubmit(value, selectedFiles);
583
+ revokeAgentChatComposerFiles(selectedFiles);
584
+ inFlightFilesRef.current = [];
566
585
  } catch {
567
586
  if (draftRevisionRef.current === clearedRevision) {
568
587
  draftRevisionRef.current += 1;
569
588
  setText(value);
570
589
  setFiles(selectedFiles);
590
+ inFlightFilesRef.current = [];
591
+ } else {
592
+ revokeAgentChatComposerFiles(selectedFiles);
593
+ inFlightFilesRef.current = [];
571
594
  }
572
595
  } finally {
573
596
  submittingRef.current = false;
@@ -579,13 +602,21 @@ function ChatComposer({
579
602
  className: "mx-auto w-full max-w-3xl px-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:px-6",
580
603
  "data-agents24-chat-composer": "",
581
604
  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}`)) }),
605
+ allowAttachments && files.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mb-2 min-w-0", children: /* @__PURE__ */ jsx7(
606
+ ChatAttachmentRows,
607
+ {
608
+ attachments: files,
609
+ onRemove: (attachment) => {
610
+ if (!attachment.id) return;
611
+ draftRevisionRef.current += 1;
612
+ setFiles((current) => {
613
+ const removed = current.find((file) => file.id === attachment.id);
614
+ if (removed) revokeAgentChatComposerFiles([removed]);
615
+ return current.filter((file) => file.id !== attachment.id);
616
+ });
617
+ }
618
+ }
619
+ ) }),
589
620
  attachmentError || recorder.error ? /* @__PURE__ */ jsx7("p", { className: "mb-2 text-xs text-destructive", role: "alert", children: attachmentError || recorder.error }) : null,
590
621
  /* @__PURE__ */ jsxs3(
591
622
  "div",
@@ -609,7 +640,7 @@ function ChatComposer({
609
640
  const selected = Array.from(event.target.files ?? []);
610
641
  const accepted = selected.filter((file) => attachmentMimeTypes.includes(canonicalInputMimeType(file.type)));
611
642
  draftRevisionRef.current += 1;
612
- setFiles((current) => [...current, ...accepted]);
643
+ setFiles((current) => [...current, ...accepted.map(createAgentChatComposerFile)]);
613
644
  setAttachmentError(
614
645
  accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent."
615
646
  );
@@ -696,7 +727,7 @@ function ChatComposer({
696
727
  }
697
728
 
698
729
  // scaffold/src/components/chat/chat-message.tsx
699
- import { AgentChatDefaultActions, ChatAttachment, ChatAttachments } from "@agents24/chat-react/ui";
730
+ import { AgentChatDefaultActions, AgentChatUserMessageActionRow, ChatAttachmentRows as ChatAttachmentRows2 } from "@agents24/chat-react/ui";
700
731
 
701
732
  // scaffold/src/components/chat/message-parts.tsx
702
733
  import {
@@ -1086,7 +1117,7 @@ function MessageFooter({ className, ...props }) {
1086
1117
  {
1087
1118
  "data-slot": "message-footer",
1088
1119
  className: cn(
1089
- "flex max-w-full min-w-0 items-center px-3.5 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
1120
+ "flex max-w-full min-w-0 items-center pl-3.5 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
1090
1121
  className
1091
1122
  ),
1092
1123
  ...props
@@ -1104,17 +1135,19 @@ function ChatMessage({
1104
1135
  onHitlAction,
1105
1136
  onRegenerate,
1106
1137
  onSetFeedback,
1138
+ resolveAttachmentContent,
1107
1139
  Timeline
1108
1140
  }) {
1109
1141
  const isUser = message.role === "user";
1110
- 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
+ return /* @__PURE__ */ jsx14(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ jsxs8(MessageContent, { className: isUser ? "gap-0" : void 0, children: [
1143
+ isUser && message.attachments?.length ? /* @__PURE__ */ jsx14(
1144
+ ChatAttachmentRows2,
1113
1145
  {
1114
- data: attachment
1115
- },
1116
- String(attachment.id || attachment.url || attachment.filename || index)
1117
- )) }) : null,
1146
+ attachments: message.attachments,
1147
+ className: "mb-2",
1148
+ resolveContent: resolveAttachmentContent
1149
+ }
1150
+ ) : null,
1118
1151
  /* @__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
1152
  MessageParts,
1120
1153
  {
@@ -1125,6 +1158,17 @@ function ChatMessage({
1125
1158
  Timeline
1126
1159
  }
1127
1160
  ) }) }),
1161
+ isUser && /* @__PURE__ */ jsx14(AgentChatUserMessageActionRow, { children: /* @__PURE__ */ jsx14(
1162
+ AgentChatDefaultActions,
1163
+ {
1164
+ handlers: {
1165
+ onCopy: (content) => {
1166
+ void navigator.clipboard.writeText(content).catch(() => void 0);
1167
+ }
1168
+ },
1169
+ message
1170
+ }
1171
+ ) }),
1128
1172
  !isUser && message.isFinal !== false && /* @__PURE__ */ jsx14(MessageFooter, { className: "gap-1 opacity-70 transition-opacity group-hover/message:opacity-100", children: /* @__PURE__ */ jsx14(
1129
1173
  AgentChatDefaultActions,
1130
1174
  {
@@ -1527,7 +1571,7 @@ function Sidebar({
1527
1571
  className: cn(
1528
1572
  "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=left]:left-0 data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] data-[side=right]:right-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] md:flex",
1529
1573
  // Adjust the padding for floating and inset variants.
1530
- variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
1574
+ variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "border-border/50 group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
1531
1575
  className
1532
1576
  ),
1533
1577
  ...props,
@@ -1926,7 +1970,47 @@ function ThreadSidebar({ activeThreadId, hasMore, identity, isLoading, isLoading
1926
1970
  }
1927
1971
 
1928
1972
  // scaffold/src/components/chat/turn-outline.tsx
1929
- import { useState as useState8 } from "react";
1973
+ import { LatestThreadScroller } from "@agents24/chat-react";
1974
+ import { jsx as jsx23 } from "react/jsx-runtime";
1975
+ function turnsFromMessages(messages) {
1976
+ return messages.reduce((turns, message) => {
1977
+ if (message.role === "user") {
1978
+ turns.push({
1979
+ id: message.id,
1980
+ label: message.content.trim() || `Turn ${turns.length + 1}`,
1981
+ messageIds: [message.id],
1982
+ responsePreview: ""
1983
+ });
1984
+ } else if (turns.length) {
1985
+ const turn = turns[turns.length - 1];
1986
+ turn.messageIds?.push(message.id);
1987
+ if (!turn.responsePreview) turn.responsePreview = message.content.trim();
1988
+ }
1989
+ return turns;
1990
+ }, []);
1991
+ }
1992
+ function TurnOutline({ messages }) {
1993
+ return /* @__PURE__ */ jsx23(
1994
+ LatestThreadScroller.Outline,
1995
+ {
1996
+ items: turnsFromMessages(messages),
1997
+ minSideGutterForRail: 0,
1998
+ side: "left",
1999
+ verticalOffset: "3.5rem"
2000
+ }
2001
+ );
2002
+ }
2003
+
2004
+ // scaffold/src/components/chat/timeline-loading.ts
2005
+ function isScaffoldTimelineLoading({
2006
+ activeRunId,
2007
+ message,
2008
+ runState,
2009
+ streamingMessageId
2010
+ }) {
2011
+ if (runState !== "streaming" && runState !== "reconnecting") return false;
2012
+ return message.id === streamingMessageId || Boolean(message.runId && message.runId === activeRunId);
2013
+ }
1930
2014
 
1931
2015
  // scaffold/src/components/ui/message-scroller.tsx
1932
2016
  import {
@@ -1936,15 +2020,15 @@ import {
1936
2020
  useMessageScrollerVisibility
1937
2021
  } from "@shadcn/react/message-scroller";
1938
2022
  import { ArrowDownIcon } from "lucide-react";
1939
- import { Fragment as Fragment3, jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
2023
+ import { Fragment as Fragment3, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
1940
2024
  function MessageScrollerProvider(props) {
1941
- return /* @__PURE__ */ jsx23(MessageScrollerPrimitive.Provider, { ...props });
2025
+ return /* @__PURE__ */ jsx24(MessageScrollerPrimitive.Provider, { ...props });
1942
2026
  }
1943
2027
  function MessageScroller({
1944
2028
  className,
1945
2029
  ...props
1946
2030
  }) {
1947
- return /* @__PURE__ */ jsx23(
2031
+ return /* @__PURE__ */ jsx24(
1948
2032
  MessageScrollerPrimitive.Root,
1949
2033
  {
1950
2034
  "data-slot": "message-scroller",
@@ -1960,7 +2044,7 @@ function MessageScrollerViewport({
1960
2044
  className,
1961
2045
  ...props
1962
2046
  }) {
1963
- return /* @__PURE__ */ jsx23(
2047
+ return /* @__PURE__ */ jsx24(
1964
2048
  MessageScrollerPrimitive.Viewport,
1965
2049
  {
1966
2050
  "data-slot": "message-scroller-viewport",
@@ -1976,11 +2060,11 @@ function MessageScrollerContent({
1976
2060
  className,
1977
2061
  ...props
1978
2062
  }) {
1979
- return /* @__PURE__ */ jsx23(
2063
+ return /* @__PURE__ */ jsx24(
1980
2064
  MessageScrollerPrimitive.Content,
1981
2065
  {
1982
2066
  "data-slot": "message-scroller-content",
1983
- className: cn("flex h-max min-h-full flex-col gap-8", className),
2067
+ className: cn("flex h-max min-h-full flex-col gap-3", className),
1984
2068
  ...props
1985
2069
  }
1986
2070
  );
@@ -1990,7 +2074,7 @@ function MessageScrollerItem({
1990
2074
  scrollAnchor = false,
1991
2075
  ...props
1992
2076
  }) {
1993
- return /* @__PURE__ */ jsx23(
2077
+ return /* @__PURE__ */ jsx24(
1994
2078
  MessageScrollerPrimitive.Item,
1995
2079
  {
1996
2080
  "data-slot": "message-scroller-item",
@@ -2012,7 +2096,7 @@ function MessageScrollerButton({
2012
2096
  size = "icon-sm",
2013
2097
  ...props
2014
2098
  }) {
2015
- return /* @__PURE__ */ jsx23(
2099
+ return /* @__PURE__ */ jsx24(
2016
2100
  MessageScrollerPrimitive.Button,
2017
2101
  {
2018
2102
  "data-slot": "message-scroller-button",
@@ -2024,88 +2108,21 @@ function MessageScrollerButton({
2024
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",
2025
2109
  className
2026
2110
  ),
2027
- render: render ?? /* @__PURE__ */ jsx23(Button, { variant, size }),
2111
+ render: render ?? /* @__PURE__ */ jsx24(Button, { variant, size }),
2028
2112
  ...props,
2029
2113
  children: children ?? /* @__PURE__ */ jsxs14(Fragment3, { children: [
2030
- /* @__PURE__ */ jsx23(
2114
+ /* @__PURE__ */ jsx24(
2031
2115
  ArrowDownIcon,
2032
2116
  {}
2033
2117
  ),
2034
- /* @__PURE__ */ jsx23("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
2118
+ /* @__PURE__ */ jsx24("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
2035
2119
  ] })
2036
2120
  }
2037
2121
  );
2038
2122
  }
2039
2123
 
2040
- // scaffold/src/components/chat/turn-outline.tsx
2041
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
2042
- function turnsFromMessages(messages) {
2043
- return messages.reduce((turns, message) => {
2044
- if (message.role === "user") {
2045
- turns.push({ id: message.id, label: message.content.trim() || `Turn ${turns.length + 1}`, messageIds: [message.id], response: "" });
2046
- } else if (turns.length) {
2047
- const turn = turns[turns.length - 1];
2048
- turn.messageIds.push(message.id);
2049
- if (!turn.response) turn.response = message.content.trim();
2050
- }
2051
- return turns;
2052
- }, []);
2053
- }
2054
- function TurnOutline({ messages }) {
2055
- const turns = turnsFromMessages(messages);
2056
- const { scrollToMessage } = useMessageScroller();
2057
- const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility();
2058
- const visible = new Set(visibleMessageIds);
2059
- const [hoveredIndex, setHoveredIndex] = useState8(null);
2060
- const focusIndex = hoveredIndex ?? -1;
2061
- if (turns.length < 2) return null;
2062
- return /* @__PURE__ */ jsx24("nav", { "aria-label": "Transcript outline", className: "pointer-events-none absolute end-2 top-1/2 z-20 hidden max-h-72 w-14 -translate-y-1/2 md:block xl:end-5", children: /* @__PURE__ */ jsx24("div", { className: "pointer-events-auto flex flex-col items-end gap-0.5 py-2", children: turns.map((turn, index) => {
2063
- const active = turn.messageIds.some((id) => visible.has(id) || currentAnchorId === id);
2064
- const distance = focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY;
2065
- const barWidth = focusIndex < 0 ? 12 : distance === 0 ? 50 : distance === 1 ? 36 : distance === 2 ? 23 : 10;
2066
- const barOpacity = focusIndex < 0 ? active ? 1 : 0.4 : distance === 0 ? 1 : distance === 1 ? 0.78 : distance === 2 ? 0.58 : 0.36;
2067
- return /* @__PURE__ */ jsxs15(Tooltip, { children: [
2068
- /* @__PURE__ */ jsx24(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx24(
2069
- "button",
2070
- {
2071
- type: "button",
2072
- "aria-label": `Jump to ${turn.label}`,
2073
- className: "group relative flex h-2 w-14 items-center justify-end outline-none after:pointer-events-auto after:absolute after:-inset-y-0.5 after:inset-x-0 after:content-['']",
2074
- onBlur: () => setHoveredIndex(null),
2075
- onFocus: () => setHoveredIndex(index),
2076
- onMouseEnter: () => setHoveredIndex(index),
2077
- onMouseLeave: () => setHoveredIndex(null),
2078
- onClick: () => scrollToMessage(turn.id, { align: "start", behavior: "smooth" }),
2079
- children: /* @__PURE__ */ jsx24(
2080
- "span",
2081
- {
2082
- className: cn("h-0.5 rounded-full bg-muted-foreground/40 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground", active && "bg-foreground"),
2083
- style: { opacity: barOpacity, width: barWidth }
2084
- }
2085
- )
2086
- }
2087
- ) }),
2088
- /* @__PURE__ */ jsxs15(TooltipContent, { side: "left", className: "w-64 flex-col items-stretch gap-0 overflow-hidden p-3", children: [
2089
- /* @__PURE__ */ jsx24("p", { className: "line-clamp-2 max-w-full wrap-break-word text-sm font-medium", children: turn.label }),
2090
- turn.response && /* @__PURE__ */ jsx24("p", { className: "mt-1 line-clamp-2 max-w-full wrap-break-word text-xs opacity-70", children: turn.response })
2091
- ] })
2092
- ] }, turn.id);
2093
- }) }) });
2094
- }
2095
-
2096
- // scaffold/src/components/chat/timeline-loading.ts
2097
- function isScaffoldTimelineLoading({
2098
- activeRunId,
2099
- message,
2100
- runState,
2101
- streamingMessageId
2102
- }) {
2103
- if (runState !== "streaming" && runState !== "reconnecting") return false;
2104
- return message.id === streamingMessageId || Boolean(message.runId && message.runId === activeRunId);
2105
- }
2106
-
2107
2124
  // scaffold/src/components/chat/chat-shell.tsx
2108
- import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
2125
+ import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-runtime";
2109
2126
  var LOADING_INPUT_CONTRACT = {
2110
2127
  version: "agents24.runtime-inputs.v2",
2111
2128
  modalities: {
@@ -2124,7 +2141,7 @@ function CollapsedHeaderActions({ onNew }) {
2124
2141
  const { isMobile, openMobile, state } = useSidebar();
2125
2142
  const sidebarVisible = isMobile ? openMobile : state === "expanded";
2126
2143
  if (sidebarVisible) return null;
2127
- return /* @__PURE__ */ jsxs16(Fragment4, { children: [
2144
+ return /* @__PURE__ */ jsxs15(Fragment4, { children: [
2128
2145
  /* @__PURE__ */ jsx25("div", { className: "h-8 w-17 shrink-0", "aria-hidden": "true" }),
2129
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, {}) }) })
2130
2147
  ] });
@@ -2132,7 +2149,7 @@ function CollapsedHeaderActions({ onNew }) {
2132
2149
  function OperationErrorNotice({ runtime }) {
2133
2150
  const error = runtime.operationError || runtime.backgroundError;
2134
2151
  if (!error) return null;
2135
- return /* @__PURE__ */ jsx25("div", { className: "pointer-events-none absolute inset-x-0 top-3 z-40 flex justify-center px-4", children: /* @__PURE__ */ jsxs16("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: [
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: [
2136
2153
  /* @__PURE__ */ jsx25(CircleAlert, { className: "mt-0.5 size-4 shrink-0 text-destructive" }),
2137
2154
  /* @__PURE__ */ jsx25("span", { className: "min-w-0 flex-1 wrap-break-word", children: agentChatRuntimeErrorMessage(error) || "The chat could not complete that action." }),
2138
2155
  /* @__PURE__ */ jsx25(
@@ -2142,7 +2159,7 @@ function OperationErrorNotice({ runtime }) {
2142
2159
  onClick: runtime.operationError ? runtime.clearOperationError : runtime.controller.clearBackgroundError,
2143
2160
  size: "icon-xs",
2144
2161
  variant: "ghost",
2145
- children: /* @__PURE__ */ jsx25(X2, {})
2162
+ children: /* @__PURE__ */ jsx25(X, {})
2146
2163
  }
2147
2164
  )
2148
2165
  ] }) });
@@ -2154,12 +2171,26 @@ function ChatShell({
2154
2171
  sidebar
2155
2172
  }) {
2156
2173
  const { controller } = runtime;
2157
- const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling" || runtime.isSubmitting;
2174
+ const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling";
2175
+ const isUploading = controller.runState === "uploading" || runtime.isUploading;
2158
2176
  const isCancelling = controller.runState === "cancelling";
2159
2177
  const activeTitle = controller.threads.find((thread) => thread.id === controller.activeThreadId)?.title;
2160
2178
  const hasBlockingError = Boolean(runtime.fatalError && controller.messages.length === 0);
2161
2179
  const isEmptyState = controller.activeThreadId === null && controller.messages.length === 0 && !hasBlockingError;
2162
- return /* @__PURE__ */ jsxs16(
2180
+ const composerOverlayRef = React8.useRef(null);
2181
+ const [composerOverlayHeight, setComposerOverlayHeight] = React8.useState(192);
2182
+ React8.useEffect(() => {
2183
+ const overlay = composerOverlayRef.current;
2184
+ if (!overlay || isEmptyState) return;
2185
+ const updateHeight = () => {
2186
+ setComposerOverlayHeight(Math.ceil(overlay.getBoundingClientRect().height));
2187
+ };
2188
+ updateHeight();
2189
+ const resizeObserver = new ResizeObserver(updateHeight);
2190
+ resizeObserver.observe(overlay);
2191
+ return () => resizeObserver.disconnect();
2192
+ }, [isEmptyState]);
2193
+ return /* @__PURE__ */ jsxs15(
2163
2194
  SidebarProvider,
2164
2195
  {
2165
2196
  className: "h-svh min-h-0 overflow-hidden",
@@ -2182,11 +2213,11 @@ function ChatShell({
2182
2213
  onOpen: runtime.openThread
2183
2214
  }
2184
2215
  ),
2185
- /* @__PURE__ */ jsxs16(SidebarInset, { className: "h-svh min-h-0 overflow-hidden bg-background", children: [
2186
- /* @__PURE__ */ jsxs16(
2216
+ /* @__PURE__ */ jsxs15(SidebarInset, { className: "h-svh min-h-0 overflow-hidden bg-background", children: [
2217
+ /* @__PURE__ */ jsxs15(
2187
2218
  "header",
2188
2219
  {
2189
- className: "flex h-12 shrink-0 items-center gap-2 border-b border-border/30 px-3 sm:px-4",
2220
+ className: "flex h-12 shrink-0 items-center gap-2 border-b border-border/50 px-3 sm:px-4",
2190
2221
  "data-agents24-chat-header": "",
2191
2222
  children: [
2192
2223
  /* @__PURE__ */ jsx25(CollapsedHeaderActions, { onNew: runtime.newThread }),
@@ -2195,7 +2226,7 @@ function ChatShell({
2195
2226
  ]
2196
2227
  }
2197
2228
  ),
2198
- /* @__PURE__ */ jsxs16(
2229
+ /* @__PURE__ */ jsxs15(
2199
2230
  "section",
2200
2231
  {
2201
2232
  "aria-label": "Conversation",
@@ -2203,14 +2234,14 @@ function ChatShell({
2203
2234
  "data-agents24-chat-transcript": "",
2204
2235
  children: [
2205
2236
  /* @__PURE__ */ jsx25(OperationErrorNotice, { runtime }),
2206
- isEmptyState ? /* @__PURE__ */ jsxs16("div", { className: "absolute inset-0 flex min-h-0 flex-col justify-center pb-20", children: [
2237
+ isEmptyState ? /* @__PURE__ */ jsxs15("div", { className: "absolute inset-0 flex min-h-0 flex-col justify-center pb-20", children: [
2207
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." }) }),
2208
2239
  /* @__PURE__ */ jsx25(
2209
2240
  ChatComposer,
2210
2241
  {
2211
2242
  inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2212
2243
  contextWindow: runtime.contextWindow,
2213
- disabled: runtime.isBootstrapping || runtime.isChangingAgent,
2244
+ disabled: runtime.isBootstrapping || runtime.isChangingAgent || isUploading,
2214
2245
  focusKey: controller.activeThreadId,
2215
2246
  forceExpanded: true,
2216
2247
  isCancelling,
@@ -2229,36 +2260,38 @@ function ChatShell({
2229
2260
  onSubmit: (text, files) => runtime.submit({
2230
2261
  text,
2231
2262
  files: files.map((file) => ({
2232
- data: file,
2233
- mediaType: file.type || "application/octet-stream",
2234
- name: file.name
2235
- }))
2263
+ data: file.source,
2264
+ mediaType: file.mediaType,
2265
+ name: file.filename
2266
+ })),
2267
+ attachments: files
2236
2268
  })
2237
2269
  }
2238
2270
  )
2239
- ] }) : /* @__PURE__ */ jsxs16(Fragment4, { children: [
2240
- /* @__PURE__ */ jsx25(
2241
- MessageScrollerProvider,
2242
- {
2243
- autoScroll: true,
2244
- defaultScrollPosition: "last-anchor",
2245
- scrollPreviousItemPeek: 64,
2246
- children: /* @__PURE__ */ jsxs16(MessageScroller, { className: "relative min-h-0 flex-1", children: [
2247
- /* @__PURE__ */ jsx25(MessageScrollerViewport, { preserveScrollOnPrepend: true, children: /* @__PURE__ */ jsxs16(
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(
2248
2280
  MessageScrollerContent,
2249
2281
  {
2250
- className: "mx-auto w-full max-w-3xl gap-7 px-4 pt-8 pb-16 sm:px-6 sm:pt-12",
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 },
2251
2284
  children: [
2252
- controller.hasOlderTurns && /* @__PURE__ */ jsxs16(Button, { className: "self-center", variant: "ghost", size: "sm", disabled: controller.isLoadingOlder, onClick: () => {
2285
+ controller.hasOlderTurns && /* @__PURE__ */ jsxs15(Button, { className: "self-center", variant: "ghost", size: "sm", disabled: controller.isLoadingOlder, onClick: () => {
2253
2286
  void controller.loadOlder().catch(() => void 0);
2254
2287
  }, children: [
2255
2288
  controller.isLoadingOlder && /* @__PURE__ */ jsx25(LoaderCircle3, { className: "animate-spin" }),
2256
2289
  " Load earlier messages"
2257
2290
  ] }),
2258
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" }) }),
2259
- hasBlockingError && /* @__PURE__ */ jsxs16("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 py-24 text-center", children: [
2292
+ hasBlockingError && /* @__PURE__ */ jsxs15("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 py-24 text-center", children: [
2260
2293
  /* @__PURE__ */ jsx25(CircleAlert, { className: "size-5 text-destructive" }),
2261
- /* @__PURE__ */ jsxs16("div", { children: [
2294
+ /* @__PURE__ */ jsxs15("div", { children: [
2262
2295
  /* @__PURE__ */ jsx25("h2", { className: "font-medium", children: "Couldn\u2019t connect" }),
2263
2296
  /* @__PURE__ */ jsx25("p", { className: "mt-1 max-w-md text-sm text-muted-foreground", children: runtime.errorMessage })
2264
2297
  ] }),
@@ -2285,6 +2318,7 @@ function ChatShell({
2285
2318
  onHitlAction: runtime.onHitlAction,
2286
2319
  onRegenerate: () => controller.regenerate(message),
2287
2320
  onSetFeedback: (input) => controller.setFeedback(message, input),
2321
+ resolveAttachmentContent: runtime.resolveAttachmentContent,
2288
2322
  Timeline: components?.Timeline
2289
2323
  }
2290
2324
  )
@@ -2294,43 +2328,56 @@ function ChatShell({
2294
2328
  ]
2295
2329
  }
2296
2330
  ) }),
2297
- /* @__PURE__ */ jsx25(TurnOutline, { messages: controller.messages }),
2298
- /* @__PURE__ */ jsx25(MessageScrollerButton, {})
2299
- ] })
2300
- }
2301
- ),
2302
- /* @__PURE__ */ jsx25("div", { className: "relative z-30 shrink-0 bg-background pt-3", children: /* @__PURE__ */ jsx25(
2303
- ChatComposer,
2304
- {
2305
- inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2306
- contextWindow: runtime.contextWindow,
2307
- disabled: runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError,
2308
- focusKey: controller.activeThreadId,
2309
- isCancelling,
2310
- isRunning,
2311
- agentId: runtime.agentId,
2312
- agents: runtime.agents,
2313
- onAgentChange: (agentId) => {
2314
- void runtime.changeAgent(agentId);
2315
- },
2316
- onStop: () => {
2317
- void controller.cancelRun().catch(() => void 0);
2318
- },
2319
- modelId: runtime.modelId,
2320
- models: runtime.models,
2321
- onModelChange: runtime.changeModel,
2322
- onSubmit: (text, files) => runtime.submit({
2323
- text,
2324
- files: files.map((file) => ({
2325
- data: file,
2326
- mediaType: file.type || "application/octet-stream",
2327
- name: file.name
2328
- }))
2329
- }),
2330
- utilityRow: "below"
2331
- }
2332
- ) })
2333
- ] })
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
+ ) })
2334
2381
  ]
2335
2382
  }
2336
2383
  )