PMFlow/backend/app/api/webhooks.py

172 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Webhook 接收 API"""
import json
import re
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from ..database import get_db
from ..models import Signal
from ..services.llm import llm_service
from ..services.plane import plane_service
from ..services.lark import LarkService
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
lark_service = LarkService()
@router.post("/git")
async def handle_git_webhook(
payload: dict,
db: AsyncSession = Depends(get_db),
):
"""接收 Git Webhook,审计代码并更新状态"""
commits = payload.get("commits", [])
if not commits:
return {"status": "ignored", "reason": "No commits found"}
updated_tasks = []
for commit in commits:
message = commit.get("message", "")
code_diff = commit.get("patch") or str(commit.get("modified", []))
task_matches = re.findall(
r"#([A-Za-z0-9-]+-\d+|[A-Za-z0-9]+-\d+)", message
)
for task_id in task_matches:
result = await db.execute(
select(Signal).order_by(Signal.created_at.desc()).limit(20)
)
signals = result.scalars().all()
target_task = None
target_signal = None
for sig in signals:
if not sig.tasks_generated:
continue
try:
tasks = json.loads(sig.tasks_generated)
for t in tasks:
if t.get("plane_key") == task_id or t.get(
"plane_id"
) == task_id:
target_task = t
target_signal = sig
break
except Exception:
continue
if target_task:
break
task_name = (
target_task.get("task_name") if target_task else f"Task {task_id}"
)
# 尽量从目标信号推断租户,避免跨租户预算串账。
tenant_id = int(getattr(target_signal, "tenant_id", 1) or 1)
try:
audit_result = await llm_service.audit_code_change_async(
task_name,
message,
code_diff,
db=db,
tenant_id=tenant_id,
)
except RuntimeError:
updated_tasks.append(
{
"task_id": task_id,
"status": "Blocked",
"audit": {"passed": False, "reason": "预算超限,AI 审计被阻止"},
}
)
continue
if audit_result.get("passed"):
await plane_service.update_issue_status(task_id, status="Done")
if target_signal and target_task:
tasks = json.loads(target_signal.tasks_generated)
for t in tasks:
if t.get("plane_key") == task_id or t.get(
"plane_id"
) == task_id:
t["status"] = "Done"
t["audit_feedback"] = audit_result.get("reason")
t["completed_at"] = (
datetime.now(timezone.utc).isoformat()
)
target_signal.tasks_generated = json.dumps(tasks)
await db.commit()
updated_tasks.append(
{"task_id": task_id, "status": "Done", "audit": audit_result}
)
else:
updated_tasks.append(
{
"task_id": task_id,
"status": "Failed",
"audit": audit_result,
}
)
return {"status": "processed", "updated_tasks": updated_tasks}
@router.post("/lark")
async def handle_lark_webhook(
request: Request,
db: AsyncSession = Depends(get_db),
):
"""接收飞书 Bot Webhook"""
try:
data = await request.json()
except Exception:
return {"status": "error", "message": "Invalid JSON"}
if "challenge" in data:
return {"challenge": data["challenge"]}
event = data.get("event", {})
if not event:
return {"status": "ignored"}
if event.get("type") == "message" or event.get("msg_type") == "text":
message_content = event.get("text_without_at_bot") or event.get(
"content", ""
)
if isinstance(message_content, str) and message_content.startswith("{"):
try:
content_obj = json.loads(message_content)
message_content = content_obj.get("text", "")
except Exception:
pass
message_content = message_content.strip()
chat_id = event.get("open_chat_id") or event.get("chat_id")
if not message_content or not chat_id:
return {"status": "ignored", "reason": "No content or chat_id"}
try:
try:
tasks = await llm_service.generate_task_breakdown_async(
message_content,
db=db,
tenant_id=1, # Webhook 来源尚未绑定租户,先按默认租户计费
)
except RuntimeError:
return {"status": "blocked", "reason": "budget_exceeded_hard_limit"}
if tasks:
await plane_service.sync_tasks(tasks)
await lark_service.send_card_message(
chat_id, message_content, tasks
)
except Exception as e:
print(f"Error processing Lark message: {e}")
return {"status": "processed"}