pixelkiln 0.64.0 → 0.66.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/README.md +2 -2
- package/dist/cli.js +581 -49
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +2 -0
- package/dist/index.cjs +587 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +784 -180
- package/dist/index.d.ts +784 -180
- package/dist/index.js +584 -49
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +20 -12
- package/docs/CHARACTERS.md +71 -0
- package/docs/ENDPOINTS.md +26 -6
- package/docs/GENERATORS.md +54 -6
- package/docs/MANIFEST.md +6 -3
- package/docs/PIXELLAB.md +5 -4
- package/docs/REVISIONS.md +13 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +57 -2
- package/schema/recipe.schema.json +2 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +14 -10
- package/skills/pixelkiln/references/pixellab.md +10 -0
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", "uiAsset", "uiElement"]);
|
|
4
|
+
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement", "imageProFlash"]);
|
|
5
5
|
var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
6
6
|
var RevisionModeSchema = z.enum([
|
|
7
7
|
"image-to-image",
|
|
@@ -186,6 +186,27 @@ var CharacterStateSchema = z.object({
|
|
|
186
186
|
height: z.number().int().min(16).max(256)
|
|
187
187
|
}).strict().optional()
|
|
188
188
|
}).strict();
|
|
189
|
+
var CharacterPortraitSizeSchema = z.union([
|
|
190
|
+
z.literal(16),
|
|
191
|
+
z.literal(32),
|
|
192
|
+
z.literal(48),
|
|
193
|
+
z.literal(64),
|
|
194
|
+
z.literal(128),
|
|
195
|
+
z.literal(160)
|
|
196
|
+
]);
|
|
197
|
+
var CharacterPortraitSchema = z.object({
|
|
198
|
+
/** The base or state to portray, in the same style. */
|
|
199
|
+
of: z.string().min(1),
|
|
200
|
+
size: CharacterPortraitSizeSchema.default(64)
|
|
201
|
+
}).strict();
|
|
202
|
+
var CharacterOutfitSchema = z.object({
|
|
203
|
+
/** The loop (a character animation asset) to re-clothe, in the same style. */
|
|
204
|
+
of: z.string().min(1),
|
|
205
|
+
/** Manifest-relative image of the outfit to transfer. */
|
|
206
|
+
reference: z.string().min(1),
|
|
207
|
+
/** Extra guidance PixelLab passes through as written, e.g. view/direction hints. */
|
|
208
|
+
additionalInstructions: z.string().max(2e3).optional()
|
|
209
|
+
}).strict();
|
|
189
210
|
var CharacterAnimationSchema = z.object({
|
|
190
211
|
/** The character to animate: a base or a state in the same style. */
|
|
191
212
|
of: z.string().min(1),
|
|
@@ -307,8 +328,22 @@ var RevisionSchema = z.object({
|
|
|
307
328
|
/** `animate-pixminimax` only: facing direction, used only alongside `enhancePrompt` to hold the sprite's facing. */
|
|
308
329
|
direction: CharacterDirectionSchema.optional(),
|
|
309
330
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
310
|
-
enhancePrompt: z.boolean().optional()
|
|
331
|
+
enhancePrompt: z.boolean().optional(),
|
|
332
|
+
/**
|
|
333
|
+
* `image-to-image`/`inpaint` only: which provider engine draws the
|
|
334
|
+
* edit. `pro-flash` selects PixelLab's Pro Flash edit and inpaint tier
|
|
335
|
+
* (`/edit-image-pro-flash`, `/inpaint-image-pro-flash`) instead of the
|
|
336
|
+
* default Pro endpoints; omit it for the default.
|
|
337
|
+
*/
|
|
338
|
+
engine: z.enum(["pro-flash"]).optional()
|
|
311
339
|
}).strict().superRefine((revision, context) => {
|
|
340
|
+
if (revision.engine !== void 0 && revision.mode !== "image-to-image" && revision.mode !== "inpaint") {
|
|
341
|
+
context.addIssue({
|
|
342
|
+
code: z.ZodIssueCode.custom,
|
|
343
|
+
message: "engine applies to image-to-image and inpaint revisions only",
|
|
344
|
+
path: ["engine"]
|
|
345
|
+
});
|
|
346
|
+
}
|
|
312
347
|
if (revision.mode === "inpaint" && !revision.mask) {
|
|
313
348
|
context.addIssue({
|
|
314
349
|
code: z.ZodIssueCode.custom,
|
|
@@ -597,8 +632,8 @@ var StyleObjectSchema = z.object({
|
|
|
597
632
|
/** `character` only. Body template: `mannequin` (default), a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`), or `custom` for `pro-flash`. */
|
|
598
633
|
template: z.string().min(1).optional(),
|
|
599
634
|
/**
|
|
600
|
-
* `character`
|
|
601
|
-
* the style image lends. Each defaults to true.
|
|
635
|
+
* `character` `pro-flash` bases and `imageProFlash` styles, with a
|
|
636
|
+
* style image: which traits the style image lends. Each defaults to true.
|
|
602
637
|
*/
|
|
603
638
|
styleTraits: z.object({
|
|
604
639
|
palette: z.boolean().optional(),
|
|
@@ -807,6 +842,10 @@ var AssetSchema = z.object({
|
|
|
807
842
|
pieces: z.array(UiPieceSchema).min(1).optional(),
|
|
808
843
|
/** `uiAsset` styles: named, auto-positioned UI element scaffolds. Combine with `pieces`; omit both for a default full-canvas panel. */
|
|
809
844
|
elements: z.array(UiElementSchema).min(1).optional(),
|
|
845
|
+
/** `character` styles: this asset is a bust portrait of another character asset's south sprite. */
|
|
846
|
+
portrait: CharacterPortraitSchema.optional(),
|
|
847
|
+
/** `character` styles: this asset re-clothes an existing loop's frames with a reference outfit. */
|
|
848
|
+
outfit: CharacterOutfitSchema.optional(),
|
|
810
849
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
811
850
|
proportions: CharacterProportionsSchema.optional(),
|
|
812
851
|
/**
|
|
@@ -883,7 +922,7 @@ var AssetSchema = z.object({
|
|
|
883
922
|
path: ["revision"]
|
|
884
923
|
});
|
|
885
924
|
}
|
|
886
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
925
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit"].filter(Boolean);
|
|
887
926
|
if (shapes.length > 1) {
|
|
888
927
|
context.addIssue({
|
|
889
928
|
code: z.ZodIssueCode.custom,
|
|
@@ -898,24 +937,24 @@ var AssetSchema = z.object({
|
|
|
898
937
|
path: ["mirror"]
|
|
899
938
|
});
|
|
900
939
|
}
|
|
901
|
-
if (asset.prompt === void 0 && !asset.mirror) {
|
|
940
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit) {
|
|
902
941
|
context.addIssue({
|
|
903
942
|
code: z.ZodIssueCode.custom,
|
|
904
943
|
message: "Required",
|
|
905
944
|
path: ["prompt"]
|
|
906
945
|
});
|
|
907
946
|
}
|
|
908
|
-
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
947
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
909
948
|
context.addIssue({
|
|
910
949
|
code: z.ZodIssueCode.custom,
|
|
911
|
-
message: "a reference sprite belongs on a base; a state, animation, or
|
|
950
|
+
message: "a reference sprite belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
912
951
|
path: ["reference"]
|
|
913
952
|
});
|
|
914
953
|
}
|
|
915
|
-
if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
|
|
954
|
+
if (asset.concept && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
916
955
|
context.addIssue({
|
|
917
956
|
code: z.ZodIssueCode.custom,
|
|
918
|
-
message: "a concept image belongs on a base; a state, animation, or
|
|
957
|
+
message: "a concept image belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
919
958
|
path: ["concept"]
|
|
920
959
|
});
|
|
921
960
|
}
|
|
@@ -1027,7 +1066,8 @@ var LockEntrySchema = z.object({
|
|
|
1027
1066
|
fps: z.number().int().min(1).max(60).optional(),
|
|
1028
1067
|
lastFrameSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1029
1068
|
direction: CharacterDirectionSchema.optional(),
|
|
1030
|
-
enhancePrompt: z.boolean().optional()
|
|
1069
|
+
enhancePrompt: z.boolean().optional(),
|
|
1070
|
+
engine: z.enum(["pro-flash"]).optional()
|
|
1031
1071
|
}).strict().nullable().default(null),
|
|
1032
1072
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
1033
1073
|
mirror: z.object({
|
|
@@ -1040,6 +1080,17 @@ var LockEntrySchema = z.object({
|
|
|
1040
1080
|
leaderAssetId: z.string().min(1).optional(),
|
|
1041
1081
|
index: z.number().int().min(1).optional()
|
|
1042
1082
|
}).strict().nullable().default(null),
|
|
1083
|
+
/**
|
|
1084
|
+
* For an outfit: the loop it re-clothed, a hash over that loop's output
|
|
1085
|
+
* hashes when this was made, and the reference image's own hash. Tracked
|
|
1086
|
+
* the way `mirror` is, not through `specHash`: manifest resolution has no
|
|
1087
|
+
* lock to hash the parent's actual current frame bytes against.
|
|
1088
|
+
*/
|
|
1089
|
+
outfit: z.object({
|
|
1090
|
+
sourceAssetId: z.string().min(1),
|
|
1091
|
+
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
1092
|
+
referenceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
1093
|
+
}).strict().nullable().default(null),
|
|
1043
1094
|
/**
|
|
1044
1095
|
* Output hashes owned by the previous generation while its replacement is
|
|
1045
1096
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -1393,6 +1444,14 @@ var CorrectPixelartResponseSchema = z2.object({
|
|
|
1393
1444
|
images: z2.array(z2.object({ base64: z2.string().min(1) }).passthrough()).min(1),
|
|
1394
1445
|
usage: z2.unknown().optional()
|
|
1395
1446
|
}).passthrough();
|
|
1447
|
+
var ProFlashImageSubmitSchema = z2.object({
|
|
1448
|
+
background_job_id: z2.string().min(1),
|
|
1449
|
+
image_id: z2.string().min(1),
|
|
1450
|
+
source_image_id: z2.string().min(1),
|
|
1451
|
+
status: z2.string().default("processing"),
|
|
1452
|
+
estimated_generations: z2.number().nullable().optional(),
|
|
1453
|
+
usage: z2.unknown().optional()
|
|
1454
|
+
}).passthrough();
|
|
1396
1455
|
var UnzoomResponseSchema = z2.object({
|
|
1397
1456
|
image: z2.object({ base64: z2.string().min(1) }).passthrough(),
|
|
1398
1457
|
original_size: z2.object({ width: z2.number().int(), height: z2.number().int() }).passthrough(),
|
|
@@ -1443,6 +1502,28 @@ var AnimateObjectSubmitSchema = z2.object({
|
|
|
1443
1502
|
submissions: z2.array(DirectionSubmissionSchema),
|
|
1444
1503
|
usage: UsageSchema
|
|
1445
1504
|
}).passthrough();
|
|
1505
|
+
var PortraitSubmitSchema = z2.object({
|
|
1506
|
+
background_job_id: z2.string().min(1),
|
|
1507
|
+
status: z2.string().default("processing"),
|
|
1508
|
+
usage: UsageSchema
|
|
1509
|
+
}).passthrough();
|
|
1510
|
+
var PortraitJobSchema = z2.object({
|
|
1511
|
+
status: z2.string(),
|
|
1512
|
+
download_url: z2.string().nullable().optional(),
|
|
1513
|
+
width: z2.number().int().nullable().optional(),
|
|
1514
|
+
height: z2.number().int().nullable().optional()
|
|
1515
|
+
}).passthrough();
|
|
1516
|
+
var SetPortraitSchema = z2.object({
|
|
1517
|
+
character_id: z2.string().min(1),
|
|
1518
|
+
size: z2.number().int().positive(),
|
|
1519
|
+
url: z2.string().min(1),
|
|
1520
|
+
usage: UsageSchema
|
|
1521
|
+
}).passthrough();
|
|
1522
|
+
var TransferOutfitSubmitSchema = z2.object({
|
|
1523
|
+
background_job_id: z2.string().min(1),
|
|
1524
|
+
status: z2.string().default("processing"),
|
|
1525
|
+
usage: UsageSchema
|
|
1526
|
+
}).passthrough();
|
|
1446
1527
|
var CharacterAnimationDirectionSchema = z2.object({
|
|
1447
1528
|
direction: z2.string(),
|
|
1448
1529
|
frame_count: z2.number().int().min(0),
|
|
@@ -1811,7 +1892,7 @@ var PixelLabClient = class {
|
|
|
1811
1892
|
const body = {
|
|
1812
1893
|
description: args.description,
|
|
1813
1894
|
n_directions: args.directions,
|
|
1814
|
-
...args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
1895
|
+
...args.sourceImageId ? { source_image_id: args.sourceImageId } : args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
1815
1896
|
...args.view ? { view: args.view } : {},
|
|
1816
1897
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1817
1898
|
...args.styleReference && args.styleReferenceSize ? {
|
|
@@ -1958,8 +2039,9 @@ var PixelLabClient = class {
|
|
|
1958
2039
|
description: args.description,
|
|
1959
2040
|
template_id: args.template,
|
|
1960
2041
|
n_directions: 8,
|
|
1961
|
-
// The author's sprite sets the canvas; a size only applies to text
|
|
1962
|
-
|
|
2042
|
+
// The author's sprite sets the canvas; a size only applies to text
|
|
2043
|
+
// creation. An owned copy of the same sprite is referenced by id.
|
|
2044
|
+
...args.sourceImageId ? { source_image_id: args.sourceImageId } : south ? { first_frame: encode(south) } : { image_size: imageSize },
|
|
1963
2045
|
...args.view ? { view: args.view } : {},
|
|
1964
2046
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1965
2047
|
...args.styleReference && args.styleReferenceSize ? {
|
|
@@ -2058,6 +2140,69 @@ var PixelLabClient = class {
|
|
|
2058
2140
|
});
|
|
2059
2141
|
return validateResponse(AnimateSubmitSchema, raw, "animate-character");
|
|
2060
2142
|
}
|
|
2143
|
+
/**
|
|
2144
|
+
* A full-body sprite in, a bust portrait out (or the reverse, unused
|
|
2145
|
+
* today). Job-based like a state or `1dir`, not the couple-generations
|
|
2146
|
+
* "conversion" the name suggests: the smallest `resultSize` (16px) billed
|
|
2147
|
+
* 20 generations live (docs/ENDPOINTS.md, "Characters, measured").
|
|
2148
|
+
*/
|
|
2149
|
+
async createPortraitCharacterPro(args) {
|
|
2150
|
+
const raw = await this.request("/portrait-character-pro", {
|
|
2151
|
+
method: "POST",
|
|
2152
|
+
body: JSON.stringify({
|
|
2153
|
+
direction: args.direction,
|
|
2154
|
+
image: { type: "base64", base64: args.image.base64, format: args.image.format },
|
|
2155
|
+
...args.view ? { view: args.view } : {},
|
|
2156
|
+
...args.resultSize != null ? { result_size: args.resultSize } : {},
|
|
2157
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
2158
|
+
})
|
|
2159
|
+
});
|
|
2160
|
+
return validateResponse(PortraitSubmitSchema, raw, "portrait-character-pro");
|
|
2161
|
+
}
|
|
2162
|
+
/** Throws PixelLabError(423) while the job is still drawing, like tiles and map objects. */
|
|
2163
|
+
async getPortraitCharacterJob(jobId) {
|
|
2164
|
+
const raw = await this.request(`/portrait-character-pro/${encodeURIComponent(jobId)}`);
|
|
2165
|
+
return validateResponse(PortraitJobSchema, raw, "portrait-character-pro/{id}");
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Attaches a portrait image to a character record. Synchronous and free
|
|
2169
|
+
* (0 generations, live); PixelLab does not link this to
|
|
2170
|
+
* `createPortraitCharacterPro` automatically, and `GET /characters/{id}`
|
|
2171
|
+
* never grows a portrait field, so this response's `url` is the only place
|
|
2172
|
+
* it appears.
|
|
2173
|
+
*/
|
|
2174
|
+
async setCharacterPortrait(characterId, image) {
|
|
2175
|
+
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}/portrait`, {
|
|
2176
|
+
method: "POST",
|
|
2177
|
+
body: JSON.stringify({ image: { type: "base64", base64: image.base64, format: image.format } })
|
|
2178
|
+
});
|
|
2179
|
+
return validateResponse(SetPortraitSchema, raw, "characters/{id}/portrait");
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Applies a reference outfit to 2–16 existing frames. Job-based like
|
|
2183
|
+
* everything else here, but the completed job returns the result frames
|
|
2184
|
+
* inline as base64 (`last_response.quantized_images`), not as storage
|
|
2185
|
+
* URLs — the one PixelLab character tool that works this way. A live
|
|
2186
|
+
* 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md).
|
|
2187
|
+
*/
|
|
2188
|
+
async transferOutfitV2(args) {
|
|
2189
|
+
const image = (img) => ({
|
|
2190
|
+
image: { type: "base64", base64: img.base64, format: img.format },
|
|
2191
|
+
size: { width: img.width, height: img.height }
|
|
2192
|
+
});
|
|
2193
|
+
const raw = await this.request("/transfer-outfit-v2", {
|
|
2194
|
+
method: "POST",
|
|
2195
|
+
body: JSON.stringify({
|
|
2196
|
+
reference_image: image(args.referenceImage),
|
|
2197
|
+
frames: args.frames.map(image),
|
|
2198
|
+
image_size: args.imageSize,
|
|
2199
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
2200
|
+
...args.noBackground !== void 0 ? { no_background: args.noBackground } : {},
|
|
2201
|
+
...args.additionalInstructions ? { additional_instructions: args.additionalInstructions } : {}
|
|
2202
|
+
})
|
|
2203
|
+
});
|
|
2204
|
+
return validateResponse(TransferOutfitSubmitSchema, raw, "transfer-outfit-v2");
|
|
2205
|
+
}
|
|
2061
2206
|
async getCharacter(characterId) {
|
|
2062
2207
|
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}`);
|
|
2063
2208
|
return validateResponse(CharacterSchema, raw, "characters/{id}");
|
|
@@ -2168,6 +2313,66 @@ var PixelLabClient = class {
|
|
|
2168
2313
|
"generate-image-v2"
|
|
2169
2314
|
);
|
|
2170
2315
|
}
|
|
2316
|
+
/**
|
|
2317
|
+
* `/create-image-pro-flash`: one native pixel-art image from a
|
|
2318
|
+
* description, optionally styled after a style image with per-trait
|
|
2319
|
+
* toggles, on the same Pro Flash model `character`/`objectPro` use for
|
|
2320
|
+
* their `pro-flash` engine. Sizes run 16 to 256 per side in multiples of 4.
|
|
2321
|
+
* A plain background job; `pollRevision`'s generic reader takes the
|
|
2322
|
+
* finished image. The response's `source_image_id` is what a Pro Flash
|
|
2323
|
+
* character or object can reuse instead of an upload.
|
|
2324
|
+
*/
|
|
2325
|
+
async createImageProFlash(args) {
|
|
2326
|
+
const body = {
|
|
2327
|
+
description: args.description,
|
|
2328
|
+
image_size: { width: args.width, height: args.height }
|
|
2329
|
+
};
|
|
2330
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2331
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2332
|
+
if (args.styleImage) {
|
|
2333
|
+
body.style_image = { image: args.styleImage.image, size: { width: args.styleImage.width, height: args.styleImage.height } };
|
|
2334
|
+
const traits = args.styleTraits;
|
|
2335
|
+
if (traits) {
|
|
2336
|
+
body.style_options = {
|
|
2337
|
+
...traits.palette !== void 0 ? { color_palette: traits.palette } : {},
|
|
2338
|
+
...traits.outline !== void 0 ? { outline: traits.outline } : {},
|
|
2339
|
+
...traits.detail !== void 0 ? { detail: traits.detail } : {},
|
|
2340
|
+
...traits.shading !== void 0 ? { shading: traits.shading } : {}
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return validateResponse(
|
|
2345
|
+
ProFlashImageSubmitSchema,
|
|
2346
|
+
await this.request("/create-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
2347
|
+
"create-image-pro-flash"
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
/** `/edit-image-pro-flash`, text method: edit one image at its own size. The canvas never grows or resizes. */
|
|
2351
|
+
async editImageProFlash(args) {
|
|
2352
|
+
const body = { image: args.image, method: "text", description: args.description };
|
|
2353
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2354
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2355
|
+
return validateResponse(
|
|
2356
|
+
ProFlashImageSubmitSchema,
|
|
2357
|
+
await this.request("/edit-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
2358
|
+
"edit-image-pro-flash"
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* `/inpaint-image-pro-flash`: replace only the white pixels of a same-size
|
|
2363
|
+
* black/white mask, keeping everything else exactly. `Modify current
|
|
2364
|
+
* layer` (the endpoint's default) returns the full composite.
|
|
2365
|
+
*/
|
|
2366
|
+
async inpaintImageProFlash(args) {
|
|
2367
|
+
const body = { image: args.image, mask_image: args.maskImage, description: args.description };
|
|
2368
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2369
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2370
|
+
return validateResponse(
|
|
2371
|
+
ProFlashImageSubmitSchema,
|
|
2372
|
+
await this.request("/inpaint-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
2373
|
+
"inpaint-image-pro-flash"
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2171
2376
|
/**
|
|
2172
2377
|
* `/generate-ui-v2` ("Generate UI (Pro)"): one UI element (a button, a
|
|
2173
2378
|
* health bar, an inventory slot, a dialogue box) from a description,
|
|
@@ -3048,6 +3253,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3048
3253
|
uiPieces: spec.uiPieces,
|
|
3049
3254
|
uiElements: spec.uiElements,
|
|
3050
3255
|
uiColorPalette: spec.uiColorPalette,
|
|
3256
|
+
styleTraits: spec.styleTraits,
|
|
3051
3257
|
styleImages: styleImageHashes,
|
|
3052
3258
|
// A character family: the engine and rotations of a base, the parent's
|
|
3053
3259
|
// generated bytes for a state or animation (so a regenerated parent
|
|
@@ -3074,7 +3280,16 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3074
3280
|
styleTraits: spec.character.styleTraits,
|
|
3075
3281
|
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
|
|
3076
3282
|
concept: spec.character.concept?.sha256,
|
|
3077
|
-
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
3283
|
+
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0,
|
|
3284
|
+
// The parent's own bytes come from its recorded outputs; the plan
|
|
3285
|
+
// compares those directly (like a mirror), so only the choice of
|
|
3286
|
+
// parent, the reference image's bytes, and any extra guidance
|
|
3287
|
+
// are identity here.
|
|
3288
|
+
outfit: spec.character.outfit ? {
|
|
3289
|
+
of: spec.character.parentAssetId,
|
|
3290
|
+
reference: spec.character.outfit.reference.sha256,
|
|
3291
|
+
additionalInstructions: spec.character.outfit.additionalInstructions
|
|
3292
|
+
} : void 0
|
|
3078
3293
|
} : void 0,
|
|
3079
3294
|
// An objectPro family: same shape as character above, minus the
|
|
3080
3295
|
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
@@ -3115,7 +3330,8 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3115
3330
|
fps: spec.revision.fps,
|
|
3116
3331
|
lastFrameSha256: spec.revision.lastFrameSha256,
|
|
3117
3332
|
direction: spec.revision.direction,
|
|
3118
|
-
enhancePrompt: spec.revision.enhancePrompt
|
|
3333
|
+
enhancePrompt: spec.revision.enhancePrompt,
|
|
3334
|
+
engine: spec.revision.engine
|
|
3119
3335
|
} : void 0
|
|
3120
3336
|
})
|
|
3121
3337
|
);
|
|
@@ -3155,7 +3371,10 @@ function characterCost(spec) {
|
|
|
3155
3371
|
if (animation.mode === "v3") return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
3156
3372
|
return generationCost(spec.width, spec.height, "1dir");
|
|
3157
3373
|
}
|
|
3158
|
-
if (character.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
3374
|
+
if (character.kind === "state" || character.kind === "portrait") return generationCost(spec.width, spec.height, "1dir");
|
|
3375
|
+
if (character.kind === "outfit") {
|
|
3376
|
+
return 20;
|
|
3377
|
+
}
|
|
3159
3378
|
if (character.mode === "standard") return 1;
|
|
3160
3379
|
if (character.mode === "v3") return 1 + Math.max(1, Math.ceil(px * 8 / 65536));
|
|
3161
3380
|
if (character.mode === "pro-flash") {
|
|
@@ -3168,8 +3387,11 @@ function proFlashCharacterCost(width, height, fromReference) {
|
|
|
3168
3387
|
const side = Math.max(32, Math.ceil(Math.max(width, height) / 4) * 4);
|
|
3169
3388
|
const rotations = Math.max(1, Math.ceil(side * side * 8 / 65536));
|
|
3170
3389
|
if (fromReference) return rotations;
|
|
3171
|
-
|
|
3172
|
-
|
|
3390
|
+
return proFlashImageCost(side, side) + rotations;
|
|
3391
|
+
}
|
|
3392
|
+
function proFlashImageCost(width, height) {
|
|
3393
|
+
const side = Math.max(width, height);
|
|
3394
|
+
return side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
3173
3395
|
}
|
|
3174
3396
|
function characterAnimationName(spec) {
|
|
3175
3397
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
@@ -3245,7 +3467,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3245
3467
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
3246
3468
|
}
|
|
3247
3469
|
supports(generator) {
|
|
3248
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement";
|
|
3470
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement" || generator === "imageProFlash";
|
|
3249
3471
|
}
|
|
3250
3472
|
/**
|
|
3251
3473
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -3272,15 +3494,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3272
3494
|
return { spacingMs: 2500, maxInFlight: 8 };
|
|
3273
3495
|
}
|
|
3274
3496
|
/**
|
|
3275
|
-
* Where synchronous pixflux results
|
|
3497
|
+
* Where synchronous pixflux results (or an outfit transfer's inline
|
|
3498
|
+
* frames) are parked between `submit`/poll and `fetch`.
|
|
3276
3499
|
*
|
|
3277
|
-
* pixflux returns the PNG inline rather than a job id,
|
|
3278
|
-
*
|
|
3279
|
-
*
|
|
3280
|
-
*
|
|
3500
|
+
* pixflux returns the PNG inline rather than a job id, and an outfit
|
|
3501
|
+
* transfer's completed job returns its frames inline too, but the
|
|
3502
|
+
* pipeline is built around submit → poll → fetch running as separate
|
|
3503
|
+
* commands. Writing the bytes to a known path keeps that model intact:
|
|
3504
|
+
* the id is the filename, polling is an existence check, and downloading
|
|
3505
|
+
* is a file read.
|
|
3281
3506
|
*/
|
|
3282
|
-
static cacheDir() {
|
|
3283
|
-
const dir = path.join(os.tmpdir(),
|
|
3507
|
+
static cacheDir(namespace = "pixflux") {
|
|
3508
|
+
const dir = path.join(os.tmpdir(), `pixelkiln-${namespace}`);
|
|
3284
3509
|
mkdirSync(dir, { recursive: true });
|
|
3285
3510
|
return dir;
|
|
3286
3511
|
}
|
|
@@ -3304,6 +3529,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3304
3529
|
const base = Math.max(1, Math.ceil(width * height * frames / 65536));
|
|
3305
3530
|
return { unit: "generations", amount: spec.revision.enhancePrompt ? base + 0.05 : base, candidates: 1 };
|
|
3306
3531
|
}
|
|
3532
|
+
if (spec.revision?.engine === "pro-flash") {
|
|
3533
|
+
return {
|
|
3534
|
+
unit: "generations",
|
|
3535
|
+
amount: proFlashImageCost(spec.revision.sourceWidth ?? spec.width, spec.revision.sourceHeight ?? spec.height),
|
|
3536
|
+
candidates: 1
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3307
3539
|
if (spec.revision) {
|
|
3308
3540
|
const width = spec.revision.sourceWidth ?? spec.width;
|
|
3309
3541
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
@@ -3318,6 +3550,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3318
3550
|
if (spec.generator === "objectPro") {
|
|
3319
3551
|
return { unit: "generations", amount: objectProCost(spec), candidates: 1 };
|
|
3320
3552
|
}
|
|
3553
|
+
if (spec.generator === "imageProFlash") {
|
|
3554
|
+
return { unit: "generations", amount: proFlashImageCost(spec.width, spec.height), candidates: 1 };
|
|
3555
|
+
}
|
|
3321
3556
|
return {
|
|
3322
3557
|
unit: "generations",
|
|
3323
3558
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
@@ -3331,7 +3566,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3331
3566
|
`PixelLab ${spec.revision.mode} reads one source image; ${spec.revision.sourceAssetId} is a ${members.length}-member set (${members[0].role}, ...). Revise one member through a declared source, or use reduce-colors, correct-pixelart, or edit-animation, which take the whole set`
|
|
3332
3567
|
);
|
|
3333
3568
|
}
|
|
3334
|
-
if (spec.revision?.
|
|
3569
|
+
if (spec.revision?.engine === "pro-flash") {
|
|
3570
|
+
const { sourceWidth: width, sourceHeight: height, mode: mode2 } = spec.revision;
|
|
3571
|
+
if (width != null && height != null && (width < 32 || height < 32 || width > 256 || height > 256 || width % 4 || height % 4)) {
|
|
3572
|
+
throw new Error(
|
|
3573
|
+
`PixelLab pro-flash ${mode2} source is ${width}x${height}; the API takes 32 to 256 pixels per side, in multiples of 4`
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
if (spec.revision.strength != null) {
|
|
3577
|
+
throw new Error(`PixelLab pro-flash ${mode2} takes no strength; the edit applies in full`);
|
|
3578
|
+
}
|
|
3579
|
+
} else if (spec.revision?.mode === "inpaint") {
|
|
3335
3580
|
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
3336
3581
|
if (width != null && height != null && (width < 32 || height < 32 || width > 512 || height > 512)) {
|
|
3337
3582
|
throw new Error(`PixelLab inpaint source is ${width}x${height}; the API takes 32 to 512 pixels per side`);
|
|
@@ -3412,6 +3657,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3412
3657
|
`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)`
|
|
3413
3658
|
);
|
|
3414
3659
|
}
|
|
3660
|
+
if (spec.generator === "imageProFlash") {
|
|
3661
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > 256 || spec.height > 256 || spec.width % 4 || spec.height % 4) {
|
|
3662
|
+
throw new Error(
|
|
3663
|
+
`PixelLab imageProFlash is ${spec.width}x${spec.height}; the API takes 16 to 256 pixels per side, in multiples of 4`
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
if (styleImages.length > 1) {
|
|
3667
|
+
throw new Error("PixelLab imageProFlash takes one style image; list at most one styleImages entry");
|
|
3668
|
+
}
|
|
3669
|
+
if (spec.styleTraits && !styleImages.length) {
|
|
3670
|
+
throw new Error("PixelLab imageProFlash: styleTraits choose what a style image lends; add a styleImages entry or drop them");
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3415
3673
|
if (spec.generator === "uiElement" && (spec.width < 16 || spec.height < 16 || spec.width > 792 || spec.height > 688)) {
|
|
3416
3674
|
throw new Error(
|
|
3417
3675
|
`PixelLab uiElement is ${spec.width}x${spec.height}; the API takes 16 to 792 wide and 16 to 688 tall (the exact ceiling depends on aspect ratio \u2014 square tops out at 512, 16:9 at 688x384)`
|
|
@@ -3540,6 +3798,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3540
3798
|
const character = spec.character;
|
|
3541
3799
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
3542
3800
|
const label = `${spec.styleId}/${spec.assetId}`;
|
|
3801
|
+
if (character.kind === "outfit") {
|
|
3802
|
+
const reference = character.outfit.reference;
|
|
3803
|
+
if (reference.width < 32 || reference.width > 256 || reference.height < 32 || reference.height > 256) {
|
|
3804
|
+
throw new Error(`${label}: outfit reference is ${reference.width}x${reference.height}; PixelLab transfer-outfit-v2 takes 32 to 256px per side`);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3543
3807
|
if (character.kind === "base" && character.proportions !== void 0) {
|
|
3544
3808
|
if (character.mode !== "standard") {
|
|
3545
3809
|
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
@@ -3551,7 +3815,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3551
3815
|
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
3552
3816
|
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
3553
3817
|
}
|
|
3554
|
-
if (character.animation) {
|
|
3818
|
+
if (character.animation && character.kind !== "outfit") {
|
|
3555
3819
|
const { startFrame, endFrame } = character.animation;
|
|
3556
3820
|
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
3557
3821
|
if (image && (image.width > 256 || image.height > 256)) {
|
|
@@ -3632,9 +3896,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3632
3896
|
if (character.styleTraits && character.mode !== "pro-flash") {
|
|
3633
3897
|
throw new Error(`${label}: styleTraits choose what a pro-flash style image lends; the ${character.mode} engine does not take them`);
|
|
3634
3898
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3899
|
+
if (character.kind !== "portrait") {
|
|
3900
|
+
const maxSize = character.mode === "v3" || character.mode === "pro-flash" ? 256 : 128;
|
|
3901
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
3902
|
+
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
3903
|
+
}
|
|
3638
3904
|
}
|
|
3639
3905
|
if (character.mode === "pro-flash" && character.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
3640
3906
|
throw new Error(`PixelLab pro-flash characters are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
@@ -3643,9 +3909,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3643
3909
|
if (!templates.includes(character.template)) {
|
|
3644
3910
|
throw new Error(`PixelLab ${character.mode} character template must be one of: ${templates.join(", ")}`);
|
|
3645
3911
|
}
|
|
3646
|
-
const views = character.mode === "standard" ? ["low top-down", "high top-down", "side", "perspective", "oblique"] : ["low top-down", "high top-down", "side"];
|
|
3912
|
+
const views = character.kind === "portrait" ? ["low top-down", "high top-down", "side"] : character.mode === "standard" ? ["low top-down", "high top-down", "side", "perspective", "oblique"] : ["low top-down", "high top-down", "side"];
|
|
3647
3913
|
if (!views.includes(spec.view)) {
|
|
3648
|
-
throw new Error(
|
|
3914
|
+
throw new Error(
|
|
3915
|
+
character.kind === "portrait" ? `PixelLab portrait view must be one of: ${views.join(", ")}` : `PixelLab ${character.mode} character view must be one of: ${views.join(", ")}`
|
|
3916
|
+
);
|
|
3649
3917
|
}
|
|
3650
3918
|
if (spec.view === "oblique" && character.directions !== 4) {
|
|
3651
3919
|
throw new Error("PixelLab oblique characters are 4-direction only");
|
|
@@ -3653,7 +3921,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3653
3921
|
if (character.kind !== "base" && !character.parentAssetId) {
|
|
3654
3922
|
throw new Error(`${spec.styleId}/${spec.assetId} is a ${character.kind} with no parent`);
|
|
3655
3923
|
}
|
|
3656
|
-
if (character.animation) {
|
|
3924
|
+
if (character.animation && character.kind !== "outfit") {
|
|
3657
3925
|
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
3658
3926
|
if (!allowed.includes(character.animation.direction)) {
|
|
3659
3927
|
throw new Error(
|
|
@@ -3755,10 +4023,48 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3755
4023
|
concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
|
|
3756
4024
|
styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0,
|
|
3757
4025
|
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
3758
|
-
styleTraits: character.styleTraits
|
|
4026
|
+
styleTraits: character.styleTraits,
|
|
4027
|
+
sourceImageId: character.mode === "pro-flash" ? context?.referenceSourceImageId : void 0
|
|
3759
4028
|
});
|
|
3760
4029
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
3761
4030
|
}
|
|
4031
|
+
if (character.kind === "outfit") {
|
|
4032
|
+
const parentOutputs = context?.parentOutputs ?? [];
|
|
4033
|
+
if (parentOutputs.length < 2) {
|
|
4034
|
+
throw new Error(
|
|
4035
|
+
`${spec.styleId}/${spec.assetId} is an outfit of ${character.parentAssetId}, which has ${parentOutputs.length} downloaded frame(s); PixelLab transfer-outfit-v2 needs 2 to 16`
|
|
4036
|
+
);
|
|
4037
|
+
}
|
|
4038
|
+
const frames = parentOutputs.slice(0, 16).map((output) => {
|
|
4039
|
+
const bytes = readFileSync(output.path);
|
|
4040
|
+
const dims = imageMetadata(bytes);
|
|
4041
|
+
if (!dims) throw new Error(`${spec.styleId}/${spec.assetId}: outfit source frame is not a readable PNG: ${output.path}`);
|
|
4042
|
+
return { base64: bytes.toString("base64"), format: "png", width: dims.width, height: dims.height };
|
|
4043
|
+
});
|
|
4044
|
+
const outfit = character.outfit;
|
|
4045
|
+
const referenceBytes = readFileSync(outfit.reference.path);
|
|
4046
|
+
if (sha256(referenceBytes) !== outfit.reference.sha256) {
|
|
4047
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: outfit reference changed after the manifest was resolved: ${outfit.reference.path}`);
|
|
4048
|
+
}
|
|
4049
|
+
const res2 = await this.client.transferOutfitV2({
|
|
4050
|
+
referenceImage: { base64: referenceBytes.toString("base64"), format: "png", width: outfit.reference.width, height: outfit.reference.height },
|
|
4051
|
+
frames,
|
|
4052
|
+
imageSize: { width: spec.width, height: spec.height },
|
|
4053
|
+
seed: spec.seed,
|
|
4054
|
+
noBackground: spec.noBackground,
|
|
4055
|
+
additionalInstructions: outfit.additionalInstructions
|
|
4056
|
+
});
|
|
4057
|
+
return {
|
|
4058
|
+
jobId: res2.background_job_id,
|
|
4059
|
+
metadata: {
|
|
4060
|
+
character: {
|
|
4061
|
+
kind: "outfit",
|
|
4062
|
+
backgroundJobId: res2.background_job_id,
|
|
4063
|
+
frameRoles: parentOutputs.slice(0, 16).map((output) => output.role ?? null)
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
3762
4068
|
const parentId = context?.parentObjectId;
|
|
3763
4069
|
if (!parentId) {
|
|
3764
4070
|
throw new Error(
|
|
@@ -3777,6 +4083,24 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3777
4083
|
});
|
|
3778
4084
|
return { jobId: res2.character_id, metadata: { character: { kind: "state", characterId: res2.character_id, parentCharacterId: parentId, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
3779
4085
|
}
|
|
4086
|
+
if (character.kind === "portrait") {
|
|
4087
|
+
const parentFile = character.parentFile;
|
|
4088
|
+
const bytes = readFileSync(parentFile);
|
|
4089
|
+
if (character.parentSha256 && sha256(bytes) !== character.parentSha256) {
|
|
4090
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: portrait source changed after the manifest was resolved: ${parentFile}`);
|
|
4091
|
+
}
|
|
4092
|
+
const res2 = await this.client.createPortraitCharacterPro({
|
|
4093
|
+
direction: "character_to_portrait",
|
|
4094
|
+
image: { base64: bytes.toString("base64"), format: "png" },
|
|
4095
|
+
view: spec.view,
|
|
4096
|
+
resultSize: character.portrait.size,
|
|
4097
|
+
seed: spec.seed
|
|
4098
|
+
});
|
|
4099
|
+
return {
|
|
4100
|
+
jobId: res2.background_job_id,
|
|
4101
|
+
metadata: { character: { kind: "portrait", parentCharacterId: parentId, backgroundJobId: res2.background_job_id } }
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
3780
4104
|
const animation = character.animation;
|
|
3781
4105
|
const name = characterAnimationName(spec);
|
|
3782
4106
|
const replaced = context?.replacedMetadata?.character ?? {};
|
|
@@ -3835,7 +4159,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3835
4159
|
reference: object.reference ? readPose(spec, "reference sprite", object.reference) : void 0,
|
|
3836
4160
|
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
3837
4161
|
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
3838
|
-
styleTraits: object.styleTraits
|
|
4162
|
+
styleTraits: object.styleTraits,
|
|
4163
|
+
sourceImageId: context?.referenceSourceImageId
|
|
3839
4164
|
});
|
|
3840
4165
|
return { jobId: res2.object_id, metadata: { objectPro: { kind: "base", objectId: res2.object_id, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
3841
4166
|
}
|
|
@@ -3890,6 +4215,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3890
4215
|
async pollCharacter(jobId, context) {
|
|
3891
4216
|
const animation = decodeAnimationJob(jobId);
|
|
3892
4217
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
4218
|
+
const kind = context?.metadata?.character?.kind ?? context?.spec?.character?.kind;
|
|
4219
|
+
if (kind === "portrait") return this.pollCharacterPortrait(jobId, context);
|
|
4220
|
+
if (kind === "outfit") return this.pollCharacterOutfit(jobId, context);
|
|
3893
4221
|
const character = await this.client.getCharacter(jobId);
|
|
3894
4222
|
if (character.status === "failed") return { status: "failed", error: "character generation failed upstream" };
|
|
3895
4223
|
if (character.status !== "completed" || !character.rotation_urls) return { status: "processing" };
|
|
@@ -3915,6 +4243,66 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3915
4243
|
billed: await this.billedForJob(backgroundJobId)
|
|
3916
4244
|
};
|
|
3917
4245
|
}
|
|
4246
|
+
/**
|
|
4247
|
+
* `jobId` here is `portrait-character-pro`'s own background job id, not a
|
|
4248
|
+
* character id: the endpoint converts an arbitrary image and never touches
|
|
4249
|
+
* the character record. Once the portrait itself is drawn, this also
|
|
4250
|
+
* attaches it to the parent's character record with `SetPortrait` — a
|
|
4251
|
+
* separate, free call PixelLab does not make automatically (docs/PIXELLAB.md).
|
|
4252
|
+
*/
|
|
4253
|
+
async pollCharacterPortrait(jobId, context) {
|
|
4254
|
+
const meta = context?.metadata?.character;
|
|
4255
|
+
try {
|
|
4256
|
+
const job = await this.client.getPortraitCharacterJob(jobId);
|
|
4257
|
+
if (job.status === "failed") return { status: "failed", error: "portrait generation failed upstream" };
|
|
4258
|
+
if (job.status !== "completed" || !job.download_url) return { status: "processing" };
|
|
4259
|
+
if (meta?.parentCharacterId) {
|
|
4260
|
+
const bytes = await this.client.download(job.download_url);
|
|
4261
|
+
await this.client.setCharacterPortrait(meta.parentCharacterId, { base64: bytes.toString("base64"), format: "png" });
|
|
4262
|
+
}
|
|
4263
|
+
return {
|
|
4264
|
+
status: "ready",
|
|
4265
|
+
objectId: jobId,
|
|
4266
|
+
sourceUrl: job.download_url,
|
|
4267
|
+
sources: [{ url: job.download_url }],
|
|
4268
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
4269
|
+
};
|
|
4270
|
+
} catch (err) {
|
|
4271
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
4272
|
+
throw err;
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
/**
|
|
4276
|
+
* No dedicated status endpoint exists for `transfer-outfit-v2`, unlike
|
|
4277
|
+
* portraits and tiles: the background job is the only place to poll. A
|
|
4278
|
+
* completed job returns its frames inline as base64
|
|
4279
|
+
* (`last_response.quantized_images`), not as storage URLs, so they are
|
|
4280
|
+
* cached locally the way a synchronous pixflux result is, and matched back
|
|
4281
|
+
* to the source frames' own roles recorded at submit time.
|
|
4282
|
+
*/
|
|
4283
|
+
async pollCharacterOutfit(jobId, context) {
|
|
4284
|
+
const meta = context?.metadata?.character;
|
|
4285
|
+
const job = await this.client.getBackgroundJob(jobId);
|
|
4286
|
+
if (job.status === "failed") return { status: "failed", error: "outfit transfer failed upstream" };
|
|
4287
|
+
if (job.status !== "completed") return { status: "processing" };
|
|
4288
|
+
const images = job.last_response?.quantized_images;
|
|
4289
|
+
if (!images?.length) return { status: "failed", error: "outfit transfer completed with no result frames" };
|
|
4290
|
+
const dir = _PixelLabProvider.cacheDir("outfit");
|
|
4291
|
+
const sources = images.map((image, index) => {
|
|
4292
|
+
if (!image.base64) throw new Error(`outfit transfer frame ${index} has no image data`);
|
|
4293
|
+
const role = meta?.frameRoles?.[index] ?? void 0;
|
|
4294
|
+
const file = path.join(dir, `${jobId}-${index}.png`);
|
|
4295
|
+
writeFileSync(file, Buffer.from(image.base64, "base64"));
|
|
4296
|
+
return { url: `file://${file}`, ...role ? { role } : {} };
|
|
4297
|
+
});
|
|
4298
|
+
return {
|
|
4299
|
+
status: "ready",
|
|
4300
|
+
objectId: jobId,
|
|
4301
|
+
sourceUrl: sources[0].url,
|
|
4302
|
+
sources,
|
|
4303
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
3918
4306
|
/**
|
|
3919
4307
|
* The background job is the authoritative record of one direction: it
|
|
3920
4308
|
* completes with the frame URLs, the animation id, and the direction, and
|
|
@@ -4071,6 +4459,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4071
4459
|
});
|
|
4072
4460
|
return { jobId: res2.ui_asset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
4073
4461
|
}
|
|
4462
|
+
if (spec.generator === "imageProFlash") {
|
|
4463
|
+
const style = styleImages[0];
|
|
4464
|
+
const res2 = await this.client.createImageProFlash({
|
|
4465
|
+
description: spec.prompt,
|
|
4466
|
+
width: spec.width,
|
|
4467
|
+
height: spec.height,
|
|
4468
|
+
noBackground: spec.noBackground,
|
|
4469
|
+
seed: spec.seed,
|
|
4470
|
+
styleImage: style ? { image: { base64: style.base64, format: style.format }, width: style.width, height: style.height } : void 0,
|
|
4471
|
+
styleTraits: spec.styleTraits
|
|
4472
|
+
});
|
|
4473
|
+
return { jobId: res2.background_job_id, metadata: { imageProFlash: proFlashRecord(res2) } };
|
|
4474
|
+
}
|
|
4074
4475
|
if (spec.generator === "uiElement") {
|
|
4075
4476
|
const concept = styleImages[0];
|
|
4076
4477
|
const res2 = await this.client.generateUiV2({
|
|
@@ -4224,6 +4625,32 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4224
4625
|
const width = revision.sourceWidth;
|
|
4225
4626
|
const height = revision.sourceHeight;
|
|
4226
4627
|
const roles = revision.sourceMembers?.map((member) => member.role);
|
|
4628
|
+
if (revision.engine === "pro-flash") {
|
|
4629
|
+
if (revision.mode === "inpaint") {
|
|
4630
|
+
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
4631
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
4632
|
+
}
|
|
4633
|
+
const maskBytes = readFileSync(revision.maskFile);
|
|
4634
|
+
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
4635
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
4636
|
+
}
|
|
4637
|
+
const res3 = await this.client.inpaintImageProFlash({
|
|
4638
|
+
image,
|
|
4639
|
+
maskImage: { base64: maskBytes.toString("base64"), format: revision.maskFormat },
|
|
4640
|
+
description: spec.prompt,
|
|
4641
|
+
noBackground: spec.noBackground,
|
|
4642
|
+
seed: spec.seed
|
|
4643
|
+
});
|
|
4644
|
+
return { jobId: res3.background_job_id, metadata: { imageProFlash: proFlashRecord(res3) } };
|
|
4645
|
+
}
|
|
4646
|
+
const res2 = await this.client.editImageProFlash({
|
|
4647
|
+
image,
|
|
4648
|
+
description: spec.prompt,
|
|
4649
|
+
noBackground: spec.noBackground,
|
|
4650
|
+
seed: spec.seed
|
|
4651
|
+
});
|
|
4652
|
+
return { jobId: res2.background_job_id, metadata: { imageProFlash: proFlashRecord(res2) } };
|
|
4653
|
+
}
|
|
4227
4654
|
if (revision.mode === "inpaint") {
|
|
4228
4655
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
4229
4656
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
@@ -4388,6 +4815,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4388
4815
|
return this.pollAnimateRevision(jobId, context);
|
|
4389
4816
|
}
|
|
4390
4817
|
if (context?.spec?.revision) return this.pollRevision(jobId);
|
|
4818
|
+
if (generator === "imageProFlash") return this.pollRevision(jobId);
|
|
4391
4819
|
if (generator === "pixflux") {
|
|
4392
4820
|
const file = path.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
4393
4821
|
if (existsSync(file)) {
|
|
@@ -4968,6 +5396,13 @@ function readPose(spec, what, image) {
|
|
|
4968
5396
|
}
|
|
4969
5397
|
return { base64: bytes.toString("base64"), format: image.format };
|
|
4970
5398
|
}
|
|
5399
|
+
function proFlashRecord(res) {
|
|
5400
|
+
return {
|
|
5401
|
+
imageId: res.image_id,
|
|
5402
|
+
sourceImageId: res.source_image_id,
|
|
5403
|
+
...res.estimated_generations != null ? { estimatedGenerations: res.estimated_generations } : {}
|
|
5404
|
+
};
|
|
5405
|
+
}
|
|
4971
5406
|
function editAnimationFrameCeiling(width, height) {
|
|
4972
5407
|
const side = Math.max(width, height);
|
|
4973
5408
|
if (side <= 64) return 16;
|
|
@@ -6171,6 +6606,11 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6171
6606
|
throw new Error("ComfyUI providerOptions.frames requires generator: frames");
|
|
6172
6607
|
}
|
|
6173
6608
|
if (spec.revision) {
|
|
6609
|
+
if (spec.revision.engine) {
|
|
6610
|
+
throw new Error(
|
|
6611
|
+
`ComfyUI has no "${spec.revision.engine}" revision engine; that selects PixelLab's Pro Flash edit/inpaint endpoints. Drop engine to run the revision through this style's own workflow.`
|
|
6612
|
+
);
|
|
6613
|
+
}
|
|
6174
6614
|
if (spec.revision.sourceMembers) {
|
|
6175
6615
|
throw new Error(
|
|
6176
6616
|
`ComfyUI revisions read one source image; ${spec.revision.sourceAssetId} is a ${spec.revision.sourceMembers.length}-member set (directions or frames)`
|
|
@@ -7513,7 +7953,7 @@ ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
|
7513
7953
|
return { manifest: parsed.data, root: path5.dirname(abs), path: abs, ...loopFamilies };
|
|
7514
7954
|
}
|
|
7515
7955
|
function parentAssetId(asset) {
|
|
7516
|
-
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
7956
|
+
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
7517
7957
|
}
|
|
7518
7958
|
function formatManifestIssues(issues) {
|
|
7519
7959
|
return issues.flatMap((issue) => {
|
|
@@ -7680,9 +8120,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7680
8120
|
height = styleImageDimensions.length ? Math.max(...styleImageDimensions.map((img) => img.height)) : style.tileSize ?? 32;
|
|
7681
8121
|
size = Math.max(width, height);
|
|
7682
8122
|
} else if (generator === "character") {
|
|
7683
|
-
size = asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
7684
|
-
width = asset.state?.canvas?.width ?? size;
|
|
7685
|
-
height = asset.state?.canvas?.height ?? size;
|
|
8123
|
+
size = asset.portrait ? asset.portrait.size : asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
8124
|
+
width = asset.portrait?.size ?? asset.state?.canvas?.width ?? size;
|
|
8125
|
+
height = asset.portrait?.size ?? asset.state?.canvas?.height ?? size;
|
|
7686
8126
|
} else if (generator === "terrain") {
|
|
7687
8127
|
size = style.terrainTileSize ?? 16;
|
|
7688
8128
|
width = size;
|
|
@@ -7709,12 +8149,17 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7709
8149
|
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
7710
8150
|
);
|
|
7711
8151
|
}
|
|
8152
|
+
if ((asset.portrait || asset.outfit) && generator !== "character") {
|
|
8153
|
+
throw new Error(
|
|
8154
|
+
`assets.${assetId}: ${asset.portrait ? "portrait" : "outfit"} needs a character style; "${styleId}" generates ${generator}`
|
|
8155
|
+
);
|
|
8156
|
+
}
|
|
7712
8157
|
if ((asset.pieces || asset.elements) && generator !== "uiAsset") {
|
|
7713
8158
|
throw new Error(
|
|
7714
8159
|
`assets.${assetId}: ${asset.pieces ? "pieces" : "elements"} needs a uiAsset style; "${styleId}" generates ${generator}`
|
|
7715
8160
|
);
|
|
7716
8161
|
}
|
|
7717
|
-
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
8162
|
+
const characterKind = asset.animation ? "animation" : asset.state ? "state" : asset.portrait ? "portrait" : "base";
|
|
7718
8163
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
7719
8164
|
let terrainLowerDescription;
|
|
7720
8165
|
let terrainUpperDescription;
|
|
@@ -7763,6 +8208,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7763
8208
|
uiPieces: generator === "uiAsset" ? asset.pieces : void 0,
|
|
7764
8209
|
uiElements: generator === "uiAsset" ? asset.elements : void 0,
|
|
7765
8210
|
uiColorPalette: generator === "uiAsset" || generator === "uiElement" ? style.uiColorPalette : void 0,
|
|
8211
|
+
...generator === "imageProFlash" && style.styleTraits ? { styleTraits: style.styleTraits } : {},
|
|
7766
8212
|
palette: style.palette,
|
|
7767
8213
|
enforcePalette: style.enforcePalette,
|
|
7768
8214
|
noBackground: style.noBackground,
|
|
@@ -7806,7 +8252,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7806
8252
|
// its parent's pixels, not a new character: giving it a base shape
|
|
7807
8253
|
// would have packing, the gallery, and adoption treat it as one.
|
|
7808
8254
|
...generator === "character" && !asset.revision ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7809
|
-
|
|
8255
|
+
// A portrait needs a character style (refused above), so an
|
|
8256
|
+
// objectPro asset is only ever a base, state, or animation.
|
|
8257
|
+
...generator === "objectPro" && !asset.revision && characterKind !== "portrait" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7810
8258
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
7811
8259
|
costUnit: "generations",
|
|
7812
8260
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -7917,7 +8365,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7917
8365
|
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: existsSync4(file) ? await sha256File(file) : null }
|
|
7918
8366
|
};
|
|
7919
8367
|
}
|
|
7920
|
-
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
8368
|
+
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
7921
8369
|
if (resolved.character && characterParent) {
|
|
7922
8370
|
const parentSpec = await finalize(characterParent);
|
|
7923
8371
|
const parentDirections = parentSpec.character?.directions ?? 8;
|
|
@@ -7950,6 +8398,38 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7950
8398
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
7951
8399
|
};
|
|
7952
8400
|
}
|
|
8401
|
+
if (asset.outfit) {
|
|
8402
|
+
if (asset.outfit.of === assetId) throw new Error(`assets.${assetId}: an asset cannot re-clothe itself`);
|
|
8403
|
+
const sourceSpec = await finalize(asset.outfit.of);
|
|
8404
|
+
if (sourceSpec.character?.kind !== "animation") {
|
|
8405
|
+
throw new Error(`assets.${assetId}: outfit "${asset.outfit.of}" is not a character loop`);
|
|
8406
|
+
}
|
|
8407
|
+
const referenceImage = await loadStyleImage(asset.outfit.reference, "Outfit reference image");
|
|
8408
|
+
resolved.character = {
|
|
8409
|
+
kind: "outfit",
|
|
8410
|
+
mode: sourceSpec.character.mode,
|
|
8411
|
+
directions: sourceSpec.character.directions,
|
|
8412
|
+
template: sourceSpec.character.template,
|
|
8413
|
+
parentAssetId: asset.outfit.of,
|
|
8414
|
+
parentSpec: sourceSpec,
|
|
8415
|
+
animation: sourceSpec.character.animation,
|
|
8416
|
+
outfit: {
|
|
8417
|
+
reference: {
|
|
8418
|
+
path: path5.resolve(root, asset.outfit.reference),
|
|
8419
|
+
sha256: referenceImage.hash,
|
|
8420
|
+
width: referenceImage.width,
|
|
8421
|
+
height: referenceImage.height,
|
|
8422
|
+
format: referenceImage.format
|
|
8423
|
+
},
|
|
8424
|
+
...asset.outfit.additionalInstructions ? { additionalInstructions: asset.outfit.additionalInstructions } : {}
|
|
8425
|
+
}
|
|
8426
|
+
};
|
|
8427
|
+
resolved.generator = sourceSpec.generator;
|
|
8428
|
+
resolved.width = sourceSpec.width;
|
|
8429
|
+
resolved.height = sourceSpec.height;
|
|
8430
|
+
resolved.size = sourceSpec.size;
|
|
8431
|
+
resolved.prompt = "";
|
|
8432
|
+
}
|
|
7953
8433
|
if (asset.revision) {
|
|
7954
8434
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
7955
8435
|
throw new Error(
|
|
@@ -8012,7 +8492,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8012
8492
|
lastFrameFormat: lastFrame?.format ?? null
|
|
8013
8493
|
} : {},
|
|
8014
8494
|
...asset.revision.direction ? { direction: asset.revision.direction } : {},
|
|
8015
|
-
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
8495
|
+
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt },
|
|
8496
|
+
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
8016
8497
|
};
|
|
8017
8498
|
}
|
|
8018
8499
|
if (asset.batch) {
|
|
@@ -8223,6 +8704,9 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
8223
8704
|
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
8224
8705
|
};
|
|
8225
8706
|
}
|
|
8707
|
+
if (asset.portrait) {
|
|
8708
|
+
shape.portrait = { size: asset.portrait.size };
|
|
8709
|
+
}
|
|
8226
8710
|
if (asset.animation) {
|
|
8227
8711
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
8228
8712
|
const pose = async (rel, what) => {
|
|
@@ -9775,6 +10259,7 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
9775
10259
|
import path11 from "path";
|
|
9776
10260
|
async function inspectRevisionReadiness(spec, lock) {
|
|
9777
10261
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
10262
|
+
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
9778
10263
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
9779
10264
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
9780
10265
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -9801,6 +10286,20 @@ async function inspectMirror(spec, lock, seen) {
|
|
|
9801
10286
|
const dependency = await inspectParent(source, lock, seen);
|
|
9802
10287
|
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
9803
10288
|
}
|
|
10289
|
+
async function inspectOutfit(spec, lock, seen) {
|
|
10290
|
+
const character = spec.character;
|
|
10291
|
+
const outfit = character.outfit;
|
|
10292
|
+
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
10293
|
+
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
10294
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
10295
|
+
if (!existsSync7(outfit.reference.path)) {
|
|
10296
|
+
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
10297
|
+
}
|
|
10298
|
+
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
10299
|
+
return { ready: false, reason: "outfit reference changed after the manifest was resolved" };
|
|
10300
|
+
}
|
|
10301
|
+
return { ready: true, reason: `${label} is current` };
|
|
10302
|
+
}
|
|
9804
10303
|
async function inspectCharacterParent(spec, lock, seen) {
|
|
9805
10304
|
const character = spec.character;
|
|
9806
10305
|
const parentSpec = character.parentSpec;
|
|
@@ -9916,7 +10415,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
9916
10415
|
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
9917
10416
|
if (!inputs.ready) return inputs;
|
|
9918
10417
|
}
|
|
9919
|
-
if (spec.character?.
|
|
10418
|
+
if (spec.character?.kind === "outfit") {
|
|
10419
|
+
const inputs = await inspectOutfit(spec, lock, nextSeen);
|
|
10420
|
+
if (!inputs.ready) return inputs;
|
|
10421
|
+
} else if (spec.character?.parentSpec) {
|
|
9920
10422
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
9921
10423
|
if (!inputs.ready) return inputs;
|
|
9922
10424
|
}
|
|
@@ -10039,6 +10541,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
10039
10541
|
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !existsSync8(currentEntryOutputPath(entry, spec, index)))) {
|
|
10040
10542
|
state = "stale";
|
|
10041
10543
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
10544
|
+
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
10545
|
+
state = "stale";
|
|
10546
|
+
reason = `outfit source ${spec.character.parentAssetId} changed; re-clothe the loop again`;
|
|
10042
10547
|
} else if (entry.status === "download-failed") {
|
|
10043
10548
|
state = "recoverable";
|
|
10044
10549
|
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
@@ -12736,6 +13241,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12736
13241
|
const supersededOutputs = previousEntry?.outputs.length ? previousEntry.outputs : previousEntry?.supersededOutputs ?? [];
|
|
12737
13242
|
const submittedAt = resumesCheckpoint ? previousEntry.submittedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
12738
13243
|
const history = historyAfterReplacing(previousEntry, historyLimit(loaded.manifest), submittedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
13244
|
+
const outfitParentEntry = spec.character?.kind === "outfit" && spec.character.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
|
|
12739
13245
|
upsert(lock, key, {
|
|
12740
13246
|
styleId: spec.styleId,
|
|
12741
13247
|
assetId: spec.assetId,
|
|
@@ -12759,7 +13265,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12759
13265
|
...spec.revision.fps == null ? {} : { fps: spec.revision.fps },
|
|
12760
13266
|
...spec.revision.lastFrameSha256 ? { lastFrameSha256: spec.revision.lastFrameSha256 } : {},
|
|
12761
13267
|
...spec.revision.direction ? { direction: spec.revision.direction } : {},
|
|
12762
|
-
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt }
|
|
13268
|
+
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt },
|
|
13269
|
+
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
13270
|
+
} : null,
|
|
13271
|
+
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
13272
|
+
sourceAssetId: spec.character.parentAssetId,
|
|
13273
|
+
sourceSha256: mirrorSourceHash(outfitParentEntry),
|
|
13274
|
+
referenceSha256: spec.character.outfit.reference.sha256
|
|
12763
13275
|
} : null,
|
|
12764
13276
|
status: "pending",
|
|
12765
13277
|
jobId: previousJobId ?? null,
|
|
@@ -12794,10 +13306,17 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12794
13306
|
const anchor = spec.character?.styleAnchor;
|
|
12795
13307
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
12796
13308
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
13309
|
+
const parentOutputs = spec.character?.kind === "outfit" && parentEntry ? parentEntry.outputs.map((output, index) => ({
|
|
13310
|
+
path: currentEntryOutputPath(parentEntry, spec.character.parentSpec, index),
|
|
13311
|
+
role: output.role
|
|
13312
|
+
})) : void 0;
|
|
13313
|
+
const referenceSourceImageId = ownedReferenceImageId(spec, lock, provider.id);
|
|
12797
13314
|
const { jobId, metadata } = await provider.submit(spec, refs, {
|
|
12798
13315
|
...previousJobId ? { previousJobId } : {},
|
|
13316
|
+
...referenceSourceImageId ? { referenceSourceImageId } : {},
|
|
12799
13317
|
...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
|
|
12800
13318
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
13319
|
+
...parentOutputs ? { parentOutputs } : {},
|
|
12801
13320
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
12802
13321
|
...previousMetadata ? { previousMetadata } : {},
|
|
12803
13322
|
checkpoint: async (checkpoint) => {
|
|
@@ -12861,6 +13380,17 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12861
13380
|
}
|
|
12862
13381
|
return { submitted, failed, spent, unit };
|
|
12863
13382
|
}
|
|
13383
|
+
function ownedReferenceImageId(spec, lock, providerId) {
|
|
13384
|
+
const sha2562 = spec.character?.kind === "base" && spec.character.mode === "pro-flash" ? spec.character.reference?.south?.sha256 : spec.objectPro?.kind === "base" ? spec.objectPro.reference?.sha256 : void 0;
|
|
13385
|
+
if (!sha2562) return void 0;
|
|
13386
|
+
for (const entry of Object.values(lock.entries)) {
|
|
13387
|
+
if (entry.provider !== providerId || entry.generator !== "imageProFlash" || entry.status !== "downloaded") continue;
|
|
13388
|
+
if (entry.outputs.length !== 1 || entry.outputs[0].sha256 !== sha2562) continue;
|
|
13389
|
+
const recorded = entry.providerMetadata?.[providerId]?.imageProFlash;
|
|
13390
|
+
if (typeof recorded?.sourceImageId === "string" && recorded.sourceImageId) return recorded.sourceImageId;
|
|
13391
|
+
}
|
|
13392
|
+
return void 0;
|
|
13393
|
+
}
|
|
12864
13394
|
|
|
12865
13395
|
// src/pipeline/poll.ts
|
|
12866
13396
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -14916,7 +15446,7 @@ function describeCharacter(spec, entry) {
|
|
|
14916
15446
|
};
|
|
14917
15447
|
}
|
|
14918
15448
|
if (entry?.generator === "character") {
|
|
14919
|
-
const kind = recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
15449
|
+
const kind = recordedCharacter?.kind === "portrait" ? "portrait" : recordedCharacter?.kind === "outfit" ? "outfit" : recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
14920
15450
|
return {
|
|
14921
15451
|
generator: "character",
|
|
14922
15452
|
kind,
|
|
@@ -15277,7 +15807,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
15277
15807
|
characters: style?.generator === "character" || style?.generator === "objectPro" ? {
|
|
15278
15808
|
bases: styleItems.filter((item) => item.character?.kind === "base").length,
|
|
15279
15809
|
states: styleItems.filter((item) => item.character?.kind === "state").length,
|
|
15280
|
-
animations: styleItems.filter((item) => item.character?.kind === "animation").length
|
|
15810
|
+
animations: styleItems.filter((item) => item.character?.kind === "animation").length,
|
|
15811
|
+
portraits: styleItems.filter((item) => item.character?.kind === "portrait").length,
|
|
15812
|
+
outfits: styleItems.filter((item) => item.character?.kind === "outfit").length
|
|
15281
15813
|
} : null,
|
|
15282
15814
|
tags: style?.tags ?? [],
|
|
15283
15815
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
@@ -17071,6 +17603,9 @@ export {
|
|
|
17071
17603
|
CharacterAnimationSchema,
|
|
17072
17604
|
CharacterDirectionSchema,
|
|
17073
17605
|
CharacterModeSchema,
|
|
17606
|
+
CharacterOutfitSchema,
|
|
17607
|
+
CharacterPortraitSchema,
|
|
17608
|
+
CharacterPortraitSizeSchema,
|
|
17074
17609
|
CharacterProportionsSchema,
|
|
17075
17610
|
CharacterStateSchema,
|
|
17076
17611
|
ComfyUIProvider,
|