use super::backend::ModelFactory as InternalFactory;
use super::config::BackendConfig;
use super::error::Result;
use super::traits::Model;
use crate::app::Config;
pub struct ModelFactory;
impl ModelFactory {
pub async fn create(model_id: &str, config: Option<&Config>) -> Result<Box<dyn Model>> {
let backend_config = if let Some(cfg) = config {
Self::config_to_backend_config(cfg)
} else {
BackendConfig::default()
};
let factory = InternalFactory::new(backend_config);
factory.create_model(model_id).await
}
pub async fn create_default(model_id: &str) -> Result<Box<dyn Model>> {
let factory = InternalFactory::new(BackendConfig::default());
factory.create_model(model_id).await
}
pub async fn create_with_provider(
model_id: &str,
config: Option<&Config>,
provider: Option<&str>,
) -> Result<Box<dyn Model>> {
let backend_config = if let Some(cfg) = config {
Self::config_to_backend_config(cfg)
} else {
BackendConfig::default()
};
let final_model_id = if let Some(provider_name) = provider {
if model_id.contains('/') {
model_id.to_string()
} else {
format!("{}/{}", provider_name, model_id)
}
} else {
model_id.to_string()
};
let factory = InternalFactory::new(backend_config);
factory.create_model(&final_model_id).await
}
pub async fn create_with_backend(
model_id: &str,
config: Option<&Config>,
backend: Option<&str>,
) -> Result<Box<dyn Model>> {
Self::create_with_provider(model_id, config, backend).await
}
pub async fn get_available_backends() -> Vec<String> {
let factory = InternalFactory::new(BackendConfig::default());
factory.available_providers().await
}
pub async fn list_all_backend_models() -> Result<Vec<String>> {
let factory = InternalFactory::new(BackendConfig::default());
let providers = factory.available_providers().await;
let mut all_models = Vec::new();
for provider in providers {
let dummy_model_id = format!("{}/dummy", provider);
if let Ok(model) = factory.create_model(&dummy_model_id).await {
if let Ok(models) = model.list_models().await {
for model_name in models {
all_models.push(format!("{}/{}", provider, model_name));
}
}
}
}
all_models.sort();
Ok(all_models)
}
fn config_to_backend_config(config: &Config) -> BackendConfig {
let ollama_url = format!("http://{}:{}", config.ollama.host, config.ollama.port);
BackendConfig {
ollama_url,
timeout_secs: 10,
request_timeout_secs: 120,
max_idle_per_host: 10,
health_check_interval_secs: 30,
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_model_spec_parsing() {
let specs = vec![
("ollama/tinyllama", Some("ollama"), "tinyllama"),
("qwen3-coder:30b", None, "qwen3-coder:30b"),
("kimi-k2.5:cloud", None, "kimi-k2.5:cloud"),
];
for (spec, expected_provider, expected_model) in specs {
let parts: Vec<&str> = spec.split('/').collect();
if parts.len() == 2 {
assert_eq!(Some(parts[0]), expected_provider);
assert_eq!(parts[1], expected_model);
} else {
assert_eq!(None, expected_provider);
assert_eq!(spec, expected_model);
}
}
}
#[test]
fn test_provider_extraction() {
fn extract_provider(spec: &str) -> Option<&str> {
spec.split('/').next().filter(|_| spec.contains('/'))
}
assert_eq!(extract_provider("ollama/tinyllama"), Some("ollama"));
assert_eq!(extract_provider("qwen3-coder:30b"), None);
}
}