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 => {
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[]>(() => {
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>(() => {
@@ -675,15 +696,6 @@ const schedulingAudit = computed<Record<string, unknown> | null>(() => {
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[]>(() => {
// 新链路:优先使用后端写入的 extra_data.pool_group_id。
const fromTrace = executableTimeline.value.filter((candidate) => extractPoolGroupId(candidate) !== null)
@@ -814,13 +826,13 @@ const buildProviderGroups = (items: CandidateRecord[]): NodeGroup[] => {
const groups: NodeGroup[] = []
let currentGroup: NodeGroup | null = null
items.forEach((candidate, index) => {
items.forEach((candidate) => {
const providerKey = candidate.provider_name || '未知'
if (currentGroup && currentGroup.id === providerKey) {
currentGroup.allAttempts.push(candidate)
currentGroup.retryCount++
currentGroup.endIndex = index
currentGroup.endIndex = candidate.candidate_index
currentGroup.totalLatency += candidate.latency_ms || 0
if (candidate.extra_data?.needs_conversion) {
currentGroup.hasConversion = true
@@ -841,8 +853,8 @@ const buildProviderGroups = (items: CandidateRecord[]): NodeGroup[] => {
allAttempts: [candidate],
retryCount: 0,
totalLatency: candidate.latency_ms || 0,
startIndex: index,
endIndex: index,
startIndex: candidate.candidate_index,
endIndex: candidate.candidate_index,
hasConversion: candidate.extra_data?.needs_conversion === true,
providerApiFormat: candidate.extra_data?.provider_api_format || null,
isPoolGroup: false,
@@ -920,8 +932,9 @@ const groupedTimeline = computed<NodeGroup[]>(() => {
return true
})
poolGroups.sort((a, b) => a.startIndex - b.startIndex)
return [...poolGroups, ...dedupedProviderGroups]
const allGroups = [...poolGroups, ...dedupedProviderGroups]
allGroups.sort((a, b) => a.startIndex - b.startIndex)
return allGroups
})
// 格式转换分界点索引(首个 hasConversion=true 的 group index