# services/comfy-agent/app/comfyui_client.py import asyncio import httpx import json import websockets from typing import Any, Dict, Optional, Callable from .config import settings ProgressCb = Callable[[float, str], None] class ComfyUIClient: def __init__(self) -> None: self.http = httpx.AsyncClient(base_url=settings.COMFYUI_HTTP, timeout=60) async def queue_prompt(self, prompt_graph: Dict[str, Any], client_id: str) -> str: # ComfyUI expects: {"prompt": {...}, "client_id": "..."} r = await self.http.post("/prompt", json={"prompt": prompt_graph, "client_id": client_id}) r.raise_for_status() data = r.json() # typically returns {"prompt_id": "...", "number": ...} return data["prompt_id"] async def wait_progress(self, client_id: str, prompt_id: str, on_progress: Optional[ProgressCb] = None) -> None: # WS emits progress/executing/status; keep generic handling ws_url = f"{settings.COMFYUI_WS}?clientId={client_id}" async with websockets.connect(ws_url, max_size=50_000_000) as ws: while True: msg = await ws.recv() evt = json.loads(msg) # Best-effort progress mapping if evt.get("type") == "progress": data = evt.get("data", {}) max_v = float(data.get("max", 1.0)) val = float(data.get("value", 0.0)) p = 0.0 if max_v <= 0 else min(1.0, val / max_v) if on_progress: on_progress(p, "progress") # completion signal varies; "executing" with node=None часто означає done if evt.get("type") == "executing": data = evt.get("data", {}) if data.get("prompt_id") == prompt_id and data.get("node") is None: if on_progress: on_progress(1.0, "done") return async def get_history(self, prompt_id: str) -> Dict[str, Any]: r = await self.http.get(f"/history/{prompt_id}") r.raise_for_status() return r.json() async def close(self) -> None: await self.http.aclose()