@assistant-ui/react
Version:
Open-source TypeScript/React library for building production-grade AI chat experiences
130 lines (129 loc) • 3.68 kB
JavaScript
"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