@assistant-ui/react 0.11.47 → 0.11.49

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 (86) hide show
  1. package/dist/client/types/Thread.d.ts +4 -0
  2. package/dist/client/types/Thread.d.ts.map +1 -1
  3. package/dist/context/react/index.d.ts +1 -1
  4. package/dist/context/react/index.d.ts.map +1 -1
  5. package/dist/context/react/index.js.map +1 -1
  6. package/dist/context/stores/ThreadViewport.js +1 -1
  7. package/dist/context/stores/ThreadViewport.js.map +1 -1
  8. package/dist/legacy-runtime/client/ThreadRuntimeClient.d.ts.map +1 -1
  9. package/dist/legacy-runtime/client/ThreadRuntimeClient.js +1 -0
  10. package/dist/legacy-runtime/client/ThreadRuntimeClient.js.map +1 -1
  11. package/dist/legacy-runtime/runtime/subscribable/SKIP_UPDATE.js +1 -1
  12. package/dist/legacy-runtime/runtime/subscribable/SKIP_UPDATE.js.map +1 -1
  13. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +2 -2
  14. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  15. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +1 -4
  16. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -1
  17. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +34 -12
  18. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
  19. package/dist/legacy-runtime/runtime-cores/external-store/auto-status.js +1 -1
  20. package/dist/legacy-runtime/runtime-cores/external-store/auto-status.js.map +1 -1
  21. package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.js +2 -2
  22. package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.js.map +1 -1
  23. package/dist/model-context/registry/ModelContextRegistry.js +3 -3
  24. package/dist/model-context/registry/ModelContextRegistry.js.map +1 -1
  25. package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts +17 -0
  26. package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -0
  27. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +54 -0
  28. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -0
  29. package/dist/primitives/actionBar/index.d.ts +1 -0
  30. package/dist/primitives/actionBar/index.d.ts.map +1 -1
  31. package/dist/primitives/actionBar/index.js +2 -0
  32. package/dist/primitives/actionBar/index.js.map +1 -1
  33. package/dist/primitives/assistant/AssistantIf.d.ts +12 -0
  34. package/dist/primitives/assistant/AssistantIf.d.ts.map +1 -0
  35. package/dist/primitives/assistant/AssistantIf.js +16 -0
  36. package/dist/primitives/assistant/AssistantIf.js.map +1 -0
  37. package/dist/primitives/composer/ComposerIf.d.ts +3 -0
  38. package/dist/primitives/composer/ComposerIf.d.ts.map +1 -1
  39. package/dist/primitives/composer/ComposerIf.js.map +1 -1
  40. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  41. package/dist/primitives/composer/ComposerInput.js +1 -0
  42. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  43. package/dist/primitives/index.d.ts +1 -0
  44. package/dist/primitives/index.d.ts.map +1 -1
  45. package/dist/primitives/index.js +2 -0
  46. package/dist/primitives/index.js.map +1 -1
  47. package/dist/primitives/message/MessageIf.d.ts +3 -0
  48. package/dist/primitives/message/MessageIf.d.ts.map +1 -1
  49. package/dist/primitives/message/MessageIf.js.map +1 -1
  50. package/dist/primitives/message/MessageParts.js +2 -2
  51. package/dist/primitives/message/MessageParts.js.map +1 -1
  52. package/dist/primitives/thread/ThreadIf.d.ts +3 -0
  53. package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
  54. package/dist/primitives/thread/ThreadIf.js +2 -3
  55. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  56. package/dist/primitives/thread/ThreadViewport.d.ts +36 -0
  57. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  58. package/dist/primitives/thread/ThreadViewport.js +21 -12
  59. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  60. package/dist/primitives/thread/ThreadViewportSlack.d.ts.map +1 -1
  61. package/dist/primitives/thread/ThreadViewportSlack.js +4 -1
  62. package/dist/primitives/thread/ThreadViewportSlack.js.map +1 -1
  63. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts +20 -2
  64. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  65. package/dist/primitives/thread/useThreadViewportAutoScroll.js +21 -2
  66. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  67. package/dist/tests/setup.js +44 -42
  68. package/dist/tests/setup.js.map +1 -1
  69. package/package.json +7 -7
  70. package/src/client/types/Thread.ts +5 -0
  71. package/src/context/react/index.ts +1 -0
  72. package/src/legacy-runtime/client/ThreadRuntimeClient.ts +1 -0
  73. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx +1 -1
  74. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +40 -17
  75. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +70 -0
  76. package/src/primitives/actionBar/index.ts +1 -0
  77. package/src/primitives/assistant/AssistantIf.tsx +25 -0
  78. package/src/primitives/composer/ComposerIf.tsx +3 -0
  79. package/src/primitives/composer/ComposerInput.tsx +3 -0
  80. package/src/primitives/index.ts +2 -0
  81. package/src/primitives/message/MessageIf.tsx +3 -0
  82. package/src/primitives/message/MessageParts.tsx +2 -2
  83. package/src/primitives/thread/ThreadIf.tsx +5 -3
  84. package/src/primitives/thread/ThreadViewport.tsx +49 -18
  85. package/src/primitives/thread/ThreadViewportSlack.tsx +4 -1
  86. package/src/primitives/thread/useThreadViewportAutoScroll.tsx +48 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport {\n type ComponentRef,\n forwardRef,\n ComponentPropsWithoutRef,\n useCallback,\n} from \"react\";\nimport { useThreadViewportAutoScroll } from \"./useThreadViewportAutoScroll\";\nimport { ThreadPrimitiveViewportProvider } from \"../../context/providers/ThreadViewportProvider\";\nimport { useSizeHandle } from \"../../utils/hooks/useSizeHandle\";\nimport { useThreadViewport } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace ThreadPrimitiveViewport {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Controls scroll anchoring behavior for new messages.\n * - \"bottom\" (default): Messages anchor at the bottom, classic chat behavior.\n * - \"top\": New user messages anchor at the top of the viewport for a focused reading experience.\n */\n turnAnchor?: \"top\" | \"bottom\" | undefined;\n };\n}\n\nconst useViewportSizeRef = () => {\n const register = useThreadViewport((s) => s.registerViewport);\n const getHeight = useCallback(\n (el: HTMLElement) =>\n el.clientHeight - parseFloat(getComputedStyle(el).paddingTop),\n [],\n );\n\n return useSizeHandle(register, getHeight);\n};\n\nconst ThreadPrimitiveViewportScrollable = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(({ autoScroll, children, ...rest }, forwardedRef) => {\n const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({\n autoScroll,\n });\n const viewportSizeRef = useViewportSizeRef();\n const ref = useComposedRefs(forwardedRef, autoScrollRef, viewportSizeRef);\n\n return (\n <Primitive.div {...rest} ref={ref}>\n {children}\n </Primitive.div>\n );\n});\n\nThreadPrimitiveViewportScrollable.displayName =\n \"ThreadPrimitive.ViewportScrollable\";\n\n/**\n * A scrollable viewport container for thread messages.\n *\n * This component provides a scrollable area for displaying thread messages with\n * automatic scrolling capabilities. It manages the viewport state and provides\n * context for child components to access viewport-related functionality.\n *\n * @example\n * ```tsx\n * <ThreadPrimitive.Viewport turnAnchor=\"top\">\n * <ThreadPrimitive.Messages components={{ Message: MyMessage }} />\n * </ThreadPrimitive.Viewport>\n * ```\n */\nexport const ThreadPrimitiveViewport = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(({ turnAnchor, ...props }, ref) => {\n return (\n <ThreadPrimitiveViewportProvider options={{ turnAnchor }}>\n <ThreadPrimitiveViewportScrollable {...props} ref={ref} />\n </ThreadPrimitiveViewportProvider>\n );\n});\n\nThreadPrimitiveViewport.displayName = \"ThreadPrimitive.Viewport\";\n"],"mappings":";;;AAEA,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AACP,SAAS,mCAAmC;AAC5C,SAAS,uCAAuC;AAChD,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AA4C9B;AAtBJ,IAAM,qBAAqB,MAAM;AAC/B,QAAM,WAAW,kBAAkB,CAAC,MAAM,EAAE,gBAAgB;AAC5D,QAAM,YAAY;AAAA,IAChB,CAAC,OACC,GAAG,eAAe,WAAW,iBAAiB,EAAE,EAAE,UAAU;AAAA,IAC9D,CAAC;AAAA,EACH;AAEA,SAAO,cAAc,UAAU,SAAS;AAC1C;AAEA,IAAM,oCAAoC,WAGxC,CAAC,EAAE,YAAY,UAAU,GAAG,KAAK,GAAG,iBAAiB;AACrD,QAAM,gBAAgB,4BAA4C;AAAA,IAChE;AAAA,EACF,CAAC;AACD,QAAM,kBAAkB,mBAAmB;AAC3C,QAAM,MAAM,gBAAgB,cAAc,eAAe,eAAe;AAExE,SACE,oBAAC,UAAU,KAAV,EAAe,GAAG,MAAM,KACtB,UACH;AAEJ,CAAC;AAED,kCAAkC,cAChC;AAgBK,IAAM,0BAA0B,WAGrC,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,QAAQ;AACnC,SACE,oBAAC,mCAAgC,SAAS,EAAE,WAAW,GACrD,8BAAC,qCAAmC,GAAG,OAAO,KAAU,GAC1D;AAEJ,CAAC;AAED,wBAAwB,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { Primitive } from \"@radix-ui/react-primitive\";\nimport {\n type ComponentRef,\n forwardRef,\n ComponentPropsWithoutRef,\n useCallback,\n} from \"react\";\nimport { useThreadViewportAutoScroll } from \"./useThreadViewportAutoScroll\";\nimport { ThreadPrimitiveViewportProvider } from \"../../context/providers/ThreadViewportProvider\";\nimport { useSizeHandle } from \"../../utils/hooks/useSizeHandle\";\nimport { useThreadViewport } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace ThreadPrimitiveViewport {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Controls scroll anchoring behavior for new messages.\n * - \"bottom\" (default): Messages anchor at the bottom, classic chat behavior.\n * - \"top\": New user messages anchor at the top of the viewport for a focused reading experience.\n */\n turnAnchor?: \"top\" | \"bottom\" | undefined;\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when thread history is first loaded.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nconst useViewportSizeRef = () => {\n const register = useThreadViewport((s) => s.registerViewport);\n const getHeight = useCallback((el: HTMLElement) => el.clientHeight, []);\n return useSizeHandle(register, getHeight);\n};\n\nconst ThreadPrimitiveViewportScrollable = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(\n (\n {\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n children,\n ...rest\n },\n forwardedRef,\n ) => {\n const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n });\n const viewportSizeRef = useViewportSizeRef();\n const ref = useComposedRefs(forwardedRef, autoScrollRef, viewportSizeRef);\n\n return (\n <Primitive.div {...rest} ref={ref}>\n {children}\n </Primitive.div>\n );\n },\n);\n\nThreadPrimitiveViewportScrollable.displayName =\n \"ThreadPrimitive.ViewportScrollable\";\n\n/**\n * A scrollable viewport container for thread messages.\n *\n * This component provides a scrollable area for displaying thread messages with\n * automatic scrolling capabilities. It manages the viewport state and provides\n * context for child components to access viewport-related functionality.\n *\n * @example\n * ```tsx\n * <ThreadPrimitive.Viewport turnAnchor=\"top\">\n * <ThreadPrimitive.Messages components={{ Message: MyMessage }} />\n * </ThreadPrimitive.Viewport>\n * ```\n */\nexport const ThreadPrimitiveViewport = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(({ turnAnchor, ...props }, ref) => {\n return (\n <ThreadPrimitiveViewportProvider options={{ turnAnchor }}>\n <ThreadPrimitiveViewportScrollable {...props} ref={ref} />\n </ThreadPrimitiveViewportProvider>\n );\n});\n\nThreadPrimitiveViewport.displayName = \"ThreadPrimitive.Viewport\";\n"],"mappings":";;;AAEA,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AACP,SAAS,mCAAmC;AAC5C,SAAS,uCAAuC;AAChD,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AA0E5B;AA/BN,IAAM,qBAAqB,MAAM;AAC/B,QAAM,WAAW,kBAAkB,CAAC,MAAM,EAAE,gBAAgB;AAC5D,QAAM,YAAY,YAAY,CAAC,OAAoB,GAAG,cAAc,CAAC,CAAC;AACtE,SAAO,cAAc,UAAU,SAAS;AAC1C;AAEA,IAAM,oCAAoC;AAAA,EAIxC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,iBACG;AACH,UAAM,gBAAgB,4BAA4C;AAAA,MAChE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,kBAAkB,mBAAmB;AAC3C,UAAM,MAAM,gBAAgB,cAAc,eAAe,eAAe;AAExE,WACE,oBAAC,UAAU,KAAV,EAAe,GAAG,MAAM,KACtB,UACH;AAAA,EAEJ;AACF;AAEA,kCAAkC,cAChC;AAgBK,IAAM,0BAA0B,WAGrC,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,QAAQ;AACnC,SACE,oBAAC,mCAAgC,SAAS,EAAE,WAAW,GACrD,8BAAC,qCAAmC,GAAG,OAAO,KAAU,GAC1D;AAEJ,CAAC;AAED,wBAAwB,cAAc;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadViewportSlack.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,EAAE,EACP,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AA2Bf,MAAM,MAAM,wBAAwB,GAAG;IACrC,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,EAAE,EAAE,CAAC,wBAAwB,CAoDrE,CAAC"}
