mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 02:14:43 +08:00
4a0b5c3dec
The per-provider proxy configuration (meta.proxyConfig) is removed because its scope is too narrow and covered by global proxy settings and proxy takeover mode. Users can achieve the same result via the global proxy panel. Changes: - Remove ProviderProxyConfig type (frontend TS + backend Rust) - Remove ProviderAdvancedConfig proxy UI block, keep testConfig/pricingConfig - Simplify http_client: delete build_proxy_url_from_config, build_client_for_provider, get_for_provider - Simplify forwarder/stream_check/model_fetch to use global client - Remove i18n keys (en/zh/ja) - Fix pre-existing test bug in transform.rs (extra None arg)
234 lines
7.2 KiB
TypeScript
234 lines
7.2 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Save } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { FullScreenPanel } from "@/components/common/FullScreenPanel";
|
|
import type { Provider } from "@/types";
|
|
import {
|
|
ProviderForm,
|
|
type ProviderFormValues,
|
|
} from "@/components/providers/forms/ProviderForm";
|
|
import { openclawApi, providersApi, vscodeApi, type AppId } from "@/lib/api";
|
|
|
|
interface EditProviderDialogProps {
|
|
open: boolean;
|
|
provider: Provider | null;
|
|
onOpenChange: (open: boolean) => void;
|
|
onSubmit: (payload: {
|
|
provider: Provider;
|
|
originalId?: string;
|
|
}) => Promise<void> | void;
|
|
appId: AppId;
|
|
isProxyTakeover?: boolean; // 代理接管模式下不读取 live(避免显示被接管后的代理配置)
|
|
}
|
|
|
|
export function EditProviderDialog({
|
|
open,
|
|
provider,
|
|
onOpenChange,
|
|
onSubmit,
|
|
appId,
|
|
isProxyTakeover = false,
|
|
}: EditProviderDialogProps) {
|
|
const { t } = useTranslation();
|
|
const [isFormSubmitting, setIsFormSubmitting] = useState(false);
|
|
|
|
// 默认使用传入的 provider.settingsConfig,若当前编辑对象是"当前生效供应商",则尝试读取实时配置替换初始值
|
|
const [liveSettings, setLiveSettings] = useState<Record<
|
|
string,
|
|
unknown
|
|
> | null>(null);
|
|
|
|
// 使用 ref 标记是否已经加载过,防止重复读取覆盖用户编辑
|
|
const [hasLoadedLive, setHasLoadedLive] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
const load = async () => {
|
|
if (!open || !provider) {
|
|
setLiveSettings(null);
|
|
setHasLoadedLive(false);
|
|
return;
|
|
}
|
|
|
|
// 关键修复:只在首次打开时加载一次
|
|
if (hasLoadedLive) {
|
|
return;
|
|
}
|
|
|
|
// 代理接管模式:Live 配置已被代理改写,读取 live 会导致编辑界面展示代理地址/占位符等内容
|
|
// 因此直接回退到 SSOT(数据库)配置,避免用户困惑与误保存
|
|
if (isProxyTakeover) {
|
|
if (!cancelled) {
|
|
setLiveSettings(null);
|
|
setHasLoadedLive(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// OpenCode uses additive mode - each provider's config is stored independently in DB
|
|
// Reading live config would return the full opencode.json (with $schema, provider, mcp etc.)
|
|
// instead of just the provider fragment, causing incorrect nested structure on save
|
|
if (appId === "opencode") {
|
|
if (!cancelled) {
|
|
setLiveSettings(null);
|
|
setHasLoadedLive(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (appId === "openclaw") {
|
|
try {
|
|
const live = await openclawApi.getLiveProvider(provider.id);
|
|
if (!cancelled && live && typeof live === "object") {
|
|
setLiveSettings(live);
|
|
} else if (!cancelled) {
|
|
setLiveSettings(null);
|
|
}
|
|
} catch {
|
|
if (!cancelled) {
|
|
setLiveSettings(null);
|
|
}
|
|
} finally {
|
|
if (!cancelled) {
|
|
setHasLoadedLive(true);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const currentId = await providersApi.getCurrent(appId);
|
|
if (currentId && provider.id === currentId) {
|
|
try {
|
|
const live = (await vscodeApi.getLiveProviderSettings(
|
|
appId,
|
|
)) as Record<string, unknown>;
|
|
if (!cancelled && live && typeof live === "object") {
|
|
setLiveSettings(live);
|
|
setHasLoadedLive(true);
|
|
}
|
|
} catch {
|
|
// 读取实时配置失败则回退到 SSOT(不打断编辑流程)
|
|
if (!cancelled) {
|
|
setLiveSettings(null);
|
|
setHasLoadedLive(true);
|
|
}
|
|
}
|
|
} else {
|
|
if (!cancelled) {
|
|
setLiveSettings(null);
|
|
setHasLoadedLive(true);
|
|
}
|
|
}
|
|
} finally {
|
|
// no-op
|
|
}
|
|
};
|
|
void load();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [open, provider?.id, appId, hasLoadedLive, isProxyTakeover]); // 只依赖 provider.id,不依赖整个 provider 对象
|
|
|
|
const initialSettingsConfig = useMemo(() => {
|
|
return (liveSettings ?? provider?.settingsConfig ?? {}) as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
}, [liveSettings, provider?.settingsConfig]); // 只依赖 settingsConfig,不依赖整个 provider
|
|
|
|
// 固定 initialData,防止 provider 对象更新时重置表单
|
|
const initialData = useMemo(() => {
|
|
if (!provider) return null;
|
|
return {
|
|
name: provider.name,
|
|
notes: provider.notes,
|
|
websiteUrl: provider.websiteUrl,
|
|
settingsConfig: initialSettingsConfig,
|
|
category: provider.category,
|
|
meta: provider.meta,
|
|
icon: provider.icon,
|
|
iconColor: provider.iconColor,
|
|
};
|
|
}, [
|
|
open, // 修复:编辑保存后再次打开显示旧数据,依赖 open 确保每次打开时重新读取最新 provider 数据
|
|
provider?.id, // 只依赖 ID,provider 对象更新不会触发重新计算
|
|
provider?.meta, // 需要依赖 meta 以便正确初始化 testConfig
|
|
initialSettingsConfig,
|
|
]);
|
|
|
|
const handleSubmit = useCallback(
|
|
async (values: ProviderFormValues) => {
|
|
if (!provider) return;
|
|
|
|
// 注意:values.settingsConfig 已经是最终的配置字符串
|
|
// ProviderForm 已经为不同的 app 类型(Claude/Codex/Gemini)正确组装了配置
|
|
const parsedConfig = JSON.parse(values.settingsConfig) as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
const nextProviderId =
|
|
(appId === "opencode" || appId === "openclaw") &&
|
|
values.providerKey?.trim()
|
|
? values.providerKey.trim()
|
|
: provider.id;
|
|
|
|
const updatedProvider: Provider = {
|
|
...provider,
|
|
id: nextProviderId,
|
|
name: values.name.trim(),
|
|
notes: values.notes?.trim() || undefined,
|
|
websiteUrl: values.websiteUrl?.trim() || undefined,
|
|
settingsConfig: parsedConfig,
|
|
icon: values.icon?.trim() || undefined,
|
|
iconColor: values.iconColor?.trim() || undefined,
|
|
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
|
// 保留或更新 meta 字段
|
|
...(values.meta ? { meta: values.meta } : {}),
|
|
};
|
|
|
|
await onSubmit({
|
|
provider: updatedProvider,
|
|
originalId: provider.id,
|
|
});
|
|
onOpenChange(false);
|
|
},
|
|
[appId, onSubmit, onOpenChange, provider],
|
|
);
|
|
|
|
if (!provider || !initialData) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<FullScreenPanel
|
|
isOpen={open}
|
|
title={t("provider.editProvider")}
|
|
onClose={() => onOpenChange(false)}
|
|
footer={
|
|
<Button
|
|
type="submit"
|
|
form="provider-form"
|
|
disabled={isFormSubmitting}
|
|
className="bg-primary text-primary-foreground hover:bg-primary/90"
|
|
>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
{t("common.save")}
|
|
</Button>
|
|
}
|
|
>
|
|
<ProviderForm
|
|
appId={appId}
|
|
providerId={provider.id}
|
|
submitLabel={t("common.save")}
|
|
onSubmit={handleSubmit}
|
|
onCancel={() => onOpenChange(false)}
|
|
onSubmittingChange={setIsFormSubmitting}
|
|
initialData={initialData}
|
|
showButtons={false}
|
|
/>
|
|
</FullScreenPanel>
|
|
);
|
|
}
|