pixelkiln 0.55.0 → 0.56.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.
@@ -1675,6 +1675,144 @@ function newRevisionForm(item) {
1675
1675
  return form;
1676
1676
  }
1677
1677
 
1678
+ /**
1679
+ * A new pose or outfit of `item`, an existing `character`/`objectPro` base
1680
+ * or state — the same family, restricted to `item`'s own style. A larger
1681
+ * canvas needs both width and height; leaving either blank keeps the
1682
+ * parent's.
1683
+ */
1684
+ function newStateForm(item) {
1685
+ const pr = projectOf(item);
1686
+ const form = el('form', 'edit');
1687
+ form.append(el('h3', null, 'New state of ' + item.assetId));
1688
+ const id = el('input'); id.type = 'text'; id.placeholder = 'asset-id'; id.required = true; id.autocomplete = 'off';
1689
+ id.pattern = '[^\\/\\\\]+';
1690
+ const prompt = el('textarea'); prompt.placeholder = 'What changes, e.g. "the chest lid open".'; prompt.required = true;
1691
+ const paletteFromReference = el('input'); paletteFromReference.type = 'checkbox';
1692
+ const paletteField = el('label', 'field check'); paletteField.append(paletteFromReference, el('span', null, 'snap colours to the parent'));
1693
+ const width = numberInput(null, 'parent’s'), height = numberInput(null, 'parent’s');
1694
+ const row1 = el('div', 'row');
1695
+ row1.append(field('new asset id', id), field('canvas width', width), field('canvas height', height));
1696
+ form.append(row1, field('what changes', prompt, 'The style still adds its prefix and suffix.'), paletteField);
1697
+ const actions = el('div', 'actions');
1698
+ const save = el('button', 'primary', 'Create state'); save.type = 'submit';
1699
+ const cancel = el('button', null, 'Cancel'); cancel.type = 'button'; cancel.onclick = () => { ui.editing = null; renderDrawer(); };
1700
+ const msg = el('span', 'msg');
1701
+ actions.append(save, cancel, msg);
1702
+ form.append(actions);
1703
+ form.onsubmit = async (e) => {
1704
+ e.preventDefault();
1705
+ save.disabled = true; msg.className = 'msg'; msg.textContent = 'saving…';
1706
+ const state = { of: item.assetId };
1707
+ if (paletteFromReference.checked) state.paletteFromReference = true;
1708
+ const w = numberOrNull(width), h = numberOrNull(height);
1709
+ if (w !== null && h !== null) state.canvas = { width: w, height: h };
1710
+ const asset = { prompt: prompt.value, styles: [item.styleId], state };
1711
+ try {
1712
+ const body = { action: 'add-asset', assetId: id.value.trim(), expectedSha256: pr.manifestSha256, asset };
1713
+ if (item.project) body.project = item.project;
1714
+ snap = await postEdit(body);
1715
+ const newId = (item.project ? item.project + ':' : '') + item.styleId + '/' + id.value.trim();
1716
+ ui.editing = null;
1717
+ ui.notice = { id: newId, text: 'Added to the manifest. Nothing is generated until you run pixelkiln gen.' };
1718
+ render();
1719
+ if (snap.items.some((i) => i.id === newId)) openItem(newId);
1720
+ } catch (err) {
1721
+ save.disabled = false;
1722
+ msg.className = 'msg bad';
1723
+ msg.textContent = err.message + (err.status === 409 ? ' Press Refresh.' : '');
1724
+ }
1725
+ };
1726
+ setTimeout(() => id.focus(), 0);
1727
+ return form;
1728
+ }
1729
+
1730
+ /**
1731
+ * A new loop of `item`, an existing `character`/`objectPro` base or state.
1732
+ * `objectPro` has no skeleton/template concept, so `template` and `subject`
1733
+ * are only offered for a `character`; `startFrame`/`endFrame` and template
1734
+ * `outline`/`shading`/`detail` hints need a manifest-relative image the
1735
+ * gallery does not offer, so hand-edit the manifest for those.
1736
+ */
1737
+ function newAnimationForm(item) {
1738
+ const pr = projectOf(item);
1739
+ const c = item.character;
1740
+ const isCharacter = c.generator === 'character';
1741
+ const form = el('form', 'edit');
1742
+ form.append(el('h3', null, 'New animation of ' + item.assetId));
1743
+ const id = el('input'); id.type = 'text'; id.placeholder = 'asset-id'; id.required = true; id.autocomplete = 'off';
1744
+ id.pattern = '[^\\/\\\\]+';
1745
+ const prompt = el('textarea'); prompt.placeholder = 'What the loop does, e.g. "a slow idle sway".'; prompt.required = true;
1746
+ const mode = el('select');
1747
+ if (isCharacter) mode.append(new Option('template', 'template'));
1748
+ mode.append(new Option('v3 (text-described)', 'v3'), new Option('pro (sequential, high quality)', 'pro'));
1749
+ mode.value = 'v3';
1750
+ const template = el('input'); template.type = 'text'; template.placeholder = 'e.g. walk, breathing-idle';
1751
+ const templateField = field('template id', template, 'A PixelLab template; costs 1 generation regardless of frame count.');
1752
+ const frames = el('input'); frames.type = 'number'; frames.min = '4'; frames.max = '16'; frames.step = '2'; frames.placeholder = '8';
1753
+ const fps = el('input'); fps.type = 'number'; fps.min = '1'; fps.max = '60'; fps.placeholder = '8';
1754
+ const directionField = (() => {
1755
+ if (c.directions === 1) return null;
1756
+ const set = c.directions === 4 ? ['south', 'west', 'east', 'north'] : ['south', 'south-east', 'east', 'north-east', 'north', 'north-west', 'west', 'south-west'];
1757
+ const select = el('select');
1758
+ for (const d of set) select.append(new Option(d, d));
1759
+ return { select, wrap: field('direction', select) };
1760
+ })();
1761
+ const subject = isCharacter ? el('input') : null;
1762
+ if (subject) { subject.type = 'text'; subject.placeholder = 'optional, when the prompt alone would mislead the model'; }
1763
+ const row1 = el('div', 'row');
1764
+ row1.append(field('new asset id', id), field('mode', mode));
1765
+ if (directionField) row1.append(directionField.wrap);
1766
+ form.append(row1, field('what it does', prompt, 'The style still adds its prefix and suffix.'));
1767
+ if (isCharacter) form.append(templateField);
1768
+ const row2 = el('div', 'row'); row2.append(field('frames', frames), field('fps', fps));
1769
+ form.append(row2);
1770
+ if (subject) form.append(field('subject override', subject));
1771
+ const syncMode = () => {
1772
+ const isTemplate = isCharacter && mode.value === 'template';
1773
+ templateField.style.display = isTemplate ? '' : 'none';
1774
+ row2.style.display = isTemplate ? 'none' : '';
1775
+ };
1776
+ if (isCharacter) { mode.onchange = syncMode; syncMode(); } else { templateField.style.display = 'none'; }
1777
+ const actions = el('div', 'actions');
1778
+ const save = el('button', 'primary', 'Create animation'); save.type = 'submit';
1779
+ const cancel = el('button', null, 'Cancel'); cancel.type = 'button'; cancel.onclick = () => { ui.editing = null; renderDrawer(); };
1780
+ const msg = el('span', 'msg');
1781
+ actions.append(save, cancel, msg);
1782
+ form.append(actions);
1783
+ form.onsubmit = async (e) => {
1784
+ e.preventDefault();
1785
+ save.disabled = true; msg.className = 'msg'; msg.textContent = 'saving…';
1786
+ const animation = { of: item.assetId };
1787
+ if (directionField) animation.direction = directionField.select.value;
1788
+ if (isCharacter && mode.value === 'template' && template.value.trim()) {
1789
+ animation.template = template.value.trim();
1790
+ } else {
1791
+ animation.mode = mode.value;
1792
+ if (frames.value.trim() !== '') animation.frames = Number(frames.value);
1793
+ if (fps.value.trim() !== '') animation.fps = Number(fps.value);
1794
+ }
1795
+ if (subject && subject.value.trim()) animation.subject = subject.value.trim();
1796
+ const asset = { prompt: prompt.value, styles: [item.styleId], animation };
1797
+ try {
1798
+ const body = { action: 'add-asset', assetId: id.value.trim(), expectedSha256: pr.manifestSha256, asset };
1799
+ if (item.project) body.project = item.project;
1800
+ snap = await postEdit(body);
1801
+ const newId = (item.project ? item.project + ':' : '') + item.styleId + '/' + id.value.trim();
1802
+ ui.editing = null;
1803
+ ui.notice = { id: newId, text: 'Added to the manifest. Nothing is generated until you run pixelkiln gen.' };
1804
+ render();
1805
+ if (snap.items.some((i) => i.id === newId)) openItem(newId);
1806
+ } catch (err) {
1807
+ save.disabled = false;
1808
+ msg.className = 'msg bad';
1809
+ msg.textContent = err.message + (err.status === 409 ? ' Press Refresh.' : '');
1810
+ }
1811
+ };
1812
+ setTimeout(() => id.focus(), 0);
1813
+ return form;
1814
+ }
1815
+
1678
1816
  function editForm(item) {
1679
1817
  const pr = projectOf(item);
1680
1818
  const a = item.asset;
@@ -2014,6 +2152,20 @@ function renderDrawer() {
2014
2152
  }
2015
2153
  row(dl, family.length === 1 ? 'depends on this' : family.length + ' depend on this', list);
2016
2154
  }
