Merge pull request #1242 from Ethan0x0000/feat/anthropic-openai-endpoint-compat
支持 Anthropic Responses / Chat Completions 兼容端点并完善会话一致性与错误可观测性
This commit is contained in:
@@ -59,7 +59,28 @@
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div>
|
||||
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ detail.model || '—' }}
|
||||
<template v-if="hasModelMapping(detail)">
|
||||
<span class="font-mono">{{ detail.requested_model }}</span>
|
||||
<span class="mx-1 text-gray-400">→</span>
|
||||
<span class="font-mono text-primary-600 dark:text-primary-400">{{ detail.upstream_model }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ displayModel(detail) || '—' }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.inboundEndpoint') }}</div>
|
||||
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ detail.inbound_endpoint || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.upstreamEndpoint') }}</div>
|
||||
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ detail.upstream_endpoint || '—' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,6 +93,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.requestType') }}</div>
|
||||
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ formatRequestTypeLabel(detail.request_type) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.message') }}</div>
|
||||
<div class="mt-1 truncate text-sm font-medium text-gray-900 dark:text-white" :title="detail.message">
|
||||
@@ -213,6 +241,31 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
|
||||
return phase === 'upstream' && owner === 'provider'
|
||||
}
|
||||
|
||||
function formatRequestTypeLabel(type: number | null | undefined): string {
|
||||
switch (type) {
|
||||
case 1: return t('admin.ops.errorDetail.requestTypeSync')
|
||||
case 2: return t('admin.ops.errorDetail.requestTypeStream')
|
||||
case 3: return t('admin.ops.errorDetail.requestTypeWs')
|
||||
default: return t('admin.ops.errorDetail.requestTypeUnknown')
|
||||
}
|
||||
}
|
||||
|
||||
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<OpsErrorDetail[]>([])
|
||||
const correlatedUpstreamLoading = ref(false)
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
|
||||
{{ t('admin.ops.errorLog.type') }}
|
||||
</th>
|
||||
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
|
||||
{{ t('admin.ops.errorLog.endpoint') }}
|
||||
</th>
|
||||
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
|
||||
{{ t('admin.ops.errorLog.platform') }}
|
||||
</th>
|
||||
@@ -42,7 +45,7 @@
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-dark-700">
|
||||
<tr v-if="rows.length === 0">
|
||||
<td colspan="9" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500">
|
||||
<td colspan="10" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500">
|
||||
{{ t('admin.ops.errorLog.noErrors') }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -74,6 +77,18 @@
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Endpoint -->
|
||||
<td class="px-4 py-2">
|
||||
<div class="max-w-[160px]">
|
||||
<el-tooltip v-if="log.inbound_endpoint" :content="formatEndpointTooltip(log)" placement="top" :show-after="500">
|
||||
<span class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
|
||||
{{ log.inbound_endpoint }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-else class="text-xs text-gray-400">-</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Platform -->
|
||||
<td class="whitespace-nowrap px-4 py-2">
|
||||
<span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold uppercase text-gray-600 dark:bg-dark-700 dark:text-gray-300">
|
||||
@@ -83,11 +98,22 @@
|
||||
|
||||
<!-- Model -->
|
||||
<td class="px-4 py-2">
|
||||
<div class="max-w-[120px] truncate" :title="log.model">
|
||||
<span v-if="log.model" class="font-mono text-[11px] text-gray-700 dark:text-gray-300">
|
||||
{{ log.model }}
|
||||
</span>
|
||||
<span v-else class="text-xs text-gray-400">-</span>
|
||||
<div class="max-w-[160px]">
|
||||
<template v-if="hasModelMapping(log)">
|
||||
<el-tooltip :content="modelMappingTooltip(log)" placement="top" :show-after="500">
|
||||
<span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
|
||||
<span class="truncate">{{ log.requested_model }}</span>
|
||||
<span class="flex-shrink-0 text-gray-400">→</span>
|
||||
<span class="truncate text-primary-600 dark:text-primary-400">{{ log.upstream_model }}</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="displayModel(log)" class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300" :title="displayModel(log)">
|
||||
{{ displayModel(log) }}
|
||||
</span>
|
||||
<span v-else class="text-xs text-gray-400">-</span>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -138,6 +164,12 @@
|
||||
>
|
||||
{{ log.severity }}
|
||||
</span>
|
||||
<span
|
||||
v-if="log.request_type != null && log.request_type > 0"
|
||||
class="rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold text-gray-600 dark:bg-dark-700 dark:text-gray-300"
|
||||
>
|
||||
{{ formatRequestType(log.request_type) }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -193,6 +225,44 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
|
||||
return phase === 'upstream' && owner === 'provider'
|
||||
}
|
||||
|
||||
function formatEndpointTooltip(log: OpsErrorLog): string {
|
||||
const parts: string[] = []
|
||||
if (log.inbound_endpoint) parts.push(`Inbound: ${log.inbound_endpoint}`)
|
||||
if (log.upstream_endpoint) parts.push(`Upstream: ${log.upstream_endpoint}`)
|
||||
return parts.join('\n') || ''
|
||||
}
|
||||
|
||||
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 formatRequestType(type: number | null | undefined): string {
|
||||
switch (type) {
|
||||
case 1: return t('admin.ops.errorLog.requestTypeSync')
|
||||
case 2: return t('admin.ops.errorLog.requestTypeStream')
|
||||
case 3: return t('admin.ops.errorLog.requestTypeWs')
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
|
||||
const phase = String(log.phase || '').toLowerCase()
|
||||
const owner = String(log.error_owner || '').toLowerCase()
|
||||
@@ -263,4 +333,4 @@ function formatSmartMessage(msg: string): string {
|
||||
return msg.length > 200 ? msg.substring(0, 200) + '...' : msg
|
||||
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user