refactor: improve endpoint management type safety and error handling
- Unify EndpointCandidate type definition in types.ts - Remove all 'as any' type assertions in useSpeedTestEndpoints - Add cleanup function to prevent race conditions in async operations - Fix stale error messages persisting after successful deletion - Improve error handling for endpoint deletion (distinguish not-found vs network errors) - Extract timeout magic numbers to ENDPOINT_TIMEOUT_SECS constant - Clarify URL validation to explicitly allow only http/https - Fix ambiguous payload.meta assignment logic in ProviderForm - Add i18n for new error messages (removeFailed, updateLastUsedFailed)
This commit is contained in:
@@ -49,6 +49,8 @@ export function EditProviderDialog({
|
|||||||
websiteUrl: values.websiteUrl?.trim() || undefined,
|
websiteUrl: values.websiteUrl?.trim() || undefined,
|
||||||
settingsConfig: parsedConfig,
|
settingsConfig: parsedConfig,
|
||||||
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
...(values.presetCategory ? { category: values.presetCategory } : {}),
|
||||||
|
// 保留或更新 meta 字段
|
||||||
|
...(values.meta ? { meta: values.meta } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
await onSubmit(updatedProvider);
|
await onSubmit(updatedProvider);
|
||||||
@@ -83,6 +85,8 @@ export function EditProviderDialog({
|
|||||||
name: provider.name,
|
name: provider.name,
|
||||||
websiteUrl: provider.websiteUrl,
|
websiteUrl: provider.websiteUrl,
|
||||||
settingsConfig: provider.settingsConfig,
|
settingsConfig: provider.settingsConfig,
|
||||||
|
category: provider.category,
|
||||||
|
meta: provider.meta,
|
||||||
}}
|
}}
|
||||||
showButtons={false}
|
showButtons={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import type { CustomEndpoint, EndpointCandidate } from "@/types";
|
||||||
|
|
||||||
// 临时类型定义,待后端 API 实现后替换
|
// 端点测速超时配置(秒)
|
||||||
interface CustomEndpoint {
|
const ENDPOINT_TIMEOUT_SECS = {
|
||||||
url: string;
|
codex: 12,
|
||||||
addedAt: number;
|
claude: 8,
|
||||||
lastUsed?: number;
|
} as const;
|
||||||
}
|
|
||||||
|
|
||||||
interface TestResult {
|
interface TestResult {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -27,12 +27,6 @@ interface TestResult {
|
|||||||
error?: string | null;
|
error?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EndpointCandidate {
|
|
||||||
id?: string;
|
|
||||||
url: string;
|
|
||||||
isCustom?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EndpointSpeedTestProps {
|
interface EndpointSpeedTestProps {
|
||||||
appType: AppType;
|
appType: AppType;
|
||||||
providerId?: string;
|
providerId?: string;
|
||||||
@@ -113,6 +107,8 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
|
|
||||||
// 加载保存的自定义端点(按正在编辑的供应商)
|
// 加载保存的自定义端点(按正在编辑的供应商)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
const loadCustomEndpoints = async () => {
|
const loadCustomEndpoints = async () => {
|
||||||
try {
|
try {
|
||||||
if (!providerId) return;
|
if (!providerId) return;
|
||||||
@@ -122,6 +118,9 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
providerId,
|
providerId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 检查是否已取消
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
const candidates: EndpointCandidate[] = customEndpoints.map(
|
const candidates: EndpointCandidate[] = customEndpoints.map(
|
||||||
(ep: CustomEndpoint) => ({
|
(ep: CustomEndpoint) => ({
|
||||||
url: ep.url,
|
url: ep.url,
|
||||||
@@ -155,13 +154,19 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
return Array.from(map.values());
|
return Array.from(map.values());
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(t("endpointTest.loadEndpointsFailed"), error);
|
if (!cancelled) {
|
||||||
|
console.error(t("endpointTest.loadEndpointsFailed"), error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
loadCustomEndpoints();
|
loadCustomEndpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
}, [appType, visible, providerId, t]);
|
}, [appType, visible, providerId, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -253,7 +258,9 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errorMsg && parsed && !parsed.protocol.startsWith("http")) {
|
// 明确只允许 http: 和 https:
|
||||||
|
const allowedProtocols = ['http:', 'https:'];
|
||||||
|
if (!errorMsg && parsed && !allowedProtocols.includes(parsed.protocol)) {
|
||||||
errorMsg = t("endpointTest.onlyHttps");
|
errorMsg = t("endpointTest.onlyHttps");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,17 +325,29 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
|
|
||||||
const handleRemoveEndpoint = useCallback(
|
const handleRemoveEndpoint = useCallback(
|
||||||
async (entry: EndpointEntry) => {
|
async (entry: EndpointEntry) => {
|
||||||
// 如果是自定义端点,尝试从后端删除(无 providerId 则仅本地删除)
|
// 清空之前的错误提示
|
||||||
|
setLastError(null);
|
||||||
|
|
||||||
|
// 如果有 providerId,尝试从后端删除
|
||||||
if (entry.isCustom && providerId) {
|
if (entry.isCustom && providerId) {
|
||||||
try {
|
try {
|
||||||
await vscodeApi.removeCustomEndpoint(appType, providerId, entry.url);
|
await vscodeApi.removeCustomEndpoint(appType, providerId, entry.url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(t("endpointTest.removeEndpointFailed"), error);
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
return;
|
|
||||||
|
// 只有"端点不存在"时才允许删除本地条目
|
||||||
|
if (errorMsg.includes('not found') || errorMsg.includes('does not exist') || errorMsg.includes('不存在')) {
|
||||||
|
console.warn(t('endpointTest.removeEndpointFailed'), errorMsg);
|
||||||
|
// 继续删除本地条目
|
||||||
|
} else {
|
||||||
|
// 其他错误:显示错误提示,阻止删除
|
||||||
|
setLastError(t('endpointTest.removeFailed', { error: errorMsg }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新本地状态
|
// 更新本地状态(删除成功)
|
||||||
setEntries((prev) => {
|
setEntries((prev) => {
|
||||||
const next = prev.filter((item) => item.id !== entry.id);
|
const next = prev.filter((item) => item.id !== entry.id);
|
||||||
if (entry.url === normalizedSelected) {
|
if (entry.url === normalizedSelected) {
|
||||||
@@ -363,7 +382,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await vscodeApi.testApiEndpoints(urls, {
|
const results = await vscodeApi.testApiEndpoints(urls, {
|
||||||
timeoutSecs: appType === "codex" ? 12 : 8,
|
timeoutSecs: ENDPOINT_TIMEOUT_SECS[appType],
|
||||||
});
|
});
|
||||||
|
|
||||||
const resultMap = new Map(
|
const resultMap = new Map(
|
||||||
@@ -425,13 +444,13 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
|||||||
try {
|
try {
|
||||||
await vscodeApi.updateEndpointLastUsed(appType, providerId, url);
|
await vscodeApi.updateEndpointLastUsed(appType, providerId, url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update endpoint last used time:", error);
|
console.error(t("endpointTest.updateLastUsedFailed"), error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(url);
|
onChange(url);
|
||||||
},
|
},
|
||||||
[normalizedSelected, onChange, appType, entries, providerId],
|
[normalizedSelected, onChange, appType, entries, providerId, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Form } from "@/components/ui/form";
|
import { Form } from "@/components/ui/form";
|
||||||
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
import { providerSchema, type ProviderFormData } from "@/lib/schemas/provider";
|
||||||
import type { AppType } from "@/lib/api";
|
import type { AppType } from "@/lib/api";
|
||||||
import type { ProviderCategory, CustomEndpoint } from "@/types";
|
import type { ProviderCategory, CustomEndpoint, ProviderMeta } from "@/types";
|
||||||
import { providerPresets, type ProviderPreset } from "@/config/providerPresets";
|
import { providerPresets, type ProviderPreset } from "@/config/providerPresets";
|
||||||
import {
|
import {
|
||||||
codexProviderPresets,
|
codexProviderPresets,
|
||||||
@@ -52,6 +52,8 @@ interface ProviderFormProps {
|
|||||||
name?: string;
|
name?: string;
|
||||||
websiteUrl?: string;
|
websiteUrl?: string;
|
||||||
settingsConfig?: Record<string, unknown>;
|
settingsConfig?: Record<string, unknown>;
|
||||||
|
category?: ProviderCategory;
|
||||||
|
meta?: ProviderMeta;
|
||||||
};
|
};
|
||||||
showButtons?: boolean;
|
showButtons?: boolean;
|
||||||
}
|
}
|
||||||
@@ -86,6 +88,7 @@ export function ProviderForm({
|
|||||||
appType,
|
appType,
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
|
initialCategory: initialData?.category,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -320,8 +323,12 @@ export function ProviderForm({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新建供应商时:添加自定义端点
|
// 处理 meta 字段(新建与编辑使用不同策略)
|
||||||
if (!initialData && customEndpointsMap) {
|
if (initialData?.meta) {
|
||||||
|
// 编辑模式:后端已通过 API 更新 meta,直接使用原有值
|
||||||
|
payload.meta = initialData.meta;
|
||||||
|
} else if (customEndpointsMap) {
|
||||||
|
// 新建模式:从表单收集的自定义端点打包到 meta
|
||||||
payload.meta = { custom_endpoints: customEndpointsMap };
|
payload.meta = { custom_endpoints: customEndpointsMap };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface UseProviderCategoryProps {
|
|||||||
appType: AppType;
|
appType: AppType;
|
||||||
selectedPresetId: string | null;
|
selectedPresetId: string | null;
|
||||||
isEditMode: boolean;
|
isEditMode: boolean;
|
||||||
|
initialCategory?: ProviderCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,14 +19,19 @@ export function useProviderCategory({
|
|||||||
appType,
|
appType,
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
|
initialCategory,
|
||||||
}: UseProviderCategoryProps) {
|
}: UseProviderCategoryProps) {
|
||||||
const [category, setCategory] = useState<ProviderCategory | undefined>(
|
const [category, setCategory] = useState<ProviderCategory | undefined>(
|
||||||
undefined,
|
// 编辑模式:使用 initialCategory
|
||||||
|
isEditMode ? initialCategory : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 编辑模式不自动设置类别
|
// 编辑模式:只在初始化时设置,后续不自动更新
|
||||||
if (isEditMode) return;
|
if (isEditMode) {
|
||||||
|
setCategory(initialCategory);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedPresetId === "custom") {
|
if (selectedPresetId === "custom") {
|
||||||
setCategory("custom");
|
setCategory("custom");
|
||||||
@@ -56,7 +62,7 @@ export function useProviderCategory({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [appType, selectedPresetId, isEditMode]);
|
}, [appType, selectedPresetId, isEditMode, initialCategory]);
|
||||||
|
|
||||||
return { category, setCategory };
|
return { category, setCategory };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
|||||||
import type { AppType } from "@/lib/api";
|
import type { AppType } from "@/lib/api";
|
||||||
import type { ProviderPreset } from "@/config/providerPresets";
|
import type { ProviderPreset } from "@/config/providerPresets";
|
||||||
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
import type { CodexProviderPreset } from "@/config/codexProviderPresets";
|
||||||
|
import type { ProviderMeta, EndpointCandidate } from "@/types";
|
||||||
|
|
||||||
type PresetEntry = {
|
type PresetEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,20 +17,18 @@ interface UseSpeedTestEndpointsProps {
|
|||||||
codexBaseUrl: string;
|
codexBaseUrl: string;
|
||||||
initialData?: {
|
initialData?: {
|
||||||
settingsConfig?: Record<string, unknown>;
|
settingsConfig?: Record<string, unknown>;
|
||||||
|
meta?: ProviderMeta;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EndpointCandidate {
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收集端点测速弹窗的初始端点列表
|
* 收集端点测速弹窗的初始端点列表
|
||||||
*
|
*
|
||||||
* 收集来源:
|
* 收集来源:
|
||||||
* 1. 当前选中的 Base URL
|
* 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
||||||
* 2. 编辑模式下的初始数据 URL
|
* 2. 当前选中的 Base URL
|
||||||
* 3. 预设中的 endpointCandidates
|
* 3. 编辑模式下的初始数据 URL
|
||||||
|
* 4. 预设中的 endpointCandidates
|
||||||
*/
|
*/
|
||||||
export function useSpeedTestEndpoints({
|
export function useSpeedTestEndpoints({
|
||||||
appType,
|
appType,
|
||||||
@@ -43,43 +42,53 @@ export function useSpeedTestEndpoints({
|
|||||||
if (appType !== "claude") return [];
|
if (appType !== "claude") return [];
|
||||||
|
|
||||||
const map = new Map<string, EndpointCandidate>();
|
const map = new Map<string, EndpointCandidate>();
|
||||||
|
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
||||||
const add = (url?: string) => {
|
const add = (url?: string) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
const sanitized = url.trim().replace(/\/+$/, "");
|
const sanitized = url.trim().replace(/\/+$/, "");
|
||||||
if (!sanitized || map.has(sanitized)) return;
|
if (!sanitized || map.has(sanitized)) return;
|
||||||
map.set(sanitized, { url: sanitized });
|
map.set(sanitized, { url: sanitized, isCustom: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. 当前 Base URL
|
// 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
||||||
|
if (initialData?.meta?.custom_endpoints) {
|
||||||
|
const customEndpoints = initialData.meta.custom_endpoints;
|
||||||
|
for (const url of Object.keys(customEndpoints)) {
|
||||||
|
add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 当前 Base URL
|
||||||
if (baseUrl) {
|
if (baseUrl) {
|
||||||
add(baseUrl);
|
add(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 编辑模式:初始数据中的 URL
|
// 3. 编辑模式:初始数据中的 URL
|
||||||
if (initialData && typeof initialData.settingsConfig === "object") {
|
if (initialData && typeof initialData.settingsConfig === "object") {
|
||||||
const envUrl = (initialData.settingsConfig as any)?.env
|
const configEnv = initialData.settingsConfig as {
|
||||||
?.ANTHROPIC_BASE_URL;
|
env?: { ANTHROPIC_BASE_URL?: string };
|
||||||
|
};
|
||||||
|
const envUrl = configEnv.env?.ANTHROPIC_BASE_URL;
|
||||||
if (typeof envUrl === "string") {
|
if (typeof envUrl === "string") {
|
||||||
add(envUrl);
|
add(envUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 预设中的 endpointCandidates
|
// 4. 预设中的 endpointCandidates(也允许用户删除)
|
||||||
if (selectedPresetId && selectedPresetId !== "custom") {
|
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||||
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
const preset = entry.preset as ProviderPreset;
|
const preset = entry.preset as ProviderPreset;
|
||||||
// 添加预设自己的 baseUrl
|
// 添加预设自己的 baseUrl
|
||||||
const presetEnv = (preset.settingsConfig as any)?.env
|
const presetEnv = preset.settingsConfig as {
|
||||||
?.ANTHROPIC_BASE_URL;
|
env?: { ANTHROPIC_BASE_URL?: string };
|
||||||
if (typeof presetEnv === "string") {
|
};
|
||||||
add(presetEnv);
|
if (presetEnv.env?.ANTHROPIC_BASE_URL) {
|
||||||
|
add(presetEnv.env.ANTHROPIC_BASE_URL);
|
||||||
}
|
}
|
||||||
// 添加预设的候选端点
|
// 添加预设的候选端点
|
||||||
if (Array.isArray((preset as any).endpointCandidates)) {
|
if (preset.endpointCandidates) {
|
||||||
for (const u of (preset as any).endpointCandidates as string[]) {
|
preset.endpointCandidates.forEach((url) => add(url));
|
||||||
add(u);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,30 +100,40 @@ export function useSpeedTestEndpoints({
|
|||||||
if (appType !== "codex") return [];
|
if (appType !== "codex") return [];
|
||||||
|
|
||||||
const map = new Map<string, EndpointCandidate>();
|
const map = new Map<string, EndpointCandidate>();
|
||||||
|
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
||||||
const add = (url?: string) => {
|
const add = (url?: string) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
const sanitized = url.trim().replace(/\/+$/, "");
|
const sanitized = url.trim().replace(/\/+$/, "");
|
||||||
if (!sanitized || map.has(sanitized)) return;
|
if (!sanitized || map.has(sanitized)) return;
|
||||||
map.set(sanitized, { url: sanitized });
|
map.set(sanitized, { url: sanitized, isCustom: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. 当前 Codex Base URL
|
// 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
||||||
|
if (initialData?.meta?.custom_endpoints) {
|
||||||
|
const customEndpoints = initialData.meta.custom_endpoints;
|
||||||
|
for (const url of Object.keys(customEndpoints)) {
|
||||||
|
add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 当前 Codex Base URL
|
||||||
if (codexBaseUrl) {
|
if (codexBaseUrl) {
|
||||||
add(codexBaseUrl);
|
add(codexBaseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 编辑模式:初始数据中的 URL
|
// 3. 编辑模式:初始数据中的 URL
|
||||||
const initialCodexConfig =
|
const initialCodexConfig =
|
||||||
initialData && typeof initialData.settingsConfig?.config === "string"
|
initialData?.settingsConfig as {
|
||||||
? (initialData.settingsConfig as any).config
|
config?: string;
|
||||||
: "";
|
} | undefined;
|
||||||
|
const configStr = initialCodexConfig?.config ?? "";
|
||||||
// 从 TOML 中提取 base_url
|
// 从 TOML 中提取 base_url
|
||||||
const match = /base_url\s*=\s*["']([^"']+)["']/i.exec(initialCodexConfig);
|
const match = /base_url\s*=\s*["']([^"']+)["']/i.exec(configStr);
|
||||||
if (match?.[1]) {
|
if (match?.[1]) {
|
||||||
add(match[1]);
|
add(match[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 预设中的 endpointCandidates
|
// 4. 预设中的 endpointCandidates(也允许用户删除)
|
||||||
if (selectedPresetId && selectedPresetId !== "custom") {
|
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||||
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
@@ -128,10 +147,8 @@ export function useSpeedTestEndpoints({
|
|||||||
add(presetMatch[1]);
|
add(presetMatch[1]);
|
||||||
}
|
}
|
||||||
// 添加预设的候选端点
|
// 添加预设的候选端点
|
||||||
if (Array.isArray((preset as any).endpointCandidates)) {
|
if (preset.endpointCandidates) {
|
||||||
for (const u of (preset as any).endpointCandidates as string[]) {
|
preset.endpointCandidates.forEach((url) => add(url));
|
||||||
add(u);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,6 +265,8 @@
|
|||||||
"loadEndpointsFailed": "Failed to load custom endpoints:",
|
"loadEndpointsFailed": "Failed to load custom endpoints:",
|
||||||
"addEndpointFailed": "Failed to add custom endpoint:",
|
"addEndpointFailed": "Failed to add custom endpoint:",
|
||||||
"removeEndpointFailed": "Failed to remove custom endpoint:",
|
"removeEndpointFailed": "Failed to remove custom endpoint:",
|
||||||
|
"removeFailed": "Remove failed: {{error}}",
|
||||||
|
"updateLastUsedFailed": "Failed to update endpoint last used time",
|
||||||
"pleaseAddEndpoint": "Please add an endpoint first",
|
"pleaseAddEndpoint": "Please add an endpoint first",
|
||||||
"testUnavailable": "Speed test unavailable",
|
"testUnavailable": "Speed test unavailable",
|
||||||
"noResult": "No result returned",
|
"noResult": "No result returned",
|
||||||
|
|||||||
@@ -265,6 +265,8 @@
|
|||||||
"loadEndpointsFailed": "加载自定义端点失败:",
|
"loadEndpointsFailed": "加载自定义端点失败:",
|
||||||
"addEndpointFailed": "添加自定义端点失败:",
|
"addEndpointFailed": "添加自定义端点失败:",
|
||||||
"removeEndpointFailed": "删除自定义端点失败:",
|
"removeEndpointFailed": "删除自定义端点失败:",
|
||||||
|
"removeFailed": "删除失败: {{error}}",
|
||||||
|
"updateLastUsedFailed": "更新端点使用时间失败",
|
||||||
"pleaseAddEndpoint": "请先添加端点",
|
"pleaseAddEndpoint": "请先添加端点",
|
||||||
"testUnavailable": "测速功能不可用",
|
"testUnavailable": "测速功能不可用",
|
||||||
"noResult": "未返回结果",
|
"noResult": "未返回结果",
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ export interface CustomEndpoint {
|
|||||||
lastUsed?: number;
|
lastUsed?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 端点候选项(用于端点测速弹窗)
|
||||||
|
export interface EndpointCandidate {
|
||||||
|
id?: string;
|
||||||
|
url: string;
|
||||||
|
isCustom?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// 用量查询脚本配置
|
// 用量查询脚本配置
|
||||||
export interface UsageScript {
|
export interface UsageScript {
|
||||||
enabled: boolean; // 是否启用用量查询
|
enabled: boolean; // 是否启用用量查询
|
||||||
|
|||||||
Reference in New Issue
Block a user