Files
PlantAI-rag/frontend/agent-status-component.html
T
2026-07-10 17:59:59 +08:00

450 lines
11 KiB
HTML
Executable File

<!--
bbav5前端Agent状态组件
用于显示RAG查询状态和知识图谱可视化
-->
<template id="agent-status-template">
<div class="agent-status-panel">
<!-- Agent状态显示 -->
<div class="status-header">
<h4>🤖 Agent状态</h4>
<span :class="['status-badge', status]">{{ statusText }}</span>
</div>
<!-- RAG查询状态 -->
<div class="rag-status" v-if="ragQuery">
<div class="query-info">
<div class="query-text">{{ ragQuery }}</div>
<div class="query-progress">
<el-progress :percentage="progress" :status="progressStatus"></el-progress>
</div>
</div>
<!-- 检索步骤 -->
<div class="retrieval-steps">
<div v-for="(step, idx) in steps" :key="idx"
:class="['step', step.status]">
<span class="step-icon">{{ step.icon }}</span>
<span class="step-name">{{ step.name }}</span>
<span class="step-time" v-if="step.time">{{ step.time }}ms</span>
</div>
</div>
<!-- Self-RAG反思状态 -->
<div class="reflection-panel" v-if="reflection">
<div class="reflection-title">📝 Self-RAG反思</div>
<div class="reflection-content">
<div>迭代: {{ reflection.iteration }}/{{ reflection.maxIterations }}</div>
<div>原因: {{ reflection.reason }}</div>
<div v-if="reflection.rewrittenQuery">
重写查询: {{ reflection.rewrittenQuery }}
</div>
</div>
</div>
<!-- MultiHopRAG状态 -->
<div class="multihop-panel" v-if="multiHop">
<div class="multihop-title">🔀 MultiHopRAG</div>
<div class="multihop-content">
<div>跳数: {{ multiHop.hops }}</div>
<div>来源: {{ multiHop.sources.join(', ') }}</div>
</div>
</div>
</div>
<!-- 知识图谱可视化 -->
<div class="kg-visualization" v-if="showKG">
<div class="kg-header">
<h5>🧬 知识图谱</h5>
<el-button size="small" @click="refreshKG">刷新</el-button>
</div>
<div id="kg-chart" class="kg-chart"></div>
<div class="kg-stats">
节点: {{ kgStats.nodes }} | 关系: {{ kgStats.edges }}
</div>
</div>
<!-- 结果展示 -->
<div class="results-panel" v-if="results.length > 0">
<h5>📄 检索结果</h5>
<div v-for="(result, idx) in results" :key="idx" class="result-item">
<div class="result-header">
<span class="result-doc">{{ result.doc_id }}</span>
<span class="result-score">Score: {{ result.score.toFixed(3) }}</span>
</div>
<div class="result-content">{{ result.content }}</div>
</div>
</div>
</div>
</template>
<script>
// Agent状态组件
const AgentStatusComponent = {
template: '#agent-status-template',
props: {
initialQuery: String,
autoStart: Boolean
},
data() {
return {
status: 'idle', // idle, analyzing, retrieving, reflecting, generating, completed, error
ragQuery: '',
progress: 0,
steps: [
{ name: '查询分析', icon: '🔍', status: 'pending', time: null },
{ name: '文档检索', icon: '📚', status: 'pending', time: null },
{ name: '结果评估', icon: '✓', status: 'pending', time: null },
{ name: '生成回答', icon: '✍', status: 'pending', time: null }
],
reflection: null,
multiHop: null,
results: [],
showKG: true,
kgStats: { nodes: 0, edges: 0 },
kgChart: null
}
},
computed: {
statusText() {
const statusMap = {
'idle': '待机',
'analyzing': '分析中',
'retrieving': '检索中',
'reflecting': '反思中',
'generating': '生成中',
'completed': '完成',
'error': '错误'
}
return statusMap[this.status] || this.status
},
progressStatus() {
if (this.status === 'error') return 'exception'
if (this.status === 'completed') return 'success'
return ''
}
},
mounted() {
if (this.initialQuery) {
this.ragQuery = this.initialQuery
}
if (this.autoStart && this.ragQuery) {
this.startRAG()
}
this.initKGChart()
},
methods: {
async startRAG() {
this.status = 'analyzing'
this.progress = 10
this.updateStep(0, 'running')
try {
// 步骤1: 查询分析
await this.simulateStep(0, 500)
this.status = 'retrieving'
this.progress = 30
// 步骤2: 文档检索
this.updateStep(1, 'running')
const searchResults = await this.searchDocuments()
this.updateStep(1, 'completed', searchResults.time)
this.results = searchResults.results
this.progress = 60
// 步骤3: 结果评估 (Self-RAG)
this.status = 'reflecting'
this.updateStep(2, 'running')
const reflection = await this.evaluateResults()
this.reflection = reflection
this.updateStep(2, 'completed', reflection.time)
this.progress = 80
// 如果需要重写,执行MultiHopRAG
if (reflection.needsRewrite) {
this.status = 'retrieving'
const multiHopResults = await this.multiHopSearch(reflection.rewrittenQuery)
this.multiHop = multiHopResults
this.results = [...this.results, ...multiHopResults.results]
}
// 步骤4: 生成回答
this.status = 'generating'
this.updateStep(3, 'running')
await this.simulateStep(3, 800)
this.updateStep(3, 'completed', 800)
this.progress = 100
this.status = 'completed'
// 更新知识图谱
this.updateKG()
} catch (error) {
this.status = 'error'
console.error('RAG查询失败:', error)
}
},
async searchDocuments() {
// 调用API进行文档检索
const startTime = Date.now()
try {
const response = await fetch('/api/v1/rag/advanced/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: this.ragQuery,
use_agentic: true,
enable_self_rag: true,
enable_multi_hop: true
})
})
const data = await response.json()
return {
results: data.sources || [],
time: Date.now() - startTime
}
} catch (error) {
console.error('检索失败:', error)
return { results: [], time: Date.now() - startTime }
}
},
async evaluateResults() {
// 模拟结果评估
const startTime = Date.now()
// 模拟Self-RAG反思
await new Promise(resolve => setTimeout(resolve, 300))
return {
iteration: 1,
maxIterations: 3,
reason: '检索结果充分',
needsRewrite: false,
rewrittenQuery: null,
time: Date.now() - startTime
}
},
async multiHopSearch(query) {
// 模拟多跳检索
const startTime = Date.now()
await new Promise(resolve => setTimeout(resolve, 600))
return {
hops: 2,
sources: ['Doc1', 'Doc2', 'Doc3'],
results: [],
time: Date.now() - startTime
}
},
async simulateStep(stepIndex, duration) {
await new Promise(resolve => setTimeout(resolve, duration))
},
updateStep(index, status, time = null) {
this.steps[index].status = status
if (time !== null) {
this.steps[index].time = time
}
},
initKGChart() {
// 初始化ECharts知识图谱
if (typeof echarts !== 'undefined') {
this.kgChart = echarts.init(document.getElementById('kg-chart'))
this.loadKGData()
}
},
async loadKGData() {
try {
const response = await fetch('/api/v1/kg/graph')
const data = await response.json()
this.kgStats = {
nodes: data.nodes?.length || 0,
edges: data.edges?.length || 0
}
this.renderKG(data.nodes || [], data.edges || [])
} catch (error) {
console.error('加载知识图谱失败:', error)
}
},
renderKG(nodes, edges) {
if (!this.kgChart) return
const option = {
tooltip: {},
series: [{
type: 'graph',
layout: 'force',
data: nodes.map(n => ({
id: n.id,
name: n.name || n.id,
symbolSize: n.degree || 20,
category: n.category || 'default'
})),
links: edges.map(e => ({
source: e.source,
target: e.target,
label: { show: true, formatter: e.relation }
})),
roam: true,
label: { show: true },
force: { repulsion: 100 }
}]
}
this.kgChart.setOption(option)
},
refreshKG() {
this.loadKGData()
},
updateKG() {
// 根据检索结果更新知识图谱
this.loadKGData()
}
}
}
</script>
<style scoped>
.agent-status-panel {
padding: 15px;
background: #f5f7fa;
border-radius: 8px;
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.status-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
}
.status-badge.idle { background: #909399; color: white; }
.status-badge.analyzing { background: #409EFF; color: white; }
.status-badge.retrieving { background: #E6A23C; color: white; }
.status-badge.reflecting { background: #67C23A; color: white; }
.status-badge.generating { background: #8E44AD; color: white; }
.status-badge.completed { background: #67C23A; color: white; }
.status-badge.error { background: #F56C6C; color: white; }
.rag-status {
background: white;
padding: 15px;
border-radius: 6px;
margin-bottom: 15px;
}
.query-text {
font-weight: bold;
margin-bottom: 10px;
color: #303133;
}
.retrieval-steps {
display: flex;
gap: 10px;
margin: 15px 0;
}
.step {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 12px;
border-radius: 4px;
background: #f0f0f0;
font-size: 12px;
}
.step.pending { opacity: 0.5; }
.step.running { background: #ecf5ff; color: #409EFF; }
.step.completed { background: #f0f9eb; color: #67C23A; }
.reflection-panel, .multihop-panel {
margin-top: 15px;
padding: 10px;
background: #fdf6ec;
border-radius: 4px;
font-size: 12px;
}
.reflection-title, .multihop-title {
font-weight: bold;
margin-bottom: 5px;
}
.kg-visualization {
background: white;
padding: 15px;
border-radius: 6px;
margin-bottom: 15px;
}
.kg-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.kg-chart {
height: 300px;
background: #f5f7fa;
border-radius: 4px;
}
.kg-stats {
margin-top: 10px;
font-size: 12px;
color: #666;
}
.results-panel {
background: white;
padding: 15px;
border-radius: 6px;
}
.result-item {
margin-bottom: 10px;
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
}
.result-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 12px;
}
.result-doc { color: #409EFF; }
.result-score { color: #67C23A; }
.result-content {
font-size: 12px;
color: #666;
line-height: 1.5;
}
</style>