@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
@@ -1,6 +1,6 @@
1
1
  import type { AgentClient, ClientBootstrap, HitlResponse, PortableUpload } from "@agents24/client";
2
2
  import type { ContextCompression, ContextWindow } from "@agents24/client/protocol";
3
- import { type AgentChatController, type ChatHitlPart } from "@agents24/react";
3
+ import { type AgentChatController, type ChatAttachment, type ChatHitlPart } from "@agents24/react";
4
4
  import type { ChatMcpOauthCompletion } from "../types";
5
5
  export type AgentChatRuntimeAgent = {
6
6
  name: string;
@@ -16,7 +16,7 @@ export type AgentChatRuntimeAgentOption = {
16
16
  id: string;
17
17
  label: string;
18
18
  };
19
- export type AgentChatRuntimeViewState = "ready" | "loading" | "streaming" | "reconnecting" | "paused" | "cancelling" | "cancelled" | "offline" | "denied" | "quota" | "failed" | "expired";
19
+ export type AgentChatRuntimeViewState = "ready" | "loading" | "uploading" | "streaming" | "reconnecting" | "paused" | "cancelling" | "cancelled" | "offline" | "denied" | "quota" | "failed" | "expired";
20
20
  export type AgentChatRuntimeConnectionState = "connected" | "connecting" | "reconnecting" | "offline" | "expired";
21
21
  export type AgentChatThreadChangeReason = "created" | "opened" | "new" | "deleted";
22
22
  export type AgentChatActiveThreadChange = {
@@ -32,6 +32,7 @@ export type AgentChatMcpAuthorizationCompletion = (input: {
32
32
  export type AgentChatRuntimeSubmit = {
33
33
  text: string;
34
34
  files?: readonly PortableUpload[];
35
+ attachments?: ChatAttachment[];
35
36
  };
36
37
  export type AgentChatRuntimeHitlActionState = {
37
38
  action: string;
@@ -79,6 +80,10 @@ export type AgentChatRuntime = {
79
80
  openThread(threadId: string): Promise<void>;
80
81
  operationError: unknown;
81
82
  retryBootstrap(): Promise<void>;
83
+ resolveAttachmentContent(attachment: ChatAttachment, signal: AbortSignal): Promise<{
84
+ url: string;
85
+ validUntil: string | null;
86
+ }>;
82
87
  state: AgentChatRuntimeViewState;
83
88
  submit(input: AgentChatRuntimeSubmit): Promise<void>;
84
89
  };
@@ -1,3 +1,4 @@
1
+ import { type AgentChatComposerFile } from "@agents24/chat-react/ui";
1
2
  import { type AgentChatInputContract } from "@agents24/chat-react/runtime";
2
3
  import type { ContextWindow } from "@agents24/client/protocol";
3
4
  type ChatComposerProps = {
@@ -21,7 +22,7 @@ type ChatComposerProps = {
21
22
  onModelChange: (modelId: string) => void;
22
23
  onAgentChange: (agentId: string) => void;
23
24
  onStop: () => void;
24
- onSubmit: (text: string, files: File[]) => Promise<void>;
25
+ onSubmit: (text: string, files: AgentChatComposerFile[]) => Promise<void>;
25
26
  utilityRow?: "inline" | "below";
26
27
  };
27
28
  export declare function ChatComposer({ inputContract, disabled, contextWindow, focusKey, forceExpanded, isCancelling, isRunning, agentId, agents, modelId, models, onModelChange, onAgentChange, onStop, onSubmit, utilityRow, }: ChatComposerProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import type { ChatHitlPart, ChatMessage as ChatMessageModel } from "@agents24/react";
2
2
  import type { FeedbackReason, HitlResponse } from "@agents24/client";
3
3
  import type { AgentChatRuntimeHitlActionState } from "@agents24/chat-react/runtime";
4
+ import { type ChatAttachmentContentResolver } from "@agents24/chat-react/ui";
4
5
  import * as React from "react";
5
6
  import type { Agents24ChatTimelineSlotProps } from "../../app";
6
7
  type Props = {
@@ -16,6 +17,7 @@ type Props = {
16
17
  comment?: string | null;
17
18
  }) => Promise<void>;
18
19
  Timeline?: React.ComponentType<Agents24ChatTimelineSlotProps>;
20
+ resolveAttachmentContent?: ChatAttachmentContentResolver;
19
21
  };
20
- export declare function ChatMessage({ allowFeedback, getHitlActionState, isLoading, message, onHitlAction, onRegenerate, onSetFeedback, Timeline, }: Props): import("react/jsx-runtime").JSX.Element;
22
+ export declare function ChatMessage({ allowFeedback, getHitlActionState, isLoading, message, onHitlAction, onRegenerate, onSetFeedback, resolveAttachmentContent, Timeline, }: Props): import("react/jsx-runtime").JSX.Element;
21
23
  export {};
@@ -1,4 +1,4 @@
1
1
  import type { ChatMessage } from "@agents24/react";
2
2
  export declare function TurnOutline({ messages }: {
3
3
  messages: ChatMessage[];
4
- }): import("react/jsx-runtime").JSX.Element | null;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -39,11 +39,12 @@ module.exports = __toCommonJS(src_exports);
39
39
 
40
40
  // scaffold/src/app.tsx
41
41
  var import_runtime3 = require("@agents24/chat-react/runtime");
42
- var import_react3 = require("@agents24/react");
42
+ var import_react2 = require("@agents24/react");
43
43
 
44
44
  // scaffold/src/components/chat/chat-shell.tsx
45
45
  var import_runtime2 = require("@agents24/chat-react/runtime");
46
46
  var import_lucide_react12 = require("lucide-react");
47
+ var React8 = __toESM(require("react"), 1);
47
48
 
48
49
  // scaffold/src/components/chat/chat-composer.tsx
49
50
  var import_lucide_react3 = require("lucide-react");
@@ -524,11 +525,20 @@ function ChatComposer({
524
525
  const [attachmentError, setAttachmentError] = React2.useState(null);
525
526
  const inputRef = React2.useRef(null);
526
527
  const textareaRef = React2.useRef(null);
528
+ const filesRef = React2.useRef([]);
529
+ const inFlightFilesRef = React2.useRef([]);
527
530
  const submittingRef = React2.useRef(false);
528
531
  const draftRevisionRef = React2.useRef(0);
529
532
  const attachmentMimeTypes = (0, import_runtime.inputMimeTypes)(inputContract);
530
533
  const allowAttachments = attachmentMimeTypes.length > 0;
531
534
  const recordingAllowed = inputContract.modalities.audio.recording_enabled;
535
+ React2.useEffect(() => {
536
+ filesRef.current = files;
537
+ }, [files]);
538
+ React2.useEffect(() => () => {
539
+ (0, import_ui.revokeAgentChatComposerFiles)(filesRef.current);
540
+ (0, import_ui.revokeAgentChatComposerFiles)(inFlightFilesRef.current);
541
+ }, []);
532
542
  React2.useEffect(() => {
533
543
  if (disabled || isRunning) return;
534
544
  const frame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }));
@@ -536,14 +546,14 @@ function ChatComposer({
536
546
  }, [disabled, focusKey, isRunning]);
537
547
  const addRecording = React2.useCallback((file) => {
538
548
  draftRevisionRef.current += 1;
539
- setFiles((current) => [...current, file]);
549
+ setFiles((current) => [...current, (0, import_ui.createAgentChatComposerFile)(file)]);
540
550
  }, []);
541
551
  const recorder = (0, import_ui.useAudioRecorder)(addRecording);
542
552
  const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
543
553
  const validationError = (0, import_runtime.validateAgentChatSubmission)(
544
554
  inputContract,
545
555
  text,
546
- files.map((file) => ({ data: file, mediaType: file.type }))
556
+ files.map((file) => ({ data: file.source, mediaType: file.mediaType }))
547
557
  );
548
558
  const selectedAgent = agents.find((item) => item.id === agentId);
549
559
  const selectedModel = models.find((item) => item.id === modelId);
@@ -591,16 +601,23 @@ function ChatComposer({
591
601
  const clearedRevision = draftRevisionRef.current + 1;
592
602
  draftRevisionRef.current = clearedRevision;
593
603
  submittingRef.current = true;
604
+ inFlightFilesRef.current = selectedFiles;
594
605
  setText("");
595
606
  setFiles([]);
596
607
  textareaRef.current?.focus();
597
608
  try {
598
609
  await onSubmit(value, selectedFiles);
610
+ (0, import_ui.revokeAgentChatComposerFiles)(selectedFiles);
611
+ inFlightFilesRef.current = [];
599
612
  } catch {
600
613
  if (draftRevisionRef.current === clearedRevision) {
601
614
  draftRevisionRef.current += 1;
602
615
  setText(value);
603
616
  setFiles(selectedFiles);
617
+ inFlightFilesRef.current = [];
618
+ } else {
619
+ (0, import_ui.revokeAgentChatComposerFiles)(selectedFiles);
620
+ inFlightFilesRef.current = [];
604
621
  }
605
622
  } finally {
606
623
  submittingRef.current = false;
@@ -612,13 +629,21 @@ function ChatComposer({
612
629
  className: "mx-auto w-full max-w-3xl px-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:px-6",
613
630
  "data-agents24-chat-composer": "",
614
631
  children: [
615
- allowAttachments && files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mb-2 flex min-w-0 gap-2 overflow-x-auto", children: files.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex max-w-48 items-center gap-2 border bg-card px-2 py-1 text-xs", children: [
616
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: file.name }),
617
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Button, { variant: "ghost", size: "icon-xs", "aria-label": `Remove ${file.name}`, onClick: () => {
618
- draftRevisionRef.current += 1;
619
- setFiles((current) => current.filter((_, itemIndex) => itemIndex !== index));
620
- }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.X, {}) })
621
- ] }, `${file.name}-${index}`)) }),
632
+ allowAttachments && files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mb-2 min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
633
+ import_ui.ChatAttachmentRows,
634
+ {
635
+ attachments: files,
636
+ onRemove: (attachment) => {
637
+ if (!attachment.id) return;
638
+ draftRevisionRef.current += 1;
639
+ setFiles((current) => {
640
+ const removed = current.find((file) => file.id === attachment.id);
641
+ if (removed) (0, import_ui.revokeAgentChatComposerFiles)([removed]);
642
+ return current.filter((file) => file.id !== attachment.id);
643
+ });
644
+ }
645
+ }
646
+ ) }),
622
647
  attachmentError || recorder.error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "mb-2 text-xs text-destructive", role: "alert", children: attachmentError || recorder.error }) : null,
