Skip to content

Commit c4a956b

Browse files
committed
feat(config): add optional AiConfig per profile
Add AiConfig struct with provider, api_key, model and optional base_url. Profile.ai is Option<AiConfig> so it can be omitted when AI is not needed. AiConfig::endpoint() resolves the correct API URL per provider (openai, anthropic, ollama) with support for custom base_url.
1 parent fb1b420 commit c4a956b

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

src/config.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,45 @@ pub struct Config {
77
pub profile: Vec<Profile>,
88
}
99

10+
#[derive(Debug, Clone, Deserialize)]
11+
pub struct AiConfig {
12+
/// Provider: "openai" | "anthropic" | "ollama"
13+
pub provider: String,
14+
/// API key (no aplica a ollama local)
15+
#[serde(default)]
16+
pub api_key: String,
17+
/// Modelo a usar, ej: "gpt-4o", "claude-3-5-sonnet-20241022", "llama3"
18+
pub model: String,
19+
/// URL base opcional. Por defecto usa el endpoint público del provider.
20+
/// Para ollama: "http://localhost:11434"
21+
pub base_url: Option<String>,
22+
}
23+
24+
impl AiConfig {
25+
pub fn endpoint(&self) -> String {
26+
if let Some(base) = &self.base_url {
27+
match self.provider.as_str() {
28+
"ollama" => format!("{}/api/chat", base.trim_end_matches('/')),
29+
_ => base.clone(),
30+
}
31+
} else {
32+
match self.provider.as_str() {
33+
"openai" => "https://api.openai.com/v1/chat/completions".to_string(),
34+
"anthropic" => "https://api.anthropic.com/v1/messages".to_string(),
35+
"ollama" => "http://localhost:11434/api/chat".to_string(),
36+
other => panic!("Provider desconocido: {}", other),
37+
}
38+
}
39+
}
40+
}
41+
1042
#[derive(Debug, Clone, Deserialize)]
1143
pub struct Profile {
1244
pub name: String,
1345
pub email: String,
1446
pub token: String,
47+
/// Configuración de IA para este perfil (opcional)
48+
pub ai: Option<AiConfig>,
1549
#[serde(default)]
1650
pub repo: Vec<RepoEntry>,
1751
}
@@ -67,3 +101,55 @@ pub fn load_config(path: &str) -> Result<Config, String> {
67101
toml::from_str::<Config>(&content)
68102
.map_err(|e| format!("Error al parsear '{}': {}", path, e))
69103
}
104+
105+
#[derive(Debug, Clone, Deserialize)]
106+
pub struct RepoEntry {
107+
pub provider: String, // github | gitlab | bitbucket
108+
pub owner: String,
109+
pub name: String,
110+
}
111+
112+
impl RepoEntry {
113+
/// Construye la URL de clone con el token embebido según el provider.
114+
pub fn clone_url(&self, token: &str) -> String {
115+
match self.provider.as_str() {
116+
"github" => format!(
117+
"https://{}@github.com/{}/{}.git",
118+
token, self.owner, self.name
119+
),
120+
"gitlab" => format!(
121+
"https://oauth2:{}@gitlab.com/{}/{}.git",
122+
token, self.owner, self.name
123+
),
124+
"bitbucket" => format!(
125+
"https://x-token-auth:{}@bitbucket.org/{}/{}.git",
126+
token, self.owner, self.name
127+
),
128+
other => format!(
129+
"https://{}@{}/{}/{}.git",
130+
token, other, self.owner, self.name
131+
),
132+
}
133+
}
134+
135+
/// Devuelve la ruta de caché local para este repo.
136+
/// ~/.cache/git-reports/{profile_name}/{provider}/{owner}/{name}
137+
pub fn cache_path(&self, profile_name: &str) -> std::path::PathBuf {
138+
let base = dirs_next::cache_dir()
139+
.unwrap_or_else(|| std::path::PathBuf::from("/tmp"))
140+
.join("git-reports")
141+
.join(profile_name)
142+
.join(&self.provider)
143+
.join(&self.owner)
144+
.join(&self.name);
145+
base
146+
}
147+
}
148+
149+
/// Carga y deserializa el archivo config.toml desde la ruta indicada.
150+
pub fn load_config(path: &str) -> Result<Config, String> {
151+
let content = fs::read_to_string(Path::new(path))
152+
.map_err(|e| format!("No se pudo leer '{}': {}", path, e))?;
153+
toml::from_str::<Config>(&content)
154+
.map_err(|e| format!("Error al parsear '{}': {}", path, e))
155+
}

0 commit comments

Comments
 (0)