fix(claude): add clearing sync and save queue for common config

- Trigger sync when clearing common config to remove from all providers
- Add saveSequenceRef and enqueueSave for ordered async saves
- Add toast notification for sync failures
- Prevent stale sync operations with sequence ID check
This commit is contained in:
YoVinchen
2026-01-25 03:41:29 +08:00
parent 788e138ece
commit 0e7e04581d
@@ -1,11 +1,14 @@
import { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { import {
updateCommonConfigSnippet, updateCommonConfigSnippet,
hasCommonConfigSnippet,
validateJsonConfig, validateJsonConfig,
hasCommonConfigSnippet,
replaceCommonConfigSnippet,
} from "@/utils/providerConfigUtils"; } from "@/utils/providerConfigUtils";
import { configApi } from "@/lib/api"; import { configApi } from "@/lib/api";
import type { ProviderMeta } from "@/types";
const LEGACY_STORAGE_KEY = "cc-switch:common-config-snippet"; const LEGACY_STORAGE_KEY = "cc-switch:common-config-snippet";
const DEFAULT_COMMON_CONFIG_SNIPPET = `{ const DEFAULT_COMMON_CONFIG_SNIPPET = `{
@@ -17,15 +20,18 @@ interface UseCommonConfigSnippetProps {
onConfigChange: (config: string) => void; onConfigChange: (config: string) => void;
initialData?: { initialData?: {
settingsConfig?: Record<string, unknown>; settingsConfig?: Record<string, unknown>;
meta?: ProviderMeta;
}; };
selectedPresetId?: string; selectedPresetId?: string;
/** When false, the hook skips all logic and returns disabled state. Default: true */ /** When false, the hook skips all logic and returns disabled state. Default: true */
enabled?: boolean; enabled?: boolean;
/** 当前正在编辑的供应商 ID(用于同步时跳过) */
currentProviderId?: string;
} }
/** /**
* 管理 Claude 通用配置片段 * 管理 Claude 通用配置片段
* 从 config.json 读取和保存,支持从 localStorage 平滑迁移 * 从数据库读取和保存,支持从 localStorage 平滑迁移
*/ */
export function useCommonConfigSnippet({ export function useCommonConfigSnippet({
settingsConfig, settingsConfig,
@@ -33,6 +39,7 @@ export function useCommonConfigSnippet({
initialData, initialData,
selectedPresetId, selectedPresetId,
enabled = true, enabled = true,
currentProviderId,
}: UseCommonConfigSnippetProps) { }: UseCommonConfigSnippetProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [useCommonConfig, setUseCommonConfig] = useState(false); const [useCommonConfig, setUseCommonConfig] = useState(false);
@@ -43,18 +50,54 @@ export function useCommonConfigSnippet({
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isExtracting, setIsExtracting] = useState(false); const [isExtracting, setIsExtracting] = useState(false);
const getSnippetApplyError = useCallback(
(snippet: string) => {
if (!snippet.trim()) {
return t("claudeConfig.noCommonConfigToApply");
}
const validationError = validateJsonConfig(snippet, "通用配置片段");
if (validationError) {
return validationError;
}
try {
const parsed = JSON.parse(snippet) as Record<string, unknown>;
if (Object.keys(parsed).length === 0) {
return t("claudeConfig.noCommonConfigToApply");
}
} catch {
return t("claudeConfig.noCommonConfigToApply");
}
return "";
},
[t],
);
// 用于跟踪是否正在通过通用配置更新 // 用于跟踪是否正在通过通用配置更新
const isUpdatingFromCommonConfig = useRef(false); const isUpdatingFromCommonConfig = useRef(false);
// 用于跟踪用户是否手动切换,避免自动检测覆盖用户意图
const hasUserToggledCommonConfig = useRef(false);
// 用于跟踪新建模式是否已初始化默认勾选 // 用于跟踪新建模式是否已初始化默认勾选
const hasInitializedNewMode = useRef(false); const hasInitializedNewMode = useRef(false);
// 用于跟踪编辑模式是否已初始化(避免反复覆盖用户切换)
const hasInitializedEditMode = useRef(false);
// 用于避免异步保存乱序导致的过期同步
const saveSequenceRef = useRef(0);
const saveQueueRef = useRef<Promise<void>>(Promise.resolve());
const enqueueSave = useCallback((saveFn: () => Promise<void>) => {
const next = saveQueueRef.current.then(saveFn);
saveQueueRef.current = next.catch(() => {});
return next;
}, []);
// 当预设变化时,重置初始化标记,使新预设能够重新触发初始化逻辑 // 当预设变化时,重置初始化标记,使新预设能够重新触发初始化逻辑
useEffect(() => { useEffect(() => {
if (!enabled) return; if (!enabled) return;
hasInitializedNewMode.current = false; hasInitializedNewMode.current = false;
hasInitializedEditMode.current = false;
hasUserToggledCommonConfig.current = false;
}, [selectedPresetId, enabled]); }, [selectedPresetId, enabled]);
// 初始化:从 config.json 加载,支持从 localStorage 迁移 // 初始化:从数据库加载,支持从 localStorage 迁移
useEffect(() => { useEffect(() => {
if (!enabled) { if (!enabled) {
setIsLoading(false); setIsLoading(false);
@@ -72,7 +115,7 @@ export function useCommonConfigSnippet({
setCommonConfigSnippetState(snippet); setCommonConfigSnippetState(snippet);
} }
} else { } else {
// 如果 config.json 中没有,尝试从 localStorage 迁移 // 如果数据库中没有,尝试从 localStorage 迁移
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
try { try {
const legacySnippet = const legacySnippet =
@@ -86,7 +129,7 @@ export function useCommonConfigSnippet({
// 清理 localStorage // 清理 localStorage
window.localStorage.removeItem(LEGACY_STORAGE_KEY); window.localStorage.removeItem(LEGACY_STORAGE_KEY);
console.log( console.log(
"[迁移] Claude 通用配置已从 localStorage 迁移到 config.json", "[迁移] Claude 通用配置已从 localStorage 迁移到数据库",
); );
} }
} catch (e) { } catch (e) {
@@ -110,18 +153,52 @@ export function useCommonConfigSnippet({
}; };
}, [enabled]); }, [enabled]);
// 初始化时检查通用配置片段(编辑模式) // 初始化时从 meta 读取启用状态(编辑模式)
// 优先使用 meta,若 meta 未定义则回退到内容检测
useEffect(() => { useEffect(() => {
if (!enabled) return; if (!enabled) return;
if (initialData && !isLoading) { if (initialData && !isLoading && !hasInitializedEditMode.current) {
const configString = JSON.stringify(initialData.settingsConfig, null, 2); hasInitializedEditMode.current = true;
const hasCommon = hasCommonConfigSnippet(
configString, // 使用 meta 中记录的按 app 启用状态
commonConfigSnippet, const metaByApp = initialData.meta?.commonConfigEnabledByApp;
); const resolvedMetaEnabled =
setUseCommonConfig(hasCommon); metaByApp?.claude ?? initialData.meta?.commonConfigEnabled;
if (resolvedMetaEnabled !== undefined) {
if (!resolvedMetaEnabled) {
setUseCommonConfig(false);
return;
}
const snippetError = getSnippetApplyError(commonConfigSnippet);
if (snippetError) {
setCommonConfigError(snippetError);
setUseCommonConfig(false);
return;
}
setCommonConfigError("");
setUseCommonConfig(true);
return;
} else {
// meta 未定义,回退到内容检测
const settingsConfigStr =
typeof initialData.settingsConfig === "string"
? initialData.settingsConfig
: JSON.stringify(initialData.settingsConfig ?? {});
const detected = hasCommonConfigSnippet(
settingsConfigStr,
commonConfigSnippet,
);
setUseCommonConfig(detected);
}
} }
}, [enabled, initialData, commonConfigSnippet, isLoading]); }, [
enabled,
initialData,
isLoading,
commonConfigSnippet,
getSnippetApplyError,
]);
// 新建模式:如果通用配置片段存在且有效,默认启用 // 新建模式:如果通用配置片段存在且有效,默认启用
useEffect(() => { useEffect(() => {
@@ -166,6 +243,15 @@ export function useCommonConfigSnippet({
// 处理通用配置开关 // 处理通用配置开关
const handleCommonConfigToggle = useCallback( const handleCommonConfigToggle = useCallback(
(checked: boolean) => { (checked: boolean) => {
hasUserToggledCommonConfig.current = true;
if (checked) {
const snippetError = getSnippetApplyError(commonConfigSnippet);
if (snippetError) {
setCommonConfigError(snippetError);
setUseCommonConfig(false);
return;
}
}
const { updatedConfig, error: snippetError } = updateCommonConfigSnippet( const { updatedConfig, error: snippetError } = updateCommonConfigSnippet(
settingsConfig, settingsConfig,
commonConfigSnippet, commonConfigSnippet,
@@ -188,7 +274,7 @@ export function useCommonConfigSnippet({
isUpdatingFromCommonConfig.current = false; isUpdatingFromCommonConfig.current = false;
}, 0); }, 0);
}, },
[settingsConfig, commonConfigSnippet, onConfigChange], [settingsConfig, commonConfigSnippet, onConfigChange, getSnippetApplyError],
); );
// 处理通用配置片段变化 // 处理通用配置片段变化
@@ -198,14 +284,34 @@ export function useCommonConfigSnippet({
setCommonConfigSnippetState(value); setCommonConfigSnippetState(value);
if (!value.trim()) { if (!value.trim()) {
const saveId = ++saveSequenceRef.current;
setCommonConfigError(""); setCommonConfigError("");
// 保存到 config.json(清空) // 保存到数据库(清空)
configApi.setCommonConfigSnippet("claude", "").catch((error) => { enqueueSave(() => configApi.setCommonConfigSnippet("claude", ""))
console.error("保存通用配置失败:", error); .then(() => {
setCommonConfigError( if (saveSequenceRef.current !== saveId) return;
t("claudeConfig.saveFailed", { error: String(error) }), // 清空时也需要同步:移除所有供应商的通用配置片段
); configApi.syncCommonConfigToProviders(
}); "claude",
previousSnippet,
"", // newSnippet 为空表示移除
replaceCommonConfigSnippet,
currentProviderId,
(result) => {
if (saveSequenceRef.current !== saveId) return;
if (result.error) {
toast.error(t("providerForm.commonConfigSyncFailed"));
}
},
);
})
.catch((error) => {
if (saveSequenceRef.current !== saveId) return;
console.error("保存通用配置失败:", error);
setCommonConfigError(
t("claudeConfig.saveFailed", { error: String(error) }),
);
});
if (useCommonConfig) { if (useCommonConfig) {
const { updatedConfig } = updateCommonConfigSnippet( const { updatedConfig } = updateCommonConfigSnippet(
@@ -223,15 +329,36 @@ export function useCommonConfigSnippet({
const validationError = validateJsonConfig(value, "通用配置片段"); const validationError = validateJsonConfig(value, "通用配置片段");
if (validationError) { if (validationError) {
setCommonConfigError(validationError); setCommonConfigError(validationError);
return;
} else { } else {
const saveId = ++saveSequenceRef.current;
setCommonConfigError(""); setCommonConfigError("");
// 保存到 config.json // 保存到数据库
configApi.setCommonConfigSnippet("claude", value).catch((error) => { enqueueSave(() => configApi.setCommonConfigSnippet("claude", value))
console.error("保存通用配置失败:", error); .then(() => {
setCommonConfigError( if (saveSequenceRef.current !== saveId) return;
t("claudeConfig.saveFailed", { error: String(error) }), // 保存成功后,同步更新所有启用了通用配置的供应商
); configApi.syncCommonConfigToProviders(
}); "claude",
previousSnippet,
value,
replaceCommonConfigSnippet,
currentProviderId,
(result) => {
if (saveSequenceRef.current !== saveId) return;
if (result.error) {
toast.error(t("providerForm.commonConfigSyncFailed"));
}
},
);
})
.catch((error) => {
if (saveSequenceRef.current !== saveId) return;
console.error("保存通用配置失败:", error);
setCommonConfigError(
t("claudeConfig.saveFailed", { error: String(error) }),
);
});
} }
// 若当前启用通用配置且格式正确,需要替换为最新片段 // 若当前启用通用配置且格式正确,需要替换为最新片段
@@ -265,21 +392,36 @@ export function useCommonConfigSnippet({
}, 0); }, 0);
} }
}, },
[commonConfigSnippet, settingsConfig, useCommonConfig, onConfigChange], [
commonConfigSnippet,
settingsConfig,
useCommonConfig,
onConfigChange,
currentProviderId,
enqueueSave,
t,
],
); );
// 当配置变化时检查是否包含通用配置(避免通过通用配置更新时检查 // 当配置变化时检查是否包含通用配置(避免通过通用配置更新时反复覆盖
useEffect(() => { useEffect(() => {
if (!enabled) return; if (!enabled) return;
if (isUpdatingFromCommonConfig.current || isLoading) { if (isUpdatingFromCommonConfig.current || isLoading) {
return; return;
} }
const metaByApp = initialData?.meta?.commonConfigEnabledByApp;
const hasExplicitMeta =
metaByApp?.claude !== undefined ||
initialData?.meta?.commonConfigEnabled !== undefined;
if (hasExplicitMeta || hasUserToggledCommonConfig.current) {
return;
}
const hasCommon = hasCommonConfigSnippet( const hasCommon = hasCommonConfigSnippet(
settingsConfig, settingsConfig,
commonConfigSnippet, commonConfigSnippet,
); );
setUseCommonConfig(hasCommon); setUseCommonConfig(hasCommon);
}, [enabled, settingsConfig, commonConfigSnippet, isLoading]); }, [enabled, settingsConfig, commonConfigSnippet, isLoading, initialData]);
// 从编辑器当前内容提取通用配置片段 // 从编辑器当前内容提取通用配置片段
const handleExtract = useCallback(async () => { const handleExtract = useCallback(async () => {