623
648
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
624
649
  "div",
@@ -642,7 +667,7 @@ function ChatComposer({
642
667
  const selected = Array.from(event.target.files ?? []);
643
668
  const accepted = selected.filter((file) => attachmentMimeTypes.includes((0, import_runtime.canonicalInputMimeType)(file.type)));
644
669
  draftRevisionRef.current += 1;
645
- setFiles((current) => [...current, ...accepted]);
670
+ setFiles((current) => [...current, ...accepted.map(import_ui.createAgentChatComposerFile)]);
646
671
  setAttachmentError(
647
672
  accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent."
648
673
  );
@@ -1116,7 +1141,7 @@ function MessageFooter({ className, ...props }) {
1116
1141
  {
1117
1142
  "data-slot": "message-footer",
1118
1143
  className: cn(
1119
- "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",
1144
+ "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",
1120
1145
  className
1121
1146
  ),
1122
1147
  ...props
@@ -1134,17 +1159,19 @@ function ChatMessage({
1134
1159
  onHitlAction,
1135
1160
  onRegenerate,
1136
1161
  onSetFeedback,
1162
+ resolveAttachmentContent,
1137
1163
  Timeline
1138
1164
  }) {
1139
1165
  const isUser = message.role === "user";
1140
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(MessageContent, { children: [
1141
- isUser && message.attachments?.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui3.ChatAttachments, { className: "mb-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1142
- import_ui3.ChatAttachment,
1166
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(MessageContent, { className: isUser ? "gap-0" : void 0, children: [
1167
+ isUser && message.attachments?.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1168
+ import_ui3.ChatAttachmentRows,
1143
1169
  {
1144
- data: attachment
1145
- },
1146
- String(attachment.id || attachment.url || attachment.filename || index)
1147
- )) }) : null,
1170
+ attachments: message.attachments,
1171
+ className: "mb-2",
1172
+ resolveContent: resolveAttachmentContent
1173
+ }
1174
+ ) : null,
1148
1175
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Bubble, { variant: isUser ? "secondary" : "ghost", align: isUser ? "end" : "start", className: isUser ? "max-w-[min(75%,42rem)]" : "max-w-full overflow-visible", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BubbleContent, { className: isUser ? "px-4 py-2.5" : "w-full overflow-visible", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1149
1176
  MessageParts,
1150
1177
  {
@@ -1155,6 +1182,17 @@ function ChatMessage({
1155
1182
  Timeline
1156
1183
  }
1157
1184
  ) }) }),
1185
+ isUser && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui3.AgentChatUserMessageActionRow, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1186
+ import_ui3.AgentChatDefaultActions,
1187
+ {
1188
+ handlers: {
1189
+ onCopy: (content) => {
1190
+ void navigator.clipboard.writeText(content).catch(() => void 0);
1191
+ }
1192
+ },
1193
+ message
1194
+ }
1195
+ ) }),
1158
1196
  !isUser && message.isFinal !== false && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageFooter, { className: "gap-1 opacity-70 transition-opacity group-hover/message:opacity-100", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1159
1197
  import_ui3.AgentChatDefaultActions,
1160
1198
  {
@@ -1557,7 +1595,7 @@ function Sidebar({
1557
1595
  className: cn(
1558
1596
  "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",
1559
1597
  // Adjust the padding for floating and inset variants.
1560
- 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",
1598
+ 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",
1561
1599
  className
1562
1600
  ),
1563
1601
  ...props,
@@ -1956,20 +1994,60 @@ function ThreadSidebar({ activeThreadId, hasMore, identity, isLoading, isLoading
1956
1994
  }
1957
1995
 
1958
1996
  // scaffold/src/components/chat/turn-outline.tsx
1959
- var import_react2 = require("react");
1997
+ var import_chat_react = require("@agents24/chat-react");
1998
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1999
+ function turnsFromMessages(messages) {
2000
+ return messages.reduce((turns, message) => {
2001
+ if (message.role === "user") {
2002
+ turns.push({
2003
+ id: message.id,
2004
+ label: message.content.trim() || `Turn ${turns.length + 1}`,
2005
+ messageIds: [message.id],
2006
+ responsePreview: ""
2007
+ });
2008
+ } else if (turns.length) {
2009
+ const turn = turns[turns.length - 1];
2010
+ turn.messageIds?.push(message.id);
2011
+ if (!turn.responsePreview) turn.responsePreview = message.content.trim();
2012
+ }
2013
+ return turns;
2014
+ }, []);
2015
+ }
2016
+ function TurnOutline({ messages }) {
2017
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2018
+ import_chat_react.LatestThreadScroller.Outline,
2019
+ {
2020
+ items: turnsFromMessages(messages),
2021
+ minSideGutterForRail: 0,
2022
+ side: "left",
2023
+ verticalOffset: "3.5rem"
2024
+ }
2025
+ );
2026
+ }
2027
+
2028
+ // scaffold/src/components/chat/timeline-loading.ts
2029
+ function isScaffoldTimelineLoading({
2030
+ activeRunId,
2031
+ message,
2032
+ runState,
2033
+ streamingMessageId
2034
+ }) {
2035
+ if (runState !== "streaming" && runState !== "reconnecting") return false;
2036
+ return message.id === streamingMessageId || Boolean(message.runId && message.runId === activeRunId);
2037
+ }
1960
2038
 
1961
2039
  // scaffold/src/components/ui/message-scroller.tsx
1962
2040
  var import_message_scroller = require("@shadcn/react/message-scroller");
1963
2041
  var import_lucide_react11 = require("lucide-react");
1964
- var import_jsx_runtime23 = require("react/jsx-runtime");
2042
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1965
2043
  function MessageScrollerProvider(props) {
1966
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_message_scroller.MessageScroller.Provider, { ...props });
2044
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_message_scroller.MessageScroller.Provider, { ...props });
1967
2045
  }
1968
2046
  function MessageScroller({
1969
2047
  className,
1970
2048
  ...props
1971
2049
  }) {
1972
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2050
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1973
2051
  import_message_scroller.MessageScroller.Root,
1974
2052
  {
1975
2053
  "data-slot": "message-scroller",
@@ -1985,7 +2063,7 @@ function MessageScrollerViewport({
1985
2063
  className,
1986
2064
  ...props
1987
2065
  }) {
1988
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2066
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1989
2067
  import_message_scroller.MessageScroller.Viewport,
1990
2068
  {
1991
2069
  "data-slot": "message-scroller-viewport",
@@ -2001,11 +2079,11 @@ function MessageScrollerContent({
2001
2079
  className,
2002
2080
  ...props
2003
2081
  }) {
2004
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2082
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2005
2083
  import_message_scroller.MessageScroller.Content,
2006
2084
  {
2007
2085
  "data-slot": "message-scroller-content",
2008
- className: cn("flex h-max min-h-full flex-col gap-8", className),
2086
+ className: cn("flex h-max min-h-full flex-col gap-3", className),
2009
2087
  ...props
2010
2088
  }
2011
2089
  );
@@ -2015,7 +2093,7 @@ function MessageScrollerItem({
2015
2093
  scrollAnchor = false,
2016
2094
  ...props
2017
2095
  }) {
2018
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2096
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2019
2097
  import_message_scroller.MessageScroller.Item,
2020
2098
  {
2021
2099
  "data-slot": "message-scroller-item",
@@ -2037,7 +2115,7 @@ function MessageScrollerButton({
2037
2115
  size = "icon-sm",
2038
2116
  ...props
2039
2117
  }) {
2040
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2118
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2041
2119
  import_message_scroller.MessageScroller.Button,
2042
2120
  {
2043
2121
  "data-slot": "message-scroller-button",
@@ -2049,86 +2127,19 @@ function MessageScrollerButton({
2049
2127
  "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",
2050
2128
  className
2051
2129
  ),
2052
- render: render ?? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Button, { variant, size }),
2130
+ render: render ?? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Button, { variant, size }),
2053
2131
  ...props,
2054
- children: children ?? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2132
+ children: children ?? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2133
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2056
2134
  import_lucide_react11.ArrowDownIcon,
2057
2135
  {}
2058
2136
  ),
2059
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
2137
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
2060
2138
  ] })
2061
2139
  }
2062
2140
  );
2063
2141
  }
2064
2142
 
2065
- // scaffold/src/components/chat/turn-outline.tsx
2066
- var import_jsx_runtime24 = require("react/jsx-runtime");
2067
- function turnsFromMessages(messages) {
2068
- return messages.reduce((turns, message) => {
2069
- if (message.role === "user") {
2070
- turns.push({ id: message.id, label: message.content.trim() || `Turn ${turns.length + 1}`, messageIds: [message.id], response: "" });
2071
- } else if (turns.length) {
2072
- const turn = turns[turns.length - 1];
2073
- turn.messageIds.push(message.id);
2074
- if (!turn.response) turn.response = message.content.trim();
2075
- }
2076
- return turns;
2077
- }, []);
2078
- }
2079
- function TurnOutline({ messages }) {
2080
- const turns = turnsFromMessages(messages);
2081
- const { scrollToMessage } = (0, import_message_scroller.useMessageScroller)();
2082
- const { currentAnchorId, visibleMessageIds } = (0, import_message_scroller.useMessageScrollerVisibility)();
2083
- const visible = new Set(visibleMessageIds);
2084
- const [hoveredIndex, setHoveredIndex] = (0, import_react2.useState)(null);
2085
- const focusIndex = hoveredIndex ?? -1;
2086
- if (turns.length < 2) return null;
2087
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("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__ */ (0, import_jsx_runtime24.jsx)("div", { className: "pointer-events-auto flex flex-col items-end gap-0.5 py-2", children: turns.map((turn, index) => {
2088
- const active = turn.messageIds.some((id) => visible.has(id) || currentAnchorId === id);
2089
- const distance = focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY;
2090
- const barWidth = focusIndex < 0 ? 12 : distance === 0 ? 50 : distance === 1 ? 36 : distance === 2 ? 23 : 10;
2091
- const barOpacity = focusIndex < 0 ? active ? 1 : 0.4 : distance === 0 ? 1 : distance === 1 ? 0.78 : distance === 2 ? 0.58 : 0.36;
2092
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(Tooltip, { children: [
2093
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2094
- "button",
2095
- {
2096
- type: "button",
2097
- "aria-label": `Jump to ${turn.label}`,
2098
- 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-['']",
2099
- onBlur: () => setHoveredIndex(null),
2100
- onFocus: () => setHoveredIndex(index),
2101
- onMouseEnter: () => setHoveredIndex(index),
2102
- onMouseLeave: () => setHoveredIndex(null),
2103
- onClick: () => scrollToMessage(turn.id, { align: "start", behavior: "smooth" }),
2104
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2105
- "span",
2106
- {
2107
- 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"),
2108
- style: { opacity: barOpacity, width: barWidth }
2109
- }
2110
- )
2111
- }
2112
- ) }),
2113
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(TooltipContent, { side: "left", className: "w-64 flex-col items-stretch gap-0 overflow-hidden p-3", children: [
2114
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "line-clamp-2 max-w-full wrap-break-word text-sm font-medium", children: turn.label }),
2115
- turn.response && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "mt-1 line-clamp-2 max-w-full wrap-break-word text-xs opacity-70", children: turn.response })
2116
- ] })
2117
- ] }, turn.id);
2118
- }) }) });
2119
- }
2120
-
2121
- // scaffold/src/components/chat/timeline-loading.ts
2122
- function isScaffoldTimelineLoading({
2123
- activeRunId,
2124
- message,
2125
- runState,
2126
- streamingMessageId
2127
- }) {
2128
- if (runState !== "streaming" && runState !== "reconnecting") return false;
2129
- return message.id === streamingMessageId || Boolean(message.runId && message.runId === activeRunId);
2130
- }
2131
-
2132
2143
  // scaffold/src/components/chat/chat-shell.tsx
