411 lines
9.1 KiB
HTML
411 lines
9.1 KiB
HTML
<!-- Agent状态显示组件 - 可嵌入index.html -->
|
|||
|
|
<template id="agent-status-component">
|
||
|
|
<div class="agent-status-panel">
|
||
|
|
<div class="agent-header">
|
||
|
|
<h3>🤖 Agent 智能助手</h3>
|
||
|
|
<span class="agent-mode">{{ currentMode }}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="agent-messages" ref="messageContainer">
|
||
|
|
<div v-for="(msg, index) in messages" :key="index"
|
||
|
|
:class="['message', msg.role]">
|
||
|
|
<div class="message-role">{{ getRoleLabel(msg.role) }}</div>
|
||
|
|
<div class="message-content" v-html="formatMessage(msg.content)"></div>
|
||
|
|
<div class="message-time">{{ formatTime(msg.timestamp) }}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="agent-input">
|
||
|
|
<textarea v-model="userInput"
|
||
|
|
placeholder="输入您的问题,例如:分析SPL基因家族"
|
||
|
|
@keydown.enter.prevent="sendMessage"
|
||
|
|
rows="3"></textarea>
|
||
|
|
<button @click="sendMessage" :disabled="isProcessing">
|
||
|
|
{{ isProcessing ? '处理中...' : '发送' }}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="agent-tasks" v-if="tasks.length > 0">
|
||
|
|
<h4>📋 任务列表</h4>
|
||
|
|
<div v-for="task in tasks" :key="task.task_id"
|
||
|
|
:class="['task-item', task.status]"
|
||
|
|
@click="selectTask(task)">
|
||
|
|
<span class="task-status">{{ getStatusIcon(task.status) }}</span>
|
||
|
|
<span class="task-message">{{ task.message }}</span>
|
||
|
|
<span class="task-progress">{{ task.progress }}%</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
// Agent状态组件
|
||
|
|
const AgentStatusComponent = {
|
||
|
|
template: '#agent-status-component',
|
||
|
|
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
messages: [],
|
||
|
|
userInput: '',
|
||
|
|
isProcessing: false,
|
||
|
|
currentMode: '待机',
|
||
|
|
sessionId: '',
|
||
|
|
tasks: [],
|
||
|
|
currentTask: null,
|
||
|
|
pollingInterval: null
|
||
|
|
};
|
||
|
|
},
|
||
|
|
|
||
|
|
mounted() {
|
||
|
|
this.sessionId = 'session_' + Date.now();
|
||
|
|
this.loadTasks();
|
||
|
|
this.startPolling();
|
||
|
|
},
|
||
|
|
|
||
|
|
beforeUnmount() {
|
||
|
|
this.stopPolling();
|
||
|
|
},
|
||
|
|
|
||
|
|
methods: {
|
||
|
|
async sendMessage() {
|
||
|
|
if (!this.userInput.trim() || this.isProcessing) return;
|
||
|
|
|
||
|
|
const message = this.userInput.trim();
|
||
|
|
this.userInput = '';
|
||
|
|
this.isProcessing = true;
|
||
|
|
|
||
|
|
// 添加用户消息
|
||
|
|
this.addMessage('user', message);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 调用Agent执行API
|
||
|
|
const response = await fetch('/api/agent/execute', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({
|
||
|
|
message: message,
|
||
|
|
session_id: this.sessionId,
|
||
|
|
model: 'kimi'
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (data.success) {
|
||
|
|
this.currentTask = data.task_id;
|
||
|
|
this.addMessage('system', `任务已提交: ${data.task_id}`);
|
||
|
|
this.loadTasks();
|
||
|
|
} else {
|
||
|
|
this.addMessage('error', data.message || '提交失败');
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
this.addMessage('error', `请求失败: ${error.message}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.isProcessing = false;
|
||
|
|
},
|
||
|
|
|
||
|
|
async loadTasks() {
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/agent/tasks');
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (data.success) {
|
||
|
|
this.tasks = data.tasks;
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('加载任务失败:', error);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
async checkTaskStatus(taskId) {
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/agent/status/${taskId}`);
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (data.success && data.task) {
|
||
|
|
const task = data.task;
|
||
|
|
|
||
|
|
// 更新任务状态
|
||
|
|
const taskIndex = this.tasks.findIndex(t => t.task_id === taskId);
|
||
|
|
if (taskIndex >= 0) {
|
||
|
|
this.tasks[taskIndex] = { ...this.tasks[taskIndex], ...task };
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果任务完成,显示结果
|
||
|
|
if (task.status === 'completed' && task.result) {
|
||
|
|
this.addMessage('assistant', task.result.response || '任务完成');
|
||
|
|
|
||
|
|
// 显示执行结果摘要
|
||
|
|
if (task.result.execution_result) {
|
||
|
|
const execResult = task.result.execution_result;
|
||
|
|
if (execResult.status === 'success') {
|
||
|
|
const manifest = execResult.manifest || {};
|
||
|
|
const metrics = manifest.metrics || {};
|
||
|
|
this.addMessage('system',
|
||
|
|
`✅ 分析完成! 找到 ${metrics.final_members || 0} 个基因成员`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (task.status === 'failed') {
|
||
|
|
this.addMessage('error', task.result?.response || '任务失败');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('检查任务状态失败:', error);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
selectTask(task) {
|
||
|
|
this.currentTask = task.task_id;
|
||
|
|
this.checkTaskStatus(task.task_id);
|
||
|
|
},
|
||
|
|
|
||
|
|
startPolling() {
|
||
|
|
this.pollingInterval = setInterval(() => {
|
||
|
|
if (this.currentTask) {
|
||
|
|
this.checkTaskStatus(this.currentTask);
|
||
|
|
}
|
||
|
|
this.loadTasks();
|
||
|
|
}, 3000); // 每3秒轮询一次
|
||
|
|
},
|
||
|
|
|
||
|
|
stopPolling() {
|
||
|
|
if (this.pollingInterval) {
|
||
|
|
clearInterval(this.pollingInterval);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
addMessage(role, content) {
|
||
|
|
this.messages.push({
|
||
|
|
role: role,
|
||
|
|
content: content,
|
||
|
|
timestamp: new Date().toISOString()
|
||
|
|
});
|
||
|
|
|
||
|
|
// 滚动到底部
|
||
|
|
setTimeout(() => {
|
||
|
|
const container = this.$refs.messageContainer;
|
||
|
|
if (container) {
|
||
|
|
container.scrollTop = container.scrollHeight;
|
||
|
|
}
|
||
|
|
}, 100);
|
||
|
|
},
|
||
|
|
|
||
|
|
getRoleLabel(role) {
|
||
|
|
const labels = {
|
||
|
|
'user': '👤 用户',
|
||
|
|
'assistant': '🤖 Agent',
|
||
|
|
'system': '💬 系统',
|
||
|
|
'error': '❌ 错误',
|
||
|
|
'execution': '⚙️ 执行',
|
||
|
|
'planning': '📋 规划',
|
||
|
|
'diagnosis': '🔍 诊断'
|
||
|
|
};
|
||
|
|
return labels[role] || role;
|
||
|
|
},
|
||
|
|
|
||
|
|
getStatusIcon(status) {
|
||
|
|
const icons = {
|
||
|
|
'pending': '⏳',
|
||
|
|
'running': '🔄',
|
||
|
|
'completed': '✅',
|
||
|
|
'failed': '❌',
|
||
|
|
'cancelled': '🚫'
|
||
|
|
};
|
||
|
|
return icons[status] || '❓';
|
||
|
|
},
|
||
|
|
|
||
|
|
formatMessage(content) {
|
||
|
|
// 简单的Markdown格式化
|
||
|
|
return content
|
||
|
|
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||
|
|
.replace(/\n/g, '<br>');
|
||
|
|
},
|
||
|
|
|
||
|
|
formatTime(timestamp) {
|
||
|
|
if (!timestamp) return '';
|
||
|
|
const date = new Date(timestamp);
|
||
|
|
return date.toLocaleTimeString('zh-CN', {
|
||
|
|
hour: '2-digit',
|
||
|
|
minute: '2-digit',
|
||
|
|
second: '2-digit'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.agent-status-panel {
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 16px;
|
||
|
|
background: #f9f9f9;
|
||
|
|
height: 100%;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 12px;
|
||
|
|
padding-bottom: 8px;
|
||
|
|
border-bottom: 1px solid #eee;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-header h3 {
|
||
|
|
margin: 0;
|
||
|
|
font-size: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-mode {
|
||
|
|
font-size: 12px;
|
||
|
|
color: #666;
|
||
|
|
background: #e0e0e0;
|
||
|
|
padding: 2px 8px;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-messages {
|
||
|
|
flex: 1;
|
||
|
|
overflow-y: auto;
|
||
|
|
max-height: 400px;
|
||
|
|
margin-bottom: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message {
|
||
|
|
margin-bottom: 12px;
|
||
|
|
padding: 8px 12px;
|
||
|
|
border-radius: 8px;
|
||
|
|
background: white;
|
||
|
|
border-left: 3px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message.user {
|
||
|
|
border-left-color: #4CAF50;
|
||
|
|
background: #E8F5E9;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message.assistant {
|
||
|
|
border-left-color: #2196F3;
|
||
|
|
background: #E3F2FD;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message.error {
|
||
|
|
border-left-color: #f44336;
|
||
|
|
background: #FFEBEE;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message.system {
|
||
|
|
border-left-color: #9E9E9E;
|
||
|
|
background: #F5F5F5;
|
||
|
|
font-size: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message-role {
|
||
|
|
font-size: 12px;
|
||
|
|
color: #666;
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message-content {
|
||
|
|
font-size: 14px;
|
||
|
|
line-height: 1.5;
|
||
|
|
}
|
||
|
|
|
||
|
|
.message-time {
|
||
|
|
font-size: 11px;
|
||
|
|
color: #999;
|
||
|
|
text-align: right;
|
||
|
|
margin-top: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-input {
|
||
|
|
display: flex;
|
||
|
|
gap: 8px;
|
||
|
|
margin-bottom: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-input textarea {
|
||
|
|
flex: 1;
|
||
|
|
padding: 8px;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
border-radius: 4px;
|
||
|
|
resize: none;
|
||
|
|
font-family: inherit;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-input button {
|
||
|
|
padding: 8px 16px;
|
||
|
|
background: #2196F3;
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-input button:disabled {
|
||
|
|
background: #ccc;
|
||
|
|
cursor: not-allowed;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-tasks {
|
||
|
|
border-top: 1px solid #eee;
|
||
|
|
padding-top: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.agent-tasks h4 {
|
||
|
|
margin: 0 0 8px 0;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 8px;
|
||
|
|
padding: 6px 8px;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
margin-bottom: 4px;
|
||
|
|
background: white;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-item:hover {
|
||
|
|
background: #f0f0f0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-item.pending {
|
||
|
|
border-left: 3px solid #FFC107;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-item.running {
|
||
|
|
border-left: 3px solid #2196F3;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-item.completed {
|
||
|
|
border-left: 3px solid #4CAF50;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-item.failed {
|
||
|
|
border-left: 3px solid #f44336;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-status {
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-message {
|
||
|
|
flex: 1;
|
||
|
|
font-size: 12px;
|
||
|
|
overflow: hidden;
|
||
|
|
text-overflow: ellipsis;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.task-progress {
|
||
|
|
font-size: 11px;
|
||
|
|
color: #666;
|
||
|
|
}
|
||
|
|
</style>
|