File size: 4,753 Bytes
f500658 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
import {
Container,
type Focusable,
fuzzyFilter,
getKeybindings,
Input,
type SelectItem,
SelectList,
type SelectListLayoutOptions,
Spacer,
Text,
} from "@earendil-works/pi-tui";
import { getSelectListTheme, theme } from "../theme/theme.ts";
import { DynamicBorder } from "./dynamic-border.ts";
import { keyDisplayText } from "./keybinding-hints.ts";
const THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {
minPrimaryColumnWidth: 12,
maxPrimaryColumnWidth: 32,
};
const LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {
off: "No reasoning",
minimal: "Very brief reasoning (~1k tokens)",
low: "Light reasoning (~2k tokens)",
medium: "Moderate reasoning (~8k tokens)",
high: "Deep reasoning (~16k tokens)",
xhigh: "Extra-high reasoning (~32k tokens)",
max: "Maximum reasoning",
};
/**
* Component that renders a thinking level selector with borders
*/
export class ThinkingSelectorComponent extends Container implements Focusable {
private searchInput: Input;
private selectList: SelectList;
private selectListChildIndex: number;
private allItems: SelectItem[];
private onSelect: (level: ThinkingLevel) => void;
private onCancel: () => void;
private onSelectAsDefault?: (level: ThinkingLevel) => void;
private _focused = false;
get focused(): boolean {
return this._focused;
}
set focused(value: boolean) {
this._focused = value;
this.searchInput.focused = value;
}
constructor(
currentLevel: ThinkingLevel,
availableLevels: ThinkingLevel[],
onSelect: (level: ThinkingLevel) => void,
onCancel: () => void,
onSelectAsDefault?: (level: ThinkingLevel) => void,
defaultThinkingLevel?: ThinkingLevel,
) {
super();
this.onSelect = onSelect;
this.onCancel = onCancel;
this.onSelectAsDefault = onSelectAsDefault;
this.allItems = availableLevels.map((level) => ({
value: level,
label: `${level === currentLevel ? "✓ " : " "}${level}`,
description:
level === defaultThinkingLevel ? `${LEVEL_DESCRIPTIONS[level]} · default` : LEVEL_DESCRIPTIONS[level],
}));
// Add top border
this.addChild(new DynamicBorder());
this.addChild(new Spacer(1));
this.addChild(new Text("Thinking Level", 0, 0));
this.addChild(new Spacer(1));
this.addChild(new Text(`${keyDisplayText("app.thinking.cycle")} cycles thinking levels in-session`, 0, 0));
this.addChild(new Spacer(1));
this.searchInput = new Input();
this.searchInput.onSubmit = () => this.selectList.handleInput("\r");
this.addChild(this.searchInput);
this.addChild(new Spacer(1));
// Create selector
this.selectList = this.buildSelectList(this.allItems, currentLevel);
this.selectListChildIndex = this.children.length;
this.addChild(this.selectList);
this.addChild(new Spacer(1));
this.addChild(
new Text(
theme.fg(
"dim",
` ${keyDisplayText("tui.select.confirm")} to select · ${keyDisplayText("app.thinking.save")} to set as default · ${keyDisplayText("tui.select.cancel")} to cancel`,
),
0,
0,
),
);
// Add bottom border
this.addChild(new DynamicBorder());
}
private buildSelectList(items: SelectItem[], preselect?: ThinkingLevel): SelectList {
const list = new SelectList(items, Math.max(1, items.length), getSelectListTheme(), THINKING_SELECT_LIST_LAYOUT);
const currentIndex = items.findIndex((item) => item.value === preselect);
if (currentIndex !== -1) {
list.setSelectedIndex(currentIndex);
}
list.onSelect = (item) => this.onSelect(item.value as ThinkingLevel);
list.onCancel = () => this.onCancel();
return list;
}
private applyFilter(query: string): void {
const filtered = query
? fuzzyFilter(this.allItems, query, (item) => `${item.value} ${item.description ?? ""}`)
: this.allItems;
const selectedValue = this.selectList.getSelectedItem()?.value as ThinkingLevel | undefined;
const newList = this.buildSelectList(filtered, selectedValue);
this.children[this.selectListChildIndex] = newList;
this.selectList = newList;
}
handleInput(keyData: string): void {
const kb = getKeybindings();
if (kb.matches(keyData, "app.thinking.save") && this.onSelectAsDefault) {
const item = this.selectList.getSelectedItem();
if (item) this.onSelectAsDefault(item.value as ThinkingLevel);
return;
}
const isNav =
kb.matches(keyData, "tui.select.up") ||
kb.matches(keyData, "tui.select.down") ||
kb.matches(keyData, "tui.select.confirm") ||
kb.matches(keyData, "tui.select.cancel");
if (isNav) {
this.selectList.handleInput(keyData);
return;
}
this.searchInput.handleInput(keyData);
this.applyFilter(this.searchInput.getValue());
}
getSelectList(): SelectList {
return this.selectList;
}
}
|