doum_cli/cli/
switch.rs

1use crate::cli::ui::prompt_text_input;
2use crate::system::DoumError::Config;
3use crate::system::error::DoumResult;
4use crate::system::{load_config, save_config};
5use dialoguer::Select;
6use rust_embed::RustEmbed;
7use serde::Deserialize;
8
9#[derive(RustEmbed)]
10#[folder = "static/presets/"]
11struct ModelPresets;
12
13#[derive(Debug, Deserialize, Clone)]
14pub struct ModelInfo {
15    pub id: String,
16    pub name: String,
17    pub description: String,
18}
19
20#[derive(Debug, Deserialize)]
21struct ModelList {
22    models: Vec<ModelInfo>,
23}
24
25/// Load model presets for a provider
26pub fn load_presets(provider: &str) -> Vec<ModelInfo> {
27    let filename = format!("{}.toml", provider);
28
29    if let Some(content) = ModelPresets::get(&filename)
30        && let Ok(data) = std::str::from_utf8(content.data.as_ref())
31        && let Ok(list) = toml::from_str::<ModelList>(data)
32    {
33        return list.models;
34    }
35
36    vec![]
37}
38
39pub fn handle_switch_command(provider: Option<String>, model: Option<String>) -> DoumResult<()> {
40    match (provider, model) {
41        // doum switch openai gpt-5
42        (Some(prov), Some(mdl)) => {
43            switch_provider_and_model(&prov, &mdl)?;
44        }
45        // doum interactive selection
46        (None, None) => {
47            select_provider_and_model()?;
48        }
49        // doum switch openai (invalid)
50        _ => {
51            return Err(Config(
52                "Usage: doum switch [provider] [model] or just doum switch".to_string(),
53            ));
54        }
55    }
56
57    Ok(())
58}
59
60/// Interactive provider/model selection
61fn select_provider_and_model() -> DoumResult<()> {
62    let providers = vec!["openai", "anthropic"];
63
64    // Build menu items
65    let mut menu_items = Vec::new();
66    let mut mapping = Vec::new(); // (provider, model_id)
67
68    for provider in &providers {
69        let models = load_presets(provider);
70        for model in models {
71            menu_items.push(format!(
72                "{} - {} | {}",
73                provider.to_uppercase(),
74                model.name,
75                model.description
76            ));
77            mapping.push((provider.to_string(), model.id.clone()));
78        }
79
80        // Add custom option
81        menu_items.push(format!(
82            "{} - Custom (enter manually)",
83            provider.to_uppercase()
84        ));
85        mapping.push((provider.to_string(), "custom".to_string()));
86    }
87
88    let selection = Select::new()
89        .with_prompt("Select provider and model")
90        .items(&menu_items)
91        .default(0)
92        .interact()?;
93
94    let (provider, model_id) = &mapping[selection];
95
96    let model = if model_id == "custom" {
97        prompt_text_input("Enter custom model name", None)?
98    } else {
99        model_id.clone()
100    };
101
102    switch_provider_and_model(provider, &model)?;
103
104    Ok(())
105}
106
107/// Switch provider and model
108fn switch_provider_and_model(provider: &str, model: &str) -> DoumResult<()> {
109    let mut config = load_config()?;
110
111    // Validate provider
112    if provider != "openai" && provider != "anthropic" {
113        return Err(Config(format!(
114            "Unknown provider: {}. Available: openai, anthropic",
115            provider
116        )));
117    }
118
119    config.llm.provider = provider.to_string();
120    config.llm.model = model.to_string();
121
122    save_config(&config)?;
123
124    println!("✅ Switched to {} - {}", provider, model);
125
126    Ok(())
127}