feat: integrate Codex common config snippet and template modal features

- Created useCodexCommonConfig hook for managing Codex TOML common config
- Persists to localStorage with key 'cc-switch:codex-common-config-snippet'
- Added isCodexTemplateModalOpen state to ProviderForm
- Connected all CodexConfigEditor props:
  - Common config snippet management (useCommonConfig, handlers)
  - Template modal state (isTemplateModalOpen, setIsTemplateModalOpen)
  - Form field callbacks (onWebsiteUrlChange, onNameChange)
  - Custom mode detection (isCustomMode)
- Hook structure mirrors useCommonConfigSnippet for consistency
This commit is contained in:
Jason
2025-10-16 21:04:32 +08:00
parent 856beb3b70
commit 8ce574bdd2
3 changed files with 214 additions and 5 deletions

View File

@@ -38,6 +38,7 @@ import {
useKimiModelSelector, useKimiModelSelector,
useTemplateValues, useTemplateValues,
useCommonConfigSnippet, useCommonConfigSnippet,
useCodexCommonConfig,
} from "./hooks"; } from "./hooks";
const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {}, config: {} }, null, 2); const CLAUDE_DEFAULT_CONFIG = JSON.stringify({ env: {}, config: {} }, null, 2);
@@ -166,6 +167,8 @@ export function ProviderForm({
const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] = const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] =
useState(false); useState(false);
const [isCodexTemplateModalOpen, setIsCodexTemplateModalOpen] =
useState(false);
useEffect(() => { useEffect(() => {
form.reset(defaultValues); form.reset(defaultValues);
@@ -247,6 +250,19 @@ export function ProviderForm({
initialData: appType === "claude" ? initialData : undefined, initialData: appType === "claude" ? initialData : undefined,
}); });
// 使用 Codex 通用配置片段 hook (仅 Codex 模式)
const {
useCommonConfig: useCodexCommonConfigFlag,
commonConfigSnippet: codexCommonConfigSnippet,
commonConfigError: codexCommonConfigError,
handleCommonConfigToggle: handleCodexCommonConfigToggle,
handleCommonConfigSnippetChange: handleCodexCommonConfigSnippetChange,
} = useCodexCommonConfig({
codexConfig,
onConfigChange: handleCodexConfigChange,
initialData: appType === "codex" ? initialData : undefined,
});
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false); const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
const handleSubmit = (values: ProviderFormData) => { const handleSubmit = (values: ProviderFormData) => {
@@ -803,12 +819,17 @@ export function ProviderForm({
configValue={codexConfig} configValue={codexConfig}
onAuthChange={setCodexAuth} onAuthChange={setCodexAuth}
onConfigChange={handleCodexConfigChange} onConfigChange={handleCodexConfigChange}
useCommonConfig={false} useCommonConfig={useCodexCommonConfigFlag}
onCommonConfigToggle={() => {}} onCommonConfigToggle={handleCodexCommonConfigToggle}
commonConfigSnippet="" commonConfigSnippet={codexCommonConfigSnippet}
onCommonConfigSnippetChange={() => {}} onCommonConfigSnippetChange={handleCodexCommonConfigSnippetChange}
commonConfigError="" commonConfigError={codexCommonConfigError}
authError={codexAuthError} authError={codexAuthError}
isCustomMode={selectedPresetId === "custom"}
onWebsiteUrlChange={(url) => form.setValue("websiteUrl", url)}
onNameChange={(name) => form.setValue("name", name)}
isTemplateModalOpen={isCodexTemplateModalOpen}
setIsTemplateModalOpen={setIsCodexTemplateModalOpen}
/> />
) : ( ) : (
<CommonConfigEditor <CommonConfigEditor

View File

@@ -8,3 +8,4 @@ export { useCustomEndpoints } from "./useCustomEndpoints";
export { useKimiModelSelector } from "./useKimiModelSelector"; export { useKimiModelSelector } from "./useKimiModelSelector";
export { useTemplateValues } from "./useTemplateValues"; export { useTemplateValues } from "./useTemplateValues";
export { useCommonConfigSnippet } from "./useCommonConfigSnippet"; export { useCommonConfigSnippet } from "./useCommonConfigSnippet";
export { useCodexCommonConfig } from "./useCodexCommonConfig";

View File

@@ -0,0 +1,187 @@
import { useState, useEffect, useCallback, useRef } from "react";
import {
updateTomlCommonConfigSnippet,
hasTomlCommonConfigSnippet,
} from "@/utils/providerConfigUtils";
const CODEX_COMMON_CONFIG_STORAGE_KEY = "cc-switch:codex-common-config-snippet";
const DEFAULT_CODEX_COMMON_CONFIG_SNIPPET = `# Common Codex config
# Add your common TOML configuration here`;
interface UseCodexCommonConfigProps {
codexConfig: string;
onConfigChange: (config: string) => void;
initialData?: {
settingsConfig?: Record<string, unknown>;
};
}
/**
* 管理 Codex 通用配置片段 (TOML 格式)
*/
export function useCodexCommonConfig({
codexConfig,
onConfigChange,
initialData,
}: UseCodexCommonConfigProps) {
const [useCommonConfig, setUseCommonConfig] = useState(false);
const [commonConfigSnippet, setCommonConfigSnippetState] = useState<string>(
() => {
if (typeof window === "undefined") {
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
}
try {
const stored = window.localStorage.getItem(CODEX_COMMON_CONFIG_STORAGE_KEY);
if (stored && stored.trim()) {
return stored;
}
} catch {
// ignore localStorage 读取失败
}
return DEFAULT_CODEX_COMMON_CONFIG_SNIPPET;
},
);
const [commonConfigError, setCommonConfigError] = useState("");
// 用于跟踪是否正在通过通用配置更新
const isUpdatingFromCommonConfig = useRef(false);
// 初始化时检查通用配置片段(编辑模式)
useEffect(() => {
if (initialData?.settingsConfig) {
const config =
typeof initialData.settingsConfig.config === "string"
? initialData.settingsConfig.config
: "";
const hasCommon = hasTomlCommonConfigSnippet(config, commonConfigSnippet);
setUseCommonConfig(hasCommon);
}
}, [initialData, commonConfigSnippet]);
// 同步本地存储的通用配置片段
useEffect(() => {
if (typeof window === "undefined") return;
try {
if (commonConfigSnippet.trim()) {
window.localStorage.setItem(
CODEX_COMMON_CONFIG_STORAGE_KEY,
commonConfigSnippet,
);
} else {
window.localStorage.removeItem(CODEX_COMMON_CONFIG_STORAGE_KEY);
}
} catch {
// ignore
}
}, [commonConfigSnippet]);
// 处理通用配置开关
const handleCommonConfigToggle = useCallback(
(checked: boolean) => {
const { updatedConfig, error: snippetError } = updateTomlCommonConfigSnippet(
codexConfig,
commonConfigSnippet,
checked,
);
if (snippetError) {
setCommonConfigError(snippetError);
setUseCommonConfig(false);
return;
}
setCommonConfigError("");
setUseCommonConfig(checked);
// 标记正在通过通用配置更新
isUpdatingFromCommonConfig.current = true;
onConfigChange(updatedConfig);
// 在下一个事件循环中重置标记
setTimeout(() => {
isUpdatingFromCommonConfig.current = false;
}, 0);
},
[codexConfig, commonConfigSnippet, onConfigChange],
);
// 处理通用配置片段变化
const handleCommonConfigSnippetChange = useCallback(
(value: string) => {
const previousSnippet = commonConfigSnippet;
setCommonConfigSnippetState(value);
if (!value.trim()) {
setCommonConfigError("");
if (useCommonConfig) {
const { updatedConfig } = updateTomlCommonConfigSnippet(
codexConfig,
previousSnippet,
false,
);
onConfigChange(updatedConfig);
setUseCommonConfig(false);
}
return;
}
// TOML 格式校验较为复杂,暂时不做校验,直接清空错误
setCommonConfigError("");
// 若当前启用通用配置,需要替换为最新片段
if (useCommonConfig) {
const removeResult = updateTomlCommonConfigSnippet(
codexConfig,
previousSnippet,
false,
);
if (removeResult.error) {
setCommonConfigError(removeResult.error);
return;
}
const addResult = updateTomlCommonConfigSnippet(
removeResult.updatedConfig,
value,
true,
);
if (addResult.error) {
setCommonConfigError(addResult.error);
return;
}
// 标记正在通过通用配置更新,避免触发状态检查
isUpdatingFromCommonConfig.current = true;
onConfigChange(addResult.updatedConfig);
// 在下一个事件循环中重置标记
setTimeout(() => {
isUpdatingFromCommonConfig.current = false;
}, 0);
}
},
[
commonConfigSnippet,
codexConfig,
useCommonConfig,
onConfigChange,
],
);
// 当配置变化时检查是否包含通用配置(但避免在通过通用配置更新时检查)
useEffect(() => {
if (isUpdatingFromCommonConfig.current) {
return;
}
const hasCommon = hasTomlCommonConfigSnippet(
codexConfig,
commonConfigSnippet,
);
setUseCommonConfig(hasCommon);
}, [codexConfig, commonConfigSnippet]);
return {
useCommonConfig,
commonConfigSnippet,
commonConfigError,
handleCommonConfigToggle,
handleCommonConfigSnippetChange,
};
}