diff --git a/src/components/providers/forms/CommonConfigEditor.tsx b/src/components/providers/forms/CommonConfigEditor.tsx
new file mode 100644
index 0000000..665560c
--- /dev/null
+++ b/src/components/providers/forms/CommonConfigEditor.tsx
@@ -0,0 +1,145 @@
+import { useTranslation } from "react-i18next";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Label } from "@/components/ui/label";
+import JsonEditor from "@/components/JsonEditor";
+import { useTheme } from "@/components/theme-provider";
+import { useMemo } from "react";
+
+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;
+}
+
+export function CommonConfigEditor({
+ value,
+ onChange,
+ useCommonConfig,
+ onCommonConfigToggle,
+ commonConfigSnippet,
+ onCommonConfigSnippetChange,
+ commonConfigError,
+ onEditClick,
+ isModalOpen,
+ onModalClose,
+}: CommonConfigEditorProps) {
+ const { t } = useTranslation();
+ const { theme } = useTheme();
+
+ const isDarkMode = useMemo(() => {
+ if (theme === "dark") return true;
+ if (theme === "light") return false;
+ return typeof window !== "undefined"
+ ? window.document.documentElement.classList.contains("dark")
+ : false;
+ }, [theme]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {commonConfigError && !isModalOpen && (
+
+ {commonConfigError}
+
+ )}
+
+
+
+
+ {t("claudeConfig.fullSettingsHint", {
+ defaultValue: "请填写完整的 Claude Code 配置",
+ })}
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx
index 53d7929..155cd7a 100644
--- a/src/components/providers/forms/ProviderForm.tsx
+++ b/src/components/providers/forms/ProviderForm.tsx
@@ -12,8 +12,6 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
-import { useTheme } from "@/components/theme-provider";
-import JsonEditor from "@/components/JsonEditor";
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
import type { AppType } from "@/lib/api";
import type { ProviderCategory, CustomEndpoint } from "@/types";
@@ -27,6 +25,7 @@ import ApiKeyInput from "@/components/ProviderForm/ApiKeyInput";
import EndpointSpeedTest from "@/components/ProviderForm/EndpointSpeedTest";
import CodexConfigEditor from "@/components/ProviderForm/CodexConfigEditor";
import KimiModelSelector from "@/components/ProviderForm/KimiModelSelector";
+import { CommonConfigEditor } from "./CommonConfigEditor";
import { Zap } from "lucide-react";
import {
useProviderCategory,
@@ -38,6 +37,7 @@ import {
useCustomEndpoints,
useKimiModelSelector,
useTemplateValues,
+ useCommonConfigSnippet,
} from "./hooks";
const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {}, config: {} }, null, 2);
@@ -68,7 +68,6 @@ export function ProviderForm({
initialData,
}: ProviderFormProps) {
const { t } = useTranslation();
- const { theme } = useTheme();
const isEditMode = Boolean(initialData);
const [selectedPresetId, setSelectedPresetId] = useState(
@@ -172,14 +171,6 @@ export function ProviderForm({
form.reset(defaultValues);
}, [defaultValues, form]);
- const isDarkMode = useMemo(() => {
- if (theme === "dark") return true;
- if (theme === "light") return false;
- return typeof window !== "undefined"
- ? window.document.documentElement.classList.contains("dark")
- : false;
- }, [theme]);
-
const presetCategoryLabels: Record = useMemo(
() => ({
official: t("providerPreset.categoryOfficial", {
@@ -243,6 +234,21 @@ export function ProviderForm({
onConfigChange: (config) => form.setValue("settingsConfig", config),
});
+ // 使用通用配置片段 hook (仅 Claude 模式)
+ const {
+ useCommonConfig,
+ commonConfigSnippet,
+ commonConfigError,
+ handleCommonConfigToggle,
+ handleCommonConfigSnippetChange,
+ } = useCommonConfigSnippet({
+ settingsConfig: form.watch("settingsConfig"),
+ onConfigChange: (config) => form.setValue("settingsConfig", config),
+ initialData: appType === "claude" ? initialData : undefined,
+ });
+
+ const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
+
const handleSubmit = (values: ProviderFormData) => {
// 验证模板变量(仅 Claude 模式)
if (appType === "claude" && templateValueEntries.length > 0) {
@@ -790,7 +796,7 @@ export function ProviderForm({
/>
)}
- {/* 配置编辑器:Claude 使用 JSON 编辑器,Codex 使用专用编辑器 */}
+ {/* 配置编辑器:Claude 使用通用配置编辑器,Codex 使用专用编辑器 */}
{appType === "codex" ? (
) : (
- (
-
-
- {t("provider.configJson", { defaultValue: "配置 JSON" })}
-
-
-
-
-
-
-
-
- )}
+ form.setValue("settingsConfig", value)}
+ useCommonConfig={useCommonConfig}
+ onCommonConfigToggle={handleCommonConfigToggle}
+ commonConfigSnippet={commonConfigSnippet}
+ onCommonConfigSnippetChange={handleCommonConfigSnippetChange}
+ commonConfigError={commonConfigError}
+ onEditClick={() => setIsCommonConfigModalOpen(true)}
+ isModalOpen={isCommonConfigModalOpen}
+ onModalClose={() => setIsCommonConfigModalOpen(false)}
/>
)}
diff --git a/src/components/providers/forms/hooks/index.ts b/src/components/providers/forms/hooks/index.ts
index 5b3579b..41745ee 100644
--- a/src/components/providers/forms/hooks/index.ts
+++ b/src/components/providers/forms/hooks/index.ts
@@ -7,3 +7,4 @@ export { useApiKeyLink } from "./useApiKeyLink";
export { useCustomEndpoints } from "./useCustomEndpoints";
export { useKimiModelSelector } from "./useKimiModelSelector";
export { useTemplateValues } from "./useTemplateValues";
+export { useCommonConfigSnippet } from "./useCommonConfigSnippet";
diff --git a/src/components/providers/forms/hooks/useCommonConfigSnippet.ts b/src/components/providers/forms/hooks/useCommonConfigSnippet.ts
new file mode 100644
index 0000000..4548241
--- /dev/null
+++ b/src/components/providers/forms/hooks/useCommonConfigSnippet.ts
@@ -0,0 +1,198 @@
+import { useState, useEffect, useCallback, useRef } from "react";
+import {
+ updateCommonConfigSnippet,
+ hasCommonConfigSnippet,
+ validateJsonConfig,
+} from "@/utils/providerConfigUtils";
+
+const COMMON_CONFIG_STORAGE_KEY = "cc-switch:common-config-snippet";
+const DEFAULT_COMMON_CONFIG_SNIPPET = `{
+ "includeCoAuthoredBy": false
+}`;
+
+interface UseCommonConfigSnippetProps {
+ settingsConfig: string;
+ onConfigChange: (config: string) => void;
+ initialData?: {
+ settingsConfig?: Record;
+ };
+}
+
+/**
+ * 管理 Claude 通用配置片段
+ */
+export function useCommonConfigSnippet({
+ settingsConfig,
+ onConfigChange,
+ initialData,
+}: UseCommonConfigSnippetProps) {
+ const [useCommonConfig, setUseCommonConfig] = useState(false);
+ const [commonConfigSnippet, setCommonConfigSnippetState] = useState(
+ () => {
+ if (typeof window === "undefined") {
+ return DEFAULT_COMMON_CONFIG_SNIPPET;
+ }
+ try {
+ const stored = window.localStorage.getItem(COMMON_CONFIG_STORAGE_KEY);
+ if (stored && stored.trim()) {
+ return stored;
+ }
+ } catch {
+ // ignore localStorage 读取失败
+ }
+ return DEFAULT_COMMON_CONFIG_SNIPPET;
+ },
+ );
+ const [commonConfigError, setCommonConfigError] = useState("");
+
+ // 用于跟踪是否正在通过通用配置更新
+ const isUpdatingFromCommonConfig = useRef(false);
+
+ // 初始化时检查通用配置片段(编辑模式)
+ useEffect(() => {
+ if (initialData) {
+ const configString = JSON.stringify(
+ initialData.settingsConfig,
+ null,
+ 2,
+ );
+ const hasCommon = hasCommonConfigSnippet(
+ configString,
+ commonConfigSnippet,
+ );
+ setUseCommonConfig(hasCommon);
+ }
+ }, [initialData, commonConfigSnippet]);
+
+ // 同步本地存储的通用配置片段
+ useEffect(() => {
+ if (typeof window === "undefined") return;
+ try {
+ if (commonConfigSnippet.trim()) {
+ window.localStorage.setItem(
+ COMMON_CONFIG_STORAGE_KEY,
+ commonConfigSnippet,
+ );
+ } else {
+ window.localStorage.removeItem(COMMON_CONFIG_STORAGE_KEY);
+ }
+ } catch {
+ // ignore
+ }
+ }, [commonConfigSnippet]);
+
+ // 处理通用配置开关
+ const handleCommonConfigToggle = useCallback(
+ (checked: boolean) => {
+ const { updatedConfig, error: snippetError } = updateCommonConfigSnippet(
+ settingsConfig,
+ commonConfigSnippet,
+ checked,
+ );
+
+ if (snippetError) {
+ setCommonConfigError(snippetError);
+ setUseCommonConfig(false);
+ return;
+ }
+
+ setCommonConfigError("");
+ setUseCommonConfig(checked);
+ // 标记正在通过通用配置更新
+ isUpdatingFromCommonConfig.current = true;
+ onConfigChange(updatedConfig);
+ // 在下一个事件循环中重置标记
+ setTimeout(() => {
+ isUpdatingFromCommonConfig.current = false;
+ }, 0);
+ },
+ [settingsConfig, commonConfigSnippet, onConfigChange],
+ );
+
+ // 处理通用配置片段变化
+ const handleCommonConfigSnippetChange = useCallback(
+ (value: string) => {
+ const previousSnippet = commonConfigSnippet;
+ setCommonConfigSnippetState(value);
+
+ if (!value.trim()) {
+ setCommonConfigError("");
+ if (useCommonConfig) {
+ const { updatedConfig } = updateCommonConfigSnippet(
+ settingsConfig,
+ previousSnippet,
+ false,
+ );
+ onConfigChange(updatedConfig);
+ setUseCommonConfig(false);
+ }
+ return;
+ }
+
+ // 验证JSON格式
+ const validationError = validateJsonConfig(value, "通用配置片段");
+ if (validationError) {
+ setCommonConfigError(validationError);
+ } else {
+ setCommonConfigError("");
+ }
+
+ // 若当前启用通用配置且格式正确,需要替换为最新片段
+ if (useCommonConfig && !validationError) {
+ const removeResult = updateCommonConfigSnippet(
+ settingsConfig,
+ previousSnippet,
+ false,
+ );
+ if (removeResult.error) {
+ setCommonConfigError(removeResult.error);
+ return;
+ }
+ const addResult = updateCommonConfigSnippet(
+ removeResult.updatedConfig,
+ value,
+ true,
+ );
+
+ if (addResult.error) {
+ setCommonConfigError(addResult.error);
+ return;
+ }
+
+ // 标记正在通过通用配置更新,避免触发状态检查
+ isUpdatingFromCommonConfig.current = true;
+ onConfigChange(addResult.updatedConfig);
+ // 在下一个事件循环中重置标记
+ setTimeout(() => {
+ isUpdatingFromCommonConfig.current = false;
+ }, 0);
+ }
+ },
+ [
+ commonConfigSnippet,
+ settingsConfig,
+ useCommonConfig,
+ onConfigChange,
+ ],
+ );
+
+ // 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
+ useEffect(() => {
+ if (isUpdatingFromCommonConfig.current) {
+ return;
+ }
+ const hasCommon = hasCommonConfigSnippet(
+ settingsConfig,
+ commonConfigSnippet,
+ );
+ setUseCommonConfig(hasCommon);
+ }, [settingsConfig, commonConfigSnippet]);
+
+ return {
+ useCommonConfig,
+ commonConfigSnippet,
+ commonConfigError,
+ handleCommonConfigToggle,
+ handleCommonConfigSnippetChange,
+ };
+}