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

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")
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")