|
|
@@ -0,0 +1,360 @@
|
|
|
+<template>
|
|
|
+ <div class="model-log-container">
|
|
|
+ <div class="header">
|
|
|
+ <h1>Agent 大模型调用日志</h1>
|
|
|
+ <div class="header-actions">
|
|
|
+ <el-button type="primary" @click="refreshLogs">
|
|
|
+ <el-icon>
|
|
|
+ <RefreshRight />
|
|
|
+ </el-icon>
|
|
|
+ 刷新
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stats-cards">
|
|
|
+ <div class="stat-card">
|
|
|
+ <div class="stat-icon success">
|
|
|
+ <SvgIcon name="check-circle" size="30" />
|
|
|
+ </div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">2,345</div>
|
|
|
+ <div class="stat-label">成功调用</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card">
|
|
|
+ <div class="stat-icon error">
|
|
|
+ <SvgIcon name="x-circle" size="30" />
|
|
|
+ </div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">12</div>
|
|
|
+ <div class="stat-label">失败调用</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card">
|
|
|
+ <div class="stat-icon">
|
|
|
+ <SvgIcon name="zap" size="30" />
|
|
|
+ </div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">1.2s</div>
|
|
|
+ <div class="stat-label">平均响应时间</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card">
|
|
|
+ <div class="stat-icon">
|
|
|
+ <SvgIcon name="dollar-sign" size="30" />
|
|
|
+ </div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">$45.67</div>
|
|
|
+ <div class="stat-label">今日消费</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="logs-table">
|
|
|
+ <el-table :data="modelLogs" stripe style="width: 100%">
|
|
|
+ <el-table-column prop="timestamp" label="时间" />
|
|
|
+ <el-table-column prop="model" label="模型" />
|
|
|
+ <el-table-column prop="workflowName" label="工作流" />
|
|
|
+ <el-table-column label="状态">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="scope.row.status === 'success' ? 'success' : 'danger'">
|
|
|
+ {{ scope.row.status === 'success' ? '成功' : '失败' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="tokens" label="Token 数" />
|
|
|
+ <el-table-column prop="cost" label="费用" />
|
|
|
+ <el-table-column prop="responseTime" label="响应时间" />
|
|
|
+ <el-table-column label="操作">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button size="small" @click="viewDetails(scope.row)">查看</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 详情对话框 -->
|
|
|
+ <el-dialog v-model="dialogVisible" title="调用详情" width="800px">
|
|
|
+ <div v-if="selectedLog" class="log-detail">
|
|
|
+ <div class="detail-section">
|
|
|
+ <h3>基本信息</h3>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">时间:</span>
|
|
|
+ <span class="value">{{ selectedLog.timestamp }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">模型:</span>
|
|
|
+ <span class="value">{{ selectedLog.model }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">工作流:</span>
|
|
|
+ <span class="value">{{ selectedLog.workflowName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">状态:</span>
|
|
|
+ <el-tag :type="selectedLog.status === 'success' ? 'success' : 'danger'">
|
|
|
+ {{ selectedLog.status === 'success' ? '成功' : '失败' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="detail-section">
|
|
|
+ <h3>请求内容</h3>
|
|
|
+ <div class="code-block">
|
|
|
+ <pre>{{ selectedLog.prompt }}</pre>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="detail-section">
|
|
|
+ <h3>响应内容</h3>
|
|
|
+ <div class="code-block">
|
|
|
+ <pre>{{ selectedLog.response }}</pre>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="detail-section">
|
|
|
+ <h3>使用统计</h3>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">输入 Token:</span>
|
|
|
+ <span class="value">{{ selectedLog.inputTokens }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">输出 Token:</span>
|
|
|
+ <span class="value">{{ selectedLog.outputTokens }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">总计:</span>
|
|
|
+ <span class="value">{{ selectedLog.tokens }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="label">费用:</span>
|
|
|
+ <span class="value">{{ selectedLog.cost }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { RefreshRight } from '@element-plus/icons-vue'
|
|
|
+import { ref } from 'vue'
|
|
|
+
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const selectedLog = ref<any>(null)
|
|
|
+
|
|
|
+const modelLogs = ref([
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ timestamp: '2026-01-28 14:25:33',
|
|
|
+ model: 'GPT-4',
|
|
|
+ workflowName: 'AI 文章生成',
|
|
|
+ status: 'success',
|
|
|
+ tokens: '1,250',
|
|
|
+ cost: '$0.025',
|
|
|
+ responseTime: '1.8s',
|
|
|
+ inputTokens: 450,
|
|
|
+ outputTokens: 800,
|
|
|
+ prompt: '请帮我生成一篇关于人工智能发展的文章...',
|
|
|
+ response: '人工智能的发展历程可以追溯到...'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ timestamp: '2026-01-28 14:23:15',
|
|
|
+ model: 'Claude-3',
|
|
|
+ workflowName: '智能客服',
|
|
|
+ status: 'success',
|
|
|
+ tokens: '680',
|
|
|
+ cost: '$0.014',
|
|
|
+ responseTime: '0.9s',
|
|
|
+ inputTokens: 280,
|
|
|
+ outputTokens: 400,
|
|
|
+ prompt: '用户询问: 如何退货?',
|
|
|
+ response: '您好,退货流程如下...'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ timestamp: '2026-01-28 14:20:42',
|
|
|
+ model: 'GPT-4',
|
|
|
+ workflowName: '代码审查助手',
|
|
|
+ status: 'success',
|
|
|
+ tokens: '2,100',
|
|
|
+ cost: '$0.042',
|
|
|
+ responseTime: '2.3s',
|
|
|
+ inputTokens: 1200,
|
|
|
+ outputTokens: 900,
|
|
|
+ prompt: '请审查以下代码...',
|
|
|
+ response: '代码审查结果: 整体结构良好...'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ timestamp: '2026-01-28 14:18:05',
|
|
|
+ model: 'GPT-3.5',
|
|
|
+ workflowName: '数据分析',
|
|
|
+ status: 'failed',
|
|
|
+ tokens: '0',
|
|
|
+ cost: '$0.000',
|
|
|
+ responseTime: '5.0s',
|
|
|
+ inputTokens: 0,
|
|
|
+ outputTokens: 0,
|
|
|
+ prompt: '分析销售数据...',
|
|
|
+ response: 'Error: API rate limit exceeded'
|
|
|
+ }
|
|
|
+])
|
|
|
+
|
|
|
+const refreshLogs = () => {
|
|
|
+ console.log('Refreshing model logs...')
|
|
|
+}
|
|
|
+
|
|
|
+const viewDetails = (log: any) => {
|
|
|
+ selectedLog.value = log
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.model-log-container {
|
|
|
+ max-width: 1400px;
|
|
|
+ margin: 0 auto;
|
|
|
+ padding: 20px;
|
|
|
+
|
|
|
+ .header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 24px;
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-cards {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 24px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+
|
|
|
+ .stat-icon {
|
|
|
+ width: 56px;
|
|
|
+ height: 56px;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 28px;
|
|
|
+
|
|
|
+ &.success {
|
|
|
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.error {
|
|
|
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-info {
|
|
|
+ .stat-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .logs-table {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .log-detail {
|
|
|
+ .detail-section {
|
|
|
+ margin-bottom: 24px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ h3 {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+
|
|
|
+ .detail-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .code-block {
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px;
|
|
|
+ max-height: 300px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ pre {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.6;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-wrap: break-word;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|