mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 02:05:57 +08:00
fix: prevent common config modal infinite reopen loop and add draft editing
The auto-open useEffect in CodexConfigEditor and GeminiConfigEditor created an inescapable loop: commonConfigError triggered modal open, closing the modal didn't clear the error, so the effect immediately reopened it — locking the entire UI. - Remove auto-open useEffect from both Codex and Gemini config editors - Convert common config modals to draft editing (edit locally, validate before save) instead of persisting on every keystroke - Add TOML/JSON pre-validation via parseCommonConfigSnippet before any merge operation to prevent invalid content from being persisted - Expose clearCommonConfigError so editors can clear stale errors on modal close
This commit is contained in:
@@ -216,43 +216,55 @@ export function useGeminiCommonConfig({
|
||||
|
||||
// 初始化时检查通用配置片段(编辑模式)
|
||||
useEffect(() => {
|
||||
if (initialData?.settingsConfig && !isLoading) {
|
||||
try {
|
||||
const env =
|
||||
isPlainObject(initialData.settingsConfig.env) &&
|
||||
Object.keys(initialData.settingsConfig.env).length > 0
|
||||
? (initialData.settingsConfig.env as Record<string, string>)
|
||||
: {};
|
||||
const parsed = parseSnippetEnv(commonConfigSnippet);
|
||||
if (parsed.error) return;
|
||||
const inferredHasCommon = hasEnvCommonConfigSnippet(
|
||||
env,
|
||||
parsed.env as Record<string, string>,
|
||||
);
|
||||
const hasCommon = initialEnabled ?? inferredHasCommon;
|
||||
setUseCommonConfig(hasCommon);
|
||||
if (
|
||||
!initialData?.settingsConfig ||
|
||||
isLoading ||
|
||||
hasInitializedEditMode.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
hasCommon &&
|
||||
!inferredHasCommon &&
|
||||
!hasInitializedEditMode.current
|
||||
) {
|
||||
hasInitializedEditMode.current = true;
|
||||
const currentEnv = envStringToObj(envValue);
|
||||
const merged = applySnippetToEnv(currentEnv, parsed.env);
|
||||
const nextEnvString = envObjToString(merged);
|
||||
hasInitializedEditMode.current = true;
|
||||
|
||||
isUpdatingFromCommonConfig.current = true;
|
||||
onEnvChange(nextEnvString);
|
||||
setTimeout(() => {
|
||||
isUpdatingFromCommonConfig.current = false;
|
||||
}, 0);
|
||||
} else {
|
||||
hasInitializedEditMode.current = true;
|
||||
try {
|
||||
const env =
|
||||
isPlainObject(initialData.settingsConfig.env) &&
|
||||
Object.keys(initialData.settingsConfig.env).length > 0
|
||||
? (initialData.settingsConfig.env as Record<string, string>)
|
||||
: {};
|
||||
const parsed = parseSnippetEnv(commonConfigSnippet);
|
||||
if (parsed.error) {
|
||||
if (commonConfigSnippet.trim()) {
|
||||
setCommonConfigError(parsed.error);
|
||||
}
|
||||
} catch {
|
||||
// ignore parse error
|
||||
setUseCommonConfig(false);
|
||||
return;
|
||||
}
|
||||
const inferredHasCommon = hasEnvCommonConfigSnippet(
|
||||
env,
|
||||
parsed.env as Record<string, string>,
|
||||
);
|
||||
const hasCommon = initialEnabled ?? inferredHasCommon;
|
||||
|
||||
if (hasCommon && !inferredHasCommon) {
|
||||
const currentEnv = envStringToObj(envValue);
|
||||
const merged = applySnippetToEnv(currentEnv, parsed.env);
|
||||
const nextEnvString = envObjToString(merged);
|
||||
|
||||
setCommonConfigError("");
|
||||
setUseCommonConfig(true);
|
||||
isUpdatingFromCommonConfig.current = true;
|
||||
onEnvChange(nextEnvString);
|
||||
setTimeout(() => {
|
||||
isUpdatingFromCommonConfig.current = false;
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
setCommonConfigError("");
|
||||
setUseCommonConfig(hasCommon);
|
||||
} catch {
|
||||
// ignore parse error
|
||||
}
|
||||
}, [
|
||||
applySnippetToEnv,
|
||||
@@ -270,26 +282,34 @@ export function useGeminiCommonConfig({
|
||||
|
||||
// 新建模式:如果通用配置片段存在且有效,默认启用
|
||||
useEffect(() => {
|
||||
// 仅新建模式、加载完成、尚未初始化过
|
||||
if (!initialData && !isLoading && !hasInitializedNewMode.current) {
|
||||
hasInitializedNewMode.current = true;
|
||||
|
||||
const parsed = parseSnippetEnv(commonConfigSnippet);
|
||||
if (parsed.error) return;
|
||||
const hasContent = Object.keys(parsed.env).length > 0;
|
||||
if (!hasContent) return;
|
||||
|
||||
setUseCommonConfig(true);
|
||||
const currentEnv = envStringToObj(envValue);
|
||||
const merged = applySnippetToEnv(currentEnv, parsed.env);
|
||||
const nextEnvString = envObjToString(merged);
|
||||
|
||||
isUpdatingFromCommonConfig.current = true;
|
||||
onEnvChange(nextEnvString);
|
||||
setTimeout(() => {
|
||||
isUpdatingFromCommonConfig.current = false;
|
||||
}, 0);
|
||||
if (initialData || isLoading || hasInitializedNewMode.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasInitializedNewMode.current = true;
|
||||
|
||||
const parsed = parseSnippetEnv(commonConfigSnippet);
|
||||
if (parsed.error) {
|
||||
if (commonConfigSnippet.trim()) {
|
||||
setCommonConfigError(parsed.error);
|
||||
}
|
||||
setUseCommonConfig(false);
|
||||
return;
|
||||
}
|
||||
const hasContent = Object.keys(parsed.env).length > 0;
|
||||
if (!hasContent) return;
|
||||
|
||||
setCommonConfigError("");
|
||||
setUseCommonConfig(true);
|
||||
const currentEnv = envStringToObj(envValue);
|
||||
const merged = applySnippetToEnv(currentEnv, parsed.env);
|
||||
const nextEnvString = envObjToString(merged);
|
||||
|
||||
isUpdatingFromCommonConfig.current = true;
|
||||
onEnvChange(nextEnvString);
|
||||
setTimeout(() => {
|
||||
isUpdatingFromCommonConfig.current = false;
|
||||
}, 0);
|
||||
}, [
|
||||
initialData,
|
||||
isLoading,
|
||||
@@ -346,13 +366,29 @@ export function useGeminiCommonConfig({
|
||||
|
||||
// 处理通用配置片段变化
|
||||
const handleCommonConfigSnippetChange = useCallback(
|
||||
(value: string) => {
|
||||
(value: string): boolean => {
|
||||
const previousSnippet = commonConfigSnippet;
|
||||
setCommonConfigSnippetState(value);
|
||||
|
||||
if (!value.trim()) {
|
||||
setCommonConfigError("");
|
||||
// 保存到 config.json(清空)
|
||||
|
||||
if (useCommonConfig) {
|
||||
const parsedPrevious = parseSnippetEnv(previousSnippet);
|
||||
if (
|
||||
!parsedPrevious.error &&
|
||||
Object.keys(parsedPrevious.env).length > 0
|
||||
) {
|
||||
const currentEnv = envStringToObj(envValue);
|
||||
const updatedEnv = removeSnippetFromEnv(
|
||||
currentEnv,
|
||||
parsedPrevious.env,
|
||||
);
|
||||
onEnvChange(envObjToString(updatedEnv));
|
||||
}
|
||||
setUseCommonConfig(false);
|
||||
}
|
||||
|
||||
setCommonConfigSnippetState("");
|
||||
configApi
|
||||
.setCommonConfigSnippet("gemini", "")
|
||||
.catch((error: unknown) => {
|
||||
@@ -361,36 +397,16 @@ export function useGeminiCommonConfig({
|
||||
t("geminiConfig.saveFailed", { error: String(error) }),
|
||||
);
|
||||
});
|
||||
|
||||
if (useCommonConfig) {
|
||||
const parsed = parseSnippetEnv(previousSnippet);
|
||||
if (!parsed.error && Object.keys(parsed.env).length > 0) {
|
||||
const currentEnv = envStringToObj(envValue);
|
||||
const updatedEnv = removeSnippetFromEnv(currentEnv, parsed.env);
|
||||
onEnvChange(envObjToString(updatedEnv));
|
||||
}
|
||||
setUseCommonConfig(false);
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 校验 JSON 格式
|
||||
const parsed = parseSnippetEnv(value);
|
||||
if (parsed.error) {
|
||||
setCommonConfigError(parsed.error);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
setCommonConfigError("");
|
||||
configApi
|
||||
.setCommonConfigSnippet("gemini", value)
|
||||
.catch((error: unknown) => {
|
||||
console.error("保存 Gemini 通用配置失败:", error);
|
||||
setCommonConfigError(
|
||||
t("geminiConfig.saveFailed", { error: String(error) }),
|
||||
);
|
||||
});
|
||||
|
||||
// 若当前启用通用配置,需要替换为最新片段
|
||||
if (useCommonConfig) {
|
||||
const prevParsed = parseSnippetEnv(previousSnippet);
|
||||
@@ -413,6 +429,19 @@ export function useGeminiCommonConfig({
|
||||
isUpdatingFromCommonConfig.current = false;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
setCommonConfigError("");
|
||||
setCommonConfigSnippetState(value);
|
||||
configApi
|
||||
.setCommonConfigSnippet("gemini", value)
|
||||
.catch((error: unknown) => {
|
||||
console.error("保存 Gemini 通用配置失败:", error);
|
||||
setCommonConfigError(
|
||||
t("geminiConfig.saveFailed", { error: String(error) }),
|
||||
);
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
[
|
||||
applySnippetToEnv,
|
||||
@@ -487,6 +516,10 @@ export function useGeminiCommonConfig({
|
||||
}
|
||||
}, [envStringToObj, envValue, parseSnippetEnv, t]);
|
||||
|
||||
const clearCommonConfigError = useCallback(() => {
|
||||
setCommonConfigError("");
|
||||
}, []);
|
||||
|
||||
return {
|
||||
useCommonConfig,
|
||||
commonConfigSnippet,
|
||||
@@ -496,5 +529,6 @@ export function useGeminiCommonConfig({
|
||||
handleCommonConfigToggle,
|
||||
handleCommonConfigSnippetChange,
|
||||
handleExtract,
|
||||
clearCommonConfigError,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user