feat(command-palette): add fuzzy model/provider filtering, expose ModelPaletteEntry, and show active model with provider in UI header
- Introduce `ModelPaletteEntry` and re‑export it for external use. - Extend `CommandPalette` with dynamic sources (models, providers) and methods to refresh suggestions based on `:model` and `:provider` prefixes. - Implement fuzzy matching via `match_score` and subsequence checks for richer suggestion ranking. - Add `provider` command spec and completions. - Update UI to display “Model (Provider)” in the header and use the new active model label helper. - Wire catalog updates throughout `ChatApp` (model palette entries, command palette refresh on state changes, model picker integration).
This commit is contained in:
@@ -80,6 +80,10 @@ const COMMANDS: &[CommandSpec] = &[
|
||||
keyword: "model",
|
||||
description: "Select a model",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "provider",
|
||||
description: "Switch active provider",
|
||||
},
|
||||
CommandSpec {
|
||||
keyword: "model info",
|
||||
description: "Show detailed information for a model",
|
||||
@@ -177,7 +181,6 @@ pub fn suggestions(input: &str) -> Vec<String> {
|
||||
if trimmed.is_empty() {
|
||||
return default_suggestions();
|
||||
}
|
||||
|
||||
COMMANDS
|
||||
.iter()
|
||||
.filter_map(|spec| {
|
||||
@@ -189,3 +192,52 @@ pub fn suggestions(input: &str) -> Vec<String> {
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn match_score(candidate: &str, query: &str) -> Option<(usize, usize)> {
|
||||
let query = query.trim();
|
||||
if query.is_empty() {
|
||||
return Some((usize::MAX, candidate.len()));
|
||||
}
|
||||
|
||||
let candidate_normalized = candidate.trim().to_lowercase();
|
||||
if candidate_normalized.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let query_normalized = query.to_lowercase();
|
||||
|
||||
if candidate_normalized == query_normalized {
|
||||
Some((0, candidate.len()))
|
||||
} else if candidate_normalized.starts_with(&query_normalized) {
|
||||
Some((1, candidate.len()))
|
||||
} else if let Some(pos) = candidate_normalized.find(&query_normalized) {
|
||||
Some((2, pos))
|
||||
} else if is_subsequence(&candidate_normalized, &query_normalized) {
|
||||
Some((3, candidate.len()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_subsequence(text: &str, pattern: &str) -> bool {
|
||||
if pattern.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut pattern_chars = pattern.chars();
|
||||
let mut current = match pattern_chars.next() {
|
||||
Some(ch) => ch,
|
||||
None => return true,
|
||||
};
|
||||
|
||||
for ch in text.chars() {
|
||||
if ch == current {
|
||||
match pattern_chars.next() {
|
||||
Some(next_ch) => current = next_ch,
|
||||
None => return true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user