mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-18 21:00:41 +00:00
- Image component returns correct number of lines (rows) for TUI accounting - Empty lines rendered first, then cursor moves up and image is drawn - This clears the space the image occupies before rendering - Add spacer before inline images in tool output - Create ShowImagesSelectorComponent with borders like other selectors - Use showSelector pattern for /show-images command
87 lines
2.2 KiB
TypeScript
87 lines
2.2 KiB
TypeScript
import {
|
|
getCapabilities,
|
|
getImageDimensions,
|
|
type ImageDimensions,
|
|
imageFallback,
|
|
renderImage,
|
|
} from "../terminal-image.js";
|
|
import type { Component } from "../tui.js";
|
|
|
|
export interface ImageTheme {
|
|
fallbackColor: (str: string) => string;
|
|
}
|
|
|
|
export interface ImageOptions {
|
|
maxWidthCells?: number;
|
|
maxHeightCells?: number;
|
|
filename?: string;
|
|
}
|
|
|
|
export class Image implements Component {
|
|
private base64Data: string;
|
|
private mimeType: string;
|
|
private dimensions: ImageDimensions;
|
|
private theme: ImageTheme;
|
|
private options: ImageOptions;
|
|
|
|
private cachedLines?: string[];
|
|
private cachedWidth?: number;
|
|
|
|
constructor(
|
|
base64Data: string,
|
|
mimeType: string,
|
|
theme: ImageTheme,
|
|
options: ImageOptions = {},
|
|
dimensions?: ImageDimensions,
|
|
) {
|
|
this.base64Data = base64Data;
|
|
this.mimeType = mimeType;
|
|
this.theme = theme;
|
|
this.options = options;
|
|
this.dimensions = dimensions || getImageDimensions(base64Data, mimeType) || { widthPx: 800, heightPx: 600 };
|
|
}
|
|
|
|
invalidate(): void {
|
|
this.cachedLines = undefined;
|
|
this.cachedWidth = undefined;
|
|
}
|
|
|
|
render(width: number): string[] {
|
|
if (this.cachedLines && this.cachedWidth === width) {
|
|
return this.cachedLines;
|
|
}
|
|
|
|
const maxWidth = Math.min(width - 2, this.options.maxWidthCells ?? 60);
|
|
|
|
const caps = getCapabilities();
|
|
let lines: string[];
|
|
|
|
if (caps.images) {
|
|
const result = renderImage(this.base64Data, this.dimensions, { maxWidthCells: maxWidth });
|
|
|
|
if (result) {
|
|
// Return `rows` lines so TUI accounts for image height
|
|
// First (rows-1) lines are empty (TUI clears them)
|
|
// Last line: move cursor back up, then output image sequence
|
|
lines = [];
|
|
for (let i = 0; i < result.rows - 1; i++) {
|
|
lines.push("");
|
|
}
|
|
// Move cursor up to first row, then output image
|
|
const moveUp = result.rows > 1 ? `\x1b[${result.rows - 1}A` : "";
|
|
lines.push(moveUp + result.sequence);
|
|
} else {
|
|
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
lines = [this.theme.fallbackColor(fallback)];
|
|
}
|
|
} else {
|
|
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
lines = [this.theme.fallbackColor(fallback)];
|
|
}
|
|
|
|
this.cachedLines = lines;
|
|
this.cachedWidth = width;
|
|
|
|
return lines;
|
|
}
|
|
}
|