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.cjs
CHANGED
|
@@ -40,6 +40,9 @@ __export(index_exports, {
|
|
|
40
40
|
CharacterAnimationSchema: () => CharacterAnimationSchema,
|
|
41
41
|
CharacterDirectionSchema: () => CharacterDirectionSchema,
|
|
42
42
|
CharacterModeSchema: () => CharacterModeSchema,
|
|
43
|
+
CharacterOutfitSchema: () => CharacterOutfitSchema,
|
|
44
|
+
CharacterPortraitSchema: () => CharacterPortraitSchema,
|
|
45
|
+
CharacterPortraitSizeSchema: () => CharacterPortraitSizeSchema,
|
|
43
46
|
CharacterProportionsSchema: () => CharacterProportionsSchema,
|
|
44
47
|
CharacterStateSchema: () => CharacterStateSchema,
|
|
45
48
|
ComfyUIProvider: () => ComfyUIProvider,
|
|
@@ -319,7 +322,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
319
322
|
// src/types.ts
|
|
320
323
|
var import_zod = require("zod");
|
|
321
324
|
var MediaTypeSchema = import_zod.z.enum(["image/png", "image/gif"]);
|
|
322
|
-
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement"]);
|
|
325
|
+
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement", "imageProFlash"]);
|
|
323
326
|
var GridConfidenceSchema = import_zod.z.enum(["low", "medium", "high"]);
|
|
324
327
|
var RevisionModeSchema = import_zod.z.enum([
|
|
325
328
|
"image-to-image",
|
|
@@ -504,6 +507,27 @@ var CharacterStateSchema = import_zod.z.object({
|
|
|
504
507
|
height: import_zod.z.number().int().min(16).max(256)
|
|
505
508
|
}).strict().optional()
|
|
506
509
|
}).strict();
|
|
510
|
+
var CharacterPortraitSizeSchema = import_zod.z.union([
|
|
511
|
+
import_zod.z.literal(16),
|
|
512
|
+
import_zod.z.literal(32),
|
|
513
|
+
import_zod.z.literal(48),
|
|
514
|
+
import_zod.z.literal(64),
|
|
515
|
+
import_zod.z.literal(128),
|
|
516
|
+
import_zod.z.literal(160)
|
|
517
|
+
]);
|
|
518
|
+
var CharacterPortraitSchema = import_zod.z.object({
|
|
519
|
+
/** The base or state to portray, in the same style. */
|
|
520
|
+
of: import_zod.z.string().min(1),
|
|
521
|
+
size: CharacterPortraitSizeSchema.default(64)
|
|
522
|
+
}).strict();
|
|
523
|
+
var CharacterOutfitSchema = import_zod.z.object({
|
|
524
|
+
/** The loop (a character animation asset) to re-clothe, in the same style. */
|
|
525
|
+
of: import_zod.z.string().min(1),
|
|
526
|
+
/** Manifest-relative image of the outfit to transfer. */
|
|
527
|
+
reference: import_zod.z.string().min(1),
|
|
528
|
+
/** Extra guidance PixelLab passes through as written, e.g. view/direction hints. */
|
|
529
|
+
additionalInstructions: import_zod.z.string().max(2e3).optional()
|
|
530
|
+
}).strict();
|
|
507
531
|
var CharacterAnimationSchema = import_zod.z.object({
|
|
508
532
|
/** The character to animate: a base or a state in the same style. */
|
|
509
533
|
of: import_zod.z.string().min(1),
|
|
@@ -625,8 +649,22 @@ var RevisionSchema = import_zod.z.object({
|
|
|
625
649
|
/** `animate-pixminimax` only: facing direction, used only alongside `enhancePrompt` to hold the sprite's facing. */
|
|
626
650
|
direction: CharacterDirectionSchema.optional(),
|
|
627
651
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
628
|
-
enhancePrompt: import_zod.z.boolean().optional()
|
|
652
|
+
enhancePrompt: import_zod.z.boolean().optional(),
|
|
653
|
+
/**
|
|
654
|
+
* `image-to-image`/`inpaint` only: which provider engine draws the
|
|
655
|
+
* edit. `pro-flash` selects PixelLab's Pro Flash edit and inpaint tier
|
|
656
|
+
* (`/edit-image-pro-flash`, `/inpaint-image-pro-flash`) instead of the
|
|
657
|
+
* default Pro endpoints; omit it for the default.
|
|
658
|
+
*/
|
|
659
|
+
engine: import_zod.z.enum(["pro-flash"]).optional()
|
|
629
660
|
}).strict().superRefine((revision, context) => {
|
|
661
|
+
if (revision.engine !== void 0 && revision.mode !== "image-to-image" && revision.mode !== "inpaint") {
|
|
662
|
+
context.addIssue({
|
|
663
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
664
|
+
message: "engine applies to image-to-image and inpaint revisions only",
|
|
665
|
+
path: ["engine"]
|
|
666
|
+
});
|
|
667
|
+
}
|
|
630
668
|
if (revision.mode === "inpaint" && !revision.mask) {
|
|
631
669
|
context.addIssue({
|
|
632
670
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -915,8 +953,8 @@ var StyleObjectSchema = import_zod.z.object({
|
|
|
915
953
|
/** `character` only. Body template: `mannequin` (default), a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`), or `custom` for `pro-flash`. */
|
|
916
954
|
template: import_zod.z.string().min(1).optional(),
|
|
917
955
|
/**
|
|
918
|
-
* `character`
|
|
919
|
-
* the style image lends. Each defaults to true.
|
|
956
|
+
* `character` `pro-flash` bases and `imageProFlash` styles, with a
|
|
957
|
+
* style image: which traits the style image lends. Each defaults to true.
|
|
920
958
|
*/
|
|
921
959
|
styleTraits: import_zod.z.object({
|
|
922
960
|
palette: import_zod.z.boolean().optional(),
|
|
@@ -1125,6 +1163,10 @@ var AssetSchema = import_zod.z.object({
|
|
|
1125
1163
|
pieces: import_zod.z.array(UiPieceSchema).min(1).optional(),
|
|
1126
1164
|
/** `uiAsset` styles: named, auto-positioned UI element scaffolds. Combine with `pieces`; omit both for a default full-canvas panel. */
|
|
1127
1165
|
elements: import_zod.z.array(UiElementSchema).min(1).optional(),
|
|
1166
|
+
/** `character` styles: this asset is a bust portrait of another character asset's south sprite. */
|
|
1167
|
+
portrait: CharacterPortraitSchema.optional(),
|
|
1168
|
+
/** `character` styles: this asset re-clothes an existing loop's frames with a reference outfit. */
|
|
1169
|
+
outfit: CharacterOutfitSchema.optional(),
|
|
1128
1170
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1129
1171
|
proportions: CharacterProportionsSchema.optional(),
|
|
1130
1172
|
/**
|
|
@@ -1201,7 +1243,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1201
1243
|
path: ["revision"]
|
|
1202
1244
|
});
|
|
1203
1245
|
}
|
|
1204
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
1246
|
+
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);
|
|
1205
1247
|
if (shapes.length > 1) {
|
|
1206
1248
|
context.addIssue({
|
|
1207
1249
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -1216,24 +1258,24 @@ var AssetSchema = import_zod.z.object({
|
|
|
1216
1258
|
path: ["mirror"]
|
|
1217
1259
|
});
|
|
1218
1260
|
}
|
|
1219
|
-
if (asset.prompt === void 0 && !asset.mirror) {
|
|
1261
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit) {
|
|
1220
1262
|
context.addIssue({
|
|
1221
1263
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1222
1264
|
message: "Required",
|
|
1223
1265
|
path: ["prompt"]
|
|
1224
1266
|
});
|
|
1225
1267
|
}
|
|
1226
|
-
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
1268
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
1227
1269
|
context.addIssue({
|
|
1228
1270
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1229
|
-
message: "a reference sprite belongs on a base; a state, animation, or
|
|
1271
|
+
message: "a reference sprite belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
1230
1272
|
path: ["reference"]
|
|
1231
1273
|
});
|
|
1232
1274
|
}
|
|
1233
|
-
if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
|
|
1275
|
+
if (asset.concept && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
1234
1276
|
context.addIssue({
|
|
1235
1277
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1236
|
-
message: "a concept image belongs on a base; a state, animation, or
|
|
1278
|
+
message: "a concept image belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
1237
1279
|
path: ["concept"]
|
|
1238
1280
|
});
|
|
1239
1281
|
}
|
|
@@ -1345,7 +1387,8 @@ var LockEntrySchema = import_zod.z.object({
|
|
|
1345
1387
|
fps: import_zod.z.number().int().min(1).max(60).optional(),
|
|
1346
1388
|
lastFrameSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1347
1389
|
direction: CharacterDirectionSchema.optional(),
|
|
1348
|
-
enhancePrompt: import_zod.z.boolean().optional()
|
|
1390
|
+
enhancePrompt: import_zod.z.boolean().optional(),
|
|
1391
|
+
engine: import_zod.z.enum(["pro-flash"]).optional()
|
|
1349
1392
|
}).strict().nullable().default(null),
|
|
1350
1393
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
1351
1394
|
mirror: import_zod.z.object({
|
|
@@ -1358,6 +1401,17 @@ var LockEntrySchema = import_zod.z.object({
|
|
|
1358
1401
|
leaderAssetId: import_zod.z.string().min(1).optional(),
|
|
1359
1402
|
index: import_zod.z.number().int().min(1).optional()
|
|
1360
1403
|
}).strict().nullable().default(null),
|
|
1404
|
+
/**
|
|
1405
|
+
* For an outfit: the loop it re-clothed, a hash over that loop's output
|
|
1406
|
+
* hashes when this was made, and the reference image's own hash. Tracked
|
|
1407
|
+
* the way `mirror` is, not through `specHash`: manifest resolution has no
|
|
1408
|
+
* lock to hash the parent's actual current frame bytes against.
|
|
1409
|
+
*/
|
|
1410
|
+
outfit: import_zod.z.object({
|
|
1411
|
+
sourceAssetId: import_zod.z.string().min(1),
|
|
1412
|
+
sourceSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/),
|
|
1413
|
+
referenceSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
1414
|
+
}).strict().nullable().default(null),
|
|
1361
1415
|
/**
|
|
1362
1416
|
* Output hashes owned by the previous generation while its replacement is
|
|
1363
1417
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -1711,6 +1765,14 @@ var CorrectPixelartResponseSchema = import_zod2.z.object({
|
|
|
1711
1765
|
images: import_zod2.z.array(import_zod2.z.object({ base64: import_zod2.z.string().min(1) }).passthrough()).min(1),
|
|
1712
1766
|
usage: import_zod2.z.unknown().optional()
|
|
1713
1767
|
}).passthrough();
|
|
1768
|
+
var ProFlashImageSubmitSchema = import_zod2.z.object({
|
|
1769
|
+
background_job_id: import_zod2.z.string().min(1),
|
|
1770
|
+
image_id: import_zod2.z.string().min(1),
|
|
1771
|
+
source_image_id: import_zod2.z.string().min(1),
|
|
1772
|
+
status: import_zod2.z.string().default("processing"),
|
|
1773
|
+
estimated_generations: import_zod2.z.number().nullable().optional(),
|
|
1774
|
+
usage: import_zod2.z.unknown().optional()
|
|
1775
|
+
}).passthrough();
|
|
1714
1776
|
var UnzoomResponseSchema = import_zod2.z.object({
|
|
1715
1777
|
image: import_zod2.z.object({ base64: import_zod2.z.string().min(1) }).passthrough(),
|
|
1716
1778
|
original_size: import_zod2.z.object({ width: import_zod2.z.number().int(), height: import_zod2.z.number().int() }).passthrough(),
|
|
@@ -1761,6 +1823,28 @@ var AnimateObjectSubmitSchema = import_zod2.z.object({
|
|
|
1761
1823
|
submissions: import_zod2.z.array(DirectionSubmissionSchema),
|
|
1762
1824
|
usage: UsageSchema
|
|
1763
1825
|
}).passthrough();
|
|
1826
|
+
var PortraitSubmitSchema = import_zod2.z.object({
|
|
1827
|
+
background_job_id: import_zod2.z.string().min(1),
|
|
1828
|
+
status: import_zod2.z.string().default("processing"),
|
|
1829
|
+
usage: UsageSchema
|
|
1830
|
+
}).passthrough();
|
|
1831
|
+
var PortraitJobSchema = import_zod2.z.object({
|
|
1832
|
+
status: import_zod2.z.string(),
|
|
1833
|
+
download_url: import_zod2.z.string().nullable().optional(),
|
|
1834
|
+
width: import_zod2.z.number().int().nullable().optional(),
|
|
1835
|
+
height: import_zod2.z.number().int().nullable().optional()
|
|
1836
|
+
}).passthrough();
|
|
1837
|
+
var SetPortraitSchema = import_zod2.z.object({
|
|
1838
|
+
character_id: import_zod2.z.string().min(1),
|
|
1839
|
+
size: import_zod2.z.number().int().positive(),
|
|
1840
|
+
url: import_zod2.z.string().min(1),
|
|
1841
|
+
usage: UsageSchema
|
|
1842
|
+
}).passthrough();
|
|
1843
|
+
var TransferOutfitSubmitSchema = import_zod2.z.object({
|
|
1844
|
+
background_job_id: import_zod2.z.string().min(1),
|
|
1845
|
+
status: import_zod2.z.string().default("processing"),
|
|
1846
|
+
usage: UsageSchema
|
|
1847
|
+
}).passthrough();
|
|
1764
1848
|
var CharacterAnimationDirectionSchema = import_zod2.z.object({
|
|
1765
1849
|
direction: import_zod2.z.string(),
|
|
1766
1850
|
frame_count: import_zod2.z.number().int().min(0),
|
|
@@ -2129,7 +2213,7 @@ var PixelLabClient = class {
|
|
|
2129
2213
|
const body = {
|
|
2130
2214
|
description: args.description,
|
|
2131
2215
|
n_directions: args.directions,
|
|
2132
|
-
...args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
2216
|
+
...args.sourceImageId ? { source_image_id: args.sourceImageId } : args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
2133
2217
|
...args.view ? { view: args.view } : {},
|
|
2134
2218
|
...args.seed != null ? { seed: args.seed } : {},
|
|
2135
2219
|
...args.styleReference && args.styleReferenceSize ? {
|
|
@@ -2276,8 +2360,9 @@ var PixelLabClient = class {
|
|
|
2276
2360
|
description: args.description,
|
|
2277
2361
|
template_id: args.template,
|
|
2278
2362
|
n_directions: 8,
|
|
2279
|
-
// The author's sprite sets the canvas; a size only applies to text
|
|
2280
|
-
|
|
2363
|
+
// The author's sprite sets the canvas; a size only applies to text
|
|
2364
|
+
// creation. An owned copy of the same sprite is referenced by id.
|
|
2365
|
+
...args.sourceImageId ? { source_image_id: args.sourceImageId } : south ? { first_frame: encode(south) } : { image_size: imageSize },
|
|
2281
2366
|
...args.view ? { view: args.view } : {},
|
|
2282
2367
|
...args.seed != null ? { seed: args.seed } : {},
|
|
2283
2368
|
...args.styleReference && args.styleReferenceSize ? {
|
|
@@ -2376,6 +2461,69 @@ var PixelLabClient = class {
|
|
|
2376
2461
|
});
|
|
2377
2462
|
return validateResponse(AnimateSubmitSchema, raw, "animate-character");
|
|
2378
2463
|
}
|
|
2464
|
+
/**
|
|
2465
|
+
* A full-body sprite in, a bust portrait out (or the reverse, unused
|
|
2466
|
+
* today). Job-based like a state or `1dir`, not the couple-generations
|
|
2467
|
+
* "conversion" the name suggests: the smallest `resultSize` (16px) billed
|
|
2468
|
+
* 20 generations live (docs/ENDPOINTS.md, "Characters, measured").
|
|
2469
|
+
*/
|
|
2470
|
+
async createPortraitCharacterPro(args) {
|
|
2471
|
+
const raw = await this.request("/portrait-character-pro", {
|
|
2472
|
+
method: "POST",
|
|
2473
|
+
body: JSON.stringify({
|
|
2474
|
+
direction: args.direction,
|
|
2475
|
+
image: { type: "base64", base64: args.image.base64, format: args.image.format },
|
|
2476
|
+
...args.view ? { view: args.view } : {},
|
|
2477
|
+
...args.resultSize != null ? { result_size: args.resultSize } : {},
|
|
2478
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
2479
|
+
})
|
|
2480
|
+
});
|
|
2481
|
+
return validateResponse(PortraitSubmitSchema, raw, "portrait-character-pro");
|
|
2482
|
+
}
|
|
2483
|
+
/** Throws PixelLabError(423) while the job is still drawing, like tiles and map objects. */
|
|
2484
|
+
async getPortraitCharacterJob(jobId) {
|
|
2485
|
+
const raw = await this.request(`/portrait-character-pro/${encodeURIComponent(jobId)}`);
|
|
2486
|
+
return validateResponse(PortraitJobSchema, raw, "portrait-character-pro/{id}");
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Attaches a portrait image to a character record. Synchronous and free
|
|
2490
|
+
* (0 generations, live); PixelLab does not link this to
|
|
2491
|
+
* `createPortraitCharacterPro` automatically, and `GET /characters/{id}`
|
|
2492
|
+
* never grows a portrait field, so this response's `url` is the only place
|
|
2493
|
+
* it appears.
|
|
2494
|
+
*/
|
|
2495
|
+
async setCharacterPortrait(characterId, image) {
|
|
2496
|
+
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}/portrait`, {
|
|
2497
|
+
method: "POST",
|
|
2498
|
+
body: JSON.stringify({ image: { type: "base64", base64: image.base64, format: image.format } })
|
|
2499
|
+
});
|
|
2500
|
+
return validateResponse(SetPortraitSchema, raw, "characters/{id}/portrait");
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* Applies a reference outfit to 2–16 existing frames. Job-based like
|
|
2504
|
+
* everything else here, but the completed job returns the result frames
|
|
2505
|
+
* inline as base64 (`last_response.quantized_images`), not as storage
|
|
2506
|
+
* URLs — the one PixelLab character tool that works this way. A live
|
|
2507
|
+
* 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md).
|
|
2508
|
+
*/
|
|
2509
|
+
async transferOutfitV2(args) {
|
|
2510
|
+
const image = (img) => ({
|
|
2511
|
+
image: { type: "base64", base64: img.base64, format: img.format },
|
|
2512
|
+
size: { width: img.width, height: img.height }
|
|
2513
|
+
});
|
|
2514
|
+
const raw = await this.request("/transfer-outfit-v2", {
|
|
2515
|
+
method: "POST",
|
|
2516
|
+
body: JSON.stringify({
|
|
2517
|
+
reference_image: image(args.referenceImage),
|
|
2518
|
+
frames: args.frames.map(image),
|
|
2519
|
+
image_size: args.imageSize,
|
|
2520
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
2521
|
+
...args.noBackground !== void 0 ? { no_background: args.noBackground } : {},
|
|
2522
|
+
...args.additionalInstructions ? { additional_instructions: args.additionalInstructions } : {}
|
|
2523
|
+
})
|
|
2524
|
+
});
|
|
2525
|
+
return validateResponse(TransferOutfitSubmitSchema, raw, "transfer-outfit-v2");
|
|
2526
|
+
}
|
|
2379
2527
|
async getCharacter(characterId) {
|
|
2380
2528
|
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}`);
|
|
2381
2529
|
return validateResponse(CharacterSchema, raw, "characters/{id}");
|
|
@@ -2486,6 +2634,66 @@ var PixelLabClient = class {
|
|
|
2486
2634
|
"generate-image-v2"
|
|
2487
2635
|
);
|
|
2488
2636
|
}
|
|
2637
|
+
/**
|
|
2638
|
+
* `/create-image-pro-flash`: one native pixel-art image from a
|
|
2639
|
+
* description, optionally styled after a style image with per-trait
|
|
2640
|
+
* toggles, on the same Pro Flash model `character`/`objectPro` use for
|
|
2641
|
+
* their `pro-flash` engine. Sizes run 16 to 256 per side in multiples of 4.
|
|
2642
|
+
* A plain background job; `pollRevision`'s generic reader takes the
|
|
2643
|
+
* finished image. The response's `source_image_id` is what a Pro Flash
|
|
2644
|
+
* character or object can reuse instead of an upload.
|
|
2645
|
+
*/
|
|
2646
|
+
async createImageProFlash(args) {
|
|
2647
|
+
const body = {
|
|
2648
|
+
description: args.description,
|
|
2649
|
+
image_size: { width: args.width, height: args.height }
|
|
2650
|
+
};
|
|
2651
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2652
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2653
|
+
if (args.styleImage) {
|
|
2654
|
+
body.style_image = { image: args.styleImage.image, size: { width: args.styleImage.width, height: args.styleImage.height } };
|
|
2655
|
+
const traits = args.styleTraits;
|
|
2656
|
+
if (traits) {
|
|
2657
|
+
body.style_options = {
|
|
2658
|
+
...traits.palette !== void 0 ? { color_palette: traits.palette } : {},
|
|
2659
|
+
...traits.outline !== void 0 ? { outline: traits.outline } : {},
|
|
2660
|
+
...traits.detail !== void 0 ? { detail: traits.detail } : {},
|
|
2661
|
+
...traits.shading !== void 0 ? { shading: traits.shading } : {}
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
return validateResponse(
|
|
2666
|
+
ProFlashImageSubmitSchema,
|
|
2667
|
+
await this.request("/create-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
2668
|
+
"create-image-pro-flash"
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
/** `/edit-image-pro-flash`, text method: edit one image at its own size. The canvas never grows or resizes. */
|
|
2672
|
+
async editImageProFlash(args) {
|
|
2673
|
+
const body = { image: args.image, method: "text", description: args.description };
|
|
2674
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2675
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2676
|
+
return validateResponse(
|
|
2677
|
+
ProFlashImageSubmitSchema,
|
|
2678
|
+
await this.request("/edit-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
2679
|
+
"edit-image-pro-flash"
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* `/inpaint-image-pro-flash`: replace only the white pixels of a same-size
|
|
2684
|
+
* black/white mask, keeping everything else exactly. `Modify current
|
|
2685
|
+
* layer` (the endpoint's default) returns the full composite.
|
|
2686
|
+
*/
|
|
2687
|
+
async inpaintImageProFlash(args) {
|
|
2688
|
+
const body = { image: args.image, mask_image: args.maskImage, description: args.description };
|
|
2689
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2690
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2691
|
+
return validateResponse(
|
|
2692
|
+
ProFlashImageSubmitSchema,
|
|
2693
|
+
await this.request("/inpaint-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
2694
|
+
"inpaint-image-pro-flash"
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2489
2697
|
/**
|
|
2490
2698
|
* `/generate-ui-v2` ("Generate UI (Pro)"): one UI element (a button, a
|
|
2491
2699
|
* health bar, an inventory slot, a dialogue box) from a description,
|
|
@@ -3366,6 +3574,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3366
3574
|
uiPieces: spec.uiPieces,
|
|
3367
3575
|
uiElements: spec.uiElements,
|
|
3368
3576
|
uiColorPalette: spec.uiColorPalette,
|
|
3577
|
+
styleTraits: spec.styleTraits,
|
|
3369
3578
|
styleImages: styleImageHashes,
|
|
3370
3579
|
// A character family: the engine and rotations of a base, the parent's
|
|
3371
3580
|
// generated bytes for a state or animation (so a regenerated parent
|
|
@@ -3392,7 +3601,16 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3392
3601
|
styleTraits: spec.character.styleTraits,
|
|
3393
3602
|
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
|
|
3394
3603
|
concept: spec.character.concept?.sha256,
|
|
3395
|
-
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
3604
|
+
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0,
|
|
3605
|
+
// The parent's own bytes come from its recorded outputs; the plan
|
|
3606
|
+
// compares those directly (like a mirror), so only the choice of
|
|
3607
|
+
// parent, the reference image's bytes, and any extra guidance
|
|
3608
|
+
// are identity here.
|
|
3609
|
+
outfit: spec.character.outfit ? {
|
|
3610
|
+
of: spec.character.parentAssetId,
|
|
3611
|
+
reference: spec.character.outfit.reference.sha256,
|
|
3612
|
+
additionalInstructions: spec.character.outfit.additionalInstructions
|
|
3613
|
+
} : void 0
|
|
3396
3614
|
} : void 0,
|
|
3397
3615
|
// An objectPro family: same shape as character above, minus the
|
|
3398
3616
|
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
@@ -3433,7 +3651,8 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3433
3651
|
fps: spec.revision.fps,
|
|
3434
3652
|
lastFrameSha256: spec.revision.lastFrameSha256,
|
|
3435
3653
|
direction: spec.revision.direction,
|
|
3436
|
-
enhancePrompt: spec.revision.enhancePrompt
|
|
3654
|
+
enhancePrompt: spec.revision.enhancePrompt,
|
|
3655
|
+
engine: spec.revision.engine
|
|
3437
3656
|
} : void 0
|
|
3438
3657
|
})
|
|
3439
3658
|
);
|
|
@@ -3473,7 +3692,10 @@ function characterCost(spec) {
|
|
|
3473
3692
|
if (animation.mode === "v3") return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
3474
3693
|
return generationCost(spec.width, spec.height, "1dir");
|
|
3475
3694
|
}
|
|
3476
|
-
if (character.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
3695
|
+
if (character.kind === "state" || character.kind === "portrait") return generationCost(spec.width, spec.height, "1dir");
|
|
3696
|
+
if (character.kind === "outfit") {
|
|
3697
|
+
return 20;
|
|
3698
|
+
}
|
|
3477
3699
|
if (character.mode === "standard") return 1;
|
|
3478
3700
|
if (character.mode === "v3") return 1 + Math.max(1, Math.ceil(px * 8 / 65536));
|
|
3479
3701
|
if (character.mode === "pro-flash") {
|
|
@@ -3486,8 +3708,11 @@ function proFlashCharacterCost(width, height, fromReference) {
|
|
|
3486
3708
|
const side = Math.max(32, Math.ceil(Math.max(width, height) / 4) * 4);
|
|
3487
3709
|
const rotations = Math.max(1, Math.ceil(side * side * 8 / 65536));
|
|
3488
3710
|
if (fromReference) return rotations;
|
|
3489
|
-
|
|
3490
|
-
|
|
3711
|
+
return proFlashImageCost(side, side) + rotations;
|
|
3712
|
+
}
|
|
3713
|
+
function proFlashImageCost(width, height) {
|
|
3714
|
+
const side = Math.max(width, height);
|
|
3715
|
+
return side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
3491
3716
|
}
|
|
3492
3717
|
function characterAnimationName(spec) {
|
|
3493
3718
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
@@ -3563,7 +3788,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3563
3788
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
3564
3789
|
}
|
|
3565
3790
|
supports(generator) {
|
|
3566
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement";
|
|
3791
|
+
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";
|
|
3567
3792
|
}
|
|
3568
3793
|
/**
|
|
3569
3794
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -3590,15 +3815,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3590
3815
|
return { spacingMs: 2500, maxInFlight: 8 };
|
|
3591
3816
|
}
|
|
3592
3817
|
/**
|
|
3593
|
-
* Where synchronous pixflux results
|
|
3818
|
+
* Where synchronous pixflux results (or an outfit transfer's inline
|
|
3819
|
+
* frames) are parked between `submit`/poll and `fetch`.
|
|
3594
3820
|
*
|
|
3595
|
-
* pixflux returns the PNG inline rather than a job id,
|
|
3596
|
-
*
|
|
3597
|
-
*
|
|
3598
|
-
*
|
|
3821
|
+
* pixflux returns the PNG inline rather than a job id, and an outfit
|
|
3822
|
+
* transfer's completed job returns its frames inline too, but the
|
|
3823
|
+
* pipeline is built around submit → poll → fetch running as separate
|
|
3824
|
+
* commands. Writing the bytes to a known path keeps that model intact:
|
|
3825
|
+
* the id is the filename, polling is an existence check, and downloading
|
|
3826
|
+
* is a file read.
|
|
3599
3827
|
*/
|
|
3600
|
-
static cacheDir() {
|
|
3601
|
-
const dir = import_node_path.default.join(import_node_os.default.tmpdir(),
|
|
3828
|
+
static cacheDir(namespace = "pixflux") {
|
|
3829
|
+
const dir = import_node_path.default.join(import_node_os.default.tmpdir(), `pixelkiln-${namespace}`);
|
|
3602
3830
|
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
3603
3831
|
return dir;
|
|
3604
3832
|
}
|
|
@@ -3622,6 +3850,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3622
3850
|
const base = Math.max(1, Math.ceil(width * height * frames / 65536));
|
|
3623
3851
|
return { unit: "generations", amount: spec.revision.enhancePrompt ? base + 0.05 : base, candidates: 1 };
|
|
3624
3852
|
}
|
|
3853
|
+
if (spec.revision?.engine === "pro-flash") {
|
|
3854
|
+
return {
|
|
3855
|
+
unit: "generations",
|
|
3856
|
+
amount: proFlashImageCost(spec.revision.sourceWidth ?? spec.width, spec.revision.sourceHeight ?? spec.height),
|
|
3857
|
+
candidates: 1
|
|
3858
|
+
};
|
|
3859
|
+
}
|
|
3625
3860
|
if (spec.revision) {
|
|
3626
3861
|
const width = spec.revision.sourceWidth ?? spec.width;
|
|
3627
3862
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
@@ -3636,6 +3871,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3636
3871
|
if (spec.generator === "objectPro") {
|
|
3637
3872
|
return { unit: "generations", amount: objectProCost(spec), candidates: 1 };
|
|
3638
3873
|
}
|
|
3874
|
+
if (spec.generator === "imageProFlash") {
|
|
3875
|
+
return { unit: "generations", amount: proFlashImageCost(spec.width, spec.height), candidates: 1 };
|
|
3876
|
+
}
|
|
3639
3877
|
return {
|
|
3640
3878
|
unit: "generations",
|
|
3641
3879
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
@@ -3649,7 +3887,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3649
3887
|
`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`
|
|
3650
3888
|
);
|
|
3651
3889
|
}
|
|
3652
|
-
if (spec.revision?.
|
|
3890
|
+
if (spec.revision?.engine === "pro-flash") {
|
|
3891
|
+
const { sourceWidth: width, sourceHeight: height, mode: mode2 } = spec.revision;
|
|
3892
|
+
if (width != null && height != null && (width < 32 || height < 32 || width > 256 || height > 256 || width % 4 || height % 4)) {
|
|
3893
|
+
throw new Error(
|
|
3894
|
+
`PixelLab pro-flash ${mode2} source is ${width}x${height}; the API takes 32 to 256 pixels per side, in multiples of 4`
|
|
3895
|
+
);
|
|
3896
|
+
}
|
|
3897
|
+
if (spec.revision.strength != null) {
|
|
3898
|
+
throw new Error(`PixelLab pro-flash ${mode2} takes no strength; the edit applies in full`);
|
|
3899
|
+
}
|
|
3900
|
+
} else if (spec.revision?.mode === "inpaint") {
|
|
3653
3901
|
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
3654
3902
|
if (width != null && height != null && (width < 32 || height < 32 || width > 512 || height > 512)) {
|
|
3655
3903
|
throw new Error(`PixelLab inpaint source is ${width}x${height}; the API takes 32 to 512 pixels per side`);
|
|
@@ -3730,6 +3978,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3730
3978
|
`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)`
|
|
3731
3979
|
);
|
|
3732
3980
|
}
|
|
3981
|
+
if (spec.generator === "imageProFlash") {
|
|
3982
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > 256 || spec.height > 256 || spec.width % 4 || spec.height % 4) {
|
|
3983
|
+
throw new Error(
|
|
3984
|
+
`PixelLab imageProFlash is ${spec.width}x${spec.height}; the API takes 16 to 256 pixels per side, in multiples of 4`
|
|
3985
|
+
);
|
|
3986
|
+
}
|
|
3987
|
+
if (styleImages.length > 1) {
|
|
3988
|
+
throw new Error("PixelLab imageProFlash takes one style image; list at most one styleImages entry");
|
|
3989
|
+
}
|
|
3990
|
+
if (spec.styleTraits && !styleImages.length) {
|
|
3991
|
+
throw new Error("PixelLab imageProFlash: styleTraits choose what a style image lends; add a styleImages entry or drop them");
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3733
3994
|
if (spec.generator === "uiElement" && (spec.width < 16 || spec.height < 16 || spec.width > 792 || spec.height > 688)) {
|
|
3734
3995
|
throw new Error(
|
|
3735
3996
|
`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)`
|
|
@@ -3858,6 +4119,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3858
4119
|
const character = spec.character;
|
|
3859
4120
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
3860
4121
|
const label = `${spec.styleId}/${spec.assetId}`;
|
|
4122
|
+
if (character.kind === "outfit") {
|
|
4123
|
+
const reference = character.outfit.reference;
|
|
4124
|
+
if (reference.width < 32 || reference.width > 256 || reference.height < 32 || reference.height > 256) {
|
|
4125
|
+
throw new Error(`${label}: outfit reference is ${reference.width}x${reference.height}; PixelLab transfer-outfit-v2 takes 32 to 256px per side`);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
3861
4128
|
if (character.kind === "base" && character.proportions !== void 0) {
|
|
3862
4129
|
if (character.mode !== "standard") {
|
|
3863
4130
|
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
@@ -3869,7 +4136,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3869
4136
|
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
3870
4137
|
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
3871
4138
|
}
|
|
3872
|
-
if (character.animation) {
|
|
4139
|
+
if (character.animation && character.kind !== "outfit") {
|
|
3873
4140
|
const { startFrame, endFrame } = character.animation;
|
|
3874
4141
|
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
3875
4142
|
if (image && (image.width > 256 || image.height > 256)) {
|
|
@@ -3950,9 +4217,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3950
4217
|
if (character.styleTraits && character.mode !== "pro-flash") {
|
|
3951
4218
|
throw new Error(`${label}: styleTraits choose what a pro-flash style image lends; the ${character.mode} engine does not take them`);
|
|
3952
4219
|
}
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
4220
|
+
if (character.kind !== "portrait") {
|
|
4221
|
+
const maxSize = character.mode === "v3" || character.mode === "pro-flash" ? 256 : 128;
|
|
4222
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
4223
|
+
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
4224
|
+
}
|
|
3956
4225
|
}
|
|
3957
4226
|
if (character.mode === "pro-flash" && character.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
3958
4227
|
throw new Error(`PixelLab pro-flash characters are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
@@ -3961,9 +4230,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3961
4230
|
if (!templates.includes(character.template)) {
|
|
3962
4231
|
throw new Error(`PixelLab ${character.mode} character template must be one of: ${templates.join(", ")}`);
|
|
3963
4232
|
}
|
|
3964
|
-
const views = character.mode === "standard" ? ["low top-down", "high top-down", "side", "perspective", "oblique"] : ["low top-down", "high top-down", "side"];
|
|
4233
|
+
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"];
|
|
3965
4234
|
if (!views.includes(spec.view)) {
|
|
3966
|
-
throw new Error(
|
|
4235
|
+
throw new Error(
|
|
4236
|
+
character.kind === "portrait" ? `PixelLab portrait view must be one of: ${views.join(", ")}` : `PixelLab ${character.mode} character view must be one of: ${views.join(", ")}`
|
|
4237
|
+
);
|
|
3967
4238
|
}
|
|
3968
4239
|
if (spec.view === "oblique" && character.directions !== 4) {
|
|
3969
4240
|
throw new Error("PixelLab oblique characters are 4-direction only");
|
|
@@ -3971,7 +4242,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3971
4242
|
if (character.kind !== "base" && !character.parentAssetId) {
|
|
3972
4243
|
throw new Error(`${spec.styleId}/${spec.assetId} is a ${character.kind} with no parent`);
|
|
3973
4244
|
}
|
|
3974
|
-
if (character.animation) {
|
|
4245
|
+
if (character.animation && character.kind !== "outfit") {
|
|
3975
4246
|
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
3976
4247
|
if (!allowed.includes(character.animation.direction)) {
|
|
3977
4248
|
throw new Error(
|
|
@@ -4073,10 +4344,48 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4073
4344
|
concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
|
|
4074
4345
|
styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0,
|
|
4075
4346
|
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
4076
|
-
styleTraits: character.styleTraits
|
|
4347
|
+
styleTraits: character.styleTraits,
|
|
4348
|
+
sourceImageId: character.mode === "pro-flash" ? context?.referenceSourceImageId : void 0
|
|
4077
4349
|
});
|
|
4078
4350
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
4079
4351
|
}
|
|
4352
|
+
if (character.kind === "outfit") {
|
|
4353
|
+
const parentOutputs = context?.parentOutputs ?? [];
|
|
4354
|
+
if (parentOutputs.length < 2) {
|
|
4355
|
+
throw new Error(
|
|
4356
|
+
`${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`
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
4359
|
+
const frames = parentOutputs.slice(0, 16).map((output) => {
|
|
4360
|
+
const bytes = (0, import_node_fs2.readFileSync)(output.path);
|
|
4361
|
+
const dims = imageMetadata(bytes);
|
|
4362
|
+
if (!dims) throw new Error(`${spec.styleId}/${spec.assetId}: outfit source frame is not a readable PNG: ${output.path}`);
|
|
4363
|
+
return { base64: bytes.toString("base64"), format: "png", width: dims.width, height: dims.height };
|
|
4364
|
+
});
|
|
4365
|
+
const outfit = character.outfit;
|
|
4366
|
+
const referenceBytes = (0, import_node_fs2.readFileSync)(outfit.reference.path);
|
|
4367
|
+
if (sha256(referenceBytes) !== outfit.reference.sha256) {
|
|
4368
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: outfit reference changed after the manifest was resolved: ${outfit.reference.path}`);
|
|
4369
|
+
}
|
|
4370
|
+
const res2 = await this.client.transferOutfitV2({
|
|
4371
|
+
referenceImage: { base64: referenceBytes.toString("base64"), format: "png", width: outfit.reference.width, height: outfit.reference.height },
|
|
4372
|
+
frames,
|
|
4373
|
+
imageSize: { width: spec.width, height: spec.height },
|
|
4374
|
+
seed: spec.seed,
|
|
4375
|
+
noBackground: spec.noBackground,
|
|
4376
|
+
additionalInstructions: outfit.additionalInstructions
|
|
4377
|
+
});
|
|
4378
|
+
return {
|
|
4379
|
+
jobId: res2.background_job_id,
|
|
4380
|
+
metadata: {
|
|
4381
|
+
character: {
|
|
4382
|
+
kind: "outfit",
|
|
4383
|
+
backgroundJobId: res2.background_job_id,
|
|
4384
|
+
frameRoles: parentOutputs.slice(0, 16).map((output) => output.role ?? null)
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4080
4389
|
const parentId = context?.parentObjectId;
|
|
4081
4390
|
if (!parentId) {
|
|
4082
4391
|
throw new Error(
|
|
@@ -4095,6 +4404,24 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4095
4404
|
});
|
|
4096
4405
|
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 } } };
|
|
4097
4406
|
}
|
|
4407
|
+
if (character.kind === "portrait") {
|
|
4408
|
+
const parentFile = character.parentFile;
|
|
4409
|
+
const bytes = (0, import_node_fs2.readFileSync)(parentFile);
|
|
4410
|
+
if (character.parentSha256 && sha256(bytes) !== character.parentSha256) {
|
|
4411
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: portrait source changed after the manifest was resolved: ${parentFile}`);
|
|
4412
|
+
}
|
|
4413
|
+
const res2 = await this.client.createPortraitCharacterPro({
|
|
4414
|
+
direction: "character_to_portrait",
|
|
4415
|
+
image: { base64: bytes.toString("base64"), format: "png" },
|
|
4416
|
+
view: spec.view,
|
|
4417
|
+
resultSize: character.portrait.size,
|
|
4418
|
+
seed: spec.seed
|
|
4419
|
+
});
|
|
4420
|
+
return {
|
|
4421
|
+
jobId: res2.background_job_id,
|
|
4422
|
+
metadata: { character: { kind: "portrait", parentCharacterId: parentId, backgroundJobId: res2.background_job_id } }
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4098
4425
|
const animation = character.animation;
|
|
4099
4426
|
const name = characterAnimationName(spec);
|
|
4100
4427
|
const replaced = context?.replacedMetadata?.character ?? {};
|
|
@@ -4153,7 +4480,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4153
4480
|
reference: object.reference ? readPose(spec, "reference sprite", object.reference) : void 0,
|
|
4154
4481
|
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
4155
4482
|
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
4156
|
-
styleTraits: object.styleTraits
|
|
4483
|
+
styleTraits: object.styleTraits,
|
|
4484
|
+
sourceImageId: context?.referenceSourceImageId
|
|
4157
4485
|
});
|
|
4158
4486
|
return { jobId: res2.object_id, metadata: { objectPro: { kind: "base", objectId: res2.object_id, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
4159
4487
|
}
|
|
@@ -4208,6 +4536,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4208
4536
|
async pollCharacter(jobId, context) {
|
|
4209
4537
|
const animation = decodeAnimationJob(jobId);
|
|
4210
4538
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
4539
|
+
const kind = context?.metadata?.character?.kind ?? context?.spec?.character?.kind;
|
|
4540
|
+
if (kind === "portrait") return this.pollCharacterPortrait(jobId, context);
|
|
4541
|
+
if (kind === "outfit") return this.pollCharacterOutfit(jobId, context);
|
|
4211
4542
|
const character = await this.client.getCharacter(jobId);
|
|
4212
4543
|
if (character.status === "failed") return { status: "failed", error: "character generation failed upstream" };
|
|
4213
4544
|
if (character.status !== "completed" || !character.rotation_urls) return { status: "processing" };
|
|
@@ -4233,6 +4564,66 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4233
4564
|
billed: await this.billedForJob(backgroundJobId)
|
|
4234
4565
|
};
|
|
4235
4566
|
}
|
|
4567
|
+
/**
|
|
4568
|
+
* `jobId` here is `portrait-character-pro`'s own background job id, not a
|
|
4569
|
+
* character id: the endpoint converts an arbitrary image and never touches
|
|
4570
|
+
* the character record. Once the portrait itself is drawn, this also
|
|
4571
|
+
* attaches it to the parent's character record with `SetPortrait` — a
|
|
4572
|
+
* separate, free call PixelLab does not make automatically (docs/PIXELLAB.md).
|
|
4573
|
+
*/
|
|
4574
|
+
async pollCharacterPortrait(jobId, context) {
|
|
4575
|
+
const meta = context?.metadata?.character;
|
|
4576
|
+
try {
|
|
4577
|
+
const job = await this.client.getPortraitCharacterJob(jobId);
|
|
4578
|
+
if (job.status === "failed") return { status: "failed", error: "portrait generation failed upstream" };
|
|
4579
|
+
if (job.status !== "completed" || !job.download_url) return { status: "processing" };
|
|
4580
|
+
if (meta?.parentCharacterId) {
|
|
4581
|
+
const bytes = await this.client.download(job.download_url);
|
|
4582
|
+
await this.client.setCharacterPortrait(meta.parentCharacterId, { base64: bytes.toString("base64"), format: "png" });
|
|
4583
|
+
}
|
|
4584
|
+
return {
|
|
4585
|
+
status: "ready",
|
|
4586
|
+
objectId: jobId,
|
|
4587
|
+
sourceUrl: job.download_url,
|
|
4588
|
+
sources: [{ url: job.download_url }],
|
|
4589
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
4590
|
+
};
|
|
4591
|
+
} catch (err) {
|
|
4592
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
4593
|
+
throw err;
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* No dedicated status endpoint exists for `transfer-outfit-v2`, unlike
|
|
4598
|
+
* portraits and tiles: the background job is the only place to poll. A
|
|
4599
|
+
* completed job returns its frames inline as base64
|
|
4600
|
+
* (`last_response.quantized_images`), not as storage URLs, so they are
|
|
4601
|
+
* cached locally the way a synchronous pixflux result is, and matched back
|
|
4602
|
+
* to the source frames' own roles recorded at submit time.
|
|
4603
|
+
*/
|
|
4604
|
+
async pollCharacterOutfit(jobId, context) {
|
|
4605
|
+
const meta = context?.metadata?.character;
|
|
4606
|
+
const job = await this.client.getBackgroundJob(jobId);
|
|
4607
|
+
if (job.status === "failed") return { status: "failed", error: "outfit transfer failed upstream" };
|
|
4608
|
+
if (job.status !== "completed") return { status: "processing" };
|
|
4609
|
+
const images = job.last_response?.quantized_images;
|
|
4610
|
+
if (!images?.length) return { status: "failed", error: "outfit transfer completed with no result frames" };
|
|
4611
|
+
const dir = _PixelLabProvider.cacheDir("outfit");
|
|
4612
|
+
const sources = images.map((image, index) => {
|
|
4613
|
+
if (!image.base64) throw new Error(`outfit transfer frame ${index} has no image data`);
|
|
4614
|
+
const role = meta?.frameRoles?.[index] ?? void 0;
|
|
4615
|
+
const file = import_node_path.default.join(dir, `${jobId}-${index}.png`);
|
|
4616
|
+
(0, import_node_fs2.writeFileSync)(file, Buffer.from(image.base64, "base64"));
|
|
4617
|
+
return { url: `file://${file}`, ...role ? { role } : {} };
|
|
4618
|
+
});
|
|
4619
|
+
return {
|
|
4620
|
+
status: "ready",
|
|
4621
|
+
objectId: jobId,
|
|
4622
|
+
sourceUrl: sources[0].url,
|
|
4623
|
+
sources,
|
|
4624
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4236
4627
|
/**
|
|
4237
4628
|
* The background job is the authoritative record of one direction: it
|
|
4238
4629
|
* completes with the frame URLs, the animation id, and the direction, and
|
|
@@ -4389,6 +4780,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4389
4780
|
});
|
|
4390
4781
|
return { jobId: res2.ui_asset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
4391
4782
|
}
|
|
4783
|
+
if (spec.generator === "imageProFlash") {
|
|
4784
|
+
const style = styleImages[0];
|
|
4785
|
+
const res2 = await this.client.createImageProFlash({
|
|
4786
|
+
description: spec.prompt,
|
|
4787
|
+
width: spec.width,
|
|
4788
|
+
height: spec.height,
|
|
4789
|
+
noBackground: spec.noBackground,
|
|
4790
|
+
seed: spec.seed,
|
|
4791
|
+
styleImage: style ? { image: { base64: style.base64, format: style.format }, width: style.width, height: style.height } : void 0,
|
|
4792
|
+
styleTraits: spec.styleTraits
|
|
4793
|
+
});
|
|
4794
|
+
return { jobId: res2.background_job_id, metadata: { imageProFlash: proFlashRecord(res2) } };
|
|
4795
|
+
}
|
|
4392
4796
|
if (spec.generator === "uiElement") {
|
|
4393
4797
|
const concept = styleImages[0];
|
|
4394
4798
|
const res2 = await this.client.generateUiV2({
|
|
@@ -4542,6 +4946,32 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4542
4946
|
const width = revision.sourceWidth;
|
|
4543
4947
|
const height = revision.sourceHeight;
|
|
4544
4948
|
const roles = revision.sourceMembers?.map((member) => member.role);
|
|
4949
|
+
if (revision.engine === "pro-flash") {
|
|
4950
|
+
if (revision.mode === "inpaint") {
|
|
4951
|
+
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
4952
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
4953
|
+
}
|
|
4954
|
+
const maskBytes = (0, import_node_fs2.readFileSync)(revision.maskFile);
|
|
4955
|
+
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
4956
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
4957
|
+
}
|
|
4958
|
+
const res3 = await this.client.inpaintImageProFlash({
|
|
4959
|
+
image,
|
|
4960
|
+
maskImage: { base64: maskBytes.toString("base64"), format: revision.maskFormat },
|
|
4961
|
+
description: spec.prompt,
|
|
4962
|
+
noBackground: spec.noBackground,
|
|
4963
|
+
seed: spec.seed
|
|
4964
|
+
});
|
|
4965
|
+
return { jobId: res3.background_job_id, metadata: { imageProFlash: proFlashRecord(res3) } };
|
|
4966
|
+
}
|
|
4967
|
+
const res2 = await this.client.editImageProFlash({
|
|
4968
|
+
image,
|
|
4969
|
+
description: spec.prompt,
|
|
4970
|
+
noBackground: spec.noBackground,
|
|
4971
|
+
seed: spec.seed
|
|
4972
|
+
});
|
|
4973
|
+
return { jobId: res2.background_job_id, metadata: { imageProFlash: proFlashRecord(res2) } };
|
|
4974
|
+
}
|
|
4545
4975
|
if (revision.mode === "inpaint") {
|
|
4546
4976
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
4547
4977
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
@@ -4706,6 +5136,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4706
5136
|
return this.pollAnimateRevision(jobId, context);
|
|
4707
5137
|
}
|
|
4708
5138
|
if (context?.spec?.revision) return this.pollRevision(jobId);
|
|
5139
|
+
if (generator === "imageProFlash") return this.pollRevision(jobId);
|
|
4709
5140
|
if (generator === "pixflux") {
|
|
4710
5141
|
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
4711
5142
|
if ((0, import_node_fs2.existsSync)(file)) {
|
|
@@ -5286,6 +5717,13 @@ function readPose(spec, what, image) {
|
|
|
5286
5717
|
}
|
|
5287
5718
|
return { base64: bytes.toString("base64"), format: image.format };
|
|
5288
5719
|
}
|
|
5720
|
+
function proFlashRecord(res) {
|
|
5721
|
+
return {
|
|
5722
|
+
imageId: res.image_id,
|
|
5723
|
+
sourceImageId: res.source_image_id,
|
|
5724
|
+
...res.estimated_generations != null ? { estimatedGenerations: res.estimated_generations } : {}
|
|
5725
|
+
};
|
|
5726
|
+
}
|
|
5289
5727
|
function editAnimationFrameCeiling(width, height) {
|
|
5290
5728
|
const side = Math.max(width, height);
|
|
5291
5729
|
if (side <= 64) return 16;
|
|
@@ -6489,6 +6927,11 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6489
6927
|
throw new Error("ComfyUI providerOptions.frames requires generator: frames");
|
|
6490
6928
|
}
|
|
6491
6929
|
if (spec.revision) {
|
|
6930
|
+
if (spec.revision.engine) {
|
|
6931
|
+
throw new Error(
|
|
6932
|
+
`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.`
|
|
6933
|
+
);
|
|
6934
|
+
}
|
|
6492
6935
|
if (spec.revision.sourceMembers) {
|
|
6493
6936
|
throw new Error(
|
|
6494
6937
|
`ComfyUI revisions read one source image; ${spec.revision.sourceAssetId} is a ${spec.revision.sourceMembers.length}-member set (directions or frames)`
|
|
@@ -7831,7 +8274,7 @@ ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
|
7831
8274
|
return { manifest: parsed.data, root: import_node_path5.default.dirname(abs), path: abs, ...loopFamilies };
|
|
7832
8275
|
}
|
|
7833
8276
|
function parentAssetId(asset) {
|
|
7834
|
-
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
8277
|
+
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
7835
8278
|
}
|
|
7836
8279
|
function formatManifestIssues(issues) {
|
|
7837
8280
|
return issues.flatMap((issue) => {
|
|
@@ -7998,9 +8441,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7998
8441
|
height = styleImageDimensions.length ? Math.max(...styleImageDimensions.map((img) => img.height)) : style.tileSize ?? 32;
|
|
7999
8442
|
size = Math.max(width, height);
|
|
8000
8443
|
} else if (generator === "character") {
|
|
8001
|
-
size = asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
8002
|
-
width = asset.state?.canvas?.width ?? size;
|
|
8003
|
-
height = asset.state?.canvas?.height ?? size;
|
|
8444
|
+
size = asset.portrait ? asset.portrait.size : asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
8445
|
+
width = asset.portrait?.size ?? asset.state?.canvas?.width ?? size;
|
|
8446
|
+
height = asset.portrait?.size ?? asset.state?.canvas?.height ?? size;
|
|
8004
8447
|
} else if (generator === "terrain") {
|
|
8005
8448
|
size = style.terrainTileSize ?? 16;
|
|
8006
8449
|
width = size;
|
|
@@ -8027,12 +8470,17 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8027
8470
|
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
8028
8471
|
);
|
|
8029
8472
|
}
|
|
8473
|
+
if ((asset.portrait || asset.outfit) && generator !== "character") {
|
|
8474
|
+
throw new Error(
|
|
8475
|
+
`assets.${assetId}: ${asset.portrait ? "portrait" : "outfit"} needs a character style; "${styleId}" generates ${generator}`
|
|
8476
|
+
);
|
|
8477
|
+
}
|
|
8030
8478
|
if ((asset.pieces || asset.elements) && generator !== "uiAsset") {
|
|
8031
8479
|
throw new Error(
|
|
8032
8480
|
`assets.${assetId}: ${asset.pieces ? "pieces" : "elements"} needs a uiAsset style; "${styleId}" generates ${generator}`
|
|
8033
8481
|
);
|
|
8034
8482
|
}
|
|
8035
|
-
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
8483
|
+
const characterKind = asset.animation ? "animation" : asset.state ? "state" : asset.portrait ? "portrait" : "base";
|
|
8036
8484
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
8037
8485
|
let terrainLowerDescription;
|
|
8038
8486
|
let terrainUpperDescription;
|
|
@@ -8081,6 +8529,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8081
8529
|
uiPieces: generator === "uiAsset" ? asset.pieces : void 0,
|
|
8082
8530
|
uiElements: generator === "uiAsset" ? asset.elements : void 0,
|
|
8083
8531
|
uiColorPalette: generator === "uiAsset" || generator === "uiElement" ? style.uiColorPalette : void 0,
|
|
8532
|
+
...generator === "imageProFlash" && style.styleTraits ? { styleTraits: style.styleTraits } : {},
|
|
8084
8533
|
palette: style.palette,
|
|
8085
8534
|
enforcePalette: style.enforcePalette,
|
|
8086
8535
|
noBackground: style.noBackground,
|
|
@@ -8124,7 +8573,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8124
8573
|
// its parent's pixels, not a new character: giving it a base shape
|
|
8125
8574
|
// would have packing, the gallery, and adoption treat it as one.
|
|
8126
8575
|
...generator === "character" && !asset.revision ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
8127
|
-
|
|
8576
|
+
// A portrait needs a character style (refused above), so an
|
|
8577
|
+
// objectPro asset is only ever a base, state, or animation.
|
|
8578
|
+
...generator === "objectPro" && !asset.revision && characterKind !== "portrait" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
8128
8579
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
8129
8580
|
costUnit: "generations",
|
|
8130
8581
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -8235,7 +8686,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8235
8686
|
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0, import_node_fs5.existsSync)(file) ? await sha256File(file) : null }
|
|
8236
8687
|
};
|
|
8237
8688
|
}
|
|
8238
|
-
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
8689
|
+
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
8239
8690
|
if (resolved.character && characterParent) {
|
|
8240
8691
|
const parentSpec = await finalize(characterParent);
|
|
8241
8692
|
const parentDirections = parentSpec.character?.directions ?? 8;
|
|
@@ -8268,6 +8719,38 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8268
8719
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
8269
8720
|
};
|
|
8270
8721
|
}
|
|
8722
|
+
if (asset.outfit) {
|
|
8723
|
+
if (asset.outfit.of === assetId) throw new Error(`assets.${assetId}: an asset cannot re-clothe itself`);
|
|
8724
|
+
const sourceSpec = await finalize(asset.outfit.of);
|
|
8725
|
+
if (sourceSpec.character?.kind !== "animation") {
|
|
8726
|
+
throw new Error(`assets.${assetId}: outfit "${asset.outfit.of}" is not a character loop`);
|
|
8727
|
+
}
|
|
8728
|
+
const referenceImage = await loadStyleImage(asset.outfit.reference, "Outfit reference image");
|
|
8729
|
+
resolved.character = {
|
|
8730
|
+
kind: "outfit",
|
|
8731
|
+
mode: sourceSpec.character.mode,
|
|
8732
|
+
directions: sourceSpec.character.directions,
|
|
8733
|
+
template: sourceSpec.character.template,
|
|
8734
|
+
parentAssetId: asset.outfit.of,
|
|
8735
|
+
parentSpec: sourceSpec,
|
|
8736
|
+
animation: sourceSpec.character.animation,
|
|
8737
|
+
outfit: {
|
|
8738
|
+
reference: {
|
|
8739
|
+
path: import_node_path5.default.resolve(root, asset.outfit.reference),
|
|
8740
|
+
sha256: referenceImage.hash,
|
|
8741
|
+
width: referenceImage.width,
|
|
8742
|
+
height: referenceImage.height,
|
|
8743
|
+
format: referenceImage.format
|
|
8744
|
+
},
|
|
8745
|
+
...asset.outfit.additionalInstructions ? { additionalInstructions: asset.outfit.additionalInstructions } : {}
|
|
8746
|
+
}
|
|
8747
|
+
};
|
|
8748
|
+
resolved.generator = sourceSpec.generator;
|
|
8749
|
+
resolved.width = sourceSpec.width;
|
|
8750
|
+
resolved.height = sourceSpec.height;
|
|
8751
|
+
resolved.size = sourceSpec.size;
|
|
8752
|
+
resolved.prompt = "";
|
|
8753
|
+
}
|
|
8271
8754
|
if (asset.revision) {
|
|
8272
8755
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
8273
8756
|
throw new Error(
|
|
@@ -8330,7 +8813,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8330
8813
|
lastFrameFormat: lastFrame?.format ?? null
|
|
8331
8814
|
} : {},
|
|
8332
8815
|
...asset.revision.direction ? { direction: asset.revision.direction } : {},
|
|
8333
|
-
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
8816
|
+
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt },
|
|
8817
|
+
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
8334
8818
|
};
|
|
8335
8819
|
}
|
|
8336
8820
|
if (asset.batch) {
|
|
@@ -8541,6 +9025,9 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
8541
9025
|
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
8542
9026
|
};
|
|
8543
9027
|
}
|
|
9028
|
+
if (asset.portrait) {
|
|
9029
|
+
shape.portrait = { size: asset.portrait.size };
|
|
9030
|
+
}
|
|
8544
9031
|
if (asset.animation) {
|
|
8545
9032
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
8546
9033
|
const pose = async (rel, what) => {
|
|
@@ -10093,6 +10580,7 @@ var import_node_fs8 = require("fs");
|
|
|
10093
10580
|
var import_node_path11 = __toESM(require("path"), 1);
|
|
10094
10581
|
async function inspectRevisionReadiness(spec, lock) {
|
|
10095
10582
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
10583
|
+
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
10096
10584
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
10097
10585
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
10098
10586
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -10119,6 +10607,20 @@ async function inspectMirror(spec, lock, seen) {
|
|
|
10119
10607
|
const dependency = await inspectParent(source, lock, seen);
|
|
10120
10608
|
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
10121
10609
|
}
|
|
10610
|
+
async function inspectOutfit(spec, lock, seen) {
|
|
10611
|
+
const character = spec.character;
|
|
10612
|
+
const outfit = character.outfit;
|
|
10613
|
+
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
10614
|
+
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
10615
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
10616
|
+
if (!(0, import_node_fs8.existsSync)(outfit.reference.path)) {
|
|
10617
|
+
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
10618
|
+
}
|
|
10619
|
+
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
10620
|
+
return { ready: false, reason: "outfit reference changed after the manifest was resolved" };
|
|
10621
|
+
}
|
|
10622
|
+
return { ready: true, reason: `${label} is current` };
|
|
10623
|
+
}
|
|
10122
10624
|
async function inspectCharacterParent(spec, lock, seen) {
|
|
10123
10625
|
const character = spec.character;
|
|
10124
10626
|
const parentSpec = character.parentSpec;
|
|
@@ -10234,7 +10736,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
10234
10736
|
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
10235
10737
|
if (!inputs.ready) return inputs;
|
|
10236
10738
|
}
|
|
10237
|
-
if (spec.character?.
|
|
10739
|
+
if (spec.character?.kind === "outfit") {
|
|
10740
|
+
const inputs = await inspectOutfit(spec, lock, nextSeen);
|
|
10741
|
+
if (!inputs.ready) return inputs;
|
|
10742
|
+
} else if (spec.character?.parentSpec) {
|
|
10238
10743
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
10239
10744
|
if (!inputs.ready) return inputs;
|
|
10240
10745
|
}
|
|
@@ -10357,6 +10862,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
10357
10862
|
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !(0, import_node_fs9.existsSync)(currentEntryOutputPath(entry, spec, index)))) {
|
|
10358
10863
|
state = "stale";
|
|
10359
10864
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
10865
|
+
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
10866
|
+
state = "stale";
|
|
10867
|
+
reason = `outfit source ${spec.character.parentAssetId} changed; re-clothe the loop again`;
|
|
10360
10868
|
} else if (entry.status === "download-failed") {
|
|
10361
10869
|
state = "recoverable";
|
|
10362
10870
|
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
@@ -13054,6 +13562,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13054
13562
|
const supersededOutputs = previousEntry?.outputs.length ? previousEntry.outputs : previousEntry?.supersededOutputs ?? [];
|
|
13055
13563
|
const submittedAt = resumesCheckpoint ? previousEntry.submittedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
13056
13564
|
const history = historyAfterReplacing(previousEntry, historyLimit(loaded.manifest), submittedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
13565
|
+
const outfitParentEntry = spec.character?.kind === "outfit" && spec.character.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
|
|
13057
13566
|
upsert(lock, key, {
|
|
13058
13567
|
styleId: spec.styleId,
|
|
13059
13568
|
assetId: spec.assetId,
|
|
@@ -13077,7 +13586,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13077
13586
|
...spec.revision.fps == null ? {} : { fps: spec.revision.fps },
|
|
13078
13587
|
...spec.revision.lastFrameSha256 ? { lastFrameSha256: spec.revision.lastFrameSha256 } : {},
|
|
13079
13588
|
...spec.revision.direction ? { direction: spec.revision.direction } : {},
|
|
13080
|
-
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt }
|
|
13589
|
+
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt },
|
|
13590
|
+
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
13591
|
+
} : null,
|
|
13592
|
+
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
13593
|
+
sourceAssetId: spec.character.parentAssetId,
|
|
13594
|
+
sourceSha256: mirrorSourceHash(outfitParentEntry),
|
|
13595
|
+
referenceSha256: spec.character.outfit.reference.sha256
|
|
13081
13596
|
} : null,
|
|
13082
13597
|
status: "pending",
|
|
13083
13598
|
jobId: previousJobId ?? null,
|
|
@@ -13112,10 +13627,17 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13112
13627
|
const anchor = spec.character?.styleAnchor;
|
|
13113
13628
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
13114
13629
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
13630
|
+
const parentOutputs = spec.character?.kind === "outfit" && parentEntry ? parentEntry.outputs.map((output, index) => ({
|
|
13631
|
+
path: currentEntryOutputPath(parentEntry, spec.character.parentSpec, index),
|
|
13632
|
+
role: output.role
|
|
13633
|
+
})) : void 0;
|
|
13634
|
+
const referenceSourceImageId = ownedReferenceImageId(spec, lock, provider.id);
|
|
13115
13635
|
const { jobId, metadata } = await provider.submit(spec, refs, {
|
|
13116
13636
|
...previousJobId ? { previousJobId } : {},
|
|
13637
|
+
...referenceSourceImageId ? { referenceSourceImageId } : {},
|
|
13117
13638
|
...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
|
|
13118
13639
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
13640
|
+
...parentOutputs ? { parentOutputs } : {},
|
|
13119
13641
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
13120
13642
|
...previousMetadata ? { previousMetadata } : {},
|
|
13121
13643
|
checkpoint: async (checkpoint) => {
|
|
@@ -13179,6 +13701,17 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13179
13701
|
}
|
|
13180
13702
|
return { submitted, failed, spent, unit };
|
|
13181
13703
|
}
|
|
13704
|
+
function ownedReferenceImageId(spec, lock, providerId) {
|
|
13705
|
+
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;
|
|
13706
|
+
if (!sha2562) return void 0;
|
|
13707
|
+
for (const entry of Object.values(lock.entries)) {
|
|
13708
|
+
if (entry.provider !== providerId || entry.generator !== "imageProFlash" || entry.status !== "downloaded") continue;
|
|
13709
|
+
if (entry.outputs.length !== 1 || entry.outputs[0].sha256 !== sha2562) continue;
|
|
13710
|
+
const recorded = entry.providerMetadata?.[providerId]?.imageProFlash;
|
|
13711
|
+
if (typeof recorded?.sourceImageId === "string" && recorded.sourceImageId) return recorded.sourceImageId;
|
|
13712
|
+
}
|
|
13713
|
+
return void 0;
|
|
13714
|
+
}
|
|
13182
13715
|
|
|
13183
13716
|
// src/pipeline/poll.ts
|
|
13184
13717
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -15234,7 +15767,7 @@ function describeCharacter(spec, entry) {
|
|
|
15234
15767
|
};
|
|
15235
15768
|
}
|
|
15236
15769
|
if (entry?.generator === "character") {
|
|
15237
|
-
const kind = recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
15770
|
+
const kind = recordedCharacter?.kind === "portrait" ? "portrait" : recordedCharacter?.kind === "outfit" ? "outfit" : recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
15238
15771
|
return {
|
|
15239
15772
|
generator: "character",
|
|
15240
15773
|
kind,
|
|
@@ -15595,7 +16128,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
15595
16128
|
characters: style?.generator === "character" || style?.generator === "objectPro" ? {
|
|
15596
16129
|
bases: styleItems.filter((item) => item.character?.kind === "base").length,
|
|
15597
16130
|
states: styleItems.filter((item) => item.character?.kind === "state").length,
|
|
15598
|
-
animations: styleItems.filter((item) => item.character?.kind === "animation").length
|
|
16131
|
+
animations: styleItems.filter((item) => item.character?.kind === "animation").length,
|
|
16132
|
+
portraits: styleItems.filter((item) => item.character?.kind === "portrait").length,
|
|
16133
|
+
outfits: styleItems.filter((item) => item.character?.kind === "outfit").length
|
|
15599
16134
|
} : null,
|
|
15600
16135
|
tags: style?.tags ?? [],
|
|
15601
16136
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
@@ -17390,6 +17925,9 @@ async function workspaceStatus(ws, dir) {
|
|
|
17390
17925
|
CharacterAnimationSchema,
|
|
17391
17926
|
CharacterDirectionSchema,
|
|
17392
17927
|
CharacterModeSchema,
|
|
17928
|
+
CharacterOutfitSchema,
|
|
17929
|
+
CharacterPortraitSchema,
|
|
17930
|
+
CharacterPortraitSizeSchema,
|
|
17393
17931
|
CharacterProportionsSchema,
|
|
17394
17932
|
CharacterStateSchema,
|
|
17395
17933
|
ComfyUIProvider,
|