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/cli.js
CHANGED
|
@@ -679,6 +679,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
679
679
|
uiPieces: spec.uiPieces,
|
|
680
680
|
uiElements: spec.uiElements,
|
|
681
681
|
uiColorPalette: spec.uiColorPalette,
|
|
682
|
+
styleTraits: spec.styleTraits,
|
|
682
683
|
styleImages: styleImageHashes,
|
|
683
684
|
// A character family: the engine and rotations of a base, the parent's
|
|
684
685
|
// generated bytes for a state or animation (so a regenerated parent
|
|
@@ -705,7 +706,16 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
705
706
|
styleTraits: spec.character.styleTraits,
|
|
706
707
|
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
|
|
707
708
|
concept: spec.character.concept?.sha256,
|
|
708
|
-
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
709
|
+
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0,
|
|
710
|
+
// The parent's own bytes come from its recorded outputs; the plan
|
|
711
|
+
// compares those directly (like a mirror), so only the choice of
|
|
712
|
+
// parent, the reference image's bytes, and any extra guidance
|
|
713
|
+
// are identity here.
|
|
714
|
+
outfit: spec.character.outfit ? {
|
|
715
|
+
of: spec.character.parentAssetId,
|
|
716
|
+
reference: spec.character.outfit.reference.sha256,
|
|
717
|
+
additionalInstructions: spec.character.outfit.additionalInstructions
|
|
718
|
+
} : void 0
|
|
709
719
|
} : void 0,
|
|
710
720
|
// An objectPro family: same shape as character above, minus the
|
|
711
721
|
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
@@ -746,7 +756,8 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
746
756
|
fps: spec.revision.fps,
|
|
747
757
|
lastFrameSha256: spec.revision.lastFrameSha256,
|
|
748
758
|
direction: spec.revision.direction,
|
|
749
|
-
enhancePrompt: spec.revision.enhancePrompt
|
|
759
|
+
enhancePrompt: spec.revision.enhancePrompt,
|
|
760
|
+
engine: spec.revision.engine
|
|
750
761
|
} : void 0
|
|
751
762
|
})
|
|
752
763
|
);
|
|
@@ -764,7 +775,7 @@ import path2 from "path";
|
|
|
764
775
|
// src/types.ts
|
|
765
776
|
import { z } from "zod";
|
|
766
777
|
var MediaTypeSchema = z.enum(["image/png", "image/gif"]);
|
|
767
|
-
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement"]);
|
|
778
|
+
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement", "imageProFlash"]);
|
|
768
779
|
var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
769
780
|
var RevisionModeSchema = z.enum([
|
|
770
781
|
"image-to-image",
|
|
@@ -949,6 +960,27 @@ var CharacterStateSchema = z.object({
|
|
|
949
960
|
height: z.number().int().min(16).max(256)
|
|
950
961
|
}).strict().optional()
|
|
951
962
|
}).strict();
|
|
963
|
+
var CharacterPortraitSizeSchema = z.union([
|
|
964
|
+
z.literal(16),
|
|
965
|
+
z.literal(32),
|
|
966
|
+
z.literal(48),
|
|
967
|
+
z.literal(64),
|
|
968
|
+
z.literal(128),
|
|
969
|
+
z.literal(160)
|
|
970
|
+
]);
|
|
971
|
+
var CharacterPortraitSchema = z.object({
|
|
972
|
+
/** The base or state to portray, in the same style. */
|
|
973
|
+
of: z.string().min(1),
|
|
974
|
+
size: CharacterPortraitSizeSchema.default(64)
|
|
975
|
+
}).strict();
|
|
976
|
+
var CharacterOutfitSchema = z.object({
|
|
977
|
+
/** The loop (a character animation asset) to re-clothe, in the same style. */
|
|
978
|
+
of: z.string().min(1),
|
|
979
|
+
/** Manifest-relative image of the outfit to transfer. */
|
|
980
|
+
reference: z.string().min(1),
|
|
981
|
+
/** Extra guidance PixelLab passes through as written, e.g. view/direction hints. */
|
|
982
|
+
additionalInstructions: z.string().max(2e3).optional()
|
|
983
|
+
}).strict();
|
|
952
984
|
var CharacterAnimationSchema = z.object({
|
|
953
985
|
/** The character to animate: a base or a state in the same style. */
|
|
954
986
|
of: z.string().min(1),
|
|
@@ -1070,8 +1102,22 @@ var RevisionSchema = z.object({
|
|
|
1070
1102
|
/** `animate-pixminimax` only: facing direction, used only alongside `enhancePrompt` to hold the sprite's facing. */
|
|
1071
1103
|
direction: CharacterDirectionSchema.optional(),
|
|
1072
1104
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
1073
|
-
enhancePrompt: z.boolean().optional()
|
|
1105
|
+
enhancePrompt: z.boolean().optional(),
|
|
1106
|
+
/**
|
|
1107
|
+
* `image-to-image`/`inpaint` only: which provider engine draws the
|
|
1108
|
+
* edit. `pro-flash` selects PixelLab's Pro Flash edit and inpaint tier
|
|
1109
|
+
* (`/edit-image-pro-flash`, `/inpaint-image-pro-flash`) instead of the
|
|
1110
|
+
* default Pro endpoints; omit it for the default.
|
|
1111
|
+
*/
|
|
1112
|
+
engine: z.enum(["pro-flash"]).optional()
|
|
1074
1113
|
}).strict().superRefine((revision, context) => {
|
|
1114
|
+
if (revision.engine !== void 0 && revision.mode !== "image-to-image" && revision.mode !== "inpaint") {
|
|
1115
|
+
context.addIssue({
|
|
1116
|
+
code: z.ZodIssueCode.custom,
|
|
1117
|
+
message: "engine applies to image-to-image and inpaint revisions only",
|
|
1118
|
+
path: ["engine"]
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1075
1121
|
if (revision.mode === "inpaint" && !revision.mask) {
|
|
1076
1122
|
context.addIssue({
|
|
1077
1123
|
code: z.ZodIssueCode.custom,
|
|
@@ -1360,8 +1406,8 @@ var StyleObjectSchema = z.object({
|
|
|
1360
1406
|
/** `character` only. Body template: `mannequin` (default), a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`), or `custom` for `pro-flash`. */
|
|
1361
1407
|
template: z.string().min(1).optional(),
|
|
1362
1408
|
/**
|
|
1363
|
-
* `character`
|
|
1364
|
-
* the style image lends. Each defaults to true.
|
|
1409
|
+
* `character` `pro-flash` bases and `imageProFlash` styles, with a
|
|
1410
|
+
* style image: which traits the style image lends. Each defaults to true.
|
|
1365
1411
|
*/
|
|
1366
1412
|
styleTraits: z.object({
|
|
1367
1413
|
palette: z.boolean().optional(),
|
|
@@ -1570,6 +1616,10 @@ var AssetSchema = z.object({
|
|
|
1570
1616
|
pieces: z.array(UiPieceSchema).min(1).optional(),
|
|
1571
1617
|
/** `uiAsset` styles: named, auto-positioned UI element scaffolds. Combine with `pieces`; omit both for a default full-canvas panel. */
|
|
1572
1618
|
elements: z.array(UiElementSchema).min(1).optional(),
|
|
1619
|
+
/** `character` styles: this asset is a bust portrait of another character asset's south sprite. */
|
|
1620
|
+
portrait: CharacterPortraitSchema.optional(),
|
|
1621
|
+
/** `character` styles: this asset re-clothes an existing loop's frames with a reference outfit. */
|
|
1622
|
+
outfit: CharacterOutfitSchema.optional(),
|
|
1573
1623
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1574
1624
|
proportions: CharacterProportionsSchema.optional(),
|
|
1575
1625
|
/**
|
|
@@ -1646,7 +1696,7 @@ var AssetSchema = z.object({
|
|
|
1646
1696
|
path: ["revision"]
|
|
1647
1697
|
});
|
|
1648
1698
|
}
|
|
1649
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
1699
|
+
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);
|
|
1650
1700
|
if (shapes.length > 1) {
|
|
1651
1701
|
context.addIssue({
|
|
1652
1702
|
code: z.ZodIssueCode.custom,
|
|
@@ -1661,24 +1711,24 @@ var AssetSchema = z.object({
|
|
|
1661
1711
|
path: ["mirror"]
|
|
1662
1712
|
});
|
|
1663
1713
|
}
|
|
1664
|
-
if (asset.prompt === void 0 && !asset.mirror) {
|
|
1714
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit) {
|
|
1665
1715
|
context.addIssue({
|
|
1666
1716
|
code: z.ZodIssueCode.custom,
|
|
1667
1717
|
message: "Required",
|
|
1668
1718
|
path: ["prompt"]
|
|
1669
1719
|
});
|
|
1670
1720
|
}
|
|
1671
|
-
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
1721
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
1672
1722
|
context.addIssue({
|
|
1673
1723
|
code: z.ZodIssueCode.custom,
|
|
1674
|
-
message: "a reference sprite belongs on a base; a state, animation, or
|
|
1724
|
+
message: "a reference sprite belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
1675
1725
|
path: ["reference"]
|
|
1676
1726
|
});
|
|
1677
1727
|
}
|
|
1678
|
-
if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
|
|
1728
|
+
if (asset.concept && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
1679
1729
|
context.addIssue({
|
|
1680
1730
|
code: z.ZodIssueCode.custom,
|
|
1681
|
-
message: "a concept image belongs on a base; a state, animation, or
|
|
1731
|
+
message: "a concept image belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
1682
1732
|
path: ["concept"]
|
|
1683
1733
|
});
|
|
1684
1734
|
}
|
|
@@ -1790,7 +1840,8 @@ var LockEntrySchema = z.object({
|
|
|
1790
1840
|
fps: z.number().int().min(1).max(60).optional(),
|
|
1791
1841
|
lastFrameSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1792
1842
|
direction: CharacterDirectionSchema.optional(),
|
|
1793
|
-
enhancePrompt: z.boolean().optional()
|
|
1843
|
+
enhancePrompt: z.boolean().optional(),
|
|
1844
|
+
engine: z.enum(["pro-flash"]).optional()
|
|
1794
1845
|
}).strict().nullable().default(null),
|
|
1795
1846
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
1796
1847
|
mirror: z.object({
|
|
@@ -1803,6 +1854,17 @@ var LockEntrySchema = z.object({
|
|
|
1803
1854
|
leaderAssetId: z.string().min(1).optional(),
|
|
1804
1855
|
index: z.number().int().min(1).optional()
|
|
1805
1856
|
}).strict().nullable().default(null),
|
|
1857
|
+
/**
|
|
1858
|
+
* For an outfit: the loop it re-clothed, a hash over that loop's output
|
|
1859
|
+
* hashes when this was made, and the reference image's own hash. Tracked
|
|
1860
|
+
* the way `mirror` is, not through `specHash`: manifest resolution has no
|
|
1861
|
+
* lock to hash the parent's actual current frame bytes against.
|
|
1862
|
+
*/
|
|
1863
|
+
outfit: z.object({
|
|
1864
|
+
sourceAssetId: z.string().min(1),
|
|
1865
|
+
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
1866
|
+
referenceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
1867
|
+
}).strict().nullable().default(null),
|
|
1806
1868
|
/**
|
|
1807
1869
|
* Output hashes owned by the previous generation while its replacement is
|
|
1808
1870
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -4080,6 +4142,7 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
4080
4142
|
import path8 from "path";
|
|
4081
4143
|
async function inspectRevisionReadiness(spec, lock) {
|
|
4082
4144
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
4145
|
+
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
4083
4146
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
4084
4147
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
4085
4148
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -4106,6 +4169,20 @@ async function inspectMirror(spec, lock, seen) {
|
|
|
4106
4169
|
const dependency = await inspectParent(source, lock, seen);
|
|
4107
4170
|
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
4108
4171
|
}
|
|
4172
|
+
async function inspectOutfit(spec, lock, seen) {
|
|
4173
|
+
const character = spec.character;
|
|
4174
|
+
const outfit = character.outfit;
|
|
4175
|
+
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
4176
|
+
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
4177
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
4178
|
+
if (!existsSync4(outfit.reference.path)) {
|
|
4179
|
+
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
4180
|
+
}
|
|
4181
|
+
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
4182
|
+
return { ready: false, reason: "outfit reference changed after the manifest was resolved" };
|
|
4183
|
+
}
|
|
4184
|
+
return { ready: true, reason: `${label} is current` };
|
|
4185
|
+
}
|
|
4109
4186
|
async function inspectCharacterParent(spec, lock, seen) {
|
|
4110
4187
|
const character = spec.character;
|
|
4111
4188
|
const parentSpec = character.parentSpec;
|
|
@@ -4221,7 +4298,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
4221
4298
|
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
4222
4299
|
if (!inputs.ready) return inputs;
|
|
4223
4300
|
}
|
|
4224
|
-
if (spec.character?.
|
|
4301
|
+
if (spec.character?.kind === "outfit") {
|
|
4302
|
+
const inputs = await inspectOutfit(spec, lock, nextSeen);
|
|
4303
|
+
if (!inputs.ready) return inputs;
|
|
4304
|
+
} else if (spec.character?.parentSpec) {
|
|
4225
4305
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
4226
4306
|
if (!inputs.ready) return inputs;
|
|
4227
4307
|
}
|
|
@@ -4344,6 +4424,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
4344
4424
|
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !existsSync5(currentEntryOutputPath(entry, spec, index)))) {
|
|
4345
4425
|
state = "stale";
|
|
4346
4426
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
4427
|
+
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
4428
|
+
state = "stale";
|
|
4429
|
+
reason = `outfit source ${spec.character.parentAssetId} changed; re-clothe the loop again`;
|
|
4347
4430
|
} else if (entry.status === "download-failed") {
|
|
4348
4431
|
state = "recoverable";
|
|
4349
4432
|
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
@@ -4970,6 +5053,11 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4970
5053
|
throw new Error("ComfyUI providerOptions.frames requires generator: frames");
|
|
4971
5054
|
}
|
|
4972
5055
|
if (spec.revision) {
|
|
5056
|
+
if (spec.revision.engine) {
|
|
5057
|
+
throw new Error(
|
|
5058
|
+
`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.`
|
|
5059
|
+
);
|
|
5060
|
+
}
|
|
4973
5061
|
if (spec.revision.sourceMembers) {
|
|
4974
5062
|
throw new Error(
|
|
4975
5063
|
`ComfyUI revisions read one source image; ${spec.revision.sourceAssetId} is a ${spec.revision.sourceMembers.length}-member set (directions or frames)`
|
|
@@ -5710,6 +5798,14 @@ var CorrectPixelartResponseSchema = z2.object({
|
|
|
5710
5798
|
images: z2.array(z2.object({ base64: z2.string().min(1) }).passthrough()).min(1),
|
|
5711
5799
|
usage: z2.unknown().optional()
|
|
5712
5800
|
}).passthrough();
|
|
5801
|
+
var ProFlashImageSubmitSchema = z2.object({
|
|
5802
|
+
background_job_id: z2.string().min(1),
|
|
5803
|
+
image_id: z2.string().min(1),
|
|
5804
|
+
source_image_id: z2.string().min(1),
|
|
5805
|
+
status: z2.string().default("processing"),
|
|
5806
|
+
estimated_generations: z2.number().nullable().optional(),
|
|
5807
|
+
usage: z2.unknown().optional()
|
|
5808
|
+
}).passthrough();
|
|
5713
5809
|
var UnzoomResponseSchema = z2.object({
|
|
5714
5810
|
image: z2.object({ base64: z2.string().min(1) }).passthrough(),
|
|
5715
5811
|
original_size: z2.object({ width: z2.number().int(), height: z2.number().int() }).passthrough(),
|
|
@@ -5760,6 +5856,28 @@ var AnimateObjectSubmitSchema = z2.object({
|
|
|
5760
5856
|
submissions: z2.array(DirectionSubmissionSchema),
|
|
5761
5857
|
usage: UsageSchema
|
|
5762
5858
|
}).passthrough();
|
|
5859
|
+
var PortraitSubmitSchema = z2.object({
|
|
5860
|
+
background_job_id: z2.string().min(1),
|
|
5861
|
+
status: z2.string().default("processing"),
|
|
5862
|
+
usage: UsageSchema
|
|
5863
|
+
}).passthrough();
|
|
5864
|
+
var PortraitJobSchema = z2.object({
|
|
5865
|
+
status: z2.string(),
|
|
5866
|
+
download_url: z2.string().nullable().optional(),
|
|
5867
|
+
width: z2.number().int().nullable().optional(),
|
|
5868
|
+
height: z2.number().int().nullable().optional()
|
|
5869
|
+
}).passthrough();
|
|
5870
|
+
var SetPortraitSchema = z2.object({
|
|
5871
|
+
character_id: z2.string().min(1),
|
|
5872
|
+
size: z2.number().int().positive(),
|
|
5873
|
+
url: z2.string().min(1),
|
|
5874
|
+
usage: UsageSchema
|
|
5875
|
+
}).passthrough();
|
|
5876
|
+
var TransferOutfitSubmitSchema = z2.object({
|
|
5877
|
+
background_job_id: z2.string().min(1),
|
|
5878
|
+
status: z2.string().default("processing"),
|
|
5879
|
+
usage: UsageSchema
|
|
5880
|
+
}).passthrough();
|
|
5763
5881
|
var CharacterAnimationDirectionSchema = z2.object({
|
|
5764
5882
|
direction: z2.string(),
|
|
5765
5883
|
frame_count: z2.number().int().min(0),
|
|
@@ -6128,7 +6246,7 @@ var PixelLabClient = class {
|
|
|
6128
6246
|
const body = {
|
|
6129
6247
|
description: args.description,
|
|
6130
6248
|
n_directions: args.directions,
|
|
6131
|
-
...args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
6249
|
+
...args.sourceImageId ? { source_image_id: args.sourceImageId } : args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
6132
6250
|
...args.view ? { view: args.view } : {},
|
|
6133
6251
|
...args.seed != null ? { seed: args.seed } : {},
|
|
6134
6252
|
...args.styleReference && args.styleReferenceSize ? {
|
|
@@ -6275,8 +6393,9 @@ var PixelLabClient = class {
|
|
|
6275
6393
|
description: args.description,
|
|
6276
6394
|
template_id: args.template,
|
|
6277
6395
|
n_directions: 8,
|
|
6278
|
-
// The author's sprite sets the canvas; a size only applies to text
|
|
6279
|
-
|
|
6396
|
+
// The author's sprite sets the canvas; a size only applies to text
|
|
6397
|
+
// creation. An owned copy of the same sprite is referenced by id.
|
|
6398
|
+
...args.sourceImageId ? { source_image_id: args.sourceImageId } : south ? { first_frame: encode(south) } : { image_size: imageSize },
|
|
6280
6399
|
...args.view ? { view: args.view } : {},
|
|
6281
6400
|
...args.seed != null ? { seed: args.seed } : {},
|
|
6282
6401
|
...args.styleReference && args.styleReferenceSize ? {
|
|
@@ -6375,6 +6494,69 @@ var PixelLabClient = class {
|
|
|
6375
6494
|
});
|
|
6376
6495
|
return validateResponse(AnimateSubmitSchema, raw, "animate-character");
|
|
6377
6496
|
}
|
|
6497
|
+
/**
|
|
6498
|
+
* A full-body sprite in, a bust portrait out (or the reverse, unused
|
|
6499
|
+
* today). Job-based like a state or `1dir`, not the couple-generations
|
|
6500
|
+
* "conversion" the name suggests: the smallest `resultSize` (16px) billed
|
|
6501
|
+
* 20 generations live (docs/ENDPOINTS.md, "Characters, measured").
|
|
6502
|
+
*/
|
|
6503
|
+
async createPortraitCharacterPro(args) {
|
|
6504
|
+
const raw = await this.request("/portrait-character-pro", {
|
|
6505
|
+
method: "POST",
|
|
6506
|
+
body: JSON.stringify({
|
|
6507
|
+
direction: args.direction,
|
|
6508
|
+
image: { type: "base64", base64: args.image.base64, format: args.image.format },
|
|
6509
|
+
...args.view ? { view: args.view } : {},
|
|
6510
|
+
...args.resultSize != null ? { result_size: args.resultSize } : {},
|
|
6511
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
6512
|
+
})
|
|
6513
|
+
});
|
|
6514
|
+
return validateResponse(PortraitSubmitSchema, raw, "portrait-character-pro");
|
|
6515
|
+
}
|
|
6516
|
+
/** Throws PixelLabError(423) while the job is still drawing, like tiles and map objects. */
|
|
6517
|
+
async getPortraitCharacterJob(jobId) {
|
|
6518
|
+
const raw = await this.request(`/portrait-character-pro/${encodeURIComponent(jobId)}`);
|
|
6519
|
+
return validateResponse(PortraitJobSchema, raw, "portrait-character-pro/{id}");
|
|
6520
|
+
}
|
|
6521
|
+
/**
|
|
6522
|
+
* Attaches a portrait image to a character record. Synchronous and free
|
|
6523
|
+
* (0 generations, live); PixelLab does not link this to
|
|
6524
|
+
* `createPortraitCharacterPro` automatically, and `GET /characters/{id}`
|
|
6525
|
+
* never grows a portrait field, so this response's `url` is the only place
|
|
6526
|
+
* it appears.
|
|
6527
|
+
*/
|
|
6528
|
+
async setCharacterPortrait(characterId, image) {
|
|
6529
|
+
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}/portrait`, {
|
|
6530
|
+
method: "POST",
|
|
6531
|
+
body: JSON.stringify({ image: { type: "base64", base64: image.base64, format: image.format } })
|
|
6532
|
+
});
|
|
6533
|
+
return validateResponse(SetPortraitSchema, raw, "characters/{id}/portrait");
|
|
6534
|
+
}
|
|
6535
|
+
/**
|
|
6536
|
+
* Applies a reference outfit to 2–16 existing frames. Job-based like
|
|
6537
|
+
* everything else here, but the completed job returns the result frames
|
|
6538
|
+
* inline as base64 (`last_response.quantized_images`), not as storage
|
|
6539
|
+
* URLs — the one PixelLab character tool that works this way. A live
|
|
6540
|
+
* 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md).
|
|
6541
|
+
*/
|
|
6542
|
+
async transferOutfitV2(args) {
|
|
6543
|
+
const image = (img) => ({
|
|
6544
|
+
image: { type: "base64", base64: img.base64, format: img.format },
|
|
6545
|
+
size: { width: img.width, height: img.height }
|
|
6546
|
+
});
|
|
6547
|
+
const raw = await this.request("/transfer-outfit-v2", {
|
|
6548
|
+
method: "POST",
|
|
6549
|
+
body: JSON.stringify({
|
|
6550
|
+
reference_image: image(args.referenceImage),
|
|
6551
|
+
frames: args.frames.map(image),
|
|
6552
|
+
image_size: args.imageSize,
|
|
6553
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
6554
|
+
...args.noBackground !== void 0 ? { no_background: args.noBackground } : {},
|
|
6555
|
+
...args.additionalInstructions ? { additional_instructions: args.additionalInstructions } : {}
|
|
6556
|
+
})
|
|
6557
|
+
});
|
|
6558
|
+
return validateResponse(TransferOutfitSubmitSchema, raw, "transfer-outfit-v2");
|
|
6559
|
+
}
|
|
6378
6560
|
async getCharacter(characterId) {
|
|
6379
6561
|
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}`);
|
|
6380
6562
|
return validateResponse(CharacterSchema, raw, "characters/{id}");
|
|
@@ -6485,6 +6667,66 @@ var PixelLabClient = class {
|
|
|
6485
6667
|
"generate-image-v2"
|
|
6486
6668
|
);
|
|
6487
6669
|
}
|
|
6670
|
+
/**
|
|
6671
|
+
* `/create-image-pro-flash`: one native pixel-art image from a
|
|
6672
|
+
* description, optionally styled after a style image with per-trait
|
|
6673
|
+
* toggles, on the same Pro Flash model `character`/`objectPro` use for
|
|
6674
|
+
* their `pro-flash` engine. Sizes run 16 to 256 per side in multiples of 4.
|
|
6675
|
+
* A plain background job; `pollRevision`'s generic reader takes the
|
|
6676
|
+
* finished image. The response's `source_image_id` is what a Pro Flash
|
|
6677
|
+
* character or object can reuse instead of an upload.
|
|
6678
|
+
*/
|
|
6679
|
+
async createImageProFlash(args) {
|
|
6680
|
+
const body = {
|
|
6681
|
+
description: args.description,
|
|
6682
|
+
image_size: { width: args.width, height: args.height }
|
|
6683
|
+
};
|
|
6684
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
6685
|
+
if (args.seed != null) body.seed = args.seed;
|
|
6686
|
+
if (args.styleImage) {
|
|
6687
|
+
body.style_image = { image: args.styleImage.image, size: { width: args.styleImage.width, height: args.styleImage.height } };
|
|
6688
|
+
const traits = args.styleTraits;
|
|
6689
|
+
if (traits) {
|
|
6690
|
+
body.style_options = {
|
|
6691
|
+
...traits.palette !== void 0 ? { color_palette: traits.palette } : {},
|
|
6692
|
+
...traits.outline !== void 0 ? { outline: traits.outline } : {},
|
|
6693
|
+
...traits.detail !== void 0 ? { detail: traits.detail } : {},
|
|
6694
|
+
...traits.shading !== void 0 ? { shading: traits.shading } : {}
|
|
6695
|
+
};
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
return validateResponse(
|
|
6699
|
+
ProFlashImageSubmitSchema,
|
|
6700
|
+
await this.request("/create-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
6701
|
+
"create-image-pro-flash"
|
|
6702
|
+
);
|
|
6703
|
+
}
|
|
6704
|
+
/** `/edit-image-pro-flash`, text method: edit one image at its own size. The canvas never grows or resizes. */
|
|
6705
|
+
async editImageProFlash(args) {
|
|
6706
|
+
const body = { image: args.image, method: "text", description: args.description };
|
|
6707
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
6708
|
+
if (args.seed != null) body.seed = args.seed;
|
|
6709
|
+
return validateResponse(
|
|
6710
|
+
ProFlashImageSubmitSchema,
|
|
6711
|
+
await this.request("/edit-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
6712
|
+
"edit-image-pro-flash"
|
|
6713
|
+
);
|
|
6714
|
+
}
|
|
6715
|
+
/**
|
|
6716
|
+
* `/inpaint-image-pro-flash`: replace only the white pixels of a same-size
|
|
6717
|
+
* black/white mask, keeping everything else exactly. `Modify current
|
|
6718
|
+
* layer` (the endpoint's default) returns the full composite.
|
|
6719
|
+
*/
|
|
6720
|
+
async inpaintImageProFlash(args) {
|
|
6721
|
+
const body = { image: args.image, mask_image: args.maskImage, description: args.description };
|
|
6722
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
6723
|
+
if (args.seed != null) body.seed = args.seed;
|
|
6724
|
+
return validateResponse(
|
|
6725
|
+
ProFlashImageSubmitSchema,
|
|
6726
|
+
await this.request("/inpaint-image-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
6727
|
+
"inpaint-image-pro-flash"
|
|
6728
|
+
);
|
|
6729
|
+
}
|
|
6488
6730
|
/**
|
|
6489
6731
|
* `/generate-ui-v2` ("Generate UI (Pro)"): one UI element (a button, a
|
|
6490
6732
|
* health bar, an inventory slot, a dialogue box) from a description,
|
|
@@ -6794,7 +7036,10 @@ function characterCost(spec) {
|
|
|
6794
7036
|
if (animation.mode === "v3") return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
6795
7037
|
return generationCost(spec.width, spec.height, "1dir");
|
|
6796
7038
|
}
|
|
6797
|
-
if (character.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
7039
|
+
if (character.kind === "state" || character.kind === "portrait") return generationCost(spec.width, spec.height, "1dir");
|
|
7040
|
+
if (character.kind === "outfit") {
|
|
7041
|
+
return 20;
|
|
7042
|
+
}
|
|
6798
7043
|
if (character.mode === "standard") return 1;
|
|
6799
7044
|
if (character.mode === "v3") return 1 + Math.max(1, Math.ceil(px * 8 / 65536));
|
|
6800
7045
|
if (character.mode === "pro-flash") {
|
|
@@ -6807,8 +7052,11 @@ function proFlashCharacterCost(width, height, fromReference) {
|
|
|
6807
7052
|
const side = Math.max(32, Math.ceil(Math.max(width, height) / 4) * 4);
|
|
6808
7053
|
const rotations = Math.max(1, Math.ceil(side * side * 8 / 65536));
|
|
6809
7054
|
if (fromReference) return rotations;
|
|
6810
|
-
|
|
6811
|
-
|
|
7055
|
+
return proFlashImageCost(side, side) + rotations;
|
|
7056
|
+
}
|
|
7057
|
+
function proFlashImageCost(width, height) {
|
|
7058
|
+
const side = Math.max(width, height);
|
|
7059
|
+
return side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
6812
7060
|
}
|
|
6813
7061
|
function characterAnimationName(spec) {
|
|
6814
7062
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
@@ -6884,7 +7132,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6884
7132
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
6885
7133
|
}
|
|
6886
7134
|
supports(generator) {
|
|
6887
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement";
|
|
7135
|
+
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";
|
|
6888
7136
|
}
|
|
6889
7137
|
/**
|
|
6890
7138
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -6911,15 +7159,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6911
7159
|
return { spacingMs: 2500, maxInFlight: 8 };
|
|
6912
7160
|
}
|
|
6913
7161
|
/**
|
|
6914
|
-
* Where synchronous pixflux results
|
|
7162
|
+
* Where synchronous pixflux results (or an outfit transfer's inline
|
|
7163
|
+
* frames) are parked between `submit`/poll and `fetch`.
|
|
6915
7164
|
*
|
|
6916
|
-
* pixflux returns the PNG inline rather than a job id,
|
|
6917
|
-
*
|
|
6918
|
-
*
|
|
6919
|
-
*
|
|
7165
|
+
* pixflux returns the PNG inline rather than a job id, and an outfit
|
|
7166
|
+
* transfer's completed job returns its frames inline too, but the
|
|
7167
|
+
* pipeline is built around submit → poll → fetch running as separate
|
|
7168
|
+
* commands. Writing the bytes to a known path keeps that model intact:
|
|
7169
|
+
* the id is the filename, polling is an existence check, and downloading
|
|
7170
|
+
* is a file read.
|
|
6920
7171
|
*/
|
|
6921
|
-
static cacheDir() {
|
|
6922
|
-
const dir = path12.join(os.tmpdir(),
|
|
7172
|
+
static cacheDir(namespace = "pixflux") {
|
|
7173
|
+
const dir = path12.join(os.tmpdir(), `pixelkiln-${namespace}`);
|
|
6923
7174
|
mkdirSync(dir, { recursive: true });
|
|
6924
7175
|
return dir;
|
|
6925
7176
|
}
|
|
@@ -6943,6 +7194,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6943
7194
|
const base = Math.max(1, Math.ceil(width * height * frames / 65536));
|
|
6944
7195
|
return { unit: "generations", amount: spec.revision.enhancePrompt ? base + 0.05 : base, candidates: 1 };
|
|
6945
7196
|
}
|
|
7197
|
+
if (spec.revision?.engine === "pro-flash") {
|
|
7198
|
+
return {
|
|
7199
|
+
unit: "generations",
|
|
7200
|
+
amount: proFlashImageCost(spec.revision.sourceWidth ?? spec.width, spec.revision.sourceHeight ?? spec.height),
|
|
7201
|
+
candidates: 1
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
6946
7204
|
if (spec.revision) {
|
|
6947
7205
|
const width = spec.revision.sourceWidth ?? spec.width;
|
|
6948
7206
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
@@ -6957,6 +7215,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6957
7215
|
if (spec.generator === "objectPro") {
|
|
6958
7216
|
return { unit: "generations", amount: objectProCost(spec), candidates: 1 };
|
|
6959
7217
|
}
|
|
7218
|
+
if (spec.generator === "imageProFlash") {
|
|
7219
|
+
return { unit: "generations", amount: proFlashImageCost(spec.width, spec.height), candidates: 1 };
|
|
7220
|
+
}
|
|
6960
7221
|
return {
|
|
6961
7222
|
unit: "generations",
|
|
6962
7223
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
@@ -6970,7 +7231,17 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6970
7231
|
`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`
|
|
6971
7232
|
);
|
|
6972
7233
|
}
|
|
6973
|
-
if (spec.revision?.
|
|
7234
|
+
if (spec.revision?.engine === "pro-flash") {
|
|
7235
|
+
const { sourceWidth: width, sourceHeight: height, mode: mode2 } = spec.revision;
|
|
7236
|
+
if (width != null && height != null && (width < 32 || height < 32 || width > 256 || height > 256 || width % 4 || height % 4)) {
|
|
7237
|
+
throw new Error(
|
|
7238
|
+
`PixelLab pro-flash ${mode2} source is ${width}x${height}; the API takes 32 to 256 pixels per side, in multiples of 4`
|
|
7239
|
+
);
|
|
7240
|
+
}
|
|
7241
|
+
if (spec.revision.strength != null) {
|
|
7242
|
+
throw new Error(`PixelLab pro-flash ${mode2} takes no strength; the edit applies in full`);
|
|
7243
|
+
}
|
|
7244
|
+
} else if (spec.revision?.mode === "inpaint") {
|
|
6974
7245
|
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
6975
7246
|
if (width != null && height != null && (width < 32 || height < 32 || width > 512 || height > 512)) {
|
|
6976
7247
|
throw new Error(`PixelLab inpaint source is ${width}x${height}; the API takes 32 to 512 pixels per side`);
|
|
@@ -7051,6 +7322,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7051
7322
|
`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)`
|
|
7052
7323
|
);
|
|
7053
7324
|
}
|
|
7325
|
+
if (spec.generator === "imageProFlash") {
|
|
7326
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > 256 || spec.height > 256 || spec.width % 4 || spec.height % 4) {
|
|
7327
|
+
throw new Error(
|
|
7328
|
+
`PixelLab imageProFlash is ${spec.width}x${spec.height}; the API takes 16 to 256 pixels per side, in multiples of 4`
|
|
7329
|
+
);
|
|
7330
|
+
}
|
|
7331
|
+
if (styleImages.length > 1) {
|
|
7332
|
+
throw new Error("PixelLab imageProFlash takes one style image; list at most one styleImages entry");
|
|
7333
|
+
}
|
|
7334
|
+
if (spec.styleTraits && !styleImages.length) {
|
|
7335
|
+
throw new Error("PixelLab imageProFlash: styleTraits choose what a style image lends; add a styleImages entry or drop them");
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7054
7338
|
if (spec.generator === "uiElement" && (spec.width < 16 || spec.height < 16 || spec.width > 792 || spec.height > 688)) {
|
|
7055
7339
|
throw new Error(
|
|
7056
7340
|
`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)`
|
|
@@ -7179,6 +7463,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7179
7463
|
const character = spec.character;
|
|
7180
7464
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
7181
7465
|
const label = `${spec.styleId}/${spec.assetId}`;
|
|
7466
|
+
if (character.kind === "outfit") {
|
|
7467
|
+
const reference = character.outfit.reference;
|
|
7468
|
+
if (reference.width < 32 || reference.width > 256 || reference.height < 32 || reference.height > 256) {
|
|
7469
|
+
throw new Error(`${label}: outfit reference is ${reference.width}x${reference.height}; PixelLab transfer-outfit-v2 takes 32 to 256px per side`);
|
|
7470
|
+
}
|
|
7471
|
+
}
|
|
7182
7472
|
if (character.kind === "base" && character.proportions !== void 0) {
|
|
7183
7473
|
if (character.mode !== "standard") {
|
|
7184
7474
|
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
@@ -7190,7 +7480,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7190
7480
|
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
7191
7481
|
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
7192
7482
|
}
|
|
7193
|
-
if (character.animation) {
|
|
7483
|
+
if (character.animation && character.kind !== "outfit") {
|
|
7194
7484
|
const { startFrame, endFrame } = character.animation;
|
|
7195
7485
|
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
7196
7486
|
if (image && (image.width > 256 || image.height > 256)) {
|
|
@@ -7271,9 +7561,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7271
7561
|
if (character.styleTraits && character.mode !== "pro-flash") {
|
|
7272
7562
|
throw new Error(`${label}: styleTraits choose what a pro-flash style image lends; the ${character.mode} engine does not take them`);
|
|
7273
7563
|
}
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7564
|
+
if (character.kind !== "portrait") {
|
|
7565
|
+
const maxSize = character.mode === "v3" || character.mode === "pro-flash" ? 256 : 128;
|
|
7566
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
7567
|
+
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
7568
|
+
}
|
|
7277
7569
|
}
|
|
7278
7570
|
if (character.mode === "pro-flash" && character.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
7279
7571
|
throw new Error(`PixelLab pro-flash characters are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
@@ -7282,9 +7574,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7282
7574
|
if (!templates.includes(character.template)) {
|
|
7283
7575
|
throw new Error(`PixelLab ${character.mode} character template must be one of: ${templates.join(", ")}`);
|
|
7284
7576
|
}
|
|
7285
|
-
const views = character.mode === "standard" ? ["low top-down", "high top-down", "side", "perspective", "oblique"] : ["low top-down", "high top-down", "side"];
|
|
7577
|
+
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"];
|
|
7286
7578
|
if (!views.includes(spec.view)) {
|
|
7287
|
-
throw new Error(
|
|
7579
|
+
throw new Error(
|
|
7580
|
+
character.kind === "portrait" ? `PixelLab portrait view must be one of: ${views.join(", ")}` : `PixelLab ${character.mode} character view must be one of: ${views.join(", ")}`
|
|
7581
|
+
);
|
|
7288
7582
|
}
|
|
7289
7583
|
if (spec.view === "oblique" && character.directions !== 4) {
|
|
7290
7584
|
throw new Error("PixelLab oblique characters are 4-direction only");
|
|
@@ -7292,7 +7586,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7292
7586
|
if (character.kind !== "base" && !character.parentAssetId) {
|
|
7293
7587
|
throw new Error(`${spec.styleId}/${spec.assetId} is a ${character.kind} with no parent`);
|
|
7294
7588
|
}
|
|
7295
|
-
if (character.animation) {
|
|
7589
|
+
if (character.animation && character.kind !== "outfit") {
|
|
7296
7590
|
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
7297
7591
|
if (!allowed.includes(character.animation.direction)) {
|
|
7298
7592
|
throw new Error(
|
|
@@ -7394,10 +7688,48 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7394
7688
|
concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
|
|
7395
7689
|
styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0,
|
|
7396
7690
|
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
7397
|
-
styleTraits: character.styleTraits
|
|
7691
|
+
styleTraits: character.styleTraits,
|
|
7692
|
+
sourceImageId: character.mode === "pro-flash" ? context?.referenceSourceImageId : void 0
|
|
7398
7693
|
});
|
|
7399
7694
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
7400
7695
|
}
|
|
7696
|
+
if (character.kind === "outfit") {
|
|
7697
|
+
const parentOutputs = context?.parentOutputs ?? [];
|
|
7698
|
+
if (parentOutputs.length < 2) {
|
|
7699
|
+
throw new Error(
|
|
7700
|
+
`${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`
|
|
7701
|
+
);
|
|
7702
|
+
}
|
|
7703
|
+
const frames = parentOutputs.slice(0, 16).map((output) => {
|
|
7704
|
+
const bytes = readFileSync2(output.path);
|
|
7705
|
+
const dims = imageMetadata(bytes);
|
|
7706
|
+
if (!dims) throw new Error(`${spec.styleId}/${spec.assetId}: outfit source frame is not a readable PNG: ${output.path}`);
|
|
7707
|
+
return { base64: bytes.toString("base64"), format: "png", width: dims.width, height: dims.height };
|
|
7708
|
+
});
|
|
7709
|
+
const outfit = character.outfit;
|
|
7710
|
+
const referenceBytes = readFileSync2(outfit.reference.path);
|
|
7711
|
+
if (sha256(referenceBytes) !== outfit.reference.sha256) {
|
|
7712
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: outfit reference changed after the manifest was resolved: ${outfit.reference.path}`);
|
|
7713
|
+
}
|
|
7714
|
+
const res2 = await this.client.transferOutfitV2({
|
|
7715
|
+
referenceImage: { base64: referenceBytes.toString("base64"), format: "png", width: outfit.reference.width, height: outfit.reference.height },
|
|
7716
|
+
frames,
|
|
7717
|
+
imageSize: { width: spec.width, height: spec.height },
|
|
7718
|
+
seed: spec.seed,
|
|
7719
|
+
noBackground: spec.noBackground,
|
|
7720
|
+
additionalInstructions: outfit.additionalInstructions
|
|
7721
|
+
});
|
|
7722
|
+
return {
|
|
7723
|
+
jobId: res2.background_job_id,
|
|
7724
|
+
metadata: {
|
|
7725
|
+
character: {
|
|
7726
|
+
kind: "outfit",
|
|
7727
|
+
backgroundJobId: res2.background_job_id,
|
|
7728
|
+
frameRoles: parentOutputs.slice(0, 16).map((output) => output.role ?? null)
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
};
|
|
7732
|
+
}
|
|
7401
7733
|
const parentId = context?.parentObjectId;
|
|
7402
7734
|
if (!parentId) {
|
|
7403
7735
|
throw new Error(
|
|
@@ -7416,6 +7748,24 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7416
7748
|
});
|
|
7417
7749
|
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 } } };
|
|
7418
7750
|
}
|
|
7751
|
+
if (character.kind === "portrait") {
|
|
7752
|
+
const parentFile = character.parentFile;
|
|
7753
|
+
const bytes = readFileSync2(parentFile);
|
|
7754
|
+
if (character.parentSha256 && sha256(bytes) !== character.parentSha256) {
|
|
7755
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: portrait source changed after the manifest was resolved: ${parentFile}`);
|
|
7756
|
+
}
|
|
7757
|
+
const res2 = await this.client.createPortraitCharacterPro({
|
|
7758
|
+
direction: "character_to_portrait",
|
|
7759
|
+
image: { base64: bytes.toString("base64"), format: "png" },
|
|
7760
|
+
view: spec.view,
|
|
7761
|
+
resultSize: character.portrait.size,
|
|
7762
|
+
seed: spec.seed
|
|
7763
|
+
});
|
|
7764
|
+
return {
|
|
7765
|
+
jobId: res2.background_job_id,
|
|
7766
|
+
metadata: { character: { kind: "portrait", parentCharacterId: parentId, backgroundJobId: res2.background_job_id } }
|
|
7767
|
+
};
|
|
7768
|
+
}
|
|
7419
7769
|
const animation = character.animation;
|
|
7420
7770
|
const name = characterAnimationName(spec);
|
|
7421
7771
|
const replaced = context?.replacedMetadata?.character ?? {};
|
|
@@ -7474,7 +7824,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7474
7824
|
reference: object.reference ? readPose(spec, "reference sprite", object.reference) : void 0,
|
|
7475
7825
|
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
7476
7826
|
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
7477
|
-
styleTraits: object.styleTraits
|
|
7827
|
+
styleTraits: object.styleTraits,
|
|
7828
|
+
sourceImageId: context?.referenceSourceImageId
|
|
7478
7829
|
});
|
|
7479
7830
|
return { jobId: res2.object_id, metadata: { objectPro: { kind: "base", objectId: res2.object_id, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
7480
7831
|
}
|
|
@@ -7529,6 +7880,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7529
7880
|
async pollCharacter(jobId, context) {
|
|
7530
7881
|
const animation = decodeAnimationJob(jobId);
|
|
7531
7882
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
7883
|
+
const kind = context?.metadata?.character?.kind ?? context?.spec?.character?.kind;
|
|
7884
|
+
if (kind === "portrait") return this.pollCharacterPortrait(jobId, context);
|
|
7885
|
+
if (kind === "outfit") return this.pollCharacterOutfit(jobId, context);
|
|
7532
7886
|
const character = await this.client.getCharacter(jobId);
|
|
7533
7887
|
if (character.status === "failed") return { status: "failed", error: "character generation failed upstream" };
|
|
7534
7888
|
if (character.status !== "completed" || !character.rotation_urls) return { status: "processing" };
|
|
@@ -7554,6 +7908,66 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7554
7908
|
billed: await this.billedForJob(backgroundJobId)
|
|
7555
7909
|
};
|
|
7556
7910
|
}
|
|
7911
|
+
/**
|
|
7912
|
+
* `jobId` here is `portrait-character-pro`'s own background job id, not a
|
|
7913
|
+
* character id: the endpoint converts an arbitrary image and never touches
|
|
7914
|
+
* the character record. Once the portrait itself is drawn, this also
|
|
7915
|
+
* attaches it to the parent's character record with `SetPortrait` — a
|
|
7916
|
+
* separate, free call PixelLab does not make automatically (docs/PIXELLAB.md).
|
|
7917
|
+
*/
|
|
7918
|
+
async pollCharacterPortrait(jobId, context) {
|
|
7919
|
+
const meta = context?.metadata?.character;
|
|
7920
|
+
try {
|
|
7921
|
+
const job = await this.client.getPortraitCharacterJob(jobId);
|
|
7922
|
+
if (job.status === "failed") return { status: "failed", error: "portrait generation failed upstream" };
|
|
7923
|
+
if (job.status !== "completed" || !job.download_url) return { status: "processing" };
|
|
7924
|
+
if (meta?.parentCharacterId) {
|
|
7925
|
+
const bytes = await this.client.download(job.download_url);
|
|
7926
|
+
await this.client.setCharacterPortrait(meta.parentCharacterId, { base64: bytes.toString("base64"), format: "png" });
|
|
7927
|
+
}
|
|
7928
|
+
return {
|
|
7929
|
+
status: "ready",
|
|
7930
|
+
objectId: jobId,
|
|
7931
|
+
sourceUrl: job.download_url,
|
|
7932
|
+
sources: [{ url: job.download_url }],
|
|
7933
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
7934
|
+
};
|
|
7935
|
+
} catch (err) {
|
|
7936
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
7937
|
+
throw err;
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
/**
|
|
7941
|
+
* No dedicated status endpoint exists for `transfer-outfit-v2`, unlike
|
|
7942
|
+
* portraits and tiles: the background job is the only place to poll. A
|
|
7943
|
+
* completed job returns its frames inline as base64
|
|
7944
|
+
* (`last_response.quantized_images`), not as storage URLs, so they are
|
|
7945
|
+
* cached locally the way a synchronous pixflux result is, and matched back
|
|
7946
|
+
* to the source frames' own roles recorded at submit time.
|
|
7947
|
+
*/
|
|
7948
|
+
async pollCharacterOutfit(jobId, context) {
|
|
7949
|
+
const meta = context?.metadata?.character;
|
|
7950
|
+
const job = await this.client.getBackgroundJob(jobId);
|
|
7951
|
+
if (job.status === "failed") return { status: "failed", error: "outfit transfer failed upstream" };
|
|
7952
|
+
if (job.status !== "completed") return { status: "processing" };
|
|
7953
|
+
const images = job.last_response?.quantized_images;
|
|
7954
|
+
if (!images?.length) return { status: "failed", error: "outfit transfer completed with no result frames" };
|
|
7955
|
+
const dir = _PixelLabProvider.cacheDir("outfit");
|
|
7956
|
+
const sources = images.map((image, index) => {
|
|
7957
|
+
if (!image.base64) throw new Error(`outfit transfer frame ${index} has no image data`);
|
|
7958
|
+
const role = meta?.frameRoles?.[index] ?? void 0;
|
|
7959
|
+
const file = path12.join(dir, `${jobId}-${index}.png`);
|
|
7960
|
+
writeFileSync(file, Buffer.from(image.base64, "base64"));
|
|
7961
|
+
return { url: `file://${file}`, ...role ? { role } : {} };
|
|
7962
|
+
});
|
|
7963
|
+
return {
|
|
7964
|
+
status: "ready",
|
|
7965
|
+
objectId: jobId,
|
|
7966
|
+
sourceUrl: sources[0].url,
|
|
7967
|
+
sources,
|
|
7968
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
7969
|
+
};
|
|
7970
|
+
}
|
|
7557
7971
|
/**
|
|
7558
7972
|
* The background job is the authoritative record of one direction: it
|
|
7559
7973
|
* completes with the frame URLs, the animation id, and the direction, and
|
|
@@ -7710,6 +8124,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7710
8124
|
});
|
|
7711
8125
|
return { jobId: res2.ui_asset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
7712
8126
|
}
|
|
8127
|
+
if (spec.generator === "imageProFlash") {
|
|
8128
|
+
const style = styleImages[0];
|
|
8129
|
+
const res2 = await this.client.createImageProFlash({
|
|
8130
|
+
description: spec.prompt,
|
|
8131
|
+
width: spec.width,
|
|
8132
|
+
height: spec.height,
|
|
8133
|
+
noBackground: spec.noBackground,
|
|
8134
|
+
seed: spec.seed,
|
|
8135
|
+
styleImage: style ? { image: { base64: style.base64, format: style.format }, width: style.width, height: style.height } : void 0,
|
|
8136
|
+
styleTraits: spec.styleTraits
|
|
8137
|
+
});
|
|
8138
|
+
return { jobId: res2.background_job_id, metadata: { imageProFlash: proFlashRecord(res2) } };
|
|
8139
|
+
}
|
|
7713
8140
|
if (spec.generator === "uiElement") {
|
|
7714
8141
|
const concept = styleImages[0];
|
|
7715
8142
|
const res2 = await this.client.generateUiV2({
|
|
@@ -7863,6 +8290,32 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7863
8290
|
const width = revision.sourceWidth;
|
|
7864
8291
|
const height = revision.sourceHeight;
|
|
7865
8292
|
const roles = revision.sourceMembers?.map((member) => member.role);
|
|
8293
|
+
if (revision.engine === "pro-flash") {
|
|
8294
|
+
if (revision.mode === "inpaint") {
|
|
8295
|
+
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
8296
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
8297
|
+
}
|
|
8298
|
+
const maskBytes = readFileSync2(revision.maskFile);
|
|
8299
|
+
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
8300
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
8301
|
+
}
|
|
8302
|
+
const res3 = await this.client.inpaintImageProFlash({
|
|
8303
|
+
image,
|
|
8304
|
+
maskImage: { base64: maskBytes.toString("base64"), format: revision.maskFormat },
|
|
8305
|
+
description: spec.prompt,
|
|
8306
|
+
noBackground: spec.noBackground,
|
|
8307
|
+
seed: spec.seed
|
|
8308
|
+
});
|
|
8309
|
+
return { jobId: res3.background_job_id, metadata: { imageProFlash: proFlashRecord(res3) } };
|
|
8310
|
+
}
|
|
8311
|
+
const res2 = await this.client.editImageProFlash({
|
|
8312
|
+
image,
|
|
8313
|
+
description: spec.prompt,
|
|
8314
|
+
noBackground: spec.noBackground,
|
|
8315
|
+
seed: spec.seed
|
|
8316
|
+
});
|
|
8317
|
+
return { jobId: res2.background_job_id, metadata: { imageProFlash: proFlashRecord(res2) } };
|
|
8318
|
+
}
|
|
7866
8319
|
if (revision.mode === "inpaint") {
|
|
7867
8320
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
7868
8321
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
@@ -8027,6 +8480,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
8027
8480
|
return this.pollAnimateRevision(jobId, context);
|
|
8028
8481
|
}
|
|
8029
8482
|
if (context?.spec?.revision) return this.pollRevision(jobId);
|
|
8483
|
+
if (generator === "imageProFlash") return this.pollRevision(jobId);
|
|
8030
8484
|
if (generator === "pixflux") {
|
|
8031
8485
|
const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
8032
8486
|
if (existsSync7(file)) {
|
|
@@ -8607,6 +9061,13 @@ function readPose(spec, what, image) {
|
|
|
8607
9061
|
}
|
|
8608
9062
|
return { base64: bytes.toString("base64"), format: image.format };
|
|
8609
9063
|
}
|
|
9064
|
+
function proFlashRecord(res) {
|
|
9065
|
+
return {
|
|
9066
|
+
imageId: res.image_id,
|
|
9067
|
+
sourceImageId: res.source_image_id,
|
|
9068
|
+
...res.estimated_generations != null ? { estimatedGenerations: res.estimated_generations } : {}
|
|
9069
|
+
};
|
|
9070
|
+
}
|
|
8610
9071
|
function editAnimationFrameCeiling(width, height) {
|
|
8611
9072
|
const side = Math.max(width, height);
|
|
8612
9073
|
if (side <= 64) return 16;
|
|
@@ -9751,7 +10212,7 @@ ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
|
9751
10212
|
return { manifest: parsed.data, root: path13.dirname(abs), path: abs, ...loopFamilies };
|
|
9752
10213
|
}
|
|
9753
10214
|
function parentAssetId(asset) {
|
|
9754
|
-
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
10215
|
+
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
9755
10216
|
}
|
|
9756
10217
|
function formatManifestIssues(issues) {
|
|
9757
10218
|
return issues.flatMap((issue) => {
|
|
@@ -9918,9 +10379,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9918
10379
|
height = styleImageDimensions.length ? Math.max(...styleImageDimensions.map((img) => img.height)) : style.tileSize ?? 32;
|
|
9919
10380
|
size = Math.max(width, height);
|
|
9920
10381
|
} else if (generator === "character") {
|
|
9921
|
-
size = asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
9922
|
-
width = asset.state?.canvas?.width ?? size;
|
|
9923
|
-
height = asset.state?.canvas?.height ?? size;
|
|
10382
|
+
size = asset.portrait ? asset.portrait.size : asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
10383
|
+
width = asset.portrait?.size ?? asset.state?.canvas?.width ?? size;
|
|
10384
|
+
height = asset.portrait?.size ?? asset.state?.canvas?.height ?? size;
|
|
9924
10385
|
} else if (generator === "terrain") {
|
|
9925
10386
|
size = style.terrainTileSize ?? 16;
|
|
9926
10387
|
width = size;
|
|
@@ -9947,12 +10408,17 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9947
10408
|
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
9948
10409
|
);
|
|
9949
10410
|
}
|
|
10411
|
+
if ((asset.portrait || asset.outfit) && generator !== "character") {
|
|
10412
|
+
throw new Error(
|
|
10413
|
+
`assets.${assetId}: ${asset.portrait ? "portrait" : "outfit"} needs a character style; "${styleId}" generates ${generator}`
|
|
10414
|
+
);
|
|
10415
|
+
}
|
|
9950
10416
|
if ((asset.pieces || asset.elements) && generator !== "uiAsset") {
|
|
9951
10417
|
throw new Error(
|
|
9952
10418
|
`assets.${assetId}: ${asset.pieces ? "pieces" : "elements"} needs a uiAsset style; "${styleId}" generates ${generator}`
|
|
9953
10419
|
);
|
|
9954
10420
|
}
|
|
9955
|
-
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
10421
|
+
const characterKind = asset.animation ? "animation" : asset.state ? "state" : asset.portrait ? "portrait" : "base";
|
|
9956
10422
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
9957
10423
|
let terrainLowerDescription;
|
|
9958
10424
|
let terrainUpperDescription;
|
|
@@ -10001,6 +10467,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10001
10467
|
uiPieces: generator === "uiAsset" ? asset.pieces : void 0,
|
|
10002
10468
|
uiElements: generator === "uiAsset" ? asset.elements : void 0,
|
|
10003
10469
|
uiColorPalette: generator === "uiAsset" || generator === "uiElement" ? style.uiColorPalette : void 0,
|
|
10470
|
+
...generator === "imageProFlash" && style.styleTraits ? { styleTraits: style.styleTraits } : {},
|
|
10004
10471
|
palette: style.palette,
|
|
10005
10472
|
enforcePalette: style.enforcePalette,
|
|
10006
10473
|
noBackground: style.noBackground,
|
|
@@ -10044,7 +10511,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10044
10511
|
// its parent's pixels, not a new character: giving it a base shape
|
|
10045
10512
|
// would have packing, the gallery, and adoption treat it as one.
|
|
10046
10513
|
...generator === "character" && !asset.revision ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
10047
|
-
|
|
10514
|
+
// A portrait needs a character style (refused above), so an
|
|
10515
|
+
// objectPro asset is only ever a base, state, or animation.
|
|
10516
|
+
...generator === "objectPro" && !asset.revision && characterKind !== "portrait" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
10048
10517
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
10049
10518
|
costUnit: "generations",
|
|
10050
10519
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -10155,7 +10624,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10155
10624
|
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: existsSync8(file) ? await sha256File(file) : null }
|
|
10156
10625
|
};
|
|
10157
10626
|
}
|
|
10158
|
-
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
10627
|
+
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
10159
10628
|
if (resolved.character && characterParent) {
|
|
10160
10629
|
const parentSpec = await finalize(characterParent);
|
|
10161
10630
|
const parentDirections = parentSpec.character?.directions ?? 8;
|
|
@@ -10188,6 +10657,38 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10188
10657
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
10189
10658
|
};
|
|
10190
10659
|
}
|
|
10660
|
+
if (asset.outfit) {
|
|
10661
|
+
if (asset.outfit.of === assetId) throw new Error(`assets.${assetId}: an asset cannot re-clothe itself`);
|
|
10662
|
+
const sourceSpec = await finalize(asset.outfit.of);
|
|
10663
|
+
if (sourceSpec.character?.kind !== "animation") {
|
|
10664
|
+
throw new Error(`assets.${assetId}: outfit "${asset.outfit.of}" is not a character loop`);
|
|
10665
|
+
}
|
|
10666
|
+
const referenceImage = await loadStyleImage(asset.outfit.reference, "Outfit reference image");
|
|
10667
|
+
resolved.character = {
|
|
10668
|
+
kind: "outfit",
|
|
10669
|
+
mode: sourceSpec.character.mode,
|
|
10670
|
+
directions: sourceSpec.character.directions,
|
|
10671
|
+
template: sourceSpec.character.template,
|
|
10672
|
+
parentAssetId: asset.outfit.of,
|
|
10673
|
+
parentSpec: sourceSpec,
|
|
10674
|
+
animation: sourceSpec.character.animation,
|
|
10675
|
+
outfit: {
|
|
10676
|
+
reference: {
|
|
10677
|
+
path: path13.resolve(root, asset.outfit.reference),
|
|
10678
|
+
sha256: referenceImage.hash,
|
|
10679
|
+
width: referenceImage.width,
|
|
10680
|
+
height: referenceImage.height,
|
|
10681
|
+
format: referenceImage.format
|
|
10682
|
+
},
|
|
10683
|
+
...asset.outfit.additionalInstructions ? { additionalInstructions: asset.outfit.additionalInstructions } : {}
|
|
10684
|
+
}
|
|
10685
|
+
};
|
|
10686
|
+
resolved.generator = sourceSpec.generator;
|
|
10687
|
+
resolved.width = sourceSpec.width;
|
|
10688
|
+
resolved.height = sourceSpec.height;
|
|
10689
|
+
resolved.size = sourceSpec.size;
|
|
10690
|
+
resolved.prompt = "";
|
|
10691
|
+
}
|
|
10191
10692
|
if (asset.revision) {
|
|
10192
10693
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
10193
10694
|
throw new Error(
|
|
@@ -10250,7 +10751,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10250
10751
|
lastFrameFormat: lastFrame?.format ?? null
|
|
10251
10752
|
} : {},
|
|
10252
10753
|
...asset.revision.direction ? { direction: asset.revision.direction } : {},
|
|
10253
|
-
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt }
|
|
10754
|
+
...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt },
|
|
10755
|
+
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
10254
10756
|
};
|
|
10255
10757
|
}
|
|
10256
10758
|
if (asset.batch) {
|
|
@@ -10458,6 +10960,9 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
10458
10960
|
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
10459
10961
|
};
|
|
10460
10962
|
}
|
|
10963
|
+
if (asset.portrait) {
|
|
10964
|
+
shape.portrait = { size: asset.portrait.size };
|
|
10965
|
+
}
|
|
10461
10966
|
if (asset.animation) {
|
|
10462
10967
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
10463
10968
|
const pose = async (rel, what) => {
|
|
@@ -13824,7 +14329,7 @@ function describeCharacter(spec, entry) {
|
|
|
13824
14329
|
};
|
|
13825
14330
|
}
|
|
13826
14331
|
if (entry?.generator === "character") {
|
|
13827
|
-
const kind = recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
14332
|
+
const kind = recordedCharacter?.kind === "portrait" ? "portrait" : recordedCharacter?.kind === "outfit" ? "outfit" : recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
13828
14333
|
return {
|
|
13829
14334
|
generator: "character",
|
|
13830
14335
|
kind,
|
|
@@ -14185,7 +14690,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
14185
14690
|
characters: style?.generator === "character" || style?.generator === "objectPro" ? {
|
|
14186
14691
|
bases: styleItems.filter((item) => item.character?.kind === "base").length,
|
|
14187
14692
|
states: styleItems.filter((item) => item.character?.kind === "state").length,
|
|
14188
|
-
animations: styleItems.filter((item) => item.character?.kind === "animation").length
|
|
14693
|
+
animations: styleItems.filter((item) => item.character?.kind === "animation").length,
|
|
14694
|
+
portraits: styleItems.filter((item) => item.character?.kind === "portrait").length,
|
|
14695
|
+
outfits: styleItems.filter((item) => item.character?.kind === "outfit").length
|
|
14189
14696
|
} : null,
|
|
14190
14697
|
tags: style?.tags ?? [],
|
|
14191
14698
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
@@ -14962,6 +15469,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14962
15469
|
const supersededOutputs = previousEntry?.outputs.length ? previousEntry.outputs : previousEntry?.supersededOutputs ?? [];
|
|
14963
15470
|
const submittedAt = resumesCheckpoint ? previousEntry.submittedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
14964
15471
|
const history = historyAfterReplacing(previousEntry, historyLimit(loaded.manifest), submittedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
15472
|
+
const outfitParentEntry = spec.character?.kind === "outfit" && spec.character.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
|
|
14965
15473
|
upsert(lock, key, {
|
|
14966
15474
|
styleId: spec.styleId,
|
|
14967
15475
|
assetId: spec.assetId,
|
|
@@ -14985,7 +15493,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14985
15493
|
...spec.revision.fps == null ? {} : { fps: spec.revision.fps },
|
|
14986
15494
|
...spec.revision.lastFrameSha256 ? { lastFrameSha256: spec.revision.lastFrameSha256 } : {},
|
|
14987
15495
|
...spec.revision.direction ? { direction: spec.revision.direction } : {},
|
|
14988
|
-
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt }
|
|
15496
|
+
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt },
|
|
15497
|
+
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
15498
|
+
} : null,
|
|
15499
|
+
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
15500
|
+
sourceAssetId: spec.character.parentAssetId,
|
|
15501
|
+
sourceSha256: mirrorSourceHash(outfitParentEntry),
|
|
15502
|
+
referenceSha256: spec.character.outfit.reference.sha256
|
|
14989
15503
|
} : null,
|
|
14990
15504
|
status: "pending",
|
|
14991
15505
|
jobId: previousJobId ?? null,
|
|
@@ -15020,10 +15534,17 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
15020
15534
|
const anchor = spec.character?.styleAnchor;
|
|
15021
15535
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
15022
15536
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
15537
|
+
const parentOutputs = spec.character?.kind === "outfit" && parentEntry ? parentEntry.outputs.map((output, index) => ({
|
|
15538
|
+
path: currentEntryOutputPath(parentEntry, spec.character.parentSpec, index),
|
|
15539
|
+
role: output.role
|
|
15540
|
+
})) : void 0;
|
|
15541
|
+
const referenceSourceImageId = ownedReferenceImageId(spec, lock, provider.id);
|
|
15023
15542
|
const { jobId, metadata } = await provider.submit(spec, refs, {
|
|
15024
15543
|
...previousJobId ? { previousJobId } : {},
|
|
15544
|
+
...referenceSourceImageId ? { referenceSourceImageId } : {},
|
|
15025
15545
|
...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
|
|
15026
15546
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
15547
|
+
...parentOutputs ? { parentOutputs } : {},
|
|
15027
15548
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
15028
15549
|
...previousMetadata ? { previousMetadata } : {},
|
|
15029
15550
|
checkpoint: async (checkpoint) => {
|
|
@@ -15087,6 +15608,17 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
15087
15608
|
}
|
|
15088
15609
|
return { submitted, failed, spent, unit };
|
|
15089
15610
|
}
|
|
15611
|
+
function ownedReferenceImageId(spec, lock, providerId) {
|
|
15612
|
+
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;
|
|
15613
|
+
if (!sha2562) return void 0;
|
|
15614
|
+
for (const entry of Object.values(lock.entries)) {
|
|
15615
|
+
if (entry.provider !== providerId || entry.generator !== "imageProFlash" || entry.status !== "downloaded") continue;
|
|
15616
|
+
if (entry.outputs.length !== 1 || entry.outputs[0].sha256 !== sha2562) continue;
|
|
15617
|
+
const recorded = entry.providerMetadata?.[providerId]?.imageProFlash;
|
|
15618
|
+
if (typeof recorded?.sourceImageId === "string" && recorded.sourceImageId) return recorded.sourceImageId;
|
|
15619
|
+
}
|
|
15620
|
+
return void 0;
|
|
15621
|
+
}
|
|
15090
15622
|
|
|
15091
15623
|
// src/pick/server.ts
|
|
15092
15624
|
import { existsSync as existsSync21 } from "fs";
|