1
+ {"version":3,"file":"ThreadViewportSlack.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,EAAE,EACP,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AA2Bf,MAAM,MAAM,wBAAwB,GAAG;IACrC,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,EAAE,EAAE,CAAC,wBAAwB,CAuDrE,CAAC"}
@@ -33,7 +33,10 @@ var ThreadPrimitiveViewportSlack = ({
33
33
  fillClampThreshold = "10em",
34
34
  fillClampOffset = "6em"
35
35
  }) => {
36
- const isLast = useAssistantState(({ message }) => message.isLast);
36
+ const isLast = useAssistantState(
37
+ // only add slack if the message is the last message and we already have at least 3 messages
38
+ ({ message }) => message.isLast && message.index >= 2
39
+ );
37
40
  const threadViewportStore = useThreadViewportStore({ optional: true });
38
41
  const isNested = useContext(SlackNestingContext);
39
42
  const callback = useCallback(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"sourcesContent":["\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n createContext,\n type FC,\n type ReactNode,\n useCallback,\n useContext,\n} from \"react\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\nimport { useAssistantState } from \"../../context\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\n\nconst SlackNestingContext = createContext(false);\n\nconst parseCssLength = (value: string, element: HTMLElement): number => {\n const match = value.match(/^([\\d.]+)(em|px|rem)$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]!);\n const unit = match[2];\n\n if (unit === \"px\") return num;\n if (unit === \"em\") {\n const fontSize = parseFloat(getComputedStyle(element).fontSize) || 16;\n return num * fontSize;\n }\n if (unit === \"rem\") {\n const rootFontSize =\n parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return num * rootFontSize;\n }\n return 0;\n};\n\nexport type ThreadViewportSlackProps = {\n /** Threshold at which the user message height clamps to the offset */\n fillClampThreshold?: string;\n /** Offset used when clamping large user messages */\n fillClampOffset?: string;\n children: ReactNode;\n};\n\n/**\n * A slot component that provides minimum height to enable scroll anchoring.\n *\n * When using `turnAnchor=\"top\"`, this component ensures there is\n * enough scroll room below the anchor point (last user message) for it to scroll\n * to the top of the viewport. The min-height is applied only to the last\n * assistant message.\n *\n * This component is used internally by MessagePrimitive.Root.\n */\nexport const ThreadPrimitiveViewportSlack: FC<ThreadViewportSlackProps> = ({\n children,\n fillClampThreshold = \"10em\",\n fillClampOffset = \"6em\",\n}) => {\n const isLast = useAssistantState(({ message }) => message.isLast);\n const threadViewportStore = useThreadViewportStore({ optional: true });\n const isNested = useContext(SlackNestingContext);\n\n const callback = useCallback(\n (el: HTMLElement) => {\n if (!threadViewportStore || isNested) return;\n\n const updateMinHeight = () => {\n const state = threadViewportStore.getState();\n if (state.turnAnchor === \"top\" && isLast) {\n const { viewport, inset, userMessage } = state.height;\n const threshold = parseCssLength(fillClampThreshold, el);\n const offset = parseCssLength(fillClampOffset, el);\n const clampAdjustment =\n userMessage <= threshold ? userMessage : offset;\n\n const minHeight = Math.max(0, viewport - inset - clampAdjustment);\n el.style.minHeight = `${minHeight}px`;\n el.style.flexShrink = \"0\";\n el.style.transition = \"min-height 0s\";\n } else {\n el.style.minHeight = \"\";\n el.style.flexShrink = \"\";\n el.style.transition = \"\";\n }\n };\n\n updateMinHeight();\n return threadViewportStore.subscribe(updateMinHeight);\n },\n [\n threadViewportStore,\n isLast,\n isNested,\n fillClampThreshold,\n fillClampOffset,\n ],\n );\n\n const ref = useManagedRef<HTMLElement>(callback);\n\n return (\n <SlackNestingContext.Provider value={true}>\n <Slot ref={ref}>{children}</Slot>\n </SlackNestingContext.Provider>\n );\n};\n\nThreadPrimitiveViewportSlack.displayName = \"ThreadPrimitive.ViewportSlack\";\n"],"mappings":";;;AAEA,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AA2FxB;AAzFN,IAAM,sBAAsB,cAAc,KAAK;AAE/C,IAAM,iBAAiB,CAAC,OAAe,YAAiC;AACtE,QAAM,QAAQ,MAAM,MAAM,uBAAuB;AACjD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,WAAW,MAAM,CAAC,CAAE;AAChC,QAAM,OAAO,MAAM,CAAC;AAEpB,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,SAAS,MAAM;AACjB,UAAM,WAAW,WAAW,iBAAiB,OAAO,EAAE,QAAQ,KAAK;AACnE,WAAO,MAAM;AAAA,EACf;AACA,MAAI,SAAS,OAAO;AAClB,UAAM,eACJ,WAAW,iBAAiB,SAAS,eAAe,EAAE,QAAQ,KAAK;AACrE,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAoBO,IAAM,+BAA6D,CAAC;AAAA,EACzE;AAAA,EACA,qBAAqB;AAAA,EACrB,kBAAkB;AACpB,MAAM;AACJ,QAAM,SAAS,kBAAkB,CAAC,EAAE,QAAQ,MAAM,QAAQ,MAAM;AAChE,QAAM,sBAAsB,uBAAuB,EAAE,UAAU,KAAK,CAAC;AACrE,QAAM,WAAW,WAAW,mBAAmB;AAE/C,QAAM,WAAW;AAAA,IACf,CAAC,OAAoB;AACnB,UAAI,CAAC,uBAAuB,SAAU;AAEtC,YAAM,kBAAkB,MAAM;AAC5B,cAAM,QAAQ,oBAAoB,SAAS;AAC3C,YAAI,MAAM,eAAe,SAAS,QAAQ;AACxC,gBAAM,EAAE,UAAU,OAAO,YAAY,IAAI,MAAM;AAC/C,gBAAM,YAAY,eAAe,oBAAoB,EAAE;AACvD,gBAAM,SAAS,eAAe,iBAAiB,EAAE;AACjD,gBAAM,kBACJ,eAAe,YAAY,cAAc;AAE3C,gBAAM,YAAY,KAAK,IAAI,GAAG,WAAW,QAAQ,eAAe;AAChE,aAAG,MAAM,YAAY,GAAG,SAAS;AACjC,aAAG,MAAM,aAAa;AACtB,aAAG,MAAM,aAAa;AAAA,QACxB,OAAO;AACL,aAAG,MAAM,YAAY;AACrB,aAAG,MAAM,aAAa;AACtB,aAAG,MAAM,aAAa;AAAA,QACxB;AAAA,MACF;AAEA,sBAAgB;AAChB,aAAO,oBAAoB,UAAU,eAAe;AAAA,IACtD;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,cAA2B,QAAQ;AAE/C,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,MACnC,8BAAC,QAAK,KAAW,UAAS,GAC5B;AAEJ;AAEA,6BAA6B,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../src/primitives/thread/ThreadViewportSlack.tsx"],"sourcesContent":["\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n createContext,\n type FC,\n type ReactNode,\n useCallback,\n useContext,\n} from \"react\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\nimport { useAssistantState } from \"../../context\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\n\nconst SlackNestingContext = createContext(false);\n\nconst parseCssLength = (value: string, element: HTMLElement): number => {\n const match = value.match(/^([\\d.]+)(em|px|rem)$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]!);\n const unit = match[2];\n\n if (unit === \"px\") return num;\n if (unit === \"em\") {\n const fontSize = parseFloat(getComputedStyle(element).fontSize) || 16;\n return num * fontSize;\n }\n if (unit === \"rem\") {\n const rootFontSize =\n parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return num * rootFontSize;\n }\n return 0;\n};\n\nexport type ThreadViewportSlackProps = {\n /** Threshold at which the user message height clamps to the offset */\n fillClampThreshold?: string;\n /** Offset used when clamping large user messages */\n fillClampOffset?: string;\n children: ReactNode;\n};\n\n/**\n * A slot component that provides minimum height to enable scroll anchoring.\n *\n * When using `turnAnchor=\"top\"`, this component ensures there is\n * enough scroll room below the anchor point (last user message) for it to scroll\n * to the top of the viewport. The min-height is applied only to the last\n * assistant message.\n *\n * This component is used internally by MessagePrimitive.Root.\n */\nexport const ThreadPrimitiveViewportSlack: FC<ThreadViewportSlackProps> = ({\n children,\n fillClampThreshold = \"10em\",\n fillClampOffset = \"6em\",\n}) => {\n const isLast = useAssistantState(\n // only add slack if the message is the last message and we already have at least 3 messages\n ({ message }) => message.isLast && message.index >= 2,\n );\n const threadViewportStore = useThreadViewportStore({ optional: true });\n const isNested = useContext(SlackNestingContext);\n\n const callback = useCallback(\n (el: HTMLElement) => {\n if (!threadViewportStore || isNested) return;\n\n const updateMinHeight = () => {\n const state = threadViewportStore.getState();\n if (state.turnAnchor === \"top\" && isLast) {\n const { viewport, inset, userMessage } = state.height;\n const threshold = parseCssLength(fillClampThreshold, el);\n const offset = parseCssLength(fillClampOffset, el);\n const clampAdjustment =\n userMessage <= threshold ? userMessage : offset;\n\n const minHeight = Math.max(0, viewport - inset - clampAdjustment);\n el.style.minHeight = `${minHeight}px`;\n el.style.flexShrink = \"0\";\n el.style.transition = \"min-height 0s\";\n } else {\n el.style.minHeight = \"\";\n el.style.flexShrink = \"\";\n el.style.transition = \"\";\n }\n };\n\n updateMinHeight();\n return threadViewportStore.subscribe(updateMinHeight);\n },\n [\n threadViewportStore,\n isLast,\n isNested,\n fillClampThreshold,\n fillClampOffset,\n ],\n );\n\n const ref = useManagedRef<HTMLElement>(callback);\n\n return (\n <SlackNestingContext.Provider value={true}>\n <Slot ref={ref}>{children}</Slot>\n </SlackNestingContext.Provider>\n );\n};\n\nThreadPrimitiveViewportSlack.displayName = \"ThreadPrimitive.ViewportSlack\";\n"],"mappings":";;;AAEA,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,OACK;AACP,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AA8FxB;AA5FN,IAAM,sBAAsB,cAAc,KAAK;AAE/C,IAAM,iBAAiB,CAAC,OAAe,YAAiC;AACtE,QAAM,QAAQ,MAAM,MAAM,uBAAuB;AACjD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,WAAW,MAAM,CAAC,CAAE;AAChC,QAAM,OAAO,MAAM,CAAC;AAEpB,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,SAAS,MAAM;AACjB,UAAM,WAAW,WAAW,iBAAiB,OAAO,EAAE,QAAQ,KAAK;AACnE,WAAO,MAAM;AAAA,EACf;AACA,MAAI,SAAS,OAAO;AAClB,UAAM,eACJ,WAAW,iBAAiB,SAAS,eAAe,EAAE,QAAQ,KAAK;AACrE,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAoBO,IAAM,+BAA6D,CAAC;AAAA,EACzE;AAAA,EACA,qBAAqB;AAAA,EACrB,kBAAkB;AACpB,MAAM;AACJ,QAAM,SAAS;AAAA;AAAA,IAEb,CAAC,EAAE,QAAQ,MAAM,QAAQ,UAAU,QAAQ,SAAS;AAAA,EACtD;AACA,QAAM,sBAAsB,uBAAuB,EAAE,UAAU,KAAK,CAAC;AACrE,QAAM,WAAW,WAAW,mBAAmB;AAE/C,QAAM,WAAW;AAAA,IACf,CAAC,OAAoB;AACnB,UAAI,CAAC,uBAAuB,SAAU;AAEtC,YAAM,kBAAkB,MAAM;AAC5B,cAAM,QAAQ,oBAAoB,SAAS;AAC3C,YAAI,MAAM,eAAe,SAAS,QAAQ;AACxC,gBAAM,EAAE,UAAU,OAAO,YAAY,IAAI,MAAM;AAC/C,gBAAM,YAAY,eAAe,oBAAoB,EAAE;AACvD,gBAAM,SAAS,eAAe,iBAAiB,EAAE;AACjD,gBAAM,kBACJ,eAAe,YAAY,cAAc;AAE3C,gBAAM,YAAY,KAAK,IAAI,GAAG,WAAW,QAAQ,eAAe;AAChE,aAAG,MAAM,YAAY,GAAG,SAAS;AACjC,aAAG,MAAM,aAAa;AACtB,aAAG,MAAM,aAAa;AAAA,QACxB,OAAO;AACL,aAAG,MAAM,YAAY;AACrB,aAAG,MAAM,aAAa;AACtB,aAAG,MAAM,aAAa;AAAA,QACxB;AAAA,MACF;AAEA,sBAAgB;AAChB,aAAO,oBAAoB,UAAU,eAAe;AAAA,IACtD;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,cAA2B,QAAQ;AAE/C,SACE,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,MACnC,8BAAC,QAAK,KAAW,UAAS,GAC5B;AAEJ;AAEA,6BAA6B,cAAc;","names":[]}
