Skip to content

Commit c5aa96a

Browse files
committed
feat(frontend): display error observability fields in ops admin panel
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
1 parent d927c0e commit c5aa96a

3 files changed

Lines changed: 67 additions & 7 deletions

File tree

frontend/src/api/admin/ops.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,10 @@ export interface OpsErrorLog {
969969
client_ip?: string | null
970970
request_path?: string
971971
stream?: boolean
972+
973+
// Model mapping context for ops error observability
974+
requested_model?: string
975+
upstream_model?: string
972976
}
973977

974978
export interface OpsErrorDetail extends OpsErrorLog {

frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,14 @@
5959
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
6060
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div>
6161
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
62-
{{ detail.model || '—' }}
62+
<template v-if="hasModelMapping(detail)">
63+
<span class="font-mono">{{ detail.requested_model }}</span>
64+
<span class="mx-1 text-gray-400">→</span>
65+
<span class="font-mono text-primary-600 dark:text-primary-400">{{ detail.upstream_model }}</span>
66+
</template>
67+
<template v-else>
68+
{{ displayModel(detail) || '—' }}
69+
</template>
6370
</div>
6471
</div>
6572

@@ -213,6 +220,22 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
213220
return phase === 'upstream' && owner === 'provider'
214221
}
215222
223+
function hasModelMapping(d: OpsErrorDetail | null): boolean {
224+
if (!d) return false
225+
const requested = String(d.requested_model || '').trim()
226+
const upstream = String(d.upstream_model || '').trim()
227+
return !!requested && !!upstream && requested !== upstream
228+
}
229+
230+
function displayModel(d: OpsErrorDetail | null): string {
231+
if (!d) return ''
232+
const upstream = String(d.upstream_model || '').trim()
233+
if (upstream) return upstream
234+
const requested = String(d.requested_model || '').trim()
235+
if (requested) return requested
236+
return String(d.model || '').trim()
237+
}
238+
216239
const correlatedUpstream = ref<OpsErrorDetail[]>([])
217240
const correlatedUpstreamLoading = ref(false)
218241

frontend/src/views/admin/ops/components/OpsErrorLogTable.vue

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,22 @@
8383

8484
<!-- Model -->
8585
<td class="px-4 py-2">
86-
<div class="max-w-[120px] truncate" :title="log.model">
87-
<span v-if="log.model" class="font-mono text-[11px] text-gray-700 dark:text-gray-300">
88-
{{ log.model }}
89-
</span>
90-
<span v-else class="text-xs text-gray-400">-</span>
86+
<div class="max-w-[180px]">
87+
<template v-if="hasModelMapping(log)">
88+
<el-tooltip :content="modelMappingTooltip(log)" placement="top" :show-after="500">
89+
<span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
90+
<span class="truncate">{{ log.requested_model }}</span>
91+
<span class="flex-shrink-0 text-gray-400">→</span>
92+
<span class="truncate text-primary-600 dark:text-primary-400">{{ log.upstream_model }}</span>
93+
</span>
94+
</el-tooltip>
95+
</template>
96+
<template v-else>
97+
<span v-if="displayModel(log)" class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300" :title="displayModel(log)">
98+
{{ displayModel(log) }}
99+
</span>
100+
<span v-else class="text-xs text-gray-400">-</span>
101+
</template>
91102
</div>
92103
</td>
93104

@@ -193,6 +204,28 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
193204
return phase === 'upstream' && owner === 'provider'
194205
}
195206
207+
function hasModelMapping(log: OpsErrorLog): boolean {
208+
const requested = String(log.requested_model || '').trim()
209+
const upstream = String(log.upstream_model || '').trim()
210+
return !!requested && !!upstream && requested !== upstream
211+
}
212+
213+
function modelMappingTooltip(log: OpsErrorLog): string {
214+
const requested = String(log.requested_model || '').trim()
215+
const upstream = String(log.upstream_model || '').trim()
216+
if (!requested && !upstream) return ''
217+
if (requested && upstream) return `${requested} → ${upstream}`
218+
return upstream || requested
219+
}
220+
221+
function displayModel(log: OpsErrorLog): string {
222+
const upstream = String(log.upstream_model || '').trim()
223+
if (upstream) return upstream
224+
const requested = String(log.requested_model || '').trim()
225+
if (requested) return requested
226+
return String(log.model || '').trim()
227+
}
228+
196229
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
197230
const phase = String(log.phase || '').toLowerCase()
198231
const owner = String(log.error_owner || '').toLowerCase()
@@ -263,4 +296,4 @@ function formatSmartMessage(msg: string): string {
263296
return msg.length > 200 ? msg.substring(0, 200) + '...' : msg
264297
265298
}
266-
</script>
299+
</script>

0 commit comments

Comments
 (0)