pixelkiln 0.30.0 → 0.32.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 +119 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +117 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +118 -22
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +18 -5
- package/docs/GETTING_STARTED.md +2 -1
- package/docs/RECOVERY.md +3 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -8312,8 +8312,14 @@ interface GenerateJob {
|
|
|
8312
8312
|
id: string;
|
|
8313
8313
|
project: string | null;
|
|
8314
8314
|
keys: string[];
|
|
8315
|
-
/**
|
|
8316
|
-
|
|
8315
|
+
/**
|
|
8316
|
+
* `resume` skips submission: poll, review, and fetch existing work at no
|
|
8317
|
+
* cost; `refresh` re-pulls changed objects; `restore` puts the recorded
|
|
8318
|
+
* bytes back on disk from the cache or the provider, as `pixelkiln restore`.
|
|
8319
|
+
*/
|
|
8320
|
+
mode: "generate" | "resume" | "refresh" | "restore";
|
|
8321
|
+
/** The job may replace a locally modified or untracked file, as `--force` does. */
|
|
8322
|
+
force: boolean;
|
|
8317
8323
|
phase: GeneratePhase;
|
|
8318
8324
|
startedAt: string;
|
|
8319
8325
|
finishedAt: string | null;
|
|
@@ -8377,36 +8383,46 @@ interface GalleryGenerateHandlers {
|
|
|
8377
8383
|
declare const GenerateRequestSchema: z.ZodEffects<z.ZodObject<{
|
|
8378
8384
|
project: z.ZodOptional<z.ZodString>;
|
|
8379
8385
|
keys: z.ZodArray<z.ZodString, "many">;
|
|
8380
|
-
/**
|
|
8386
|
+
/**
|
|
8387
|
+
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
8388
|
+
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
8389
|
+
* the recorded bytes back over a modified file.
|
|
8390
|
+
*/
|
|
8381
8391
|
force: z.ZodOptional<z.ZodBoolean>;
|
|
8382
8392
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
8383
8393
|
resume: z.ZodOptional<z.ZodBoolean>;
|
|
8384
8394
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
8385
8395
|
refresh: z.ZodOptional<z.ZodBoolean>;
|
|
8396
|
+
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
8397
|
+
restore: z.ZodOptional<z.ZodBoolean>;
|
|
8386
8398
|
}, "strict", z.ZodTypeAny, {
|
|
8387
8399
|
keys: string[];
|
|
8388
8400
|
resume?: boolean | undefined;
|
|
8389
8401
|
force?: boolean | undefined;
|
|
8390
8402
|
project?: string | undefined;
|
|
8391
8403
|
refresh?: boolean | undefined;
|
|
8404
|
+
restore?: boolean | undefined;
|
|
8392
8405
|
}, {
|
|
8393
8406
|
keys: string[];
|
|
8394
8407
|
resume?: boolean | undefined;
|
|
8395
8408
|
force?: boolean | undefined;
|
|
8396
8409
|
project?: string | undefined;
|
|
8397
8410
|
refresh?: boolean | undefined;
|
|
8411
|
+
restore?: boolean | undefined;
|
|
8398
8412
|
}>, {
|
|
8399
8413
|
keys: string[];
|
|
8400
8414
|
resume?: boolean | undefined;
|
|
8401
8415
|
force?: boolean | undefined;
|
|
8402
8416
|
project?: string | undefined;
|
|
8403
8417
|
refresh?: boolean | undefined;
|
|
8418
|
+
restore?: boolean | undefined;
|
|
8404
8419
|
}, {
|
|
8405
8420
|
keys: string[];
|
|
8406
8421
|
resume?: boolean | undefined;
|
|
8407
8422
|
force?: boolean | undefined;
|
|
8408
8423
|
project?: string | undefined;
|
|
8409
8424
|
refresh?: boolean | undefined;
|
|
8425
|
+
restore?: boolean | undefined;
|
|
8410
8426
|
}>;
|
|
8411
8427
|
declare class GenerateRequestError extends Error {
|
|
8412
8428
|
readonly status: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -8312,8 +8312,14 @@ interface GenerateJob {
|
|
|
8312
8312
|
id: string;
|
|
8313
8313
|
project: string | null;
|
|
8314
8314
|
keys: string[];
|
|
8315
|
-
/**
|
|
8316
|
-
|
|
8315
|
+
/**
|
|
8316
|
+
* `resume` skips submission: poll, review, and fetch existing work at no
|
|
8317
|
+
* cost; `refresh` re-pulls changed objects; `restore` puts the recorded
|
|
8318
|
+
* bytes back on disk from the cache or the provider, as `pixelkiln restore`.
|
|
8319
|
+
*/
|
|
8320
|
+
mode: "generate" | "resume" | "refresh" | "restore";
|
|
8321
|
+
/** The job may replace a locally modified or untracked file, as `--force` does. */
|
|
8322
|
+
force: boolean;
|
|
8317
8323
|
phase: GeneratePhase;
|
|
8318
8324
|
startedAt: string;
|
|
8319
8325
|
finishedAt: string | null;
|
|
@@ -8377,36 +8383,46 @@ interface GalleryGenerateHandlers {
|
|
|
8377
8383
|
declare const GenerateRequestSchema: z.ZodEffects<z.ZodObject<{
|
|
8378
8384
|
project: z.ZodOptional<z.ZodString>;
|
|
8379
8385
|
keys: z.ZodArray<z.ZodString, "many">;
|
|
8380
|
-
/**
|
|
8386
|
+
/**
|
|
8387
|
+
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
8388
|
+
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
8389
|
+
* the recorded bytes back over a modified file.
|
|
8390
|
+
*/
|
|
8381
8391
|
force: z.ZodOptional<z.ZodBoolean>;
|
|
8382
8392
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
8383
8393
|
resume: z.ZodOptional<z.ZodBoolean>;
|
|
8384
8394
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
8385
8395
|
refresh: z.ZodOptional<z.ZodBoolean>;
|
|
8396
|
+
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
8397
|
+
restore: z.ZodOptional<z.ZodBoolean>;
|
|
8386
8398
|
}, "strict", z.ZodTypeAny, {
|
|
8387
8399
|
keys: string[];
|
|
8388
8400
|
resume?: boolean | undefined;
|
|
8389
8401
|
force?: boolean | undefined;
|
|
8390
8402
|
project?: string | undefined;
|
|
8391
8403
|
refresh?: boolean | undefined;
|
|
8404
|
+
restore?: boolean | undefined;
|
|
8392
8405
|
}, {
|
|
8393
8406
|
keys: string[];
|
|
8394
8407
|
resume?: boolean | undefined;
|
|
8395
8408
|
force?: boolean | undefined;
|
|
8396
8409
|
project?: string | undefined;
|
|
8397
8410
|
refresh?: boolean | undefined;
|
|
8411
|
+
restore?: boolean | undefined;
|
|
8398
8412
|
}>, {
|
|
8399
8413
|
keys: string[];
|
|
8400
8414
|
resume?: boolean | undefined;
|
|
8401
8415
|
force?: boolean | undefined;
|
|
8402
8416
|
project?: string | undefined;
|
|
8403
8417
|
refresh?: boolean | undefined;
|
|
8418
|
+
restore?: boolean | undefined;
|
|
8404
8419
|
}, {
|
|
8405
8420
|
keys: string[];
|
|
8406
8421
|
resume?: boolean | undefined;
|
|
8407
8422
|
force?: boolean | undefined;
|
|
8408
8423
|
project?: string | undefined;
|
|
8409
8424
|
refresh?: boolean | undefined;
|
|
8425
|
+
restore?: boolean | undefined;
|
|
8410
8426
|
}>;
|
|
8411
8427
|
declare class GenerateRequestError extends Error {
|
|
8412
8428
|
readonly status: number;
|
package/dist/index.js
CHANGED
|
@@ -7770,7 +7770,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
7770
7770
|
}
|
|
7771
7771
|
|
|
7772
7772
|
// src/pipeline/fetch.ts
|
|
7773
|
-
import { existsSync as existsSync13 } from "fs";
|
|
7773
|
+
import { existsSync as existsSync13, readFileSync as readFileSync3 } from "fs";
|
|
7774
7774
|
import { mkdir as mkdir4, readFile as readFile11, rename as rename4, rm as rm6, writeFile as writeFile4 } from "fs/promises";
|
|
7775
7775
|
import path16 from "path";
|
|
7776
7776
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
@@ -7787,7 +7787,12 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7787
7787
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
7788
7788
|
if (!opts.repair) return false;
|
|
7789
7789
|
const spec = specByKey.get(key);
|
|
7790
|
-
|
|
7790
|
+
if (e.outputs.length === 0 || !spec) return true;
|
|
7791
|
+
return e.outputs.some((output) => {
|
|
7792
|
+
const file = resolveOutputPath(output.path, spec.root);
|
|
7793
|
+
if (!existsSync13(file)) return true;
|
|
7794
|
+
return Boolean(opts.force) && sha256(readFileSync3(file)) !== output.sha256;
|
|
7795
|
+
});
|
|
7791
7796
|
});
|
|
7792
7797
|
const concurrency = Math.min(Math.max(1, opts.concurrency ?? 8), Math.max(1, pending.length));
|
|
7793
7798
|
let cursor = 0;
|
|
@@ -10556,6 +10561,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
10556
10561
|
.sheet.editor-host { width:min(1400px, 96vw); grid-template-rows:auto 1fr; }
|
|
10557
10562
|
.sheet.editor-host .rbar b { font:650 12.5px/1.4 var(--mono); color:var(--text); }
|
|
10558
10563
|
.sheet.editor-host .rbar .acts { margin-left:auto; display:flex; gap:8px; align-items:center; }
|
|
10564
|
+
.sheet.editor-host .rbar .chip { color:var(--dim); font-size:12.5px; display:inline-flex; align-items:center; gap:5px; margin-right:6px; }
|
|
10559
10565
|
.sheet.editor-host .rbar .st { display:inline-flex; align-items:center; gap:6px; font:650 11.5px/1 var(--mono); }
|
|
10560
10566
|
.sheet.editor-host .rbar .msg { color:var(--bad); font-size:12.5px; }
|
|
10561
10567
|
.sheet.editor-host .stage { position:relative; min-height:0; }
|
|
@@ -10603,6 +10609,8 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
10603
10609
|
.tray > button { padding:5px 11px; font-size:12.5px; }
|
|
10604
10610
|
@media (max-width: 900px) { .sheet.review-host, .sheet.compare-host, .sheet.editor-host { width:100vw; } }
|
|
10605
10611
|
.drawer .gen { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }
|
|
10612
|
+
.drawer .readonly-hint { margin-top:10px; font-size:12.5px; line-height:1.5; }
|
|
10613
|
+
.drawer .readonly-hint code { font:12px var(--mono); color:var(--text); }
|
|
10606
10614
|
.pair { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin:8px 0 10px; }
|
|
10607
10615
|
.pair figure { margin:0; border:1px solid var(--line); background:var(--panel-deep); background-image:var(--checker);
|
|
10608
10616
|
background-size:12px 12px; background-position:0 0,6px 6px; display:grid; place-items:center; padding:8px; min-height:96px; }
|
|
@@ -10812,7 +10820,7 @@ function renderJobs() {
|
|
|
10812
10820
|
const row = el('div', 'job');
|
|
10813
10821
|
const ph = el('span', 'ph');
|
|
10814
10822
|
ph.append(el('i', 'dot ' + PHASE_TONE[job.phase]), document.createTextNode(job.phase));
|
|
10815
|
-
const what = (job.mode === 'refresh' ? 'pull upstream for ' : job.mode === 'resume' ? 'resume ' : 'generate ') + job.keys.length + (job.keys.length === 1 ? ' asset' : ' assets') +
|
|
10823
|
+
const what = (job.mode === 'refresh' ? 'pull upstream for ' : job.mode === 'restore' ? 'restore ' : job.mode === 'resume' ? 'resume ' : 'generate ') + job.keys.length + (job.keys.length === 1 ? ' asset' : ' assets') +
|
|
10816
10824
|
(job.project ? ' in ' + job.project : '');
|
|
10817
10825
|
const last = el('span', 'last', what + (job.messages.length ? ' \u2014 ' + job.messages[job.messages.length - 1].trim() : ''));
|
|
10818
10826
|
last.title = job.keys.join('\\n');
|
|
@@ -10937,13 +10945,16 @@ function budgetLine() {
|
|
|
10937
10945
|
|
|
10938
10946
|
// The confirm step: what will be sent, what it is estimated to cost, and what
|
|
10939
10947
|
// this session may still spend. Mirrors gen's "Spend \u2026 on N asset(s)?" prompt.
|
|
10940
|
-
function generateDialog(items, { project = null, force = false, resume = false, refresh = false } = {}) {
|
|
10948
|
+
function generateDialog(items, { project = null, force = false, resume = false, refresh = false, restore = false } = {}) {
|
|
10941
10949
|
const host = $('dialog-host');
|
|
10942
10950
|
host.textContent = '';
|
|
10943
10951
|
const wrap = el('div', 'dialog');
|
|
10944
10952
|
const form = el('form');
|
|
10945
10953
|
const noun = items.length + (items.length === 1 ? ' asset' : ' assets');
|
|
10954
|
+
const free = resume || refresh || restore;
|
|
10955
|
+
const replacing = items.filter((i) => i.state === 'orphaned' && i.outputs.every((o) => o.exists) || i.state === 'untracked');
|
|
10946
10956
|
form.append(el('h3', null, refresh ? 'Pull upstream changes for ' + noun
|
|
10957
|
+
: restore ? 'Restore ' + noun
|
|
10947
10958
|
: resume ? 'Resume ' + noun
|
|
10948
10959
|
: (force ? 'Regenerate ' : 'Generate ') + noun));
|
|
10949
10960
|
const table = el('table');
|
|
@@ -10953,9 +10964,9 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
10953
10964
|
for (const item of items) {
|
|
10954
10965
|
const tr = el('tr');
|
|
10955
10966
|
tr.append(el('td', 'mono', item.key), el('td', null, item.state), el('td', 'n', item.candidates ?? '\u2014'),
|
|
10956
|
-
el('td', 'n',
|
|
10967
|
+
el('td', 'n', free ? 'no cost' : fmtCost(item.costUnit, item.estimatedCost ?? 0)));
|
|
10957
10968
|
table.append(tr);
|
|
10958
|
-
if (!
|
|
10969
|
+
if (!free) {
|
|
10959
10970
|
const g = byProvider.get(item.provider) || { cost: 0, unit: item.costUnit };
|
|
10960
10971
|
g.cost += item.estimatedCost ?? 0; byProvider.set(item.provider, g);
|
|
10961
10972
|
}
|
|
@@ -10964,6 +10975,9 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
10964
10975
|
const sum = el('div', 'sum');
|
|
10965
10976
|
if (refresh) {
|
|
10966
10977
|
sum.append(el('div', null, 'Re-downloads each object from the provider and replaces the local file only when the object changed upstream \u2014 for example after editing it in the provider\u2019s own editor. Unchanged objects are left alone; a file you changed locally is refused. Nothing is submitted.'));
|
|
10978
|
+
} else if (restore) {
|
|
10979
|
+
sum.append(el('div', null, 'Puts the recorded bytes back on disk from the local cache or the provider object. Nothing is generated or submitted.'));
|
|
10980
|
+
if (force && replacing.length) sum.append(el('div', 'warn', 'The file on disk was changed after PixelKiln wrote it. Restoring discards that change. To keep it instead, cancel and use Edit by hand or Edit in browser first: the edit is copied beside the generated file, and the record stays intact.'));
|
|
10967
10981
|
} else if (resume) {
|
|
10968
10982
|
sum.append(el('div', null, 'Polls, reviews, and downloads existing provider work. Nothing is submitted.'));
|
|
10969
10983
|
} else {
|
|
@@ -10976,12 +10990,13 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
10976
10990
|
if (left !== null && g.cost > left) line.className = 'warn';
|
|
10977
10991
|
sum.append(line);
|
|
10978
10992
|
}
|
|
10979
|
-
if (force) sum.append(el('div', 'warn', '
|
|
10993
|
+
if (force && replacing.length) sum.append(el('div', 'warn', (replacing.length === 1 ? 'This file' : replacing.length + ' of these files') + ' changed on disk after PixelKiln wrote it (or was never recorded). Generating replaces it and that change is lost. To keep it, cancel and use Edit by hand or Edit in browser first.'));
|
|
10994
|
+
else if (force) sum.append(el('div', 'warn', 'Regenerating replaces the current art. The previous file is replaced when the new result is fetched; the provider objects it came from are not deleted.'));
|
|
10980
10995
|
sum.append(el('div', null, 'Candidate sets land in review; you choose from them here before anything is downloaded.'));
|
|
10981
10996
|
}
|
|
10982
10997
|
form.append(sum);
|
|
10983
10998
|
const actions = el('div', 'actions');
|
|
10984
|
-
const go = el('button', 'primary', refresh ? 'Pull changes' : resume ? 'Resume' : 'Generate');
|
|
10999
|
+
const go = el('button', 'primary', refresh ? 'Pull changes' : restore ? (force && replacing.length ? 'Restore and discard my change' : 'Restore') : resume ? 'Resume' : force && replacing.length ? 'Generate and replace' : 'Generate');
|
|
10985
11000
|
go.type = 'submit';
|
|
10986
11001
|
const cancel = el('button', null, 'Cancel'); cancel.type = 'button'; cancel.onclick = () => { host.textContent = ''; };
|
|
10987
11002
|
const msg = el('span', 'msg');
|
|
@@ -10996,6 +11011,7 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
10996
11011
|
if (force) body.force = true;
|
|
10997
11012
|
if (resume) body.resume = true;
|
|
10998
11013
|
if (refresh) body.refresh = true;
|
|
11014
|
+
if (restore) body.restore = true;
|
|
10999
11015
|
const job = await postGenerate(body);
|
|
11000
11016
|
host.textContent = '';
|
|
11001
11017
|
GEN.jobs.unshift(job);
|
|
@@ -11084,11 +11100,16 @@ function openEditorSheet(item) {
|
|
|
11084
11100
|
const status = el('span', 'st');
|
|
11085
11101
|
const acts = el('div', 'acts');
|
|
11086
11102
|
const msg = el('span', 'msg');
|
|
11103
|
+
const compare = el('label', 'chip'); compare.hidden = true;
|
|
11104
|
+
compare.title = 'The generated art on a locked layer at 50%, for comparison; never part of the saved file';
|
|
11105
|
+
const compareBox = el('input'); compareBox.type = 'checkbox'; compareBox.checked = true;
|
|
11106
|
+
compareBox.onchange = () => showReference(compareBox.checked);
|
|
11107
|
+
compare.append(compareBox, document.createTextNode(' show generated'));
|
|
11087
11108
|
const save = el('button', 'primary', 'Save to project'); save.type = 'button';
|
|
11088
11109
|
save.onclick = () => requestEditorSave(false);
|
|
11089
11110
|
const saveClose = el('button', null, 'Save & close'); saveClose.type = 'button';
|
|
11090
11111
|
saveClose.onclick = () => requestEditorSave(true);
|
|
11091
|
-
acts.append(msg, save, saveClose);
|
|
11112
|
+
acts.append(msg, compare, save, saveClose);
|
|
11092
11113
|
bar.append(close, title, status, acts);
|
|
11093
11114
|
const stage = el('div', 'stage');
|
|
11094
11115
|
const frame = el('iframe');
|
|
@@ -11101,7 +11122,7 @@ function openEditorSheet(item) {
|
|
|
11101
11122
|
stage.append(frame, loading);
|
|
11102
11123
|
panel.append(bar, stage);
|
|
11103
11124
|
host.append(scrim, panel);
|
|
11104
|
-
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, roles: null };
|
|
11125
|
+
SHEET = { item, frame, loading, status, msg, save, saveClose, compare, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, sentReference: false, reference: false, roles: null };
|
|
11105
11126
|
sheetStatus('cool', 'loading');
|
|
11106
11127
|
sheetButtons();
|
|
11107
11128
|
close.focus({ preventScroll: true });
|
|
@@ -11121,7 +11142,11 @@ async function sendOpen() {
|
|
|
11121
11142
|
sheetStatus('bad', 'unsupported');
|
|
11122
11143
|
return;
|
|
11123
11144
|
}
|
|
11145
|
+
// When an edit is being opened, the generated art rides along as a
|
|
11146
|
+
// reference layer: what the editor shows through at 50% for comparison.
|
|
11147
|
+
const referenceFiles = sources.fromEdit && SHEET.ready.version >= 4 && item.outputs.every((o) => o.exists && o.url) ? item.outputs : null;
|
|
11124
11148
|
const pngs = [];
|
|
11149
|
+
const reference = [];
|
|
11125
11150
|
let pxo = null;
|
|
11126
11151
|
try {
|
|
11127
11152
|
for (const file of sources.files) {
|
|
@@ -11133,6 +11158,10 @@ async function sendOpen() {
|
|
|
11133
11158
|
const p = await fetch(projectUrl, { cache: 'no-store' });
|
|
11134
11159
|
if (p.ok) pxo = await p.arrayBuffer();
|
|
11135
11160
|
}
|
|
11161
|
+
for (const file of referenceFiles || []) {
|
|
11162
|
+
const res = await fetch(file.url, { cache: 'no-store' });
|
|
11163
|
+
if (res.ok) reference.push({ role: file.role || null, png: await res.arrayBuffer() });
|
|
11164
|
+
}
|
|
11136
11165
|
} catch (err) {
|
|
11137
11166
|
if (SHEET) { SHEET.msg.textContent = 'Could not load the image: ' + err.message; sheetStatus('bad', 'failed'); }
|
|
11138
11167
|
return;
|
|
@@ -11157,9 +11186,18 @@ async function sendOpen() {
|
|
|
11157
11186
|
transfer.push(message.png);
|
|
11158
11187
|
}
|
|
11159
11188
|
if (pxo) { message.pxo = pxo; transfer.push(pxo); }
|
|
11189
|
+
if (reference.length === (referenceFiles || []).length && reference.length) {
|
|
11190
|
+
message.reference = reference;
|
|
11191
|
+
for (const r of reference) transfer.push(r.png);
|
|
11192
|
+
}
|
|
11193
|
+
SHEET.sentReference = !!message.reference;
|
|
11160
11194
|
SHEET.frame.contentWindow.postMessage(message, location.origin, transfer);
|
|
11161
11195
|
sheetStatus('cool', 'opening');
|
|
11162
11196
|
}
|
|
11197
|
+
function showReference(visible) {
|
|
11198
|
+
if (!SHEET || !SHEET.opened || !SHEET.reference) return;
|
|
11199
|
+
SHEET.frame.contentWindow.postMessage({ type: 'pixelkiln:reference', visible }, location.origin);
|
|
11200
|
+
}
|
|
11163
11201
|
function requestEditorSave(close) {
|
|
11164
11202
|
if (!SHEET || !SHEET.opened || SHEET.pending) return;
|
|
11165
11203
|
const request = 'save-' + (++SHEET.requests);
|
|
@@ -11183,10 +11221,13 @@ async function onEditorMessage(m) {
|
|
|
11183
11221
|
case 'pixelkiln:opened':
|
|
11184
11222
|
SHEET.opened = true;
|
|
11185
11223
|
SHEET.loading.remove();
|
|
11224
|
+
SHEET.reference = !!m.reference;
|
|
11225
|
+
SHEET.compare.hidden = !SHEET.reference;
|
|
11186
11226
|
if (m.source === 'pxo') sheetStatus('ok', 'editing the edit file' + (m.frames > 1 ? 's' : '') + ' with ' + (m.layers === 1 ? 'the layer' : m.layers + ' layers') + (m.frames > 1 ? ' and ' + m.frames + ' ' + memberNoun(SHEET.item, m.frames) : '') + ' restored');
|
|
11187
11227
|
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file' + (m.frames > 1 ? 's' : '') + '; the layer file could not be opened');
|
|
11188
11228
|
else if (m.source === 'frames') sheetStatus('ok', (SHEET.item.edits.length ? 'editing the edit files' : 'editing a copy of the generated ' + memberNoun(SHEET.item, m.frames)) + ' as ' + m.frames + ' frames' + (isFrameSet(SHEET.item) ? '' : ', one per ' + memberNoun(SHEET.item, 1)));
|
|
11189
11229
|
else sheetStatus('ok', SHEET.item.edit && SHEET.item.edit.exists ? 'editing the edit file' : 'editing a copy of the generated art');
|
|
11230
|
+
if (SHEET.reference) SHEET.status.append(document.createTextNode(' \xB7 generated art shown through at 50%'));
|
|
11190
11231
|
sheetButtons();
|
|
11191
11232
|
break;
|
|
11192
11233
|
case 'pixelkiln:dirty':
|
|
@@ -11629,6 +11670,13 @@ function generateActions(item) {
|
|
|
11629
11670
|
};
|
|
11630
11671
|
if (item.state === 'missing' || item.state === 'stale' || item.state === 'failed') add('Generate' + cost, 'primary', {});
|
|
11631
11672
|
else if (item.state === 'ok') add('Regenerate' + cost, null, { force: true });
|
|
11673
|
+
else if (item.state === 'orphaned' && item.outputs.every((o) => o.exists)) {
|
|
11674
|
+
// The file changed after download. Putting the recorded bytes back and
|
|
11675
|
+
// regenerating both discard that change, so both say so and ask.
|
|
11676
|
+
add('Restore recorded bytes \xB7 no cost', null, { restore: true, force: true });
|
|
11677
|
+
add('Regenerate' + cost, null, { force: true });
|
|
11678
|
+
} else if (item.state === 'orphaned') add('Restore \xB7 no cost', 'primary', { restore: true });
|
|
11679
|
+
else if (item.state === 'untracked') add('Generate' + cost + ' \xB7 replaces the file', null, { force: true });
|
|
11632
11680
|
if (item.state === 'in-flight' || item.state === 'recoverable') {
|
|
11633
11681
|
// A job already parked this asset in review: go straight to its sheet.
|
|
11634
11682
|
const holder = GEN.jobs.find((job) => job.phase === 'review' && job.project === item.project && job.review.includes(item.key));
|
|
@@ -12319,6 +12367,15 @@ function renderDrawer() {
|
|
|
12319
12367
|
if (ui.notice && ui.notice.id === item.id) body.append(el('div', 'notice', ui.notice.text));
|
|
12320
12368
|
body.append(stateNode(item.state, item.reason));
|
|
12321
12369
|
if (GENERATION) body.append(generateActions(item));
|
|
12370
|
+
// Writes are opt-in flags on the command line; say so where the buttons would be.
|
|
12371
|
+
if ((!EDITABLE || !GENERATION) && item.declared) {
|
|
12372
|
+
const missing = [];
|
|
12373
|
+
if (!EDITABLE) missing.push('--edit to change its prompt, size, and tags or open it in the browser editor');
|
|
12374
|
+
if (!GENERATION) missing.push('--budget <n> to generate or regenerate it from here');
|
|
12375
|
+
const hint = el('div', 'state-dim readonly-hint');
|
|
12376
|
+
hint.append(document.createTextNode('This gallery is read-only. Restart it with '), el('code', null, 'pixelkiln gallery'), document.createTextNode(' plus ' + missing.join(', and ') + '.'));
|
|
12377
|
+
body.append(hint);
|
|
12378
|
+
}
|
|
12322
12379
|
if (canEdit && ui.editing === item.id) body.append(editForm(item));
|
|
12323
12380
|
// Plan already quotes the error as the reason for a failed entry; only a
|
|
12324
12381
|
// stale or superseded failure needs its own line.
|
|
@@ -12629,8 +12686,8 @@ var pin_default = {
|
|
|
12629
12686
|
pixelorama: "v1.2.2",
|
|
12630
12687
|
godot: "4.7.2",
|
|
12631
12688
|
extensionsApi: 9,
|
|
12632
|
-
protocol:
|
|
12633
|
-
release: "editor-pixelorama-v1.2.2-pk.
|
|
12689
|
+
protocol: 4,
|
|
12690
|
+
release: "editor-pixelorama-v1.2.2-pk.18",
|
|
12634
12691
|
files: {
|
|
12635
12692
|
"index.apple-touch-icon.png": {
|
|
12636
12693
|
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
@@ -12645,7 +12702,7 @@ var pin_default = {
|
|
|
12645
12702
|
bytes: 7298
|
|
12646
12703
|
},
|
|
12647
12704
|
"index.html": {
|
|
12648
|
-
sha256: "
|
|
12705
|
+
sha256: "d9adf06036660c4b76ef5386e872bc03dc0d5b0628fd6ac660d480893ee1f47a",
|
|
12649
12706
|
bytes: 5442
|
|
12650
12707
|
},
|
|
12651
12708
|
"index.icon.png": {
|
|
@@ -12657,8 +12714,8 @@ var pin_default = {
|
|
|
12657
12714
|
bytes: 279815
|
|
12658
12715
|
},
|
|
12659
12716
|
"index.pck": {
|
|
12660
|
-
sha256: "
|
|
12661
|
-
bytes:
|
|
12717
|
+
sha256: "a41d9c3a39aa99ea5ee49ddcedfeb8b092006aa75c0de67139f13297c17f42e4",
|
|
12718
|
+
bytes: 6431692
|
|
12662
12719
|
},
|
|
12663
12720
|
"index.png": {
|
|
12664
12721
|
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|
|
@@ -13207,13 +13264,21 @@ import { z as z10 } from "zod";
|
|
|
13207
13264
|
var GenerateRequestSchema = z10.object({
|
|
13208
13265
|
project: z10.string().min(1).optional(),
|
|
13209
13266
|
keys: z10.array(z10.string().min(1)).min(1).max(500),
|
|
13210
|
-
/**
|
|
13267
|
+
/**
|
|
13268
|
+
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
13269
|
+
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
13270
|
+
* the recorded bytes back over a modified file.
|
|
13271
|
+
*/
|
|
13211
13272
|
force: z10.boolean().optional(),
|
|
13212
13273
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
13213
13274
|
resume: z10.boolean().optional(),
|
|
13214
13275
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
13215
|
-
refresh: z10.boolean().optional()
|
|
13216
|
-
|
|
13276
|
+
refresh: z10.boolean().optional(),
|
|
13277
|
+
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
13278
|
+
restore: z10.boolean().optional()
|
|
13279
|
+
}).strict().refine((request) => [request.resume, request.refresh, request.restore].filter(Boolean).length <= 1, {
|
|
13280
|
+
message: "resume, refresh, and restore are exclusive"
|
|
13281
|
+
});
|
|
13217
13282
|
var GenerateRequestError = class extends Error {
|
|
13218
13283
|
constructor(message6, status = 400) {
|
|
13219
13284
|
super(message6);
|
|
@@ -13301,7 +13366,9 @@ function createGenerateHandlers(opts) {
|
|
|
13301
13366
|
for (const [providerId, providerSpecs] of groupByRecordedProvider(specs, ctx.lock)) {
|
|
13302
13367
|
const provider = opts.providerFor(job.project ?? void 0, providerId);
|
|
13303
13368
|
const res = await fetchAssets(provider, providerSpecs, ctx.lock, ctx.lockPath, {
|
|
13304
|
-
onProgress: (line) => say(job, line)
|
|
13369
|
+
onProgress: (line) => say(job, line),
|
|
13370
|
+
// A forced regeneration replaces whatever is on disk, as `gen --force`.
|
|
13371
|
+
force: job.force
|
|
13305
13372
|
});
|
|
13306
13373
|
job.counts.downloaded += res.downloaded;
|
|
13307
13374
|
if (res.downloaded || res.failed) {
|
|
@@ -13316,12 +13383,32 @@ function createGenerateHandlers(opts) {
|
|
|
13316
13383
|
job.finishedAt = now().toISOString();
|
|
13317
13384
|
}
|
|
13318
13385
|
}
|
|
13386
|
+
async function restore(job, ctx, specs) {
|
|
13387
|
+
job.phase = "fetching";
|
|
13388
|
+
for (const [providerId, providerSpecs] of groupByRecordedProvider(specs, ctx.lock)) {
|
|
13389
|
+
const provider = opts.providerFor(job.project ?? void 0, providerId);
|
|
13390
|
+
const res = await fetchAssets(provider, providerSpecs, ctx.lock, ctx.lockPath, {
|
|
13391
|
+
onProgress: (line) => say(job, line),
|
|
13392
|
+
repair: true,
|
|
13393
|
+
force: job.force
|
|
13394
|
+
});
|
|
13395
|
+
job.counts.downloaded += res.downloaded;
|
|
13396
|
+
say(job, `${providerId}: restored ${res.downloaded}, skipped ${res.skipped}, failed ${res.failed}`);
|
|
13397
|
+
if (res.failed) throw new Error(`${res.failed} file(s) could not be restored`);
|
|
13398
|
+
}
|
|
13399
|
+
job.phase = "done";
|
|
13400
|
+
job.finishedAt = now().toISOString();
|
|
13401
|
+
}
|
|
13319
13402
|
async function run(job, ctx, specs, groups) {
|
|
13320
13403
|
try {
|
|
13321
13404
|
if (job.mode === "refresh") {
|
|
13322
13405
|
await refresh(job, ctx, specs);
|
|
13323
13406
|
return;
|
|
13324
13407
|
}
|
|
13408
|
+
if (job.mode === "restore") {
|
|
13409
|
+
await restore(job, ctx, specs);
|
|
13410
|
+
return;
|
|
13411
|
+
}
|
|
13325
13412
|
if (job.mode === "generate") {
|
|
13326
13413
|
job.phase = "submitting";
|
|
13327
13414
|
for (const group of groups) {
|
|
@@ -13384,8 +13471,16 @@ function createGenerateHandlers(opts) {
|
|
|
13384
13471
|
const holder = busy.get(`${request.project ?? ""}:${key}`);
|
|
13385
13472
|
if (holder) throw new GenerateRequestError(`"${key}" is already being worked on by job ${holder}`, 409);
|
|
13386
13473
|
}
|
|
13474
|
+
if (request.restore) {
|
|
13475
|
+
for (const key of request.keys) {
|
|
13476
|
+
const entry = ctx.lock.entries[key];
|
|
13477
|
+
if (!entry || entry.status !== "downloaded" || !entry.outputs.length) {
|
|
13478
|
+
throw new GenerateRequestError(`"${key}" has no downloaded output recorded to restore`);
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13481
|
+
}
|
|
13387
13482
|
let groups = [];
|
|
13388
|
-
if (!request.resume && !request.refresh) {
|
|
13483
|
+
if (!request.resume && !request.refresh && !request.restore) {
|
|
13389
13484
|
const plan = await buildPlan(specs, ctx.lock, { force: request.force });
|
|
13390
13485
|
if (!plan.actionable.length) {
|
|
13391
13486
|
const reasons = plan.items.map((item) => `${item.key}: ${item.state} \u2014 ${item.reason}`).slice(0, 5);
|
|
@@ -13405,7 +13500,8 @@ function createGenerateHandlers(opts) {
|
|
|
13405
13500
|
id: randomBytes2(8).toString("hex"),
|
|
13406
13501
|
project: request.project ?? null,
|
|
13407
13502
|
keys: request.keys,
|
|
13408
|
-
mode: request.refresh ? "refresh" : request.resume ? "resume" : "generate",
|
|
13503
|
+
mode: request.refresh ? "refresh" : request.resume ? "resume" : request.restore ? "restore" : "generate",
|
|
13504
|
+
force: Boolean(request.force),
|
|
13409
13505
|
phase: "queued",
|
|
13410
13506
|
startedAt: now().toISOString(),
|
|
13411
13507
|
finishedAt: null,
|
|
@@ -13417,7 +13513,7 @@ function createGenerateHandlers(opts) {
|
|
|
13417
13513
|
};
|
|
13418
13514
|
jobs.set(job.id, job);
|
|
13419
13515
|
contexts.set(job.id, ctx);
|
|
13420
|
-
say(job, request.refresh ? `checking ${request.keys.length} asset(s) upstream at no cost` : request.resume ? `resuming ${request.keys.length} asset(s) at no cost` : `generating ${groups.reduce((n, g) => n + g.actionable.length, 0)} asset(s): ` + groups.map((g) => `${g.provider} ${formatCost(g.costUnit, g.cost)}`).join("; "));
|
|
13516
|
+
say(job, request.refresh ? `checking ${request.keys.length} asset(s) upstream at no cost` : request.restore ? `restoring ${request.keys.length} asset(s) at no cost${request.force ? " (replacing modified files)" : ""}` : request.resume ? `resuming ${request.keys.length} asset(s) at no cost` : `generating ${groups.reduce((n, g) => n + g.actionable.length, 0)} asset(s): ` + groups.map((g) => `${g.provider} ${formatCost(g.costUnit, g.cost)}`).join("; "));
|
|
13421
13517
|
void run(job, ctx, specs, groups);
|
|
13422
13518
|
return job;
|
|
13423
13519
|
},
|