feat: Add ability to rename jobs in the persistent queue
This commit is contained in:
@@ -128,6 +128,32 @@ class QueueDatabase:
|
||||
with self._get_conn() as conn:
|
||||
conn.execute('UPDATE queue_items SET priority = ? WHERE prompt_id = ?', (new_priority, prompt_id))
|
||||
|
||||
def update_job_name(self, prompt_id: str, new_name: str) -> bool:
|
||||
"""Update the human-friendly name in the stored workflow JSON.
|
||||
|
||||
Attempts to set either workflow.name (nested) or top-level name, depending on structure.
|
||||
Returns True on success, False if no such job.
|
||||
"""
|
||||
with self._get_conn() as conn:
|
||||
cur = conn.execute('SELECT workflow FROM queue_items WHERE prompt_id = ? LIMIT 1', (prompt_id,))
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return False
|
||||
wf_text = row['workflow']
|
||||
try:
|
||||
wf = json.loads(wf_text) if isinstance(wf_text, str) else (wf_text or {})
|
||||
except Exception:
|
||||
wf = {}
|
||||
# Prefer nested workflow.name if object contains a workflow field
|
||||
if isinstance(wf, dict):
|
||||
if isinstance(wf.get('workflow'), dict):
|
||||
wf['workflow']['name'] = str(new_name)
|
||||
else:
|
||||
wf['name'] = str(new_name)
|
||||
updated = json.dumps(wf) if wf is not None else None
|
||||
conn.execute('UPDATE queue_items SET workflow = ? WHERE prompt_id = ?', (updated, prompt_id))
|
||||
return True
|
||||
|
||||
def add_history(
|
||||
self,
|
||||
prompt_id: str,
|
||||
|
||||
@@ -273,6 +273,7 @@ class RoutesHelper:
|
||||
web.post('/api/pqueue/reorder', manager._api_reorder),
|
||||
web.patch('/api/pqueue/priority', manager._api_priority),
|
||||
web.post('/api/pqueue/delete', manager._api_delete),
|
||||
web.patch('/api/pqueue/rename', manager._api_rename),
|
||||
]
|
||||
self.app.add_routes(routes)
|
||||
|
||||
@@ -510,6 +511,39 @@ class PersistentQueueManager:
|
||||
logging.warning(f"PersistentQueue delete failed: {e}")
|
||||
return web.json_response({"ok": False, "error": str(e)}, status=400)
|
||||
|
||||
async def _api_rename(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
from server import PromptServer
|
||||
body = await request.json()
|
||||
prompt_id: str = body.get('prompt_id')
|
||||
new_name: str = body.get('name')
|
||||
if not prompt_id or new_name is None:
|
||||
return web.json_response({"ok": False, "error": "prompt_id and name required"}, status=400)
|
||||
# Persist to DB
|
||||
ok = self.db.update_job_name(prompt_id, str(new_name))
|
||||
if not ok:
|
||||
return web.json_response({"ok": False, "error": "job not found"}, status=404)
|
||||
# Update in-memory queue copy to reflect quickly
|
||||
q = PromptServer.instance.prompt_queue
|
||||
with q.mutex:
|
||||
for idx, item in enumerate(list(q.queue)):
|
||||
try:
|
||||
if item[1] == prompt_id:
|
||||
# item = (number, prompt_id, prompt, extra_data, outputs_to_execute)
|
||||
prompt = item[2]
|
||||
if isinstance(prompt, dict):
|
||||
if isinstance(prompt.get('workflow'), dict):
|
||||
prompt['workflow']['name'] = str(new_name)
|
||||
else:
|
||||
prompt['name'] = str(new_name)
|
||||
q.queue[idx] = (item[0], item[1], prompt, item[3], item[4])
|
||||
except Exception:
|
||||
pass
|
||||
return web.json_response({"ok": True})
|
||||
except Exception as e:
|
||||
logging.warning(f"PersistentQueue rename failed: {e}")
|
||||
return web.json_response({"ok": False, "error": str(e)}, status=400)
|
||||
|
||||
def _rebuild_queue_by_prompt_ids(self, ordered_prompt_ids: List[str]) -> None:
|
||||
from server import PromptServer
|
||||
q = PromptServer.instance.prompt_queue
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ prompt_ids }),
|
||||
}),
|
||||
rename: (prompt_id, name) =>
|
||||
fetch("/api/pqueue/rename", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ prompt_id, name }),
|
||||
}),
|
||||
};
|
||||
|
||||
const Icons = {
|
||||
@@ -579,6 +585,49 @@
|
||||
|
||||
const status = UI.statusBadge(db.status || "pending", { subtle: true });
|
||||
const primaryLabel = UI.buildWorkflowLabel(pid, item, db);
|
||||
primaryLabel.contentEditable = "true";
|
||||
primaryLabel.spellcheck = false;
|
||||
primaryLabel.dataset.id = pid;
|
||||
primaryLabel.dataset.original = primaryLabel.textContent || "";
|
||||
primaryLabel.classList.add("pqueue-editable");
|
||||
const finishEdit = async (node, save) => {
|
||||
try {
|
||||
const id = node?.dataset?.id;
|
||||
if (!id) return;
|
||||
const original = node.dataset.original || "";
|
||||
const current = (node.textContent || "").trim();
|
||||
if (!save) {
|
||||
node.textContent = original;
|
||||
return;
|
||||
}
|
||||
if (current === original || !current) return;
|
||||
await API.rename(id, current);
|
||||
state.workflowNameCache.set(id, current);
|
||||
setStatusMessage("Name updated");
|
||||
} catch (err) {
|
||||
console.error("pqueue: rename failed", err);
|
||||
state.error = err?.message || "Failed to rename";
|
||||
UI.updateToolbarStatus();
|
||||
} finally {
|
||||
node.dataset.original = node.textContent || "";
|
||||
}
|
||||
};
|
||||
primaryLabel.addEventListener("keydown", async (e) => {
|
||||
e.stopPropagation();
|
||||
const target = e.currentTarget;
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
await finishEdit(target, true);
|
||||
target.blur();
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
await finishEdit(target, false);
|
||||
target.blur();
|
||||
}
|
||||
});
|
||||
primaryLabel.addEventListener("blur", async (e) => {
|
||||
await finishEdit(e.currentTarget, true);
|
||||
});
|
||||
const line1 = UI.el("div", { class: "pqueue-pcard__line1" }, [
|
||||
UI.el("div", { class: "pqueue-pcard__l1-left" }, [checkbox, primaryLabel]),
|
||||
UI.el("div", { class: "pqueue-pcard__l1-right" }, [UI.el("span", { class: "pqueue-muted", text: db.created_at ? Format.relative(db.created_at) : "—" })])
|
||||
|
||||
Reference in New Issue
Block a user