feat(studio): 补齐剩余工作台聚合接口与真实对接

This commit is contained in:
2026-06-01 05:28:11 +08:00
parent 8f7ffd6cc9
commit ebe0fc5a12
35 changed files with 2092 additions and 123 deletions

View File

@@ -1,7 +1,72 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { ChatDotRound, Coin, Timer } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { chatMessages, citations, traceSteps } from '@/data/studioMock';
import type { AgentMessageRecord, AgentWorkspace } from '@/api/agent';
import { appendAgentMessage, getAgentWorkspace } from '@/api/agent';
const loading = ref(false);
const agentId = ref('1001');
const workspace = ref<AgentWorkspace | null>(null);
const draftMessage = ref('');
const citations = computed(() => {
return (workspace.value?.messages ?? [])
.filter((message) => message.citationJson?.includes('chunkId'))
.map((message, index) => ({
key: `${message.id || index}`,
title: `引用 ${index + 1}`,
text: message.citationJson ?? '',
}));
});
const latestAssistantMessage = computed(() => {
const assistantMessages = (workspace.value?.messages ?? []).filter((item) => item.role === 'assistant');
return assistantMessages.length > 0 ? assistantMessages[assistantMessages.length - 1] : null;
});
function roleLabel(role: AgentMessageRecord['role']) {
if (role === 'user') {
return '用户';
}
if (role === 'assistant') {
return 'Agent';
}
return '系统';
}
async function loadWorkspace() {
loading.value = true;
try {
const response = await getAgentWorkspace(agentId.value);
workspace.value = response.data;
} finally {
loading.value = false;
}
}
async function sendMessage() {
if (!workspace.value?.sessionId || !draftMessage.value.trim()) {
return;
}
await appendAgentMessage(workspace.value.sessionId, {
role: 'user',
content: draftMessage.value.trim(),
});
ElMessage.success('调试消息已写入会话');
draftMessage.value = '';
await loadWorkspace();
}
function formatCost(tokens?: number) {
if (!tokens) {
return '¥0.000';
}
return `¥${(tokens / 100000).toFixed(3)}`;
}
onMounted(loadWorkspace);
</script>
<template>
@@ -11,38 +76,47 @@ import { chatMessages, citations, traceSteps } from '@/data/studioMock';
<p class="studio-kicker">AgentWorkspaceView</p>
<h1>Agent 对话调试</h1>
</div>
<el-button type="primary">发布 Agent</el-button>
<el-button type="primary">{{ workspace?.status || 'Draft' }}</el-button>
</header>
<div class="agent-layout">
<div class="agent-layout" v-loading="loading">
<section class="studio-panel chat-panel">
<div class="panel-heading">
<div>
<h2>售前问答 Agent</h2>
<span>POST /api/agents/1001/runs</span>
<h2>{{ workspace?.agentName || 'Agent 工作台' }}</h2>
<span>GET /api/agent-sessions/workspace</span>
</div>
<el-tag>Draft</el-tag>
<el-tag>{{ workspace?.sessionStatus || workspace?.status || 'DRAFT' }}</el-tag>
</div>
<div class="message-list">
<article v-for="message in chatMessages" :key="message.content" :class="message.role">
<strong>{{ message.role === 'user' ? '用户' : 'Agent' }}</strong>
<article
v-for="message in workspace?.messages ?? []"
:key="message.id || message.content"
:class="message.role"
:data-test="`agent-message-${message.role}`"
>
<strong>{{ roleLabel(message.role) }}</strong>
<p>{{ message.content }}</p>
</article>
</div>
<div class="chat-composer">
<span>输入调试问题运行会写入 agent_session / agent_message 草案</span>
<el-button type="primary"><el-icon><ChatDotRound /></el-icon> 发送</el-button>
<span>输入调试问题写入当前 agent_session / agent_message</span>
<input v-model="draftMessage" data-test="agent-message-input" type="text" />
<el-button data-test="agent-send-message" type="primary" @click="sendMessage">
<el-icon><ChatDotRound /></el-icon>
发送
</el-button>
</div>
</section>
<aside class="studio-panel citation-panel">
<div class="panel-heading compact">
<h2>引用切片</h2>
<span>3 个来源</span>
<span>{{ workspace?.citationCount || 0 }} 个来源</span>
</div>
<article v-for="citation in citations" :key="citation.title" class="citation-card">
<article v-for="citation in citations" :key="citation.key" class="citation-card" data-test="agent-citation-card">
<strong>{{ citation.title }}</strong>
<el-tag type="success">score {{ citation.score }}</el-tag>
<el-tag type="success">引用</el-tag>
<p>{{ citation.text }}</p>
</article>
</aside>
@@ -50,17 +124,17 @@ import { chatMessages, citations, traceSteps } from '@/data/studioMock';
<aside class="studio-panel run-inspector">
<div class="panel-heading compact">
<h2>运行追踪</h2>
<span>modelRequestId: f4215d</span>
<span data-test="agent-latest-request-id">requestId: {{ workspace?.latestRequestId || '-' }}</span>
</div>
<div class="metric-mini">
<span><el-icon><Timer /></el-icon> 1.42s</span>
<span><el-icon><Coin /></el-icon> ¥0.018</span>
<span>1,248 tokens</span>
<span><el-icon><Timer /></el-icon> {{ workspace?.sessionStatus || '-' }}</span>
<span><el-icon><Coin /></el-icon> {{ formatCost(workspace?.totalTokens) }}</span>
<span>{{ workspace?.totalTokens || 0 }} tokens</span>
</div>
<ol class="log-list">
<li v-for="step in traceSteps" :key="step.node">
<time>{{ step.duration }}</time>
<span>{{ step.node }} · {{ step.output }}</span>
<li v-if="latestAssistantMessage">
<time>{{ workspace?.sessionCode || '-' }}</time>
<span>{{ latestAssistantMessage.content }}</span>
</li>
</ol>
</aside>