182 lines
5.2 KiB
Python
182 lines
5.2 KiB
Python
import asyncio
|
|
from typing import Any, Dict
|
|
|
|
|
|
try:
|
|
from comfy.model_management import processing_interrupted, InterruptProcessingException
|
|
INTERRUPT_AVAILABLE = True
|
|
except Exception:
|
|
INTERRUPT_AVAILABLE = False
|
|
processing_interrupted = lambda: False
|
|
|
|
class InterruptProcessingException(Exception):
|
|
pass
|
|
|
|
|
|
SUCCESS_STATUSES = {"succeed", "succeeded", "success", "completed", "done", "finished"}
|
|
FAILURE_STATUSES = {
|
|
"fail",
|
|
"failed",
|
|
"failure",
|
|
"error",
|
|
"expired",
|
|
"timeout",
|
|
"timed_out",
|
|
"cancel",
|
|
"canceled",
|
|
"cancelled",
|
|
"rejected",
|
|
}
|
|
|
|
|
|
def check_interrupt() -> None:
|
|
if INTERRUPT_AVAILABLE and processing_interrupted():
|
|
raise InterruptProcessingException()
|
|
|
|
|
|
async def interruptible_sleep(seconds: float, step: float = 0.2) -> None:
|
|
elapsed = 0.0
|
|
while elapsed < seconds:
|
|
check_interrupt()
|
|
delay = min(step, seconds - elapsed)
|
|
await asyncio.sleep(delay)
|
|
elapsed += delay
|
|
check_interrupt()
|
|
|
|
|
|
async def run_with_interrupt(coro, step: float = 0.2):
|
|
task = asyncio.ensure_future(coro)
|
|
try:
|
|
while not task.done():
|
|
check_interrupt()
|
|
await asyncio.wait({task}, timeout=step)
|
|
check_interrupt()
|
|
return await task
|
|
except InterruptProcessingException:
|
|
task.cancel()
|
|
try:
|
|
await task
|
|
except BaseException:
|
|
pass
|
|
raise
|
|
|
|
|
|
def _as_dict(value: Any) -> Dict[str, Any]:
|
|
return value if isinstance(value, dict) else {}
|
|
|
|
|
|
def _nested_payloads(payload: Dict[str, Any]):
|
|
root = _as_dict(payload)
|
|
data = _as_dict(root.get("data"))
|
|
inner = _as_dict(data.get("data"))
|
|
return root, data, inner
|
|
|
|
|
|
def extract_status(payload: Dict[str, Any]) -> str:
|
|
root, data, inner = _nested_payloads(payload)
|
|
keys = ("status", "task_status", "state", "task_state")
|
|
statuses = []
|
|
for source in (data, inner, root):
|
|
for key in keys:
|
|
value = source.get(key)
|
|
if value is not None and str(value).strip():
|
|
statuses.append(str(value).strip().lower())
|
|
for status in statuses:
|
|
if status in FAILURE_STATUSES or any(
|
|
token in status for token in ("fail", "error", "reject", "timeout", "cancel")
|
|
):
|
|
return status
|
|
for status in statuses:
|
|
if status in SUCCESS_STATUSES:
|
|
return status
|
|
return statuses[0] if statuses else ""
|
|
|
|
|
|
def extract_progress(payload: Dict[str, Any]) -> int:
|
|
root, data, inner = _nested_payloads(payload)
|
|
for source in (data, inner, root):
|
|
value = source.get("progress")
|
|
if value is None:
|
|
continue
|
|
try:
|
|
return max(0, min(100, int(float(str(value).strip().rstrip("%")))))
|
|
except (TypeError, ValueError):
|
|
return 0
|
|
return 0
|
|
|
|
|
|
def extract_error_message(payload: Dict[str, Any], default: str = "未知错误") -> str:
|
|
root, data, inner = _nested_payloads(payload)
|
|
keys = (
|
|
"fail_reason",
|
|
"failure_reason",
|
|
"task_status_msg",
|
|
"status_msg",
|
|
"error_message",
|
|
"message",
|
|
"msg",
|
|
"reason",
|
|
"detail",
|
|
"details",
|
|
)
|
|
for source in (data, inner, root):
|
|
error = source.get("error")
|
|
if isinstance(error, dict):
|
|
for key in ("message", "msg", "detail", "reason", "code"):
|
|
value = error.get(key)
|
|
if value:
|
|
return str(value)
|
|
elif error:
|
|
return str(error)
|
|
|
|
for key in keys:
|
|
value = source.get(key)
|
|
if value:
|
|
return str(value)
|
|
return default
|
|
|
|
|
|
def extract_video_url(payload: Dict[str, Any]) -> str | None:
|
|
root, data, inner = _nested_payloads(payload)
|
|
for source in (data, inner, root):
|
|
for key in ("video_url", "result_url", "url", "download_url"):
|
|
value = source.get(key)
|
|
if value:
|
|
return str(value)
|
|
|
|
result = _as_dict(source.get("result"))
|
|
for key in ("video_url", "result_url", "url", "download_url"):
|
|
value = result.get(key)
|
|
if value:
|
|
return str(value)
|
|
|
|
content = _as_dict(source.get("content"))
|
|
value = content.get("video_url") or content.get("url")
|
|
if value:
|
|
return str(value)
|
|
|
|
task_result = _as_dict(source.get("task_result"))
|
|
videos = task_result.get("videos")
|
|
if isinstance(videos, list) and videos:
|
|
first = _as_dict(videos[0])
|
|
value = first.get("url") or first.get("video_url")
|
|
if value:
|
|
return str(value)
|
|
return None
|
|
|
|
|
|
def is_success_status(status: str) -> bool:
|
|
return status in SUCCESS_STATUSES
|
|
|
|
|
|
def is_failure_status(status: str, payload: Dict[str, Any] | None = None) -> bool:
|
|
if status in FAILURE_STATUSES:
|
|
return True
|
|
if any(token in status for token in ("fail", "error", "reject", "timeout", "cancel")):
|
|
return True
|
|
if payload is None:
|
|
return False
|
|
root, data, inner = _nested_payloads(payload)
|
|
failure_keys = ("error", "fail_reason", "failure_reason", "task_status_msg", "error_message")
|
|
return any(any(source.get(key) for key in failure_keys) for source in (data, inner, root))
|