From 7d11ab80678abb42d9a99f135175ccc169d76da9 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Mon, 29 Jun 2026 15:27:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=88=9B=E5=BB=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E7=A1=AE=E4=BF=9D=E9=A6=96=E6=AC=A1=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=97=B6=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/mod.rs | 52 +++++++++++++++++++++++++++++++++++++++++++--- src/gateway/mod.rs | 14 ++++++++++--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 03ad281..de44528 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,8 +10,11 @@ use std::str::FromStr; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Config { + #[serde(default)] pub providers: HashMap, + #[serde(default)] pub models: HashMap, + #[serde(default)] pub agents: HashMap, #[serde(default)] pub time: TimeConfig, @@ -886,9 +889,12 @@ impl Config { tracing::info!(path = %fallback.display(), "Config loaded from fallback path"); fs::read_to_string(fallback)? } else { - return Err(Box::new(ConfigError::ConfigNotFound( - path.to_string_lossy().to_string(), - ))); + // Auto-create a minimal config on first startup + tracing::info!( + path = %path.display(), + "Config not found, auto-creating minimal config" + ); + Self::create_default_config(path)? } }; let content = resolve_env_placeholders(&content); @@ -906,6 +912,46 @@ impl Config { Ok(config) } + /// Create the default config file with a minimal template on first startup. + /// This ensures the gateway can start and the user can configure via web UI. + fn create_default_config(path: &Path) -> Result> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + let default_config = serde_json::json!({ + "providers": { + "default": { + "type": "openai", + "base_url": "https://api.openai.com/v1", + "api_key": "", + "extra_headers": {} + } + }, + "models": { + "default": { + "model_id": "gpt-4o", + "temperature": 0.7, + "context_window_tokens": 128000 + } + }, + "agents": { + "default": { + "provider": "default", + "model": "default" + } + } + }); + + let content = serde_json::to_string_pretty(&default_config)?; + fs::write(path, &content)?; + tracing::info!( + path = %path.display(), + "Created default config file — please configure your API key and model" + ); + Ok(content) + } + pub fn get_provider_config(&self, agent_name: &str) -> Result { let agent = self .agents diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index b77229b..afb574b 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -31,7 +31,7 @@ pub mod ws; use axum::{Router, routing}; use std::collections::HashMap; use std::sync::Arc; -use tokio::net::TcpListener; +use tokio::net::TcpSocket; use tokio::sync::Semaphore; use tower_http::services::ServeDir; @@ -223,8 +223,16 @@ pub async fn run( .with_state(state.clone()) }; - let addr = format!("{}:{}", bind_host, bind_port); - let listener = TcpListener::bind(&addr).await?; + let addr: std::net::SocketAddr = format!("{}:{}", bind_host, bind_port).parse()?; + let listener = { + let socket = match addr { + std::net::SocketAddr::V4(_) => TcpSocket::new_v4()?, + std::net::SocketAddr::V6(_) => TcpSocket::new_v6()?, + }; + socket.set_reuseaddr(true)?; + socket.bind(addr)?; + socket.listen(1024)? + }; tracing::info!(address = %addr, "Gateway listening"); // Graceful shutdown / restart signal