feat: implement job resumption, custom queue reordering, and auto-venv start script setup
This commit is contained in:
+101
-6
@@ -1276,6 +1276,37 @@ async function loadGallery() {
|
||||
}
|
||||
}
|
||||
|
||||
async function moveQueueItem(jobId, direction) {
|
||||
try {
|
||||
const res = await fetch("/api/queue");
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const queue = data.queue;
|
||||
const index = queue.indexOf(jobId);
|
||||
if (index === -1) return;
|
||||
|
||||
const newIndex = index + direction;
|
||||
if (newIndex < 0 || newIndex >= queue.length) return;
|
||||
|
||||
// Swap
|
||||
const temp = queue[index];
|
||||
queue[index] = queue[newIndex];
|
||||
queue[newIndex] = temp;
|
||||
|
||||
const reorderRes = await fetch("/api/queue/reorder", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ job_ids: queue })
|
||||
});
|
||||
|
||||
if (reorderRes.ok) {
|
||||
loadQueue();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error reordering queue:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadQueue() {
|
||||
const queueList = document.getElementById("queue-list");
|
||||
if (!queueList) return;
|
||||
@@ -1287,7 +1318,7 @@ async function loadQueue() {
|
||||
const jobs = await res.json();
|
||||
|
||||
const displayJobs = jobs.filter(job =>
|
||||
["queued", "analyzing", "extracting", "upscaling", "assembling", "completed", "failed", "cancelled"].includes(job.status)
|
||||
["queued", "analyzing", "extracting", "upscaling", "assembling", "completed", "failed", "cancelled", "interrupted"].includes(job.status)
|
||||
);
|
||||
|
||||
if (displayJobs.length === 0) {
|
||||
@@ -1318,13 +1349,40 @@ async function loadQueue() {
|
||||
|
||||
if (job.status === "failed" && job.error) {
|
||||
metaHtml += `<span style="color: var(--danger);"><i class="fa-solid fa-triangle-exclamation"></i> Error: ${job.error}</span>`;
|
||||
} else if (job.status !== "queued" && job.status !== "failed" && job.status !== "cancelled" && job.status !== "completed") {
|
||||
} else if (job.status !== "queued" && job.status !== "failed" && job.status !== "cancelled" && job.status !== "completed" && job.status !== "interrupted") {
|
||||
metaHtml += `<span><i class="fa-solid fa-clock"></i> ETA: ${job.eta || 'Calculating...'}</span>`;
|
||||
}
|
||||
|
||||
let actionsHtml = "";
|
||||
if (["queued", "analyzing", "extracting", "upscaling", "assembling"].includes(job.status)) {
|
||||
let reorderHtml = "";
|
||||
|
||||
if (job.status === "queued" && job.queue_position !== null && job.queue_position !== undefined) {
|
||||
const queuedJobs = displayJobs.filter(j => j.status === "queued");
|
||||
const isFirst = job.queue_position === 0;
|
||||
const isLast = job.queue_position === queuedJobs.length - 1;
|
||||
|
||||
reorderHtml = `
|
||||
<button class="btn btn-secondary btn-small btn-move-up" data-id="${job.job_id}" style="padding: 0.25rem 0.5rem; width: 30px; height: 30px; border-radius: 8px; font-size: 0.8rem; display: inline-flex; align-items: center; justify-content: center; ${isFirst ? 'visibility: hidden;' : ''}" title="Move Up">
|
||||
<i class="fa-solid fa-arrow-up"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-small btn-move-down" data-id="${job.job_id}" style="padding: 0.25rem 0.5rem; width: 30px; height: 30px; border-radius: 8px; font-size: 0.8rem; display: inline-flex; align-items: center; justify-content: center; ${isLast ? 'visibility: hidden;' : ''}" title="Move Down">
|
||||
<i class="fa-solid fa-arrow-down"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
if (job.status === "interrupted") {
|
||||
actionsHtml = `
|
||||
<button class="btn btn-primary btn-small btn-resume-job" data-id="${job.job_id}">
|
||||
<i class="fa-solid fa-play"></i> Resume
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-small btn-delete-job" data-id="${job.job_id}">
|
||||
<i class="fa-solid fa-trash"></i> Clear
|
||||
</button>
|
||||
`;
|
||||
} else if (["queued", "analyzing", "extracting", "upscaling", "assembling"].includes(job.status)) {
|
||||
actionsHtml = `
|
||||
${reorderHtml}
|
||||
<button class="btn btn-primary btn-small btn-view-job" data-id="${job.job_id}">
|
||||
<i class="fa-solid fa-eye"></i> View
|
||||
</button>
|
||||
@@ -1366,9 +1424,44 @@ async function loadQueue() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const upBtn = item.querySelector(".btn-move-up");
|
||||
if (upBtn) {
|
||||
upBtn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
await moveQueueItem(job.job_id, -1);
|
||||
});
|
||||
}
|
||||
|
||||
const downBtn = item.querySelector(".btn-move-down");
|
||||
if (downBtn) {
|
||||
downBtn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
await moveQueueItem(job.job_id, 1);
|
||||
});
|
||||
}
|
||||
|
||||
const resumeBtn = item.querySelector(".btn-resume-job");
|
||||
if (resumeBtn) {
|
||||
resumeBtn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
try {
|
||||
const resumeRes = await fetch(`/api/upscale/resume/${job.job_id}`, { method: "POST" });
|
||||
if (resumeRes.ok) {
|
||||
loadQueue();
|
||||
} else {
|
||||
const err = await resumeRes.json();
|
||||
alert("Failed to resume job: " + formatFetchError(err, "unknown error"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error resuming job:", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const abortBtn = item.querySelector(".btn-abort-job");
|
||||
if (abortBtn) {
|
||||
abortBtn.addEventListener("click", async () => {
|
||||
abortBtn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm("Are you sure you want to abort this upscaling job?")) {
|
||||
try {
|
||||
const abortRes = await fetch(`/api/upscale/cancel/${job.job_id}`, { method: "POST" });
|
||||
@@ -1384,7 +1477,8 @@ async function loadQueue() {
|
||||
|
||||
const deleteBtn = item.querySelector(".btn-delete-job");
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener("click", async () => {
|
||||
deleteBtn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm("Are you sure you want to clear this job from history?")) {
|
||||
try {
|
||||
const delRes = await fetch(`/api/jobs/${job.job_id}`, { method: "DELETE" });
|
||||
@@ -1401,7 +1495,8 @@ async function loadQueue() {
|
||||
|
||||
const viewBtn = item.querySelector(".btn-view-job");
|
||||
if (viewBtn) {
|
||||
viewBtn.addEventListener("click", () => {
|
||||
viewBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
currentJobId = job.job_id;
|
||||
if (socket) {
|
||||
try {
|
||||
|
||||
@@ -656,6 +656,7 @@ input:checked + .slider:before {
|
||||
.status-badge.assembling { background: rgba(0, 240, 255, 0.1); border: 1px solid var(--primary); color: var(--primary); }
|
||||
.status-badge.completed { background: rgba(0, 255, 135, 0.1); border: 1px solid var(--success); color: var(--success); }
|
||||
.status-badge.failed { background: rgba(255, 0, 85, 0.1); border: 1px solid var(--danger); color: var(--danger); }
|
||||
.status-badge.interrupted { background: rgba(255, 255, 255, 0.08); border: 1px solid var(--text-muted); color: var(--text-muted); }
|
||||
|
||||
.bar-container {
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user