2155
+ // A loop cannot itself be posed or animated further; only a base or state can.
2156
+ if (canEdit && c.kind !== 'animation') {
2157
+ const stateKey = 'state:' + item.id, animKey = 'anim:' + item.id;
2158
+ const addState = el('button', ui.editing === stateKey ? null : 'add', ui.editing === stateKey ? 'Cancel' : '+ New state');
2159
+ addState.type = 'button';
2160
+ addState.onclick = () => { ui.editing = ui.editing === stateKey ? null : stateKey; ui.notice = null; renderDrawer(); };
2161
+ const addAnim = el('button', ui.editing === animKey ? null : 'add', ui.editing === animKey ? 'Cancel' : '+ New animation');
2162
+ addAnim.type = 'button';
2163
+ addAnim.onclick = () => { ui.editing = ui.editing === animKey ? null : animKey; ui.notice = null; renderDrawer(); };
2164
+ const buttons = el('div', 'actions'); buttons.append(addState, addAnim);
2165
+ s.append(buttons);
2166
+ if (ui.editing === stateKey) s.append(newStateForm(item));
2167
+ if (ui.editing === animKey) s.append(newAnimationForm(item));
2168
+ }
2017
2169
  body.append(s);
2018
2170
  }
2019
2171
 
package/dist/index.cjs CHANGED
@@ -13049,6 +13049,25 @@ var AssetPatchSchema = import_zod7.z.object({
13049
13049
  size: import_zod7.z.number().int().nullable().optional(),
13050
13050
  tags: import_zod7.z.array(import_zod7.z.string()).optional()
13051
13051
  }).strict();
