Files
CC-Switch/src/components/settings/DirectorySettings.tsx
T
YoVinchen d83312ad3f feat(settings): enhance settings page with accordion layout and tool versions
Major settings page improvements:

AboutSection:
- Add local tool version detection (Claude, Codex, Gemini)
- Display installed vs latest version comparison with visual indicators
- Show update availability badges and environment check cards

SettingsPage:
- Reorganize advanced settings into collapsible accordion sections
- Add proxy control panel with inline status toggle
- Integrate auto-failover configuration with accordion UI
- Add database and cost calculation config sections

DirectorySettings & WindowSettings:
- Minor styling adjustments for consistency

settings.ts API:
- Add getToolVersions() wrapper for new backend command
2025-12-08 17:05:48 +08:00

189 lines
5.4 KiB
TypeScript

import { useMemo } from "react";
import { FolderSearch, Undo2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useTranslation } from "react-i18next";
import type { AppId } from "@/lib/api";
import type { ResolvedDirectories } from "@/hooks/useSettings";
interface DirectorySettingsProps {
appConfigDir?: string;
resolvedDirs: ResolvedDirectories;
onAppConfigChange: (value?: string) => void;
onBrowseAppConfig: () => Promise<void>;
onResetAppConfig: () => Promise<void>;
claudeDir?: string;
codexDir?: string;
geminiDir?: string;
onDirectoryChange: (app: AppId, value?: string) => void;
onBrowseDirectory: (app: AppId) => Promise<void>;
onResetDirectory: (app: AppId) => Promise<void>;
}
export function DirectorySettings({
appConfigDir,
resolvedDirs,
onAppConfigChange,
onBrowseAppConfig,
onResetAppConfig,
claudeDir,
codexDir,
geminiDir,
onDirectoryChange,
onBrowseDirectory,
onResetDirectory,
}: DirectorySettingsProps) {
const { t } = useTranslation();
return (
<>
{/* CC Switch 配置目录 - 独立区块 */}
<section className="space-y-4">
<header className="space-y-1">
<h3 className="text-sm font-medium">{t("settings.appConfigDir")}</h3>
<p className="text-xs text-muted-foreground">
{t("settings.appConfigDirDescription")}
</p>
</header>
<div className="flex items-center gap-2">
<Input
value={appConfigDir ?? resolvedDirs.appConfig ?? ""}
placeholder={t("settings.browsePlaceholderApp")}
className="text-xs"
onChange={(event) => onAppConfigChange(event.target.value)}
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={onBrowseAppConfig}
title={t("settings.browseDirectory")}
>
<FolderSearch className="h-4 w-4" />
</Button>
<Button
type="button"
variant="outline"
size="icon"
onClick={onResetAppConfig}
title={t("settings.resetDefault")}
>
<Undo2 className="h-4 w-4" />
</Button>
</div>
</section>
{/* Claude/Codex 配置目录 - 独立区块 */}
<section className="space-y-4">
<header className="space-y-1">
<h3 className="text-sm font-medium">
{t("settings.configDirectoryOverride")}
</h3>
<p className="text-xs text-muted-foreground">
{t("settings.configDirectoryDescription")}
</p>
</header>
<DirectoryInput
label={t("settings.claudeConfigDir")}
description={undefined}
value={claudeDir}
resolvedValue={resolvedDirs.claude}
placeholder={t("settings.browsePlaceholderClaude")}
onChange={(val) => onDirectoryChange("claude", val)}
onBrowse={() => onBrowseDirectory("claude")}
onReset={() => onResetDirectory("claude")}
/>
<DirectoryInput
label={t("settings.codexConfigDir")}
description={undefined}
value={codexDir}
resolvedValue={resolvedDirs.codex}
placeholder={t("settings.browsePlaceholderCodex")}
onChange={(val) => onDirectoryChange("codex", val)}
onBrowse={() => onBrowseDirectory("codex")}
onReset={() => onResetDirectory("codex")}
/>
<DirectoryInput
label={t("settings.geminiConfigDir")}
description={undefined}
value={geminiDir}
resolvedValue={resolvedDirs.gemini}
placeholder={t("settings.browsePlaceholderGemini")}
onChange={(val) => onDirectoryChange("gemini", val)}
onBrowse={() => onBrowseDirectory("gemini")}
onReset={() => onResetDirectory("gemini")}
/>
</section>
</>
);
}
interface DirectoryInputProps {
label: string;
description?: string;
value?: string;
resolvedValue: string;
placeholder?: string;
onChange: (value?: string) => void;
onBrowse: () => Promise<void>;
onReset: () => Promise<void>;
}
function DirectoryInput({
label,
description,
value,
resolvedValue,
placeholder,
onChange,
onBrowse,
onReset,
}: DirectoryInputProps) {
const { t } = useTranslation();
const displayValue = useMemo(
() => value ?? resolvedValue ?? "",
[value, resolvedValue],
);
return (
<div className="space-y-1.5">
<div className="space-y-1">
<p className="text-xs font-medium text-foreground">{label}</p>
{description ? (
<p className="text-xs text-muted-foreground">{description}</p>
) : null}
</div>
<div className="flex items-center gap-2">
<Input
value={displayValue}
placeholder={placeholder}
className="text-xs"
onChange={(event) => onChange(event.target.value)}
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={onBrowse}
title={t("settings.browseDirectory")}
>
<FolderSearch className="h-4 w-4" />
</Button>
<Button
type="button"
variant="outline"
size="icon"
onClick={onReset}
title={t("settings.resetDefault")}
>
<Undo2 className="h-4 w-4" />
</Button>
</div>
</div>
);
}