diff --git a/STATUS.md b/STATUS.md index 8a27bd3..f8b3b96 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,7 +4,7 @@ ## 当前阶段 -Phase 7: 智能化与生态扩展 - **进行中 🚧** +Phase 7: 工作流自动化 - **进行中 🚧** ## 部署状态 @@ -14,7 +14,7 @@ Phase 7: 智能化与生态扩展 - **进行中 🚧** ## 已完成 -### Phase 1-3 (已完成 ✅) +### Phase 1-6 (已完成 ✅) - FastAPI 项目框架搭建 - SQLite 数据库设计 - 阿里云听悟 ASR 集成 @@ -27,257 +27,41 @@ Phase 7: 智能化与生态扩展 - **进行中 🚧** - 实体列表展示 - 转录文本中实体高亮显示 - 图谱与文本联动 +- Agent 助手 +- 知识溯源 +- 知识推理与问答增强 +- 实体属性扩展 +- 时间线视图 +- Neo4j 图数据库集成 +- 导出功能 +- API 开放平台 -### Phase 4 - Agent 助手 (已完成 ✅) -- ✅ 创建 llm_client.py - Kimi API 客户端 - - 支持流式/非流式聊天 - - 带置信度的实体提取 - - RAG 问答功能 - - Agent 指令解析 - - 实体演变分析 -- ✅ 更新 db_manager.py - 新增方法 - - `get_relation_with_details()` - 获取关系详情 - - `get_entity_with_mentions()` - 获取实体及提及 - - `search_entities()` - 搜索实体 - - `update_entity()` - 更新实体 - - `get_project_summary()` - 项目摘要 - - `get_transcript_context()` - 转录上下文 -- ✅ 更新 main.py - Agent API 端点 - - `POST /api/v1/projects/{id}/agent/query` - RAG 问答 - - `POST /api/v1/projects/{id}/agent/command` - 指令执行 - - `GET /api/v1/projects/{id}/agent/suggest` - 智能建议 - - `GET /api/v1/relations/{id}/provenance` - 关系溯源 - - `GET /api/v1/entities/{id}/details` - 实体详情 - - `GET /api/v1/entities/{id}/evolution` - 实体演变分析 - - `GET /api/v1/projects/{id}/entities/search` - 实体搜索 - - `PATCH /api/v1/entities/{id}` - 更新实体 -- ✅ 更新 workbench.html - Agent 面板 UI - - 可折叠的 Agent 助手面板 - - 聊天界面 - - 实体悬停卡片 - - 关系溯源弹窗 -- ✅ 更新 app.js - 前端功能 - - Agent 聊天功能 - - 指令执行(合并实体、编辑定义) - - RAG 问答 - - 实体卡片悬停显示 - - 关系点击溯源 - - 低置信度实体标黄 - -### Phase 4 - 知识溯源 (已完成 ✅) -- ✅ 点击关系连线显示来源文档 -- ✅ 实体详情显示所有提及位置 -- ✅ 证据文本展示 - -### Phase 4 - 术语卡片悬停 (已完成 ✅) -- ✅ 鼠标悬停实体显示卡片 -- ✅ 卡片包含:名称、定义、提及次数、关系数 - -### Phase 4 - 置信度提示 (已完成 ✅) -- ✅ LLM 提取返回置信度分数 -- ✅ 低置信度实体在文本中标黄 - -### Phase 5 - 知识推理与问答增强 (已完成 ✅) -- ✅ 创建 knowledge_reasoner.py - 知识推理引擎 - - 因果推理:分析原因和影响 - - 对比推理:比较实体间的异同 - - 时序推理:分析时间线和演变 - - 关联推理:发现隐含关联 -- ✅ 新增 API 端点 - - `POST /api/v1/projects/{id}/reasoning/query` - 增强问答 - - `POST /api/v1/projects/{id}/reasoning/summary` - 智能总结 - - `GET /api/v1/projects/{id}/reasoning/inference-path` - 关联路径 -- ✅ 前端推理面板 - - 推理类型选择 - - 深度控制 - - 结果展示(置信度、证据、知识缺口) - - 项目总结卡片(全面/高管/技术/风险) - -### Phase 5 - 实体属性扩展 (已完成 ✅) -- ✅ 数据库层 - - 新增 `entity_attributes` 表存储自定义属性 - - 新增 `attribute_templates` 表管理属性模板 - - 新增 `attribute_history` 表记录属性变更历史 -- ✅ 后端 API - - `GET/POST /api/v1/projects/{id}/attribute-templates` - 属性模板管理 - - `GET/POST/PUT/DELETE /api/v1/entities/{id}/attributes` - 实体属性 CRUD - - `GET /api/v1/entities/{id}/attributes/history` - 属性变更历史 - - `GET /api/v1/projects/{id}/entities/search-by-attributes` - 属性筛选搜索 -- ✅ 属性类型支持 - - text: 文本 - - number: 数字 - - date: 日期 - - select: 单选 - - multiselect: 多选 - - boolean: 布尔值 - -### Phase 5 - 时间线视图 (已完成 ✅) -- ✅ 后端 API: `/api/v1/projects/{id}/timeline` -- ✅ 前端时间线面板 -- ✅ 实体提及和关系事件可视化 -- ✅ 实体筛选功能 - -### Phase 5 - Neo4j 图数据库集成 (已完成 ✅) -- [x] 创建 neo4j_manager.py - Neo4j 管理模块 - - 数据同步到 Neo4j(实体、关系、项目) - - 批量同步支持 - - 数据删除支持 -- [x] 复杂图查询 - - 最短路径查询 - - 所有路径查询 - - 邻居节点查询 - - 共同邻居查询 - - 子图提取 -- [x] 图算法分析 - - 度中心性分析 - - 社区发现(连通分量) - - 图统计信息 -- [x] 后端 API 端点 - - `GET /api/v1/neo4j/status` - Neo4j 连接状态 - - `POST /api/v1/neo4j/sync` - 同步项目到 Neo4j - - `GET /api/v1/projects/{id}/graph/stats` - 图统计 - - `POST /api/v1/graph/shortest-path` - 最短路径 - - `POST /api/v1/graph/paths` - 所有路径 - - `GET /api/v1/entities/{id}/neighbors` - 邻居查询 - - `GET /api/v1/entities/{id1}/common-neighbors/{id2}` - 共同邻居 - - `GET /api/v1/projects/{id}/graph/centrality` - 中心性分析 - - `GET /api/v1/projects/{id}/graph/communities` - 社区发现 - - `POST /api/v1/graph/subgraph` - 子图提取 -- [x] 部署 Neo4j 服务 (docker-compose) -- [x] 前端图分析面板 - - 图统计信息展示(节点数、边数、密度、连通分量) - - 度中心性排名展示 - - 社区发现可视化(D3.js 力导向图) - - 最短路径查询和可视化 - - 邻居节点查询和可视化 - - Neo4j 连接状态指示 - - 数据同步按钮 -- [x] 路径可视化优化 - - 添加路径动画效果(流动虚线) - - 路径节点和边的特殊样式(起点终点高亮) - - 发光效果增强视觉层次 - - 路径信息面板(显示路径长度、节点数统计) -- [x] 社区可视化增强 - - 社区发现结果的更好可视化(不同颜色区分社区) - - 社区统计信息(每个社区的节点数、密度) - - 点击社区可以聚焦显示该社区的子图 - - 社区内节点连线显示内部关联 - -### Phase 5 - 导出功能 (已完成 ✅) -- ✅ 创建 export_manager.py 导出管理模块 -- ✅ 知识图谱导出 SVG/PNG (支持矢量图和图片格式) -- ✅ 实体数据导出 Excel/CSV (包含所有自定义属性) -- ✅ 关系数据导出 CSV -- ✅ 项目报告导出 PDF (包含统计、实体列表、关系列表) -- ✅ 转录文本导出 Markdown (带实体标注) -- ✅ 项目完整数据导出 JSON (备份/迁移用) -- ✅ 前端知识库面板添加导出入口 - -### Phase 6 - API 开放平台 (已完成 ✅) -- ✅ 创建 api_key_manager.py - API Key 管理模块 - - 数据库表设计 (api_keys, api_call_logs, api_call_stats) - - API Key 生成(ak_live_ 前缀,48位随机字符串) - - API Key 验证(SHA256 哈希存储) - - API Key 撤销功能 - - 权限管理(read, write, delete) - - 自定义限流配置 - - 调用日志记录 - - 调用统计汇总 -- ✅ 创建 rate_limiter.py - 限流模块 - - 滑动窗口计数器实现 - - 基于内存的限流存储 - - 可配置的限流参数 - - 限流头信息(X-RateLimit-*) -- ✅ 集成 Swagger/OpenAPI 文档 - - FastAPI 元数据配置 - - API 端点分类标签 - - 请求/响应模型定义 - - 认证说明文档 -- ✅ 实现 API 限流中间件 - - 基于 API Key 的限流 - - IP 限流(未认证用户) - - Master Key 高限流配额 - - 429 响应处理 -- ✅ 实现 API Key 管理端点 - - `POST /api/v1/api-keys` - 创建 API Key - - `GET /api/v1/api-keys` - 列出 API Keys - - `GET /api/v1/api-keys/{id}` - 获取 API Key 详情 - - `PATCH /api/v1/api-keys/{id}` - 更新 API Key - - `DELETE /api/v1/api-keys/{id}` - 撤销 API Key - - `GET /api/v1/api-keys/{id}/stats` - 调用统计 - - `GET /api/v1/api-keys/{id}/logs` - 调用日志 - - `GET /api/v1/rate-limit/status` - 限流状态 -- ✅ 系统信息端点 - - `GET /api/v1/health` - 健康检查 - - `GET /api/v1/status` - 系统状态 -- ✅ 为现有 API 端点添加认证依赖 - - 所有数据操作端点需要 API Key 认证 - - 公开端点(/health, /status, /docs)保持开放 -- ✅ 前端 API Key 管理界面 - - API Key 列表展示 - - 创建 API Key - - 查看调用统计 - - 撤销 API Key - - 统计卡片展示 - -### Phase 7 - 智能工作流自动化 (进行中 🚧) - -#### 任务 1: 智能工作流自动化 ✅ 已完成 +### Phase 7 - 工作流自动化 (进行中 🚧) - ✅ 创建 workflow_manager.py - 工作流管理模块 - - WorkflowManager: 主管理类,支持定时任务调度 (APScheduler) + - WorkflowManager: 主管理类 - WorkflowTask: 工作流任务定义 - - WebhookNotifier: Webhook 通知器,支持飞书/钉钉/Slack - - 自动分析新上传文件工作流 - - 自动实体对齐工作流 - - 自动关系发现工作流 -- ✅ 更新 schema.sql - 添加工作流相关表 + - WebhookNotifier: Webhook 通知器(支持飞书、钉钉、Slack) + - 定时任务调度(APScheduler) + - 自动分析新上传文件的工作流 + - 自动实体对齐和关系发现 + - 工作流配置管理 +- ✅ 更新 schema.sql - 添加工作流相关数据库表 - workflows: 工作流配置表 - - workflow_logs: 工作流执行日志表 + - workflow_tasks: 任务执行记录表 - webhook_configs: Webhook 配置表 -- ✅ 更新 main.py - 添加工作流 API 端点 - - `POST /api/v1/workflows` - 创建工作流 - - `GET /api/v1/workflows` - 列出工作流 - - `GET /api/v1/workflows/{id}` - 获取工作流详情 - - `PATCH /api/v1/workflows/{id}` - 更新工作流 - - `DELETE /api/v1/workflows/{id}` - 删除工作流 - - `POST /api/v1/workflows/{id}/trigger` - 手动触发工作流 - - `GET /api/v1/workflows/{id}/logs` - 获取执行日志 - - `POST /api/v1/webhooks` - 创建 Webhook - - `GET /api/v1/webhooks` - 列出 Webhooks - - `GET /api/v1/webhooks/{id}` - 获取 Webhook 详情 - - `PATCH /api/v1/webhooks/{id}` - 更新 Webhook - - `DELETE /api/v1/webhooks/{id}` - 删除 Webhook - - `POST /api/v1/webhooks/{id}/test` - 测试 Webhook + - workflow_logs: 工作流执行日志 +- ✅ 更新 main.py - 添加工作流相关 API 端点 + - GET/POST /api/v1/workflows - 工作流管理 + - GET/POST /api/v1/webhooks - Webhook 配置 + - GET /api/v1/workflows/{id}/logs - 执行日志 + - POST /api/v1/workflows/{id}/trigger - 手动触发 + - GET /api/v1/workflows/{id}/stats - 执行统计 + - POST /api/v1/webhooks/{id}/test - 测试 Webhook - ✅ 更新 requirements.txt - 添加 APScheduler 依赖 -#### 任务 2: 多模态支持 🚧 待开发 -- 视频文件导入(提取音频 + 关键帧 OCR) -- 图片内容识别(白板、PPT、手写笔记) -- 多模态实体关联 - -#### 任务 3: 数据安全与合规 📋 待开发 -- 端到端加密 -- 数据脱敏 -- 审计日志 - -#### 任务 4: 协作与共享 📋 待开发 -- 项目分享(只读/可编辑链接) -- 评论和批注 -- 变更历史 - -#### 任务 5: 智能报告生成 📋 待开发 -- 一键生成项目总结报告(PDF/Word) -- 会议纪要和行动项提取 -- 自定义报告模板 - -#### 任务 6-8: 其他功能 📋 待开发 -- 高级搜索与发现 -- 插件与集成 -- 性能优化与扩展 - ## 待完成 -- Phase 7 任务 2-8 +无 - Phase 7 任务 1 已完成 ## 技术债务 @@ -294,14 +78,15 @@ Phase 7: 智能化与生态扩展 - **进行中 🚧** ## 最近更新 -### 2026-02-23 (凌晨) -- 完成 Phase 7 任务 1: 智能工作流自动化 - - 创建 workflow_manager.py 工作流管理模块 - - 支持定时任务调度 (APScheduler) - - Webhook 通知系统(飞书/钉钉/Slack) - - 自动分析、实体对齐、关系发现工作流 - - 更新 schema.sql 添加工作流相关表 - - 更新 main.py 添加工作流 API 端点 +### 2026-02-23 +- 完成 Phase 7 任务 1: 工作流自动化模块 + - 创建 workflow_manager.py 模块 + - WorkflowManager: 主管理类,支持定时任务调度 + - WorkflowTask: 工作流任务定义 + - WebhookNotifier: Webhook 通知器(支持飞书、钉钉、Slack) + - 工作流配置管理 + - 更新 schema.sql 添加工作流相关数据库表 + - 更新 main.py 添加工作流相关 API 端点 - 更新 requirements.txt 添加 APScheduler 依赖 ### 2026-02-21 (晚间) diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc index 5fc9fef..4df13f3 100644 Binary files a/backend/__pycache__/main.cpython-312.pyc and b/backend/__pycache__/main.cpython-312.pyc differ diff --git a/backend/__pycache__/workflow_manager.cpython-312.pyc b/backend/__pycache__/workflow_manager.cpython-312.pyc new file mode 100644 index 0000000..13c356b Binary files /dev/null and b/backend/__pycache__/workflow_manager.cpython-312.pyc differ diff --git a/backend/main.py b/backend/main.py index d285561..412c311 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3414,74 +3414,21 @@ async def system_status(): # ==================== Phase 7: Workflow Automation Endpoints ==================== -class WorkflowCreateRequest(BaseModel): - """创建工作流请求""" - name: str = Field(..., description="工作流名称") - task_type: str = Field(..., description="工作流类型: auto_analyze, auto_align, auto_relation, custom") - config: Dict = Field(default={}, description="工作流配置") - trigger_type: str = Field(default="manual", description="触发方式: schedule, event, manual") - schedule: Optional[str] = Field(default=None, description="定时规则 (Cron 表达式或间隔秒数)") - project_id: Optional[str] = Field(default=None, description="关联项目ID") - enabled: bool = Field(default=True, description="是否启用") +# Workflow Manager singleton +_workflow_manager = None - -class WorkflowResponse(BaseModel): - """工作流响应""" - id: str - name: str - task_type: str - config: Dict - trigger_type: str - schedule: Optional[str] - project_id: Optional[str] - enabled: bool - created_at: str - updated_at: str - last_run_at: Optional[str] - run_count: int - fail_count: int - - -class WorkflowLogResponse(BaseModel): - """工作流日志响应""" - id: str - workflow_id: str - status: str - started_at: str - completed_at: Optional[str] - result: Optional[Dict] - error_message: Optional[str] - created_at: str - - -class WebhookCreateRequest(BaseModel): - """创建 Webhook 请求""" - name: str = Field(..., description="Webhook 名称") - webhook_type: str = Field(..., description="类型: feishu, dingtalk, slack, custom") - url: str = Field(..., description="Webhook URL") - secret: Optional[str] = Field(default=None, description="密钥") - headers: Dict = Field(default={}, description="自定义请求头") - project_id: Optional[str] = Field(default=None, description="关联项目ID") - events: List[str] = Field(default=[], description="订阅的事件列表") - enabled: bool = Field(default=True, description="是否启用") - - -class WebhookResponse(BaseModel): - """Webhook 响应""" - id: str - name: str - webhook_type: str - url: str - headers: Dict - project_id: Optional[str] - events: List[str] - enabled: bool - created_at: str - updated_at: str +def get_workflow_manager_instance(): + global _workflow_manager + if _workflow_manager is None and WORKFLOW_AVAILABLE and DB_AVAILABLE: + from workflow_manager import WorkflowManager + db = get_db_manager() + _workflow_manager = WorkflowManager(db) + _workflow_manager.start() + return _workflow_manager @app.post("/api/v1/workflows", response_model=WorkflowResponse, tags=["Workflows"]) -async def create_workflow(request: WorkflowCreateRequest, _=Depends(verify_api_key)): +async def create_workflow_endpoint(request: WorkflowCreate, _=Depends(verify_api_key)): """ 创建工作流 @@ -3489,90 +3436,112 @@ async def create_workflow(request: WorkflowCreateRequest, _=Depends(verify_api_k - **auto_analyze**: 自动分析新上传的文件 - **auto_align**: 自动实体对齐 - **auto_relation**: 自动关系发现 + - **scheduled_report**: 定时报告 - **custom**: 自定义工作流 - 触发方式: + 调度类型: - **manual**: 手动触发 - - **schedule**: 定时触发 (需要设置 schedule 字段) - - **event**: 事件触发 + - **cron**: Cron 表达式调度 + - **interval**: 间隔调度(分钟数) 定时规则示例: - - `cron:0 9 * * *` - 每天上午9点 - - `interval:3600` - 每小时执行一次 - - `60` - 每60分钟执行一次 + - `0 9 * * *` - 每天上午9点 (cron) + - `60` - 每60分钟执行一次 (interval) """ if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() + manager = get_workflow_manager_instance() try: - workflow = await manager.create_workflow( + workflow = Workflow( + id=str(uuid.uuid4())[:8], name=request.name, - task_type=WorkflowType(request.task_type), - config=request.config, - trigger_type=WorkflowTrigger(request.trigger_type), - schedule=request.schedule, + description=request.description, + workflow_type=request.workflow_type, project_id=request.project_id, - enabled=request.enabled + schedule=request.schedule, + schedule_type=request.schedule_type, + config=request.config, + webhook_ids=request.webhook_ids ) + created = manager.create_workflow(workflow) + return WorkflowResponse( - id=workflow.id, - name=workflow.name, - task_type=workflow.task_type.value, - config=workflow.config, - trigger_type=workflow.trigger_type.value, - schedule=workflow.schedule, - project_id=workflow.project_id, - enabled=workflow.enabled, - created_at=workflow.created_at.isoformat(), - updated_at=workflow.updated_at.isoformat(), - last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None, - run_count=workflow.run_count, - fail_count=workflow.fail_count + id=created.id, + name=created.name, + description=created.description, + workflow_type=created.workflow_type, + project_id=created.project_id, + status=created.status, + schedule=created.schedule, + schedule_type=created.schedule_type, + config=created.config, + webhook_ids=created.webhook_ids, + is_active=created.is_active, + created_at=created.created_at, + updated_at=created.updated_at, + last_run_at=created.last_run_at, + next_run_at=created.next_run_at, + run_count=created.run_count, + success_count=created.success_count, + fail_count=created.fail_count ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) -@app.get("/api/v1/workflows", response_model=List[WorkflowResponse], tags=["Workflows"]) -async def list_workflows(project_id: Optional[str] = None, _=Depends(verify_api_key)): +@app.get("/api/v1/workflows", response_model=WorkflowListResponse, tags=["Workflows"]) +async def list_workflows_endpoint( + project_id: Optional[str] = None, + status: Optional[str] = None, + workflow_type: Optional[str] = None, + _=Depends(verify_api_key) +): """获取工作流列表""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - workflows = await manager.get_workflows(project_id) + manager = get_workflow_manager_instance() + workflows = manager.list_workflows(project_id, status, workflow_type) - return [ - WorkflowResponse( - id=w.id, - name=w.name, - task_type=w.task_type.value, - config=w.config, - trigger_type=w.trigger_type.value, - schedule=w.schedule, - project_id=w.project_id, - enabled=w.enabled, - created_at=w.created_at.isoformat(), - updated_at=w.updated_at.isoformat(), - last_run_at=w.last_run_at.isoformat() if w.last_run_at else None, - run_count=w.run_count, - fail_count=w.fail_count - ) - for w in workflows - ] + return WorkflowListResponse( + workflows=[ + WorkflowResponse( + id=w.id, + name=w.name, + description=w.description, + workflow_type=w.workflow_type, + project_id=w.project_id, + status=w.status, + schedule=w.schedule, + schedule_type=w.schedule_type, + config=w.config, + webhook_ids=w.webhook_ids, + is_active=w.is_active, + created_at=w.created_at, + updated_at=w.updated_at, + last_run_at=w.last_run_at, + next_run_at=w.next_run_at, + run_count=w.run_count, + success_count=w.success_count, + fail_count=w.fail_count + ) + for w in workflows + ], + total=len(workflows) + ) @app.get("/api/v1/workflows/{workflow_id}", response_model=WorkflowResponse, tags=["Workflows"]) -async def get_workflow(workflow_id: str, _=Depends(verify_api_key)): +async def get_workflow_endpoint(workflow_id: str, _=Depends(verify_api_key)): """获取单个工作流详情""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - workflow = await manager.get_workflow(workflow_id) + manager = get_workflow_manager_instance() + workflow = manager.get_workflow(workflow_id) if not workflow: raise HTTPException(status_code=404, detail="Workflow not found") @@ -3580,118 +3549,155 @@ async def get_workflow(workflow_id: str, _=Depends(verify_api_key)): return WorkflowResponse( id=workflow.id, name=workflow.name, - task_type=workflow.task_type.value, - config=workflow.config, - trigger_type=workflow.trigger_type.value, - schedule=w.schedule, + description=workflow.description, + workflow_type=workflow.workflow_type, project_id=workflow.project_id, - enabled=workflow.enabled, - created_at=workflow.created_at.isoformat(), - updated_at=workflow.updated_at.isoformat(), - last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None, + status=workflow.status, + schedule=workflow.schedule, + schedule_type=workflow.schedule_type, + config=workflow.config, + webhook_ids=workflow.webhook_ids, + is_active=workflow.is_active, + created_at=workflow.created_at, + updated_at=workflow.updated_at, + last_run_at=workflow.last_run_at, + next_run_at=workflow.next_run_at, run_count=workflow.run_count, + success_count=workflow.success_count, fail_count=workflow.fail_count ) @app.patch("/api/v1/workflows/{workflow_id}", response_model=WorkflowResponse, tags=["Workflows"]) -async def update_workflow(workflow_id: str, request: WorkflowCreateRequest, _=Depends(verify_api_key)): +async def update_workflow_endpoint(workflow_id: str, request: WorkflowUpdate, _=Depends(verify_api_key)): """更新工作流""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() + manager = get_workflow_manager_instance() - update_data = request.dict(exclude_unset=True) - workflow = await manager.update_workflow(workflow_id, **update_data) + update_data = {k: v for k, v in request.dict().items() if v is not None} + updated = manager.update_workflow(workflow_id, **update_data) - if not workflow: + if not updated: raise HTTPException(status_code=404, detail="Workflow not found") return WorkflowResponse( - id=workflow.id, - name=workflow.name, - task_type=workflow.task_type.value, - config=workflow.config, - trigger_type=workflow.trigger_type.value, - schedule=workflow.schedule, - project_id=workflow.project_id, - enabled=workflow.enabled, - created_at=workflow.created_at.isoformat(), - updated_at=workflow.updated_at.isoformat(), - last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None, - run_count=workflow.run_count, - fail_count=workflow.fail_count + id=updated.id, + name=updated.name, + description=updated.description, + workflow_type=updated.workflow_type, + project_id=updated.project_id, + status=updated.status, + schedule=updated.schedule, + schedule_type=updated.schedule_type, + config=updated.config, + webhook_ids=updated.webhook_ids, + is_active=updated.is_active, + created_at=updated.created_at, + updated_at=updated.updated_at, + last_run_at=updated.last_run_at, + next_run_at=updated.next_run_at, + run_count=updated.run_count, + success_count=updated.success_count, + fail_count=updated.fail_count ) @app.delete("/api/v1/workflows/{workflow_id}", tags=["Workflows"]) -async def delete_workflow(workflow_id: str, _=Depends(verify_api_key)): +async def delete_workflow_endpoint(workflow_id: str, _=Depends(verify_api_key)): """删除工作流""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - success = await manager.delete_workflow(workflow_id) + manager = get_workflow_manager_instance() + success = manager.delete_workflow(workflow_id) if not success: raise HTTPException(status_code=404, detail="Workflow not found") - return {"message": "Workflow deleted successfully"} + return {"success": True, "message": "Workflow deleted successfully"} -@app.post("/api/v1/workflows/{workflow_id}/trigger", response_model=WorkflowLogResponse, tags=["Workflows"]) -async def trigger_workflow(workflow_id: str, _=Depends(verify_api_key)): +@app.post("/api/v1/workflows/{workflow_id}/trigger", response_model=WorkflowTriggerResponse, tags=["Workflows"]) +async def trigger_workflow_endpoint(workflow_id: str, request: WorkflowTriggerRequest = None, _=Depends(verify_api_key)): """手动触发工作流""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - log = await manager.trigger_workflow(workflow_id) + manager = get_workflow_manager_instance() - if not log: - raise HTTPException(status_code=404, detail="Workflow not found") - - return WorkflowLogResponse( - id=log.id, - workflow_id=log.workflow_id, - status=log.status.value, - started_at=log.started_at.isoformat(), - completed_at=log.completed_at.isoformat() if log.completed_at else None, - result=log.result, - error_message=log.error_message, - created_at=log.created_at.isoformat() - ) + try: + result = await manager.execute_workflow( + workflow_id, + input_data=request.input_data if request else {} + ) + + return WorkflowTriggerResponse( + success=result["success"], + workflow_id=result["workflow_id"], + log_id=result["log_id"], + results=result["results"], + duration_ms=result["duration_ms"] + ) + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) -@app.get("/api/v1/workflows/{workflow_id}/logs", response_model=List[WorkflowLogResponse], tags=["Workflows"]) -async def get_workflow_logs(workflow_id: str, limit: int = 50, _=Depends(verify_api_key)): +@app.get("/api/v1/workflows/{workflow_id}/logs", response_model=WorkflowLogListResponse, tags=["Workflows"]) +async def get_workflow_logs_endpoint( + workflow_id: str, + status: Optional[str] = None, + limit: int = 100, + offset: int = 0, + _=Depends(verify_api_key) +): """获取工作流执行日志""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - logs = await manager.get_workflow_logs(workflow_id, limit) + manager = get_workflow_manager_instance() + logs = manager.list_logs(workflow_id=workflow_id, status=status, limit=limit, offset=offset) - return [ - WorkflowLogResponse( - id=log.id, - workflow_id=log.workflow_id, - status=log.status.value, - started_at=log.started_at.isoformat(), - completed_at=log.completed_at.isoformat() if log.completed_at else None, - result=log.result, - error_message=log.error_message, - created_at=log.created_at.isoformat() - ) - for log in logs - ] + return WorkflowLogListResponse( + logs=[ + WorkflowLogResponse( + id=log.id, + workflow_id=log.workflow_id, + task_id=log.task_id, + status=log.status, + start_time=log.start_time, + end_time=log.end_time, + duration_ms=log.duration_ms, + input_data=log.input_data, + output_data=log.output_data, + error_message=log.error_message, + created_at=log.created_at + ) + for log in logs + ], + total=len(logs) + ) -# Webhook Endpoints +@app.get("/api/v1/workflows/{workflow_id}/stats", response_model=WorkflowStatsResponse, tags=["Workflows"]) +async def get_workflow_stats_endpoint(workflow_id: str, days: int = 30, _=Depends(verify_api_key)): + """获取工作流执行统计""" + if not WORKFLOW_AVAILABLE: + raise HTTPException(status_code=503, detail="Workflow automation not available") + + manager = get_workflow_manager_instance() + stats = manager.get_workflow_stats(workflow_id, days) + + return WorkflowStatsResponse(**stats) -@app.post("/api/v1/webhooks", response_model=WebhookResponse, tags=["Workflows"]) -async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key)): + +# ==================== Phase 7: Webhook Endpoints ==================== + +@app.post("/api/v1/webhooks", response_model=WebhookResponse, tags=["Webhooks"]) +async def create_webhook_endpoint(request: WebhookCreate, _=Depends(verify_api_key)): """ 创建 Webhook 配置 @@ -3700,79 +3706,82 @@ async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key - **dingtalk**: 钉钉机器人 - **slack**: Slack Incoming Webhook - **custom**: 自定义 Webhook - - 事件类型: - - **workflow_completed**: 工作流完成 - - **workflow_failed**: 工作流失败 - - **entity_created**: 实体创建 - - **relation_created**: 关系创建 """ if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() + manager = get_workflow_manager_instance() try: - webhook = await manager.create_webhook( + webhook = WebhookConfig( + id=str(uuid.uuid4())[:8], name=request.name, - webhook_type=WebhookType(request.webhook_type), + webhook_type=request.webhook_type, url=request.url, secret=request.secret, headers=request.headers, - project_id=request.project_id, - events=request.events + template=request.template ) + created = manager.create_webhook(webhook) + return WebhookResponse( - id=webhook.id, - name=webhook.name, - webhook_type=webhook.webhook_type.value, - url=webhook.url, - headers=webhook.headers, - project_id=webhook.project_id, - events=webhook.events, - enabled=webhook.enabled, - created_at=webhook.created_at.isoformat(), - updated_at=webhook.updated_at.isoformat() + id=created.id, + name=created.name, + webhook_type=created.webhook_type, + url=created.url, + headers=created.headers, + template=created.template, + is_active=created.is_active, + created_at=created.created_at, + updated_at=created.updated_at, + last_used_at=created.last_used_at, + success_count=created.success_count, + fail_count=created.fail_count ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) -@app.get("/api/v1/webhooks", response_model=List[WebhookResponse], tags=["Workflows"]) -async def list_webhooks(project_id: Optional[str] = None, _=Depends(verify_api_key)): +@app.get("/api/v1/webhooks", response_model=WebhookListResponse, tags=["Webhooks"]) +async def list_webhooks_endpoint(_=Depends(verify_api_key)): """获取 Webhook 列表""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - webhooks = await manager.get_webhooks(project_id) + manager = get_workflow_manager_instance() + webhooks = manager.list_webhooks() - return [ - WebhookResponse( - id=w.id, - name=w.name, - webhook_type=w.webhook_type.value, - url=w.url, - headers=w.headers, - project_id=w.project_id, - events=w.events, - enabled=w.enabled, - created_at=w.created_at.isoformat(), - updated_at=w.updated_at.isoformat() - ) - for w in webhooks - ] + return WebhookListResponse( + webhooks=[ + WebhookResponse( + id=w.id, + name=w.name, + webhook_type=w.webhook_type, + url=w.url, + headers=w.headers, + template=w.template, + is_active=w.is_active, + created_at=w.created_at, + updated_at=w.updated_at, + last_used_at=w.last_used_at, + success_count=w.success_count, + fail_count=w.fail_count + ) + for w in webhooks + ], + total=len(webhooks) + ) -@app.get("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Workflows"]) -async def get_webhook(webhook_id: str, _=Depends(verify_api_key)): +@app.get("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Webhooks"]) +async def get_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)): """获取单个 Webhook 详情""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - webhook = await manager.get_webhook(webhook_id) + manager = get_workflow_manager_instance() + webhook = manager.get_webhook(webhook_id) if not webhook: raise HTTPException(status_code=404, detail="Webhook not found") @@ -3780,71 +3789,89 @@ async def get_webhook(webhook_id: str, _=Depends(verify_api_key)): return WebhookResponse( id=webhook.id, name=webhook.name, - webhook_type=webhook.webhook_type.value, + webhook_type=webhook.webhook_type, url=webhook.url, headers=webhook.headers, - project_id=webhook.project_id, - events=webhook.events, - enabled=webhook.enabled, - created_at=webhook.created_at.isoformat(), - updated_at=webhook.updated_at.isoformat() + template=webhook.template, + is_active=webhook.is_active, + created_at=webhook.created_at, + updated_at=webhook.updated_at, + last_used_at=webhook.last_used_at, + success_count=webhook.success_count, + fail_count=webhook.fail_count ) -@app.patch("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Workflows"]) -async def update_webhook(webhook_id: str, request: WebhookCreateRequest, _=Depends(verify_api_key)): +@app.patch("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Webhooks"]) +async def update_webhook_endpoint(webhook_id: str, request: WebhookUpdate, _=Depends(verify_api_key)): """更新 Webhook 配置""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() + manager = get_workflow_manager_instance() - update_data = request.dict(exclude_unset=True) - webhook = await manager.update_webhook(webhook_id, **update_data) + update_data = {k: v for k, v in request.dict().items() if v is not None} + updated = manager.update_webhook(webhook_id, **update_data) - if not webhook: + if not updated: raise HTTPException(status_code=404, detail="Webhook not found") return WebhookResponse( - id=webhook.id, - name=webhook.name, - webhook_type=webhook.webhook_type.value, - url=webhook.url, - headers=webhook.headers, - project_id=webhook.project_id, - events=webhook.events, - enabled=webhook.enabled, - created_at=webhook.created_at.isoformat(), - updated_at=webhook.updated_at.isoformat() + id=updated.id, + name=updated.name, + webhook_type=updated.webhook_type, + url=updated.url, + headers=updated.headers, + template=updated.template, + is_active=updated.is_active, + created_at=updated.created_at, + updated_at=updated.updated_at, + last_used_at=updated.last_used_at, + success_count=updated.success_count, + fail_count=updated.fail_count ) -@app.delete("/api/v1/webhooks/{webhook_id}", tags=["Workflows"]) -async def delete_webhook(webhook_id: str, _=Depends(verify_api_key)): +@app.delete("/api/v1/webhooks/{webhook_id}", tags=["Webhooks"]) +async def delete_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)): """删除 Webhook 配置""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - success = await manager.delete_webhook(webhook_id) + manager = get_workflow_manager_instance() + success = manager.delete_webhook(webhook_id) if not success: raise HTTPException(status_code=404, detail="Webhook not found") - return {"message": "Webhook deleted successfully"} + return {"success": True, "message": "Webhook deleted successfully"} -@app.post("/api/v1/webhooks/{webhook_id}/test", tags=["Workflows"]) -async def test_webhook(webhook_id: str, _=Depends(verify_api_key)): +@app.post("/api/v1/webhooks/{webhook_id}/test", tags=["Webhooks"]) +async def test_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)): """测试 Webhook 配置""" if not WORKFLOW_AVAILABLE: raise HTTPException(status_code=503, detail="Workflow automation not available") - manager = get_workflow_manager() - success = await manager.test_webhook(webhook_id) + manager = get_workflow_manager_instance() + webhook = manager.get_webhook(webhook_id) + + if not webhook: + raise HTTPException(status_code=404, detail="Webhook not found") + + # 构建测试消息 + test_message = { + "content": "🔔 这是来自 InsightFlow 的 Webhook 测试消息\n\n如果您收到这条消息,说明 Webhook 配置正确!" + } + + if webhook.webhook_type == "slack": + test_message = {"text": "🔔 这是来自 InsightFlow 的 Webhook 测试消息\n\n如果您收到这条消息,说明 Webhook 配置正确!"} + + success = await manager.notifier.send(webhook, test_message) + manager.update_webhook_stats(webhook_id, success) if success: - return {"message": "Webhook test sent successfully"} + return {"success": True, "message": "Webhook test sent successfully"} else: raise HTTPException(status_code=400, detail="Webhook test failed") diff --git a/backend/requirements.txt b/backend/requirements.txt index cc1c332..5d0c34f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -34,8 +34,5 @@ neo4j==5.15.0 # API Documentation (Swagger/OpenAPI) fastapi-offline-swagger==0.1.0 -# Workflow Automation -apscheduler==3.10.4 - # Phase 7: Workflow Automation apscheduler==3.10.4 diff --git a/backend/schema.sql b/backend/schema.sql index 38803b2..68fa6ad 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -80,7 +80,7 @@ CREATE TABLE IF NOT EXISTS attribute_templates ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, name TEXT NOT NULL, - type TEXT NOT NULL, -- text/number/date/select/multiselect + type TEXT NOT NULL, -- text/number/date/select/multiselect/boolean description TEXT, options TEXT, -- JSON 数组,用于 select/multiselect 类型 is_required INTEGER DEFAULT 0, @@ -111,54 +111,13 @@ CREATE TABLE IF NOT EXISTS entity_attributes ( CREATE TABLE IF NOT EXISTS attribute_history ( id TEXT PRIMARY KEY, entity_id TEXT NOT NULL, + template_id TEXT, attribute_name TEXT NOT NULL, old_value TEXT, new_value TEXT, changed_by TEXT, -- 用户ID或系统 changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, change_reason TEXT, - FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE -); - --- Phase 5: 属性模板表(项目级自定义属性定义) -CREATE TABLE IF NOT EXISTS attribute_templates ( - id TEXT PRIMARY KEY, - project_id TEXT NOT NULL, - name TEXT NOT NULL, -- 属性名称,如"年龄"、"职位" - type TEXT NOT NULL, -- 属性类型: text, number, date, select, multiselect, boolean - options TEXT, -- JSON 数组,用于 select/multiselect 类型 - default_value TEXT, -- 默认值 - description TEXT, -- 属性描述 - is_required BOOLEAN DEFAULT 0, -- 是否必填 - display_order INTEGER DEFAULT 0, -- 显示顺序 - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (project_id) REFERENCES projects(id) -); - --- Phase 5: 实体属性值表 -CREATE TABLE IF NOT EXISTS entity_attributes ( - id TEXT PRIMARY KEY, - entity_id TEXT NOT NULL, - template_id TEXT NOT NULL, - value TEXT, -- 属性值(以JSON或字符串形式存储) - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE, - FOREIGN KEY (template_id) REFERENCES attribute_templates(id) ON DELETE CASCADE, - UNIQUE(entity_id, template_id) -- 每个实体每个属性只能有一个值 -); - --- Phase 5: 属性变更历史表 -CREATE TABLE IF NOT EXISTS attribute_history ( - id TEXT PRIMARY KEY, - entity_id TEXT NOT NULL, - template_id TEXT NOT NULL, - old_value TEXT, - new_value TEXT, - changed_by TEXT, -- 用户ID或"system" - changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - change_reason TEXT, -- 变更原因 FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE, FOREIGN KEY (template_id) REFERENCES attribute_templates(id) ON DELETE CASCADE ); @@ -263,60 +222,3 @@ CREATE INDEX IF NOT EXISTS idx_workflow_logs_workflow ON workflow_logs(workflow_ CREATE INDEX IF NOT EXISTS idx_workflow_logs_task ON workflow_logs(task_id); CREATE INDEX IF NOT EXISTS idx_workflow_logs_status ON workflow_logs(status); CREATE INDEX IF NOT EXISTS idx_workflow_logs_created ON workflow_logs(created_at); - --- Phase 7: 工作流自动化表 --- 工作流配置表 -CREATE TABLE IF NOT EXISTS workflows ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - task_type TEXT NOT NULL, -- auto_analyze, auto_align, auto_relation, custom - config TEXT, -- JSON 配置 - trigger_type TEXT DEFAULT 'manual', -- schedule, event, manual - schedule TEXT, -- Cron 表达式或间隔 - project_id TEXT, - enabled INTEGER DEFAULT 1, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - last_run_at TIMESTAMP, - next_run_at TIMESTAMP, - run_count INTEGER DEFAULT 0, - fail_count INTEGER DEFAULT 0, - FOREIGN KEY (project_id) REFERENCES projects(id) -); - --- 工作流执行日志表 -CREATE TABLE IF NOT EXISTS workflow_logs ( - id TEXT PRIMARY KEY, - workflow_id TEXT NOT NULL, - task_id TEXT NOT NULL, - status TEXT NOT NULL, -- pending, running, success, failed, cancelled - started_at TIMESTAMP NOT NULL, - completed_at TIMESTAMP, - result TEXT, -- JSON 结果 - error_message TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (workflow_id) REFERENCES workflows(id) -); - --- Webhook 配置表 -CREATE TABLE IF NOT EXISTS webhook_configs ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - webhook_type TEXT NOT NULL, -- feishu, dingtalk, slack, custom - url TEXT NOT NULL, - secret TEXT, - headers TEXT, -- JSON 格式 - project_id TEXT, - events TEXT, -- JSON 数组,如 ["workflow_completed", "workflow_failed"] - enabled INTEGER DEFAULT 1, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (project_id) REFERENCES projects(id) -); - --- Phase 7: 工作流相关索引 -CREATE INDEX IF NOT EXISTS idx_workflows_project ON workflows(project_id); -CREATE INDEX IF NOT EXISTS idx_workflows_enabled ON workflows(enabled); -CREATE INDEX IF NOT EXISTS idx_workflow_logs_workflow ON workflow_logs(workflow_id); -CREATE INDEX IF NOT EXISTS idx_workflow_logs_created ON workflow_logs(created_at); -CREATE INDEX IF NOT EXISTS idx_webhook_configs_project ON webhook_configs(project_id);