Archived
fix: show raw chat history range
This commit is contained in:
@@ -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.
|
||||||
@@ -8,6 +8,7 @@ export interface AppState {
|
|||||||
sessions: SessionInfo[];
|
sessions: SessionInfo[];
|
||||||
messages: ChatLine[];
|
messages: ChatLine[];
|
||||||
messagePageStart: number;
|
messagePageStart: number;
|
||||||
|
messagePageEnd: number;
|
||||||
messagePageTotal: number;
|
messagePageTotal: number;
|
||||||
isLoadingEarlierMessages: boolean;
|
isLoadingEarlierMessages: boolean;
|
||||||
isReceivingPartialStream: boolean;
|
isReceivingPartialStream: boolean;
|
||||||
@@ -96,6 +97,7 @@ export function initialAppState(): AppState {
|
|||||||
sessions: [],
|
sessions: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
messagePageStart: 0,
|
messagePageStart: 0,
|
||||||
|
messagePageEnd: 0,
|
||||||
messagePageTotal: 0,
|
messagePageTotal: 0,
|
||||||
isLoadingEarlierMessages: false,
|
isLoadingEarlierMessages: false,
|
||||||
isReceivingPartialStream: false,
|
isReceivingPartialStream: false,
|
||||||
|
|||||||
@@ -29,10 +29,26 @@ describe("ChatTranscriptStore", () => {
|
|||||||
{ role: "assistant", parts: [{ type: "text", text: "hello" }] },
|
{ role: "assistant", parts: [{ type: "text", text: "hello" }] },
|
||||||
],
|
],
|
||||||
messagePageStart: 0,
|
messagePageStart: 0,
|
||||||
|
messagePageEnd: 2,
|
||||||
messagePageTotal: 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", () => {
|
it("keeps live streamed transcript state out of the raw history cache", () => {
|
||||||
const cache = new MemoryChatHistoryCache();
|
const cache = new MemoryChatHistoryCache();
|
||||||
const store = new ChatTranscriptStore(cache);
|
const store = new ChatTranscriptStore(cache);
|
||||||
@@ -51,6 +67,7 @@ describe("ChatTranscriptStore", () => {
|
|||||||
{ role: "user", parts: [{ type: "text", text: "next" }] },
|
{ role: "user", parts: [{ type: "text", text: "next" }] },
|
||||||
],
|
],
|
||||||
messagePageStart: 0,
|
messagePageStart: 0,
|
||||||
|
messagePageEnd: 3,
|
||||||
messagePageTotal: 3,
|
messagePageTotal: 3,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import type { SessionUiEvent } from "./sessionSocket";
|
|||||||
export interface ChatTranscriptView {
|
export interface ChatTranscriptView {
|
||||||
messages: ChatLine[];
|
messages: ChatLine[];
|
||||||
messagePageStart: number;
|
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;
|
messagePageTotal: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +51,11 @@ export class ChatTranscriptStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function transcriptViewFromHistory(history: RawMessagePage | undefined): ChatTranscriptView {
|
export function transcriptViewFromHistory(history: RawMessagePage | undefined): ChatTranscriptView {
|
||||||
|
const start = history?.start ?? 0;
|
||||||
return {
|
return {
|
||||||
messages: normalizeMessages(history?.messages ?? []),
|
messages: normalizeMessages(history?.messages ?? []),
|
||||||
messagePageStart: history?.start ?? 0,
|
messagePageStart: start,
|
||||||
|
messagePageEnd: start + (history?.messages.length ?? 0),
|
||||||
messagePageTotal: history?.total ?? 0,
|
messagePageTotal: history?.total ?? 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export class ChatView extends LitElement {
|
|||||||
@property({ attribute: false }) messages: ChatLine[] = [];
|
@property({ attribute: false }) messages: ChatLine[] = [];
|
||||||
@property() sessionId = "";
|
@property() sessionId = "";
|
||||||
@property({ type: Number }) messageStart = 0;
|
@property({ type: Number }) messageStart = 0;
|
||||||
|
@property({ type: Number }) messageEnd = 0;
|
||||||
@property({ type: Number }) messageTotal = 0;
|
@property({ type: Number }) messageTotal = 0;
|
||||||
@property({ type: Boolean }) hasMore = false;
|
@property({ type: Boolean }) hasMore = false;
|
||||||
@property({ type: Boolean }) loadingMore = false;
|
@property({ type: Boolean }) loadingMore = false;
|
||||||
@@ -307,8 +308,13 @@ export class ChatView extends LitElement {
|
|||||||
private historyRangeLabel() {
|
private historyRangeLabel() {
|
||||||
if (!this.messages.length || this.messageTotal <= 0) return null;
|
if (!this.messages.length || this.messageTotal <= 0) return null;
|
||||||
const from = this.messageStart + 1;
|
const from = this.messageStart + 1;
|
||||||
const to = this.messageStart + this.messages.length;
|
const to = this.loadedRawMessageEnd();
|
||||||
return html`<small>Showing messages ${from}–${to} of ${this.messageTotal}</small>`;
|
const total = Math.max(this.messageTotal, to);
|
||||||
|
return html`<small>Showing messages ${from}–${to} of ${total}</small>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadedRawMessageEnd(): number {
|
||||||
|
return Math.max(this.messageEnd, this.messageStart + this.messages.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderMessage(message: ChatLine, index: number) {
|
private renderMessage(message: ChatLine, index: number) {
|
||||||
|
|||||||
@@ -1334,7 +1334,7 @@ export class PiWebApp extends LitElement {
|
|||||||
${state.error ? html`<div class="error">${state.error}</div>` : null}
|
${state.error ? html`<div class="error">${state.error}</div>` : null}
|
||||||
<div class="mobile-navigation-panel">${this.isMobileNavigationLayout ? this.renderNavigationPanel(true) : null}</div>
|
<div class="mobile-navigation-panel">${this.isMobileNavigationLayout ? this.renderNavigationPanel(true) : null}</div>
|
||||||
${state.selectedSession ? html`
|
${state.selectedSession ? html`
|
||||||
<chat-view .sessionId=${state.selectedSession.id} .messages=${state.messages} .messageStart=${state.messagePageStart} .messageTotal=${state.messagePageTotal} .hasMore=${state.messagePageStart > 0} .loadingMore=${state.isLoadingEarlierMessages} .isReceivingPartialStream=${state.isReceivingPartialStream} .isCompacting=${state.status?.isCompacting === true} .pendingMessageCount=${state.status?.pendingMessageCount ?? 0} .status=${state.status} .activity=${state.activity} .onLoadMore=${() => this.withChatPrependTransition(() => this.sessions.loadEarlierMessages())}></chat-view>
|
<chat-view .sessionId=${state.selectedSession.id} .messages=${state.messages} .messageStart=${state.messagePageStart} .messageEnd=${state.messagePageEnd} .messageTotal=${state.messagePageTotal} .hasMore=${state.messagePageStart > 0} .loadingMore=${state.isLoadingEarlierMessages} .isReceivingPartialStream=${state.isReceivingPartialStream} .isCompacting=${state.status?.isCompacting === true} .pendingMessageCount=${state.status?.pendingMessageCount ?? 0} .status=${state.status} .activity=${state.activity} .onLoadMore=${() => this.withChatPrependTransition(() => this.sessions.loadEarlierMessages())}></chat-view>
|
||||||
<prompt-editor .sessionId=${state.selectedSession.id} .cwd=${state.selectedWorkspace?.path} .disabled=${state.selectedSession.archived === true} .canSteer=${state.status?.isStreaming === true} .isCompacting=${state.status?.isCompacting === true} .canStop=${state.status?.isStreaming === true || state.status?.isBashRunning === true || state.status?.isCompacting === true || (state.status?.pendingMessageCount ?? 0) > 0} .status=${state.status} .onSend=${(text: string, streamingBehavior?: "steer" | "followUp") => { this.sendPrompt(text, streamingBehavior); }} .onStop=${() => this.sessions.stopActiveWork()} .onSelectModel=${() => { void this.openModelDialog(); }} .onSelectThinking=${() => { void this.openThinkingDialog(); }}></prompt-editor>
|
<prompt-editor .sessionId=${state.selectedSession.id} .cwd=${state.selectedWorkspace?.path} .disabled=${state.selectedSession.archived === true} .canSteer=${state.status?.isStreaming === true} .isCompacting=${state.status?.isCompacting === true} .canStop=${state.status?.isStreaming === true || state.status?.isBashRunning === true || state.status?.isCompacting === true || (state.status?.pendingMessageCount ?? 0) > 0} .status=${state.status} .onSend=${(text: string, streamingBehavior?: "steer" | "followUp") => { this.sendPrompt(text, streamingBehavior); }} .onStop=${() => this.sessions.stopActiveWork()} .onSelectModel=${() => { void this.openModelDialog(); }} .onSelectThinking=${() => { void this.openThinkingDialog(); }}></prompt-editor>
|
||||||
<status-bar .status=${state.status} .workspace=${state.selectedWorkspace} .workspaceLabelItems=${state.selectedWorkspace === undefined ? [] : this.plugins.getWorkspaceLabelItems(state, state.selectedWorkspace)}></status-bar>
|
<status-bar .status=${state.status} .workspace=${state.selectedWorkspace} .workspaceLabelItems=${state.selectedWorkspace === undefined ? [] : this.plugins.getWorkspaceLabelItems(state, state.selectedWorkspace)}></status-bar>
|
||||||
${state.commandDialog !== undefined ? html`<command-picker .title=${state.commandDialog.title} .options=${state.commandDialog.options} .onPick=${(value: string) => this.sessions.respondToCommand(state.commandDialog?.requestId ?? "", value)} .onCancel=${() => { this.sessions.cancelCommand(); }}></command-picker>` : null}
|
${state.commandDialog !== undefined ? html`<command-picker .title=${state.commandDialog.title} .options=${state.commandDialog.options} .onPick=${(value: string) => this.sessions.respondToCommand(state.commandDialog?.requestId ?? "", value)} .onCancel=${() => { this.sessions.cancelCommand(); }}></command-picker>` : null}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export class SessionController {
|
|||||||
this.socket.close();
|
this.socket.close();
|
||||||
this.catchupStreamSessionId = undefined;
|
this.catchupStreamSessionId = undefined;
|
||||||
this.clearPendingTranscriptEvents();
|
this.clearPendingTranscriptEvents();
|
||||||
this.setState({ selectedSession: undefined, messages: [], messagePageStart: 0, messagePageTotal: 0, isLoadingEarlierMessages: false, isReceivingPartialStream: false, status: undefined, activity: undefined });
|
this.setState({ selectedSession: undefined, messages: [], messagePageStart: 0, messagePageEnd: 0, messagePageTotal: 0, isLoadingEarlierMessages: false, isReceivingPartialStream: false, status: undefined, activity: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
deselectSession(options?: { forgetRememberedSelection?: boolean | undefined; updateUrl?: boolean | undefined }) {
|
deselectSession(options?: { forgetRememberedSelection?: boolean | undefined; updateUrl?: boolean | undefined }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user