pixelkiln 0.58.0 → 0.60.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 +369 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +375 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1265 -145
- package/dist/index.d.ts +1265 -145
- package/dist/index.js +372 -22
- package/dist/index.js.map +1 -1
- package/docs/ENDPOINTS.md +104 -15
- package/docs/GENERATORS.md +104 -0
- package/docs/MANIFEST.md +4 -1
- package/docs/REVISIONS.md +21 -21
- package/package.json +1 -1
- package/schema/manifest.schema.json +174 -2
- package/schema/recipe.schema.json +6 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +69 -29
- package/skills/pixelkiln/references/pixellab.md +65 -4
- package/skills/pixelkiln/references/revisions.md +3 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/types.ts
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
var MediaTypeSchema = z.enum(["image/png", "image/gif"]);
|
|
4
|
-
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro"]);
|
|
4
|
+
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset"]);
|
|
5
5
|
var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
6
6
|
var RevisionModeSchema = z.enum([
|
|
7
7
|
"image-to-image",
|
|
@@ -123,6 +123,56 @@ var CharacterProportionsSchema = z.union([
|
|
|
123
123
|
}).strict()
|
|
124
124
|
]);
|
|
125
125
|
var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
|
|
126
|
+
var UiPieceSchema = z.discriminatedUnion("kind", [
|
|
127
|
+
z.object({
|
|
128
|
+
id: z.string().min(1),
|
|
129
|
+
kind: z.literal("rounded_rect"),
|
|
130
|
+
label: z.string().optional(),
|
|
131
|
+
x: z.number(),
|
|
132
|
+
y: z.number(),
|
|
133
|
+
w: z.number(),
|
|
134
|
+
h: z.number(),
|
|
135
|
+
radius: z.number().min(0).optional()
|
|
136
|
+
}).strict(),
|
|
137
|
+
z.object({
|
|
138
|
+
id: z.string().min(1),
|
|
139
|
+
kind: z.literal("circle"),
|
|
140
|
+
label: z.string().optional(),
|
|
141
|
+
x: z.number(),
|
|
142
|
+
y: z.number(),
|
|
143
|
+
r: z.number()
|
|
144
|
+
}).strict(),
|
|
145
|
+
z.object({
|
|
146
|
+
id: z.string().min(1),
|
|
147
|
+
kind: z.literal("polygon"),
|
|
148
|
+
label: z.string().optional(),
|
|
149
|
+
x: z.number(),
|
|
150
|
+
y: z.number(),
|
|
151
|
+
r: z.number(),
|
|
152
|
+
sides: z.number().int().min(3),
|
|
153
|
+
phase: z.number().optional()
|
|
154
|
+
}).strict()
|
|
155
|
+
]);
|
|
156
|
+
var UiElementSchema = z.enum([
|
|
157
|
+
"button",
|
|
158
|
+
"icon_button",
|
|
159
|
+
"toolbar",
|
|
160
|
+
"tab",
|
|
161
|
+
"panel",
|
|
162
|
+
"window",
|
|
163
|
+
"health_bar",
|
|
164
|
+
"avatar",
|
|
165
|
+
"triangle",
|
|
166
|
+
"pentagon",
|
|
167
|
+
"hexagon",
|
|
168
|
+
"octagon"
|
|
169
|
+
]);
|
|
170
|
+
var AssetBatchSchema = z.object({
|
|
171
|
+
/** The batch leader: another `1dir` asset in the same style. */
|
|
172
|
+
of: z.string().min(1),
|
|
173
|
+
/** 1-based slot in the batch (the leader is implicitly slot 0). Unique and contiguous among siblings. */
|
|
174
|
+
index: z.number().int().min(1)
|
|
175
|
+
}).strict();
|
|
126
176
|
var CharacterStateSchema = z.object({
|
|
127
177
|
/** The base character, or another state, in the same style. */
|
|
128
178
|
of: z.string().min(1),
|
|
@@ -419,6 +469,12 @@ var StyleObjectSchema = z.object({
|
|
|
419
469
|
* in place of `terrainShapeStyle`. The API default is `standard`.
|
|
420
470
|
*/
|
|
421
471
|
terrainMode: z.enum(["standard", "pro"]).optional(),
|
|
472
|
+
/**
|
|
473
|
+
* `uiAsset` generator only. A natural-language palette hint sent as-is
|
|
474
|
+
* (e.g. "brown and gold"), distinct from `palette`'s hex-color array —
|
|
475
|
+
* this is a prompt-level steer, not a local quantization target.
|
|
476
|
+
*/
|
|
477
|
+
uiColorPalette: z.string().max(200).optional(),
|
|
422
478
|
/**
|
|
423
479
|
* `terrain` generator only, `terrainMode: "standard"`. Procedural
|
|
424
480
|
* boundary geometry: `square` or `round`, 16px or 32px tiles only.
|
|
@@ -696,6 +752,16 @@ var AssetSchema = z.object({
|
|
|
696
752
|
* `prompt` instead.
|
|
697
753
|
*/
|
|
698
754
|
animation: CharacterAnimationSchema.optional(),
|
|
755
|
+
/**
|
|
756
|
+
* `1dir` styles only: this asset rides along on another `1dir` asset's
|
|
757
|
+
* batch submission instead of generating on its own. See
|
|
758
|
+
* `AssetBatchSchema`.
|
|
759
|
+
*/
|
|
760
|
+
batch: AssetBatchSchema.optional(),
|
|
761
|
+
/** `uiAsset` styles: precise shape regions the panel is composited from. Combine with `elements`; omit both for a default full-canvas panel. */
|
|
762
|
+
pieces: z.array(UiPieceSchema).min(1).optional(),
|
|
763
|
+
/** `uiAsset` styles: named, auto-positioned UI element scaffolds. Combine with `pieces`; omit both for a default full-canvas panel. */
|
|
764
|
+
elements: z.array(UiElementSchema).min(1).optional(),
|
|
699
765
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
700
766
|
proportions: CharacterProportionsSchema.optional(),
|
|
701
767
|
/**
|
|
@@ -772,7 +838,7 @@ var AssetSchema = z.object({
|
|
|
772
838
|
path: ["revision"]
|
|
773
839
|
});
|
|
774
840
|
}
|
|
775
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror"].filter(Boolean);
|
|
841
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
776
842
|
if (shapes.length > 1) {
|
|
777
843
|
context.addIssue({
|
|
778
844
|
code: z.ZodIssueCode.custom,
|
|
@@ -923,6 +989,12 @@ var LockEntrySchema = z.object({
|
|
|
923
989
|
sourceAssetId: z.string().min(1),
|
|
924
990
|
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
925
991
|
}).strict().nullable().default(null),
|
|
992
|
+
/** For a `1dir` batch member: which leader and slot it rode along on. */
|
|
993
|
+
batch: z.object({
|
|
994
|
+
role: z.enum(["leader", "member"]),
|
|
995
|
+
leaderAssetId: z.string().min(1).optional(),
|
|
996
|
+
index: z.number().int().min(1).optional()
|
|
997
|
+
}).strict().nullable().default(null),
|
|
926
998
|
/**
|
|
927
999
|
* Output hashes owned by the previous generation while its replacement is
|
|
928
1000
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -1207,6 +1279,19 @@ var IsometricTileGetSchema = z2.object({
|
|
|
1207
1279
|
image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
|
|
1208
1280
|
usage: UsageSchema
|
|
1209
1281
|
}).passthrough();
|
|
1282
|
+
var UiAssetSubmitSchema = z2.object({
|
|
1283
|
+
ui_asset_id: z2.string().min(1),
|
|
1284
|
+
background_job_id: z2.string().min(1),
|
|
1285
|
+
status: z2.string().default("processing"),
|
|
1286
|
+
usage: UsageSchema
|
|
1287
|
+
}).passthrough();
|
|
1288
|
+
var UiAssetGetSchema = z2.object({
|
|
1289
|
+
id: z2.string().min(1),
|
|
1290
|
+
status: z2.string().nullable().default(null),
|
|
1291
|
+
image_url: z2.string().nullable().optional(),
|
|
1292
|
+
progress_percent: z2.number().nullable().optional(),
|
|
1293
|
+
eta_seconds: z2.number().nullable().optional()
|
|
1294
|
+
}).passthrough();
|
|
1210
1295
|
var RevisionJobSubmitSchema = z2.object({
|
|
1211
1296
|
background_job_id: z2.string().min(1),
|
|
1212
1297
|
status: z2.string().default("processing")
|
|
@@ -1567,6 +1652,48 @@ var PixelLabClient = class {
|
|
|
1567
1652
|
);
|
|
1568
1653
|
return { image: res.image, usage: res.usage ?? null };
|
|
1569
1654
|
}
|
|
1655
|
+
/**
|
|
1656
|
+
* `/create-ui-asset`: one composited panel image from a shape template —
|
|
1657
|
+
* `pieces` (precise rect/circle/polygon regions on a virtual 0–512
|
|
1658
|
+
* editor canvas) and/or named `elements` (auto-positioned scaffolds:
|
|
1659
|
+
* button, icon_button, toolbar, tab, panel, window, health_bar, avatar,
|
|
1660
|
+
* triangle, pentagon, hexagon, octagon), or neither for a default
|
|
1661
|
+
* full-canvas rounded-rect panel. Unlike every other generator here, the
|
|
1662
|
+
* result is one flat image with no per-piece sub-regions or nine-slice
|
|
1663
|
+
* metadata returned — cropping a `pieces` layout into separate files
|
|
1664
|
+
* would be pixelkiln's own local work, not modeled yet.
|
|
1665
|
+
*/
|
|
1666
|
+
async createUiAsset(args) {
|
|
1667
|
+
const body = {
|
|
1668
|
+
description: args.description,
|
|
1669
|
+
image_size: { width: args.width, height: args.height }
|
|
1670
|
+
};
|
|
1671
|
+
if (args.pieces?.length) body.pieces = args.pieces;
|
|
1672
|
+
if (args.elements?.length) body.elements = args.elements;
|
|
1673
|
+
if (args.styleImage) body.style_image = args.styleImage;
|
|
1674
|
+
if (args.colorPalette) body.color_palette = args.colorPalette;
|
|
1675
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
1676
|
+
if (args.seed != null) body.seed = args.seed;
|
|
1677
|
+
const res = await validateResponse(
|
|
1678
|
+
UiAssetSubmitSchema,
|
|
1679
|
+
await this.request("/create-ui-asset", { method: "POST", body: JSON.stringify(body) }),
|
|
1680
|
+
"create ui asset"
|
|
1681
|
+
);
|
|
1682
|
+
return { ui_asset_id: res.ui_asset_id, background_job_id: res.background_job_id, usage: res.usage };
|
|
1683
|
+
}
|
|
1684
|
+
async getUiAsset(uiAssetId) {
|
|
1685
|
+
const res = await validateResponse(
|
|
1686
|
+
UiAssetGetSchema,
|
|
1687
|
+
await this.request(`/ui-assets/${encodeURIComponent(uiAssetId)}`),
|
|
1688
|
+
"get ui asset"
|
|
1689
|
+
);
|
|
1690
|
+
return {
|
|
1691
|
+
status: res.status,
|
|
1692
|
+
imageUrl: res.image_url ?? null,
|
|
1693
|
+
progressPercent: res.progress_percent ?? null,
|
|
1694
|
+
etaSeconds: res.eta_seconds ?? null
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1570
1697
|
/**
|
|
1571
1698
|
* Synchronous single-image generation. Returns the PNG inline rather than a
|
|
1572
1699
|
* job id, and is the only endpoint that honours a forced palette;
|
|
@@ -1973,14 +2100,15 @@ var PixelLabClient = class {
|
|
|
1973
2100
|
/**
|
|
1974
2101
|
* `/reduce-colors`, PixelLab's "Cleanup" tier: quantize one image onto a
|
|
1975
2102
|
* smaller palette, synchronously — no `background_job_id`, the result
|
|
1976
|
-
* comes back in this same response, like `createImagePixflux`.
|
|
1977
|
-
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
*
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
2103
|
+
* comes back in this same response, like `createImagePixflux`. The
|
|
2104
|
+
* schema's own response example is dollar-denominated (`usage: {type:
|
|
2105
|
+
* "usd", usd: 0.02}`), which — matching this codebase's repeated
|
|
2106
|
+
* experience with PixelLab's documented-vs-billed cost mismatches
|
|
2107
|
+
* (`isometricTile`, `objectPro`) — did not hold: confirmed live against a
|
|
2108
|
+
* Tier 2 account at exactly 0.1 generations for a 32x32 source
|
|
2109
|
+
* (docs/REVISIONS.md). `numColors` and `paletteImage` are mutually
|
|
2110
|
+
* exclusive upstream; the manifest schema already enforces that before
|
|
2111
|
+
* this is ever called.
|
|
1984
2112
|
*/
|
|
1985
2113
|
async reduceColors(args) {
|
|
1986
2114
|
const body = { images: [args.image] };
|
|
@@ -2003,8 +2131,9 @@ var PixelLabClient = class {
|
|
|
2003
2131
|
/**
|
|
2004
2132
|
* `/correct-pixelart`, PixelLab's "Cleanup" tier: sharpen edges and drop
|
|
2005
2133
|
* stray pixels without resizing, synchronously — same shape as
|
|
2006
|
-
* `reduceColors` above, no background job. Cost is likewise
|
|
2007
|
-
*
|
|
2134
|
+
* `reduceColors` above, no background job. Cost is likewise confirmed
|
|
2135
|
+
* live at 0.1 generations for a 32x32 source, not the schema's own
|
|
2136
|
+
* dollar-denominated example (`usage: {type: "usd", usd: 0.02}`).
|
|
2008
2137
|
*/
|
|
2009
2138
|
async correctPixelart(args) {
|
|
2010
2139
|
const body = { images: [args.image] };
|
|
@@ -2702,16 +2831,20 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2702
2831
|
detail: spec.detail ?? null,
|
|
2703
2832
|
seed: spec.seed ?? null,
|
|
2704
2833
|
palette: spec.palette,
|
|
2705
|
-
// `noBackground` only reaches the wire for pixflux; the
|
|
2706
|
-
// undefined for every other generator. `tileSize` is
|
|
2707
|
-
// absent; width/height are derived from it, so it is
|
|
2708
|
-
|
|
2834
|
+
// `noBackground` only reaches the wire for pixflux and uiAsset; the
|
|
2835
|
+
// tile fields are undefined for every other generator. `tileSize` is
|
|
2836
|
+
// intentionally absent; width/height are derived from it, so it is
|
|
2837
|
+
// already covered.
|
|
2838
|
+
noBackground: spec.generator === "pixflux" || spec.generator === "uiAsset" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
2709
2839
|
tileType: spec.tileType,
|
|
2710
2840
|
tileView: spec.tileView,
|
|
2711
2841
|
tileFeature: spec.tileFeature,
|
|
2712
2842
|
outlineMode: spec.outlineMode,
|
|
2713
2843
|
isometricTileSize: spec.isometricTileSize,
|
|
2714
2844
|
isometricTileShape: spec.isometricTileShape,
|
|
2845
|
+
uiPieces: spec.uiPieces,
|
|
2846
|
+
uiElements: spec.uiElements,
|
|
2847
|
+
uiColorPalette: spec.uiColorPalette,
|
|
2715
2848
|
styleImages: styleImageHashes,
|
|
2716
2849
|
// A character family: the engine and rotations of a base, the parent's
|
|
2717
2850
|
// generated bytes for a state or animation (so a regenerated parent
|
|
@@ -2760,6 +2893,11 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2760
2893
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2761
2894
|
// compares those directly, so only the choice of source is identity.
|
|
2762
2895
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
2896
|
+
// The whole group's ordered descriptions, not just this asset's own —
|
|
2897
|
+
// any sibling's prompt changing, or a member being added or removed,
|
|
2898
|
+
// must mark every member stale together, since they all ride on one
|
|
2899
|
+
// submitted job with no way to amend it after the fact.
|
|
2900
|
+
batch: spec.batch ? { role: spec.batch.role, itemDescriptions: spec.batch.itemDescriptions, index: spec.batch.index } : void 0,
|
|
2763
2901
|
revision: spec.revision ? {
|
|
2764
2902
|
mode: spec.revision.mode,
|
|
2765
2903
|
from: spec.revision.sourceAssetId,
|
|
@@ -2904,7 +3042,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2904
3042
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
2905
3043
|
}
|
|
2906
3044
|
supports(generator) {
|
|
2907
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro";
|
|
3045
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset";
|
|
2908
3046
|
}
|
|
2909
3047
|
/**
|
|
2910
3048
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -2941,6 +3079,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2941
3079
|
return dir;
|
|
2942
3080
|
}
|
|
2943
3081
|
estimate(spec) {
|
|
3082
|
+
if (spec.batch?.role === "member") {
|
|
3083
|
+
return { unit: "generations", amount: 0, candidates: 1 };
|
|
3084
|
+
}
|
|
2944
3085
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
2945
3086
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
2946
3087
|
}
|
|
@@ -3017,6 +3158,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3017
3158
|
`PixelLab imagePro is ${spec.width}x${spec.height}; the API takes 16 to 792 wide and 16 to 688 tall (the exact ceiling also depends on aspect ratio)`
|
|
3018
3159
|
);
|
|
3019
3160
|
}
|
|
3161
|
+
if (spec.generator === "uiAsset" && (spec.width < 192 || spec.height < 192 || spec.width > 688 || spec.height > 688)) {
|
|
3162
|
+
throw new Error(
|
|
3163
|
+
`PixelLab uiAsset is ${spec.width}x${spec.height}; the API takes 192 to 688 pixels per side (the exact ceiling also depends on aspect ratio \u2014 square tops out at 512, 16:9 at 688x384, 9:16 at 384x688, 4:3 at 600x448, 3:4 at 448x600)`
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3020
3166
|
if (spec.generator === "map") {
|
|
3021
3167
|
requirePixelLabOption("map", "view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
3022
3168
|
requirePixelLabOption("map", "outline", spec.outline, [
|
|
@@ -3654,6 +3800,20 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3654
3800
|
if (spec.revision) return this.submitRevision(spec);
|
|
3655
3801
|
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
3656
3802
|
if (spec.generator === "objectPro") return this.submitObjectPro(spec, styleImages, context);
|
|
3803
|
+
if (spec.generator === "uiAsset") {
|
|
3804
|
+
const res2 = await this.client.createUiAsset({
|
|
3805
|
+
description: spec.prompt,
|
|
3806
|
+
width: spec.width,
|
|
3807
|
+
height: spec.height,
|
|
3808
|
+
pieces: spec.uiPieces,
|
|
3809
|
+
elements: spec.uiElements,
|
|
3810
|
+
styleImage: styleImages[0] ? { base64: styleImages[0].base64, format: styleImages[0].format } : void 0,
|
|
3811
|
+
colorPalette: spec.uiColorPalette,
|
|
3812
|
+
noBackground: spec.noBackground,
|
|
3813
|
+
seed: spec.seed
|
|
3814
|
+
});
|
|
3815
|
+
return { jobId: res2.ui_asset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
3816
|
+
}
|
|
3657
3817
|
if (spec.generator === "pixflux") {
|
|
3658
3818
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
3659
3819
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -3739,11 +3899,21 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3739
3899
|
return { jobId: res2.background_job_id };
|
|
3740
3900
|
}
|
|
3741
3901
|
if (spec.generator === "1dir") {
|
|
3902
|
+
if (spec.batch?.role === "member") {
|
|
3903
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: a batch member cannot submit on its own`);
|
|
3904
|
+
}
|
|
3742
3905
|
const res2 = await this.client.create1Direction({
|
|
3743
3906
|
description: spec.prompt,
|
|
3744
3907
|
size: spec.size,
|
|
3745
3908
|
view: spec.view === "sidescroller" ? "sidescroller" : "top-down",
|
|
3746
|
-
styleImages
|
|
3909
|
+
styleImages,
|
|
3910
|
+
// The leader's own subject is item_descriptions[0], so `description`
|
|
3911
|
+
// matches it exactly. Confirmed live: item_descriptions[0] owns
|
|
3912
|
+
// candidate slot 0 (a 3-item batch returned exactly the declared
|
|
3913
|
+
// items at frames 0/1/2, in order; docs/GENERATORS.md#1dir), so
|
|
3914
|
+
// sending the same text as `description` too is redundant but
|
|
3915
|
+
// harmless, not a hedge against ambiguity.
|
|
3916
|
+
itemDescriptions: spec.batch?.role === "leader" ? spec.batch.itemDescriptions : void 0
|
|
3747
3917
|
});
|
|
3748
3918
|
return { jobId: res2.object_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
3749
3919
|
}
|
|
@@ -3920,6 +4090,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3920
4090
|
if (generator === "imagePro") return this.pollImagePro(jobId);
|
|
3921
4091
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
3922
4092
|
if (generator === "objectPro") return this.pollObjectPro(jobId, context);
|
|
4093
|
+
if (generator === "uiAsset") return this.pollUiAsset(jobId, context);
|
|
3923
4094
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
3924
4095
|
const obj = await this.client.getObject(jobId);
|
|
3925
4096
|
if (obj.status === "review") {
|
|
@@ -4239,6 +4410,29 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4239
4410
|
throw err;
|
|
4240
4411
|
}
|
|
4241
4412
|
}
|
|
4413
|
+
/**
|
|
4414
|
+
* `/ui-assets/{id}` reports progress with its own `status` field
|
|
4415
|
+
* ("processing"/"completed"/"failed") rather than a 423, and its result is
|
|
4416
|
+
* one flat composited image at a hosted `image_url` — no per-piece
|
|
4417
|
+
* sub-images, no nine-slice metadata, confirmed absent from the response
|
|
4418
|
+
* schema. There is nothing to review: one job is one ready image, like
|
|
4419
|
+
* `isometricTile`.
|
|
4420
|
+
*/
|
|
4421
|
+
async pollUiAsset(uiAssetId, context) {
|
|
4422
|
+
const asset = await this.client.getUiAsset(uiAssetId);
|
|
4423
|
+
if (asset.status === "failed") return { status: "failed", error: "UI asset generation failed upstream" };
|
|
4424
|
+
if (asset.status !== "completed" || !asset.imageUrl) {
|
|
4425
|
+
return { status: "processing", progressPercent: asset.progressPercent, etaSeconds: asset.etaSeconds };
|
|
4426
|
+
}
|
|
4427
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
4428
|
+
return {
|
|
4429
|
+
status: "ready",
|
|
4430
|
+
objectId: uiAssetId,
|
|
4431
|
+
sourceUrl: asset.imageUrl,
|
|
4432
|
+
sources: [{ url: asset.imageUrl }],
|
|
4433
|
+
billed: await this.billedForJob(backgroundJobId)
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4242
4436
|
async selectCandidate(jobId, index, commonTag, generator) {
|
|
4243
4437
|
if (generator === "tiles") {
|
|
4244
4438
|
const set = await this.client.getTilesPro(jobId);
|
|
@@ -7019,6 +7213,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7019
7213
|
size = asset.size ?? style.size ?? 64;
|
|
7020
7214
|
width = size;
|
|
7021
7215
|
height = size;
|
|
7216
|
+
} else if (generator === "uiAsset") {
|
|
7217
|
+
width = asset.width ?? style.size ?? 256;
|
|
7218
|
+
height = asset.height ?? style.size ?? 256;
|
|
7219
|
+
size = Math.max(width, height);
|
|
7022
7220
|
} else {
|
|
7023
7221
|
width = asset.width ?? style.size ?? 64;
|
|
7024
7222
|
height = asset.height ?? style.size ?? 64;
|
|
@@ -7029,6 +7227,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7029
7227
|
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
7030
7228
|
);
|
|
7031
7229
|
}
|
|
7230
|
+
if ((asset.pieces || asset.elements) && generator !== "uiAsset") {
|
|
7231
|
+
throw new Error(
|
|
7232
|
+
`assets.${assetId}: ${asset.pieces ? "pieces" : "elements"} needs a uiAsset style; "${styleId}" generates ${generator}`
|
|
7233
|
+
);
|
|
7234
|
+
}
|
|
7032
7235
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
7033
7236
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
7034
7237
|
let terrainLowerDescription;
|
|
@@ -7075,6 +7278,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7075
7278
|
shading: style.shading,
|
|
7076
7279
|
detail: style.detail,
|
|
7077
7280
|
seed: style.seed,
|
|
7281
|
+
uiPieces: generator === "uiAsset" ? asset.pieces : void 0,
|
|
7282
|
+
uiElements: generator === "uiAsset" ? asset.elements : void 0,
|
|
7283
|
+
uiColorPalette: generator === "uiAsset" ? style.uiColorPalette : void 0,
|
|
7078
7284
|
palette: style.palette,
|
|
7079
7285
|
enforcePalette: style.enforcePalette,
|
|
7080
7286
|
noBackground: style.noBackground,
|
|
@@ -7153,6 +7359,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7153
7359
|
};
|
|
7154
7360
|
styleSpecs.set(assetId, resolved);
|
|
7155
7361
|
}
|
|
7362
|
+
const batchMembersByLeader = /* @__PURE__ */ new Map();
|
|
7363
|
+
for (const id of styleSpecs.keys()) {
|
|
7364
|
+
const batch = manifest.assets[id].batch;
|
|
7365
|
+
if (!batch) continue;
|
|
7366
|
+
const members = batchMembersByLeader.get(batch.of) ?? [];
|
|
7367
|
+
members.push({ id, index: batch.index });
|
|
7368
|
+
batchMembersByLeader.set(batch.of, members);
|
|
7369
|
+
}
|
|
7156
7370
|
const finalized = /* @__PURE__ */ new Set();
|
|
7157
7371
|
const finalize = async (assetId) => {
|
|
7158
7372
|
const resolved = styleSpecs.get(assetId);
|
|
@@ -7311,6 +7525,56 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7311
7525
|
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
7312
7526
|
};
|
|
7313
7527
|
}
|
|
7528
|
+
if (asset.batch) {
|
|
7529
|
+
if (asset.batch.of === assetId) throw new Error(`assets.${assetId}: a batch cannot name itself as its own leader`);
|
|
7530
|
+
const leaderSpec = await finalize(asset.batch.of);
|
|
7531
|
+
if (!leaderSpec.batch || leaderSpec.batch.role !== "leader") {
|
|
7532
|
+
throw new Error(`assets.${assetId}: ${asset.batch.of} is not a 1dir batch leader`);
|
|
7533
|
+
}
|
|
7534
|
+
if (resolved.size !== leaderSpec.size) {
|
|
7535
|
+
throw new Error(
|
|
7536
|
+
`assets.${assetId}: batch members share the leader's canvas size; ${asset.batch.of} is ${leaderSpec.size}px, this is ${resolved.size}px`
|
|
7537
|
+
);
|
|
7538
|
+
}
|
|
7539
|
+
resolved.batch = {
|
|
7540
|
+
role: "member",
|
|
7541
|
+
itemDescriptions: leaderSpec.batch.itemDescriptions,
|
|
7542
|
+
index: asset.batch.index,
|
|
7543
|
+
leaderAssetId: asset.batch.of,
|
|
7544
|
+
leaderSpec
|
|
7545
|
+
};
|
|
7546
|
+
} else {
|
|
7547
|
+
const members = batchMembersByLeader.get(assetId);
|
|
7548
|
+
if (members) {
|
|
7549
|
+
if (style.generator !== "1dir") {
|
|
7550
|
+
throw new Error(`assets.${assetId}: only a 1dir asset can lead a batch`);
|
|
7551
|
+
}
|
|
7552
|
+
const sorted = [...members].sort((a, b) => a.index - b.index);
|
|
7553
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7554
|
+
for (const { index } of sorted) {
|
|
7555
|
+
if (seen.has(index)) throw new Error(`assets.${assetId}: two batch members both claim index ${index}`);
|
|
7556
|
+
seen.add(index);
|
|
7557
|
+
}
|
|
7558
|
+
const expectedIndices = sorted.map((_, i) => i + 1).join(",");
|
|
7559
|
+
if (sorted.map((m) => m.index).join(",") !== expectedIndices) {
|
|
7560
|
+
throw new Error(
|
|
7561
|
+
`assets.${assetId}: batch member indices must be 1..${sorted.length} with no gaps; got ${sorted.map((m) => m.index).join(",")}`
|
|
7562
|
+
);
|
|
7563
|
+
}
|
|
7564
|
+
const total = 1 + sorted.length;
|
|
7565
|
+
const limit = candidateCount(resolved.size);
|
|
7566
|
+
if (total > limit) {
|
|
7567
|
+
throw new Error(
|
|
7568
|
+
`assets.${assetId}: a ${resolved.size}px batch holds at most ${limit} items (1 leader + ${limit - 1} members); this one declares ${total}`
|
|
7569
|
+
);
|
|
7570
|
+
}
|
|
7571
|
+
resolved.batch = {
|
|
7572
|
+
role: "leader",
|
|
7573
|
+
itemDescriptions: [resolved.prompt, ...sorted.map(({ id }) => styleSpecs.get(id).prompt)],
|
|
7574
|
+
memberAssetIds: sorted.map((m) => m.id)
|
|
7575
|
+
};
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7314
7578
|
resolved.specHash = specHash(
|
|
7315
7579
|
resolved,
|
|
7316
7580
|
styleImageHashes,
|
|
@@ -11696,6 +11960,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11696
11960
|
for (const styleId of new Set(items.map((i) => i.spec.styleId))) {
|
|
11697
11961
|
styleImages.set(styleId, await resolveStyleImages(loaded, styleId));
|
|
11698
11962
|
}
|
|
11963
|
+
const byKey = new Map(items.map((item) => [item.key, item]));
|
|
11699
11964
|
let submitted = 0;
|
|
11700
11965
|
let failed = 0;
|
|
11701
11966
|
let spent = 0;
|
|
@@ -11725,8 +11990,62 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11725
11990
|
await pruneInFlight();
|
|
11726
11991
|
}
|
|
11727
11992
|
}
|
|
11993
|
+
function writeBatchMembers(leaderSpec, leaderKey, error) {
|
|
11994
|
+
if (leaderSpec.batch?.role !== "leader") return;
|
|
11995
|
+
const leaderEntry = lock.entries[leaderKey];
|
|
11996
|
+
for (const memberAssetId of leaderSpec.batch.memberAssetIds ?? []) {
|
|
11997
|
+
const memberKey = lockKey(leaderSpec.styleId, memberAssetId);
|
|
11998
|
+
const memberItem = byKey.get(memberKey);
|
|
11999
|
+
if (!memberItem) {
|
|
12000
|
+
throw new Error(
|
|
12001
|
+
`${leaderKey}: batch member "${memberAssetId}" must be submitted in the same run as its leader; run without --only, or include every sibling`
|
|
12002
|
+
);
|
|
12003
|
+
}
|
|
12004
|
+
const memberSpec = memberItem.spec;
|
|
12005
|
+
const memberEstimate = estimates.get(memberKey);
|
|
12006
|
+
const previousMemberEntry = lock.entries[memberKey];
|
|
12007
|
+
const memberSubmittedAt = leaderEntry.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12008
|
+
upsert(lock, memberKey, {
|
|
12009
|
+
styleId: memberSpec.styleId,
|
|
12010
|
+
assetId: memberSpec.assetId,
|
|
12011
|
+
specHash: memberSpec.specHash,
|
|
12012
|
+
generator: memberSpec.generator,
|
|
12013
|
+
prompt: memberSpec.prompt,
|
|
12014
|
+
width: memberSpec.width,
|
|
12015
|
+
height: memberSpec.height,
|
|
12016
|
+
batch: { role: "member", leaderAssetId: leaderSpec.assetId, index: memberSpec.batch.index },
|
|
12017
|
+
status: error ? "failed" : "processing",
|
|
12018
|
+
error,
|
|
12019
|
+
jobId: error ? null : leaderEntry.jobId,
|
|
12020
|
+
submissionComplete: error ? void 0 : true,
|
|
12021
|
+
reviewObjectId: error ? null : leaderEntry.reviewObjectId,
|
|
12022
|
+
objectId: null,
|
|
12023
|
+
candidateIndex: null,
|
|
12024
|
+
outputs: [],
|
|
12025
|
+
supersededOutputs: previousMemberEntry?.outputs.length ? previousMemberEntry.outputs : previousMemberEntry?.supersededOutputs ?? [],
|
|
12026
|
+
providerMetadata: leaderEntry.providerMetadata,
|
|
12027
|
+
sourceUrl: null,
|
|
12028
|
+
sourceUrls: [],
|
|
12029
|
+
submittedAt: memberSubmittedAt,
|
|
12030
|
+
history: historyAfterReplacing(previousMemberEntry, historyLimit(loaded.manifest), memberSubmittedAt),
|
|
12031
|
+
cost: memberEstimate.amount,
|
|
12032
|
+
costUnit: memberEstimate.unit,
|
|
12033
|
+
provider: provider.id,
|
|
12034
|
+
downloadedAt: null
|
|
12035
|
+
});
|
|
12036
|
+
if (error) failed++;
|
|
12037
|
+
else {
|
|
12038
|
+
submitted++;
|
|
12039
|
+
spent += memberEstimate.amount;
|
|
12040
|
+
log(` ${memberKey} \u2192 ${leaderEntry.jobId} (rides on ${leaderKey}'s batch)`);
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
12043
|
+
}
|
|
11728
12044
|
for (const { spec, key } of items) {
|
|
11729
12045
|
const estimate = estimates.get(key);
|
|
12046
|
+
if (spec.batch?.role === "member") {
|
|
12047
|
+
continue;
|
|
12048
|
+
}
|
|
11730
12049
|
if (spec.mirror) {
|
|
11731
12050
|
try {
|
|
11732
12051
|
await requireRevisionReady(spec, lock);
|
|
@@ -11759,6 +12078,14 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11759
12078
|
const since = Date.now() - lastSubmitAt;
|
|
11760
12079
|
if (since < spacing) await sleep(spacing - since);
|
|
11761
12080
|
await requireRevisionReady(spec, lock);
|
|
12081
|
+
if (spec.batch?.role === "leader") {
|
|
12082
|
+
const missing = (spec.batch.memberAssetIds ?? []).filter((id) => !byKey.has(lockKey(spec.styleId, id)));
|
|
12083
|
+
if (missing.length) {
|
|
12084
|
+
throw new Error(
|
|
12085
|
+
`${key}: batch member(s) ${missing.join(", ")} must be submitted in the same run as this leader; run without --only, or include every sibling`
|
|
12086
|
+
);
|
|
12087
|
+
}
|
|
12088
|
+
}
|
|
11762
12089
|
const previousEntry = lock.entries[key];
|
|
11763
12090
|
const resumesCheckpoint = Boolean(
|
|
11764
12091
|
previousEntry?.specHash === spec.specHash && previousEntry.provider === provider.id && previousEntry.generator === spec.generator && previousEntry.submissionComplete === false && previousEntry.jobId
|
|
@@ -11864,6 +12191,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11864
12191
|
inFlight.set(jobId, spec);
|
|
11865
12192
|
submitted++;
|
|
11866
12193
|
spent += estimate.amount;
|
|
12194
|
+
writeBatchMembers(spec, key, null);
|
|
11867
12195
|
log(
|
|
11868
12196
|
` ${key} \u2192 ${jobId} (${spec.width}x${spec.height}` + (estimate.candidates > 1 ? `, ${estimate.candidates} ${spec.tileFeature ? "outputs" : "candidates"}` : "") + `, ${estimate.amount})`
|
|
11869
12197
|
);
|
|
@@ -11875,6 +12203,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11875
12203
|
inFlight.set(entry.jobId, spec);
|
|
11876
12204
|
submitted++;
|
|
11877
12205
|
spent += estimate.amount;
|
|
12206
|
+
writeBatchMembers(spec, key, null);
|
|
11878
12207
|
log(` ${key} \u2192 ${entry.jobId} (recovered from completed checkpoint)`);
|
|
11879
12208
|
} else {
|
|
11880
12209
|
failed++;
|
|
@@ -11883,6 +12212,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11883
12212
|
error: message6,
|
|
11884
12213
|
cost: entry.jobId ? estimate.amount : 0
|
|
11885
12214
|
});
|
|
12215
|
+
writeBatchMembers(spec, key, message6);
|
|
11886
12216
|
log(` FAILED ${key}: ${message6}`);
|
|
11887
12217
|
}
|
|
11888
12218
|
}
|
|
@@ -12474,6 +12804,9 @@ function renderSheet(groups, options = {}) {
|
|
|
12474
12804
|
.cand.active { border-color:var(--dim); box-shadow:0 0 0 2px color-mix(in srgb, var(--dim) 20%, transparent); }
|
|
12475
12805
|
.cand.sel { border-color:var(--ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent); }
|
|
12476
12806
|
.cand.set-frame { cursor:pointer; }
|
|
12807
|
+
.cand.recommended:not(.sel) { border-color:var(--accent); }
|
|
12808
|
+
.cand.recommended .badge { position:absolute; top:-1px; left:-1px; font-size:9.5px; font-weight:650;
|
|
12809
|
+
text-transform:uppercase; letter-spacing:.03em; color:var(--bg); background:var(--accent); padding:1px 5px; }
|
|
12477
12810
|
.cand img { image-rendering:pixelated; display:block;
|
|
12478
12811
|
background-image:
|
|
12479
12812
|
linear-gradient(45deg,#0000 25%,#7f7f7f22 25%,#7f7f7f22 75%,#0000 75%),
|
|
@@ -12631,14 +12964,17 @@ GROUPS.forEach((g, gi) => {
|
|
|
12631
12964
|
}).observe(loop);
|
|
12632
12965
|
}
|
|
12633
12966
|
}
|
|
12967
|
+
const recommended = g.recommendedIndex ?? 0;
|
|
12634
12968
|
g.frameUrls.forEach((url, i) => {
|
|
12635
12969
|
const c = document.createElement('button');
|
|
12636
12970
|
c.type = 'button';
|
|
12637
|
-
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '')
|
|
12971
|
+
c.className = 'cand' + (g.mode === 'frame-set' ? ' set-frame' : '') +
|
|
12972
|
+
(g.recommendedIndex != null && i === recommended ? ' recommended' : '');
|
|
12638
12973
|
c.tabIndex = -1;
|
|
12639
|
-
c.setAttribute('aria-label', g.mode === 'frame-set'
|
|
12974
|
+
c.setAttribute('aria-label', (g.mode === 'frame-set'
|
|
12640
12975
|
? 'Accept ordered frame set from frame ' + (i + 1)
|
|
12641
|
-
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length)
|
|
12976
|
+
: 'Choose candidate ' + (i + 1) + ' of ' + g.frameUrls.length) +
|
|
12977
|
+
(g.recommendedIndex != null && i === recommended ? ' (this asset\u2019s declared slot)' : ''));
|
|
12642
12978
|
|
|
12643
12979
|
const preview = document.createElement('img');
|
|
12644
12980
|
preview.className = 'preview';
|
|
@@ -12651,6 +12987,12 @@ GROUPS.forEach((g, gi) => {
|
|
|
12651
12987
|
index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
|
|
12652
12988
|
g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
|
|
12653
12989
|
c.append(preview, index);
|
|
12990
|
+
if (g.recommendedIndex != null && i === recommended) {
|
|
12991
|
+
const badge = document.createElement('span');
|
|
12992
|
+
badge.className = 'badge';
|
|
12993
|
+
badge.textContent = 'declared';
|
|
12994
|
+
c.append(badge);
|
|
12995
|
+
}
|
|
12654
12996
|
if (displayScale > 1 && largestSide <= 96) {
|
|
12655
12997
|
const actual = document.createElement('img');
|
|
12656
12998
|
actual.className = 'actual';
|
|
@@ -12675,6 +13017,7 @@ GROUPS.forEach((g, gi) => {
|
|
|
12675
13017
|
});
|
|
12676
13018
|
frames.dataset.active = '0';
|
|
12677
13019
|
frames.querySelector('.cand')?.classList.add('active');
|
|
13020
|
+
if (recommended > 0) activate(recommended, frames);
|
|
12678
13021
|
root.appendChild(el);
|
|
12679
13022
|
});
|
|
12680
13023
|
|
|
@@ -12930,7 +13273,11 @@ async function prepareReview(provider, lock, opts = {}) {
|
|
|
12930
13273
|
height: spec.revision.sourceHeight
|
|
12931
13274
|
}
|
|
12932
13275
|
} : {},
|
|
12933
|
-
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {}
|
|
13276
|
+
...current && currentRoute ? { current: { url: currentRoute, width: current.width, height: current.height } } : {},
|
|
13277
|
+
// A batch member's declared slot in the shared candidate set — the
|
|
13278
|
+
// leader's own slot 0 is already the sheet's ordinary default, so
|
|
13279
|
+
// only a member (whose slot is 1+) needs to say so explicitly.
|
|
13280
|
+
...entry.batch?.index != null ? { recommendedIndex: entry.batch.index } : {}
|
|
12934
13281
|
});
|
|
12935
13282
|
} catch (err) {
|
|
12936
13283
|
log(` could not load candidates for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -15977,6 +16324,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
15977
16324
|
};
|
|
15978
16325
|
}
|
|
15979
16326
|
export {
|
|
16327
|
+
AssetBatchSchema,
|
|
15980
16328
|
AssetSchema,
|
|
15981
16329
|
BudgetError,
|
|
15982
16330
|
CHARACTER_DIRECTIONS_4,
|
|
@@ -16043,6 +16391,8 @@ export {
|
|
|
16043
16391
|
ScenarioProvider,
|
|
16044
16392
|
StyleInputSchema,
|
|
16045
16393
|
StyleSchema,
|
|
16394
|
+
UiElementSchema,
|
|
16395
|
+
UiPieceSchema,
|
|
16046
16396
|
UnsupportedCapabilityError,
|
|
16047
16397
|
UsageError,
|
|
16048
16398
|
WorkspaceProjectSchema,
|