feat: add tui ui kit
This commit is contained in:
130
src/tui/components/tool-execution.ts
Normal file
130
src/tui/components/tool-execution.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Box, Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
||||
import { markdownTheme, theme } from "../theme/theme.js";
|
||||
|
||||
type ToolResultContent = {
|
||||
type?: string;
|
||||
text?: string;
|
||||
mimeType?: string;
|
||||
bytes?: number;
|
||||
omitted?: boolean;
|
||||
};
|
||||
|
||||
type ToolResult = {
|
||||
content?: ToolResultContent[];
|
||||
details?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const PREVIEW_LINES = 12;
|
||||
|
||||
function formatArgs(toolName: string, args: unknown): string {
|
||||
if (!args || typeof args !== "object") return "";
|
||||
const record = args as Record<string, unknown>;
|
||||
if (toolName === "bash" && typeof record.command === "string") {
|
||||
return record.command;
|
||||
}
|
||||
const path = typeof record.path === "string" ? record.path : undefined;
|
||||
if (path) return path;
|
||||
try {
|
||||
return JSON.stringify(args);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function extractText(result?: ToolResult): string {
|
||||
if (!result?.content) return "";
|
||||
const lines: string[] = [];
|
||||
for (const entry of result.content) {
|
||||
if (entry.type === "text" && entry.text) {
|
||||
lines.push(entry.text);
|
||||
} else if (entry.type === "image") {
|
||||
const mime = entry.mimeType ?? "image";
|
||||
const size = entry.bytes ? ` ${Math.round(entry.bytes / 1024)}kb` : "";
|
||||
const omitted = entry.omitted ? " (omitted)" : "";
|
||||
lines.push(`[${mime}${size}${omitted}]`);
|
||||
}
|
||||
}
|
||||
return lines.join("\n").trim();
|
||||
}
|
||||
|
||||
export class ToolExecutionComponent extends Container {
|
||||
private box: Box;
|
||||
private header: Text;
|
||||
private argsLine: Text;
|
||||
private output: Markdown;
|
||||
private toolName: string;
|
||||
private args: unknown;
|
||||
private result?: ToolResult;
|
||||
private expanded = false;
|
||||
private isError = false;
|
||||
private isPartial = true;
|
||||
|
||||
constructor(toolName: string, args: unknown) {
|
||||
super();
|
||||
this.toolName = toolName;
|
||||
this.args = args;
|
||||
this.box = new Box(1, 1, (line) => theme.toolPendingBg(line));
|
||||
this.header = new Text("", 0, 0);
|
||||
this.argsLine = new Text("", 0, 0);
|
||||
this.output = new Markdown("", 0, 0, markdownTheme, {
|
||||
color: (line) => theme.toolOutput(line),
|
||||
});
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(this.box);
|
||||
this.box.addChild(this.header);
|
||||
this.box.addChild(this.argsLine);
|
||||
this.box.addChild(this.output);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setArgs(args: unknown) {
|
||||
this.args = args;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean) {
|
||||
this.expanded = expanded;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setResult(result: ToolResult | undefined, opts?: { isError?: boolean }) {
|
||||
this.result = result;
|
||||
this.isPartial = false;
|
||||
this.isError = Boolean(opts?.isError);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
setPartialResult(result: ToolResult | undefined) {
|
||||
this.result = result;
|
||||
this.isPartial = true;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
const bg = this.isPartial
|
||||
? theme.toolPendingBg
|
||||
: this.isError
|
||||
? theme.toolErrorBg
|
||||
: theme.toolSuccessBg;
|
||||
this.box.setBgFn((line) => bg(line));
|
||||
|
||||
const title = `${this.toolName}${this.isPartial ? " (running)" : ""}`;
|
||||
this.header.setText(theme.toolTitle(theme.bold(title)));
|
||||
|
||||
const argLine = formatArgs(this.toolName, this.args);
|
||||
this.argsLine.setText(argLine ? theme.dim(argLine) : theme.dim(" "));
|
||||
|
||||
const raw = extractText(this.result);
|
||||
const text = raw || (this.isPartial ? "…" : "");
|
||||
if (!this.expanded && text) {
|
||||
const lines = text.split("\n");
|
||||
const preview =
|
||||
lines.length > PREVIEW_LINES
|
||||
? `${lines.slice(0, PREVIEW_LINES).join("\n")}\n…`
|
||||
: text;
|
||||
this.output.setText(preview);
|
||||
} else {
|
||||
this.output.setText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user