UNPKG

@assistant-ui/react

Version:

Open-source TypeScript/React library for building production-grade AI chat experiences

130 lines (129 loc) • 3.68 kB
"use client"; import { notifyEventListeners } from "@assistant-ui/core/internal"; import { create } from "zustand"; import { createThreadViewportAutoScroll } from "@assistant-ui/store/client"; // src/context/stores/ThreadViewport.ts const createSizeRegistry = (onChange) => { const entries = /* @__PURE__ */ new Map(); const recalculate = () => { let total = 0; for (const height of entries.values()) total += height; onChange(total); }; return { register: () => { const id = Symbol(); entries.set(id, 0); return { setHeight: (height) => { if (entries.get(id) !== height) { entries.set(id, height); recalculate(); } }, unregister: () => { entries.delete(id); recalculate(); } }; } }; }; const viewportAutoScroll = /* @__PURE__ */ new WeakMap(); const getThreadViewportAutoScroll = (store) => viewportAutoScroll.get(store); const makeThreadViewportStore = (options = {}) => { const scrollToBottomListeners = /* @__PURE__ */ new Set(); const viewportRegistry = createSizeRegistry((total) => { store.setState({ height: { ...store.getState().height, viewport: total } }); }); const autoScroll = { getOptions: () => ({ autoScroll: options.turnAnchor !== "top", scrollToBottomOnInitialize: true, scrollToBottomOnRunStart: options.turnAnchor !== "top", scrollToBottomOnThreadSwitch: true }), controller: createThreadViewportAutoScroll({ getOptions: () => autoScroll.getOptions(), onAtBottomChange: (isAtBottom) => store.setState({ isAtBottom }), onContentInsetChange: (inset) => { store.setState({ height: { ...store.getState().height, inset } }); } }) }; const registerElementSlot = (key, element) => { store.setState({ element: { ...store.getState().element, [key]: element } }); return () => { if (store.getState().element[key] !== element) return; store.setState({ element: { ...store.getState().element, [key]: null } }); }; }; const store = create(() => ({ isAtBottom: true, scrollToBottom: ({ behavior = "auto" } = {}) => { notifyEventListeners(scrollToBottomListeners, () => ({ behavior }), "Thread viewport"); }, onScrollToBottom: (callback) => { scrollToBottomListeners.add(callback); return () => { scrollToBottomListeners.delete(callback); }; }, turnAnchor: options.turnAnchor ?? "bottom", topAnchorMessageClamp: { tallerThan: options.topAnchorMessageClamp?.tallerThan ?? "10em", visibleHeight: options.topAnchorMessageClamp?.visibleHeight ?? "6em" }, height: { viewport: 0, inset: 0 }, element: { viewport: null, anchor: null, target: null }, targetConfig: null, topAnchorTurn: null, registerViewport: viewportRegistry.register, registerContentInset: autoScroll.controller.registerContentInset, registerViewportElement: (element) => registerElementSlot("viewport", element), registerAnchorElement: (element) => registerElementSlot("anchor", element), registerAnchorTargetElement: (element, config) => { store.setState({ element: { ...store.getState().element, target: element }, targetConfig: element && config ? config : null }); return () => { if (store.getState().element.target !== element) return; store.setState({ element: { ...store.getState().element, target: null }, targetConfig: null }); }; }, setTopAnchorTurn: (topAnchorTurn) => { store.setState({ topAnchorTurn }); } })); viewportAutoScroll.set(store, autoScroll); return store; }; export { getThreadViewportAutoScroll, makeThreadViewportStore }; //# sourceMappingURL=ThreadViewport.js.map