Refresh chat history after reconnect

This commit is contained in:
Federico Jaramillo Martinez
2026-05-11 21:33:36 +02:00
parent 1ccae8b1b7
commit f24094c0fa
3 changed files with 43 additions and 2 deletions
+8
View File
@@ -68,6 +68,10 @@ export class PiWebApp extends LitElement {
private terminalAutoStartWorkspaceId: string | undefined; private terminalAutoStartWorkspaceId: string | undefined;
private readonly plugins = createPluginRegistry(); private readonly plugins = createPluginRegistry();
private readonly onPopState = () => void this.withChatScrollTransition(() => this.restoreRoute(false)); private readonly onPopState = () => void this.withChatScrollTransition(() => this.restoreRoute(false));
private readonly onFocus = () => { void this.sessions.refreshSelectedSession(); };
private readonly onVisibilityChange = () => {
if (document.visibilityState === "visible") void this.sessions.refreshSelectedSession();
};
private readonly onKeyDown = (event: KeyboardEvent) => { private readonly onKeyDown = (event: KeyboardEvent) => {
if (this.keyboard.handle(event, this.getActions())) { if (this.keyboard.handle(event, this.getActions())) {
event.preventDefault(); event.preventDefault();
@@ -78,6 +82,8 @@ export class PiWebApp extends LitElement {
override connectedCallback(): void { override connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
window.addEventListener("popstate", this.onPopState); window.addEventListener("popstate", this.onPopState);
window.addEventListener("focus", this.onFocus);
document.addEventListener("visibilitychange", this.onVisibilityChange);
window.addEventListener("keydown", this.onKeyDown); window.addEventListener("keydown", this.onKeyDown);
this.connectRealtime(); this.connectRealtime();
void this.loadExternalPlugins(); void this.loadExternalPlugins();
@@ -86,6 +92,8 @@ export class PiWebApp extends LitElement {
override disconnectedCallback(): void { override disconnectedCallback(): void {
window.removeEventListener("popstate", this.onPopState); window.removeEventListener("popstate", this.onPopState);
window.removeEventListener("focus", this.onFocus);
document.removeEventListener("visibilitychange", this.onVisibilityChange);
window.removeEventListener("keydown", this.onKeyDown); window.removeEventListener("keydown", this.onKeyDown);
this.keyboard.reset(); this.keyboard.reset();
this.sessions.dispose(); this.sessions.dispose();
@@ -74,7 +74,11 @@ export class SessionController {
return; return;
} }
const buffered: SessionUiEvent[] = []; const buffered: SessionUiEvent[] = [];
this.socket.connect(session.id, (event) => buffered.push(event)); this.socket.connect(
session.id,
(event) => buffered.push(event),
() => { void this.refreshSelectedSession(session.id); },
);
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);
@@ -278,6 +282,28 @@ export class SessionController {
} }
} }
async refreshSelectedSession(sessionId = this.getState().selectedSession?.id): Promise<void> {
const session = this.getState().selectedSession;
if (sessionId === undefined || session?.id !== sessionId || session.archived === true) return;
try {
this.flushPendingTranscriptEvents();
const [page, status] = await Promise.all([api.messages(sessionId, { limit: MESSAGE_PAGE_SIZE }), api.status(sessionId)]);
if (this.getState().selectedSession?.id !== sessionId) return;
const history = this.mergeAndCacheHistory(sessionId, page);
this.setState({
messages: normalizeMessages(history.messages),
messagePageStart: history.start,
messagePageTotal: history.total,
status,
activity: this.getState().sessionActivities[sessionId],
isReceivingPartialStream: status.isStreaming,
});
this.applyStatus(status);
} catch (error) {
if (this.getState().selectedSession?.id === sessionId) this.setState({ error: String(error) });
}
}
private replaceSession(session: SessionInfo) { private replaceSession(session: SessionInfo) {
const current = this.getState().selectedSession; const current = this.getState().selectedSession;
this.setState({ this.setState({
+8 -1
View File
@@ -10,11 +10,14 @@ export class SessionSocket {
private reconnectTimer?: number; private reconnectTimer?: number;
private reconnectDelay = 500; private reconnectDelay = 500;
private shouldReconnect = false; private shouldReconnect = false;
private hasOpened = false;
private onReconnect: (() => void) | undefined;
connect(sessionId: string, onEvent: (event: SessionUiEvent) => void): void { connect(sessionId: string, onEvent: (event: SessionUiEvent) => void, onReconnect?: () => void): void {
this.close(); this.close();
this.sessionId = sessionId; this.sessionId = sessionId;
this.onEvent = onEvent; this.onEvent = onEvent;
this.onReconnect = onReconnect;
this.shouldReconnect = true; this.shouldReconnect = true;
this.open(); this.open();
} }
@@ -30,6 +33,8 @@ export class SessionSocket {
this.socket = undefined; this.socket = undefined;
this.sessionId = undefined; this.sessionId = undefined;
this.onEvent = undefined; this.onEvent = undefined;
this.onReconnect = undefined;
this.hasOpened = false;
} }
private open(): void { private open(): void {
@@ -38,6 +43,8 @@ export class SessionSocket {
this.socket = socket; this.socket = socket;
socket.onopen = () => { socket.onopen = () => {
this.reconnectDelay = 500; this.reconnectDelay = 500;
if (this.hasOpened) this.onReconnect?.();
this.hasOpened = true;
}; };
socket.onmessage = (message) => void this.handleMessage(message.data); socket.onmessage = (message) => void this.handleMessage(message.data);
socket.onerror = () => { socket.close(); }; socket.onerror = () => { socket.close(); };