diff --git a/.changeset/fix-chat-history-range.md b/.changeset/fix-chat-history-range.md new file mode 100644 index 0000000..afeac10 --- /dev/null +++ b/.changeset/fix-chat-history-range.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Correct the chat history range label when normalized display messages are fewer than the raw session transcript entries. diff --git a/src/client/src/appState.ts b/src/client/src/appState.ts index 409bf73..100bfd3 100644 --- a/src/client/src/appState.ts +++ b/src/client/src/appState.ts @@ -8,6 +8,7 @@ export interface AppState { sessions: SessionInfo[]; messages: ChatLine[]; messagePageStart: number; + messagePageEnd: number; messagePageTotal: number; isLoadingEarlierMessages: boolean; isReceivingPartialStream: boolean; @@ -96,6 +97,7 @@ export function initialAppState(): AppState { sessions: [], messages: [], messagePageStart: 0, + messagePageEnd: 0, messagePageTotal: 0, isLoadingEarlierMessages: false, isReceivingPartialStream: false, diff --git a/src/client/src/chatTranscriptStore.test.ts b/src/client/src/chatTranscriptStore.test.ts index fcf5045..0bc5eb6 100644 --- a/src/client/src/chatTranscriptStore.test.ts +++ b/src/client/src/chatTranscriptStore.test.ts @@ -29,10 +29,26 @@ describe("ChatTranscriptStore", () => { { role: "assistant", parts: [{ type: "text", text: "hello" }] }, ], messagePageStart: 0, + messagePageEnd: 2, messagePageTotal: 2, }); }); + it("tracks the raw page end separately from normalized display messages", () => { + const store = new ChatTranscriptStore(new MemoryChatHistoryCache()); + + const view = store.mergeHistory("s1", page(0, 3, [ + { role: "user", content: "run the tool" }, + { role: "assistant", content: [{ type: "toolCall", id: "tool-1", name: "read", arguments: { path: "src/app.ts" } }] }, + { role: "toolResult", toolCallId: "tool-1", toolName: "read", content: [{ type: "text", text: "ok" }] }, + ])); + + expect(view.messages).toHaveLength(2); + expect(view.messagePageStart).toBe(0); + expect(view.messagePageEnd).toBe(3); + expect(view.messagePageTotal).toBe(3); + }); + it("keeps live streamed transcript state out of the raw history cache", () => { const cache = new MemoryChatHistoryCache(); const store = new ChatTranscriptStore(cache); @@ -51,6 +67,7 @@ describe("ChatTranscriptStore", () => { { role: "user", parts: [{ type: "text", text: "next" }] }, ], messagePageStart: 0, + messagePageEnd: 3, messagePageTotal: 3, }); }); diff --git a/src/client/src/chatTranscriptStore.ts b/src/client/src/chatTranscriptStore.ts index 1d696c4..cd8eb95 100644 --- a/src/client/src/chatTranscriptStore.ts +++ b/src/client/src/chatTranscriptStore.ts @@ -7,6 +7,9 @@ import type { SessionUiEvent } from "./sessionSocket"; export interface ChatTranscriptView { messages: ChatLine[]; messagePageStart: number; + // End offset in the raw transcript. Normalization may coalesce multiple raw + // entries into one displayed chat message, especially tool calls/results. + messagePageEnd: number; messagePageTotal: number; } @@ -48,9 +51,11 @@ export class ChatTranscriptStore { } export function transcriptViewFromHistory(history: RawMessagePage | undefined): ChatTranscriptView { + const start = history?.start ?? 0; return { messages: normalizeMessages(history?.messages ?? []), - messagePageStart: history?.start ?? 0, + messagePageStart: start, + messagePageEnd: start + (history?.messages.length ?? 0), messagePageTotal: history?.total ?? 0, }; } diff --git a/src/client/src/components/ChatView.ts b/src/client/src/components/ChatView.ts index 8b8d994..869e5a3 100644 --- a/src/client/src/components/ChatView.ts +++ b/src/client/src/components/ChatView.ts @@ -43,6 +43,7 @@ export class ChatView extends LitElement { @property({ attribute: false }) messages: ChatLine[] = []; @property() sessionId = ""; @property({ type: Number }) messageStart = 0; + @property({ type: Number }) messageEnd = 0; @property({ type: Number }) messageTotal = 0; @property({ type: Boolean }) hasMore = false; @property({ type: Boolean }) loadingMore = false; @@ -307,8 +308,13 @@ export class ChatView extends LitElement { private historyRangeLabel() { if (!this.messages.length || this.messageTotal <= 0) return null; const from = this.messageStart + 1; - const to = this.messageStart + this.messages.length; - return html`Showing messages ${from}–${to} of ${this.messageTotal}`; + const to = this.loadedRawMessageEnd(); + const total = Math.max(this.messageTotal, to); + return html`Showing messages ${from}–${to} of ${total}`; + } + + private loadedRawMessageEnd(): number { + return Math.max(this.messageEnd, this.messageStart + this.messages.length); } private renderMessage(message: ChatLine, index: number) { diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index 14706c6..92329dd 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -1334,7 +1334,7 @@ export class PiWebApp extends LitElement { ${state.error ? html`