diff --git a/assets/css/display.css b/assets/css/display.css index d7b59a8..4dc8798 100644 --- a/assets/css/display.css +++ b/assets/css/display.css @@ -590,6 +590,37 @@ background: var(--muted); } +.job-status[data-state='enhancing'] .dot, +.job-status[data-state='waking'] .dot, +.job-status[data-state='submitting'] .dot, +.job-status[data-state='rendering'] .dot, +.job-status[data-state='saving'] .dot { + background: var(--amber); +} + +.job-progress { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; +} + +.job-progress-bar { + flex: 1; + height: 4px; + accent-color: var(--amber); +} + +.job-progress[data-indeterminate='true'] .job-progress-bar { + opacity: 0.85; +} + +.job-elapsed { + flex-shrink: 0; + color: var(--muted); + font-size: 11px; +} + .kebab { border: 0; background: transparent; diff --git a/components/studio2/Bench.vue b/components/studio2/Bench.vue index 58ad219..70bcc4c 100644 --- a/components/studio2/Bench.vue +++ b/components/studio2/Bench.vue @@ -302,6 +302,14 @@ {{ card.statusLabel }} +
No active jobs
@@ -374,7 +382,7 @@ import {filterLorasForStudio,filterLorasForUse} from '~/utils/loras' import {sectionsFrom} from '~/shared/studio2/contracts.mjs' import {aspectOptions,resolveSize,bindClip,restoreAll,useAsHero,useAsSource,hydrateStudio2Job} from '~/shared/studio2/input.mjs' import {folderAssets,newestOutput,assetLabel,reconcileSelection,previewHidden} from '~/shared/studio2/viewer.mjs' -import {queueJobs} from '~/shared/studio2/queue-view.mjs' +import {queueJobs,jobStageLabel,jobProgressView,elapsedSeconds} from '~/shared/studio2/queue-view.mjs' import {gpuStatus} from '~/shared/studio2/status.mjs' import {applyUploadedStill} from '~/shared/studio2/upload.mjs' import {defaultNegatives,parseBatch} from '~/shared/studio2/styles.mjs' @@ -534,6 +542,7 @@ const slotPairHint=computed(()=>{ }) const queueCards=computed(()=>{ const cards:any[]=[] + const now=Date.now() for(const job of benchJobs.value){ const action=job.request?.promptSections?.action || 'Job' const eng=job.request?.engine==='ltx'?'LTX':job.request?.engine==='krea'?'Krea 2':job.request?.engine==='qwen21'?'Qwen 2.1':job.request?.engine==='minimax'?'MiniMax H3':'Klein' @@ -541,6 +550,8 @@ const queueCards=computed(()=>{ const modeLabel=mode.charAt(0).toUpperCase()+mode.slice(1) const aspect=job.request?.settings?.aspect || 'auto' const state=job.state==='waiting'?'queued':job.state + const secs=elapsedSeconds(job,now) + const progress=jobProgressView(job) cards.push({ id:job.id, job, @@ -548,7 +559,9 @@ const queueCards=computed(()=>{ title:action.slice(0,72), meta:`${modeLabel} · ${eng} · ${aspect}`, state, - statusLabel:state==='queued'?'Queued':state==='rendering'||state==='submitting'||state==='waking'||state==='saving'?'Running':state==='complete'?'Ready':state, + statusLabel:jobStageLabel(state), + progress, + elapsed:secs>0?`${Math.floor(secs/60)}m ${secs%60}s`:'', cancelable:!['complete','failed','cancelled'].includes(job.state) }) } diff --git a/components/studio2/JobProgress.vue b/components/studio2/JobProgress.vue index d5e550b..4da4f2b 100644 --- a/components/studio2/JobProgress.vue +++ b/components/studio2/JobProgress.vue @@ -2,7 +2,8 @@{{ elapsed }} {{ job.startedAt?'elapsed':'waiting' }} · {{ job.request.settings.duration }}s target · Step {{ job.progress.step }}/{{ job.progress.maxStep }} ({{ job.progress.percent }}%)
- + +{{ job.error }}
No active jobs
@@ -581,5 +589,7 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' }) .m-job-id { color: var(--muted); font-size: 12px; } .m-job-title { font-weight: 600; } .m-job-status { display: flex; justify-content: space-between; margin-top: 6px; font-size: 12px; } +.m-job-progress { display: flex; align-items: center; gap: 8px; margin-top: 6px; } +.m-job-progress-bar { flex: 1; height: 4px; accent-color: var(--amber); } .m-link { border: 0; background: transparent; color: var(--amber); text-align: left; } diff --git a/server/utils/studio2/runner.ts b/server/utils/studio2/runner.ts index feedaae..ed65529 100644 --- a/server/utils/studio2/runner.ts +++ b/server/utils/studio2/runner.ts @@ -125,9 +125,14 @@ async function runQwen21PromptEnhance(r: any, job: Job) { } } r.peGraphId = edit ? 'studio2_qwen21_pe_edit.json' : 'studio2_qwen21_pe_t2i.json' + // PE is stage 1 of the same studio job — not a complete/requeue. + r.progress = null saveRecord(r) const queued = await queuePrompt(graph, job.clientId) r.pePromptId = queued.prompt_id + // watchProgress listens on promptId; point it at the PE Comfy prompt for this stage. + r.promptId = queued.prompt_id + job.promptId = queued.prompt_id saveRecord(r) const history = await waitPromptHistory(r, job, queued.prompt_id) const result = harvestQwen21Pe(history, queued.prompt_id, edit) @@ -142,11 +147,19 @@ async function runQwen21PromptEnhance(r: any, job: Job) { wh_ratio: result.wh_ratio || '', ...(edit ? { ratio_follow: result.ratio_follow || '' } : {}), parse_ok: true, - thinking: result.thinking || '' + thinking: result.thinking || '', + comfyPromptId: queued.prompt_id, } + // Stage flip PE → generate: clear PE prompt id and reset the bar (no leftover 100%). r.pePromptId = '' + r.promptId = '' + job.promptId = '' + r.progress = null saveRecord(r) await freeComfyVram() + // Same job continues into generate — do not complete or requeue. + update(r, 'submitting') + emitJob(job, { type: 'status', message: 'Generating…' }) } async function upload(r: any, name: string, data: Buffer): Promise