pixelkiln 0.52.0 → 0.53.1
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 +40 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +73 -2
- package/dist/index.cjs +42 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +41 -4
- package/dist/index.js.map +1 -1
- package/docs/REVISIONS.md +10 -0
- package/docs/TILES.md +41 -0
- package/package.json +1 -1
- package/skills/pixelkiln/references/pixellab-fidelity.md +4 -4
- 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
|
@@ -188,6 +188,7 @@ __export(index_exports, {
|
|
|
188
188
|
mountStyle: () => mountStyle,
|
|
189
189
|
normalizeLockOutputPaths: () => normalizeLockOutputPaths,
|
|
190
190
|
normalizeRefinementPalette: () => normalizeRefinementPalette,
|
|
191
|
+
normalizeTerrainTiles: () => normalizeTerrainTiles,
|
|
191
192
|
normalizeTileRules: () => normalizeTileRules,
|
|
192
193
|
openExternal: () => openExternal,
|
|
193
194
|
openInEditor: () => openInEditor,
|
|
@@ -10486,6 +10487,25 @@ function normalizeTileRules(raw) {
|
|
|
10486
10487
|
raw
|
|
10487
10488
|
};
|
|
10488
10489
|
}
|
|
10490
|
+
function normalizeTerrainTiles(metadata) {
|
|
10491
|
+
if (!isRecord2(metadata) || !Array.isArray(metadata.terrainTiles)) return null;
|
|
10492
|
+
const terrains = Array.isArray(metadata.terrainTypes) ? metadata.terrainTypes.filter((item) => typeof item === "string") : [];
|
|
10493
|
+
if (terrains.length < 2) return null;
|
|
10494
|
+
const masks = {};
|
|
10495
|
+
metadata.terrainTiles.forEach((tile, index) => {
|
|
10496
|
+
if (!isRecord2(tile) || !isRecord2(tile.corners)) return;
|
|
10497
|
+
const corners = tile.corners;
|
|
10498
|
+
const bit = (corner) => corners[corner] === terrains[0] ? 1 : 0;
|
|
10499
|
+
masks[index] = bit("NW") << 3 | bit("NE") << 2 | bit("SW") << 1 | bit("SE");
|
|
10500
|
+
});
|
|
10501
|
+
return {
|
|
10502
|
+
ruleType: "corner",
|
|
10503
|
+
arity: 4,
|
|
10504
|
+
terrains,
|
|
10505
|
+
masks,
|
|
10506
|
+
raw: metadata
|
|
10507
|
+
};
|
|
10508
|
+
}
|
|
10489
10509
|
function exportTileset(entry, spec, options) {
|
|
10490
10510
|
let outputs = resolveSpecEntryOutputs(entry, spec);
|
|
10491
10511
|
if (!outputs.length) throw new Error(`${spec.styleId}/${spec.assetId} has no downloaded outputs`);
|
|
@@ -10506,14 +10526,16 @@ function exportTileset(entry, spec, options) {
|
|
|
10506
10526
|
}
|
|
10507
10527
|
const metadata = entry.providerMetadata?.[entry.provider];
|
|
10508
10528
|
const rawRules = isRecord2(metadata) ? metadata.tileRules : void 0;
|
|
10509
|
-
const
|
|
10529
|
+
const terrainRules = normalizeTerrainTiles(metadata);
|
|
10530
|
+
const rules = normalizeTileRules(rawRules) ?? terrainRules;
|
|
10531
|
+
const providerRules = isRecord2(rawRules) ? rawRules : terrainRules ? metadata : null;
|
|
10510
10532
|
const frames = packed.atlas.frames;
|
|
10511
10533
|
const generic = buildGeneric(
|
|
10512
10534
|
spec,
|
|
10513
10535
|
options.imageName,
|
|
10514
10536
|
packed.atlas,
|
|
10515
10537
|
frames,
|
|
10516
|
-
|
|
10538
|
+
providerRules,
|
|
10517
10539
|
rules
|
|
10518
10540
|
);
|
|
10519
10541
|
if (options.format === "generic") {
|
|
@@ -10526,7 +10548,7 @@ function exportTileset(entry, spec, options) {
|
|
|
10526
10548
|
};
|
|
10527
10549
|
}
|
|
10528
10550
|
assertUniformTiles(generic);
|
|
10529
|
-
if ((rawRules !== void 0 || entry.tileFeature) && !rules) {
|
|
10551
|
+
if ((rawRules !== void 0 || entry.tileFeature || entry.generator === "terrain") && !rules) {
|
|
10530
10552
|
throw new Error(
|
|
10531
10553
|
`${options.format} export needs recognized adjacency rules; use --format generic to retain this provider rule object without guessing`
|
|
10532
10554
|
);
|
|
@@ -12341,7 +12363,19 @@ var NewAssetSchema = import_zod7.z.object({
|
|
|
12341
12363
|
size: import_zod7.z.number().int().optional(),
|
|
12342
12364
|
tags: import_zod7.z.array(import_zod7.z.string()).optional(),
|
|
12343
12365
|
/** Restrict to these styles; empty or omitted means every style. */
|
|
12344
|
-
styles: import_zod7.z.array(import_zod7.z.string().min(1)).optional()
|
|
12366
|
+
styles: import_zod7.z.array(import_zod7.z.string().min(1)).optional(),
|
|
12367
|
+
/**
|
|
12368
|
+
* A controlled regeneration of another asset in the same manifest,
|
|
12369
|
+
* instead of a fresh generation. `inpaint` needs a mask upload the
|
|
12370
|
+
* gallery does not offer yet, so only `image-to-image` can be created
|
|
12371
|
+
* here; hand-edit the manifest for the other modes.
|
|
12372
|
+
*/
|
|
12373
|
+
revision: import_zod7.z.object({
|
|
12374
|
+
mode: import_zod7.z.literal("image-to-image"),
|
|
12375
|
+
/** Asset id of the parent this revises; must already exist. */
|
|
12376
|
+
from: import_zod7.z.string().min(1),
|
|
12377
|
+
strength: import_zod7.z.number().min(0).max(1).optional()
|
|
12378
|
+
}).strict().optional()
|
|
12345
12379
|
}).strict();
|
|
12346
12380
|
var CANDIDATE_OPTION = {
|
|
12347
12381
|
retrodiffusion: "numImages",
|
|
@@ -12507,6 +12541,9 @@ function applyEdit(raw, edit) {
|
|
|
12507
12541
|
}
|
|
12508
12542
|
asset2.styles = styles;
|
|
12509
12543
|
}
|
|
12544
|
+
if (edit.asset.revision && !Object.hasOwn(raw.assets, edit.asset.revision.from)) {
|
|
12545
|
+
throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
|
|
12546
|
+
}
|
|
12510
12547
|
raw.assets[edit.assetId] = asset2;
|
|
12511
12548
|
return;
|
|
12512
12549
|
}
|
|
@@ -15201,6 +15238,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
15201
15238
|
mountStyle,
|
|
15202
15239
|
normalizeLockOutputPaths,
|
|
15203
15240
|
normalizeRefinementPalette,
|
|
15241
|
+
normalizeTerrainTiles,
|
|
15204
15242
|
normalizeTileRules,
|
|
15205
15243
|
openExternal,
|
|
15206
15244
|
openInEditor,
|