use anyhow::Result;
use std::path::PathBuf;
use crate::{
app::{load_config, persist_last_model, Config},
cli::{handle_command, Cli},
models::ModelFactory,
ollama::{ensure_model as ensure_ollama_model, require_any_model},
session::{select_conversation, ConversationManager},
tui::{run_ui, App},
utils::{check_ollama_available, log_error, log_info, log_progress, log_warn},
};
pub struct Orchestrator {
cli: Cli,
config: Config,
}
impl Orchestrator {
pub fn new(cli: Cli) -> Result<Self> {
let config = match load_config() {
Ok(cfg) => cfg,
Err(e) => {
log_warn("CONFIG", format!("Config load failed: {:#}. Using defaults.", e));
Config::default()
},
};
Ok(Self {
cli,
config,
})
}
pub async fn run(self) -> Result<()> {
let total_steps = 6; let mut current_step = 0;
current_step += 1;
log_progress(current_step, total_steps, "Processing commands");
if let Some(command) = &self.cli.command {
if handle_command(command).await? {
return Ok(()); }
}
current_step += 1;
log_progress(current_step, total_steps, "Configuring model");
let cli_model_provided = self.cli.model.is_some();
let model_id = if let Some(model) = &self.cli.model {
model.clone()
} else if let Some(last_model) = &self.config.last_used_model {
last_model.clone()
} else if !self.config.default_model.provider.is_empty()
&& !self.config.default_model.name.is_empty()
{
format!(
"{}/{}",
self.config.default_model.provider, self.config.default_model.name
)
} else {
let available = require_any_model().await?;
format!("ollama/{}", available[0])
};
log_info(
"MERMAID",
format!("Starting Mermaid with model: {}", model_id),
);
current_step += 1;
if is_local_model(&model_id) {
log_progress(current_step, total_steps, "Checking Ollama availability");
let ollama_check = check_ollama_available(
&self.config.ollama.host,
self.config.ollama.port,
).await;
if !ollama_check.available {
log_error("OLLAMA", &ollama_check.message);
std::process::exit(1);
}
} else {
log_progress(current_step, total_steps, "Using API provider");
}
current_step += 1;
log_progress(current_step, total_steps, "Checking model availability");
ensure_ollama_model(&model_id, true).await?;
if cli_model_provided {
if let Err(e) = persist_last_model(&model_id) {
log_warn("CONFIG", format!("Failed to persist model choice: {}", e));
}
}
current_step += 1;
log_progress(current_step, total_steps, "Initializing model");
let backend = if self.config.behavior.backend == "auto" {
None
} else {
Some(self.config.behavior.backend.as_str())
};
let model = match ModelFactory::create_with_backend(
&model_id,
Some(&self.config),
backend,
)
.await
{
Ok(m) => m,
Err(e) => {
log_error("ERROR", format!("Failed to initialize model: {}", e));
log_error(
"",
"Make sure the model is available and properly configured.",
);
std::process::exit(1);
},
};
let project_path = self.cli.path.clone().unwrap_or_else(|| PathBuf::from("."));
current_step += 1;
log_progress(current_step, total_steps, "Starting UI");
let mut app = App::new(model, model_id.clone());
if self.cli.continue_session || self.cli.sessions {
let conversation_manager = ConversationManager::new(&project_path)?;
if self.cli.sessions {
let conversations = conversation_manager.list_conversations()?;
if !conversations.is_empty() {
if let Some(selected) = select_conversation(conversations)? {
log_info(
"RESUME",
format!("Resuming conversation: {}", selected.title),
);
app.load_conversation(selected);
}
} else {
log_info("INFO", "No previous conversations found in this directory");
}
} else {
if let Some(last_conv) = conversation_manager.load_last_conversation()? {
log_info(
"RESUME",
format!("Resuming: {}", last_conv.title),
);
app.load_conversation(last_conv);
} else {
log_info("INFO", "No previous conversation to continue");
}
}
}
run_ui(app).await
}
}
fn is_local_model(_model_id: &str) -> bool {
true
}