@@ -1,4 +1,4 @@
1
- import { RefCallback } from "react";
1
+ import { type RefCallback } from "react";
2
2
  export declare namespace useThreadViewportAutoScroll {
3
3
  type Options = {
4
4
  /**
@@ -8,7 +8,25 @@ export declare namespace useThreadViewportAutoScroll {
8
8
  * Default false if `turnAnchor` is "top", otherwise defaults to true.
9
9
  */
10
10
  autoScroll?: boolean | undefined;
11
+ /**
12
+ * Whether to scroll to bottom when a new run starts.
13
+ *
14
+ * Defaults to true.
15
+ */
16
+ scrollToBottomOnRunStart?: boolean | undefined;
17
+ /**
18
+ * Whether to scroll to bottom when thread history is first loaded.
19
+ *
20
+ * Defaults to true.
21
+ */
22
+ scrollToBottomOnInitialize?: boolean | undefined;
23
+ /**
24
+ * Whether to scroll to bottom when switching to a different thread.
25
+ *
26
+ * Defaults to true.
27
+ */
28
+ scrollToBottomOnThreadSwitch?: boolean | undefined;
11
29
  };
12
30
  }
13
- export declare const useThreadViewportAutoScroll: <TElement extends HTMLElement>({ autoScroll, }: useThreadViewportAutoScroll.Options) => RefCallback<TElement>;
31
+ export declare const useThreadViewportAutoScroll: <TElement extends HTMLElement>({ autoScroll, scrollToBottomOnRunStart, scrollToBottomOnInitialize, scrollToBottomOnThreadSwitch, }: useThreadViewportAutoScroll.Options) => RefCallback<TElement>;
14
32
  //# sourceMappingURL=useThreadViewportAutoScroll.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useThreadViewportAutoScroll.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAuB,MAAM,OAAO,CAAC;AAQzD,yBAAiB,2BAA2B,CAAC;IAC3C,KAAY,OAAO,GAAG;QACpB;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KAClC,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS,WAAW,EAAE,iBAEvE,2BAA2B,CAAC,OAAO,KAAG,WAAW,CAAC,QAAQ,CAiF5D,CAAC"}
