Files
task-board/public/index.html
T
work-1 f229b0cd97 新增 v2.0 Web 管理界面
新界面功能:
 项目管理 - 创建、查看、选择项目
 项目详情 - 任务统计、规约查看、共享笔记
 任务列表 - 按项目/状态过滤,支持 Markdown 渲染
 智能体列表 - 查看状态和心跳
 全局规约 - 编辑和查看全局协作规约
 项目规约 - 每个项目单独设置规约
 信息共享清单 - 添加和查看项目笔记
 实时统计 - 6个统计卡片展示

特性:
- 4个标签页:项目、任务、智能体、规约
- Markdown 实时渲染(使用 marked.js)
- 模态框表单(创建项目/任务,编辑规约)
- 自动刷新(每30秒)
- 响应式设计

旧界面保留为 index-v1.html
2026-02-15 21:51:05 +08:00

885 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多智能体任务管理系统 v2.0</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: 1600px;
margin: 0 auto;
}
header {
text-align: center;
color: white;
margin-bottom: 30px;
}
h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.version {
background: rgba(255,255,255,0.2);
padding: 5px 15px;
border-radius: 20px;
display: inline-block;
font-size: 0.9em;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
background: white;
padding: 10px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.tab {
padding: 12px 24px;
border: none;
background: #f0f0f0;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
}
.tab.active {
background: #667eea;
color: white;
}
.tab:hover:not(.active) {
background: #e0e0e0;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.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;
}
.panel {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.panel h2 {
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #667eea;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.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: 100px;
font-family: 'Courier New', monospace;
}
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);
}
button.secondary {
background: #95a5a6;
}
button.secondary:hover {
background: #7f8c8d;
}
button.danger {
background: #e74c3c;
}
button.danger:hover {
background: #c0392b;
}
.item-card {
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
background: #f8f9fa;
border-left: 4px solid #667eea;
cursor: pointer;
transition: all 0.3s;
}
.item-card:hover {
transform: translateX(5px);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.item-card.selected {
background: #e8f4f8;
border-left-color: #3498db;
}
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85em;
font-weight: bold;
margin-left: 10px;
}
.badge.initial { background: #3498db; color: white; }
.badge.in_progress { background: #f39c12; color: white; }
.badge.testing_pending { background: #9b59b6; color: white; }
.badge.testing { background: #e67e22; 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; }
.badge.requirement { background: #9b59b6; color: white; }
.badge.design { background: #3498db; color: white; }
.badge.development { background: #2ecc71; color: white; }
.badge.testing { background: #e67e22; color: white; }
.badge.deployment { background: #e74c3c; color: white; }
.empty-state {
text-align: center;
color: #999;
padding: 40px;
}
.markdown-preview {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
border: 1px solid #ddd;
margin-top: 10px;
}
.markdown-preview h1 { font-size: 1.8em; margin-bottom: 10px; }
.markdown-preview h2 { font-size: 1.5em; margin-bottom: 8px; }
.markdown-preview h3 { font-size: 1.2em; margin-bottom: 5px; }
.markdown-preview p { margin-bottom: 10px; line-height: 1.6; }
.markdown-preview ul, .markdown-preview ol { margin-left: 20px; margin-bottom: 10px; }
.markdown-preview code { background: #e8e8e8; padding: 2px 6px; border-radius: 3px; }
.markdown-preview pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 5px; overflow-x: auto; }
.note-item {
background: #fff9e6;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
border-left: 4px solid #f39c12;
}
.note-meta {
font-size: 0.85em;
color: #666;
margin-top: 8px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 10px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.refresh-btn {
float: right;
padding: 5px 15px;
font-size: 0.9em;
}
#rulesEditor {
font-family: 'Courier New', monospace;
min-height: 200px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🤖 多智能体任务管理系统</h1>
<span class="version">v2.0 - 项目管理版</span>
</header>
<div class="stats" id="stats"></div>
<div class="tabs">
<button class="tab active" onclick="switchTab('projects')">📁 项目管理</button>
<button class="tab" onclick="switchTab('tasks')">📋 任务列表</button>
<button class="tab" onclick="switchTab('agents')">🤖 智能体</button>
<button class="tab" onclick="switchTab('rules')">📜 协作规约</button>
</div>
<!-- 项目管理标签页 -->
<div id="tab-projects" class="tab-content active">
<div class="grid-2">
<div class="panel">
<h2>项目列表 <button class="refresh-btn" onclick="loadProjects()">刷新</button></h2>
<div id="projectList"></div>
<button onclick="showCreateProjectModal()" style="margin-top: 15px;">创建新项目</button>
</div>
<div class="panel">
<h2>项目详情</h2>
<div id="projectDetail">
<div class="empty-state">请选择一个项目</div>
</div>
</div>
</div>
</div>
<!-- 任务列表标签页 -->
<div id="tab-tasks" class="tab-content">
<div class="panel">
<h2>任务列表 <button class="refresh-btn" onclick="loadTasks()">刷新</button></h2>
<div style="margin-bottom: 15px;">
<select id="filterProject" onchange="loadTasks()" style="width: 200px; margin-right: 10px;">
<option value="">所有项目</option>
</select>
<select id="filterStatus" onchange="loadTasks()" style="width: 150px; margin-right: 10px;">
<option value="">所有状态</option>
<option value="initial">初始</option>
<option value="in_progress">进行中</option>
<option value="testing_pending">待测试</option>
<option value="testing">测试中</option>
<option value="completed">完成</option>
</select>
</div>
<div id="taskList"></div>
<button onclick="showCreateTaskModal()" style="margin-top: 15px;">创建新任务</button>
</div>
</div>
<!-- 智能体标签页 -->
<div id="tab-agents" class="tab-content">
<div class="panel">
<h2>智能体列表 <button class="refresh-btn" onclick="loadAgents()">刷新</button></h2>
<div id="agentList"></div>
</div>
</div>
<!-- 协作规约标签页 -->
<div id="tab-rules" class="tab-content">
<div class="panel">
<h2>全局协作规约</h2>
<div id="globalRulesContent"></div>
<button onclick="showEditGlobalRulesModal()">编辑全局规约</button>
</div>
<div class="panel">
<h2>项目协作规约</h2>
<p style="color: #666; margin-bottom: 15px;">请在"项目管理"标签页中选择项目后设置项目规约</p>
</div>
</div>
</div>
<!-- 创建项目模态框 -->
<div id="createProjectModal" class="modal">
<div class="modal-content">
<h2>创建新项目</h2>
<form id="createProjectForm">
<div class="form-group">
<label>项目名称 *</label>
<input type="text" id="projectName" required>
</div>
<div class="form-group">
<label>项目描述</label>
<textarea id="projectDesc"></textarea>
</div>
<button type="submit">创建</button>
<button type="button" class="secondary" onclick="closeModal('createProjectModal')">取消</button>
</form>
</div>
</div>
<!-- 创建任务模态框 -->
<div id="createTaskModal" class="modal">
<div class="modal-content">
<h2>创建新任务</h2>
<form id="createTaskForm">
<div class="form-group">
<label>所属项目 *</label>
<select id="taskProject" required></select>
</div>
<div class="form-group">
<label>任务标题 *</label>
<input type="text" id="taskTitle" required>
</div>
<div class="form-group">
<label>任务描述(Markdown</label>
<textarea id="taskDesc"></textarea>
</div>
<div class="form-group">
<label>任务类型</label>
<select id="taskType">
<option value="requirement">需求</option>
<option value="design">设计</option>
<option value="development" selected>开发</option>
<option value="testing">测试</option>
<option value="deployment">部署</option>
</select>
</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>
<button type="button" class="secondary" onclick="closeModal('createTaskModal')">取消</button>
</form>
</div>
</div>
<!-- 编辑全局规约模态框 -->
<div id="editGlobalRulesModal" class="modal">
<div class="modal-content">
<h2>编辑全局协作规约</h2>
<form id="editGlobalRulesForm">
<div class="form-group">
<label>规约内容(Markdown</label>
<textarea id="rulesEditor"></textarea>
</div>
<button type="submit">保存</button>
<button type="button" class="secondary" onclick="closeModal('editGlobalRulesModal')">取消</button>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
const API_BASE = '/api';
let currentProject = null;
let projects = [];
// 标签页切换
function switchTab(tabName) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(`tab-${tabName}`).classList.add('active');
// 加载对应数据
if (tabName === 'projects') loadProjects();
if (tabName === 'tasks') loadTasks();
if (tabName === 'agents') loadAgents();
if (tabName === 'rules') loadGlobalRules();
}
// 加载统计数据
async function loadStats() {
try {
const response = await fetch(`${API_BASE}/stats`);
const stats = await response.json();
document.getElementById('stats').innerHTML = `
<div class="stat-card">
<h3>项目总数</h3>
<div class="number">${stats.total_projects || 0}</div>
</div>
<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.total_tasks || 0}</div>
</div>
<div class="stat-card">
<h3>进行中任务</h3>
<div class="number">${stats.tasks_by_status.in_progress || 0}</div>
</div>
<div class="stat-card">
<h3>已完成任务</h3>
<div class="number">${stats.tasks_by_status.completed || 0}</div>
</div>
`;
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
// 加载项目列表
async function loadProjects() {
try {
const response = await fetch(`${API_BASE}/projects`);
projects = await response.json();
const projectList = document.getElementById('projectList');
if (projects.length === 0) {
projectList.innerHTML = '<div class="empty-state">暂无项目</div>';
return;
}
projectList.innerHTML = projects.map(project => `
<div class="item-card ${currentProject?.id === project.id ? 'selected' : ''}"
onclick="selectProject('${project.id}')">
<strong>${project.name}</strong>
<span class="badge" style="background: ${project.status === 'active' ? '#27ae60' : '#95a5a6'}">${project.status === 'active' ? '活跃' : '归档'}</span>
<div style="margin-top: 8px; font-size: 0.9em; color: #666;">
${project.description || '无描述'}
</div>
<div style="margin-top: 5px; font-size: 0.85em; color: #999;">
创建时间: ${new Date(parseInt(project.created_at)).toLocaleString('zh-CN')}
</div>
</div>
`).join('');
// 更新任务创建表单中的项目选择
updateProjectSelects();
} catch (error) {
console.error('加载项目列表失败:', error);
}
}
// 选择项目
async function selectProject(projectId) {
currentProject = projects.find(p => p.id === projectId);
loadProjects(); // 重新渲染高亮
// 加载项目详情
const detail = document.getElementById('projectDetail');
detail.innerHTML = '<div style="text-align: center; padding: 20px;">加载中...</div>';
try {
// 获取项目任务
const tasksResponse = await fetch(`${API_BASE}/tasks?project_id=${projectId}`);
const tasks = await tasksResponse.json();
// 获取项目规约
const rulesResponse = await fetch(`${API_BASE}/projects/${projectId}/rules`);
const rules = await rulesResponse.json();
// 获取项目笔记
const notesResponse = await fetch(`${API_BASE}/projects/${projectId}/notes`);
const notes = await notesResponse.json();
detail.innerHTML = `
<h3>${currentProject.name}</h3>
<p style="color: #666; margin-bottom: 20px;">${currentProject.description || '无描述'}</p>
<h4>📋 任务统计</h4>
<div style="margin-bottom: 20px;">
<span class="badge">总计: ${tasks.length}</span>
<span class="badge initial">初始: ${tasks.filter(t => t.status === 'initial').length}</span>
<span class="badge in_progress">进行中: ${tasks.filter(t => t.status === 'in_progress').length}</span>
<span class="badge completed">完成: ${tasks.filter(t => t.status === 'completed').length}</span>
</div>
<h4>📜 项目规约</h4>
<div style="margin-bottom: 20px;">
${rules ? `<div class="markdown-preview">${marked.parse(rules.content)}</div>` : '<p style="color: #999;">未设置项目规约</p>'}
<button onclick="editProjectRules('${projectId}')" style="margin-top: 10px;">编辑规约</button>
</div>
<h4>📝 信息共享清单 (${notes.length})</h4>
<div id="projectNotes">
${notes.length > 0 ? notes.map(note => `
<div class="note-item">
<div class="markdown-preview">${marked.parse(note.content)}</div>
<div class="note-meta">
${note.created_by} 创建于 ${new Date(parseInt(note.created_at)).toLocaleString('zh-CN')}
</div>
</div>
`).join('') : '<p style="color: #999;">暂无共享笔记</p>'}
</div>
<button onclick="addProjectNote('${projectId}')">添加笔记</button>
`;
} catch (error) {
detail.innerHTML = '<div class="empty-state">加载失败</div>';
console.error('加载项目详情失败:', error);
}
}
// 加载任务列表
async function loadTasks() {
try {
const projectFilter = document.getElementById('filterProject')?.value || '';
const statusFilter = document.getElementById('filterStatus')?.value || '';
let url = `${API_BASE}/tasks?`;
if (projectFilter) url += `project_id=${projectFilter}&`;
if (statusFilter) url += `status=${statusFilter}&`;
const response = await fetch(url);
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 = {
'initial': '初始',
'in_progress': '进行中',
'testing_pending': '待测试',
'testing': '测试中',
'completed': '完成'
}[task.status] || task.status;
const typeText = {
'requirement': '需求',
'design': '设计',
'development': '开发',
'testing': '测试',
'deployment': '部署'
}[task.task_type] || task.task_type;
const priorityText = {
'low': '低',
'normal': '普通',
'high': '高'
}[task.priority] || task.priority;
return `
<div class="item-card">
<strong>${task.title}</strong>
<span class="badge ${task.status}">${statusText}</span>
<span class="badge ${task.task_type}">${typeText}</span>
<span class="badge ${task.priority}">${priorityText}</span>
${task.description ? `<div class="markdown-preview" style="margin-top: 10px;">${marked.parse(task.description)}</div>` : ''}
<div style="margin-top: 8px; font-size: 0.85em; color: #666;">
创建时间: ${new Date(parseInt(task.created_at)).toLocaleString('zh-CN')} |
${task.assigned_to ? `执行者: ${task.assigned_to}` : '未分配'}
</div>
</div>
`;
}).join('');
} catch (error) {
console.error('加载任务列表失败:', error);
}
}
// 加载智能体列表
async function loadAgents() {
try {
const response = await fetch(`${API_BASE}/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 => `
<div class="item-card">
<strong>${agent.name}</strong>
<span class="badge ${agent.status}">${agent.status === 'idle' ? '空闲' : '忙碌'}</span>
<div style="margin-top: 8px; font-size: 0.9em; color: #666;">
类型: ${agent.type} | 最后心跳: ${new Date(parseInt(agent.last_heartbeat)).toLocaleString('zh-CN')}
</div>
</div>
`).join('');
} catch (error) {
console.error('加载智能体列表失败:', error);
}
}
// 加载全局规约
async function loadGlobalRules() {
try {
const response = await fetch(`${API_BASE}/rules/global`);
const rules = await response.json();
const content = document.getElementById('globalRulesContent');
if (rules) {
content.innerHTML = `<div class="markdown-preview">${marked.parse(rules.content)}</div>`;
} else {
content.innerHTML = '<p style="color: #999;">未设置全局规约</p>';
}
} catch (error) {
console.error('加载全局规约失败:', error);
}
}
// 更新项目选择器
function updateProjectSelects() {
const selects = [document.getElementById('taskProject'), document.getElementById('filterProject')];
selects.forEach(select => {
if (!select) return;
const isFilter = select.id === 'filterProject';
select.innerHTML = (isFilter ? '<option value="">所有项目</option>' : '') +
projects.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
});
}
// 模态框控制
function showCreateProjectModal() {
document.getElementById('createProjectModal').classList.add('active');
}
function showCreateTaskModal() {
updateProjectSelects();
document.getElementById('createTaskModal').classList.add('active');
}
function showEditGlobalRulesModal() {
fetch(`${API_BASE}/rules/global`)
.then(r => r.json())
.then(rules => {
document.getElementById('rulesEditor').value = rules?.content || '';
document.getElementById('editGlobalRulesModal').classList.add('active');
});
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
function editProjectRules(projectId) {
fetch(`${API_BASE}/projects/${projectId}/rules`)
.then(r => r.json())
.then(rules => {
const content = prompt('编辑项目规约(Markdown:', rules?.content || '');
if (content !== null) {
fetch(`${API_BASE}/projects/${projectId}/rules`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({content, created_by: 'human'})
}).then(() => {
alert('规约已更新');
selectProject(projectId);
});
}
});
}
function addProjectNote(projectId) {
const content = prompt('添加共享笔记(Markdown:');
if (content) {
fetch(`${API_BASE}/projects/${projectId}/notes`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({content, created_by: 'human'})
}).then(() => {
alert('笔记已添加');
selectProject(projectId);
});
}
}
// 表单提交
document.getElementById('createProjectForm').addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
name: document.getElementById('projectName').value,
description: document.getElementById('projectDesc').value,
created_by: 'human'
};
try {
const response = await fetch(`${API_BASE}/projects`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
if (response.ok) {
alert('✅ 项目创建成功!');
closeModal('createProjectModal');
document.getElementById('createProjectForm').reset();
loadProjects();
loadStats();
}
} catch (error) {
alert('❌ 创建失败: ' + error.message);
}
});
document.getElementById('createTaskForm').addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
project_id: document.getElementById('taskProject').value,
title: document.getElementById('taskTitle').value,
description: document.getElementById('taskDesc').value,
task_type: document.getElementById('taskType').value,
priority: document.getElementById('taskPriority').value,
created_by: 'human'
};
try {
const response = await fetch(`${API_BASE}/tasks`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
if (response.ok) {
alert('✅ 任务创建成功!');
closeModal('createTaskModal');
document.getElementById('createTaskForm').reset();
loadTasks();
loadStats();
}
} catch (error) {
alert('❌ 创建失败: ' + error.message);
}
});
document.getElementById('editGlobalRulesForm').addEventListener('submit', async (e) => {
e.preventDefault();
const content = document.getElementById('rulesEditor').value;
try {
const response = await fetch(`${API_BASE}/rules/global`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({content, created_by: 'human'})
});
if (response.ok) {
alert('✅ 全局规约已更新!');
closeModal('editGlobalRulesModal');
loadGlobalRules();
}
} catch (error) {
alert('❌ 更新失败: ' + error.message);
}
});
// 初始加载
loadStats();
loadProjects();
// 自动刷新(每30秒)
setInterval(() => {
loadStats();
const activeTab = document.querySelector('.tab.active').textContent;
if (activeTab.includes('项目')) loadProjects();
if (activeTab.includes('任务')) loadTasks();
if (activeTab.includes('智能体')) loadAgents();
}, 30000);
</script>
</body>
</html>