diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index afdcc6b..288a783 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -584,6 +584,7 @@ struct AgentConfigStatus { config_valid: bool, configured: bool, current_model: Option, + oauth_configuration: bool, modification_enabled: bool, modification_state: String, backup_available: bool, @@ -2422,9 +2423,11 @@ fn inspect_agent_config( let paths = agent_config_paths(client, home); let config_exists = paths.iter().any(|path| path.is_file()); let result = inspect_agent_managed_config(client, &paths, port, api_key); - let (configured, current_model, config_valid, error) = match result { - Ok((configured, model)) => (configured, model, true, None), - Err(error) => (false, None, false, Some(error)), + let (configured, current_model, oauth_configuration, config_valid, error) = match result { + Ok((configured, model, oauth_configuration)) => { + (configured, model, oauth_configuration, true, None) + } + Err(error) => (false, None, false, false, Some(error)), }; let launch_targets = agent_launch_targets(client, home); let executable = find_agent_executable(client, home); @@ -2458,6 +2461,7 @@ fn inspect_agent_config( config_valid, configured, current_model, + oauth_configuration, modification_enabled: modification.enabled, modification_state: modification.state, backup_available: modification.backup_available, @@ -2498,17 +2502,22 @@ fn inspect_agent_managed_config( paths: &[PathBuf], port: u16, api_key: &str, -) -> Result<(bool, Option), String> { +) -> Result<(bool, Option, bool), String> { match client { - AgentClient::ClaudeCode => inspect_claude_agent_config(&paths[0], port, api_key), + AgentClient::ClaudeCode => inspect_claude_agent_config(&paths[0], port, api_key) + .map(|(configured, model)| (configured, model, false)), AgentClient::ClaudeDesktop if client.supported_platform() => { inspect_claude_desktop_agent_config(paths, port, api_key) + .map(|(configured, model)| (configured, model, false)) } - AgentClient::ClaudeDesktop => Ok((false, None)), + AgentClient::ClaudeDesktop => Ok((false, None, false)), AgentClient::Codex => inspect_codex_agent_config(&paths[0], port, api_key), - AgentClient::OpenCode => inspect_opencode_agent_config(&paths[0], port, api_key), - AgentClient::OpenClaw => inspect_openclaw_agent_config(&paths[0], port, api_key), - AgentClient::Hermes => inspect_hermes_agent_config(&paths[0], port, api_key), + AgentClient::OpenCode => inspect_opencode_agent_config(&paths[0], port, api_key) + .map(|(configured, model)| (configured, model, false)), + AgentClient::OpenClaw => inspect_openclaw_agent_config(&paths[0], port, api_key) + .map(|(configured, model)| (configured, model, false)), + AgentClient::Hermes => inspect_hermes_agent_config(&paths[0], port, api_key) + .map(|(configured, model)| (configured, model, false)), } } @@ -3410,9 +3419,9 @@ fn inspect_codex_agent_config( path: &Path, port: u16, api_key: &str, -) -> Result<(bool, Option), String> { +) -> Result<(bool, Option, bool), String> { if !path.is_file() { - return Ok((false, None)); + return Ok((false, None, false)); } let root: toml::Value = toml::from_str( &fs::read_to_string(path).map_err(|error| format!("读取 Codex 配置失败: {error}"))?, @@ -3428,11 +3437,10 @@ fn inspect_codex_agent_config( .and_then(|provider| provider.get("experimental_bearer_token")) .and_then(toml::Value::as_str) == Some(api_key); - let uses_oauth = provider + let oauth_configuration = provider .and_then(|provider| provider.get("requires_openai_auth")) .and_then(toml::Value::as_bool) - == Some(true) - && provider.is_some_and(|provider| !provider.contains_key("experimental_bearer_token")); + == Some(true); let configured = root.get("model_provider").and_then(toml::Value::as_str) == Some(MANAGED_AGENT_PROVIDER_ID) && root.get("model_catalog_json").and_then(toml::Value::as_str) @@ -3445,7 +3453,7 @@ fn inspect_codex_agent_config( .and_then(|provider| provider.get("base_url")) .and_then(toml::Value::as_str) == Some(expected_base.as_str()) - && (uses_api_key || uses_oauth) + && uses_api_key && provider .and_then(|provider| provider.get("wire_api")) .and_then(toml::Value::as_str) @@ -3456,7 +3464,7 @@ fn inspect_codex_agent_config( .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_string); - Ok((configured, model)) + Ok((configured, model, oauth_configuration)) } fn inspect_opencode_agent_config( @@ -4017,12 +4025,11 @@ fn build_codex_agent_config_with_oauth( set_codex_table_item(provider, "name", value("EasyCLIProxyAPI")); set_codex_table_item(provider, "base_url", value(base_url)); set_codex_table_item(provider, "wire_api", value("responses")); + set_codex_table_item(provider, "experimental_bearer_token", value(api_key)); if oauth_configuration { - provider.remove("experimental_bearer_token"); set_codex_table_item(provider, "requires_openai_auth", value(true)); } else { provider.remove("requires_openai_auth"); - set_codex_table_item(provider, "experimental_bearer_token", value(api_key)); } let rendered = document.to_string(); toml::from_str::(&rendered) @@ -5771,7 +5778,8 @@ fn enable_agent_modification( Vec::new(), )); } - let (_, current_model) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; + let (_, current_model, _) = + inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if agent_has_managed_marker(client, &paths)? { if let Some(current_model) = current_model.as_deref() { if let Some(record) = build_legacy_agent_record(client, home, port, current_model)? { @@ -5849,7 +5857,8 @@ fn disable_agent_modification( let mut record = match load_agent_record(client, &paths)? { Some(record) => record, None => { - let (_, model) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; + let (_, model, _) = + inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if !agent_has_managed_marker(client, &paths)? { return Ok(action_result( "disabled", @@ -5934,7 +5943,7 @@ fn update_agent_modification( let record = match load_agent_record(client, &paths)? { Some(record) => record, None => { - let (_, current_model) = + let (_, current_model, _) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?; if !agent_has_managed_marker(client, &paths)? { return Err("请先应用配置修改".to_string()); @@ -13938,7 +13947,9 @@ mod tests { } #[test] - fn codex_oauth_configuration_uses_openai_auth_without_bearer_token() { + fn codex_oauth_configuration_uses_openai_auth_with_bearer_token() { + let home = agent_test_home("codex-oauth-configuration"); + let path = home.join(".codex/config.toml"); let rendered = build_codex_agent_config_with_oauth( Some("[model_providers.cpa-gui]\nexperimental_bearer_token = \"old-key\"\n"), "http://127.0.0.1:8317/v1", @@ -13958,7 +13969,21 @@ mod tests { .and_then(toml::Value::as_bool), Some(true) ); - assert!(!provider.contains_key("experimental_bearer_token")); + assert_eq!( + provider + .get("experimental_bearer_token") + .and_then(toml::Value::as_str), + Some(DEFAULT_API_KEY) + ); + + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(&path, rendered).unwrap(); + let (configured, model, oauth_configuration) = + inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(); + assert!(configured); + assert_eq!(model.as_deref(), Some("gpt-test")); + assert!(oauth_configuration); + fs::remove_dir_all(home).unwrap(); } #[test] @@ -14620,7 +14645,7 @@ custom_option = "keep-original" provider["custom_option"] = toml_edit::value("keep-updated"); fs::write(&path, externally_edited.to_string()).unwrap(); - let (configured, current_model) = + let (configured, current_model, _) = inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(); let inspection = inspect_agent_modification( AgentClient::Codex, diff --git a/src/App.tsx b/src/App.tsx index 0faafe9..60fb4c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,12 +14,10 @@ import { Languages, LogIn, MessageCircle, - Moon, Network, PackageOpen, ServerCog, Settings, - Sun, X, } from 'lucide-react'; import appLogo from './assets/logo.jpg'; @@ -308,22 +306,30 @@ function AppContent() {
- + + +
{status ? ( {status.text} diff --git a/src/styles.css b/src/styles.css index d5f6a40..dcb6dd2 100644 --- a/src/styles.css +++ b/src/styles.css @@ -440,71 +440,40 @@ input:focus-visible { border-top: 1px solid var(--theme-e3e1db); } -.sidebar-theme-toggle { +.sidebar-theme-selector { display: grid; - grid-template-columns: 18px minmax(0, 1fr) 36px; - align-items: center; - gap: 8px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 3px; width: 100%; - min-height: 40px; - padding: 0 10px; + padding: 3px; border: 1px solid var(--theme-d8d4cc); border-radius: 8px; background: var(--theme-fffdf8); - color: var(--theme-716b61); - text-align: left; } -.sidebar-theme-toggle:hover { - border-color: var(--theme-c7c2b8); +.sidebar-theme-selector button { + min-width: 0; + min-height: 32px; + padding: 0 8px; + border: 0; + border-radius: 5px; + background: transparent; + color: var(--theme-716b61); + font-size: 12px; + font-weight: 700; + transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease; + white-space: nowrap; +} + +.sidebar-theme-selector button:hover { background: var(--theme-e8e5dc); color: var(--theme-2d2a26); } -.sidebar-theme-toggle > svg { - color: var(--theme-7a746c); -} - -.sidebar-theme-toggle > span { - min-width: 0; - overflow: hidden; - font-size: 12px; - font-weight: 700; - text-overflow: ellipsis; - white-space: nowrap; -} - -.theme-switch-track { - position: relative; - display: block; - width: 36px; - height: 20px; - border: 1px solid var(--theme-c7c2b8); - border-radius: 999px; +.sidebar-theme-selector button.active { background: var(--theme-dfdcd4); - transition: border-color 160ms ease, background 160ms ease; -} - -.theme-switch-track b { - position: absolute; - top: 2px; - left: 2px; - display: block; - width: 14px; - height: 14px; - border-radius: 50%; - background: var(--theme-fff); - box-shadow: 0 1px 3px rgba(45, 42, 38, 0.24); - transition: transform 180ms ease, background 160ms ease; -} - -.sidebar-theme-toggle[aria-pressed='true'] .theme-switch-track { - border-color: var(--theme-8069ad); - background: var(--theme-654f91); -} - -.sidebar-theme-toggle[aria-pressed='true'] .theme-switch-track b { - transform: translateX(16px); + box-shadow: inset 0 0 0 1px var(--theme-d5d2cb); + color: var(--theme-2d2a26); } .sidebar-language {