mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 11:01:36 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77b22e2b68 |
+3
-5
@@ -29,7 +29,7 @@ import {
|
|||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import type { Provider, VisibleApps } from "@/types";
|
import type { Provider, VisibleApps } from "@/types";
|
||||||
import type { EnvConflict } from "@/types/env";
|
import type { EnvConflict } from "@/types/env";
|
||||||
import { proxyKeys, useProvidersQuery, useSettingsQuery } from "@/lib/query";
|
import { useProvidersQuery, useSettingsQuery } from "@/lib/query";
|
||||||
import {
|
import {
|
||||||
providersApi,
|
providersApi,
|
||||||
settingsApi,
|
settingsApi,
|
||||||
@@ -389,10 +389,8 @@ function App() {
|
|||||||
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
||||||
await queryClient.invalidateQueries({ queryKey: ["mcp", "all"] });
|
await queryClient.invalidateQueries({ queryKey: ["mcp", "all"] });
|
||||||
await queryClient.invalidateQueries({ queryKey: ["skills"] });
|
await queryClient.invalidateQueries({ queryKey: ["skills"] });
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
queryKey: proxyKeys.takeoverStatus,
|
await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
});
|
|
||||||
await queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: ["providers", "claude-desktop"],
|
queryKey: ["providers", "claude-desktop"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { ToggleRow } from "@/components/ui/toggle-row";
|
import { ToggleRow } from "@/components/ui/toggle-row";
|
||||||
|
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useFailoverQueue } from "@/lib/query/failover";
|
import { useFailoverQueue } from "@/lib/query/failover";
|
||||||
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
|
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
|
||||||
import { useProviderHealth } from "@/lib/query/failover";
|
import { useProviderHealth } from "@/lib/query/failover";
|
||||||
import {
|
import {
|
||||||
useProxyStatusQuery,
|
|
||||||
useProxyTakeoverStatus,
|
useProxyTakeoverStatus,
|
||||||
useSetProxyTakeoverForApp,
|
useSetProxyTakeoverForApp,
|
||||||
useGlobalProxyConfig,
|
useGlobalProxyConfig,
|
||||||
@@ -45,8 +45,7 @@ export function ProxyPanel({
|
|||||||
isProxyPending,
|
isProxyPending,
|
||||||
}: ProxyPanelProps) {
|
}: ProxyPanelProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data: status } = useProxyStatusQuery();
|
const { status, isRunning } = useProxyStatus();
|
||||||
const isRunning = status?.running ?? false;
|
|
||||||
|
|
||||||
// 获取应用接管状态
|
// 获取应用接管状态
|
||||||
const { data: takeoverStatus } = useProxyTakeoverStatus();
|
const { data: takeoverStatus } = useProxyTakeoverStatus();
|
||||||
|
|||||||
+88
-19
@@ -2,15 +2,15 @@
|
|||||||
* 代理服务状态管理 Hook
|
* 代理服务状态管理 Hook
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { proxyApi } from "@/lib/api/proxy";
|
import type {
|
||||||
import {
|
ProxyStatus,
|
||||||
proxyKeys,
|
ProxyServerInfo,
|
||||||
useProxyStatusQuery,
|
ProxyTakeoverStatus,
|
||||||
useProxyTakeoverStatus,
|
} from "@/types/proxy";
|
||||||
} from "@/lib/query/proxy";
|
|
||||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,14 +21,25 @@ export function useProxyStatus() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 查询状态(自动轮询)
|
// 查询状态(自动轮询)
|
||||||
const { data: status } = useProxyStatusQuery();
|
const { data: status, isLoading } = useQuery({
|
||||||
|
queryKey: ["proxyStatus"],
|
||||||
|
queryFn: () => invoke<ProxyStatus>("get_proxy_status"),
|
||||||
|
// 仅在服务运行时轮询
|
||||||
|
refetchInterval: (query) => (query.state.data?.running ? 2000 : false),
|
||||||
|
// 保持之前的数据,避免闪烁
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
});
|
||||||
|
|
||||||
// 查询各应用接管状态
|
// 查询各应用接管状态
|
||||||
const { data: takeoverStatus } = useProxyTakeoverStatus(false);
|
const { data: takeoverStatus } = useQuery({
|
||||||
|
queryKey: ["proxyTakeoverStatus"],
|
||||||
|
queryFn: () => invoke<ProxyTakeoverStatus>("get_proxy_takeover_status"),
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
});
|
||||||
|
|
||||||
// 启动服务器(总开关:仅启动服务,不接管)
|
// 启动服务器(总开关:仅启动服务,不接管)
|
||||||
const startProxyServerMutation = useMutation({
|
const startProxyServerMutation = useMutation({
|
||||||
mutationFn: () => proxyApi.startProxyServer(),
|
mutationFn: () => invoke<ProxyServerInfo>("start_proxy_server"),
|
||||||
onSuccess: (info) => {
|
onSuccess: (info) => {
|
||||||
toast.success(
|
toast.success(
|
||||||
t("proxy.server.started", {
|
t("proxy.server.started", {
|
||||||
@@ -38,7 +49,7 @@ export function useProxyStatus() {
|
|||||||
}),
|
}),
|
||||||
{ closeButton: true },
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
const detail =
|
const detail =
|
||||||
@@ -55,7 +66,7 @@ export function useProxyStatus() {
|
|||||||
|
|
||||||
// 停止服务器(仅停止服务,不改写/恢复其它应用接管状态)
|
// 停止服务器(仅停止服务,不改写/恢复其它应用接管状态)
|
||||||
const stopProxyServerMutation = useMutation({
|
const stopProxyServerMutation = useMutation({
|
||||||
mutationFn: () => proxyApi.stopProxyServer(),
|
mutationFn: () => invoke("stop_proxy_server"),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(
|
toast.success(
|
||||||
t("proxy.server.stopped", {
|
t("proxy.server.stopped", {
|
||||||
@@ -63,7 +74,7 @@ export function useProxyStatus() {
|
|||||||
}),
|
}),
|
||||||
{ closeButton: true },
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
const detail =
|
const detail =
|
||||||
@@ -80,7 +91,7 @@ export function useProxyStatus() {
|
|||||||
|
|
||||||
// 停止服务器(总开关关闭:强制恢复所有已接管的 Live 配置)
|
// 停止服务器(总开关关闭:强制恢复所有已接管的 Live 配置)
|
||||||
const stopWithRestoreMutation = useMutation({
|
const stopWithRestoreMutation = useMutation({
|
||||||
mutationFn: () => proxyApi.stopProxyWithRestore(),
|
mutationFn: () => invoke("stop_proxy_with_restore"),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(
|
toast.success(
|
||||||
t("proxy.stoppedWithRestore", {
|
t("proxy.stoppedWithRestore", {
|
||||||
@@ -88,8 +99,8 @@ export function useProxyStatus() {
|
|||||||
}),
|
}),
|
||||||
{ closeButton: true },
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus });
|
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
// 彻底删除所有供应商健康状态缓存(后端已清空数据库记录)
|
// 彻底删除所有供应商健康状态缓存(后端已清空数据库记录)
|
||||||
queryClient.removeQueries({ queryKey: ["providerHealth"] });
|
queryClient.removeQueries({ queryKey: ["providerHealth"] });
|
||||||
// 彻底删除所有熔断器统计缓存(代理停止后熔断器状态已重置)
|
// 彻底删除所有熔断器统计缓存(代理停止后熔断器状态已重置)
|
||||||
@@ -112,7 +123,7 @@ export function useProxyStatus() {
|
|||||||
// 按应用开启/关闭接管
|
// 按应用开启/关闭接管
|
||||||
const setTakeoverForAppMutation = useMutation({
|
const setTakeoverForAppMutation = useMutation({
|
||||||
mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) =>
|
mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) =>
|
||||||
proxyApi.setProxyTakeoverForApp(appType, enabled),
|
invoke("set_proxy_takeover_for_app", { appType, enabled }),
|
||||||
onSuccess: (_data, variables) => {
|
onSuccess: (_data, variables) => {
|
||||||
const appLabel =
|
const appLabel =
|
||||||
variables.appType === "claude"
|
variables.appType === "claude"
|
||||||
@@ -138,8 +149,8 @@ export function useProxyStatus() {
|
|||||||
{ closeButton: true },
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus });
|
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
const detail =
|
const detail =
|
||||||
@@ -154,10 +165,60 @@ export function useProxyStatus() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 代理模式切换供应商(热切换)
|
||||||
|
const switchProxyProviderMutation = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
appType,
|
||||||
|
providerId,
|
||||||
|
}: {
|
||||||
|
appType: string;
|
||||||
|
providerId: string;
|
||||||
|
}) => invoke("switch_proxy_provider", { appType, providerId }),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
const detail =
|
||||||
|
extractErrorMessage(error) ||
|
||||||
|
t("common.unknown", { defaultValue: "未知错误" });
|
||||||
|
toast.error(
|
||||||
|
t("proxy.switchFailed", {
|
||||||
|
error: detail,
|
||||||
|
defaultValue: `切换失败: ${detail}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否运行中
|
||||||
|
const checkRunning = async () => {
|
||||||
|
try {
|
||||||
|
return await invoke<boolean>("is_proxy_running");
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查接管状态
|
||||||
|
const checkTakeoverActive = async () => {
|
||||||
|
try {
|
||||||
|
return await invoke<boolean>("is_live_takeover_active");
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
|
isLoading,
|
||||||
isRunning: status?.running || false,
|
isRunning: status?.running || false,
|
||||||
takeoverStatus,
|
takeoverStatus,
|
||||||
|
isTakeoverActive:
|
||||||
|
takeoverStatus?.claude ||
|
||||||
|
takeoverStatus?.codex ||
|
||||||
|
takeoverStatus?.gemini ||
|
||||||
|
takeoverStatus?.grokbuild ||
|
||||||
|
false,
|
||||||
|
|
||||||
// 启动/停止(总开关)
|
// 启动/停止(总开关)
|
||||||
startProxyServer: startProxyServerMutation.mutateAsync,
|
startProxyServer: startProxyServerMutation.mutateAsync,
|
||||||
@@ -167,9 +228,17 @@ export function useProxyStatus() {
|
|||||||
// 按应用接管开关
|
// 按应用接管开关
|
||||||
setTakeoverForApp: setTakeoverForAppMutation.mutateAsync,
|
setTakeoverForApp: setTakeoverForAppMutation.mutateAsync,
|
||||||
|
|
||||||
|
// 代理模式下切换供应商
|
||||||
|
switchProxyProvider: switchProxyProviderMutation.mutateAsync,
|
||||||
|
|
||||||
|
// 状态检查
|
||||||
|
checkRunning,
|
||||||
|
checkTakeoverActive,
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
isStarting: startProxyServerMutation.isPending,
|
isStarting: startProxyServerMutation.isPending,
|
||||||
isStoppingServer: stopProxyServerMutation.isPending,
|
isStoppingServer: stopProxyServerMutation.isPending,
|
||||||
|
isStopping: stopWithRestoreMutation.isPending,
|
||||||
isPending:
|
isPending:
|
||||||
startProxyServerMutation.isPending ||
|
startProxyServerMutation.isPending ||
|
||||||
stopProxyServerMutation.isPending ||
|
stopProxyServerMutation.isPending ||
|
||||||
|
|||||||
+31
-5
@@ -1,5 +1,6 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import type {
|
import type {
|
||||||
|
ProxyConfig,
|
||||||
ProxyStatus,
|
ProxyStatus,
|
||||||
ProxyServerInfo,
|
ProxyServerInfo,
|
||||||
ProxyTakeoverStatus,
|
ProxyTakeoverStatus,
|
||||||
@@ -15,11 +16,6 @@ export const proxyApi = {
|
|||||||
return invoke("start_proxy_server");
|
return invoke("start_proxy_server");
|
||||||
},
|
},
|
||||||
|
|
||||||
// 停止代理服务器(不恢复已接管配置)
|
|
||||||
async stopProxyServer(): Promise<void> {
|
|
||||||
return invoke("stop_proxy_server");
|
|
||||||
},
|
|
||||||
|
|
||||||
// 停止代理服务器并恢复配置
|
// 停止代理服务器并恢复配置
|
||||||
async stopProxyWithRestore(): Promise<void> {
|
async stopProxyWithRestore(): Promise<void> {
|
||||||
return invoke("stop_proxy_with_restore");
|
return invoke("stop_proxy_with_restore");
|
||||||
@@ -30,6 +26,24 @@ export const proxyApi = {
|
|||||||
return invoke("get_proxy_status");
|
return invoke("get_proxy_status");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 检查代理服务器是否正在运行
|
||||||
|
async isProxyRunning(): Promise<boolean> {
|
||||||
|
return invoke("is_proxy_running");
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查是否处于接管模式
|
||||||
|
async isLiveTakeoverActive(): Promise<boolean> {
|
||||||
|
return invoke("is_live_takeover_active");
|
||||||
|
},
|
||||||
|
|
||||||
|
// 代理模式下切换供应商
|
||||||
|
async switchProxyProvider(
|
||||||
|
appType: string,
|
||||||
|
providerId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return invoke("switch_proxy_provider", { appType, providerId });
|
||||||
|
},
|
||||||
|
|
||||||
// ========== 接管状态 API ==========
|
// ========== 接管状态 API ==========
|
||||||
|
|
||||||
// 获取各应用接管状态
|
// 获取各应用接管状态
|
||||||
@@ -45,6 +59,18 @@ export const proxyApi = {
|
|||||||
return invoke("set_proxy_takeover_for_app", { appType, enabled });
|
return invoke("set_proxy_takeover_for_app", { appType, enabled });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ========== Legacy 代理配置 API (兼容) ==========
|
||||||
|
|
||||||
|
// 获取代理配置(旧版 v2 兼容接口)
|
||||||
|
async getProxyConfig(): Promise<ProxyConfig> {
|
||||||
|
return invoke("get_proxy_config");
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新代理配置(旧版 v2 兼容接口)
|
||||||
|
async updateProxyConfig(config: ProxyConfig): Promise<void> {
|
||||||
|
return invoke("update_proxy_config", { config });
|
||||||
|
},
|
||||||
|
|
||||||
// ========== v3+ 全局/应用级配置 API ==========
|
// ========== v3+ 全局/应用级配置 API ==========
|
||||||
|
|
||||||
// 获取全局代理配置
|
// 获取全局代理配置
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { failoverApi } from "@/lib/api/failover";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||||
import { proxyKeys } from "@/lib/query/proxy";
|
|
||||||
|
|
||||||
// ========== 熔断器 Hooks ==========
|
// ========== 熔断器 Hooks ==========
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ export function useResetCircuitBreaker() {
|
|||||||
});
|
});
|
||||||
// 刷新代理状态(更新 active_targets)
|
// 刷新代理状态(更新 active_targets)
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: proxyKeys.status,
|
queryKey: ["proxyStatus"],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -284,7 +283,7 @@ export function useSetAutoFailoverEnabled() {
|
|||||||
queryKey: ["providers", variables.appType],
|
queryKey: ["providers", variables.appType],
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: proxyKeys.status,
|
queryKey: ["proxyStatus"],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { extractErrorMessage } from "@/utils/errorUtils";
|
|||||||
import { generateUUID } from "@/utils/uuid";
|
import { generateUUID } from "@/utils/uuid";
|
||||||
import { openclawKeys } from "@/hooks/useOpenClaw";
|
import { openclawKeys } from "@/hooks/useOpenClaw";
|
||||||
import { invalidateHermesProviderCaches } from "@/hooks/useHermes";
|
import { invalidateHermesProviderCaches } from "@/hooks/useHermes";
|
||||||
import { proxyKeys } from "@/lib/query/proxy";
|
|
||||||
import { usageKeys } from "@/lib/query/usage";
|
import { usageKeys } from "@/lib/query/usage";
|
||||||
import {
|
import {
|
||||||
CODEX_OFFICIAL_PROVIDER_ID,
|
CODEX_OFFICIAL_PROVIDER_ID,
|
||||||
@@ -287,7 +286,7 @@ export const useSwitchProviderMutation = (appId: AppId) => {
|
|||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
|
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
|
||||||
if (appId === "claude-desktop") {
|
if (appId === "claude-desktop") {
|
||||||
await queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: ["claudeDesktopStatus"],
|
queryKey: ["claudeDesktopStatus"],
|
||||||
});
|
});
|
||||||
|
|||||||
+136
-34
@@ -2,54 +2,90 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|||||||
import { proxyApi } from "@/lib/api/proxy";
|
import { proxyApi } from "@/lib/api/proxy";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type {
|
import type { GlobalProxyConfig, AppProxyConfig } from "@/types/proxy";
|
||||||
GlobalProxyConfig,
|
|
||||||
AppProxyConfig,
|
|
||||||
ProxyTakeoverStatus,
|
|
||||||
} from "@/types/proxy";
|
|
||||||
|
|
||||||
export const proxyKeys = {
|
|
||||||
status: ["proxyStatus"] as const,
|
|
||||||
takeoverStatus: ["proxyTakeoverStatus"] as const,
|
|
||||||
globalConfig: ["globalProxyConfig"] as const,
|
|
||||||
appConfig: (appType: string) => ["appProxyConfig", appType] as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ========== 代理服务器状态 Hooks ==========
|
// ========== 代理服务器状态 Hooks ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取代理服务器状态
|
* 获取代理服务器状态
|
||||||
*/
|
*/
|
||||||
export function useProxyStatusQuery() {
|
export function useProxyStatus() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: proxyKeys.status,
|
queryKey: ["proxyStatus"],
|
||||||
queryFn: () => proxyApi.getProxyStatus(),
|
queryFn: () => proxyApi.getProxyStatus(),
|
||||||
// 仅在服务运行时轮询
|
refetchInterval: 5000, // 每 5 秒刷新一次
|
||||||
refetchInterval: (query) => (query.state.data?.running ? 2000 : false),
|
});
|
||||||
// 保持之前的数据,避免闪烁
|
}
|
||||||
placeholderData: (previousData) => previousData,
|
|
||||||
|
/**
|
||||||
|
* 检查代理服务器是否运行
|
||||||
|
*/
|
||||||
|
export function useIsProxyRunning() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["proxyRunning"],
|
||||||
|
queryFn: () => proxyApi.isProxyRunning(),
|
||||||
|
refetchInterval: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否处于接管模式
|
||||||
|
*/
|
||||||
|
export function useIsLiveTakeoverActive() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["liveTakeoverActive"],
|
||||||
|
queryFn: () => proxyApi.isLiveTakeoverActive(),
|
||||||
|
refetchInterval: 2000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取各应用接管状态
|
* 获取各应用接管状态
|
||||||
*/
|
*/
|
||||||
export function useProxyTakeoverStatus(poll = true) {
|
export function useProxyTakeoverStatus() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: proxyKeys.takeoverStatus,
|
queryKey: ["proxyTakeoverStatus"],
|
||||||
queryFn: () => proxyApi.getProxyTakeoverStatus(),
|
queryFn: () => proxyApi.getProxyTakeoverStatus(),
|
||||||
refetchInterval: poll ? 2000 : false,
|
refetchInterval: 2000,
|
||||||
...(poll
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
placeholderData: (previousData: ProxyTakeoverStatus | undefined) =>
|
|
||||||
previousData,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 代理服务器控制 Hooks ==========
|
// ========== 代理服务器控制 Hooks ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动代理服务器
|
||||||
|
*/
|
||||||
|
export function useStartProxyServer() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: () => proxyApi.startProxyServer(),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyRunning"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止代理服务器
|
||||||
|
*/
|
||||||
|
export function useStopProxyServer() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: () => proxyApi.stopProxyWithRestore(),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyRunning"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置应用接管状态
|
* 设置应用接管状态
|
||||||
*/
|
*/
|
||||||
@@ -60,11 +96,75 @@ export function useSetProxyTakeoverForApp() {
|
|||||||
mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) =>
|
mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) =>
|
||||||
proxyApi.setProxyTakeoverForApp(appType, enabled),
|
proxyApi.setProxyTakeoverForApp(appType, enabled),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus });
|
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理模式下切换供应商
|
||||||
|
*/
|
||||||
|
export function useSwitchProxyProvider() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
appType,
|
||||||
|
providerId,
|
||||||
|
}: {
|
||||||
|
appType: string;
|
||||||
|
providerId: string;
|
||||||
|
}) => proxyApi.switchProxyProvider(appType, providerId),
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["providers", variables.appType],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(t("proxy.switchFailed", { error: error.message }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Legacy 代理配置 Hooks (兼容) ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代理配置(旧版)
|
||||||
|
*/
|
||||||
|
export function useProxyConfig() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { data: config, isLoading } = useQuery({
|
||||||
|
queryKey: ["proxyConfig"],
|
||||||
|
queryFn: () => proxyApi.getProxyConfig(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateMutation = useMutation({
|
||||||
|
mutationFn: proxyApi.updateProxyConfig,
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyConfig"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(
|
||||||
|
t("proxy.settings.toast.saveFailed", { error: error.message }),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
isLoading,
|
||||||
|
updateConfig: updateMutation.mutateAsync,
|
||||||
|
isUpdating: updateMutation.isPending,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ========== v3+ 全局/应用级配置 Hooks ==========
|
// ========== v3+ 全局/应用级配置 Hooks ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,7 +172,7 @@ export function useSetProxyTakeoverForApp() {
|
|||||||
*/
|
*/
|
||||||
export function useGlobalProxyConfig() {
|
export function useGlobalProxyConfig() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: proxyKeys.globalConfig,
|
queryKey: ["globalProxyConfig"],
|
||||||
queryFn: () => proxyApi.getGlobalProxyConfig(),
|
queryFn: () => proxyApi.getGlobalProxyConfig(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -89,8 +189,9 @@ export function useUpdateGlobalProxyConfig() {
|
|||||||
proxyApi.updateGlobalProxyConfig(config),
|
proxyApi.updateGlobalProxyConfig(config),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
|
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.globalConfig });
|
queryClient.invalidateQueries({ queryKey: ["globalProxyConfig"] });
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
queryClient.invalidateQueries({ queryKey: ["proxyConfig"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
toast.error(
|
toast.error(
|
||||||
@@ -105,7 +206,7 @@ export function useUpdateGlobalProxyConfig() {
|
|||||||
*/
|
*/
|
||||||
export function useAppProxyConfig(appType: string) {
|
export function useAppProxyConfig(appType: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: proxyKeys.appConfig(appType),
|
queryKey: ["appProxyConfig", appType],
|
||||||
queryFn: () => proxyApi.getProxyConfigForApp(appType),
|
queryFn: () => proxyApi.getProxyConfigForApp(appType),
|
||||||
enabled: !!appType,
|
enabled: !!appType,
|
||||||
});
|
});
|
||||||
@@ -124,13 +225,14 @@ export function useUpdateAppProxyConfig() {
|
|||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
|
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: proxyKeys.appConfig(variables.appType),
|
queryKey: ["appProxyConfig", variables.appType],
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["autoFailoverEnabled", variables.appType],
|
queryKey: ["autoFailoverEnabled", variables.appType],
|
||||||
});
|
});
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["proxyConfig"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["circuitBreakerConfig"] });
|
queryClient.invalidateQueries({ queryKey: ["circuitBreakerConfig"] });
|
||||||
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
|
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -22,10 +22,17 @@ vi.mock("react-i18next", () => ({
|
|||||||
|
|
||||||
vi.mock("@/hooks/useProxyStatus", () => ({
|
vi.mock("@/hooks/useProxyStatus", () => ({
|
||||||
useProxyStatus: () => ({
|
useProxyStatus: () => ({
|
||||||
|
status: null,
|
||||||
|
isLoading: false,
|
||||||
isRunning: false,
|
isRunning: false,
|
||||||
takeoverStatus: null,
|
isTakeoverActive: false,
|
||||||
startProxyServer: vi.fn(),
|
startWithTakeover: vi.fn(),
|
||||||
stopWithRestore: vi.fn(),
|
stopWithRestore: vi.fn(),
|
||||||
|
switchProxyProvider: vi.fn(),
|
||||||
|
checkRunning: vi.fn(),
|
||||||
|
checkTakeoverActive: vi.fn(),
|
||||||
|
isStarting: false,
|
||||||
|
isStopping: false,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { renderHook, act, waitFor } from "@testing-library/react";
|
|||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
import { useProxyStatus } from "@/hooks/useProxyStatus";
|
||||||
import { proxyKeys } from "@/lib/query/proxy";
|
|
||||||
import { createTestQueryClient } from "../utils/testQueryClient";
|
import { createTestQueryClient } from "../utils/testQueryClient";
|
||||||
|
|
||||||
const toastSuccessMock = vi.fn();
|
const toastSuccessMock = vi.fn();
|
||||||
@@ -100,19 +99,12 @@ describe("useProxyStatus", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the established proxy query key shapes", () => {
|
|
||||||
expect(proxyKeys.status).toEqual(["proxyStatus"]);
|
|
||||||
expect(proxyKeys.takeoverStatus).toEqual(["proxyTakeoverStatus"]);
|
|
||||||
expect(proxyKeys.globalConfig).toEqual(["globalProxyConfig"]);
|
|
||||||
expect(proxyKeys.appConfig("claude")).toEqual(["appProxyConfig", "claude"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows interpolated address and port after proxy server starts", async () => {
|
it("shows interpolated address and port after proxy server starts", async () => {
|
||||||
const { wrapper } = createWrapper();
|
const { wrapper } = createWrapper();
|
||||||
const { result } = renderHook(() => useProxyStatus(), { wrapper });
|
const { result } = renderHook(() => useProxyStatus(), { wrapper });
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(result.current.status).toBeDefined();
|
expect(result.current.isLoading).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
@@ -123,12 +115,5 @@ describe("useProxyStatus", () => {
|
|||||||
"代理服务已启动 - 127.0.0.1:15721",
|
"代理服务已启动 - 127.0.0.1:15721",
|
||||||
{ closeButton: true },
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await result.current.stopProxyServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(invokeMock).toHaveBeenCalledWith("start_proxy_server");
|
|
||||||
expect(invokeMock).toHaveBeenCalledWith("stop_proxy_server");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user