diff --git a/src/editor-rewrite/popover.ts b/src/editor-rewrite/popover.ts index 85112577..75b2ff7c 100644 --- a/src/editor-rewrite/popover.ts +++ b/src/editor-rewrite/popover.ts @@ -6,7 +6,7 @@ import { isRewriteGenerateKey } from "./keys"; import { canApplyRewrite, type RewriteRuntimeSettings, type RewriteSession } from "./model"; import { RewriteOutputError } from "./output"; import { buildRewritePrompt } from "./prompt"; -import { runRewriteSelection } from "./runner"; +import { runRewriteSelection, type RewriteActivity } from "./runner"; import type { SendShortcut } from "../settings/model"; const POPOVER_MARGIN = 8; @@ -127,7 +127,7 @@ export class RewriteSelectionPopover { this.previewEl?.setText(""); this.diffEl?.empty(); this.renderDebug(); - this.setStatus("Generating patch..."); + this.setStatus("Generating patch", { active: true }); this.syncControls(); try { @@ -136,6 +136,7 @@ export class RewriteSelectionPopover { cwd: this.options.cwd, prompt: buildRewritePrompt(this.options.session), runtimeSettings: this.options.runtimeSettings, + onActivity: (activity) => this.updateActivity(activity), onPreview: (text) => this.updatePreview(text), }); this.options.session.replacementText = output.replacementText; @@ -156,9 +157,14 @@ export class RewriteSelectionPopover { private updatePreview(text: string): void { this.options.session.streamText = text; this.previewEl?.setText(text); + this.setStatus("Writing replacement", { active: true }); this.position(); } + private updateActivity(activity: RewriteActivity): void { + this.setStatus(activity === "reasoning" ? "Reasoning" : "Writing replacement", { active: true }); + } + private renderDiff(): void { const replacement = this.options.session.replacementText; if (replacement === null || !this.diffEl) return; @@ -199,8 +205,16 @@ export class RewriteSelectionPopover { this.close(); } - private setStatus(text: string): void { - this.statusEl?.setText(text); + private setStatus(text: string, options: { active?: boolean } = {}): void { + if (!this.statusEl) return; + this.statusEl.empty(); + this.statusEl.classList.toggle("is-active", Boolean(options.active)); + this.statusEl.createSpan({ text }); + if (!options.active) return; + const dots = this.statusEl.createSpan({ cls: "codex-panel-rewrite-popover__status-dots" }); + dots.createSpan({ text: "." }); + dots.createSpan({ text: "." }); + dots.createSpan({ text: "." }); } private syncControls(): void { diff --git a/src/editor-rewrite/runner.ts b/src/editor-rewrite/runner.ts index bf0e052f..63e9cb43 100644 --- a/src/editor-rewrite/runner.ts +++ b/src/editor-rewrite/runner.ts @@ -28,9 +28,12 @@ export interface RunRewriteSelectionOptions { cwd: string; prompt: string; runtimeSettings?: RewriteRuntimeSettings; + onActivity?: (activity: RewriteActivity) => void; onPreview?: (text: string) => void; } +export type RewriteActivity = "reasoning" | "writing"; + export async function runRewriteSelection(options: RunRewriteSelectionOptions): Promise { let threadId: string | null = null; let expectedTurnId: string | null = null; @@ -54,10 +57,21 @@ export async function runRewriteSelection(options: RunRewriteSelectionOptions): if (notification.method === "item/agentMessage/delta") { if (!threadId || notification.params.threadId !== threadId) return; if (expectedTurnId && notification.params.turnId !== expectedTurnId) return; + options.onActivity?.("writing"); preview += notification.params.delta; options.onPreview?.(preview); return; } + if ( + notification.method === "item/reasoning/summaryTextDelta" || + notification.method === "item/reasoning/textDelta" || + notification.method === "item/reasoning/summaryPartAdded" + ) { + if (!threadId || notification.params.threadId !== threadId) return; + if (expectedTurnId && notification.params.turnId !== expectedTurnId) return; + options.onActivity?.("reasoning"); + return; + } if (notification.method === "item/completed") { if (!threadId || notification.params.threadId !== threadId) return; if (expectedTurnId && notification.params.turnId !== expectedTurnId) return; diff --git a/styles.css b/styles.css index 4c7d33a3..afbf814d 100644 --- a/styles.css +++ b/styles.css @@ -1430,6 +1430,26 @@ font-size: var(--font-ui-small); } +.codex-panel-rewrite-popover__status.is-active { + color: var(--text-faint); +} + +.codex-panel-rewrite-popover__status-dots span { + opacity: 0.35; +} + +.codex-panel-rewrite-popover__status.is-active .codex-panel-rewrite-popover__status-dots span { + animation: codex-panel-reasoning-dot 1.2s infinite ease-in-out; +} + +.codex-panel-rewrite-popover__status.is-active .codex-panel-rewrite-popover__status-dots span:nth-child(2) { + animation-delay: 0.18s; +} + +.codex-panel-rewrite-popover__status.is-active .codex-panel-rewrite-popover__status-dots span:nth-child(3) { + animation-delay: 0.36s; +} + .codex-panel-rewrite-popover__preview { display: none; max-height: 120px;