fix(usage): 修正请求时间线 unused 候选过滤与分组排序逻辑

- 号池内 unused key 不再展示,非号池 unused 仅保留 retry_index=0
- buildProviderGroups 使用 candidate_index 替代数组下标作为分组索引
- 号池组与供应商组合并后统一按 startIndex 排序
This commit is contained in:
fawney19
2026-03-04 10:26:19 +08:00
parent 82a9fb3c39
commit 095e312ab3

View File

@@ -643,7 +643,16 @@ const normalizeTimelineStatus = (value: unknown): CandidateRecord['status'] => {
} }
const isPlannedOnlyStatus = (status: CandidateRecord['status']): boolean => { const isPlannedOnlyStatus = (status: CandidateRecord['status']): boolean => {
return status === 'available' || status === 'unused' return status === 'available'
}
const extractPoolGroupId = (candidate: CandidateRecord): string | null => {
const extra = candidate.extra_data
if (!extra || typeof extra !== 'object' || Array.isArray(extra)) return null
const value = (extra as Record<string, unknown>).pool_group_id
if (typeof value !== 'string') return null
const text = value.trim()
return text || null
} }
// 候选时间线(按实际执行顺序排序) // 候选时间线(按实际执行顺序排序)
@@ -662,9 +671,21 @@ const rawTimeline = computed<CandidateRecord[]>(() => {
}) })
}) })
// 仅保留已进入执行链路”的节点,过滤预创建但未执行的 available/unused 占位记录。 // 仅保留已进入执行链路”的节点,过滤预创建但未执行的 available 占位记录。
// unused 候选:代表因前序候选成功而未被执行的备选项。
// - 号池内 unused key不显示号池只展示实际参与调度的 key
// - 非号池 unused每个 candidate_index 保留 retry_index=0代表该候选存在但未执行
const executableTimeline = computed<CandidateRecord[]>(() => { const executableTimeline = computed<CandidateRecord[]>(() => {
return rawTimeline.value.filter(candidate => !isPlannedOnlyStatus(candidate.status)) return rawTimeline.value.filter(candidate => {
if (isPlannedOnlyStatus(candidate.status)) return false
if (candidate.status === 'unused') {
// 号池内 unused key 不需要展示
if (extractPoolGroupId(candidate) !== null) return false
// 非号池的 unused retry slot 只保留首个
return candidate.retry_index === 0
}
return true
})
}) })
const schedulingAudit = computed<Record<string, unknown> | null>(() => { const schedulingAudit = computed<Record<string, unknown> | null>(() => {
@@ -675,15 +696,6 @@ const schedulingAudit = computed<Record<string, unknown> | null>(() => {
return raw as Record<string, unknown> return raw as Record<string, unknown>
}) })
const extractPoolGroupId = (candidate: CandidateRecord): string | null => {
const extra = candidate.extra_data
if (!extra || typeof extra !== 'object' || Array.isArray(extra)) return null
const value = (extra as Record<string, unknown>).pool_group_id
if (typeof value !== 'string') return null
const text = value.trim()
return text || null
}
const poolAttemptCandidates = computed<CandidateRecord[]>(() => { const poolAttemptCandidates = computed<CandidateRecord[]>(() => {
// 新链路:优先使用后端写入的 extra_data.pool_group_id。 // 新链路:优先使用后端写入的 extra_data.pool_group_id。
const fromTrace = executableTimeline.value.filter((candidate) => extractPoolGroupId(candidate) !== null) const fromTrace = executableTimeline.value.filter((candidate) => extractPoolGroupId(candidate) !== null)
@@ -814,13 +826,13 @@ const buildProviderGroups = (items: CandidateRecord[]): NodeGroup[] => {
const groups: NodeGroup[] = [] const groups: NodeGroup[] = []
let currentGroup: NodeGroup | null = null let currentGroup: NodeGroup | null = null
items.forEach((candidate, index) => { items.forEach((candidate) => {
const providerKey = candidate.provider_name || '未知' const providerKey = candidate.provider_name || '未知'
if (currentGroup && currentGroup.id === providerKey) { if (currentGroup && currentGroup.id === providerKey) {
currentGroup.allAttempts.push(candidate) currentGroup.allAttempts.push(candidate)
currentGroup.retryCount++ currentGroup.retryCount++
currentGroup.endIndex = index currentGroup.endIndex = candidate.candidate_index
currentGroup.totalLatency += candidate.latency_ms || 0 currentGroup.totalLatency += candidate.latency_ms || 0
if (candidate.extra_data?.needs_conversion) { if (candidate.extra_data?.needs_conversion) {
currentGroup.hasConversion = true currentGroup.hasConversion = true
@@ -841,8 +853,8 @@ const buildProviderGroups = (items: CandidateRecord[]): NodeGroup[] => {
allAttempts: [candidate], allAttempts: [candidate],
retryCount: 0, retryCount: 0,
totalLatency: candidate.latency_ms || 0, totalLatency: candidate.latency_ms || 0,
startIndex: index, startIndex: candidate.candidate_index,
endIndex: index, endIndex: candidate.candidate_index,
hasConversion: candidate.extra_data?.needs_conversion === true, hasConversion: candidate.extra_data?.needs_conversion === true,
providerApiFormat: candidate.extra_data?.provider_api_format || null, providerApiFormat: candidate.extra_data?.provider_api_format || null,
isPoolGroup: false, isPoolGroup: false,
@@ -920,8 +932,9 @@ const groupedTimeline = computed<NodeGroup[]>(() => {
return true return true
}) })
poolGroups.sort((a, b) => a.startIndex - b.startIndex) const allGroups = [...poolGroups, ...dedupedProviderGroups]
return [...poolGroups, ...dedupedProviderGroups] allGroups.sort((a, b) => a.startIndex - b.startIndex)
return allGroups
}) })
// 格式转换分界点索引(首个 hasConversion=true 的 group index // 格式转换分界点索引(首个 hasConversion=true 的 group index