feat(tui): wire Model browser to PluginBridge for search & install #105

Merged
claude-desktop merged 2 commits from tui/model-browser-wire-89 into main 2026-04-12 15:37:00 +00:00
Collaborator

Summary

  • Wire CivitAI/HuggingFace tabs to search_all_models + search_all_loras via tokio::spawn, with results delivered through an mpsc channel and drained on each Tick event
  • Wire Local tab to list_models/list_loras with client-side query filtering
  • Wire Enter key to install_model on the selected result card via the Loom extension
  • Debounce, pagination (n/N), source switching, and IP-Adapter filter all trigger fresh searches through the same async pipeline
  • Status line shows search/install progress and errors
  • New unit tests for filename_from_url, subdir_for_type, drain_results, and source-switch re-trigger

Closes charles/loom#89

Test plan

  • cargo clippy -p loom-tui -- -D warnings clean
  • cargo test -p loom-tui passes (118 tests)
  • Manual: launch TUI with a backend running, navigate to Model Browser, type a query, verify results populate after debounce
  • Manual: press Enter on a result with a download URL, verify install status appears
  • Manual: switch tabs (1/2/3/Tab), verify results clear and re-search fires
  • Manual: press n/N to paginate, verify fresh results load

🤖 Generated with Claude Code

## Summary - Wire CivitAI/HuggingFace tabs to `search_all_models` + `search_all_loras` via `tokio::spawn`, with results delivered through an `mpsc` channel and drained on each Tick event - Wire Local tab to `list_models`/`list_loras` with client-side query filtering - Wire Enter key to `install_model` on the selected result card via the Loom extension - Debounce, pagination (n/N), source switching, and IP-Adapter filter all trigger fresh searches through the same async pipeline - Status line shows search/install progress and errors - New unit tests for `filename_from_url`, `subdir_for_type`, `drain_results`, and source-switch re-trigger Closes charles/loom#89 ## Test plan - [x] `cargo clippy -p loom-tui -- -D warnings` clean - [x] `cargo test -p loom-tui` passes (118 tests) - [ ] Manual: launch TUI with a backend running, navigate to Model Browser, type a query, verify results populate after debounce - [ ] Manual: press Enter on a result with a download URL, verify install status appears - [ ] Manual: switch tabs (1/2/3/Tab), verify results clear and re-search fires - [ ] Manual: press n/N to paginate, verify fresh results load 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Connect the scaffolded ModelBrowserScreen to live PluginBridge calls:

- CivitAI/HuggingFace tabs dispatch search_all_models + search_all_loras
  via tokio::spawn, with results delivered through an mpsc channel and
  drained on each Tick event
- Local tab uses list_models/list_loras with client-side query filtering
- Enter key triggers install_model on the selected result card
- Debounce, pagination (n/N), and IP-Adapter filter all fire fresh
  searches through the same async pipeline
- Status line shows search/install progress and errors
- New tests for filename_from_url, subdir_for_type, drain_results, and
  source-switch re-trigger

Closes charles/loom#89

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Collaborator

Review — PR #105: Model browser wired to PluginBridge for search & install

Well-structured async search pattern. Debounce + mpsc channel + drain-on-tick is the right approach for a TUI.

Concern: filename_from_url edge case

fn filename_from_url(url: &str) -> Option<String> {
    url.rsplit('/').next()
        .map(|seg| seg.split('?').next().unwrap_or(seg))
        .filter(|s| !s.is_empty())
        .map(String::from)
}

CivitAI download URLs look like https://civitai.com/api/download/models/12345?type=Model&format=SafeTensor. This returns "12345" as the filename, which loses the extension. The filename field has a fallback (format!("{}.safetensors", card.name.replace(' ', "_"))) so it's not broken, but the filename_from_url result is misleading. Consider checking if the result has an extension before using it.

Minor

  • run_remote_search always searches both models AND LoRAs and merges them. For the IP-Adapter filter case, you probably don't want LoRA results mixed in. Consider skipping the LoRA search when ip_adapter_only is true.
  • install_selected fires-and-forgets the install task. There's no way to track progress or cancel. The status line shows "installing…" but never updates to "installed" unless another BridgeResult arrives. Consider adding an InstallComplete variant.
  • #[allow(clippy::cast_possible_truncation)] on the entire render function is broad. Consider narrowing it to the specific cast.

Good test coverage.

## Review — PR #105: Model browser wired to PluginBridge for search & install **Well-structured async search pattern.** Debounce + mpsc channel + drain-on-tick is the right approach for a TUI. ### Concern: `filename_from_url` edge case ```rust fn filename_from_url(url: &str) -> Option<String> { url.rsplit('/').next() .map(|seg| seg.split('?').next().unwrap_or(seg)) .filter(|s| !s.is_empty()) .map(String::from) } ``` CivitAI download URLs look like `https://civitai.com/api/download/models/12345?type=Model&format=SafeTensor`. This returns `"12345"` as the filename, which loses the extension. The `filename` field has a fallback (`format!("{}.safetensors", card.name.replace(' ', "_"))`) so it's not broken, but the `filename_from_url` result is misleading. Consider checking if the result has an extension before using it. ### Minor - `run_remote_search` always searches both models AND LoRAs and merges them. For the IP-Adapter filter case, you probably don't want LoRA results mixed in. Consider skipping the LoRA search when `ip_adapter_only` is true. - `install_selected` fires-and-forgets the install task. There's no way to track progress or cancel. The status line shows "installing…" but never updates to "installed" unless another `BridgeResult` arrives. Consider adding an `InstallComplete` variant. - `#[allow(clippy::cast_possible_truncation)]` on the entire render function is broad. Consider narrowing it to the specific cast. Good test coverage.
charles force-pushed tui/model-browser-wire-89 from fddb19ddc8 to a09a1bafa5 2026-04-12 14:47:41 +00:00 Compare
claude-desktop changed target branch from tui/image-ctx-85 to main 2026-04-12 15:36:29 +00:00
claude-desktop deleted branch tui/model-browser-wire-89 2026-04-12 15:37:01 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/loom!105
No description provided.