diff --git a/src/components/settings/AboutSection.tsx b/src/components/settings/AboutSection.tsx index 0011c0b28..ba38430b4 100644 --- a/src/components/settings/AboutSection.tsx +++ b/src/components/settings/AboutSection.tsx @@ -1,5 +1,14 @@ import { useCallback, useEffect, useState } from "react"; -import { Download, ExternalLink, Info, Loader2, RefreshCw } from "lucide-react"; +import { + Download, + ExternalLink, + Info, + Loader2, + RefreshCw, + Terminal, + CheckCircle2, + AlertCircle, +} from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -7,16 +16,27 @@ import { getVersion } from "@tauri-apps/api/app"; import { settingsApi } from "@/lib/api"; import { useUpdate } from "@/contexts/UpdateContext"; import { relaunchApp } from "@/lib/updater"; +import { Badge } from "@/components/ui/badge"; interface AboutSectionProps { isPortable: boolean; } +interface ToolVersion { + name: string; + version: string | null; + latest_version: string | null; + error: string | null; +} + export function AboutSection({ isPortable }: AboutSectionProps) { + // ... (use hooks as before) ... const { t } = useTranslation(); const [version, setVersion] = useState(null); const [isLoadingVersion, setIsLoadingVersion] = useState(true); const [isDownloading, setIsDownloading] = useState(false); + const [toolVersions, setToolVersions] = useState([]); + const [isLoadingTools, setIsLoadingTools] = useState(true); const { hasUpdate, @@ -31,18 +51,24 @@ export function AboutSection({ isPortable }: AboutSectionProps) { let active = true; const load = async () => { try { - const loaded = await getVersion(); + const [appVersion, tools] = await Promise.all([ + getVersion(), + settingsApi.getToolVersions(), + ]); + if (active) { - setVersion(loaded); + setVersion(appVersion); + setToolVersions(tools); } } catch (error) { - console.error("[AboutSection] Failed to get version", error); + console.error("[AboutSection] Failed to load info", error); if (active) { setVersion(null); } } finally { if (active) { setIsLoadingVersion(false); + setIsLoadingTools(false); } } }; @@ -53,6 +79,8 @@ export function AboutSection({ isPortable }: AboutSectionProps) { }; }, []); + // ... (handlers like handleOpenReleaseNotes, handleCheckUpdate) ... + const handleOpenReleaseNotes = useCallback(async () => { try { const targetVersion = updateInfo?.availableVersion ?? version ?? ""; @@ -125,7 +153,7 @@ export function AboutSection({ isPortable }: AboutSectionProps) { const displayVersion = version ?? t("common.unknown"); return ( -
+

{t("common.about")}

@@ -133,24 +161,28 @@ export function AboutSection({ isPortable }: AboutSectionProps) {

-
+
-
-

CC Switch

-

- {t("common.version")}{" "} - {isLoadingVersion ? ( - - ) : ( - `v${displayVersion}` +

+

CC Switch

+
+ + + {t("common.version")} + + {isLoadingVersion ? ( + + ) : ( + {`v${displayVersion}`} + )} + + {isPortable && ( + + + {t("settings.portableMode")} + )} -

- {isPortable ? ( -

- - {t("settings.portableMode")} -

- ) : null} +
@@ -159,6 +191,7 @@ export function AboutSection({ isPortable }: AboutSectionProps) { variant="outline" size="sm" onClick={handleOpenReleaseNotes} + className="h-9" > {t("settings.releaseNotes")} @@ -168,7 +201,7 @@ export function AboutSection({ isPortable }: AboutSectionProps) { size="sm" onClick={handleCheckUpdate} disabled={isChecking || isDownloading} - className="min-w-[140px]" + className="min-w-[140px] h-9" > {isDownloading ? ( @@ -194,18 +227,71 @@ export function AboutSection({ isPortable }: AboutSectionProps) {
- {hasUpdate && updateInfo ? ( -
-

+ {hasUpdate && updateInfo && ( +

+

{t("settings.updateAvailable", { version: updateInfo.availableVersion, })}

- {updateInfo.notes ? ( -

{updateInfo.notes}

- ) : null} + {updateInfo.notes && ( +

+ {updateInfo.notes} +

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

+ 本地环境检查 +

+
+ {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} + + )} + +
+ ) : ( + + )} +
+
+
+ {tool.version ? tool.version : tool.error || "未安装"} +
+
+
+ ))} +
); diff --git a/src/components/settings/DirectorySettings.tsx b/src/components/settings/DirectorySettings.tsx index 17bb269c9..d33b2d776 100644 --- a/src/components/settings/DirectorySettings.tsx +++ b/src/components/settings/DirectorySettings.tsx @@ -50,7 +50,7 @@ export function DirectorySettings({ onAppConfigChange(event.target.value)} /> - + ) : null} @@ -275,10 +473,7 @@ export function SettingsPage({ open={showRestartPrompt} onOpenChange={(open) => !open && handleRestartLater()} > - + {t("settings.restartRequired")} @@ -291,7 +486,7 @@ export function SettingsPage({ diff --git a/src/components/settings/WindowSettings.tsx b/src/components/settings/WindowSettings.tsx index 06f0d39a8..05121ccfe 100644 --- a/src/components/settings/WindowSettings.tsx +++ b/src/components/settings/WindowSettings.tsx @@ -1,6 +1,7 @@ import { Switch } from "@/components/ui/switch"; import { useTranslation } from "react-i18next"; import type { SettingsFormState } from "@/hooks/useSettings"; +import { AppWindow, MonitorUp, Power } from "lucide-react"; interface WindowSettingsProps { settings: SettingsFormState; @@ -12,40 +13,46 @@ export function WindowSettings({ settings, onChange }: WindowSettingsProps) { return (
-
+
+

{t("settings.windowBehavior")}

-

- {t("settings.windowBehaviorHint")} -

-
+ - onChange({ launchOnStartup: value })} - /> +
+ } + title={t("settings.launchOnStartup")} + description={t("settings.launchOnStartupDescription")} + checked={!!settings.launchOnStartup} + onCheckedChange={(value) => onChange({ launchOnStartup: value })} + /> - onChange({ minimizeToTrayOnClose: value })} - /> + } + title={t("settings.minimizeToTray")} + description={t("settings.minimizeToTrayDescription")} + checked={settings.minimizeToTrayOnClose} + onCheckedChange={(value) => + onChange({ minimizeToTrayOnClose: value }) + } + /> - - onChange({ enableClaudePluginIntegration: value }) - } - /> + } + title={t("settings.enableClaudePluginIntegration")} + description={t("settings.enableClaudePluginIntegrationDescription")} + checked={!!settings.enableClaudePluginIntegration} + onCheckedChange={(value) => + onChange({ enableClaudePluginIntegration: value }) + } + /> +
); } interface ToggleRowProps { + icon: React.ReactNode; title: string; description?: string; checked: boolean; @@ -53,18 +60,24 @@ interface ToggleRowProps { } function ToggleRow({ + icon, title, description, checked, onCheckedChange, }: ToggleRowProps) { return ( -
-
-

{title}

- {description ? ( -

{description}

- ) : null} +
+
+
+ {icon} +
+
+

{title}

+ {description ? ( +

{description}

+ ) : null} +
{ return await invoke("get_auto_launch_status"); }, + + async getToolVersions(): Promise< + Array<{ + name: string; + version: string | null; + latest_version: string | null; + error: string | null; + }> + > { + return await invoke("get_tool_versions"); + }, };