@xynogen/pix-models 0.1.21 → 0.1.23

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/models.ts +48 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-models",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Pi extension — enhanced /models picker with BenchLM ranks",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -40,7 +40,7 @@
40
40
  "dependencies": {
41
41
  "@xynogen/pix-data": "^0.4.0",
42
42
  "@xynogen/pix-pretty": "^1.7.9",
43
- "@xynogen/pix-runtime": "^0.2.0"
43
+ "@xynogen/pix-runtime": "^0.3.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "*",
package/src/models.ts CHANGED
@@ -20,7 +20,13 @@ import {
20
20
  } from "@earendil-works/pi-tui";
21
21
  import { benchScoreColor, lookupBenchmark, lookupModelsDev } from "@xynogen/pix-data";
22
22
  import { icon } from "@xynogen/pix-pretty/icon-catalog";
23
- import { frameLines, modalWidth } from "@xynogen/pix-pretty/modal-frame";
23
+ import {
24
+ frameModal,
25
+ MIN_MODAL_HEIGHT,
26
+ ModalPager,
27
+ modalWidth,
28
+ terminalModalHeight,
29
+ } from "@xynogen/pix-pretty/modal-frame";
24
30
  import { patchOutBuiltinModelCommand } from "./patch-builtin";
25
31
 
26
32
  // ─── Pure logic (exported for tests) ─────────────────────────────────────────
@@ -271,7 +277,7 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
271
277
  // items built inside the custom() factory so we have theme access for colors
272
278
 
273
279
  const result = await ctx.ui.custom<string | null>(
274
- (tui, theme, _kb, done) => {
280
+ (tui, theme, kb, done) => {
275
281
  const accent = "accent";
276
282
 
277
283
  // Find max rank width across all benchmarked rows for # padding
@@ -322,7 +328,9 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
322
328
  rankPrefix = mute("#") + mute(dash);
323
329
  }
324
330
  // Display model id only; m.provider is routing provider, not part of id.
325
- const idColored = theme.fg(accent, m.id);
331
+ // Color the name by bench score so high-scoring models visually pop.
332
+ const nameColor = bench ? benchScoreColor(bench.overallScore) : accent;
333
+ const idColored = theme.fg(nameColor, m.id);
326
334
  const label = `${marker} ${rankPrefix} ${idColored}`;
327
335
 
328
336
  // Description: ctx · cost · score stars
@@ -373,7 +381,7 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
373
381
  const search = new Input();
374
382
  const list = new SelectList(
375
383
  items,
376
- Math.min(items.length, 14),
384
+ Math.max(1, items.length),
377
385
  {
378
386
  selectedPrefix: (t) => theme.fg(accent, t),
379
387
  selectedText: (t) => theme.fg(accent, t),
@@ -418,6 +426,7 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
418
426
  // We seed it from the getter, then advance it in lock-step with each
419
427
  // setThinkingLevel() call and reconcile back to the getter when present.
420
428
  let localLevel = pi.getThinkingLevel?.() ?? "";
429
+ const pager = new ModalPager();
421
430
  const thinkLine = () => {
422
431
  const live = pi.getThinkingLevel?.();
423
432
  const resolved = live ?? localLevel;
@@ -434,30 +443,48 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
434
443
  render(w: number) {
435
444
  const mw = modalWidth(w);
436
445
  const inner = mw - 4; // CHROME = 2 border + 2 padding
437
- const lines: string[] = [
438
- theme.fg(accent, theme.bold(`${icon("picker.model")} Select model`)),
439
- theme.fg("dim", "context · pricing · coding rank & score from modelgrep.com"),
440
- thinkLine(),
441
- theme.fg("muted", "Search:"),
442
- ...search.render(inner),
443
- ...list.render(inner),
444
- theme.fg(
445
- "dim",
446
- "fuzzy search · ↑↓ navigate · shift+←/→ thinking · enter select · esc cancel",
447
- ),
448
- ];
449
- return frameLines({
446
+ const result = frameModal({
450
447
  width: mw,
451
- lines,
448
+ maxHeight: terminalModalHeight(tui.terminal.rows),
449
+ minHeight: MIN_MODAL_HEIGHT,
450
+ header: [
451
+ theme.fg(accent, theme.bold(`${icon("picker.model")} Select model`)),
452
+ theme.fg("dim", "context · pricing · coding rank & score from modelgrep.com"),
453
+ thinkLine(),
454
+ theme.fg("muted", "Search:"),
455
+ ...search.render(inner),
456
+ ],
457
+ body: list.render(inner),
458
+ selectedBodyRange: (() => {
459
+ const internal = list as unknown as { selectedIndex: number };
460
+ return pager.selectedRange({
461
+ start: internal.selectedIndex,
462
+ end: internal.selectedIndex + 1,
463
+ });
464
+ })(),
465
+ footer: [
466
+ theme.fg(
467
+ "dim",
468
+ "fuzzy search · ↑↓ navigate · ←→/PgUp/PgDn inspect · shift+←/→ thinking · enter select · esc cancel",
469
+ ),
470
+ ],
471
+ bodyOffset: pager.bodyOffset,
452
472
  color: (s) => theme.fg(accent, s),
453
473
  bg: (s) => theme.bg("customMessageBg", s),
474
+ fg: (s) => theme.fg("text", s),
454
475
  });
476
+ pager.sync(result);
477
+ return result.lines;
455
478
  },
456
479
  invalidate() {
457
480
  list.invalidate();
458
481
  search.invalidate();
459
482
  },
460
483
  handleInput(data: string) {
484
+ if (pager.handleInput(data, kb, true)) {
485
+ tui.requestRender();
486
+ return;
487
+ }
461
488
  // Detect keys via pi-tui's own parser — the same recognition
462
489
  // SelectList uses. Arrows arrive as named keys ("up"/"down"),
463
490
  // not raw escape sequences, so string-equality checks fail.
@@ -480,17 +507,19 @@ async function showEnhancedPicker(pi: ExtensionAPI, ctx: ExtensionContext): Prom
480
507
  return;
481
508
  } else if (isNav || matchesKey(data, "enter")) {
482
509
  list.handleInput?.(data);
510
+ if (isNav) pager.followSelection();
483
511
  } else if (matchesKey(data, "escape")) {
484
512
  done(null);
485
513
  } else {
486
514
  search.handleInput?.(data);
487
515
  applyFuzzy(search.getValue?.() ?? "");
516
+ pager.followSelection();
488
517
  }
489
518
  list.invalidate();
490
519
  },
491
520
  };
492
521
  },
493
- { overlay: true },
522
+ { overlay: true, overlayOptions: { maxHeight: "80%" } },
494
523
  );
495
524
 
496
525
  if (!result) return;