AMB only uses: training goes to its own plugin. This takes back the record of what works (api/ai_evidence.py), the training network and rounds (api/ai_training.py, ui_ai_network*.js, the round form), the Gates' record switch, the ways' order by record and the external_skill field of log_outside that only fed the record. Kept, as part of using AMB: - the user's word under a finished run, now only told to the AI (ai_run_control.set_verdict, ui_ai_verdict.js); - the Gates' "Custom nodes: only the ways'" rule; - a picks block left to the AI takes its recommended picture. Reverts73518dc,67bf5b2,6647b05and49d05cf, then puts those three back with tests in test_ai_run_watch, test_ai_gates and test_ai_panels. Full suite and structure check pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
4.7 KiB
Python
91 lines
4.7 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")
|
|
if step.get("state") != "running" or not step.get("prompt_ids"):
|
|
raise RunControlError("This run is not going any more")
|
|
prompts = list(step["prompt_ids"])
|
|
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)
|
|
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
|