mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 16:21:50 +03:00
fix(webui): preserve semantic history anchors
This commit is contained in:
@@ -136,6 +136,7 @@ export function ThreadMessages({
|
|||||||
return (
|
return (
|
||||||
<ThreadDisplayUnit
|
<ThreadDisplayUnit
|
||||||
key={unitKeys[index]}
|
key={unitKeys[index]}
|
||||||
|
unitKey={unitKeys[index]}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
marginTop={marginTop}
|
marginTop={marginTop}
|
||||||
userPromptId={userPromptId}
|
userPromptId={userPromptId}
|
||||||
@@ -225,6 +226,7 @@ function pendingTurnProjection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ThreadDisplayUnitProps {
|
interface ThreadDisplayUnitProps {
|
||||||
|
unitKey: string;
|
||||||
unit: DisplayUnit;
|
unit: DisplayUnit;
|
||||||
marginTop: string;
|
marginTop: string;
|
||||||
userPromptId?: string;
|
userPromptId?: string;
|
||||||
@@ -243,6 +245,7 @@ interface ThreadDisplayUnitProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ThreadDisplayUnit = memo(function ThreadDisplayUnit({
|
const ThreadDisplayUnit = memo(function ThreadDisplayUnit({
|
||||||
|
unitKey,
|
||||||
unit,
|
unit,
|
||||||
marginTop,
|
marginTop,
|
||||||
userPromptId,
|
userPromptId,
|
||||||
@@ -273,7 +276,7 @@ const ThreadDisplayUnit = memo(function ThreadDisplayUnit({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`${marginTop}${stableDeferOffscreenRender ? " thread-render-unit" : ""}`}
|
className={`${marginTop}${stableDeferOffscreenRender ? " thread-render-unit" : ""}`}
|
||||||
data-thread-display-unit
|
data-thread-display-unit={unitKey}
|
||||||
data-user-prompt-id={userPromptId}
|
data-user-prompt-id={userPromptId}
|
||||||
>
|
>
|
||||||
{unit.type === "activity" ? (
|
{unit.type === "activity" ? (
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const INITIAL_HISTORY_WINDOW = 160;
|
|||||||
export const HISTORY_WINDOW_INCREMENT = 120;
|
export const HISTORY_WINDOW_INCREMENT = 120;
|
||||||
|
|
||||||
interface HistoryScrollAnchor {
|
interface HistoryScrollAnchor {
|
||||||
element: HTMLElement;
|
key: string;
|
||||||
offsetTop: number;
|
offsetTop: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,8 +316,10 @@ export const ThreadViewport = forwardRef<ThreadViewportHandle, ThreadViewportPro
|
|||||||
for (const element of units) {
|
for (const element of units) {
|
||||||
const bounds = element.getBoundingClientRect();
|
const bounds = element.getBoundingClientRect();
|
||||||
if (bounds.bottom <= scrollerTop + 0.5) continue;
|
if (bounds.bottom <= scrollerTop + 0.5) continue;
|
||||||
|
const key = element.dataset.threadDisplayUnit;
|
||||||
|
if (!key) continue;
|
||||||
historyScrollAnchorRef.current = {
|
historyScrollAnchorRef.current = {
|
||||||
element,
|
key,
|
||||||
offsetTop: bounds.top - scrollerTop,
|
offsetTop: bounds.top - scrollerTop,
|
||||||
};
|
};
|
||||||
return true;
|
return true;
|
||||||
@@ -328,15 +330,19 @@ export const ThreadViewport = forwardRef<ThreadViewportHandle, ThreadViewportPro
|
|||||||
|
|
||||||
const reconcileHistoryScrollAnchor = useCallback(() => {
|
const reconcileHistoryScrollAnchor = useCallback(() => {
|
||||||
const scroller = scrollRef.current;
|
const scroller = scrollRef.current;
|
||||||
|
const content = messageContentRef.current;
|
||||||
const anchor = historyScrollAnchorRef.current;
|
const anchor = historyScrollAnchorRef.current;
|
||||||
if (!scroller || !anchor) return false;
|
if (!scroller || !content || !anchor) return false;
|
||||||
if (!anchor.element.isConnected) {
|
const element = Array.from(
|
||||||
|
content.querySelectorAll<HTMLElement>("[data-thread-display-unit]"),
|
||||||
|
).find((candidate) => candidate.dataset.threadDisplayUnit === anchor.key);
|
||||||
|
if (!element) {
|
||||||
historyScrollAnchorRef.current = null;
|
historyScrollAnchorRef.current = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextOffset =
|
const nextOffset =
|
||||||
anchor.element.getBoundingClientRect().top
|
element.getBoundingClientRect().top
|
||||||
- scroller.getBoundingClientRect().top;
|
- scroller.getBoundingClientRect().top;
|
||||||
const delta = nextOffset - anchor.offsetTop;
|
const delta = nextOffset - anchor.offsetTop;
|
||||||
if (Math.abs(delta) < 0.5) return true;
|
if (Math.abs(delta) < 0.5) return true;
|
||||||
@@ -344,7 +350,7 @@ export const ThreadViewport = forwardRef<ThreadViewportHandle, ThreadViewportPro
|
|||||||
const nextTop = Math.min(maxScrollTop, Math.max(0, scroller.scrollTop + delta));
|
const nextTop = Math.min(maxScrollTop, Math.max(0, scroller.scrollTop + delta));
|
||||||
threadMotionRef.current?.jumpTo(nextTop);
|
threadMotionRef.current?.jumpTo(nextTop);
|
||||||
return true;
|
return true;
|
||||||
}, [captureHistoryScrollAnchor]);
|
}, []);
|
||||||
|
|
||||||
const scrollToBottomNow = useCallback((smooth = false) => {
|
const scrollToBottomNow = useCallback((smooth = false) => {
|
||||||
historyScrollAnchorRef.current = null;
|
historyScrollAnchorRef.current = null;
|
||||||
|
|||||||
@@ -1521,8 +1521,18 @@ describe("ThreadViewport", () => {
|
|||||||
dispatchUserScroll(scroller);
|
dispatchUserScroll(scroller);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const replacement = anchor.cloneNode(true) as HTMLElement;
|
||||||
|
anchor.replaceWith(replacement);
|
||||||
anchorDocumentTop += 180;
|
anchorDocumentTop += 180;
|
||||||
scrollHeight += 180;
|
scrollHeight += 180;
|
||||||
|
Object.defineProperty(replacement, "getBoundingClientRect", {
|
||||||
|
configurable: true,
|
||||||
|
value: () => DOMRect.fromRect({
|
||||||
|
y: anchorDocumentTop - scroller.scrollTop,
|
||||||
|
width: 800,
|
||||||
|
height: 40,
|
||||||
|
}),
|
||||||
|
});
|
||||||
const content = screen.getByTestId("thread-message-region").firstElementChild;
|
const content = screen.getByTestId("thread-message-region").firstElementChild;
|
||||||
const observer = resizeObserver.observers.find((candidate) =>
|
const observer = resizeObserver.observers.find((candidate) =>
|
||||||
content ? candidate.elements.includes(content) : false,
|
content ? candidate.elements.includes(content) : false,
|
||||||
@@ -1533,7 +1543,7 @@ describe("ThreadViewport", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(scroller.scrollTop).toBe(260);
|
expect(scroller.scrollTop).toBe(260);
|
||||||
expect(anchor.getBoundingClientRect().top).toBe(120);
|
expect(replacement.getBoundingClientRect().top).toBe(120);
|
||||||
} finally {
|
} finally {
|
||||||
resizeObserver.restore();
|
resizeObserver.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user