Archived
Cache loaded chat history in browser
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
const CACHE_PREFIX = "pi-web:chat-history:";
|
||||
const CACHE_TTL_MS = 30 * 60 * 1000;
|
||||
|
||||
export interface RawMessagePage {
|
||||
messages: unknown[];
|
||||
start: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface CachedChatHistory extends RawMessagePage {
|
||||
savedAt: number;
|
||||
}
|
||||
|
||||
export function readChatHistoryCache(sessionId: string): RawMessagePage | undefined {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(cacheKey(sessionId));
|
||||
if (raw === null || raw === "") return undefined;
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
if (!isCachedHistory(parsed)) return undefined;
|
||||
if (Date.now() - parsed.savedAt > CACHE_TTL_MS) {
|
||||
sessionStorage.removeItem(cacheKey(sessionId));
|
||||
return undefined;
|
||||
}
|
||||
return { messages: parsed.messages, start: parsed.start, total: parsed.total };
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeChatHistoryCache(sessionId: string, page: RawMessagePage): void {
|
||||
try {
|
||||
sessionStorage.setItem(cacheKey(sessionId), JSON.stringify({ ...page, savedAt: Date.now() }));
|
||||
} catch {
|
||||
// Ignore quota/private-mode failures; history paging still works without cache.
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeChatHistory(existing: RawMessagePage | undefined, incoming: RawMessagePage): RawMessagePage {
|
||||
if (!existing || existing.total !== incoming.total) return incoming;
|
||||
|
||||
const start = Math.min(existing.start, incoming.start);
|
||||
const end = Math.max(existing.start + existing.messages.length, incoming.start + incoming.messages.length);
|
||||
const messages = new Array<unknown>(end - start);
|
||||
copyInto(messages, start, existing);
|
||||
copyInto(messages, start, incoming);
|
||||
|
||||
return { start, total: incoming.total, messages: messages.filter((message) => message !== undefined) };
|
||||
}
|
||||
|
||||
function copyInto(target: unknown[], targetStart: number, page: RawMessagePage): void {
|
||||
page.messages.forEach((message, index) => {
|
||||
target[page.start - targetStart + index] = message;
|
||||
});
|
||||
}
|
||||
|
||||
function cacheKey(sessionId: string): string {
|
||||
return `${CACHE_PREFIX}${sessionId}`;
|
||||
}
|
||||
|
||||
function isCachedHistory(value: unknown): value is CachedChatHistory {
|
||||
return typeof value === "object"
|
||||
&& value !== null
|
||||
&& "messages" in value
|
||||
&& "start" in value
|
||||
&& "total" in value
|
||||
&& "savedAt" in value
|
||||
&& Array.isArray(value.messages)
|
||||
&& typeof value.start === "number"
|
||||
&& typeof value.total === "number"
|
||||
&& typeof value.savedAt === "number";
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { api, type CommandResult, type SessionActivity, type SessionInfo, type S
|
||||
|
||||
const MESSAGE_PAGE_SIZE = 100;
|
||||
import { normalizeMessages, textMessage } from "../chatMessages";
|
||||
import { readChatHistoryCache, mergeChatHistory, writeChatHistoryCache, type RawMessagePage } from "../chatHistoryCache";
|
||||
import { applyTranscriptEvent } from "../chatTranscript";
|
||||
import { isShellInput } from "../inputModes";
|
||||
import { GlobalSessionSocket, SessionSocket, type SessionUiEvent } from "../sessionSocket";
|
||||
@@ -48,7 +49,8 @@ export class SessionController {
|
||||
const buffered: SessionUiEvent[] = [];
|
||||
this.socket.connect(session.id, (event) => buffered.push(event));
|
||||
const [page, status] = await Promise.all([api.messages(session.id, { limit: MESSAGE_PAGE_SIZE }), api.status(session.id)]);
|
||||
this.setState({ selectedSession: session, messages: normalizeMessages(page.messages), messagePageStart: page.start, messagePageTotal: page.total, isLoadingEarlierMessages: false, status });
|
||||
const history = this.mergeAndCacheHistory(session.id, page);
|
||||
this.setState({ selectedSession: session, messages: normalizeMessages(history.messages), messagePageStart: history.start, messagePageTotal: history.total, isLoadingEarlierMessages: false, status });
|
||||
this.applyStatus(status);
|
||||
for (const event of buffered) this.applyEvent(event);
|
||||
this.socket.setHandler((event) => { this.applyEvent(event); });
|
||||
@@ -66,10 +68,11 @@ export class SessionController {
|
||||
try {
|
||||
const page = await api.messages(session.id, { before: state.messagePageStart, limit: MESSAGE_PAGE_SIZE });
|
||||
if (this.getState().selectedSession?.id !== session.id) return;
|
||||
const history = this.mergeAndCacheHistory(session.id, page);
|
||||
this.setState({
|
||||
messages: [...normalizeMessages(page.messages), ...this.getState().messages],
|
||||
messagePageStart: page.start,
|
||||
messagePageTotal: page.total,
|
||||
messages: normalizeMessages(history.messages),
|
||||
messagePageStart: history.start,
|
||||
messagePageTotal: history.total,
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({ error: String(error) });
|
||||
@@ -142,6 +145,12 @@ export class SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
private mergeAndCacheHistory(sessionId: string, page: RawMessagePage): RawMessagePage {
|
||||
const history = mergeChatHistory(readChatHistoryCache(sessionId), page);
|
||||
writeChatHistoryCache(sessionId, history);
|
||||
return history;
|
||||
}
|
||||
|
||||
private applyCommandResult(result: CommandResult) {
|
||||
if (result.type === "select") {
|
||||
this.setState({ commandDialog: result });
|
||||
|
||||
Reference in New Issue
Block a user