Archived
Stabilize chat history pagination
This commit is contained in:
@@ -31,6 +31,10 @@ describe("chat history loading decisions", () => {
|
|||||||
expect(shouldRequestEarlierMessages({ ...base, canRequest: false, scrollTop: 0 })).toBe(false);
|
expect(shouldRequestEarlierMessages({ ...base, canRequest: false, scrollTop: 0 })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not request while the scroll container is hidden", () => {
|
||||||
|
expect(shouldRequestEarlierMessages({ ...base, scrollTop: 0, scrollHeight: 0, clientHeight: 0 })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("uses a small tolerance for underfilled viewports", () => {
|
it("uses a small tolerance for underfilled viewports", () => {
|
||||||
expect(doesNotFillViewport({ scrollHeight: 501, clientHeight: 500 })).toBe(true);
|
expect(doesNotFillViewport({ scrollHeight: 501, clientHeight: 500 })).toBe(true);
|
||||||
expect(doesNotFillViewport({ scrollHeight: 502, clientHeight: 500 })).toBe(false);
|
expect(doesNotFillViewport({ scrollHeight: 502, clientHeight: 500 })).toBe(false);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const DEFAULT_TOP_THRESHOLD = 64;
|
|||||||
const VIEWPORT_FILL_TOLERANCE = 1;
|
const VIEWPORT_FILL_TOLERANCE = 1;
|
||||||
|
|
||||||
export function shouldRequestEarlierMessages(state: ChatHistoryLoadState): boolean {
|
export function shouldRequestEarlierMessages(state: ChatHistoryLoadState): boolean {
|
||||||
if (!state.hasMore || state.loadingMore || !state.canRequest) return false;
|
if (!state.hasMore || state.loadingMore || !state.canRequest || state.clientHeight <= 0) return false;
|
||||||
return isNearTop(state) || doesNotFillViewport(state);
|
return isNearTop(state) || doesNotFillViewport(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class SessionController {
|
|||||||
if (session.archived === true) {
|
if (session.archived === true) {
|
||||||
const page = await api.messages(session.id, { limit: MESSAGE_PAGE_SIZE });
|
const page = await api.messages(session.id, { limit: MESSAGE_PAGE_SIZE });
|
||||||
if (seq !== this.selectionSeq || this.getState().selectedSession?.id !== session.id) return;
|
if (seq !== this.selectionSeq || this.getState().selectedSession?.id !== session.id) return;
|
||||||
const history = this.mergeAndCacheHistory(session.id, page);
|
const history = this.mergeAndCacheHistory(session.id, page, this.currentHistoryPage());
|
||||||
this.setState({ messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total, isLoadingEarlierMessages: false, isReceivingPartialStream: false, status: undefined, activity: undefined });
|
this.setState({ messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total, isLoadingEarlierMessages: false, isReceivingPartialStream: false, status: undefined, activity: undefined });
|
||||||
if (options?.updateUrl !== false) this.updateUrl();
|
if (options?.updateUrl !== false) this.updateUrl();
|
||||||
return;
|
return;
|
||||||
@@ -81,7 +81,7 @@ export class SessionController {
|
|||||||
);
|
);
|
||||||
const [page, status] = await Promise.all([api.messages(session.id, { limit: MESSAGE_PAGE_SIZE }), api.status(session.id)]);
|
const [page, status] = await Promise.all([api.messages(session.id, { limit: MESSAGE_PAGE_SIZE }), api.status(session.id)]);
|
||||||
if (seq !== this.selectionSeq || this.getState().selectedSession?.id !== session.id) return;
|
if (seq !== this.selectionSeq || this.getState().selectedSession?.id !== session.id) return;
|
||||||
const history = this.mergeAndCacheHistory(session.id, page);
|
const history = this.mergeAndCacheHistory(session.id, page, this.currentHistoryPage());
|
||||||
const isReceivingPartialStream = status.isStreaming;
|
const isReceivingPartialStream = status.isStreaming;
|
||||||
this.catchupStreamSessionId = isReceivingPartialStream ? session.id : undefined;
|
this.catchupStreamSessionId = isReceivingPartialStream ? session.id : undefined;
|
||||||
this.setState({ messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total, isLoadingEarlierMessages: false, isReceivingPartialStream, status, activity: this.getState().sessionActivities[session.id] });
|
this.setState({ messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total, isLoadingEarlierMessages: false, isReceivingPartialStream, status, activity: this.getState().sessionActivities[session.id] });
|
||||||
@@ -100,9 +100,10 @@ export class SessionController {
|
|||||||
if (!session || state.isLoadingEarlierMessages || state.messagePageStart <= 0) return;
|
if (!session || state.isLoadingEarlierMessages || state.messagePageStart <= 0) return;
|
||||||
this.setState({ isLoadingEarlierMessages: true });
|
this.setState({ isLoadingEarlierMessages: true });
|
||||||
try {
|
try {
|
||||||
|
const base = this.currentHistoryPage();
|
||||||
const page = await api.messages(session.id, { before: state.messagePageStart, limit: MESSAGE_PAGE_SIZE });
|
const page = await api.messages(session.id, { before: state.messagePageStart, limit: MESSAGE_PAGE_SIZE });
|
||||||
if (this.getState().selectedSession?.id !== session.id) return;
|
if (this.getState().selectedSession?.id !== session.id) return;
|
||||||
const history = this.mergeAndCacheHistory(session.id, page);
|
const history = this.mergeAndCacheHistory(session.id, page, base);
|
||||||
this.setState({
|
this.setState({
|
||||||
messages: normalizeMessages(history.messages),
|
messages: normalizeMessages(history.messages),
|
||||||
messagePageStart: history.start,
|
messagePageStart: history.start,
|
||||||
@@ -287,9 +288,10 @@ export class SessionController {
|
|||||||
if (sessionId === undefined || session?.id !== sessionId || session.archived === true) return;
|
if (sessionId === undefined || session?.id !== sessionId || session.archived === true) return;
|
||||||
try {
|
try {
|
||||||
this.flushPendingTranscriptEvents();
|
this.flushPendingTranscriptEvents();
|
||||||
|
const base = this.currentHistoryPage();
|
||||||
const [page, status] = await Promise.all([api.messages(sessionId, { limit: MESSAGE_PAGE_SIZE }), api.status(sessionId)]);
|
const [page, status] = await Promise.all([api.messages(sessionId, { limit: MESSAGE_PAGE_SIZE }), api.status(sessionId)]);
|
||||||
if (this.getState().selectedSession?.id !== sessionId) return;
|
if (this.getState().selectedSession?.id !== sessionId) return;
|
||||||
const history = this.mergeAndCacheHistory(sessionId, page);
|
const history = this.mergeAndCacheHistory(sessionId, page, base);
|
||||||
this.setState({
|
this.setState({
|
||||||
messages: normalizeMessages(history.messages),
|
messages: normalizeMessages(history.messages),
|
||||||
messagePageStart: history.start,
|
messagePageStart: history.start,
|
||||||
@@ -312,12 +314,18 @@ export class SessionController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private mergeAndCacheHistory(sessionId: string, page: RawMessagePage): RawMessagePage {
|
private mergeAndCacheHistory(sessionId: string, page: RawMessagePage, base = readChatHistoryCache(sessionId)): RawMessagePage {
|
||||||
const history = mergeChatHistory(readChatHistoryCache(sessionId), page);
|
const history = mergeChatHistory(base, page);
|
||||||
writeChatHistoryCache(sessionId, history);
|
writeChatHistoryCache(sessionId, history);
|
||||||
return history;
|
return history;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private currentHistoryPage(): RawMessagePage | undefined {
|
||||||
|
const state = this.getState();
|
||||||
|
if (state.messages.length === 0 && state.messagePageTotal === 0) return undefined;
|
||||||
|
return { messages: state.messages, start: state.messagePageStart, total: state.messagePageTotal };
|
||||||
|
}
|
||||||
|
|
||||||
private applyCommandResult(result: CommandResult) {
|
private applyCommandResult(result: CommandResult) {
|
||||||
if (result.type === "select") {
|
if (result.type === "select") {
|
||||||
this.setState({ commandDialog: result });
|
this.setState({ commandDialog: result });
|
||||||
@@ -425,9 +433,10 @@ export class SessionController {
|
|||||||
|
|
||||||
private async refreshMessages(sessionId: string) {
|
private async refreshMessages(sessionId: string) {
|
||||||
try {
|
try {
|
||||||
|
const base = this.currentHistoryPage();
|
||||||
const page = await api.messages(sessionId, { limit: MESSAGE_PAGE_SIZE });
|
const page = await api.messages(sessionId, { limit: MESSAGE_PAGE_SIZE });
|
||||||
if (this.getState().selectedSession?.id !== sessionId) return;
|
if (this.getState().selectedSession?.id !== sessionId) return;
|
||||||
const history = this.mergeAndCacheHistory(sessionId, page);
|
const history = this.mergeAndCacheHistory(sessionId, page, base);
|
||||||
this.setState({ messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total });
|
this.setState({ messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.getState().selectedSession?.id === sessionId) this.setState({ error: String(error) });
|
if (this.getState().selectedSession?.id === sessionId) this.setState({ error: String(error) });
|
||||||
|
|||||||
Reference in New Issue
Block a user