pixelkiln 0.89.3 → 0.89.5

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.
@@ -362,6 +362,15 @@
362
362
  }
363
363
 
364
364
  // src/gallery/client/src/form-kit.ts
365
+ var skeletonV3Allowed = (engine) => engine !== "standard";
366
+ var defaultLoopMode = (engine) => skeletonV3Allowed(engine) ? "skeleton-v3" : "template";
367
+ function syncSkeletonOption(select, engine) {
368
+ const option = [...select.options].find((o) => o.value === "skeleton-v3");
369
+ if (!option) return;
370
+ const allowed = skeletonV3Allowed(engine);
371
+ option.disabled = !allowed;
372
+ option.title = allowed ? "" : "PixelLab poses a template only onto a v3 character. A standard base has no 3D skeleton; use template.";
373
+ }
365
374
  var nextStep = () => GENERATION ? "Generate it from its record when you are ready." : "Nothing is generated until you run pixelkiln gen.";
366
375
  async function addAssetAndOpen(body, where, what = "Added to the manifest.") {
367
376
  S.snap = await postEdit(body);
@@ -455,7 +464,9 @@
455
464
  );
456
465
  }
457
466
  mode.append(new Option("v3 (text-described)", "v3"), new Option("pro (sequential, high quality)", "pro"));
458
- mode.value = isCharacter ? "skeleton-v3" : "v3";
467
+ const engineId = isCharacter ? c.kind === "animation" ? S.snap.styles.find((st) => st.id === item.styleId && st.project === (item.project ?? null))?.characterMode : c.mode : null;
468
+ if (isCharacter) syncSkeletonOption(mode, engineId);
469
+ mode.value = isCharacter ? defaultLoopMode(engineId) : "v3";
459
470
  const template = el("input");
460
471
  template.type = "text";
461
472
  template.placeholder = "e.g. walk, breathing-idle";
@@ -5204,6 +5215,7 @@
5204
5215
  const characterStyles = () => S.snap.styles.filter((s) => s.generator === "character" && s.project === projectId());
5205
5216
  const chosenEngine = () => ENGINES.find((e) => e.id === engine.value);
5206
5217
  const isNewStyle = () => style.value === "";
5218
+ const loopEngine = () => isNewStyle() ? chosenEngine().id : characterStyles().find((s) => s.id === style.value)?.characterMode ?? "standard";
5207
5219
  const styleDirections = () => {
5208
5220
  if (isNewStyle()) return Number(directions.value) === 4 ? FOUR : COMPASS2;
5209
5221
  return COMPASS2;
@@ -5233,7 +5245,10 @@
5233
5245
  newStyle.hidden = !isNewStyle();
5234
5246
  styleId.required = isNewStyle();
5235
5247
  if (!outDirTouched) outDir.value = "art/" + (styleId.value.trim() || "characters");
5236
- for (const row2 of loops2) syncLoopDirections(row2);
5248
+ for (const row2 of loops2) {
5249
+ syncLoopDirections(row2);
5250
+ syncLoopEngine(row2);
5251
+ }
5237
5252
  }
5238
5253
  function syncLoopDirections(row2) {
5239
5254
  const allowed = styleDirections();
@@ -5243,6 +5258,13 @@
5243
5258
  if (wrap.hidden) box.checked = false;
5244
5259
  }
5245
5260
  }
