Files
cc-switch/src/components/providers/forms/hooks/useCodexCommonConfig.ts
T
2026-01-05 21:53:01 +08:00

309 lines
9.4 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
import {
updateTomlCommonConfigSnippet,
hasTomlCommonConfigSnippet,
} from "@/utils/providerConfigUtils";
import { configApi } from "@/lib/api";
const LEGACY_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>;
};
selectedPresetId?: string;
}
/**
* 管理 Codex 通用配置片段 (TOML 格式)
* 从 config.json 读取和保存,支持从 localStorage 平滑迁移
*/
export function useCodexCommonConfig({
codexConfig,
onConfigChange,
initialData,
selectedPresetId,
}: UseCodexCommonConfigProps) {
const { t } = useTranslation();
const [useCommonConfig, setUseCommonConfig] = useState(false);
const [commonConfigSnippet, setCommonConfigSnippetState] = useState<string>(
DEFAULT_CODEX_COMMON_CONFIG_SNIPPET,
);
const [commonConfigError, setCommonConfigError] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [isExtracting, setIsExtracting] = useState(false);
// 用于跟踪是否正在通过通用配置更新
const isUpdatingFromCommonConfig = useRef(false);
// 用于跟踪新建模式是否已初始化默认勾选
const hasInitializedNewMode = useRef(false);
// 当预设变化时,重置初始化标记,使新预设能够重新触发初始化逻辑
useEffect(() => {
hasInitializedNewMode.current = false;
}, [selectedPresetId]);
// 初始化:从 config.json 加载,支持从 localStorage 迁移
useEffect(() => {
let mounted = true;
const loadSnippet = async () => {
try {
// 使用统一 API 加载
const snippet = await configApi.getCommonConfigSnippet("codex");
if (snippet && snippet.trim()) {
if (mounted) {
setCommonConfigSnippetState(snippet);
}
} else {
// 如果 config.json 中没有,尝试从 localStorage 迁移
if (typeof window !== "undefined") {
try {
const legacySnippet =
window.localStorage.getItem(LEGACY_STORAGE_KEY);
if (legacySnippet && legacySnippet.trim()) {
// 迁移到 config.json
await configApi.setCommonConfigSnippet("codex", legacySnippet);
if (mounted) {
setCommonConfigSnippetState(legacySnippet);
}
// 清理 localStorage
window.localStorage.removeItem(LEGACY_STORAGE_KEY);
console.log(
"[迁移] Codex 通用配置已从 localStorage 迁移到 config.json",
);
}
} catch (e) {
console.warn("[迁移] 从 localStorage 迁移失败:", e);
}
}
}
} catch (error) {
console.error("加载 Codex 通用配置失败:", error);
} finally {
if (mounted) {
setIsLoading(false);
}
}
};
loadSnippet();
return () => {
mounted = false;
};
}, []);
// 初始化时检查通用配置片段(编辑模式)
useEffect(() => {
if (initialData?.settingsConfig && !isLoading) {
const config =
typeof initialData.settingsConfig.config === "string"
? initialData.settingsConfig.config
: "";
const hasCommon = hasTomlCommonConfigSnippet(config, commonConfigSnippet);
setUseCommonConfig(hasCommon);
}
}, [initialData, commonConfigSnippet, isLoading]);
// 新建模式:如果通用配置片段存在且有效,默认启用
useEffect(() => {
// 仅新建模式、加载完成、尚未初始化过
if (!initialData && !isLoading && !hasInitializedNewMode.current) {
hasInitializedNewMode.current = true;
// 检查 TOML 片段是否有实质内容(不只是注释和空行)
const lines = commonConfigSnippet.split("\n");
const hasContent = lines.some((line) => {
const trimmed = line.trim();
return trimmed && !trimmed.startsWith("#");
});
if (hasContent) {
setUseCommonConfig(true);
// 合并通用配置到当前配置
const { updatedConfig, error } = updateTomlCommonConfigSnippet(
codexConfig,
commonConfigSnippet,
true,
);
if (!error) {
isUpdatingFromCommonConfig.current = true;
onConfigChange(updatedConfig);
setTimeout(() => {
isUpdatingFromCommonConfig.current = false;
}, 0);
}
}
}
}, [
initialData,
commonConfigSnippet,
isLoading,
codexConfig,
onConfigChange,
]);
// 处理通用配置开关
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("");
// 保存到 config.json(清空)
configApi.setCommonConfigSnippet("codex", "").catch((error) => {
console.error("保存 Codex 通用配置失败:", error);
setCommonConfigError(
t("codexConfig.saveFailed", { error: String(error) }),
);
});
if (useCommonConfig) {
const { updatedConfig } = updateTomlCommonConfigSnippet(
codexConfig,
previousSnippet,
false,
);
onConfigChange(updatedConfig);
setUseCommonConfig(false);
}
return;
}
// TOML 格式校验较为复杂,暂时不做校验,直接清空错误
setCommonConfigError("");
// 保存到 config.json
configApi.setCommonConfigSnippet("codex", value).catch((error) => {
console.error("保存 Codex 通用配置失败:", error);
setCommonConfigError(
t("codexConfig.saveFailed", { error: String(error) }),
);
});
// 若当前启用通用配置,需要替换为最新片段
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 || isLoading) {
return;
}
const hasCommon = hasTomlCommonConfigSnippet(
codexConfig,
commonConfigSnippet,
);
setUseCommonConfig(hasCommon);
}, [codexConfig, commonConfigSnippet, isLoading]);
// 从编辑器当前内容提取通用配置片段
const handleExtract = useCallback(async () => {
setIsExtracting(true);
setCommonConfigError("");
try {
const extracted = await configApi.extractCommonConfigSnippet("codex", {
settingsConfig: JSON.stringify({
config: codexConfig ?? "",
}),
});
if (!extracted || !extracted.trim()) {
setCommonConfigError(t("codexConfig.extractNoCommonConfig"));
return;
}
// 更新片段状态
setCommonConfigSnippetState(extracted);
// 保存到后端
await configApi.setCommonConfigSnippet("codex", extracted);
} catch (error) {
console.error("提取 Codex 通用配置失败:", error);
setCommonConfigError(
t("codexConfig.extractFailed", { error: String(error) }),
);
} finally {
setIsExtracting(false);
}
}, [codexConfig, t]);
return {
useCommonConfig,
commonConfigSnippet,
commonConfigError,
isLoading,
isExtracting,
handleCommonConfigToggle,
handleCommonConfigSnippetChange,
handleExtract,
};
}