Skip to main content

mermaid_cli/tui/state/
model.rs

1//! Model state management
2//!
3//! Handles LLM configuration and identity.
4
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8use crate::models::{Model, ModelConfig, OllamaOptions};
9
10/// Model state - LLM configuration and identity
11pub struct ModelState {
12    pub model: Arc<RwLock<Box<dyn Model>>>,
13    pub model_id: String,
14    pub model_name: String,
15    /// Thinking mode state:
16    /// - Some(true) = model supports thinking, currently enabled
17    /// - Some(false) = model supports thinking, currently disabled
18    /// - None = model does not support thinking (or unknown)
19    pub thinking_enabled: Option<bool>,
20    /// Vision support state:
21    /// - Some(true) = model supports vision
22    /// - Some(false) = model does not support vision (detected from error)
23    /// - None = unknown (optimistic default)
24    pub vision_supported: Option<bool>,
25    /// Temperature from app config (wired through so build_config uses it)
26    pub temperature: f32,
27    /// Max tokens from app config
28    pub max_tokens: usize,
29    /// Ollama-specific hardware options from app config
30    pub ollama_options: OllamaOptions,
31}
32
33impl ModelState {
34    pub fn new(model: Box<dyn Model>, model_id: String) -> Self {
35        let model_name = model.name().to_string();
36        Self {
37            model: Arc::new(RwLock::new(model)),
38            model_id,
39            model_name,
40            thinking_enabled: Some(true),
41            vision_supported: None,
42            temperature: crate::constants::DEFAULT_TEMPERATURE,
43            max_tokens: crate::constants::DEFAULT_MAX_TOKENS,
44            ollama_options: OllamaOptions::default(),
45        }
46    }
47
48    /// Get a reference to the model for reading
49    pub fn model_ref(&self) -> &Arc<RwLock<Box<dyn Model>>> {
50        &self.model
51    }
52
53    /// Toggle thinking mode (only if model supports it)
54    /// Returns the new state, or None if model doesn't support thinking
55    pub fn toggle_thinking(&mut self) -> Option<bool> {
56        match self.thinking_enabled {
57            Some(enabled) => {
58                self.thinking_enabled = Some(!enabled);
59                self.thinking_enabled
60            },
61            None => None, // Model doesn't support thinking, can't toggle
62        }
63    }
64
65    /// Mark model as not supporting thinking
66    /// Called when we get "does not support thinking" error from Ollama
67    pub fn disable_thinking_support(&mut self) {
68        self.thinking_enabled = None;
69    }
70
71    /// Check if thinking is currently active
72    pub fn is_thinking_active(&self) -> bool {
73        self.thinking_enabled == Some(true)
74    }
75
76    /// Build a ModelConfig for API calls using current model state
77    pub fn build_config(&self) -> ModelConfig {
78        let mut config = ModelConfig {
79            model: self.model_id.clone(),
80            thinking_enabled: self.thinking_enabled,
81            temperature: self.temperature,
82            max_tokens: self.max_tokens,
83            ..ModelConfig::default()
84        };
85        // Wire Ollama-specific options from app config
86        if let Some(v) = self.ollama_options.num_gpu {
87            config.set_backend_option("ollama".into(), "num_gpu".into(), v.to_string());
88        }
89        if let Some(v) = self.ollama_options.num_ctx {
90            config.set_backend_option("ollama".into(), "num_ctx".into(), v.to_string());
91        }
92        if let Some(v) = self.ollama_options.num_thread {
93            config.set_backend_option("ollama".into(), "num_thread".into(), v.to_string());
94        }
95        if let Some(v) = self.ollama_options.numa {
96            config.set_backend_option("ollama".into(), "numa".into(), v.to_string());
97        }
98        config
99    }
100}