13052
+ var NewStateSchema = import_zod7.z.object({
13053
+ /** Asset id of the parent this states; must already exist. */
13054
+ of: import_zod7.z.string().min(1),
13055
+ paletteFromReference: import_zod7.z.boolean().optional(),
13056
+ canvas: import_zod7.z.object({ width: import_zod7.z.number().int().min(16).max(256), height: import_zod7.z.number().int().min(16).max(256) }).strict().optional()
13057
+ }).strict();
13058
+ var NewAnimationSchema = import_zod7.z.object({
13059
+ /** Asset id of the parent this animates; must already exist. */
13060
+ of: import_zod7.z.string().min(1),
13061
+ /** A PixelLab template id (`character` only; `objectPro` has no template concept). */
13062
+ template: import_zod7.z.string().min(1).optional(),
13063
+ direction: CharacterDirectionSchema.optional(),
13064
+ frames: import_zod7.z.number().int().min(4).max(16).optional(),
13065
+ fps: import_zod7.z.number().int().min(1).max(60).optional(),
13066
+ mode: CharacterAnimationModeSchema.optional(),
13067
+ /** What is being animated, when the parent's own description would mislead the model (`character` only). */
13068
+ subject: import_zod7.z.string().min(1).optional(),
13069
+ enhancePrompt: import_zod7.z.boolean().optional()
13070
+ }).strict();
13052
13071
  var NewAssetSchema = import_zod7.z.object({
13053
13072
  prompt: import_zod7.z.string(),
13054
13073
  category: import_zod7.z.string().optional(),
@@ -13069,8 +13088,16 @@ var NewAssetSchema = import_zod7.z.object({
13069
13088
  /** Asset id of the parent this revises; must already exist. */
13070
13089
  from: import_zod7.z.string().min(1),
13071
13090
  strength: import_zod7.z.number().min(0).max(1).optional()
13072
- }).strict().optional()
13073
- }).strict();
13091
+ }).strict().optional(),
13092
+ /** A pose or outfit of an existing `character`/`objectPro` base or state. */
13093
+ state: NewStateSchema.optional(),
13094
+ /** A loop of an existing `character`/`objectPro` base or state. */
13095
+ animation: NewAnimationSchema.optional()
13096
+ }).strict().refine((asset) => !(asset.revision && (asset.state || asset.animation)), {
13097
+ message: "an asset is a revision, a state, or an animation \u2014 not more than one"
13098
+ }).refine((asset) => !(asset.state && asset.animation), {
13099
+ message: "an asset is a state or an animation \u2014 not both"
13100
+ });
13074
13101
  var CANDIDATE_OPTION = {
13075
13102
  retrodiffusion: "numImages",
13076
13103
  comfyui: "numImages",
@@ -13238,6 +13265,12 @@ function applyEdit(raw, edit) {
13238
13265
  if (edit.asset.revision && !Object.hasOwn(raw.assets, edit.asset.revision.from)) {
13239
13266
  throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
13240
13267
  }
13268
+ if (edit.asset.state && !Object.hasOwn(raw.assets, edit.asset.state.of)) {
13269
+ throw new ManifestEditError(`state parent "${edit.asset.state.of}" is not declared by the manifest`);
13270
+ }
13271
+ if (edit.asset.animation && !Object.hasOwn(raw.assets, edit.asset.animation.of)) {
13272
+ throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
13273
+ }
13241
13274
  raw.assets[edit.assetId] = asset2;
13242
13275
  return;
13243
13276
  }
@@ -13714,27 +13747,54 @@ async function samePixels(a, b) {
13714
13747
  }
13715
13748
  var metadataFps = (entry) => entry ? frameSetFps(entry) : null;
13716
13749
  function describeCharacter(spec, entry) {
13717
- const recorded = entry?.providerMetadata?.[entry.provider]?.character;
13750
+ const recordedCharacter = entry?.providerMetadata?.[entry.provider]?.character;
13718
13751
  if (spec?.character) {
13719
13752
  const character = spec.character;
13720
13753
  return {
13754
+ generator: "character",
13721
13755
  kind: character.kind,
13722
13756
  parentKey: character.parentAssetId ? lockKey(spec.styleId, character.parentAssetId) : null,
13723
13757
  mode: character.kind === "animation" ? character.animation.mode : character.mode,
13724
13758
  directions: character.directions,
13725
13759
  direction: character.animation?.direction ?? null,
13726
- characterId: recorded?.characterId ?? entry?.objectId?.split("#")[0] ?? null
13760
+ characterId: recordedCharacter?.characterId ?? entry?.objectId?.split("#")[0] ?? null
13761
+ };
13762
+ }
13763
+ if (spec?.objectPro) {
13764
+ const objectPro = spec.objectPro;
13765
+ return {
13766
+ generator: "objectPro",
13767
+ kind: objectPro.kind,
13768
+ parentKey: objectPro.parentAssetId ? lockKey(spec.styleId, objectPro.parentAssetId) : null,
13769
+ mode: objectPro.kind === "animation" ? objectPro.animation.mode : "pro-flash",
13770
+ directions: objectPro.directions,
13771
+ direction: objectPro.animation?.direction ?? null,
13772
+ characterId: entry?.objectId?.split("#")[0] ?? null
13727
13773
  };
13728
13774
  }
13729
13775
  if (entry?.generator === "character") {
13730
- const kind = recorded?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
13776
+ const kind = recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
13777
+ return {
13778
+ generator: "character",
13779
+ kind,
13780
+ parentKey: null,
13781
+ mode: recordedCharacter?.mode ?? "standard",
13782
+ directions: recordedCharacter?.directions ?? entry.outputs.length,
13783
+ direction: recordedCharacter?.direction ?? null,
13784
+ characterId: recordedCharacter?.characterId ?? entry.objectId?.split("#")[0] ?? null
13785
+ };
13786
+ }
13787
+ if (entry?.generator === "objectPro") {
13788
+ const recordedObject = entry.providerMetadata?.[entry.provider]?.objectPro;
13789
+ const kind = recordedObject?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
13731
13790
  return {
13791
+ generator: "objectPro",
13732
13792
  kind,
13733
13793
  parentKey: null,
13734
- mode: recorded?.mode ?? "standard",
13735
- directions: recorded?.directions ?? entry.outputs.length,
13736
- direction: recorded?.direction ?? null,
13737
- characterId: recorded?.characterId ?? entry.objectId?.split("#")[0] ?? null
13794
+ mode: "pro-flash",
13795
+ directions: recordedObject?.directions ?? entry.outputs.length,
13796
+ direction: recordedObject?.direction ?? null,
13797
+ characterId: entry.objectId?.split("#")[0] ?? null
13738
13798
  };
13739
13799
  }
13740
13800
  return null;
@@ -14070,7 +14130,7 @@ async function buildGallerySnapshot(opts) {
14070
14130
  view: style?.view ?? null,
14071
14131
  noBackground: style?.noBackground ?? true,
14072
14132
  quality: Boolean(style?.quality),
14073
- characters: style?.generator === "character" ? {
14133
+ characters: style?.generator === "character" || style?.generator === "objectPro" ? {
14074
14134
  bases: styleItems.filter((item) => item.character?.kind === "base").length,
14075
14135
  states: styleItems.filter((item) => item.character?.kind === "state").length,
14076
14136
  animations: styleItems.filter((item) => item.character?.kind === "animation").length