5261
+ function syncLoopEngine(row2) {
5262
+ const engineId = loopEngine();
5263
+ syncSkeletonOption(row2.mode, engineId);
5264
+ if (!row2.picked) row2.mode.value = defaultLoopMode(engineId);
5265
+ else if (row2.mode.value === "skeleton-v3" && !skeletonV3Allowed(engineId)) row2.mode.value = "template";
5266
+ syncLoopMode(row2);
5267
+ }
5246
5268
  function syncLoopMode(row2) {
5247
5269
  const t = templated(row2.mode.value);
5248
5270
  row2.what.placeholder = t ? "template, e.g. walk, breathing-idle, running-8-frames" : 'the motion, e.g. "swings a sword overhead"';
@@ -5305,7 +5327,7 @@
5305
5327
  id.value = defaults.id ?? "";
5306
5328
  const mode = el("select");
5307
5329
  for (const m of LOOP_MODES) mode.append(new Option(m.label, m.id));
5308
- mode.value = defaults.mode ?? "skeleton-v3";
5330
+ mode.value = defaults.mode ?? defaultLoopMode(loopEngine());
5309
5331
  const what = el("input");
5310
5332
  what.type = "text";
5311
5333
  what.required = true;
@@ -5325,7 +5347,7 @@
5325
5347
  remove.type = "button";
5326
5348
  const preview = el("div", "studio-tpl-preview");
5327
5349
  preview.hidden = true;
5328
- const row2 = { id, mode, what, directions: boxes, wrap, preview };
5350
+ const row2 = { id, mode, picked: defaults.mode !== void 0, what, directions: boxes, wrap, preview };
5329
5351
  what.addEventListener("input", () => syncPreview(row2));
5330
5352
  remove.onclick = () => {
5331
5353
  loops2.splice(loops2.indexOf(row2), 1);
@@ -5334,6 +5356,7 @@
5334
5356
  keepDraft();
5335
5357
  };
5336
5358
  mode.onchange = () => {
5359
+ row2.picked = true;
5337
5360
  syncLoopMode(row2);
5338
5361
  schedulePrice();
5339
5362
  };
@@ -5342,8 +5365,8 @@
5342
5365
  wrap.append(line, preview, dirs, remove);
5343
5366
  loopList.append(wrap);
5344
5367
  loops2.push(row2);
5345
- syncLoopMode(row2);
5346
5368
  syncLoopDirections(row2);
5369
+ syncLoopEngine(row2);
5347
5370
  }
5348
5371
  function edits() {
5349
5372
  const out = [];
@@ -5646,8 +5669,8 @@
5646
5669
  note.append(discard);
5647
5670
  form.prepend(note);
5648
5671
  } else {
5649
- addLoopRow({ id: "walk", mode: "skeleton-v3", what: "walk", directions: ["south", "west", "north"] });
5650
- addLoopRow({ id: "idle", mode: "skeleton-v3", what: "breathing-idle", directions: ["south"] });
5672
+ addLoopRow({ id: "walk", what: "walk", directions: ["south", "west", "north"] });
5673
+ addLoopRow({ id: "idle", what: "breathing-idle", directions: ["south"] });
5651
5674
  }
5652
5675
  updatePrice();
5653
5676
  setTimeout(() => baseId.focus(), 0);
package/dist/index.cjs CHANGED
@@ -2438,7 +2438,8 @@ var PixelLabClient = class {
2438
2438
  });
2439
2439
  const text = await res.text();
