Reorganize file structure: core/, utils/, modes/interactive/components/, modes/interactive/theme/

This commit is contained in:
Mario Zechner 2025-12-09 00:51:33 +01:00
parent 00982705f2
commit 83a6c26969
56 changed files with 133 additions and 128 deletions

View file

@ -0,0 +1,87 @@
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
import { getMarkdownTheme, theme } from "../theme/theme.js";
/**
* Component that renders a complete assistant message
*/
export class AssistantMessageComponent extends Container {
private contentContainer: Container;
private hideThinkingBlock: boolean;
constructor(message?: AssistantMessage, hideThinkingBlock = false) {
super();
this.hideThinkingBlock = hideThinkingBlock;
// Container for text/thinking content
this.contentContainer = new Container();
this.addChild(this.contentContainer);
if (message) {
this.updateContent(message);
}
}
setHideThinkingBlock(hide: boolean): void {
this.hideThinkingBlock = hide;
}
updateContent(message: AssistantMessage): void {
// Clear content container
this.contentContainer.clear();
if (
message.content.length > 0 &&
message.content.some(
(c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
)
) {
this.contentContainer.addChild(new Spacer(1));
}
// Render content in order
for (let i = 0; i < message.content.length; i++) {
const content = message.content[i];
if (content.type === "text" && content.text.trim()) {
// Assistant text messages with no background - trim the text
// Set paddingY=0 to avoid extra spacing before tool executions
this.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, getMarkdownTheme()));
} else if (content.type === "thinking" && content.thinking.trim()) {
// Check if there's text content after this thinking block
const hasTextAfter = message.content.slice(i + 1).some((c) => c.type === "text" && c.text.trim());
if (this.hideThinkingBlock) {
// Show static "Thinking..." label when hidden
this.contentContainer.addChild(new Text(theme.fg("muted", "Thinking..."), 1, 0));
if (hasTextAfter) {
this.contentContainer.addChild(new Spacer(1));
}
} else {
// Thinking traces in muted color, italic
// Use Markdown component with default text style for consistent styling
this.contentContainer.addChild(
new Markdown(content.thinking.trim(), 1, 0, getMarkdownTheme(), {
color: (text: string) => theme.fg("muted", text),
italic: true,
}),
);
this.contentContainer.addChild(new Spacer(1));
}
}
}
// Check if aborted - show after partial content
// But only if there are no tool calls (tool execution components will show the error)
const hasToolCalls = message.content.some((c) => c.type === "toolCall");
if (!hasToolCalls) {
if (message.stopReason === "aborted") {
this.contentContainer.addChild(new Text(theme.fg("error", "\nAborted"), 1, 0));
} else if (message.stopReason === "error") {
const errorMsg = message.errorMessage || "Unknown error";
this.contentContainer.addChild(new Spacer(1));
this.contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
}
}
}
}