pixelkiln 0.32.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +76 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +76 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +76 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2488,6 +2488,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2488
2488
|
if (obj.status === "failed") return { status: "failed", error: "generation failed upstream" };
|
|
2489
2489
|
return { status: "processing" };
|
|
2490
2490
|
} catch (err) {
|
|
2491
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
2491
2492
|
if (!(err instanceof PixelLabError) || err.status !== 404) throw err;
|
|
2492
2493
|
const survivor = await this.client.getObject(jobId).catch(() => null);
|
|
2493
2494
|
const url = firstUrl(survivor?.rotation_urls) ?? survivor?.preview_url ?? null;
|
|
@@ -9431,6 +9432,18 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
9431
9432
|
background-size:12px 12px; background-position:0 0,6px 6px; display:grid; place-items:center; padding:8px; min-height:96px; }
|
|
9432
9433
|
.pair figure img { image-rendering:pixelated; display:block; max-width:100%; }
|
|
9433
9434
|
.pair figcaption { font:11px/1.4 var(--mono); color:var(--dim); background:var(--panel); padding:2px 6px; margin-top:6px; }
|
|
9435
|
+
.dot.busy { background:var(--cool); animation: pulse 1.2s ease-in-out infinite; }
|
|
9436
|
+
@keyframes pulse { 0%, 100% { opacity:1; } 50% { opacity:.25; } }
|
|
9437
|
+
@media (prefers-reduced-motion: reduce) { .dot.busy { animation:none; } }
|
|
9438
|
+
.card .busy-badge { position:absolute; left:6px; bottom:6px; display:inline-flex; align-items:center; gap:5px;
|
|
9439
|
+
background:var(--panel); color:var(--cool); border:1px solid var(--cool); padding:2px 7px; font:650 10.5px/1.4 var(--mono); }
|
|
9440
|
+
.drawer .job-strip { display:grid; grid-template-columns:auto 1fr; gap:4px 12px; align-items:baseline; margin-top:10px;
|
|
9441
|
+
padding:10px 12px; border:1px solid var(--cool); background:color-mix(in srgb, var(--cool) 8%, var(--panel-deep)); font-size:12.5px; }
|
|
9442
|
+
.drawer .job-strip .ph { display:inline-flex; align-items:center; gap:6px; font:650 12px/1.4 var(--mono); color:var(--cool); white-space:nowrap; }
|
|
9443
|
+
.drawer .job-strip .last { color:var(--dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }
|
|
9444
|
+
.drawer .job-strip .what { grid-column:1 / -1; color:var(--text); }
|
|
9445
|
+
.drawer .job-strip .acts { grid-column:1 / -1; display:flex; gap:6px; }
|
|
9446
|
+
.drawer .job-strip .acts button { padding:3px 9px; font-size:12px; }
|
|
9434
9447
|
.card .pen { position:absolute; right:6px; top:6px; background:var(--panel); color:var(--accent-soft); border:1px solid var(--accent);
|
|
9435
9448
|
font:700 11px/1 var(--mono); padding:3px 5px; }
|
|
9436
9449
|
.hand-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; }
|
|
@@ -9589,6 +9602,11 @@ const splitTags = (value) => value.split(',').map((t) => t.trim()).filter(Boolea
|
|
|
9589
9602
|
|
|
9590
9603
|
const ACTIVE_PHASES = new Set(['queued', 'submitting', 'polling', 'fetching', 'review']);
|
|
9591
9604
|
const PHASE_TONE = { queued: 'cool', submitting: 'cool', polling: 'cool', fetching: 'cool', review: 'warn', done: 'ok', failed: 'bad' };
|
|
9605
|
+
// The job still working on this record, if any: what the drawer and its card
|
|
9606
|
+
// show while a regeneration, restore, or pull is in flight.
|
|
9607
|
+
const activeJobFor = (item) => GEN.jobs.find((job) => ACTIVE_PHASES.has(job.phase) && job.project === item.project && job.keys.includes(item.key)) || null;
|
|
9608
|
+
const JOB_VERB = { generate: 'regenerating', resume: 'resuming', refresh: 'pulling upstream changes for', restore: 'restoring' };
|
|
9609
|
+
const PHASE_TEXT = { queued: 'queued', submitting: 'submitting to the provider', polling: 'waiting for the provider to finish', fetching: 'downloading the result', review: 'candidates are ready to review' };
|
|
9592
9610
|
const remainingBudget = (provider) => {
|
|
9593
9611
|
const keyed = GEN.budget.byProvider[provider];
|
|
9594
9612
|
const ceiling = keyed !== undefined ? keyed : GEN.budget.amount;
|
|
@@ -9618,10 +9636,33 @@ async function pollJobs() {
|
|
|
9618
9636
|
renderHeader();
|
|
9619
9637
|
let changed = false;
|
|
9620
9638
|
for (const job of GEN.jobs) {
|
|
9621
|
-
if (!ACTIVE_PHASES.has(job.phase) && !ui.settled.has(job.id)) {
|
|
9639
|
+
if (!ACTIVE_PHASES.has(job.phase) && !ui.settled.has(job.id)) {
|
|
9640
|
+
ui.settled.add(job.id); changed = true;
|
|
9641
|
+
// The open record hears how its job ended, where the strip was.
|
|
9642
|
+
const open = ui.open && snap.items.find((i) => i.id === ui.open);
|
|
9643
|
+
if (open && job.project === open.project && job.keys.includes(open.key)) {
|
|
9644
|
+
ui.notice = { id: open.id, text: job.phase === 'failed'
|
|
9645
|
+
? (JOB_VERB[job.mode] || job.mode).replace(/ing$/, 'ing') + ' this asset failed: ' + (job.error || 'see the log above')
|
|
9646
|
+
: job.mode === 'generate' ? 'Regenerated. The new result is on disk and recorded as this generation.'
|
|
9647
|
+
: job.mode === 'restore' ? 'Restored. The recorded bytes are back on disk.'
|
|
9648
|
+
: job.mode === 'refresh' ? (job.counts.downloaded ? 'Pulled the upstream change; the lockfile records the new bytes.' : 'Nothing changed upstream; the file was left alone.')
|
|
9649
|
+
: 'Done.' };
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9622
9652
|
if (job.phase === 'review' && !ui.settled.has(job.id + ':review')) { ui.settled.add(job.id + ':review'); changed = true; }
|
|
9623
9653
|
}
|
|
9624
9654
|
if (changed) refresh();
|
|
9655
|
+
else {
|
|
9656
|
+
// A job in flight: keep the open record's strip and the card badges honest without a full re-render.
|
|
9657
|
+
if (ui.open && !$('dialog-host').childNodes.length) renderDrawer();
|
|
9658
|
+
for (const c of document.querySelectorAll('.card')) {
|
|
9659
|
+
const item = snap.items.find((i) => i.id === c.dataset.key);
|
|
9660
|
+
const job = item && activeJobFor(item);
|
|
9661
|
+
const badge = c.querySelector('.busy-badge');
|
|
9662
|
+
if (job && !badge) c.replaceWith(card(item));
|
|
9663
|
+
else if (!job && badge) badge.remove();
|
|
9664
|
+
}
|
|
9665
|
+
}
|
|
9625
9666
|
const active = GEN.jobs.some((job) => ACTIVE_PHASES.has(job.phase) && job.phase !== 'review');
|
|
9626
9667
|
clearTimeout(jobsTimer);
|
|
9627
9668
|
if (active) jobsTimer = setTimeout(pollJobs, 2000);
|
|
@@ -10270,6 +10311,13 @@ function card(item) {
|
|
|
10270
10311
|
if (item.editStatus === 'edited' || item.editStatus === 'regenerated-since') {
|
|
10271
10312
|
const pen = el('span', 'pen', '\u270E'); pen.title = 'hand-edited'; cell.append(pen);
|
|
10272
10313
|
}
|
|
10314
|
+
const job = activeJobFor(item);
|
|
10315
|
+
if (job) {
|
|
10316
|
+
const badge = el('span', 'busy-badge');
|
|
10317
|
+
badge.append(el('i', 'dot busy'), document.createTextNode(job.phase === 'review' ? 'review' : job.mode === 'generate' ? 'generating' : job.mode));
|
|
10318
|
+
badge.title = (JOB_VERB[job.mode] || job.mode) + ' this asset \u2014 ' + (PHASE_TEXT[job.phase] || job.phase);
|
|
10319
|
+
cell.append(badge);
|
|
10320
|
+
}
|
|
10273
10321
|
c.append(cell, body);
|
|
10274
10322
|
c.onclick = (e) => { if (e.shiftKey) toggleCompare(item.id); else openItem(item.id); };
|
|
10275
10323
|
return c;
|
|
@@ -10476,6 +10524,30 @@ function upstreamSection(item) {
|
|
|
10476
10524
|
return s;
|
|
10477
10525
|
}
|
|
10478
10526
|
|
|
10527
|
+
// What a job is doing to this record right now. The record's state line still
|
|
10528
|
+
// says what is on disk; this says what is about to replace it.
|
|
10529
|
+
function jobStrip(item, job) {
|
|
10530
|
+
const strip = el('div', 'job-strip');
|
|
10531
|
+
const ph = el('span', 'ph');
|
|
10532
|
+
ph.append(el('i', 'dot busy'), document.createTextNode(job.phase));
|
|
10533
|
+
const last = el('span', 'last', job.messages.length ? job.messages[job.messages.length - 1].trim() : '');
|
|
10534
|
+
last.title = job.messages.join('\\n');
|
|
10535
|
+
const what = el('span', 'what', (JOB_VERB[job.mode] || job.mode) + ' this asset \u2014 ' + (PHASE_TEXT[job.phase] || job.phase) +
|
|
10536
|
+
(job.mode === 'generate' && job.phase !== 'review' ? '. The current file stays until the new result is downloaded.' : '.'));
|
|
10537
|
+
strip.append(ph, last, what);
|
|
10538
|
+
const acts = el('div', 'acts');
|
|
10539
|
+
if (job.phase === 'review' && job.review.includes(item.key)) {
|
|
10540
|
+
const b = el('button', 'primary', 'Review candidates'); b.type = 'button'; b.onclick = () => openReview(job.id);
|
|
10541
|
+
acts.append(b);
|
|
10542
|
+
}
|
|
10543
|
+
const logBtn = el('button', null, ui.logs.has(job.id) ? 'Hide log' : 'Log'); logBtn.type = 'button';
|
|
10544
|
+
logBtn.onclick = () => { ui.logs.has(job.id) ? ui.logs.delete(job.id) : ui.logs.add(job.id); renderDrawer(); };
|
|
10545
|
+
acts.append(logBtn);
|
|
10546
|
+
strip.append(acts);
|
|
10547
|
+
if (ui.logs.has(job.id)) { const pre = el('pre', null, job.messages.join('\\n')); pre.style.gridColumn = '1 / -1'; strip.append(pre); }
|
|
10548
|
+
return strip;
|
|
10549
|
+
}
|
|
10550
|
+
|
|
10479
10551
|
function generateActions(item) {
|
|
10480
10552
|
const row = el('div', 'gen');
|
|
10481
10553
|
if (!GENERATION || !item.declared || item.currentSpecHash === null) return row;
|
|
@@ -11183,7 +11255,9 @@ function renderDrawer() {
|
|
|
11183
11255
|
body.append(previewHost);
|
|
11184
11256
|
if (ui.notice && ui.notice.id === item.id) body.append(el('div', 'notice', ui.notice.text));
|
|
11185
11257
|
body.append(stateNode(item.state, item.reason));
|
|
11186
|
-
|
|
11258
|
+
const job = GENERATION ? activeJobFor(item) : null;
|
|
11259
|
+
if (job) body.append(jobStrip(item, job));
|
|
11260
|
+
else if (GENERATION) body.append(generateActions(item));
|
|
11187
11261
|
// Writes are opt-in flags on the command line; say so where the buttons would be.
|
|
11188
11262
|
if ((!EDITABLE || !GENERATION) && item.declared) {
|
|
11189
11263
|
const missing = [];
|