From c4e82aabbeca57b19b4f65ccaf79d9639ba5b9c2 Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Thu, 7 May 2026 16:20:12 +0200 Subject: [PATCH] Improve compaction session UX --- src/client/src/components/ChatView.ts | 14 ++++++++++++++ src/client/src/components/PiWebApp.ts | 4 ++-- src/client/src/components/PromptEditor.ts | 11 +++++++---- src/client/src/components/shared.ts | 4 ++++ src/server/sessions/piSessionService.ts | 4 ++-- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/client/src/components/ChatView.ts b/src/client/src/components/ChatView.ts index a0ea093..edd56b8 100644 --- a/src/client/src/components/ChatView.ts +++ b/src/client/src/components/ChatView.ts @@ -27,6 +27,8 @@ export class ChatView extends LitElement { @property({ type: Number }) messageTotal = 0; @property({ type: Boolean }) hasMore = false; @property({ type: Boolean }) loadingMore = false; + @property({ type: Boolean }) isCompacting = false; + @property({ type: Number }) pendingMessageCount = 0; @property({ attribute: false }) onLoadMore?: () => void; @query(".chat") private chat?: HTMLDivElement; @state() private pinnedToBottom = true; @@ -60,11 +62,23 @@ export class ChatView extends LitElement { ${groupChatMessages(this.messages, this.messageStart).map((group) => group.kind === "message" ? this.renderMessage(group.message, group.index) : this.renderMessageGroup(group.messages, group.startIndex))} + ${this.renderSessionActivity()} `; } + private renderSessionActivity() { + if (!this.isCompacting) return null; + return html` + + `; + } + private renderHistoryIndicator() { if (!this.messages.length || this.messageTotal <= 0) return null; const loadedCount = this.messages.length; diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index c050ddc..c5b5672 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -114,8 +114,8 @@ export class PiWebApp extends LitElement {
${state.error ? html`
${state.error}
` : null} ${state.selectedSession ? html` - 0} .loadingMore=${state.isLoadingEarlierMessages} .onLoadMore=${() => this.withChatPrependTransition(() => this.sessions.loadEarlierMessages())}> - this.sessions.send(text, streamingBehavior)} .onStopSession=${() => this.sessions.stopSession()}> + 0} .loadingMore=${state.isLoadingEarlierMessages} .isCompacting=${state.status?.isCompacting === true} .pendingMessageCount=${state.status?.pendingMessageCount ?? 0} .onLoadMore=${() => this.withChatPrependTransition(() => this.sessions.loadEarlierMessages())}> + this.sessions.send(text, streamingBehavior)} .onStopSession=${() => this.sessions.stopSession()}> ${state.commandDialog !== undefined ? html` this.sessions.respondToCommand(state.commandDialog?.requestId ?? "", value)} .onCancel=${() => { this.sessions.cancelCommand(); }}>` : null} ` : html`
Select or start a session.
`} diff --git a/src/client/src/components/PromptEditor.ts b/src/client/src/components/PromptEditor.ts index 46f1bed..01041b7 100644 --- a/src/client/src/components/PromptEditor.ts +++ b/src/client/src/components/PromptEditor.ts @@ -11,6 +11,7 @@ export class PromptEditor extends LitElement { @property() sessionId?: string; @property() cwd?: string; @property({ type: Boolean }) canSteer = false; + @property({ type: Boolean }) isCompacting = false; @property({ attribute: false }) onSend?: (text: string, streamingBehavior?: "steer" | "followUp") => void; @property({ attribute: false }) onStopSession?: () => void; @query("textarea") private textarea?: HTMLTextAreaElement; @@ -35,6 +36,7 @@ export class PromptEditor extends LitElement { override render() { const inputMode = inputModeForDraft(this.draft); const shellMode = inputMode.kind === "shell"; + const queuesInput = this.canSteer || this.isCompacting; return html`
@@ -48,11 +50,12 @@ export class PromptEditor extends LitElement { placeholder="Message pi... Use / for commands, @ for files" > ${shellMode ? html`
Shell command${inputMode.excludeFromContext ? " · excluded from context" : ""}
` : null} + ${this.isCompacting && !shellMode ? html`
Compacting history · message will be queued
` : null} { this.pick(item); }}>
- - ${this.canSteer ? html`` : null} + + ${this.canSteer && !this.isCompacting ? html`` : null}
@@ -144,7 +147,7 @@ export class PromptEditor extends LitElement { } if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); - this.send(this.canSteer ? "followUp" : undefined); + this.send(this.canSteer || this.isCompacting ? "followUp" : undefined); } } @@ -160,7 +163,7 @@ export class PromptEditor extends LitElement { this.draft = ""; if (this.sessionId !== undefined && this.sessionId !== "") clearDraft(this.sessionId); this.completions = []; - this.onSend?.(text, this.canSteer ? streamingBehavior : undefined); + this.onSend?.(text, this.canSteer || this.isCompacting ? streamingBehavior : undefined); } static override styles = promptEditorStyles; diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index ad06ab5..9514ded 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -69,6 +69,10 @@ export const chatStyles = css` .group-msg.system { color: #ff7b72; } .group-msg.bash { color: #3fb950; } .history-boundary { display: grid; gap: 3px; margin: 0 0 14px; color: #8b949e; font-size: 12px; text-align: center; } + .session-activity { display: grid; gap: 4px; margin: 0 0 14px; padding: 12px; border: 1px solid #30363d; border-radius: 10px; background: #161b22; color: #e6edf3; } + .session-activity.compacting { border-color: #a371f7; background: #21132f; } + .session-activity strong { color: #d2a8ff; } + .session-activity span, .session-activity small { color: #8b949e; } .history-boundary small { color: #6e7681; } .label { display: block; margin-bottom: 8px; color: #8b949e; font-size: 12px; text-transform: uppercase; } formatted-text.part { display: block; } diff --git a/src/server/sessions/piSessionService.ts b/src/server/sessions/piSessionService.ts index 4968013..5a7b9f5 100644 --- a/src/server/sessions/piSessionService.ts +++ b/src/server/sessions/piSessionService.ts @@ -105,8 +105,8 @@ export class PiSessionService { async prompt(sessionId: string, text: string, streamingBehavior?: "steer" | "followUp"): Promise { const session = await this.getOrOpen(sessionId); - const behavior = session.isStreaming ? streamingBehavior ?? "followUp" : undefined; - this.publishActivity(session, behavior === "steer" ? "steering queued" : behavior === "followUp" ? "message queued" : "prompt accepted", "active"); + const behavior = session.isStreaming || session.isCompacting ? streamingBehavior ?? "followUp" : undefined; + this.publishActivity(session, session.isCompacting ? "message queued during compaction" : behavior === "steer" ? "steering queued" : behavior === "followUp" ? "message queued" : "prompt accepted", "active"); void session.prompt(text, behavior === undefined ? undefined : { streamingBehavior: behavior }).catch((error: unknown) => { const message = error instanceof Error ? error.message : String(error); this.publishActivity(session, "error", "error", message);