pi-advisor-flow 0.2.0 → 0.2.1

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/src/ui.ts CHANGED
@@ -5,12 +5,36 @@ import {
5
5
  fuzzyFilter,
6
6
  Input,
7
7
  Key,
8
+ type Keybindings,
9
+ type KeybindingsManager,
8
10
  matchesKey,
9
11
  truncateToWidth,
10
12
  } from "@earendil-works/pi-tui";
11
13
 
14
+ interface RenderRequester {
15
+ requestRender: () => void;
16
+ }
17
+ interface SearchableModelSelectorOptions {
18
+ allOptions: string[];
19
+ keybindings: KeybindingsManager;
20
+ onCancel: () => void;
21
+ onSelect: (value: string) => void;
22
+ theme: Theme;
23
+ title: string;
24
+ tui: RenderRequester;
25
+ }
26
+ interface AdvisorSettingsSelectorOptions {
27
+ effortLevels: string[];
28
+ initial: AdvisorSettings;
29
+ onCancel: () => void;
30
+ onSave: (settings: AdvisorSettings) => void;
31
+ presets: ContextPreset[];
32
+ theme: Theme;
33
+ tui: RenderRequester;
34
+ }
35
+
12
36
  export class SearchableModelSelector implements Component, Focusable {
13
- private readonly tui: any;
37
+ private readonly tui: RenderRequester;
14
38
  private readonly searchInput: Input;
15
39
  private readonly allOptions: string[];
16
40
  private filteredOptions: string[];
@@ -19,26 +43,18 @@ export class SearchableModelSelector implements Component, Focusable {
19
43
  private readonly onSelect: (value: string) => void;
20
44
  private readonly onCancel: () => void;
21
45
  private readonly theme: Theme;
22
- private readonly keybindings: any;
46
+ private readonly keybindings: KeybindingsManager;
23
47
  private _focused = false;
24
48
 
25
- public get focused(): boolean {
49
+ get focused(): boolean {
26
50
  return this._focused;
27
51
  }
28
- public set focused(val: boolean) {
52
+ set focused(val: boolean) {
29
53
  this._focused = val;
30
54
  this.searchInput.focused = val;
31
55
  }
32
56
 
33
- constructor(options: {
34
- tui: any;
35
- title: string;
36
- allOptions: string[];
37
- theme: Theme;
38
- keybindings: any;
39
- onSelect: (value: string) => void;
40
- onCancel: () => void;
41
- }) {
57
+ constructor(options: SearchableModelSelectorOptions) {
42
58
  this.tui = options.tui;
43
59
  this.title = options.title;
44
60
  this.allOptions = options.allOptions;
@@ -85,7 +101,7 @@ export class SearchableModelSelector implements Component, Focusable {
85
101
  )
86
102
  );
87
103
  const endIndex = Math.min(startIndex + maxVisible, total);
88
- for (let i = startIndex; i < endIndex; i++) {
104
+ for (let i = startIndex; i < endIndex; i += 1) {
89
105
  const item = this.filteredOptions[i];
90
106
  if (i === this.selectedIndex) {
91
107
  lines.push(
@@ -111,44 +127,53 @@ export class SearchableModelSelector implements Component, Focusable {
111
127
  }
112
128
 
113
129
  handleInput(keyData: string): void {
114
- const kb = this.keybindings;
115
- if (kb.matches(keyData, "tui.select.up") || keyData === "\u001b[A") {
116
- if (this.filteredOptions.length > 0) {
117
- this.selectedIndex =
118
- this.selectedIndex === 0
119
- ? this.filteredOptions.length - 1
120
- : this.selectedIndex - 1;
121
- }
122
- this.tui.requestRender();
123
- } else if (
124
- kb.matches(keyData, "tui.select.down") ||
125
- keyData === "\u001b[B"
126
- ) {
127
- if (this.filteredOptions.length > 0) {
128
- this.selectedIndex =
129
- this.selectedIndex === this.filteredOptions.length - 1
130
- ? 0
131
- : this.selectedIndex + 1;
132
- }
133
- this.tui.requestRender();
134
- } else if (
135
- kb.matches(keyData, "tui.select.confirm") ||
136
- keyData === "\n" ||
130
+ if (this.matchesAction(keyData, "tui.select.up", "\u001b[A")) {
131
+ this.moveSelection(-1);
132
+ return;
133
+ }
134
+ if (this.matchesAction(keyData, "tui.select.down", "\u001b[B")) {
135
+ this.moveSelection(1);
136
+ return;
137
+ }
138
+ if (
139
+ this.matchesAction(keyData, "tui.select.confirm", "\n") ||
137
140
  keyData === "\r"
138
141
  ) {
139
142
  if (this.filteredOptions.length > 0) {
140
143
  this.onSelect(this.filteredOptions[this.selectedIndex]);
141
144
  }
142
- } else if (
143
- kb.matches(keyData, "tui.select.cancel") ||
144
- keyData === "\u001b"
145
- ) {
145
+ return;
146
+ }
147
+ if (this.matchesAction(keyData, "tui.select.cancel", "\u001b")) {
146
148
  this.onCancel();
147
- } else {
148
- this.searchInput.handleInput(keyData);
149
- this.selectedIndex = 0;
150
- this.tui.requestRender();
149
+ return;
151
150
  }
151
+ this.searchInput.handleInput(keyData);
152
+ this.selectedIndex = 0;
153
+ this.tui.requestRender();
154
+ }
155
+
156
+ private matchesAction(
157
+ keyData: string,
158
+ action: keyof Keybindings,
159
+ fallback: string
160
+ ) {
161
+ return this.keybindings.matches(keyData, action) || keyData === fallback;
162
+ }
163
+
164
+ private moveSelection(direction: -1 | 1) {
165
+ if (this.filteredOptions.length > 0) {
166
+ const lastIndex = this.filteredOptions.length - 1;
167
+ const nextIndex = this.selectedIndex + direction;
168
+ if (nextIndex < 0) {
169
+ this.selectedIndex = lastIndex;
170
+ } else if (nextIndex > lastIndex) {
171
+ this.selectedIndex = 0;
172
+ } else {
173
+ this.selectedIndex = nextIndex;
174
+ }
175
+ }
176
+ this.tui.requestRender();
152
177
  }
153
178
  }
154
179
 
@@ -178,33 +203,27 @@ export interface AdvisorSettings {
178
203
  }
179
204
 
180
205
  export class AdvisorSettingsSelector implements Component, Focusable {
181
- private selectedRow = 0;
206
+ private selectedRow: number;
182
207
  private contextIndex: number;
183
208
  private effortIndex: number;
184
209
  private readonly settings: AdvisorSettings;
185
210
  private readonly customInput = new Input();
186
- private editingCustom = false;
211
+ private editingCustom: boolean;
187
212
  private _focused = false;
213
+ private readonly options: AdvisorSettingsSelectorOptions;
188
214
 
189
- public get focused() {
215
+ get focused(): boolean {
190
216
  return this._focused;
191
217
  }
192
- public set focused(value: boolean) {
218
+ set focused(value: boolean) {
193
219
  this._focused = value;
194
220
  this.customInput.focused = value && this.editingCustom;
195
221
  }
196
222
 
197
- constructor(
198
- private readonly options: {
199
- tui: any;
200
- theme: Theme;
201
- presets: ContextPreset[];
202
- effortLevels: string[];
203
- initial: AdvisorSettings;
204
- onSave: (settings: AdvisorSettings) => void;
205
- onCancel: () => void;
206
- }
207
- ) {
223
+ constructor(options: AdvisorSettingsSelectorOptions) {
224
+ this.selectedRow = 0;
225
+ this.editingCustom = false;
226
+ this.options = options;
208
227
  this.settings = { ...options.initial };
209
228
  this.contextIndex = Math.max(
210
229
  0,
@@ -235,8 +254,20 @@ export class AdvisorSettingsSelector implements Component, Focusable {
235
254
  this.options.tui.requestRender();
236
255
  }
237
256
 
238
- private currentContext() {
239
- return this.options.presets[this.contextIndex];
257
+ private currentContext(): ContextPreset {
258
+ const preset = this.options.presets.find(
259
+ (item) => item.value === this.settings.contextMaxChars
260
+ );
261
+ return (
262
+ preset ?? {
263
+ description: "Current custom context limit",
264
+ label: String(this.settings.contextMaxChars),
265
+ value: this.settings.contextMaxChars,
266
+ }
267
+ );
268
+ }
269
+ private currentEffort() {
270
+ return this.settings.effort || "Default (Model Default)";
240
271
  }
241
272
  private row(label: string, value: string, index: number) {
242
273
  const { theme } = this.options;
@@ -256,8 +287,8 @@ export class AdvisorSettingsSelector implements Component, Focusable {
256
287
  const track = Array.from({ length: trackWidth }, () => "─");
257
288
  track[positions[this.contextIndex]] = "▲";
258
289
  const labels = Array.from({ length: trackWidth }, () => " ");
259
- for (let index = 0; index < presets.length; index++) {
260
- const label = presets[index].label;
290
+ for (let index = 0; index < presets.length; index += 1) {
291
+ const { label } = presets[index];
261
292
  const start = Math.max(
262
293
  0,
263
294
  Math.min(
@@ -265,7 +296,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
265
296
  positions[index] - Math.floor(label.length / 2)
266
297
  )
267
298
  );
268
- for (let char = 0; char < label.length; char++) {
299
+ for (let char = 0; char < label.length; char += 1) {
269
300
  labels[start + char] = label[char];
270
301
  }
271
302
  }
@@ -273,11 +304,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
273
304
  const onOff = (value: boolean) => (value ? "On" : "Off");
274
305
  const rows = [
275
306
  this.row("Context window", this.currentContext().label, 0),
276
- this.row(
277
- "Advisor reasoning",
278
- this.options.effortLevels[this.effortIndex],
279
- 1
280
- ),
307
+ this.row("Advisor reasoning", this.currentEffort(), 1),
281
308
  this.row("Plan gate", onOff(this.settings.planGate), 2),
282
309
  this.row("Failure gate", onOff(this.settings.failureGate), 3),
283
310
  this.row("Completion gate", onOff(this.settings.completionGate), 4),
@@ -377,15 +404,17 @@ export class AdvisorSettingsSelector implements Component, Focusable {
377
404
  return;
378
405
  }
379
406
  if (this.selectedRow === 16) {
380
- return this.options.onSave({
407
+ this.options.onSave({
381
408
  ...this.settings,
382
409
  contextMaxChars: this.currentContext().value,
383
- effort: this.options.effortLevels[this.effortIndex],
410
+ effort: this.currentEffort(),
384
411
  });
412
+ return;
385
413
  }
386
414
  this.adjust(1);
387
415
  } else if (matchesKey(keyData, Key.escape)) {
388
- return this.options.onCancel();
416
+ this.options.onCancel();
417
+ return;
389
418
  } else {
390
419
  return;
391
420
  }
@@ -402,6 +431,8 @@ export class AdvisorSettingsSelector implements Component, Focusable {
402
431
  this.contextIndex + direction
403
432
  )
404
433
  );
434
+ this.settings.contextMaxChars =
435
+ this.options.presets[this.contextIndex].value;
405
436
  break;
406
437
  case 1:
407
438
  this.effortIndex = Math.max(
@@ -411,6 +442,7 @@ export class AdvisorSettingsSelector implements Component, Focusable {
411
442
  this.effortIndex + direction
412
443
  )
413
444
  );
445
+ this.settings.effort = this.options.effortLevels[this.effortIndex];
414
446
  break;
415
447
  case 2:
416
448
  this.settings.planGate = !this.settings.planGate;
@@ -488,6 +520,8 @@ export class AdvisorSettingsSelector implements Component, Focusable {
488
520
  values[Math.max(0, Math.min(values.length - 1, index + direction))];
489
521
  break;
490
522
  }
523
+ default:
524
+ break;
491
525
  }
492
526
  }
493
527
  }