Skip to content

feat: fuzzy search for the model picker (WebUI + TUI + CLI)#36928

Merged
teknium1 merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:feat/fuzzy-model-picker-30849
Jun 1, 2026
Merged

feat: fuzzy search for the model picker (WebUI + TUI + CLI)#36928
teknium1 merged 3 commits into
NousResearch:mainfrom
kshitijk4poor:feat/fuzzy-model-picker-30849

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds fuzzy search to the model picker across all three surfaces from #30849 — the WebUI dashboard, the TUI, and the CLI curses picker — with consistent ranking everywhere.

Before this: the WebUI used exact-substring matching (g4o wouldn't match gpt-4o); the TUI had no search (arrow keys only); the CLI's curses model picker required scrolling 100+ models with no filter.

Surfaces

Shared scorer (same algorithm in 3 languages/places, kept in sync):

  • ui-tui/src/lib/fuzzy.ts + a logically identical copy at web/src/lib/fuzzy.ts (separate TS packages, no shared module; the TUI copy carries the vitest suite since web has no test runner)
  • a Python port in hermes_cli/curses_ui.py (_fuzzy_score / _filter_indices)

All three rank identically: ordered subsequence match (g4ogpt-4o), quality scoring (exact > prefix > word-boundary > contiguous > scattered), multi-token AND (clad snntclaude-sonnet-4), shorter-id tiebreak.

WebUI (ModelPickerDialog.tsx): ranked fuzzy filter on providers + models; matched characters highlighted via <mark>.

TUI (modelPicker.tsx): type-to-filter on provider + model stages with live ranking; Backspace/Ctrl+U edit, Esc clears-then-back; g/d shortcuts moved to Ctrl+G/Ctrl+D so letters feed the filter.

CLI (curses_ui.py): / opens a type-to-filter prompt in the curses pickers; BACKSPACE edits, Ctrl+U clears, ESC stops search then cancels. Wired into the shared _run_curses_menu driver behind a searchable flag, so both curses_radiolist and curses_single_select get it without per-menu duplication. Enabled on _prompt_model_selection (the canonical picker across the model flows) and the custom-provider model list. Returned values are always original item indices.

Salvage / credit

The CLI curses-search helpers (subsequence matcher, filtered-index mapping, cursor reconciliation, scroll clamping, active-search key handler) and their unit tests are salvaged from #22758 by @counterposition. That PR predates a refactor on main that extracted the curses event loop into a shared _run_curses_menu driver, so a clean cherry-pick of its inline-loop edits wasn't possible — the pure helpers + tests carry over unchanged (committed under the contributor's authorship), and the driver integration is rebuilt on the current surface in a follow-up commit. We also upgraded the contributor's plain subsequence filter to the ranked scorer so the CLI matches the TUI/WebUI ordering.

#22758 also touched the TUI (modelPicker, sessionPicker, skillsHub, agentsOverlay) with a subsequence-only, no-ranking, no-highlight approach; this PR's TUI half supersedes that. #23623 (Telegram model-picker search) is a separate surface, untouched.

Test plan

  • TUI: npm run build ✓, eslint clean ✓, 15 vitest tests pass (src/lib/fuzzy.test.ts)
  • WebUI: tsc -b ✓, vite build ✓, eslint clean ✓
  • CLI: ruff check clean ✓; 14 new pytest tests (test_curses_ui_search.py = contributor's; test_curses_ui_fuzzy_rank.py = ranked scorer); full curses/menu/setup suite green (46 passed); updated two setup-menu tests whose mock signatures predated the new searchable kwarg
  • Cross-surface scorer parity (same inputs, same ranking): g4ogpt-4o, son4claude-sonnet-4, clad snntclaude-sonnet-4, 4ogpt-4o, xyz→no match

Closes #30849

Infographic

fuzzy-model-picker

kshitijk4poor and others added 2 commits June 1, 2026 23:05
Adds fuzzy subsequence matching with quality ranking to the model
pickers, replacing the WebUI's exact-substring filter and giving the
TUI a search where it previously had none.

- New fuzzy scorer (ui-tui/src/lib/fuzzy.ts + an identical copy at
  web/src/lib/fuzzy.ts, since the two are separate TS packages with no
  shared module). Matches a query as an ordered subsequence (so `g4o`
  matches `gpt-4o`), scores by quality (exact > prefix > word-boundary >
  contiguous > scattered) and returns matched character positions for
  highlighting. Multi-token AND semantics (`clad snnt` -> claude-sonnet).
  15 vitest tests cover the algorithm.

- WebUI ModelPickerDialog: ranked fuzzy filter on providers + models;
  matched characters in model rows are highlighted via <mark>.

- TUI modelPicker: type-to-filter on the provider and model stages with
  live ranking. Backspace edits the filter, Ctrl+U clears it, Esc clears
  a non-empty filter before navigating back. Persist-global / disconnect
  shortcuts moved from g/d to Ctrl+G / Ctrl+D so letters feed the filter.

Closes NousResearch#30849
Pure, refactor-independent helpers for type-to-filter search in the
curses single-/radio-select menus: subsequence matching, filtered-index
mapping, cursor reconciliation, scroll clamping, and an active-search
key handler, plus unit tests.

Salvaged from NousResearch#22758 (the curses event loop was since refactored into a
shared driver on main, so the integration is rebuilt in a follow-up
commit; these pure helpers and their tests carry over unchanged).
@kshitijk4poor kshitijk4poor changed the title feat: fuzzy search for the model picker (WebUI + TUI) feat: fuzzy search for the model picker (WebUI + TUI + CLI) Jun 1, 2026
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 1, 2026
Wires the salvaged search helpers into the shared curses menu driver and
turns on type-to-filter for the CLI model pickers (the 100+ model lists
that previously required scrolling).

- Search lives in the shared `_run_curses_menu` driver behind a
  `searchable` flag + `search_labels`, so both `curses_radiolist` and
  `curses_single_select` get it without per-menu duplication. `/` opens
  the filter, BACKSPACE edits, Ctrl+U clears, ESC clears the filter then
  cancels. Returned values are always original item indices.
- `_filter_indices` RANKS matches (best-first) via a Python port of the
  TS scorer in ui-tui/src/lib/fuzzy.ts and web/src/lib/fuzzy.ts. The port
  is byte-identical in score: same per-char bonuses, prefix (+8) and
  exact (+20) bonuses, camelCase/word-boundary detection (matching on the
  lowercased target, boundary on the original case), and the -len*0.01
  length tiebreak — so the CLI, TUI, and WebUI rank results identically.
  A cross-language parity test pins the exact scores.
- `_prompt_model_selection` (the canonical picker across the model flows)
  and the custom-provider model list pass `searchable=True`.
- Split `_decode_menu_key` out of `read_menu_key` so the search loop can
  peek the raw key (catch `/`) before nav decoding.
- ESC during active search now clears the query (restores the full list)
  so a no-match filter can't strand the user; printable-key capture is
  restricted to ASCII to avoid Latin-1 mojibake.
- Update two setup-menu tests whose mock signatures predate the new
  `searchable` kwarg; add ranked-scorer + parity + state-machine tests.
@kshitijk4poor kshitijk4poor force-pushed the feat/fuzzy-model-picker-30849 branch from d9f9879 to 4215ad9 Compare June 1, 2026 18:03
@teknium1 teknium1 merged commit 0fdab53 into NousResearch:main Jun 1, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fuzzy search for models in model picker (WebUI + TUI)

4 participants