Fix duplicate dependency in requirements.txt

This commit is contained in:
OpenClaw Bot
2026-02-23 00:15:32 +08:00
parent bb5c2361e8
commit 08535e54ba
6 changed files with 324 additions and 613 deletions

289
STATUS.md
View File

@@ -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 (晚间)

Binary file not shown.

View File

@@ -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")
try:
result = await manager.execute_workflow(
workflow_id,
input_data=request.input_data if request else {}
)
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()
)
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")
@app.post("/api/v1/webhooks", response_model=WebhookResponse, tags=["Workflows"])
async def create_webhook(request: WebhookCreateRequest, _=Depends(verify_api_key)):
manager = get_workflow_manager_instance()
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 配置
@@ -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")

View File

@@ -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

View File

@@ -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);