diff --git a/src-tauri/src/commands/misc.rs b/src-tauri/src/commands/misc.rs index 1d2124aef..f848fe6bf 100644 --- a/src-tauri/src/commands/misc.rs +++ b/src-tauri/src/commands/misc.rs @@ -89,7 +89,7 @@ pub struct ToolVersion { #[tauri::command] pub async fn get_tool_versions() -> Result, String> { - let tools = vec!["claude", "codex", "gemini"]; + let tools = vec!["claude", "codex", "gemini", "opencode"]; let mut results = Vec::new(); // 使用全局 HTTP 客户端(已包含代理配置) @@ -116,6 +116,7 @@ pub async fn get_tool_versions() -> Result, String> { "claude" => fetch_npm_latest_version(&client, "@anthropic-ai/claude-code").await, "codex" => fetch_npm_latest_version(&client, "@openai/codex").await, "gemini" => fetch_npm_latest_version(&client, "@google/gemini-cli").await, + "opencode" => fetch_github_latest_version(&client, "anomalyco/opencode").await, _ => None, }; @@ -148,6 +149,29 @@ async fn fetch_npm_latest_version(client: &reqwest::Client, package: &str) -> Op } } +/// Helper function to fetch latest version from GitHub releases +async fn fetch_github_latest_version(client: &reqwest::Client, repo: &str) -> Option { + let url = format!("https://api.github.com/repos/{repo}/releases/latest"); + match client + .get(&url) + .header("User-Agent", "cc-switch") + .header("Accept", "application/vnd.github+json") + .send() + .await + { + Ok(resp) => { + if let Ok(json) = resp.json::().await { + json.get("tag_name") + .and_then(|v| v.as_str()) + .map(|s| s.strip_prefix('v').unwrap_or(s).to_string()) + } else { + None + } + } + Err(_) => None, + } +} + /// 预编译的版本号正则表达式 static VERSION_RE: Lazy = Lazy::new(|| Regex::new(r"\d+\.\d+\.\d+(-[\w.]+)?").expect("Invalid version regex")); @@ -224,7 +248,7 @@ fn try_get_version_wsl(tool: &str, distro: &str) -> (Option, Option (Option, Option) { } } + // 添加 Go 路径支持 (opencode 使用 go install 安装) + if tool == "opencode" { + search_paths.push(home.join("go/bin")); // go install 默认路径 + if let Ok(gopath) = std::env::var("GOPATH") { + search_paths.push(std::path::PathBuf::from(gopath).join("bin")); + } + } + // 在每个路径中查找工具 for path in &search_paths { let tool_path = if cfg!(target_os = "windows") { @@ -405,6 +437,7 @@ fn wsl_distro_for_tool(tool: &str) -> Option { "claude" => crate::settings::get_claude_override_dir(), "codex" => crate::settings::get_codex_override_dir(), "gemini" => crate::settings::get_gemini_override_dir(), + "opencode" => crate::settings::get_opencode_override_dir(), _ => None, }?; diff --git a/src/components/settings/AboutSection.tsx b/src/components/settings/AboutSection.tsx index f301e2fbe..dab867d92 100644 --- a/src/components/settings/AboutSection.tsx +++ b/src/components/settings/AboutSection.tsx @@ -37,7 +37,9 @@ curl -fsSL https://claude.ai/install.sh | bash # Codex npm i -g @openai/codex@latest # Gemini CLI -npm i -g @google/gemini-cli@latest`; +npm i -g @google/gemini-cli@latest +# OpenCode +curl -fsSL https://opencode.ai/install | bash`; export function AboutSection({ isPortable }: AboutSectionProps) { // ... (use hooks as before) ... @@ -315,10 +317,14 @@ export function AboutSection({ isPortable }: AboutSectionProps) { {isLoadingTools ? t("common.refreshing") : t("common.refresh")} -
- {["claude", "codex", "gemini"].map((toolName, index) => { +
+ {["claude", "codex", "gemini", "opencode"].map((toolName, index) => { const tool = toolVersions.find((item) => item.name === toolName); - const displayName = tool?.name ?? toolName; + // Special case for OpenCode (capital C), others use capitalize + const displayName = + toolName === "opencode" + ? "OpenCode" + : toolName.charAt(0).toUpperCase() + toolName.slice(1); const title = tool?.version || tool?.error || t("common.unknown"); return ( @@ -333,9 +339,7 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
- - {displayName} - + {displayName}
{isLoadingTools ? ( diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx index ebd61a9a1..6f339610f 100644 --- a/src/components/settings/SettingsPage.tsx +++ b/src/components/settings/SettingsPage.tsx @@ -232,405 +232,405 @@ export function SettingsPage({
- {settings ? ( - - handleAutoSave({ language: lang })} - /> - - - - - handleAutoSave({ skillSyncMethod: method }) - } - /> - - handleAutoSave({ preferredTerminal: terminal }) - } - /> - - ) : null} - - - - {settings ? ( - - - - -
- -
-

- {t("settings.advanced.configDir.title")} -

-

- {t("settings.advanced.configDir.description")} -

-
-
-
- - - -
+ handleAutoSave({ language: lang })} + /> + + + + + handleAutoSave({ skillSyncMethod: method }) + } + /> + + handleAutoSave({ preferredTerminal: terminal }) + } + /> +
+ ) : null} +
- + {settings ? ( + + - - + +
- +

- {t("settings.advanced.proxy.title")} + {t("settings.advanced.configDir.title")}

- {t("settings.advanced.proxy.description")} + {t("settings.advanced.configDir.description")}

- -
- -
- - - {isRunning - ? t("settings.advanced.proxy.running") - : t("settings.advanced.proxy.stopped")} - - + + -
-
- - - -
+ + - - -
- -
-

- {t("settings.advanced.failover.title")} -

-

- {t("settings.advanced.failover.description")} -

+ + + +
+ +
+

+ {t("settings.advanced.proxy.title")} +

+

+ {t("settings.advanced.proxy.description")} +

+
+
+ +
+ +
+ + + {isRunning + ? t("settings.advanced.proxy.running") + : t("settings.advanced.proxy.stopped")} + +
-
- - -
- {/* 代理未运行时的提示 */} - {!isRunning && ( -
-

- {t("proxy.failover.proxyRequired", { - defaultValue: - "需要先启动代理服务才能配置故障转移", - })} + + + + + + + + +

+ +
+

+ {t("settings.advanced.failover.title")} +

+

+ {t("settings.advanced.failover.description")}

- )} +
+ + +
+ {/* 代理未运行时的提示 */} + {!isRunning && ( +
+

+ {t("proxy.failover.proxyRequired", { + defaultValue: + "需要先启动代理服务才能配置故障转移", + })} +

+
+ )} - {/* 故障转移设置 - 按应用分组 */} - - - Claude - Codex - Gemini - - -
-
-

- {t("proxy.failoverQueue.title")} -

-

- {t("proxy.failoverQueue.description")} -

+ {/* 故障转移设置 - 按应用分组 */} + + + Claude + Codex + Gemini + + +
+
+

+ {t("proxy.failoverQueue.title")} +

+

+ {t("proxy.failoverQueue.description")} +

+
+
- -
-
- -
- - -
-
-

- {t("proxy.failoverQueue.title")} -

-

- {t("proxy.failoverQueue.description")} -

+
+
- -
-
- -
- - -
-
-

- {t("proxy.failoverQueue.title")} -

-

- {t("proxy.failoverQueue.description")} -

+ + +
+
+

+ {t("proxy.failoverQueue.title")} +

+

+ {t("proxy.failoverQueue.description")} +

+
+
- -
-
- -
- - -
- - - - - -
- -
-

- {t("settings.advanced.rectifier.title")} -

-

- {t("settings.advanced.rectifier.description")} -

+
+ +
+ + +
+
+

+ {t("proxy.failoverQueue.title")} +

+

+ {t("proxy.failoverQueue.description")} +

+
+ +
+
+ +
+
+
-
-
- - - -
+ + - - -
- -
-

- {t("settings.advanced.modelTest.title")} -

-

- {t("settings.advanced.modelTest.description")} -

+ + +
+ +
+

+ {t("settings.advanced.rectifier.title")} +

+

+ {t("settings.advanced.rectifier.description")} +

+
-
- - - - - + + + + + - - -
- -
-

- {t("settings.advanced.pricing.title")} -

-

- {t("settings.advanced.pricing.description")} -

+ + +
+ +
+

+ {t("settings.advanced.modelTest.title")} +

+

+ {t("settings.advanced.modelTest.description")} +

+
-
- - - - - + + + + + - - -
- -
-

- {t("settings.advanced.globalProxy.title")} -

-

- {t("settings.advanced.globalProxy.description")} -

+ + +
+ +
+

+ {t("settings.advanced.pricing.title")} +

+

+ {t("settings.advanced.pricing.description")} +

+
-
- - - - - + + + + + - - -
- -
-

- {t("settings.advanced.data.title")} -

-

- {t("settings.advanced.data.description")} -

+ + +
+ +
+

+ {t("settings.advanced.globalProxy.title")} +

+

+ {t("settings.advanced.globalProxy.description")} +

+
-
- - - - - + + + + + - - -
- -
-

- {t("settings.advanced.logConfig.title")} -

-

- {t("settings.advanced.logConfig.description")} -

+ + +
+ +
+

+ {t("settings.advanced.data.title")} +

+

+ {t("settings.advanced.data.description")} +

+
-
- - - - - - - - ) : null} - + + + + + - - - + + +
+ +
+

+ {t("settings.advanced.logConfig.title")} +

+

+ {t("settings.advanced.logConfig.description")} +

+
+
+
+ + + +
+ + + ) : null} + - - - + + + + + + +
{activeTab === "advanced" && settings && ( @@ -639,10 +639,7 @@ export function SettingsPage({ style={{ backgroundColor: "hsl(var(--background))" }} >
-