fix(proxy): stabilize live takeover and provider editing

- Skip live writes when takeover is active and proxy is running
- Refresh live backups from provider edits during takeover
- Sync live tokens to DB without clobbering real keys with placeholders
- Avoid injecting extra placeholder keys into Claude live env
- Reapply takeover after proxy listen address/port changes
- In takeover mode, edit dialog uses DB config and keeps API key state in sync
This commit is contained in:
Jason
2025-12-16 23:10:52 +08:00
parent 18e973b920
commit 3d514c8250
5 changed files with 254 additions and 112 deletions
+1
View File
@@ -578,6 +578,7 @@ function App() {
}}
onSubmit={handleEditProvider}
appId={activeApp}
isProxyTakeover={isProxyRunning && isTakeoverActive}
/>
{usageProvider && (
@@ -16,6 +16,7 @@ interface EditProviderDialogProps {
onOpenChange: (open: boolean) => void;
onSubmit: (provider: Provider) => Promise<void> | void;
appId: AppId;
isProxyTakeover?: boolean; // 代理接管模式下不读取 live(避免显示被接管后的代理配置)
}
export function EditProviderDialog({
@@ -24,6 +25,7 @@ export function EditProviderDialog({
onOpenChange,
onSubmit,
appId,
isProxyTakeover = false,
}: EditProviderDialogProps) {
const { t } = useTranslation();
@@ -50,6 +52,16 @@ export function EditProviderDialog({
return;
}
// 代理接管模式:Live 配置已被代理改写,读取 live 会导致编辑界面展示代理地址/占位符等内容
// 因此直接回退到 SSOT(数据库)配置,避免用户困惑与误保存
if (isProxyTakeover) {
if (!cancelled) {
setLiveSettings(null);
setHasLoadedLive(true);
}
return;
}
try {
const currentId = await providersApi.getCurrent(appId);
if (currentId && provider.id === currentId) {
@@ -82,7 +94,7 @@ export function EditProviderDialog({
return () => {
cancelled = true;
};
}, [open, provider?.id, appId, hasLoadedLive]); // 只依赖 provider.id,不依赖整个 provider 对象
}, [open, provider?.id, appId, hasLoadedLive, isProxyTakeover]); // 只依赖 provider.id,不依赖整个 provider 对象
const initialSettingsConfig = useMemo(() => {
return (liveSettings ?? provider?.settingsConfig ?? {}) as Record<
@@ -1,4 +1,4 @@
import { useState, useCallback } from "react";
import { useEffect, useState, useCallback } from "react";
import type { ProviderCategory } from "@/types";
import {
getApiKeyFromConfig,
@@ -32,6 +32,28 @@ export function useApiKeyState({
return "";
});
// 当外部通过 form.reset / 读取 live 等方式更新配置时,同步回 API Key 状态
// - 仅在 JSON 可解析时同步,避免用户编辑 JSON 过程中因临时无效导致输入框闪烁
useEffect(() => {
if (!initialConfig) return;
try {
JSON.parse(initialConfig);
} catch {
return;
}
// 仅当配置确实包含 API Key 字段时才同步(避免无意清空用户正在输入的 key)
if (!hasApiKeyField(initialConfig, appType)) {
return;
}
const extracted = getApiKeyFromConfig(initialConfig, appType);
if (extracted !== apiKey) {
setApiKey(extracted);
}
}, [initialConfig, appType, apiKey]);
const handleApiKeyChange = useCallback(
(key: string) => {
setApiKey(key);