Compare commits

..

1 Commits

Author SHA1 Message Date
SaladDay 483256ad92 refactor: remove redundant proxy query paths 2026-07-30 15:08:42 +00:00
11 changed files with 3151 additions and 2512 deletions
+54 -54
View File
@@ -19,74 +19,74 @@
"author": "Jason Young",
"license": "MIT",
"devDependencies": {
"@tauri-apps/cli": "^2.11.4",
"@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@tauri-apps/cli": "^2.8.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^26.1.2",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.5",
"autoprefixer": "^10.5.4",
"code-inspector-plugin": "^2.0.6",
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.20",
"code-inspector-plugin": "^1.3.3",
"cross-fetch": "^4.1.0",
"jsdom": "^30.0.1",
"msw": "^2.15.0",
"postcss": "^8.5.25",
"prettier": "^3.9.6",
"tailwindcss": "^4.3.3",
"typescript": "^7.0.2",
"vite": "^8.2.0",
"vitest": "^4.1.10"
"jsdom": "^25.0.0",
"msw": "^2.11.6",
"postcss": "^8.4.49",
"prettier": "^3.6.2",
"tailwindcss": "^3.4.17",
"typescript": "^5.3.0",
"vite": "^7.3.0",
"vitest": "^2.0.5"
},
"dependencies": {
"@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.5.1",
"@codemirror/lint": "^6.9.7",
"@codemirror/state": "^6.7.1",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/lint": "^6.8.5",
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.43.7",
"@codemirror/view": "^6.38.2",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^5.5.7",
"@radix-ui/react-accordion": "^1.2.20",
"@radix-ui/react-checkbox": "^1.3.11",
"@radix-ui/react-collapsible": "^1.1.20",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-popover": "^1.1.23",
"@radix-ui/react-scroll-area": "^1.2.18",
"@radix-ui/react-select": "^2.3.7",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-switch": "^1.3.7",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-tooltip": "^1.2.16",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-virtual": "^3.14.9",
"@tauri-apps/api": "^2.11.1",
"@tauri-apps/plugin-dialog": "^2.7.2",
"@tauri-apps/plugin-log": "2.9.0",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-query": "^5.90.3",
"@tanstack/react-virtual": "^3.13.23",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-log": "2.8.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-updater": "^2.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"codemirror": "^6.0.2",
"flexsearch": "^0.8.212",
"framer-motion": "^12.43.0",
"i18next": "^26.3.6",
"lucide-react": "^1.28.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-hook-form": "^7.83.0",
"react-i18next": "^17.0.11",
"recharts": "^3.10.1",
"smol-toml": "^1.7.1",
"framer-motion": "^12.23.25",
"i18next": "^25.5.2",
"lucide-react": "^0.542.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.65.0",
"react-i18next": "^16.0.0",
"recharts": "^3.5.1",
"smol-toml": "^1.4.2",
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"zod": "^4.4.3"
"tailwind-merge": "^3.3.1",
"zod": "^4.1.12"
}
}
+3008 -2185
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -29,7 +29,7 @@ import {
import { getCurrentWindow } from "@tauri-apps/api/window";
import type { Provider, VisibleApps } from "@/types";
import type { EnvConflict } from "@/types/env";
import { useProvidersQuery, useSettingsQuery } from "@/lib/query";
import { proxyKeys, useProvidersQuery, useSettingsQuery } from "@/lib/query";
import {
providersApi,
settingsApi,
@@ -389,8 +389,10 @@ function App() {
await queryClient.invalidateQueries({ queryKey: ["profiles"] });
await queryClient.invalidateQueries({ queryKey: ["mcp", "all"] });
await queryClient.invalidateQueries({ queryKey: ["skills"] });
await queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
await queryClient.invalidateQueries({
queryKey: proxyKeys.takeoverStatus,
});
await queryClient.invalidateQueries({ queryKey: proxyKeys.status });
await queryClient.invalidateQueries({
queryKey: ["providers", "claude-desktop"],
});
+3 -2
View File
@@ -15,12 +15,12 @@ import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { ToggleRow } from "@/components/ui/toggle-row";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import { toast } from "sonner";
import { useFailoverQueue } from "@/lib/query/failover";
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
import { useProviderHealth } from "@/lib/query/failover";
import {
useProxyStatusQuery,
useProxyTakeoverStatus,
useSetProxyTakeoverForApp,
useGlobalProxyConfig,
@@ -45,7 +45,8 @@ export function ProxyPanel({
isProxyPending,
}: ProxyPanelProps) {
const { t } = useTranslation();
const { status, isRunning } = useProxyStatus();
const { data: status } = useProxyStatusQuery();
const isRunning = status?.running ?? false;
// 获取应用接管状态
const { data: takeoverStatus } = useProxyTakeoverStatus();
+19 -88
View File
@@ -2,15 +2,15 @@
* 代理服务状态管理 Hook
*/
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import type {
ProxyStatus,
ProxyServerInfo,
ProxyTakeoverStatus,
} from "@/types/proxy";
import { proxyApi } from "@/lib/api/proxy";
import {
proxyKeys,
useProxyStatusQuery,
useProxyTakeoverStatus,
} from "@/lib/query/proxy";
import { extractErrorMessage } from "@/utils/errorUtils";
/**
@@ -21,25 +21,14 @@ export function useProxyStatus() {
const { t } = useTranslation();
// 查询状态(自动轮询)
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: status } = useProxyStatusQuery();
// 查询各应用接管状态
const { data: takeoverStatus } = useQuery({
queryKey: ["proxyTakeoverStatus"],
queryFn: () => invoke<ProxyTakeoverStatus>("get_proxy_takeover_status"),
placeholderData: (previousData) => previousData,
});
const { data: takeoverStatus } = useProxyTakeoverStatus(false);
// 启动服务器(总开关:仅启动服务,不接管)
const startProxyServerMutation = useMutation({
mutationFn: () => invoke<ProxyServerInfo>("start_proxy_server"),
mutationFn: () => proxyApi.startProxyServer(),
onSuccess: (info) => {
toast.success(
t("proxy.server.started", {
@@ -49,7 +38,7 @@ export function useProxyStatus() {
}),
{ closeButton: true },
);
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
},
onError: (error: Error) => {
const detail =
@@ -66,7 +55,7 @@ export function useProxyStatus() {
// 停止服务器(仅停止服务,不改写/恢复其它应用接管状态)
const stopProxyServerMutation = useMutation({
mutationFn: () => invoke("stop_proxy_server"),
mutationFn: () => proxyApi.stopProxyServer(),
onSuccess: () => {
toast.success(
t("proxy.server.stopped", {
@@ -74,7 +63,7 @@ export function useProxyStatus() {
}),
{ closeButton: true },
);
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
},
onError: (error: Error) => {
const detail =
@@ -91,7 +80,7 @@ export function useProxyStatus() {
// 停止服务器(总开关关闭:强制恢复所有已接管的 Live 配置)
const stopWithRestoreMutation = useMutation({
mutationFn: () => invoke("stop_proxy_with_restore"),
mutationFn: () => proxyApi.stopProxyWithRestore(),
onSuccess: () => {
toast.success(
t("proxy.stoppedWithRestore", {
@@ -99,8 +88,8 @@ export function useProxyStatus() {
}),
{ closeButton: true },
);
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus });
// 彻底删除所有供应商健康状态缓存(后端已清空数据库记录)
queryClient.removeQueries({ queryKey: ["providerHealth"] });
// 彻底删除所有熔断器统计缓存(代理停止后熔断器状态已重置)
@@ -123,7 +112,7 @@ export function useProxyStatus() {
// 按应用开启/关闭接管
const setTakeoverForAppMutation = useMutation({
mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) =>
invoke("set_proxy_takeover_for_app", { appType, enabled }),
proxyApi.setProxyTakeoverForApp(appType, enabled),
onSuccess: (_data, variables) => {
const appLabel =
variables.appType === "claude"
@@ -149,8 +138,8 @@ export function useProxyStatus() {
{ closeButton: true },
);
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus });
},
onError: (error: Error) => {
const detail =
@@ -165,60 +154,10 @@ 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 {
status,
isLoading,
isRunning: status?.running || false,
takeoverStatus,
isTakeoverActive:
takeoverStatus?.claude ||
takeoverStatus?.codex ||
takeoverStatus?.gemini ||
takeoverStatus?.grokbuild ||
false,
// 启动/停止(总开关)
startProxyServer: startProxyServerMutation.mutateAsync,
@@ -228,17 +167,9 @@ export function useProxyStatus() {
// 按应用接管开关
setTakeoverForApp: setTakeoverForAppMutation.mutateAsync,
// 代理模式下切换供应商
switchProxyProvider: switchProxyProviderMutation.mutateAsync,
// 状态检查
checkRunning,
checkTakeoverActive,
// 加载状态
isStarting: startProxyServerMutation.isPending,
isStoppingServer: stopProxyServerMutation.isPending,
isStopping: stopWithRestoreMutation.isPending,
isPending:
startProxyServerMutation.isPending ||
stopProxyServerMutation.isPending ||
+5 -31
View File
@@ -1,6 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import type {
ProxyConfig,
ProxyStatus,
ProxyServerInfo,
ProxyTakeoverStatus,
@@ -16,6 +15,11 @@ export const proxyApi = {
return invoke("start_proxy_server");
},
// 停止代理服务器(不恢复已接管配置)
async stopProxyServer(): Promise<void> {
return invoke("stop_proxy_server");
},
// 停止代理服务器并恢复配置
async stopProxyWithRestore(): Promise<void> {
return invoke("stop_proxy_with_restore");
@@ -26,24 +30,6 @@ export const proxyApi = {
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 ==========
// 获取各应用接管状态
@@ -59,18 +45,6 @@ export const proxyApi = {
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 ==========
// 获取全局代理配置
+3 -2
View File
@@ -3,6 +3,7 @@ import { failoverApi } from "@/lib/api/failover";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import { extractErrorMessage } from "@/utils/errorUtils";
import { proxyKeys } from "@/lib/query/proxy";
// ========== 熔断器 Hooks ==========
@@ -47,7 +48,7 @@ export function useResetCircuitBreaker() {
});
// 刷新代理状态(更新 active_targets
queryClient.invalidateQueries({
queryKey: ["proxyStatus"],
queryKey: proxyKeys.status,
});
},
});
@@ -283,7 +284,7 @@ export function useSetAutoFailoverEnabled() {
queryKey: ["providers", variables.appType],
});
queryClient.invalidateQueries({
queryKey: ["proxyStatus"],
queryKey: proxyKeys.status,
});
},
});
+2 -1
View File
@@ -9,6 +9,7 @@ import { extractErrorMessage } from "@/utils/errorUtils";
import { generateUUID } from "@/utils/uuid";
import { openclawKeys } from "@/hooks/useOpenClaw";
import { invalidateHermesProviderCaches } from "@/hooks/useHermes";
import { proxyKeys } from "@/lib/query/proxy";
import { usageKeys } from "@/lib/query/usage";
import {
CODEX_OFFICIAL_PROVIDER_ID,
@@ -286,7 +287,7 @@ export const useSwitchProviderMutation = (appId: AppId) => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["providers", appId] });
if (appId === "claude-desktop") {
await queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
await queryClient.invalidateQueries({ queryKey: proxyKeys.status });
await queryClient.invalidateQueries({
queryKey: ["claudeDesktopStatus"],
});
+34 -136
View File
@@ -2,90 +2,54 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { proxyApi } from "@/lib/api/proxy";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import type { GlobalProxyConfig, AppProxyConfig } from "@/types/proxy";
import type {
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 ==========
/**
* 获取代理服务器状态
*/
export function useProxyStatus() {
export function useProxyStatusQuery() {
return useQuery({
queryKey: ["proxyStatus"],
queryKey: proxyKeys.status,
queryFn: () => proxyApi.getProxyStatus(),
refetchInterval: 5000, // 每 5 秒刷新一次
});
}
/**
* 检查代理服务器是否运行
*/
export function useIsProxyRunning() {
return useQuery({
queryKey: ["proxyRunning"],
queryFn: () => proxyApi.isProxyRunning(),
refetchInterval: 2000,
});
}
/**
* 检查是否处于接管模式
*/
export function useIsLiveTakeoverActive() {
return useQuery({
queryKey: ["liveTakeoverActive"],
queryFn: () => proxyApi.isLiveTakeoverActive(),
refetchInterval: 2000,
// 仅在服务运行时轮询
refetchInterval: (query) => (query.state.data?.running ? 2000 : false),
// 保持之前的数据,避免闪烁
placeholderData: (previousData) => previousData,
});
}
/**
* 获取各应用接管状态
*/
export function useProxyTakeoverStatus() {
export function useProxyTakeoverStatus(poll = true) {
return useQuery({
queryKey: ["proxyTakeoverStatus"],
queryKey: proxyKeys.takeoverStatus,
queryFn: () => proxyApi.getProxyTakeoverStatus(),
refetchInterval: 2000,
refetchInterval: poll ? 2000 : false,
...(poll
? {}
: {
placeholderData: (previousData: ProxyTakeoverStatus | undefined) =>
previousData,
}),
});
}
// ========== 代理服务器控制 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"] });
},
});
}
/**
* 设置应用接管状态
*/
@@ -96,75 +60,11 @@ export function useSetProxyTakeoverForApp() {
mutationFn: ({ appType, enabled }: { appType: string; enabled: boolean }) =>
proxyApi.setProxyTakeoverForApp(appType, enabled),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
queryClient.invalidateQueries({ queryKey: ["liveTakeoverActive"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.takeoverStatus });
},
});
}
/**
* 代理模式下切换供应商
*/
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 ==========
/**
@@ -172,7 +72,7 @@ export function useProxyConfig() {
*/
export function useGlobalProxyConfig() {
return useQuery({
queryKey: ["globalProxyConfig"],
queryKey: proxyKeys.globalConfig,
queryFn: () => proxyApi.getGlobalProxyConfig(),
});
}
@@ -189,9 +89,8 @@ export function useUpdateGlobalProxyConfig() {
proxyApi.updateGlobalProxyConfig(config),
onSuccess: () => {
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
queryClient.invalidateQueries({ queryKey: ["globalProxyConfig"] });
queryClient.invalidateQueries({ queryKey: ["proxyConfig"] });
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.globalConfig });
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
},
onError: (error: Error) => {
toast.error(
@@ -206,7 +105,7 @@ export function useUpdateGlobalProxyConfig() {
*/
export function useAppProxyConfig(appType: string) {
return useQuery({
queryKey: ["appProxyConfig", appType],
queryKey: proxyKeys.appConfig(appType),
queryFn: () => proxyApi.getProxyConfigForApp(appType),
enabled: !!appType,
});
@@ -225,14 +124,13 @@ export function useUpdateAppProxyConfig() {
onSuccess: (_, variables) => {
toast.success(t("proxy.settings.toast.saved"), { closeButton: true });
queryClient.invalidateQueries({
queryKey: ["appProxyConfig", variables.appType],
queryKey: proxyKeys.appConfig(variables.appType),
});
queryClient.invalidateQueries({
queryKey: ["autoFailoverEnabled", variables.appType],
});
queryClient.invalidateQueries({ queryKey: ["proxyConfig"] });
queryClient.invalidateQueries({ queryKey: ["circuitBreakerConfig"] });
queryClient.invalidateQueries({ queryKey: ["proxyStatus"] });
queryClient.invalidateQueries({ queryKey: proxyKeys.status });
},
onError: (error: Error) => {
toast.error(
+2 -9
View File
@@ -22,17 +22,10 @@ vi.mock("react-i18next", () => ({
vi.mock("@/hooks/useProxyStatus", () => ({
useProxyStatus: () => ({
status: null,
isLoading: false,
isRunning: false,
isTakeoverActive: false,
startWithTakeover: vi.fn(),
takeoverStatus: null,
startProxyServer: vi.fn(),
stopWithRestore: vi.fn(),
switchProxyProvider: vi.fn(),
checkRunning: vi.fn(),
checkTakeoverActive: vi.fn(),
isStarting: false,
isStopping: false,
isPending: false,
}),
}));
+16 -1
View File
@@ -3,6 +3,7 @@ import { renderHook, act, waitFor } from "@testing-library/react";
import { QueryClientProvider } from "@tanstack/react-query";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import { proxyKeys } from "@/lib/query/proxy";
import { createTestQueryClient } from "../utils/testQueryClient";
const toastSuccessMock = vi.fn();
@@ -99,12 +100,19 @@ 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 () => {
const { wrapper } = createWrapper();
const { result } = renderHook(() => useProxyStatus(), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
expect(result.current.status).toBeDefined();
});
await act(async () => {
@@ -115,5 +123,12 @@ describe("useProxyStatus", () => {
"代理服务已启动 - 127.0.0.1:15721",
{ closeButton: true },
);
await act(async () => {
await result.current.stopProxyServer();
});
expect(invokeMock).toHaveBeenCalledWith("start_proxy_server");
expect(invokeMock).toHaveBeenCalledWith("stop_proxy_server");
});
});