use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
#[serde(skip_serializing_if = "Option::is_none")]
pub default_environment: Option<String>,
#[serde(default)]
pub environments: HashMap<String, EnvironmentConfig>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProviderPreset {
Anthropic,
MiniMax,
Zai,
Custom,
}
impl Default for ProviderPreset {
fn default() -> Self {
Self::Anthropic
}
}
impl ProviderPreset {
pub fn all() -> &'static [ProviderPreset] {
&[
ProviderPreset::Anthropic,
ProviderPreset::MiniMax,
ProviderPreset::Zai,
ProviderPreset::Custom,
]
}
pub fn name(&self) -> &'static str {
match self {
ProviderPreset::Anthropic => "Anthropic (Default)",
ProviderPreset::MiniMax => "MiniMax",
ProviderPreset::Zai => "Z.AI",
ProviderPreset::Custom => "Custom",
}
}
pub fn base_url(&self) -> Option<&'static str> {
match self {
ProviderPreset::Anthropic => None,
ProviderPreset::MiniMax => Some("https://api.minimax.io/anthropic"),
ProviderPreset::Zai => Some("https://api.z.ai/api/anthropic"),
ProviderPreset::Custom => None,
}
}
pub fn default_models(&self) -> Option<ProviderModels> {
match self {
ProviderPreset::Anthropic => None,
ProviderPreset::MiniMax => Some(ProviderModels {
opus: "MiniMax-M2.1".to_string(),
sonnet: "MiniMax-M2.1".to_string(),
haiku: "MiniMax-M2.1".to_string(),
}),
ProviderPreset::Zai => Some(ProviderModels {
opus: "glm-4.7".to_string(),
sonnet: "glm-4.7".to_string(),
haiku: "glm-4.5-air".to_string(),
}),
ProviderPreset::Custom => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProviderModels {
pub opus: String,
pub sonnet: String,
pub haiku: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProviderConfig {
#[serde(default)]
pub preset: ProviderPreset,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_base_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub models: Option<ProviderModels>,
}
impl ProviderConfig {
pub fn is_custom(&self) -> bool {
self.preset != ProviderPreset::Anthropic
}
pub fn effective_base_url(&self) -> Option<String> {
match self.preset {
ProviderPreset::Custom => self.custom_base_url.clone(),
_ => self.preset.base_url().map(|s| s.to_string()),
}
}
pub fn effective_models(&self) -> Option<ProviderModels> {
self.models.clone().or_else(|| self.preset.default_models())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentConfig {
pub name: String,
#[serde(default = "default_border_color")]
pub border_color: String,
pub created_at: DateTime<Utc>,
#[serde(default)]
pub provider: ProviderConfig,
}
fn default_border_color() -> String {
"#3498db".to_string()
}
impl EnvironmentConfig {
pub fn new(name: String) -> Self {
Self {
name,
border_color: default_border_color(),
created_at: Utc::now(),
provider: ProviderConfig::default(),
}
}
pub fn border_color_rgb(&self) -> (u8, u8, u8) {
parse_hex_color(&self.border_color).unwrap_or((52, 152, 219)) }
}
fn parse_hex_color(hex: &str) -> Option<(u8, u8, u8)> {
let hex = hex.trim_start_matches('#');
if hex.len() != 6 {
return None;
}
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some((r, g, b))
}
impl Config {
pub fn get_environment(&self, name: &str) -> Option<&EnvironmentConfig> {
self.environments.get(name)
}
pub fn get_default_environment(&self) -> Option<&str> {
self.default_environment.as_ref().and_then(|name| {
if self.environments.contains_key(name) {
Some(name.as_str())
} else {
None
}
})
}
pub fn add_environment(&mut self, key: String, config: EnvironmentConfig) {
self.environments.insert(key, config);
}
pub fn remove_environment(&mut self, name: &str) -> Option<EnvironmentConfig> {
if self.default_environment.as_deref() == Some(name) {
self.default_environment = None;
}
self.environments.remove(name)
}
pub fn set_default(&mut self, name: Option<String>) {
self.default_environment = name;
}
pub fn list_environments(&self) -> Vec<&str> {
self.environments.keys().map(|s| s.as_str()).collect()
}
}