mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
db66348ff8
* feat(providers): add baidu qianfan coding plan presets * refactor(providers): align qianfan presets with existing format * chore(providers): narrow qianfan coding plan scope
141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import { useState, useCallback } from "react";
|
|
import { toast } from "sonner";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
streamCheckProvider,
|
|
type StreamCheckResult,
|
|
} from "@/lib/api/model-test";
|
|
import type { AppId } from "@/lib/api";
|
|
import { useResetCircuitBreaker } from "@/lib/query/failover";
|
|
|
|
export function useStreamCheck(appId: AppId) {
|
|
const { t } = useTranslation();
|
|
const [checkingIds, setCheckingIds] = useState<Set<string>>(new Set());
|
|
const resetCircuitBreaker = useResetCircuitBreaker();
|
|
|
|
const checkProvider = useCallback(
|
|
async (
|
|
providerId: string,
|
|
providerName: string,
|
|
): Promise<StreamCheckResult | null> => {
|
|
setCheckingIds((prev) => new Set(prev).add(providerId));
|
|
|
|
try {
|
|
const result = await streamCheckProvider(appId, providerId);
|
|
|
|
if (result.status === "operational") {
|
|
toast.success(
|
|
t("streamCheck.operational", {
|
|
providerName: providerName,
|
|
responseTimeMs: result.responseTimeMs,
|
|
defaultValue: `${providerName} 运行正常 (${result.responseTimeMs}ms)`,
|
|
}),
|
|
{ closeButton: true },
|
|
);
|
|
|
|
// 测试通过后重置熔断器状态
|
|
resetCircuitBreaker.mutate({ providerId, appType: appId });
|
|
} else if (result.status === "degraded") {
|
|
toast.warning(
|
|
t("streamCheck.degraded", {
|
|
providerName: providerName,
|
|
responseTimeMs: result.responseTimeMs,
|
|
defaultValue: `${providerName} 响应较慢 (${result.responseTimeMs}ms)`,
|
|
}),
|
|
);
|
|
|
|
// 降级状态也重置熔断器,因为至少能通信
|
|
resetCircuitBreaker.mutate({ providerId, appType: appId });
|
|
} else if (result.errorCategory === "modelNotFound") {
|
|
// 专门处理"模型不存在/已下架":指向配置入口,比通用 404 文案更有指导性
|
|
toast.error(
|
|
t("streamCheck.modelNotFound", {
|
|
providerName: providerName,
|
|
model: result.modelUsed,
|
|
defaultValue: `${providerName} 测试模型 ${result.modelUsed} 不存在或已下架`,
|
|
}),
|
|
{
|
|
description: t("streamCheck.modelNotFoundHint", {
|
|
defaultValue: "",
|
|
}),
|
|
duration: 10000,
|
|
closeButton: true,
|
|
},
|
|
);
|
|
} else if (result.errorCategory === "quotaExceeded") {
|
|
toast.warning(
|
|
t("streamCheck.quotaExceeded", {
|
|
providerName: providerName,
|
|
defaultValue: `${providerName} Coding Plan quota has been exceeded`,
|
|
}),
|
|
{
|
|
description: t("streamCheck.quotaExceededHint", {
|
|
defaultValue: "",
|
|
}),
|
|
duration: 10000,
|
|
closeButton: true,
|
|
},
|
|
);
|
|
} else {
|
|
const httpStatus = result.httpStatus;
|
|
const hintKey = httpStatus
|
|
? `streamCheck.httpHint.${httpStatus >= 500 ? "5xx" : httpStatus}`
|
|
: null;
|
|
const description =
|
|
(hintKey ? t(hintKey, { defaultValue: "" }) : "") || undefined;
|
|
|
|
// 401/403/400 = 检查被拒(供应商可能正常);429/5xx = 临时问题
|
|
const isProbeRejection =
|
|
httpStatus != null &&
|
|
([401, 403, 400, 429].includes(httpStatus) || httpStatus >= 500);
|
|
|
|
if (isProbeRejection) {
|
|
toast.warning(
|
|
t("streamCheck.rejected", {
|
|
providerName: providerName,
|
|
message: result.message,
|
|
defaultValue: `${providerName} 检查被拒: ${result.message}`,
|
|
}),
|
|
{ description, duration: 8000, closeButton: true },
|
|
);
|
|
} else {
|
|
toast.error(
|
|
t("streamCheck.failed", {
|
|
providerName: providerName,
|
|
message: result.message,
|
|
defaultValue: `${providerName} 检查失败: ${result.message}`,
|
|
}),
|
|
{ description, duration: 8000, closeButton: true },
|
|
);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
toast.error(
|
|
t("streamCheck.error", {
|
|
providerName: providerName,
|
|
error: String(e),
|
|
defaultValue: `${providerName} 检查出错: ${String(e)}`,
|
|
}),
|
|
);
|
|
return null;
|
|
} finally {
|
|
setCheckingIds((prev) => {
|
|
const next = new Set(prev);
|
|
next.delete(providerId);
|
|
return next;
|
|
});
|
|
}
|
|
},
|
|
[appId, t, resetCircuitBreaker],
|
|
);
|
|
|
|
const isChecking = useCallback(
|
|
(providerId: string) => checkingIds.has(providerId),
|
|
[checkingIds],
|
|
);
|
|
|
|
return { checkProvider, isChecking };
|
|
}
|