新界面功能: ✅ 项目管理 - 创建、查看、选择项目 ✅ 项目详情 - 任务统计、规约查看、共享笔记 ✅ 任务列表 - 按项目/状态过滤,支持 Markdown 渲染 ✅ 智能体列表 - 查看状态和心跳 ✅ 全局规约 - 编辑和查看全局协作规约 ✅ 项目规约 - 每个项目单独设置规约 ✅ 信息共享清单 - 添加和查看项目笔记 ✅ 实时统计 - 6个统计卡片展示 特性: - 4个标签页:项目、任务、智能体、规约 - Markdown 实时渲染(使用 marked.js) - 模态框表单(创建项目/任务,编辑规约) - 自动刷新(每30秒) - 响应式设计 旧界面保留为 index-v1.html
885 lines
27 KiB
HTML
885 lines
27 KiB
HTML
<!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>
|