Files
ai-ticket-platform/backend/apps/tickets/automation_api.py
2026-09-18 15:26:19 +08:00

68 lines
2.2 KiB
Python

import json
import time
from django.conf import settings
from django.http import StreamingHttpResponse
from ninja import Router
from ninja.errors import HttpError
from apps.accounts.api import require_permission
from apps.tickets.models import TicketAutomationRun
router = Router(tags=["ticket-automation-runs"])
def _cursor(request, after: int | None) -> int:
raw = request.headers.get("Last-Event-ID") or after or 0
try:
return max(int(raw), 0)
except (TypeError, ValueError) as error:
raise HttpError(400, "事件游标无效") from error
def _format_event(event) -> str:
payload = {
"sequence": event.sequence,
"event_type": event.event_type,
"payload": event.payload,
"created_at": event.created_at.isoformat(),
}
return (
f"id: {event.sequence}\n"
f"event: {event.event_type}\n"
f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
)
@router.get("/{public_id}/events")
def stream_ticket_automation_events(request, public_id: str, after: int | None = None):
require_permission(request, "ticket.view")
run = TicketAutomationRun.objects.filter(public_id=public_id).first()
if run is None:
raise HttpError(404, "工单自动化运行不存在")
cursor = _cursor(request, after)
terminal = {"completed", "failed", "cancelled"}
max_seconds = getattr(settings, "AGENT_RUN_SSE_MAX_SECONDS", 60)
def event_stream():
current = cursor
started = time.monotonic()
heartbeat = started
while time.monotonic() - started < max_seconds:
events = list(run.events.filter(sequence__gt=current)[:100])
for event in events:
current = event.sequence
yield _format_event(event)
if events and events[-1].event_type in terminal:
return
now = time.monotonic()
if now - heartbeat >= 15:
yield ": keep-alive\n\n"
heartbeat = now
time.sleep(0.5)
response = StreamingHttpResponse(event_stream(), content_type="text/event-stream")
response["Cache-Control"] = "no-cache"
response["X-Accel-Buffering"] = "no"
return response