mermaid_cli/runtime/
orchestrator.rs1use anyhow::Result;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use crate::{
6 agents::mark_mcp_init_started,
7 app::{Config, load_config, persist_last_model},
8 cli::{Cli, handle_command},
9 mcp::McpServerManager,
10 models::{ModelConfig, ModelFactory},
11 ollama::ensure_model as ensure_ollama_model,
12 session::{ConversationManager, select_conversation},
13 tui::{App, McpInitResult, run_ui},
14 utils::{check_ollama_available, log_error, log_info, log_progress, log_warn},
15};
16
17pub struct Orchestrator {
19 cli: Cli,
20 config: Config,
21}
22
23impl Orchestrator {
24 pub fn new(cli: Cli) -> Result<Self> {
26 let config = match load_config() {
28 Ok(cfg) => cfg,
29 Err(e) => {
30 log_warn(
31 "CONFIG",
32 format!("Config load failed: {:#}. Using defaults.", e),
33 );
34 Config::default()
35 },
36 };
37
38 Ok(Self { cli, config })
39 }
40
41 pub async fn run(self) -> Result<()> {
43 let total_steps = 6; let mut current_step = 0;
46
47 current_step += 1;
49 log_progress(current_step, total_steps, "Processing commands");
50 if let Some(command) = &self.cli.command
51 && handle_command(command).await?
52 {
53 return Ok(()); }
55 current_step += 1;
59 log_progress(current_step, total_steps, "Configuring model");
60
61 let cli_model_provided = self.cli.model.is_some();
62 let model_id =
63 crate::app::resolve_model_id(self.cli.model.as_deref(), &self.config).await?;
64
65 log_info(
66 "MERMAID",
67 format!("Starting Mermaid with model: {}", model_id),
68 );
69
70 current_step += 1;
72 log_progress(current_step, total_steps, "Checking Ollama availability");
73 let ollama_check =
74 check_ollama_available(&self.config.ollama.host, self.config.ollama.port).await;
75
76 if !ollama_check.available {
77 log_error("OLLAMA", &ollama_check.message);
78 anyhow::bail!("{}", ollama_check.message);
79 }
80
81 current_step += 1;
83 log_progress(current_step, total_steps, "Checking model availability");
84 ensure_ollama_model(&model_id).await?;
85
86 if cli_model_provided && let Err(e) = persist_last_model(&model_id) {
88 log_warn("CONFIG", format!("Failed to persist model choice: {}", e));
89 }
90
91 current_step += 1;
93 log_progress(current_step, total_steps, "Initializing model");
94 let model = ModelFactory::create(
95 &model_id,
96 Some(&self.config),
97 )
98 .await
99 .map_err(|e| {
100 log_error("ERROR", format!("Failed to initialize model: {}", e));
101 anyhow::anyhow!(
102 "Failed to initialize model: {}. Make sure the model is available and properly configured.",
103 e
104 )
105 })?;
106
107 let project_path = self.cli.path.clone().unwrap_or_else(|| PathBuf::from("."));
109
110 current_step += 1;
112 log_progress(current_step, total_steps, "Starting UI");
113 let base_config = ModelConfig::from_app_config(&self.config, &model_id);
114 let mut app = App::new(model, model_id.clone(), base_config);
115
116 if !self.config.mcp_servers.is_empty() {
118 let server_count = self.config.mcp_servers.len();
119 log_info("MCP", format!("Starting {} MCP server(s) in background...", server_count));
120 mark_mcp_init_started();
121
122 let mcp_configs = self.config.mcp_servers.clone();
123 app.mcp_init_task = Some(tokio::spawn(async move {
124 let manager = McpServerManager::start(&mcp_configs).await;
125 if manager.has_servers() {
126 let tools =
127 crate::models::tools::mcp_tools_to_ollama(manager.get_all_tools());
128 log_info("MCP", format!("{} MCP tool(s) available", tools.len()));
129 McpInitResult {
130 tools,
131 manager: Some(Arc::new(manager)),
132 }
133 } else {
134 McpInitResult {
135 tools: Vec::new(),
136 manager: None,
137 }
138 }
139 }));
140 }
141
142 if self.cli.continue_session || self.cli.sessions {
147 let conversation_manager = ConversationManager::new(&project_path)?;
148
149 if self.cli.sessions {
150 let conversations = conversation_manager.list_conversations()?;
152 if !conversations.is_empty() {
153 if let Some(selected) = select_conversation(conversations)? {
154 log_info(
155 "RESUME",
156 format!("Resuming conversation: {}", selected.title),
157 );
158 app.load_conversation(selected);
159 }
160 } else {
161 log_info("INFO", "No previous conversations found in this directory");
162 }
163 } else {
164 if let Some(last_conv) = conversation_manager.load_last_conversation()? {
166 log_info("RESUME", format!("Resuming: {}", last_conv.title));
167 app.load_conversation(last_conv);
168 } else {
169 log_info("INFO", "No previous conversation to continue");
170 }
171 }
172 }
173
174 run_ui(app).await
176 }
177}