pi-subagents-lite 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/package.json +5 -4
- package/src/agents/agent-manager.ts +2 -1
- package/src/agents/agent-status.ts +2 -2
- package/src/agents/output-file.ts +87 -5
- package/src/agents/tool-execution.ts +8 -3
- package/src/config/config-store.ts +7 -0
- package/src/config/types.ts +1 -0
- package/src/events.ts +3 -1
- package/src/models/model-precedence.ts +2 -0
- package/src/spawn/spawn-coordinator.ts +37 -22
- package/src/spawn/worktree-validator.ts +9 -3
- package/src/ui/agent-widget.ts +1 -1
- package/src/ui/menu/helpers.ts +37 -7
- package/src/ui/menu/menu-concurrency.ts +29 -17
- package/src/ui/menu/menu-model-settings.ts +26 -22
- package/src/ui/menu/menu-spawn-options.ts +5 -0
- package/src/ui/menu/menu-spawn-wizard.ts +30 -15
- package/src/ui/menu/menu-system-prompt.ts +5 -1
- package/src/ui/menu/menu-widget-settings.ts +28 -0
- package/src/ui/menu/submenus/model-select.ts +3 -3
- package/src/ui/menu/wrappers/settings-list.ts +73 -105
- package/src/ui/result-viewer.ts +42 -10
- package/src/{models/model-selector.ts → ui/searchable-select.ts} +29 -29
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
* Wraps a SettingsList or SelectList with:
|
|
5
5
|
* - Top separator line
|
|
6
6
|
* - Header with title
|
|
7
|
-
* - List content
|
|
7
|
+
* - List content (SettingsList renders the highlighted item's description and a
|
|
8
|
+
* hint line below the items itself; SelectList renders inline descriptions)
|
|
8
9
|
* - Bottom separator line
|
|
10
|
+
*
|
|
11
|
+
* The Back button has been removed. Menus still close via Escape, the
|
|
12
|
+
* back-arrow key, and Ctrl-C — the underlying list components call their
|
|
13
|
+
* `onCancel` on those keys, and the wrapper wires that to `closeMenu` for
|
|
14
|
+
* SelectList (SettingsList receives its own `onCancel` at construction).
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
17
|
import { type Component, isFocusable } from "@earendil-works/pi-tui";
|
|
@@ -40,108 +46,63 @@ export class SettingsListWrapper implements Component {
|
|
|
40
46
|
this.separatorChar = options.separatorChar ?? "─";
|
|
41
47
|
this.passthroughKeys = options.passthroughKeys ?? false;
|
|
42
48
|
|
|
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
|
-
let next = i + 1;
|
|
101
|
-
while (next < curItems.length && isSep(curItems[next])) next++;
|
|
102
|
-
if (next < curItems.length) i = next;
|
|
103
|
-
else {
|
|
104
|
-
next = i - 1;
|
|
105
|
-
while (next >= 0 && isSep(curItems[next])) next--;
|
|
106
|
-
if (next >= 0) i = next;
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
let next = i - 1;
|
|
110
|
-
while (next >= 0 && isSep(curItems[next])) next--;
|
|
111
|
-
if (next >= 0) i = next;
|
|
112
|
-
else {
|
|
113
|
-
next = i + 1;
|
|
114
|
-
while (next < curItems.length && isSep(curItems[next])) next++;
|
|
115
|
-
if (next < curItems.length) i = next;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
list[_rawIndex] = i;
|
|
120
|
-
},
|
|
121
|
-
configurable: true,
|
|
122
|
-
});
|
|
123
|
-
list[_rawIndex] = list.selectedIndex ?? 0;
|
|
124
|
-
|
|
125
|
-
// Expose rebuild callback
|
|
126
|
-
if (options.onRebuild) {
|
|
127
|
-
const isSelectList = !!list.onSelect;
|
|
128
|
-
const rebuild = (newItems: any[]) => {
|
|
129
|
-
const wrapperItems = [
|
|
130
|
-
isSelectList
|
|
131
|
-
? { value: "__sep__", label: " " }
|
|
132
|
-
: { id: "__sep__", label: " ", currentValue: "" },
|
|
133
|
-
isSelectList
|
|
134
|
-
? { value: "__back__", label: "Back" }
|
|
135
|
-
: { id: "__back__", label: "Back", currentValue: "" },
|
|
136
|
-
];
|
|
137
|
-
const fullItems = [...newItems, ...wrapperItems];
|
|
138
|
-
list.items = fullItems;
|
|
139
|
-
list.filteredItems = fullItems;
|
|
140
|
-
list.selectedIndex = 0;
|
|
141
|
-
list.submenuComponent = null;
|
|
142
|
-
};
|
|
143
|
-
options.onRebuild(rebuild);
|
|
144
|
-
}
|
|
49
|
+
const list = this.settingsList as any;
|
|
50
|
+
|
|
51
|
+
// SelectList has no onCancel of its own; wire closeMenu so Escape,
|
|
52
|
+
// back-arrow (converted to Escape below), and Ctrl-C close the menu.
|
|
53
|
+
// SettingsList receives its own onCancel at construction, so leave it be.
|
|
54
|
+
if (options.onCancel && !list.onCancel) {
|
|
55
|
+
const closeMenu = options.onCancel;
|
|
56
|
+
list.onCancel = () => closeMenu();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Auto-skip __sep__ items when navigating, so the cursor never lands on a
|
|
60
|
+
// separator section header. Menus push their own __sep__ items.
|
|
61
|
+
if (options.onCancel && Array.isArray(list.items)) {
|
|
62
|
+
const _rawIndex = Symbol("rawIndex");
|
|
63
|
+
const isSep = (item: any) => item?.value === "__sep__" || item?.id === "__sep__";
|
|
64
|
+
// Starting just past `start`, walk in `step` direction and return the
|
|
65
|
+
// first non-separator index (or an out-of-bounds sentinel if none).
|
|
66
|
+
const firstNonSepFrom = (start: number, step: number): number => {
|
|
67
|
+
let next = start + step;
|
|
68
|
+
while (next >= 0 && next < list.items.length && isSep(list.items[next])) next += step;
|
|
69
|
+
return next;
|
|
70
|
+
};
|
|
71
|
+
const inBounds = (i: number) => i >= 0 && i < list.items.length;
|
|
72
|
+
Object.defineProperty(list, "selectedIndex", {
|
|
73
|
+
get() { return list[_rawIndex] ?? 0; },
|
|
74
|
+
set(idx) {
|
|
75
|
+
const items = list.items;
|
|
76
|
+
const cur = list[_rawIndex] ?? 0;
|
|
77
|
+
const clamped = Math.max(0, Math.min(idx, items.length - 1));
|
|
78
|
+
if (!isSep(items[clamped])) {
|
|
79
|
+
list[_rawIndex] = clamped;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Landed on a separator: search in the travel direction first,
|
|
83
|
+
// fall back to the opposite direction so the cursor always ends on
|
|
84
|
+
// a real item (or stays put if everything is a separator).
|
|
85
|
+
const step = idx > cur ? 1 : -1;
|
|
86
|
+
const fwd = firstNonSepFrom(clamped, step);
|
|
87
|
+
const back = firstNonSepFrom(clamped, -step);
|
|
88
|
+
list[_rawIndex] = inBounds(fwd) ? fwd : inBounds(back) ? back : clamped;
|
|
89
|
+
},
|
|
90
|
+
configurable: true,
|
|
91
|
+
});
|
|
92
|
+
list[_rawIndex] = list.selectedIndex ?? 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Expose rebuild callback. Items are set directly without appending any
|
|
96
|
+
// wrapper-controlled items: descriptions are read dynamically at render
|
|
97
|
+
// time, so they remain correct after a rebuild.
|
|
98
|
+
if (options.onRebuild) {
|
|
99
|
+
const rebuild = (newItems: any[]) => {
|
|
100
|
+
list.items = newItems;
|
|
101
|
+
list.filteredItems = newItems;
|
|
102
|
+
list.selectedIndex = 0;
|
|
103
|
+
list.submenuComponent = null;
|
|
104
|
+
};
|
|
105
|
+
options.onRebuild(rebuild);
|
|
145
106
|
}
|
|
146
107
|
}
|
|
147
108
|
|
|
@@ -192,9 +153,16 @@ export class SettingsListWrapper implements Component {
|
|
|
192
153
|
lines.push(" " + styledTitle);
|
|
193
154
|
lines.push("");
|
|
194
155
|
|
|
195
|
-
// SettingsList content
|
|
156
|
+
// SettingsList content — strip the hint line that pi-tui always appends
|
|
157
|
+
// (empty line + "Enter/Space to change · Esc to cancel"). Descriptions
|
|
158
|
+
// already explain what each item does, so the hint is redundant.
|
|
196
159
|
const settingsLines = this.settingsList.render(width);
|
|
197
|
-
|
|
160
|
+
const hintPattern = /Enter\/Space|Esc to cancel/;
|
|
161
|
+
if (settingsLines.length >= 2 && hintPattern.test(settingsLines[settingsLines.length - 1] ?? "")) {
|
|
162
|
+
lines.push(...settingsLines.slice(0, -2));
|
|
163
|
+
} else {
|
|
164
|
+
lines.push(...settingsLines);
|
|
165
|
+
}
|
|
198
166
|
|
|
199
167
|
// Bottom separator
|
|
200
168
|
lines.push("");
|
package/src/ui/result-viewer.ts
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
Spacer,
|
|
15
15
|
Text,
|
|
16
16
|
type MarkdownTheme,
|
|
17
|
+
visibleWidth,
|
|
17
18
|
} from "@earendil-works/pi-tui";
|
|
18
|
-
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import { type LifetimeUsage, formatTokens } from "../agents/usage.js";
|
|
20
20
|
import type { Theme } from "./agent-widget.js";
|
|
21
21
|
import { formatMs } from "./format.js";
|
|
@@ -44,9 +44,15 @@ export interface ResultViewerStats {
|
|
|
44
44
|
/** Lines scrolled per PageUp/PageDown (kept at a fixed, comfortable amount). */
|
|
45
45
|
const PAGE_STEP = 14;
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/** Total outer render width including side borders. */
|
|
48
48
|
const RENDER_WIDTH = 78;
|
|
49
49
|
|
|
50
|
+
/** Horizontal margin (spaces) on each side of content. */
|
|
51
|
+
const MARGIN = 2;
|
|
52
|
+
|
|
53
|
+
/** Width available for content: outer width − side borders (2) − margins (2×MARGIN). */
|
|
54
|
+
const CONTENT_WIDTH = RENDER_WIDTH - 2 - MARGIN * 2;
|
|
55
|
+
|
|
50
56
|
/** Fixed non-viewport lines in the component (borders, title, spacers, hints, etc.). */
|
|
51
57
|
const BASE_OVERHEAD = 10;
|
|
52
58
|
|
|
@@ -143,15 +149,14 @@ export class ResultViewer extends Container implements Component {
|
|
|
143
149
|
// Build markdown renderer (pre-render to get total lines)
|
|
144
150
|
const mdTheme = buildMarkdownTheme(theme);
|
|
145
151
|
this.markdown = new Markdown(text, 0, 0, mdTheme);
|
|
146
|
-
this.renderedLines = this.markdown.render(
|
|
152
|
+
this.renderedLines = this.markdown.render(CONTENT_WIDTH);
|
|
147
153
|
|
|
148
154
|
this.buildUI(title, stats);
|
|
149
155
|
this.updateViewport();
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
/** Build the full UI tree —
|
|
158
|
+
/** Build the full UI tree — title, stats, viewport, hints. Borders drawn by render(). */
|
|
153
159
|
private buildUI(title: string, stats?: ResultViewerStats): void {
|
|
154
|
-
this.addChild(new DynamicBorder());
|
|
155
160
|
this.addChild(new Spacer(1));
|
|
156
161
|
|
|
157
162
|
// Title bar
|
|
@@ -171,7 +176,7 @@ export class ResultViewer extends Container implements Component {
|
|
|
171
176
|
|
|
172
177
|
// Separator
|
|
173
178
|
this.addChild(
|
|
174
|
-
new Text(this.theme.fg("muted", "─".repeat(
|
|
179
|
+
new Text(this.theme.fg("muted", "─".repeat(CONTENT_WIDTH)), 0, 0),
|
|
175
180
|
);
|
|
176
181
|
this.addChild(new Spacer(1));
|
|
177
182
|
|
|
@@ -183,7 +188,7 @@ export class ResultViewer extends Container implements Component {
|
|
|
183
188
|
this.scrollIndicator = new Container();
|
|
184
189
|
this.addChild(this.scrollIndicator);
|
|
185
190
|
|
|
186
|
-
// Bottom spacer + key hints
|
|
191
|
+
// Bottom spacer + key hints
|
|
187
192
|
this.addChild(new Spacer(1));
|
|
188
193
|
const refreshHint = this.callbacks.onRefresh ? " · r refresh" : "";
|
|
189
194
|
const hints = this.theme.fg(
|
|
@@ -192,7 +197,6 @@ export class ResultViewer extends Container implements Component {
|
|
|
192
197
|
);
|
|
193
198
|
this.addChild(new Text(hints, 0, 0));
|
|
194
199
|
this.addChild(new Spacer(1));
|
|
195
|
-
this.addChild(new DynamicBorder());
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
/**
|
|
@@ -278,7 +282,7 @@ export class ResultViewer extends Container implements Component {
|
|
|
278
282
|
this.textRef.text = newText;
|
|
279
283
|
const mdTheme = buildMarkdownTheme(this.theme);
|
|
280
284
|
this.markdown = new Markdown(newText, 0, 0, mdTheme);
|
|
281
|
-
this.renderedLines = this.markdown.render(
|
|
285
|
+
this.renderedLines = this.markdown.render(CONTENT_WIDTH);
|
|
282
286
|
// Preserve scroll position, clamped to new content bounds
|
|
283
287
|
this.scrollOffset = Math.min(oldOffset, this.renderedLines.length - 1);
|
|
284
288
|
this.updateViewport();
|
|
@@ -295,6 +299,33 @@ export class ResultViewer extends Container implements Component {
|
|
|
295
299
|
|
|
296
300
|
invalidate(): void {}
|
|
297
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Wrap all child output with box-drawing borders (┌─┐/│/└─┘).
|
|
304
|
+
*
|
|
305
|
+
* Children render at contentWidth so wrapped lines align with the side
|
|
306
|
+
* borders: innerWidth (between the two │) must equal the dash row width,
|
|
307
|
+
* and the 1-space padding on each side of the line is reserved inside it,
|
|
308
|
+
* otherwise content rows end up 2 columns wider than the borders and the
|
|
309
|
+
* TUI overlay compositor truncates the right │ away.
|
|
310
|
+
*/
|
|
311
|
+
override render(width: number): string[] {
|
|
312
|
+
const innerWidth = Math.max(1, width - 2);
|
|
313
|
+
const contentWidth = Math.max(0, innerWidth - 2);
|
|
314
|
+
const innerLines = super.render(contentWidth);
|
|
315
|
+
|
|
316
|
+
const border = (str: string) => this.theme.fg("muted", str);
|
|
317
|
+
const vline = border("│");
|
|
318
|
+
const hbar = "─".repeat(innerWidth);
|
|
319
|
+
|
|
320
|
+
const result: string[] = [border(`┌${hbar}┐`)];
|
|
321
|
+
for (const line of innerLines) {
|
|
322
|
+
const pad = Math.max(0, contentWidth - visibleWidth(line));
|
|
323
|
+
result.push(`${vline} ${line}${" ".repeat(pad)} ${vline}`);
|
|
324
|
+
}
|
|
325
|
+
result.push(border(`└${hbar}┘`));
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
298
329
|
private scrollTo(offset: number): void {
|
|
299
330
|
this.scrollOffset = Math.max(0, Math.min(this.renderedLines.length - 1, offset));
|
|
300
331
|
this.updateViewport();
|
|
@@ -311,7 +342,8 @@ export class ResultViewer extends Container implements Component {
|
|
|
311
342
|
for (let i = 0; i < visibleLines; i++) {
|
|
312
343
|
const lineIdx = this.scrollOffset + i;
|
|
313
344
|
const line = this.renderedLines[lineIdx] ?? "";
|
|
314
|
-
|
|
345
|
+
const pad = " ".repeat(MARGIN);
|
|
346
|
+
this.viewport.addChild(new Text(pad + line, 0, 0));
|
|
315
347
|
}
|
|
316
348
|
|
|
317
349
|
// Pad AFTER content to keep viewport at fixed height so the footer
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* searchable-select.ts — TUI paginated, searchable pick-list dialog.
|
|
3
3
|
*
|
|
4
4
|
* Reuses the same building blocks as pi's ModelSelectorComponent but without
|
|
5
5
|
* the SettingsManager dependency — no side effects, just callbacks.
|
|
@@ -15,52 +15,52 @@ import {
|
|
|
15
15
|
Text,
|
|
16
16
|
} from "@earendil-works/pi-tui";
|
|
17
17
|
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import type { Theme } from "
|
|
18
|
+
import type { Theme } from "./agent-widget.js";
|
|
19
19
|
|
|
20
20
|
/* ------------------------------------------------------------------ */
|
|
21
21
|
/* Types */
|
|
22
22
|
/* ------------------------------------------------------------------ */
|
|
23
23
|
|
|
24
|
-
export interface
|
|
25
|
-
/**
|
|
24
|
+
export interface SelectOption {
|
|
25
|
+
/** The value returned on selection (e.g. "provider/model-id"). */
|
|
26
26
|
value: string;
|
|
27
|
-
/** Display label
|
|
27
|
+
/** Display label. */
|
|
28
28
|
label: string;
|
|
29
|
-
/** Provider name for badge */
|
|
30
|
-
provider
|
|
29
|
+
/** Provider name for badge; omitted when the label already conveys it (e.g. provider/type lists). */
|
|
30
|
+
provider?: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
interface
|
|
33
|
+
interface SelectDialogCallbacks {
|
|
34
34
|
onSelect: (value: string) => void;
|
|
35
35
|
onCancel: () => void;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/* ------------------------------------------------------------------ */
|
|
39
|
-
/*
|
|
39
|
+
/* SearchableSelectDialog */
|
|
40
40
|
/* ------------------------------------------------------------------ */
|
|
41
41
|
|
|
42
42
|
const MAX_VISIBLE = 10;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* A paginated, searchable
|
|
45
|
+
* A paginated, searchable selection dialog.
|
|
46
46
|
*
|
|
47
47
|
* Rendering mirrors pi's ModelSelectorComponent:
|
|
48
48
|
* - Top border
|
|
49
49
|
* - Search input
|
|
50
|
-
* - Paginated
|
|
50
|
+
* - Paginated option list (10 at a time, centered on selection)
|
|
51
51
|
* - Scroll indicator "(3/47)"
|
|
52
52
|
* - Bottom border
|
|
53
53
|
*
|
|
54
54
|
* Key bindings: up/down/pageup/pagedown/enter/escape + pass-through to search.
|
|
55
55
|
*/
|
|
56
|
-
export class
|
|
56
|
+
export class SearchableSelectDialog extends Container implements Focusable {
|
|
57
57
|
private searchInput: Input;
|
|
58
58
|
private listContainer: Container;
|
|
59
|
-
private items:
|
|
60
|
-
private filteredItems:
|
|
59
|
+
private items: SelectOption[];
|
|
60
|
+
private filteredItems: SelectOption[];
|
|
61
61
|
private selectedIndex: number;
|
|
62
|
-
private
|
|
63
|
-
private callbacks:
|
|
62
|
+
private currentValue: string | null;
|
|
63
|
+
private callbacks: SelectDialogCallbacks;
|
|
64
64
|
private theme: Theme;
|
|
65
65
|
|
|
66
66
|
// Focusable implementation — propagate to searchInput for IME cursor
|
|
@@ -76,21 +76,21 @@ export class ModelSelectorDialog extends Container implements Focusable {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
constructor(
|
|
79
|
-
items:
|
|
80
|
-
|
|
81
|
-
callbacks:
|
|
79
|
+
items: SelectOption[],
|
|
80
|
+
currentValue: string | null,
|
|
81
|
+
callbacks: SelectDialogCallbacks,
|
|
82
82
|
theme: Theme,
|
|
83
83
|
) {
|
|
84
84
|
super();
|
|
85
85
|
|
|
86
86
|
this.items = items;
|
|
87
|
-
this.
|
|
87
|
+
this.currentValue = currentValue;
|
|
88
88
|
this.callbacks = callbacks;
|
|
89
89
|
this.theme = theme;
|
|
90
90
|
this.filteredItems = [...items];
|
|
91
91
|
|
|
92
|
-
// Pre-select current
|
|
93
|
-
const currentIdx = items.findIndex((m) => m.value ===
|
|
92
|
+
// Pre-select current value if present
|
|
93
|
+
const currentIdx = items.findIndex((m) => m.value === currentValue);
|
|
94
94
|
this.selectedIndex = currentIdx >= 0 ? currentIdx : 0;
|
|
95
95
|
|
|
96
96
|
// Build UI
|
|
@@ -185,7 +185,7 @@ export class ModelSelectorDialog extends Container implements Focusable {
|
|
|
185
185
|
|
|
186
186
|
// Everything else → search input (triggers fuzzy filter)
|
|
187
187
|
this.searchInput.handleInput(keyData);
|
|
188
|
-
this.
|
|
188
|
+
this.filterItems();
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
invalidate(): void {
|
|
@@ -196,7 +196,7 @@ export class ModelSelectorDialog extends Container implements Focusable {
|
|
|
196
196
|
/* Private helpers */
|
|
197
197
|
/* ------------------------------------------------------------------ */
|
|
198
198
|
|
|
199
|
-
private
|
|
199
|
+
private filterItems(): void {
|
|
200
200
|
const query = this.searchInput.getValue();
|
|
201
201
|
if (!query) {
|
|
202
202
|
this.filteredItems = [...this.items];
|
|
@@ -221,7 +221,7 @@ export class ModelSelectorDialog extends Container implements Focusable {
|
|
|
221
221
|
const { filteredItems } = this;
|
|
222
222
|
if (filteredItems.length === 0) {
|
|
223
223
|
this.listContainer.addChild(
|
|
224
|
-
new Text(this.theme.fg("muted", " No matching
|
|
224
|
+
new Text(this.theme.fg("muted", " No matching items"), 0, 0),
|
|
225
225
|
);
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
@@ -241,14 +241,14 @@ export class ModelSelectorDialog extends Container implements Focusable {
|
|
|
241
241
|
if (!item) continue;
|
|
242
242
|
|
|
243
243
|
const isSelected = i === this.selectedIndex;
|
|
244
|
-
const isCurrent = item.value === this.
|
|
244
|
+
const isCurrent = item.value === this.currentValue;
|
|
245
245
|
|
|
246
|
-
const
|
|
246
|
+
const labelText = isSelected
|
|
247
247
|
? this.theme.fg("accent", "→ ") + this.theme.fg("accent", item.label)
|
|
248
248
|
: ` ${item.label}`;
|
|
249
|
-
const providerBadge = this.theme.fg("muted", `[${item.provider}]`);
|
|
249
|
+
const providerBadge = item.provider ? this.theme.fg("muted", `[${item.provider}]`) : "";
|
|
250
250
|
const checkmark = isCurrent ? this.theme.fg("success", " ✓") : "";
|
|
251
|
-
const line = `${
|
|
251
|
+
const line = `${labelText} ${providerBadge}${checkmark}`;
|
|
252
252
|
|
|
253
253
|
this.listContainer.addChild(new Text(line, 0, 0));
|
|
254
254
|
}
|