mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
11f1ef33e4
Add 3 quick-toggle checkboxes above the JSON editor in Claude provider settings: - Hide AI Attribution (sets attribution.commit/pr to empty string) - Extended Thinking (sets alwaysThinkingEnabled to true) - Teammates Mode (sets env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS to "1") Uses local state to keep toggles and JsonEditor in sync without triggering ProviderForm re-renders. Includes i18n for zh/en/ja. Co-authored-by: Jason <farion1231@gmail.com>
281 lines
9.2 KiB
TypeScript
281 lines
9.2 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { useEffect, useState, useCallback, useMemo } from "react";
|
|
import { FullScreenPanel } from "@/components/common/FullScreenPanel";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Save, Download, Loader2 } from "lucide-react";
|
|
import JsonEditor from "@/components/JsonEditor";
|
|
|
|
interface CommonConfigEditorProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
useCommonConfig: boolean;
|
|
onCommonConfigToggle: (checked: boolean) => void;
|
|
commonConfigSnippet: string;
|
|
onCommonConfigSnippetChange: (value: string) => void;
|
|
commonConfigError: string;
|
|
onEditClick: () => void;
|
|
isModalOpen: boolean;
|
|
onModalClose: () => void;
|
|
onExtract?: () => void;
|
|
isExtracting?: boolean;
|
|
}
|
|
|
|
export function CommonConfigEditor({
|
|
value,
|
|
onChange,
|
|
useCommonConfig,
|
|
onCommonConfigToggle,
|
|
commonConfigSnippet,
|
|
onCommonConfigSnippetChange,
|
|
commonConfigError,
|
|
onEditClick,
|
|
isModalOpen,
|
|
onModalClose,
|
|
onExtract,
|
|
isExtracting,
|
|
}: CommonConfigEditorProps) {
|
|
const { t } = useTranslation();
|
|
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setIsDarkMode(document.documentElement.classList.contains("dark"));
|
|
|
|
const observer = new MutationObserver(() => {
|
|
setIsDarkMode(document.documentElement.classList.contains("dark"));
|
|
});
|
|
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ["class"],
|
|
});
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
// Mirror value prop to local state so checkbox toggles and JsonEditor stay in sync
|
|
// (parent uses form.getValues which doesn't trigger re-renders)
|
|
const [localValue, setLocalValue] = useState(value);
|
|
|
|
useEffect(() => {
|
|
setLocalValue(value);
|
|
}, [value]);
|
|
|
|
const handleLocalChange = useCallback(
|
|
(newValue: string) => {
|
|
setLocalValue(newValue);
|
|
onChange(newValue);
|
|
},
|
|
[onChange],
|
|
);
|
|
|
|
const toggleStates = useMemo(() => {
|
|
try {
|
|
const config = JSON.parse(localValue);
|
|
return {
|
|
hideAttribution:
|
|
config?.attribution?.commit === "" && config?.attribution?.pr === "",
|
|
alwaysThinking: config?.alwaysThinkingEnabled === true,
|
|
teammates:
|
|
config?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1" ||
|
|
config?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === 1,
|
|
};
|
|
} catch {
|
|
return {
|
|
hideAttribution: false,
|
|
alwaysThinking: false,
|
|
teammates: false,
|
|
};
|
|
}
|
|
}, [localValue]);
|
|
|
|
const handleToggle = useCallback(
|
|
(toggleKey: string, checked: boolean) => {
|
|
try {
|
|
const config = JSON.parse(localValue || "{}");
|
|
|
|
switch (toggleKey) {
|
|
case "hideAttribution":
|
|
if (checked) {
|
|
config.attribution = { commit: "", pr: "" };
|
|
} else {
|
|
delete config.attribution;
|
|
}
|
|
break;
|
|
case "alwaysThinking":
|
|
if (checked) {
|
|
config.alwaysThinkingEnabled = true;
|
|
} else {
|
|
delete config.alwaysThinkingEnabled;
|
|
}
|
|
break;
|
|
case "teammates":
|
|
if (!config.env) config.env = {};
|
|
if (checked) {
|
|
config.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
} else {
|
|
delete config.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
if (Object.keys(config.env).length === 0) delete config.env;
|
|
}
|
|
break;
|
|
}
|
|
|
|
handleLocalChange(JSON.stringify(config, null, 2));
|
|
} catch {
|
|
// Don't modify if JSON is invalid
|
|
}
|
|
},
|
|
[localValue, handleLocalChange],
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label htmlFor="settingsConfig">{t("provider.configJson")}</Label>
|
|
<div className="flex items-center gap-2">
|
|
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
id="useCommonConfig"
|
|
checked={useCommonConfig}
|
|
onChange={(e) => onCommonConfigToggle(e.target.checked)}
|
|
className="w-4 h-4 text-blue-500 bg-white dark:bg-gray-800 border-border-default rounded focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2"
|
|
/>
|
|
<span>
|
|
{t("claudeConfig.writeCommonConfig", {
|
|
defaultValue: "写入通用配置",
|
|
})}
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-end">
|
|
<button
|
|
type="button"
|
|
onClick={onEditClick}
|
|
className="text-xs text-blue-400 dark:text-blue-500 hover:text-blue-500 dark:hover:text-blue-400 transition-colors"
|
|
>
|
|
{t("claudeConfig.editCommonConfig", {
|
|
defaultValue: "编辑通用配置",
|
|
})}
|
|
</button>
|
|
</div>
|
|
{commonConfigError && !isModalOpen && (
|
|
<p className="text-xs text-red-500 dark:text-red-400 text-right">
|
|
{commonConfigError}
|
|
</p>
|
|
)}
|
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-1">
|
|
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={toggleStates.hideAttribution}
|
|
onChange={(e) =>
|
|
handleToggle("hideAttribution", e.target.checked)
|
|
}
|
|
className="w-4 h-4 text-blue-500 bg-white dark:bg-gray-800 border-border-default rounded focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2"
|
|
/>
|
|
<span>{t("claudeConfig.hideAttribution")}</span>
|
|
</label>
|
|
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={toggleStates.alwaysThinking}
|
|
onChange={(e) => handleToggle("alwaysThinking", e.target.checked)}
|
|
className="w-4 h-4 text-blue-500 bg-white dark:bg-gray-800 border-border-default rounded focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2"
|
|
/>
|
|
<span>{t("claudeConfig.alwaysThinking")}</span>
|
|
</label>
|
|
<label className="inline-flex items-center gap-2 text-sm text-muted-foreground cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={toggleStates.teammates}
|
|
onChange={(e) => handleToggle("teammates", e.target.checked)}
|
|
className="w-4 h-4 text-blue-500 bg-white dark:bg-gray-800 border-border-default rounded focus:ring-blue-500 dark:focus:ring-blue-400 focus:ring-2"
|
|
/>
|
|
<span>{t("claudeConfig.enableTeammates")}</span>
|
|
</label>
|
|
</div>
|
|
<JsonEditor
|
|
value={localValue}
|
|
onChange={handleLocalChange}
|
|
placeholder={`{
|
|
"env": {
|
|
"ANTHROPIC_BASE_URL": "https://your-api-endpoint.com",
|
|
"ANTHROPIC_AUTH_TOKEN": "your-api-key-here"
|
|
}
|
|
}`}
|
|
darkMode={isDarkMode}
|
|
rows={14}
|
|
showValidation={true}
|
|
language="json"
|
|
/>
|
|
</div>
|
|
|
|
<FullScreenPanel
|
|
isOpen={isModalOpen}
|
|
title={t("claudeConfig.editCommonConfigTitle", {
|
|
defaultValue: "编辑通用配置片段",
|
|
})}
|
|
onClose={onModalClose}
|
|
footer={
|
|
<>
|
|
{onExtract && (
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={onExtract}
|
|
disabled={isExtracting}
|
|
className="gap-2"
|
|
>
|
|
{isExtracting ? (
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
) : (
|
|
<Download className="w-4 h-4" />
|
|
)}
|
|
{t("claudeConfig.extractFromCurrent", {
|
|
defaultValue: "从编辑内容提取",
|
|
})}
|
|
</Button>
|
|
)}
|
|
<Button type="button" variant="outline" onClick={onModalClose}>
|
|
{t("common.cancel")}
|
|
</Button>
|
|
<Button type="button" onClick={onModalClose} className="gap-2">
|
|
<Save className="w-4 h-4" />
|
|
{t("common.save")}
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<div className="space-y-4">
|
|
<p className="text-sm text-muted-foreground">
|
|
{t("claudeConfig.commonConfigHint", {
|
|
defaultValue: "通用配置片段将合并到所有启用它的供应商配置中",
|
|
})}
|
|
</p>
|
|
<JsonEditor
|
|
value={commonConfigSnippet}
|
|
onChange={onCommonConfigSnippetChange}
|
|
placeholder={`{
|
|
"env": {
|
|
"ANTHROPIC_BASE_URL": "https://your-api-endpoint.com"
|
|
}
|
|
}`}
|
|
darkMode={isDarkMode}
|
|
rows={16}
|
|
showValidation={true}
|
|
language="json"
|
|
/>
|
|
{commonConfigError && (
|
|
<p className="text-sm text-red-500 dark:text-red-400">
|
|
{commonConfigError}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</FullScreenPanel>
|
|
</>
|
|
);
|
|
}
|