pixelkiln 0.52.0 → 0.53.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 +16 -1
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +73 -2
- package/dist/index.cjs +16 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/docs/REVISIONS.md +10 -0
- package/package.json +1 -1
- package/skills/pixelkiln/references/revisions.md +4 -0
package/dist/client/gallery.js
CHANGED
|
@@ -111,6 +111,20 @@ const paletteRuleOf = (item) => {
|
|
|
111
111
|
const style = snap.styles.find((s) => s.id === item.styleId && s.project === item.project);
|
|
112
112
|
return style && style.enforcePalette && style.palette.length >= 2 ? style.palette : null;
|
|
113
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Whether this item has usable pixels to revise: committed `source` art
|
|
116
|
+
* always qualifies, otherwise it needs a current, downloaded generation, or
|
|
117
|
+
* (for a quality-gated style) a current, human-approved refined output. This
|
|
118
|
+
* mirrors docs/REVISIONS.md's dependency gate closely enough to gate the
|
|
119
|
+
* button; the server's own plan is still the final word once the revision
|
|
120
|
+
* asset exists, and reports `blocked` with the real reason if this guess
|
|
121
|
+
* were ever wrong.
|
|
122
|
+
*/
|
|
123
|
+
const isRevisable = (item) => {
|
|
124
|
+
if (item.source) return true;
|
|
125
|
+
if (item.quality) return item.quality.review?.status === 'approved' && !!item.quality.check?.current;
|
|
126
|
+
return item.status === 'downloaded' && item.state === 'ok';
|
|
127
|
+
};
|
|
114
128
|
const activeJobFor = (item) => GEN.jobs.find((job) => ACTIVE_PHASES.has(job.phase) && job.project === item.project && job.keys.includes(item.key)) || null;
|
|
115
129
|
const JOB_VERB = { generate: 'regenerating', resume: 'resuming', refresh: 'pulling upstream changes for', restore: 'restoring', revert: 'bringing back a previous generation of' };
|
|
116
130
|
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' };
|
|
@@ -1570,7 +1584,7 @@ function addAssetForm(style) {
|
|
|
1570
1584
|
const form = el('form', 'edit');
|
|
1571
1585
|
form.append(el('h3', null, 'New asset in ' + style.id));
|
|
1572
1586
|
const id = el('input'); id.type = 'text'; id.placeholder = 'asset-id'; id.required = true; id.autocomplete = 'off';
|
|
1573
|
-
id.pattern = '[
|
|
1587
|
+
id.pattern = '[^\\/\\\\]+';
|
|
1574
1588
|
const prompt = el('textarea'); prompt.placeholder = 'What to generate. The style adds its prefix and suffix.'; prompt.required = true;
|
|
1575
1589
|
const width = numberInput(null, 'style default'), height = numberInput(null, 'style default');
|
|
1576
1590
|
const category = el('input'); category.type = 'text'; category.placeholder = 'optional subfolder';
|
|
@@ -1613,6 +1627,54 @@ function addAssetForm(style) {
|
|
|
1613
1627
|
return form;
|
|
1614
1628
|
}
|
|
1615
1629
|
|
|
1630
|
+
/**
|
|
1631
|
+
* A new `image-to-image` revision of `item`, restricted to `item`'s own
|
|
1632
|
+
* style (a revision's `from` resolves per style, and this button starts
|
|
1633
|
+
* from one specific generation). `inpaint` needs a mask upload the gallery
|
|
1634
|
+
* does not offer yet, so this form only ever creates `image-to-image`.
|
|
1635
|
+
*/
|
|
1636
|
+
function newRevisionForm(item) {
|
|
1637
|
+
const pr = projectOf(item);
|
|
1638
|
+
const form = el('form', 'edit');
|
|
1639
|
+
form.append(el('h3', null, 'New revision of ' + item.assetId));
|
|
1640
|
+
const id = el('input'); id.type = 'text'; id.placeholder = 'asset-id'; id.required = true; id.autocomplete = 'off';
|
|
1641
|
+
id.pattern = '[^\\/\\\\]+';
|
|
1642
|
+
const prompt = el('textarea'); prompt.placeholder = 'Edit instruction, e.g. "add snow on the roof".'; prompt.required = true;
|
|
1643
|
+
const strength = el('input'); strength.type = 'number'; strength.min = '0'; strength.max = '1'; strength.step = '0.05'; strength.placeholder = 'provider default';
|
|
1644
|
+
const row1 = el('div', 'row');
|
|
1645
|
+
row1.append(field('new asset id', id), field('strength', strength, 'PixelLab rejects an explicit strength for image-to-image; leave this blank there.'));
|
|
1646
|
+
form.append(row1, field('edit instruction', prompt, 'The style still adds its prefix and suffix.'));
|
|
1647
|
+
const actions = el('div', 'actions');
|
|
1648
|
+
const save = el('button', 'primary', 'Create revision'); save.type = 'submit';
|
|
1649
|
+
const cancel = el('button', null, 'Cancel'); cancel.type = 'button'; cancel.onclick = () => { ui.editing = null; renderDrawer(); };
|
|
1650
|
+
const msg = el('span', 'msg');
|
|
1651
|
+
actions.append(save, cancel, msg);
|
|
1652
|
+
form.append(actions);
|
|
1653
|
+
form.onsubmit = async (e) => {
|
|
1654
|
+
e.preventDefault();
|
|
1655
|
+
save.disabled = true; msg.className = 'msg'; msg.textContent = 'saving…';
|
|
1656
|
+
const revision = { mode: 'image-to-image', from: item.assetId };
|
|
1657
|
+
if (strength.value.trim() !== '') revision.strength = Number(strength.value);
|
|
1658
|
+
const asset = { prompt: prompt.value, styles: [item.styleId], revision };
|
|
1659
|
+
try {
|
|
1660
|
+
const body = { action: 'add-asset', assetId: id.value.trim(), expectedSha256: pr.manifestSha256, asset };
|
|
1661
|
+
if (item.project) body.project = item.project;
|
|
1662
|
+
snap = await postEdit(body);
|
|
1663
|
+
const newId = (item.project ? item.project + ':' : '') + item.styleId + '/' + id.value.trim();
|
|
1664
|
+
ui.editing = null;
|
|
1665
|
+
ui.notice = { id: newId, text: 'Added to the manifest. Nothing is generated until you run pixelkiln gen.' };
|
|
1666
|
+
render();
|
|
1667
|
+
if (snap.items.some((i) => i.id === newId)) openItem(newId);
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
save.disabled = false;
|
|
1670
|
+
msg.className = 'msg bad';
|
|
1671
|
+
msg.textContent = err.message + (err.status === 409 ? ' Press Refresh.' : '');
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
setTimeout(() => id.focus(), 0);
|
|
1675
|
+
return form;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1616
1678
|
function editForm(item) {
|
|
1617
1679
|
const pr = projectOf(item);
|
|
1618
1680
|
const a = item.asset;
|
|
@@ -1980,9 +2042,18 @@ function renderDrawer() {
|
|
|
1980
2042
|
body.append(s);
|
|
1981
2043
|
}
|
|
1982
2044
|
const children = snap.items.filter((i) => i.revisionParentKey === item.key && i.project === item.project);
|
|
1983
|
-
|
|
2045
|
+
const reviseKey = 'revise:' + item.id;
|
|
2046
|
+
const canRevise = canEdit && isRevisable(item);
|
|
2047
|
+
if (children.length || canRevise) {
|
|
1984
2048
|
const { s, dl } = section('Revisions from this asset');
|
|
1985
2049
|
for (const c of children) row(dl, c.revision ? c.revision.mode : 'child', keyLink(item, c.key));
|
|
2050
|
+
if (canRevise) {
|
|
2051
|
+
const revise = el('button', ui.editing === reviseKey ? null : 'add', ui.editing === reviseKey ? 'Cancel' : '+ New revision');
|
|
2052
|
+
revise.type = 'button';
|
|
2053
|
+
revise.onclick = () => { ui.editing = ui.editing === reviseKey ? null : reviseKey; ui.notice = null; renderDrawer(); };
|
|
2054
|
+
s.append(revise);
|
|
2055
|
+
if (ui.editing === reviseKey) s.append(newRevisionForm(item));
|
|
2056
|
+
}
|
|
1986
2057
|
body.append(s);
|
|
1987
2058
|
}
|
|
1988
2059
|
|
package/dist/index.cjs
CHANGED
|
@@ -12341,7 +12341,19 @@ var NewAssetSchema = import_zod7.z.object({
|
|
|
12341
12341
|
size: import_zod7.z.number().int().optional(),
|
|
12342
12342
|
tags: import_zod7.z.array(import_zod7.z.string()).optional(),
|
|
12343
12343
|
/** Restrict to these styles; empty or omitted means every style. */
|
|
12344
|
-
styles: import_zod7.z.array(import_zod7.z.string().min(1)).optional()
|
|
12344
|
+
styles: import_zod7.z.array(import_zod7.z.string().min(1)).optional(),
|
|
12345
|
+
/**
|
|
12346
|
+
* A controlled regeneration of another asset in the same manifest,
|
|
12347
|
+
* instead of a fresh generation. `inpaint` needs a mask upload the
|
|
12348
|
+
* gallery does not offer yet, so only `image-to-image` can be created
|
|
12349
|
+
* here; hand-edit the manifest for the other modes.
|
|
12350
|
+
*/
|
|
12351
|
+
revision: import_zod7.z.object({
|
|
12352
|
+
mode: import_zod7.z.literal("image-to-image"),
|
|
12353
|
+
/** Asset id of the parent this revises; must already exist. */
|
|
12354
|
+
from: import_zod7.z.string().min(1),
|
|
12355
|
+
strength: import_zod7.z.number().min(0).max(1).optional()
|
|
12356
|
+
}).strict().optional()
|
|
12345
12357
|
}).strict();
|
|
12346
12358
|
var CANDIDATE_OPTION = {
|
|
12347
12359
|
retrodiffusion: "numImages",
|
|
@@ -12507,6 +12519,9 @@ function applyEdit(raw, edit) {
|
|
|
12507
12519
|
}
|
|
12508
12520
|
asset2.styles = styles;
|
|
12509
12521
|
}
|
|
12522
|
+
if (edit.asset.revision && !Object.hasOwn(raw.assets, edit.asset.revision.from)) {
|
|
12523
|
+
throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
|
|
12524
|
+
}
|
|
12510
12525
|
raw.assets[edit.assetId] = asset2;
|
|
12511
12526
|
return;
|
|
12512
12527
|
}
|