Files
DemonGatanjieu-Anomalous_Mo…/api/ai_run_control.py
T
DemonGatanjieuandClaude Opus 5.5 5c0e9ea133 Add the job sheet: many runs of one canvas, from the user or the AI
A card on the canvas (right-click > Job sheet) takes many prompts, a line each,
the runs of each, a rest between pictures and the same seeds if wanted; with no
prompt it runs the canvas as it is. The page runs it from copies of the workflow
kept at the start, as saves run, so the canvas stays as it is; pause, go on and
stop from any page, a failed or interrupted run pauses it, a reload goes on and
another page takes over a sheet whose page is gone. run_workflow(sheet=...) lets
an AI hand over prompts or settings by name; the sheet is that call's run step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 04:36:40 +08:00

100 lines
5.1 KiB
Python

"""Stopping a run an AI started (the workbench's stop button, the AI's stop_run): its prompts still
waiting leave ComfyUI's queue and the one running is interrupted, only while it is still that one
(ComfyUI's own cancel; never anyone else's prompt). The run's watcher (ai_task_runs.py) then ends
the step as "cancelled" with who stopped it; when the user did, the AI is told with its next answer.
And the user's word on a finished run (set_verdict, the buttons under its pictures): it turned out
well, or not, kept on the run's step and told to the AI as their message."""
import time
import uuid
from . import ai_task_runs, ai_tasks
VERDICTS = ("good", "bad")
class RunControlError(ValueError):
pass
def _interrupt(queue, prompt_id):
"""Interrupts `prompt_id` if it is the prompt running now; True when it was."""
if hasattr(queue, "interrupt_if_running"):
return bool(queue.interrupt_if_running(prompt_id))
running, _ = queue.get_current_queue() # an older ComfyUI: check, then the global interrupt
if not any(item[1] == prompt_id for item in running):
return False
import nodes
nodes.interrupt_processing()
return True
def stop(step_id, by="user", task_id=None, now=None):
"""Stops run step `step_id` (in task `task_id` when given). Returns {"task", "removed", "interrupted"}."""
now = time.time() if now is None else now
with ai_tasks._lock:
tasks = ai_tasks._load()
task = next((item for item in tasks.values() if (not task_id or item["id"] == task_id)
and any(step["id"] == step_id for step in item.get("steps") or [])), None)
step = next((item for item in (task or {}).get("steps", []) if item["id"] == step_id), None)
if not step or step.get("tool") != "run_workflow":
raise RunControlError("No such run in this task")
sheet = step.get("sheet") # a job sheet: its own stop takes its runs out (job_sheets.py)
if step.get("state") != "running" or not (step.get("prompt_ids") or sheet):
raise RunControlError("This run is not going any more")
prompts = list(step.get("prompt_ids") or [])
step["stop"] = by
if by == "user":
task["steps"].append({
"id": uuid.uuid4().hex[:10], "at": now, "ms": 0, "tool": "user_stop", "kind": "user", "ok": True,
"state": "done", "summary": "user_stop", "stopped": step_id, "delivered": False, "results": [],
"text": "The user stopped your run in Anomalous. Do not start it again unless they ask; ask_user what to change.",
})
del task["steps"][:-ai_tasks.MAX_STEPS]
task["updated"] = max(task.get("updated", 0), now)
ai_tasks._save(task)
if sheet:
from . import job_sheets
try:
job_sheets.ask(sheet, "stop")
except job_sheets.SheetError:
pass # gone or ended already: nothing left to stop
ai_tasks._notify(task["id"])
return {"task": task["id"], "removed": 0, "interrupted": 0}
queue = ai_task_runs._queue()
removed = sum(1 for prompt_id in prompts if queue.delete_queue_item(lambda item, pid=prompt_id: item[1] == pid))
interrupted = sum(1 for prompt_id in prompts if _interrupt(queue, prompt_id))
ai_tasks._notify(task["id"])
return {"task": task["id"], "removed": removed, "interrupted": interrupted}
def set_verdict(task_id, step_id, verdict):
"""The user's word on finished run `step_id` of task `task_id`: "good", "bad" or None (taken back);
a word given goes to the AI. Returns it."""
if verdict not in (*VERDICTS, None):
raise RunControlError("verdict is good, bad or null")
with ai_tasks._lock:
task = ai_tasks._load().get(task_id)
steps = (task or {}).get("steps", [])
step = next((item for item in steps if item["id"] == step_id), None)
if not step or step.get("tool") != "run_workflow" or step.get("state") != "done":
raise RunControlError("No such finished run")
if step.get("verdict") == verdict:
return verdict
if verdict:
step["verdict"] = verdict
number = sum(1 for item in steps[:steps.index(step) + 1] if item.get("tool") == "run_workflow")
ai_tasks._add_step(task, {"id": uuid.uuid4().hex[:10], "at": time.time(), "ms": 0, "tool": "user_verdict", "kind": "user",
"ok": True, "state": "done", "summary": "user_verdict", "verdict": verdict, "run": step_id,
"delivered": False, "results": [], "text": (
f"The user says run {number} of this task (step {step_id}) turned out well."
if verdict == "good" else
f"The user says run {number} of this task (step {step_id}) did not turn out well: "
"change the way, a setting or the prompt before running again, or ask them what is off.")})
else:
step.pop("verdict", None)
ai_tasks._save(task)
ai_tasks._notify(task_id)
return verdict