pixelkiln 0.64.0 → 0.65.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 +372 -32
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +2 -0
- package/dist/index.cjs +378 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +532 -94
- package/dist/index.d.ts +532 -94
- package/dist/index.js +375 -32
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +20 -12
- package/docs/CHARACTERS.md +61 -0
- package/docs/ENDPOINTS.md +26 -6
- package/docs/GENERATORS.md +10 -6
- package/docs/MANIFEST.md +3 -1
- package/docs/PIXELLAB.md +5 -4
- package/package.json +1 -1
- package/schema/manifest.schema.json +47 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +5 -4
package/dist/cli.js
CHANGED
|
@@ -705,7 +705,16 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
705
705
|
styleTraits: spec.character.styleTraits,
|
|
706
706
|
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
|
|
707
707
|
concept: spec.character.concept?.sha256,
|
|
708
|
-
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
708
|
+
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0,
|
|
709
|
+
// The parent's own bytes come from its recorded outputs; the plan
|
|
710
|
+
// compares those directly (like a mirror), so only the choice of
|
|
711
|
+
// parent, the reference image's bytes, and any extra guidance
|
|
712
|
+
// are identity here.
|
|
713
|
+
outfit: spec.character.outfit ? {
|
|
714
|
+
of: spec.character.parentAssetId,
|
|
715
|
+
reference: spec.character.outfit.reference.sha256,
|
|
716
|
+
additionalInstructions: spec.character.outfit.additionalInstructions
|
|
717
|
+
} : void 0
|
|
709
718
|
} : void 0,
|
|
710
719
|
// An objectPro family: same shape as character above, minus the
|
|
711
720
|
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
@@ -949,6 +958,27 @@ var CharacterStateSchema = z.object({
|
|
|
949
958
|
height: z.number().int().min(16).max(256)
|
|
950
959
|
}).strict().optional()
|
|
951
960
|
}).strict();
|
|
961
|
+
var CharacterPortraitSizeSchema = z.union([
|
|
962
|
+
z.literal(16),
|
|
963
|
+
z.literal(32),
|
|
964
|
+
z.literal(48),
|
|
965
|
+
z.literal(64),
|
|
966
|
+
z.literal(128),
|
|
967
|
+
z.literal(160)
|
|
968
|
+
]);
|
|
969
|
+
var CharacterPortraitSchema = z.object({
|
|
970
|
+
/** The base or state to portray, in the same style. */
|
|
971
|
+
of: z.string().min(1),
|
|
972
|
+
size: CharacterPortraitSizeSchema.default(64)
|
|
973
|
+
}).strict();
|
|
974
|
+
var CharacterOutfitSchema = z.object({
|
|
975
|
+
/** The loop (a character animation asset) to re-clothe, in the same style. */
|
|
976
|
+
of: z.string().min(1),
|
|
977
|
+
/** Manifest-relative image of the outfit to transfer. */
|
|
978
|
+
reference: z.string().min(1),
|
|
979
|
+
/** Extra guidance PixelLab passes through as written, e.g. view/direction hints. */
|
|
980
|
+
additionalInstructions: z.string().max(2e3).optional()
|
|
981
|
+
}).strict();
|
|
952
982
|
var CharacterAnimationSchema = z.object({
|
|
953
983
|
/** The character to animate: a base or a state in the same style. */
|
|
954
984
|
of: z.string().min(1),
|
|
@@ -1570,6 +1600,10 @@ var AssetSchema = z.object({
|
|
|
1570
1600
|
pieces: z.array(UiPieceSchema).min(1).optional(),
|
|
1571
1601
|
/** `uiAsset` styles: named, auto-positioned UI element scaffolds. Combine with `pieces`; omit both for a default full-canvas panel. */
|
|
1572
1602
|
elements: z.array(UiElementSchema).min(1).optional(),
|
|
1603
|
+
/** `character` styles: this asset is a bust portrait of another character asset's south sprite. */
|
|
1604
|
+
portrait: CharacterPortraitSchema.optional(),
|
|
1605
|
+
/** `character` styles: this asset re-clothes an existing loop's frames with a reference outfit. */
|
|
1606
|
+
outfit: CharacterOutfitSchema.optional(),
|
|
1573
1607
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1574
1608
|
proportions: CharacterProportionsSchema.optional(),
|
|
1575
1609
|
/**
|
|
@@ -1646,7 +1680,7 @@ var AssetSchema = z.object({
|
|
|
1646
1680
|
path: ["revision"]
|
|
1647
1681
|
});
|
|
1648
1682
|
}
|
|
1649
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch"].filter(Boolean);
|
|
1683
|
+
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
1684
|
if (shapes.length > 1) {
|
|
1651
1685
|
context.addIssue({
|
|
1652
1686
|
code: z.ZodIssueCode.custom,
|
|
@@ -1661,24 +1695,24 @@ var AssetSchema = z.object({
|
|
|
1661
1695
|
path: ["mirror"]
|
|
1662
1696
|
});
|
|
1663
1697
|
}
|
|
1664
|
-
if (asset.prompt === void 0 && !asset.mirror) {
|
|
1698
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit) {
|
|
1665
1699
|
context.addIssue({
|
|
1666
1700
|
code: z.ZodIssueCode.custom,
|
|
1667
1701
|
message: "Required",
|
|
1668
1702
|
path: ["prompt"]
|
|
1669
1703
|
});
|
|
1670
1704
|
}
|
|
1671
|
-
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
1705
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
1672
1706
|
context.addIssue({
|
|
1673
1707
|
code: z.ZodIssueCode.custom,
|
|
1674
|
-
message: "a reference sprite belongs on a base; a state, animation, or
|
|
1708
|
+
message: "a reference sprite belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
1675
1709
|
path: ["reference"]
|
|
1676
1710
|
});
|
|
1677
1711
|
}
|
|
1678
|
-
if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
|
|
1712
|
+
if (asset.concept && (asset.state || asset.animation || asset.mirror || asset.portrait || asset.outfit)) {
|
|
1679
1713
|
context.addIssue({
|
|
1680
1714
|
code: z.ZodIssueCode.custom,
|
|
1681
|
-
message: "a concept image belongs on a base; a state, animation, or
|
|
1715
|
+
message: "a concept image belongs on a base; a state, animation, mirror, portrait, or outfit takes its look from its parent",
|
|
1682
1716
|
path: ["concept"]
|
|
1683
1717
|
});
|
|
1684
1718
|
}
|
|
@@ -1803,6 +1837,17 @@ var LockEntrySchema = z.object({
|
|
|
1803
1837
|
leaderAssetId: z.string().min(1).optional(),
|
|
1804
1838
|
index: z.number().int().min(1).optional()
|
|
1805
1839
|
}).strict().nullable().default(null),
|
|
1840
|
+
/**
|
|
1841
|
+
* For an outfit: the loop it re-clothed, a hash over that loop's output
|
|
1842
|
+
* hashes when this was made, and the reference image's own hash. Tracked
|
|
1843
|
+
* the way `mirror` is, not through `specHash`: manifest resolution has no
|
|
1844
|
+
* lock to hash the parent's actual current frame bytes against.
|
|
1845
|
+
*/
|
|
1846
|
+
outfit: z.object({
|
|
1847
|
+
sourceAssetId: z.string().min(1),
|
|
1848
|
+
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
1849
|
+
referenceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
1850
|
+
}).strict().nullable().default(null),
|
|
1806
1851
|
/**
|
|
1807
1852
|
* Output hashes owned by the previous generation while its replacement is
|
|
1808
1853
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -4080,6 +4125,7 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
4080
4125
|
import path8 from "path";
|
|
4081
4126
|
async function inspectRevisionReadiness(spec, lock) {
|
|
4082
4127
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
4128
|
+
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
4083
4129
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
4084
4130
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
4085
4131
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -4106,6 +4152,20 @@ async function inspectMirror(spec, lock, seen) {
|
|
|
4106
4152
|
const dependency = await inspectParent(source, lock, seen);
|
|
4107
4153
|
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
4108
4154
|
}
|
|
4155
|
+
async function inspectOutfit(spec, lock, seen) {
|
|
4156
|
+
const character = spec.character;
|
|
4157
|
+
const outfit = character.outfit;
|
|
4158
|
+
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
4159
|
+
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
4160
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
4161
|
+
if (!existsSync4(outfit.reference.path)) {
|
|
4162
|
+
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
4163
|
+
}
|
|
4164
|
+
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
4165
|
+
return { ready: false, reason: "outfit reference changed after the manifest was resolved" };
|
|
4166
|
+
}
|
|
4167
|
+
return { ready: true, reason: `${label} is current` };
|
|
4168
|
+
}
|
|
4109
4169
|
async function inspectCharacterParent(spec, lock, seen) {
|
|
4110
4170
|
const character = spec.character;
|
|
4111
4171
|
const parentSpec = character.parentSpec;
|
|
@@ -4221,7 +4281,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
4221
4281
|
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
4222
4282
|
if (!inputs.ready) return inputs;
|
|
4223
4283
|
}
|
|
4224
|
-
if (spec.character?.
|
|
4284
|
+
if (spec.character?.kind === "outfit") {
|
|
4285
|
+
const inputs = await inspectOutfit(spec, lock, nextSeen);
|
|
4286
|
+
if (!inputs.ready) return inputs;
|
|
4287
|
+
} else if (spec.character?.parentSpec) {
|
|
4225
4288
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
4226
4289
|
if (!inputs.ready) return inputs;
|
|
4227
4290
|
}
|
|
@@ -4344,6 +4407,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
4344
4407
|
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !existsSync5(currentEntryOutputPath(entry, spec, index)))) {
|
|
4345
4408
|
state = "stale";
|
|
4346
4409
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
4410
|
+
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
4411
|
+
state = "stale";
|
|
4412
|
+
reason = `outfit source ${spec.character.parentAssetId} changed; re-clothe the loop again`;
|
|
4347
4413
|
} else if (entry.status === "download-failed") {
|
|
4348
4414
|
state = "recoverable";
|
|
4349
4415
|
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
@@ -5760,6 +5826,28 @@ var AnimateObjectSubmitSchema = z2.object({
|
|
|
5760
5826
|
submissions: z2.array(DirectionSubmissionSchema),
|
|
5761
5827
|
usage: UsageSchema
|
|
5762
5828
|
}).passthrough();
|
|
5829
|
+
var PortraitSubmitSchema = z2.object({
|
|
5830
|
+
background_job_id: z2.string().min(1),
|
|
5831
|
+
status: z2.string().default("processing"),
|
|
5832
|
+
usage: UsageSchema
|
|
5833
|
+
}).passthrough();
|
|
5834
|
+
var PortraitJobSchema = z2.object({
|
|
5835
|
+
status: z2.string(),
|
|
5836
|
+
download_url: z2.string().nullable().optional(),
|
|
5837
|
+
width: z2.number().int().nullable().optional(),
|
|
5838
|
+
height: z2.number().int().nullable().optional()
|
|
5839
|
+
}).passthrough();
|
|
5840
|
+
var SetPortraitSchema = z2.object({
|
|
5841
|
+
character_id: z2.string().min(1),
|
|
5842
|
+
size: z2.number().int().positive(),
|
|
5843
|
+
url: z2.string().min(1),
|
|
5844
|
+
usage: UsageSchema
|
|
5845
|
+
}).passthrough();
|
|
5846
|
+
var TransferOutfitSubmitSchema = z2.object({
|
|
5847
|
+
background_job_id: z2.string().min(1),
|
|
5848
|
+
status: z2.string().default("processing"),
|
|
5849
|
+
usage: UsageSchema
|
|
5850
|
+
}).passthrough();
|
|
5763
5851
|
var CharacterAnimationDirectionSchema = z2.object({
|
|
5764
5852
|
direction: z2.string(),
|
|
5765
5853
|
frame_count: z2.number().int().min(0),
|
|
@@ -6375,6 +6463,69 @@ var PixelLabClient = class {
|
|
|
6375
6463
|
});
|
|
6376
6464
|
return validateResponse(AnimateSubmitSchema, raw, "animate-character");
|
|
6377
6465
|
}
|
|
6466
|
+
/**
|
|
6467
|
+
* A full-body sprite in, a bust portrait out (or the reverse, unused
|
|
6468
|
+
* today). Job-based like a state or `1dir`, not the couple-generations
|
|
6469
|
+
* "conversion" the name suggests: the smallest `resultSize` (16px) billed
|
|
6470
|
+
* 20 generations live (docs/ENDPOINTS.md, "Characters, measured").
|
|
6471
|
+
*/
|
|
6472
|
+
async createPortraitCharacterPro(args) {
|
|
6473
|
+
const raw = await this.request("/portrait-character-pro", {
|
|
6474
|
+
method: "POST",
|
|
6475
|
+
body: JSON.stringify({
|
|
6476
|
+
direction: args.direction,
|
|
6477
|
+
image: { type: "base64", base64: args.image.base64, format: args.image.format },
|
|
6478
|
+
...args.view ? { view: args.view } : {},
|
|
6479
|
+
...args.resultSize != null ? { result_size: args.resultSize } : {},
|
|
6480
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
6481
|
+
})
|
|
6482
|
+
});
|
|
6483
|
+
return validateResponse(PortraitSubmitSchema, raw, "portrait-character-pro");
|
|
6484
|
+
}
|
|
6485
|
+
/** Throws PixelLabError(423) while the job is still drawing, like tiles and map objects. */
|
|
6486
|
+
async getPortraitCharacterJob(jobId) {
|
|
6487
|
+
const raw = await this.request(`/portrait-character-pro/${encodeURIComponent(jobId)}`);
|
|
6488
|
+
return validateResponse(PortraitJobSchema, raw, "portrait-character-pro/{id}");
|
|
6489
|
+
}
|
|
6490
|
+
/**
|
|
6491
|
+
* Attaches a portrait image to a character record. Synchronous and free
|
|
6492
|
+
* (0 generations, live); PixelLab does not link this to
|
|
6493
|
+
* `createPortraitCharacterPro` automatically, and `GET /characters/{id}`
|
|
6494
|
+
* never grows a portrait field, so this response's `url` is the only place
|
|
6495
|
+
* it appears.
|
|
6496
|
+
*/
|
|
6497
|
+
async setCharacterPortrait(characterId, image) {
|
|
6498
|
+
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}/portrait`, {
|
|
6499
|
+
method: "POST",
|
|
6500
|
+
body: JSON.stringify({ image: { type: "base64", base64: image.base64, format: image.format } })
|
|
6501
|
+
});
|
|
6502
|
+
return validateResponse(SetPortraitSchema, raw, "characters/{id}/portrait");
|
|
6503
|
+
}
|
|
6504
|
+
/**
|
|
6505
|
+
* Applies a reference outfit to 2–16 existing frames. Job-based like
|
|
6506
|
+
* everything else here, but the completed job returns the result frames
|
|
6507
|
+
* inline as base64 (`last_response.quantized_images`), not as storage
|
|
6508
|
+
* URLs — the one PixelLab character tool that works this way. A live
|
|
6509
|
+
* 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md).
|
|
6510
|
+
*/
|
|
6511
|
+
async transferOutfitV2(args) {
|
|
6512
|
+
const image = (img) => ({
|
|
6513
|
+
image: { type: "base64", base64: img.base64, format: img.format },
|
|
6514
|
+
size: { width: img.width, height: img.height }
|
|
6515
|
+
});
|
|
6516
|
+
const raw = await this.request("/transfer-outfit-v2", {
|
|
6517
|
+
method: "POST",
|
|
6518
|
+
body: JSON.stringify({
|
|
6519
|
+
reference_image: image(args.referenceImage),
|
|
6520
|
+
frames: args.frames.map(image),
|
|
6521
|
+
image_size: args.imageSize,
|
|
6522
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
6523
|
+
...args.noBackground !== void 0 ? { no_background: args.noBackground } : {},
|
|
6524
|
+
...args.additionalInstructions ? { additional_instructions: args.additionalInstructions } : {}
|
|
6525
|
+
})
|
|
6526
|
+
});
|
|
6527
|
+
return validateResponse(TransferOutfitSubmitSchema, raw, "transfer-outfit-v2");
|
|
6528
|
+
}
|
|
6378
6529
|
async getCharacter(characterId) {
|
|
6379
6530
|
const raw = await this.request(`/characters/${encodeURIComponent(characterId)}`);
|
|
6380
6531
|
return validateResponse(CharacterSchema, raw, "characters/{id}");
|
|
@@ -6794,7 +6945,10 @@ function characterCost(spec) {
|
|
|
6794
6945
|
if (animation.mode === "v3") return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
6795
6946
|
return generationCost(spec.width, spec.height, "1dir");
|
|
6796
6947
|
}
|
|
6797
|
-
if (character.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
6948
|
+
if (character.kind === "state" || character.kind === "portrait") return generationCost(spec.width, spec.height, "1dir");
|
|
6949
|
+
if (character.kind === "outfit") {
|
|
6950
|
+
return 20;
|
|
6951
|
+
}
|
|
6798
6952
|
if (character.mode === "standard") return 1;
|
|
6799
6953
|
if (character.mode === "v3") return 1 + Math.max(1, Math.ceil(px * 8 / 65536));
|
|
6800
6954
|
if (character.mode === "pro-flash") {
|
|
@@ -6911,15 +7065,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6911
7065
|
return { spacingMs: 2500, maxInFlight: 8 };
|
|
6912
7066
|
}
|
|
6913
7067
|
/**
|
|
6914
|
-
* Where synchronous pixflux results
|
|
7068
|
+
* Where synchronous pixflux results (or an outfit transfer's inline
|
|
7069
|
+
* frames) are parked between `submit`/poll and `fetch`.
|
|
6915
7070
|
*
|
|
6916
|
-
* pixflux returns the PNG inline rather than a job id,
|
|
6917
|
-
*
|
|
6918
|
-
*
|
|
6919
|
-
*
|
|
7071
|
+
* pixflux returns the PNG inline rather than a job id, and an outfit
|
|
7072
|
+
* transfer's completed job returns its frames inline too, but the
|
|
7073
|
+
* pipeline is built around submit → poll → fetch running as separate
|
|
7074
|
+
* commands. Writing the bytes to a known path keeps that model intact:
|
|
7075
|
+
* the id is the filename, polling is an existence check, and downloading
|
|
7076
|
+
* is a file read.
|
|
6920
7077
|
*/
|
|
6921
|
-
static cacheDir() {
|
|
6922
|
-
const dir = path12.join(os.tmpdir(),
|
|
7078
|
+
static cacheDir(namespace = "pixflux") {
|
|
7079
|
+
const dir = path12.join(os.tmpdir(), `pixelkiln-${namespace}`);
|
|
6923
7080
|
mkdirSync(dir, { recursive: true });
|
|
6924
7081
|
return dir;
|
|
6925
7082
|
}
|
|
@@ -7179,6 +7336,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7179
7336
|
const character = spec.character;
|
|
7180
7337
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
7181
7338
|
const label = `${spec.styleId}/${spec.assetId}`;
|
|
7339
|
+
if (character.kind === "outfit") {
|
|
7340
|
+
const reference = character.outfit.reference;
|
|
7341
|
+
if (reference.width < 32 || reference.width > 256 || reference.height < 32 || reference.height > 256) {
|
|
7342
|
+
throw new Error(`${label}: outfit reference is ${reference.width}x${reference.height}; PixelLab transfer-outfit-v2 takes 32 to 256px per side`);
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7182
7345
|
if (character.kind === "base" && character.proportions !== void 0) {
|
|
7183
7346
|
if (character.mode !== "standard") {
|
|
7184
7347
|
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
@@ -7190,7 +7353,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7190
7353
|
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
7191
7354
|
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
7192
7355
|
}
|
|
7193
|
-
if (character.animation) {
|
|
7356
|
+
if (character.animation && character.kind !== "outfit") {
|
|
7194
7357
|
const { startFrame, endFrame } = character.animation;
|
|
7195
7358
|
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
7196
7359
|
if (image && (image.width > 256 || image.height > 256)) {
|
|
@@ -7271,9 +7434,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7271
7434
|
if (character.styleTraits && character.mode !== "pro-flash") {
|
|
7272
7435
|
throw new Error(`${label}: styleTraits choose what a pro-flash style image lends; the ${character.mode} engine does not take them`);
|
|
7273
7436
|
}
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7437
|
+
if (character.kind !== "portrait") {
|
|
7438
|
+
const maxSize = character.mode === "v3" || character.mode === "pro-flash" ? 256 : 128;
|
|
7439
|
+
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
7440
|
+
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
7441
|
+
}
|
|
7277
7442
|
}
|
|
7278
7443
|
if (character.mode === "pro-flash" && character.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
7279
7444
|
throw new Error(`PixelLab pro-flash characters are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
@@ -7282,9 +7447,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7282
7447
|
if (!templates.includes(character.template)) {
|
|
7283
7448
|
throw new Error(`PixelLab ${character.mode} character template must be one of: ${templates.join(", ")}`);
|
|
7284
7449
|
}
|
|
7285
|
-
const views = character.mode === "standard" ? ["low top-down", "high top-down", "side", "perspective", "oblique"] : ["low top-down", "high top-down", "side"];
|
|
7450
|
+
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
7451
|
if (!views.includes(spec.view)) {
|
|
7287
|
-
throw new Error(
|
|
7452
|
+
throw new Error(
|
|
7453
|
+
character.kind === "portrait" ? `PixelLab portrait view must be one of: ${views.join(", ")}` : `PixelLab ${character.mode} character view must be one of: ${views.join(", ")}`
|
|
7454
|
+
);
|
|
7288
7455
|
}
|
|
7289
7456
|
if (spec.view === "oblique" && character.directions !== 4) {
|
|
7290
7457
|
throw new Error("PixelLab oblique characters are 4-direction only");
|
|
@@ -7292,7 +7459,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7292
7459
|
if (character.kind !== "base" && !character.parentAssetId) {
|
|
7293
7460
|
throw new Error(`${spec.styleId}/${spec.assetId} is a ${character.kind} with no parent`);
|
|
7294
7461
|
}
|
|
7295
|
-
if (character.animation) {
|
|
7462
|
+
if (character.animation && character.kind !== "outfit") {
|
|
7296
7463
|
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
7297
7464
|
if (!allowed.includes(character.animation.direction)) {
|
|
7298
7465
|
throw new Error(
|
|
@@ -7398,6 +7565,43 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7398
7565
|
});
|
|
7399
7566
|
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
7567
|
}
|
|
7568
|
+
if (character.kind === "outfit") {
|
|
7569
|
+
const parentOutputs = context?.parentOutputs ?? [];
|
|
7570
|
+
if (parentOutputs.length < 2) {
|
|
7571
|
+
throw new Error(
|
|
7572
|
+
`${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`
|
|
7573
|
+
);
|
|
7574
|
+
}
|
|
7575
|
+
const frames = parentOutputs.slice(0, 16).map((output) => {
|
|
7576
|
+
const bytes = readFileSync2(output.path);
|
|
7577
|
+
const dims = imageMetadata(bytes);
|
|
7578
|
+
if (!dims) throw new Error(`${spec.styleId}/${spec.assetId}: outfit source frame is not a readable PNG: ${output.path}`);
|
|
7579
|
+
return { base64: bytes.toString("base64"), format: "png", width: dims.width, height: dims.height };
|
|
7580
|
+
});
|
|
7581
|
+
const outfit = character.outfit;
|
|
7582
|
+
const referenceBytes = readFileSync2(outfit.reference.path);
|
|
7583
|
+
if (sha256(referenceBytes) !== outfit.reference.sha256) {
|
|
7584
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: outfit reference changed after the manifest was resolved: ${outfit.reference.path}`);
|
|
7585
|
+
}
|
|
7586
|
+
const res2 = await this.client.transferOutfitV2({
|
|
7587
|
+
referenceImage: { base64: referenceBytes.toString("base64"), format: "png", width: outfit.reference.width, height: outfit.reference.height },
|
|
7588
|
+
frames,
|
|
7589
|
+
imageSize: { width: spec.width, height: spec.height },
|
|
7590
|
+
seed: spec.seed,
|
|
7591
|
+
noBackground: spec.noBackground,
|
|
7592
|
+
additionalInstructions: outfit.additionalInstructions
|
|
7593
|
+
});
|
|
7594
|
+
return {
|
|
7595
|
+
jobId: res2.background_job_id,
|
|
7596
|
+
metadata: {
|
|
7597
|
+
character: {
|
|
7598
|
+
kind: "outfit",
|
|
7599
|
+
backgroundJobId: res2.background_job_id,
|
|
7600
|
+
frameRoles: parentOutputs.slice(0, 16).map((output) => output.role ?? null)
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
};
|
|
7604
|
+
}
|
|
7401
7605
|
const parentId = context?.parentObjectId;
|
|
7402
7606
|
if (!parentId) {
|
|
7403
7607
|
throw new Error(
|
|
@@ -7416,6 +7620,24 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7416
7620
|
});
|
|
7417
7621
|
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
7622
|
}
|
|
7623
|
+
if (character.kind === "portrait") {
|
|
7624
|
+
const parentFile = character.parentFile;
|
|
7625
|
+
const bytes = readFileSync2(parentFile);
|
|
7626
|
+
if (character.parentSha256 && sha256(bytes) !== character.parentSha256) {
|
|
7627
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: portrait source changed after the manifest was resolved: ${parentFile}`);
|
|
7628
|
+
}
|
|
7629
|
+
const res2 = await this.client.createPortraitCharacterPro({
|
|
7630
|
+
direction: "character_to_portrait",
|
|
7631
|
+
image: { base64: bytes.toString("base64"), format: "png" },
|
|
7632
|
+
view: spec.view,
|
|
7633
|
+
resultSize: character.portrait.size,
|
|
7634
|
+
seed: spec.seed
|
|
7635
|
+
});
|
|
7636
|
+
return {
|
|
7637
|
+
jobId: res2.background_job_id,
|
|
7638
|
+
metadata: { character: { kind: "portrait", parentCharacterId: parentId, backgroundJobId: res2.background_job_id } }
|
|
7639
|
+
};
|
|
7640
|
+
}
|
|
7419
7641
|
const animation = character.animation;
|
|
7420
7642
|
const name = characterAnimationName(spec);
|
|
7421
7643
|
const replaced = context?.replacedMetadata?.character ?? {};
|
|
@@ -7529,6 +7751,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7529
7751
|
async pollCharacter(jobId, context) {
|
|
7530
7752
|
const animation = decodeAnimationJob(jobId);
|
|
7531
7753
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
7754
|
+
const kind = context?.metadata?.character?.kind ?? context?.spec?.character?.kind;
|
|
7755
|
+
if (kind === "portrait") return this.pollCharacterPortrait(jobId, context);
|
|
7756
|
+
if (kind === "outfit") return this.pollCharacterOutfit(jobId, context);
|
|
7532
7757
|
const character = await this.client.getCharacter(jobId);
|
|
7533
7758
|
if (character.status === "failed") return { status: "failed", error: "character generation failed upstream" };
|
|
7534
7759
|
if (character.status !== "completed" || !character.rotation_urls) return { status: "processing" };
|
|
@@ -7554,6 +7779,66 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
7554
7779
|
billed: await this.billedForJob(backgroundJobId)
|
|
7555
7780
|
};
|
|
7556
7781
|
}
|
|
7782
|
+
/**
|
|
7783
|
+
* `jobId` here is `portrait-character-pro`'s own background job id, not a
|
|
7784
|
+
* character id: the endpoint converts an arbitrary image and never touches
|
|
7785
|
+
* the character record. Once the portrait itself is drawn, this also
|
|
7786
|
+
* attaches it to the parent's character record with `SetPortrait` — a
|
|
7787
|
+
* separate, free call PixelLab does not make automatically (docs/PIXELLAB.md).
|
|
7788
|
+
*/
|
|
7789
|
+
async pollCharacterPortrait(jobId, context) {
|
|
7790
|
+
const meta = context?.metadata?.character;
|
|
7791
|
+
try {
|
|
7792
|
+
const job = await this.client.getPortraitCharacterJob(jobId);
|
|
7793
|
+
if (job.status === "failed") return { status: "failed", error: "portrait generation failed upstream" };
|
|
7794
|
+
if (job.status !== "completed" || !job.download_url) return { status: "processing" };
|
|
7795
|
+
if (meta?.parentCharacterId) {
|
|
7796
|
+
const bytes = await this.client.download(job.download_url);
|
|
7797
|
+
await this.client.setCharacterPortrait(meta.parentCharacterId, { base64: bytes.toString("base64"), format: "png" });
|
|
7798
|
+
}
|
|
7799
|
+
return {
|
|
7800
|
+
status: "ready",
|
|
7801
|
+
objectId: jobId,
|
|
7802
|
+
sourceUrl: job.download_url,
|
|
7803
|
+
sources: [{ url: job.download_url }],
|
|
7804
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
7805
|
+
};
|
|
7806
|
+
} catch (err) {
|
|
7807
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
7808
|
+
throw err;
|
|
7809
|
+
}
|
|
7810
|
+
}
|
|
7811
|
+
/**
|
|
7812
|
+
* No dedicated status endpoint exists for `transfer-outfit-v2`, unlike
|
|
7813
|
+
* portraits and tiles: the background job is the only place to poll. A
|
|
7814
|
+
* completed job returns its frames inline as base64
|
|
7815
|
+
* (`last_response.quantized_images`), not as storage URLs, so they are
|
|
7816
|
+
* cached locally the way a synchronous pixflux result is, and matched back
|
|
7817
|
+
* to the source frames' own roles recorded at submit time.
|
|
7818
|
+
*/
|
|
7819
|
+
async pollCharacterOutfit(jobId, context) {
|
|
7820
|
+
const meta = context?.metadata?.character;
|
|
7821
|
+
const job = await this.client.getBackgroundJob(jobId);
|
|
7822
|
+
if (job.status === "failed") return { status: "failed", error: "outfit transfer failed upstream" };
|
|
7823
|
+
if (job.status !== "completed") return { status: "processing" };
|
|
7824
|
+
const images = job.last_response?.quantized_images;
|
|
7825
|
+
if (!images?.length) return { status: "failed", error: "outfit transfer completed with no result frames" };
|
|
7826
|
+
const dir = _PixelLabProvider.cacheDir("outfit");
|
|
7827
|
+
const sources = images.map((image, index) => {
|
|
7828
|
+
if (!image.base64) throw new Error(`outfit transfer frame ${index} has no image data`);
|
|
7829
|
+
const role = meta?.frameRoles?.[index] ?? void 0;
|
|
7830
|
+
const file = path12.join(dir, `${jobId}-${index}.png`);
|
|
7831
|
+
writeFileSync(file, Buffer.from(image.base64, "base64"));
|
|
7832
|
+
return { url: `file://${file}`, ...role ? { role } : {} };
|
|
7833
|
+
});
|
|
7834
|
+
return {
|
|
7835
|
+
status: "ready",
|
|
7836
|
+
objectId: jobId,
|
|
7837
|
+
sourceUrl: sources[0].url,
|
|
7838
|
+
sources,
|
|
7839
|
+
billed: await this.billedForJob(meta?.backgroundJobId)
|
|
7840
|
+
};
|
|
7841
|
+
}
|
|
7557
7842
|
/**
|
|
7558
7843
|
* The background job is the authoritative record of one direction: it
|
|
7559
7844
|
* completes with the frame URLs, the animation id, and the direction, and
|
|
@@ -9751,7 +10036,7 @@ ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
|
9751
10036
|
return { manifest: parsed.data, root: path13.dirname(abs), path: abs, ...loopFamilies };
|
|
9752
10037
|
}
|
|
9753
10038
|
function parentAssetId(asset) {
|
|
9754
|
-
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
10039
|
+
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
9755
10040
|
}
|
|
9756
10041
|
function formatManifestIssues(issues) {
|
|
9757
10042
|
return issues.flatMap((issue) => {
|
|
@@ -9918,9 +10203,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9918
10203
|
height = styleImageDimensions.length ? Math.max(...styleImageDimensions.map((img) => img.height)) : style.tileSize ?? 32;
|
|
9919
10204
|
size = Math.max(width, height);
|
|
9920
10205
|
} 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;
|
|
10206
|
+
size = asset.portrait ? asset.portrait.size : asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
10207
|
+
width = asset.portrait?.size ?? asset.state?.canvas?.width ?? size;
|
|
10208
|
+
height = asset.portrait?.size ?? asset.state?.canvas?.height ?? size;
|
|
9924
10209
|
} else if (generator === "terrain") {
|
|
9925
10210
|
size = style.terrainTileSize ?? 16;
|
|
9926
10211
|
width = size;
|
|
@@ -9947,12 +10232,17 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9947
10232
|
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
9948
10233
|
);
|
|
9949
10234
|
}
|
|
10235
|
+
if ((asset.portrait || asset.outfit) && generator !== "character") {
|
|
10236
|
+
throw new Error(
|
|
10237
|
+
`assets.${assetId}: ${asset.portrait ? "portrait" : "outfit"} needs a character style; "${styleId}" generates ${generator}`
|
|
10238
|
+
);
|
|
10239
|
+
}
|
|
9950
10240
|
if ((asset.pieces || asset.elements) && generator !== "uiAsset") {
|
|
9951
10241
|
throw new Error(
|
|
9952
10242
|
`assets.${assetId}: ${asset.pieces ? "pieces" : "elements"} needs a uiAsset style; "${styleId}" generates ${generator}`
|
|
9953
10243
|
);
|
|
9954
10244
|
}
|
|
9955
|
-
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
10245
|
+
const characterKind = asset.animation ? "animation" : asset.state ? "state" : asset.portrait ? "portrait" : "base";
|
|
9956
10246
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
9957
10247
|
let terrainLowerDescription;
|
|
9958
10248
|
let terrainUpperDescription;
|
|
@@ -10044,7 +10334,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10044
10334
|
// its parent's pixels, not a new character: giving it a base shape
|
|
10045
10335
|
// would have packing, the gallery, and adoption treat it as one.
|
|
10046
10336
|
...generator === "character" && !asset.revision ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
10047
|
-
|
|
10337
|
+
// A portrait needs a character style (refused above), so an
|
|
10338
|
+
// objectPro asset is only ever a base, state, or animation.
|
|
10339
|
+
...generator === "objectPro" && !asset.revision && characterKind !== "portrait" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
10048
10340
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
10049
10341
|
costUnit: "generations",
|
|
10050
10342
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -10155,7 +10447,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10155
10447
|
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: existsSync8(file) ? await sha256File(file) : null }
|
|
10156
10448
|
};
|
|
10157
10449
|
}
|
|
10158
|
-
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
10450
|
+
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
10159
10451
|
if (resolved.character && characterParent) {
|
|
10160
10452
|
const parentSpec = await finalize(characterParent);
|
|
10161
10453
|
const parentDirections = parentSpec.character?.directions ?? 8;
|
|
@@ -10188,6 +10480,38 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10188
10480
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
10189
10481
|
};
|
|
10190
10482
|
}
|
|
10483
|
+
if (asset.outfit) {
|
|
10484
|
+
if (asset.outfit.of === assetId) throw new Error(`assets.${assetId}: an asset cannot re-clothe itself`);
|
|
10485
|
+
const sourceSpec = await finalize(asset.outfit.of);
|
|
10486
|
+
if (sourceSpec.character?.kind !== "animation") {
|
|
10487
|
+
throw new Error(`assets.${assetId}: outfit "${asset.outfit.of}" is not a character loop`);
|
|
10488
|
+
}
|
|
10489
|
+
const referenceImage = await loadStyleImage(asset.outfit.reference, "Outfit reference image");
|
|
10490
|
+
resolved.character = {
|
|
10491
|
+
kind: "outfit",
|
|
10492
|
+
mode: sourceSpec.character.mode,
|
|
10493
|
+
directions: sourceSpec.character.directions,
|
|
10494
|
+
template: sourceSpec.character.template,
|
|
10495
|
+
parentAssetId: asset.outfit.of,
|
|
10496
|
+
parentSpec: sourceSpec,
|
|
10497
|
+
animation: sourceSpec.character.animation,
|
|
10498
|
+
outfit: {
|
|
10499
|
+
reference: {
|
|
10500
|
+
path: path13.resolve(root, asset.outfit.reference),
|
|
10501
|
+
sha256: referenceImage.hash,
|
|
10502
|
+
width: referenceImage.width,
|
|
10503
|
+
height: referenceImage.height,
|
|
10504
|
+
format: referenceImage.format
|
|
10505
|
+
},
|
|
10506
|
+
...asset.outfit.additionalInstructions ? { additionalInstructions: asset.outfit.additionalInstructions } : {}
|
|
10507
|
+
}
|
|
10508
|
+
};
|
|
10509
|
+
resolved.generator = sourceSpec.generator;
|
|
10510
|
+
resolved.width = sourceSpec.width;
|
|
10511
|
+
resolved.height = sourceSpec.height;
|
|
10512
|
+
resolved.size = sourceSpec.size;
|
|
10513
|
+
resolved.prompt = "";
|
|
10514
|
+
}
|
|
10191
10515
|
if (asset.revision) {
|
|
10192
10516
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
10193
10517
|
throw new Error(
|
|
@@ -10458,6 +10782,9 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
10458
10782
|
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
10459
10783
|
};
|
|
10460
10784
|
}
|
|
10785
|
+
if (asset.portrait) {
|
|
10786
|
+
shape.portrait = { size: asset.portrait.size };
|
|
10787
|
+
}
|
|
10461
10788
|
if (asset.animation) {
|
|
10462
10789
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
10463
10790
|
const pose = async (rel, what) => {
|
|
@@ -13824,7 +14151,7 @@ function describeCharacter(spec, entry) {
|
|
|
13824
14151
|
};
|
|
13825
14152
|
}
|
|
13826
14153
|
if (entry?.generator === "character") {
|
|
13827
|
-
const kind = recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
14154
|
+
const kind = recordedCharacter?.kind === "portrait" ? "portrait" : recordedCharacter?.kind === "outfit" ? "outfit" : recordedCharacter?.kind === "animation" || entry.outputs.some((o) => o.role?.startsWith("frame-")) ? "animation" : "base";
|
|
13828
14155
|
return {
|
|
13829
14156
|
generator: "character",
|
|
13830
14157
|
kind,
|
|
@@ -14185,7 +14512,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
14185
14512
|
characters: style?.generator === "character" || style?.generator === "objectPro" ? {
|
|
14186
14513
|
bases: styleItems.filter((item) => item.character?.kind === "base").length,
|
|
14187
14514
|
states: styleItems.filter((item) => item.character?.kind === "state").length,
|
|
14188
|
-
animations: styleItems.filter((item) => item.character?.kind === "animation").length
|
|
14515
|
+
animations: styleItems.filter((item) => item.character?.kind === "animation").length,
|
|
14516
|
+
portraits: styleItems.filter((item) => item.character?.kind === "portrait").length,
|
|
14517
|
+
outfits: styleItems.filter((item) => item.character?.kind === "outfit").length
|
|
14189
14518
|
} : null,
|
|
14190
14519
|
tags: style?.tags ?? [],
|
|
14191
14520
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
@@ -14962,6 +15291,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14962
15291
|
const supersededOutputs = previousEntry?.outputs.length ? previousEntry.outputs : previousEntry?.supersededOutputs ?? [];
|
|
14963
15292
|
const submittedAt = resumesCheckpoint ? previousEntry.submittedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
14964
15293
|
const history = historyAfterReplacing(previousEntry, historyLimit(loaded.manifest), submittedAt ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
15294
|
+
const outfitParentEntry = spec.character?.kind === "outfit" && spec.character.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
|
|
14965
15295
|
upsert(lock, key, {
|
|
14966
15296
|
styleId: spec.styleId,
|
|
14967
15297
|
assetId: spec.assetId,
|
|
@@ -14987,6 +15317,11 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14987
15317
|
...spec.revision.direction ? { direction: spec.revision.direction } : {},
|
|
14988
15318
|
...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt }
|
|
14989
15319
|
} : null,
|
|
15320
|
+
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
15321
|
+
sourceAssetId: spec.character.parentAssetId,
|
|
15322
|
+
sourceSha256: mirrorSourceHash(outfitParentEntry),
|
|
15323
|
+
referenceSha256: spec.character.outfit.reference.sha256
|
|
15324
|
+
} : null,
|
|
14990
15325
|
status: "pending",
|
|
14991
15326
|
jobId: previousJobId ?? null,
|
|
14992
15327
|
...previousJobId ? { submissionComplete: false } : { submissionComplete: void 0 },
|
|
@@ -15020,10 +15355,15 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
15020
15355
|
const anchor = spec.character?.styleAnchor;
|
|
15021
15356
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
15022
15357
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
15358
|
+
const parentOutputs = spec.character?.kind === "outfit" && parentEntry ? parentEntry.outputs.map((output, index) => ({
|
|
15359
|
+
path: currentEntryOutputPath(parentEntry, spec.character.parentSpec, index),
|
|
15360
|
+
role: output.role
|
|
15361
|
+
})) : void 0;
|
|
15023
15362
|
const { jobId, metadata } = await provider.submit(spec, refs, {
|
|
15024
15363
|
...previousJobId ? { previousJobId } : {},
|
|
15025
15364
|
...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
|
|
15026
15365
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
15366
|
+
...parentOutputs ? { parentOutputs } : {},
|
|
15027
15367
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
15028
15368
|
...previousMetadata ? { previousMetadata } : {},
|
|
15029
15369
|
checkpoint: async (checkpoint) => {
|