feat: 新增 JsonViewer 和 AiCallLogDetailDialog 组件
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="调用详情"
|
||||
width="800px"
|
||||
:close-on-click-modal="true"
|
||||
:close-on-press-escape="true"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="log" class="detail-dialog">
|
||||
<div class="detail-section">
|
||||
<div class="section-title">基本信息</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">调用时间</span>
|
||||
<span class="info-value">{{ log.createTime }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">场景</span>
|
||||
<span class="info-value">{{ log.sceneCode || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">服务商</span>
|
||||
<span class="info-value">{{ log.providerCode || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">接口</span>
|
||||
<span class="info-value">{{ log.endpointCode || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">状态</span>
|
||||
<span class="info-value">
|
||||
<el-tag :type="statusType(log.status)" size="small" effect="plain">
|
||||
{{ statusLabel(log.status) }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">首字耗时</span>
|
||||
<span class="info-value">{{ formatMs(log.firstTokenMs) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">总耗时</span>
|
||||
<span class="info-value">{{ formatMs(log.durationMs) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">片段数</span>
|
||||
<span class="info-value">{{ log.streamChunks ?? '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">用户 ID</span>
|
||||
<span class="info-value">{{ log.userId || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">请求 ID</span>
|
||||
<span class="info-value">{{ log.requestId || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<JsonViewer title="入参" :data="log.inputText" />
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<JsonViewer title="出参" :data="log.outputText" />
|
||||
</div>
|
||||
|
||||
<div v-if="log.status === 'failed'" class="detail-section">
|
||||
<div class="section-title">错误信息</div>
|
||||
<div class="error-box">
|
||||
{{ log.errorCode }}: {{ log.errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import JsonViewer from '@/components/JsonViewer.vue'
|
||||
import type { AiCallLog } from '@/types/aiconfig'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
log: AiCallLog | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
function statusType(status?: string) {
|
||||
if (status === 'success') return 'success'
|
||||
if (status === 'failed') return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
function statusLabel(status?: string) {
|
||||
const map: Record<string, string> = { running: '运行中', success: '成功', failed: '失败' }
|
||||
return status ? map[status] || status : '-'
|
||||
}
|
||||
|
||||
function formatMs(ms?: number) {
|
||||
if (ms == null) return '-'
|
||||
return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.detail-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.section-title {
|
||||
color: rgba(226, 232, 240, 0.85);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px 16px;
|
||||
}
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.info-label {
|
||||
color: var(--ls-text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.info-value {
|
||||
color: rgba(226, 232, 240, 0.92);
|
||||
font-size: 13px;
|
||||
}
|
||||
.error-box {
|
||||
padding: 12px;
|
||||
color: #f56c6c;
|
||||
font-size: 13px;
|
||||
background: rgba(245, 108, 108, 0.08);
|
||||
border: 1px solid rgba(245, 108, 108, 0.2);
|
||||
border-radius: 6px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.info-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user