pixelkiln 0.83.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +712 -482
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +2 -0
- package/dist/client/gallery.js +3366 -3087
- package/dist/index.cjs +425 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +767 -71
- package/dist/index.d.ts +767 -71
- package/dist/index.js +421 -191
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +10 -6
- package/docs/REVISIONS.md +62 -13
- package/package.json +1 -1
- package/schema/manifest.schema.json +22 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +10 -0
- package/skills/pixelkiln/references/pixellab.md +6 -3
- package/skills/pixelkiln/references/revisions.md +13 -7
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ var RevisionModeSchema = z.enum([
|
|
|
9
9
|
"outpaint",
|
|
10
10
|
"reduce-colors",
|
|
11
11
|
"correct-pixelart",
|
|
12
|
+
"remove-background",
|
|
12
13
|
"animate",
|
|
13
14
|
"animate-pixminimax",
|
|
14
15
|
"animate-skeleton",
|
|
@@ -340,7 +341,22 @@ var RevisionSchema = z.object({
|
|
|
340
341
|
* `interpolate`: required, the ending keyframe; the parent is the start.
|
|
341
342
|
*/
|
|
342
343
|
lastFrame: z.string().min(1).optional(),
|
|
343
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* `animate`/`animate-pixminimax` only: colour de-flicker sensitivity.
|
|
346
|
+
* Frames whose foreground drifts from the first frame beyond this are
|
|
347
|
+
* corrected toward it; 0 corrects every frame and higher values correct
|
|
348
|
+
* fewer. Omit for PixelLab's default.
|
|
349
|
+
*/
|
|
350
|
+
driftThreshold: z.number().min(0).optional(),
|
|
351
|
+
/**
|
|
352
|
+
* `animate-pixminimax` only: one noun phrase for what the parent shows.
|
|
353
|
+
* PixelLab reads it off the image when omitted, and can decline some
|
|
354
|
+
* images, failing the job until it is sent.
|
|
355
|
+
*/
|
|
356
|
+
subjectDescription: z.string().min(1).max(300).optional(),
|
|
357
|
+
/** `animate-pixminimax` only: the pose the parent is in, read off the image by PixelLab when omitted. */
|
|
358
|
+
initialPose: z.string().min(1).max(300).optional(),
|
|
359
|
+
/** `animate-pixminimax` only: facing direction, one of the model's caption fields, so it also holds the sprite's facing. */
|
|
344
360
|
direction: CharacterDirectionSchema.optional(),
|
|
345
361
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
346
362
|
enhancePrompt: z.boolean().optional(),
|
|
@@ -360,12 +376,15 @@ var RevisionSchema = z.object({
|
|
|
360
376
|
* same `mannequin`/`bear`/`cat`/`dog`/`horse`/`lion` vocabulary.
|
|
361
377
|
*/
|
|
362
378
|
skeletonTemplate: z.string().min(1).optional(),
|
|
379
|
+
/** `remove-background` only: `simple` is faster and suits flat backgrounds; `complex` handles detailed edges. PixelLab defaults to `simple`. */
|
|
380
|
+
removalTask: z.enum(["simple", "complex"]).optional(),
|
|
363
381
|
/**
|
|
364
|
-
* `animate-skeleton
|
|
382
|
+
* `animate-skeleton`: what the character *looks like*, a noun
|
|
365
383
|
* phrase (colours, clothing, held items) — distinct from the asset's
|
|
366
384
|
* own `prompt`, which this mode sends as the motion's short label
|
|
367
385
|
* (PixelLab's `action`), the same `prompt`-as-motion convention
|
|
368
|
-
* `animate`/`animate-pixminimax` already use.
|
|
386
|
+
* `animate`/`animate-pixminimax` already use. `remove-background`: a
|
|
387
|
+
* short description of the foreground, which PixelLab uses as a hint.
|
|
369
388
|
*/
|
|
370
389
|
description: z.string().min(1).optional(),
|
|
371
390
|
/**
|
|
@@ -482,7 +501,44 @@ var RevisionSchema = z.object({
|
|
|
482
501
|
path: ["keypointsFile"]
|
|
483
502
|
});
|
|
484
503
|
}
|
|
485
|
-
|
|
504
|
+
if (revision.driftThreshold !== void 0 && revision.mode !== "animate" && revision.mode !== "animate-pixminimax") {
|
|
505
|
+
context.addIssue({
|
|
506
|
+
code: z.ZodIssueCode.custom,
|
|
507
|
+
message: "driftThreshold applies to animate/animate-pixminimax revisions only",
|
|
508
|
+
path: ["driftThreshold"]
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
for (const field of ["subjectDescription", "initialPose"]) {
|
|
512
|
+
if (revision[field] !== void 0 && revision.mode !== "animate-pixminimax") {
|
|
513
|
+
context.addIssue({
|
|
514
|
+
code: z.ZodIssueCode.custom,
|
|
515
|
+
message: `${field} applies to animate-pixminimax revisions only`,
|
|
516
|
+
path: [field]
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (revision.removalTask !== void 0 && revision.mode !== "remove-background") {
|
|
521
|
+
context.addIssue({
|
|
522
|
+
code: z.ZodIssueCode.custom,
|
|
523
|
+
message: "removalTask applies to remove-background revisions only",
|
|
524
|
+
path: ["removalTask"]
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (revision.strength !== void 0 && revision.mode === "remove-background") {
|
|
528
|
+
context.addIssue({
|
|
529
|
+
code: z.ZodIssueCode.custom,
|
|
530
|
+
message: "remove-background revisions do not take a strength",
|
|
531
|
+
path: ["strength"]
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (revision.description !== void 0 && revision.mode !== "animate-skeleton" && revision.mode !== "remove-background") {
|
|
535
|
+
context.addIssue({
|
|
536
|
+
code: z.ZodIssueCode.custom,
|
|
537
|
+
message: "description applies to animate-skeleton/remove-background revisions only",
|
|
538
|
+
path: ["description"]
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
for (const field of ["keypointsFile", "skeletonTemplate"]) {
|
|
486
542
|
if (revision[field] !== void 0 && revision.mode !== "animate-skeleton") {
|
|
487
543
|
context.addIssue({
|
|
488
544
|
code: z.ZodIssueCode.custom,
|
|
@@ -1136,6 +1192,10 @@ var LockEntrySchema = z.object({
|
|
|
1136
1192
|
keypointsSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1137
1193
|
skeletonTemplate: z.string().min(1).optional(),
|
|
1138
1194
|
description: z.string().min(1).optional(),
|
|
1195
|
+
removalTask: z.enum(["simple", "complex"]).optional(),
|
|
1196
|
+
driftThreshold: z.number().min(0).optional(),
|
|
1197
|
+
subjectDescription: z.string().min(1).max(300).optional(),
|
|
1198
|
+
initialPose: z.string().min(1).max(300).optional(),
|
|
1139
1199
|
engine: z.enum(["pro-flash"]).optional()
|
|
1140
1200
|
}).strict().nullable().default(null),
|
|
1141
1201
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
@@ -1564,6 +1624,7 @@ var CorrectPixelartResponseSchema = z3.object({
|
|
|
1564
1624
|
images: z3.array(z3.object({ base64: z3.string().min(1) }).passthrough()).min(1),
|
|
1565
1625
|
usage: z3.unknown().optional()
|
|
1566
1626
|
}).passthrough();
|
|
1627
|
+
var RemoveBackgroundResponseSchema = z3.object({ image: z3.object({ base64: z3.string().min(1) }).passthrough(), usage: z3.unknown().optional() }).passthrough();
|
|
1567
1628
|
var ProFlashImageSubmitSchema = z3.object({
|
|
1568
1629
|
background_job_id: z3.string().min(1),
|
|
1569
1630
|
image_id: z3.string().min(1),
|
|
@@ -1755,9 +1816,9 @@ var PixelLabClient = class {
|
|
|
1755
1816
|
apiKey;
|
|
1756
1817
|
http;
|
|
1757
1818
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1758
|
-
async request(
|
|
1819
|
+
async request(path37, init) {
|
|
1759
1820
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1760
|
-
const res = await this.http(`${BASE}${
|
|
1821
|
+
const res = await this.http(`${BASE}${path37}`, {
|
|
1761
1822
|
...init,
|
|
1762
1823
|
headers: {
|
|
1763
1824
|
Authorization: auth,
|
|
@@ -1767,13 +1828,13 @@ var PixelLabClient = class {
|
|
|
1767
1828
|
});
|
|
1768
1829
|
const text = await res.text();
|
|
1769
1830
|
if (!res.ok) {
|
|
1770
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1831
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path37} \u2192 ${res.status}`, res.status, text);
|
|
1771
1832
|
}
|
|
1772
1833
|
if (!text) return {};
|
|
1773
1834
|
try {
|
|
1774
1835
|
return JSON.parse(text);
|
|
1775
1836
|
} catch {
|
|
1776
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1837
|
+
throw new Error(`${init?.method ?? "GET"} ${path37} returned invalid JSON`);
|
|
1777
1838
|
}
|
|
1778
1839
|
}
|
|
1779
1840
|
async balance() {
|
|
@@ -2178,10 +2239,10 @@ var PixelLabClient = class {
|
|
|
2178
2239
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2179
2240
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2180
2241
|
const south = args.reference?.south;
|
|
2181
|
-
let
|
|
2242
|
+
let path37;
|
|
2182
2243
|
let body;
|
|
2183
2244
|
if (args.mode === "v3") {
|
|
2184
|
-
|
|
2245
|
+
path37 = "/create-character-v3";
|
|
2185
2246
|
body = {
|
|
2186
2247
|
description: args.description,
|
|
2187
2248
|
image_size: imageSize,
|
|
@@ -2195,7 +2256,7 @@ var PixelLabClient = class {
|
|
|
2195
2256
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2196
2257
|
};
|
|
2197
2258
|
} else if (args.mode === "pro-flash") {
|
|
2198
|
-
|
|
2259
|
+
path37 = "/create-character-pro-flash";
|
|
2199
2260
|
const traits = args.styleTraits;
|
|
2200
2261
|
body = {
|
|
2201
2262
|
description: args.description,
|
|
@@ -2219,7 +2280,7 @@ var PixelLabClient = class {
|
|
|
2219
2280
|
} : {}
|
|
2220
2281
|
};
|
|
2221
2282
|
} else if (args.mode === "pro") {
|
|
2222
|
-
|
|
2283
|
+
path37 = "/create-character-pro";
|
|
2223
2284
|
body = {
|
|
2224
2285
|
description: args.description,
|
|
2225
2286
|
image_size: imageSize,
|
|
@@ -2233,7 +2294,7 @@ var PixelLabClient = class {
|
|
|
2233
2294
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2234
2295
|
};
|
|
2235
2296
|
} else {
|
|
2236
|
-
|
|
2297
|
+
path37 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2237
2298
|
body = {
|
|
2238
2299
|
description: args.description,
|
|
2239
2300
|
image_size: imageSize,
|
|
@@ -2250,8 +2311,8 @@ var PixelLabClient = class {
|
|
|
2250
2311
|
...palette
|
|
2251
2312
|
};
|
|
2252
2313
|
}
|
|
2253
|
-
const raw = await this.request(
|
|
2254
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2314
|
+
const raw = await this.request(path37, { method: "POST", body: JSON.stringify(body) });
|
|
2315
|
+
return validateResponse(CharacterSubmitSchema, raw, path37.slice(1));
|
|
2255
2316
|
}
|
|
2256
2317
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2257
2318
|
async createCharacterState(args) {
|
|
@@ -2628,6 +2689,27 @@ var PixelLabClient = class {
|
|
|
2628
2689
|
const pngs = cleanupResults(res.images, images.length, "correct-pixelart");
|
|
2629
2690
|
return { png: pngs[0], pngs, usage: res.usage };
|
|
2630
2691
|
}
|
|
2692
|
+
/**
|
|
2693
|
+
* `/remove-background`: a transparent PNG from one pixel-art image,
|
|
2694
|
+
* synchronously, like the rest of the Cleanup tier. The API takes at most
|
|
2695
|
+
* 400x400 worth of area, one image per call. `simple` is faster and suits
|
|
2696
|
+
* flat backgrounds; `complex` is slower and handles detailed edges. The
|
|
2697
|
+
* schema's response example is dollar-denominated (`usd: 0.01`); the
|
|
2698
|
+
* subscription cost measured 1 generation (docs/ENDPOINTS.md), and the
|
|
2699
|
+
* default `simple` task is the one measured.
|
|
2700
|
+
*/
|
|
2701
|
+
async removeBackground(args) {
|
|
2702
|
+
const body = { image: args.image, image_size: { width: args.width, height: args.height } };
|
|
2703
|
+
if (args.task) body.background_removal_task = args.task === "complex" ? "remove_complex_background" : "remove_simple_background";
|
|
2704
|
+
if (args.text) body.text = args.text;
|
|
2705
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2706
|
+
const res = validateResponse(
|
|
2707
|
+
RemoveBackgroundResponseSchema,
|
|
2708
|
+
await this.request("/remove-background", { method: "POST", body: JSON.stringify(body) }),
|
|
2709
|
+
"remove-background"
|
|
2710
|
+
);
|
|
2711
|
+
return { png: Buffer.from(res.image.base64, "base64"), usage: res.usage };
|
|
2712
|
+
}
|
|
2631
2713
|
/**
|
|
2632
2714
|
* `/unzoom`: recover native-resolution pixel art from an upscaled image (a
|
|
2633
2715
|
* 32x32 sprite saved at 512x512). Synchronous like the Cleanup tier.
|
|
@@ -2736,6 +2818,7 @@ var PixelLabClient = class {
|
|
|
2736
2818
|
*/
|
|
2737
2819
|
async animateWithTextV3(args) {
|
|
2738
2820
|
const body = { first_frame: args.firstFrame, action: args.action };
|
|
2821
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
2739
2822
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
2740
2823
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
2741
2824
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -2755,6 +2838,10 @@ var PixelLabClient = class {
|
|
|
2755
2838
|
*/
|
|
2756
2839
|
async animatePixminimax(args) {
|
|
2757
2840
|
const body = { first_frame: args.firstFrame, description: args.description };
|
|
2841
|
+
if (args.view) body.view = args.view;
|
|
2842
|
+
if (args.subjectDescription) body.subject_description = args.subjectDescription;
|
|
2843
|
+
if (args.initialPose) body.initial_pose = args.initialPose;
|
|
2844
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
2758
2845
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
2759
2846
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
2760
2847
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -2790,6 +2877,7 @@ var PixelLabClient = class {
|
|
|
2790
2877
|
keypoints: args.keypoints,
|
|
2791
2878
|
direction: args.direction
|
|
2792
2879
|
};
|
|
2880
|
+
if (args.view) body.view = args.view;
|
|
2793
2881
|
if (args.templateId) body.template_id = args.templateId;
|
|
2794
2882
|
if (args.action) body.action = args.action;
|
|
2795
2883
|
if (args.description) body.description = args.description;
|
|
@@ -3437,10 +3525,10 @@ import { createReadStream } from "fs";
|
|
|
3437
3525
|
function sha256(data) {
|
|
3438
3526
|
return createHash("sha256").update(data).digest("hex");
|
|
3439
3527
|
}
|
|
3440
|
-
async function sha256File(
|
|
3528
|
+
async function sha256File(path37) {
|
|
3441
3529
|
return new Promise((resolve, reject) => {
|
|
3442
3530
|
const hash = createHash("sha256");
|
|
3443
|
-
const stream = createReadStream(
|
|
3531
|
+
const stream = createReadStream(path37);
|
|
3444
3532
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3445
3533
|
stream.on("error", reject);
|
|
3446
3534
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3564,6 +3652,10 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3564
3652
|
keypointsSha256: spec.revision.keypointsSha256,
|
|
3565
3653
|
skeletonTemplate: spec.revision.skeletonTemplate,
|
|
3566
3654
|
description: spec.revision.description,
|
|
3655
|
+
removalTask: spec.revision.removalTask,
|
|
3656
|
+
driftThreshold: spec.revision.driftThreshold,
|
|
3657
|
+
subjectDescription: spec.revision.subjectDescription,
|
|
3658
|
+
initialPose: spec.revision.initialPose,
|
|
3567
3659
|
engine: spec.revision.engine
|
|
3568
3660
|
} : void 0
|
|
3569
3661
|
})
|
|
@@ -3739,7 +3831,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3739
3831
|
* outpaint path yet.
|
|
3740
3832
|
*/
|
|
3741
3833
|
supportsRevision(mode2) {
|
|
3742
|
-
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
3834
|
+
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "remove-background" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
3743
3835
|
}
|
|
3744
3836
|
/** PixelLab's own constraints: submissions must be >2s apart, and
|
|
3745
3837
|
* background jobs in flight are capped by subscription tier (Tier 1=8,
|
|
@@ -3767,6 +3859,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3767
3859
|
if (spec.batch?.role === "member") {
|
|
3768
3860
|
return { unit: "generations", amount: 0, candidates: 1 };
|
|
3769
3861
|
}
|
|
3862
|
+
if (spec.revision?.mode === "remove-background") {
|
|
3863
|
+
return { unit: "generations", amount: 1, candidates: 1 };
|
|
3864
|
+
}
|
|
3770
3865
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
3771
3866
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
3772
3867
|
}
|
|
@@ -3855,6 +3950,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3855
3950
|
throw new Error(`PixelLab correct-pixelart source is ${width}x${height}; the API takes at most 1024 pixels per side`);
|
|
3856
3951
|
}
|
|
3857
3952
|
}
|
|
3953
|
+
if (spec.revision?.mode === "remove-background") {
|
|
3954
|
+
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
3955
|
+
if (width != null && height != null && (width > 400 || height > 400)) {
|
|
3956
|
+
throw new Error(`PixelLab remove-background source is ${width}x${height}; the API takes at most 400x400`);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3858
3959
|
if (spec.revision?.mode === "animate-skeleton") {
|
|
3859
3960
|
const { sourceWidth: width, sourceHeight: height, frames, skeletonTemplate } = spec.revision;
|
|
3860
3961
|
if (width != null && height != null && (width > 256 || height > 256)) {
|
|
@@ -3875,6 +3976,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3875
3976
|
if (mode2 === "animate" && frames != null && frames > 16) {
|
|
3876
3977
|
throw new Error(`PixelLab animate takes 4 to 16 frames; ${frames} is too many (use animate-pixminimax for up to 40)`);
|
|
3877
3978
|
}
|
|
3979
|
+
if (mode2 === "animate-pixminimax" && frames != null && frames % 4 !== 0) {
|
|
3980
|
+
throw new Error(`PixelLab animate-pixminimax takes a multiple of 4 frames (4 to 40); ${frames} is not one`);
|
|
3981
|
+
}
|
|
3878
3982
|
if (lastFrameWidth != null && lastFrameHeight != null && width != null && height != null && (lastFrameWidth !== width || lastFrameHeight !== height)) {
|
|
3879
3983
|
throw new Error(
|
|
3880
3984
|
`PixelLab ${mode2} lastFrame is ${lastFrameWidth}x${lastFrameHeight}; source is ${width}x${height} \u2014 they must match`
|
|
@@ -4966,6 +5070,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4966
5070
|
const res2 = await this.client.correctPixelart({ images, strength: revision.strength });
|
|
4967
5071
|
return this.cacheCleanupResult(res2.pngs, res2.usage, roles, revision.sourceFps);
|
|
4968
5072
|
}
|
|
5073
|
+
if (revision.mode === "remove-background") {
|
|
5074
|
+
if (width == null || height == null) throw new Error(`${spec.styleId}/${spec.assetId}: remove-background needs the source's size`);
|
|
5075
|
+
const res2 = await this.client.removeBackground({
|
|
5076
|
+
image,
|
|
5077
|
+
width,
|
|
5078
|
+
height,
|
|
5079
|
+
task: revision.removalTask,
|
|
5080
|
+
text: revision.description,
|
|
5081
|
+
seed: spec.seed
|
|
5082
|
+
});
|
|
5083
|
+
return this.cacheCleanupResult([res2.png], res2.usage, roles, revision.sourceFps);
|
|
5084
|
+
}
|
|
4969
5085
|
if (revision.mode === "interpolate") {
|
|
4970
5086
|
const lastFrame = this.readLastFrame(spec);
|
|
4971
5087
|
if (!lastFrame) throw new Error(`${spec.styleId}/${spec.assetId}: interpolate needs lastFrame, the ending keyframe`);
|
|
@@ -5001,7 +5117,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5001
5117
|
frameCount: revision.frames,
|
|
5002
5118
|
seed: spec.seed,
|
|
5003
5119
|
noBackground: spec.noBackground,
|
|
5004
|
-
enhancePrompt: revision.enhancePrompt
|
|
5120
|
+
enhancePrompt: revision.enhancePrompt,
|
|
5121
|
+
driftThreshold: revision.driftThreshold
|
|
5005
5122
|
});
|
|
5006
5123
|
return { jobId: res3.background_job_id };
|
|
5007
5124
|
}
|
|
@@ -5013,7 +5130,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5013
5130
|
seed: spec.seed,
|
|
5014
5131
|
noBackground: spec.noBackground,
|
|
5015
5132
|
enhancePrompt: revision.enhancePrompt,
|
|
5016
|
-
direction: revision.direction
|
|
5133
|
+
direction: revision.direction,
|
|
5134
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5135
|
+
subjectDescription: revision.subjectDescription,
|
|
5136
|
+
initialPose: revision.initialPose,
|
|
5137
|
+
driftThreshold: revision.driftThreshold
|
|
5017
5138
|
});
|
|
5018
5139
|
return { jobId: res2.background_job_id };
|
|
5019
5140
|
}
|
|
@@ -5030,6 +5151,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5030
5151
|
firstFrameKeypoints: revision.skeleton.firstFrameKeypoints,
|
|
5031
5152
|
keypoints: revision.skeleton.frames,
|
|
5032
5153
|
direction: revision.direction,
|
|
5154
|
+
// The style's camera, when it is one this endpoint knows; its enum is
|
|
5155
|
+
// narrower than a style's free-string `view`.
|
|
5156
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5033
5157
|
templateId: revision.skeletonTemplate,
|
|
5034
5158
|
action: spec.prompt,
|
|
5035
5159
|
description: revision.description,
|
|
@@ -5100,7 +5224,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5100
5224
|
}
|
|
5101
5225
|
async poll(jobId, generator, context) {
|
|
5102
5226
|
const revisionMode = context?.spec?.revision?.mode;
|
|
5103
|
-
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
|
|
5227
|
+
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart" || revisionMode === "remove-background") {
|
|
5104
5228
|
return this.pollCachedRevision(jobId, context);
|
|
5105
5229
|
}
|
|
5106
5230
|
if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "animate-skeleton" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
|
|
@@ -5808,9 +5932,9 @@ var RetroDiffusionClient = class {
|
|
|
5808
5932
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
5809
5933
|
return Buffer.from(await response.arrayBuffer());
|
|
5810
5934
|
}
|
|
5811
|
-
async call(
|
|
5935
|
+
async call(path37, init = {}) {
|
|
5812
5936
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5813
|
-
const response = await this.request(`${this.baseUrl}${
|
|
5937
|
+
const response = await this.request(`${this.baseUrl}${path37}`, {
|
|
5814
5938
|
...init,
|
|
5815
5939
|
headers: {
|
|
5816
5940
|
"Content-Type": "application/json",
|
|
@@ -8816,6 +8940,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8816
8940
|
} : {},
|
|
8817
8941
|
...asset.revision.skeletonTemplate ? { skeletonTemplate: asset.revision.skeletonTemplate } : {},
|
|
8818
8942
|
...asset.revision.description ? { description: asset.revision.description } : {},
|
|
8943
|
+
...asset.revision.removalTask ? { removalTask: asset.revision.removalTask } : {},
|
|
8944
|
+
...asset.revision.driftThreshold == null ? {} : { driftThreshold: asset.revision.driftThreshold },
|
|
8945
|
+
...asset.revision.subjectDescription ? { subjectDescription: asset.revision.subjectDescription } : {},
|
|
8946
|
+
...asset.revision.initialPose ? { initialPose: asset.revision.initialPose } : {},
|
|
8819
8947
|
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
8820
8948
|
};
|
|
8821
8949
|
}
|
|
@@ -12099,10 +12227,10 @@ function isSha256Hash(value) {
|
|
|
12099
12227
|
function parseCache(value) {
|
|
12100
12228
|
return HashCacheSchema.parse(value);
|
|
12101
12229
|
}
|
|
12102
|
-
async function loadCache(
|
|
12103
|
-
if (!existsSync13(
|
|
12230
|
+
async function loadCache(path37) {
|
|
12231
|
+
if (!existsSync13(path37)) return { version: 1, hashes: {} };
|
|
12104
12232
|
try {
|
|
12105
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(
|
|
12233
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path37, "utf8")));
|
|
12106
12234
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12107
12235
|
return {
|
|
12108
12236
|
version: 1,
|
|
@@ -12114,18 +12242,18 @@ async function loadCache(path36) {
|
|
|
12114
12242
|
return { version: 1, hashes: {} };
|
|
12115
12243
|
}
|
|
12116
12244
|
}
|
|
12117
|
-
async function saveCache(
|
|
12245
|
+
async function saveCache(path37, cache) {
|
|
12118
12246
|
const sorted = {};
|
|
12119
12247
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12120
12248
|
const hash = cache.hashes[key];
|
|
12121
12249
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12122
12250
|
sorted[key] = hash;
|
|
12123
12251
|
}
|
|
12124
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12125
|
-
const tmp = `${
|
|
12252
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path37)), { recursive: true });
|
|
12253
|
+
const tmp = `${path37}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12126
12254
|
try {
|
|
12127
12255
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12128
|
-
await rename4(tmp,
|
|
12256
|
+
await rename4(tmp, path37);
|
|
12129
12257
|
} finally {
|
|
12130
12258
|
await rm4(tmp, { force: true });
|
|
12131
12259
|
}
|
|
@@ -13641,6 +13769,10 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13641
13769
|
...spec.revision.keypointsSha256 ? { keypointsSha256: spec.revision.keypointsSha256 } : {},
|
|
13642
13770
|
...spec.revision.skeletonTemplate ? { skeletonTemplate: spec.revision.skeletonTemplate } : {},
|
|
13643
13771
|
...spec.revision.description ? { description: spec.revision.description } : {},
|
|
13772
|
+
...spec.revision.removalTask ? { removalTask: spec.revision.removalTask } : {},
|
|
13773
|
+
...spec.revision.driftThreshold == null ? {} : { driftThreshold: spec.revision.driftThreshold },
|
|
13774
|
+
...spec.revision.subjectDescription ? { subjectDescription: spec.revision.subjectDescription } : {},
|
|
13775
|
+
...spec.revision.initialPose ? { initialPose: spec.revision.initialPose } : {},
|
|
13644
13776
|
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
13645
13777
|
} : null,
|
|
13646
13778
|
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
@@ -14930,9 +15062,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
14930
15062
|
|
|
14931
15063
|
// src/gallery/snapshot.ts
|
|
14932
15064
|
import { createHash as createHash4 } from "crypto";
|
|
14933
|
-
import { existsSync as
|
|
14934
|
-
import { readFile as
|
|
14935
|
-
import
|
|
15065
|
+
import { existsSync as existsSync26 } from "fs";
|
|
15066
|
+
import { readFile as readFile24, stat as stat3 } from "fs/promises";
|
|
15067
|
+
import path32 from "path";
|
|
14936
15068
|
|
|
14937
15069
|
// src/workspace.ts
|
|
14938
15070
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
|
|
@@ -15068,15 +15200,16 @@ function validateWorkspace(ws, dir) {
|
|
|
15068
15200
|
}
|
|
15069
15201
|
|
|
15070
15202
|
// src/gallery/edit.ts
|
|
15071
|
-
import { readFile as
|
|
15072
|
-
import
|
|
15073
|
-
import { z as
|
|
15203
|
+
import { readFile as readFile23, rename as rename9, writeFile as writeFile15 } from "fs/promises";
|
|
15204
|
+
import path31 from "path";
|
|
15205
|
+
import { z as z13 } from "zod";
|
|
15074
15206
|
|
|
15075
15207
|
// src/manifest-edit.ts
|
|
15076
15208
|
import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
|
|
15077
15209
|
import path26 from "path";
|
|
15078
15210
|
import { z as z8 } from "zod";
|
|
15079
15211
|
var HexSha = z8.string().regex(/^[0-9a-f]{64}$/);
|
|
15212
|
+
var ProjectFileSchema = z8.string().min(1).refine((file) => !path26.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15080
15213
|
var AssetPatchSchema = z8.object({
|
|
15081
15214
|
prompt: z8.string().optional(),
|
|
15082
15215
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -15125,10 +15258,11 @@ var NewAssetSchema = z8.object({
|
|
|
15125
15258
|
styles: z8.array(z8.string().min(1)).optional(),
|
|
15126
15259
|
/**
|
|
15127
15260
|
* A controlled regeneration of another asset in the same manifest,
|
|
15128
|
-
* instead of a fresh generation.
|
|
15129
|
-
* `
|
|
15130
|
-
*
|
|
15131
|
-
*
|
|
15261
|
+
* instead of a fresh generation. Every mode but `outpaint` can be
|
|
15262
|
+
* created here: `inpaint` names a mask file the gallery's mask editor
|
|
15263
|
+
* wrote first, `interpolate` names an existing image as its ending
|
|
15264
|
+
* keyframe, and `animate-skeleton` names the keypoints file the gallery
|
|
15265
|
+
* wrote first.
|
|
15132
15266
|
*/
|
|
15133
15267
|
revision: z8.discriminatedUnion("mode", [
|
|
15134
15268
|
z8.object({
|
|
@@ -15145,15 +15279,38 @@ var NewAssetSchema = z8.object({
|
|
|
15145
15279
|
dithering: RevisionDitheringSchema.optional(),
|
|
15146
15280
|
ditheringStrength: z8.number().min(0).max(10).optional()
|
|
15147
15281
|
}).strict(),
|
|
15282
|
+
z8.object({
|
|
15283
|
+
mode: z8.literal("inpaint"),
|
|
15284
|
+
from: z8.string().min(1),
|
|
15285
|
+
/** Black-and-white PNG the same size as the parent; white is repainted. */
|
|
15286
|
+
mask: ProjectFileSchema
|
|
15287
|
+
}).strict(),
|
|
15288
|
+
z8.object({
|
|
15289
|
+
mode: z8.literal("interpolate"),
|
|
15290
|
+
from: z8.string().min(1),
|
|
15291
|
+
/** The ending keyframe: an image already in the project, the parent's size. */
|
|
15292
|
+
lastFrame: ProjectFileSchema,
|
|
15293
|
+
fps: z8.number().int().min(1).max(60).optional()
|
|
15294
|
+
}).strict(),
|
|
15148
15295
|
z8.object({ mode: z8.literal("correct-pixelart"), from: z8.string().min(1), strength: z8.number().min(0).max(1).optional() }).strict(),
|
|
15296
|
+
z8.object({
|
|
15297
|
+
mode: z8.literal("remove-background"),
|
|
15298
|
+
from: z8.string().min(1),
|
|
15299
|
+
removalTask: z8.enum(["simple", "complex"]).optional(),
|
|
15300
|
+
/** A short description of the foreground, sent as PixelLab's text hint. */
|
|
15301
|
+
description: z8.string().min(1).optional()
|
|
15302
|
+
}).strict(),
|
|
15149
15303
|
z8.object({
|
|
15150
15304
|
mode: z8.enum(["animate", "animate-pixminimax"]),
|
|
15151
15305
|
from: z8.string().min(1),
|
|
15152
15306
|
frames: z8.number().int().min(4).max(40).refine((n) => n % 2 === 0, "frames must be even").optional(),
|
|
15153
15307
|
fps: z8.number().int().min(1).max(60).optional(),
|
|
15154
15308
|
enhancePrompt: z8.boolean().optional(),
|
|
15155
|
-
|
|
15156
|
-
|
|
15309
|
+
driftThreshold: z8.number().min(0).optional(),
|
|
15310
|
+
/** `animate-pixminimax` only; the loader refuses these on `animate`. */
|
|
15311
|
+
direction: CharacterDirectionSchema.optional(),
|
|
15312
|
+
subjectDescription: z8.string().min(1).max(300).optional(),
|
|
15313
|
+
initialPose: z8.string().min(1).max(300).optional()
|
|
15157
15314
|
}).strict(),
|
|
15158
15315
|
z8.object({
|
|
15159
15316
|
mode: z8.literal("edit-animation"),
|
|
@@ -15804,62 +15961,118 @@ async function saveProjectImage(root, request) {
|
|
|
15804
15961
|
return relative.split(path28.sep).join("/");
|
|
15805
15962
|
}
|
|
15806
15963
|
|
|
15807
|
-
// src/gallery/
|
|
15964
|
+
// src/gallery/image-revision.ts
|
|
15808
15965
|
import { existsSync as existsSync24 } from "fs";
|
|
15809
|
-
import {
|
|
15966
|
+
import { readFile as readFile21, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
15810
15967
|
import path29 from "path";
|
|
15811
15968
|
import { z as z11 } from "zod";
|
|
15812
|
-
var
|
|
15813
|
-
|
|
15814
|
-
styleId: z11.string().min(1),
|
|
15815
|
-
assetId: z11.string().min(1),
|
|
15816
|
-
frames: z11.number().int().min(3).max(15).optional()
|
|
15817
|
-
}).strict();
|
|
15818
|
-
var SkeletonAnimationRequestSchema = z11.object({
|
|
15819
|
-
action: z11.literal("create-skeleton-animation"),
|
|
15969
|
+
var InpaintRevisionRequestSchema = z11.object({
|
|
15970
|
+
action: z11.literal("create-inpaint-revision"),
|
|
15820
15971
|
project: z11.string().min(1).optional(),
|
|
15821
15972
|
expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/),
|
|
15822
15973
|
styleId: z11.string().min(1),
|
|
15823
|
-
/** The sprite
|
|
15974
|
+
/** The sprite being repainted. */
|
|
15824
15975
|
from: z11.string().min(1),
|
|
15825
15976
|
assetId: z11.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15826
|
-
/**
|
|
15977
|
+
/** What to paint into the masked area. */
|
|
15827
15978
|
prompt: z11.string().min(1),
|
|
15979
|
+
/** Manifest-relative `.png` destination for the mask. */
|
|
15980
|
+
maskPath: z11.string().min(1),
|
|
15981
|
+
maskBase64: z11.string().regex(/^[A-Za-z0-9+/]*={0,2}$/),
|
|
15982
|
+
/** Replace a file already at `maskPath`. */
|
|
15983
|
+
overwrite: z11.boolean().optional()
|
|
15984
|
+
}).strict();
|
|
15985
|
+
async function createInpaintRevision(manifestPath, request) {
|
|
15986
|
+
const root = path29.dirname(path29.resolve(manifestPath));
|
|
15987
|
+
if (path29.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
15988
|
+
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
15989
|
+
}
|
|
15990
|
+
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
15991
|
+
const metadata = imageMetadata(bytes);
|
|
15992
|
+
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
15993
|
+
const file = path29.resolve(root, request.maskPath);
|
|
15994
|
+
const previous = existsSync24(file) ? await readFile21(file) : null;
|
|
15995
|
+
const mask = await saveProjectImage(root, {
|
|
15996
|
+
action: "upload-image",
|
|
15997
|
+
path: request.maskPath,
|
|
15998
|
+
base64: request.maskBase64,
|
|
15999
|
+
...request.overwrite ? { overwrite: true } : {}
|
|
16000
|
+
});
|
|
16001
|
+
try {
|
|
16002
|
+
await applyManifestEdit(manifestPath, {
|
|
16003
|
+
action: "add-asset",
|
|
16004
|
+
assetId: request.assetId,
|
|
16005
|
+
expectedSha256: request.expectedSha256,
|
|
16006
|
+
asset: {
|
|
16007
|
+
prompt: request.prompt,
|
|
16008
|
+
styles: [request.styleId],
|
|
16009
|
+
revision: { mode: "inpaint", from: request.from, mask }
|
|
16010
|
+
}
|
|
16011
|
+
});
|
|
16012
|
+
} catch (error) {
|
|
16013
|
+
if (previous) await writeFile13(file, previous);
|
|
16014
|
+
else await rm10(file, { force: true });
|
|
16015
|
+
throw error;
|
|
16016
|
+
}
|
|
16017
|
+
return mask;
|
|
16018
|
+
}
|
|
16019
|
+
|
|
16020
|
+
// src/gallery/skeleton.ts
|
|
16021
|
+
import { existsSync as existsSync25 } from "fs";
|
|
16022
|
+
import { mkdir as mkdir10, readFile as readFile22, rm as rm11, writeFile as writeFile14 } from "fs/promises";
|
|
16023
|
+
import path30 from "path";
|
|
16024
|
+
import { z as z12 } from "zod";
|
|
16025
|
+
var SkeletonEstimateRequestSchema = z12.object({
|
|
16026
|
+
project: z12.string().min(1).optional(),
|
|
16027
|
+
styleId: z12.string().min(1),
|
|
16028
|
+
assetId: z12.string().min(1),
|
|
16029
|
+
frames: z12.number().int().min(3).max(15).optional()
|
|
16030
|
+
}).strict();
|
|
16031
|
+
var SkeletonAnimationRequestSchema = z12.object({
|
|
16032
|
+
action: z12.literal("create-skeleton-animation"),
|
|
16033
|
+
project: z12.string().min(1).optional(),
|
|
16034
|
+
expectedSha256: z12.string().regex(/^[0-9a-f]{64}$/),
|
|
16035
|
+
styleId: z12.string().min(1),
|
|
16036
|
+
/** The sprite the animation poses. */
|
|
16037
|
+
from: z12.string().min(1),
|
|
16038
|
+
assetId: z12.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16039
|
+
/** The motion, sent as PixelLab's `action`. */
|
|
16040
|
+
prompt: z12.string().min(1),
|
|
15828
16041
|
direction: CharacterDirectionSchema,
|
|
15829
|
-
description:
|
|
15830
|
-
skeletonTemplate:
|
|
16042
|
+
description: z12.string().min(1).optional(),
|
|
16043
|
+
skeletonTemplate: z12.string().min(1).optional(),
|
|
15831
16044
|
/** Manifest-relative JSON path inside the project. */
|
|
15832
|
-
keypointsFile:
|
|
16045
|
+
keypointsFile: z12.string().min(1),
|
|
15833
16046
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
15834
16047
|
set: SkeletonSetSchema.optional(),
|
|
15835
16048
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
15836
|
-
overwrite:
|
|
16049
|
+
overwrite: z12.boolean().optional()
|
|
15837
16050
|
}).strict();
|
|
15838
|
-
var SkeletonKeypointsUpdateSchema =
|
|
15839
|
-
action:
|
|
15840
|
-
project:
|
|
15841
|
-
styleId:
|
|
15842
|
-
assetId:
|
|
16051
|
+
var SkeletonKeypointsUpdateSchema = z12.object({
|
|
16052
|
+
action: z12.literal("update-skeleton-keypoints"),
|
|
16053
|
+
project: z12.string().min(1).optional(),
|
|
16054
|
+
styleId: z12.string().min(1),
|
|
16055
|
+
assetId: z12.string().min(1),
|
|
15843
16056
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
15844
|
-
expectedSha256:
|
|
16057
|
+
expectedSha256: z12.string().regex(/^[0-9a-f]{64}$/),
|
|
15845
16058
|
set: SkeletonSetSchema
|
|
15846
16059
|
}).strict();
|
|
15847
16060
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
15848
|
-
const root =
|
|
15849
|
-
const file =
|
|
15850
|
-
const relative =
|
|
15851
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16061
|
+
const root = path30.dirname(path30.resolve(manifestPath));
|
|
16062
|
+
const file = path30.resolve(root, request.keypointsFile);
|
|
16063
|
+
const relative = path30.relative(root, file);
|
|
16064
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path30.isAbsolute(relative)) {
|
|
15852
16065
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15853
16066
|
}
|
|
15854
|
-
const existed =
|
|
16067
|
+
const existed = existsSync25(file);
|
|
15855
16068
|
let previous = null;
|
|
15856
16069
|
if (request.set) {
|
|
15857
16070
|
if (existed && !request.overwrite) {
|
|
15858
16071
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15859
16072
|
}
|
|
15860
|
-
if (existed) previous = await
|
|
15861
|
-
await mkdir10(
|
|
15862
|
-
await
|
|
16073
|
+
if (existed) previous = await readFile22(file);
|
|
16074
|
+
await mkdir10(path30.dirname(file), { recursive: true });
|
|
16075
|
+
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15863
16076
|
} else if (!existed) {
|
|
15864
16077
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
15865
16078
|
}
|
|
@@ -15874,7 +16087,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15874
16087
|
revision: {
|
|
15875
16088
|
mode: "animate-skeleton",
|
|
15876
16089
|
from: request.from,
|
|
15877
|
-
keypointsFile: relative.split(
|
|
16090
|
+
keypointsFile: relative.split(path30.sep).join("/"),
|
|
15878
16091
|
direction: request.direction,
|
|
15879
16092
|
...request.description ? { description: request.description } : {},
|
|
15880
16093
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -15883,8 +16096,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15883
16096
|
});
|
|
15884
16097
|
} catch (error) {
|
|
15885
16098
|
if (request.set) {
|
|
15886
|
-
if (previous) await
|
|
15887
|
-
else await
|
|
16099
|
+
if (previous) await writeFile14(file, previous);
|
|
16100
|
+
else await rm11(file, { force: true });
|
|
15888
16101
|
}
|
|
15889
16102
|
throw error;
|
|
15890
16103
|
}
|
|
@@ -15895,64 +16108,64 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
15895
16108
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
15896
16109
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
15897
16110
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
15898
|
-
const relative =
|
|
15899
|
-
if (relative.startsWith("..") ||
|
|
16111
|
+
const relative = path30.relative(ctx.loaded.root, file);
|
|
16112
|
+
if (relative.startsWith("..") || path30.isAbsolute(relative)) {
|
|
15900
16113
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
15901
16114
|
}
|
|
15902
|
-
const current =
|
|
16115
|
+
const current = existsSync25(file) ? sha256(await readFile22(file)) : null;
|
|
15903
16116
|
if (current !== request.expectedSha256) {
|
|
15904
16117
|
throw Object.assign(
|
|
15905
|
-
new Error(`${relative.split(
|
|
16118
|
+
new Error(`${relative.split(path30.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
15906
16119
|
{ status: 409 }
|
|
15907
16120
|
);
|
|
15908
16121
|
}
|
|
15909
|
-
await
|
|
15910
|
-
return relative.split(
|
|
16122
|
+
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16123
|
+
return relative.split(path30.sep).join("/");
|
|
15911
16124
|
}
|
|
15912
16125
|
|
|
15913
16126
|
// src/gallery/edit.ts
|
|
15914
|
-
var Base64 =
|
|
15915
|
-
var HandEditRequestSchema =
|
|
15916
|
-
|
|
15917
|
-
action:
|
|
15918
|
-
project:
|
|
15919
|
-
assetId:
|
|
15920
|
-
styleId:
|
|
15921
|
-
expectedSha256:
|
|
16127
|
+
var Base64 = z13.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
16128
|
+
var HandEditRequestSchema = z13.discriminatedUnion("action", [
|
|
16129
|
+
z13.object({
|
|
16130
|
+
action: z13.literal("start-edit"),
|
|
16131
|
+
project: z13.string().min(1).optional(),
|
|
16132
|
+
assetId: z13.string().min(1),
|
|
16133
|
+
styleId: z13.string().min(1),
|
|
16134
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/),
|
|
15922
16135
|
/** Also launch the author's editor on the file. */
|
|
15923
|
-
open:
|
|
16136
|
+
open: z13.boolean().optional()
|
|
15924
16137
|
}).strict(),
|
|
15925
|
-
|
|
15926
|
-
action:
|
|
15927
|
-
project:
|
|
15928
|
-
assetId:
|
|
15929
|
-
styleId:
|
|
15930
|
-
expectedSha256:
|
|
16138
|
+
z13.object({
|
|
16139
|
+
action: z13.literal("detach-edit"),
|
|
16140
|
+
project: z13.string().min(1).optional(),
|
|
16141
|
+
assetId: z13.string().min(1),
|
|
16142
|
+
styleId: z13.string().min(1),
|
|
16143
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/)
|
|
15931
16144
|
}).strict(),
|
|
15932
16145
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
15933
|
-
|
|
15934
|
-
action:
|
|
15935
|
-
project:
|
|
15936
|
-
assetId:
|
|
15937
|
-
styleId:
|
|
15938
|
-
expectedSha256:
|
|
16146
|
+
z13.object({
|
|
16147
|
+
action: z13.literal("save-edit"),
|
|
16148
|
+
project: z13.string().min(1).optional(),
|
|
16149
|
+
assetId: z13.string().min(1),
|
|
16150
|
+
styleId: z13.string().min(1),
|
|
16151
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/),
|
|
15939
16152
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
15940
16153
|
png: Base64.min(1).optional(),
|
|
15941
16154
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
15942
|
-
frames:
|
|
16155
|
+
frames: z13.array(z13.object({ role: z13.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
15943
16156
|
/** The editor's layered project file, kept beside the edit. */
|
|
15944
16157
|
pxo: Base64.optional(),
|
|
15945
16158
|
/** Editor identity from the bridge's ready message. */
|
|
15946
|
-
editor:
|
|
15947
|
-
protocol:
|
|
16159
|
+
editor: z13.string().min(1).max(200),
|
|
16160
|
+
protocol: z13.number().int().positive()
|
|
15948
16161
|
}).strict()
|
|
15949
16162
|
]);
|
|
15950
16163
|
var UNDO_DEPTH = 20;
|
|
15951
|
-
var UndoRequestSchema =
|
|
15952
|
-
action:
|
|
15953
|
-
project:
|
|
16164
|
+
var UndoRequestSchema = z13.object({
|
|
16165
|
+
action: z13.literal("undo-edit"),
|
|
16166
|
+
project: z13.string().min(1).optional(),
|
|
15954
16167
|
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
15955
|
-
expectedSha256:
|
|
16168
|
+
expectedSha256: z13.string().regex(/^[0-9a-f]{64}$/)
|
|
15956
16169
|
}).strict();
|
|
15957
16170
|
function createGalleryEditHandler(opts) {
|
|
15958
16171
|
const log = opts.onProgress ?? (() => {
|
|
@@ -15961,7 +16174,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15961
16174
|
const history = /* @__PURE__ */ new Map();
|
|
15962
16175
|
const manifestOf = async (project) => {
|
|
15963
16176
|
try {
|
|
15964
|
-
return
|
|
16177
|
+
return path31.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
15965
16178
|
} catch {
|
|
15966
16179
|
return null;
|
|
15967
16180
|
}
|
|
@@ -15971,7 +16184,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15971
16184
|
if (undo.success) {
|
|
15972
16185
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
15973
16186
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
15974
|
-
const current = await
|
|
16187
|
+
const current = await readFile23(manifestPath2, "utf8");
|
|
15975
16188
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
15976
16189
|
const stack = history.get(manifestPath2) ?? [];
|
|
15977
16190
|
const last = stack[stack.length - 1];
|
|
@@ -15981,17 +16194,17 @@ function createGalleryEditHandler(opts) {
|
|
|
15981
16194
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
15982
16195
|
}
|
|
15983
16196
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
15984
|
-
await
|
|
16197
|
+
await writeFile15(tmp, last.before);
|
|
15985
16198
|
await rename9(tmp, manifestPath2);
|
|
15986
16199
|
stack.pop();
|
|
15987
|
-
log(` manifest edit undone in ${
|
|
16200
|
+
log(` manifest edit undone in ${path31.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
15988
16201
|
return opts.reload();
|
|
15989
16202
|
}
|
|
15990
16203
|
const manifestPath = await manifestOf(body?.project);
|
|
15991
|
-
const before = manifestPath ? await
|
|
16204
|
+
const before = manifestPath ? await readFile23(manifestPath, "utf8").catch(() => null) : null;
|
|
15992
16205
|
const build = await apply(body);
|
|
15993
16206
|
if (manifestPath && before !== null) {
|
|
15994
|
-
const after = await
|
|
16207
|
+
const after = await readFile23(manifestPath, "utf8").catch(() => null);
|
|
15995
16208
|
if (after !== null && after !== before) {
|
|
15996
16209
|
const stack = history.get(manifestPath) ?? [];
|
|
15997
16210
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16046,7 +16259,7 @@ function createEditApplier(opts, log) {
|
|
|
16046
16259
|
} catch (error) {
|
|
16047
16260
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16048
16261
|
}
|
|
16049
|
-
const written = await saveProjectImage(
|
|
16262
|
+
const written = await saveProjectImage(path31.dirname(path31.resolve(manifestPath2)), upload.data);
|
|
16050
16263
|
log(` image added: ${written}`);
|
|
16051
16264
|
return opts.reload();
|
|
16052
16265
|
}
|
|
@@ -16082,6 +16295,23 @@ function createEditApplier(opts, log) {
|
|
|
16082
16295
|
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16083
16296
|
);
|
|
16084
16297
|
}
|
|
16298
|
+
const inpaint = InpaintRevisionRequestSchema.safeParse(body);
|
|
16299
|
+
if (inpaint.success) {
|
|
16300
|
+
let manifestPath2;
|
|
16301
|
+
try {
|
|
16302
|
+
manifestPath2 = await opts.manifestFor(inpaint.data.project);
|
|
16303
|
+
} catch (error) {
|
|
16304
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16305
|
+
}
|
|
16306
|
+
const mask = await createInpaintRevision(manifestPath2, inpaint.data);
|
|
16307
|
+
log(` manifest edited: added inpaint revision ${inpaint.data.assetId} (mask ${mask})`);
|
|
16308
|
+
return opts.reload();
|
|
16309
|
+
}
|
|
16310
|
+
if (body?.action === "create-inpaint-revision") {
|
|
16311
|
+
throw new ManifestEditError(
|
|
16312
|
+
"invalid inpaint revision: " + inpaint.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16313
|
+
);
|
|
16314
|
+
}
|
|
16085
16315
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
16086
16316
|
if (!parsed.success) {
|
|
16087
16317
|
throw new ManifestEditError(
|
|
@@ -16096,7 +16326,7 @@ function createEditApplier(opts, log) {
|
|
|
16096
16326
|
}
|
|
16097
16327
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16098
16328
|
if (result.changed) {
|
|
16099
|
-
const relative =
|
|
16329
|
+
const relative = path31.relative(process.cwd(), result.manifestPath);
|
|
16100
16330
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16101
16331
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16102
16332
|
}
|
|
@@ -16124,7 +16354,7 @@ function describeEdit(edit) {
|
|
|
16124
16354
|
// src/gallery/snapshot.ts
|
|
16125
16355
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16126
16356
|
function galleryMediaId(absolutePath) {
|
|
16127
|
-
return createHash4("sha256").update(
|
|
16357
|
+
return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16128
16358
|
}
|
|
16129
16359
|
function galleryMediaRoute(id) {
|
|
16130
16360
|
return `/media/${id}`;
|
|
@@ -16143,9 +16373,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16143
16373
|
return (entry.history ?? []).map((generation, i) => {
|
|
16144
16374
|
const outputs = generation.outputs.map((output) => {
|
|
16145
16375
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16146
|
-
const file =
|
|
16376
|
+
const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16147
16377
|
let url = null;
|
|
16148
|
-
if (
|
|
16378
|
+
if (existsSync26(file)) {
|
|
16149
16379
|
const id = galleryMediaId(file);
|
|
16150
16380
|
media.set(id, { path: file, contentType: mediaType });
|
|
16151
16381
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16177,7 +16407,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16177
16407
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16178
16408
|
async function samePixels(a, b) {
|
|
16179
16409
|
try {
|
|
16180
|
-
const [left, right] = await Promise.all([
|
|
16410
|
+
const [left, right] = await Promise.all([readFile24(a), readFile24(b)]);
|
|
16181
16411
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16182
16412
|
const x = decodePng(left);
|
|
16183
16413
|
const y = decodePng(right);
|
|
@@ -16275,10 +16505,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16275
16505
|
async function describeQuality(media, root, inspection) {
|
|
16276
16506
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16277
16507
|
const outputs = await Promise.all(
|
|
16278
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16508
|
+
outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
|
|
16279
16509
|
);
|
|
16280
|
-
const recordPath =
|
|
16281
|
-
const recordExists =
|
|
16510
|
+
const recordPath = path32.resolve(root, inspection.record);
|
|
16511
|
+
const recordExists = existsSync26(recordPath);
|
|
16282
16512
|
let options = null;
|
|
16283
16513
|
let check = null;
|
|
16284
16514
|
if (recordExists) {
|
|
@@ -16297,7 +16527,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16297
16527
|
return {
|
|
16298
16528
|
state: inspection.state,
|
|
16299
16529
|
reason: inspection.reason,
|
|
16300
|
-
output: portableOutputPath(
|
|
16530
|
+
output: portableOutputPath(path32.resolve(root, inspection.output), root),
|
|
16301
16531
|
record: portableOutputPath(recordPath, root),
|
|
16302
16532
|
recordExists,
|
|
16303
16533
|
outputs,
|
|
@@ -16335,9 +16565,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16335
16565
|
const media = /* @__PURE__ */ new Map();
|
|
16336
16566
|
const plan = await buildPlan(specs, lock);
|
|
16337
16567
|
const items = [];
|
|
16338
|
-
const manifestText = await
|
|
16568
|
+
const manifestText = await readFile24(loaded.path, "utf8");
|
|
16339
16569
|
const manifestSha256 = sha256(manifestText);
|
|
16340
|
-
const cacheDir =
|
|
16570
|
+
const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16341
16571
|
let rawStyles = {};
|
|
16342
16572
|
try {
|
|
16343
16573
|
const raw = JSON.parse(manifestText);
|
|
@@ -16361,12 +16591,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16361
16591
|
)
|
|
16362
16592
|
);
|
|
16363
16593
|
if (spec.source) {
|
|
16364
|
-
const editPath =
|
|
16594
|
+
const editPath = path32.resolve(root, spec.source);
|
|
16365
16595
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16366
16596
|
const editShas = await Promise.all(
|
|
16367
16597
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16368
16598
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16369
|
-
return { file, sha256:
|
|
16599
|
+
return { file, sha256: existsSync26(file) ? await sha256File(file) : null };
|
|
16370
16600
|
})
|
|
16371
16601
|
);
|
|
16372
16602
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16374,7 +16604,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16374
16604
|
const companion = await readHandEditCompanion(editPath);
|
|
16375
16605
|
if (companion) {
|
|
16376
16606
|
const projectPath = handEditProjectPath(editPath);
|
|
16377
|
-
const project = companion.project &&
|
|
16607
|
+
const project = companion.project && existsSync26(projectPath) ? await fileInfo(projectPath) : null;
|
|
16378
16608
|
let projectUrl = null;
|
|
16379
16609
|
if (project) {
|
|
16380
16610
|
const id = galleryMediaId(projectPath);
|
|
@@ -16410,8 +16640,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16410
16640
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16411
16641
|
}
|
|
16412
16642
|
} else if (spec.source) {
|
|
16413
|
-
outputs = [await describeOutput(media, root,
|
|
16414
|
-
} else if (!entry &&
|
|
16643
|
+
outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
|
|
16644
|
+
} else if (!entry && existsSync26(spec.outFile)) {
|
|
16415
16645
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16416
16646
|
} else {
|
|
16417
16647
|
outputs = [];
|
|
@@ -16614,7 +16844,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16614
16844
|
id: loaded.manifest.name,
|
|
16615
16845
|
name: loaded.manifest.name,
|
|
16616
16846
|
manifest: loaded.path,
|
|
16617
|
-
lock:
|
|
16847
|
+
lock: path32.resolve(opts.lockPath),
|
|
16618
16848
|
root,
|
|
16619
16849
|
provider: loaded.manifest.provider,
|
|
16620
16850
|
account: null,
|
|
@@ -16640,7 +16870,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16640
16870
|
return { snapshot, media };
|
|
16641
16871
|
}
|
|
16642
16872
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16643
|
-
const dir =
|
|
16873
|
+
const dir = path32.dirname(path32.resolve(opts.workspacePath));
|
|
16644
16874
|
const media = /* @__PURE__ */ new Map();
|
|
16645
16875
|
const projects = [];
|
|
16646
16876
|
const items = [];
|
|
@@ -16683,11 +16913,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16683
16913
|
id: project.id,
|
|
16684
16914
|
name: loaded.manifest.name,
|
|
16685
16915
|
manifest: manifestPath,
|
|
16686
|
-
lock:
|
|
16916
|
+
lock: path32.resolve(lockPath),
|
|
16687
16917
|
root: loaded.root,
|
|
16688
16918
|
provider: loaded.manifest.provider,
|
|
16689
16919
|
account: project.account ?? null,
|
|
16690
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
16920
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile24(manifestPath, "utf8")),
|
|
16691
16921
|
historyLimit: historyLimit(loaded.manifest),
|
|
16692
16922
|
entries: Object.keys(lock.entries).length,
|
|
16693
16923
|
items: projectItems.length,
|
|
@@ -16699,8 +16929,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16699
16929
|
id: project.id,
|
|
16700
16930
|
name: project.id,
|
|
16701
16931
|
manifest: manifestPath,
|
|
16702
|
-
lock:
|
|
16703
|
-
root:
|
|
16932
|
+
lock: path32.resolve(lockPath),
|
|
16933
|
+
root: path32.dirname(manifestPath),
|
|
16704
16934
|
provider: project.provider,
|
|
16705
16935
|
account: project.account ?? null,
|
|
16706
16936
|
manifestSha256: null,
|
|
@@ -16727,7 +16957,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16727
16957
|
version: 1,
|
|
16728
16958
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16729
16959
|
project: null,
|
|
16730
|
-
workspace: { path:
|
|
16960
|
+
workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
|
|
16731
16961
|
filter,
|
|
16732
16962
|
totals: {
|
|
16733
16963
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16744,7 +16974,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16744
16974
|
}
|
|
16745
16975
|
|
|
16746
16976
|
// src/gallery/page.ts
|
|
16747
|
-
import { existsSync as
|
|
16977
|
+
import { existsSync as existsSync27, readFileSync as readFileSync5 } from "fs";
|
|
16748
16978
|
|
|
16749
16979
|
// src/gallery/client-bundle.ts
|
|
16750
16980
|
import { createRequire } from "module";
|
|
@@ -16785,7 +17015,7 @@ function clientAssets() {
|
|
|
16785
17015
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16786
17016
|
client ??= {
|
|
16787
17017
|
css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16788
|
-
js:
|
|
17018
|
+
js: existsSync27(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
|
|
16789
17019
|
};
|
|
16790
17020
|
return client;
|
|
16791
17021
|
}
|
|
@@ -16928,10 +17158,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16928
17158
|
|
|
16929
17159
|
// src/editor/install.ts
|
|
16930
17160
|
import { createHash as createHash5 } from "crypto";
|
|
16931
|
-
import { existsSync as
|
|
16932
|
-
import { mkdir as mkdir11, readFile as
|
|
17161
|
+
import { existsSync as existsSync28 } from "fs";
|
|
17162
|
+
import { mkdir as mkdir11, readFile as readFile25, rename as rename10, rm as rm12, stat as stat4, writeFile as writeFile16 } from "fs/promises";
|
|
16933
17163
|
import os2 from "os";
|
|
16934
|
-
import
|
|
17164
|
+
import path33 from "path";
|
|
16935
17165
|
var EditorInstallError = class extends Error {
|
|
16936
17166
|
constructor(message6, file) {
|
|
16937
17167
|
super(message6);
|
|
@@ -16942,15 +17172,15 @@ var EditorInstallError = class extends Error {
|
|
|
16942
17172
|
};
|
|
16943
17173
|
function editorToolsRoot() {
|
|
16944
17174
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
16945
|
-
if (override) return
|
|
16946
|
-
if (process.platform === "darwin") return
|
|
17175
|
+
if (override) return path33.resolve(override);
|
|
17176
|
+
if (process.platform === "darwin") return path33.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
16947
17177
|
if (process.platform === "win32") {
|
|
16948
|
-
return
|
|
17178
|
+
return path33.join(process.env.LOCALAPPDATA ?? path33.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
16949
17179
|
}
|
|
16950
|
-
return
|
|
17180
|
+
return path33.join(process.env.XDG_CACHE_HOME ?? path33.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
16951
17181
|
}
|
|
16952
17182
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
16953
|
-
return
|
|
17183
|
+
return path33.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
16954
17184
|
}
|
|
16955
17185
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
16956
17186
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -16960,7 +17190,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16960
17190
|
}
|
|
16961
17191
|
async function sha256Of(file) {
|
|
16962
17192
|
try {
|
|
16963
|
-
return createHash5("sha256").update(await
|
|
17193
|
+
return createHash5("sha256").update(await readFile25(file)).digest("hex");
|
|
16964
17194
|
} catch {
|
|
16965
17195
|
return null;
|
|
16966
17196
|
}
|
|
@@ -16973,8 +17203,8 @@ async function editorStatus(opts = {}) {
|
|
|
16973
17203
|
let totalBytes = 0;
|
|
16974
17204
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
16975
17205
|
totalBytes += expected.bytes;
|
|
16976
|
-
const file =
|
|
16977
|
-
const info =
|
|
17206
|
+
const file = path33.join(dir, name);
|
|
17207
|
+
const info = existsSync28(file) ? await stat4(file).catch(() => null) : null;
|
|
16978
17208
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
16979
17209
|
installedBytes += expected.bytes;
|
|
16980
17210
|
} else {
|
|
@@ -17031,13 +17261,13 @@ async function installEditor(opts = {}) {
|
|
|
17031
17261
|
if (digest2 !== expected.sha256) {
|
|
17032
17262
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17033
17263
|
}
|
|
17034
|
-
const final =
|
|
17264
|
+
const final = path33.join(dir, name);
|
|
17035
17265
|
const tmp = `${final}.${process.pid}.part`;
|
|
17036
17266
|
try {
|
|
17037
|
-
await
|
|
17267
|
+
await writeFile16(tmp, bytes);
|
|
17038
17268
|
await rename10(tmp, final);
|
|
17039
17269
|
} finally {
|
|
17040
|
-
await
|
|
17270
|
+
await rm12(tmp, { force: true });
|
|
17041
17271
|
}
|
|
17042
17272
|
fetchedBytes += bytes.length;
|
|
17043
17273
|
downloaded.push(name);
|
|
@@ -17077,12 +17307,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17077
17307
|
".json": "application/json; charset=utf-8"
|
|
17078
17308
|
};
|
|
17079
17309
|
function editorContentType(name) {
|
|
17080
|
-
return EDITOR_CONTENT_TYPES[
|
|
17310
|
+
return EDITOR_CONTENT_TYPES[path33.extname(name).toLowerCase()] ?? null;
|
|
17081
17311
|
}
|
|
17082
17312
|
|
|
17083
17313
|
// src/gallery/editor.ts
|
|
17084
|
-
import { readFile as
|
|
17085
|
-
import
|
|
17314
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
17315
|
+
import path34 from "path";
|
|
17086
17316
|
function editorRoute(release, name = "index.html") {
|
|
17087
17317
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17088
17318
|
}
|
|
@@ -17155,7 +17385,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17155
17385
|
if (!status.installed) return null;
|
|
17156
17386
|
let bytes;
|
|
17157
17387
|
try {
|
|
17158
|
-
bytes = await
|
|
17388
|
+
bytes = await readFile26(path34.join(dir, name));
|
|
17159
17389
|
} catch {
|
|
17160
17390
|
verified = null;
|
|
17161
17391
|
return null;
|
|
@@ -17172,7 +17402,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17172
17402
|
// src/gallery/server.ts
|
|
17173
17403
|
import { randomBytes } from "crypto";
|
|
17174
17404
|
import { createServer as createServer2 } from "http";
|
|
17175
|
-
import { readFile as
|
|
17405
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
17176
17406
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17177
17407
|
var EDITOR_CSP = [
|
|
17178
17408
|
"default-src 'self'",
|
|
@@ -17353,7 +17583,7 @@ async function serveGallery(opts) {
|
|
|
17353
17583
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17354
17584
|
if (!asset) return fail(404, "no such review asset");
|
|
17355
17585
|
try {
|
|
17356
|
-
const bytes = await
|
|
17586
|
+
const bytes = await readFile27(asset.path);
|
|
17357
17587
|
res.writeHead(200, {
|
|
17358
17588
|
"Content-Type": asset.contentType,
|
|
17359
17589
|
"Cache-Control": "no-store",
|
|
@@ -17436,7 +17666,7 @@ async function serveGallery(opts) {
|
|
|
17436
17666
|
if (!asset) return fail(404, "no such gallery media");
|
|
17437
17667
|
let bytes;
|
|
17438
17668
|
try {
|
|
17439
|
-
bytes = await
|
|
17669
|
+
bytes = await readFile27(asset.path);
|
|
17440
17670
|
} catch {
|
|
17441
17671
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17442
17672
|
}
|
|
@@ -17480,27 +17710,27 @@ async function serveGallery(opts) {
|
|
|
17480
17710
|
|
|
17481
17711
|
// src/gallery/generate.ts
|
|
17482
17712
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
17483
|
-
import { z as
|
|
17484
|
-
var GenerateRequestSchema =
|
|
17485
|
-
project:
|
|
17486
|
-
keys:
|
|
17713
|
+
import { z as z14 } from "zod";
|
|
17714
|
+
var GenerateRequestSchema = z14.object({
|
|
17715
|
+
project: z14.string().min(1).optional(),
|
|
17716
|
+
keys: z14.array(z14.string().min(1)).min(1).max(500),
|
|
17487
17717
|
/**
|
|
17488
17718
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17489
17719
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17490
17720
|
* the recorded bytes back over a modified file.
|
|
17491
17721
|
*/
|
|
17492
|
-
force:
|
|
17722
|
+
force: z14.boolean().optional(),
|
|
17493
17723
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17494
|
-
resume:
|
|
17724
|
+
resume: z14.boolean().optional(),
|
|
17495
17725
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17496
|
-
refresh:
|
|
17726
|
+
refresh: z14.boolean().optional(),
|
|
17497
17727
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17498
|
-
restore:
|
|
17728
|
+
restore: z14.boolean().optional(),
|
|
17499
17729
|
/**
|
|
17500
17730
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17501
17731
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17502
17732
|
*/
|
|
17503
|
-
revert:
|
|
17733
|
+
revert: z14.string().min(1).max(64).optional()
|
|
17504
17734
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17505
17735
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17506
17736
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -17846,19 +18076,19 @@ function createGenerateHandlers(opts) {
|
|
|
17846
18076
|
}
|
|
17847
18077
|
|
|
17848
18078
|
// src/pipeline/salvage.ts
|
|
17849
|
-
import { readFile as
|
|
17850
|
-
import { existsSync as
|
|
17851
|
-
import
|
|
18079
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
18080
|
+
import { existsSync as existsSync29 } from "fs";
|
|
18081
|
+
import path35 from "path";
|
|
17852
18082
|
function providerClaimId(provider, objectId) {
|
|
17853
18083
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
17854
18084
|
}
|
|
17855
18085
|
async function loadClaims(lockPaths, opts = {}) {
|
|
17856
18086
|
const claimed = /* @__PURE__ */ new Set();
|
|
17857
18087
|
for (const p of lockPaths) {
|
|
17858
|
-
if (!
|
|
18088
|
+
if (!existsSync29(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17859
18089
|
let parsed;
|
|
17860
18090
|
try {
|
|
17861
|
-
parsed = parseLock(JSON.parse(await
|
|
18091
|
+
parsed = parseLock(JSON.parse(await readFile28(p, "utf8")));
|
|
17862
18092
|
} catch {
|
|
17863
18093
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17864
18094
|
}
|
|
@@ -17962,20 +18192,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
17962
18192
|
return matchStyleByPattern(prompt, manifest);
|
|
17963
18193
|
}
|
|
17964
18194
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
17965
|
-
const own =
|
|
18195
|
+
const own = path35.resolve(ownManifestPath);
|
|
17966
18196
|
const siblingManifestPaths = [
|
|
17967
18197
|
.../* @__PURE__ */ new Set([
|
|
17968
18198
|
...workspaceManifestPaths,
|
|
17969
|
-
...claimPaths.map((c) =>
|
|
18199
|
+
...claimPaths.map((c) => path35.join(path35.dirname(path35.resolve(c)), "pixelkiln.manifest.json"))
|
|
17970
18200
|
])
|
|
17971
18201
|
];
|
|
17972
18202
|
const siblings = [];
|
|
17973
18203
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
17974
|
-
if (
|
|
17975
|
-
if (!
|
|
18204
|
+
if (path35.resolve(siblingManifestPath) === own) continue;
|
|
18205
|
+
if (!existsSync29(siblingManifestPath)) continue;
|
|
17976
18206
|
try {
|
|
17977
18207
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
17978
|
-
siblings.push({ label:
|
|
18208
|
+
siblings.push({ label: path35.basename(path35.dirname(siblingManifestPath)), manifest });
|
|
17979
18209
|
} catch {
|
|
17980
18210
|
}
|
|
17981
18211
|
}
|
|
@@ -18095,8 +18325,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18095
18325
|
}
|
|
18096
18326
|
|
|
18097
18327
|
// src/pick/salvage-server.ts
|
|
18098
|
-
import { mkdir as mkdir12, writeFile as
|
|
18099
|
-
import
|
|
18328
|
+
import { mkdir as mkdir12, writeFile as writeFile17, readFile as readFile29 } from "fs/promises";
|
|
18329
|
+
import path36 from "path";
|
|
18100
18330
|
|
|
18101
18331
|
// src/pick/salvage-sheet.ts
|
|
18102
18332
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18262,7 +18492,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18262
18492
|
});
|
|
18263
18493
|
const html = renderSalvageSheet(orphans, {
|
|
18264
18494
|
styleId: ctx.styleId,
|
|
18265
|
-
importDir:
|
|
18495
|
+
importDir: path36.relative(process.cwd(), ctx.importDir) || "."
|
|
18266
18496
|
});
|
|
18267
18497
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18268
18498
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18318,10 +18548,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18318
18548
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18319
18549
|
decodePng(buf);
|
|
18320
18550
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18321
|
-
const rel =
|
|
18322
|
-
const outFile =
|
|
18323
|
-
await mkdir12(
|
|
18324
|
-
await
|
|
18551
|
+
const rel = path36.join("_salvaged", `${assetId}.png`);
|
|
18552
|
+
const outFile = path36.resolve(ctx.importDir, rel);
|
|
18553
|
+
await mkdir12(path36.dirname(outFile), { recursive: true });
|
|
18554
|
+
await writeFile17(outFile, buf);
|
|
18325
18555
|
ctx.manifest.assets[assetId] = {
|
|
18326
18556
|
prompt: orphan.prompt,
|
|
18327
18557
|
promptByStyle: {},
|
|
@@ -18353,7 +18583,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18353
18583
|
sourceUrl: durableSource,
|
|
18354
18584
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18355
18585
|
outputs: [{
|
|
18356
|
-
path: portableOutputPath(outFile,
|
|
18586
|
+
path: portableOutputPath(outFile, path36.dirname(ctx.manifestPath)),
|
|
18357
18587
|
sha256: sha256(buf)
|
|
18358
18588
|
}],
|
|
18359
18589
|
submittedAt: orphan.createdAt,
|
|
@@ -18377,9 +18607,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18377
18607
|
}
|
|
18378
18608
|
}
|
|
18379
18609
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18380
|
-
const raw = JSON.parse(await
|
|
18610
|
+
const raw = JSON.parse(await readFile29(ctx.manifestPath, "utf8"));
|
|
18381
18611
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18382
|
-
await
|
|
18612
|
+
await writeFile17(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18383
18613
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18384
18614
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18385
18615
|
return result;
|