2440
2440
  if (!res.ok) {
2441
- throw new PixelLabError(`${init?.method ?? "GET"} ${path39} \u2192 ${res.status}`, res.status, text);
2441
+ const hint = res.status === 500 && text.includes("Failed to start any animation jobs") ? '; a skeleton-v3 loop needs a character with a stored 3D skeleton (a v3 base), use mode: "template" on a standard one' : "";
2442
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path39} \u2192 ${res.status}${hint}`, res.status, text);
2442
2443
  }
2443
2444
  if (!text) return {};
2444
2445
  try {
@@ -6064,6 +6065,11 @@ var PixelLabProvider = class _PixelLabProvider {
6064
6065
  if (character.animation.mode !== "template" && character.animation.mode !== "skeleton-v3" && !spec.prompt.trim()) {
6065
6066
  throw new Error(`${spec.styleId}/${spec.assetId} needs a prompt describing the motion, or a template`);
6066
6067
  }
6068
+ if (character.animation.mode === "skeleton-v3" && character.mode === "standard") {
6069
+ throw new Error(
6070
+ `${spec.styleId}/${spec.assetId}: skeleton-v3 poses a template onto a 3D skeleton, which a ${character.mode} character has none of; use a v3 base (style mode: "v3"), or mode: "template" for this loop`
6071
+ );
6072
+ }
6067
6073
  }
6068
6074
  }
6069
6075
  validateObjectPro(spec, styleImages = []) {
@@ -6370,7 +6376,10 @@ var PixelLabProvider = class _PixelLabProvider {
6370
6376
  stateName: character.state_name ?? null,
6371
6377
  directions: character.directions,
6372
6378
  size: character.size,
6373
- updatedAt: character.updated_at ?? null
6379
+ updatedAt: character.updated_at ?? null,
6380
+ // Kept: this metadata replaces the submit-time `character` entry,
6381
+ // and the job id is the only way to read the bill again later.
6382
+ ...backgroundJobId ? { backgroundJobId } : {}
6374
6383
  }
6375
6384
  },
6376
6385
  billed: await this.billedForJob(backgroundJobId)
@@ -7021,7 +7030,12 @@ var PixelLabProvider = class _PixelLabProvider {
7021
7030
  if (!backgroundJobId) return null;
7022
7031
  try {
7023
7032
  const job = await this.client.getBackgroundJob(backgroundJobId);
7024
- return billedFromUsage(job.usage);
7033
+ const nested = job.last_response?.billing_usage;
7034
+ for (const usage of [job.usage, nested]) {
7035
+ const billed = billedFromUsage(usage);
7036
+ if (billed && !(billed.unit === "usd" && billed.amount === 0)) return billed;
7037
+ }
7038
+ return null;
7025
7039
  } catch {
7026
7040
  return null;
7027
7041
  }
@@ -17021,8 +17035,18 @@ GROUPS.forEach((g, gi) => {
17021
17035
  preview.loading = 'lazy';
17022
17036
  const index = document.createElement('span');
17023
17037
  index.className = 'idx';
17024
- index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
17025
- g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
17038
+ // A frame's own size, not the manifest's: a 96px loop of a 64px
17039
+ // character is 96\xD796. Known up front for a cached file, else once loaded.
17040
+ const label = (w, h) => {
17041
+ index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' + w + '\xD7' + h + ' \xB7 ' + scaleLabel;
17042
+ };
17043
+ const known = g.frameSizes?.[i];
17044
+ label(known ? known.width : g.width, known ? known.height : g.height);
17045
+ if (!known) {
17046
+ preview.addEventListener('load', () => {
17047
+ if (preview.naturalWidth && preview.naturalHeight) label(preview.naturalWidth, preview.naturalHeight);
17048
+ }, { once: true });
17049
+ }
17026
17050
  c.append(preview, index);
17027
17051
  if (g.recommendedIndex != null && i === recommended) {
17028
17052
  const badge = document.createElement('span');
@@ -17284,12 +17308,15 @@ async function prepareReview(provider, lock, opts = {}) {
17284
17308
  const urls = state.status === "review" ? state.candidateUrls : state.frameUrls;
17285
17309
  const frameUrls = [];
17286
17310
  const frameAssets = [];
17311
+ const frameSizes = [];
17287
17312
  for (const [index, url] of urls.entries()) {
17288
17313
  const cached = await cachedFrame(url);
17289
17314
  if (!cached) {
17290
17315
  frameUrls.push(url);
17316
+ frameSizes.push(null);
17291
17317
  continue;
17292
17318
  }
17319
+ frameSizes.push(cached.contentType === "image/png" ? pngSize(await (0, import_promises19.readFile)(cached.path).catch(() => Buffer.alloc(0))) : null);
17293
17320
  const route = `${prefix}/review-frame/${encodeURIComponent(String(groups.length))}/${index}`;
17294
17321
  frameAssets.push([route, cached]);
17295
17322
  frameUrls.push(route);
@@ -17315,6 +17342,7 @@ async function prepareReview(provider, lock, opts = {}) {
17315
17342
  prompt: entry.prompt,
17316
17343
  reviewObjectId: entry.reviewObjectId,
17317
17344
  frameUrls,
17345
+ ...frameSizes.some(Boolean) ? { frameSizes } : {},
17318
17346
  width: entry.width,
17319
17347
  height: entry.height,
17320
17348
  ...state.status === "review-set" ? {
@@ -19197,6 +19225,7 @@ async function buildGallerySnapshot(opts) {
19197
19225
  view: style?.view ?? null,
19198
19226
  noBackground: style?.noBackground ?? true,
19199
19227
  quality: Boolean(style?.quality),
19228
+ characterMode: style?.generator === "character" ? style.mode ?? "standard" : null,
19200
19229
  characters: style?.generator === "character" || style?.generator === "objectPro" ? {
19201
19230
  bases: styleItems.filter((item) => item.character?.kind === "base").length,
19202
19231
  states: styleItems.filter((item) => item.character?.kind === "state").length,