mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-26 23:56:02 +08:00
6dd809701b
* refactor(proxy): simplify logging for better readability - Delete 17 verbose debug logs from handlers, streaming, and response_processor - Convert excessive INFO logs to DEBUG level for internal processing details - Add 2 critical INFO logs in forwarder.rs for failover scenarios: - Log when switching to next provider after failure - Log when all providers have been exhausted - Fix clippy uninlined_format_args warning This reduces log noise while maintaining visibility into key user-facing decisions. * fix: replace unsafe unwrap() calls with proper error handling - database/dao/mcp.rs: Use map_err for serde_json serialization - database/dao/providers.rs: Use map_err for settings_config and meta serialization - commands/misc.rs: Use expect() for compile-time regex pattern - services/prompt.rs: Use unwrap_or_default() for SystemTime - deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks Reduces potential panic points from 26 to 1 (static regex init, safe). * refactor(proxy): simplify verbose logging output - Remove response JSON full output logging in response_processor - Remove per-request INFO logs in provider_router (failover status, provider selection) - Change model mapping log from INFO to DEBUG - Change usage logging failure from INFO to WARN - Remove redundant debug logs for circuit breaker operations Reduces log noise significantly while preserving important warnings and errors. * feat(proxy): add structured log codes for i18n support Add error code system to proxy module logs for multi-language support: - CB-001~006: Circuit breaker state transitions and triggers - SRV-001~004: Proxy server lifecycle events - FWD-001~002: Request forwarding and failover - FO-001~005: Failover switch operations - USG-001~002: Usage logging errors Log format: [CODE] Chinese message Frontend/log tools can map codes to any language. New file: src/proxy/log_codes.rs - centralized code definitions * chore: bump version to 3.9.1 * style: format code with prettier and rustfmt * fix(ui): allow number inputs to be fully cleared before saving - Convert numeric state to string type for controlled inputs - Use isNaN() check instead of || fallback to allow 0 values - Apply fix to ProxyPanel, CircuitBreakerConfigPanel, AutoFailoverConfigPanel, and ModelTestConfigPanel * feat(pricing): support @ separator in model name matching - Refactor model name cleaning into chained method calls - Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low) - Add test case for @ separator matching * fix(proxy): improve validation and error handling in proxy config panels - Add StopTimeout/StopFailed error types for proper stop() error reporting - Replace silent clamp with validation-and-block in config panels - Add listenAddress format validation in ProxyPanel - Use log_codes constants instead of hardcoded strings - Use once_cell::Lazy for regex precompilation * fix(proxy): harden error handling and input validation - Handle RwLock poisoning in settings.rs with unwrap_or_else - Add fallback for dirs::home_dir() in config modules - Normalize localhost to 127.0.0.1 in ProxyPanel - Format IPv6 addresses with brackets for valid URLs - Strict port validation with pure digit regex - Treat NaN as validation failure in config panels - Log warning on cost_multiplier parse failure - Align timeoutSeconds range to [0, 300] across all panels
212 lines
6.3 KiB
TypeScript
212 lines
6.3 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Save, Loader2 } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import {
|
|
getStreamCheckConfig,
|
|
saveStreamCheckConfig,
|
|
type StreamCheckConfig,
|
|
} from "@/lib/api/model-test";
|
|
|
|
export function ModelTestConfigPanel() {
|
|
const { t } = useTranslation();
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
// 使用字符串状态以支持完全清空数字输入框
|
|
const [config, setConfig] = useState({
|
|
timeoutSecs: "45",
|
|
maxRetries: "2",
|
|
degradedThresholdMs: "6000",
|
|
claudeModel: "claude-haiku-4-5-20251001",
|
|
codexModel: "gpt-5.1-codex@low",
|
|
geminiModel: "gemini-3-pro-preview",
|
|
});
|
|
|
|
useEffect(() => {
|
|
loadConfig();
|
|
}, []);
|
|
|
|
async function loadConfig() {
|
|
try {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
const data = await getStreamCheckConfig();
|
|
setConfig({
|
|
timeoutSecs: String(data.timeoutSecs),
|
|
maxRetries: String(data.maxRetries),
|
|
degradedThresholdMs: String(data.degradedThresholdMs),
|
|
claudeModel: data.claudeModel,
|
|
codexModel: data.codexModel,
|
|
geminiModel: data.geminiModel,
|
|
});
|
|
} catch (e) {
|
|
setError(String(e));
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
|
|
async function handleSave() {
|
|
// 解析数字,空值使用默认值,0 是有效值
|
|
const parseNum = (val: string, defaultVal: number) => {
|
|
const n = parseInt(val);
|
|
return isNaN(n) ? defaultVal : n;
|
|
};
|
|
try {
|
|
setIsSaving(true);
|
|
const parsed: StreamCheckConfig = {
|
|
timeoutSecs: parseNum(config.timeoutSecs, 45),
|
|
maxRetries: parseNum(config.maxRetries, 2),
|
|
degradedThresholdMs: parseNum(config.degradedThresholdMs, 6000),
|
|
claudeModel: config.claudeModel,
|
|
codexModel: config.codexModel,
|
|
geminiModel: config.geminiModel,
|
|
};
|
|
await saveStreamCheckConfig(parsed);
|
|
toast.success(t("streamCheck.configSaved"), {
|
|
closeButton: true,
|
|
});
|
|
} catch (e) {
|
|
toast.error(t("streamCheck.configSaveFailed") + ": " + String(e));
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-4">
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{error && (
|
|
<Alert variant="destructive">
|
|
<AlertDescription>{error}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{/* 测试模型配置 */}
|
|
<div className="space-y-4">
|
|
<h4 className="text-sm font-medium text-muted-foreground">
|
|
{t("streamCheck.testModels")}
|
|
</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="claudeModel">{t("streamCheck.claudeModel")}</Label>
|
|
<Input
|
|
id="claudeModel"
|
|
value={config.claudeModel}
|
|
onChange={(e) =>
|
|
setConfig({ ...config, claudeModel: e.target.value })
|
|
}
|
|
placeholder="claude-3-5-haiku-latest"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="codexModel">{t("streamCheck.codexModel")}</Label>
|
|
<Input
|
|
id="codexModel"
|
|
value={config.codexModel}
|
|
onChange={(e) =>
|
|
setConfig({ ...config, codexModel: e.target.value })
|
|
}
|
|
placeholder="gpt-4o-mini"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="geminiModel">{t("streamCheck.geminiModel")}</Label>
|
|
<Input
|
|
id="geminiModel"
|
|
value={config.geminiModel}
|
|
onChange={(e) =>
|
|
setConfig({ ...config, geminiModel: e.target.value })
|
|
}
|
|
placeholder="gemini-1.5-flash"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 检查参数配置 */}
|
|
<div className="space-y-4">
|
|
<h4 className="text-sm font-medium text-muted-foreground">
|
|
{t("streamCheck.checkParams")}
|
|
</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="timeoutSecs">{t("streamCheck.timeout")}</Label>
|
|
<Input
|
|
id="timeoutSecs"
|
|
type="number"
|
|
min={10}
|
|
max={120}
|
|
value={config.timeoutSecs}
|
|
onChange={(e) =>
|
|
setConfig({ ...config, timeoutSecs: e.target.value })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="maxRetries">{t("streamCheck.maxRetries")}</Label>
|
|
<Input
|
|
id="maxRetries"
|
|
type="number"
|
|
min={0}
|
|
max={5}
|
|
value={config.maxRetries}
|
|
onChange={(e) =>
|
|
setConfig({ ...config, maxRetries: e.target.value })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="degradedThresholdMs">
|
|
{t("streamCheck.degradedThreshold")}
|
|
</Label>
|
|
<Input
|
|
id="degradedThresholdMs"
|
|
type="number"
|
|
min={1000}
|
|
max={30000}
|
|
step={1000}
|
|
value={config.degradedThresholdMs}
|
|
onChange={(e) =>
|
|
setConfig({ ...config, degradedThresholdMs: e.target.value })
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end">
|
|
<Button onClick={handleSave} disabled={isSaving}>
|
|
{isSaving ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
{t("common.saving")}
|
|
</>
|
|
) : (
|
|
<>
|
|
<Save className="mr-2 h-4 w-4" />
|
|
{t("common.save")}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|