diff --git a/src/features/providers/ProvidersWorkbenchPage.tsx b/src/features/providers/ProvidersWorkbenchPage.tsx index 0c6fa82..eef5dd2 100644 --- a/src/features/providers/ProvidersWorkbenchPage.tsx +++ b/src/features/providers/ProvidersWorkbenchPage.tsx @@ -11,7 +11,6 @@ import { type ProviderRecentUsageMap, } from '@/components/providers/utils'; import type { OpenAIProviderConfig } from '@/types'; -import { isRecord } from '@/utils/helpers'; import { ProviderHeaderCard } from './components/ProviderHeaderCard'; import { ProviderCategoryList } from './components/ProviderCategoryList'; import { ProviderResourcePanel } from './components/ProviderResourcePanel'; @@ -67,38 +66,6 @@ const matchesFilter = (r: ProviderResource, normalized: string): boolean => { return haystack.some((v) => v.includes(normalized)); }; -const getResourceModels = (resource: ProviderResource): string[] => { - if (!isRecord(resource.raw)) return []; - if (resource.brand === 'ampcode') { - const mappings = resource.raw.modelMappings; - if (!Array.isArray(mappings)) return []; - const seen = new Set(); - mappings.forEach((mapping) => { - if (!isRecord(mapping)) return; - const from = typeof mapping.from === 'string' ? mapping.from.trim() : ''; - const to = typeof mapping.to === 'string' ? mapping.to.trim() : ''; - if (from) seen.add(from); - if (to) seen.add(to); - }); - return Array.from(seen); - } - const models = resource.raw.models; - if (!Array.isArray(models)) return []; - const seen = new Set(); - models.forEach((model) => { - if (!isRecord(model)) return; - const name = typeof model.name === 'string' ? model.name.trim() : ''; - if (name) seen.add(name); - }); - return Array.from(seen); -}; - -const getResourcePriority = (resource: ProviderResource): number => { - if (!isRecord(resource.raw)) return 0; - const priority = resource.raw.priority; - return typeof priority === 'number' && Number.isFinite(priority) ? priority : 0; -}; - const getResourceSortName = (resource: ProviderResource): string => (resource.name ?? resource.identifier ?? resource.apiKeyPreview ?? '').toLowerCase(); @@ -216,7 +183,7 @@ export function ProvidersWorkbenchPage() { if (!activeGroup) return []; const seen = new Set(); activeGroup.resources.forEach((r) => { - getResourceModels(r).forEach((name) => seen.add(name)); + r.models.forEach((name) => seen.add(name)); }); return Array.from(seen).sort(); }, [activeGroup]); @@ -232,10 +199,7 @@ export function ProvidersWorkbenchPage() { const visibleResources = useMemo(() => { let arr = filteredResources; if (selectedModels.size > 0) { - arr = arr.filter((r) => { - const models = getResourceModels(r); - return models.some((name) => selectedModels.has(name)); - }); + arr = arr.filter((r) => r.models.some((name) => selectedModels.has(name))); } const sorted = [...arr].sort((a, b) => { @@ -243,9 +207,7 @@ export function ProvidersWorkbenchPage() { if (providerSortBy === 'name') { diff = getResourceSortName(a).localeCompare(getResourceSortName(b)); } else if (providerSortBy === 'priority') { - const ap = getResourcePriority(a); - const bp = getResourcePriority(b); - diff = ap - bp; + diff = a.priority - b.priority; } else { diff = getResourceRecentSuccess(a, usageByProvider) - diff --git a/src/features/providers/adapters.ts b/src/features/providers/adapters.ts index e406b30..a2b5b43 100644 --- a/src/features/providers/adapters.ts +++ b/src/features/providers/adapters.ts @@ -18,6 +18,29 @@ import type { const countHeaders = (headers?: Record): number => headers ? Object.keys(headers).length : 0; +const collectModelNames = (models?: Array<{ name?: string }>): string[] => { + const seen = new Set(); + (models ?? []).forEach((model) => { + const name = (model?.name ?? '').trim(); + if (name) seen.add(name); + }); + return Array.from(seen); +}; + +const collectAmpcodeModelNames = (mappings: AmpcodeConfig['modelMappings']): string[] => { + const seen = new Set(); + (mappings ?? []).forEach((mapping) => { + const from = (mapping?.from ?? '').trim(); + const to = (mapping?.to ?? '').trim(); + if (from) seen.add(from); + if (to) seen.add(to); + }); + return Array.from(seen); +}; + +const normalizePriority = (priority?: number): number => + typeof priority === 'number' && Number.isFinite(priority) ? priority : 0; + const buildId = (brand: ProviderBrand, index: number, fragment: string) => `${brand}:${index}:${fragment || 'item'}`; @@ -64,6 +87,8 @@ function providerKeyToResource( proxyUrl: config.proxyUrl ?? null, prefix: config.prefix ?? null, modelCount: config.models?.length ?? 0, + models: collectModelNames(config.models), + priority: normalizePriority(config.priority), headerCount: countHeaders(config.headers), excludedModelCount: stripDisableAllModelsRule(config.excludedModels).length, apiKeyEntryCount: 0, @@ -110,6 +135,8 @@ export function openaiToResource( proxyUrl: null, prefix: config.prefix ?? null, modelCount: config.models?.length ?? 0, + models: collectModelNames(config.models), + priority: normalizePriority(config.priority), headerCount: countHeaders(config.headers), excludedModelCount: 0, apiKeyEntryCount: config.apiKeyEntries?.length ?? 0, @@ -139,6 +166,8 @@ export function ampcodeToResource(config?: AmpcodeConfig | null): ProviderResour proxyUrl: null, prefix: null, modelCount: safe.modelMappings?.length ?? 0, + models: collectAmpcodeModelNames(safe.modelMappings), + priority: 0, headerCount: 0, excludedModelCount: 0, apiKeyEntryCount: upstreamKeyMappingsCount, diff --git a/src/features/providers/types.ts b/src/features/providers/types.ts index a62fa27..6c7aead 100644 --- a/src/features/providers/types.ts +++ b/src/features/providers/types.ts @@ -50,6 +50,10 @@ export interface ProviderResource { proxyUrl: string | null; prefix: string | null; modelCount: number; + /** 去重后的模型名(ampcode 为映射两端), 供筛选/搜索用 */ + models: string[]; + /** 排序用优先级,未配置时为 0 */ + priority: number; headerCount: number; excludedModelCount: number; /** 仅 OpenAI 有意义,其它 brand 该字段不展示但保留 */