Skip to main content

netget/cli/
model_select.rs

1//! Startup model resolution shared by the interactive UIs (rolling TUI and
2//! full-screen dashboard): decide which model to use from args/settings,
3//! validate or auto-select it, and produce the human-readable lines the UI
4//! should show about the outcome.
5
6use anyhow::Result;
7use tracing::{error, info, warn};
8
9use crate::settings::Settings;
10
11/// Outcome of startup model resolution.
12pub struct StartupModel {
13    /// Base URL of the model backend (Ollama or OpenAI-compatible).
14    pub base_url: String,
15    /// The selected model; empty string when none is available (interactive
16    /// mode continues and the user picks one later).
17    pub model: String,
18    /// Lines the UI should surface about how the model was chosen.
19    pub messages: Vec<String>,
20}
21
22/// Resolve the startup model exactly as the rolling TUI always has: args
23/// override settings; OpenAI mode trusts the pre-validated model; Ollama mode
24/// validates or auto-selects, tolerating an unavailable Ollama.
25pub async fn resolve_startup_model(
26    args: &super::Args,
27    settings: &Settings,
28) -> Result<StartupModel> {
29    let configured_model = args.model.clone().or(settings.model.clone());
30
31    let base_url = args
32        .openai_url
33        .as_deref()
34        .or(args.ollama_url.as_deref())
35        .unwrap_or("http://localhost:11434")
36        .to_string();
37
38    if args.openai_url.is_some() {
39        // OpenAI mode: model was already validated as required in create_llm_client
40        let model = configured_model.unwrap_or_default();
41        let messages = vec![format!("✓  Using OpenAI-compatible backend: {}", base_url)];
42        return Ok(StartupModel {
43            base_url,
44            model,
45            messages,
46        });
47    }
48
49    match crate::llm::select_or_validate_model(configured_model.clone(), true, &base_url).await {
50        Ok(Some(model)) => {
51            info!("✓  Using model: {}", model);
52            let messages = if let Some(ref config_model) = configured_model {
53                if config_model == &model {
54                    info!("✓  Using configured model: {}", model);
55                    vec![]
56                } else {
57                    info!(
58                        "⚠  Configured model '{}' not found, auto-selected: {}",
59                        config_model, model
60                    );
61                    vec![]
62                }
63            } else {
64                vec![
65                    format!(
66                        "⚠  No model configured, auto-selected: {} (largest/most recent)",
67                        model
68                    ),
69                    "   To set a different model, use: /model or edit ~/.netget settings"
70                        .to_string(),
71                ]
72            };
73            Ok(StartupModel {
74                base_url,
75                model,
76                messages,
77            })
78        }
79        Ok(None) => {
80            warn!("⚠  No model selected. Use /model command to select one.");
81            let messages = vec![
82                "✗  Ollama is not available or no models found.".to_string(),
83                "   Please ensure Ollama is running: https://ollama.ai".to_string(),
84                "   Use `/model` to list and select a model once Ollama is running.".to_string(),
85            ];
86            Ok(StartupModel {
87                base_url,
88                model: String::new(),
89                messages,
90            })
91        }
92        Err(e) => {
93            error!("Failed to initialize model: {}", e);
94            eprintln!("✗  Failed to initialize model: {}", e);
95            Err(e)
96        }
97    }
98}