/** * [WHO]: Provides ImagePipelineController, ImagePipelineContext, Attachment — clipboard/attachment/image handling * [FROM]: Depends on @catui/tui (Container/Component/matchesKey), @catui/ai (ImageContent), * modes/utils (clipboard-image, image-resize), utils/mime, components/attachments-bar, theme * [TO]: Consumed by modes/interactive/interactive-mode.ts (constructs one, delegates paste/attachment/image methods) * [HERE]: modes/interactive/controllers/image-pipeline-controller.ts — first P5 UI slice (UI02, 纯搬) * * Extracted from InteractiveMode (P5 image-pipeline). Owns clipboard image paste, the attachments * bar state, attachment key navigation, and text→image extraction. Reads the editor-shell layout and * mount capabilities through a narrow ImagePipelineContext (no InteractiveMode reference). Behavior is * identical to the former InteractiveMode methods. */ import type { ImageContent } from "@catui/ai/types"; import { type Component, Container } from "@catui/tui"; export interface Attachment { path: string; mimeType?: string; bytes?: Uint8Array; } /** Narrow capability seam: mount/render capabilities the image pipeline needs, no InteractiveMode. */ export interface ImagePipelineContext { /** Current working directory (where clipboard images are written). */ getCwd(): string; /** Request a TUI re-render. */ requestRender(): void; /** Show a transient status line. */ showStatus(message: string): void; /** Active theme name (for the attachments bar). */ getThemeName(): string | undefined; /** * Whether the editor cursor is on the first (visual) line, so ↑ has nowhere to * move the cursor up — that's when ↑ should enter the attachments bar instead. * With multi-line text below the first line, ↑ keeps moving the cursor. */ isEditorCursorAtTop(): boolean; /** The editor shell container that hosts the attachments bar. */ getEditorContainer(): Container; /** The container the attachments bar is mounted into. */ getAttachmentsContainer(): Container | undefined; /** The editor/buddy layout node the attachments bar is placed before. */ getEditorBuddyLayout(): Component; } export declare class ImagePipelineController { private readonly ctx; private clipboardImageSeq; private clipboardImageFiles; private clipboardPastePromise; private attachments; private selectedAttachmentIndex; private attachmentsBar; constructor(ctx: ImagePipelineContext); /** Await any in-flight clipboard paste so a rapid Enter still waits for attachment registration. */ awaitPendingPaste(): Promise; /** Whether there are pending attachments (for editor-shell remount). */ hasAttachments(): boolean; /** * Take all pending attachments (clears the bar, resets selection + sequence). * Returns [] without side effects when empty. Used by the submit pipeline. */ takePendingAttachments(): Attachment[]; /** * Discard attachments and their on-disk clipboard files — used on turn end and * session new/switch/fork/tree. Deleting the files (not just the in-memory list) * is what prevents a new paste from reusing a stale path: the sequence restarts * at 1, so without deleting `_np_clipboard_image_1.png` the next paste would * collide with the previous image's filename. Sent images already live in the * model context; the on-disk copies are only scratch. */ clearAttachments(): void; handleClipboardImagePaste(): void; /** * Arrow/Delete handling for the attachments bar. The editor offers these keys * here before its own cursor/history handling. * * Model: the bar sits above the editor. * - ↑ from the editor (only when the editor is empty, so ↑ would otherwise browse * history) ENTERS the bar at the last (most recent) attachment. * - Inside the bar, ↑/↓ move the selection; stepping past the top/bottom leaves * the bar (returns false so the editor handles the key, e.g. history). * - Delete/Backspace removes the selected attachment. * This works for a single attachment too (the previous code only intercepted when * there were 2+, so one image could never be selected/deleted by keyboard). */ handleAttachmentKeyNavigation(data: string): boolean; private setSelectedIndex; /** * Convert attachment files to ImageContent array for sending to the model. * Prefers in-memory bytes (clipboard) then falls back to disk read. */ processAttachmentFiles(attachments: Attachment[]): Promise; /** * Extract image file paths from text, read them as base64 ImageContent, * and return the cleaned text with image references plus the image array. */ extractImagesFromText(text: string): Promise<{ text: string; images: ImageContent[]; }>; cleanupStaleClipboardFiles(): void; cleanupClipboardImages(): void; /** Chain clipboard work so rapid Enter after paste still waits for attachment registration. */ private enqueueClipboardPaste; private loadClipboardImageIntoAttachments; private deleteAttachment; private updateAttachmentsBar; }