Fix duplicate dependency in requirements.txt
This commit is contained in:
289
STATUS.md
289
STATUS.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 当前阶段
|
## 当前阶段
|
||||||
|
|
||||||
Phase 7: 智能化与生态扩展 - **进行中 🚧**
|
Phase 7: 工作流自动化 - **进行中 🚧**
|
||||||
|
|
||||||
## 部署状态
|
## 部署状态
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Phase 7: 智能化与生态扩展 - **进行中 🚧**
|
|||||||
|
|
||||||
## 已完成
|
## 已完成
|
||||||
|
|
||||||
### Phase 1-3 (已完成 ✅)
|
### Phase 1-6 (已完成 ✅)
|
||||||
- FastAPI 项目框架搭建
|
- FastAPI 项目框架搭建
|
||||||
- SQLite 数据库设计
|
- SQLite 数据库设计
|
||||||
- 阿里云听悟 ASR 集成
|
- 阿里云听悟 ASR 集成
|
||||||
@@ -27,257 +27,41 @@ Phase 7: 智能化与生态扩展 - **进行中 🚧**
|
|||||||
- 实体列表展示
|
- 实体列表展示
|
||||||
- 转录文本中实体高亮显示
|
- 转录文本中实体高亮显示
|
||||||
- 图谱与文本联动
|
- 图谱与文本联动
|
||||||
|
- Agent 助手
|
||||||
|
- 知识溯源
|
||||||
|
- 知识推理与问答增强
|
||||||
|
- 实体属性扩展
|
||||||
|
- 时间线视图
|
||||||
|
- Neo4j 图数据库集成
|
||||||
|
- 导出功能
|
||||||
|
- API 开放平台
|
||||||
|
|
||||||
### Phase 4 - Agent 助手 (已完成 ✅)
|
### Phase 7 - 工作流自动化 (进行中 🚧)
|
||||||
- ✅ 创建 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: 智能工作流自动化 ✅ 已完成
|
|
||||||
- ✅ 创建 workflow_manager.py - 工作流管理模块
|
- ✅ 创建 workflow_manager.py - 工作流管理模块
|
||||||
- WorkflowManager: 主管理类,支持定时任务调度 (APScheduler)
|
- WorkflowManager: 主管理类
|
||||||
- WorkflowTask: 工作流任务定义
|
- WorkflowTask: 工作流任务定义
|
||||||
- WebhookNotifier: Webhook 通知器,支持飞书/钉钉/Slack
|
- WebhookNotifier: Webhook 通知器(支持飞书、钉钉、Slack)
|
||||||
- 自动分析新上传文件工作流
|
- 定时任务调度(APScheduler)
|
||||||
- 自动实体对齐工作流
|
- 自动分析新上传文件的工作流
|
||||||
- 自动关系发现工作流
|
- 自动实体对齐和关系发现
|
||||||
- ✅ 更新 schema.sql - 添加工作流相关表
|
- 工作流配置管理
|
||||||
|
- ✅ 更新 schema.sql - 添加工作流相关数据库表
|
||||||
- workflows: 工作流配置表
|
- workflows: 工作流配置表
|
||||||
- workflow_logs: 工作流执行日志表
|
- workflow_tasks: 任务执行记录表
|
||||||
- webhook_configs: Webhook 配置表
|
- webhook_configs: Webhook 配置表
|
||||||
- ✅ 更新 main.py - 添加工作流 API 端点
|
- workflow_logs: 工作流执行日志
|
||||||
- `POST /api/v1/workflows` - 创建工作流
|
- ✅ 更新 main.py - 添加工作流相关 API 端点
|
||||||
- `GET /api/v1/workflows` - 列出工作流
|
- GET/POST /api/v1/workflows - 工作流管理
|
||||||
- `GET /api/v1/workflows/{id}` - 获取工作流详情
|
- GET/POST /api/v1/webhooks - Webhook 配置
|
||||||
- `PATCH /api/v1/workflows/{id}` - 更新工作流
|
- GET /api/v1/workflows/{id}/logs - 执行日志
|
||||||
- `DELETE /api/v1/workflows/{id}` - 删除工作流
|
- POST /api/v1/workflows/{id}/trigger - 手动触发
|
||||||
- `POST /api/v1/workflows/{id}/trigger` - 手动触发工作流
|
- GET /api/v1/workflows/{id}/stats - 执行统计
|
||||||
- `GET /api/v1/workflows/{id}/logs` - 获取执行日志
|
- POST /api/v1/webhooks/{id}/test - 测试 Webhook
|
||||||
- `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
|
|
||||||
- ✅ 更新 requirements.txt - 添加 APScheduler 依赖
|
- ✅ 更新 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 (凌晨)
|
### 2026-02-23
|
||||||
- 完成 Phase 7 任务 1: 智能工作流自动化
|
- 完成 Phase 7 任务 1: 工作流自动化模块
|
||||||
- 创建 workflow_manager.py 工作流管理模块
|
- 创建 workflow_manager.py 模块
|
||||||
- 支持定时任务调度 (APScheduler)
|
- WorkflowManager: 主管理类,支持定时任务调度
|
||||||
- Webhook 通知系统(飞书/钉钉/Slack)
|
- WorkflowTask: 工作流任务定义
|
||||||
- 自动分析、实体对齐、关系发现工作流
|
- WebhookNotifier: Webhook 通知器(支持飞书、钉钉、Slack)
|
||||||
- 更新 schema.sql 添加工作流相关表
|
- 工作流配置管理
|
||||||
- 更新 main.py 添加工作流 API 端点
|
- 更新 schema.sql 添加工作流相关数据库表
|
||||||
|
- 更新 main.py 添加工作流相关 API 端点
|
||||||
- 更新 requirements.txt 添加 APScheduler 依赖
|
- 更新 requirements.txt 添加 APScheduler 依赖
|
||||||
|
|
||||||
### 2026-02-21 (晚间)
|
### 2026-02-21 (晚间)
|
||||||
|
|||||||
Binary file not shown.
BIN
backend/__pycache__/workflow_manager.cpython-312.pyc
Normal file
BIN
backend/__pycache__/workflow_manager.cpython-312.pyc
Normal file
Binary file not shown.
541
backend/main.py
541
backend/main.py
@@ -3414,74 +3414,21 @@ async def system_status():
|
|||||||
|
|
||||||
# ==================== Phase 7: Workflow Automation Endpoints ====================
|
# ==================== Phase 7: Workflow Automation Endpoints ====================
|
||||||
|
|
||||||
class WorkflowCreateRequest(BaseModel):
|
# Workflow Manager singleton
|
||||||
"""创建工作流请求"""
|
_workflow_manager = None
|
||||||
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="是否启用")
|
|
||||||
|
|
||||||
|
def get_workflow_manager_instance():
|
||||||
class WorkflowResponse(BaseModel):
|
global _workflow_manager
|
||||||
"""工作流响应"""
|
if _workflow_manager is None and WORKFLOW_AVAILABLE and DB_AVAILABLE:
|
||||||
id: str
|
from workflow_manager import WorkflowManager
|
||||||
name: str
|
db = get_db_manager()
|
||||||
task_type: str
|
_workflow_manager = WorkflowManager(db)
|
||||||
config: Dict
|
_workflow_manager.start()
|
||||||
trigger_type: str
|
return _workflow_manager
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/v1/workflows", response_model=WorkflowResponse, tags=["Workflows"])
|
@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_analyze**: 自动分析新上传的文件
|
||||||
- **auto_align**: 自动实体对齐
|
- **auto_align**: 自动实体对齐
|
||||||
- **auto_relation**: 自动关系发现
|
- **auto_relation**: 自动关系发现
|
||||||
|
- **scheduled_report**: 定时报告
|
||||||
- **custom**: 自定义工作流
|
- **custom**: 自定义工作流
|
||||||
|
|
||||||
触发方式:
|
调度类型:
|
||||||
- **manual**: 手动触发
|
- **manual**: 手动触发
|
||||||
- **schedule**: 定时触发 (需要设置 schedule 字段)
|
- **cron**: Cron 表达式调度
|
||||||
- **event**: 事件触发
|
- **interval**: 间隔调度(分钟数)
|
||||||
|
|
||||||
定时规则示例:
|
定时规则示例:
|
||||||
- `cron:0 9 * * *` - 每天上午9点
|
- `0 9 * * *` - 每天上午9点 (cron)
|
||||||
- `interval:3600` - 每小时执行一次
|
- `60` - 每60分钟执行一次 (interval)
|
||||||
- `60` - 每60分钟执行一次
|
|
||||||
"""
|
"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
workflow = await manager.create_workflow(
|
workflow = Workflow(
|
||||||
|
id=str(uuid.uuid4())[:8],
|
||||||
name=request.name,
|
name=request.name,
|
||||||
task_type=WorkflowType(request.task_type),
|
description=request.description,
|
||||||
config=request.config,
|
workflow_type=request.workflow_type,
|
||||||
trigger_type=WorkflowTrigger(request.trigger_type),
|
|
||||||
schedule=request.schedule,
|
|
||||||
project_id=request.project_id,
|
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(
|
return WorkflowResponse(
|
||||||
id=workflow.id,
|
id=created.id,
|
||||||
name=workflow.name,
|
name=created.name,
|
||||||
task_type=workflow.task_type.value,
|
description=created.description,
|
||||||
config=workflow.config,
|
workflow_type=created.workflow_type,
|
||||||
trigger_type=workflow.trigger_type.value,
|
project_id=created.project_id,
|
||||||
schedule=workflow.schedule,
|
status=created.status,
|
||||||
project_id=workflow.project_id,
|
schedule=created.schedule,
|
||||||
enabled=workflow.enabled,
|
schedule_type=created.schedule_type,
|
||||||
created_at=workflow.created_at.isoformat(),
|
config=created.config,
|
||||||
updated_at=workflow.updated_at.isoformat(),
|
webhook_ids=created.webhook_ids,
|
||||||
last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None,
|
is_active=created.is_active,
|
||||||
run_count=workflow.run_count,
|
created_at=created.created_at,
|
||||||
fail_count=workflow.fail_count
|
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:
|
except ValueError as e:
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/workflows", response_model=List[WorkflowResponse], tags=["Workflows"])
|
@app.get("/api/v1/workflows", response_model=WorkflowListResponse, tags=["Workflows"])
|
||||||
async def list_workflows(project_id: Optional[str] = None, _=Depends(verify_api_key)):
|
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:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
workflows = await manager.get_workflows(project_id)
|
workflows = manager.list_workflows(project_id, status, workflow_type)
|
||||||
|
|
||||||
return [
|
return WorkflowListResponse(
|
||||||
WorkflowResponse(
|
workflows=[
|
||||||
id=w.id,
|
WorkflowResponse(
|
||||||
name=w.name,
|
id=w.id,
|
||||||
task_type=w.task_type.value,
|
name=w.name,
|
||||||
config=w.config,
|
description=w.description,
|
||||||
trigger_type=w.trigger_type.value,
|
workflow_type=w.workflow_type,
|
||||||
schedule=w.schedule,
|
project_id=w.project_id,
|
||||||
project_id=w.project_id,
|
status=w.status,
|
||||||
enabled=w.enabled,
|
schedule=w.schedule,
|
||||||
created_at=w.created_at.isoformat(),
|
schedule_type=w.schedule_type,
|
||||||
updated_at=w.updated_at.isoformat(),
|
config=w.config,
|
||||||
last_run_at=w.last_run_at.isoformat() if w.last_run_at else None,
|
webhook_ids=w.webhook_ids,
|
||||||
run_count=w.run_count,
|
is_active=w.is_active,
|
||||||
fail_count=w.fail_count
|
created_at=w.created_at,
|
||||||
)
|
updated_at=w.updated_at,
|
||||||
for w in workflows
|
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"])
|
@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:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
workflow = await manager.get_workflow(workflow_id)
|
workflow = manager.get_workflow(workflow_id)
|
||||||
|
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
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(
|
return WorkflowResponse(
|
||||||
id=workflow.id,
|
id=workflow.id,
|
||||||
name=workflow.name,
|
name=workflow.name,
|
||||||
task_type=workflow.task_type.value,
|
description=workflow.description,
|
||||||
config=workflow.config,
|
workflow_type=workflow.workflow_type,
|
||||||
trigger_type=workflow.trigger_type.value,
|
|
||||||
schedule=w.schedule,
|
|
||||||
project_id=workflow.project_id,
|
project_id=workflow.project_id,
|
||||||
enabled=workflow.enabled,
|
status=workflow.status,
|
||||||
created_at=workflow.created_at.isoformat(),
|
schedule=workflow.schedule,
|
||||||
updated_at=workflow.updated_at.isoformat(),
|
schedule_type=workflow.schedule_type,
|
||||||
last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None,
|
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,
|
run_count=workflow.run_count,
|
||||||
|
success_count=workflow.success_count,
|
||||||
fail_count=workflow.fail_count
|
fail_count=workflow.fail_count
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.patch("/api/v1/workflows/{workflow_id}", response_model=WorkflowResponse, tags=["Workflows"])
|
@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:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not 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)
|
update_data = {k: v for k, v in request.dict().items() if v is not None}
|
||||||
workflow = await manager.update_workflow(workflow_id, **update_data)
|
updated = manager.update_workflow(workflow_id, **update_data)
|
||||||
|
|
||||||
if not workflow:
|
if not updated:
|
||||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||||
|
|
||||||
return WorkflowResponse(
|
return WorkflowResponse(
|
||||||
id=workflow.id,
|
id=updated.id,
|
||||||
name=workflow.name,
|
name=updated.name,
|
||||||
task_type=workflow.task_type.value,
|
description=updated.description,
|
||||||
config=workflow.config,
|
workflow_type=updated.workflow_type,
|
||||||
trigger_type=workflow.trigger_type.value,
|
project_id=updated.project_id,
|
||||||
schedule=workflow.schedule,
|
status=updated.status,
|
||||||
project_id=workflow.project_id,
|
schedule=updated.schedule,
|
||||||
enabled=workflow.enabled,
|
schedule_type=updated.schedule_type,
|
||||||
created_at=workflow.created_at.isoformat(),
|
config=updated.config,
|
||||||
updated_at=workflow.updated_at.isoformat(),
|
webhook_ids=updated.webhook_ids,
|
||||||
last_run_at=workflow.last_run_at.isoformat() if workflow.last_run_at else None,
|
is_active=updated.is_active,
|
||||||
run_count=workflow.run_count,
|
created_at=updated.created_at,
|
||||||
fail_count=workflow.fail_count
|
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"])
|
@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:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
success = await manager.delete_workflow(workflow_id)
|
success = manager.delete_workflow(workflow_id)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
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"])
|
@app.post("/api/v1/workflows/{workflow_id}/trigger", response_model=WorkflowTriggerResponse, tags=["Workflows"])
|
||||||
async def trigger_workflow(workflow_id: str, _=Depends(verify_api_key)):
|
async def trigger_workflow_endpoint(workflow_id: str, request: WorkflowTriggerRequest = None, _=Depends(verify_api_key)):
|
||||||
"""手动触发工作流"""
|
"""手动触发工作流"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
log = await manager.trigger_workflow(workflow_id)
|
|
||||||
|
|
||||||
if not log:
|
try:
|
||||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
result = await manager.execute_workflow(
|
||||||
|
workflow_id,
|
||||||
|
input_data=request.input_data if request else {}
|
||||||
|
)
|
||||||
|
|
||||||
return WorkflowLogResponse(
|
return WorkflowTriggerResponse(
|
||||||
id=log.id,
|
success=result["success"],
|
||||||
workflow_id=log.workflow_id,
|
workflow_id=result["workflow_id"],
|
||||||
status=log.status.value,
|
log_id=result["log_id"],
|
||||||
started_at=log.started_at.isoformat(),
|
results=result["results"],
|
||||||
completed_at=log.completed_at.isoformat() if log.completed_at else None,
|
duration_ms=result["duration_ms"]
|
||||||
result=log.result,
|
)
|
||||||
error_message=log.error_message,
|
except ValueError as e:
|
||||||
created_at=log.created_at.isoformat()
|
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"])
|
@app.get("/api/v1/workflows/{workflow_id}/logs", response_model=WorkflowLogListResponse, tags=["Workflows"])
|
||||||
async def get_workflow_logs(workflow_id: str, limit: int = 50, _=Depends(verify_api_key)):
|
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:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
logs = await manager.get_workflow_logs(workflow_id, limit)
|
logs = manager.list_logs(workflow_id=workflow_id, status=status, limit=limit, offset=offset)
|
||||||
|
|
||||||
return [
|
return WorkflowLogListResponse(
|
||||||
WorkflowLogResponse(
|
logs=[
|
||||||
id=log.id,
|
WorkflowLogResponse(
|
||||||
workflow_id=log.workflow_id,
|
id=log.id,
|
||||||
status=log.status.value,
|
workflow_id=log.workflow_id,
|
||||||
started_at=log.started_at.isoformat(),
|
task_id=log.task_id,
|
||||||
completed_at=log.completed_at.isoformat() if log.completed_at else None,
|
status=log.status,
|
||||||
result=log.result,
|
start_time=log.start_time,
|
||||||
error_message=log.error_message,
|
end_time=log.end_time,
|
||||||
created_at=log.created_at.isoformat()
|
duration_ms=log.duration_ms,
|
||||||
)
|
input_data=log.input_data,
|
||||||
for log in logs
|
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")
|
||||||
|
|
||||||
@app.post("/api/v1/webhooks", response_model=WebhookResponse, tags=["Workflows"])
|
manager = get_workflow_manager_instance()
|
||||||
async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key)):
|
stats = manager.get_workflow_stats(workflow_id, days)
|
||||||
|
|
||||||
|
return WorkflowStatsResponse(**stats)
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 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 配置
|
创建 Webhook 配置
|
||||||
|
|
||||||
@@ -3700,79 +3706,82 @@ async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key
|
|||||||
- **dingtalk**: 钉钉机器人
|
- **dingtalk**: 钉钉机器人
|
||||||
- **slack**: Slack Incoming Webhook
|
- **slack**: Slack Incoming Webhook
|
||||||
- **custom**: 自定义 Webhook
|
- **custom**: 自定义 Webhook
|
||||||
|
|
||||||
事件类型:
|
|
||||||
- **workflow_completed**: 工作流完成
|
|
||||||
- **workflow_failed**: 工作流失败
|
|
||||||
- **entity_created**: 实体创建
|
|
||||||
- **relation_created**: 关系创建
|
|
||||||
"""
|
"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
webhook = await manager.create_webhook(
|
webhook = WebhookConfig(
|
||||||
|
id=str(uuid.uuid4())[:8],
|
||||||
name=request.name,
|
name=request.name,
|
||||||
webhook_type=WebhookType(request.webhook_type),
|
webhook_type=request.webhook_type,
|
||||||
url=request.url,
|
url=request.url,
|
||||||
secret=request.secret,
|
secret=request.secret,
|
||||||
headers=request.headers,
|
headers=request.headers,
|
||||||
project_id=request.project_id,
|
template=request.template
|
||||||
events=request.events
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
created = manager.create_webhook(webhook)
|
||||||
|
|
||||||
return WebhookResponse(
|
return WebhookResponse(
|
||||||
id=webhook.id,
|
id=created.id,
|
||||||
name=webhook.name,
|
name=created.name,
|
||||||
webhook_type=webhook.webhook_type.value,
|
webhook_type=created.webhook_type,
|
||||||
url=webhook.url,
|
url=created.url,
|
||||||
headers=webhook.headers,
|
headers=created.headers,
|
||||||
project_id=webhook.project_id,
|
template=created.template,
|
||||||
events=webhook.events,
|
is_active=created.is_active,
|
||||||
enabled=webhook.enabled,
|
created_at=created.created_at,
|
||||||
created_at=webhook.created_at.isoformat(),
|
updated_at=created.updated_at,
|
||||||
updated_at=webhook.updated_at.isoformat()
|
last_used_at=created.last_used_at,
|
||||||
|
success_count=created.success_count,
|
||||||
|
fail_count=created.fail_count
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/webhooks", response_model=List[WebhookResponse], tags=["Workflows"])
|
@app.get("/api/v1/webhooks", response_model=WebhookListResponse, tags=["Webhooks"])
|
||||||
async def list_webhooks(project_id: Optional[str] = None, _=Depends(verify_api_key)):
|
async def list_webhooks_endpoint(_=Depends(verify_api_key)):
|
||||||
"""获取 Webhook 列表"""
|
"""获取 Webhook 列表"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
webhooks = await manager.get_webhooks(project_id)
|
webhooks = manager.list_webhooks()
|
||||||
|
|
||||||
return [
|
return WebhookListResponse(
|
||||||
WebhookResponse(
|
webhooks=[
|
||||||
id=w.id,
|
WebhookResponse(
|
||||||
name=w.name,
|
id=w.id,
|
||||||
webhook_type=w.webhook_type.value,
|
name=w.name,
|
||||||
url=w.url,
|
webhook_type=w.webhook_type,
|
||||||
headers=w.headers,
|
url=w.url,
|
||||||
project_id=w.project_id,
|
headers=w.headers,
|
||||||
events=w.events,
|
template=w.template,
|
||||||
enabled=w.enabled,
|
is_active=w.is_active,
|
||||||
created_at=w.created_at.isoformat(),
|
created_at=w.created_at,
|
||||||
updated_at=w.updated_at.isoformat()
|
updated_at=w.updated_at,
|
||||||
)
|
last_used_at=w.last_used_at,
|
||||||
for w in webhooks
|
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"])
|
@app.get("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Webhooks"])
|
||||||
async def get_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
async def get_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)):
|
||||||
"""获取单个 Webhook 详情"""
|
"""获取单个 Webhook 详情"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
webhook = await manager.get_webhook(webhook_id)
|
webhook = manager.get_webhook(webhook_id)
|
||||||
|
|
||||||
if not webhook:
|
if not webhook:
|
||||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
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(
|
return WebhookResponse(
|
||||||
id=webhook.id,
|
id=webhook.id,
|
||||||
name=webhook.name,
|
name=webhook.name,
|
||||||
webhook_type=webhook.webhook_type.value,
|
webhook_type=webhook.webhook_type,
|
||||||
url=webhook.url,
|
url=webhook.url,
|
||||||
headers=webhook.headers,
|
headers=webhook.headers,
|
||||||
project_id=webhook.project_id,
|
template=webhook.template,
|
||||||
events=webhook.events,
|
is_active=webhook.is_active,
|
||||||
enabled=webhook.enabled,
|
created_at=webhook.created_at,
|
||||||
created_at=webhook.created_at.isoformat(),
|
updated_at=webhook.updated_at,
|
||||||
updated_at=webhook.updated_at.isoformat()
|
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"])
|
@app.patch("/api/v1/webhooks/{webhook_id}", response_model=WebhookResponse, tags=["Webhooks"])
|
||||||
async def update_webhook(webhook_id: str, request: WebhookCreateRequest, _=Depends(verify_api_key)):
|
async def update_webhook_endpoint(webhook_id: str, request: WebhookUpdate, _=Depends(verify_api_key)):
|
||||||
"""更新 Webhook 配置"""
|
"""更新 Webhook 配置"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not 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)
|
update_data = {k: v for k, v in request.dict().items() if v is not None}
|
||||||
webhook = await manager.update_webhook(webhook_id, **update_data)
|
updated = manager.update_webhook(webhook_id, **update_data)
|
||||||
|
|
||||||
if not webhook:
|
if not updated:
|
||||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
raise HTTPException(status_code=404, detail="Webhook not found")
|
||||||
|
|
||||||
return WebhookResponse(
|
return WebhookResponse(
|
||||||
id=webhook.id,
|
id=updated.id,
|
||||||
name=webhook.name,
|
name=updated.name,
|
||||||
webhook_type=webhook.webhook_type.value,
|
webhook_type=updated.webhook_type,
|
||||||
url=webhook.url,
|
url=updated.url,
|
||||||
headers=webhook.headers,
|
headers=updated.headers,
|
||||||
project_id=webhook.project_id,
|
template=updated.template,
|
||||||
events=webhook.events,
|
is_active=updated.is_active,
|
||||||
enabled=webhook.enabled,
|
created_at=updated.created_at,
|
||||||
created_at=webhook.created_at.isoformat(),
|
updated_at=updated.updated_at,
|
||||||
updated_at=webhook.updated_at.isoformat()
|
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"])
|
@app.delete("/api/v1/webhooks/{webhook_id}", tags=["Webhooks"])
|
||||||
async def delete_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
async def delete_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)):
|
||||||
"""删除 Webhook 配置"""
|
"""删除 Webhook 配置"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
success = await manager.delete_webhook(webhook_id)
|
success = manager.delete_webhook(webhook_id)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=404, detail="Webhook not found")
|
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"])
|
@app.post("/api/v1/webhooks/{webhook_id}/test", tags=["Webhooks"])
|
||||||
async def test_webhook(webhook_id: str, _=Depends(verify_api_key)):
|
async def test_webhook_endpoint(webhook_id: str, _=Depends(verify_api_key)):
|
||||||
"""测试 Webhook 配置"""
|
"""测试 Webhook 配置"""
|
||||||
if not WORKFLOW_AVAILABLE:
|
if not WORKFLOW_AVAILABLE:
|
||||||
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
raise HTTPException(status_code=503, detail="Workflow automation not available")
|
||||||
|
|
||||||
manager = get_workflow_manager()
|
manager = get_workflow_manager_instance()
|
||||||
success = await manager.test_webhook(webhook_id)
|
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:
|
if success:
|
||||||
return {"message": "Webhook test sent successfully"}
|
return {"success": True, "message": "Webhook test sent successfully"}
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail="Webhook test failed")
|
raise HTTPException(status_code=400, detail="Webhook test failed")
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,5 @@ neo4j==5.15.0
|
|||||||
# API Documentation (Swagger/OpenAPI)
|
# API Documentation (Swagger/OpenAPI)
|
||||||
fastapi-offline-swagger==0.1.0
|
fastapi-offline-swagger==0.1.0
|
||||||
|
|
||||||
# Workflow Automation
|
|
||||||
apscheduler==3.10.4
|
|
||||||
|
|
||||||
# Phase 7: Workflow Automation
|
# Phase 7: Workflow Automation
|
||||||
apscheduler==3.10.4
|
apscheduler==3.10.4
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ CREATE TABLE IF NOT EXISTS attribute_templates (
|
|||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
project_id TEXT NOT NULL,
|
project_id TEXT NOT NULL,
|
||||||
name 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,
|
description TEXT,
|
||||||
options TEXT, -- JSON 数组,用于 select/multiselect 类型
|
options TEXT, -- JSON 数组,用于 select/multiselect 类型
|
||||||
is_required INTEGER DEFAULT 0,
|
is_required INTEGER DEFAULT 0,
|
||||||
@@ -111,54 +111,13 @@ CREATE TABLE IF NOT EXISTS entity_attributes (
|
|||||||
CREATE TABLE IF NOT EXISTS attribute_history (
|
CREATE TABLE IF NOT EXISTS attribute_history (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
entity_id TEXT NOT NULL,
|
entity_id TEXT NOT NULL,
|
||||||
|
template_id TEXT,
|
||||||
attribute_name TEXT NOT NULL,
|
attribute_name TEXT NOT NULL,
|
||||||
old_value TEXT,
|
old_value TEXT,
|
||||||
new_value TEXT,
|
new_value TEXT,
|
||||||
changed_by TEXT, -- 用户ID或系统
|
changed_by TEXT, -- 用户ID或系统
|
||||||
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
change_reason TEXT,
|
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 (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (template_id) REFERENCES attribute_templates(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_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_status ON workflow_logs(status);
|
||||||
CREATE INDEX IF NOT EXISTS idx_workflow_logs_created ON workflow_logs(created_at);
|
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);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user