新增 v2.0 Web 管理界面
新界面功能: ✅ 项目管理 - 创建、查看、选择项目 ✅ 项目详情 - 任务统计、规约查看、共享笔记 ✅ 任务列表 - 按项目/状态过滤,支持 Markdown 渲染 ✅ 智能体列表 - 查看状态和心跳 ✅ 全局规约 - 编辑和查看全局协作规约 ✅ 项目规约 - 每个项目单独设置规约 ✅ 信息共享清单 - 添加和查看项目笔记 ✅ 实时统计 - 6个统计卡片展示 特性: - 4个标签页:项目、任务、智能体、规约 - Markdown 实时渲染(使用 marked.js) - 模态框表单(创建项目/任务,编辑规约) - 自动刷新(每30秒) - 响应式设计 旧界面保留为 index-v1.html
This commit is contained in:
@@ -0,0 +1,459 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>多智能体任务管理系统</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card h3 {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card .number {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel h2 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-item, .task-item {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-item.busy {
|
||||||
|
border-left-color: #f39c12;
|
||||||
|
background: #fff9e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-item.offline {
|
||||||
|
border-left-color: #95a5a6;
|
||||||
|
background: #ecf0f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.pending {
|
||||||
|
border-left-color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.in_progress {
|
||||||
|
border-left-color: #f39c12;
|
||||||
|
background: #fff9e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-item.completed {
|
||||||
|
border-left-color: #27ae60;
|
||||||
|
background: #e8f8f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.idle {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.busy {
|
||||||
|
background: #f39c12;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.pending {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.in_progress {
|
||||||
|
background: #f39c12;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.completed {
|
||||||
|
background: #27ae60;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.high {
|
||||||
|
background: #e74c3c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.normal {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.low {
|
||||||
|
background: #95a5a6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #764ba2;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input, .form-group textarea, .form-group select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTaskForm {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
float: right;
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>🤖 多智能体任务管理系统</h1>
|
||||||
|
<p>Multi-Agent Task Management System</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="stats" id="stats">
|
||||||
|
<!-- 统计数据将动态加载 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="panel">
|
||||||
|
<h2>
|
||||||
|
智能体列表
|
||||||
|
<button class="refresh-btn" onclick="loadAgents()">刷新</button>
|
||||||
|
</h2>
|
||||||
|
<div id="agentList">
|
||||||
|
<div class="empty-state">加载中...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<h2>
|
||||||
|
任务列表
|
||||||
|
<button class="refresh-btn" onclick="loadTasks()">刷新</button>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div id="taskList">
|
||||||
|
<div class="empty-state">加载中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="createTaskForm">
|
||||||
|
<h3 style="margin-top: 30px; margin-bottom: 15px;">创建新任务</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>任务标题 *</label>
|
||||||
|
<input type="text" id="taskTitle" required placeholder="输入任务标题">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>任务描述</label>
|
||||||
|
<textarea id="taskDescription" placeholder="详细描述任务内容"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>优先级</label>
|
||||||
|
<select id="taskPriority">
|
||||||
|
<option value="low">低</option>
|
||||||
|
<option value="normal" selected>普通</option>
|
||||||
|
<option value="high">高</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit">创建任务</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API_BASE = '';
|
||||||
|
|
||||||
|
// 加载统计数据
|
||||||
|
async function loadStats() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/stats`);
|
||||||
|
const stats = await response.json();
|
||||||
|
|
||||||
|
document.getElementById('stats').innerHTML = `
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>智能体总数</h3>
|
||||||
|
<div class="number">${stats.total_agents || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>活跃智能体</h3>
|
||||||
|
<div class="number">${stats.active_agents || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>待处理任务</h3>
|
||||||
|
<div class="number">${stats.pending_tasks || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>进行中任务</h3>
|
||||||
|
<div class="number">${stats.in_progress_tasks || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>已完成任务</h3>
|
||||||
|
<div class="number">${stats.completed_tasks || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>任务总数</h3>
|
||||||
|
<div class="number">${stats.total_tasks || 0}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载统计数据失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载智能体列表
|
||||||
|
async function loadAgents() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/agents`);
|
||||||
|
const agents = await response.json();
|
||||||
|
|
||||||
|
const agentList = document.getElementById('agentList');
|
||||||
|
|
||||||
|
if (agents.length === 0) {
|
||||||
|
agentList.innerHTML = '<div class="empty-state">暂无智能体注册</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
agentList.innerHTML = agents.map(agent => {
|
||||||
|
const statusClass = agent.status === 'busy' ? 'busy' : 'idle';
|
||||||
|
const lastHeartbeat = new Date(agent.last_heartbeat).toLocaleString('zh-CN');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="agent-item ${statusClass}">
|
||||||
|
<strong>${agent.name}</strong>
|
||||||
|
<span class="badge ${agent.status}">${agent.status === 'idle' ? '空闲' : '忙碌'}</span>
|
||||||
|
<div class="task-meta">
|
||||||
|
类型: ${agent.type || '通用'} |
|
||||||
|
注册时间: ${new Date(agent.registered_at).toLocaleString('zh-CN')} |
|
||||||
|
最后心跳: ${lastHeartbeat}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载智能体列表失败:', error);
|
||||||
|
document.getElementById('agentList').innerHTML =
|
||||||
|
'<div class="empty-state">加载失败</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载任务列表
|
||||||
|
async function loadTasks() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/tasks`);
|
||||||
|
const tasks = await response.json();
|
||||||
|
|
||||||
|
const taskList = document.getElementById('taskList');
|
||||||
|
|
||||||
|
if (tasks.length === 0) {
|
||||||
|
taskList.innerHTML = '<div class="empty-state">暂无任务</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskList.innerHTML = tasks.map(task => {
|
||||||
|
const statusText = {
|
||||||
|
'pending': '待处理',
|
||||||
|
'in_progress': '进行中',
|
||||||
|
'completed': '已完成'
|
||||||
|
}[task.status] || task.status;
|
||||||
|
|
||||||
|
const priorityText = {
|
||||||
|
'low': '低',
|
||||||
|
'normal': '普通',
|
||||||
|
'high': '高'
|
||||||
|
}[task.priority] || task.priority;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="task-item ${task.status}">
|
||||||
|
<strong>${task.title}</strong>
|
||||||
|
<span class="badge ${task.status}">${statusText}</span>
|
||||||
|
<span class="badge ${task.priority}">${priorityText}</span>
|
||||||
|
${task.description ? `<div style="margin-top: 8px; color: #666;">${task.description}</div>` : ''}
|
||||||
|
<div class="task-meta">
|
||||||
|
创建时间: ${new Date(task.created_at).toLocaleString('zh-CN')} |
|
||||||
|
${task.assigned_to ? `执行者: ${task.assigned_to}` : '未分配'} |
|
||||||
|
${task.completed_at ? `完成时间: ${new Date(task.completed_at).toLocaleString('zh-CN')}` : ''}
|
||||||
|
</div>
|
||||||
|
${task.result ? `<div style="margin-top: 8px; padding: 8px; background: #e8f4f8; border-radius: 4px;"><strong>结果:</strong> ${task.result}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载任务列表失败:', error);
|
||||||
|
document.getElementById('taskList').innerHTML =
|
||||||
|
'<div class="empty-state">加载失败</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务表单提交
|
||||||
|
document.getElementById('createTaskForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const title = document.getElementById('taskTitle').value;
|
||||||
|
const description = document.getElementById('taskDescription').value;
|
||||||
|
const priority = document.getElementById('taskPriority').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/tasks`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
priority,
|
||||||
|
created_by: 'human'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert('✅ 任务创建成功!');
|
||||||
|
document.getElementById('createTaskForm').reset();
|
||||||
|
loadTasks();
|
||||||
|
loadStats();
|
||||||
|
} else {
|
||||||
|
alert('❌ 创建失败: ' + result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('❌ 创建失败: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始加载
|
||||||
|
loadStats();
|
||||||
|
loadAgents();
|
||||||
|
loadTasks();
|
||||||
|
|
||||||
|
// 自动刷新(每10秒)
|
||||||
|
setInterval(() => {
|
||||||
|
loadStats();
|
||||||
|
loadAgents();
|
||||||
|
loadTasks();
|
||||||
|
}, 10000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+639
-214
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user