From c5aa96a3aab3648b2c3c0b73f8fb9ed4270490cf Mon Sep 17 00:00:00 2001 From: Ethan0x0000 <3352979663@qq.com> Date: Mon, 23 Mar 2026 16:24:59 +0800 Subject: [PATCH] feat(frontend): display error observability fields in ops admin panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show endpoint, model mapping, and request type in the ops error log table and detail modal: - Endpoint column with inbound/upstream tooltip - Model column showing requested→upstream mapping with arrow - Request type badge (sync/stream/ws) in status column - New detail cards for inbound endpoint, upstream endpoint, request type --- frontend/src/api/admin/ops.ts | 4 ++ .../ops/components/OpsErrorDetailModal.vue | 25 ++++++++++- .../admin/ops/components/OpsErrorLogTable.vue | 45 ++++++++++++++++--- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/frontend/src/api/admin/ops.ts b/frontend/src/api/admin/ops.ts index 64f6a6d0..0541bb59 100644 --- a/frontend/src/api/admin/ops.ts +++ b/frontend/src/api/admin/ops.ts @@ -969,6 +969,10 @@ export interface OpsErrorLog { client_ip?: string | null request_path?: string stream?: boolean + + // Model mapping context for ops error observability + requested_model?: string + upstream_model?: string } export interface OpsErrorDetail extends OpsErrorLog { diff --git a/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue b/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue index a7edff96..0817f239 100644 --- a/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue +++ b/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue @@ -59,7 +59,14 @@
{{ t('admin.ops.errorDetail.model') }}
- {{ detail.model || '—' }} + +
@@ -213,6 +220,22 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean { return phase === 'upstream' && owner === 'provider' } +function hasModelMapping(d: OpsErrorDetail | null): boolean { + if (!d) return false + const requested = String(d.requested_model || '').trim() + const upstream = String(d.upstream_model || '').trim() + return !!requested && !!upstream && requested !== upstream +} + +function displayModel(d: OpsErrorDetail | null): string { + if (!d) return '' + const upstream = String(d.upstream_model || '').trim() + if (upstream) return upstream + const requested = String(d.requested_model || '').trim() + if (requested) return requested + return String(d.model || '').trim() +} + const correlatedUpstream = ref([]) const correlatedUpstreamLoading = ref(false) diff --git a/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue b/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue index 28868552..58846db6 100644 --- a/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue +++ b/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue @@ -83,11 +83,22 @@ -
- - {{ log.model }} - - - +
+ +
@@ -193,6 +204,28 @@ function isUpstreamRow(log: OpsErrorLog): boolean { return phase === 'upstream' && owner === 'provider' } +function hasModelMapping(log: OpsErrorLog): boolean { + const requested = String(log.requested_model || '').trim() + const upstream = String(log.upstream_model || '').trim() + return !!requested && !!upstream && requested !== upstream +} + +function modelMappingTooltip(log: OpsErrorLog): string { + const requested = String(log.requested_model || '').trim() + const upstream = String(log.upstream_model || '').trim() + if (!requested && !upstream) return '' + if (requested && upstream) return `${requested} → ${upstream}` + return upstream || requested +} + +function displayModel(log: OpsErrorLog): string { + const upstream = String(log.upstream_model || '').trim() + if (upstream) return upstream + const requested = String(log.requested_model || '').trim() + if (requested) return requested + return String(log.model || '').trim() +} + function getTypeBadge(log: OpsErrorLog): { label: string; className: string } { const phase = String(log.phase || '').toLowerCase() const owner = String(log.error_owner || '').toLowerCase() @@ -263,4 +296,4 @@ function formatSmartMessage(msg: string): string { return msg.length > 200 ? msg.substring(0, 200) + '...' : msg } - \ No newline at end of file +