68 lines
2.2 KiB
Python
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
|