Merge PR #2950: feat(coding-agent): add copy affordance to completed /btw answers (@wolfiesch)
Adds 'c copy' to the completed /btw panel footer (alongside b branch / Esc dismiss), copying the sanitized visible answer to the clipboard. The copy shortcut is guarded by canCopyBtw + main-editor focus + empty editor.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import btwUserPrompt from "../../prompts/system/btw-user.md" with { type: "text" };
|
||||
import { copyToClipboard } from "../../utils/clipboard";
|
||||
import { BtwPanelComponent } from "../components/btw-panel";
|
||||
import type { InteractiveModeContext } from "../types";
|
||||
|
||||
@@ -39,6 +40,8 @@ export class BtwController {
|
||||
#lastAssistantMessage: AssistantMessage | undefined;
|
||||
#lastLeafId: string | null | undefined;
|
||||
#branchInFlight = false;
|
||||
#lastCopyText: string | undefined;
|
||||
#copyInFlight = false;
|
||||
|
||||
constructor(private readonly ctx: InteractiveModeContext) {}
|
||||
|
||||
@@ -58,6 +61,27 @@ export class BtwController {
|
||||
);
|
||||
}
|
||||
|
||||
canCopy(): boolean {
|
||||
return (
|
||||
!this.#copyInFlight && this.#activeRequest?.component.isCopyable() === true && this.#lastCopyText !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
async handleCopy(): Promise<boolean> {
|
||||
if (!this.canCopy() || this.#lastCopyText === undefined) return false;
|
||||
this.#copyInFlight = true;
|
||||
try {
|
||||
await copyToClipboard(this.#lastCopyText);
|
||||
this.ctx.showStatus("Copied /btw answer to clipboard");
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
||||
return true;
|
||||
} finally {
|
||||
this.#copyInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
async handleBranch(): Promise<boolean> {
|
||||
if (!this.canBranch() || !this.#lastQuestion || !this.#lastAssistantMessage) return false;
|
||||
this.#branchInFlight = true;
|
||||
@@ -123,17 +147,17 @@ export class BtwController {
|
||||
if (!this.#isActiveRequest(request)) {
|
||||
return;
|
||||
}
|
||||
if (replyText) {
|
||||
request.component.setAnswer(replyText);
|
||||
}
|
||||
request.component.setAnswer(replyText);
|
||||
request.component.markComplete();
|
||||
if (request.component.isBranchable()) {
|
||||
const copyText = request.component.getCopyText();
|
||||
if (copyText !== undefined) {
|
||||
this.#lastQuestion = request.question;
|
||||
this.#lastReplyText = replyText;
|
||||
this.#lastCopyText = copyText;
|
||||
this.#lastAssistantMessage = assistantMessageWithReplyText(assistantMessage, replyText);
|
||||
this.#lastLeafId = request.leafId;
|
||||
} else {
|
||||
this.#clearBranchState();
|
||||
this.#clearCompletedState();
|
||||
}
|
||||
} catch (error) {
|
||||
if (!this.#isActiveRequest(request)) {
|
||||
@@ -151,7 +175,7 @@ export class BtwController {
|
||||
const request = this.#activeRequest;
|
||||
if (!request) return;
|
||||
this.#activeRequest = undefined;
|
||||
this.#clearBranchState();
|
||||
this.#clearCompletedState();
|
||||
if (options.abort) {
|
||||
request.abortController.abort();
|
||||
}
|
||||
@@ -160,10 +184,11 @@ export class BtwController {
|
||||
this.ctx.ui.requestRender();
|
||||
}
|
||||
|
||||
#clearBranchState(): void {
|
||||
#clearCompletedState(): void {
|
||||
this.#lastQuestion = undefined;
|
||||
this.#lastReplyText = undefined;
|
||||
this.#lastAssistantMessage = undefined;
|
||||
this.#lastCopyText = undefined;
|
||||
this.#lastLeafId = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ export class InputController {
|
||||
#enhancedPaste?: EnhancedPasteController;
|
||||
#focusedLeftTapListenerInstalled = false;
|
||||
#btwBranchListenerInstalled = false;
|
||||
#btwCopyListenerInstalled = false;
|
||||
// Tap counter for the double-← gesture; reset whenever a quiet gap
|
||||
// (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
|
||||
// #detectLeftDoubleTap.
|
||||
@@ -174,6 +175,17 @@ export class InputController {
|
||||
return { consume: true };
|
||||
});
|
||||
}
|
||||
if (!this.#btwCopyListenerInstalled) {
|
||||
this.#btwCopyListenerInstalled = true;
|
||||
this.ctx.ui.addInputListener(data => {
|
||||
if (!matchesKey(data, "c")) return undefined;
|
||||
if (!this.ctx.canCopyBtw()) return undefined;
|
||||
if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
|
||||
if (this.ctx.editor.getText().trim()) return undefined;
|
||||
void this.ctx.handleBtwCopyKey();
|
||||
return { consume: true };
|
||||
});
|
||||
}
|
||||
this.ctx.editor.onEscape = () => {
|
||||
// Active context maintenance owns Esc: auto/manual compaction,
|
||||
// handoff generation, and auto-retry backoff all advertise
|
||||
|
||||
Reference in New Issue
Block a user