mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 17:00:59 +00:00
Fix tree selector gutter alignment, add page navigation, improve styling
- Fix gutter/connector alignment by tracking gutter positions with GutterInfo - Convert recursive markContains to iterative to avoid stack overflow - Add left/right arrow keys for page up/down navigation - Consistent assistant message styling (green prefix, appropriate status colors) - Remove leaf marker (*), active path bullets are sufficient - Add Expandable interface for toggle expansion - Fix BranchSummaryMessageComponent expansion toggle
This commit is contained in:
parent
975e90ea8c
commit
159e19a010
5 changed files with 186 additions and 104 deletions
|
|
@ -3620,7 +3620,7 @@ export const MODELS = {
|
|||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 196608,
|
||||
maxTokens: 131072,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"deepcogito/cogito-v2-preview-llama-405b": {
|
||||
id: "deepcogito/cogito-v2-preview-llama-405b",
|
||||
|
|
@ -4623,7 +4623,7 @@ export const MODELS = {
|
|||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 128000,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-oss-20b": {
|
||||
id: "openai/gpt-oss-20b",
|
||||
|
|
@ -6104,9 +6104,9 @@ export const MODELS = {
|
|||
contextWindow: 32768,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"anthropic/claude-3.5-haiku": {
|
||||
id: "anthropic/claude-3.5-haiku",
|
||||
name: "Anthropic: Claude 3.5 Haiku",
|
||||
"anthropic/claude-3.5-haiku-20241022": {
|
||||
id: "anthropic/claude-3.5-haiku-20241022",
|
||||
name: "Anthropic: Claude 3.5 Haiku (2024-10-22)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
|
|
@ -6121,9 +6121,9 @@ export const MODELS = {
|
|||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"anthropic/claude-3.5-haiku-20241022": {
|
||||
id: "anthropic/claude-3.5-haiku-20241022",
|
||||
name: "Anthropic: Claude 3.5 Haiku (2024-10-22)",
|
||||
"anthropic/claude-3.5-haiku": {
|
||||
id: "anthropic/claude-3.5-haiku",
|
||||
name: "Anthropic: Claude 3.5 Haiku",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
|
|
@ -6359,6 +6359,23 @@ export const MODELS = {
|
|||
contextWindow: 128000,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-8b-instruct": {
|
||||
id: "meta-llama/llama-3.1-8b-instruct",
|
||||
name: "Meta: Llama 3.1 8B Instruct",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.02,
|
||||
output: 0.03,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-405b-instruct": {
|
||||
id: "meta-llama/llama-3.1-405b-instruct",
|
||||
name: "Meta: Llama 3.1 405B Instruct",
|
||||
|
|
@ -6393,23 +6410,6 @@ export const MODELS = {
|
|||
contextWindow: 131072,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-8b-instruct": {
|
||||
id: "meta-llama/llama-3.1-8b-instruct",
|
||||
name: "Meta: Llama 3.1 8B Instruct",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.02,
|
||||
output: 0.03,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"mistralai/mistral-nemo": {
|
||||
id: "mistralai/mistral-nemo",
|
||||
name: "Mistral: Mistral Nemo",
|
||||
|
|
@ -6546,6 +6546,23 @@ export const MODELS = {
|
|||
contextWindow: 128000,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-4o-2024-05-13": {
|
||||
id: "openai/gpt-4o-2024-05-13",
|
||||
name: "OpenAI: GPT-4o (2024-05-13)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 5,
|
||||
output: 15,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 128000,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-4o": {
|
||||
id: "openai/gpt-4o",
|
||||
name: "OpenAI: GPT-4o",
|
||||
|
|
@ -6580,23 +6597,6 @@ export const MODELS = {
|
|||
contextWindow: 128000,
|
||||
maxTokens: 64000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-4o-2024-05-13": {
|
||||
id: "openai/gpt-4o-2024-05-13",
|
||||
name: "OpenAI: GPT-4o (2024-05-13)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 5,
|
||||
output: 15,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 128000,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3-70b-instruct": {
|
||||
id: "meta-llama/llama-3-70b-instruct",
|
||||
name: "Meta: Llama 3 70B Instruct",
|
||||
|
|
@ -6835,23 +6835,6 @@ export const MODELS = {
|
|||
contextWindow: 8191,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-3.5-turbo": {
|
||||
id: "openai/gpt-3.5-turbo",
|
||||
name: "OpenAI: GPT-3.5 Turbo",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.5,
|
||||
output: 1.5,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 16385,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-4": {
|
||||
id: "openai/gpt-4",
|
||||
name: "OpenAI: GPT-4",
|
||||
|
|
@ -6869,6 +6852,23 @@ export const MODELS = {
|
|||
contextWindow: 8191,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-3.5-turbo": {
|
||||
id: "openai/gpt-3.5-turbo",
|
||||
name: "OpenAI: GPT-3.5 Turbo",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.5,
|
||||
output: 1.5,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 16385,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openrouter/auto": {
|
||||
id: "openrouter/auto",
|
||||
name: "OpenRouter: Auto Router",
|
||||
|
|
|
|||
|
|
@ -23,8 +23,13 @@ function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string {
|
|||
|
||||
/**
|
||||
* Generate a unified diff string with line numbers and context
|
||||
* Returns both the diff string and the first changed line number (in the new file)
|
||||
*/
|
||||
function generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {
|
||||
function generateDiffString(
|
||||
oldContent: string,
|
||||
newContent: string,
|
||||
contextLines = 4,
|
||||
): { diff: string; firstChangedLine: number | undefined } {
|
||||
const parts = Diff.diffLines(oldContent, newContent);
|
||||
const output: string[] = [];
|
||||
|
||||
|
|
@ -36,6 +41,7 @@ function generateDiffString(oldContent: string, newContent: string, contextLines
|
|||
let oldLineNum = 1;
|
||||
let newLineNum = 1;
|
||||
let lastWasChange = false;
|
||||
let firstChangedLine: number | undefined;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
|
|
@ -45,6 +51,11 @@ function generateDiffString(oldContent: string, newContent: string, contextLines
|
|||
}
|
||||
|
||||
if (part.added || part.removed) {
|
||||
// Capture the first changed line (in the new file)
|
||||
if (firstChangedLine === undefined) {
|
||||
firstChangedLine = newLineNum;
|
||||
}
|
||||
|
||||
// Show the change
|
||||
for (const line of raw) {
|
||||
if (part.added) {
|
||||
|
|
@ -113,7 +124,7 @@ function generateDiffString(oldContent: string, newContent: string, contextLines
|
|||
}
|
||||
}
|
||||
|
||||
return output.join("\n");
|
||||
return { diff: output.join("\n"), firstChangedLine };
|
||||
}
|
||||
|
||||
const editSchema = Type.Object({
|
||||
|
|
@ -125,6 +136,8 @@ const editSchema = Type.Object({
|
|||
export interface EditToolDetails {
|
||||
/** Unified diff of the changes made */
|
||||
diff: string;
|
||||
/** Line number of the first change in the new file (for editor navigation) */
|
||||
firstChangedLine?: number;
|
||||
}
|
||||
|
||||
export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
|
||||
|
|
@ -143,7 +156,7 @@ export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
|
|||
|
||||
return new Promise<{
|
||||
content: Array<{ type: "text"; text: string }>;
|
||||
details: { diff: string } | undefined;
|
||||
details: EditToolDetails | undefined;
|
||||
}>((resolve, reject) => {
|
||||
// Check if already aborted
|
||||
if (signal?.aborted) {
|
||||
|
|
@ -262,6 +275,7 @@ export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
|
|||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
|
||||
const diffResult = generateDiffString(normalizedContent, normalizedNewContent);
|
||||
resolve({
|
||||
content: [
|
||||
{
|
||||
|
|
@ -269,7 +283,7 @@ export function createEditTool(cwd: string): AgentTool<typeof editSchema> {
|
|||
text: `Successfully replaced text in ${path}.`,
|
||||
},
|
||||
],
|
||||
details: { diff: generateDiffString(normalizedContent, normalizedNewContent) },
|
||||
details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine },
|
||||
});
|
||||
} catch (error: any) {
|
||||
// Clean up abort handler
|
||||
|
|
|
|||
|
|
@ -415,10 +415,14 @@ export class ToolExecutionComponent extends Container {
|
|||
} else if (this.toolName === "edit") {
|
||||
const rawPath = this.args?.file_path || this.args?.path || "";
|
||||
const path = shortenPath(rawPath);
|
||||
text =
|
||||
theme.fg("toolTitle", theme.bold("edit")) +
|
||||
" " +
|
||||
(path ? theme.fg("accent", path) : theme.fg("toolOutput", "..."));
|
||||
|
||||
// Build path display, appending :line if we have a successful result with line info
|
||||
let pathDisplay = path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
|
||||
if (this.result && !this.result.isError && this.result.details?.firstChangedLine) {
|
||||
pathDisplay += theme.fg("warning", `:${this.result.details.firstChangedLine}`);
|
||||
}
|
||||
|
||||
text = `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
|
||||
|
||||
if (this.result) {
|
||||
if (this.result.isError) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import {
|
|||
Container,
|
||||
Input,
|
||||
isArrowDown,
|
||||
isArrowLeft,
|
||||
isArrowRight,
|
||||
isArrowUp,
|
||||
isBackspace,
|
||||
isCtrlC,
|
||||
|
|
@ -18,17 +20,23 @@ import type { SessionTreeNode } from "../../../core/session-manager.js";
|
|||
import { theme } from "../theme/theme.js";
|
||||
import { DynamicBorder } from "./dynamic-border.js";
|
||||
|
||||
/** Gutter info: position (displayIndent where connector was) and whether to show │ */
|
||||
interface GutterInfo {
|
||||
position: number; // displayIndent level where the connector was shown
|
||||
show: boolean; // true = show │, false = show spaces
|
||||
}
|
||||
|
||||
/** Flattened tree node for navigation */
|
||||
interface FlatNode {
|
||||
node: SessionTreeNode;
|
||||
/** Indentation level (each level = 2 spaces) */
|
||||
/** Indentation level (each level = 3 chars) */
|
||||
indent: number;
|
||||
/** Whether to show connector (├─ or └─) - true if parent has multiple children */
|
||||
showConnector: boolean;
|
||||
/** If showConnector, true = last sibling (└─), false = not last (├─) */
|
||||
isLast: boolean;
|
||||
/** For each ancestor branch point, true = show │ (more siblings below), false = show space */
|
||||
gutters: boolean[];
|
||||
/** Gutter info for each ancestor branch point */
|
||||
gutters: GutterInfo[];
|
||||
/** True if this node is a root under a virtual branching root (multiple roots) */
|
||||
isVirtualRootChild: boolean;
|
||||
}
|
||||
|
|
@ -109,24 +117,36 @@ class TreeList implements Component {
|
|||
// - At indent 2+: stay flat for single-child chains, +1 only if parent branches
|
||||
|
||||
// Stack items: [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild]
|
||||
type StackItem = [SessionTreeNode, number, boolean, boolean, boolean, boolean[], boolean];
|
||||
type StackItem = [SessionTreeNode, number, boolean, boolean, boolean, GutterInfo[], boolean];
|
||||
const stack: StackItem[] = [];
|
||||
|
||||
// Determine which subtrees contain the active leaf (to sort current branch first)
|
||||
// Use iterative post-order traversal to avoid stack overflow
|
||||
const containsActive = new Map<SessionTreeNode, boolean>();
|
||||
const leafId = this.currentLeafId;
|
||||
const markContains = (node: SessionTreeNode): boolean => {
|
||||
let has = leafId !== null && node.entry.id === leafId;
|
||||
for (const child of node.children) {
|
||||
if (markContains(child)) {
|
||||
has = true;
|
||||
{
|
||||
// Build list in pre-order, then process in reverse for post-order effect
|
||||
const allNodes: SessionTreeNode[] = [];
|
||||
const preOrderStack: SessionTreeNode[] = [...roots];
|
||||
while (preOrderStack.length > 0) {
|
||||
const node = preOrderStack.pop()!;
|
||||
allNodes.push(node);
|
||||
// Push children in reverse so they're processed left-to-right
|
||||
for (let i = node.children.length - 1; i >= 0; i--) {
|
||||
preOrderStack.push(node.children[i]);
|
||||
}
|
||||
}
|
||||
containsActive.set(node, has);
|
||||
return has;
|
||||
};
|
||||
for (const root of roots) {
|
||||
markContains(root);
|
||||
// Process in reverse (post-order): children before parents
|
||||
for (let i = allNodes.length - 1; i >= 0; i--) {
|
||||
const node = allNodes[i];
|
||||
let has = leafId !== null && node.entry.id === leafId;
|
||||
for (const child of node.children) {
|
||||
if (containsActive.get(child)) {
|
||||
has = true;
|
||||
}
|
||||
}
|
||||
containsActive.set(node, has);
|
||||
}
|
||||
}
|
||||
|
||||
// Add roots in reverse order, prioritizing the one containing the active leaf
|
||||
|
|
@ -189,7 +209,15 @@ class TreeList implements Component {
|
|||
|
||||
// Build gutters for children
|
||||
// If this node showed a connector, add a gutter entry for descendants
|
||||
const childGutters = showConnector ? [...gutters, !isLast] : gutters;
|
||||
// Only add gutter if connector is actually displayed (not suppressed for virtual root children)
|
||||
const connectorDisplayed = showConnector && !isVirtualRootChild;
|
||||
// When connector is displayed, add a gutter entry at the connector's position
|
||||
// Connector is at position (displayIndent - 1), so gutter should be there too
|
||||
const currentDisplayIndent = this.multipleRoots ? Math.max(0, indent - 1) : indent;
|
||||
const connectorPosition = Math.max(0, currentDisplayIndent - 1);
|
||||
const childGutters: GutterInfo[] = connectorDisplayed
|
||||
? [...gutters, { position: connectorPosition, show: !isLast }]
|
||||
: gutters;
|
||||
|
||||
// Add children in reverse order
|
||||
for (let i = orderedChildren.length - 1; i >= 0; i--) {
|
||||
|
|
@ -378,22 +406,48 @@ class TreeList implements Component {
|
|||
const flatNode = this.filteredNodes[i];
|
||||
const entry = flatNode.node.entry;
|
||||
const isSelected = i === this.selectedIndex;
|
||||
const isCurrentLeaf = entry.id === this.currentLeafId;
|
||||
|
||||
// Build line: cursor + gutters + connector + extra indent + label + content + suffix
|
||||
// Build line: cursor + prefix + path marker + label + content
|
||||
const cursor = isSelected ? theme.fg("accent", "› ") : " ";
|
||||
|
||||
// If multiple roots, shift display (roots at 0, not 1)
|
||||
const displayIndent = this.multipleRoots ? Math.max(0, flatNode.indent - 1) : flatNode.indent;
|
||||
|
||||
// Build prefix: gutters + connector + extra spaces
|
||||
const gutterStr = flatNode.gutters.map((g) => (g ? "│ " : " ")).join("");
|
||||
// Build prefix with gutters at their correct positions
|
||||
// Each gutter has a position (displayIndent where its connector was shown)
|
||||
const connector =
|
||||
flatNode.showConnector && !flatNode.isVirtualRootChild ? (flatNode.isLast ? "└─ " : "├─ ") : "";
|
||||
// Extra indent for visual grouping beyond gutters/connector
|
||||
const prefixLevels = flatNode.gutters.length + (connector ? 1 : 0);
|
||||
const extraIndent = " ".repeat(Math.max(0, displayIndent - prefixLevels));
|
||||
const prefix = gutterStr + connector + extraIndent;
|
||||
const connectorPosition = connector ? displayIndent - 1 : -1;
|
||||
|
||||
// Build prefix char by char, placing gutters and connector at their positions
|
||||
const totalChars = displayIndent * 3;
|
||||
const prefixChars: string[] = [];
|
||||
for (let i = 0; i < totalChars; i++) {
|
||||
const level = Math.floor(i / 3);
|
||||
const posInLevel = i % 3;
|
||||
|
||||
// Check if there's a gutter at this level
|
||||
const gutter = flatNode.gutters.find((g) => g.position === level);
|
||||
if (gutter) {
|
||||
if (posInLevel === 0) {
|
||||
prefixChars.push(gutter.show ? "│" : " ");
|
||||
} else {
|
||||
prefixChars.push(" ");
|
||||
}
|
||||
} else if (connector && level === connectorPosition) {
|
||||
// Connector at this level
|
||||
if (posInLevel === 0) {
|
||||
prefixChars.push(flatNode.isLast ? "└" : "├");
|
||||
} else if (posInLevel === 1) {
|
||||
prefixChars.push("─");
|
||||
} else {
|
||||
prefixChars.push(" ");
|
||||
}
|
||||
} else {
|
||||
prefixChars.push(" ");
|
||||
}
|
||||
}
|
||||
const prefix = prefixChars.join("");
|
||||
|
||||
// Active path marker - shown right before the entry text
|
||||
const isOnActivePath = this.activePathIds.has(entry.id);
|
||||
|
|
@ -401,9 +455,8 @@ class TreeList implements Component {
|
|||
|
||||
const label = flatNode.node.label ? theme.fg("warning", `[${flatNode.node.label}] `) : "";
|
||||
const content = this.getEntryDisplayText(flatNode.node, isSelected);
|
||||
const suffix = isCurrentLeaf ? theme.fg("accent", " *") : "";
|
||||
|
||||
const line = cursor + theme.fg("dim", prefix) + pathMarker + label + content + suffix;
|
||||
const line = cursor + theme.fg("dim", prefix) + pathMarker + label + content;
|
||||
lines.push(truncateToWidth(line, width));
|
||||
}
|
||||
|
||||
|
|
@ -437,12 +490,12 @@ class TreeList implements Component {
|
|||
if (textContent) {
|
||||
result = theme.fg("success", "assistant: ") + textContent;
|
||||
} else if (msgWithContent.stopReason === "aborted") {
|
||||
result = theme.fg("warning", "assistant: ") + theme.fg("muted", "(aborted)");
|
||||
result = theme.fg("success", "assistant: ") + theme.fg("muted", "(aborted)");
|
||||
} else if (msgWithContent.errorMessage) {
|
||||
const errMsg = normalize(msgWithContent.errorMessage).slice(0, 80);
|
||||
result = theme.fg("error", "assistant: ") + errMsg;
|
||||
result = theme.fg("success", "assistant: ") + theme.fg("error", errMsg);
|
||||
} else {
|
||||
result = theme.fg("muted", "assistant: (no content)");
|
||||
result = theme.fg("success", "assistant: ") + theme.fg("muted", "(no content)");
|
||||
}
|
||||
} else if (role === "toolResult") {
|
||||
const toolMsg = msg as { toolCallId?: string; toolName?: string };
|
||||
|
|
@ -586,6 +639,12 @@ class TreeList implements Component {
|
|||
this.selectedIndex = this.selectedIndex === 0 ? this.filteredNodes.length - 1 : this.selectedIndex - 1;
|
||||
} else if (isArrowDown(keyData)) {
|
||||
this.selectedIndex = this.selectedIndex === this.filteredNodes.length - 1 ? 0 : this.selectedIndex + 1;
|
||||
} else if (isArrowLeft(keyData)) {
|
||||
// Page up
|
||||
this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines);
|
||||
} else if (isArrowRight(keyData)) {
|
||||
// Page down
|
||||
this.selectedIndex = Math.min(this.filteredNodes.length - 1, this.selectedIndex + this.maxVisibleLines);
|
||||
} else if (isEnter(keyData)) {
|
||||
const selected = this.filteredNodes[this.selectedIndex];
|
||||
if (selected && this.onSelect) {
|
||||
|
|
@ -719,9 +778,11 @@ export class TreeSelectorComponent extends Container {
|
|||
this.labelInputContainer = new Container();
|
||||
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.bold("Session Tree"), 1, 0));
|
||||
this.addChild(new DynamicBorder());
|
||||
this.addChild(new TruncatedText(theme.fg("muted", " Type to search. l: label. ^O: cycle filter"), 0, 0));
|
||||
this.addChild(new Text(theme.bold(" Session Tree"), 1, 0));
|
||||
this.addChild(
|
||||
new TruncatedText(theme.fg("muted", " ↑/↓: move. ←/→: page. l: label. ^O: filter. Type to search"), 0, 0),
|
||||
);
|
||||
this.addChild(new SearchLine(this.treeList));
|
||||
this.addChild(new DynamicBorder());
|
||||
this.addChild(new Spacer(1));
|
||||
|
|
|
|||
|
|
@ -56,6 +56,15 @@ import { UserMessageComponent } from "./components/user-message.js";
|
|||
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
||||
import { getAvailableThemes, getEditorTheme, getMarkdownTheme, onThemeChange, setTheme, theme } from "./theme/theme.js";
|
||||
|
||||
/** Interface for components that can be expanded/collapsed */
|
||||
interface Expandable {
|
||||
setExpanded(expanded: boolean): void;
|
||||
}
|
||||
|
||||
function isExpandable(obj: unknown): obj is Expandable {
|
||||
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
||||
}
|
||||
|
||||
export class InteractiveMode {
|
||||
private session: AgentSession;
|
||||
private ui: TUI;
|
||||
|
|
@ -1303,13 +1312,7 @@ export class InteractiveMode {
|
|||
private toggleToolOutputExpansion(): void {
|
||||
this.toolOutputExpanded = !this.toolOutputExpanded;
|
||||
for (const child of this.chatContainer.children) {
|
||||
if (child instanceof ToolExecutionComponent) {
|
||||
child.setExpanded(this.toolOutputExpanded);
|
||||
} else if (child instanceof CompactionSummaryMessageComponent) {
|
||||
child.setExpanded(this.toolOutputExpanded);
|
||||
} else if (child instanceof BashExecutionComponent) {
|
||||
child.setExpanded(this.toolOutputExpanded);
|
||||
} else if (child instanceof HookMessageComponent) {
|
||||
if (isExpandable(child)) {
|
||||
child.setExpanded(this.toolOutputExpanded);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue