feat: v3.1 - 统一规约视图、项目树导航、移动端支持
主要更新: - 统一规约管理视图(全局/项目规约共享表格) - 项目树形导航(看板/项目规约子菜单) - 智能高亮与状态持久化 - 移动端响应式支持(汉堡菜单) - 规约类型更新为7个专业分类 - 文件清理(85→15文件,减少82%) 修复问题: - 修复创建项目功能(JavaScript语法错误) - 修复协作规约错误高亮 - 修复看板需要点击2次才高亮 - 修复项目树自动收起问题 新增工具: - debug.html(项目创建测试) - test.html(移动端测试) - status.html(系统诊断) 技术改进: - 上下文状态管理(currentRuleContext) - 展开状态持久化(projectExpandedState) - 触摸事件支持 - 移除console.log提升兼容性
This commit is contained in:
@@ -1,422 +0,0 @@
|
|||||||
# API v2 升级说明
|
|
||||||
|
|
||||||
## 🎉 新功能概览
|
|
||||||
|
|
||||||
### 1. 项目管理
|
|
||||||
- 所有任务必须隶属于项目
|
|
||||||
- 项目由人类创建和管理
|
|
||||||
|
|
||||||
### 2. 协作规约
|
|
||||||
- 每个项目可设置协作规约
|
|
||||||
- 智能体领取任务时会收到规约提醒
|
|
||||||
- 智能体必须严格遵守规约内容
|
|
||||||
|
|
||||||
### 3. 信息共享清单
|
|
||||||
- 每个项目维护共享笔记列表
|
|
||||||
- 人类和智能体都可以添加/编辑
|
|
||||||
- 支持 CRUD 操作
|
|
||||||
|
|
||||||
### 4. 任务增强
|
|
||||||
- **Markdown 支持**: 任务描述使用 Markdown 格式
|
|
||||||
- **附件管理**: 支持文件上传(最大 10MB)
|
|
||||||
- **子任务**: 支持父子任务关系
|
|
||||||
- **任务类型**: requirement(需求)| design(设计)| development(开发)| testing(测试)| deployment(部署)
|
|
||||||
- **新状态流**: initial(初始)→ in_progress(进行中)→ testing_pending(待测试)→ testing(测试中)→ completed(完成)
|
|
||||||
|
|
||||||
## 📡 API 端点
|
|
||||||
|
|
||||||
### 项目管理
|
|
||||||
|
|
||||||
#### 创建项目
|
|
||||||
```http
|
|
||||||
POST /api/projects
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "项目名称",
|
|
||||||
"description": "项目描述",
|
|
||||||
"created_by": "human"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取所有项目
|
|
||||||
```http
|
|
||||||
GET /api/projects
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取项目详情
|
|
||||||
```http
|
|
||||||
GET /api/projects/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更新项目
|
|
||||||
```http
|
|
||||||
PUT /api/projects/:id
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "新名称",
|
|
||||||
"description": "新描述",
|
|
||||||
"status": "active" | "archived"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 协作规约
|
|
||||||
|
|
||||||
#### 设置全局协作规约
|
|
||||||
```http
|
|
||||||
POST /api/rules/global
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"content": "# 全局协作规约\n\n适用于所有项目...",
|
|
||||||
"created_by": "human"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取全局协作规约
|
|
||||||
```http
|
|
||||||
GET /api/rules/global
|
|
||||||
|
|
||||||
响应:
|
|
||||||
{
|
|
||||||
"id": "xxx",
|
|
||||||
"content": "规约内容(Markdown)",
|
|
||||||
"created_by": "human",
|
|
||||||
"created_at": 1234567890,
|
|
||||||
"updated_at": 1234567890
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 设置项目协作规约
|
|
||||||
```http
|
|
||||||
POST /api/projects/:projectId/rules
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"content": "协作规约内容(Markdown)",
|
|
||||||
"created_by": "human"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取协作规约
|
|
||||||
```http
|
|
||||||
GET /api/projects/:projectId/rules
|
|
||||||
```
|
|
||||||
|
|
||||||
### 信息共享清单
|
|
||||||
|
|
||||||
#### 添加共享笔记
|
|
||||||
```http
|
|
||||||
POST /api/projects/:projectId/notes
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"content": "笔记内容(Markdown)",
|
|
||||||
"created_by": "human" | "agent_id"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取共享笔记列表
|
|
||||||
```http
|
|
||||||
GET /api/projects/:projectId/notes
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更新共享笔记
|
|
||||||
```http
|
|
||||||
PUT /api/projects/:projectId/notes/:noteId
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"content": "更新后的内容"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 删除共享笔记
|
|
||||||
```http
|
|
||||||
DELETE /api/projects/:projectId/notes/:noteId
|
|
||||||
```
|
|
||||||
|
|
||||||
### 任务管理(增强)
|
|
||||||
|
|
||||||
#### 创建任务
|
|
||||||
```http
|
|
||||||
POST /api/tasks
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"project_id": "项目ID", // 必填
|
|
||||||
"parent_task_id": "父任务ID", // 可选,创建子任务时使用
|
|
||||||
"title": "任务标题",
|
|
||||||
"description": "# 任务描述\n\n使用 **Markdown** 格式",
|
|
||||||
"task_type": "development", // requirement | design | development | testing | deployment
|
|
||||||
"priority": "normal", // low | normal | high
|
|
||||||
"created_by": "human"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取任务列表
|
|
||||||
```http
|
|
||||||
GET /api/tasks?project_id=xxx&status=initial&root=true
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- project_id: 按项目过滤
|
|
||||||
- status: initial | in_progress | testing_pending | testing | completed
|
|
||||||
- parent_task_id: 获取某任务的子任务
|
|
||||||
- root=true: 只获取根任务(无父任务)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取任务详情(含子任务和附件)
|
|
||||||
```http
|
|
||||||
GET /api/tasks/:id
|
|
||||||
|
|
||||||
响应:
|
|
||||||
{
|
|
||||||
"id": "xxx",
|
|
||||||
"title": "...",
|
|
||||||
...
|
|
||||||
"subtasks": [ ... ], // 子任务列表
|
|
||||||
"attachments": [ ... ] // 附件列表
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 领取任务(智能体会同时收到全局规约和项目规约)
|
|
||||||
```http
|
|
||||||
POST /api/tasks/:id/claim
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"agent_id": "智能体ID"
|
|
||||||
}
|
|
||||||
|
|
||||||
响应(如果有规约):
|
|
||||||
{
|
|
||||||
"message": "任务领取成功",
|
|
||||||
"collaboration_rules": {
|
|
||||||
"global_rules": "全局协作规约内容",
|
|
||||||
"project_rules": "项目协作规约内容"
|
|
||||||
},
|
|
||||||
"notice": "⚠️ 请严格遵守协作规约"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更新任务
|
|
||||||
```http
|
|
||||||
PUT /api/tasks/:id
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "in_progress",
|
|
||||||
"description": "更新描述",
|
|
||||||
"task_type": "testing",
|
|
||||||
"priority": "high",
|
|
||||||
"result": "任务结果"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 附件管理
|
|
||||||
|
|
||||||
#### 上传附件
|
|
||||||
```http
|
|
||||||
POST /api/tasks/:taskId/attachments
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
|
|
||||||
file: [文件]
|
|
||||||
uploaded_by: "human" | "agent_id"
|
|
||||||
|
|
||||||
限制:单文件最大 10MB
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取任务附件列表
|
|
||||||
```http
|
|
||||||
GET /api/tasks/:taskId/attachments
|
|
||||||
|
|
||||||
响应:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "xxx",
|
|
||||||
"filename": "...",
|
|
||||||
"original_name": "原始文件名",
|
|
||||||
"file_size": 12345,
|
|
||||||
"mime_type": "image/png",
|
|
||||||
"url": "/uploads/xxx",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 删除附件
|
|
||||||
```http
|
|
||||||
DELETE /api/tasks/:taskId/attachments/:attachmentId
|
|
||||||
```
|
|
||||||
|
|
||||||
### 统计信息(增强)
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/stats
|
|
||||||
|
|
||||||
响应:
|
|
||||||
{
|
|
||||||
"total_agents": 5,
|
|
||||||
"active_agents": 2,
|
|
||||||
"total_projects": 3,
|
|
||||||
"total_tasks": 20,
|
|
||||||
"tasks_by_status": {
|
|
||||||
"initial": 5,
|
|
||||||
"in_progress": 3,
|
|
||||||
"testing_pending": 2,
|
|
||||||
"testing": 1,
|
|
||||||
"completed": 9
|
|
||||||
},
|
|
||||||
"tasks_by_type": {
|
|
||||||
"requirement": 4,
|
|
||||||
"design": 3,
|
|
||||||
"development": 8,
|
|
||||||
"testing": 3,
|
|
||||||
"deployment": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🗄️ 数据库变更
|
|
||||||
|
|
||||||
### 新增表
|
|
||||||
|
|
||||||
1. **projects** - 项目表
|
|
||||||
- id, name, description, created_by, created_at, updated_at, status
|
|
||||||
|
|
||||||
2. **collaboration_rules** - 协作规约表
|
|
||||||
- id, project_id, content, created_by, created_at, updated_at
|
|
||||||
|
|
||||||
3. **project_notes** - 项目共享笔记表
|
|
||||||
- id, project_id, content, created_by, created_at, updated_at
|
|
||||||
|
|
||||||
4. **attachments** - 附件表
|
|
||||||
- id, task_id, filename, original_name, file_path, file_size, mime_type, uploaded_by, uploaded_at
|
|
||||||
|
|
||||||
### 修改表
|
|
||||||
|
|
||||||
**tasks** 表新增字段:
|
|
||||||
- `project_id` - 所属项目(外键,必填)
|
|
||||||
- `parent_task_id` - 父任务ID(外键,可选)
|
|
||||||
- `task_type` - 任务类型(requirement/design/development/testing/deployment)
|
|
||||||
- 状态值更新为:initial | in_progress | testing_pending | testing | completed
|
|
||||||
|
|
||||||
## 🔧 使用示例
|
|
||||||
|
|
||||||
### Python 客户端示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
API_BASE = "http://localhost:3000/api"
|
|
||||||
|
|
||||||
# 1. 创建项目
|
|
||||||
project = requests.post(f"{API_BASE}/projects", json={
|
|
||||||
"name": "智能客服系统",
|
|
||||||
"description": "基于 AI 的客服系统开发项目"
|
|
||||||
}).json()
|
|
||||||
|
|
||||||
project_id = project['id']
|
|
||||||
|
|
||||||
# 2. 设置协作规约
|
|
||||||
requests.post(f"{API_BASE}/projects/{project_id}/rules", json={
|
|
||||||
"content": """
|
|
||||||
# 协作规约
|
|
||||||
|
|
||||||
1. **代码规范**: 严格遵循 PEP 8
|
|
||||||
2. **提交规范**: 每次提交必须包含测试
|
|
||||||
3. **沟通规范**: 遇到问题及时在共享清单记录
|
|
||||||
4. **测试要求**: 覆盖率不低于 80%
|
|
||||||
"""
|
|
||||||
})
|
|
||||||
|
|
||||||
# 3. 创建任务
|
|
||||||
task = requests.post(f"{API_BASE}/tasks", json={
|
|
||||||
"project_id": project_id,
|
|
||||||
"title": "设计数据库架构",
|
|
||||||
"description": "## 需求\n\n设计用户、会话、消息三张表",
|
|
||||||
"task_type": "design",
|
|
||||||
"priority": "high"
|
|
||||||
}).json()
|
|
||||||
|
|
||||||
task_id = task['id']
|
|
||||||
|
|
||||||
# 4. 智能体注册并领取任务
|
|
||||||
agent = requests.post(f"{API_BASE}/agents/register", json={
|
|
||||||
"name": "数据库设计专家",
|
|
||||||
"type": "design",
|
|
||||||
"capabilities": ["database_design", "sql"]
|
|
||||||
}).json()
|
|
||||||
|
|
||||||
response = requests.post(f"{API_BASE}/tasks/{task_id}/claim", json={
|
|
||||||
"agent_id": agent['id']
|
|
||||||
}).json()
|
|
||||||
|
|
||||||
# 智能体会收到协作规约
|
|
||||||
if 'collaboration_rules' in response:
|
|
||||||
print("收到协作规约:")
|
|
||||||
print(response['collaboration_rules'])
|
|
||||||
|
|
||||||
# 5. 添加共享笔记
|
|
||||||
requests.post(f"{API_BASE}/projects/{project_id}/notes", json={
|
|
||||||
"content": "数据库已选择 PostgreSQL 15",
|
|
||||||
"created_by": agent['id']
|
|
||||||
})
|
|
||||||
|
|
||||||
# 6. 上传附件(设计图)
|
|
||||||
with open('database_schema.png', 'rb') as f:
|
|
||||||
requests.post(
|
|
||||||
f"{API_BASE}/tasks/{task_id}/attachments",
|
|
||||||
files={'file': f},
|
|
||||||
data={'uploaded_by': agent['id']}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 7. 完成任务
|
|
||||||
requests.put(f"{API_BASE}/tasks/{task_id}", json={
|
|
||||||
"status": "completed",
|
|
||||||
"result": "数据库架构设计完成,详见附件"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 迁移指南
|
|
||||||
|
|
||||||
如果你有使用 v1 版本的数据,需要:
|
|
||||||
|
|
||||||
1. **备份数据库**
|
|
||||||
```bash
|
|
||||||
pg_dump agent_tasks > backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **运行 v2 服务**(会自动创建新表)
|
|
||||||
```bash
|
|
||||||
node server-v2.js
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **手动迁移任务**
|
|
||||||
- 创建一个默认项目
|
|
||||||
- 将所有 v1 任务关联到该项目
|
|
||||||
|
|
||||||
## 🚀 部署
|
|
||||||
|
|
||||||
使用 `server-v2.js` 替代 `server-postgres.js`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DB_HOST=localhost DB_PORT=5433 DB_PASSWORD=postgres node server-v2.js
|
|
||||||
```
|
|
||||||
|
|
||||||
或更新 `package.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"start": "node server-v2.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚠️ 重要变更
|
|
||||||
|
|
||||||
1. **所有任务必须属于项目** - 创建任务时 `project_id` 为必填
|
|
||||||
2. **任务状态变更** - 从 3 个状态扩展到 5 个状态
|
|
||||||
3. **新增任务类型** - 必须指定任务类型
|
|
||||||
4. **协作规约强制推送** - 智能体领取任务时自动获取规约
|
|
||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
# v3.0 功能优化完成报告
|
||||||
|
|
||||||
|
**日期**: 2026-02-24
|
||||||
|
**版本**: v3.1
|
||||||
|
**状态**: ✅ 已完成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 修改清单
|
||||||
|
|
||||||
|
### 修改 4: 侧边栏收起优化 ✅
|
||||||
|
|
||||||
|
**需求**: 左侧导航栏收起时隐藏文字只显示logo
|
||||||
|
|
||||||
|
**实现**:
|
||||||
|
- 添加收起状态 CSS 样式
|
||||||
|
- 收起时宽度: 240px → 60px
|
||||||
|
- 自动隐藏所有文字内容
|
||||||
|
- 只显示图标和 emoji
|
||||||
|
|
||||||
|
**效果**:
|
||||||
|
```
|
||||||
|
展开状态: 📊 仪表盘 | 🤖 智能体管理 | 📁 项目名称
|
||||||
|
收起状态: 📊 | 🤖 | 📁
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSS 改动**:
|
||||||
|
```css
|
||||||
|
#sidebar.collapsed {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar.collapsed .section-title,
|
||||||
|
#sidebar.collapsed .agent-summary,
|
||||||
|
#sidebar.collapsed .project-item span,
|
||||||
|
#sidebar.collapsed .project-delete-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar.collapsed .project-item::before {
|
||||||
|
content: '📁';
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 修改 5: 规约多条目管理 ✅
|
||||||
|
|
||||||
|
**需求**: 规约应支持多个条目,每条包括(项目编号,规约编号,规约类型,内容描述),全局规约不指定具体的项目编号
|
||||||
|
|
||||||
|
**数据库表结构**:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE rules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project_id INTEGER, -- NULL = 全局规约
|
||||||
|
rule_no TEXT NOT NULL, -- 规约编号 (R001, R002...)
|
||||||
|
rule_type TEXT NOT NULL, -- 规约类型
|
||||||
|
description TEXT NOT NULL, -- 内容描述
|
||||||
|
created_at DATETIME,
|
||||||
|
updated_at DATETIME,
|
||||||
|
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**API 端点**:
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | /api/rules/global | 获取全局规约列表 |
|
||||||
|
| GET | /api/rules/project/:id | 获取项目规约列表 |
|
||||||
|
| POST | /api/rules | 创建规约 |
|
||||||
|
| PUT | /api/rules/:id | 更新规约 |
|
||||||
|
| DELETE | /api/rules/:id | 删除规约 |
|
||||||
|
|
||||||
|
**前端界面**:
|
||||||
|
- 表格形式展示规约列表
|
||||||
|
- 列: 项目编号 / 规约编号 / 规约类型 / 内容描述
|
||||||
|
- 全局规约 Tab + 项目规约 Tab
|
||||||
|
- "添加规约" 按钮
|
||||||
|
- 表单包含: 规约编号、类型选择、描述输入
|
||||||
|
|
||||||
|
**规约类型**:
|
||||||
|
- 代码规范
|
||||||
|
- 提交规范
|
||||||
|
- 测试规范
|
||||||
|
- 文档规范
|
||||||
|
- 安全规范
|
||||||
|
- 其他
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 修改 6: 统计栏优化 ✅
|
||||||
|
|
||||||
|
**需求**: 缩小统计栏所占的高度
|
||||||
|
|
||||||
|
**改进**:
|
||||||
|
1. **布局改变**: 从垂直布局改为横向布局
|
||||||
|
2. **高度缩减**: padding 从 32px → 16px
|
||||||
|
3. **图标缩小**: 48px → 32px
|
||||||
|
4. **数字缩小**: 36px → 24px
|
||||||
|
5. **间距优化**: gap 从 24px → 16px
|
||||||
|
|
||||||
|
**对比**:
|
||||||
|
```
|
||||||
|
修改前:
|
||||||
|
┌─────────────┐
|
||||||
|
│ 📁 │
|
||||||
|
│ │
|
||||||
|
│ 总项目数 │
|
||||||
|
│ │
|
||||||
|
│ 8 │
|
||||||
|
│ │
|
||||||
|
└─────────────┘
|
||||||
|
高度: ~120px
|
||||||
|
|
||||||
|
修改后:
|
||||||
|
┌─────────────┐
|
||||||
|
│ 📁 总项目数 │
|
||||||
|
│ 8 │
|
||||||
|
└─────────────┘
|
||||||
|
高度: ~60px (缩减 50%)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSS 改动**:
|
||||||
|
```css
|
||||||
|
.stat-card {
|
||||||
|
padding: var(--spacing-md); /* 16px (原 32px) */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card-icon {
|
||||||
|
font-size: 32px; /* 原 48px */
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card-value {
|
||||||
|
font-size: 24px; /* 原 36px */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 代码变化
|
||||||
|
|
||||||
|
| 文件 | 修改前 | 修改后 | 增量 |
|
||||||
|
|------|--------|--------|------|
|
||||||
|
| index-v3.html | 2406 行 | 2582 行 | +176 行 |
|
||||||
|
| server-v3.js | 285 行 | 343 行 | +58 行 |
|
||||||
|
| **总计** | **2691 行** | **2925 行** | **+234 行** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 测试结果
|
||||||
|
|
||||||
|
### 侧边栏测试 ✅
|
||||||
|
- [x] 点击折叠按钮
|
||||||
|
- [x] 宽度变化流畅
|
||||||
|
- [x] 文字正确隐藏
|
||||||
|
- [x] 图标居中显示
|
||||||
|
- [x] hover 提示正常
|
||||||
|
|
||||||
|
### 规约功能测试 ✅
|
||||||
|
- [x] 创建全局规约
|
||||||
|
- [x] 创建项目规约
|
||||||
|
- [x] 规约列表显示
|
||||||
|
- [x] Tab 切换正常
|
||||||
|
- [x] 表格渲染完整
|
||||||
|
|
||||||
|
**测试数据**:
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/rules \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"rule_no": "R001",
|
||||||
|
"rule_type": "代码规范",
|
||||||
|
"description": "所有代码必须通过 ESLint 检查"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 响应: {"id":1, "rule_no":"R001", ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 统计栏测试 ✅
|
||||||
|
- [x] 高度明显缩小
|
||||||
|
- [x] 横向布局正确
|
||||||
|
- [x] 图标和数字对齐
|
||||||
|
- [x] 响应式正常
|
||||||
|
- [x] hover 效果保留
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 影响文件
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- `test-rules.sh` - 规约 API 测试脚本
|
||||||
|
|
||||||
|
### 修改
|
||||||
|
- `public/index-v3.html` - 前端界面
|
||||||
|
- 侧边栏收起样式
|
||||||
|
- 统计卡片布局
|
||||||
|
- 规约管理界面
|
||||||
|
- `server-v3.js` - 后端服务器
|
||||||
|
- 规约数据库表
|
||||||
|
- 规约 CRUD API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 功能对比
|
||||||
|
|
||||||
|
| 功能 | 修改前 | 修改后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| 侧边栏收起 | 仅缩小宽度 | 隐藏文字只显示图标 ✅ |
|
||||||
|
| 规约管理 | Markdown单文本 | 多条目表格管理 ✅ |
|
||||||
|
| 统计栏高度 | ~120px | ~60px ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 使用说明
|
||||||
|
|
||||||
|
### 侧边栏操作
|
||||||
|
1. 点击左上角 ☰ 按钮
|
||||||
|
2. 侧边栏收起,只显示图标
|
||||||
|
3. 再次点击展开
|
||||||
|
|
||||||
|
### 规约管理
|
||||||
|
1. 点击顶部 "📜 规约" 按钮
|
||||||
|
2. 选择 "全局规约" 或 "项目规约" Tab
|
||||||
|
3. 点击 "+ 添加规约" 按钮
|
||||||
|
4. 填写:
|
||||||
|
- 规约编号 (如 R001)
|
||||||
|
- 规约类型 (选择)
|
||||||
|
- 内容描述
|
||||||
|
5. 提交保存
|
||||||
|
|
||||||
|
### 查看统计
|
||||||
|
- Dashboard 页面自动显示
|
||||||
|
- 4个统计卡片紧凑排列
|
||||||
|
- 一目了然
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 验收确认
|
||||||
|
|
||||||
|
### 功能完整性
|
||||||
|
- [x] 侧边栏收起正常工作
|
||||||
|
- [x] 规约多条目管理完整
|
||||||
|
- [x] 统计栏高度已缩小
|
||||||
|
- [x] 所有 API 测试通过
|
||||||
|
- [x] 前端界面美观
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
- [x] 交互流畅无卡顿
|
||||||
|
- [x] 界面布局合理
|
||||||
|
- [x] 提示信息清晰
|
||||||
|
- [x] 响应式适配正常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 注意事项
|
||||||
|
|
||||||
|
1. **侧边栏状态**: 收起状态不会持久化,刷新页面后恢复展开
|
||||||
|
2. **规约编号**: 建议使用统一格式,如 R001, R002, R003...
|
||||||
|
3. **统计栏**: 数据每30秒自动刷新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**修改完成时间**: 2026-02-24 10:53
|
||||||
|
**修改人**: AI Assistant
|
||||||
|
**状态**: ✅ 生产就绪
|
||||||
|
**访问地址**: http://10.0.6.5:3000/index-v3.html
|
||||||
+1001
-36
File diff suppressed because it is too large
Load Diff
-19
@@ -1,19 +0,0 @@
|
|||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# 复制 package 文件
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# 安装依赖
|
|
||||||
RUN npm ci --only=production
|
|
||||||
|
|
||||||
# 复制应用代码
|
|
||||||
COPY server-postgres.js ./server.js
|
|
||||||
COPY public ./public
|
|
||||||
|
|
||||||
# 暴露端口
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# 启动应用
|
|
||||||
CMD ["node", "server.js"]
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# v3.0 项目文件清单
|
||||||
|
|
||||||
|
## 📁 核心文件
|
||||||
|
|
||||||
|
### 后端服务器
|
||||||
|
- **server-v3.js** (NEW) - SQLite 后端服务器,完整 API
|
||||||
|
- server-v2.js - PostgreSQL 版本 (备用)
|
||||||
|
- server.js - 旧版本 (已废弃)
|
||||||
|
|
||||||
|
### 前端界面
|
||||||
|
- **public/index-v3.html** - v3.0 主界面 (2,341 行)
|
||||||
|
- public/index-v3-backup.html - 备份文件
|
||||||
|
- public/index-v2-backup.html - v2 备份
|
||||||
|
- public/index.html - v2 界面
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- **tasks-v3.db** - SQLite 数据库 (自动创建)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 文档文件
|
||||||
|
|
||||||
|
### 设计文档
|
||||||
|
- **DESIGN-v3.md** - 界面设计规范
|
||||||
|
- **TASKS-v3.md** - 开发任务清单
|
||||||
|
- **API-V2.md** - API 接口文档
|
||||||
|
|
||||||
|
### 完成报告
|
||||||
|
- **COMPLETION-REPORT-v3.md** - 开发完成报告
|
||||||
|
- **FIX-REPORT.md** - 测试与修复报告
|
||||||
|
- **USER-GUIDE-v3.md** - 用户使用指南
|
||||||
|
|
||||||
|
### 测试文档
|
||||||
|
- **test-v3.md** - 测试清单
|
||||||
|
- **check-design.md** - 设计符合性检查
|
||||||
|
- test-report.md - 自动测试报告
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 工具脚本
|
||||||
|
|
||||||
|
### 测试和数据
|
||||||
|
- **test-and-fix.sh** - 自动化测试脚本
|
||||||
|
- **create-demo-data.sh** - 演示数据生成
|
||||||
|
- create-test-data.sh - 测试数据创建
|
||||||
|
|
||||||
|
### 开发工具
|
||||||
|
- enhance-v3.js - 页面增强脚本
|
||||||
|
- add-css.js - CSS 添加脚本
|
||||||
|
- add-js-functions.js - JS 功能添加
|
||||||
|
- add-p2-features.js - P2 功能添加
|
||||||
|
- add-parent-task.js - 父任务选择添加
|
||||||
|
- fix-render-projects.js - 修复 renderProjects
|
||||||
|
- convert-to-sqlite.js - PostgreSQL 转 SQLite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 统计信息
|
||||||
|
|
||||||
|
| 文件类型 | 数量 | 说明 |
|
||||||
|
|---------|------|------|
|
||||||
|
| 后端服务器 | 1 | server-v3.js (生产) |
|
||||||
|
| 前端界面 | 1 | index-v3.html (生产) |
|
||||||
|
| 数据库 | 1 | tasks-v3.db (自动) |
|
||||||
|
| 设计文档 | 3 | DESIGN/TASKS/API |
|
||||||
|
| 完成报告 | 3 | COMPLETION/FIX/USER-GUIDE |
|
||||||
|
| 测试文档 | 3 | test-v3/check-design/test-report |
|
||||||
|
| 工具脚本 | 10 | 测试/数据/开发工具 |
|
||||||
|
| **总计** | **22** | **完整项目文件** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 访问地址
|
||||||
|
|
||||||
|
### 生产环境
|
||||||
|
- http://10.0.6.5:3000/index-v3.html
|
||||||
|
|
||||||
|
### 本地开发
|
||||||
|
- http://localhost:3000/index-v3.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 进入项目目录
|
||||||
|
cd /data/openclaw/workspace/agent-task-service
|
||||||
|
|
||||||
|
# 2. 启动服务器
|
||||||
|
node server-v3.js
|
||||||
|
|
||||||
|
# 3. 访问界面
|
||||||
|
# 浏览器打开: http://10.0.6.5:3000/index-v3.html
|
||||||
|
|
||||||
|
# 4. (可选) 创建演示数据
|
||||||
|
./create-demo-data.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 重要说明
|
||||||
|
|
||||||
|
### 生产文件
|
||||||
|
**只需这 3 个文件即可运行**:
|
||||||
|
1. server-v3.js (后端)
|
||||||
|
2. public/index-v3.html (前端)
|
||||||
|
3. tasks-v3.db (数据库,自动创建)
|
||||||
|
|
||||||
|
### 其他文件
|
||||||
|
- 文档类: 供参考和维护
|
||||||
|
- 备份类: 用于回滚
|
||||||
|
- 工具类: 用于开发和测试
|
||||||
|
- 旧版本: 历史版本保留
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**版本**: v3.0
|
||||||
|
**状态**: ✅ 生产就绪
|
||||||
|
**更新**: 2026-02-24
|
||||||
-833
@@ -1,833 +0,0 @@
|
|||||||
# v3.0 界面开发任务列表
|
|
||||||
|
|
||||||
## 📋 模块化拆分
|
|
||||||
|
|
||||||
### 🏗️ 架构层级
|
|
||||||
|
|
||||||
```
|
|
||||||
v3.0 界面
|
|
||||||
├── 1. 基础架构层
|
|
||||||
│ ├── 1.1 HTML 骨架
|
|
||||||
│ ├── 1.2 CSS 设计系统
|
|
||||||
│ └── 1.3 JavaScript 核心类
|
|
||||||
├── 2. 布局组件层
|
|
||||||
│ ├── 2.1 Header 组件
|
|
||||||
│ ├── 2.2 Sidebar 组件
|
|
||||||
│ └── 2.3 Main Content 容器
|
|
||||||
├── 3. 页面模块层
|
|
||||||
│ ├── 3.1 项目概览页
|
|
||||||
│ ├── 3.2 任务看板页
|
|
||||||
│ ├── 3.3 智能体管理页
|
|
||||||
│ └── 3.4 全局规约浮层
|
|
||||||
└── 4. 交互功能层
|
|
||||||
├── 4.1 任务详情侧边栏
|
|
||||||
├── 4.2 拖拽功能
|
|
||||||
├── 4.3 表单处理
|
|
||||||
└── 4.4 实时更新
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 详细任务清单
|
|
||||||
|
|
||||||
### Phase 1: 基础架构层 (预计 1-2 小时)
|
|
||||||
|
|
||||||
#### ✅ 任务 1.1: HTML 骨架搭建
|
|
||||||
**目标**: 创建基本的 HTML 结构
|
|
||||||
|
|
||||||
**文件**: `public/index-v3.html`
|
|
||||||
|
|
||||||
**内容**:
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
- Meta 标签
|
|
||||||
- CSS 样式引用
|
|
||||||
- 第三方库 CDN (marked.js, sortable.js)
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app">
|
|
||||||
<header id="header"></header>
|
|
||||||
<div class="app-body">
|
|
||||||
<aside id="sidebar"></aside>
|
|
||||||
<main id="main-content"></main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 浮层 -->
|
|
||||||
<div id="task-detail-sidebar"></div>
|
|
||||||
<div id="overlay"></div>
|
|
||||||
<div id="global-rules-modal"></div>
|
|
||||||
|
|
||||||
<script src="app.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] HTML 结构完整
|
|
||||||
- [ ] 基本布局可见
|
|
||||||
- [ ] 无控制台错误
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 1.2: CSS 设计系统
|
|
||||||
**目标**: 定义 CSS 变量和基础样式
|
|
||||||
|
|
||||||
**内容**:
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* 主题色 */
|
|
||||||
--primary: #667eea;
|
|
||||||
--primary-dark: #764ba2;
|
|
||||||
|
|
||||||
/* 状态色 */
|
|
||||||
--status-initial: #3498db;
|
|
||||||
--status-in-progress: #f39c12;
|
|
||||||
--status-testing-pending: #9b59b6;
|
|
||||||
--status-testing: #e67e22;
|
|
||||||
--status-completed: #27ae60;
|
|
||||||
|
|
||||||
/* 间距 */
|
|
||||||
--spacing-xs: 4px;
|
|
||||||
--spacing-sm: 8px;
|
|
||||||
--spacing-md: 16px;
|
|
||||||
--spacing-lg: 24px;
|
|
||||||
--spacing-xl: 32px;
|
|
||||||
|
|
||||||
/* 其他变量... */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset & Base Styles */
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body { font-family: 'Segoe UI', sans-serif; }
|
|
||||||
|
|
||||||
/* 布局样式 */
|
|
||||||
#app { display: flex; flex-direction: column; height: 100vh; }
|
|
||||||
.app-body { display: flex; flex: 1; overflow: hidden; }
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] CSS 变量定义完整
|
|
||||||
- [ ] 基础样式无冲突
|
|
||||||
- [ ] 布局正常显示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 1.3: JavaScript 核心类
|
|
||||||
**目标**: 创建核心管理类
|
|
||||||
|
|
||||||
**内容**:
|
|
||||||
```javascript
|
|
||||||
// API 管理类
|
|
||||||
class API {
|
|
||||||
constructor(baseURL = '/api') {
|
|
||||||
this.baseURL = baseURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(endpoint) { /* ... */ }
|
|
||||||
async post(endpoint, data) { /* ... */ }
|
|
||||||
async put(endpoint, data) { /* ... */ }
|
|
||||||
async delete(endpoint) { /* ... */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态管理类
|
|
||||||
class AppState {
|
|
||||||
constructor() {
|
|
||||||
this.currentProject = null;
|
|
||||||
this.projects = [];
|
|
||||||
this.agents = [];
|
|
||||||
this.stats = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentProject(project) { /* ... */ }
|
|
||||||
updateStats(stats) { /* ... */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 路由管理类
|
|
||||||
class Router {
|
|
||||||
constructor() {
|
|
||||||
this.routes = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
register(name, handler) { /* ... */ }
|
|
||||||
navigate(name, params) { /* ... */ }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] API 类可正常调用
|
|
||||||
- [ ] 状态管理可读写
|
|
||||||
- [ ] 路由切换无误
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 2: 布局组件层 (预计 2-3 小时)
|
|
||||||
|
|
||||||
#### ✅ 任务 2.1: Header 组件
|
|
||||||
**目标**: 实现顶部导航栏
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. Logo 和标题
|
|
||||||
2. 全局统计卡片 (3-4个小卡片)
|
|
||||||
3. 全局规约按钮
|
|
||||||
4. 通知按钮(占位)
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<header id="header">
|
|
||||||
<div class="header-left">
|
|
||||||
<h1>🤖 任务管理系统 v3.0</h1>
|
|
||||||
</div>
|
|
||||||
<div class="header-center">
|
|
||||||
<div class="stat-mini">📁 项目: <span id="stat-projects">0</span></div>
|
|
||||||
<div class="stat-mini">📋 任务: <span id="stat-tasks">0</span></div>
|
|
||||||
<div class="stat-mini">🤖 智能体: <span id="stat-agents">0</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="header-right">
|
|
||||||
<button id="btn-global-rules">📜 规约</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] Header 固定在顶部
|
|
||||||
- [ ] 统计数字实时更新
|
|
||||||
- [ ] 规约按钮点击弹窗
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 2.2: Sidebar 组件
|
|
||||||
**目标**: 实现左侧导航栏
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 项目列表(可点击选择)
|
|
||||||
2. 智能体入口
|
|
||||||
3. 折叠/展开功能
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<aside id="sidebar">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<button id="btn-collapse">☰</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-section">
|
|
||||||
<div class="section-title">
|
|
||||||
📁 项目
|
|
||||||
<button id="btn-new-project">+</button>
|
|
||||||
</div>
|
|
||||||
<ul id="project-list">
|
|
||||||
<!-- 动态渲染 -->
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-section">
|
|
||||||
<div class="section-title">🤖 智能体</div>
|
|
||||||
<div class="agent-summary">
|
|
||||||
<div>⚪ 空闲: <span id="agents-idle">0</span></div>
|
|
||||||
<div>🟢 工作: <span id="agents-busy">0</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 项目列表可渲染
|
|
||||||
- [ ] 选中项目高亮
|
|
||||||
- [ ] 点击切换主内容区
|
|
||||||
- [ ] 折叠功能正常
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 2.3: Main Content 容器
|
|
||||||
**目标**: 主内容区框架
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 空状态提示
|
|
||||||
2. 加载状态
|
|
||||||
3. 内容动态切换
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<main id="main-content">
|
|
||||||
<div id="content-loading" class="hidden">
|
|
||||||
<div class="spinner">加载中...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content-empty" class="empty-state">
|
|
||||||
<p>请选择一个项目</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content-project-overview" class="hidden">
|
|
||||||
<!-- 项目概览页内容 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content-task-board" class="hidden">
|
|
||||||
<!-- 任务看板页内容 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content-agents" class="hidden">
|
|
||||||
<!-- 智能体管理页内容 -->
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 多页面切换无闪烁
|
|
||||||
- [ ] 加载状态显示正常
|
|
||||||
- [ ] 空状态友好提示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 3: 页面模块层 (预计 4-5 小时)
|
|
||||||
|
|
||||||
#### ✅ 任务 3.1: 项目概览页
|
|
||||||
**目标**: 展示项目详情和统计
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 项目标题和描述
|
|
||||||
2. 任务统计卡片
|
|
||||||
3. 最近任务列表
|
|
||||||
4. Tab 切换(概览/任务/规约/共享清单)
|
|
||||||
|
|
||||||
**数据来源**:
|
|
||||||
- `GET /api/projects/:id`
|
|
||||||
- `GET /api/tasks?project_id=xxx`
|
|
||||||
- `GET /api/projects/:id/rules`
|
|
||||||
- `GET /api/projects/:id/notes`
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 项目信息正确显示
|
|
||||||
- [ ] 任务统计准确
|
|
||||||
- [ ] 最近任务可点击
|
|
||||||
- [ ] Tab 切换流畅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 3.2: 任务看板页 (Kanban)
|
|
||||||
**目标**: 可视化任务流转
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 5列看板(初始/进行中/待测试/测试中/完成)
|
|
||||||
2. 任务卡片渲染
|
|
||||||
3. 拖拽改变状态
|
|
||||||
4. 过滤和排序
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<div id="kanban-board">
|
|
||||||
<div class="kanban-header">
|
|
||||||
<button id="btn-new-task">+ 新建任务</button>
|
|
||||||
<input type="text" id="task-search" placeholder="🔍 搜索...">
|
|
||||||
<select id="task-filter-type"><!-- 任务类型 --></select>
|
|
||||||
<select id="task-filter-priority"><!-- 优先级 --></select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="kanban-columns">
|
|
||||||
<div class="kanban-column" data-status="initial">
|
|
||||||
<h3>初始 <span class="count">3</span></h3>
|
|
||||||
<div class="task-list" id="tasks-initial">
|
|
||||||
<!-- 任务卡片 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 其他列... -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**任务卡片结构**:
|
|
||||||
```html
|
|
||||||
<div class="task-card" data-task-id="xxx">
|
|
||||||
<div class="task-title">实现用户登录</div>
|
|
||||||
<div class="task-meta">
|
|
||||||
<span class="badge type-development">开发</span>
|
|
||||||
<span class="badge priority-high">高</span>
|
|
||||||
</div>
|
|
||||||
<div class="task-footer">
|
|
||||||
<span class="assignee">🤖 智能体A</span>
|
|
||||||
<div class="task-icons">
|
|
||||||
<span>🔗 3</span> <!-- 子任务数 -->
|
|
||||||
<span>📎 2</span> <!-- 附件数 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 看板正确分列
|
|
||||||
- [ ] 任务卡片显示完整
|
|
||||||
- [ ] 拖拽流畅无卡顿
|
|
||||||
- [ ] 拖拽后状态更新成功
|
|
||||||
- [ ] 过滤功能正常
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 3.3: 智能体管理页
|
|
||||||
**目标**: 查看和管理智能体
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 智能体列表(表格形式)
|
|
||||||
2. 状态筛选
|
|
||||||
3. 选中显示详情
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<div id="agents-page">
|
|
||||||
<div class="page-header">
|
|
||||||
<h2>🤖 智能体管理</h2>
|
|
||||||
<button id="btn-register-agent">注册新智能体</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-tabs">
|
|
||||||
<button class="active" data-filter="all">全部</button>
|
|
||||||
<button data-filter="idle">空闲</button>
|
|
||||||
<button data-filter="busy">工作中</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="agents-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>名称</th>
|
|
||||||
<th>状态</th>
|
|
||||||
<th>当前任务</th>
|
|
||||||
<th>完成数</th>
|
|
||||||
<th>最后心跳</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="agents-table-body">
|
|
||||||
<!-- 动态渲染 -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div id="agent-detail-panel" class="hidden">
|
|
||||||
<!-- 选中智能体的详情 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 智能体列表正确显示
|
|
||||||
- [ ] 状态筛选有效
|
|
||||||
- [ ] 点击显示详情
|
|
||||||
- [ ] 心跳时间正确格式化
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 3.4: 全局规约浮层
|
|
||||||
**目标**: 查看和编辑规约
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 全局规约展示
|
|
||||||
2. 项目规约展示(如果选中项目)
|
|
||||||
3. Markdown 渲染
|
|
||||||
4. 编辑模式
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<div id="global-rules-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2>📜 协作规约</h2>
|
|
||||||
<button class="btn-close">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-tabs">
|
|
||||||
<button class="active" data-tab="global">全局规约</button>
|
|
||||||
<button data-tab="project">项目规约</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="tab-global-rules" class="tab-content">
|
|
||||||
<div id="global-rules-display" class="markdown-content">
|
|
||||||
<!-- Markdown 渲染 -->
|
|
||||||
</div>
|
|
||||||
<button id="btn-edit-global-rules">编辑</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="tab-project-rules" class="tab-content hidden">
|
|
||||||
<!-- 同上 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 模态框正确显示
|
|
||||||
- [ ] Markdown 正确渲染
|
|
||||||
- [ ] Tab 切换正常
|
|
||||||
- [ ] 编辑功能可用
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 4: 交互功能层 (预计 3-4 小时)
|
|
||||||
|
|
||||||
#### ✅ 任务 4.1: 任务详情侧边栏
|
|
||||||
**目标**: 从右侧滑出显示任务详情
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 滑入/滑出动画
|
|
||||||
2. 任务完整信息展示
|
|
||||||
3. 状态/类型/优先级下拉选择
|
|
||||||
4. 附件上传
|
|
||||||
5. 子任务管理
|
|
||||||
|
|
||||||
**HTML 结构**:
|
|
||||||
```html
|
|
||||||
<div id="task-detail-sidebar" class="sidebar-right">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<button class="btn-close">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-content">
|
|
||||||
<h2 id="task-detail-title">任务标题</h2>
|
|
||||||
<div class="task-actions">
|
|
||||||
<button id="btn-edit-task">编辑</button>
|
|
||||||
<button id="btn-delete-task" class="danger">删除</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-section">
|
|
||||||
<label>状态</label>
|
|
||||||
<select id="task-status-select">
|
|
||||||
<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 class="detail-section">
|
|
||||||
<label>描述</label>
|
|
||||||
<div id="task-description" class="markdown-content">
|
|
||||||
<!-- Markdown 渲染 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-section">
|
|
||||||
<label>📎 附件 (<span id="attachment-count">0</span>)</label>
|
|
||||||
<ul id="attachment-list"></ul>
|
|
||||||
<input type="file" id="file-upload" hidden>
|
|
||||||
<button id="btn-upload">上传附件</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-section">
|
|
||||||
<label>🔀 子任务 (<span id="subtask-progress">0/0</span>)</label>
|
|
||||||
<ul id="subtask-list"></ul>
|
|
||||||
<button id="btn-add-subtask">+ 添加子任务</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
**CSS 动画**:
|
|
||||||
```css
|
|
||||||
.sidebar-right {
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: 60px;
|
|
||||||
width: 480px;
|
|
||||||
height: calc(100vh - 60px);
|
|
||||||
background: white;
|
|
||||||
box-shadow: -4px 0 16px rgba(0,0,0,0.15);
|
|
||||||
transform: translateX(100%);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-right.active {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 点击任务卡片弹出侧边栏
|
|
||||||
- [ ] 动画流畅
|
|
||||||
- [ ] 所有信息正确显示
|
|
||||||
- [ ] 状态修改即时生效
|
|
||||||
- [ ] 附件上传成功
|
|
||||||
- [ ] 子任务可添加
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 4.2: 拖拽功能
|
|
||||||
**目标**: 实现任务卡片拖拽
|
|
||||||
|
|
||||||
**技术选型**: Sortable.js
|
|
||||||
|
|
||||||
**实现代码**:
|
|
||||||
```javascript
|
|
||||||
// 初始化拖拽
|
|
||||||
function initKanbanDrag() {
|
|
||||||
const columns = document.querySelectorAll('.task-list');
|
|
||||||
|
|
||||||
columns.forEach(column => {
|
|
||||||
new Sortable(column, {
|
|
||||||
group: 'tasks',
|
|
||||||
animation: 150,
|
|
||||||
ghostClass: 'task-ghost',
|
|
||||||
chosenClass: 'task-chosen',
|
|
||||||
dragClass: 'task-drag',
|
|
||||||
|
|
||||||
onEnd: async function(evt) {
|
|
||||||
const taskId = evt.item.dataset.taskId;
|
|
||||||
const oldStatus = evt.from.parentElement.dataset.status;
|
|
||||||
const newStatus = evt.to.parentElement.dataset.status;
|
|
||||||
|
|
||||||
if (oldStatus !== newStatus) {
|
|
||||||
await updateTaskStatus(taskId, newStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateTaskStatus(taskId, newStatus) {
|
|
||||||
try {
|
|
||||||
await api.put(`/tasks/${taskId}`, { status: newStatus });
|
|
||||||
showToast('任务状态已更新', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
showToast('更新失败: ' + error.message, 'error');
|
|
||||||
// 刷新看板恢复原状
|
|
||||||
loadKanbanBoard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 可以拖拽任务卡片
|
|
||||||
- [ ] 拖拽有视觉反馈
|
|
||||||
- [ ] 放下后API调用成功
|
|
||||||
- [ ] 失败时有错误提示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 4.3: 表单处理
|
|
||||||
**目标**: 创建/编辑项目和任务
|
|
||||||
|
|
||||||
**功能点**:
|
|
||||||
1. 创建项目表单
|
|
||||||
2. 创建任务表单
|
|
||||||
3. 表单验证
|
|
||||||
4. 提交反馈
|
|
||||||
|
|
||||||
**实现**:
|
|
||||||
```javascript
|
|
||||||
// 创建项目
|
|
||||||
async function showCreateProjectForm() {
|
|
||||||
const formHTML = `
|
|
||||||
<form id="form-create-project">
|
|
||||||
<input type="text" name="name" placeholder="项目名称" required>
|
|
||||||
<textarea name="description" placeholder="项目描述"></textarea>
|
|
||||||
<button type="submit">创建</button>
|
|
||||||
<button type="button" onclick="closeModal()">取消</button>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
showModal('创建项目', formHTML);
|
|
||||||
|
|
||||||
document.getElementById('form-create-project').addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const data = new FormData(e.target);
|
|
||||||
const project = {
|
|
||||||
name: data.get('name'),
|
|
||||||
description: data.get('description'),
|
|
||||||
created_by: 'human'
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await api.post('/projects', project);
|
|
||||||
showToast('项目创建成功', 'success');
|
|
||||||
closeModal();
|
|
||||||
await loadProjects();
|
|
||||||
} catch (error) {
|
|
||||||
showToast('创建失败: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 表单可正常提交
|
|
||||||
- [ ] 验证错误有提示
|
|
||||||
- [ ] 成功后刷新列表
|
|
||||||
- [ ] 失败有错误提示
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### ✅ 任务 4.4: 实时更新
|
|
||||||
**目标**: 定期刷新数据
|
|
||||||
|
|
||||||
**实现**:
|
|
||||||
```javascript
|
|
||||||
class RealtimeUpdater {
|
|
||||||
constructor(interval = 30000) { // 30秒
|
|
||||||
this.interval = interval;
|
|
||||||
this.timerId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
this.timerId = setInterval(async () => {
|
|
||||||
await this.update();
|
|
||||||
}, this.interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if (this.timerId) {
|
|
||||||
clearInterval(this.timerId);
|
|
||||||
this.timerId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async update() {
|
|
||||||
// 更新统计
|
|
||||||
const stats = await api.get('/stats');
|
|
||||||
appState.updateStats(stats);
|
|
||||||
updateHeaderStats(stats);
|
|
||||||
|
|
||||||
// 如果在看板页面,刷新看板
|
|
||||||
if (router.currentRoute === 'kanban') {
|
|
||||||
await loadKanbanBoard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updater = new RealtimeUpdater();
|
|
||||||
updater.start();
|
|
||||||
```
|
|
||||||
|
|
||||||
**验收标准**:
|
|
||||||
- [ ] 统计数字定期更新
|
|
||||||
- [ ] 看板数据自动刷新
|
|
||||||
- [ ] 不影响用户操作
|
|
||||||
- [ ] 页面卸载时停止
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 5: 优化与测试 (预计 2-3 小时)
|
|
||||||
|
|
||||||
#### ✅ 任务 5.1: 性能优化
|
|
||||||
**检查项**:
|
|
||||||
- [ ] 减少不必要的 API 调用
|
|
||||||
- [ ] 使用防抖/节流
|
|
||||||
- [ ] 虚拟滚动(如果任务数量巨大)
|
|
||||||
- [ ] 图片懒加载
|
|
||||||
|
|
||||||
#### ✅ 任务 5.2: 错误处理
|
|
||||||
**检查项**:
|
|
||||||
- [ ] 网络错误提示
|
|
||||||
- [ ] API 错误提示
|
|
||||||
- [ ] 404 页面
|
|
||||||
- [ ] 权限错误处理
|
|
||||||
|
|
||||||
#### ✅ 任务 5.3: 加载状态
|
|
||||||
**检查项**:
|
|
||||||
- [ ] Skeleton 骨架屏
|
|
||||||
- [ ] Loading 转圈
|
|
||||||
- [ ] 按钮禁用状态
|
|
||||||
- [ ] 进度条
|
|
||||||
|
|
||||||
#### ✅ 任务 5.4: 响应式适配
|
|
||||||
**检查项**:
|
|
||||||
- [ ] 移动端布局
|
|
||||||
- [ ] 平板适配
|
|
||||||
- [ ] 触摸事件支持
|
|
||||||
- [ ] 横屏适配
|
|
||||||
|
|
||||||
#### ✅ 任务 5.5: 浏览器兼容
|
|
||||||
**检查项**:
|
|
||||||
- [ ] Chrome 测试
|
|
||||||
- [ ] Firefox 测试
|
|
||||||
- [ ] Safari 测试
|
|
||||||
- [ ] Edge 测试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 任务优先级
|
|
||||||
|
|
||||||
### P0 (必须完成)
|
|
||||||
1. 任务 1.1-1.3: 基础架构
|
|
||||||
2. 任务 2.1-2.3: 布局组件
|
|
||||||
3. 任务 3.2: 任务看板
|
|
||||||
4. 任务 4.1: 任务详情侧边栏
|
|
||||||
5. 任务 4.2: 拖拽功能
|
|
||||||
|
|
||||||
### P1 (重要)
|
|
||||||
6. 任务 3.1: 项目概览
|
|
||||||
7. 任务 3.3: 智能体管理
|
|
||||||
8. 任务 4.3: 表单处理
|
|
||||||
|
|
||||||
### P2 (可选)
|
|
||||||
9. 任务 3.4: 全局规约浮层
|
|
||||||
10. 任务 4.4: 实时更新
|
|
||||||
11. 任务 5.1-5.5: 优化与测试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 实施计划
|
|
||||||
|
|
||||||
### 第一轮迭代 (今晚完成)
|
|
||||||
- ✅ Phase 1: 基础架构 (1-2h)
|
|
||||||
- ✅ Phase 2: 布局组件 (2-3h)
|
|
||||||
- ✅ 任务 3.2: 看板页面 (2-3h)
|
|
||||||
- ✅ 任务 4.2: 拖拽功能 (1h)
|
|
||||||
|
|
||||||
**目标**: 实现核心功能,可以查看和拖拽任务
|
|
||||||
|
|
||||||
### 第二轮迭代 (明天)
|
|
||||||
- ✅ 任务 4.1: 任务详情侧边栏 (2-3h)
|
|
||||||
- ✅ 任务 3.1: 项目概览 (1-2h)
|
|
||||||
- ✅ 任务 3.3: 智能体管理 (1-2h)
|
|
||||||
- ✅ Phase 5: 优化与测试 (2-3h)
|
|
||||||
|
|
||||||
**目标**: 完整功能和优化
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 验收清单
|
|
||||||
|
|
||||||
### 功能完整性
|
|
||||||
- [ ] 可以查看项目列表
|
|
||||||
- [ ] 可以选择项目查看详情
|
|
||||||
- [ ] 可以看到任务看板
|
|
||||||
- [ ] 可以拖拽改变任务状态
|
|
||||||
- [ ] 可以点击任务查看详情
|
|
||||||
- [ ] 可以创建项目
|
|
||||||
- [ ] 可以创建任务
|
|
||||||
- [ ] 可以上传附件
|
|
||||||
- [ ] 可以管理子任务
|
|
||||||
- [ ] 可以查看智能体列表
|
|
||||||
- [ ] 可以查看规约
|
|
||||||
|
|
||||||
### 交互体验
|
|
||||||
- [ ] 拖拽流畅无卡顿
|
|
||||||
- [ ] 侧边栏动画平滑
|
|
||||||
- [ ] 加载状态友好
|
|
||||||
- [ ] 错误提示清晰
|
|
||||||
- [ ] 操作有即时反馈
|
|
||||||
|
|
||||||
### 视觉规范
|
|
||||||
- [ ] 颜色符合设计系统
|
|
||||||
- [ ] 间距统一
|
|
||||||
- [ ] 字体大小一致
|
|
||||||
- [ ] 阴影效果合理
|
|
||||||
|
|
||||||
### 性能指标
|
|
||||||
- [ ] 首屏加载 < 2秒
|
|
||||||
- [ ] API 响应 < 500ms
|
|
||||||
- [ ] 拖拽延迟 < 100ms
|
|
||||||
- [ ] 动画帧率 > 30fps
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 开发日志
|
|
||||||
|
|
||||||
### 2026-02-15 22:33
|
|
||||||
- ✅ 完成设计文档
|
|
||||||
- ✅ 完成模块化拆分
|
|
||||||
- ✅ 生成任务列表
|
|
||||||
- ⏳ 等待开始实施...
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**总预计时间**: 11-17 小时
|
|
||||||
**分阶段实施**: 2轮迭代完成
|
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# v3.0 使用指南
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 启动服务器
|
||||||
|
```bash
|
||||||
|
cd /data/openclaw/workspace/agent-task-service
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 访问界面
|
||||||
|
浏览器打开: `http://10.0.6.5:3000/index-v3.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 功能说明
|
||||||
|
|
||||||
|
### 🏠 首页 - Dashboard 仪表盘
|
||||||
|
- **统计卡片**: 显示项目总数、任务总数、进行中任务、已完成任务
|
||||||
|
- **最近项目**: 快速访问最近活跃的项目
|
||||||
|
- **默认显示**: 打开页面即显示
|
||||||
|
|
||||||
|
### 📁 项目管理
|
||||||
|
1. **创建项目**
|
||||||
|
- 点击侧边栏 `+ 新建项目`
|
||||||
|
- 填写项目名称和描述
|
||||||
|
- 提交创建
|
||||||
|
|
||||||
|
2. **查看项目**
|
||||||
|
- 点击侧边栏项目名称
|
||||||
|
- 自动切换到 Kanban 看板
|
||||||
|
|
||||||
|
### 📋 任务管理(Kanban 看板)
|
||||||
|
|
||||||
|
#### 查看任务
|
||||||
|
- **5列布局**: 初始 → 进行中 → 待测试 → 测试中 → 完成
|
||||||
|
- **任务卡片**: 显示标题、类型、优先级、执行者、子任务数、附件数
|
||||||
|
|
||||||
|
#### 创建任务
|
||||||
|
1. 点击 `+ 新建任务`
|
||||||
|
2. 填写信息:
|
||||||
|
- 任务标题 ✅
|
||||||
|
- 任务描述 (支持 Markdown) ✅
|
||||||
|
- 类型 (需求/设计/开发/测试/部署) ✅
|
||||||
|
- 优先级 (低/普通/高) ✅
|
||||||
|
- **父任务** (可选,创建子任务) ✅
|
||||||
|
- **执行者** (可选,分配智能体) ✅
|
||||||
|
3. 提交创建
|
||||||
|
|
||||||
|
#### 拖拽改变状态
|
||||||
|
- 鼠标按住任务卡片
|
||||||
|
- 拖动到目标列
|
||||||
|
- 松开鼠标即可更新状态
|
||||||
|
|
||||||
|
#### 查看任务详情
|
||||||
|
- 点击任务卡片
|
||||||
|
- 右侧弹出详情侧边栏
|
||||||
|
- 可以:
|
||||||
|
- 修改状态/类型/优先级/执行者
|
||||||
|
- 查看 Markdown 描述
|
||||||
|
- 上传/下载/删除附件
|
||||||
|
- 管理子任务
|
||||||
|
- 添加子任务 (**新功能**)
|
||||||
|
- 删除任务
|
||||||
|
|
||||||
|
#### 搜索和过滤
|
||||||
|
- **搜索框**: 输入关键词实时搜索
|
||||||
|
- **类型过滤**: 按需求/设计/开发/测试/部署筛选
|
||||||
|
- **优先级过滤**: 按高/普通/低筛选
|
||||||
|
|
||||||
|
### 🤖 智能体管理
|
||||||
|
|
||||||
|
#### 查看智能体
|
||||||
|
- 点击侧边栏 `🤖 智能体管理`
|
||||||
|
- 查看所有注册的智能体
|
||||||
|
- 显示: 名称、状态、当前任务、完成数、最后心跳
|
||||||
|
|
||||||
|
#### 状态筛选
|
||||||
|
- **全部**: 显示所有智能体
|
||||||
|
- **空闲**: 仅显示空闲的智能体
|
||||||
|
- **工作中**: 仅显示正在工作的智能体
|
||||||
|
|
||||||
|
### 📜 协作规约
|
||||||
|
|
||||||
|
#### 查看规约
|
||||||
|
- 点击顶部 `📜 规约` 按钮
|
||||||
|
- 弹出规约浮层
|
||||||
|
|
||||||
|
#### Tab 切换 (**新功能**)
|
||||||
|
- **全局规约**: 适用于所有项目的规则
|
||||||
|
- **项目规约**: 当前项目的特定规则
|
||||||
|
- 点击 Tab 标签即可切换
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 界面特色
|
||||||
|
|
||||||
|
### 视觉设计
|
||||||
|
- 🎨 **渐变主题**: 紫蓝渐变 (primary → primary-dark)
|
||||||
|
- 📊 **状态色**: 每种任务状态有独特颜色
|
||||||
|
- 🏷️ **类型徽章**: 需求/设计/开发/测试/部署一目了然
|
||||||
|
- 🚨 **优先级**: 高/普通/低清晰标识
|
||||||
|
|
||||||
|
### 交互体验
|
||||||
|
- 🖱️ **流畅拖拽**: 拖动任务改变状态
|
||||||
|
- ✨ **平滑动画**: 侧边栏滑入/滑出
|
||||||
|
- 📱 **响应式**: 适配桌面/平板/手机
|
||||||
|
- ⚡ **实时更新**: 30秒自动刷新数据
|
||||||
|
- 🔍 **搜索防抖**: 减少不必要的请求
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
- 💬 **Toast 提示**: 操作成功/失败即时反馈
|
||||||
|
- ⏳ **加载状态**: Loading 转圈友好提示
|
||||||
|
- ❌ **错误处理**: 清晰的错误提示
|
||||||
|
- 🎯 **快捷操作**: 最少点击完成任务
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 快捷操作
|
||||||
|
|
||||||
|
### 侧边栏导航
|
||||||
|
- 点击 `📊 仪表盘` → 查看全局统计
|
||||||
|
- 点击 `🤖 智能体管理` → 管理智能体
|
||||||
|
- 点击项目名称 → 查看项目任务看板
|
||||||
|
|
||||||
|
### Kanban 看板
|
||||||
|
- 点击任务卡片 → 查看详情
|
||||||
|
- 拖拽任务卡片 → 改变状态
|
||||||
|
- 搜索框输入 → 实时搜索
|
||||||
|
- 下拉菜单选择 → 快速过滤
|
||||||
|
|
||||||
|
### 任务详情
|
||||||
|
- 下拉选择器 → 即时更新字段
|
||||||
|
- 点击子任务 → 切换完成状态
|
||||||
|
- 点击附件 → 下载文件
|
||||||
|
- 点击 ✕ → 删除附件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 高级功能
|
||||||
|
|
||||||
|
### 子任务管理
|
||||||
|
1. 打开父任务详情
|
||||||
|
2. 滚动到 `🔀 子任务` 部分
|
||||||
|
3. 点击 `+ 添加子任务`
|
||||||
|
4. 输入子任务标题
|
||||||
|
5. 提交即可创建
|
||||||
|
|
||||||
|
### 父子任务关联
|
||||||
|
- **创建时选择**: 创建任务时选择父任务
|
||||||
|
- **自动关联**: 子任务自动归属于父任务
|
||||||
|
- **层级显示**: 父任务卡片显示子任务数量
|
||||||
|
- **独立管理**: 子任务也可以独立操作
|
||||||
|
|
||||||
|
### 执行者分配
|
||||||
|
- **创建时分配**: 创建任务时选择执行者
|
||||||
|
- **详情中修改**: 任务详情中更改执行者
|
||||||
|
- **状态显示**: 智能体管理页查看当前任务
|
||||||
|
- **智能提示**: 显示智能体状态 (空闲/工作中)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 使用技巧
|
||||||
|
|
||||||
|
### 1. 高效创建任务
|
||||||
|
- 先创建父任务(主功能)
|
||||||
|
- 再创建子任务(具体步骤)
|
||||||
|
- 父任务标题要简洁明确
|
||||||
|
- 子任务标题要具体可执行
|
||||||
|
|
||||||
|
### 2. 合理分配优先级
|
||||||
|
- **高**: 紧急且重要,立即处理
|
||||||
|
- **普通**: 常规任务,按计划完成
|
||||||
|
- **低**: 可延后的任务
|
||||||
|
|
||||||
|
### 3. 充分利用 Markdown
|
||||||
|
- 任务描述支持 Markdown 格式
|
||||||
|
- 可以添加标题、列表、代码块
|
||||||
|
- 让任务说明更清晰易读
|
||||||
|
|
||||||
|
### 4. 定期查看 Dashboard
|
||||||
|
- 了解全局项目进展
|
||||||
|
- 发现瓶颈和延期任务
|
||||||
|
- 及时调整资源分配
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 常见问题
|
||||||
|
|
||||||
|
### Q: 拖拽后任务状态没有更新?
|
||||||
|
A: 检查后端服务器是否正常运行,查看浏览器控制台是否有错误提示。
|
||||||
|
|
||||||
|
### Q: 附件上传失败?
|
||||||
|
A: 检查文件大小是否超过 10MB,确认后端 API 正常。
|
||||||
|
|
||||||
|
### Q: 智能体列表为空?
|
||||||
|
A: 需要先通过 API 注册智能体,或使用测试脚本创建测试数据。
|
||||||
|
|
||||||
|
### Q: 搜索没有结果?
|
||||||
|
A: 搜索是实时的,检查拼写是否正确,尝试使用关键词而非完整句子。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 最佳实践
|
||||||
|
|
||||||
|
### 项目管理
|
||||||
|
1. 为每个项目设置清晰的描述
|
||||||
|
2. 定期查看项目规约
|
||||||
|
3. 保持项目任务数量适中(建议 <50)
|
||||||
|
|
||||||
|
### 任务管理
|
||||||
|
1. 任务标题简洁明确 (≤20字)
|
||||||
|
2. 任务描述详细完整
|
||||||
|
3. 合理使用父子任务分解复杂工作
|
||||||
|
4. 及时更新任务状态
|
||||||
|
5. 重要任务上传相关附件
|
||||||
|
|
||||||
|
### 智能体协作
|
||||||
|
1. 为智能体分配明确的任务
|
||||||
|
2. 定期检查智能体状态
|
||||||
|
3. 合理分配工作量,避免过载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
### 文档
|
||||||
|
- **设计文档**: `DESIGN-v3.md`
|
||||||
|
- **任务清单**: `TASKS-v3.md`
|
||||||
|
- **完成报告**: `COMPLETION-REPORT-v3.md`
|
||||||
|
- **符合性检查**: `check-design.md`
|
||||||
|
|
||||||
|
### 代码
|
||||||
|
- **主文件**: `public/index-v3.html` (2,341 行)
|
||||||
|
- **备份文件**: `public/index-v3-backup.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**版本**: v3.0
|
||||||
|
**更新日期**: 2026-02-24
|
||||||
|
**状态**: ✅ 生产就绪
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
API_BASE="http://localhost:3000/api"
|
||||||
|
|
||||||
|
echo "📝 创建演示数据..."
|
||||||
|
|
||||||
|
# 创建智能体
|
||||||
|
curl -s -X POST "$API_BASE/agents" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"Agent-001","type":"backend","capabilities":["python","database"]}' > /dev/null
|
||||||
|
|
||||||
|
curl -s -X POST "$API_BASE/agents" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"Agent-002","type":"frontend","capabilities":["react","css"]}' > /dev/null
|
||||||
|
|
||||||
|
curl -s -X POST "$API_BASE/agents" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"Agent-003","type":"testing","capabilities":["jest","e2e"]}' > /dev/null
|
||||||
|
|
||||||
|
echo "✅ 智能体创建完成"
|
||||||
|
|
||||||
|
# 创建项目
|
||||||
|
PROJECT_ID=$(curl -s -X POST "$API_BASE/projects" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"智能客服系统","description":"基于AI的客服平台","created_by":"human"}' | grep -o '"id":[0-9]*' | cut -d: -f2)
|
||||||
|
|
||||||
|
echo "✅ 项目创建完成 (ID: $PROJECT_ID)"
|
||||||
|
|
||||||
|
# 创建任务
|
||||||
|
curl -s -X POST "$API_BASE/tasks" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"project_id\":$PROJECT_ID,\"title\":\"设计数据库架构\",\"type\":\"design\",\"priority\":\"high\",\"status\":\"completed\"}" > /dev/null
|
||||||
|
|
||||||
|
curl -s -X POST "$API_BASE/tasks" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"project_id\":$PROJECT_ID,\"title\":\"实现用户认证API\",\"type\":\"development\",\"priority\":\"high\",\"status\":\"in_progress\",\"assigned_to\":\"Agent-001\"}" > /dev/null
|
||||||
|
|
||||||
|
curl -s -X POST "$API_BASE/tasks" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"project_id\":$PROJECT_ID,\"title\":\"开发聊天界面\",\"type\":\"development\",\"priority\":\"high\",\"status\":\"in_progress\",\"assigned_to\":\"Agent-002\"}" > /dev/null
|
||||||
|
|
||||||
|
curl -s -X POST "$API_BASE/tasks" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"project_id\":$PROJECT_ID,\"title\":\"实现知识库检索\",\"type\":\"development\",\"priority\":\"normal\",\"status\":\"testing_pending\"}" > /dev/null
|
||||||
|
|
||||||
|
curl -s -X POST "$API_BASE/tasks" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"project_id\":$PROJECT_ID,\"title\":\"编写单元测试\",\"type\":\"testing\",\"priority\":\"normal\",\"status\":\"initial\"}" > /dev/null
|
||||||
|
|
||||||
|
echo "✅ 任务创建完成"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 演示数据创建成功!"
|
||||||
|
echo "🌐 访问: http://10.0.6.5:3000/index-v3.html"
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
# PostgreSQL 数据库
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
container_name: agent-task-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: agent_tasks
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
networks:
|
|
||||||
- agent-network
|
|
||||||
|
|
||||||
# 应用服务
|
|
||||||
app:
|
|
||||||
build: .
|
|
||||||
container_name: agent-task-service
|
|
||||||
environment:
|
|
||||||
DB_HOST: postgres
|
|
||||||
DB_PORT: 5432
|
|
||||||
DB_NAME: agent_tasks
|
|
||||||
DB_USER: postgres
|
|
||||||
DB_PASSWORD: postgres
|
|
||||||
PORT: 3000
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- agent-network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
networks:
|
|
||||||
agent-network:
|
|
||||||
driver: bridge
|
|
||||||
-194
@@ -1,194 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const { Pool } = require('pg');
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
host: process.env.DB_HOST || 'localhost',
|
|
||||||
port: process.env.DB_PORT || 5432,
|
|
||||||
database: process.env.DB_NAME || 'agent_tasks_v2',
|
|
||||||
user: process.env.DB_USER || 'postgres',
|
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
|
||||||
});
|
|
||||||
|
|
||||||
async function migrate() {
|
|
||||||
const client = await pool.connect();
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('🔧 开始数据库迁移...');
|
|
||||||
|
|
||||||
// 创建智能体表
|
|
||||||
console.log('创建 agents 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS agents (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
type VARCHAR(100),
|
|
||||||
capabilities JSONB,
|
|
||||||
status VARCHAR(50) DEFAULT 'idle',
|
|
||||||
registered_at BIGINT,
|
|
||||||
last_heartbeat BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建项目表
|
|
||||||
console.log('创建 projects 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS projects (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
name VARCHAR(500) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT,
|
|
||||||
status VARCHAR(50) DEFAULT 'active'
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建全局协作规约表
|
|
||||||
console.log('创建 global_collaboration_rules 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS global_collaboration_rules (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建项目协作规约表
|
|
||||||
console.log('创建 collaboration_rules 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS collaboration_rules (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
project_id VARCHAR(255),
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建项目信息共享清单表
|
|
||||||
console.log('创建 project_notes 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS project_notes (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
project_id VARCHAR(255),
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建任务表
|
|
||||||
console.log('创建 tasks 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS tasks (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
project_id VARCHAR(255),
|
|
||||||
parent_task_id VARCHAR(255),
|
|
||||||
title VARCHAR(500) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
task_type VARCHAR(50) DEFAULT 'development',
|
|
||||||
priority VARCHAR(50) DEFAULT 'normal',
|
|
||||||
status VARCHAR(50) DEFAULT 'initial',
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
assigned_to VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT,
|
|
||||||
completed_at BIGINT,
|
|
||||||
result TEXT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建附件表
|
|
||||||
console.log('创建 attachments 表...');
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS attachments (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
task_id VARCHAR(255),
|
|
||||||
filename VARCHAR(500) NOT NULL,
|
|
||||||
original_name VARCHAR(500) NOT NULL,
|
|
||||||
file_path VARCHAR(1000) NOT NULL,
|
|
||||||
file_size BIGINT,
|
|
||||||
mime_type VARCHAR(100),
|
|
||||||
uploaded_by VARCHAR(255),
|
|
||||||
uploaded_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 现在添加外键约束
|
|
||||||
console.log('添加外键约束...');
|
|
||||||
await client.query(`
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
-- collaboration_rules 外键
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_constraint WHERE conname = 'collaboration_rules_project_id_fkey'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE collaboration_rules
|
|
||||||
ADD CONSTRAINT collaboration_rules_project_id_fkey
|
|
||||||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- project_notes 外键
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_constraint WHERE conname = 'project_notes_project_id_fkey'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE project_notes
|
|
||||||
ADD CONSTRAINT project_notes_project_id_fkey
|
|
||||||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- tasks 外键
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_constraint WHERE conname = 'tasks_project_id_fkey'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE tasks
|
|
||||||
ADD CONSTRAINT tasks_project_id_fkey
|
|
||||||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_constraint WHERE conname = 'tasks_parent_task_id_fkey'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE tasks
|
|
||||||
ADD CONSTRAINT tasks_parent_task_id_fkey
|
|
||||||
FOREIGN KEY (parent_task_id) REFERENCES tasks(id) ON DELETE SET NULL;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- attachments 外键
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_constraint WHERE conname = 'attachments_task_id_fkey'
|
|
||||||
) THEN
|
|
||||||
ALTER TABLE attachments
|
|
||||||
ADD CONSTRAINT attachments_task_id_fkey
|
|
||||||
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE;
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建索引
|
|
||||||
console.log('创建索引...');
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_attachments_task ON attachments(task_id)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_project_notes_project ON project_notes(project_id)`);
|
|
||||||
|
|
||||||
console.log('✅ 数据库迁移完成!');
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('❌ 迁移失败:', err);
|
|
||||||
process.exit(1);
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
await pool.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
migrate();
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>调试 - 新建项目</title>
|
||||||
|
<style>
|
||||||
|
body { padding: 20px; font-family: Arial; }
|
||||||
|
.log { background: #f5f5f5; padding: 10px; margin: 10px 0; border-radius: 5px; }
|
||||||
|
button { padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
|
||||||
|
input, textarea { width: 100%; padding: 8px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>新建项目调试页面</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>测试1: 按钮点击</h3>
|
||||||
|
<button id="test-btn">点击测试</button>
|
||||||
|
<div id="log1" class="log">等待点击...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>测试2: 创建项目</h3>
|
||||||
|
<input type="text" id="project-name" placeholder="项目名称">
|
||||||
|
<textarea id="project-desc" placeholder="项目描述" rows="3"></textarea>
|
||||||
|
<button id="create-btn">创建项目</button>
|
||||||
|
<div id="log2" class="log">等待创建...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>测试3: 项目列表</h3>
|
||||||
|
<button id="list-btn">加载项目</button>
|
||||||
|
<div id="log3" class="log">等待加载...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const log = (id, msg) => {
|
||||||
|
document.getElementById(id).innerHTML += `<div>${new Date().toLocaleTimeString()}: ${msg}</div>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 测试1
|
||||||
|
document.getElementById('test-btn').addEventListener('click', () => {
|
||||||
|
log('log1', '✅ 按钮点击事件正常');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试2
|
||||||
|
document.getElementById('create-btn').addEventListener('click', async () => {
|
||||||
|
const name = document.getElementById('project-name').value;
|
||||||
|
const desc = document.getElementById('project-desc').value;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
log('log2', '❌ 请输入项目名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('log2', '发送请求...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/projects', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name, description: desc, created_by: 'debug' })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
log('log2', `✅ 创建成功: ${JSON.stringify(data)}`);
|
||||||
|
} catch (error) {
|
||||||
|
log('log2', `❌ 错误: ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试3
|
||||||
|
document.getElementById('list-btn').addEventListener('click', async () => {
|
||||||
|
log('log3', '加载中...');
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/projects');
|
||||||
|
const data = await response.json();
|
||||||
|
log('log3', `✅ 获取到 ${data.length} 个项目`);
|
||||||
|
data.forEach(p => log('log3', `- ${p.name}`));
|
||||||
|
} catch (error) {
|
||||||
|
log('log3', `❌ 错误: ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,459 +0,0 @@
|
|||||||
<!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>
|
|
||||||
+2968
-734
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
|||||||
|
<!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>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.status-card {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.status-ok { color: #4caf50; }
|
||||||
|
.status-error { color: #f44336; }
|
||||||
|
h1 { color: #667eea; }
|
||||||
|
.info { margin: 10px 0; padding: 10px; background: #f9f9f9; border-radius: 5px; }
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
button:hover { opacity: 0.9; }
|
||||||
|
#log { font-family: monospace; font-size: 12px; white-space: pre-wrap; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="status-card">
|
||||||
|
<h1>🚀 系统状态检查</h1>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>服务器:</strong> <span id="server-status" class="status-ok">✅ 运行中</span><br>
|
||||||
|
<strong>时间:</strong> <span id="current-time"></span><br>
|
||||||
|
<strong>浏览器:</strong> <span id="browser"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>功能测试</h3>
|
||||||
|
<button onclick="testAPI()">测试 API</button>
|
||||||
|
<button onclick="testProjects()">测试项目列表</button>
|
||||||
|
<button onclick="testRules()">测试规约</button>
|
||||||
|
<button onclick="clearCache()">清除缓存</button>
|
||||||
|
<button onclick="location.href='/'">返回主页</button>
|
||||||
|
|
||||||
|
<div id="log" class="info" style="margin-top: 20px; max-height: 300px; overflow-y: auto;">
|
||||||
|
等待测试...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const log = (msg, isError = false) => {
|
||||||
|
const logDiv = document.getElementById('log');
|
||||||
|
const time = new Date().toLocaleTimeString();
|
||||||
|
const prefix = isError ? '❌' : '✅';
|
||||||
|
logDiv.innerHTML += `\n${time} ${prefix} ${msg}`;
|
||||||
|
logDiv.scrollTop = logDiv.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('current-time').textContent = new Date().toLocaleString('zh-CN');
|
||||||
|
document.getElementById('browser').textContent = navigator.userAgent.split(' ').pop();
|
||||||
|
|
||||||
|
async function testAPI() {
|
||||||
|
log('测试 /api/stats ...');
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/stats');
|
||||||
|
const data = await res.json();
|
||||||
|
log(`API 正常 - 项目:${data.projects} 任务:${data.tasks}`);
|
||||||
|
} catch (e) {
|
||||||
|
log('API 错误: ' + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testProjects() {
|
||||||
|
log('测试 /api/projects ...');
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/projects');
|
||||||
|
const data = await res.json();
|
||||||
|
log(`获取到 ${data.length} 个项目`);
|
||||||
|
data.forEach(p => log(` - ${p.name}`));
|
||||||
|
} catch (e) {
|
||||||
|
log('项目列表错误: ' + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testRules() {
|
||||||
|
log('测试 /api/rules/global ...');
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/rules/global');
|
||||||
|
const data = await res.json();
|
||||||
|
log(`获取到 ${data.length} 条全局规约`);
|
||||||
|
} catch (e) {
|
||||||
|
log('规约错误: ' + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCache() {
|
||||||
|
log('清除缓存...');
|
||||||
|
if ('caches' in window) {
|
||||||
|
caches.keys().then(names => {
|
||||||
|
names.forEach(name => caches.delete(name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
log('缓存已清除,请刷新页面');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动运行基础测试
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
log('=== 自动测试开始 ===');
|
||||||
|
testAPI();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<!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>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.test-box {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 50px auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 15px;
|
||||||
|
background: #e7f5e7;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #2d6a2d;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background: #5568d3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="test-box">
|
||||||
|
<h1>✅ 服务器正常运行</h1>
|
||||||
|
<div class="status">
|
||||||
|
<strong>状态:</strong> 连接成功<br>
|
||||||
|
<strong>时间:</strong> <span id="time"></span><br>
|
||||||
|
<strong>User-Agent:</strong> <span id="ua"></span>
|
||||||
|
</div>
|
||||||
|
<button onclick="testAPI()">测试 API</button>
|
||||||
|
<button onclick="location.href='/'">返回主页</button>
|
||||||
|
<div id="result" style="margin-top: 20px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('time').textContent = new Date().toLocaleString('zh-CN');
|
||||||
|
document.getElementById('ua').textContent = navigator.userAgent;
|
||||||
|
|
||||||
|
async function testAPI() {
|
||||||
|
const result = document.getElementById('result');
|
||||||
|
result.innerHTML = '正在测试...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/stats');
|
||||||
|
const data = await response.json();
|
||||||
|
result.innerHTML = `
|
||||||
|
<div style="background: #e7f5e7; padding: 15px; border-radius: 5px; margin-top: 10px;">
|
||||||
|
✅ API 正常<br>
|
||||||
|
项目数: ${data.projects || 0}<br>
|
||||||
|
任务数: ${data.tasks || 0}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} catch (error) {
|
||||||
|
result.innerHTML = `
|
||||||
|
<div style="background: #ffe7e7; padding: 15px; border-radius: 5px; margin-top: 10px; color: #d32f2f;">
|
||||||
|
❌ API 错误: ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const { Pool } = require('pg');
|
|
||||||
const cors = require('cors');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const { v4: uuidv4 } = require('uuid');
|
|
||||||
const os = require('os');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const PORT = process.env.PORT || 3000;
|
|
||||||
|
|
||||||
// 中间件
|
|
||||||
app.use(cors());
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
app.use(express.static('public'));
|
|
||||||
|
|
||||||
// PostgreSQL 连接池
|
|
||||||
const pool = new Pool({
|
|
||||||
host: process.env.DB_HOST || 'postgres',
|
|
||||||
port: process.env.DB_PORT || 5432,
|
|
||||||
database: process.env.DB_NAME || 'agent_tasks',
|
|
||||||
user: process.env.DB_USER || 'postgres',
|
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
|
||||||
max: 20,
|
|
||||||
idleTimeoutMillis: 30000,
|
|
||||||
connectionTimeoutMillis: 2000,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 测试数据库连接
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('❌ 数据库连接失败:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.log('✅ 已连接到 PostgreSQL 数据库');
|
|
||||||
release();
|
|
||||||
initDatabase();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建表结构
|
|
||||||
async function initDatabase() {
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
// 智能体表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS agents (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
type VARCHAR(100),
|
|
||||||
capabilities JSONB,
|
|
||||||
status VARCHAR(50) DEFAULT 'idle',
|
|
||||||
registered_at BIGINT,
|
|
||||||
last_heartbeat BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 任务表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS tasks (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
title VARCHAR(500) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
priority VARCHAR(50) DEFAULT 'normal',
|
|
||||||
status VARCHAR(50) DEFAULT 'pending',
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
assigned_to VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT,
|
|
||||||
completed_at BIGINT,
|
|
||||||
result TEXT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建索引
|
|
||||||
await client.query(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status)
|
|
||||||
`);
|
|
||||||
await client.query(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)
|
|
||||||
`);
|
|
||||||
await client.query(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to)
|
|
||||||
`);
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
console.log('✅ 数据库表已初始化');
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
console.error('❌ 数据库初始化失败:', err);
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== API 路由 ====================
|
|
||||||
|
|
||||||
// 1. 智能体注册
|
|
||||||
app.post('/api/agents/register', async (req, res) => {
|
|
||||||
const { name, type, capabilities } = req.body;
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return res.status(400).json({ error: '智能体名称不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO agents (id, name, type, capabilities, status, registered_at, last_heartbeat)
|
|
||||||
VALUES ($1, $2, $3, $4, 'idle', $5, $6)`,
|
|
||||||
[id, name, type || 'general', JSON.stringify(capabilities || []), now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
type: type || 'general',
|
|
||||||
status: 'idle',
|
|
||||||
message: '智能体注册成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. 获取所有智能体
|
|
||||||
app.get('/api/agents', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query('SELECT * FROM agents ORDER BY registered_at DESC');
|
|
||||||
res.json(result.rows.map(row => ({
|
|
||||||
...row,
|
|
||||||
capabilities: row.capabilities || []
|
|
||||||
})));
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 更新智能体状态(心跳)
|
|
||||||
app.post('/api/agents/:id/heartbeat', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { status } = req.body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'UPDATE agents SET status = $1, last_heartbeat = $2 WHERE id = $3',
|
|
||||||
[status || 'idle', Date.now(), id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rowCount === 0) {
|
|
||||||
return res.status(404).json({ error: '智能体不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: '心跳更新成功' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 创建任务
|
|
||||||
app.post('/api/tasks', async (req, res) => {
|
|
||||||
const { title, description, priority, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
return res.status(400).json({ error: '任务标题不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO tasks (id, title, description, priority, status, created_by, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)`,
|
|
||||||
[id, title, description || '', priority || 'normal', created_by || 'human', now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
status: 'pending',
|
|
||||||
message: '任务创建成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5. 获取所有任务
|
|
||||||
app.get('/api/tasks', async (req, res) => {
|
|
||||||
const { status } = req.query;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let query = 'SELECT * FROM tasks';
|
|
||||||
let params = [];
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
query += ' WHERE status = $1';
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
query += ' ORDER BY created_at DESC';
|
|
||||||
|
|
||||||
const result = await pool.query(query, params);
|
|
||||||
res.json(result.rows);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. 领取任务
|
|
||||||
app.post('/api/tasks/:id/claim', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { agent_id } = req.body;
|
|
||||||
|
|
||||||
if (!agent_id) {
|
|
||||||
return res.status(400).json({ error: '需要提供智能体ID' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
const taskResult = await client.query('SELECT status FROM tasks WHERE id = $1', [id]);
|
|
||||||
|
|
||||||
if (taskResult.rows.length === 0) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (taskResult.rows[0].status !== 'pending') {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(400).json({ error: '任务已被领取或已完成' });
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query(
|
|
||||||
'UPDATE tasks SET status = $1, assigned_to = $2, updated_at = $3 WHERE id = $4',
|
|
||||||
['in_progress', agent_id, Date.now(), id]
|
|
||||||
);
|
|
||||||
|
|
||||||
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['busy', agent_id]);
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ message: '任务领取成功' });
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 7. 更新任务状态
|
|
||||||
app.put('/api/tasks/:id', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { status, result } = req.body;
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const updates = [];
|
|
||||||
const params = [];
|
|
||||||
let paramIndex = 1;
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
updates.push(`status = $${paramIndex++}`);
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
if (result !== undefined) {
|
|
||||||
updates.push(`result = $${paramIndex++}`);
|
|
||||||
params.push(result);
|
|
||||||
}
|
|
||||||
if (status === 'completed') {
|
|
||||||
updates.push(`completed_at = $${paramIndex++}`);
|
|
||||||
params.push(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.push(`updated_at = $${paramIndex++}`);
|
|
||||||
params.push(now);
|
|
||||||
params.push(id);
|
|
||||||
|
|
||||||
const updateResult = await client.query(
|
|
||||||
`UPDATE tasks SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.rowCount === 0) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果任务完成,将智能体状态改为 idle
|
|
||||||
if (status === 'completed') {
|
|
||||||
const taskResult = await client.query('SELECT assigned_to FROM tasks WHERE id = $1', [id]);
|
|
||||||
if (taskResult.rows.length > 0 && taskResult.rows[0].assigned_to) {
|
|
||||||
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['idle', taskResult.rows[0].assigned_to]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ message: '任务状态更新成功' });
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 8. 获取单个任务详情
|
|
||||||
app.get('/api/tasks/:id', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query('SELECT * FROM tasks WHERE id = $1', [req.params.id]);
|
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
res.json(result.rows[0]);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 9. 统计信息
|
|
||||||
app.get('/api/stats', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const stats = {
|
|
||||||
total_agents: 0,
|
|
||||||
active_agents: 0,
|
|
||||||
total_tasks: 0,
|
|
||||||
pending_tasks: 0,
|
|
||||||
in_progress_tasks: 0,
|
|
||||||
completed_tasks: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const results = await Promise.all([
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM agents'),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM agents WHERE status = $1', ['busy']),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM tasks'),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['pending']),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['in_progress']),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['completed'])
|
|
||||||
]);
|
|
||||||
|
|
||||||
stats.total_agents = parseInt(results[0].rows[0].count);
|
|
||||||
stats.active_agents = parseInt(results[1].rows[0].count);
|
|
||||||
stats.total_tasks = parseInt(results[2].rows[0].count);
|
|
||||||
stats.pending_tasks = parseInt(results[3].rows[0].count);
|
|
||||||
stats.in_progress_tasks = parseInt(results[4].rows[0].count);
|
|
||||||
stats.completed_tasks = parseInt(results[5].rows[0].count);
|
|
||||||
|
|
||||||
res.json(stats);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 健康检查
|
|
||||||
app.get('/health', async (req, res) => {
|
|
||||||
try {
|
|
||||||
await pool.query('SELECT 1');
|
|
||||||
res.json({ status: 'healthy', database: 'connected' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(503).json({ status: 'unhealthy', error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取本机 IP 地址
|
|
||||||
function getLocalIP() {
|
|
||||||
const interfaces = os.networkInterfaces();
|
|
||||||
for (const name of Object.keys(interfaces)) {
|
|
||||||
for (const iface of interfaces[name]) {
|
|
||||||
if (iface.family === 'IPv4' && !iface.internal) {
|
|
||||||
return iface.address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 'localhost';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动服务器
|
|
||||||
app.listen(PORT, '0.0.0.0', () => {
|
|
||||||
const localIP = getLocalIP();
|
|
||||||
console.log(`🚀 多智能体任务服务已启动`);
|
|
||||||
console.log(` 本地访问: http://localhost:${PORT}`);
|
|
||||||
console.log(` 远程访问: http://${localIP}:${PORT}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 优雅关闭
|
|
||||||
process.on('SIGINT', async () => {
|
|
||||||
console.log('\n正在关闭服务...');
|
|
||||||
await pool.end();
|
|
||||||
console.log('数据库连接已关闭');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
-929
@@ -1,929 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const { Pool } = require('pg');
|
|
||||||
const cors = require('cors');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const { v4: uuidv4 } = require('uuid');
|
|
||||||
const os = require('os');
|
|
||||||
const multer = require('multer');
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const PORT = process.env.PORT || 3000;
|
|
||||||
|
|
||||||
// 中间件
|
|
||||||
app.use(cors());
|
|
||||||
app.use(bodyParser.json({ limit: '50mb' }));
|
|
||||||
app.use(express.static('public'));
|
|
||||||
app.use('/uploads', express.static('uploads'));
|
|
||||||
|
|
||||||
// 配置文件上传
|
|
||||||
const storage = multer.diskStorage({
|
|
||||||
destination: (req, file, cb) => {
|
|
||||||
const dir = 'uploads/';
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
|
||||||
}
|
|
||||||
cb(null, dir);
|
|
||||||
},
|
|
||||||
filename: (req, file, cb) => {
|
|
||||||
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
|
|
||||||
cb(null, uniqueName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const upload = multer({
|
|
||||||
storage,
|
|
||||||
limits: { fileSize: 10 * 1024 * 1024 } // 10MB
|
|
||||||
});
|
|
||||||
|
|
||||||
// PostgreSQL 连接池
|
|
||||||
const pool = new Pool({
|
|
||||||
host: process.env.DB_HOST || 'postgres',
|
|
||||||
port: process.env.DB_PORT || 5432,
|
|
||||||
database: process.env.DB_NAME || 'agent_tasks',
|
|
||||||
user: process.env.DB_USER || 'postgres',
|
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
|
||||||
max: 20,
|
|
||||||
idleTimeoutMillis: 30000,
|
|
||||||
connectionTimeoutMillis: 2000,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 测试数据库连接
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('❌ 数据库连接失败:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.log('✅ 已连接到 PostgreSQL 数据库');
|
|
||||||
release();
|
|
||||||
// 数据库表已通过 migrate.js 初始化
|
|
||||||
console.log('💡 提示: 如需初始化数据库,请运行: node migrate.js');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建表结构
|
|
||||||
async function initDatabase() {
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
// 智能体表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS agents (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
type VARCHAR(100),
|
|
||||||
capabilities JSONB,
|
|
||||||
status VARCHAR(50) DEFAULT 'idle',
|
|
||||||
registered_at BIGINT,
|
|
||||||
last_heartbeat BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 项目表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS projects (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
name VARCHAR(500) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT,
|
|
||||||
status VARCHAR(50) DEFAULT 'active'
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 全局协作规约表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS global_collaboration_rules (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 项目协作规约表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS collaboration_rules (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
project_id VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 项目信息共享清单表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS project_notes (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
project_id VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 任务表(扩展)
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS tasks (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
project_id VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE,
|
|
||||||
parent_task_id VARCHAR(255) REFERENCES tasks(id) ON DELETE SET NULL,
|
|
||||||
title VARCHAR(500) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
task_type VARCHAR(50) DEFAULT 'development',
|
|
||||||
priority VARCHAR(50) DEFAULT 'normal',
|
|
||||||
status VARCHAR(50) DEFAULT 'initial',
|
|
||||||
created_by VARCHAR(255),
|
|
||||||
assigned_to VARCHAR(255),
|
|
||||||
created_at BIGINT,
|
|
||||||
updated_at BIGINT,
|
|
||||||
completed_at BIGINT,
|
|
||||||
result TEXT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 附件表
|
|
||||||
await client.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS attachments (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
task_id VARCHAR(255) REFERENCES tasks(id) ON DELETE CASCADE,
|
|
||||||
filename VARCHAR(500) NOT NULL,
|
|
||||||
original_name VARCHAR(500) NOT NULL,
|
|
||||||
file_path VARCHAR(1000) NOT NULL,
|
|
||||||
file_size BIGINT,
|
|
||||||
mime_type VARCHAR(100),
|
|
||||||
uploaded_by VARCHAR(255),
|
|
||||||
uploaded_at BIGINT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 创建索引
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_attachments_task ON attachments(task_id)`);
|
|
||||||
await client.query(`CREATE INDEX IF NOT EXISTS idx_project_notes_project ON project_notes(project_id)`);
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
console.log('✅ 数据库表已初始化');
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
console.error('❌ 数据库初始化失败:', err);
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 项目管理 API ====================
|
|
||||||
|
|
||||||
// 创建项目
|
|
||||||
app.post('/api/projects', async (req, res) => {
|
|
||||||
const { name, description, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return res.status(400).json({ error: '项目名称不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO projects (id, name, description, created_by, created_at, updated_at, status)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, 'active')`,
|
|
||||||
[id, name, description || '', created_by || 'human', now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
status: 'active',
|
|
||||||
message: '项目创建成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取所有项目
|
|
||||||
app.get('/api/projects', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query('SELECT * FROM projects ORDER BY created_at DESC');
|
|
||||||
res.json(result.rows);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取单个项目详情
|
|
||||||
app.get('/api/projects/:id', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query('SELECT * FROM projects WHERE id = $1', [req.params.id]);
|
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: '项目不存在' });
|
|
||||||
}
|
|
||||||
res.json(result.rows[0]);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新项目
|
|
||||||
app.put('/api/projects/:id', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { name, description, status } = req.body;
|
|
||||||
|
|
||||||
const updates = [];
|
|
||||||
const params = [];
|
|
||||||
let paramIndex = 1;
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
updates.push(`name = $${paramIndex++}`);
|
|
||||||
params.push(name);
|
|
||||||
}
|
|
||||||
if (description !== undefined) {
|
|
||||||
updates.push(`description = $${paramIndex++}`);
|
|
||||||
params.push(description);
|
|
||||||
}
|
|
||||||
if (status) {
|
|
||||||
updates.push(`status = $${paramIndex++}`);
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.push(`updated_at = $${paramIndex++}`);
|
|
||||||
params.push(Date.now());
|
|
||||||
params.push(id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
`UPDATE projects SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rowCount === 0) {
|
|
||||||
return res.status(404).json({ error: '项目不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: '项目更新成功' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== 协作规约 API ====================
|
|
||||||
|
|
||||||
// 设置全局协作规约
|
|
||||||
app.post('/api/rules/global', async (req, res) => {
|
|
||||||
const { content, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
return res.status(400).json({ error: '规约内容不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 删除旧的全局规约(只保留一个)
|
|
||||||
await pool.query('DELETE FROM global_collaboration_rules');
|
|
||||||
|
|
||||||
// 创建新的全局规约
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO global_collaboration_rules (id, content, created_by, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5)`,
|
|
||||||
[id, content, created_by || 'human', now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
message: '全局协作规约设置成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取全局协作规约
|
|
||||||
app.get('/api/rules/global', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query('SELECT * FROM global_collaboration_rules');
|
|
||||||
res.json(result.rows[0] || null);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置项目协作规约
|
|
||||||
app.post('/api/projects/:projectId/rules', async (req, res) => {
|
|
||||||
const { projectId } = req.params;
|
|
||||||
const { content, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
return res.status(400).json({ error: '规约内容不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 检查项目是否存在
|
|
||||||
const projectCheck = await pool.query('SELECT id FROM projects WHERE id = $1', [projectId]);
|
|
||||||
if (projectCheck.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: '项目不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除旧规约(每个项目只保留一个)
|
|
||||||
await pool.query('DELETE FROM collaboration_rules WHERE project_id = $1', [projectId]);
|
|
||||||
|
|
||||||
// 创建新规约
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO collaboration_rules (id, project_id, content, created_by, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
||||||
[id, projectId, content, created_by || 'human', now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
message: '协作规约设置成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取协作规约
|
|
||||||
app.get('/api/projects/:projectId/rules', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'SELECT * FROM collaboration_rules WHERE project_id = $1',
|
|
||||||
[req.params.projectId]
|
|
||||||
);
|
|
||||||
res.json(result.rows[0] || null);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== 信息共享清单 API ====================
|
|
||||||
|
|
||||||
// 添加共享笔记
|
|
||||||
app.post('/api/projects/:projectId/notes', async (req, res) => {
|
|
||||||
const { projectId } = req.params;
|
|
||||||
const { content, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
return res.status(400).json({ error: '笔记内容不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO project_notes (id, project_id, content, created_by, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
||||||
[id, projectId, content, created_by || 'human', now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
message: '笔记添加成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取共享笔记列表
|
|
||||||
app.get('/api/projects/:projectId/notes', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'SELECT * FROM project_notes WHERE project_id = $1 ORDER BY created_at DESC',
|
|
||||||
[req.params.projectId]
|
|
||||||
);
|
|
||||||
res.json(result.rows);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新共享笔记
|
|
||||||
app.put('/api/projects/:projectId/notes/:noteId', async (req, res) => {
|
|
||||||
const { noteId } = req.params;
|
|
||||||
const { content } = req.body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'UPDATE project_notes SET content = $1, updated_at = $2 WHERE id = $3',
|
|
||||||
[content, Date.now(), noteId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rowCount === 0) {
|
|
||||||
return res.status(404).json({ error: '笔记不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: '笔记更新成功' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除共享笔记
|
|
||||||
app.delete('/api/projects/:projectId/notes/:noteId', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'DELETE FROM project_notes WHERE id = $1',
|
|
||||||
[req.params.noteId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rowCount === 0) {
|
|
||||||
return res.status(404).json({ error: '笔记不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: '笔记删除成功' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== 智能体 API ====================
|
|
||||||
|
|
||||||
// 注册智能体
|
|
||||||
app.post('/api/agents/register', async (req, res) => {
|
|
||||||
const { name, type, capabilities } = req.body;
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return res.status(400).json({ error: '智能体名称不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO agents (id, name, type, capabilities, status, registered_at, last_heartbeat)
|
|
||||||
VALUES ($1, $2, $3, $4, 'idle', $5, $6)`,
|
|
||||||
[id, name, type || 'general', JSON.stringify(capabilities || []), now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
type: type || 'general',
|
|
||||||
status: 'idle',
|
|
||||||
message: '智能体注册成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取所有智能体
|
|
||||||
app.get('/api/agents', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query('SELECT * FROM agents ORDER BY registered_at DESC');
|
|
||||||
res.json(result.rows.map(row => ({
|
|
||||||
...row,
|
|
||||||
capabilities: row.capabilities || []
|
|
||||||
})));
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新智能体状态(心跳)
|
|
||||||
app.post('/api/agents/:id/heartbeat', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { status } = req.body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'UPDATE agents SET status = $1, last_heartbeat = $2 WHERE id = $3',
|
|
||||||
[status || 'idle', Date.now(), id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rowCount === 0) {
|
|
||||||
return res.status(404).json({ error: '智能体不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: '心跳更新成功' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== 任务管理 API ====================
|
|
||||||
|
|
||||||
// 创建任务(支持子任务)
|
|
||||||
app.post('/api/tasks', async (req, res) => {
|
|
||||||
const { project_id, parent_task_id, title, description, task_type, priority, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
return res.status(400).json({ error: '任务标题不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!project_id) {
|
|
||||||
return res.status(400).json({ error: '必须指定项目ID' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO tasks (id, project_id, parent_task_id, title, description, task_type, priority, status, created_by, created_at, updated_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, 'initial', $8, $9, $10)`,
|
|
||||||
[id, project_id, parent_task_id || null, title, description || '',
|
|
||||||
task_type || 'development', priority || 'normal', created_by || 'human', now, now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
status: 'initial',
|
|
||||||
message: '任务创建成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取任务列表(支持按项目、状态、父任务过滤)
|
|
||||||
app.get('/api/tasks', async (req, res) => {
|
|
||||||
const { project_id, status, parent_task_id } = req.query;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let query = 'SELECT * FROM tasks WHERE 1=1';
|
|
||||||
const params = [];
|
|
||||||
let paramIndex = 1;
|
|
||||||
|
|
||||||
if (project_id) {
|
|
||||||
query += ` AND project_id = $${paramIndex++}`;
|
|
||||||
params.push(project_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
query += ` AND status = $${paramIndex++}`;
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent_task_id) {
|
|
||||||
query += ` AND parent_task_id = $${paramIndex++}`;
|
|
||||||
params.push(parent_task_id);
|
|
||||||
} else if (req.query.root === 'true') {
|
|
||||||
query += ' AND parent_task_id IS NULL';
|
|
||||||
}
|
|
||||||
|
|
||||||
query += ' ORDER BY created_at DESC';
|
|
||||||
|
|
||||||
const result = await pool.query(query, params);
|
|
||||||
res.json(result.rows);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取任务详情(包含子任务和附件)
|
|
||||||
app.get('/api/tasks/:id', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const taskResult = await pool.query('SELECT * FROM tasks WHERE id = $1', [req.params.id]);
|
|
||||||
if (taskResult.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = taskResult.rows[0];
|
|
||||||
|
|
||||||
// 获取子任务
|
|
||||||
const subtasksResult = await pool.query(
|
|
||||||
'SELECT * FROM tasks WHERE parent_task_id = $1 ORDER BY created_at',
|
|
||||||
[req.params.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 获取附件
|
|
||||||
const attachmentsResult = await pool.query(
|
|
||||||
'SELECT * FROM attachments WHERE task_id = $1 ORDER BY uploaded_at',
|
|
||||||
[req.params.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
...task,
|
|
||||||
subtasks: subtasksResult.rows,
|
|
||||||
attachments: attachmentsResult.rows
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 领取任务
|
|
||||||
app.post('/api/tasks/:id/claim', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { agent_id } = req.body;
|
|
||||||
|
|
||||||
if (!agent_id) {
|
|
||||||
return res.status(400).json({ error: '需要提供智能体ID' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
const taskResult = await client.query('SELECT status, project_id FROM tasks WHERE id = $1', [id]);
|
|
||||||
|
|
||||||
if (taskResult.rows.length === 0) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = taskResult.rows[0];
|
|
||||||
|
|
||||||
if (task.status !== 'initial' && task.status !== 'in_progress') {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(400).json({ error: '任务状态不允许领取' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取全局协作规约
|
|
||||||
const globalRulesResult = await client.query(
|
|
||||||
'SELECT content FROM global_collaboration_rules'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 获取项目协作规约
|
|
||||||
const projectRulesResult = await client.query(
|
|
||||||
'SELECT content FROM collaboration_rules WHERE project_id = $1',
|
|
||||||
[task.project_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
await client.query(
|
|
||||||
'UPDATE tasks SET status = $1, assigned_to = $2, updated_at = $3 WHERE id = $4',
|
|
||||||
['in_progress', agent_id, Date.now(), id]
|
|
||||||
);
|
|
||||||
|
|
||||||
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['busy', agent_id]);
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
message: '任务领取成功'
|
|
||||||
};
|
|
||||||
|
|
||||||
// 返回规约给智能体
|
|
||||||
const rules = {};
|
|
||||||
|
|
||||||
if (globalRulesResult.rows.length > 0) {
|
|
||||||
rules.global_rules = globalRulesResult.rows[0].content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectRulesResult.rows.length > 0) {
|
|
||||||
rules.project_rules = projectRulesResult.rows[0].content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rules.global_rules || rules.project_rules) {
|
|
||||||
response.collaboration_rules = rules;
|
|
||||||
response.notice = '⚠️ 请严格遵守协作规约';
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(response);
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新任务状态
|
|
||||||
app.put('/api/tasks/:id', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { status, result, description, task_type, priority } = req.body;
|
|
||||||
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const updates = [];
|
|
||||||
const params = [];
|
|
||||||
let paramIndex = 1;
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
updates.push(`status = $${paramIndex++}`);
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
if (result !== undefined) {
|
|
||||||
updates.push(`result = $${paramIndex++}`);
|
|
||||||
params.push(result);
|
|
||||||
}
|
|
||||||
if (description !== undefined) {
|
|
||||||
updates.push(`description = $${paramIndex++}`);
|
|
||||||
params.push(description);
|
|
||||||
}
|
|
||||||
if (task_type) {
|
|
||||||
updates.push(`task_type = $${paramIndex++}`);
|
|
||||||
params.push(task_type);
|
|
||||||
}
|
|
||||||
if (priority) {
|
|
||||||
updates.push(`priority = $${paramIndex++}`);
|
|
||||||
params.push(priority);
|
|
||||||
}
|
|
||||||
if (status === 'completed') {
|
|
||||||
updates.push(`completed_at = $${paramIndex++}`);
|
|
||||||
params.push(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.push(`updated_at = $${paramIndex++}`);
|
|
||||||
params.push(now);
|
|
||||||
params.push(id);
|
|
||||||
|
|
||||||
const updateResult = await client.query(
|
|
||||||
`UPDATE tasks SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateResult.rowCount === 0) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果任务完成,将智能体状态改为 idle
|
|
||||||
if (status === 'completed') {
|
|
||||||
const taskResult = await client.query('SELECT assigned_to FROM tasks WHERE id = $1', [id]);
|
|
||||||
if (taskResult.rows.length > 0 && taskResult.rows[0].assigned_to) {
|
|
||||||
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['idle', taskResult.rows[0].assigned_to]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query('COMMIT');
|
|
||||||
res.json({ message: '任务状态更新成功' });
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK');
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== 附件管理 API ====================
|
|
||||||
|
|
||||||
// 上传附件
|
|
||||||
app.post('/api/tasks/:taskId/attachments', upload.single('file'), async (req, res) => {
|
|
||||||
const { taskId } = req.params;
|
|
||||||
const { uploaded_by } = req.body;
|
|
||||||
|
|
||||||
if (!req.file) {
|
|
||||||
return res.status(400).json({ error: '没有上传文件' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await pool.query(
|
|
||||||
`INSERT INTO attachments (id, task_id, filename, original_name, file_path, file_size, mime_type, uploaded_by, uploaded_at)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
||||||
[id, taskId, req.file.filename, req.file.originalname, req.file.path,
|
|
||||||
req.file.size, req.file.mimetype, uploaded_by || 'human', now]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
filename: req.file.filename,
|
|
||||||
original_name: req.file.originalname,
|
|
||||||
url: `/uploads/${req.file.filename}`,
|
|
||||||
message: '附件上传成功'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取任务附件列表
|
|
||||||
app.get('/api/tasks/:taskId/attachments', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'SELECT * FROM attachments WHERE task_id = $1 ORDER BY uploaded_at DESC',
|
|
||||||
[req.params.taskId]
|
|
||||||
);
|
|
||||||
res.json(result.rows.map(row => ({
|
|
||||||
...row,
|
|
||||||
url: `/uploads/${row.filename}`
|
|
||||||
})));
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除附件
|
|
||||||
app.delete('/api/tasks/:taskId/attachments/:attachmentId', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const result = await pool.query(
|
|
||||||
'SELECT file_path FROM attachments WHERE id = $1',
|
|
||||||
[req.params.attachmentId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
|
||||||
return res.status(404).json({ error: '附件不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除文件
|
|
||||||
const filePath = result.rows[0].file_path;
|
|
||||||
if (fs.existsSync(filePath)) {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除数据库记录
|
|
||||||
await pool.query('DELETE FROM attachments WHERE id = $1', [req.params.attachmentId]);
|
|
||||||
|
|
||||||
res.json({ message: '附件删除成功' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==================== 统计信息 API ====================
|
|
||||||
|
|
||||||
app.get('/api/stats', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const stats = {
|
|
||||||
total_agents: 0,
|
|
||||||
active_agents: 0,
|
|
||||||
total_projects: 0,
|
|
||||||
total_tasks: 0,
|
|
||||||
tasks_by_status: {},
|
|
||||||
tasks_by_type: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const results = await Promise.all([
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM agents'),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM agents WHERE status = $1', ['busy']),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM projects'),
|
|
||||||
pool.query('SELECT COUNT(*) as count FROM tasks'),
|
|
||||||
pool.query('SELECT status, COUNT(*) as count FROM tasks GROUP BY status'),
|
|
||||||
pool.query('SELECT task_type, COUNT(*) as count FROM tasks GROUP BY task_type')
|
|
||||||
]);
|
|
||||||
|
|
||||||
stats.total_agents = parseInt(results[0].rows[0].count);
|
|
||||||
stats.active_agents = parseInt(results[1].rows[0].count);
|
|
||||||
stats.total_projects = parseInt(results[2].rows[0].count);
|
|
||||||
stats.total_tasks = parseInt(results[3].rows[0].count);
|
|
||||||
|
|
||||||
results[4].rows.forEach(row => {
|
|
||||||
stats.tasks_by_status[row.status] = parseInt(row.count);
|
|
||||||
});
|
|
||||||
|
|
||||||
results[5].rows.forEach(row => {
|
|
||||||
stats.tasks_by_type[row.task_type] = parseInt(row.count);
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json(stats);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 健康检查
|
|
||||||
app.get('/health', async (req, res) => {
|
|
||||||
try {
|
|
||||||
await pool.query('SELECT 1');
|
|
||||||
res.json({ status: 'healthy', database: 'connected' });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(503).json({ status: 'unhealthy', error: err.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取本机 IP 地址
|
|
||||||
function getLocalIP() {
|
|
||||||
const interfaces = os.networkInterfaces();
|
|
||||||
for (const name of Object.keys(interfaces)) {
|
|
||||||
for (const iface of interfaces[name]) {
|
|
||||||
if (iface.family === 'IPv4' && !iface.internal) {
|
|
||||||
return iface.address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 'localhost';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动服务器
|
|
||||||
app.listen(PORT, '0.0.0.0', () => {
|
|
||||||
const localIP = getLocalIP();
|
|
||||||
console.log(`🚀 多智能体任务服务已启动 (v2.0)`);
|
|
||||||
console.log(` 本地访问: http://localhost:${PORT}`);
|
|
||||||
console.log(` 远程访问: http://${localIP}:${PORT}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 优雅关闭
|
|
||||||
process.on('SIGINT', async () => {
|
|
||||||
console.log('\n正在关闭服务...');
|
|
||||||
await pool.end();
|
|
||||||
console.log('数据库连接已关闭');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
@@ -1,306 +1,113 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sqlite3 = require('sqlite3').verbose();
|
const sqlite3 = require('sqlite3').verbose();
|
||||||
const cors = require('cors');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const { v4: uuidv4 } = require('uuid');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = 3000;
|
||||||
|
|
||||||
// 中间件
|
// 中间件
|
||||||
app.use(cors());
|
app.use(express.json());
|
||||||
app.use(bodyParser.json());
|
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|
||||||
// 初始化数据库
|
// 数据库连接
|
||||||
const db = new sqlite3.Database('./agent_tasks.db', (err) => {
|
const db = new sqlite3.Database('./tasks.db', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('数据库连接失败:', err.message);
|
console.error('❌ 数据库连接失败:', err.message);
|
||||||
} else {
|
process.exit(1);
|
||||||
console.log('✅ 已连接到 SQLite 数据库');
|
|
||||||
initDatabase();
|
|
||||||
}
|
}
|
||||||
|
console.log('✅ 已连接到 SQLite 数据库');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建表结构
|
// 初始化数据库表
|
||||||
function initDatabase() {
|
db.serialize(() => {
|
||||||
db.serialize(() => {
|
// 项目表
|
||||||
// 智能体表
|
db.run(`CREATE TABLE IF NOT EXISTS projects (
|
||||||
db.run(`CREATE TABLE IF NOT EXISTS agents (
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
id TEXT PRIMARY KEY,
|
name TEXT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
description TEXT,
|
||||||
type TEXT,
|
status TEXT DEFAULT 'active',
|
||||||
capabilities TEXT,
|
created_by TEXT DEFAULT 'human',
|
||||||
status TEXT DEFAULT 'idle',
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
registered_at INTEGER,
|
)`);
|
||||||
last_heartbeat INTEGER
|
|
||||||
)`);
|
|
||||||
|
|
||||||
// 任务表
|
// 任务表
|
||||||
db.run(`CREATE TABLE IF NOT EXISTS tasks (
|
db.run(`CREATE TABLE IF NOT EXISTS tasks (
|
||||||
id TEXT PRIMARY KEY,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
title TEXT NOT NULL,
|
project_id INTEGER NOT NULL,
|
||||||
description TEXT,
|
parent_id INTEGER,
|
||||||
priority TEXT DEFAULT 'normal',
|
title TEXT NOT NULL,
|
||||||
status TEXT DEFAULT 'pending',
|
description TEXT,
|
||||||
created_by TEXT,
|
type TEXT DEFAULT 'development',
|
||||||
assigned_to TEXT,
|
priority TEXT DEFAULT 'normal',
|
||||||
created_at INTEGER,
|
status TEXT DEFAULT 'initial',
|
||||||
updated_at INTEGER,
|
assigned_to TEXT,
|
||||||
completed_at INTEGER,
|
created_by TEXT DEFAULT 'human',
|
||||||
result TEXT
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
)`);
|
FOREIGN KEY (project_id) REFERENCES projects(id),
|
||||||
|
FOREIGN KEY (parent_id) REFERENCES tasks(id)
|
||||||
|
)`);
|
||||||
|
|
||||||
console.log('✅ 数据库表已初始化');
|
// 智能体表
|
||||||
});
|
db.run(`CREATE TABLE IF NOT EXISTS agents (
|
||||||
}
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT UNIQUE NOT NULL,
|
||||||
|
type TEXT,
|
||||||
|
capabilities TEXT,
|
||||||
|
status TEXT DEFAULT 'idle',
|
||||||
|
current_task TEXT,
|
||||||
|
completed_tasks INTEGER DEFAULT 0,
|
||||||
|
last_heartbeat DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)`);
|
||||||
|
|
||||||
|
// 附件表
|
||||||
|
db.run(`CREATE TABLE IF NOT EXISTS attachments (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
task_id INTEGER NOT NULL,
|
||||||
|
filename TEXT NOT NULL,
|
||||||
|
filepath TEXT NOT NULL,
|
||||||
|
size INTEGER,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (task_id) REFERENCES tasks(id)
|
||||||
|
)`);
|
||||||
|
|
||||||
|
// 规约表 (支持全局和项目级别)
|
||||||
|
db.run(`CREATE TABLE IF NOT EXISTS rules (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project_id INTEGER,
|
||||||
|
rule_no TEXT NOT NULL,
|
||||||
|
rule_type TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||||
|
)`);
|
||||||
|
|
||||||
|
console.log('✅ 数据库表已初始化');
|
||||||
|
});
|
||||||
|
|
||||||
// ==================== API 路由 ====================
|
// ==================== API 路由 ====================
|
||||||
|
|
||||||
// 1. 智能体注册
|
// 统计信息
|
||||||
app.post('/api/agents/register', (req, res) => {
|
|
||||||
const { name, type, capabilities } = req.body;
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
return res.status(400).json({ error: '智能体名称不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
db.run(
|
|
||||||
`INSERT INTO agents (id, name, type, capabilities, status, registered_at, last_heartbeat)
|
|
||||||
VALUES (?, ?, ?, ?, 'idle', ?, ?)`,
|
|
||||||
[id, name, type || 'general', JSON.stringify(capabilities || []), now, now],
|
|
||||||
function(err) {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
type: type || 'general',
|
|
||||||
status: 'idle',
|
|
||||||
message: '智能体注册成功'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. 获取所有智能体
|
|
||||||
app.get('/api/agents', (req, res) => {
|
|
||||||
db.all('SELECT * FROM agents ORDER BY registered_at DESC', [], (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
res.json(rows.map(row => ({
|
|
||||||
...row,
|
|
||||||
capabilities: JSON.parse(row.capabilities || '[]')
|
|
||||||
})));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 更新智能体状态(心跳)
|
|
||||||
app.post('/api/agents/:id/heartbeat', (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { status } = req.body;
|
|
||||||
|
|
||||||
db.run(
|
|
||||||
'UPDATE agents SET status = ?, last_heartbeat = ? WHERE id = ?',
|
|
||||||
[status || 'idle', Date.now(), id],
|
|
||||||
function(err) {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
if (this.changes === 0) {
|
|
||||||
return res.status(404).json({ error: '智能体不存在' });
|
|
||||||
}
|
|
||||||
res.json({ message: '心跳更新成功' });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 创建任务
|
|
||||||
app.post('/api/tasks', (req, res) => {
|
|
||||||
const { title, description, priority, created_by } = req.body;
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
return res.status(400).json({ error: '任务标题不能为空' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
db.run(
|
|
||||||
`INSERT INTO tasks (id, title, description, priority, status, created_by, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)`,
|
|
||||||
[id, title, description || '', priority || 'normal', created_by || 'human', now, now],
|
|
||||||
function(err) {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
res.json({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
status: 'pending',
|
|
||||||
message: '任务创建成功'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5. 获取所有任务
|
|
||||||
app.get('/api/tasks', (req, res) => {
|
|
||||||
const { status } = req.query;
|
|
||||||
|
|
||||||
let query = 'SELECT * FROM tasks';
|
|
||||||
let params = [];
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
query += ' WHERE status = ?';
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
query += ' ORDER BY created_at DESC';
|
|
||||||
|
|
||||||
db.all(query, params, (err, rows) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
res.json(rows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. 领取任务
|
|
||||||
app.post('/api/tasks/:id/claim', (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { agent_id } = req.body;
|
|
||||||
|
|
||||||
if (!agent_id) {
|
|
||||||
return res.status(400).json({ error: '需要提供智能体ID' });
|
|
||||||
}
|
|
||||||
|
|
||||||
db.get('SELECT status FROM tasks WHERE id = ?', [id], (err, task) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
if (!task) {
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
if (task.status !== 'pending') {
|
|
||||||
return res.status(400).json({ error: '任务已被领取或已完成' });
|
|
||||||
}
|
|
||||||
|
|
||||||
db.run(
|
|
||||||
'UPDATE tasks SET status = ?, assigned_to = ?, updated_at = ? WHERE id = ?',
|
|
||||||
['in_progress', agent_id, Date.now(), id],
|
|
||||||
function(err) {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同时更新智能体状态
|
|
||||||
db.run('UPDATE agents SET status = ? WHERE id = ?', ['busy', agent_id]);
|
|
||||||
|
|
||||||
res.json({ message: '任务领取成功' });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 7. 更新任务状态
|
|
||||||
app.put('/api/tasks/:id', (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
const { status, result } = req.body;
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const updates = [];
|
|
||||||
const params = [];
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
updates.push('status = ?');
|
|
||||||
params.push(status);
|
|
||||||
}
|
|
||||||
if (result !== undefined) {
|
|
||||||
updates.push('result = ?');
|
|
||||||
params.push(result);
|
|
||||||
}
|
|
||||||
if (status === 'completed') {
|
|
||||||
updates.push('completed_at = ?');
|
|
||||||
params.push(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.push('updated_at = ?');
|
|
||||||
params.push(now);
|
|
||||||
params.push(id);
|
|
||||||
|
|
||||||
db.run(
|
|
||||||
`UPDATE tasks SET ${updates.join(', ')} WHERE id = ?`,
|
|
||||||
params,
|
|
||||||
function(err) {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
if (this.changes === 0) {
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果任务完成,将智能体状态改为 idle
|
|
||||||
if (status === 'completed') {
|
|
||||||
db.get('SELECT assigned_to FROM tasks WHERE id = ?', [id], (err, task) => {
|
|
||||||
if (task && task.assigned_to) {
|
|
||||||
db.run('UPDATE agents SET status = ? WHERE id = ?', ['idle', task.assigned_to]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ message: '任务状态更新成功' });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 8. 获取单个任务详情
|
|
||||||
app.get('/api/tasks/:id', (req, res) => {
|
|
||||||
db.get('SELECT * FROM tasks WHERE id = ?', [req.params.id], (err, row) => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
if (!row) {
|
|
||||||
return res.status(404).json({ error: '任务不存在' });
|
|
||||||
}
|
|
||||||
res.json(row);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 9. 统计信息
|
|
||||||
app.get('/api/stats', (req, res) => {
|
app.get('/api/stats', (req, res) => {
|
||||||
const stats = {
|
const stats = {};
|
||||||
total_agents: 0,
|
|
||||||
active_agents: 0,
|
|
||||||
total_tasks: 0,
|
|
||||||
pending_tasks: 0,
|
|
||||||
in_progress_tasks: 0,
|
|
||||||
completed_tasks: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
db.get('SELECT COUNT(*) as total FROM agents', [], (err, row) => {
|
db.get('SELECT COUNT(*) as count FROM projects', (err, row) => {
|
||||||
if (!err && row) stats.total_agents = row.total;
|
stats.projects = row ? row.count : 0;
|
||||||
|
|
||||||
db.get('SELECT COUNT(*) as active FROM agents WHERE status = "busy"', [], (err, row) => {
|
db.get('SELECT COUNT(*) as count FROM tasks', (err, row) => {
|
||||||
if (!err && row) stats.active_agents = row.active;
|
stats.tasks = row ? row.count : 0;
|
||||||
|
|
||||||
db.get('SELECT COUNT(*) as total FROM tasks', [], (err, row) => {
|
db.get('SELECT COUNT(*) as count FROM tasks WHERE status = "in_progress"', (err, row) => {
|
||||||
if (!err && row) stats.total_tasks = row.total;
|
stats.in_progress_tasks = row ? row.count : 0;
|
||||||
|
|
||||||
db.get('SELECT COUNT(*) as pending FROM tasks WHERE status = "pending"', [], (err, row) => {
|
db.get('SELECT COUNT(*) as count FROM tasks WHERE status = "completed"', (err, row) => {
|
||||||
if (!err && row) stats.pending_tasks = row.pending;
|
stats.completed_tasks = row ? row.count : 0;
|
||||||
|
|
||||||
db.get('SELECT COUNT(*) as in_progress FROM tasks WHERE status = "in_progress"', [], (err, row) => {
|
db.get('SELECT COUNT(*) as count FROM agents', (err, row) => {
|
||||||
if (!err && row) stats.in_progress_tasks = row.in_progress;
|
stats.total_agents = row ? row.count : 0;
|
||||||
|
|
||||||
db.get('SELECT COUNT(*) as completed FROM tasks WHERE status = "completed"', [], (err, row) => {
|
db.get('SELECT COUNT(*) as count FROM agents WHERE status = "busy"', (err, row) => {
|
||||||
if (!err && row) stats.completed_tasks = row.completed;
|
stats.active_agents = row ? row.count : 0;
|
||||||
res.json(stats);
|
res.json(stats);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -310,18 +117,243 @@ app.get('/api/stats', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 项目管理
|
||||||
|
app.get('/api/projects', (req, res) => {
|
||||||
|
db.all('SELECT * FROM projects ORDER BY created_at DESC', (err, rows) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json(rows || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/projects', (req, res) => {
|
||||||
|
const { name, description, created_by = 'human' } = req.body;
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO projects (name, description, created_by) VALUES (?, ?, ?)',
|
||||||
|
[name, description, created_by],
|
||||||
|
function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ id: this.lastID, name, description, created_by });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/projects/:id', (req, res) => {
|
||||||
|
db.get('SELECT * FROM projects WHERE id = ?', [req.params.id], (err, row) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
if (!row) return res.status(404).json({ error: 'Project not found' });
|
||||||
|
res.json(row);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/projects/:id', (req, res) => {
|
||||||
|
// 先删除项目下的所有任务
|
||||||
|
db.run('DELETE FROM tasks WHERE project_id = ?', [req.params.id], (err) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
|
||||||
|
// 然后删除项目
|
||||||
|
db.run('DELETE FROM projects WHERE id = ?', [req.params.id], function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
if (this.changes === 0) return res.status(404).json({ error: 'Project not found' });
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 任务管理
|
||||||
|
app.get('/api/tasks', (req, res) => {
|
||||||
|
const { project_id } = req.query;
|
||||||
|
let query = 'SELECT t.*, (SELECT COUNT(*) FROM tasks WHERE parent_id = t.id) as subtask_count, (SELECT COUNT(*) FROM attachments WHERE task_id = t.id) as attachment_count FROM tasks t';
|
||||||
|
let params = [];
|
||||||
|
|
||||||
|
if (project_id) {
|
||||||
|
query += ' WHERE t.project_id = ?';
|
||||||
|
params.push(project_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ' ORDER BY t.created_at DESC';
|
||||||
|
|
||||||
|
db.all(query, params, (err, rows) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json(rows || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/tasks', (req, res) => {
|
||||||
|
const { project_id, parent_id, title, description, type = 'development', priority = 'normal', status = 'initial', assigned_to, created_by = 'human' } = req.body;
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO tasks (project_id, parent_id, title, description, type, priority, status, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||||
|
[project_id, parent_id || null, title, description, type, priority, status, assigned_to || null, created_by],
|
||||||
|
function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ id: this.lastID, project_id, title, type, priority, status });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/tasks/:id', (req, res) => {
|
||||||
|
db.get('SELECT * FROM tasks WHERE id = ?', [req.params.id], (err, task) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
if (!task) return res.status(404).json({ error: 'Task not found' });
|
||||||
|
|
||||||
|
// 获取附件
|
||||||
|
db.all('SELECT * FROM attachments WHERE task_id = ?', [req.params.id], (err, attachments) => {
|
||||||
|
task.attachments = attachments || [];
|
||||||
|
|
||||||
|
// 获取子任务
|
||||||
|
db.all('SELECT * FROM tasks WHERE parent_id = ?', [req.params.id], (err, subtasks) => {
|
||||||
|
task.subtasks = subtasks || [];
|
||||||
|
res.json(task);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/api/tasks/:id', (req, res) => {
|
||||||
|
const updates = [];
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
['status', 'type', 'priority', 'assigned_to', 'title', 'description'].forEach(field => {
|
||||||
|
if (req.body[field] !== undefined) {
|
||||||
|
updates.push(`${field} = ?`);
|
||||||
|
values.push(req.body[field]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updates.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'No fields to update' });
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(req.params.id);
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
`UPDATE tasks SET ${updates.join(', ')} WHERE id = ?`,
|
||||||
|
values,
|
||||||
|
function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
if (this.changes === 0) return res.status(404).json({ error: 'Task not found' });
|
||||||
|
res.json({ success: true });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/tasks/:id', (req, res) => {
|
||||||
|
db.run('DELETE FROM tasks WHERE id = ?', [req.params.id], function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 智能体管理
|
||||||
|
app.get('/api/agents', (req, res) => {
|
||||||
|
db.all('SELECT * FROM agents ORDER BY name', (err, rows) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json(rows || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/agents', (req, res) => {
|
||||||
|
const { name, type, capabilities } = req.body;
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO agents (name, type, capabilities) VALUES (?, ?, ?)',
|
||||||
|
[name, type, JSON.stringify(capabilities)],
|
||||||
|
function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ id: this.lastID, name, type, capabilities });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 规约管理 (支持多条目)
|
||||||
|
// 获取全局规约列表
|
||||||
|
app.get('/api/rules/global', (req, res) => {
|
||||||
|
db.all('SELECT * FROM rules WHERE project_id IS NULL ORDER BY rule_no', (err, rows) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json(rows || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取项目规约列表
|
||||||
|
app.get('/api/rules/project/:projectId', (req, res) => {
|
||||||
|
db.all('SELECT * FROM rules WHERE project_id = ? ORDER BY rule_no', [req.params.projectId], (err, rows) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json(rows || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取单个规约
|
||||||
|
app.get('/api/rules/:id', (req, res) => {
|
||||||
|
db.get('SELECT * FROM rules WHERE id = ?', [req.params.id], (err, row) => {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
if (!row) return res.status(404).json({ error: 'Rule not found' });
|
||||||
|
res.json(row);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建规约
|
||||||
|
app.post('/api/rules', (req, res) => {
|
||||||
|
const { project_id, rule_no, rule_type, description } = req.body;
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO rules (project_id, rule_no, rule_type, description) VALUES (?, ?, ?, ?)',
|
||||||
|
[project_id || null, rule_no, rule_type, description],
|
||||||
|
function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ id: this.lastID, project_id, rule_no, rule_type, description });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新规约
|
||||||
|
app.put('/api/rules/:id', (req, res) => {
|
||||||
|
const { rule_no, rule_type, description } = req.body;
|
||||||
|
const updates = [];
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
if (rule_no !== undefined) { updates.push('rule_no = ?'); values.push(rule_no); }
|
||||||
|
if (rule_type !== undefined) { updates.push('rule_type = ?'); values.push(rule_type); }
|
||||||
|
if (description !== undefined) { updates.push('description = ?'); values.push(description); }
|
||||||
|
updates.push('updated_at = CURRENT_TIMESTAMP');
|
||||||
|
|
||||||
|
if (values.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'No fields to update' });
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(req.params.id);
|
||||||
|
|
||||||
|
db.run(
|
||||||
|
`UPDATE rules SET ${updates.join(', ')} WHERE id = ?`,
|
||||||
|
values,
|
||||||
|
function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ success: true });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除规约
|
||||||
|
app.delete('/api/rules/:id', (req, res) => {
|
||||||
|
db.run('DELETE FROM rules WHERE id = ?', [req.params.id], function(err) {
|
||||||
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
|
res.json({ success: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`🚀 多智能体任务服务已启动: http://localhost:${PORT}`);
|
console.log(`🚀 多智能体任务管理系统已启动`);
|
||||||
|
console.log(` 本地访问: http://localhost:${PORT}`);
|
||||||
|
console.log(` 远程访问: http://10.0.6.5:${PORT}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 优雅关闭
|
// 优雅关闭
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
db.close((err) => {
|
db.close((err) => {
|
||||||
if (err) {
|
if (err) console.error(err);
|
||||||
console.error(err.message);
|
console.log('\n👋 服务器已关闭');
|
||||||
}
|
|
||||||
console.log('数据库连接已关闭');
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "🚀 启动多智能体任务管理系统 v2.0..."
|
|
||||||
|
|
||||||
# 配置
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_PORT=5433
|
|
||||||
DB_NAME=agent_tasks_v2
|
|
||||||
DB_PASSWORD=postgres
|
|
||||||
|
|
||||||
# 检查数据库容器
|
|
||||||
if ! docker ps | grep -q test-postgres; then
|
|
||||||
echo "❌ PostgreSQL 容器未运行"
|
|
||||||
echo "启动命令: docker run -d --name test-postgres -e POSTGRES_PASSWORD=postgres -p 5433:5432 postgres:15-alpine"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查数据库是否存在
|
|
||||||
DB_EXISTS=$(docker exec test-postgres psql -U postgres -lqt | cut -d \| -f 1 | grep -w $DB_NAME | wc -l)
|
|
||||||
|
|
||||||
if [ "$DB_EXISTS" -eq 0 ]; then
|
|
||||||
echo "📦 创建数据库 $DB_NAME..."
|
|
||||||
docker exec test-postgres psql -U postgres -c "CREATE DATABASE $DB_NAME;"
|
|
||||||
|
|
||||||
echo "🔧 运行数据库迁移..."
|
|
||||||
DB_HOST=$DB_HOST DB_PORT=$DB_PORT DB_NAME=$DB_NAME DB_PASSWORD=$DB_PASSWORD node migrate.js
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 停止旧服务
|
|
||||||
pkill -f "server-v2.js" 2>/dev/null
|
|
||||||
|
|
||||||
# 启动服务
|
|
||||||
echo "🎯 启动服务..."
|
|
||||||
DB_HOST=$DB_HOST \
|
|
||||||
DB_PORT=$DB_PORT \
|
|
||||||
DB_NAME=$DB_NAME \
|
|
||||||
DB_PASSWORD=$DB_PASSWORD \
|
|
||||||
nohup node server-v2.js > server-v2.log 2>&1 &
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# 检查服务状态
|
|
||||||
if curl -s http://localhost:3000/health > /dev/null; then
|
|
||||||
echo ""
|
|
||||||
echo "✅ 服务启动成功!"
|
|
||||||
echo ""
|
|
||||||
echo "🌐 访问地址:"
|
|
||||||
echo " 本地: http://localhost:3000"
|
|
||||||
echo " 远程: http://192.168.0.250:3000"
|
|
||||||
echo ""
|
|
||||||
echo "📊 统计信息: curl http://localhost:3000/api/stats"
|
|
||||||
echo "📝 查看日志: tail -f server-v2.log"
|
|
||||||
echo "🛑 停止服务: pkill -f server-v2.js"
|
|
||||||
else
|
|
||||||
echo "❌ 服务启动失败,请查看 server-v2.log"
|
|
||||||
tail -20 server-v2.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "🚀 启动多智能体任务管理系统..."
|
|
||||||
|
|
||||||
# 检查 Docker 是否在运行
|
|
||||||
if ! docker info > /dev/null 2>&1; then
|
|
||||||
echo "❌ Docker 未运行,请先启动 Docker"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 停止并清理旧容器
|
|
||||||
echo "清理旧容器..."
|
|
||||||
docker stop test-postgres 2>/dev/null || true
|
|
||||||
docker rm test-postgres 2>/dev/null || true
|
|
||||||
|
|
||||||
# 启动 PostgreSQL
|
|
||||||
echo "启动 PostgreSQL..."
|
|
||||||
docker run -d \
|
|
||||||
--name agent-task-postgres \
|
|
||||||
--restart unless-stopped \
|
|
||||||
-e POSTGRES_DB=agent_tasks \
|
|
||||||
-e POSTGRES_USER=postgres \
|
|
||||||
-e POSTGRES_PASSWORD=postgres \
|
|
||||||
-p 5432:5432 \
|
|
||||||
postgres:15-alpine
|
|
||||||
|
|
||||||
# 等待数据库就绪
|
|
||||||
echo "等待数据库启动..."
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
# 启动应用服务
|
|
||||||
echo "启动应用服务..."
|
|
||||||
pkill -f "node.*server-postgres.js" || true
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
DB_HOST=localhost \
|
|
||||||
DB_PORT=5433 \
|
|
||||||
DB_PASSWORD=postgres \
|
|
||||||
nohup node server-postgres.js > server.log 2>&1 &
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# 检查服务状态
|
|
||||||
if curl -s http://localhost:3000/health > /dev/null; then
|
|
||||||
echo ""
|
|
||||||
echo "✅ 服务启动成功!"
|
|
||||||
echo ""
|
|
||||||
echo "🌐 Web 界面: http://localhost:3000"
|
|
||||||
echo "📡 API 地址: http://localhost:3000/api"
|
|
||||||
echo "📊 健康检查: http://localhost:3000/health"
|
|
||||||
echo ""
|
|
||||||
echo "📝 查看日志: tail -f server.log"
|
|
||||||
echo "🛑 停止服务: ./stop.sh"
|
|
||||||
else
|
|
||||||
echo "❌ 服务启动失败,请查看 server.log"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "🛑 停止多智能体任务管理系统..."
|
|
||||||
|
|
||||||
# 停止 Node.js 服务
|
|
||||||
pkill -f "node.*server-postgres.js"
|
|
||||||
|
|
||||||
# 停止 PostgreSQL 容器
|
|
||||||
docker stop agent-task-postgres 2>/dev/null
|
|
||||||
docker rm agent-task-postgres 2>/dev/null
|
|
||||||
|
|
||||||
echo "✅ 服务已停止"
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
API_BASE="http://localhost:3000/api"
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " 多智能体任务管理系统 - 功能演示"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 1. 注册智能体
|
|
||||||
echo "📝 步骤 1: 注册智能体..."
|
|
||||||
AGENT_RESPONSE=$(curl -s -X POST $API_BASE/agents/register \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"name": "演示智能体",
|
|
||||||
"type": "general",
|
|
||||||
"capabilities": ["数据分析", "任务处理"]
|
|
||||||
}')
|
|
||||||
|
|
||||||
AGENT_ID=$(echo $AGENT_RESPONSE | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
|
|
||||||
echo "✅ 智能体已注册"
|
|
||||||
echo " ID: $AGENT_ID"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 2. 查看所有智能体
|
|
||||||
echo "📋 步骤 2: 查看所有智能体..."
|
|
||||||
curl -s $API_BASE/agents | jq '.[0] | {id, name, type, status}'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 3. 创建任务
|
|
||||||
echo "📌 步骤 3: 创建任务..."
|
|
||||||
TASK_RESPONSE=$(curl -s -X POST $API_BASE/tasks \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"title": "测试任务:数据分析",
|
|
||||||
"description": "分析用户行为数据并生成报告",
|
|
||||||
"priority": "high",
|
|
||||||
"created_by": "human"
|
|
||||||
}')
|
|
||||||
|
|
||||||
TASK_ID=$(echo $TASK_RESPONSE | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
|
|
||||||
echo "✅ 任务已创建"
|
|
||||||
echo " ID: $TASK_ID"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 4. 查看待处理任务
|
|
||||||
echo "📋 步骤 4: 查看待处理任务..."
|
|
||||||
curl -s "$API_BASE/tasks?status=pending" | jq '.[0] | {id, title, priority, status}'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 5. 智能体领取任务
|
|
||||||
echo "🤖 步骤 5: 智能体领取任务..."
|
|
||||||
curl -s -X POST $API_BASE/tasks/$TASK_ID/claim \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"agent_id\": \"$AGENT_ID\"}" | jq '.'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 6. 发送心跳(标记为忙碌)
|
|
||||||
echo "💓 步骤 6: 发送心跳(忙碌状态)..."
|
|
||||||
curl -s -X POST $API_BASE/agents/$AGENT_ID/heartbeat \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"status": "busy"}' | jq '.'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 7. 模拟任务执行
|
|
||||||
echo "⏳ 步骤 7: 模拟任务执行(3秒)..."
|
|
||||||
sleep 3
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 8. 完成任务
|
|
||||||
echo "✅ 步骤 8: 标记任务完成..."
|
|
||||||
curl -s -X PUT $API_BASE/tasks/$TASK_ID \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"status": "completed",
|
|
||||||
"result": "数据分析完成,发现3个关键趋势:1) 用户活跃度提升20% 2) 转化率增长15% 3) 留存率稳定在85%"
|
|
||||||
}' | jq '.'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 9. 查看任务详情
|
|
||||||
echo "📊 步骤 9: 查看完成的任务详情..."
|
|
||||||
curl -s $API_BASE/tasks/$TASK_ID | jq '{id, title, status, result, completed_at}'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 10. 查看统计信息
|
|
||||||
echo "📈 步骤 10: 查看系统统计..."
|
|
||||||
curl -s $API_BASE/stats | jq '.'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo " 演示完成!"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo "💡 提示:"
|
|
||||||
echo " - Web 界面: http://localhost:3000"
|
|
||||||
echo " - 查看所有智能体: curl $API_BASE/agents | jq"
|
|
||||||
echo " - 查看所有任务: curl $API_BASE/tasks | jq"
|
|
||||||
Reference in New Issue
Block a user