Skip to main content

offline_intelligence/engine_management/
mod.rs

1//! Engine Management System
2//!
3//! Provides comprehensive llama.cpp engine lifecycle management including:
4//! - Hardware capability detection and analysis
5//! - Engine registry and metadata storage
6//! - Download from official llama.cpp releases
7//! - Local engine storage management
8//! - Automatic engine selection based on hardware
9//! - Cross-platform compatibility (Windows, macOS, Linux)
10
11pub mod registry;
12pub mod downloader;
13pub mod analyzer;
14pub mod download_progress;
15
16pub use registry::{EngineRegistry, EngineInfo, EngineStatus, AccelerationType};
17pub use downloader::{EngineDownloader, EngineSource};
18pub use analyzer::{HardwareAnalyzer, HardwareProfile};
19pub use download_progress::{EngineDownloadProgressTracker, EngineDownloadProgress, EngineDownloadStatus};
20
21use anyhow::Result;
22use std::sync::Arc;
23use tokio::sync::RwLock;
24
25use crate::config::Config;
26use crate::model_runtime::platform_detector::HardwareCapabilities;
27
28/// Main engine management service
29pub struct EngineManager {
30    pub registry: Arc<RwLock<EngineRegistry>>,
31    pub downloader: Arc<EngineDownloader>,
32    pub analyzer: Arc<HardwareAnalyzer>,
33    pub hardware_capabilities: HardwareCapabilities,
34}
35
36impl EngineManager {
37    pub fn new() -> Result<Self> {
38        let hardware_capabilities = HardwareCapabilities::detect();
39        let analyzer = Arc::new(HardwareAnalyzer::new(hardware_capabilities.clone()));
40        let registry = Arc::new(RwLock::new(EngineRegistry::new()?));
41        let downloader = Arc::new(EngineDownloader::new());
42
43        Ok(Self {
44            registry,
45            downloader,
46            analyzer,
47            hardware_capabilities,
48        })
49    }
50
51    /// Initialize the engine manager and ensure a suitable engine is available
52    pub async fn initialize(&self, _cfg: &Config) -> Result<bool> {
53        // Scan for existing engines and populate registry
54        // This also refreshes available engines from official sources
55        {
56            let mut registry = self.registry.write().await;
57            registry.scan_installed_engines(&self.hardware_capabilities).await?;
58        }
59
60        // Check if we have any installed engines at all (first run detection)
61        let installed_engines_count = self.registry.read().await.installed_engines.len();
62
63        if installed_engines_count == 0 {
64            tracing::info!("First run detected - no engines installed, automatically downloading most compatible engine");
65
66            // Block startup for up to 600 seconds (10 minutes) waiting for engine download
67            // This ensures first-run experience completes engine setup before allowing app usage
68            match tokio::time::timeout(
69                std::time::Duration::from_secs(600),
70                self.download_suitable_engine()
71            ).await {
72                Ok(Ok(engine)) => {
73                    tracing::info!("✅ Engine downloaded successfully on first run: {}", engine.name);
74                    // Set newly downloaded engine as default
75                    let mut reg = self.registry.write().await;
76                    if let Some(first_engine_id) = reg.installed_engines.keys().next().cloned() {
77                        reg.set_default_engine(&first_engine_id)?;
78                    }
79                    return Ok(true);
80                }
81                Ok(Err(e)) => {
82                    tracing::warn!("⚠️ Engine download failed: {}. App will continue but models won't work until engine is downloaded.", e);
83                    return Ok(false); // Return false to signal engine unavailable
84                }
85                Err(_) => {
86                    tracing::warn!("⚠️ Engine download timed out after 600 seconds (10 minutes). App will continue with background retry.");
87                    return Ok(false);
88                }
89            }
90        } else {
91            // Check if we have a suitable installed engine for current hardware
92            let has_suitable_engine = self.check_suitable_engine().await?;
93
94            if !has_suitable_engine {
95                tracing::info!("No suitable engine found for current hardware configuration");
96                tracing::info!("Attempting to download most compatible engine");
97
98                match self.download_suitable_engine().await {
99                    Ok(engine) => {
100                        tracing::info!("Automatically installed engine: {}", engine.name);
101                        // Set the newly installed engine as default
102                        self.registry.write().await.set_default_engine(&engine.id)?;
103                    }
104                    Err(e) => {
105                        tracing::warn!("Failed to automatically install suitable engine: {}", e);
106                        // Still return true - the manager is functional, just no suitable engines installed
107                        // Users can download engines through the UI
108                    }
109                }
110            } else {
111                // Set the best available engine as default
112                self.select_best_engine().await?;
113            }
114        }
115
116        Ok(true)
117    }
118
119    /// Check if we have an engine suitable for current hardware
120    pub async fn check_suitable_engine(&self) -> Result<bool> {
121        let registry = self.registry.read().await;
122        let suitable_engines = registry.get_compatible_engines(&self.hardware_capabilities);
123        Ok(!suitable_engines.is_empty())
124    }
125
126    /// Select and set the best engine for current hardware as default
127    pub async fn select_best_engine(&self) -> Result<Option<EngineInfo>> {
128        let mut registry = self.registry.write().await;
129        let best_engine = registry.select_best_compatible_engine(&self.hardware_capabilities);
130
131        if let Some(engine) = &best_engine {
132            registry.set_default_engine(&engine.id)?;
133            tracing::info!("Selected engine: {} for hardware: {:?}",
134                engine.name, self.hardware_capabilities);
135        }
136
137        Ok(best_engine)
138    }
139
140    /// Download and install a suitable engine for current hardware
141    pub async fn download_suitable_engine(&self) -> Result<EngineInfo> {
142        // Check if another download is already in progress
143        let can_start = {
144            let registry = self.registry.read().await;
145            registry.mark_download_started()
146        };
147
148        if !can_start {
149            return Err(anyhow::anyhow!("Another engine download is already in progress"));
150        }
151
152        let recommended_engine = {
153            let registry = self.registry.read().await;
154            registry.get_recommended_engine(&self.hardware_capabilities)
155                .ok_or_else(|| anyhow::anyhow!("No recommended engine found for current hardware"))?
156        };
157
158        tracing::info!("Downloading recommended engine: {}", recommended_engine.name);
159
160        // Update registry to show this engine is being downloaded
161        {
162            let mut registry = self.registry.write().await;
163            let mut engine_to_download = recommended_engine.clone();
164            engine_to_download.status = EngineStatus::Downloading;
165            registry.add_installed_engine(engine_to_download).await?;
166        }
167
168        // Perform the download
169        let download_result = self.downloader.download_engine(&recommended_engine).await;
170
171        // Mark download as finished regardless of success/failure
172        {
173            let registry = self.registry.read().await;
174            registry.mark_download_finished();
175        }
176
177        // Now handle the download result
178        let engine = download_result?;
179        self.registry.write().await.add_installed_engine(engine.clone()).await?;
180
181        Ok(engine)
182    }
183
184    /// Ensures an engine is available, downloading if necessary.
185    /// This method is designed for automatic recovery scenarios.
186    /// Returns Ok(true) if engine is available, Ok(false) if download failed.
187    pub async fn ensure_engine_available(&self) -> Result<bool> {
188        let registry = self.registry.read().await;
189
190        // Check if we have a working engine installed
191        if registry.has_installed_engine() {
192            return Ok(true);
193        }
194
195        drop(registry); // Release read lock before downloading
196
197        // No engine found - download one
198        tracing::info!("No engine available, downloading suitable engine...");
199        match self.download_suitable_engine().await {
200            Ok(_) => {
201                tracing::info!("Engine downloaded successfully");
202                Ok(true)
203            }
204            Err(e) => {
205                tracing::error!("Failed to download engine: {}", e);
206                Ok(false)
207            }
208        }
209    }
210
211    /// Get information about current hardware capabilities
212    pub fn get_hardware_info(&self) -> &HardwareCapabilities {
213        &self.hardware_capabilities
214    }
215
216    /// Refresh available engines from remote sources
217    pub async fn refresh_available_engines(&self) -> Result<()> {
218        let mut registry = self.registry.write().await;
219        registry.refresh_available_engines(&self.hardware_capabilities).await?;
220        Ok(())
221    }
222
223    /// Get detailed status information about the engine manager
224    pub async fn get_status_info(&self) -> String {
225        let registry = self.registry.read().await;
226        let installed_count = registry.installed_engines.len();
227        let available_count = registry.available_engines.len();
228        let default_engine = registry.default_engine.as_deref().unwrap_or("None");
229
230        format!(
231            "Engine Manager Status:\n  Installed Engines: {}\n  Available Engines: {}\n  Default Engine: {}\n  Hardware: {:?} {:?} (CUDA: {})\n  Recommended Engine: {:?}",
232            installed_count,
233            available_count,
234            default_engine,
235            self.hardware_capabilities.platform,
236            self.hardware_capabilities.architecture,
237            self.hardware_capabilities.has_cuda,
238            registry.get_recommended_engine(&self.hardware_capabilities).map(|e| e.name)
239        )
240    }
241    
242    /// Install a specific engine by ID
243    pub async fn install_engine_by_id(&self, engine_id: &str) -> Result<EngineInfo> {
244        let registry = self.registry.read().await;
245        let engine_to_install = registry.available_engines.iter()
246            .find(|engine| engine.id == engine_id)
247            .cloned();
248
249        match engine_to_install {
250            Some(engine_info) => {
251                drop(registry); // Release read lock before downloading
252                let installed_engine = self.downloader.download_engine(&engine_info).await?;
253                self.registry.write().await.add_installed_engine(installed_engine.clone()).await?;
254                Ok(installed_engine)
255            }
256            None => {
257                Err(anyhow::anyhow!("Engine not found: {}", engine_id))
258            }
259        }
260    }
261}