Files
co5dt-ComfyUI-Persistent-Queue/web/js/pqueue.events.js
T

707 lines
34 KiB
JavaScript

(function () {
"use strict";
let state = window.PQueue?.state;
let API = window.PQueue?.API || window.API;
let UI = window.PQueue?.UI || window.UI;
let setStatusMessage = window.PQueue?.setStatusMessage || window.setStatusMessage;
function refreshRefs() {
state = window.PQueue?.state;
UI = window.PQueue?.UI || window.UI;
API = window.PQueue?.API || window.API;
setStatusMessage = window.PQueue?.setStatusMessage || window.setStatusMessage || function () {};
}
let dragRow = null;
let dropHover = null;
const Events = {
bind() {
refreshRefs();
document.getElementById("pqueue-toggle")?.addEventListener("click", Events.togglePause);
document.getElementById("pqueue-clear")?.addEventListener("click", Events.clearPending);
document.getElementById("pqueue-refresh")?.addEventListener("click", Events.manualRefresh);
state.dom.filterInput?.addEventListener("input", Events.handleFilter);
state.dom.deleteSelectedBtn?.addEventListener("click", Events.deleteSelected);
state.dom.pendingTable?.querySelector(".pqueue-select-all")?.addEventListener("change", Events.toggleSelectAll);
const table = state.dom.pendingTable;
if (table) {
table.addEventListener("dragstart", Events.handleDragStart);
table.addEventListener("dragover", Events.handleDragOver);
table.addEventListener("dragleave", Events.handleDragLeave);
table.addEventListener("drop", Events.handleDrop);
table.addEventListener("dragend", Events.handleDragEnd);
table.addEventListener("change", Events.handleTableChange);
table.addEventListener("click", Events.handleTableClick);
table.addEventListener("keydown", Events.handleTableKey);
}
UI.ensureHistoryObserver();
try {
const root = state.container;
if (root && !root._pqlockBound) {
const lock = () => { state.renderLockUntil = Date.now() + 300; };
const unlockSoon = () => { state.renderLockUntil = Date.now() + 100; };
root.addEventListener('pointerdown', lock, true);
root.addEventListener('pointerup', unlockSoon, true);
root.addEventListener('click', unlockSoon, true);
root._pqlockBound = true;
}
} catch (err) { /* noop */ }
},
async syncLatestForAsc() {
refreshRefs();
try {
const paging = state.historyPaging;
const params = paging?.params || {};
if ((params.sort_dir || 'desc') !== 'asc') return;
const latestParams = { ...params, sort_dir: 'desc', limit: Math.min(10, params.limit || 10) };
delete latestParams.cursor_id;
delete latestParams.cursor_value;
const result = await API.getHistoryPaginated(latestParams);
let list = Array.isArray(result?.history) ? result.history : [];
if (!list.length) return;
try {
const dir = (state.historyPaging?.params?.sort_dir === 'asc') ? 'asc' : 'desc';
list = list.slice().sort((a, b) => {
const [ams, aid] = UI.historyKeyParts(a);
const [bms, bid] = UI.historyKeyParts(b);
if (ams !== bms) return dir === 'desc' ? (bms - ams) : (ams - bms);
return dir === 'desc' ? (bid - aid) : (aid - bid);
});
} catch (err) { /* noop */ }
if (!state.historyIds) state.historyIds = new Set();
let added = 0;
for (const row of list) {
const id = row?.id;
if (id == null || state.historyIds.has(id)) continue;
state.historyIds.add(id);
state.history.push(row);
added += 1;
}
if (added) UI.reconcileHistoryFromState();
} catch (err) { /* noop */ }
},
async togglePause() {
refreshRefs();
try {
if (state.paused) await API.resume();
else await API.pause();
setStatusMessage(state.paused ? "Resuming queue…" : "Pausing queue…");
await refresh({ force: true });
} catch (err) {
console.error("pqueue: toggle pause failed", err);
state.error = err?.message || "Failed to toggle queue";
UI.updateToolbarStatus();
}
},
async clearPending() {
refreshRefs();
if (!state.queue_pending.length) {
setStatusMessage("Queue already empty");
return;
}
if (!window.confirm(`Remove ${state.queue_pending.length} pending prompt${state.queue_pending.length === 1 ? "" : "s"}?`)) return;
try {
const ids = state.queue_pending.map((item) => item[1]).filter(Boolean);
await API.del(ids);
setStatusMessage(`Cleared ${ids.length} prompt${ids.length === 1 ? "" : "s"}`);
await refresh({ force: true });
} catch (err) {
console.error("pqueue: clear failed", err);
state.error = err?.message || "Failed to clear queue";
UI.updateToolbarStatus();
}
},
manualRefresh() {
refreshRefs();
if (state.isRefreshing) return;
state.history = [];
state.historyIds = new Set();
const pad = (n) => String(n).padStart(2, '0');
const fmt = (dt) => `${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;
const buildParams = () => {
const params = { sort_by: "id", sort_dir: "desc", limit: 60 };
const s = state.filters?.historySince;
const u = state.filters?.historyUntil;
if (s) {
const [y, m, d] = s.split('-').map((x) => parseInt(x, 10));
const start = new Date(y, (m || 1) - 1, d || 1, 0, 0, 0, 0);
params.since = fmt(start);
}
if (u) {
const [y, m, d] = u.split('-').map((x) => parseInt(x, 10));
const end = new Date(y, (m || 1) - 1, d || 1, 23, 59, 59, 999);
params.until = fmt(end);
}
return params;
};
state.historyPaging = { isLoading: false, hasMore: true, nextCursor: null, params: buildParams() };
refresh();
UI.updateHistorySubtitle();
},
handleFilter(event) {
refreshRefs();
state.filters.pending = event.target.value;
UI.refreshFilter();
UI.updateSelectionUI();
},
toggleSelectAll(event) {
refreshRefs();
const checked = event.target.checked;
const rows = Array.from(state.dom.pendingTable.querySelectorAll(".pqueue-row[data-id]"));
rows.forEach((row) => {
if (row.style.display === "none") return;
if (checked) state.selectedPending.add(row.dataset.id);
else state.selectedPending.delete(row.dataset.id);
});
UI.updateSelectionUI();
},
handleTableChange(event) {
refreshRefs();
if (!event.target.classList.contains("pqueue-select")) return;
const row = event.target.closest(".pqueue-row[data-id]");
if (!row) return;
const id = row.dataset.id;
if (event.target.checked) state.selectedPending.add(id);
else state.selectedPending.delete(id);
UI.updateSelectionUI();
},
handleTableClick(event) {
refreshRefs();
const btn = event.target.closest("button[data-action]");
if (!btn) return;
const action = btn.dataset.action;
const id = btn.dataset.id;
if (action === "delete") Events.deleteSingle(id);
else if (action === "move-top") Events.moveSingle(id, "top");
else if (action === "move-bottom") Events.moveSingle(id, "bottom");
},
handleTableKey() { refreshRefs(); },
handleDragStart(event) {
refreshRefs();
const row = event.target.closest(".pqueue-row[data-id]");
if (!row) return;
dragRow = row;
row.classList.add("is-dragging");
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", row.dataset.id);
},
handleDragOver(event) {
refreshRefs();
if (!dragRow) return;
const target = event.target.closest(".pqueue-row[data-id]");
if (!target || target === dragRow) return;
event.preventDefault();
if (dropHover && dropHover !== target) dropHover.classList.remove("pqueue-row--before", "pqueue-row--after");
const rect = target.getBoundingClientRect();
const before = event.clientY - rect.top < rect.height / 2;
target.classList.toggle("pqueue-row--before", before);
target.classList.toggle("pqueue-row--after", !before);
dropHover = target;
},
handleDragLeave(event) {
refreshRefs();
const target = event.target.closest(".pqueue-row[data-id]");
if (target && target !== dragRow) target.classList.remove("pqueue-row--before", "pqueue-row--after");
},
async handleDrop(event) {
refreshRefs();
if (!dragRow) return;
event.preventDefault();
const table = state.dom.pendingTable;
const target = event.target.closest(".pqueue-row[data-id]");
if (!table || !target || target === dragRow) return;
const rect = target.getBoundingClientRect();
const before = event.clientY - rect.top < rect.height / 2;
target.classList.remove("pqueue-row--before", "pqueue-row--after");
table.insertBefore(dragRow, before ? target : target.nextSibling);
const order = Array.from(table.querySelectorAll(".pqueue-row[data-id]"))
.filter((row) => row.style.display !== "none")
.map((row) => row.dataset.id);
try {
await API.reorder(order);
setStatusMessage("Queue reordered");
await refresh({ skipIfBusy: true });
} catch (err) {
console.error("pqueue: reorder failed", err);
state.error = err?.message || "Failed to reorder queue";
UI.updateToolbarStatus();
}
},
handleDragEnd() {
refreshRefs();
if (dropHover) dropHover.classList.remove("pqueue-row--before", "pqueue-row--after");
dropHover = null;
if (dragRow) {
dragRow.classList.remove("is-dragging");
dragRow = null;
}
},
async deleteSingle(id) {
refreshRefs();
if (!id) return;
if (!window.confirm(`Delete prompt ${id}?`)) return;
try {
await API.del([id]);
state.selectedPending.delete(id);
setStatusMessage(`Deleted ${id}`);
await refresh({ force: true });
} catch (err) {
console.error("pqueue: delete failed", err);
state.error = err?.message || "Failed to delete prompt";
UI.updateToolbarStatus();
}
},
// priority editing is not exposed in the current UI; left intentionally unimplemented
async moveSingle(id, where) {
refreshRefs();
try {
const table = state.dom.pendingTable;
if (!table) return;
// Optimistically move the row in the DOM for immediate feedback
const rowEl = table.querySelector(`.pqueue-row[data-id="${id}"]`);
const header = table.querySelector('.pqueue-list__header');
if (!rowEl) return;
if (where === 'top') {
if (header && header.nextSibling) table.insertBefore(rowEl, header.nextSibling);
else table.insertBefore(rowEl, table.firstChild);
} else {
table.appendChild(rowEl);
}
// Build new order based on current DOM row order
const ids = Array.from(table.querySelectorAll('.pqueue-row[data-id]')).map((r) => r.dataset.id);
await API.reorder(ids);
setStatusMessage(where === 'top' ? 'Moved to top' : 'Moved to bottom');
await refresh({ force: true });
} catch (err) {
console.error('pqueue: move failed', err);
state.error = err?.message || 'Failed to move prompt';
UI.updateToolbarStatus();
}
},
async loadMoreHistory() {
refreshRefs();
try {
const paging = state.historyPaging;
if (!paging || paging.isLoading || !paging.hasMore) return;
paging.isLoading = true;
const params = { ...paging.params };
if (paging.nextCursor?.id != null) params.cursor_id = paging.nextCursor.id;
if (paging.nextCursor?.value != null) params.cursor_value = paging.nextCursor.value;
const result = await API.getHistoryPaginated(params);
const list = Array.isArray(result?.history) ? result.history : [];
const grid = state.dom.historyGrid;
const sentinel = state.dom.historySentinel;
const dir = (state.historyPaging?.params?.sort_dir === 'asc') ? 'asc' : 'desc';
if (grid && sentinel && list.length) {
UI.withStableAnchor(() => {
try { Array.from(grid.querySelectorAll('.pqueue-empty')).forEach((n) => n.remove()); } catch (err) { /* noop */ }
for (const row of list) {
const id = row?.id;
if (id == null || state.historyIds.has(id)) continue;
state.historyIds.add(id);
const card = UI.historyCard(row);
const key = Number(card.getAttribute('data-key') || 0);
const key2 = Number(card.getAttribute('data-key2') || 0);
const existingCards = Array.from(grid.querySelectorAll('.pqueue-history-card'));
let inserted = false;
if (dir === 'desc') {
for (const existing of existingCards) {
const otherKey = Number(existing.getAttribute('data-key') || 0);
const otherKey2 = Number(existing.getAttribute('data-key2') || 0);
if (key > otherKey || (key === otherKey && key2 >= otherKey2)) { grid.insertBefore(card, existing); inserted = true; break; }
}
if (!inserted) grid.insertBefore(card, sentinel);
} else {
for (let i = existingCards.length - 1; i >= 0; i--) {
const existing = existingCards[i];
const otherKey = Number(existing.getAttribute('data-key') || 0);
const otherKey2 = Number(existing.getAttribute('data-key2') || 0);
if (key > otherKey || (key === otherKey && key2 >= otherKey2)) { grid.insertBefore(card, existing.nextSibling || sentinel); inserted = true; break; }
}
if (!inserted) {
const firstCard = existingCards[0] || grid.querySelector('.pqueue-history-card');
grid.insertBefore(card, firstCard || sentinel);
}
}
if (Array.isArray(state.history)) {
if (dir === 'desc') state.history.push(row);
else state.history.push(row);
}
}
});
} else if (list.length) {
if (Array.isArray(state.history)) state.history.push(...list);
}
if (typeof result?.total === 'number') state.historyTotal = result.total;
paging.nextCursor = result?.next_cursor || null;
paging.hasMore = !!result?.has_more;
if (!paging.hasMore && sentinel) sentinel.style.display = "none";
try { deriveMetrics(); } catch (err) { /* noop */ }
UI.updateHistorySubtitle();
} catch (err) {
/* ignore load errors to avoid UI jank */
} finally {
if (state.historyPaging) state.historyPaging.isLoading = false;
}
},
toggleFiltersPopover(anchor) {
refreshRefs();
try {
if (state.dom.filtersPopover) {
const existing = state.dom.filtersPopover;
if (typeof existing._closeAnimated === 'function') existing._closeAnimated();
else {
if (state.dom.filtersPopoverListeners) {
const { onDocPointer, onResize, onScroll } = state.dom.filtersPopoverListeners;
if (onDocPointer) document.removeEventListener('pointerdown', onDocPointer, true);
if (onResize) window.removeEventListener('resize', onResize);
if (onScroll) window.removeEventListener('scroll', onScroll, true);
state.dom.filtersPopoverListeners = null;
}
existing.remove();
state.dom.filtersPopover = null;
}
return;
}
const pop = UI.buildFiltersPopover(anchor);
document.body.appendChild(pop);
state.dom.filtersPopover = pop;
const position = () => {
try {
const margin = 10;
const rect = anchor.getBoundingClientRect();
const viewportW = window.innerWidth;
const viewportH = window.innerHeight;
const scrollX = window.scrollX;
const scrollY = window.scrollY;
pop.classList.remove('pqueue-popover--above');
pop.style.maxHeight = '80vh';
const popW = pop.offsetWidth;
const popH = pop.offsetHeight;
const anchorCenter = rect.left + rect.width / 2 + scrollX;
let left = Math.round(anchorCenter - popW / 2);
left = Math.max(scrollX + margin, Math.min(left, scrollX + viewportW - popW - margin));
let top = rect.bottom + margin + scrollY;
const bottomOverflow = (top + popH) - (scrollY + viewportH - margin);
if (bottomOverflow > 0) {
const aboveTop = rect.top + scrollY - popH - margin;
if (aboveTop >= scrollY + margin) {
top = aboveTop;
pop.classList.add('pqueue-popover--above');
} else {
top = Math.max(scrollY + margin, Math.min(top, scrollY + viewportH - popH - margin));
}
}
pop.style.left = `${left}px`;
pop.style.top = `${top}px`;
const arrow = pop.querySelector('.pqueue-popover__arrow');
if (arrow) {
const popLeft = left;
const offset = Math.max(14, Math.min(anchorCenter - popLeft, popW - 14));
arrow.style.left = `${offset}px`;
}
} catch (err) { /* noop */ }
};
const closeAnimated = () => {
try {
if (!state.dom.filtersPopover) return;
const node = state.dom.filtersPopover;
document.removeEventListener('pointerdown', onDocPointer, true);
window.removeEventListener('resize', onResize);
window.removeEventListener('scroll', onScroll, true);
state.dom.filtersPopoverListeners = null;
let removed = false;
const cleanup = () => {
if (removed) return;
removed = true;
try { node.remove(); } catch (err) {}
if (state.dom.filtersPopover === node) state.dom.filtersPopover = null;
};
node.setAttribute('data-open', 'false');
const timeoutId = window.setTimeout(cleanup, 180);
node.addEventListener('transitionend', () => { window.clearTimeout(timeoutId); cleanup(); }, { once: true });
} catch (err) { /* noop */ }
};
const onDocPointer = (ev) => {
try {
if (!state.dom.filtersPopover) return;
const target = ev.target;
if (state.dom.filtersPopover.contains(target)) return;
if (anchor && (target === anchor || (anchor.contains && anchor.contains(target)))) return;
closeAnimated();
} catch (err) { /* noop */ }
};
const onResize = () => position();
const onScroll = () => position();
document.addEventListener('pointerdown', onDocPointer, true);
window.addEventListener('resize', onResize);
window.addEventListener('scroll', onScroll, { capture: true, passive: true });
state.dom.filtersPopoverListeners = { onDocPointer, onResize, onScroll };
pop.addEventListener('pqueue-close', closeAnimated);
pop._closeAnimated = closeAnimated;
position();
requestAnimationFrame(() => { try { pop.setAttribute('data-open', 'true'); } catch (err) {} });
} catch (err) { /* noop */ }
},
async setHistorySort(dir) {
refreshRefs();
try {
if (!state.historyPaging) state.historyPaging = { isLoading: false, hasMore: true, nextCursor: null, params: { sort_by: 'id', sort_dir: 'desc', limit: 60 } };
const params = state.historyPaging.params || {};
params.sort_dir = (dir === 'asc') ? 'asc' : 'desc';
state.historyPaging.params = params;
const grid = state.dom.historyGrid;
const scroller = UI.getScrollContainer();
let restore = null;
if (grid && scroller) {
const rect = grid.getBoundingClientRect();
const scRect = scroller.getBoundingClientRect();
const beforeOffset = rect.top - scRect.top;
const beforeHeight = rect.height;
grid.style.minHeight = `${beforeHeight}px`;
restore = () => {
try { grid.style.minHeight = ''; } catch (err) {}
try { scroller.scrollTop = Math.max(0, scroller.scrollTop + ((grid.getBoundingClientRect().top - scroller.getBoundingClientRect().top) - beforeOffset)); } catch (err) {}
};
}
const sentinel = state.dom.historySentinel;
if (grid) Array.from(grid.querySelectorAll('.pqueue-history-card')).forEach((n) => n.remove());
state.history = [];
state.historyIds = new Set();
state.historyTotal = null;
state.historyPaging.nextCursor = null;
state.historyPaging.hasMore = true;
if (sentinel) sentinel.style.display = '';
await Events.loadMoreHistory();
await Events.syncLatestForAsc();
UI.updateHistorySubtitle();
UI.updateSortToggle();
try { UI.ensureHistoryObserver(); } catch (err) { /* noop */ }
if (typeof restore === 'function') requestAnimationFrame(restore);
} catch (err) { /* noop */ }
},
updateHistoryDateFilters(sinceVal, untilVal, sinceTimeVal = "", untilTimeVal = "") {
refreshRefs();
const pad = (n) => String(n).padStart(2, '0');
const fmt = (dt) => `${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;
const toLocalStart = (d, t) => {
if (!d) return "";
const parts = d.split('-').map((x) => parseInt(x, 10));
let hh = 0, mm = 0, ss = 0;
if (t && /^\d{2}:\d{2}(:\d{2})?$/.test(t)) {
const tt = t.split(':').map((x) => parseInt(x, 10));
hh = tt[0] ?? 0; mm = tt[1] ?? 0; ss = tt[2] ?? 0;
}
const dt = new Date(parts[0], (parts[1] || 1) - 1, parts[2] || 1, hh, mm, ss, 0);
return fmt(dt);
};
const toLocalEnd = (d, t) => {
if (!d) return "";
const parts = d.split('-').map((x) => parseInt(x, 10));
let hh = 23, mm = 59, ss = 59;
if (t && /^\d{2}:\d{2}(:\d{2})?$/.test(t)) {
const tt = t.split(':').map((x) => parseInt(x, 10));
hh = tt[0] ?? 23; mm = tt[1] ?? 59; ss = tt[2] ?? 59;
}
const dt = new Date(parts[0], (parts[1] || 1) - 1, parts[2] || 1, hh, mm, ss, 999);
return fmt(dt);
};
state.filters.historySince = sinceVal || "";
state.filters.historyUntil = untilVal || "";
state.filters.historySinceTime = sinceTimeVal || "";
state.filters.historyUntilTime = untilTimeVal || "";
if (!state.historyPaging) state.historyPaging = { isLoading: false, hasMore: true, nextCursor: null, params: { sort_by: "id", sort_dir: "desc", limit: 60 } };
const params = state.historyPaging.params || {};
params.since = toLocalStart(state.filters.historySince, state.filters.historySinceTime) || undefined;
params.until = toLocalEnd(state.filters.historyUntil, state.filters.historyUntilTime) || undefined;
state.historyPaging.params = params;
const grid = state.dom.historyGrid;
const sentinel = state.dom.historySentinel;
if (grid) {
Array.from(grid.querySelectorAll('.pqueue-history-card')).forEach((n) => n.remove());
}
state.history = [];
state.historyIds = new Set();
state.historyTotal = null;
state.historyPaging.nextCursor = null;
state.historyPaging.hasMore = true;
if (sentinel) sentinel.style.display = "";
Events.loadMoreHistory();
UI.updateHistorySubtitle();
},
applyHistoryPreset(kind) {
refreshRefs();
try {
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
const toDateStr = (dt) => `${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())}`;
const toTimeStr = (dt) => `${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;
if (kind === 'clear') {
state.historyPreset = undefined;
state.filters.historySince = "";
state.filters.historySinceTime = "";
state.filters.historyUntil = "";
state.filters.historyUntilTime = "";
} else if (kind === 'today') {
state.historyPreset = 'today';
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
state.filters.historySince = toDateStr(start);
state.filters.historySinceTime = '00:00:00';
state.filters.historyUntil = toDateStr(end);
state.filters.historyUntilTime = '23:59:59';
} else if (kind === '24h') {
state.historyPreset = '24h';
const start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
state.filters.historySince = toDateStr(start);
state.filters.historySinceTime = toTimeStr(start);
state.filters.historyUntil = toDateStr(now);
state.filters.historyUntilTime = toTimeStr(now);
} else if (kind === '7d') {
state.historyPreset = '7d';
const start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
state.filters.historySince = toDateStr(start);
state.filters.historySinceTime = toTimeStr(start);
state.filters.historyUntil = toDateStr(now);
state.filters.historyUntilTime = toTimeStr(now);
}
if (state.dom.historySince) state.dom.historySince.value = state.filters.historySince;
if (state.dom.historySinceTime) state.dom.historySinceTime.value = state.filters.historySinceTime;
if (state.dom.historyUntil) state.dom.historyUntil.value = state.filters.historyUntil;
if (state.dom.historyUntilTime) state.dom.historyUntilTime.value = state.filters.historyUntilTime;
Events.updateHistoryDateFilters(
state.filters.historySince,
state.filters.historyUntil,
state.filters.historySinceTime,
state.filters.historyUntilTime
);
UI.updateHistorySubtitle();
} catch (err) { /* noop */ }
},
async deleteSelected() {
refreshRefs();
if (!state.selectedPending.size) return;
if (!window.confirm(`Delete ${state.selectedPending.size} selected prompt${state.selectedPending.size === 1 ? "" : "s"}?`)) return;
try {
await API.del(Array.from(state.selectedPending));
state.selectedPending.clear();
setStatusMessage("Deleted selected prompts");
await refresh({ skipIfBusy: true });
} catch (err) {
console.error("pqueue: bulk delete failed", err);
state.error = err?.message || "Failed to delete prompts";
UI.updateToolbarStatus();
}
},
async executeSelectedJobs() {
refreshRefs();
if (!state.selectedPending.size) return;
const isPaused = !!state.paused;
const action = isPaused ? "run" : "skip";
const selectedIds = Array.from(state.selectedPending);
try {
if (isPaused) {
// Run selected jobs
console.log('Running selected jobs:', selectedIds);
const response = await API.runSelectedJobs(selectedIds);
const result = await response.json();
console.log('Run selected response:', response.status, result);
if (!response.ok) {
throw new Error(result.error || 'Failed to run selected jobs');
}
setStatusMessage(`Running ${selectedIds.length} selected job${selectedIds.length === 1 ? "" : "s"}`);
// Force an immediate refresh to reflect the new top-of-queue order
await refresh({ force: true });
// Schedule a follow-up refresh to account for any async server re-heapify
window.setTimeout(() => { try { refresh({ skipIfBusy: true }); } catch (err) {} }, 150);
} else {
// Skip selected jobs (remove from queue)
console.log('Skipping selected jobs:', selectedIds);
const response = await API.skipSelectedJobs(selectedIds);
const result = await response.json();
console.log('Skip selected response:', response.status, result);
if (!response.ok) {
throw new Error(result.error || 'Failed to skip selected jobs');
}
setStatusMessage(`Skipped ${selectedIds.length} selected job${selectedIds.length === 1 ? "" : "s"}`);
}
state.selectedPending.clear();
UI.updateSelectionUI();
} catch (err) {
console.error(`pqueue: ${action} selected jobs failed`, err);
state.error = err?.message || `Failed to ${action} selected jobs`;
UI.updateToolbarStatus();
}
},
viewWorkflow(id) {
refreshRefs();
const info = lookupWorkflow(id);
if (!info) {
setStatusMessage("Workflow not available", 3000);
return;
}
const text = typeof info.workflow === "string" ? info.workflow : JSON.stringify(info.workflow, null, 2);
UI.openWorkflowModal({ promptId: id, workflowText: text, sourceLabel: info.source });
},
};
window.PQueue = window.PQueue || {};
window.PQueue.Events = Events;
window.Events = Events;
})();