const terminal = new Set(['complete','failed','cancelled']) // Include enhancing so PE is the same queue row as generate (not a vanished "Queue 0"). const running = new Set(['waking','enhancing','submitting','rendering','saving']) export function queueJobs(jobs) { return jobs.filter(j=>running.has(j.state)||['waiting','queued','held'].includes(j.state)).sort((a,b)=>Number(running.has(b.state))-Number(running.has(a.state)) || (a.queuedAt||0)-(b.queuedAt||0)) } export function jobView(record, queue, live) { const statuses={complete:'complete',error:'failed',cancelled:'cancelled'} const state=statuses[queue?.status] || (!live?.saving && statuses[live?.status]) || (queue?.status==='held'?'held':record.state) const finishedAt=terminal.has(state)?record.finishedAt || (statuses[queue?.status]?queue.updatedAt:0) || (state==='complete'?record.savedAt:0) || record.updatedAt || record.startedAt || record.queuedAt:undefined return {...record,state,finishedAt,error:record.error || queue?.lastError || live?.error} } export function elapsedSeconds(job, now) { const start=job.startedAt || job.queuedAt const end=terminal.has(job.state)?job.finishedAt || job.savedAt || job.updatedAt || start:now return Number.isFinite(start)&&Number.isFinite(end)?Math.max(0,Math.floor((end-start)/1000)):0 } /** Stage label shown on the queue row (header pill uses status.mjs). */ export function jobStageLabel(state) { const labels={ waiting:'Queued',queued:'Queued',held:'Held', waking:'Waking up',enhancing:'Enhancing prompt…', submitting:'Generating…',rendering:'Generating…',saving:'Saving', complete:'Ready',failed:'Failed',cancelled:'Cancelled', } return labels[state] || state } /** Compact progress for the queue row. Enhancing prefers indeterminate over a fake 0%. */ export function jobProgressView(job) { const state=job?.state if(!['enhancing','submitting','rendering','waking','saving'].includes(state)) return null const p=job?.progress if(state==='enhancing'){ if(p && Number(p.maxStep)>0 && Number(p.step)>=0) return {indeterminate:false,percent:Math.max(0,Math.min(100,Number(p.percent)||Math.round(Number(p.step)/Number(p.maxStep)*100)))} return {indeterminate:true,percent:null} } if(state==='rendering' && p && Number(p.maxStep)>0) return {indeterminate:false,percent:Math.max(0,Math.min(100,Number(p.percent)||Math.round(Number(p.step)/Number(p.maxStep)*100)))} // waking / submitting / saving / rendering without steps: no fake bar if(state==='submitting' || state==='waking') return {indeterminate:true,percent:null} return null }