mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 19:12:04 +08:00
fix(config): defer common config save until form submission
- Change handleExtract and handleCommonConfigSnippetChange to use delayed save pattern - Add hasUnsavedCommonConfig state to track pending changes - Add getPendingCommonConfigSnippet and markCommonConfigSaved helper functions - Remove immediate backend API calls, save only on form submit - Add extractSuccessNeedSave and saveCommonConfigFailed i18n keys
This commit is contained in:
@@ -8,6 +8,7 @@ import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
||||||
import type { AppId } from "@/lib/api";
|
import type { AppId } from "@/lib/api";
|
||||||
|
import { configApi } from "@/lib/api";
|
||||||
import type {
|
import type {
|
||||||
ProviderCategory,
|
ProviderCategory,
|
||||||
ProviderMeta,
|
ProviderMeta,
|
||||||
@@ -478,6 +479,8 @@ export function ProviderForm({
|
|||||||
handleExtract: handleClaudeExtract,
|
handleExtract: handleClaudeExtract,
|
||||||
isLoading: isClaudeCommonConfigLoading,
|
isLoading: isClaudeCommonConfigLoading,
|
||||||
finalConfig: claudeFinalConfig,
|
finalConfig: claudeFinalConfig,
|
||||||
|
getPendingCommonConfigSnippet: getPendingClaudeCommonConfigSnippet,
|
||||||
|
markCommonConfigSaved: markClaudeCommonConfigSaved,
|
||||||
} = useCommonConfigSnippet({
|
} = useCommonConfigSnippet({
|
||||||
settingsConfig: form.getValues("settingsConfig"),
|
settingsConfig: form.getValues("settingsConfig"),
|
||||||
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
onConfigChange: (config) => form.setValue("settingsConfig", config),
|
||||||
@@ -498,6 +501,8 @@ export function ProviderForm({
|
|||||||
handleExtract: handleCodexExtract,
|
handleExtract: handleCodexExtract,
|
||||||
isLoading: isCodexCommonConfigLoading,
|
isLoading: isCodexCommonConfigLoading,
|
||||||
finalConfig: codexFinalConfig,
|
finalConfig: codexFinalConfig,
|
||||||
|
getPendingCommonConfigSnippet: getPendingCodexCommonConfigSnippet,
|
||||||
|
markCommonConfigSaved: markCodexCommonConfigSaved,
|
||||||
} = useCodexCommonConfig({
|
} = useCodexCommonConfig({
|
||||||
codexConfig,
|
codexConfig,
|
||||||
onConfigChange: handleCodexConfigChange,
|
onConfigChange: handleCodexConfigChange,
|
||||||
@@ -587,6 +592,8 @@ export function ProviderForm({
|
|||||||
handleExtract: handleGeminiExtract,
|
handleExtract: handleGeminiExtract,
|
||||||
isLoading: isGeminiCommonConfigLoading,
|
isLoading: isGeminiCommonConfigLoading,
|
||||||
finalEnv: geminiFinalEnv,
|
finalEnv: geminiFinalEnv,
|
||||||
|
getPendingCommonConfigSnippet: getPendingGeminiCommonConfigSnippet,
|
||||||
|
markCommonConfigSaved: markGeminiCommonConfigSaved,
|
||||||
} = useGeminiCommonConfig({
|
} = useGeminiCommonConfig({
|
||||||
envValue: geminiEnv,
|
envValue: geminiEnv,
|
||||||
onEnvChange: handleGeminiEnvChange,
|
onEnvChange: handleGeminiEnvChange,
|
||||||
@@ -817,7 +824,7 @@ export function ProviderForm({
|
|||||||
|
|
||||||
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (values: ProviderFormData) => {
|
const handleSubmit = async (values: ProviderFormData) => {
|
||||||
// 检查通用配置是否仍在加载中
|
// 检查通用配置是否仍在加载中
|
||||||
const isCommonConfigLoading =
|
const isCommonConfigLoading =
|
||||||
(appId === "claude" && isClaudeCommonConfigLoading) ||
|
(appId === "claude" && isClaudeCommonConfigLoading) ||
|
||||||
@@ -970,6 +977,37 @@ export function ProviderForm({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存待保存的通用配置(延迟保存模式:在表单提交时统一保存)
|
||||||
|
try {
|
||||||
|
if (appId === "claude") {
|
||||||
|
const pendingSnippet = getPendingClaudeCommonConfigSnippet();
|
||||||
|
if (pendingSnippet !== null) {
|
||||||
|
await configApi.setCommonConfigSnippet("claude", pendingSnippet);
|
||||||
|
markClaudeCommonConfigSaved();
|
||||||
|
}
|
||||||
|
} else if (appId === "codex") {
|
||||||
|
const pendingSnippet = getPendingCodexCommonConfigSnippet();
|
||||||
|
if (pendingSnippet !== null) {
|
||||||
|
await configApi.setCommonConfigSnippet("codex", pendingSnippet);
|
||||||
|
markCodexCommonConfigSaved();
|
||||||
|
}
|
||||||
|
} else if (appId === "gemini") {
|
||||||
|
const pendingSnippet = getPendingGeminiCommonConfigSnippet();
|
||||||
|
if (pendingSnippet !== null) {
|
||||||
|
await configApi.setCommonConfigSnippet("gemini", pendingSnippet);
|
||||||
|
markGeminiCommonConfigSaved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("保存通用配置失败:", error);
|
||||||
|
toast.error(
|
||||||
|
t("providerForm.saveCommonConfigFailed", {
|
||||||
|
defaultValue: "保存通用配置失败",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let settingsConfig: string;
|
let settingsConfig: string;
|
||||||
|
|
||||||
// Codex: 组合 auth 和 config
|
// Codex: 组合 auth 和 config
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ export interface UseCodexCommonConfigReturn {
|
|||||||
handleExtract: () => Promise<void>;
|
handleExtract: () => Promise<void>;
|
||||||
/** 最终配置(运行时合并结果,只读) */
|
/** 最终配置(运行时合并结果,只读) */
|
||||||
finalConfig: string;
|
finalConfig: string;
|
||||||
|
/** 是否有待保存的通用配置变更 */
|
||||||
|
hasUnsavedCommonConfig: boolean;
|
||||||
|
/** 获取待保存的通用配置片段(用于 handleSubmit) */
|
||||||
|
getPendingCommonConfigSnippet: () => string | null;
|
||||||
|
/** 标记通用配置已保存 */
|
||||||
|
markCommonConfigSaved: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,18 +124,8 @@ export function useCodexCommonConfig({
|
|||||||
const [commonConfigError, setCommonConfigError] = useState("");
|
const [commonConfigError, setCommonConfigError] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isExtracting, setIsExtracting] = useState(false);
|
const [isExtracting, setIsExtracting] = useState(false);
|
||||||
|
// 是否有待保存的通用配置变更
|
||||||
// 用于避免异步保存乱序
|
const [hasUnsavedCommonConfig, setHasUnsavedCommonConfig] = useState(false);
|
||||||
const saveSequenceRef = useRef(0);
|
|
||||||
const saveQueueRef = useRef<Promise<void>>(Promise.resolve());
|
|
||||||
const enqueueSave = useCallback((saveFn: () => Promise<void>) => {
|
|
||||||
const next = saveQueueRef.current.then(saveFn);
|
|
||||||
// Log errors to help with debugging, but don't block subsequent saves
|
|
||||||
saveQueueRef.current = next.catch((e) => {
|
|
||||||
console.error("[SaveQueue] Codex common config save failed:", e);
|
|
||||||
});
|
|
||||||
return next;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 用于跟踪编辑模式是否已初始化
|
// 用于跟踪编辑模式是否已初始化
|
||||||
const hasInitializedEditMode = useRef(false);
|
const hasInitializedEditMode = useRef(false);
|
||||||
@@ -340,24 +336,15 @@ export function useCodexCommonConfig({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 处理通用配置片段变化
|
// 处理通用配置片段变化(延迟保存模式:只更新本地状态,实际保存在表单提交时)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const handleCommonConfigSnippetChange = useCallback(
|
const handleCommonConfigSnippetChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
setCommonConfigSnippetState(value);
|
setCommonConfigSnippetState(value);
|
||||||
|
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
const saveId = ++saveSequenceRef.current;
|
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
enqueueSave(() => configApi.setCommonConfigSnippet("codex", "")).catch(
|
setHasUnsavedCommonConfig(true);
|
||||||
(error) => {
|
|
||||||
if (saveSequenceRef.current !== saveId) return;
|
|
||||||
console.error("保存 Codex 通用配置失败:", error);
|
|
||||||
setCommonConfigError(
|
|
||||||
t("codexConfig.saveFailed", { error: String(error) }),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,26 +359,17 @@ export function useCodexCommonConfig({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveId = ++saveSequenceRef.current;
|
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
enqueueSave(() => configApi.setCommonConfigSnippet("codex", value)).catch(
|
setHasUnsavedCommonConfig(true);
|
||||||
(error) => {
|
|
||||||
if (saveSequenceRef.current !== saveId) return;
|
|
||||||
console.error("保存 Codex 通用配置失败:", error);
|
|
||||||
setCommonConfigError(
|
|
||||||
t("codexConfig.saveFailed", { error: String(error) }),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 注意:新架构下不再需要同步到其他供应商的 settingsConfig
|
// 注意:新架构下不再需要同步到其他供应商的 settingsConfig
|
||||||
// 因为 finalConfig 是运行时计算的
|
// 因为 finalConfig 是运行时计算的
|
||||||
},
|
},
|
||||||
[enqueueSave, t],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 从当前最终配置提取通用配置片段
|
// 从当前最终配置提取通用配置片段(延迟保存模式:只更新本地状态,实际保存在表单提交时)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const handleExtract = useCallback(async () => {
|
const handleExtract = useCallback(async () => {
|
||||||
setIsExtracting(true);
|
setIsExtracting(true);
|
||||||
@@ -420,11 +398,9 @@ export function useCodexCommonConfig({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新片段状态
|
// 更新片段状态(延迟保存:不立即调用后端 API)
|
||||||
setCommonConfigSnippetState(extracted);
|
setCommonConfigSnippetState(extracted);
|
||||||
|
setHasUnsavedCommonConfig(true);
|
||||||
// 保存到后端
|
|
||||||
await configApi.setCommonConfigSnippet("codex", extracted);
|
|
||||||
|
|
||||||
// 提取成功后,从 config 中移除与 extracted 相同的部分
|
// 提取成功后,从 config 中移除与 extracted 相同的部分
|
||||||
const customToml = extractConfigToml(codexConfig);
|
const customToml = extractConfigToml(codexConfig);
|
||||||
@@ -435,10 +411,10 @@ export function useCodexCommonConfig({
|
|||||||
const parsed = JSON.parse(codexConfig);
|
const parsed = JSON.parse(codexConfig);
|
||||||
parsed.config = diffResult.customToml;
|
parsed.config = diffResult.customToml;
|
||||||
onConfigChange(JSON.stringify(parsed, null, 2));
|
onConfigChange(JSON.stringify(parsed, null, 2));
|
||||||
// Notify user that config was modified
|
// Notify user that config was modified (提示用户需要保存)
|
||||||
toast.success(
|
toast.success(
|
||||||
t("codexConfig.extractSuccess", {
|
t("codexConfig.extractSuccessNeedSave", {
|
||||||
defaultValue: "已提取通用配置,自定义配置已自动更新",
|
defaultValue: "已提取通用配置,点击保存按钮完成保存",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
@@ -458,6 +434,20 @@ export function useCodexCommonConfig({
|
|||||||
}
|
}
|
||||||
}, [finalConfig, codexConfig, onConfigChange, extractConfigToml, t]);
|
}, [finalConfig, codexConfig, onConfigChange, extractConfigToml, t]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 获取待保存的通用配置片段(用于 handleSubmit 中统一保存)
|
||||||
|
// ============================================================================
|
||||||
|
const getPendingCommonConfigSnippet = useCallback(() => {
|
||||||
|
return hasUnsavedCommonConfig ? commonConfigSnippet : null;
|
||||||
|
}, [hasUnsavedCommonConfig, commonConfigSnippet]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 标记通用配置已保存
|
||||||
|
// ============================================================================
|
||||||
|
const markCommonConfigSaved = useCallback(() => {
|
||||||
|
setHasUnsavedCommonConfig(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useCommonConfig,
|
useCommonConfig,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
@@ -468,5 +458,8 @@ export function useCodexCommonConfig({
|
|||||||
handleCommonConfigSnippetChange,
|
handleCommonConfigSnippetChange,
|
||||||
handleExtract,
|
handleExtract,
|
||||||
finalConfig,
|
finalConfig,
|
||||||
|
hasUnsavedCommonConfig,
|
||||||
|
getPendingCommonConfigSnippet,
|
||||||
|
markCommonConfigSaved,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export interface UseCommonConfigSnippetReturn {
|
|||||||
handleExtract: () => Promise<void>;
|
handleExtract: () => Promise<void>;
|
||||||
/** 最终配置(运行时合并结果,只读) */
|
/** 最终配置(运行时合并结果,只读) */
|
||||||
finalConfig: string;
|
finalConfig: string;
|
||||||
|
/** 是否有待保存的通用配置变更 */
|
||||||
|
hasUnsavedCommonConfig: boolean;
|
||||||
|
/** 获取待保存的通用配置片段(用于 handleSubmit) */
|
||||||
|
getPendingCommonConfigSnippet: () => string | null;
|
||||||
|
/** 标记通用配置已保存 */
|
||||||
|
markCommonConfigSaved: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,18 +102,8 @@ export function useCommonConfigSnippet({
|
|||||||
const [commonConfigError, setCommonConfigError] = useState("");
|
const [commonConfigError, setCommonConfigError] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isExtracting, setIsExtracting] = useState(false);
|
const [isExtracting, setIsExtracting] = useState(false);
|
||||||
|
// 是否有待保存的通用配置变更
|
||||||
// 用于避免异步保存乱序
|
const [hasUnsavedCommonConfig, setHasUnsavedCommonConfig] = useState(false);
|
||||||
const saveSequenceRef = useRef(0);
|
|
||||||
const saveQueueRef = useRef<Promise<void>>(Promise.resolve());
|
|
||||||
const enqueueSave = useCallback((saveFn: () => Promise<void>) => {
|
|
||||||
const next = saveQueueRef.current.then(saveFn);
|
|
||||||
// Log errors to help with debugging, but don't block subsequent saves
|
|
||||||
saveQueueRef.current = next.catch((e) => {
|
|
||||||
console.error("[SaveQueue] Claude common config save failed:", e);
|
|
||||||
});
|
|
||||||
return next;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 用于跟踪编辑模式是否已初始化
|
// 用于跟踪编辑模式是否已初始化
|
||||||
const hasInitializedEditMode = useRef(false);
|
const hasInitializedEditMode = useRef(false);
|
||||||
@@ -344,54 +340,33 @@ export function useCommonConfigSnippet({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 处理通用配置片段变化
|
// 处理通用配置片段变化(延迟保存模式:只更新本地状态,实际保存在表单提交时)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const handleCommonConfigSnippetChange = useCallback(
|
const handleCommonConfigSnippetChange = useCallback((value: string) => {
|
||||||
(value: string) => {
|
setCommonConfigSnippetState(value);
|
||||||
setCommonConfigSnippetState(value);
|
|
||||||
|
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
const saveId = ++saveSequenceRef.current;
|
|
||||||
setCommonConfigError("");
|
|
||||||
enqueueSave(() => configApi.setCommonConfigSnippet("claude", "")).catch(
|
|
||||||
(error) => {
|
|
||||||
if (saveSequenceRef.current !== saveId) return;
|
|
||||||
console.error("保存通用配置失败:", error);
|
|
||||||
setCommonConfigError(
|
|
||||||
t("claudeConfig.saveFailed", { error: String(error) }),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON 格式校验
|
|
||||||
const validationError = validateJsonConfig(value, "通用配置片段");
|
|
||||||
if (validationError) {
|
|
||||||
setCommonConfigError(validationError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveId = ++saveSequenceRef.current;
|
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
enqueueSave(() =>
|
setHasUnsavedCommonConfig(true);
|
||||||
configApi.setCommonConfigSnippet("claude", value),
|
return;
|
||||||
).catch((error) => {
|
}
|
||||||
if (saveSequenceRef.current !== saveId) return;
|
|
||||||
console.error("保存通用配置失败:", error);
|
|
||||||
setCommonConfigError(
|
|
||||||
t("claudeConfig.saveFailed", { error: String(error) }),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 注意:新架构下不再需要同步到其他供应商的 settingsConfig
|
// JSON 格式校验
|
||||||
// 因为 finalConfig 是运行时计算的
|
const validationError = validateJsonConfig(value, "通用配置片段");
|
||||||
},
|
if (validationError) {
|
||||||
[enqueueSave, t],
|
setCommonConfigError(validationError);
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCommonConfigError("");
|
||||||
|
setHasUnsavedCommonConfig(true);
|
||||||
|
|
||||||
|
// 注意:新架构下不再需要同步到其他供应商的 settingsConfig
|
||||||
|
// 因为 finalConfig 是运行时计算的
|
||||||
|
}, []);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 从当前最终配置提取通用配置片段
|
// 从当前最终配置提取通用配置片段(延迟保存模式:只更新本地状态,实际保存在表单提交时)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const handleExtract = useCallback(async () => {
|
const handleExtract = useCallback(async () => {
|
||||||
setIsExtracting(true);
|
setIsExtracting(true);
|
||||||
@@ -414,11 +389,9 @@ export function useCommonConfigSnippet({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新片段状态
|
// 更新片段状态(延迟保存:不立即调用后端 API)
|
||||||
setCommonConfigSnippetState(extracted);
|
setCommonConfigSnippetState(extracted);
|
||||||
|
setHasUnsavedCommonConfig(true);
|
||||||
// 保存到后端
|
|
||||||
await configApi.setCommonConfigSnippet("claude", extracted);
|
|
||||||
|
|
||||||
// 提取成功后,从 settingsConfig 中移除与 extracted 相同的部分
|
// 提取成功后,从 settingsConfig 中移除与 extracted 相同的部分
|
||||||
try {
|
try {
|
||||||
@@ -428,10 +401,10 @@ export function useCommonConfigSnippet({
|
|||||||
if (isPlainObject(customParsed) && isPlainObject(extractedParsed)) {
|
if (isPlainObject(customParsed) && isPlainObject(extractedParsed)) {
|
||||||
const diffResult = extractDifference(customParsed, extractedParsed);
|
const diffResult = extractDifference(customParsed, extractedParsed);
|
||||||
onConfigChange(JSON.stringify(diffResult.customConfig, null, 2));
|
onConfigChange(JSON.stringify(diffResult.customConfig, null, 2));
|
||||||
// Notify user that config was modified
|
// Notify user that config was modified (提示用户需要保存)
|
||||||
toast.success(
|
toast.success(
|
||||||
t("claudeConfig.extractSuccess", {
|
t("claudeConfig.extractSuccessNeedSave", {
|
||||||
defaultValue: "已提取通用配置,自定义配置已自动更新",
|
defaultValue: "已提取通用配置,点击保存按钮完成保存",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -451,6 +424,20 @@ export function useCommonConfigSnippet({
|
|||||||
}
|
}
|
||||||
}, [finalConfig, settingsConfig, onConfigChange, t]);
|
}, [finalConfig, settingsConfig, onConfigChange, t]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 获取待保存的通用配置片段(用于 handleSubmit 中统一保存)
|
||||||
|
// ============================================================================
|
||||||
|
const getPendingCommonConfigSnippet = useCallback(() => {
|
||||||
|
return hasUnsavedCommonConfig ? commonConfigSnippet : null;
|
||||||
|
}, [hasUnsavedCommonConfig, commonConfigSnippet]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 标记通用配置已保存
|
||||||
|
// ============================================================================
|
||||||
|
const markCommonConfigSaved = useCallback(() => {
|
||||||
|
setHasUnsavedCommonConfig(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useCommonConfig,
|
useCommonConfig,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
@@ -461,5 +448,8 @@ export function useCommonConfigSnippet({
|
|||||||
handleCommonConfigSnippetChange,
|
handleCommonConfigSnippetChange,
|
||||||
handleExtract,
|
handleExtract,
|
||||||
finalConfig,
|
finalConfig,
|
||||||
|
hasUnsavedCommonConfig,
|
||||||
|
getPendingCommonConfigSnippet,
|
||||||
|
markCommonConfigSaved,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ export interface UseGeminiCommonConfigReturn {
|
|||||||
handleExtract: () => Promise<void>;
|
handleExtract: () => Promise<void>;
|
||||||
/** 最终 env 对象(运行时合并结果,只读) */
|
/** 最终 env 对象(运行时合并结果,只读) */
|
||||||
finalEnv: Record<string, string>;
|
finalEnv: Record<string, string>;
|
||||||
|
/** 是否有待保存的通用配置变更 */
|
||||||
|
hasUnsavedCommonConfig: boolean;
|
||||||
|
/** 获取待保存的通用配置片段(用于 handleSubmit) */
|
||||||
|
getPendingCommonConfigSnippet: () => string | null;
|
||||||
|
/** 标记通用配置已保存 */
|
||||||
|
markCommonConfigSaved: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,18 +103,8 @@ export function useGeminiCommonConfig({
|
|||||||
const [commonConfigError, setCommonConfigError] = useState("");
|
const [commonConfigError, setCommonConfigError] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isExtracting, setIsExtracting] = useState(false);
|
const [isExtracting, setIsExtracting] = useState(false);
|
||||||
|
// 是否有待保存的通用配置变更
|
||||||
// 用于避免异步保存乱序
|
const [hasUnsavedCommonConfig, setHasUnsavedCommonConfig] = useState(false);
|
||||||
const saveSequenceRef = useRef(0);
|
|
||||||
const saveQueueRef = useRef<Promise<void>>(Promise.resolve());
|
|
||||||
const enqueueSave = useCallback((saveFn: () => Promise<void>) => {
|
|
||||||
const next = saveQueueRef.current.then(saveFn);
|
|
||||||
// Log errors to help with debugging, but don't block subsequent saves
|
|
||||||
saveQueueRef.current = next.catch((e) => {
|
|
||||||
console.error("[SaveQueue] Gemini common config save failed:", e);
|
|
||||||
});
|
|
||||||
return next;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 用于跟踪编辑模式是否已初始化
|
// 用于跟踪编辑模式是否已初始化
|
||||||
const hasInitializedEditMode = useRef(false);
|
const hasInitializedEditMode = useRef(false);
|
||||||
@@ -330,24 +326,15 @@ export function useGeminiCommonConfig({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 处理通用配置片段变化
|
// 处理通用配置片段变化(延迟保存模式:只更新本地状态,实际保存在表单提交时)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const handleCommonConfigSnippetChange = useCallback(
|
const handleCommonConfigSnippetChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
setCommonConfigSnippetState(value);
|
setCommonConfigSnippetState(value);
|
||||||
|
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
const saveId = ++saveSequenceRef.current;
|
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
enqueueSave(() => configApi.setCommonConfigSnippet("gemini", "")).catch(
|
setHasUnsavedCommonConfig(true);
|
||||||
(error) => {
|
|
||||||
if (saveSequenceRef.current !== saveId) return;
|
|
||||||
console.error("保存 Gemini 通用配置失败:", error);
|
|
||||||
setCommonConfigError(
|
|
||||||
t("geminiConfig.saveFailed", { error: String(error) }),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,26 +345,17 @@ export function useGeminiCommonConfig({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveId = ++saveSequenceRef.current;
|
|
||||||
setCommonConfigError("");
|
setCommonConfigError("");
|
||||||
enqueueSave(() =>
|
setHasUnsavedCommonConfig(true);
|
||||||
configApi.setCommonConfigSnippet("gemini", value),
|
|
||||||
).catch((error) => {
|
|
||||||
if (saveSequenceRef.current !== saveId) return;
|
|
||||||
console.error("保存 Gemini 通用配置失败:", error);
|
|
||||||
setCommonConfigError(
|
|
||||||
t("geminiConfig.saveFailed", { error: String(error) }),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 注意:新架构下不再需要同步到其他供应商的 settingsConfig
|
// 注意:新架构下不再需要同步到其他供应商的 settingsConfig
|
||||||
// 因为 finalEnv 是运行时计算的
|
// 因为 finalEnv 是运行时计算的
|
||||||
},
|
},
|
||||||
[enqueueSave, parseSnippetEnv, t],
|
[parseSnippetEnv],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 从当前最终 env 提取通用配置片段
|
// 从当前最终 env 提取通用配置片段(延迟保存模式:只更新本地状态,实际保存在表单提交时)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
const handleExtract = useCallback(async () => {
|
const handleExtract = useCallback(async () => {
|
||||||
setIsExtracting(true);
|
setIsExtracting(true);
|
||||||
@@ -402,11 +380,9 @@ export function useGeminiCommonConfig({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新片段状态
|
// 更新片段状态(延迟保存:不立即调用后端 API)
|
||||||
setCommonConfigSnippetState(extracted);
|
setCommonConfigSnippetState(extracted);
|
||||||
|
setHasUnsavedCommonConfig(true);
|
||||||
// 保存到后端
|
|
||||||
await configApi.setCommonConfigSnippet("gemini", extracted);
|
|
||||||
|
|
||||||
// 提取成功后,从 customEnv 中移除与 extracted 相同的部分
|
// 提取成功后,从 customEnv 中移除与 extracted 相同的部分
|
||||||
const diffResult = extractDifference(
|
const diffResult = extractDifference(
|
||||||
@@ -420,10 +396,10 @@ export function useGeminiCommonConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onEnvChange(envObjToString(newCustomEnv));
|
onEnvChange(envObjToString(newCustomEnv));
|
||||||
// Notify user that config was modified
|
// Notify user that config was modified (提示用户需要保存)
|
||||||
toast.success(
|
toast.success(
|
||||||
t("geminiConfig.extractSuccess", {
|
t("geminiConfig.extractSuccessNeedSave", {
|
||||||
defaultValue: "已提取通用配置,自定义配置已自动更新",
|
defaultValue: "已提取通用配置,点击保存按钮完成保存",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -436,6 +412,20 @@ export function useGeminiCommonConfig({
|
|||||||
}
|
}
|
||||||
}, [finalEnv, customEnv, onEnvChange, envObjToString, parseSnippetEnv, t]);
|
}, [finalEnv, customEnv, onEnvChange, envObjToString, parseSnippetEnv, t]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 获取待保存的通用配置片段(用于 handleSubmit 中统一保存)
|
||||||
|
// ============================================================================
|
||||||
|
const getPendingCommonConfigSnippet = useCallback(() => {
|
||||||
|
return hasUnsavedCommonConfig ? commonConfigSnippet : null;
|
||||||
|
}, [hasUnsavedCommonConfig, commonConfigSnippet]);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 标记通用配置已保存
|
||||||
|
// ============================================================================
|
||||||
|
const markCommonConfigSaved = useCallback(() => {
|
||||||
|
setHasUnsavedCommonConfig(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
useCommonConfig,
|
useCommonConfig,
|
||||||
commonConfigSnippet,
|
commonConfigSnippet,
|
||||||
@@ -446,5 +436,8 @@ export function useGeminiCommonConfig({
|
|||||||
handleCommonConfigSnippetChange,
|
handleCommonConfigSnippetChange,
|
||||||
handleExtract,
|
handleExtract,
|
||||||
finalEnv,
|
finalEnv,
|
||||||
|
hasUnsavedCommonConfig,
|
||||||
|
getPendingCommonConfigSnippet,
|
||||||
|
markCommonConfigSaved,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"extractFromCurrent": "Extract from Editor",
|
"extractFromCurrent": "Extract from Editor",
|
||||||
"extractNoCommonConfig": "No common config available to extract from editor",
|
"extractNoCommonConfig": "No common config available to extract from editor",
|
||||||
"extractFailed": "Extract failed: {{error}}",
|
"extractFailed": "Extract failed: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "Common config extracted, click Save to complete",
|
||||||
"saveFailed": "Save failed: {{error}}",
|
"saveFailed": "Save failed: {{error}}",
|
||||||
"hidePreview": "Hide merged preview",
|
"hidePreview": "Hide merged preview",
|
||||||
"showPreview": "Show merged preview",
|
"showPreview": "Show merged preview",
|
||||||
@@ -493,7 +494,8 @@
|
|||||||
"categoryAggregation": "Aggregation",
|
"categoryAggregation": "Aggregation",
|
||||||
"categoryThirdParty": "Third Party",
|
"categoryThirdParty": "Third Party",
|
||||||
"commonConfigLoading": "Common config is still loading, please wait before saving",
|
"commonConfigLoading": "Common config is still loading, please wait before saving",
|
||||||
"commonConfigSyncFailed": "Common config sync failed for some providers. Please retry."
|
"commonConfigSyncFailed": "Common config sync failed for some providers. Please retry.",
|
||||||
|
"saveCommonConfigFailed": "Failed to save common config"
|
||||||
},
|
},
|
||||||
"endpointTest": {
|
"endpointTest": {
|
||||||
"title": "API Endpoint Management",
|
"title": "API Endpoint Management",
|
||||||
@@ -563,6 +565,7 @@
|
|||||||
"extractFromCurrent": "Extract from Editor",
|
"extractFromCurrent": "Extract from Editor",
|
||||||
"extractNoCommonConfig": "No common config available to extract from editor",
|
"extractNoCommonConfig": "No common config available to extract from editor",
|
||||||
"extractFailed": "Extract failed: {{error}}",
|
"extractFailed": "Extract failed: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "Common config extracted, click Save to complete",
|
||||||
"saveFailed": "Save failed: {{error}}"
|
"saveFailed": "Save failed: {{error}}"
|
||||||
},
|
},
|
||||||
"geminiConfig": {
|
"geminiConfig": {
|
||||||
@@ -577,6 +580,7 @@
|
|||||||
"extractFromCurrent": "Extract from Editor",
|
"extractFromCurrent": "Extract from Editor",
|
||||||
"extractNoCommonConfig": "No common config available to extract from editor",
|
"extractNoCommonConfig": "No common config available to extract from editor",
|
||||||
"extractFailed": "Extract failed: {{error}}",
|
"extractFailed": "Extract failed: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "Common config extracted, click Save to complete",
|
||||||
"saveFailed": "Save failed: {{error}}",
|
"saveFailed": "Save failed: {{error}}",
|
||||||
"extractedConfigInvalid": "Extracted config format is invalid",
|
"extractedConfigInvalid": "Extracted config format is invalid",
|
||||||
"invalidJsonFormat": "Common config snippet format error (must be valid JSON)",
|
"invalidJsonFormat": "Common config snippet format error (must be valid JSON)",
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"extractFromCurrent": "編集内容から抽出",
|
"extractFromCurrent": "編集内容から抽出",
|
||||||
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
|
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
|
||||||
"extractFailed": "抽出に失敗しました: {{error}}",
|
"extractFailed": "抽出に失敗しました: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "共通設定を抽出しました。保存ボタンをクリックして完了してください",
|
||||||
"saveFailed": "保存に失敗しました: {{error}}",
|
"saveFailed": "保存に失敗しました: {{error}}",
|
||||||
"hidePreview": "マージプレビューを非表示",
|
"hidePreview": "マージプレビューを非表示",
|
||||||
"showPreview": "マージプレビューを表示",
|
"showPreview": "マージプレビューを表示",
|
||||||
@@ -493,7 +494,8 @@
|
|||||||
"categoryAggregation": "アグリゲーター",
|
"categoryAggregation": "アグリゲーター",
|
||||||
"categoryThirdParty": "サードパーティ",
|
"categoryThirdParty": "サードパーティ",
|
||||||
"commonConfigLoading": "共通設定を読み込み中です。保存する前にお待ちください",
|
"commonConfigLoading": "共通設定を読み込み中です。保存する前にお待ちください",
|
||||||
"commonConfigSyncFailed": "共通設定の同期に失敗しました。再試行してください"
|
"commonConfigSyncFailed": "共通設定の同期に失敗しました。再試行してください",
|
||||||
|
"saveCommonConfigFailed": "共通設定の保存に失敗しました"
|
||||||
},
|
},
|
||||||
"endpointTest": {
|
"endpointTest": {
|
||||||
"title": "API エンドポイント管理",
|
"title": "API エンドポイント管理",
|
||||||
@@ -563,6 +565,7 @@
|
|||||||
"extractFromCurrent": "編集内容から抽出",
|
"extractFromCurrent": "編集内容から抽出",
|
||||||
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
|
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
|
||||||
"extractFailed": "抽出に失敗しました: {{error}}",
|
"extractFailed": "抽出に失敗しました: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "共通設定を抽出しました。保存ボタンをクリックして完了してください",
|
||||||
"saveFailed": "保存に失敗しました: {{error}}"
|
"saveFailed": "保存に失敗しました: {{error}}"
|
||||||
},
|
},
|
||||||
"geminiConfig": {
|
"geminiConfig": {
|
||||||
@@ -577,6 +580,7 @@
|
|||||||
"extractFromCurrent": "編集内容から抽出",
|
"extractFromCurrent": "編集内容から抽出",
|
||||||
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
|
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
|
||||||
"extractFailed": "抽出に失敗しました: {{error}}",
|
"extractFailed": "抽出に失敗しました: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "共通設定を抽出しました。保存ボタンをクリックして完了してください",
|
||||||
"saveFailed": "保存に失敗しました: {{error}}",
|
"saveFailed": "保存に失敗しました: {{error}}",
|
||||||
"extractedConfigInvalid": "抽出した設定のフォーマットが不正です",
|
"extractedConfigInvalid": "抽出した設定のフォーマットが不正です",
|
||||||
"invalidJsonFormat": "共通設定スニペットの形式が不正です(有効な JSON でなければなりません)",
|
"invalidJsonFormat": "共通設定スニペットの形式が不正です(有効な JSON でなければなりません)",
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"extractFromCurrent": "从编辑内容提取",
|
"extractFromCurrent": "从编辑内容提取",
|
||||||
"extractNoCommonConfig": "当前编辑内容没有可提取的通用配置",
|
"extractNoCommonConfig": "当前编辑内容没有可提取的通用配置",
|
||||||
"extractFailed": "提取失败: {{error}}",
|
"extractFailed": "提取失败: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "已提取通用配置,点击保存按钮完成保存",
|
||||||
"saveFailed": "保存失败: {{error}}",
|
"saveFailed": "保存失败: {{error}}",
|
||||||
"hidePreview": "隐藏合并预览",
|
"hidePreview": "隐藏合并预览",
|
||||||
"showPreview": "显示合并预览",
|
"showPreview": "显示合并预览",
|
||||||
@@ -493,7 +494,8 @@
|
|||||||
"categoryAggregation": "聚合服务",
|
"categoryAggregation": "聚合服务",
|
||||||
"categoryThirdParty": "第三方",
|
"categoryThirdParty": "第三方",
|
||||||
"commonConfigLoading": "通用配置正在加载中,请稍候再保存",
|
"commonConfigLoading": "通用配置正在加载中,请稍候再保存",
|
||||||
"commonConfigSyncFailed": "通用配置同步部分失败,请重试"
|
"commonConfigSyncFailed": "通用配置同步部分失败,请重试",
|
||||||
|
"saveCommonConfigFailed": "保存通用配置失败"
|
||||||
},
|
},
|
||||||
"endpointTest": {
|
"endpointTest": {
|
||||||
"title": "请求地址管理",
|
"title": "请求地址管理",
|
||||||
@@ -577,6 +579,7 @@
|
|||||||
"extractFromCurrent": "从编辑内容提取",
|
"extractFromCurrent": "从编辑内容提取",
|
||||||
"extractNoCommonConfig": "当前编辑内容没有可提取的通用配置",
|
"extractNoCommonConfig": "当前编辑内容没有可提取的通用配置",
|
||||||
"extractFailed": "提取失败: {{error}}",
|
"extractFailed": "提取失败: {{error}}",
|
||||||
|
"extractSuccessNeedSave": "已提取通用配置,点击保存按钮完成保存",
|
||||||
"saveFailed": "保存失败: {{error}}",
|
"saveFailed": "保存失败: {{error}}",
|
||||||
"extractedConfigInvalid": "提取的配置格式错误",
|
"extractedConfigInvalid": "提取的配置格式错误",
|
||||||
"invalidJsonFormat": "通用配置片段格式错误(必须是有效的 JSON)",
|
"invalidJsonFormat": "通用配置片段格式错误(必须是有效的 JSON)",
|
||||||
|
|||||||
Reference in New Issue
Block a user