diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1b20bf21..7d6e773b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -530,7 +530,7 @@ pub fn run() { Ok(config) => { if config.enabled { log::info!("代理服务配置为启用,正在启动..."); - match state.proxy_service.start().await { + match state.proxy_service.start_with_takeover().await { Ok(info) => log::info!( "代理服务器自动启动成功: {}:{}", info.address, diff --git a/src-tauri/src/proxy/provider_router.rs b/src-tauri/src/proxy/provider_router.rs index 6f470598..4ae6ffdf 100644 --- a/src-tauri/src/proxy/provider_router.rs +++ b/src-tauri/src/proxy/provider_router.rs @@ -30,58 +30,42 @@ impl ProviderRouter { /// 选择可用的供应商(支持故障转移) /// 返回按优先级排序的可用供应商列表 pub async fn select_providers(&self, app_type: &str) -> Result, AppError> { - // 1. 获取所有启用代理的供应商 - let providers = self.db.get_proxy_targets(app_type).await?; + // 直接获取当前选中的供应商(基于 is_current 字段) + let current_id = self.db.get_current_provider(app_type)? + .ok_or_else(|| AppError::Config( + format!("No current provider for {}", app_type) + ))?; - if providers.is_empty() { - return Err(AppError::Config( - "No proxy target providers configured".to_string(), - )); - } - - log::debug!( - "Found {} proxy target providers for app_type: {}", - providers.len(), - app_type - ); - - // 2. 按 sort_index 排序(已经在数据库查询中排序了) - let sorted_providers: Vec<_> = providers.into_values().collect(); - - // 3. 过滤可用的供应商(检查熔断器状态) - let mut available_providers = Vec::new(); - - for provider in sorted_providers { - let circuit_key = format!("{}:{}", app_type, provider.id); - let breaker = self.get_or_create_circuit_breaker(&circuit_key).await; - - if breaker.allow_request().await { - log::debug!( - "Provider {} is available (circuit state: {:?})", - provider.id, - breaker.get_state().await - ); - available_providers.push(provider); - } else { - log::warn!( - "Provider {} is unavailable (circuit breaker open)", - provider.id - ); - } - } - - if available_providers.is_empty() { - return Err(AppError::Config( - "All proxy target providers are unavailable (circuit breakers open)".to_string(), - )); - } + let providers = self.db.get_all_providers(app_type)?; + let provider = providers.get(¤t_id) + .ok_or_else(|| AppError::Config( + format!("Current provider {} not found", current_id) + ))? + .clone(); log::info!( - "Selected {} available providers for failover chain", - available_providers.len() + "[{}] Selected current provider: {} ({})", + app_type, + provider.name, + provider.id ); - Ok(available_providers) + // 检查熔断器状态 + let circuit_key = format!("{}:{}", app_type, provider.id); + let breaker = self.get_or_create_circuit_breaker(&circuit_key).await; + + if !breaker.allow_request().await { + log::warn!( + "Provider {} is unavailable (circuit breaker open)", + provider.id + ); + return Err(AppError::Config( + format!("Current provider {} is unavailable (circuit breaker open)", provider.name) + )); + } + + // 返回单个供应商(保留 Vec 接口以兼容现有代码) + Ok(vec![provider]) } /// 记录供应商请求结果 diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index a1e8e025..b96d6ec7 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -206,14 +206,17 @@ impl ProviderService { id ); - // Update database is_current (proxy server reads this) + // Update database is_current state.db.set_current_provider(app_type.as_str(), id)?; + // 同时更新 is_proxy_target(代理路由器使用此字段选择供应商) + state.db.set_proxy_target_provider(app_type.as_str(), id)?; + // Update local settings for consistency crate::settings::set_current_provider(&app_type, Some(id))?; // Note: No Live config write, no MCP sync - // The proxy server will route requests to the new provider + // The proxy server will route requests to the new provider via is_proxy_target return Ok(()); } diff --git a/src/components/providers/ProviderCard.tsx b/src/components/providers/ProviderCard.tsx index b0fbef10..6dfedbd3 100644 --- a/src/components/providers/ProviderCard.tsx +++ b/src/components/providers/ProviderCard.tsx @@ -11,13 +11,10 @@ import { cn } from "@/lib/utils"; import { ProviderActions } from "@/components/providers/ProviderActions"; import { ProviderIcon } from "@/components/ProviderIcon"; import UsageFooter from "@/components/UsageFooter"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge"; import { useProviderHealth, useResetCircuitBreaker, - useSetProxyTarget, } from "@/lib/query/failover"; import { toast } from "sonner"; @@ -41,8 +38,6 @@ interface ProviderCardProps { isTesting?: boolean; isProxyRunning: boolean; isProxyTakeover?: boolean; // 代理接管模式(Live配置已被接管,切换为热切换) - proxyPriority?: number; // 代理目标的实际优先级 (1, 2, 3...) - allProviders?: Provider[]; // 所有供应商列表,用于计算开启后的优先级 dragHandleProps?: DragHandleProps; } @@ -95,8 +90,6 @@ export function ProviderCard({ isTesting, isProxyRunning, isProxyTakeover = false, - proxyPriority, - allProviders, dragHandleProps, }: ProviderCardProps) { const { t } = useTranslation(); @@ -104,77 +97,9 @@ export function ProviderCard({ // 获取供应商健康状态 const { data: health } = useProviderHealth(provider.id, appId); - // 设置代理目标 - const setProxyTargetMutation = useSetProxyTarget(); - // 重置熔断器 const resetCircuitBreaker = useResetCircuitBreaker(); - const handleSetProxyTarget = async (enabled: boolean) => { - try { - await setProxyTargetMutation.mutateAsync({ - providerId: provider.id, - appType: appId, - enabled, - }); - - // 计算实际优先级(开启时) - let actualPriority: number | undefined; - if (enabled && allProviders) { - // 模拟开启后的状态:获取所有将要启用代理的 providers - const futureProxyTargets = allProviders.filter((p) => { - // 包括:已经是代理目标的 或 当前要开启的这个 - if (p.id === provider.id) return true; - return p.isProxyTarget; - }); - - // 按 sortIndex 排序 - const sortedTargets = futureProxyTargets.sort((a, b) => { - const indexA = a.sortIndex ?? Number.MAX_SAFE_INTEGER; - const indexB = b.sortIndex ?? Number.MAX_SAFE_INTEGER; - return indexA - indexB; - }); - - // 找到当前 provider 的位置 - const position = sortedTargets.findIndex((p) => p.id === provider.id); - actualPriority = position >= 0 ? position + 1 : undefined; - } - - const message = enabled - ? actualPriority - ? t("provider.proxyTargetEnabled", { - defaultValue: `已启用代理目标(优先级:P${actualPriority})`, - }) - : t("provider.proxyTargetEnabled", { - defaultValue: "已启用代理目标", - }) - : t("provider.proxyTargetDisabled", { - defaultValue: "已禁用代理目标", - }); - - const description = enabled - ? t("provider.proxyTargetEnabledDesc", { - defaultValue: "下次请求将按优先级自动选择此供应商", - }) - : t("provider.proxyTargetDisabledDesc", { - defaultValue: "后续请求将使用其他可用供应商", - }); - - toast.success(message, { - description, - duration: 4000, - }); - } catch (error) { - toast.error( - t("provider.setProxyTargetFailed", { - defaultValue: "操作失败", - }) + - ": " + - String(error), - ); - } - }; - const handleResetCircuitBreaker = async () => { try { await resetCircuitBreaker.mutateAsync({ @@ -277,25 +202,10 @@ export function ProviderCard({ {/* 健康状态徽章和优先级 */} - {isProxyRunning && ( -
- {/* 健康徽章:代理目标启用时始终显示,没有健康数据时默认为正常(0失败) */} - {(provider.isProxyTarget || health) && ( - - )} - {/* 优先级:仅在代理目标启用时显示 */} - {provider.isProxyTarget && proxyPriority && ( - - P{proxyPriority} - - )} -
+ {isProxyRunning && health && ( + )} {provider.category === "third_party" && @@ -309,42 +219,6 @@ export function ProviderCard({ ⭐ )} - - {/* 代理目标开关 - 仅在代理服务运行时显示 */} - {isProxyRunning && ( -
e.stopPropagation()} - > - { - handleSetProxyTarget(checked); - }} - disabled={setProxyTargetMutation.isPending} - className="scale-75 data-[state=checked]:bg-green-500" - /> - {provider.isProxyTarget && ( - - )} - {!provider.isProxyTarget && ( - - )} -
- )} {displayUrl && ( diff --git a/src/components/providers/ProviderHealthBadge.tsx b/src/components/providers/ProviderHealthBadge.tsx index dbacb6ca..1a9f901d 100644 --- a/src/components/providers/ProviderHealthBadge.tsx +++ b/src/components/providers/ProviderHealthBadge.tsx @@ -3,7 +3,6 @@ import { ProviderHealthStatus } from "@/types/proxy"; interface ProviderHealthBadgeProps { consecutiveFailures: number; - isProxyTarget?: boolean; className?: string; } @@ -13,14 +12,8 @@ interface ProviderHealthBadgeProps { */ export function ProviderHealthBadge({ consecutiveFailures, - isProxyTarget, className, }: ProviderHealthBadgeProps) { - // 如果代理目标已关闭但有失败记录,仍然显示(自动熔断场景) - // 如果代理目标启用,始终显示 - // 如果代理目标关闭且无失败记录,隐藏 - if (!isProxyTarget && consecutiveFailures === 0) return null; - // 根据失败次数计算状态 const getStatus = () => { if (consecutiveFailures === 0) { diff --git a/src/components/providers/ProviderList.tsx b/src/components/providers/ProviderList.tsx index 610f16ae..49965c6f 100644 --- a/src/components/providers/ProviderList.tsx +++ b/src/components/providers/ProviderList.tsx @@ -5,7 +5,6 @@ import { useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; -import { useMemo } from "react"; import type { CSSProperties } from "react"; import type { Provider } from "@/types"; import type { AppId } from "@/lib/api"; @@ -53,27 +52,6 @@ export function ProviderList({ // 模型测试 const { testProvider, isTesting } = useModelTest(appId); - // 计算代理目标的实际优先级映射 (P1, P2, P3...) - const proxyPriorityMap = useMemo(() => { - // 获取所有启用代理目标的供应商 - const proxyTargets = sortedProviders.filter((p) => p.isProxyTarget); - - // 按 sortIndex 排序 - const sortedTargets = proxyTargets.sort((a, b) => { - const indexA = a.sortIndex ?? Number.MAX_SAFE_INTEGER; - const indexB = b.sortIndex ?? Number.MAX_SAFE_INTEGER; - return indexA - indexB; - }); - - // 创建优先级映射 - const map = new Map(); - sortedTargets.forEach((provider, index) => { - map.set(provider.id, index + 1); // P1, P2, P3... - }); - - return map; - }, [sortedProviders]); - const handleTest = (provider: Provider) => { testProvider(provider.id, provider.name); }; @@ -125,8 +103,6 @@ export function ProviderList({ isTesting={isTesting(provider.id)} isProxyRunning={isProxyRunning} isProxyTakeover={isProxyTakeover} - proxyPriority={proxyPriorityMap.get(provider.id)} - allProviders={sortedProviders} /> ))} @@ -149,8 +125,6 @@ interface SortableProviderCardProps { isTesting: boolean; isProxyRunning: boolean; isProxyTakeover: boolean; - proxyPriority?: number; // 代理目标的实际优先级 (1, 2, 3...) - allProviders?: Provider[]; // 所有供应商列表 } function SortableProviderCard({ @@ -167,8 +141,6 @@ function SortableProviderCard({ isTesting, isProxyRunning, isProxyTakeover, - proxyPriority, - allProviders, }: SortableProviderCardProps) { const { setNodeRef, @@ -202,8 +174,6 @@ function SortableProviderCard({ isTesting={isTesting} isProxyRunning={isProxyRunning} isProxyTakeover={isProxyTakeover} - proxyPriority={proxyPriority} - allProviders={allProviders} dragHandleProps={{ attributes, listeners, diff --git a/src/components/proxy/ProxyPanel.tsx b/src/components/proxy/ProxyPanel.tsx index fab2fb54..c4cbabeb 100644 --- a/src/components/proxy/ProxyPanel.tsx +++ b/src/components/proxy/ProxyPanel.tsx @@ -302,10 +302,9 @@ function ProviderQueueItem({ )} - {/* 健康徽章:队列中的代理目标始终显示,没有健康数据时默认为正常 */} + {/* 健康徽章 */} );