refactor(health-check): remove per-provider test config

This commit is contained in:
Jason
2026-07-10 13:42:09 +08:00
parent 442799879d
commit 06039540ff
18 changed files with 48 additions and 468 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ export function FirstRunNoticeDialog() {
const { data: settings } = useSettingsQuery();
// 后端启动时已经决定好要不要弹:条件不满足的话字段会立即被写成 true,
// 所以前端这里只需要判空即可——完全对齐 streamCheckConfirmed 等既有 flag 的模式。
// 所以前端这里只需要判空即可——与其他既有确认标记的模式一致
const isOpen = settings != null && settings.firstRunNoticeConfirmed !== true;
const handleAcknowledge = async () => {
@@ -174,7 +174,7 @@ export function EditProviderDialog({
}, [
open, // 修复:编辑保存后再次打开显示旧数据,依赖 open 确保每次打开时重新读取最新 provider 数据
provider?.id, // 只依赖 ID,provider 对象更新不会触发重新计算
provider?.meta, // 需要依赖 meta 以便正确初始化 testConfig
provider?.meta, // 供应商元数据变化时重新初始化表单
initialSettingsConfig,
]);
@@ -1,6 +1,6 @@
import React from "react";
import { cn } from "@/lib/utils";
import type { HealthStatus } from "@/lib/api/model-test";
import type { HealthStatus } from "@/lib/api/connectivity-check";
import { useTranslation } from "react-i18next";
interface HealthStatusIndicatorProps {
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import { useState, useEffect } from "react";
import { ChevronDown, ChevronRight, FlaskConical, Coins } from "lucide-react";
import { ChevronDown, ChevronRight, Coins } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
@@ -12,8 +12,6 @@ import {
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import type { ProviderTestConfig } from "@/types";
export type PricingModelSourceOption = "inherit" | "request" | "response";
interface ProviderPricingConfig {
@@ -23,170 +21,25 @@ interface ProviderPricingConfig {
}
interface ProviderAdvancedConfigProps {
testConfig: ProviderTestConfig;
pricingConfig: ProviderPricingConfig;
onTestConfigChange: (config: ProviderTestConfig) => void;
onPricingConfigChange: (config: ProviderPricingConfig) => void;
}
export function ProviderAdvancedConfig({
testConfig,
pricingConfig,
onTestConfigChange,
onPricingConfigChange,
}: ProviderAdvancedConfigProps) {
const { t } = useTranslation();
const [isTestConfigOpen, setIsTestConfigOpen] = useState(testConfig.enabled);
const [isPricingConfigOpen, setIsPricingConfigOpen] = useState(
pricingConfig.enabled,
);
useEffect(() => {
setIsTestConfigOpen(testConfig.enabled);
}, [testConfig.enabled]);
useEffect(() => {
setIsPricingConfigOpen(pricingConfig.enabled);
}, [pricingConfig.enabled]);
return (
<div className="space-y-4">
<div className="rounded-lg border border-border/50 bg-muted/20">
<button
type="button"
className="flex w-full items-center justify-between p-4 hover:bg-muted/30 transition-colors"
onClick={() => setIsTestConfigOpen(!isTestConfigOpen)}
>
<div className="flex items-center gap-3">
<FlaskConical className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">
{t("providerAdvanced.testConfig", {
defaultValue: "连通检测配置",
})}
</span>
</div>
<div className="flex items-center gap-3">
<div
className="flex items-center gap-2"
onClick={(e) => e.stopPropagation()}
>
<Label
htmlFor="test-config-enabled"
className="text-sm text-muted-foreground"
>
{t("providerAdvanced.useCustomConfig", {
defaultValue: "使用单独配置",
})}
</Label>
<Switch
id="test-config-enabled"
checked={testConfig.enabled}
onCheckedChange={(checked) => {
onTestConfigChange({ ...testConfig, enabled: checked });
if (checked) setIsTestConfigOpen(true);
}}
/>
</div>
{isTestConfigOpen ? (
<ChevronDown className="h-4 w-4 text-muted-foreground" />
) : (
<ChevronRight className="h-4 w-4 text-muted-foreground" />
)}
</div>
</button>
<div
className={cn(
"overflow-hidden transition-all duration-200",
isTestConfigOpen
? "max-h-[500px] opacity-100"
: "max-h-0 opacity-0",
)}
>
<div className="border-t border-border/50 p-4 space-y-4">
<p className="text-sm text-muted-foreground">
{t("providerAdvanced.testConfigDesc", {
defaultValue:
"为此供应商配置单独的连通检测参数(超时/阈值/重试),不启用时使用全局配置。",
})}
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="test-timeout">
{t("providerAdvanced.timeoutSecs", {
defaultValue: "超时时间(秒)",
})}
</Label>
<Input
id="test-timeout"
type="number"
min={1}
max={60}
value={testConfig.timeoutSecs || ""}
onChange={(e) =>
onTestConfigChange({
...testConfig,
timeoutSecs: e.target.value
? parseInt(e.target.value, 10)
: undefined,
})
}
placeholder="8"
disabled={!testConfig.enabled}
/>
</div>
<div className="space-y-2">
<Label htmlFor="degraded-threshold">
{t("providerAdvanced.degradedThreshold", {
defaultValue: "降级阈值(毫秒)",
})}
</Label>
<Input
id="degraded-threshold"
type="number"
min={100}
max={60000}
value={testConfig.degradedThresholdMs || ""}
onChange={(e) =>
onTestConfigChange({
...testConfig,
degradedThresholdMs: e.target.value
? parseInt(e.target.value, 10)
: undefined,
})
}
placeholder="6000"
disabled={!testConfig.enabled}
/>
</div>
<div className="space-y-2">
<Label htmlFor="max-retries">
{t("providerAdvanced.maxRetries", {
defaultValue: "最大重试次数",
})}
</Label>
<Input
id="max-retries"
type="number"
min={0}
max={5}
value={testConfig.maxRetries ?? ""}
onChange={(e) =>
onTestConfigChange({
...testConfig,
maxRetries: e.target.value
? parseInt(e.target.value, 10)
: undefined,
})
}
placeholder="1"
disabled={!testConfig.enabled}
/>
</div>
</div>
</div>
</div>
</div>
{/* 计费配置 */}
<div className="rounded-lg border border-border/50 bg-muted/20">
<button
@@ -17,7 +17,6 @@ import { useDarkMode } from "@/hooks/useDarkMode";
import type {
ProviderCategory,
ProviderMeta,
ProviderTestConfig,
ClaudeApiFormat,
CodexApiFormat,
CodexCatalogModel,
@@ -313,9 +312,6 @@ function ProviderFormFull({
return initialData?.meta?.isFullUrl ?? false;
});
const [testConfig, setTestConfig] = useState<ProviderTestConfig>(
() => initialData?.meta?.testConfig ?? { enabled: false },
);
const [pricingConfig, setPricingConfig] = useState<{
enabled: boolean;
costMultiplier?: string;
@@ -351,7 +347,6 @@ function ProviderFormFull({
setLocalIsFullUrl(
supportsFullUrl ? (initialData?.meta?.isFullUrl ?? false) : false,
);
setTestConfig(initialData?.meta?.testConfig ?? { enabled: false });
setPricingConfig({
enabled:
initialData?.meta?.costMultiplier !== undefined ||
@@ -1497,7 +1492,6 @@ function ProviderFormFull({
localProxyRequestOverrides: shouldApplyLocalProxyRequestOverrides
? overridesResult.overrides
: undefined,
testConfig: testConfig.enabled ? testConfig : undefined,
costMultiplier: pricingConfig.enabled
? pricingConfig.costMultiplier
: undefined,
@@ -2469,9 +2463,7 @@ function ProviderFormFull({
appId !== "openclaw" &&
appId !== "hermes" && (
<ProviderAdvancedConfig
testConfig={testConfig}
pricingConfig={pricingConfig}
onTestConfigChange={setTestConfig}
onPricingConfigChange={setPricingConfig}
/>
)}
+7 -5
View File
@@ -47,7 +47,7 @@ import { BackupListSection } from "@/components/settings/BackupListSection";
import { WebdavSyncSection } from "@/components/settings/WebdavSyncSection";
import { AboutSection } from "@/components/settings/AboutSection";
import { ProxyTabContent } from "@/components/settings/ProxyTabContent";
import { ModelTestConfigPanel } from "@/components/usage/ModelTestConfigPanel";
import { ConnectivityCheckConfigPanel } from "@/components/usage/ConnectivityCheckConfigPanel";
import { UsageDashboard } from "@/components/usage/UsageDashboard";
import { LogConfigPanel } from "@/components/settings/LogConfigPanel";
import { AuthCenterPanel } from "@/components/settings/AuthCenterPanel";
@@ -455,7 +455,7 @@ export function SettingsPage({
</AccordionItem>
<AccordionItem
value="test"
value="connectivityCheck"
className="rounded-xl glass-card overflow-hidden"
>
<AccordionTrigger className="px-6 py-4 hover:no-underline hover:bg-muted/50 data-[state=open]:bg-muted/50">
@@ -463,16 +463,18 @@ export function SettingsPage({
<FlaskConical className="h-5 w-5 text-emerald-500" />
<div className="text-left">
<h3 className="text-base font-semibold">
{t("settings.advanced.modelTest.title")}
{t("settings.advanced.connectivityCheck.title")}
</h3>
<p className="text-sm text-muted-foreground font-normal">
{t("settings.advanced.modelTest.description")}
{t(
"settings.advanced.connectivityCheck.description",
)}
</p>
</div>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-6 pt-4 border-t border-border/50">
<ModelTestConfigPanel />
<ConnectivityCheckConfigPanel />
</AccordionContent>
</AccordionItem>
@@ -10,9 +10,9 @@ import {
getStreamCheckConfig,
saveStreamCheckConfig,
type StreamCheckConfig,
} from "@/lib/api/model-test";
} from "@/lib/api/connectivity-check";
export function ModelTestConfigPanel() {
export function ConnectivityCheckConfigPanel() {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);