CLI: improve model selection ux (#4071)

This commit is contained in:
Angela Ning
2025-08-13 14:47:40 -04:00
committed by GitHub
parent a7f158ee11
commit c42da79479
+141 -11
View File
@@ -279,7 +279,146 @@ async fn handle_oauth_configuration(
}
}
/// Dialog for configuring the AI provider and model
/// Interactive model search that truncates the list to improve UX
fn interactive_model_search(models: &[String]) -> Result<String, Box<dyn std::error::Error>> {
const MAX_VISIBLE: usize = 30;
let mut query = String::new();
loop {
let _ = cliclack::clear_screen();
let _ = cliclack::log::info(format!(
"🔍 {} models available. Type to filter.",
models.len()
));
let input: String = cliclack::input("Filtering models, press Enter to search")
.placeholder("e.g., gpt, sonnet, llama, qwen")
.default_input(&query)
.interact::<String>()?;
query = input.trim().to_string();
let filtered: Vec<String> = if query.is_empty() {
models.to_vec()
} else {
let q = query.to_lowercase();
models
.iter()
.filter(|m| m.to_lowercase().contains(&q))
.cloned()
.collect()
};
if filtered.is_empty() {
let _ = cliclack::log::warning("No matching models. Try a different search.");
continue;
}
let mut items: Vec<(String, String, &str)> = filtered
.iter()
.take(MAX_VISIBLE)
.map(|m| (m.clone(), m.clone(), ""))
.collect();
if filtered.len() > MAX_VISIBLE {
items.insert(
0,
(
"__refine__".to_string(),
format!(
"Refine search to see more (showing {} of {} results)",
MAX_VISIBLE,
filtered.len()
),
"Too many matches",
),
);
} else {
items.insert(
0,
(
"__new_search__".to_string(),
"Start a new search...".to_string(),
"Enter a different search term",
),
);
}
let selection = cliclack::select("Select a model:")
.items(&items)
.interact()?;
if selection == "__refine__" {
continue;
} else if selection == "__new_search__" {
query.clear();
continue;
} else {
return Ok(selection);
}
}
}
fn select_model_from_list(
models: &[String],
provider_meta: &goose::providers::base::ProviderMetadata,
) -> Result<String, Box<dyn std::error::Error>> {
const MAX_MODELS: usize = 10;
// Smart model selection:
// If we have more than MAX_MODELS models, show the recommended models with additional search option.
// Otherwise, show all models without search.
if models.len() > MAX_MODELS {
// Get recommended models from provider metadata
let recommended_models: Vec<String> = provider_meta
.known_models
.iter()
.map(|m| m.name.clone())
.filter(|name| models.contains(name))
.collect();
if !recommended_models.is_empty() {
let mut model_items: Vec<(String, String, &str)> = recommended_models
.iter()
.map(|m| (m.clone(), m.clone(), "Recommended"))
.collect();
model_items.insert(
0,
(
"search_all".to_string(),
"Search all models...".to_string(),
"Search complete model list",
),
);
let selection = cliclack::select("Select a model:")
.items(&model_items)
.interact()?;
if selection == "search_all" {
Ok(interactive_model_search(models)?)
} else {
Ok(selection)
}
} else {
Ok(interactive_model_search(models)?)
}
} else {
// just a few models, show all without search for better UX
Ok(cliclack::select("Select a model:")
.items(
&models
.iter()
.map(|m| (m, m.as_str(), ""))
.collect::<Vec<_>>(),
)
.interact()?
.to_string())
}
}
/// Dialog for configuring the A provider and model
pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
// Get global config instance
let config = Config::global();
@@ -427,16 +566,7 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
cliclack::outro(style(e.to_string()).on_red().white())?;
return Ok(false);
}
Ok(Some(models)) => cliclack::select("Select a model:")
.items(
&models
.iter()
.map(|m| (m, m.as_str(), ""))
.collect::<Vec<_>>(),
)
.filter_mode() // enable "fuzzy search" filtering for the list of models
.interact()?
.to_string(),
Ok(Some(models)) => select_model_from_list(&models, provider_meta)?,
Ok(None) => {
let default_model =
std::env::var("GOOSE_MODEL").unwrap_or(provider_meta.default_model.clone());