use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct AppConfig {
pub anthropic_api_key: Option<String>,
pub groq_api_key: Option<String>,
pub google_api_key: Option<String>,
pub default_provider: String,
pub default_model: Option<String>,
pub ollama_url: String,
pub lm_studio_url: String,
pub llama_cpp_url: String,
pub custom_api_url: String,
pub custom_api_key: Option<String>,
pub tech_query_url: String,
pub session_dir: PathBuf,
pub max_tool_turns: usize,
pub max_tokens: u32,
}
impl AppConfig {
pub fn prompt_for_key_if_missing(&mut self, provider: &str) -> anyhow::Result<()> {
let is_missing = match provider.to_lowercase().as_str() {
"anthropic" => self.anthropic_api_key.is_none(),
"groq" => self.groq_api_key.is_none(),
"google" => self.google_api_key.is_none(),
"custom" => self.custom_api_key.is_none(),
_ => false, };
if is_missing {
use colored::*;
println!("{}", format!("No API key found for provider: {}", provider).yellow());
print!("Please enter your {} API Key: ", provider);
use std::io::Write;
let _ = std::io::stdout().flush();
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let key = input.trim().to_string();
if key.is_empty() {
anyhow::bail!("API key cannot be empty.");
}
let mut store = crate::config_store::ConfigStore::load();
match provider.to_lowercase().as_str() {
"anthropic" => {
self.anthropic_api_key = Some(key.clone());
store.anthropic_api_key = Some(key);
}
"groq" => {
self.groq_api_key = Some(key.clone());
store.groq_api_key = Some(key);
}
"google" => {
self.google_api_key = Some(key.clone());
store.google_api_key = Some(key);
}
"custom" => {
self.custom_api_key = Some(key.clone());
store.custom_api_key = Some(key);
}
_ => {}
}
store.save()?;
println!("{}", "✓ API Key saved successfully to ~/.gigi_config.json\n".green());
}
Ok(())
}
pub fn from_env() -> Self {
use std::io::IsTerminal;
let mut store = crate::config_store::ConfigStore::load();
if !crate::config_store::ConfigStore::exists() && std::io::stdin().is_terminal() {
if let Ok(new_store) = crate::config_store::ConfigStore::run_setup_wizard() {
store = new_store;
}
}
Self {
anthropic_api_key: std::env::var("ANTHROPIC_API_KEY")
.ok()
.or_else(|| store.anthropic_api_key.clone()),
groq_api_key: std::env::var("GROQ_API_KEY")
.ok()
.or_else(|| store.groq_api_key.clone()),
google_api_key: std::env::var("GOOGLE_API_KEY")
.or_else(|_| std::env::var("GEMINI_API_KEY"))
.ok()
.or_else(|| store.google_api_key.clone()),
default_provider: std::env::var("MYAPP_PROVIDER")
.ok()
.or_else(|| store.default_provider.clone())
.unwrap_or_else(|| "anthropic".to_string()),
default_model: std::env::var("MYAPP_MODEL")
.ok()
.or_else(|| store.default_model.clone()),
ollama_url: std::env::var("OLLAMA_URL")
.ok()
.or_else(|| store.ollama_url.clone())
.unwrap_or_else(|| "http://localhost:11434".to_string()),
lm_studio_url: std::env::var("LM_STUDIO_URL")
.ok()
.or_else(|| store.lm_studio_url.clone())
.unwrap_or_else(|| "http://localhost:1234".to_string()),
llama_cpp_url: std::env::var("LLAMA_CPP_URL")
.ok()
.or_else(|| store.llama_cpp_url.clone())
.unwrap_or_else(|| "http://localhost:8080".to_string()),
custom_api_url: std::env::var("CUSTOM_API_URL")
.ok()
.or_else(|| store.custom_api_url.clone())
.unwrap_or_else(|| "http://localhost:8000".to_string()),
custom_api_key: std::env::var("CUSTOM_API_KEY")
.ok()
.or_else(|| store.custom_api_key.clone()),
tech_query_url: std::env::var("TECH_QUERY_URL")
.ok()
.or_else(|| store.tech_query_url.clone())
.unwrap_or_else(|| "http://localhost:5000/search".to_string()),
session_dir: std::env::var("MYAPP_SESSION_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(".sessions")),
max_tool_turns: std::env::var("MYAPP_MAX_TOOL_TURNS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(25),
max_tokens: std::env::var("MYAPP_MAX_TOKENS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(8192),
}
}
pub fn display_summary(&self) -> String {
let redact = |key: &Option<String>| -> String {
match key {
Some(k) if k.len() > 8 => format!("{}...{}", &k[..4], &k[k.len() - 4..]),
Some(_) => "****".to_string(),
None => "(not set)".to_string(),
}
};
format!(
"Configuration:\n\
├── Provider: {}\n\
├── Model: {}\n\
├── Anthropic key: {}\n\
├── Groq key: {}\n\
├── Google key: {}\n\
├── Ollama URL: {}\n\
├── LM Studio URL: {}\n\
├── llama.cpp URL: {}\n\
├── Custom URL: {}\n\
├── Tech Query URL: {}\n\
├── Session dir: {}\n\
├── Max tool turns: {}\n\
└── Max tokens: {}",
self.default_provider,
self.default_model.as_deref().unwrap_or("(provider default)"),
redact(&self.anthropic_api_key),
redact(&self.groq_api_key),
redact(&self.google_api_key),
self.ollama_url,
self.lm_studio_url,
self.llama_cpp_url,
self.custom_api_url,
self.tech_query_url,
self.session_dir.display(),
self.max_tool_turns,
self.max_tokens,
)
}
}