1
+ {"version":3,"file":"useThreadViewportAutoScroll.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAQ9D,yBAAiB,2BAA2B,CAAC;IAC3C,KAAY,OAAO,GAAG;QACpB;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjC;;;;WAIG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjD;;;;WAIG;QACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACpD,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS,WAAW,EAAE,qGAKvE,2BAA2B,CAAC,OAAO,KAAG,WAAW,CAAC,QAAQ,CAuG5D,CAAC"}
@@ -10,7 +10,10 @@ import { useManagedRef } from "../../utils/hooks/useManagedRef.js";
10
10
  import { writableStore } from "../../context/ReadonlyStore.js";
11
11
  import { useThreadViewportStore } from "../../context/react/ThreadViewportContext.js";
12
12
  var useThreadViewportAutoScroll = ({
13
- autoScroll
13
+ autoScroll,
14
+ scrollToBottomOnRunStart = true,
15
+ scrollToBottomOnInitialize = true,
16
+ scrollToBottomOnThreadSwitch = true
14
17
  }) => {
15
18
  const divRef = useRef(null);
16
19
  const threadViewportStore = useThreadViewportStore();
@@ -35,7 +38,8 @@ var useThreadViewportAutoScroll = ({
35
38
  if (newIsAtBottom) {
36
39
  scrollingToBottomBehaviorRef.current = null;
37
40
  }
38
- if (newIsAtBottom !== isAtBottom) {
41
+ const shouldUpdate = newIsAtBottom || scrollingToBottomBehaviorRef.current === null;
42
+ if (shouldUpdate && newIsAtBottom !== isAtBottom) {
39
43
  writableStore(threadViewportStore).setState({
40
44
  isAtBottom: newIsAtBottom
41
45
  });
@@ -62,11 +66,26 @@ var useThreadViewportAutoScroll = ({
62
66
  scrollToBottom(behavior);
63
67
  });
64
68
  useAssistantEvent("thread.run-start", () => {
69
+ if (!scrollToBottomOnRunStart) return;
65
70
  scrollingToBottomBehaviorRef.current = "auto";
66
71
  requestAnimationFrame(() => {
67
72
  scrollToBottom("auto");
68
73
  });
69
74
  });
75
+ useAssistantEvent("thread.initialize", () => {
76
+ if (!scrollToBottomOnInitialize) return;
77
+ scrollingToBottomBehaviorRef.current = "instant";
78
+ requestAnimationFrame(() => {
79
+ scrollToBottom("instant");
80
+ });
81
+ });
82
+ useAssistantEvent("thread-list-item.switched-to", () => {
83
+ if (!scrollToBottomOnThreadSwitch) return;
84
+ scrollingToBottomBehaviorRef.current = "instant";
85
+ requestAnimationFrame(() => {
86
+ scrollToBottom("instant");
87
+ });
88
+ });
70
89
  const autoScrollRef = useComposedRefs(resizeRef, scrollRef, divRef);
71
90
  return autoScrollRef;
72
91
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { RefCallback, useCallback, useRef } from \"react\";\nimport { useAssistantEvent } from \"../../context\";\nimport { useOnResizeContent } from \"../../utils/hooks/useOnResizeContent\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace useThreadViewportAutoScroll {\n export type Options = {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n };\n}\n\nexport const useThreadViewportAutoScroll = <TElement extends HTMLElement>({\n autoScroll,\n}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {\n const divRef = useRef<TElement>(null);\n\n const threadViewportStore = useThreadViewportStore();\n if (autoScroll === undefined) {\n autoScroll = threadViewportStore.getState().turnAnchor !== \"top\";\n }\n\n const lastScrollTop = useRef<number>(0);\n\n // bug: when ScrollToBottom's button changes its disabled state, the scroll stops\n // fix: delay the state change until the scroll is done\n // stores the scroll behavior to reuse during content resize, or null if not scrolling\n const scrollingToBottomBehaviorRef = useRef<ScrollBehavior | null>(null);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior) => {\n const div = divRef.current;\n if (!div) return;\n\n scrollingToBottomBehaviorRef.current = behavior;\n div.scrollTo({ top: div.scrollHeight, behavior });\n }, []);\n\n const handleScroll = () => {\n const div = divRef.current;\n if (!div) return;\n\n const isAtBottom = threadViewportStore.getState().isAtBottom;\n const newIsAtBottom =\n Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) < 1 ||\n div.scrollHeight <= div.clientHeight;\n\n if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {\n // ignore scroll down\n } else {\n if (newIsAtBottom) {\n scrollingToBottomBehaviorRef.current = null;\n }\n\n if (newIsAtBottom !== isAtBottom) {\n writableStore(threadViewportStore).setState({\n isAtBottom: newIsAtBottom,\n });\n }\n }\n\n lastScrollTop.current = div.scrollTop;\n };\n\n const resizeRef = useOnResizeContent(() => {\n const scrollBehavior = scrollingToBottomBehaviorRef.current;\n if (scrollBehavior) {\n scrollToBottom(scrollBehavior);\n } else if (autoScroll && threadViewportStore.getState().isAtBottom) {\n scrollToBottom(\"instant\");\n }\n\n handleScroll();\n });\n\n const scrollRef = useManagedRef<HTMLElement>((el) => {\n el.addEventListener(\"scroll\", handleScroll);\n return () => {\n el.removeEventListener(\"scroll\", handleScroll);\n };\n });\n\n useOnScrollToBottom(({ behavior }) => {\n scrollToBottom(behavior);\n });\n\n // autoscroll on run start\n useAssistantEvent(\"thread.run-start\", () => {\n scrollingToBottomBehaviorRef.current = \"auto\";\n requestAnimationFrame(() => {\n scrollToBottom(\"auto\");\n });\n });\n\n const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);\n return autoScrollRef as RefCallback<TElement>;\n};\n"],"mappings":";;;AAEA,SAAS,uBAAuB;AAChC,SAAsB,aAAa,cAAc;AACjD,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AAchC,IAAM,8BAA8B,CAA+B;AAAA,EACxE;AACF,MAAkE;AAChE,QAAM,SAAS,OAAiB,IAAI;AAEpC,QAAM,sBAAsB,uBAAuB;AACnD,MAAI,eAAe,QAAW;AAC5B,iBAAa,oBAAoB,SAAS,EAAE,eAAe;AAAA,EAC7D;AAEA,QAAM,gBAAgB,OAAe,CAAC;AAKtC,QAAM,+BAA+B,OAA8B,IAAI;AAEvE,QAAM,iBAAiB,YAAY,CAAC,aAA6B;AAC/D,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,IAAK;AAEV,iCAA6B,UAAU;AACvC,QAAI,SAAS,EAAE,KAAK,IAAI,cAAc,SAAS,CAAC;AAAA,EAClD,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM;AACzB,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,IAAK;AAEV,UAAM,aAAa,oBAAoB,SAAS,EAAE;AAClD,UAAM,gBACJ,KAAK,IAAI,IAAI,eAAe,IAAI,YAAY,IAAI,YAAY,IAAI,KAChE,IAAI,gBAAgB,IAAI;AAE1B,QAAI,CAAC,iBAAiB,cAAc,UAAU,IAAI,WAAW;AAAA,IAE7D,OAAO;AACL,UAAI,eAAe;AACjB,qCAA6B,UAAU;AAAA,MACzC;AAEA,UAAI,kBAAkB,YAAY;AAChC,sBAAc,mBAAmB,EAAE,SAAS;AAAA,UAC1C,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAEA,kBAAc,UAAU,IAAI;AAAA,EAC9B;AAEA,QAAM,YAAY,mBAAmB,MAAM;AACzC,UAAM,iBAAiB,6BAA6B;AACpD,QAAI,gBAAgB;AAClB,qBAAe,cAAc;AAAA,IAC/B,WAAW,cAAc,oBAAoB,SAAS,EAAE,YAAY;AAClE,qBAAe,SAAS;AAAA,IAC1B;AAEA,iBAAa;AAAA,EACf,CAAC;AAED,QAAM,YAAY,cAA2B,CAAC,OAAO;AACnD,OAAG,iBAAiB,UAAU,YAAY;AAC1C,WAAO,MAAM;AACX,SAAG,oBAAoB,UAAU,YAAY;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,sBAAoB,CAAC,EAAE,SAAS,MAAM;AACpC,mBAAe,QAAQ;AAAA,EACzB,CAAC;AAGD,oBAAkB,oBAAoB,MAAM;AAC1C,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,MAAM;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AAED,QAAM,gBAAgB,gBAA0B,WAAW,WAAW,MAAM;AAC5E,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport { useCallback, useRef, type RefCallback } from \"react\";\nimport { useAssistantEvent } from \"../../context\";\nimport { useOnResizeContent } from \"../../utils/hooks/useOnResizeContent\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\n\nexport namespace useThreadViewportAutoScroll {\n export type Options = {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when thread history is first loaded.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nexport const useThreadViewportAutoScroll = <TElement extends HTMLElement>({\n autoScroll,\n scrollToBottomOnRunStart = true,\n scrollToBottomOnInitialize = true,\n scrollToBottomOnThreadSwitch = true,\n}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {\n const divRef = useRef<TElement>(null);\n\n const threadViewportStore = useThreadViewportStore();\n if (autoScroll === undefined) {\n autoScroll = threadViewportStore.getState().turnAnchor !== \"top\";\n }\n\n const lastScrollTop = useRef<number>(0);\n\n // bug: when ScrollToBottom's button changes its disabled state, the scroll stops\n // fix: delay the state change until the scroll is done\n // stores the scroll behavior to reuse during content resize, or null if not scrolling\n const scrollingToBottomBehaviorRef = useRef<ScrollBehavior | null>(null);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior) => {\n const div = divRef.current;\n if (!div) return;\n\n scrollingToBottomBehaviorRef.current = behavior;\n div.scrollTo({ top: div.scrollHeight, behavior });\n }, []);\n\n const handleScroll = () => {\n const div = divRef.current;\n if (!div) return;\n\n const isAtBottom = threadViewportStore.getState().isAtBottom;\n const newIsAtBottom =\n Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) < 1 ||\n div.scrollHeight <= div.clientHeight;\n\n if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {\n // ignore scroll down\n } else {\n if (newIsAtBottom) {\n scrollingToBottomBehaviorRef.current = null;\n }\n\n const shouldUpdate =\n newIsAtBottom || scrollingToBottomBehaviorRef.current === null;\n\n if (shouldUpdate && newIsAtBottom !== isAtBottom) {\n writableStore(threadViewportStore).setState({\n isAtBottom: newIsAtBottom,\n });\n }\n }\n\n lastScrollTop.current = div.scrollTop;\n };\n\n const resizeRef = useOnResizeContent(() => {\n const scrollBehavior = scrollingToBottomBehaviorRef.current;\n if (scrollBehavior) {\n scrollToBottom(scrollBehavior);\n } else if (autoScroll && threadViewportStore.getState().isAtBottom) {\n scrollToBottom(\"instant\");\n }\n\n handleScroll();\n });\n\n const scrollRef = useManagedRef<HTMLElement>((el) => {\n el.addEventListener(\"scroll\", handleScroll);\n return () => {\n el.removeEventListener(\"scroll\", handleScroll);\n };\n });\n\n useOnScrollToBottom(({ behavior }) => {\n scrollToBottom(behavior);\n });\n\n // autoscroll on run start\n useAssistantEvent(\"thread.run-start\", () => {\n if (!scrollToBottomOnRunStart) return;\n scrollingToBottomBehaviorRef.current = \"auto\";\n requestAnimationFrame(() => {\n scrollToBottom(\"auto\");\n });\n });\n\n // scroll to bottom instantly when thread history is first loaded\n useAssistantEvent(\"thread.initialize\", () => {\n if (!scrollToBottomOnInitialize) return;\n scrollingToBottomBehaviorRef.current = \"instant\";\n requestAnimationFrame(() => {\n scrollToBottom(\"instant\");\n });\n });\n\n // scroll to bottom instantly when switching threads\n useAssistantEvent(\"thread-list-item.switched-to\", () => {\n if (!scrollToBottomOnThreadSwitch) return;\n scrollingToBottomBehaviorRef.current = \"instant\";\n requestAnimationFrame(() => {\n scrollToBottom(\"instant\");\n });\n });\n\n const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);\n return autoScrollRef as RefCallback<TElement>;\n};\n"],"mappings":";;;AAEA,SAAS,uBAAuB;AAChC,SAAS,aAAa,cAAgC;AACtD,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,8BAA8B;AAmChC,IAAM,8BAA8B,CAA+B;AAAA,EACxE;AAAA,EACA,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,+BAA+B;AACjC,MAAkE;AAChE,QAAM,SAAS,OAAiB,IAAI;AAEpC,QAAM,sBAAsB,uBAAuB;AACnD,MAAI,eAAe,QAAW;AAC5B,iBAAa,oBAAoB,SAAS,EAAE,eAAe;AAAA,EAC7D;AAEA,QAAM,gBAAgB,OAAe,CAAC;AAKtC,QAAM,+BAA+B,OAA8B,IAAI;AAEvE,QAAM,iBAAiB,YAAY,CAAC,aAA6B;AAC/D,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,IAAK;AAEV,iCAA6B,UAAU;AACvC,QAAI,SAAS,EAAE,KAAK,IAAI,cAAc,SAAS,CAAC;AAAA,EAClD,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM;AACzB,UAAM,MAAM,OAAO;AACnB,QAAI,CAAC,IAAK;AAEV,UAAM,aAAa,oBAAoB,SAAS,EAAE;AAClD,UAAM,gBACJ,KAAK,IAAI,IAAI,eAAe,IAAI,YAAY,IAAI,YAAY,IAAI,KAChE,IAAI,gBAAgB,IAAI;AAE1B,QAAI,CAAC,iBAAiB,cAAc,UAAU,IAAI,WAAW;AAAA,IAE7D,OAAO;AACL,UAAI,eAAe;AACjB,qCAA6B,UAAU;AAAA,MACzC;AAEA,YAAM,eACJ,iBAAiB,6BAA6B,YAAY;AAE5D,UAAI,gBAAgB,kBAAkB,YAAY;AAChD,sBAAc,mBAAmB,EAAE,SAAS;AAAA,UAC1C,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAEA,kBAAc,UAAU,IAAI;AAAA,EAC9B;AAEA,QAAM,YAAY,mBAAmB,MAAM;AACzC,UAAM,iBAAiB,6BAA6B;AACpD,QAAI,gBAAgB;AAClB,qBAAe,cAAc;AAAA,IAC/B,WAAW,cAAc,oBAAoB,SAAS,EAAE,YAAY;AAClE,qBAAe,SAAS;AAAA,IAC1B;AAEA,iBAAa;AAAA,EACf,CAAC;AAED,QAAM,YAAY,cAA2B,CAAC,OAAO;AACnD,OAAG,iBAAiB,UAAU,YAAY;AAC1C,WAAO,MAAM;AACX,SAAG,oBAAoB,UAAU,YAAY;AAAA,IAC/C;AAAA,EACF,CAAC;AAED,sBAAoB,CAAC,EAAE,SAAS,MAAM;AACpC,mBAAe,QAAQ;AAAA,EACzB,CAAC;AAGD,oBAAkB,oBAAoB,MAAM;AAC1C,QAAI,CAAC,yBAA0B;AAC/B,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,MAAM;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AAGD,oBAAkB,qBAAqB,MAAM;AAC3C,QAAI,CAAC,2BAA4B;AACjC,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,SAAS;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AAGD,oBAAkB,gCAAgC,MAAM;AACtD,QAAI,CAAC,6BAA8B;AACnC,iCAA6B,UAAU;AACvC,0BAAsB,MAAM;AAC1B,qBAAe,SAAS;AAAA,IAC1B,CAAC;AAAA,EACH,CAAC;AAED,QAAM,gBAAgB,gBAA0B,WAAW,WAAW,MAAM;AAC5E,SAAO;AACT;","names":[]}
@@ -1,9 +1,8 @@
1
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/chunks/vi.BiaV1qII.js
1
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/chunks/vi.2VT5v0um.js
2
2
  import { chai, equals, iterableEquality, subsetEquality, JestExtend, JestChaiExpect, JestAsymmetricMatchers, GLOBAL_EXPECT, ASYMMETRIC_MATCHERS_OBJECT, getState, setState, addCustomEqualityTesters, customMatchers } from "@vitest/expect.js";
3
3
  import { getCurrentTest } from "@vitest/runner.js";
4
- import { getNames, getTestName } from "@vitest/runner/utils.js";
5
4
 
6
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/chunks/utils.DvEY5TfP.js
5
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/chunks/utils.DvEY5TfP.js
7
6
  import { getSafeTimers } from "@vitest/utils/timers.js";
8
7
  var NAME_WORKER_STATE = "__vitest_worker__";
9
8
  function getWorkerState() {
@@ -44,8 +43,9 @@ async function waitForImportsToResolve() {
44
43
  await waitForImportsToResolve();
45
44
  }
46
45
 
47
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/chunks/vi.BiaV1qII.js
48
- import { getSafeTimers as getSafeTimers2 } from "@vitest/utils/timers.js";
46
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/chunks/vi.2VT5v0um.js
47
+ import { getSafeTimers as getSafeTimers2, delay } from "@vitest/utils/timers.js";
48
+ import { getNames } from "@vitest/runner/utils.js";
49
49
  import { stripSnapshotIndentation, addSerializer, SnapshotClient } from "@vitest/snapshot.js";
50
50
  import "@vitest/utils/error.js";
51
51
  import { assertTypes, createSimpleStackTrace } from "@vitest/utils/helpers.js";
@@ -53,10 +53,10 @@ import { fn, spyOn, restoreAllMocks, resetAllMocks, clearAllMocks, isMockFunctio
53
53
  import "@vitest/utils/offset.js";
54
54
  import { parseSingleStack } from "@vitest/utils/source-map.js";
55
55
 
56
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js
56
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js
57
57
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
58
58
 
59
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/chunks/date.Bq6ZW5rf.js
59
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/chunks/date.Bq6ZW5rf.js
60
60
  var RealDate = Date;
61
61
  var now = null;
62
62
  var MockDate = class _MockDate extends RealDate {
@@ -104,7 +104,7 @@ function resetDate() {
104
104
  globalThis.Date = RealDate;
105
105
  }
106
106
 
107
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/chunks/vi.BiaV1qII.js
107
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/chunks/vi.2VT5v0um.js
108
108
  var unsupported = [
109
109
  "matchSnapshot",
110
110
  "toMatchSnapshot",
@@ -117,6 +117,10 @@ var unsupported = [
117
117
  "toThrow",
118
118
  "toThrowError"
119
119
  ];
120
+ function throwWithCause(error, source) {
121
+ if (error.cause == null) error.cause = /* @__PURE__ */ new Error("Matcher did not succeed in time.");
122
+ throw copyStackTrace$1(error, source);
123
+ }
120
124
  function createExpectPoll(expect) {
121
125
  return function poll(fn2, options = {}) {
122
126
  const defaults = getWorkerState().config.expect?.poll ?? {};
@@ -132,35 +136,33 @@ function createExpectPoll(expect) {
132
136
  if (typeof key === "string" && unsupported.includes(key)) throw new SyntaxError(`expect.poll() is not supported in combination with .${key}(). Use vi.waitFor() if your assertion condition is unstable.`);
133
137
  return function(...args) {
134
138
  const STACK_TRACE_ERROR = /* @__PURE__ */ new Error("STACK_TRACE_ERROR");
135
- const promise = () => new Promise((resolve, reject) => {
136
- let intervalId;
137
- let timeoutId;
138
- let lastError;
139
+ const promise = async () => {
139
140
  const { setTimeout, clearTimeout } = getSafeTimers2();
140
- const check = async () => {
141
- try {
142
- chai.util.flag(assertion, "_name", key);
143
- const obj = await fn2();
144
- chai.util.flag(assertion, "object", obj);
145
- resolve(await assertionFunction.call(assertion, ...args));
146
- clearTimeout(intervalId);
147
- clearTimeout(timeoutId);
148
- } catch (err) {
149
- lastError = err;
150
- if (!chai.util.flag(assertion, "_isLastPollAttempt")) intervalId = setTimeout(check, interval);
151
- }
152
- };
153
- timeoutId = setTimeout(() => {
154
- clearTimeout(intervalId);
155
- chai.util.flag(assertion, "_isLastPollAttempt", true);
156
- const rejectWithCause = (error) => {
157
- if (error.cause == null) error.cause = /* @__PURE__ */ new Error("Matcher did not succeed in time.");
158
- reject(copyStackTrace$1(error, STACK_TRACE_ERROR));
159
- };
160
- check().then(() => rejectWithCause(lastError)).catch((e) => rejectWithCause(e));
141
+ let executionPhase = "fn";
142
+ let hasTimedOut = false;
143
+ const timerId = setTimeout(() => {
144
+ hasTimedOut = true;
161
145
  }, timeout);
162
- check();
163
- });
146
+ chai.util.flag(assertion, "_name", key);
147
+ try {
148
+ while (true) {
149
+ const isLastAttempt = hasTimedOut;
150
+ if (isLastAttempt) chai.util.flag(assertion, "_isLastPollAttempt", true);
151
+ try {
152
+ executionPhase = "fn";
153
+ const obj = await fn2();
154
+ chai.util.flag(assertion, "object", obj);
155
+ executionPhase = "assertion";
156
+ return await assertionFunction.call(assertion, ...args);
157
+ } catch (err) {
158
+ if (isLastAttempt || executionPhase === "assertion" && chai.util.flag(assertion, "_poll.assert_once")) throwWithCause(err, STACK_TRACE_ERROR);
159
+ await delay(interval, setTimeout);
160
+ }
161
+ }
162
+ } finally {
163
+ clearTimeout(timerId);
164
+ }
165
+ };
164
166
  let awaited = false;
165
167
  test2.onFinished ??= [];
166
168
  test2.onFinished.push(() => {
@@ -397,7 +399,7 @@ function createExpect(test2) {
397
399
  get testPath() {
398
400
  return getWorkerState().filepath;
399
401
  },
400
- currentTestName: test2 ? getTestName(test2) : globalState.currentTestName
402
+ currentTestName: test2 ? test2.fullTestName ?? "" : globalState.currentTestName
401
403
  }, expect);
402
404
  expect.assert = chai.assert;
403
405
  expect.extend = (matchers) => chai.expect.extend(expect, matchers);
@@ -2056,7 +2058,7 @@ To automatically clean-up native timers, use \`shouldClearNativeTimers\`.`
2056
2058
  methodName: "setTimeout",
2057
2059
  original: timersPromisesModule.setTimeout
2058
2060
  });
2059
- timersPromisesModule.setTimeout = (delay, value, options = {}) => new Promise((resolve, reject) => {
2061
+ timersPromisesModule.setTimeout = (delay2, value, options = {}) => new Promise((resolve, reject) => {
2060
2062
  const abort = () => {
2061
2063
  options.signal.removeEventListener(
2062
2064
  "abort",
@@ -2075,7 +2077,7 @@ To automatically clean-up native timers, use \`shouldClearNativeTimers\`.`
2075
2077
  clock.abortListenerMap.delete(abort);
2076
2078
  }
2077
2079
  resolve(value);
2078
- }, delay);
2080
+ }, delay2);
2079
2081
  if (options.signal) {
2080
2082
  if (options.signal.aborted) {
2081
2083
  abort();
@@ -2136,7 +2138,7 @@ To automatically clean-up native timers, use \`shouldClearNativeTimers\`.`
2136
2138
  methodName: "setInterval",
2137
2139
  original: timersPromisesModule.setInterval
2138
2140
  });
2139
- timersPromisesModule.setInterval = (delay, value, options = {}) => ({
2141
+ timersPromisesModule.setInterval = (delay2, value, options = {}) => ({
2140
2142
  [Symbol.asyncIterator]: () => {
2141
2143
  const createResolvable = () => {
2142
2144
  let resolve, reject;
@@ -2159,7 +2161,7 @@ To automatically clean-up native timers, use \`shouldClearNativeTimers\`.`
2159
2161
  } else {
2160
2162
  nextAvailable++;
2161
2163
  }
2162
- }, delay);
2164
+ }, delay2);
2163
2165
  const abort = () => {
2164
2166
  options.signal.removeEventListener(
2165
2167
  "abort",
@@ -2696,12 +2698,12 @@ function getImporter(name) {
2696
2698
  }) + 1])?.file || "";
2697
2699
  }
2698
2700
 
2699
- // ../../node_modules/.pnpm/vitest@4.0.14_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.14_jiti@2.6.1_3f103b75dedc0d5a18696ce035670cf5/node_modules/vitest/dist/index.js
2701
+ // ../../node_modules/.pnpm/vitest@4.0.15_@opentelemetry+api@1.9.0_@types+node@24.10.1_@vitest+ui@4.0.15_jiti@2.6.1_1316c7438d8db3eab2efa08f08502397/node_modules/vitest/dist/index.js
2700
2702
  import { expectTypeOf } from "expect-type.js";
2701
2703
  import { afterAll, afterEach, beforeAll, beforeEach, describe, it, onTestFailed, onTestFinished, recordArtifact, suite, test } from "@vitest/runner.js";
2702
2704
  import { chai as chai2 } from "@vitest/expect.js";
2703
- import "@vitest/runner/utils.js";
2704
2705
  import "@vitest/utils/timers.js";
2706
+ import "@vitest/runner/utils.js";
2705
2707
  import "@vitest/snapshot.js";
2706
2708
  import "@vitest/utils/error.js";
2707
2709
  import "@vitest/utils/helpers.js";