pixelkiln 0.48.0 → 0.50.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 CHANGED
@@ -938,17 +938,37 @@ var StyleObjectSchema = z.object({
938
938
  shading: z.string().optional(),
939
939
  detail: z.string().optional(),
940
940
  /**
941
- * `tiles` generator only. Edge length of one tile, 16-256.
941
+ * `tiles` generator only. Edge length of one tile, 16-128 (the API's own
942
+ * range; connectable sets narrow it further per shape, e.g. square
943
+ * top-down roads are fixed at 32).
942
944
  *
943
945
  * Ignored when `styleImages` is set: style mode takes the tile's shape
944
946
  * and dimensions from the reference image, which is the whole reason to
945
947
  * use it against an existing sheet.
946
948
  */
947
- tileSize: z.number().int().min(16).max(256).optional(),
949
+ tileSize: z.number().int().min(16).max(128).optional(),
950
+ /** `tiles` generator only. Height in pixels (16-256) for a non-square
951
+ * tile (e.g. a tall building wall); omit to compute it from `tileType`
952
+ * geometry and the view angle. */
953
+ tileHeight: z.number().int().min(16).max(256).optional(),
948
954
  /** `tiles` generator only. Defaults to the API's `isometric`. */
949
955
  tileType: z.enum(["hex", "hex_pointy", "isometric", "oblique", "octagon", "square_topdown"]).optional(),
950
956
  /** `tiles` generator only. Defaults to the API's `low top-down`. */
951
957
  tileView: z.enum(["top-down", "high top-down", "low top-down", "side"]).optional(),
958
+ /** `tiles` generator only. Continuous view angle in degrees (0 = side,
959
+ * 90 = top-down), overriding `tileView` when set. */
960
+ tileViewAngle: z.number().min(0).max(90).optional(),
961
+ /** `tiles` generator only. Tile depth/thickness as a ratio (0-1) of the
962
+ * tile's height, overriding the default the API computes from the
963
+ * view. This is the tutorials' "thickness" control. */
964
+ tileDepthRatio: z.number().min(0).max(1).optional(),
965
+ /** `tiles` generator only, `tileType: "isometric"`. Top/bottom cap
966
+ * width in pixels: 2 for the classic look, 4 for a more modern one. */
967
+ tileFlatTopPx: z.number().int().min(2).max(8).optional(),
968
+ /** `tiles` generator only, `tileType: "oblique"` (ground tiles or
969
+ * building walls). Horizontal shear per pixel of height (0-1); 0.5 is
970
+ * a classic cabinet projection (~27°), 1.0 a full 45° diagonal. */
971
+ obliqueLean: z.number().min(0).max(1).optional(),
952
972
  /**
953
973
  * `tiles` generator only. Asks for a connectable set instead of
954
974
  * independent variations:
@@ -963,6 +983,28 @@ var StyleObjectSchema = z.object({
963
983
  * is load-bearing; do not sort a connectable set by anything else.
964
984
  */
965
985
  tileFeature: z.enum(["roads", "tileset", "building"]).optional(),
986
+ /** `tileFeature: "building"` only. Wall height in tiles (1-3); the API
987
+ * defaults to 2. */
988
+ buildingWallTiles: z.number().int().min(1).max(3).optional(),
989
+ /** `tileFeature: "building"` only. `"grid"` paints each shaped piece
990
+ * individually (richer, the isometric default); `"materials"` paints
991
+ * flat swatches and renders pieces from them (more consistent for
992
+ * square top-down building kits). */
993
+ buildingLayout: z.enum(["grid", "materials"]).optional(),
994
+ /** `tileFeature: "building"` only. Wall material, e.g. "stone brick
995
+ * walls" — more reliable than relying on the main `prompt` being split
996
+ * into wall/floor parts. */
997
+ buildingWallDescription: z.string().min(1).max(500).optional(),
998
+ /** `tileFeature: "building"` only. Floor material, e.g. "wooden plank
999
+ * floor". */
1000
+ buildingFloorDescription: z.string().min(1).max(500).optional(),
1001
+ /** `tileFeature: "building"` only. Upper-storey or roof surface;
1002
+ * defaults to the wall material when omitted. */
1003
+ buildingFloor2Description: z.string().min(1).max(500).optional(),
1004
+ /** `tileFeature: "building"` with `tileType: "square_topdown"` only.
1005
+ * Wall storey height as its own camera angle in degrees (5-90),
1006
+ * decoupled from the ground's pitch. */
1007
+ buildingWallAngle: z.number().min(5).max(90).optional(),
966
1008
  /**
967
1009
  * `tiles` generator only. How tile edges are drawn.
968
1010
  *
@@ -5125,6 +5167,10 @@ var TilesProSchema = z2.object({
5125
5167
  kind: z2.string().nullable().default(null),
5126
5168
  tile_rules: z2.record(z2.unknown()).nullable().optional()
5127
5169
  }).passthrough();
5170
+ var RevisionJobSubmitSchema = z2.object({
5171
+ background_job_id: z2.string().min(1),
5172
+ status: z2.string().default("processing")
5173
+ }).passthrough();
5128
5174
  var PixelLabObjectSchema = z2.object({
5129
5175
  id: z2.string().min(1),
5130
5176
  name: z2.string().nullable().default(null),
@@ -5341,9 +5387,20 @@ var PixelLabClient = class {
5341
5387
  async createTilesPro(args) {
5342
5388
  const body = { description: args.description };
5343
5389
  if (args.tileSize != null) body.tile_size = args.tileSize;
5390
+ if (args.tileHeight != null) body.tile_height = args.tileHeight;
5344
5391
  if (args.tileType) body.tile_type = args.tileType;
5345
5392
  if (args.tileView) body.tile_view = args.tileView;
5393
+ if (args.tileViewAngle != null) body.tile_view_angle = args.tileViewAngle;
5394
+ if (args.tileDepthRatio != null) body.tile_depth_ratio = args.tileDepthRatio;
5395
+ if (args.tileFlatTopPx != null) body.tile_flat_top_px = args.tileFlatTopPx;
5396
+ if (args.obliqueLean != null) body.oblique_lean = args.obliqueLean;
5346
5397
  if (args.tileFeature) body.tile_feature = args.tileFeature;
5398
+ if (args.buildingWallTiles != null) body.building_wall_tiles = args.buildingWallTiles;
5399
+ if (args.buildingLayout) body.building_layout = args.buildingLayout;
5400
+ if (args.buildingWallDescription) body.building_wall_description = args.buildingWallDescription;
5401
+ if (args.buildingFloorDescription) body.building_floor_description = args.buildingFloorDescription;
5402
+ if (args.buildingFloor2Description) body.building_floor2_description = args.buildingFloor2Description;
5403
+ if (args.buildingWallAngle != null) body.building_wall_angle = args.buildingWallAngle;
5347
5404
  if (args.outlineMode) body.outline_mode = args.outlineMode;
5348
5405
  if (args.seed != null) body.seed = args.seed;
5349
5406
  if (args.styleImages?.length) body.style_images = args.styleImages;
@@ -5596,6 +5653,71 @@ var PixelLabClient = class {
5596
5653
  if (page.characters.length === 0 || offset >= page.total) return;
5597
5654
  }
5598
5655
  }
5656
+ /**
5657
+ * Masked inpaint, PixelLab's `/inpaint-v3` (the endpoint its own docs list
5658
+ * first in the Inpaint section, its convention for "reach for this by
5659
+ * default"). Exercised live at nine sizes, all returning `last_response.
5660
+ * image` as a single `{type, base64, width, height}` (the first shape
5661
+ * `pollRevision` in pixellab.ts checks, so no change was ever needed):
5662
+ * 32x32 (1024px², billed 20, this adapter's tiering floor) through
5663
+ * 256x256 (65536px²) all billed 20 (the tiering wrongly predicts 25 from
5664
+ * 1024px² up, see estimate() in pixellab.ts); 288x288 (82944px²) and
5665
+ * 320x320 (102400px²) billed 25 — the middle tier is real, just starting
5666
+ * far higher than this tiering assumes; 352x352 (123904px²), 384x384
5667
+ * (147456px²), and 512x512 (262144px², the tiering ceiling) all billed
5668
+ * 40. Both breakpoints are now tightly bracketed: 20->25 in
5669
+ * (65536px², 82944px²], 25->40 in (102400px², 123904px²].
5670
+ * `editImagesV2` below returns the same fields under `images`, plural
5671
+ * and array-wrapped, not `image` — do not assume the two endpoints share
5672
+ * one response shape. See docs/ENDPOINTS.md and docs/REVISIONS.md for
5673
+ * the full shape and cost picture, including what is still unconfirmed.
5674
+ *
5675
+ * `crop_to_mask` defaults true upstream (confirmed in the schema): PixelLab
5676
+ * otherwise blends generated pixels outside the mask edge to "fit
5677
+ * naturally," which is the opposite of what a mask boundary is for.
5678
+ */
5679
+ async inpaintV3(args) {
5680
+ const size = { width: args.width, height: args.height };
5681
+ const body = {
5682
+ description: args.description,
5683
+ inpainting_image: { image: args.image, size },
5684
+ mask_image: { image: args.maskImage, size }
5685
+ };
5686
+ if (args.noBackground != null) body.no_background = args.noBackground;
5687
+ if (args.cropToMask != null) body.crop_to_mask = args.cropToMask;
5688
+ if (args.seed != null) body.seed = args.seed;
5689
+ return validateResponse(
5690
+ RevisionJobSubmitSchema,
5691
+ await this.request("/inpaint-v3", { method: "POST", body: JSON.stringify(body) }),
5692
+ "inpaint-v3"
5693
+ );
5694
+ }
5695
+ /**
5696
+ * Whole-image edit with no mask, PixelLab's `/edit-images-v2`. `edit_images`
5697
+ * takes an array (the endpoint supports editing several images with one
5698
+ * instruction); pixelkiln's `revision` model is one parent per child, so
5699
+ * this always sends exactly one. Exercised live at its floor size (32x32):
5700
+ * billed exactly the 20-generation estimate this adapter borrows from
5701
+ * `1dir`, matching `inpaintV3`'s floor exactly. The completed response is
5702
+ * `last_response.images`, an *array* of `{type, base64, width, height}` —
5703
+ * plural and array-wrapped, unlike `inpaintV3`'s singular `image` above,
5704
+ * even though exactly one image is ever sent or expected here.
5705
+ */
5706
+ async editImagesV2(args) {
5707
+ const body = {
5708
+ method: "edit_with_text",
5709
+ description: args.description,
5710
+ edit_images: [{ image: args.image, width: args.width, height: args.height }],
5711
+ image_size: { width: args.width, height: args.height }
5712
+ };
5713
+ if (args.noBackground != null) body.no_background = args.noBackground;
5714
+ if (args.seed != null) body.seed = args.seed;
5715
+ return validateResponse(
5716
+ RevisionJobSubmitSchema,
5717
+ await this.request("/edit-images-v2", { method: "POST", body: JSON.stringify(body) }),
5718
+ "edit-images-v2"
5719
+ );
5720
+ }
5599
5721
  async getBackgroundJob(jobId) {
5600
5722
  const raw = await this.request(`/background-jobs/${encodeURIComponent(jobId)}`);
5601
5723
  return validateResponse(BackgroundJobSchema, raw, "background-jobs/{id}");
@@ -5736,6 +5858,15 @@ var PixelLabProvider = class _PixelLabProvider {
5736
5858
  supports(generator) {
5737
5859
  return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "character";
5738
5860
  }
5861
+ /**
5862
+ * `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
5863
+ * no mask) both exist on PixelLab. `outpaint` does not: there is no
5864
+ * canvas-expansion endpoint in the API, matching docs/REVISIONS.md's note
5865
+ * that no provider ships a tested outpaint path yet.
5866
+ */
5867
+ supportsRevision(mode2) {
5868
+ return mode2 === "inpaint" || mode2 === "image-to-image";
5869
+ }
5739
5870
  /** PixelLab's own constraints: submissions must be >2s apart, and
5740
5871
  * background jobs in flight are capped by subscription tier (Tier 1=8,
5741
5872
  * Tier 2=10, Tier 3=20); 8 is the safe floor across every tier. */
@@ -5756,6 +5887,11 @@ var PixelLabProvider = class _PixelLabProvider {
5756
5887
  return dir;
5757
5888
  }
5758
5889
  estimate(spec) {
5890
+ if (spec.revision) {
5891
+ const width = spec.revision.sourceWidth ?? spec.width;
5892
+ const height = spec.revision.sourceHeight ?? spec.height;
5893
+ return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
5894
+ }
5759
5895
  if (spec.generator === "tiles") {
5760
5896
  return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
5761
5897
  }
@@ -5769,6 +5905,12 @@ var PixelLabProvider = class _PixelLabProvider {
5769
5905
  };
5770
5906
  }
5771
5907
  validate(spec, styleImages) {
5908
+ if (spec.revision?.mode === "inpaint") {
5909
+ const { sourceWidth: width, sourceHeight: height } = spec.revision;
5910
+ if (width != null && height != null && (width < 32 || height < 32 || width > 512 || height > 512)) {
5911
+ throw new Error(`PixelLab inpaint source is ${width}x${height}; the API takes 32 to 512 pixels per side`);
5912
+ }
5913
+ }
5772
5914
  if (spec.generator === "1dir" && (spec.width < 32 || spec.width > 256)) {
5773
5915
  throw new Error("PixelLab 1dir dimensions must be between 32 and 256 pixels");
5774
5916
  }
@@ -6118,6 +6260,7 @@ var PixelLabProvider = class _PixelLabProvider {
6118
6260
  }
6119
6261
  async submit(spec, styleImages, context) {
6120
6262
  this.validate(spec, styleImages);
6263
+ if (spec.revision) return this.submitRevision(spec);
6121
6264
  if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
6122
6265
  if (spec.generator === "pixflux") {
6123
6266
  const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
@@ -6137,9 +6280,20 @@ var PixelLabProvider = class _PixelLabProvider {
6137
6280
  const res2 = await this.client.createTilesPro({
6138
6281
  description: spec.prompt,
6139
6282
  tileSize: spec.tileSize,
6283
+ tileHeight: spec.tileHeight,
6140
6284
  tileType: spec.tileType,
6141
6285
  tileView: spec.tileView,
6286
+ tileViewAngle: spec.tileViewAngle,
6287
+ tileDepthRatio: spec.tileDepthRatio,
6288
+ tileFlatTopPx: spec.tileFlatTopPx,
6289
+ obliqueLean: spec.obliqueLean,
6142
6290
  tileFeature: spec.tileFeature,
6291
+ buildingWallTiles: spec.buildingWallTiles,
6292
+ buildingLayout: spec.buildingLayout,
6293
+ buildingWallDescription: spec.buildingWallDescription,
6294
+ buildingFloorDescription: spec.buildingFloorDescription,
6295
+ buildingFloor2Description: spec.buildingFloor2Description,
6296
+ buildingWallAngle: spec.buildingWallAngle,
6143
6297
  outlineMode: spec.outlineMode,
6144
6298
  seed: spec.seed,
6145
6299
  // TilesProStyleImage is flat and wants the reference's real dimensions,
@@ -6169,6 +6323,60 @@ var PixelLabProvider = class _PixelLabProvider {
6169
6323
  });
6170
6324
  return { jobId: res.object_id, metadata: { backgroundJobId: res.background_job_id } };
6171
6325
  }
6326
+ /**
6327
+ * `inpaint` reads the mask as PixelLab's own convention: white marks the
6328
+ * area to generate, black the area to preserve (`InpaintV3Request`'s own
6329
+ * field description). Unlike ComfyUI's revision graph, there is no
6330
+ * configurable side to this — document it as fixed rather than leave an
6331
+ * agent to guess, the way docs/REVISIONS.md tells a ComfyUI author to test
6332
+ * their own graph.
6333
+ */
6334
+ async submitRevision(spec) {
6335
+ const revision = spec.revision;
6336
+ if (!revision.sourceSha256 || !revision.sourceFormat || revision.sourceWidth == null || revision.sourceHeight == null) {
6337
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
6338
+ }
6339
+ const sourceBytes = readFileSync2(revision.sourceFile);
6340
+ if (sha256(sourceBytes) !== revision.sourceSha256) {
6341
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
6342
+ }
6343
+ const image = { base64: sourceBytes.toString("base64"), format: revision.sourceFormat };
6344
+ const width = revision.sourceWidth;
6345
+ const height = revision.sourceHeight;
6346
+ if (revision.mode === "inpaint") {
6347
+ if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
6348
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
6349
+ }
6350
+ const maskBytes = readFileSync2(revision.maskFile);
6351
+ if (sha256(maskBytes) !== revision.maskSha256) {
6352
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
6353
+ }
6354
+ const res2 = await this.client.inpaintV3({
6355
+ description: spec.prompt,
6356
+ image,
6357
+ width,
6358
+ height,
6359
+ maskImage: { base64: maskBytes.toString("base64"), format: revision.maskFormat },
6360
+ noBackground: spec.noBackground,
6361
+ seed: spec.seed
6362
+ });
6363
+ return { jobId: res2.background_job_id };
6364
+ }
6365
+ if (revision.strength != null) {
6366
+ throw new Error(
6367
+ `${spec.styleId}/${spec.assetId}: PixelLab image-to-image revisions take no strength; edit-images-v2 always applies the full instruction`
6368
+ );
6369
+ }
6370
+ const res = await this.client.editImagesV2({
6371
+ description: spec.prompt,
6372
+ image,
6373
+ width,
6374
+ height,
6375
+ noBackground: spec.noBackground,
6376
+ seed: spec.seed
6377
+ });
6378
+ return { jobId: res.background_job_id };
6379
+ }
6172
6380
  /**
6173
6381
  * What a background job actually billed, once its own record is still
6174
6382
  * around to ask; see `billedFromUsage`. A stale or already-cleaned-up job
@@ -6185,6 +6393,7 @@ var PixelLabProvider = class _PixelLabProvider {
6185
6393
  }
6186
6394
  }
6187
6395
  async poll(jobId, generator, context) {
6396
+ if (context?.spec?.revision) return this.pollRevision(jobId);
6188
6397
  if (generator === "pixflux") {
6189
6398
  const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
6190
6399
  if (existsSync7(file)) {
@@ -6215,6 +6424,59 @@ var PixelLabProvider = class _PixelLabProvider {
6215
6424
  etaSeconds: obj.eta_seconds ?? null
6216
6425
  };
6217
6426
  }
6427
+ /**
6428
+ * `/inpaint-v3` and `/edit-images-v2` both hand back a plain background
6429
+ * job with no resource of its own, polled generically at
6430
+ * `GET /background-jobs/{id}`. Confirmed live for both, and the two do not
6431
+ * match: a completed `inpaint-v3` job's `last_response.image` is a single
6432
+ * `{type: "base64", base64, width, height}`, matched by the `imageKeys`
6433
+ * branch below; a completed `edit-images-v2` job's is `last_response.images`,
6434
+ * an *array* of that same shape, matched by the `done.images` branch below
6435
+ * instead — exactly why both branches exist rather than just the first one
6436
+ * (docs/ENDPOINTS.md, docs/REVISIONS.md). Beyond these two confirmed
6437
+ * shapes, this also checks a nested `{image: {base64, format}}` and a
6438
+ * hosted URL under a handful of plausible keys, and fails loudly, naming
6439
+ * the keys it actually got, rather than guess wrong silently, for whatever
6440
+ * shape still isn't covered.
6441
+ */
6442
+ async pollRevision(jobId) {
6443
+ const job = await this.client.getBackgroundJob(jobId);
6444
+ if (job.status === "failed") return { status: "failed", error: "revision job failed upstream" };
6445
+ if (job.status !== "completed") return { status: "processing" };
6446
+ const billed = billedFromUsage(job.usage);
6447
+ const done = job.last_response ?? {};
6448
+ const urlKeys = ["image_url", "download_url", "url", "output_url"];
6449
+ for (const key of urlKeys) {
6450
+ const url = done[key];
6451
+ if (typeof url === "string" && url) {
6452
+ return { status: "ready", objectId: jobId, sourceUrl: url, sources: [{ url }], billed };
6453
+ }
6454
+ }
6455
+ const imageKeys = ["image", "output_image", "result_image", "edited_image"];
6456
+ for (const key of imageKeys) {
6457
+ const candidate = done[key];
6458
+ const base64 = extractBase64(candidate);
6459
+ if (base64) {
6460
+ const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
6461
+ writeFileSync(file, Buffer.from(base64, "base64"));
6462
+ const sourceUrl = `file://${file}`;
6463
+ return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed };
6464
+ }
6465
+ }
6466
+ if (Array.isArray(done.images) && done.images.length) {
6467
+ const base64 = extractBase64(done.images[0]);
6468
+ if (base64) {
6469
+ const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
6470
+ writeFileSync(file, Buffer.from(base64, "base64"));
6471
+ const sourceUrl = `file://${file}`;
6472
+ return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed };
6473
+ }
6474
+ }
6475
+ return {
6476
+ status: "failed",
6477
+ error: `Invalid PixelLab response for revision job ${jobId}: completed with no recognized image field (got: ${Object.keys(done).join(", ") || "no keys"}); update pollRevision in src/providers/pixellab.ts with the real shape`
6478
+ };
6479
+ }
6218
6480
  /**
6219
6481
  * Map objects need their own path because the `/map-objects/{id}` record is
6220
6482
  * deleted upstream roughly 8 hours after creation while the image survives in
@@ -6406,6 +6668,15 @@ function firstUrl(urls) {
6406
6668
  if (!urls) return null;
6407
6669
  return Object.values(urls).find((u) => typeof u === "string") ?? null;
6408
6670
  }
6671
+ function extractBase64(value) {
6672
+ if (typeof value === "string" && value) return value;
6673
+ if (value && typeof value === "object") {
6674
+ const obj = value;
6675
+ if (typeof obj.base64 === "string" && obj.base64) return obj.base64;
6676
+ if (obj.image) return extractBase64(obj.image);
6677
+ }
6678
+ return null;
6679
+ }
6409
6680
  function readReference(spec, reference) {
6410
6681
  const images = {};
6411
6682
  for (const [direction, image] of Object.entries(reference)) {
@@ -7663,9 +7934,20 @@ async function resolveSpecs(loaded, filter) {
7663
7934
  enforcePalette: style.enforcePalette,
7664
7935
  noBackground: style.noBackground,
7665
7936
  tileSize: generator === "tiles" ? tileSize : void 0,
7937
+ tileHeight: generator === "tiles" ? style.tileHeight : void 0,
7666
7938
  tileType: generator === "tiles" ? style.tileType : void 0,
7667
7939
  tileView: generator === "tiles" ? style.tileView : void 0,
7940
+ tileViewAngle: generator === "tiles" ? style.tileViewAngle : void 0,
7941
+ tileDepthRatio: generator === "tiles" ? style.tileDepthRatio : void 0,
7942
+ tileFlatTopPx: generator === "tiles" ? style.tileFlatTopPx : void 0,
7943
+ obliqueLean: generator === "tiles" ? style.obliqueLean : void 0,
7668
7944
  tileFeature: generator === "tiles" ? style.tileFeature : void 0,
7945
+ buildingWallTiles: generator === "tiles" ? style.buildingWallTiles : void 0,
7946
+ buildingLayout: generator === "tiles" ? style.buildingLayout : void 0,
7947
+ buildingWallDescription: generator === "tiles" ? style.buildingWallDescription : void 0,
7948
+ buildingFloorDescription: generator === "tiles" ? style.buildingFloorDescription : void 0,
7949
+ buildingFloor2Description: generator === "tiles" ? style.buildingFloor2Description : void 0,
7950
+ buildingWallAngle: generator === "tiles" ? style.buildingWallAngle : void 0,
7669
7951
  outlineMode: generator === "tiles" ? style.outlineMode : void 0,
7670
7952
  ...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
7671
7953
  cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
@@ -10926,7 +11208,9 @@ function describeHistory(media, entry, cacheDir) {
10926
11208
  retiredAt: generation.retiredAt,
10927
11209
  outputs,
10928
11210
  cached: outputs.length > 0 && outputs.every((output) => output.url !== null),
10929
- upstreamUrl: generation.provider === "pixellab" ? pixelLabObjectUrl(generation.generator, generation.objectId) : null
11211
+ // A revision's jobId is a plain background job id, not a PixelLab
11212
+ // account object: there is no `/create-object` page to point at.
11213
+ upstreamUrl: generation.provider === "pixellab" && !entry.revision ? pixelLabObjectUrl(generation.generator, generation.objectId) : null
10930
11214
  };
10931
11215
  });
10932
11216
  }
@@ -11191,7 +11475,7 @@ async function buildGallerySnapshot(opts) {
11191
11475
  editChanged,
11192
11476
  editStatus,
11193
11477
  editMeta,
11194
- upstreamUrl: entry?.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
11478
+ upstreamUrl: entry?.provider === "pixellab" && !spec.revision ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
11195
11479
  refreshable: Boolean(entry && entry.status === "downloaded" && entry.outputs.length && (entry.sourceUrls?.length || entry.sourceUrl)),
11196
11480
  history: entry ? describeHistory(media, entry, cacheDir) : [],
11197
11481
  tags: spec.tags,
@@ -11255,7 +11539,7 @@ async function buildGallerySnapshot(opts) {
11255
11539
  editChanged: [],
11256
11540
  editStatus: null,
11257
11541
  editMeta: null,
11258
- upstreamUrl: entry.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
11542
+ upstreamUrl: entry.provider === "pixellab" && !entry.revision ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
11259
11543
  refreshable: false,
11260
11544
  history: describeHistory(media, entry, cacheDir),
11261
11545
  tags: [],