@tychenjiajun/pi-voice 0.5.0 → 0.6.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
@@ -116,6 +116,13 @@ When FFmpeg is unavailable, `transcribe_file` reports platform-specific guidance
116
116
 
117
117
  ## Changelog
118
118
 
119
+ ### 0.6.0
120
+
121
+ **Changed — Fuzzy model search**
122
+
123
+ - 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.
124
+ - 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.
125
+
119
126
  ### 0.5.0
120
127
 
121
128
  **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.6.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