@agents24/chat-react 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/model.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ChatPart, ChatContextWindow, ChatMessage, ChatReasoningStep, ChatThreadDetail, ChatThreadSummary, ChatToolState } from "./types";
2
2
  export declare const DEFAULT_THREAD_PAGE_SIZE = 5;
3
3
  export declare const isRunningThreadStatus: (status?: string | null) => boolean;
4
+ export declare const isAutoAttachThreadStatus: (status?: string | null) => boolean;
4
5
  export declare const createChatId: () => string;
5
6
  export declare const titleFromMessage: (text: string, files?: Array<{
6
7
  filename?: string;
@@ -8,7 +9,6 @@ export declare const titleFromMessage: (text: string, files?: Array<{
8
9
  export declare const threadActivityDate: (thread: ChatThreadSummary) => string;
9
10
  export declare const assistantTextFromResponseBlocks: (blocks?: Array<Record<string, unknown>>) => string;
10
11
  export declare const assistantTextFromParts: (parts?: ChatPart[]) => string;
11
- export declare const textFromFinalOutput: (value: unknown) => string;
12
12
  export declare const toolStateFromStatus: (status?: unknown) => ChatToolState;
13
13
  export declare const partsFromResponseBlocks: (blocks?: Array<Record<string, unknown>>, fallbackText?: string) => ChatPart[];
14
14
  export declare const mergeReasoningSteps: (steps?: ChatReasoningStep[], options?: {
@@ -22,6 +22,7 @@ export declare const threadPaging: (thread: ChatThreadDetail) => {
22
22
  };
23
23
  export declare const latestContextWindowFromThread: (thread: ChatThreadDetail) => ChatContextWindow | null;
24
24
  export declare const activeRunIdFromThread: (thread?: Partial<ChatThreadSummary> | null) => string | null;
25
+ export declare const autoAttachRunIdFromThread: (thread?: Partial<ChatThreadSummary> | null) => string | null;
25
26
  export declare const hasUnfinishedAssistantMessage: (messages?: ChatMessage[]) => boolean;
26
27
  export declare const hasStaleUnfinishedAssistantCache: (thread?: (Partial<ChatThreadSummary> & {
27
28
  messages?: ChatMessage[];
@@ -6,5 +6,5 @@ export declare const DefaultChatPart: ({ part, message, renderOptions, }: {
6
6
  part: ChatPart;
7
7
  message: ChatMessage;
8
8
  renderOptions?: Agents24ChatRenderOptions;
9
- }) => import("react").JSX.Element;
9
+ }) => import("react").JSX.Element | null;
10
10
  export declare const renderChatPart: (part: ChatPart, message: ChatMessage, renderOptions?: Agents24ChatRenderOptions) => import("react").JSX.Element;
package/dist/sse.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  import type { ChatRuntimeEvent, ChatStreamResult } from "./types";
2
2
  export declare const parseSseBlock: (block: string) => ChatRuntimeEvent | null;
3
- export declare const consumeSseResponse: (response: Response, onEvent: (event: ChatRuntimeEvent) => void | Promise<void>) => Promise<ChatStreamResult>;
3
+ export declare const consumeSseResponse: (response: Response, onEvent: (event: ChatRuntimeEvent) => void | Promise<void>, options?: {
4
+ signal?: AbortSignal;
5
+ }) => Promise<ChatStreamResult>;
@@ -4,6 +4,10 @@ import type { ChatMessage } from "../types";
4
4
  export type AgentChatShellMessage = Pick<ChatMessage, "id" | "role" | "content"> & {
5
5
  [key: string]: unknown;
6
6
  };
7
+ export type AgentChatShellScrollState = {
8
+ canScroll: boolean;
9
+ isScrolled: boolean;
10
+ };
7
11
  export type AgentChatShellProps<TMessage extends AgentChatShellMessage> = {
8
12
  className?: string;
9
13
  contentClassName?: string;
@@ -12,14 +16,22 @@ export type AgentChatShellProps<TMessage extends AgentChatShellMessage> = {
12
16
  composerOverlayHeight?: number;
13
17
  defaultComposerOverlayHeight?: number;
14
18
  dir?: "ltr" | "rtl";
19
+ emptyInputAttachmentCount?: number;
20
+ emptyTitle?: string;
15
21
  hideComposer?: boolean;
16
22
  isLoadingOlder?: boolean;
17
23
  messages: TMessage[];
18
24
  onLoadOlder?: (() => Promise<unknown> | unknown) | null;
25
+ onScrollStateChange?: (state: AgentChatShellScrollState) => void;
19
26
  outlineItems?: LatestThreadScrollerOutlineItem[];
20
27
  outlineSide?: "left" | "right";
28
+ renderEmptyState?: (input: {
29
+ composer?: React.ReactNode;
30
+ title: string;
31
+ }) => React.ReactNode;
21
32
  renderMessage: (message: TMessage, index: number) => React.ReactNode;
22
33
  renderOlderLoader?: () => React.ReactNode;
23
34
  };
35
+ export declare function getAgentChatShellScrollState(viewport: Pick<HTMLElement, "clientHeight" | "scrollHeight" | "scrollTop">): AgentChatShellScrollState;
24
36
  export declare function buildAgentChatOutlineItems<TMessage extends AgentChatShellMessage>(messages: TMessage[], getLabel?: (message: TMessage, index: number) => string, getResponsePreview?: (message: TMessage) => string): LatestThreadScrollerOutlineItem[];
25
- export declare function AgentChatShell<TMessage extends AgentChatShellMessage>({ className, composer, composerOverlayClassName, composerOverlayHeight, contentClassName, defaultComposerOverlayHeight, dir, hideComposer, isLoadingOlder, messages, onLoadOlder, outlineItems, outlineSide, renderMessage, renderOlderLoader, }: AgentChatShellProps<TMessage>): React.JSX.Element;
37
+ export declare function AgentChatShell<TMessage extends AgentChatShellMessage>({ className, composer, composerOverlayClassName, composerOverlayHeight, contentClassName, defaultComposerOverlayHeight, dir, emptyInputAttachmentCount, emptyTitle, hideComposer, isLoadingOlder, messages, onLoadOlder, onScrollStateChange, outlineItems, outlineSide, renderEmptyState, renderMessage, renderOlderLoader, }: AgentChatShellProps<TMessage>): React.JSX.Element;
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var templates_exports = {};
32
32
  __export(templates_exports, {
33
33
  AgentChatShell: () => AgentChatShell,
34
- buildAgentChatOutlineItems: () => buildAgentChatOutlineItems
34
+ buildAgentChatOutlineItems: () => buildAgentChatOutlineItems,
35
+ getAgentChatShellScrollState: () => getAgentChatShellScrollState
35
36
  });
36
37
  module.exports = __toCommonJS(templates_exports);
37
38
 
@@ -252,6 +253,7 @@ function LatestThreadScrollerOutline({
252
253
  );
253
254
  };
254
255
  updateSideRoom();
256
+ if (typeof ResizeObserver === "undefined") return;
255
257
  const resizeObserver = new ResizeObserver(updateSideRoom);
256
258
  resizeObserver.observe(root);
257
259
  const content = layout?.contentElement;
@@ -390,6 +392,13 @@ function cn(...values) {
390
392
 
391
393
  // src/templates/agent-chat-shell.tsx
392
394
  var import_jsx_runtime2 = require("react/jsx-runtime");
395
+ function getAgentChatShellScrollState(viewport) {
396
+ const canScroll = viewport.scrollHeight - viewport.clientHeight > 1;
397
+ return {
398
+ canScroll,
399
+ isScrolled: canScroll && viewport.scrollTop > 4
400
+ };
401
+ }
393
402
  var defaultOutlineLabel = (content, index) => {
394
403
  const label = String(content || "").replace(/\s+/g, " ").trim();
395
404
  return label || `Turn ${index + 1}`;
@@ -421,16 +430,23 @@ function AgentChatShell({
421
430
  contentClassName,
422
431
  defaultComposerOverlayHeight = 192,
423
432
  dir = "ltr",
433
+ emptyInputAttachmentCount = 0,
434
+ emptyTitle = "Ready when you are.",
424
435
  hideComposer = false,
425
436
  isLoadingOlder = false,
426
437
  messages,
427
438
  onLoadOlder,
439
+ onScrollStateChange,
428
440
  outlineItems,
429
441
  outlineSide = "left",
442
+ renderEmptyState,
430
443
  renderMessage,
431
444
  renderOlderLoader
432
445
  }) {
433
446
  const overlayRef = React2.useRef(null);
447
+ const viewportRef = React2.useRef(null);
448
+ const contentRef = React2.useRef(null);
449
+ const lastScrollStateRef = React2.useRef(null);
434
450
  const [measuredOverlayHeight, setMeasuredOverlayHeight] = React2.useState(
435
451
  defaultComposerOverlayHeight
436
452
  );
@@ -439,6 +455,17 @@ function AgentChatShell({
439
455
  () => outlineItems || buildAgentChatOutlineItems(messages),
440
456
  [messages, outlineItems]
441
457
  );
458
+ const updateScrollState = React2.useCallback(() => {
459
+ const viewport = viewportRef.current;
460
+ if (!viewport || !onScrollStateChange) return;
461
+ const next = getAgentChatShellScrollState(viewport);
462
+ const previous = lastScrollStateRef.current;
463
+ if (previous && previous.canScroll === next.canScroll && previous.isScrolled === next.isScrolled) {
464
+ return;
465
+ }
466
+ lastScrollStateRef.current = next;
467
+ onScrollStateChange(next);
468
+ }, [onScrollStateChange]);
442
469
  React2.useEffect(() => {
443
470
  const overlay = overlayRef.current;
444
471
  if (!overlay || hideComposer) return;
@@ -450,8 +477,20 @@ function AgentChatShell({
450
477
  observer.observe(overlay);
451
478
  return () => observer.disconnect();
452
479
  }, [hideComposer]);
480
+ React2.useEffect(() => {
481
+ if (!onScrollStateChange) return;
482
+ updateScrollState();
483
+ const viewport = viewportRef.current;
484
+ const content = contentRef.current;
485
+ if (!viewport || typeof ResizeObserver === "undefined") return;
486
+ const observer = new ResizeObserver(updateScrollState);
487
+ observer.observe(viewport);
488
+ if (content) observer.observe(content);
489
+ return () => observer.disconnect();
490
+ }, [messages, onScrollStateChange, updateScrollState]);
453
491
  const handleScroll = React2.useCallback(
454
492
  (event) => {
493
+ updateScrollState();
455
494
  if (!onLoadOlder || isLoadingOlder || olderPageRequestRef.current || event.currentTarget.scrollTop > 48) {
456
495
  return;
457
496
  }
@@ -460,33 +499,60 @@ function AgentChatShell({
460
499
  olderPageRequestRef.current = false;
461
500
  });
462
501
  },
463
- [isLoadingOlder, onLoadOlder]
502
+ [isLoadingOlder, onLoadOlder, updateScrollState]
464
503
  );
465
504
  const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
466
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Provider, { children: [
467
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Root, { children: [
468
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LatestThreadScroller.Viewport, { onScroll: handleScroll, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
469
- LatestThreadScroller.Content,
505
+ const isEmptyState = messages.length === 0;
506
+ if (isEmptyState) {
507
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
509
+ "p",
470
510
  {
471
511
  className: cn(
472
- "mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
473
- contentClassName
512
+ "pb-6 text-3xl font-semibold transition-transform duration-200",
513
+ emptyInputAttachmentCount > 0 && "-translate-y-12"
474
514
  ),
475
- style: { paddingBottom: bottomPadding },
476
- children: [
477
- isLoadingOlder ? renderOlderLoader?.() : null,
478
- messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
479
- LatestThreadScroller.Item,
480
- {
481
- messageId: message.id,
482
- scrollAnchor: message.role === "user",
483
- children: renderMessage(message, index)
484
- },
485
- message.id
486
- ))
487
- ]
515
+ children: emptyTitle
488
516
  }
489
- ) }),
517
+ ),
518
+ !hideComposer && composer ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mx-auto w-full max-w-3xl px-4", children: composer }) : null
519
+ ] }) }) });
520
+ }
521
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Provider, { children: [
522
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Root, { children: [
523
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
524
+ LatestThreadScroller.Viewport,
525
+ {
526
+ onScroll: handleScroll,
527
+ onViewportChange: (node) => {
528
+ viewportRef.current = node;
529
+ updateScrollState();
530
+ },
531
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
532
+ LatestThreadScroller.Content,
533
+ {
534
+ ref: contentRef,
535
+ className: cn(
536
+ "mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
537
+ contentClassName
538
+ ),
539
+ style: { paddingBottom: bottomPadding },
540
+ children: [
541
+ isLoadingOlder ? renderOlderLoader?.() : null,
542
+ messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
543
+ LatestThreadScroller.Item,
544
+ {
545
+ messageId: message.id,
546
+ scrollAnchor: message.role === "user",
547
+ children: renderMessage(message, index)
548
+ },
549
+ message.id
550
+ ))
551
+ ]
552
+ }
553
+ )
554
+ }
555
+ ),
490
556
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
491
557
  LatestThreadScroller.Outline,
492
558
  {
@@ -509,7 +575,7 @@ function AgentChatShell({
509
575
  },
510
576
  children: [
511
577
  /* @__PURE__ */ (0, import_jsx_runtime2.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_runtime2.jsx)(LatestThreadScroller.Button, {}) }),
512
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl", children: composer })
578
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
513
579
  ]
514
580
  }
515
581
  ) : null
@@ -518,6 +584,7 @@ function AgentChatShell({
518
584
  // Annotate the CommonJS export names for ESM import in node:
519
585
  0 && (module.exports = {
520
586
  AgentChatShell,
521
- buildAgentChatOutlineItems
587
+ buildAgentChatOutlineItems,
588
+ getAgentChatShellScrollState
522
589
  });
523
590
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/templates/index.ts","../../src/templates/agent-chat-shell.tsx","../../src/latest-thread-scroller.tsx","../../src/ui/utils.ts"],"sourcesContent":["export * from \"./agent-chat-shell\";\n","import * as React from \"react\";\n\nimport {\n LatestThreadScroller,\n type LatestThreadScrollerOutlineItem,\n} from \"../latest-thread-scroller\";\nimport type { ChatMessage } from \"../types\";\nimport { cn } from \"../ui/utils\";\n\nexport type AgentChatShellMessage = Pick<ChatMessage, \"id\" | \"role\" | \"content\"> & {\n [key: string]: unknown;\n};\n\nexport type AgentChatShellProps<TMessage extends AgentChatShellMessage> = {\n className?: string;\n contentClassName?: string;\n composer?: React.ReactNode;\n composerOverlayClassName?: string;\n composerOverlayHeight?: number;\n defaultComposerOverlayHeight?: number;\n dir?: \"ltr\" | \"rtl\";\n hideComposer?: boolean;\n isLoadingOlder?: boolean;\n messages: TMessage[];\n onLoadOlder?: (() => Promise<unknown> | unknown) | null;\n outlineItems?: LatestThreadScrollerOutlineItem[];\n outlineSide?: \"left\" | \"right\";\n renderMessage: (message: TMessage, index: number) => React.ReactNode;\n renderOlderLoader?: () => React.ReactNode;\n};\n\nconst defaultOutlineLabel = (content: unknown, index: number) => {\n const label = String(content || \"\").replace(/\\s+/g, \" \").trim();\n return label || `Turn ${index + 1}`;\n};\n\nconst defaultOutlinePreview = (content: unknown) =>\n String(content || \"\").replace(/\\s+/g, \" \").trim();\n\nexport function buildAgentChatOutlineItems<TMessage extends AgentChatShellMessage>(\n messages: TMessage[],\n getLabel: (message: TMessage, index: number) => string = (message, index) =>\n defaultOutlineLabel(message.content, index),\n getResponsePreview: (message: TMessage) => string = (message) =>\n defaultOutlinePreview(message.content)\n): LatestThreadScrollerOutlineItem[] {\n return messages.reduce((items, message) => {\n if (message.role === \"user\") {\n items.push({\n id: message.id,\n label: getLabel(message, items.length),\n messageIds: [message.id],\n });\n return items;\n }\n const currentItem = items[items.length - 1];\n currentItem?.messageIds?.push(message.id);\n if (\n currentItem &&\n !currentItem.responsePreview &&\n message.role === \"assistant\"\n ) {\n currentItem.responsePreview = getResponsePreview(message);\n }\n return items;\n }, [] as LatestThreadScrollerOutlineItem[]);\n}\n\nexport function AgentChatShell<TMessage extends AgentChatShellMessage>({\n className,\n composer,\n composerOverlayClassName,\n composerOverlayHeight,\n contentClassName,\n defaultComposerOverlayHeight = 192,\n dir = \"ltr\",\n hideComposer = false,\n isLoadingOlder = false,\n messages,\n onLoadOlder,\n outlineItems,\n outlineSide = \"left\",\n renderMessage,\n renderOlderLoader,\n}: AgentChatShellProps<TMessage>) {\n const overlayRef = React.useRef<HTMLDivElement | null>(null);\n const [measuredOverlayHeight, setMeasuredOverlayHeight] = React.useState(\n defaultComposerOverlayHeight\n );\n const olderPageRequestRef = React.useRef(false);\n const items = React.useMemo(\n () => outlineItems || buildAgentChatOutlineItems(messages),\n [messages, outlineItems]\n );\n\n React.useEffect(() => {\n const overlay = overlayRef.current;\n if (!overlay || hideComposer) return;\n const updateHeight = () => {\n setMeasuredOverlayHeight(Math.ceil(overlay.getBoundingClientRect().height));\n };\n updateHeight();\n const observer = new ResizeObserver(updateHeight);\n observer.observe(overlay);\n return () => observer.disconnect();\n }, [hideComposer]);\n\n const handleScroll = React.useCallback(\n (event: React.UIEvent<HTMLDivElement>) => {\n if (\n !onLoadOlder ||\n isLoadingOlder ||\n olderPageRequestRef.current ||\n event.currentTarget.scrollTop > 48\n ) {\n return;\n }\n olderPageRequestRef.current = true;\n void Promise.resolve(onLoadOlder()).finally(() => {\n olderPageRequestRef.current = false;\n });\n },\n [isLoadingOlder, onLoadOlder]\n );\n\n const bottomPadding = hideComposer\n ? 32\n : (composerOverlayHeight ?? measuredOverlayHeight) + 72;\n\n return (\n <div className={cn(\"relative flex h-full min-h-0 w-full flex-col\", className)} dir={dir}>\n <LatestThreadScroller.Provider>\n <LatestThreadScroller.Root>\n <LatestThreadScroller.Viewport onScroll={handleScroll}>\n <LatestThreadScroller.Content\n className={cn(\n \"mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12\",\n contentClassName\n )}\n style={{ paddingBottom: bottomPadding }}\n >\n {isLoadingOlder ? renderOlderLoader?.() : null}\n {messages.map((message, index) => (\n <LatestThreadScroller.Item\n key={message.id}\n messageId={message.id}\n scrollAnchor={message.role === \"user\"}\n >\n {renderMessage(message, index)}\n </LatestThreadScroller.Item>\n ))}\n </LatestThreadScroller.Content>\n </LatestThreadScroller.Viewport>\n <LatestThreadScroller.Outline\n dir={dir}\n items={items}\n side={outlineSide}\n />\n </LatestThreadScroller.Root>\n\n {!hideComposer && composer ? (\n <div\n ref={overlayRef}\n className={cn(\n \"pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10\",\n composerOverlayClassName\n )}\n style={{\n background:\n \"linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--chat-background, var(--background)) 85%, transparent) 45%, var(--chat-background, var(--background)) 65%, var(--chat-background, var(--background)) 100%)\",\n }}\n >\n <div className=\"pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3\">\n <LatestThreadScroller.Button />\n </div>\n <div className=\"pointer-events-auto mx-auto w-full max-w-3xl\">\n {composer}\n </div>\n </div>\n ) : null}\n </LatestThreadScroller.Provider>\n </div>\n );\n}\n","import * as React from \"react\";\nimport {\n MessageScroller,\n useMessageScroller,\n useMessageScrollerVisibility,\n} from \"@shadcn/react/message-scroller\";\n\nconst cx = (...values: Array<string | false | null | undefined>) =>\n values.filter(Boolean).join(\" \");\n\ntype MessageScrollerProviderProps = React.ComponentProps<\n typeof MessageScroller.Provider\n>;\ntype MessageScrollerRootProps = React.ComponentProps<typeof MessageScroller.Root>;\ntype MessageScrollerViewportProps = React.ComponentProps<\n typeof MessageScroller.Viewport\n>;\ntype MessageScrollerContentProps = React.ComponentProps<\n typeof MessageScroller.Content\n>;\ntype MessageScrollerItemProps = React.ComponentProps<typeof MessageScroller.Item>;\ntype MessageScrollerButtonProps = React.ComponentProps<typeof MessageScroller.Button>;\n\ntype LatestThreadScrollerLayoutContextValue = {\n contentElement: HTMLElement | null;\n rootElement: HTMLElement | null;\n setContentElement: (node: HTMLElement | null) => void;\n setRootElement: (node: HTMLElement | null) => void;\n};\n\nconst LatestThreadScrollerLayoutContext =\n React.createContext<LatestThreadScrollerLayoutContextValue | null>(null);\n\nconst useLatestThreadScrollerLayout = () =>\n React.useContext(LatestThreadScrollerLayoutContext);\n\nexport type LatestThreadScrollerProviderProps = MessageScrollerProviderProps;\n\nexport function LatestThreadScrollerProvider({\n autoScroll = true,\n defaultScrollPosition = \"last-anchor\",\n scrollPreviousItemPeek = 64,\n ...props\n}: LatestThreadScrollerProviderProps) {\n return (\n <MessageScroller.Provider\n autoScroll={autoScroll}\n defaultScrollPosition={defaultScrollPosition}\n scrollPreviousItemPeek={scrollPreviousItemPeek}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerRootProps = MessageScrollerRootProps;\n\nexport function LatestThreadScrollerRoot({\n className,\n ref,\n ...props\n}: LatestThreadScrollerRootProps) {\n const [rootElement, setRootElement] = React.useState<HTMLElement | null>(null);\n const [contentElement, setContentElement] =\n React.useState<HTMLElement | null>(null);\n const layoutContext = React.useMemo<LatestThreadScrollerLayoutContextValue>(\n () => ({\n contentElement,\n rootElement,\n setContentElement,\n setRootElement,\n }),\n [contentElement, rootElement]\n );\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n setRootElement(node);\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n return (\n <LatestThreadScrollerLayoutContext.Provider value={layoutContext}>\n <MessageScroller.Root\n ref={setRef}\n className={cx(\n \"relative flex size-full min-h-0 flex-1 flex-col overflow-hidden\",\n className\n )}\n {...props}\n />\n </LatestThreadScrollerLayoutContext.Provider>\n );\n}\n\nexport type LatestThreadScrollerViewportProps =\n MessageScrollerViewportProps & {\n onViewportChange?: (node: HTMLElement | null) => void;\n };\n\nexport function LatestThreadScrollerViewport({\n className,\n onViewportChange,\n preserveScrollOnPrepend = true,\n ref,\n ...props\n}: LatestThreadScrollerViewportProps) {\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n onViewportChange?.(node);\n },\n [onViewportChange, ref]\n );\n\n return (\n <MessageScroller.Viewport\n ref={setRef}\n className={cx(\n \"size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain\",\n className\n )}\n preserveScrollOnPrepend={preserveScrollOnPrepend}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerContentProps = MessageScrollerContentProps;\n\nexport function LatestThreadScrollerContent({\n className,\n ref,\n ...props\n}: LatestThreadScrollerContentProps) {\n const layout = useLatestThreadScrollerLayout();\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n layout?.setContentElement(node);\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [layout, ref]\n );\n\n return (\n <MessageScroller.Content\n ref={setRef}\n data-agents24-latest-thread-content=\"\"\n className={cx(\"flex h-max min-h-full flex-col gap-8 p-4\", className)}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerItemProps = Omit<\n MessageScrollerItemProps,\n \"messageId\"\n> & {\n messageId: string;\n};\n\nexport function LatestThreadScrollerItem({\n className,\n messageId,\n ...props\n}: LatestThreadScrollerItemProps) {\n return (\n <MessageScroller.Item\n className={cx(\n \"min-w-0 shrink-0 overflow-visible\",\n className\n )}\n messageId={messageId}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerButtonProps = Omit<\n MessageScrollerButtonProps,\n \"render\"\n> & {\n className?: string;\n render?: MessageScrollerButtonProps[\"render\"];\n};\n\nexport function LatestThreadScrollerButton({\n children,\n className,\n direction = \"end\",\n render,\n ...props\n}: LatestThreadScrollerButtonProps) {\n if (render) {\n return (\n <MessageScroller.Button\n direction={direction}\n render={render}\n {...props}\n />\n );\n }\n\n return (\n <MessageScroller.Button\n direction={direction}\n render={(buttonProps) => (\n <button\n {...(buttonProps as Record<string, unknown>)}\n aria-label={\n direction === \"end\"\n ? \"Scroll to latest message\"\n : \"Scroll to first message\"\n }\n className={cx(\n \"absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-all duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100\",\n className\n )}\n type=\"button\"\n >\n {children ?? (direction === \"end\" ? \"↓\" : \"↑\")}\n </button>\n )}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerOutlineItem = {\n id: string;\n label: string;\n messageIds?: string[];\n responsePreview?: string;\n};\n\nexport type LatestThreadScrollerOutlineProps = {\n cardClassName?: string;\n className?: string;\n contentMaxWidth?: number;\n dir?: \"ltr\" | \"rtl\";\n items: LatestThreadScrollerOutlineItem[];\n minContainerWidthForRail?: number;\n minSideGutterForRail?: number;\n side?: \"left\" | \"right\";\n};\n\nexport function LatestThreadScrollerOutline({\n cardClassName,\n className,\n contentMaxWidth = 768,\n dir = \"ltr\",\n items,\n minContainerWidthForRail = 0,\n minSideGutterForRail = 48,\n side,\n}: LatestThreadScrollerOutlineProps) {\n const navRef = React.useRef<HTMLElement | null>(null);\n const layout = useLatestThreadScrollerLayout();\n const [hasSideRoom, setHasSideRoom] = React.useState(false);\n const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null);\n const { scrollToMessage } = useMessageScroller();\n const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility();\n const visibleMessageIdSet = React.useMemo(\n () => new Set(visibleMessageIds),\n [visibleMessageIds]\n );\n const focusIndex = hoveredIndex ?? -1;\n const railSide = side ?? (dir === \"rtl\" ? \"right\" : \"left\");\n\n React.useEffect(() => {\n const root = layout?.rootElement ?? navRef.current?.parentElement;\n if (!root) return;\n\n const updateSideRoom = () => {\n const rootRect = root.getBoundingClientRect();\n const rootWidth = rootRect.width;\n const content = layout?.contentElement;\n const sideGutter = content\n ? Math.max(\n 0,\n railSide === \"left\"\n ? content.getBoundingClientRect().left - rootRect.left\n : rootRect.right - content.getBoundingClientRect().right\n )\n : Math.max(0, (rootWidth - contentMaxWidth) / 2);\n const nextHasSideRoom =\n rootWidth >= minContainerWidthForRail &&\n sideGutter >= minSideGutterForRail;\n setHasSideRoom((current) =>\n current === nextHasSideRoom ? current : nextHasSideRoom\n );\n };\n\n updateSideRoom();\n const resizeObserver = new ResizeObserver(updateSideRoom);\n resizeObserver.observe(root);\n const content = layout?.contentElement;\n if (content) {\n resizeObserver.observe(content);\n }\n return () => resizeObserver.disconnect();\n }, [\n contentMaxWidth,\n layout?.contentElement,\n layout?.rootElement,\n minContainerWidthForRail,\n minSideGutterForRail,\n railSide,\n ]);\n\n if (items.length < 2) {\n return null;\n }\n\n return (\n <nav\n ref={navRef}\n aria-label=\"Transcript outline\"\n className={cx(\n \"pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible\",\n hasSideRoom ? \"hidden md:block\" : \"hidden\",\n railSide === \"left\" ? \"left-1\" : \"right-1\",\n className\n )}\n dir={dir}\n >\n <div className=\"pointer-events-auto flex flex-col gap-0 py-1\">\n {items.map((item, index) => {\n const itemMessageIds =\n item.messageIds && item.messageIds.length > 0\n ? item.messageIds\n : [item.id];\n const active = itemMessageIds.some(\n (messageId) =>\n visibleMessageIdSet.has(messageId) || messageId === currentAnchorId\n );\n const distance =\n focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY;\n const isFocused = distance === 0;\n const isNear = distance === 1;\n const isNextNear = distance === 2;\n const barWidth = isFocused\n ? 50\n : isNear\n ? 36\n : isNextNear\n ? 23\n : 10;\n const barOpacity = isFocused ? 1 : isNear ? 0.78 : isNextNear ? 0.58 : 0.36;\n const fallbackLabel = `Turn ${index + 1}`;\n const previewLabel =\n item.label && item.label !== fallbackLabel ? item.label : \"\";\n const responsePreview = (item.responsePreview || \"\").trim();\n\n return (\n <button\n key={item.id}\n type=\"button\"\n data-active={active}\n dir=\"ltr\"\n className={cx(\n \"group relative flex h-2 w-16 items-center rounded-sm outline-none\",\n railSide === \"left\" ? \"justify-start\" : \"justify-end\"\n )}\n aria-label={`Jump to ${item.label || fallbackLabel}`}\n onBlur={() => setHoveredIndex(null)}\n onFocus={() => setHoveredIndex(index)}\n onMouseEnter={() => setHoveredIndex(index)}\n onMouseLeave={() => setHoveredIndex(null)}\n onClick={() =>\n scrollToMessage(item.id, {\n align: \"start\",\n behavior: \"smooth\",\n })\n }\n >\n <span\n className=\"h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground\"\n style={{\n opacity: barOpacity,\n width: barWidth,\n }}\n />\n {previewLabel ? (\n <span\n dir={dir}\n className={cx(\n \"pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100\",\n railSide === \"left\"\n ? \"left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0\"\n : \"right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0\",\n cardClassName\n )}\n >\n <span\n className=\"block overflow-hidden text-sm leading-5 text-foreground\"\n style={{\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: responsePreview ? 2 : 4,\n }}\n >\n {previewLabel}\n </span>\n {responsePreview ? (\n <span\n className=\"mt-1 block overflow-hidden text-sm leading-5 text-muted-foreground\"\n style={{\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: 2,\n }}\n >\n {responsePreview}\n </span>\n ) : null}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n </nav>\n );\n}\n\nexport const LatestThreadScroller = {\n Provider: LatestThreadScrollerProvider,\n Root: LatestThreadScrollerRoot,\n Viewport: LatestThreadScrollerViewport,\n Content: LatestThreadScrollerContent,\n Item: LatestThreadScrollerItem,\n Button: LatestThreadScrollerButton,\n Outline: LatestThreadScrollerOutline,\n};\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...values: ClassValue[]) {\n return twMerge(clsx(values));\n}\n"],"mappings":"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","names":["React","content","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/templates/index.ts","../../src/templates/agent-chat-shell.tsx","../../src/latest-thread-scroller.tsx","../../src/ui/utils.ts"],"sourcesContent":["export * from \"./agent-chat-shell\";\n","import * as React from \"react\";\n\nimport {\n LatestThreadScroller,\n type LatestThreadScrollerOutlineItem,\n} from \"../latest-thread-scroller\";\nimport type { ChatMessage } from \"../types\";\nimport { cn } from \"../ui/utils\";\n\nexport type AgentChatShellMessage = Pick<ChatMessage, \"id\" | \"role\" | \"content\"> & {\n [key: string]: unknown;\n};\n\nexport type AgentChatShellScrollState = {\n canScroll: boolean;\n isScrolled: boolean;\n};\n\nexport type AgentChatShellProps<TMessage extends AgentChatShellMessage> = {\n className?: string;\n contentClassName?: string;\n composer?: React.ReactNode;\n composerOverlayClassName?: string;\n composerOverlayHeight?: number;\n defaultComposerOverlayHeight?: number;\n dir?: \"ltr\" | \"rtl\";\n emptyInputAttachmentCount?: number;\n emptyTitle?: string;\n hideComposer?: boolean;\n isLoadingOlder?: boolean;\n messages: TMessage[];\n onLoadOlder?: (() => Promise<unknown> | unknown) | null;\n onScrollStateChange?: (state: AgentChatShellScrollState) => void;\n outlineItems?: LatestThreadScrollerOutlineItem[];\n outlineSide?: \"left\" | \"right\";\n renderEmptyState?: (input: { composer?: React.ReactNode; title: string }) => React.ReactNode;\n renderMessage: (message: TMessage, index: number) => React.ReactNode;\n renderOlderLoader?: () => React.ReactNode;\n};\n\nexport function getAgentChatShellScrollState(\n viewport: Pick<HTMLElement, \"clientHeight\" | \"scrollHeight\" | \"scrollTop\">\n): AgentChatShellScrollState {\n const canScroll = viewport.scrollHeight - viewport.clientHeight > 1;\n return {\n canScroll,\n isScrolled: canScroll && viewport.scrollTop > 4,\n };\n}\n\nconst defaultOutlineLabel = (content: unknown, index: number) => {\n const label = String(content || \"\").replace(/\\s+/g, \" \").trim();\n return label || `Turn ${index + 1}`;\n};\n\nconst defaultOutlinePreview = (content: unknown) =>\n String(content || \"\").replace(/\\s+/g, \" \").trim();\n\nexport function buildAgentChatOutlineItems<TMessage extends AgentChatShellMessage>(\n messages: TMessage[],\n getLabel: (message: TMessage, index: number) => string = (message, index) =>\n defaultOutlineLabel(message.content, index),\n getResponsePreview: (message: TMessage) => string = (message) =>\n defaultOutlinePreview(message.content)\n): LatestThreadScrollerOutlineItem[] {\n return messages.reduce((items, message) => {\n if (message.role === \"user\") {\n items.push({\n id: message.id,\n label: getLabel(message, items.length),\n messageIds: [message.id],\n });\n return items;\n }\n const currentItem = items[items.length - 1];\n currentItem?.messageIds?.push(message.id);\n if (\n currentItem &&\n !currentItem.responsePreview &&\n message.role === \"assistant\"\n ) {\n currentItem.responsePreview = getResponsePreview(message);\n }\n return items;\n }, [] as LatestThreadScrollerOutlineItem[]);\n}\n\nexport function AgentChatShell<TMessage extends AgentChatShellMessage>({\n className,\n composer,\n composerOverlayClassName,\n composerOverlayHeight,\n contentClassName,\n defaultComposerOverlayHeight = 192,\n dir = \"ltr\",\n emptyInputAttachmentCount = 0,\n emptyTitle = \"Ready when you are.\",\n hideComposer = false,\n isLoadingOlder = false,\n messages,\n onLoadOlder,\n onScrollStateChange,\n outlineItems,\n outlineSide = \"left\",\n renderEmptyState,\n renderMessage,\n renderOlderLoader,\n}: AgentChatShellProps<TMessage>) {\n const overlayRef = React.useRef<HTMLDivElement | null>(null);\n const viewportRef = React.useRef<HTMLElement | null>(null);\n const contentRef = React.useRef<HTMLDivElement | null>(null);\n const lastScrollStateRef = React.useRef<AgentChatShellScrollState | null>(null);\n const [measuredOverlayHeight, setMeasuredOverlayHeight] = React.useState(\n defaultComposerOverlayHeight\n );\n const olderPageRequestRef = React.useRef(false);\n const items = React.useMemo(\n () => outlineItems || buildAgentChatOutlineItems(messages),\n [messages, outlineItems]\n );\n\n const updateScrollState = React.useCallback(() => {\n const viewport = viewportRef.current;\n if (!viewport || !onScrollStateChange) return;\n const next = getAgentChatShellScrollState(viewport);\n const previous = lastScrollStateRef.current;\n if (\n previous &&\n previous.canScroll === next.canScroll &&\n previous.isScrolled === next.isScrolled\n ) {\n return;\n }\n lastScrollStateRef.current = next;\n onScrollStateChange(next);\n }, [onScrollStateChange]);\n\n React.useEffect(() => {\n const overlay = overlayRef.current;\n if (!overlay || hideComposer) return;\n const updateHeight = () => {\n setMeasuredOverlayHeight(Math.ceil(overlay.getBoundingClientRect().height));\n };\n updateHeight();\n const observer = new ResizeObserver(updateHeight);\n observer.observe(overlay);\n return () => observer.disconnect();\n }, [hideComposer]);\n\n React.useEffect(() => {\n if (!onScrollStateChange) return;\n updateScrollState();\n const viewport = viewportRef.current;\n const content = contentRef.current;\n if (!viewport || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(updateScrollState);\n observer.observe(viewport);\n if (content) observer.observe(content);\n return () => observer.disconnect();\n }, [messages, onScrollStateChange, updateScrollState]);\n\n const handleScroll = React.useCallback(\n (event: React.UIEvent<HTMLDivElement>) => {\n updateScrollState();\n if (\n !onLoadOlder ||\n isLoadingOlder ||\n olderPageRequestRef.current ||\n event.currentTarget.scrollTop > 48\n ) {\n return;\n }\n olderPageRequestRef.current = true;\n void Promise.resolve(onLoadOlder()).finally(() => {\n olderPageRequestRef.current = false;\n });\n },\n [isLoadingOlder, onLoadOlder, updateScrollState]\n );\n\n const bottomPadding = hideComposer\n ? 32\n : (composerOverlayHeight ?? measuredOverlayHeight) + 72;\n const isEmptyState = messages.length === 0;\n\n if (isEmptyState) {\n return (\n <div className={cn(\"relative flex h-full min-h-0 w-full flex-col overflow-visible px-4\", className)} dir={dir}>\n {renderEmptyState ? (\n renderEmptyState({ composer: hideComposer ? undefined : composer, title: emptyTitle })\n ) : (\n <div className=\"relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13\">\n <div className=\"flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500\">\n <p\n className={cn(\n \"pb-6 text-3xl font-semibold transition-transform duration-200\",\n emptyInputAttachmentCount > 0 && \"-translate-y-12\"\n )}\n >\n {emptyTitle}\n </p>\n {!hideComposer && composer ? (\n <div className=\"mx-auto w-full max-w-3xl px-4\">{composer}</div>\n ) : null}\n </div>\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={cn(\"relative flex h-full min-h-0 w-full flex-col\", className)} dir={dir}>\n <LatestThreadScroller.Provider>\n <LatestThreadScroller.Root>\n <LatestThreadScroller.Viewport\n onScroll={handleScroll}\n onViewportChange={(node) => {\n viewportRef.current = node;\n updateScrollState();\n }}\n >\n <LatestThreadScroller.Content\n ref={contentRef}\n className={cn(\n \"mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12\",\n contentClassName\n )}\n style={{ paddingBottom: bottomPadding }}\n >\n {isLoadingOlder ? renderOlderLoader?.() : null}\n {messages.map((message, index) => (\n <LatestThreadScroller.Item\n key={message.id}\n messageId={message.id}\n scrollAnchor={message.role === \"user\"}\n >\n {renderMessage(message, index)}\n </LatestThreadScroller.Item>\n ))}\n </LatestThreadScroller.Content>\n </LatestThreadScroller.Viewport>\n <LatestThreadScroller.Outline\n dir={dir}\n items={items}\n side={outlineSide}\n />\n </LatestThreadScroller.Root>\n\n {!hideComposer && composer ? (\n <div\n ref={overlayRef}\n className={cn(\n \"pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10\",\n composerOverlayClassName\n )}\n style={{\n background:\n \"linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--chat-background, var(--background)) 85%, transparent) 45%, var(--chat-background, var(--background)) 65%, var(--chat-background, var(--background)) 100%)\",\n }}\n >\n <div className=\"pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3\">\n <LatestThreadScroller.Button />\n </div>\n <div className=\"pointer-events-auto mx-auto w-full max-w-3xl px-4\">\n {composer}\n </div>\n </div>\n ) : null}\n </LatestThreadScroller.Provider>\n </div>\n );\n}\n","import * as React from \"react\";\nimport {\n MessageScroller,\n useMessageScroller,\n useMessageScrollerVisibility,\n} from \"@shadcn/react/message-scroller\";\n\nconst cx = (...values: Array<string | false | null | undefined>) =>\n values.filter(Boolean).join(\" \");\n\ntype MessageScrollerProviderProps = React.ComponentProps<\n typeof MessageScroller.Provider\n>;\ntype MessageScrollerRootProps = React.ComponentProps<typeof MessageScroller.Root>;\ntype MessageScrollerViewportProps = React.ComponentProps<\n typeof MessageScroller.Viewport\n>;\ntype MessageScrollerContentProps = React.ComponentProps<\n typeof MessageScroller.Content\n>;\ntype MessageScrollerItemProps = React.ComponentProps<typeof MessageScroller.Item>;\ntype MessageScrollerButtonProps = React.ComponentProps<typeof MessageScroller.Button>;\n\ntype LatestThreadScrollerLayoutContextValue = {\n contentElement: HTMLElement | null;\n rootElement: HTMLElement | null;\n setContentElement: (node: HTMLElement | null) => void;\n setRootElement: (node: HTMLElement | null) => void;\n};\n\nconst LatestThreadScrollerLayoutContext =\n React.createContext<LatestThreadScrollerLayoutContextValue | null>(null);\n\nconst useLatestThreadScrollerLayout = () =>\n React.useContext(LatestThreadScrollerLayoutContext);\n\nexport type LatestThreadScrollerProviderProps = MessageScrollerProviderProps;\n\nexport function LatestThreadScrollerProvider({\n autoScroll = true,\n defaultScrollPosition = \"last-anchor\",\n scrollPreviousItemPeek = 64,\n ...props\n}: LatestThreadScrollerProviderProps) {\n return (\n <MessageScroller.Provider\n autoScroll={autoScroll}\n defaultScrollPosition={defaultScrollPosition}\n scrollPreviousItemPeek={scrollPreviousItemPeek}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerRootProps = MessageScrollerRootProps;\n\nexport function LatestThreadScrollerRoot({\n className,\n ref,\n ...props\n}: LatestThreadScrollerRootProps) {\n const [rootElement, setRootElement] = React.useState<HTMLElement | null>(null);\n const [contentElement, setContentElement] =\n React.useState<HTMLElement | null>(null);\n const layoutContext = React.useMemo<LatestThreadScrollerLayoutContextValue>(\n () => ({\n contentElement,\n rootElement,\n setContentElement,\n setRootElement,\n }),\n [contentElement, rootElement]\n );\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n setRootElement(node);\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n return (\n <LatestThreadScrollerLayoutContext.Provider value={layoutContext}>\n <MessageScroller.Root\n ref={setRef}\n className={cx(\n \"relative flex size-full min-h-0 flex-1 flex-col overflow-hidden\",\n className\n )}\n {...props}\n />\n </LatestThreadScrollerLayoutContext.Provider>\n );\n}\n\nexport type LatestThreadScrollerViewportProps =\n MessageScrollerViewportProps & {\n onViewportChange?: (node: HTMLElement | null) => void;\n };\n\nexport function LatestThreadScrollerViewport({\n className,\n onViewportChange,\n preserveScrollOnPrepend = true,\n ref,\n ...props\n}: LatestThreadScrollerViewportProps) {\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n onViewportChange?.(node);\n },\n [onViewportChange, ref]\n );\n\n return (\n <MessageScroller.Viewport\n ref={setRef}\n className={cx(\n \"size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain\",\n className\n )}\n preserveScrollOnPrepend={preserveScrollOnPrepend}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerContentProps = MessageScrollerContentProps;\n\nexport function LatestThreadScrollerContent({\n className,\n ref,\n ...props\n}: LatestThreadScrollerContentProps) {\n const layout = useLatestThreadScrollerLayout();\n const setRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n layout?.setContentElement(node);\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [layout, ref]\n );\n\n return (\n <MessageScroller.Content\n ref={setRef}\n data-agents24-latest-thread-content=\"\"\n className={cx(\"flex h-max min-h-full flex-col gap-8 p-4\", className)}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerItemProps = Omit<\n MessageScrollerItemProps,\n \"messageId\"\n> & {\n messageId: string;\n};\n\nexport function LatestThreadScrollerItem({\n className,\n messageId,\n ...props\n}: LatestThreadScrollerItemProps) {\n return (\n <MessageScroller.Item\n className={cx(\n \"min-w-0 shrink-0 overflow-visible\",\n className\n )}\n messageId={messageId}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerButtonProps = Omit<\n MessageScrollerButtonProps,\n \"render\"\n> & {\n className?: string;\n render?: MessageScrollerButtonProps[\"render\"];\n};\n\nexport function LatestThreadScrollerButton({\n children,\n className,\n direction = \"end\",\n render,\n ...props\n}: LatestThreadScrollerButtonProps) {\n if (render) {\n return (\n <MessageScroller.Button\n direction={direction}\n render={render}\n {...props}\n />\n );\n }\n\n return (\n <MessageScroller.Button\n direction={direction}\n render={(buttonProps) => (\n <button\n {...(buttonProps as Record<string, unknown>)}\n aria-label={\n direction === \"end\"\n ? \"Scroll to latest message\"\n : \"Scroll to first message\"\n }\n className={cx(\n \"absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-all duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100\",\n className\n )}\n type=\"button\"\n >\n {children ?? (direction === \"end\" ? \"↓\" : \"↑\")}\n </button>\n )}\n {...props}\n />\n );\n}\n\nexport type LatestThreadScrollerOutlineItem = {\n id: string;\n label: string;\n messageIds?: string[];\n responsePreview?: string;\n};\n\nexport type LatestThreadScrollerOutlineProps = {\n cardClassName?: string;\n className?: string;\n contentMaxWidth?: number;\n dir?: \"ltr\" | \"rtl\";\n items: LatestThreadScrollerOutlineItem[];\n minContainerWidthForRail?: number;\n minSideGutterForRail?: number;\n side?: \"left\" | \"right\";\n};\n\nexport function LatestThreadScrollerOutline({\n cardClassName,\n className,\n contentMaxWidth = 768,\n dir = \"ltr\",\n items,\n minContainerWidthForRail = 0,\n minSideGutterForRail = 48,\n side,\n}: LatestThreadScrollerOutlineProps) {\n const navRef = React.useRef<HTMLElement | null>(null);\n const layout = useLatestThreadScrollerLayout();\n const [hasSideRoom, setHasSideRoom] = React.useState(false);\n const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null);\n const { scrollToMessage } = useMessageScroller();\n const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility();\n const visibleMessageIdSet = React.useMemo(\n () => new Set(visibleMessageIds),\n [visibleMessageIds]\n );\n const focusIndex = hoveredIndex ?? -1;\n const railSide = side ?? (dir === \"rtl\" ? \"right\" : \"left\");\n\n React.useEffect(() => {\n const root = layout?.rootElement ?? navRef.current?.parentElement;\n if (!root) return;\n\n const updateSideRoom = () => {\n const rootRect = root.getBoundingClientRect();\n const rootWidth = rootRect.width;\n const content = layout?.contentElement;\n const sideGutter = content\n ? Math.max(\n 0,\n railSide === \"left\"\n ? content.getBoundingClientRect().left - rootRect.left\n : rootRect.right - content.getBoundingClientRect().right\n )\n : Math.max(0, (rootWidth - contentMaxWidth) / 2);\n const nextHasSideRoom =\n rootWidth >= minContainerWidthForRail &&\n sideGutter >= minSideGutterForRail;\n setHasSideRoom((current) =>\n current === nextHasSideRoom ? current : nextHasSideRoom\n );\n };\n\n updateSideRoom();\n if (typeof ResizeObserver === \"undefined\") return;\n const resizeObserver = new ResizeObserver(updateSideRoom);\n resizeObserver.observe(root);\n const content = layout?.contentElement;\n if (content) {\n resizeObserver.observe(content);\n }\n return () => resizeObserver.disconnect();\n }, [\n contentMaxWidth,\n layout?.contentElement,\n layout?.rootElement,\n minContainerWidthForRail,\n minSideGutterForRail,\n railSide,\n ]);\n\n if (items.length < 2) {\n return null;\n }\n\n return (\n <nav\n ref={navRef}\n aria-label=\"Transcript outline\"\n className={cx(\n \"pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible\",\n hasSideRoom ? \"hidden md:block\" : \"hidden\",\n railSide === \"left\" ? \"left-1\" : \"right-1\",\n className\n )}\n dir={dir}\n >\n <div className=\"pointer-events-auto flex flex-col gap-0 py-1\">\n {items.map((item, index) => {\n const itemMessageIds =\n item.messageIds && item.messageIds.length > 0\n ? item.messageIds\n : [item.id];\n const active = itemMessageIds.some(\n (messageId) =>\n visibleMessageIdSet.has(messageId) || messageId === currentAnchorId\n );\n const distance =\n focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY;\n const isFocused = distance === 0;\n const isNear = distance === 1;\n const isNextNear = distance === 2;\n const barWidth = isFocused\n ? 50\n : isNear\n ? 36\n : isNextNear\n ? 23\n : 10;\n const barOpacity = isFocused ? 1 : isNear ? 0.78 : isNextNear ? 0.58 : 0.36;\n const fallbackLabel = `Turn ${index + 1}`;\n const previewLabel =\n item.label && item.label !== fallbackLabel ? item.label : \"\";\n const responsePreview = (item.responsePreview || \"\").trim();\n\n return (\n <button\n key={item.id}\n type=\"button\"\n data-active={active}\n dir=\"ltr\"\n className={cx(\n \"group relative flex h-2 w-16 items-center rounded-sm outline-none\",\n railSide === \"left\" ? \"justify-start\" : \"justify-end\"\n )}\n aria-label={`Jump to ${item.label || fallbackLabel}`}\n onBlur={() => setHoveredIndex(null)}\n onFocus={() => setHoveredIndex(index)}\n onMouseEnter={() => setHoveredIndex(index)}\n onMouseLeave={() => setHoveredIndex(null)}\n onClick={() =>\n scrollToMessage(item.id, {\n align: \"start\",\n behavior: \"smooth\",\n })\n }\n >\n <span\n className=\"h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground\"\n style={{\n opacity: barOpacity,\n width: barWidth,\n }}\n />\n {previewLabel ? (\n <span\n dir={dir}\n className={cx(\n \"pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100\",\n railSide === \"left\"\n ? \"left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0\"\n : \"right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0\",\n cardClassName\n )}\n >\n <span\n className=\"block overflow-hidden text-sm leading-5 text-foreground\"\n style={{\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: responsePreview ? 2 : 4,\n }}\n >\n {previewLabel}\n </span>\n {responsePreview ? (\n <span\n className=\"mt-1 block overflow-hidden text-sm leading-5 text-muted-foreground\"\n style={{\n display: \"-webkit-box\",\n WebkitBoxOrient: \"vertical\",\n WebkitLineClamp: 2,\n }}\n >\n {responsePreview}\n </span>\n ) : null}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n </nav>\n );\n}\n\nexport const LatestThreadScroller = {\n Provider: LatestThreadScrollerProvider,\n Root: LatestThreadScrollerRoot,\n Viewport: LatestThreadScrollerViewport,\n Content: LatestThreadScrollerContent,\n Item: LatestThreadScrollerItem,\n Button: LatestThreadScrollerButton,\n Outline: LatestThreadScrollerOutline,\n};\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...values: ClassValue[]) {\n return twMerge(clsx(values));\n}\n"],"mappings":"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","names":["React","content","import_jsx_runtime"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LatestThreadScroller
3
- } from "../chunk-E3TJ3I6G.js";
3
+ } from "../chunk-FFO6KEB4.js";
4
4
  import {
5
5
  cn
6
6
  } from "../chunk-UU7OXHL3.js";
@@ -8,6 +8,13 @@ import {
8
8
  // src/templates/agent-chat-shell.tsx
9
9
  import * as React from "react";
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
11
+ function getAgentChatShellScrollState(viewport) {
12
+ const canScroll = viewport.scrollHeight - viewport.clientHeight > 1;
13
+ return {
14
+ canScroll,
15
+ isScrolled: canScroll && viewport.scrollTop > 4
16
+ };
17
+ }
11
18
  var defaultOutlineLabel = (content, index) => {
12
19
  const label = String(content || "").replace(/\s+/g, " ").trim();
13
20
  return label || `Turn ${index + 1}`;
@@ -39,16 +46,23 @@ function AgentChatShell({
39
46
  contentClassName,
40
47
  defaultComposerOverlayHeight = 192,
41
48
  dir = "ltr",
49
+ emptyInputAttachmentCount = 0,
50
+ emptyTitle = "Ready when you are.",
42
51
  hideComposer = false,
43
52
  isLoadingOlder = false,
44
53
  messages,
45
54
  onLoadOlder,
55
+ onScrollStateChange,
46
56
  outlineItems,
47
57
  outlineSide = "left",
58
+ renderEmptyState,
48
59
  renderMessage,
49
60
  renderOlderLoader
50
61
  }) {
51
62
  const overlayRef = React.useRef(null);
63
+ const viewportRef = React.useRef(null);
64
+ const contentRef = React.useRef(null);
65
+ const lastScrollStateRef = React.useRef(null);
52
66
  const [measuredOverlayHeight, setMeasuredOverlayHeight] = React.useState(
53
67
  defaultComposerOverlayHeight
54
68
  );
@@ -57,6 +71,17 @@ function AgentChatShell({
57
71
  () => outlineItems || buildAgentChatOutlineItems(messages),
58
72
  [messages, outlineItems]
59
73
  );
74
+ const updateScrollState = React.useCallback(() => {
75
+ const viewport = viewportRef.current;
76
+ if (!viewport || !onScrollStateChange) return;
77
+ const next = getAgentChatShellScrollState(viewport);
78
+ const previous = lastScrollStateRef.current;
79
+ if (previous && previous.canScroll === next.canScroll && previous.isScrolled === next.isScrolled) {
80
+ return;
81
+ }
82
+ lastScrollStateRef.current = next;
83
+ onScrollStateChange(next);
84
+ }, [onScrollStateChange]);
60
85
  React.useEffect(() => {
61
86
  const overlay = overlayRef.current;
62
87
  if (!overlay || hideComposer) return;
@@ -68,8 +93,20 @@ function AgentChatShell({
68
93
  observer.observe(overlay);
69
94
  return () => observer.disconnect();
70
95
  }, [hideComposer]);
96
+ React.useEffect(() => {
97
+ if (!onScrollStateChange) return;
98
+ updateScrollState();
99
+ const viewport = viewportRef.current;
100
+ const content = contentRef.current;
101
+ if (!viewport || typeof ResizeObserver === "undefined") return;
102
+ const observer = new ResizeObserver(updateScrollState);
103
+ observer.observe(viewport);
104
+ if (content) observer.observe(content);
105
+ return () => observer.disconnect();
106
+ }, [messages, onScrollStateChange, updateScrollState]);
71
107
  const handleScroll = React.useCallback(
72
108
  (event) => {
109
+ updateScrollState();
73
110
  if (!onLoadOlder || isLoadingOlder || olderPageRequestRef.current || event.currentTarget.scrollTop > 48) {
74
111
  return;
75
112
  }
@@ -78,33 +115,60 @@ function AgentChatShell({
78
115
  olderPageRequestRef.current = false;
79
116
  });
80
117
  },
81
- [isLoadingOlder, onLoadOlder]
118
+ [isLoadingOlder, onLoadOlder, updateScrollState]
82
119
  );
83
120
  const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
84
- return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ jsxs(LatestThreadScroller.Provider, { children: [
85
- /* @__PURE__ */ jsxs(LatestThreadScroller.Root, { children: [
86
- /* @__PURE__ */ jsx(LatestThreadScroller.Viewport, { onScroll: handleScroll, children: /* @__PURE__ */ jsxs(
87
- LatestThreadScroller.Content,
121
+ const isEmptyState = messages.length === 0;
122
+ if (isEmptyState) {
123
+ return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ jsx("div", { className: "relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
124
+ /* @__PURE__ */ jsx(
125
+ "p",
88
126
  {
89
127
  className: cn(
90
- "mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
91
- contentClassName
128
+ "pb-6 text-3xl font-semibold transition-transform duration-200",
129
+ emptyInputAttachmentCount > 0 && "-translate-y-12"
92
130
  ),
93
- style: { paddingBottom: bottomPadding },
94
- children: [
95
- isLoadingOlder ? renderOlderLoader?.() : null,
96
- messages.map((message, index) => /* @__PURE__ */ jsx(
97
- LatestThreadScroller.Item,
98
- {
99
- messageId: message.id,
100
- scrollAnchor: message.role === "user",
101
- children: renderMessage(message, index)
102
- },
103
- message.id
104
- ))
105
- ]
131
+ children: emptyTitle
132
+ }
133
+ ),
134
+ !hideComposer && composer ? /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl px-4", children: composer }) : null
135
+ ] }) }) });
136
+ }
137
+ return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ jsxs(LatestThreadScroller.Provider, { children: [
138
+ /* @__PURE__ */ jsxs(LatestThreadScroller.Root, { children: [
139
+ /* @__PURE__ */ jsx(
140
+ LatestThreadScroller.Viewport,
141
+ {
142
+ onScroll: handleScroll,
143
+ onViewportChange: (node) => {
144
+ viewportRef.current = node;
145
+ updateScrollState();
146
+ },
147
+ children: /* @__PURE__ */ jsxs(
148
+ LatestThreadScroller.Content,
149
+ {
150
+ ref: contentRef,
151
+ className: cn(
152
+ "mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
153
+ contentClassName
154
+ ),
155
+ style: { paddingBottom: bottomPadding },
156
+ children: [
157
+ isLoadingOlder ? renderOlderLoader?.() : null,
158
+ messages.map((message, index) => /* @__PURE__ */ jsx(
159
+ LatestThreadScroller.Item,
160
+ {
161
+ messageId: message.id,
162
+ scrollAnchor: message.role === "user",
163
+ children: renderMessage(message, index)
164
+ },
165
+ message.id
166
+ ))
167
+ ]
168
+ }
169
+ )
106
170
  }
107
- ) }),
171
+ ),
108
172
  /* @__PURE__ */ jsx(
109
173
  LatestThreadScroller.Outline,
110
174
  {
@@ -127,7 +191,7 @@ function AgentChatShell({
127
191
  },
128
192
  children: [
129
193
  /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ jsx(LatestThreadScroller.Button, {}) }),
130
- /* @__PURE__ */ jsx("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl", children: composer })
194
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
131
195
  ]
132
196
  }
133
197
  ) : null
@@ -135,6 +199,7 @@ function AgentChatShell({
135
199
  }
136
200
  export {
137
201
  AgentChatShell,
138
- buildAgentChatOutlineItems
202
+ buildAgentChatOutlineItems,
203
+ getAgentChatShellScrollState
139
204
  };
140
205
  //# sourceMappingURL=index.js.map