mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 03:01:56 +00:00
feat(coding-agent): clipboard image paste support via Ctrl+V (fixes #419)
This commit is contained in:
parent
97bb411988
commit
5c5084481b
8 changed files with 233 additions and 2 deletions
|
|
@ -3,9 +3,11 @@
|
|||
* Handles TUI rendering and user interaction, delegating business logic to AgentSession.
|
||||
*/
|
||||
|
||||
import * as crypto from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import Clipboard from "@crosscopy/clipboard";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { AssistantMessage, Message, OAuthProvider } from "@mariozechner/pi-ai";
|
||||
import type { SlashCommand } from "@mariozechner/pi-tui";
|
||||
|
|
@ -141,6 +143,10 @@ export class InteractiveMode {
|
|||
// Custom tools for custom rendering
|
||||
private customTools: Map<string, LoadedCustomTool>;
|
||||
|
||||
// Clipboard image tracking: imageId -> temp file path
|
||||
private clipboardImages = new Map<number, string>();
|
||||
private clipboardImageCounter = 0;
|
||||
|
||||
// Convenience accessors
|
||||
private get agent() {
|
||||
return this.session.agent;
|
||||
|
|
@ -291,6 +297,9 @@ export class InteractiveMode {
|
|||
theme.fg("dim", followUp) +
|
||||
theme.fg("muted", " to queue follow-up") +
|
||||
"\n" +
|
||||
theme.fg("dim", "ctrl+v") +
|
||||
theme.fg("muted", " to paste image") +
|
||||
"\n" +
|
||||
theme.fg("dim", "drop files") +
|
||||
theme.fg("muted", " to attach");
|
||||
const header = new Text(`${logo}\n${instructions}`, 1, 0);
|
||||
|
|
@ -819,6 +828,52 @@ export class InteractiveMode {
|
|||
this.updateEditorBorderColor();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle clipboard image paste (triggered on Ctrl+V)
|
||||
this.editor.onPasteImage = () => {
|
||||
this.handleClipboardImagePaste();
|
||||
};
|
||||
}
|
||||
|
||||
private async handleClipboardImagePaste(): Promise<void> {
|
||||
try {
|
||||
if (!Clipboard.hasImage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageData = await Clipboard.getImageBinary();
|
||||
if (!imageData || imageData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write to temp file
|
||||
const imageId = ++this.clipboardImageCounter;
|
||||
const tmpDir = os.tmpdir();
|
||||
const fileName = `pi-clipboard-${crypto.randomUUID()}.png`;
|
||||
const filePath = path.join(tmpDir, fileName);
|
||||
fs.writeFileSync(filePath, Buffer.from(imageData));
|
||||
|
||||
// Store mapping and insert marker
|
||||
this.clipboardImages.set(imageId, filePath);
|
||||
this.editor.insertTextAtCursor(`[image #${imageId}]`);
|
||||
this.ui.requestRender();
|
||||
} catch {
|
||||
// Silently ignore clipboard errors (may not have permission, etc.)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace [image #N] markers with actual file paths and clear the image map.
|
||||
*/
|
||||
private replaceImageMarkers(text: string): string {
|
||||
let result = text;
|
||||
for (const [imageId, filePath] of this.clipboardImages) {
|
||||
const marker = `[image #${imageId}]`;
|
||||
result = result.replace(marker, filePath);
|
||||
}
|
||||
this.clipboardImages.clear();
|
||||
this.clipboardImageCounter = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
private setupEditorSubmitHandler(): void {
|
||||
|
|
@ -948,6 +1003,9 @@ export class InteractiveMode {
|
|||
}
|
||||
|
||||
// If streaming, use prompt() with steer behavior
|
||||
// Replace image markers with actual file paths
|
||||
text = this.replaceImageMarkers(text);
|
||||
|
||||
// This handles hook commands (execute immediately), slash command expansion, and queueing
|
||||
if (this.session.isStreaming) {
|
||||
this.editor.addToHistory(text);
|
||||
|
|
@ -2379,6 +2437,7 @@ export class InteractiveMode {
|
|||
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
||||
| \`${externalEditor}\` | Edit message in external editor |
|
||||
| \`${followUp}\` | Queue follow-up message |
|
||||
| \`Ctrl+V\` | Paste image from clipboard |
|
||||
| \`/\` | Slash commands |
|
||||
| \`!\` | Run bash command |
|
||||
`;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue