diff --git a/src-tauri/src/commands/misc.rs b/src-tauri/src/commands/misc.rs index 50f4f7f0f..612cba56f 100644 --- a/src-tauri/src/commands/misc.rs +++ b/src-tauri/src/commands/misc.rs @@ -155,11 +155,17 @@ fn try_get_version(tool: &str) -> (Option, Option) { match output { Ok(out) => { + let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string(); + let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string(); if out.status.success() { - let raw = String::from_utf8_lossy(&out.stdout).trim().to_string(); - (Some(extract_version(&raw)), None) + let raw = if stdout.is_empty() { &stderr } else { &stdout }; + if raw.is_empty() { + (None, Some("未安装或无法执行".to_string())) + } else { + (Some(extract_version(raw)), None) + } } else { - let err = String::from_utf8_lossy(&out.stderr).trim().to_string(); + let err = if stderr.is_empty() { stdout } else { stderr }; ( None, Some(if err.is_empty() { @@ -239,9 +245,13 @@ fn scan_cli_version(tool: &str) -> (Option, Option) { .output(); if let Ok(out) = output { + let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string(); + let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string(); if out.status.success() { - let raw = String::from_utf8_lossy(&out.stdout).trim().to_string(); - return (Some(extract_version(&raw)), None); + let raw = if stdout.is_empty() { &stderr } else { &stdout }; + if !raw.is_empty() { + return (Some(extract_version(raw)), None); + } } } } diff --git a/src/components/settings/AboutSection.tsx b/src/components/settings/AboutSection.tsx index cf9465ec4..d40233f97 100644 --- a/src/components/settings/AboutSection.tsx +++ b/src/components/settings/AboutSection.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { Download, + Copy, ExternalLink, Info, Loader2, @@ -8,6 +9,7 @@ import { Terminal, CheckCircle2, AlertCircle, + Sparkles, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTranslation } from "react-i18next"; @@ -17,6 +19,7 @@ import { settingsApi } from "@/lib/api"; import { useUpdate } from "@/contexts/UpdateContext"; import { relaunchApp } from "@/lib/updater"; import { Badge } from "@/components/ui/badge"; +import { motion } from "framer-motion"; interface AboutSectionProps { isPortable: boolean; @@ -29,6 +32,10 @@ interface ToolVersion { error: string | null; } +const ONE_CLICK_INSTALL_COMMANDS = `npm i -g @anthropic-ai/claude-code@latest +npm i -g @openai/codex@latest +npm i -g @google/gemini-cli@latest`; + export function AboutSection({ isPortable }: AboutSectionProps) { // ... (use hooks as before) ... const { t } = useTranslation(); @@ -47,6 +54,18 @@ export function AboutSection({ isPortable }: AboutSectionProps) { isChecking, } = useUpdate(); + const loadToolVersions = useCallback(async () => { + setIsLoadingTools(true); + try { + const tools = await settingsApi.getToolVersions(); + setToolVersions(tools); + } catch (error) { + console.error("[AboutSection] Failed to load tool versions", error); + } finally { + setIsLoadingTools(false); + } + }, []); + useEffect(() => { let active = true; const load = async () => { @@ -150,10 +169,25 @@ export function AboutSection({ isPortable }: AboutSectionProps) { } }, [checkUpdate, hasUpdate, isPortable, resetDismiss, t, updateHandle]); + const handleCopyInstallCommands = useCallback(async () => { + try { + await navigator.clipboard.writeText(ONE_CLICK_INSTALL_COMMANDS); + toast.success(t("settings.installCommandsCopied"), { closeButton: true }); + } catch (error) { + console.error("[AboutSection] Failed to copy install commands", error); + toast.error(t("settings.installCommandsCopyFailed")); + } + }, [t]); + const displayVersion = version ?? t("common.unknown"); return ( -
+

{t("common.about")}

@@ -161,12 +195,22 @@ export function AboutSection({ isPortable }: AboutSectionProps) {

-
+
-

CC Switch

- + +

+ CC Switch +

+
+
+ {t("common.version")} @@ -185,15 +229,15 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
-
+
{hasUpdate && updateInfo && ( -
+

{t("settings.updateAvailable", { version: updateInfo.availableVersion, @@ -239,60 +290,111 @@ export function AboutSection({ isPortable }: AboutSectionProps) { {updateInfo.notes}

)} -
+ )} -
+
-

- 本地环境检查 -

+
+

{t("settings.localEnvCheck")}

+ +
- {isLoadingTools - ? Array.from({ length: 3 }).map((_, i) => ( -
- )) - : toolVersions.map((tool) => ( -
-
-
- - - {tool.name} - -
- {tool.version ? ( -
- {tool.latest_version && - tool.version !== tool.latest_version && ( - - Update: {tool.latest_version} - - )} - -
- ) : ( - - )} + {["claude", "codex", "gemini"].map((toolName, index) => { + const tool = toolVersions.find((item) => item.name === toolName); + const displayName = tool?.name ?? toolName; + const title = tool?.version || tool?.error || t("common.unknown"); + + return ( + +
+
+ + + {displayName} +
-
-
- {tool.version ? tool.version : tool.error || "未安装"} + {isLoadingTools ? ( + + ) : tool?.version ? ( +
+ {tool.latest_version && + tool.version !== tool.latest_version && ( + + {tool.latest_version} + + )} +
-
+ ) : ( + + )}
- ))} +
+ {isLoadingTools + ? t("common.loading") + : tool?.version + ? tool.version + : tool?.error || t("common.notInstalled")} +
+ + ); + })}
-
+ + +

+ {t("settings.oneClickInstall")} +

+
+
+

+ {t("settings.oneClickInstallHint")} +

+ +
+
+            {ONE_CLICK_INSTALL_COMMANDS}
+          
+
+
+ ); } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 2226b977b..911e446e9 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -17,6 +17,7 @@ "about": "About", "version": "Version", "loading": "Loading...", + "notInstalled": "Not installed", "success": "Success", "error": "Error", "unknown": "Unknown", @@ -28,7 +29,10 @@ "formatError": "Format failed: {{error}}", "copy": "Copy", "view": "View", - "back": "Back" + "back": "Back", + "refresh": "Refresh", + "refreshing": "Refreshing...", + "notInstalled": "Not installed" }, "apiKeyInput": { "placeholder": "Enter API Key", @@ -205,6 +209,11 @@ "releaseNotes": "Release Notes", "viewReleaseNotes": "View release notes for this version", "viewCurrentReleaseNotes": "View current version release notes", + "oneClickInstall": "One-click Install", + "oneClickInstallHint": "Install Claude Code / Codex / Gemini CLI", + "localEnvCheck": "Local environment check", + "installCommandsCopied": "Install commands copied", + "installCommandsCopyFailed": "Copy failed, please copy manually.", "importFailedError": "Import config failed: {{message}}", "exportFailedError": "Export config failed:", "restartRequired": "Restart Required", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 07f1a32ca..6f9ff89f0 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -17,6 +17,7 @@ "about": "バージョン情報", "version": "バージョン", "loading": "読み込み中...", + "notInstalled": "未インストール", "success": "成功", "error": "エラー", "unknown": "不明", @@ -28,7 +29,10 @@ "formatError": "整形に失敗しました: {{error}}", "copy": "コピー", "view": "表示", - "back": "戻る" + "back": "戻る", + "refresh": "更新", + "refreshing": "更新中...", + "notInstalled": "未インストール" }, "apiKeyInput": { "placeholder": "API Key を入力", @@ -205,6 +209,11 @@ "releaseNotes": "リリースノート", "viewReleaseNotes": "このバージョンのリリースノートを見る", "viewCurrentReleaseNotes": "現在のバージョンのリリースノートを見る", + "oneClickInstall": "ワンクリックインストール", + "oneClickInstallHint": "Claude Code / Codex / Gemini CLI をインストール", + "localEnvCheck": "ローカル環境チェック", + "installCommandsCopied": "インストールコマンドをコピーしました", + "installCommandsCopyFailed": "コピーに失敗しました。手動でコピーしてください。", "importFailedError": "設定のインポートに失敗しました: {{message}}", "exportFailedError": "設定のエクスポートに失敗しました:", "restartRequired": "再起動が必要です", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index c63bbc74c..12eabee5a 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -17,6 +17,7 @@ "about": "关于", "version": "版本", "loading": "加载中...", + "notInstalled": "未安装", "success": "成功", "error": "错误", "unknown": "未知", @@ -28,7 +29,10 @@ "formatError": "格式化失败:{{error}}", "copy": "复制", "view": "查看", - "back": "返回" + "back": "返回", + "refresh": "刷新", + "refreshing": "刷新中...", + "notInstalled": "未安装" }, "apiKeyInput": { "placeholder": "请输入API Key", @@ -205,6 +209,11 @@ "releaseNotes": "更新日志", "viewReleaseNotes": "查看该版本更新日志", "viewCurrentReleaseNotes": "查看当前版本更新日志", + "oneClickInstall": "一键安装", + "oneClickInstallHint": "安装 Claude Code / Codex / Gemini CLI", + "localEnvCheck": "本地环境检查", + "installCommandsCopied": "安装命令已复制", + "installCommandsCopyFailed": "复制失败,请手动复制。", "importFailedError": "导入配置失败:{{message}}", "exportFailedError": "导出配置失败:", "restartRequired": "需要重启应用",