fix(web): 无用户行钉顶、闲置不上留白,并修会话列表 tsc 两错
This commit is contained in:
@@ -76,9 +76,26 @@ export function applyFollowBottom(
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
|
||||
export function lastTurnTail(container: HTMLElement): HTMLElement | null {
|
||||
return (container.querySelector(".message-list > :last-child .message-assistant")
|
||||
?? container.querySelector(".message-assistant:last-of-type")
|
||||
?? container.querySelector(".message-user:last-of-type")) as HTMLElement | null;
|
||||
}
|
||||
|
||||
export function turnHeadHeightForSpacer(head: HTMLElement | null, tail: HTMLElement | null): number {
|
||||
if (!head) return 0;
|
||||
// The last assistant carries the min-height spacer. Measuring it as the turn
|
||||
// head feeds the spacer back into itself (BUG-931).
|
||||
if (tail && head === tail) return 0;
|
||||
return head.offsetHeight;
|
||||
}
|
||||
|
||||
export function applyTurnSpacer(container: HTMLElement, head: HTMLElement | null): void {
|
||||
container.style.setProperty("--conversation-viewport", `${container.clientHeight}px`);
|
||||
container.style.setProperty("--latest-turn-head-height", `${head?.offsetHeight ?? 0}px`);
|
||||
container.style.setProperty(
|
||||
"--latest-turn-head-height",
|
||||
`${turnHeadHeightForSpacer(head, lastTurnTail(container))}px`,
|
||||
);
|
||||
}
|
||||
|
||||
export function clearTurnSpacer(container: HTMLElement): void {
|
||||
@@ -86,12 +103,6 @@ export function clearTurnSpacer(container: HTMLElement): void {
|
||||
container.style.removeProperty("--latest-turn-head-height");
|
||||
}
|
||||
|
||||
export function lastTurnTail(container: HTMLElement): HTMLElement | null {
|
||||
return (container.querySelector(".message-list > :last-child .message-assistant")
|
||||
?? container.querySelector(".message-assistant:last-of-type")
|
||||
?? container.querySelector(".message-user:last-of-type")) as HTMLElement | null;
|
||||
}
|
||||
|
||||
export function turnTailOverflow(container: HTMLElement): number {
|
||||
const last = lastTurnTail(container);
|
||||
if (!last) return conversationDistanceFromBottom(container);
|
||||
@@ -198,8 +209,8 @@ export function useConversationScrollAnchor(
|
||||
}
|
||||
if (anchoredRef.current) {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
} else {
|
||||
applyTurnSpacer(element, pinnedHeadRef.current ?? resolveTurnHead(element));
|
||||
} else if (pinnedHeadRef.current) {
|
||||
applyTurnSpacer(element, pinnedHeadRef.current);
|
||||
}
|
||||
setLatestBelowFold(latestContentBelowFold(turnTailOverflow(element)));
|
||||
};
|
||||
@@ -240,7 +251,9 @@ export function useConversationScrollAnchor(
|
||||
const element = container.current;
|
||||
holdUnpin.current = false;
|
||||
shouldLand.current = false;
|
||||
pinnedHeadRef.current = null;
|
||||
if (element) {
|
||||
clearTurnSpacer(element);
|
||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
element.scrollTo({ top: element.scrollHeight, behavior: reduceMotion ? "auto" : "smooth" });
|
||||
setLatestBelowFold(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ export function toSidebarSessionRow(
|
||||
): SidebarSession {
|
||||
return {
|
||||
id: session.id,
|
||||
title: sessionSidebarTitle(session, library),
|
||||
title: sessionSidebarTitle(session),
|
||||
subtitle: sessionSidebarSubtitle(session, library) || null,
|
||||
pinned: session.pinned,
|
||||
archived: Boolean(session.archivedAt),
|
||||
|
||||
@@ -86,7 +86,10 @@ test("sidebar titles drop the chart-name prefix and only subtitle others", () =>
|
||||
archivedAt: null,
|
||||
messagesHydrated: true,
|
||||
};
|
||||
assert.equal(sessionSidebarTitle(base, library), "半年内换工作时机");
|
||||
// 原值: sessionSidebarTitle(base, library)
|
||||
// 新值: sessionSidebarTitle(base)
|
||||
// 原因: sessionSidebarTitle 只吃 session;第二参是 e4e73f56 的 TS 错,挡住 tsc / next build
|
||||
assert.equal(sessionSidebarTitle(base), "半年内换工作时机");
|
||||
assert.equal(sessionSidebarSubtitle(base, library), "9月14日 12:14");
|
||||
const other = { ...base, chartProfileId: "other-1", chartProfileName: "对方", chartProfileRole: "other" as const };
|
||||
assert.equal(sessionSidebarSubtitle(other, library), "9月14日 12:14 · 资料已删除 · 对方");
|
||||
|
||||
@@ -235,6 +235,49 @@ test("turn spacer uses conversation viewport variables and is not sticky", () =>
|
||||
assert.equal(scroller.style.getPropertyValue("--latest-turn-head-height"), "");
|
||||
});
|
||||
|
||||
test("a turn with no user row pins the new assistant and uses a full-viewport spacer", () => {
|
||||
const priorUser = fakeRow("message-user", 8, 40);
|
||||
const priorAssistant = fakeRow("message-assistant", 56, 80);
|
||||
const laterUser = fakeRow("message-user", 152, 40);
|
||||
const laterAssistant = fakeRow("message-assistant", 200, 80);
|
||||
const incoming = fakeRow("message-assistant", 480, 80);
|
||||
const scroller = fakeScroller([priorUser, priorAssistant, laterUser, laterAssistant, incoming], 600);
|
||||
const head = resolveTurnHead(asElement(scroller));
|
||||
assert.equal(head, asElement(incoming));
|
||||
applyTurnSpacer(asElement(scroller), head);
|
||||
assert.equal(scroller.style.getPropertyValue("--latest-turn-head-height"), "0px");
|
||||
scroller.scrollTop = pinTurnScrollTop(asElement(scroller), asElement(incoming), 16);
|
||||
assert.equal(scroller.scrollTop, incoming.offsetTop - 16);
|
||||
const start = scroller.scrollTop;
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
incoming.offsetHeight += 80;
|
||||
scroller.scrollHeight += 80;
|
||||
applyFollowBottom(scroller, false);
|
||||
}
|
||||
assert.equal(scroller.scrollTop, start);
|
||||
});
|
||||
|
||||
test("idle history does not write a spacer when the reader has only scrolled up", () => {
|
||||
// 原值: follow() 在 !anchored 时无条件 applyTurnSpacer
|
||||
// 新值: 只在 pinnedHeadRef 非空时写留白
|
||||
// 原因: BUG-932 闲置会话上滑后子元素尺寸变化不应把最后一条助手行撑开
|
||||
assert.match(
|
||||
anchorSource,
|
||||
/if \(anchoredRef\.current\) \{\s*element\.scrollTop = element\.scrollHeight;\s*\} else if \(pinnedHeadRef\.current\) \{\s*applyTurnSpacer\(element, pinnedHeadRef\.current\);/,
|
||||
);
|
||||
assert.match(anchorSource, /pinnedHeadRef\.current = null;\s*if \(element\) \{\s*clearTurnSpacer\(element\);/);
|
||||
const user = fakeRow("message-user", 8, 40);
|
||||
const assistant = fakeRow("message-assistant", 56, 900);
|
||||
const scroller = fakeScroller([user, assistant], 600);
|
||||
scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
|
||||
scroller.scrollTop = Math.max(0, scroller.scrollTop - 400);
|
||||
const start = scroller.scrollTop;
|
||||
assistant.offsetHeight += 30;
|
||||
scroller.scrollHeight += 30;
|
||||
assert.equal(scroller.style.getPropertyValue("--conversation-viewport"), "");
|
||||
assert.equal(Math.abs(scroller.scrollTop - start) <= 1, true);
|
||||
});
|
||||
|
||||
test("consultation send and rectification new turns call pinLatestTurn", () => {
|
||||
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
|
||||
const pins = chat.match(/conversationAnchor\.pinLatestTurn\(\)/g) ?? [];
|
||||
|
||||
Reference in New Issue
Block a user