@tychenjiajun/pi-voice 0.5.0 → 0.7.0

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 CHANGED
@@ -64,7 +64,7 @@ After ASR produces a transcript, Pi Voice can refine it with the same model (and
64
64
  Set it up in `/voice-settings` → **Post-processing** → **On**. You then choose the model:
65
65
 
66
66
  - **Pi's current model** (default) — whichever model the agent is using, exactly like compaction;
67
- - **a different model** — any model from Pi's registry, using that provider's configured authentication.
67
+ - **a different model** — any model from Pi's registry, using that provider's configured authentication. The picker is searchable: type a keyword (a model name or part of `provider/id`) to filter the list, `Enter` selects the highlighted match, and `Esc` clears the search.
68
68
 
69
69
  Pi Voice makes a small nested request through Pi's model registry, so it automatically reuses Pi's model definitions and API keys (or OAuth). No base URL, key, or timeout is stored in Pi Voice settings.
70
70
 
@@ -116,6 +116,21 @@ When FFmpeg is unavailable, `transcribe_file` reports platform-specific guidance
116
116
 
117
117
  ## Changelog
118
118
 
119
+ ### 0.7.0
120
+
121
+ **Added — Searchable post-processing model picker**
122
+
123
+ - `/voice-settings` → **Post-processing** → model now has a search box, so a registry with a thousand-plus models can be narrowed by typing instead of arrowing through the whole list.
124
+ - The search uses the same fuzzy matcher as the transcription model picker (`fuzzyFilter` from `@earendil-works/pi-tui`) and matches the model name as well as `provider/id`, so `wspr` finds `openai/whisper-1` and `claude` finds `anthropic/claude-opus-4-5`.
125
+ - Results are sorted by match quality with the cursor on the best match (Enter picks it), `Esc` clears the search and restores the full list, and the subtitle reminds you that typing filters the models.
126
+
127
+ ### 0.6.0
128
+
129
+ **Changed — Fuzzy model search**
130
+
131
+ - The model picker now uses the same fuzzy filter Pi's own model selector uses (`fuzzyFilter` from `@earendil-works/pi-tui`), so typing partial or abbreviated names finds matches that exact substring search could not — e.g. `wspr` matches `whisper-medium`, and `pkrt` matches `parakeet`. Results within each section are also sorted by match quality, putting the best-scoring model at the top.
132
+ - While a search query is active the cursor stays on the best match instead of following a model that may have moved or dropped out of the results, matching Pi's model selector interaction.
133
+
119
134
  ### 0.5.0
120
135
 
121
136
  **Changed — Exclusive local / remote modes (no fallback)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tychenjiajun/pi-voice",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Speech-to-text for Pi (local model or OpenAI-compatible remote ASR, compaction-style post-processing)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,6 +14,7 @@ import {
14
14
  } from "@earendil-works/pi-tui";
15
15
  import {
16
16
  CATALOG_MODELS,
17
+ catalogModelSearchText,
17
18
  canonicalLanguage,
18
19
  languageIdentity,
19
20
  displayLanguage,
@@ -32,7 +33,6 @@ import {
32
33
  import {
33
34
  MANUAL_LANGUAGE_TAG,
34
35
  ON_DISK_LABEL,
35
- matchesCatalogSearch,
36
36
  modelDetailText,
37
37
  modelTableLayout,
38
38
  modelTableRow,
@@ -576,14 +576,19 @@ export class CatalogModelPicker extends Container implements Focusable {
576
576
 
577
577
  // The sectioned list: models on disk first, then the ranked catalog with
578
578
  // each of them left out, so a model is listed once. A search filters each
579
- // section in place, in its own order, and reaches the folded models too:
580
- // while a query is on they are a section of their own, so a match there
581
- // says why it was folded.
579
+ // section in place by fuzzy match quality (best matches first) and reaches
580
+ // the folded models too: while a query is on they are a section of their
581
+ // own, so a match there says why it was folded.
582
582
  private buildRows(query: string): ListRow[] {
583
583
  const downloaded = this.downloadedModels();
584
584
  const onDisk = new Set(downloaded.map((model) => model.id));
585
+ // fuzzyFilter is the same matcher Pi's own model selector uses: each
586
+ // token's characters must appear in order but not consecutively, so typing
587
+ // "wspr" finds "whisper". It also re-sorts each section by match quality so
588
+ // the best-scoring match is at the top, which makes a 1000+ model catalog
589
+ // navigable by typing rather than scrolling.
585
590
  const matching = (models: readonly CatalogModel[]) =>
586
- query ? models.filter((model) => matchesCatalogSearch(model, query)) : models;
591
+ query ? fuzzyFilter([...models], query, catalogModelSearchText) : models;
587
592
  const keep = (models: readonly CatalogModel[]) =>
588
593
  matching(models).filter((model) => !onDisk.has(model.id));
589
594
  const rows: ListRow[] = [];
@@ -688,9 +693,13 @@ export class CatalogModelPicker extends Container implements Focusable {
688
693
  this.body.addChild(this.footer);
689
694
 
690
695
  const query = this.search.getValue().trim();
691
- // Selecting a model reorders the Downloaded section, so the cursor
692
- // follows the highlighted model rather than its old row number.
693
- const highlightedId = this.highlightedModel()?.id;
696
+ // Selecting a model reorders the Downloaded section, so the cursor follows
697
+ // the highlighted model rather than its old row number — but only when not
698
+ // searching. While a query is on, handleInput resets selectedIndex to 0
699
+ // on each keystroke, and fuzzyFilter re-sorts every section by match
700
+ // quality, so the best match should stay at the top rather than chasing a
701
+ // model that may have moved or dropped out of the results.
702
+ const highlightedId = query ? undefined : this.highlightedModel()?.id;
694
703
  this.rows = this.buildRows(query);
695
704
  this.filtered = this.rows.flatMap((row) => (row.type === "model" ? [row.model] : []));
696
705
  const followed = highlightedId === undefined
@@ -389,7 +389,7 @@ function samePolishModel(
389
389
  }
390
390
 
391
391
  /** Pick Pi's current model (default) or any model with configured auth. */
392
- async function choosePolishModel(
392
+ export async function choosePolishModel(
393
393
  ctx: ExtensionContext,
394
394
  current: PolishModelRef | undefined,
395
395
  ): Promise<"current" | PolishModelRef | undefined> {
@@ -423,7 +423,14 @@ async function choosePolishModel(
423
423
  keybindings,
424
424
  choices,
425
425
  current ? polishModelKey(current) : "current",
426
- { title: "Post-processing model", cancelLabel: "back" },
426
+ {
427
+ title: "Post-processing model",
428
+ subtitle: "Type to filter models",
429
+ // The registry can hold a thousand-plus models; arrowing through them
430
+ // is unusable, so the list is fuzzy-searchable like every model picker.
431
+ searchable: true,
432
+ cancelLabel: "back",
433
+ },
427
434
  done,
428
435
  ),
429
436
  );