2133
2144
  var import_jsx_runtime25 = require("react/jsx-runtime");
2134
2145
  var LOADING_INPUT_CONTRACT = {
@@ -2179,11 +2190,25 @@ function ChatShell({
2179
2190
  sidebar
2180
2191
  }) {
2181
2192
  const { controller } = runtime;
2182
- const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling" || runtime.isSubmitting;
2193
+ const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling";
2194
+ const isUploading = controller.runState === "uploading" || runtime.isUploading;
2183
2195
  const isCancelling = controller.runState === "cancelling";
2184
2196
  const activeTitle = controller.threads.find((thread) => thread.id === controller.activeThreadId)?.title;
2185
2197
  const hasBlockingError = Boolean(runtime.fatalError && controller.messages.length === 0);
2186
2198
  const isEmptyState = controller.activeThreadId === null && controller.messages.length === 0 && !hasBlockingError;
2199
+ const composerOverlayRef = React8.useRef(null);
2200
+ const [composerOverlayHeight, setComposerOverlayHeight] = React8.useState(192);
2201
+ React8.useEffect(() => {
2202
+ const overlay = composerOverlayRef.current;
2203
+ if (!overlay || isEmptyState) return;
2204
+ const updateHeight = () => {
2205
+ setComposerOverlayHeight(Math.ceil(overlay.getBoundingClientRect().height));
2206
+ };
2207
+ updateHeight();
2208
+ const resizeObserver = new ResizeObserver(updateHeight);
2209
+ resizeObserver.observe(overlay);
2210
+ return () => resizeObserver.disconnect();
2211
+ }, [isEmptyState]);
2187
2212
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2188
2213
  SidebarProvider,
2189
2214
  {
@@ -2211,7 +2236,7 @@ function ChatShell({
2211
2236
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2212
2237
  "header",
2213
2238
  {
2214
- className: "flex h-12 shrink-0 items-center gap-2 border-b border-border/30 px-3 sm:px-4",
2239
+ className: "flex h-12 shrink-0 items-center gap-2 border-b border-border/50 px-3 sm:px-4",
2215
2240
  "data-agents24-chat-header": "",
2216
2241
  children: [
2217
2242
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CollapsedHeaderActions, { onNew: runtime.newThread }),
@@ -2235,7 +2260,7 @@ function ChatShell({
2235
2260
  {
2236
2261
  inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2237
2262
  contextWindow: runtime.contextWindow,
2238
- disabled: runtime.isBootstrapping || runtime.isChangingAgent,
2263
+ disabled: runtime.isBootstrapping || runtime.isChangingAgent || isUploading,
2239
2264
  focusKey: controller.activeThreadId,
2240
2265
  forceExpanded: true,
2241
2266
  isCancelling,
@@ -2254,25 +2279,27 @@ function ChatShell({
2254
2279
  onSubmit: (text, files) => runtime.submit({
2255
2280
  text,
2256
2281
  files: files.map((file) => ({
2257
- data: file,
2258
- mediaType: file.type || "application/octet-stream",
2259
- name: file.name
2260
- }))
2282
+ data: file.source,
2283
+ mediaType: file.mediaType,
2284
+ name: file.filename
2285
+ })),
2286
+ attachments: files
2261
2287
  })
2262
2288
  }
2263
2289
  )
2264
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
2265
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2266
- MessageScrollerProvider,
2267
- {
2268
- autoScroll: true,
2269
- defaultScrollPosition: "last-anchor",
2270
- scrollPreviousItemPeek: 64,
2271
- children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(MessageScroller, { className: "relative min-h-0 flex-1", children: [
2290
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2291
+ MessageScrollerProvider,
2292
+ {
2293
+ autoScroll: true,
2294
+ defaultScrollPosition: "last-anchor",
2295
+ scrollPreviousItemPeek: 64,
2296
+ children: [
2297
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(MessageScroller, { className: "relative min-h-0 flex-1", children: [
2272
2298
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MessageScrollerViewport, { preserveScrollOnPrepend: true, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2273
2299
  MessageScrollerContent,
2274
2300
  {
2275
- className: "mx-auto w-full max-w-3xl gap-7 px-4 pt-8 pb-16 sm:px-6 sm:pt-12",
2301
+ className: "mx-auto w-full max-w-3xl gap-3 px-4 pt-8 pb-0 sm:px-6 sm:pt-12",
2302
+ style: { paddingBottom: composerOverlayHeight + 72 },
2276
2303
  children: [
2277
2304
  controller.hasOlderTurns && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(Button, { className: "self-center", variant: "ghost", size: "sm", disabled: controller.isLoadingOlder, onClick: () => {
2278
2305
  void controller.loadOlder().catch(() => void 0);
@@ -2310,6 +2337,7 @@ function ChatShell({
2310
2337
  onHitlAction: runtime.onHitlAction,
2311
2338
  onRegenerate: () => controller.regenerate(message),
2312
2339
  onSetFeedback: (input) => controller.setFeedback(message, input),
2340
+ resolveAttachmentContent: runtime.resolveAttachmentContent,
2313
2341
  Timeline: components?.Timeline
2314
2342
  }
2315
2343
  )
@@ -2319,43 +2347,56 @@ function ChatShell({
2319
2347
  ]
2320
2348
  }
2321
2349
  ) }),
2322
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TurnOutline, { messages: controller.messages }),
2323
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MessageScrollerButton, {})
2324
- ] })
2325
- }
2326
- ),
2327
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "relative z-30 shrink-0 bg-background pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2328
- ChatComposer,
2329
- {
2330
- inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2331
- contextWindow: runtime.contextWindow,
2332
- disabled: runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError,
2333
- focusKey: controller.activeThreadId,
2334
- isCancelling,
2335
- isRunning,
2336
- agentId: runtime.agentId,
2337
- agents: runtime.agents,
2338
- onAgentChange: (agentId) => {
2339
- void runtime.changeAgent(agentId);
2340
- },
2341
- onStop: () => {
2342
- void controller.cancelRun().catch(() => void 0);
2343
- },
2344
- modelId: runtime.modelId,
2345
- models: runtime.models,
2346
- onModelChange: runtime.changeModel,
2347
- onSubmit: (text, files) => runtime.submit({
2348
- text,
2349
- files: files.map((file) => ({
2350
- data: file,
2351
- mediaType: file.type || "application/octet-stream",
2352
- name: file.name
2353
- }))
2354
- }),
2355
- utilityRow: "below"
2356
- }
2357
- ) })
2358
- ] })
2350
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TurnOutline, { messages: controller.messages })
2351
+ ] }),
2352
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
2353
+ "div",
2354
+ {
2355
+ ref: composerOverlayRef,
2356
+ className: "pointer-events-none absolute inset-x-0 bottom-0 z-30 overflow-visible pt-10",
2357
+ style: {
2358
+ background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--background) 85%, transparent) 45%, var(--background) 65%, var(--background) 100%)"
2359
+ },
2360
+ children: [
2361
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MessageScrollerButton, { className: "!relative !inset-auto !translate-x-0 rtl:!translate-x-0" }) }),
2362
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2363
+ ChatComposer,
2364
+ {
2365
+ inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
2366
+ contextWindow: runtime.contextWindow,
2367
+ disabled: runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError || isUploading,
2368
+ focusKey: controller.activeThreadId,
2369
+ isCancelling,
2370
+ isRunning,
2371
+ agentId: runtime.agentId,
2372
+ agents: runtime.agents,
2373
+ onAgentChange: (agentId) => {
2374
+ void runtime.changeAgent(agentId);
2375
+ },
2376
+ onStop: () => {
2377
+ void controller.cancelRun().catch(() => void 0);
2378
+ },
2379
+ modelId: runtime.modelId,
2380
+ models: runtime.models,
2381
+ onModelChange: runtime.changeModel,
2382
+ onSubmit: (text, files) => runtime.submit({
2383
+ text,
2384
+ files: files.map((file) => ({
2385
+ data: file.source,
2386
+ mediaType: file.mediaType,
2387
+ name: file.filename
2388
+ })),
2389
+ attachments: files
2390
+ }),
2391
+ utilityRow: "below"
2392
+ }
2393
+ ) })
2394
+ ]
2395
+ }
2396
+ )
2397
+ ]
2398
+ }
2399
+ ) })
2359
2400
  ]
2360
2401
  }
2361
2402
  )
@@ -2384,7 +2425,7 @@ function RuntimeChat({
2384
2425
  }
2385
2426
  function App(props) {
2386
2427
  const identity = props.identity || GUEST_CHAT_IDENTITY;
2387
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react3.Agents24Provider, { client: props.client, createAbortController: createBrowserAbortController, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(RuntimeChat, { ...props }, identity.state) });
2428
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react2.Agents24Provider, { client: props.client, createAbortController: createBrowserAbortController, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(RuntimeChat, { ...props }, identity.state) });
2388
2429
  }
2389
2430
 
2390
2431
  // scaffold/src/index.tsx