Expose local scheduling failures in usage UI

This commit is contained in:
MMEXA
2026-05-18 16:28:37 +00:00
parent d51b44d642
commit 4317ff78b1
15 changed files with 620 additions and 24 deletions
@@ -547,6 +547,38 @@
</Card>
</div>
<!-- Local Scheduling Failure State -->
<Card
v-else-if="schedulingFailureNotice"
class="border-red-200 dark:border-red-800"
>
<div class="p-4 space-y-2">
<div class="flex flex-wrap items-center gap-2">
<Badge variant="destructive">
调度失败
</Badge>
<h4 class="text-sm font-semibold text-red-950 dark:text-red-100">
{{ schedulingFailureNotice.title }}
</h4>
</div>
<p class="text-sm leading-6 text-red-900 dark:text-red-100">
{{ schedulingFailureNotice.message }}
</p>
<div
v-if="schedulingFailureNotice.meta.length > 0"
class="flex flex-wrap gap-1.5"
>
<span
v-for="item in schedulingFailureNotice.meta"
:key="item"
class="rounded-full border border-red-200 bg-white/70 px-2 py-0.5 text-[11px] font-mono text-red-700 dark:border-red-900 dark:bg-red-950/50 dark:text-red-200"
>
{{ item }}
</span>
</div>
</div>
</Card>
<!-- Empty State -->
<Card
v-else
@@ -575,6 +607,7 @@ import { parseApiError } from '@/utils/errorParser'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import { useDarkMode } from '@/composables/useDarkMode'
import { resolveTimelineFinalStatus } from '../utils/status'
import type { RequestSchedulingFailure } from '@/api/dashboard'
import {
buildPoolGroupVisibleAttempts,
buildPoolParticipatedCandidates,
@@ -637,6 +670,8 @@ const props = defineProps<{
usageData?: UsageData | null
/** 请求元数据(用于号池调度组装) */
requestMetadata?: Record<string, unknown> | null
/** 本地调度失败摘要;用于没有 trace candidate 时替代空态 */
schedulingFailure?: RequestSchedulingFailure | null
/** 已获取的追踪数据;传入时不再内部拉取 */
traceData?: RequestTrace | null
}>()
@@ -812,6 +847,62 @@ const computedFinalStatus = computed(() => {
})
})
const nonEmptyNoticeString = (value: string | null | undefined): string | null => {
const trimmed = value?.trim()
return trimmed ? trimmed : null
}
const uniqueNoticeMeta = (values: Array<string | null | undefined>): string[] => {
return Array.from(new Set(values.map(value => value?.trim()).filter((value): value is string => Boolean(value))))
}
const isUnknownProviderHint = (value: string | null): boolean => {
const normalized = value?.trim().toLowerCase()
return !normalized || ['unknown', 'unknow', 'pending'].includes(normalized)
}
const schedulingFailureProviderHint = (failure: RequestSchedulingFailure): string | null => {
const name = nonEmptyNoticeString(failure.provider_hint?.name)
if (name && !isUnknownProviderHint(name)) return name
const id = nonEmptyNoticeString(failure.provider_hint?.id)
if (id && !isUnknownProviderHint(id)) return id
return null
}
const schedulingFailureEndpointHint = (failure: RequestSchedulingFailure): string | null => {
return nonEmptyNoticeString(failure.endpoint_hint?.api_format)
?? nonEmptyNoticeString(failure.endpoint_hint?.id)
}
const schedulingFailureNotice = computed(() => {
const failure = props.schedulingFailure
if (!failure) return null
const title = nonEmptyNoticeString(failure.title) ?? '本地调度失败'
const message = nonEmptyNoticeString(failure.message)
?? nonEmptyNoticeString(failure.reason_summary)
?? nonEmptyNoticeString(failure.reason_label)
?? nonEmptyNoticeString(failure.reason)
?? '本地调度阶段没有选出可用上游提供商'
return {
title,
message,
meta: uniqueNoticeMeta([
schedulingFailureProviderHint(failure),
schedulingFailureEndpointHint(failure),
nonEmptyNoticeString(failure.requested_model),
nonEmptyNoticeString(failure.reason_summary),
nonEmptyNoticeString(failure.reason_label),
nonEmptyNoticeString(failure.reason),
typeof failure.status_code === 'number' ? `HTTP ${failure.status_code}` : null,
failure.no_upstream_attempt ? '未进入上游执行' : null,
]),
}
})
const compareBySchedulingOrder = (a: CandidateRecord, b: CandidateRecord): number => {
if (a.candidate_index !== b.candidate_index) {
return a.candidate_index - b.candidate_index