refactor(endpoint): separate edit and create mode endpoint management (#192)
Optimize custom endpoint management logic to distinguish between edit and create modes: - Edit mode: endpoints are read/written directly to backend via API - Create mode: use draftCustomEndpoints to stage, save on submit - Remove duplicate endpoint loading in useSpeedTestEndpoints - Add isSaving state and initialCustomUrls tracking
This commit is contained in:
@@ -34,7 +34,7 @@ interface ClaudeFormFieldsProps {
|
||||
onBaseUrlChange: (url: string) => void;
|
||||
isEndpointModalOpen: boolean;
|
||||
onEndpointModalToggle: (open: boolean) => void;
|
||||
onCustomEndpointsChange: (endpoints: string[]) => void;
|
||||
onCustomEndpointsChange?: (endpoints: string[]) => void;
|
||||
|
||||
// Model Selector
|
||||
shouldShowModelSelector: boolean;
|
||||
|
||||
@@ -24,7 +24,7 @@ interface CodexFormFieldsProps {
|
||||
onBaseUrlChange: (url: string) => void;
|
||||
isEndpointModalOpen: boolean;
|
||||
onEndpointModalToggle: (open: boolean) => void;
|
||||
onCustomEndpointsChange: (endpoints: string[]) => void;
|
||||
onCustomEndpointsChange?: (endpoints: string[]) => void;
|
||||
|
||||
// Speed Test Endpoints
|
||||
speedTestEndpoints: EndpointCandidate[];
|
||||
|
||||
@@ -36,7 +36,8 @@ interface EndpointSpeedTestProps {
|
||||
initialEndpoints: EndpointCandidate[];
|
||||
visible?: boolean;
|
||||
onClose: () => void;
|
||||
// 当自定义端点列表变化时回传(仅包含 isCustom 的条目)
|
||||
// 新建模式:当自定义端点列表变化时回传(仅包含 isCustom 的条目)
|
||||
// 编辑模式:不使用此回调,端点直接保存到后端
|
||||
onCustomEndpointsChange?: (urls: string[]) => void;
|
||||
}
|
||||
|
||||
@@ -101,25 +102,31 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
const [autoSelect, setAutoSelect] = useState(true);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
const [lastError, setLastError] = useState<string | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
// 记录初始的自定义端点,用于对比变化
|
||||
const [initialCustomUrls, setInitialCustomUrls] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
const normalizedSelected = normalizeEndpointUrl(value);
|
||||
|
||||
const hasEndpoints = entries.length > 0;
|
||||
const isEditMode = Boolean(providerId); // 编辑模式有 providerId
|
||||
|
||||
// 加载保存的自定义端点(按正在编辑的供应商)
|
||||
// 编辑模式:加载已保存的自定义端点
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const loadCustomEndpoints = async () => {
|
||||
try {
|
||||
if (!providerId) return;
|
||||
if (!providerId) return; // 新建模式不加载
|
||||
|
||||
const customEndpoints = await vscodeApi.getCustomEndpoints(
|
||||
appId,
|
||||
providerId,
|
||||
);
|
||||
|
||||
// 检查是否已取消
|
||||
if (cancelled) return;
|
||||
|
||||
const candidates: EndpointCandidate[] = customEndpoints.map(
|
||||
@@ -129,6 +136,13 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
}),
|
||||
);
|
||||
|
||||
// 记录初始的自定义端点
|
||||
const customUrls = new Set(
|
||||
customEndpoints.map((ep) => normalizeEndpointUrl(ep.url)),
|
||||
);
|
||||
setInitialCustomUrls(customUrls);
|
||||
|
||||
// 合并自定义端点与初始端点
|
||||
setEntries((prev) => {
|
||||
const map = new Map<string, EndpointEntry>();
|
||||
|
||||
@@ -137,7 +151,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
map.set(entry.url, entry);
|
||||
});
|
||||
|
||||
// 合并自定义端点
|
||||
// 添加从后端加载的自定义端点
|
||||
candidates.forEach((candidate) => {
|
||||
const sanitized = normalizeEndpointUrl(candidate.url);
|
||||
if (sanitized && !map.has(sanitized)) {
|
||||
@@ -161,60 +175,20 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (visible) {
|
||||
// 只在编辑模式下加载
|
||||
if (providerId) {
|
||||
loadCustomEndpoints();
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [appId, visible, providerId, t]);
|
||||
}, [appId, providerId, t, initialEndpoints]);
|
||||
|
||||
// 新建模式:将自定义端点变化透传给父组件(仅限 isCustom)
|
||||
// 编辑模式:不使用此回调,端点已通过 API 直接保存
|
||||
useEffect(() => {
|
||||
setEntries((prev) => {
|
||||
const map = new Map<string, EndpointEntry>();
|
||||
prev.forEach((entry) => {
|
||||
map.set(entry.url, entry);
|
||||
});
|
||||
|
||||
let changed = false;
|
||||
|
||||
const mergeCandidate = (candidate: EndpointCandidate) => {
|
||||
const sanitized = candidate.url
|
||||
? normalizeEndpointUrl(candidate.url)
|
||||
: "";
|
||||
if (!sanitized) return;
|
||||
const existing = map.get(sanitized);
|
||||
if (existing) return;
|
||||
|
||||
map.set(sanitized, {
|
||||
id: candidate.id ?? randomId(),
|
||||
url: sanitized,
|
||||
isCustom: candidate.isCustom ?? false,
|
||||
latency: null,
|
||||
status: undefined,
|
||||
error: null,
|
||||
});
|
||||
changed = true;
|
||||
};
|
||||
|
||||
initialEndpoints.forEach(mergeCandidate);
|
||||
|
||||
if (normalizedSelected && !map.has(normalizedSelected)) {
|
||||
mergeCandidate({ url: normalizedSelected, isCustom: true });
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return Array.from(map.values());
|
||||
});
|
||||
}, [initialEndpoints, normalizedSelected]);
|
||||
|
||||
// 将自定义端点变化透传给父组件(仅限 isCustom)
|
||||
useEffect(() => {
|
||||
if (!onCustomEndpointsChange) return;
|
||||
if (!onCustomEndpointsChange || isEditMode) return; // 编辑模式不使用回调
|
||||
try {
|
||||
const customUrls = Array.from(
|
||||
new Set(
|
||||
@@ -228,8 +202,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
// 仅在 entries 变化时同步
|
||||
}, [entries, onCustomEndpointsChange]);
|
||||
}, [entries, onCustomEndpointsChange, isEditMode]);
|
||||
|
||||
const sortedEntries = useMemo(() => {
|
||||
return entries.slice().sort((a: TestResult, b: TestResult) => {
|
||||
@@ -268,7 +241,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
let sanitized = "";
|
||||
if (!errorMsg && parsed) {
|
||||
sanitized = normalizeEndpointUrl(parsed.toString());
|
||||
// 使用当前 entries 做去重校验,避免依赖可能过期的 addError
|
||||
// 使用当前 entries 做去重校验
|
||||
const isDuplicate = entries.some((entry) => entry.url === sanitized);
|
||||
if (isDuplicate) {
|
||||
errorMsg = t("endpointTest.urlExists");
|
||||
@@ -281,8 +254,9 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
}
|
||||
|
||||
setAddError(null);
|
||||
setLastError(null);
|
||||
|
||||
// 更新本地状态(延迟提交,不立即保存到后端)
|
||||
// 更新本地状态(延迟保存,点击保存按钮时统一处理)
|
||||
setEntries((prev) => {
|
||||
if (prev.some((e) => e.url === sanitized)) return prev;
|
||||
return [
|
||||
@@ -303,14 +277,14 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
}
|
||||
|
||||
setCustomUrl("");
|
||||
}, [customUrl, entries, normalizedSelected, onChange]);
|
||||
}, [customUrl, entries, normalizedSelected, onChange, t]);
|
||||
|
||||
const handleRemoveEndpoint = useCallback(
|
||||
(entry: EndpointEntry) => {
|
||||
// 清空之前的错误提示
|
||||
setLastError(null);
|
||||
|
||||
// 更新本地状态(延迟提交,不立即从后端删除)
|
||||
// 更新本地状态(延迟保存,点击保存按钮时统一处理)
|
||||
setEntries((prev) => {
|
||||
const next = prev.filter((item) => item.id !== entry.id);
|
||||
if (entry.url === normalizedSelected) {
|
||||
@@ -405,6 +379,58 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
[normalizedSelected, onChange],
|
||||
);
|
||||
|
||||
// 保存端点变更
|
||||
const handleSave = useCallback(async () => {
|
||||
// 编辑模式:对比初始端点和当前端点,批量保存变更
|
||||
if (isEditMode && providerId) {
|
||||
setIsSaving(true);
|
||||
setLastError(null);
|
||||
|
||||
try {
|
||||
// 获取当前的自定义端点
|
||||
const currentCustomUrls = new Set(
|
||||
entries
|
||||
.filter((e) => e.isCustom)
|
||||
.map((e) => normalizeEndpointUrl(e.url)),
|
||||
);
|
||||
|
||||
// 找出新增的端点
|
||||
const toAdd = Array.from(currentCustomUrls).filter(
|
||||
(url) => !initialCustomUrls.has(url),
|
||||
);
|
||||
|
||||
// 找出删除的端点
|
||||
const toRemove = Array.from(initialCustomUrls).filter(
|
||||
(url) => !currentCustomUrls.has(url),
|
||||
);
|
||||
|
||||
// 批量添加
|
||||
for (const url of toAdd) {
|
||||
await vscodeApi.addCustomEndpoint(appId, providerId, url);
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
for (const url of toRemove) {
|
||||
await vscodeApi.removeCustomEndpoint(appId, providerId, url);
|
||||
}
|
||||
|
||||
// 更新初始端点列表
|
||||
setInitialCustomUrls(currentCustomUrls);
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : t("endpointTest.saveFailed");
|
||||
setLastError(message);
|
||||
setIsSaving(false);
|
||||
return;
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
onClose();
|
||||
}, [isEditMode, providerId, entries, initialCustomUrls, appId, onClose, t]);
|
||||
|
||||
return (
|
||||
<Dialog open={visible} onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent
|
||||
@@ -580,10 +606,32 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" onClick={onClose} className="gap-2">
|
||||
<Save className="w-4 h-4" />
|
||||
{t("common.save")}
|
||||
<DialogFooter className="gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
className="gap-2"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
{t("common.saving")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-4 h-4" />
|
||||
{t("common.save")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -100,16 +100,16 @@ export function ProviderForm({
|
||||
partnerPromotionKey?: string;
|
||||
} | null>(null);
|
||||
const [isEndpointModalOpen, setIsEndpointModalOpen] = useState(false);
|
||||
const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] =
|
||||
useState(false);
|
||||
|
||||
// 新建供应商:收集端点测速弹窗中的"自定义端点",提交时一次性落盘到 meta.custom_endpoints
|
||||
// 编辑供应商:从 initialData.meta.custom_endpoints 恢复端点列表
|
||||
// 编辑供应商:端点已通过 API 直接保存,不再需要此状态
|
||||
const [draftCustomEndpoints, setDraftCustomEndpoints] = useState<string[]>(
|
||||
() => {
|
||||
if (!initialData?.meta?.custom_endpoints) {
|
||||
return [];
|
||||
}
|
||||
// 从 Record<string, CustomEndpoint> 中提取 URL 列表
|
||||
return Object.keys(initialData.meta.custom_endpoints);
|
||||
// 仅在新建模式下使用
|
||||
if (initialData) return [];
|
||||
return [];
|
||||
},
|
||||
);
|
||||
|
||||
@@ -125,10 +125,8 @@ export function ProviderForm({
|
||||
setSelectedPresetId(initialData ? null : "custom");
|
||||
setActivePreset(null);
|
||||
|
||||
// 重新初始化 draftCustomEndpoints(编辑模式时从 meta 恢复)
|
||||
if (initialData?.meta?.custom_endpoints) {
|
||||
setDraftCustomEndpoints(Object.keys(initialData.meta.custom_endpoints));
|
||||
} else {
|
||||
// 编辑模式不需要恢复 draftCustomEndpoints,端点已通过 API 管理
|
||||
if (!initialData) {
|
||||
setDraftCustomEndpoints([]);
|
||||
}
|
||||
}, [appId, initialData]);
|
||||
@@ -220,8 +218,6 @@ export function ProviderForm({
|
||||
[originalHandleCodexConfigChange, debouncedValidate],
|
||||
);
|
||||
|
||||
const [isCodexEndpointModalOpen, setIsCodexEndpointModalOpen] =
|
||||
useState(false);
|
||||
const [isCodexTemplateModalOpen, setIsCodexTemplateModalOpen] =
|
||||
useState(false);
|
||||
|
||||
@@ -361,60 +357,51 @@ export function ProviderForm({
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 meta 字段:基于 draftCustomEndpoints 生成 custom_endpoints
|
||||
// 注意:不使用 customEndpointsMap,因为它包含了候选端点(预设、Base URL 等)
|
||||
// 而我们只需要保存用户真正添加的自定义端点
|
||||
const customEndpointsToSave: Record<
|
||||
string,
|
||||
import("@/types").CustomEndpoint
|
||||
> | null =
|
||||
draftCustomEndpoints.length > 0
|
||||
? draftCustomEndpoints.reduce(
|
||||
(acc, url) => {
|
||||
// 尝试从 initialData.meta 中获取原有的端点元数据(保留 addedAt 和 lastUsed)
|
||||
const existing = initialData?.meta?.custom_endpoints?.[url];
|
||||
if (existing) {
|
||||
acc[url] = existing;
|
||||
} else {
|
||||
// 新端点:使用当前时间戳
|
||||
const now = Date.now();
|
||||
acc[url] = { url, addedAt: now, lastUsed: undefined };
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, import("@/types").CustomEndpoint>,
|
||||
)
|
||||
: null;
|
||||
// 处理 meta 字段:仅在新建模式下从 draftCustomEndpoints 生成 custom_endpoints
|
||||
// 编辑模式:端点已通过 API 直接保存,不在此处理
|
||||
if (!isEditMode && draftCustomEndpoints.length > 0) {
|
||||
const customEndpointsToSave: Record<
|
||||
string,
|
||||
import("@/types").CustomEndpoint
|
||||
> = draftCustomEndpoints.reduce(
|
||||
(acc, url) => {
|
||||
const now = Date.now();
|
||||
acc[url] = { url, addedAt: now, lastUsed: undefined };
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, import("@/types").CustomEndpoint>,
|
||||
);
|
||||
|
||||
// 检测是否需要清空端点(重要:区分"用户清空端点"和"用户没有修改端点")
|
||||
const hadEndpoints =
|
||||
initialData?.meta?.custom_endpoints &&
|
||||
Object.keys(initialData.meta.custom_endpoints).length > 0;
|
||||
const needsClearEndpoints =
|
||||
hadEndpoints && draftCustomEndpoints.length === 0;
|
||||
// 检测是否需要清空端点(重要:区分"用户清空端点"和"用户没有修改端点")
|
||||
const hadEndpoints =
|
||||
initialData?.meta?.custom_endpoints &&
|
||||
Object.keys(initialData.meta.custom_endpoints).length > 0;
|
||||
const needsClearEndpoints =
|
||||
hadEndpoints && draftCustomEndpoints.length === 0;
|
||||
|
||||
// 如果用户明确清空了端点,传递空对象(而不是 null)让后端知道要删除
|
||||
let mergedMeta = needsClearEndpoints
|
||||
? mergeProviderMeta(initialData?.meta, {})
|
||||
: mergeProviderMeta(initialData?.meta, customEndpointsToSave);
|
||||
// 如果用户明确清空了端点,传递空对象(而不是 null)让后端知道要删除
|
||||
let mergedMeta = needsClearEndpoints
|
||||
? mergeProviderMeta(initialData?.meta, {})
|
||||
: mergeProviderMeta(initialData?.meta, customEndpointsToSave);
|
||||
|
||||
// 添加合作伙伴标识与促销 key
|
||||
if (activePreset?.isPartner) {
|
||||
mergedMeta = {
|
||||
...(mergedMeta ?? {}),
|
||||
isPartner: true,
|
||||
};
|
||||
}
|
||||
// 添加合作伙伴标识与促销 key
|
||||
if (activePreset?.isPartner) {
|
||||
mergedMeta = {
|
||||
...(mergedMeta ?? {}),
|
||||
isPartner: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (activePreset?.partnerPromotionKey) {
|
||||
mergedMeta = {
|
||||
...(mergedMeta ?? {}),
|
||||
partnerPromotionKey: activePreset.partnerPromotionKey,
|
||||
};
|
||||
}
|
||||
if (activePreset?.partnerPromotionKey) {
|
||||
mergedMeta = {
|
||||
...(mergedMeta ?? {}),
|
||||
partnerPromotionKey: activePreset.partnerPromotionKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (mergedMeta !== undefined) {
|
||||
payload.meta = mergedMeta;
|
||||
if (mergedMeta !== undefined) {
|
||||
payload.meta = mergedMeta;
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(payload);
|
||||
@@ -609,7 +596,9 @@ export function ProviderForm({
|
||||
onBaseUrlChange={handleClaudeBaseUrlChange}
|
||||
isEndpointModalOpen={isEndpointModalOpen}
|
||||
onEndpointModalToggle={setIsEndpointModalOpen}
|
||||
onCustomEndpointsChange={setDraftCustomEndpoints}
|
||||
onCustomEndpointsChange={
|
||||
isEditMode ? undefined : setDraftCustomEndpoints
|
||||
}
|
||||
shouldShowModelSelector={category !== "official"}
|
||||
claudeModel={claudeModel}
|
||||
defaultHaikuModel={defaultHaikuModel}
|
||||
@@ -636,7 +625,9 @@ export function ProviderForm({
|
||||
onBaseUrlChange={handleCodexBaseUrlChange}
|
||||
isEndpointModalOpen={isCodexEndpointModalOpen}
|
||||
onEndpointModalToggle={setIsCodexEndpointModalOpen}
|
||||
onCustomEndpointsChange={setDraftCustomEndpoints}
|
||||
onCustomEndpointsChange={
|
||||
isEditMode ? undefined : setDraftCustomEndpoints
|
||||
}
|
||||
speedTestEndpoints={speedTestEndpoints}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -25,10 +25,12 @@ interface UseSpeedTestEndpointsProps {
|
||||
* 收集端点测速弹窗的初始端点列表
|
||||
*
|
||||
* 收集来源:
|
||||
* 1. 编辑模式:从 meta.custom_endpoints 读取已保存的端点(优先)
|
||||
* 2. 当前选中的 Base URL
|
||||
* 3. 编辑模式下的初始数据 URL
|
||||
* 4. 预设中的 endpointCandidates
|
||||
* 1. 当前选中的 Base URL
|
||||
* 2. 编辑模式下的初始数据 URL
|
||||
* 3. 预设中的 endpointCandidates
|
||||
*
|
||||
* 注意:已保存的自定义端点通过 getCustomEndpoints API 在 EndpointSpeedTest 组件中加载,
|
||||
* 不在此处读取,避免重复导入。
|
||||
*/
|
||||
export function useSpeedTestEndpoints({
|
||||
appId,
|
||||
@@ -43,28 +45,21 @@ export function useSpeedTestEndpoints({
|
||||
if (appId !== "claude" && appId !== "gemini") return [];
|
||||
|
||||
const map = new Map<string, EndpointCandidate>();
|
||||
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
||||
const add = (url?: string) => {
|
||||
// 候选端点标记为 isCustom: false,表示来自预设或配置
|
||||
// 已保存的自定义端点会在 EndpointSpeedTest 组件中通过 API 加载
|
||||
const add = (url?: string, isCustom = false) => {
|
||||
if (!url) return;
|
||||
const sanitized = url.trim().replace(/\/+$/, "");
|
||||
if (!sanitized || map.has(sanitized)) return;
|
||||
map.set(sanitized, { url: sanitized, isCustom: true });
|
||||
map.set(sanitized, { url: sanitized, isCustom });
|
||||
};
|
||||
|
||||
// 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
|
||||
// 1. 当前 Base URL
|
||||
if (baseUrl) {
|
||||
add(baseUrl);
|
||||
}
|
||||
|
||||
// 3. 编辑模式:初始数据中的 URL
|
||||
// 2. 编辑模式:初始数据中的 URL
|
||||
if (initialData && typeof initialData.settingsConfig === "object") {
|
||||
const configEnv = initialData.settingsConfig as {
|
||||
env?: { ANTHROPIC_BASE_URL?: string; GOOGLE_GEMINI_BASE_URL?: string };
|
||||
@@ -78,7 +73,7 @@ export function useSpeedTestEndpoints({
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 预设中的 endpointCandidates(也允许用户删除)
|
||||
// 3. 预设中的 endpointCandidates
|
||||
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
||||
if (entry) {
|
||||
@@ -112,28 +107,21 @@ export function useSpeedTestEndpoints({
|
||||
if (appId !== "codex") return [];
|
||||
|
||||
const map = new Map<string, EndpointCandidate>();
|
||||
// 所有端点都标记为 isCustom: true,给用户完全的管理自由
|
||||
const add = (url?: string) => {
|
||||
// 候选端点标记为 isCustom: false,表示来自预设或配置
|
||||
// 已保存的自定义端点会在 EndpointSpeedTest 组件中通过 API 加载
|
||||
const add = (url?: string, isCustom = false) => {
|
||||
if (!url) return;
|
||||
const sanitized = url.trim().replace(/\/+$/, "");
|
||||
if (!sanitized || map.has(sanitized)) return;
|
||||
map.set(sanitized, { url: sanitized, isCustom: true });
|
||||
map.set(sanitized, { url: sanitized, isCustom });
|
||||
};
|
||||
|
||||
// 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
|
||||
// 1. 当前 Codex Base URL
|
||||
if (codexBaseUrl) {
|
||||
add(codexBaseUrl);
|
||||
}
|
||||
|
||||
// 3. 编辑模式:初始数据中的 URL
|
||||
// 2. 编辑模式:初始数据中的 URL
|
||||
const initialCodexConfig = initialData?.settingsConfig as
|
||||
| {
|
||||
config?: string;
|
||||
@@ -146,7 +134,7 @@ export function useSpeedTestEndpoints({
|
||||
add(match[1]);
|
||||
}
|
||||
|
||||
// 4. 预设中的 endpointCandidates(也允许用户删除)
|
||||
// 3. 预设中的 endpointCandidates
|
||||
if (selectedPresetId && selectedPresetId !== "custom") {
|
||||
const entry = presetEntries.find((item) => item.id === selectedPresetId);
|
||||
if (entry) {
|
||||
|
||||
Reference in New Issue
Block a user