pixelkiln 0.72.0 → 0.74.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 +773 -516
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +16 -0
- package/dist/client/gallery.js +452 -0
- package/dist/index.cjs +476 -266
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1876 -259
- package/dist/index.d.ts +1876 -259
- package/dist/index.js +459 -249
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +12 -0
- package/docs/CLI.md +4 -1
- package/docs/GENERATORS.md +1 -1
- package/docs/MANIFEST.md +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1325,7 +1325,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1325
1325
|
path: ["mirror"]
|
|
1326
1326
|
});
|
|
1327
1327
|
}
|
|
1328
|
-
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit) {
|
|
1328
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit && !asset.animation?.template) {
|
|
1329
1329
|
context.addIssue({
|
|
1330
1330
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1331
1331
|
message: "Required",
|
|
@@ -2060,9 +2060,9 @@ var PixelLabClient = class {
|
|
|
2060
2060
|
apiKey;
|
|
2061
2061
|
http;
|
|
2062
2062
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
2063
|
-
async request(
|
|
2063
|
+
async request(path36, init) {
|
|
2064
2064
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
2065
|
-
const res = await this.http(`${BASE}${
|
|
2065
|
+
const res = await this.http(`${BASE}${path36}`, {
|
|
2066
2066
|
...init,
|
|
2067
2067
|
headers: {
|
|
2068
2068
|
Authorization: auth,
|
|
@@ -2072,13 +2072,13 @@ var PixelLabClient = class {
|
|
|
2072
2072
|
});
|
|
2073
2073
|
const text = await res.text();
|
|
2074
2074
|
if (!res.ok) {
|
|
2075
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
2075
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path36} \u2192 ${res.status}`, res.status, text);
|
|
2076
2076
|
}
|
|
2077
2077
|
if (!text) return {};
|
|
2078
2078
|
try {
|
|
2079
2079
|
return JSON.parse(text);
|
|
2080
2080
|
} catch {
|
|
2081
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
2081
|
+
throw new Error(`${init?.method ?? "GET"} ${path36} returned invalid JSON`);
|
|
2082
2082
|
}
|
|
2083
2083
|
}
|
|
2084
2084
|
async balance() {
|
|
@@ -2470,10 +2470,10 @@ var PixelLabClient = class {
|
|
|
2470
2470
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2471
2471
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2472
2472
|
const south = args.reference?.south;
|
|
2473
|
-
let
|
|
2473
|
+
let path36;
|
|
2474
2474
|
let body;
|
|
2475
2475
|
if (args.mode === "v3") {
|
|
2476
|
-
|
|
2476
|
+
path36 = "/create-character-v3";
|
|
2477
2477
|
body = {
|
|
2478
2478
|
description: args.description,
|
|
2479
2479
|
image_size: imageSize,
|
|
@@ -2487,7 +2487,7 @@ var PixelLabClient = class {
|
|
|
2487
2487
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2488
2488
|
};
|
|
2489
2489
|
} else if (args.mode === "pro-flash") {
|
|
2490
|
-
|
|
2490
|
+
path36 = "/create-character-pro-flash";
|
|
2491
2491
|
const traits = args.styleTraits;
|
|
2492
2492
|
body = {
|
|
2493
2493
|
description: args.description,
|
|
@@ -2511,7 +2511,7 @@ var PixelLabClient = class {
|
|
|
2511
2511
|
} : {}
|
|
2512
2512
|
};
|
|
2513
2513
|
} else if (args.mode === "pro") {
|
|
2514
|
-
|
|
2514
|
+
path36 = "/create-character-pro";
|
|
2515
2515
|
body = {
|
|
2516
2516
|
description: args.description,
|
|
2517
2517
|
image_size: imageSize,
|
|
@@ -2525,7 +2525,7 @@ var PixelLabClient = class {
|
|
|
2525
2525
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2526
2526
|
};
|
|
2527
2527
|
} else {
|
|
2528
|
-
|
|
2528
|
+
path36 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2529
2529
|
body = {
|
|
2530
2530
|
description: args.description,
|
|
2531
2531
|
image_size: imageSize,
|
|
@@ -2542,8 +2542,8 @@ var PixelLabClient = class {
|
|
|
2542
2542
|
...palette
|
|
2543
2543
|
};
|
|
2544
2544
|
}
|
|
2545
|
-
const raw = await this.request(
|
|
2546
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2545
|
+
const raw = await this.request(path36, { method: "POST", body: JSON.stringify(body) });
|
|
2546
|
+
return validateResponse(CharacterSubmitSchema, raw, path36.slice(1));
|
|
2547
2547
|
}
|
|
2548
2548
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2549
2549
|
async createCharacterState(args) {
|
|
@@ -3729,10 +3729,10 @@ var import_node_fs = require("fs");
|
|
|
3729
3729
|
function sha256(data) {
|
|
3730
3730
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3731
3731
|
}
|
|
3732
|
-
async function sha256File(
|
|
3732
|
+
async function sha256File(path36) {
|
|
3733
3733
|
return new Promise((resolve, reject) => {
|
|
3734
3734
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3735
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3735
|
+
const stream = (0, import_node_fs.createReadStream)(path36);
|
|
3736
3736
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3737
3737
|
stream.on("error", reject);
|
|
3738
3738
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3902,7 +3902,11 @@ function characterCost(spec) {
|
|
|
3902
3902
|
return 20;
|
|
3903
3903
|
}
|
|
3904
3904
|
if (character.mode === "standard") return 1;
|
|
3905
|
-
if (character.mode === "v3")
|
|
3905
|
+
if (character.mode === "v3") {
|
|
3906
|
+
const south = character.reference?.south;
|
|
3907
|
+
if (south) return Math.max(1, Math.ceil(south.width * south.height * 8 / 65536));
|
|
3908
|
+
return 1 + Math.max(1, Math.ceil(px * 8 / 65536));
|
|
3909
|
+
}
|
|
3906
3910
|
if (character.mode === "pro-flash") {
|
|
3907
3911
|
const south = character.reference?.south;
|
|
3908
3912
|
return south ? proFlashCharacterCost(south.width, south.height, true) : proFlashCharacterCost(spec.width, spec.height, false);
|
|
@@ -6096,9 +6100,9 @@ var RetroDiffusionClient = class {
|
|
|
6096
6100
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
6097
6101
|
return Buffer.from(await response.arrayBuffer());
|
|
6098
6102
|
}
|
|
6099
|
-
async call(
|
|
6103
|
+
async call(path36, init = {}) {
|
|
6100
6104
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
6101
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6105
|
+
const response = await this.request(`${this.baseUrl}${path36}`, {
|
|
6102
6106
|
...init,
|
|
6103
6107
|
headers: {
|
|
6104
6108
|
"Content-Type": "application/json",
|
|
@@ -12387,10 +12391,10 @@ function isSha256Hash(value) {
|
|
|
12387
12391
|
function parseCache(value) {
|
|
12388
12392
|
return HashCacheSchema.parse(value);
|
|
12389
12393
|
}
|
|
12390
|
-
async function loadCache(
|
|
12391
|
-
if (!(0, import_node_fs14.existsSync)(
|
|
12394
|
+
async function loadCache(path36) {
|
|
12395
|
+
if (!(0, import_node_fs14.existsSync)(path36)) return { version: 1, hashes: {} };
|
|
12392
12396
|
try {
|
|
12393
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(
|
|
12397
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(path36, "utf8")));
|
|
12394
12398
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12395
12399
|
return {
|
|
12396
12400
|
version: 1,
|
|
@@ -12402,18 +12406,18 @@ async function loadCache(path35) {
|
|
|
12402
12406
|
return { version: 1, hashes: {} };
|
|
12403
12407
|
}
|
|
12404
12408
|
}
|
|
12405
|
-
async function saveCache(
|
|
12409
|
+
async function saveCache(path36, cache) {
|
|
12406
12410
|
const sorted = {};
|
|
12407
12411
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12408
12412
|
const hash = cache.hashes[key];
|
|
12409
12413
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12410
12414
|
sorted[key] = hash;
|
|
12411
12415
|
}
|
|
12412
|
-
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(
|
|
12413
|
-
const tmp = `${
|
|
12416
|
+
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(path36)), { recursive: true });
|
|
12417
|
+
const tmp = `${path36}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12414
12418
|
try {
|
|
12415
12419
|
await (0, import_promises11.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12416
|
-
await (0, import_promises11.rename)(tmp,
|
|
12420
|
+
await (0, import_promises11.rename)(tmp, path36);
|
|
12417
12421
|
} finally {
|
|
12418
12422
|
await (0, import_promises11.rm)(tmp, { force: true });
|
|
12419
12423
|
}
|
|
@@ -15218,9 +15222,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15218
15222
|
|
|
15219
15223
|
// src/gallery/snapshot.ts
|
|
15220
15224
|
var import_node_crypto6 = require("crypto");
|
|
15221
|
-
var
|
|
15222
|
-
var
|
|
15223
|
-
var
|
|
15225
|
+
var import_node_fs27 = require("fs");
|
|
15226
|
+
var import_promises24 = require("fs/promises");
|
|
15227
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
15224
15228
|
|
|
15225
15229
|
// src/workspace.ts
|
|
15226
15230
|
var import_promises19 = require("fs/promises");
|
|
@@ -15356,8 +15360,8 @@ function validateWorkspace(ws, dir) {
|
|
|
15356
15360
|
}
|
|
15357
15361
|
|
|
15358
15362
|
// src/gallery/edit.ts
|
|
15359
|
-
var
|
|
15360
|
-
var
|
|
15363
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
15364
|
+
var import_zod12 = require("zod");
|
|
15361
15365
|
|
|
15362
15366
|
// src/manifest-edit.ts
|
|
15363
15367
|
var import_promises20 = require("fs/promises");
|
|
@@ -15388,13 +15392,16 @@ var NewAnimationSchema = import_zod8.z.object({
|
|
|
15388
15392
|
direction: CharacterDirectionSchema.optional(),
|
|
15389
15393
|
frames: import_zod8.z.number().int().min(4).max(16).optional(),
|
|
15390
15394
|
fps: import_zod8.z.number().int().min(1).max(60).optional(),
|
|
15395
|
+
/** One loop per named direction, plus free mirrors; expanded when the manifest loads. */
|
|
15396
|
+
directions: import_zod8.z.array(CharacterDirectionSchema).min(1).optional(),
|
|
15391
15397
|
mode: CharacterAnimationModeSchema.optional(),
|
|
15392
15398
|
/** What is being animated, when the parent's own description would mislead the model (`character` only). */
|
|
15393
15399
|
subject: import_zod8.z.string().min(1).optional(),
|
|
15394
15400
|
enhancePrompt: import_zod8.z.boolean().optional()
|
|
15395
15401
|
}).strict();
|
|
15396
15402
|
var NewAssetSchema = import_zod8.z.object({
|
|
15397
|
-
|
|
15403
|
+
/** A portrait, an outfit, or a mirror has none of its own; the loader says where one is needed. */
|
|
15404
|
+
prompt: import_zod8.z.string().optional(),
|
|
15398
15405
|
category: import_zod8.z.string().optional(),
|
|
15399
15406
|
width: import_zod8.z.number().int().optional(),
|
|
15400
15407
|
height: import_zod8.z.number().int().optional(),
|
|
@@ -15429,7 +15436,24 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15429
15436
|
/** A pose or outfit of an existing `character`/`objectPro` base or state. */
|
|
15430
15437
|
state: NewStateSchema.optional(),
|
|
15431
15438
|
/** A loop of an existing `character`/`objectPro` base or state. */
|
|
15432
|
-
animation: NewAnimationSchema.optional()
|
|
15439
|
+
animation: NewAnimationSchema.optional(),
|
|
15440
|
+
/** A bust of an existing character. */
|
|
15441
|
+
portrait: CharacterPortraitSchema.optional(),
|
|
15442
|
+
/** An existing loop re-clothed from a reference image (uploaded first). */
|
|
15443
|
+
outfit: CharacterOutfitSchema.optional(),
|
|
15444
|
+
/** Another asset of the same style, flipped; free. */
|
|
15445
|
+
mirror: import_zod8.z.string().min(1).optional(),
|
|
15446
|
+
/**
|
|
15447
|
+
* `character`/`objectPro` bases: the subject's own sprite (uploaded
|
|
15448
|
+
* first), or one per direction, which PixelLab rotates instead of drawing.
|
|
15449
|
+
*/
|
|
15450
|
+
reference: import_zod8.z.union([import_zod8.z.string().min(1), import_zod8.z.record(CharacterDirectionSchema, import_zod8.z.string().min(1))]).optional(),
|
|
15451
|
+
/** `character` pro bases: a concept image that seeds the design. */
|
|
15452
|
+
concept: import_zod8.z.string().min(1).optional(),
|
|
15453
|
+
/** `character` pro bases: another character in the style whose look this one follows. */
|
|
15454
|
+
styleCharacter: import_zod8.z.string().min(1).optional(),
|
|
15455
|
+
/** `character` standard humanoid bases: this character's proportions, over the style's. */
|
|
15456
|
+
proportions: CharacterProportionsSchema.optional()
|
|
15433
15457
|
}).strict().refine((asset) => !(asset.revision && (asset.state || asset.animation)), {
|
|
15434
15458
|
message: "an asset is a revision, a state, or an animation \u2014 not more than one"
|
|
15435
15459
|
}).refine((asset) => !(asset.state && asset.animation), {
|
|
@@ -15440,59 +15464,95 @@ var CANDIDATE_OPTION = {
|
|
|
15440
15464
|
comfyui: "numImages",
|
|
15441
15465
|
scenario: "numOutputs"
|
|
15442
15466
|
};
|
|
15467
|
+
var NewStyleSchema = import_zod8.z.object({
|
|
15468
|
+
generator: import_zod8.z.enum(["character", "objectPro"]),
|
|
15469
|
+
/** Where the style's art is written, inside the project. */
|
|
15470
|
+
outDir: import_zod8.z.string().min(1).refine((dir) => !dir.split(/[\\/]/).includes("..") && !/^([a-z]:)?[\\/]/i.test(dir), {
|
|
15471
|
+
message: "outDir must be a path inside the project"
|
|
15472
|
+
}),
|
|
15473
|
+
size: import_zod8.z.number().int().min(16).max(256).optional(),
|
|
15474
|
+
mode: CharacterModeSchema.optional(),
|
|
15475
|
+
directions: import_zod8.z.union([import_zod8.z.literal(4), import_zod8.z.literal(8)]).optional(),
|
|
15476
|
+
template: import_zod8.z.string().min(1).optional(),
|
|
15477
|
+
view: import_zod8.z.string().min(1).max(64).optional(),
|
|
15478
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
15479
|
+
isometric: import_zod8.z.boolean().optional(),
|
|
15480
|
+
textGuidanceScale: import_zod8.z.number().min(1).max(20).optional(),
|
|
15481
|
+
enhancePrompt: import_zod8.z.boolean().optional(),
|
|
15482
|
+
promptPrefix: import_zod8.z.string().optional(),
|
|
15483
|
+
promptSuffix: import_zod8.z.string().optional(),
|
|
15484
|
+
palette: import_zod8.z.array(import_zod8.z.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).optional()
|
|
15485
|
+
}).strict();
|
|
15486
|
+
var PatchAssetEdit = import_zod8.z.object({
|
|
15487
|
+
action: import_zod8.z.literal("patch-asset"),
|
|
15488
|
+
assetId: import_zod8.z.string().min(1),
|
|
15489
|
+
patch: AssetPatchSchema
|
|
15490
|
+
}).strict();
|
|
15491
|
+
var AddAssetEdit = import_zod8.z.object({
|
|
15492
|
+
action: import_zod8.z.literal("add-asset"),
|
|
15493
|
+
assetId: import_zod8.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15494
|
+
asset: NewAssetSchema
|
|
15495
|
+
}).strict();
|
|
15496
|
+
var SetSourceEdit = import_zod8.z.object({
|
|
15497
|
+
action: import_zod8.z.literal("set-source"),
|
|
15498
|
+
assetId: import_zod8.z.string().min(1),
|
|
15499
|
+
styleId: import_zod8.z.string().min(1),
|
|
15500
|
+
/** Manifest-relative path of the art that stands in for this asset in this style. */
|
|
15501
|
+
source: import_zod8.z.string().min(1)
|
|
15502
|
+
}).strict();
|
|
15503
|
+
var ClearSourceEdit = import_zod8.z.object({
|
|
15504
|
+
action: import_zod8.z.literal("clear-source"),
|
|
15505
|
+
assetId: import_zod8.z.string().min(1),
|
|
15506
|
+
styleId: import_zod8.z.string().min(1)
|
|
15507
|
+
}).strict();
|
|
15508
|
+
var PatchStyleEdit = import_zod8.z.object({
|
|
15509
|
+
action: import_zod8.z.literal("patch-style"),
|
|
15510
|
+
styleId: import_zod8.z.string().min(1),
|
|
15511
|
+
patch: import_zod8.z.object({
|
|
15512
|
+
/** Candidates per generation, written to the provider's own option. */
|
|
15513
|
+
candidates: import_zod8.z.number().int().min(1).max(64).optional(),
|
|
15514
|
+
/** Empty clears the style's own value (inherit, or the default). */
|
|
15515
|
+
promptPrefix: import_zod8.z.string().optional(),
|
|
15516
|
+
promptSuffix: import_zod8.z.string().optional(),
|
|
15517
|
+
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
15518
|
+
palette: import_zod8.z.array(import_zod8.z.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
15519
|
+
/** Snap downloaded art to the palette; false clears the style's own value. */
|
|
15520
|
+
enforcePalette: import_zod8.z.boolean().optional(),
|
|
15521
|
+
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
15522
|
+
view: import_zod8.z.string().max(64).nullable().optional(),
|
|
15523
|
+
/** pixflux only; null clears the style's own value. */
|
|
15524
|
+
noBackground: import_zod8.z.boolean().nullable().optional()
|
|
15525
|
+
}).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
|
|
15526
|
+
}).strict();
|
|
15527
|
+
var AddStyleEdit = import_zod8.z.object({
|
|
15528
|
+
action: import_zod8.z.literal("add-style"),
|
|
15529
|
+
styleId: import_zod8.z.string().min(1).regex(/^[^/\\:]+$/, "style ids cannot contain slashes or colons"),
|
|
15530
|
+
style: NewStyleSchema
|
|
15531
|
+
}).strict();
|
|
15532
|
+
var Envelope = {
|
|
15533
|
+
/** Workspace project id; omitted for a single-project gallery. */
|
|
15534
|
+
project: import_zod8.z.string().min(1).optional(),
|
|
15535
|
+
/** Manifest bytes the page last saw; a mismatch refuses the write. */
|
|
15536
|
+
expectedSha256: HexSha
|
|
15537
|
+
};
|
|
15538
|
+
var SingleEdit = import_zod8.z.discriminatedUnion("action", [PatchAssetEdit, AddAssetEdit, SetSourceEdit, ClearSourceEdit, PatchStyleEdit, AddStyleEdit]);
|
|
15443
15539
|
var ManifestEditSchema = import_zod8.z.discriminatedUnion("action", [
|
|
15540
|
+
PatchAssetEdit.extend(Envelope),
|
|
15541
|
+
AddAssetEdit.extend(Envelope),
|
|
15542
|
+
SetSourceEdit.extend(Envelope),
|
|
15543
|
+
ClearSourceEdit.extend(Envelope),
|
|
15544
|
+
PatchStyleEdit.extend(Envelope),
|
|
15545
|
+
AddStyleEdit.extend(Envelope),
|
|
15546
|
+
/**
|
|
15547
|
+
* Several edits as one write: a new character's style, base, and loops
|
|
15548
|
+
* land together or not at all, validated once as the finished manifest.
|
|
15549
|
+
* Each edit sees the ones before it, so a base can name a style the same
|
|
15550
|
+
* batch adds.
|
|
15551
|
+
*/
|
|
15444
15552
|
import_zod8.z.object({
|
|
15445
|
-
action: import_zod8.z.literal("
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
assetId: import_zod8.z.string().min(1),
|
|
15449
|
-
/** Manifest bytes the page last saw; a mismatch refuses the write. */
|
|
15450
|
-
expectedSha256: HexSha,
|
|
15451
|
-
patch: AssetPatchSchema
|
|
15452
|
-
}).strict(),
|
|
15453
|
-
import_zod8.z.object({
|
|
15454
|
-
action: import_zod8.z.literal("add-asset"),
|
|
15455
|
-
project: import_zod8.z.string().min(1).optional(),
|
|
15456
|
-
assetId: import_zod8.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15457
|
-
expectedSha256: HexSha,
|
|
15458
|
-
asset: NewAssetSchema
|
|
15459
|
-
}).strict(),
|
|
15460
|
-
import_zod8.z.object({
|
|
15461
|
-
action: import_zod8.z.literal("set-source"),
|
|
15462
|
-
project: import_zod8.z.string().min(1).optional(),
|
|
15463
|
-
assetId: import_zod8.z.string().min(1),
|
|
15464
|
-
styleId: import_zod8.z.string().min(1),
|
|
15465
|
-
/** Manifest-relative path of the art that stands in for this asset in this style. */
|
|
15466
|
-
source: import_zod8.z.string().min(1),
|
|
15467
|
-
expectedSha256: HexSha
|
|
15468
|
-
}).strict(),
|
|
15469
|
-
import_zod8.z.object({
|
|
15470
|
-
action: import_zod8.z.literal("clear-source"),
|
|
15471
|
-
project: import_zod8.z.string().min(1).optional(),
|
|
15472
|
-
assetId: import_zod8.z.string().min(1),
|
|
15473
|
-
styleId: import_zod8.z.string().min(1),
|
|
15474
|
-
expectedSha256: HexSha
|
|
15475
|
-
}).strict(),
|
|
15476
|
-
import_zod8.z.object({
|
|
15477
|
-
action: import_zod8.z.literal("patch-style"),
|
|
15478
|
-
project: import_zod8.z.string().min(1).optional(),
|
|
15479
|
-
styleId: import_zod8.z.string().min(1),
|
|
15480
|
-
expectedSha256: HexSha,
|
|
15481
|
-
patch: import_zod8.z.object({
|
|
15482
|
-
/** Candidates per generation, written to the provider's own option. */
|
|
15483
|
-
candidates: import_zod8.z.number().int().min(1).max(64).optional(),
|
|
15484
|
-
/** Empty clears the style's own value (inherit, or the default). */
|
|
15485
|
-
promptPrefix: import_zod8.z.string().optional(),
|
|
15486
|
-
promptSuffix: import_zod8.z.string().optional(),
|
|
15487
|
-
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
15488
|
-
palette: import_zod8.z.array(import_zod8.z.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
15489
|
-
/** Snap downloaded art to the palette; false clears the style's own value. */
|
|
15490
|
-
enforcePalette: import_zod8.z.boolean().optional(),
|
|
15491
|
-
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
15492
|
-
view: import_zod8.z.string().max(64).nullable().optional(),
|
|
15493
|
-
/** pixflux only; null clears the style's own value. */
|
|
15494
|
-
noBackground: import_zod8.z.boolean().nullable().optional()
|
|
15495
|
-
}).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
|
|
15553
|
+
action: import_zod8.z.literal("batch"),
|
|
15554
|
+
...Envelope,
|
|
15555
|
+
edits: import_zod8.z.array(SingleEdit).min(1).max(64)
|
|
15496
15556
|
}).strict()
|
|
15497
15557
|
]);
|
|
15498
15558
|
var ManifestDriftError = class extends Error {
|
|
@@ -15541,6 +15601,18 @@ function undeclaredAsset(raw, assetId) {
|
|
|
15541
15601
|
}
|
|
15542
15602
|
function applyEdit(raw, edit) {
|
|
15543
15603
|
raw.assets ??= {};
|
|
15604
|
+
if (edit.action === "batch") {
|
|
15605
|
+
for (const each of edit.edits) applyEdit(raw, each);
|
|
15606
|
+
return;
|
|
15607
|
+
}
|
|
15608
|
+
if (edit.action === "add-style") {
|
|
15609
|
+
raw.styles ??= {};
|
|
15610
|
+
if (Object.hasOwn(raw.styles, edit.styleId)) throw new ManifestEditError(`style "${edit.styleId}" already exists`);
|
|
15611
|
+
const style = { ...edit.style };
|
|
15612
|
+
if (edit.style.palette) style.palette = edit.style.palette.map((color) => "#" + color.replace(/^#/, "").toLowerCase());
|
|
15613
|
+
raw.styles[edit.styleId] = style;
|
|
15614
|
+
return;
|
|
15615
|
+
}
|
|
15544
15616
|
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
15545
15617
|
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
15546
15618
|
if (!asset2) throw undeclaredAsset(raw, edit.assetId);
|
|
@@ -15618,6 +15690,15 @@ function applyEdit(raw, edit) {
|
|
|
15618
15690
|
if (edit.asset.animation && !declaredAssets(raw).has(edit.asset.animation.of)) {
|
|
15619
15691
|
throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
|
|
15620
15692
|
}
|
|
15693
|
+
for (const [what, parent] of [
|
|
15694
|
+
["portrait", edit.asset.portrait?.of],
|
|
15695
|
+
["outfit", edit.asset.outfit?.of],
|
|
15696
|
+
["mirror", edit.asset.mirror]
|
|
15697
|
+
]) {
|
|
15698
|
+
if (parent && !declaredAssets(raw).has(parent)) {
|
|
15699
|
+
throw new ManifestEditError(`${what} source "${parent}" is not declared by the manifest`);
|
|
15700
|
+
}
|
|
15701
|
+
}
|
|
15621
15702
|
raw.assets[edit.assetId] = asset2;
|
|
15622
15703
|
return;
|
|
15623
15704
|
}
|
|
@@ -15645,11 +15726,10 @@ function applyEdit(raw, edit) {
|
|
|
15645
15726
|
else delete asset.tags;
|
|
15646
15727
|
}
|
|
15647
15728
|
}
|
|
15648
|
-
async function
|
|
15729
|
+
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
15649
15730
|
const absolute = import_node_path27.default.resolve(manifestPath);
|
|
15650
15731
|
const original = await (0, import_promises20.readFile)(absolute, "utf8");
|
|
15651
|
-
|
|
15652
|
-
if (before !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
15732
|
+
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
15653
15733
|
let raw;
|
|
15654
15734
|
try {
|
|
15655
15735
|
raw = JSON.parse(original);
|
|
@@ -15660,25 +15740,35 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
15660
15740
|
}
|
|
15661
15741
|
applyEdit(raw, edit);
|
|
15662
15742
|
const next = serializeLike(original, raw);
|
|
15663
|
-
if (next === original) return
|
|
15743
|
+
if (next === original) return unchanged(absolute, original);
|
|
15664
15744
|
const tmp = import_node_path27.default.join(
|
|
15665
15745
|
import_node_path27.default.dirname(absolute),
|
|
15666
15746
|
`.${import_node_path27.default.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
15667
15747
|
);
|
|
15668
15748
|
try {
|
|
15669
15749
|
await (0, import_promises20.writeFile)(tmp, next);
|
|
15750
|
+
let specs;
|
|
15670
15751
|
try {
|
|
15671
|
-
|
|
15672
|
-
await resolveSpecs(loaded);
|
|
15752
|
+
specs = await resolveSpecs(await loadManifest(tmp));
|
|
15673
15753
|
} catch (error) {
|
|
15674
15754
|
const message6 = error instanceof Error ? error.message : String(error);
|
|
15675
15755
|
throw new ManifestEditError(message6.replace(tmp, absolute));
|
|
15676
15756
|
}
|
|
15677
|
-
await (
|
|
15757
|
+
return await use({ absolute, original, next, tmp, specs });
|
|
15678
15758
|
} finally {
|
|
15679
15759
|
await (0, import_promises20.rm)(tmp, { force: true });
|
|
15680
15760
|
}
|
|
15681
|
-
|
|
15761
|
+
}
|
|
15762
|
+
async function applyManifestEdit(manifestPath, edit) {
|
|
15763
|
+
return withCandidate(
|
|
15764
|
+
manifestPath,
|
|
15765
|
+
edit,
|
|
15766
|
+
async ({ absolute, next, tmp }) => {
|
|
15767
|
+
await (0, import_promises20.rename)(tmp, absolute);
|
|
15768
|
+
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
15769
|
+
},
|
|
15770
|
+
(absolute, original) => ({ manifestPath: absolute, sha256: sha256(original), changed: false })
|
|
15771
|
+
);
|
|
15682
15772
|
}
|
|
15683
15773
|
|
|
15684
15774
|
// src/pipeline/hand-edit.ts
|
|
@@ -15929,62 +16019,103 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
15929
16019
|
return openExternal(file, command);
|
|
15930
16020
|
}
|
|
15931
16021
|
|
|
15932
|
-
// src/gallery/
|
|
16022
|
+
// src/gallery/upload.ts
|
|
15933
16023
|
var import_node_fs25 = require("fs");
|
|
15934
16024
|
var import_promises22 = require("fs/promises");
|
|
15935
16025
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
15936
16026
|
var import_zod10 = require("zod");
|
|
15937
|
-
var
|
|
16027
|
+
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
16028
|
+
var UploadImageSchema = import_zod10.z.object({
|
|
16029
|
+
action: import_zod10.z.literal("upload-image"),
|
|
15938
16030
|
project: import_zod10.z.string().min(1).optional(),
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
16031
|
+
/** Manifest-relative destination, ending .png, .jpg, or .jpeg. */
|
|
16032
|
+
path: import_zod10.z.string().min(1),
|
|
16033
|
+
base64: import_zod10.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_UPLOAD_BYTES / 3) * 4),
|
|
16034
|
+
/** Replace a file already at `path`. */
|
|
16035
|
+
overwrite: import_zod10.z.boolean().optional()
|
|
15942
16036
|
}).strict();
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
16037
|
+
async function saveProjectImage(root, request) {
|
|
16038
|
+
const file = import_node_path29.default.resolve(root, request.path);
|
|
16039
|
+
const relative = import_node_path29.default.relative(root, file);
|
|
16040
|
+
if (relative.startsWith("..") || import_node_path29.default.isAbsolute(relative)) {
|
|
16041
|
+
throw new ManifestEditError("an upload must stay inside the project");
|
|
16042
|
+
}
|
|
16043
|
+
const extension = import_node_path29.default.extname(file).toLowerCase();
|
|
16044
|
+
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
16045
|
+
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
16046
|
+
const bytes = Buffer.from(request.base64, "base64");
|
|
16047
|
+
if (!bytes.length || bytes.length > MAX_UPLOAD_BYTES) {
|
|
16048
|
+
throw new ManifestEditError(`an upload must be between 1 byte and ${MAX_UPLOAD_BYTES / 1024 / 1024} MB`);
|
|
16049
|
+
}
|
|
16050
|
+
const metadata = imageMetadata(bytes);
|
|
16051
|
+
if (!metadata) throw new ManifestEditError("the upload is not a readable PNG or JPEG");
|
|
16052
|
+
if (metadata.format !== expected) {
|
|
16053
|
+
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
16054
|
+
}
|
|
16055
|
+
if ((0, import_node_fs25.existsSync)(file) && !request.overwrite) {
|
|
16056
|
+
throw new ManifestEditError(`${relative.split(import_node_path29.default.sep).join("/")} already exists; choose another name, or replace it`);
|
|
16057
|
+
}
|
|
16058
|
+
await (0, import_promises22.mkdir)(import_node_path29.default.dirname(file), { recursive: true });
|
|
16059
|
+
await (0, import_promises22.writeFile)(file, bytes);
|
|
16060
|
+
return relative.split(import_node_path29.default.sep).join("/");
|
|
16061
|
+
}
|
|
16062
|
+
|
|
16063
|
+
// src/gallery/skeleton.ts
|
|
16064
|
+
var import_node_fs26 = require("fs");
|
|
16065
|
+
var import_promises23 = require("fs/promises");
|
|
16066
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
16067
|
+
var import_zod11 = require("zod");
|
|
16068
|
+
var SkeletonEstimateRequestSchema = import_zod11.z.object({
|
|
16069
|
+
project: import_zod11.z.string().min(1).optional(),
|
|
16070
|
+
styleId: import_zod11.z.string().min(1),
|
|
16071
|
+
assetId: import_zod11.z.string().min(1),
|
|
16072
|
+
frames: import_zod11.z.number().int().min(3).max(15).optional()
|
|
16073
|
+
}).strict();
|
|
16074
|
+
var SkeletonAnimationRequestSchema = import_zod11.z.object({
|
|
16075
|
+
action: import_zod11.z.literal("create-skeleton-animation"),
|
|
16076
|
+
project: import_zod11.z.string().min(1).optional(),
|
|
16077
|
+
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16078
|
+
styleId: import_zod11.z.string().min(1),
|
|
15948
16079
|
/** The sprite the animation poses. */
|
|
15949
|
-
from:
|
|
15950
|
-
assetId:
|
|
16080
|
+
from: import_zod11.z.string().min(1),
|
|
16081
|
+
assetId: import_zod11.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15951
16082
|
/** The motion, sent as PixelLab's `action`. */
|
|
15952
|
-
prompt:
|
|
16083
|
+
prompt: import_zod11.z.string().min(1),
|
|
15953
16084
|
direction: CharacterDirectionSchema,
|
|
15954
|
-
description:
|
|
15955
|
-
skeletonTemplate:
|
|
16085
|
+
description: import_zod11.z.string().min(1).optional(),
|
|
16086
|
+
skeletonTemplate: import_zod11.z.string().min(1).optional(),
|
|
15956
16087
|
/** Manifest-relative JSON path inside the project. */
|
|
15957
|
-
keypointsFile:
|
|
16088
|
+
keypointsFile: import_zod11.z.string().min(1),
|
|
15958
16089
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
15959
16090
|
set: SkeletonSetSchema.optional(),
|
|
15960
16091
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
15961
|
-
overwrite:
|
|
16092
|
+
overwrite: import_zod11.z.boolean().optional()
|
|
15962
16093
|
}).strict();
|
|
15963
|
-
var SkeletonKeypointsUpdateSchema =
|
|
15964
|
-
action:
|
|
15965
|
-
project:
|
|
15966
|
-
styleId:
|
|
15967
|
-
assetId:
|
|
16094
|
+
var SkeletonKeypointsUpdateSchema = import_zod11.z.object({
|
|
16095
|
+
action: import_zod11.z.literal("update-skeleton-keypoints"),
|
|
16096
|
+
project: import_zod11.z.string().min(1).optional(),
|
|
16097
|
+
styleId: import_zod11.z.string().min(1),
|
|
16098
|
+
assetId: import_zod11.z.string().min(1),
|
|
15968
16099
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
15969
|
-
expectedSha256:
|
|
16100
|
+
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
15970
16101
|
set: SkeletonSetSchema
|
|
15971
16102
|
}).strict();
|
|
15972
16103
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
15973
|
-
const root =
|
|
15974
|
-
const file =
|
|
15975
|
-
const relative =
|
|
15976
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16104
|
+
const root = import_node_path30.default.dirname(import_node_path30.default.resolve(manifestPath));
|
|
16105
|
+
const file = import_node_path30.default.resolve(root, request.keypointsFile);
|
|
16106
|
+
const relative = import_node_path30.default.relative(root, file);
|
|
16107
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || import_node_path30.default.isAbsolute(relative)) {
|
|
15977
16108
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15978
16109
|
}
|
|
15979
|
-
const existed = (0,
|
|
16110
|
+
const existed = (0, import_node_fs26.existsSync)(file);
|
|
15980
16111
|
let previous = null;
|
|
15981
16112
|
if (request.set) {
|
|
15982
16113
|
if (existed && !request.overwrite) {
|
|
15983
16114
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15984
16115
|
}
|
|
15985
|
-
if (existed) previous = await (0,
|
|
15986
|
-
await (0,
|
|
15987
|
-
await (0,
|
|
16116
|
+
if (existed) previous = await (0, import_promises23.readFile)(file);
|
|
16117
|
+
await (0, import_promises23.mkdir)(import_node_path30.default.dirname(file), { recursive: true });
|
|
16118
|
+
await (0, import_promises23.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15988
16119
|
} else if (!existed) {
|
|
15989
16120
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
15990
16121
|
}
|
|
@@ -15999,7 +16130,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15999
16130
|
revision: {
|
|
16000
16131
|
mode: "animate-skeleton",
|
|
16001
16132
|
from: request.from,
|
|
16002
|
-
keypointsFile: relative.split(
|
|
16133
|
+
keypointsFile: relative.split(import_node_path30.default.sep).join("/"),
|
|
16003
16134
|
direction: request.direction,
|
|
16004
16135
|
...request.description ? { description: request.description } : {},
|
|
16005
16136
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16008,8 +16139,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16008
16139
|
});
|
|
16009
16140
|
} catch (error) {
|
|
16010
16141
|
if (request.set) {
|
|
16011
|
-
if (previous) await (0,
|
|
16012
|
-
else await (0,
|
|
16142
|
+
if (previous) await (0, import_promises23.writeFile)(file, previous);
|
|
16143
|
+
else await (0, import_promises23.rm)(file, { force: true });
|
|
16013
16144
|
}
|
|
16014
16145
|
throw error;
|
|
16015
16146
|
}
|
|
@@ -16020,56 +16151,56 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16020
16151
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16021
16152
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16022
16153
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16023
|
-
const relative =
|
|
16024
|
-
if (relative.startsWith("..") ||
|
|
16154
|
+
const relative = import_node_path30.default.relative(ctx.loaded.root, file);
|
|
16155
|
+
if (relative.startsWith("..") || import_node_path30.default.isAbsolute(relative)) {
|
|
16025
16156
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16026
16157
|
}
|
|
16027
|
-
const current = (0,
|
|
16158
|
+
const current = (0, import_node_fs26.existsSync)(file) ? sha256(await (0, import_promises23.readFile)(file)) : null;
|
|
16028
16159
|
if (current !== request.expectedSha256) {
|
|
16029
16160
|
throw Object.assign(
|
|
16030
|
-
new Error(`${relative.split(
|
|
16161
|
+
new Error(`${relative.split(import_node_path30.default.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16031
16162
|
{ status: 409 }
|
|
16032
16163
|
);
|
|
16033
16164
|
}
|
|
16034
|
-
await (0,
|
|
16035
|
-
return relative.split(
|
|
16165
|
+
await (0, import_promises23.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16166
|
+
return relative.split(import_node_path30.default.sep).join("/");
|
|
16036
16167
|
}
|
|
16037
16168
|
|
|
16038
16169
|
// src/gallery/edit.ts
|
|
16039
|
-
var Base64 =
|
|
16040
|
-
var HandEditRequestSchema =
|
|
16041
|
-
|
|
16042
|
-
action:
|
|
16043
|
-
project:
|
|
16044
|
-
assetId:
|
|
16045
|
-
styleId:
|
|
16046
|
-
expectedSha256:
|
|
16170
|
+
var Base64 = import_zod12.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
16171
|
+
var HandEditRequestSchema = import_zod12.z.discriminatedUnion("action", [
|
|
16172
|
+
import_zod12.z.object({
|
|
16173
|
+
action: import_zod12.z.literal("start-edit"),
|
|
16174
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16175
|
+
assetId: import_zod12.z.string().min(1),
|
|
16176
|
+
styleId: import_zod12.z.string().min(1),
|
|
16177
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16047
16178
|
/** Also launch the author's editor on the file. */
|
|
16048
|
-
open:
|
|
16179
|
+
open: import_zod12.z.boolean().optional()
|
|
16049
16180
|
}).strict(),
|
|
16050
|
-
|
|
16051
|
-
action:
|
|
16052
|
-
project:
|
|
16053
|
-
assetId:
|
|
16054
|
-
styleId:
|
|
16055
|
-
expectedSha256:
|
|
16181
|
+
import_zod12.z.object({
|
|
16182
|
+
action: import_zod12.z.literal("detach-edit"),
|
|
16183
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16184
|
+
assetId: import_zod12.z.string().min(1),
|
|
16185
|
+
styleId: import_zod12.z.string().min(1),
|
|
16186
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/)
|
|
16056
16187
|
}).strict(),
|
|
16057
16188
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
16058
|
-
|
|
16059
|
-
action:
|
|
16060
|
-
project:
|
|
16061
|
-
assetId:
|
|
16062
|
-
styleId:
|
|
16063
|
-
expectedSha256:
|
|
16189
|
+
import_zod12.z.object({
|
|
16190
|
+
action: import_zod12.z.literal("save-edit"),
|
|
16191
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16192
|
+
assetId: import_zod12.z.string().min(1),
|
|
16193
|
+
styleId: import_zod12.z.string().min(1),
|
|
16194
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16064
16195
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
16065
16196
|
png: Base64.min(1).optional(),
|
|
16066
16197
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
16067
|
-
frames:
|
|
16198
|
+
frames: import_zod12.z.array(import_zod12.z.object({ role: import_zod12.z.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
16068
16199
|
/** The editor's layered project file, kept beside the edit. */
|
|
16069
16200
|
pxo: Base64.optional(),
|
|
16070
16201
|
/** Editor identity from the bridge's ready message. */
|
|
16071
|
-
editor:
|
|
16072
|
-
protocol:
|
|
16202
|
+
editor: import_zod12.z.string().min(1).max(200),
|
|
16203
|
+
protocol: import_zod12.z.number().int().positive()
|
|
16073
16204
|
}).strict()
|
|
16074
16205
|
]);
|
|
16075
16206
|
function createGalleryEditHandler(opts) {
|
|
@@ -16110,6 +16241,21 @@ function createGalleryEditHandler(opts) {
|
|
|
16110
16241
|
}
|
|
16111
16242
|
return opts.reload();
|
|
16112
16243
|
}
|
|
16244
|
+
const upload = UploadImageSchema.safeParse(body);
|
|
16245
|
+
if (upload.success) {
|
|
16246
|
+
let manifestPath2;
|
|
16247
|
+
try {
|
|
16248
|
+
manifestPath2 = await opts.manifestFor(upload.data.project);
|
|
16249
|
+
} catch (error) {
|
|
16250
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16251
|
+
}
|
|
16252
|
+
const written = await saveProjectImage(import_node_path31.default.dirname(import_node_path31.default.resolve(manifestPath2)), upload.data);
|
|
16253
|
+
log(` image added: ${written}`);
|
|
16254
|
+
return opts.reload();
|
|
16255
|
+
}
|
|
16256
|
+
if (body?.action === "upload-image") {
|
|
16257
|
+
throw new ManifestEditError("invalid upload: " + upload.error.issues.slice(0, 2).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
|
|
16258
|
+
}
|
|
16113
16259
|
const poses = SkeletonKeypointsUpdateSchema.safeParse(body);
|
|
16114
16260
|
if (poses.success) {
|
|
16115
16261
|
if (!opts.loadProject) throw new ManifestEditError("pose edits are not available in this gallery");
|
|
@@ -16153,19 +16299,35 @@ function createGalleryEditHandler(opts) {
|
|
|
16153
16299
|
}
|
|
16154
16300
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16155
16301
|
if (result.changed) {
|
|
16156
|
-
const relative =
|
|
16302
|
+
const relative = import_node_path31.default.relative(process.cwd(), result.manifestPath);
|
|
16157
16303
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16158
|
-
|
|
16159
|
-
log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
16304
|
+
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16160
16305
|
}
|
|
16161
16306
|
return opts.reload();
|
|
16162
16307
|
};
|
|
16163
16308
|
}
|
|
16309
|
+
function describeEdit(edit) {
|
|
16310
|
+
switch (edit.action) {
|
|
16311
|
+
case "batch":
|
|
16312
|
+
return `${edit.edits.length} changes (${edit.edits.map(describeEdit).join("; ")})`;
|
|
16313
|
+
case "add-style":
|
|
16314
|
+
return `added style ${edit.styleId}`;
|
|
16315
|
+
case "patch-style":
|
|
16316
|
+
return `changed style ${edit.styleId}`;
|
|
16317
|
+
case "set-source":
|
|
16318
|
+
case "clear-source":
|
|
16319
|
+
return `changed ${edit.styleId}/${edit.assetId} source`;
|
|
16320
|
+
case "add-asset":
|
|
16321
|
+
return `added ${edit.assetId}`;
|
|
16322
|
+
case "patch-asset":
|
|
16323
|
+
return `changed ${edit.assetId}`;
|
|
16324
|
+
}
|
|
16325
|
+
}
|
|
16164
16326
|
|
|
16165
16327
|
// src/gallery/snapshot.ts
|
|
16166
16328
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16167
16329
|
function galleryMediaId(absolutePath) {
|
|
16168
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
16330
|
+
return (0, import_node_crypto6.createHash)("sha256").update(import_node_path32.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16169
16331
|
}
|
|
16170
16332
|
function galleryMediaRoute(id) {
|
|
16171
16333
|
return `/media/${id}`;
|
|
@@ -16184,9 +16346,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16184
16346
|
return (entry.history ?? []).map((generation, i) => {
|
|
16185
16347
|
const outputs = generation.outputs.map((output) => {
|
|
16186
16348
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16187
|
-
const file =
|
|
16349
|
+
const file = import_node_path32.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16188
16350
|
let url = null;
|
|
16189
|
-
if ((0,
|
|
16351
|
+
if ((0, import_node_fs27.existsSync)(file)) {
|
|
16190
16352
|
const id = galleryMediaId(file);
|
|
16191
16353
|
media.set(id, { path: file, contentType: mediaType });
|
|
16192
16354
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16218,7 +16380,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16218
16380
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16219
16381
|
async function samePixels(a, b) {
|
|
16220
16382
|
try {
|
|
16221
|
-
const [left, right] = await Promise.all([(0,
|
|
16383
|
+
const [left, right] = await Promise.all([(0, import_promises24.readFile)(a), (0, import_promises24.readFile)(b)]);
|
|
16222
16384
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16223
16385
|
const x = decodePng(left);
|
|
16224
16386
|
const y = decodePng(right);
|
|
@@ -16284,7 +16446,7 @@ function describeCharacter(spec, entry) {
|
|
|
16284
16446
|
}
|
|
16285
16447
|
async function fileInfo(absolutePath) {
|
|
16286
16448
|
try {
|
|
16287
|
-
const info = await (0,
|
|
16449
|
+
const info = await (0, import_promises24.stat)(absolutePath);
|
|
16288
16450
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
16289
16451
|
} catch {
|
|
16290
16452
|
return null;
|
|
@@ -16316,10 +16478,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16316
16478
|
async function describeQuality(media, root, inspection) {
|
|
16317
16479
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16318
16480
|
const outputs = await Promise.all(
|
|
16319
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16481
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path32.default.resolve(root, file)))
|
|
16320
16482
|
);
|
|
16321
|
-
const recordPath =
|
|
16322
|
-
const recordExists = (0,
|
|
16483
|
+
const recordPath = import_node_path32.default.resolve(root, inspection.record);
|
|
16484
|
+
const recordExists = (0, import_node_fs27.existsSync)(recordPath);
|
|
16323
16485
|
let options = null;
|
|
16324
16486
|
let check = null;
|
|
16325
16487
|
if (recordExists) {
|
|
@@ -16338,7 +16500,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16338
16500
|
return {
|
|
16339
16501
|
state: inspection.state,
|
|
16340
16502
|
reason: inspection.reason,
|
|
16341
|
-
output: portableOutputPath(
|
|
16503
|
+
output: portableOutputPath(import_node_path32.default.resolve(root, inspection.output), root),
|
|
16342
16504
|
record: portableOutputPath(recordPath, root),
|
|
16343
16505
|
recordExists,
|
|
16344
16506
|
outputs,
|
|
@@ -16376,9 +16538,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16376
16538
|
const media = /* @__PURE__ */ new Map();
|
|
16377
16539
|
const plan = await buildPlan(specs, lock);
|
|
16378
16540
|
const items = [];
|
|
16379
|
-
const manifestText = await (0,
|
|
16541
|
+
const manifestText = await (0, import_promises24.readFile)(loaded.path, "utf8");
|
|
16380
16542
|
const manifestSha256 = sha256(manifestText);
|
|
16381
|
-
const cacheDir =
|
|
16543
|
+
const cacheDir = import_node_path32.default.resolve(import_node_path32.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16382
16544
|
let rawStyles = {};
|
|
16383
16545
|
try {
|
|
16384
16546
|
const raw = JSON.parse(manifestText);
|
|
@@ -16402,12 +16564,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16402
16564
|
)
|
|
16403
16565
|
);
|
|
16404
16566
|
if (spec.source) {
|
|
16405
|
-
const editPath =
|
|
16567
|
+
const editPath = import_node_path32.default.resolve(root, spec.source);
|
|
16406
16568
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16407
16569
|
const editShas = await Promise.all(
|
|
16408
16570
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16409
16571
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16410
|
-
return { file, sha256: (0,
|
|
16572
|
+
return { file, sha256: (0, import_node_fs27.existsSync)(file) ? await sha256File(file) : null };
|
|
16411
16573
|
})
|
|
16412
16574
|
);
|
|
16413
16575
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16415,7 +16577,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16415
16577
|
const companion = await readHandEditCompanion(editPath);
|
|
16416
16578
|
if (companion) {
|
|
16417
16579
|
const projectPath = handEditProjectPath(editPath);
|
|
16418
|
-
const project = companion.project && (0,
|
|
16580
|
+
const project = companion.project && (0, import_node_fs27.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
16419
16581
|
let projectUrl = null;
|
|
16420
16582
|
if (project) {
|
|
16421
16583
|
const id = galleryMediaId(projectPath);
|
|
@@ -16451,8 +16613,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16451
16613
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16452
16614
|
}
|
|
16453
16615
|
} else if (spec.source) {
|
|
16454
|
-
outputs = [await describeOutput(media, root,
|
|
16455
|
-
} else if (!entry && (0,
|
|
16616
|
+
outputs = [await describeOutput(media, root, import_node_path32.default.resolve(root, spec.source))];
|
|
16617
|
+
} else if (!entry && (0, import_node_fs27.existsSync)(spec.outFile)) {
|
|
16456
16618
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16457
16619
|
} else {
|
|
16458
16620
|
outputs = [];
|
|
@@ -16653,7 +16815,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16653
16815
|
id: loaded.manifest.name,
|
|
16654
16816
|
name: loaded.manifest.name,
|
|
16655
16817
|
manifest: loaded.path,
|
|
16656
|
-
lock:
|
|
16818
|
+
lock: import_node_path32.default.resolve(opts.lockPath),
|
|
16657
16819
|
root,
|
|
16658
16820
|
provider: loaded.manifest.provider,
|
|
16659
16821
|
account: null,
|
|
@@ -16679,7 +16841,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16679
16841
|
return { snapshot, media };
|
|
16680
16842
|
}
|
|
16681
16843
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16682
|
-
const dir =
|
|
16844
|
+
const dir = import_node_path32.default.dirname(import_node_path32.default.resolve(opts.workspacePath));
|
|
16683
16845
|
const media = /* @__PURE__ */ new Map();
|
|
16684
16846
|
const projects = [];
|
|
16685
16847
|
const items = [];
|
|
@@ -16722,11 +16884,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16722
16884
|
id: project.id,
|
|
16723
16885
|
name: loaded.manifest.name,
|
|
16724
16886
|
manifest: manifestPath,
|
|
16725
|
-
lock:
|
|
16887
|
+
lock: import_node_path32.default.resolve(lockPath),
|
|
16726
16888
|
root: loaded.root,
|
|
16727
16889
|
provider: loaded.manifest.provider,
|
|
16728
16890
|
account: project.account ?? null,
|
|
16729
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
16891
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises24.readFile)(manifestPath, "utf8")),
|
|
16730
16892
|
historyLimit: historyLimit(loaded.manifest),
|
|
16731
16893
|
entries: Object.keys(lock.entries).length,
|
|
16732
16894
|
items: projectItems.length,
|
|
@@ -16738,8 +16900,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16738
16900
|
id: project.id,
|
|
16739
16901
|
name: project.id,
|
|
16740
16902
|
manifest: manifestPath,
|
|
16741
|
-
lock:
|
|
16742
|
-
root:
|
|
16903
|
+
lock: import_node_path32.default.resolve(lockPath),
|
|
16904
|
+
root: import_node_path32.default.dirname(manifestPath),
|
|
16743
16905
|
provider: project.provider,
|
|
16744
16906
|
account: project.account ?? null,
|
|
16745
16907
|
manifestSha256: null,
|
|
@@ -16766,7 +16928,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16766
16928
|
version: 1,
|
|
16767
16929
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16768
16930
|
project: null,
|
|
16769
|
-
workspace: { path:
|
|
16931
|
+
workspace: { path: import_node_path32.default.resolve(opts.workspacePath), dir, projects },
|
|
16770
16932
|
filter,
|
|
16771
16933
|
totals: {
|
|
16772
16934
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16783,7 +16945,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16783
16945
|
}
|
|
16784
16946
|
|
|
16785
16947
|
// src/gallery/page.ts
|
|
16786
|
-
var
|
|
16948
|
+
var import_node_fs28 = require("fs");
|
|
16787
16949
|
|
|
16788
16950
|
// src/gallery/client-bundle.ts
|
|
16789
16951
|
var import_node_module = require("module");
|
|
@@ -16823,8 +16985,8 @@ var client;
|
|
|
16823
16985
|
function clientAssets() {
|
|
16824
16986
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16825
16987
|
client ??= {
|
|
16826
|
-
css: (0,
|
|
16827
|
-
js: (0,
|
|
16988
|
+
css: (0, import_node_fs28.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16989
|
+
js: (0, import_node_fs28.existsSync)(prebuilt) ? (0, import_node_fs28.readFileSync)(prebuilt, "utf8") : bundleGalleryClient()
|
|
16828
16990
|
};
|
|
16829
16991
|
return client;
|
|
16830
16992
|
}
|
|
@@ -16866,6 +17028,7 @@ ${css}</style>
|
|
|
16866
17028
|
<option value="none">Group: none</option>
|
|
16867
17029
|
</select>
|
|
16868
17030
|
<span id="editing" class="editing" hidden title="This gallery can write the manifest. It never contacts a provider.">editing</span>
|
|
17031
|
+
<button id="studio" type="button" hidden title="Draft a new character: its style, base, loops, and portrait, priced as you go">+ New character</button>
|
|
16869
17032
|
<button id="refresh" type="button" title="Re-read the manifest, lockfile, and disk">Refresh</button>
|
|
16870
17033
|
<label class="chip" title="Refresh every 5 seconds while this tab is visible"><input id="auto" type="checkbox"> auto</label>
|
|
16871
17034
|
</div>
|
|
@@ -16951,10 +17114,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16951
17114
|
|
|
16952
17115
|
// src/editor/install.ts
|
|
16953
17116
|
var import_node_crypto7 = require("crypto");
|
|
16954
|
-
var
|
|
16955
|
-
var
|
|
17117
|
+
var import_node_fs29 = require("fs");
|
|
17118
|
+
var import_promises25 = require("fs/promises");
|
|
16956
17119
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16957
|
-
var
|
|
17120
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
16958
17121
|
var EditorInstallError = class extends Error {
|
|
16959
17122
|
constructor(message6, file) {
|
|
16960
17123
|
super(message6);
|
|
@@ -16965,15 +17128,15 @@ var EditorInstallError = class extends Error {
|
|
|
16965
17128
|
};
|
|
16966
17129
|
function editorToolsRoot() {
|
|
16967
17130
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
16968
|
-
if (override) return
|
|
16969
|
-
if (process.platform === "darwin") return
|
|
17131
|
+
if (override) return import_node_path33.default.resolve(override);
|
|
17132
|
+
if (process.platform === "darwin") return import_node_path33.default.join(import_node_os3.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
16970
17133
|
if (process.platform === "win32") {
|
|
16971
|
-
return
|
|
17134
|
+
return import_node_path33.default.join(process.env.LOCALAPPDATA ?? import_node_path33.default.join(import_node_os3.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
16972
17135
|
}
|
|
16973
|
-
return
|
|
17136
|
+
return import_node_path33.default.join(process.env.XDG_CACHE_HOME ?? import_node_path33.default.join(import_node_os3.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
16974
17137
|
}
|
|
16975
17138
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
16976
|
-
return
|
|
17139
|
+
return import_node_path33.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
16977
17140
|
}
|
|
16978
17141
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
16979
17142
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -16983,7 +17146,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16983
17146
|
}
|
|
16984
17147
|
async function sha256Of(file) {
|
|
16985
17148
|
try {
|
|
16986
|
-
return (0, import_node_crypto7.createHash)("sha256").update(await (0,
|
|
17149
|
+
return (0, import_node_crypto7.createHash)("sha256").update(await (0, import_promises25.readFile)(file)).digest("hex");
|
|
16987
17150
|
} catch {
|
|
16988
17151
|
return null;
|
|
16989
17152
|
}
|
|
@@ -16996,8 +17159,8 @@ async function editorStatus(opts = {}) {
|
|
|
16996
17159
|
let totalBytes = 0;
|
|
16997
17160
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
16998
17161
|
totalBytes += expected.bytes;
|
|
16999
|
-
const file =
|
|
17000
|
-
const info = (0,
|
|
17162
|
+
const file = import_node_path33.default.join(dir, name);
|
|
17163
|
+
const info = (0, import_node_fs29.existsSync)(file) ? await (0, import_promises25.stat)(file).catch(() => null) : null;
|
|
17001
17164
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17002
17165
|
installedBytes += expected.bytes;
|
|
17003
17166
|
} else {
|
|
@@ -17026,7 +17189,7 @@ async function installEditor(opts = {}) {
|
|
|
17026
17189
|
);
|
|
17027
17190
|
}
|
|
17028
17191
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17029
|
-
await (0,
|
|
17192
|
+
await (0, import_promises25.mkdir)(dir, { recursive: true });
|
|
17030
17193
|
const downloaded = [];
|
|
17031
17194
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17032
17195
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17054,13 +17217,13 @@ async function installEditor(opts = {}) {
|
|
|
17054
17217
|
if (digest2 !== expected.sha256) {
|
|
17055
17218
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17056
17219
|
}
|
|
17057
|
-
const final =
|
|
17220
|
+
const final = import_node_path33.default.join(dir, name);
|
|
17058
17221
|
const tmp = `${final}.${process.pid}.part`;
|
|
17059
17222
|
try {
|
|
17060
|
-
await (0,
|
|
17061
|
-
await (0,
|
|
17223
|
+
await (0, import_promises25.writeFile)(tmp, bytes);
|
|
17224
|
+
await (0, import_promises25.rename)(tmp, final);
|
|
17062
17225
|
} finally {
|
|
17063
|
-
await (0,
|
|
17226
|
+
await (0, import_promises25.rm)(tmp, { force: true });
|
|
17064
17227
|
}
|
|
17065
17228
|
fetchedBytes += bytes.length;
|
|
17066
17229
|
downloaded.push(name);
|
|
@@ -17100,12 +17263,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17100
17263
|
".json": "application/json; charset=utf-8"
|
|
17101
17264
|
};
|
|
17102
17265
|
function editorContentType(name) {
|
|
17103
|
-
return EDITOR_CONTENT_TYPES[
|
|
17266
|
+
return EDITOR_CONTENT_TYPES[import_node_path33.default.extname(name).toLowerCase()] ?? null;
|
|
17104
17267
|
}
|
|
17105
17268
|
|
|
17106
17269
|
// src/gallery/editor.ts
|
|
17107
|
-
var
|
|
17108
|
-
var
|
|
17270
|
+
var import_promises26 = require("fs/promises");
|
|
17271
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
17109
17272
|
function editorRoute(release, name = "index.html") {
|
|
17110
17273
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17111
17274
|
}
|
|
@@ -17178,7 +17341,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17178
17341
|
if (!status.installed) return null;
|
|
17179
17342
|
let bytes;
|
|
17180
17343
|
try {
|
|
17181
|
-
bytes = await (0,
|
|
17344
|
+
bytes = await (0, import_promises26.readFile)(import_node_path34.default.join(dir, name));
|
|
17182
17345
|
} catch {
|
|
17183
17346
|
verified = null;
|
|
17184
17347
|
return null;
|
|
@@ -17195,7 +17358,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17195
17358
|
// src/gallery/server.ts
|
|
17196
17359
|
var import_node_crypto8 = require("crypto");
|
|
17197
17360
|
var import_node_http2 = require("http");
|
|
17198
|
-
var
|
|
17361
|
+
var import_promises27 = require("fs/promises");
|
|
17199
17362
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17200
17363
|
var EDITOR_CSP = [
|
|
17201
17364
|
"default-src 'self'",
|
|
@@ -17223,7 +17386,7 @@ async function readJsonBody(req) {
|
|
|
17223
17386
|
async function serveGallery(opts) {
|
|
17224
17387
|
const log = opts.onProgress ?? (() => {
|
|
17225
17388
|
});
|
|
17226
|
-
const session = opts.edit || opts.generate || opts.editor || opts.skeleton ? (0, import_node_crypto8.randomBytes)(16).toString("hex") : null;
|
|
17389
|
+
const session = opts.edit || opts.generate || opts.editor || opts.skeleton || opts.price ? (0, import_node_crypto8.randomBytes)(16).toString("hex") : null;
|
|
17227
17390
|
let media = /* @__PURE__ */ new Map();
|
|
17228
17391
|
let loading = null;
|
|
17229
17392
|
const reviewAssets = /* @__PURE__ */ new Map();
|
|
@@ -17298,6 +17461,17 @@ async function serveGallery(opts) {
|
|
|
17298
17461
|
}
|
|
17299
17462
|
return;
|
|
17300
17463
|
}
|
|
17464
|
+
if (req.method === "POST" && url2.pathname === "/api/price") {
|
|
17465
|
+
if (!opts.price || !session) return fail(405, "pricing an edit needs the gallery started with --edit");
|
|
17466
|
+
try {
|
|
17467
|
+
const body = await guardedBody("price requests");
|
|
17468
|
+
if (body === void 0) return;
|
|
17469
|
+
json(200, await opts.price(body));
|
|
17470
|
+
} catch (err) {
|
|
17471
|
+
reportError("price", err);
|
|
17472
|
+
}
|
|
17473
|
+
return;
|
|
17474
|
+
}
|
|
17301
17475
|
if (req.method === "POST" && url2.pathname === "/api/skeleton/estimate") {
|
|
17302
17476
|
if (!opts.skeleton || !session) return fail(405, "skeleton estimates need the gallery started with --edit");
|
|
17303
17477
|
try {
|
|
@@ -17365,7 +17539,7 @@ async function serveGallery(opts) {
|
|
|
17365
17539
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17366
17540
|
if (!asset) return fail(404, "no such review asset");
|
|
17367
17541
|
try {
|
|
17368
|
-
const bytes = await (0,
|
|
17542
|
+
const bytes = await (0, import_promises27.readFile)(asset.path);
|
|
17369
17543
|
res.writeHead(200, {
|
|
17370
17544
|
"Content-Type": asset.contentType,
|
|
17371
17545
|
"Cache-Control": "no-store",
|
|
@@ -17448,7 +17622,7 @@ async function serveGallery(opts) {
|
|
|
17448
17622
|
if (!asset) return fail(404, "no such gallery media");
|
|
17449
17623
|
let bytes;
|
|
17450
17624
|
try {
|
|
17451
|
-
bytes = await (0,
|
|
17625
|
+
bytes = await (0, import_promises27.readFile)(asset.path);
|
|
17452
17626
|
} catch {
|
|
17453
17627
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17454
17628
|
}
|
|
@@ -17492,27 +17666,27 @@ async function serveGallery(opts) {
|
|
|
17492
17666
|
|
|
17493
17667
|
// src/gallery/generate.ts
|
|
17494
17668
|
var import_node_crypto9 = require("crypto");
|
|
17495
|
-
var
|
|
17496
|
-
var GenerateRequestSchema =
|
|
17497
|
-
project:
|
|
17498
|
-
keys:
|
|
17669
|
+
var import_zod13 = require("zod");
|
|
17670
|
+
var GenerateRequestSchema = import_zod13.z.object({
|
|
17671
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
17672
|
+
keys: import_zod13.z.array(import_zod13.z.string().min(1)).min(1).max(500),
|
|
17499
17673
|
/**
|
|
17500
17674
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17501
17675
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17502
17676
|
* the recorded bytes back over a modified file.
|
|
17503
17677
|
*/
|
|
17504
|
-
force:
|
|
17678
|
+
force: import_zod13.z.boolean().optional(),
|
|
17505
17679
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17506
|
-
resume:
|
|
17680
|
+
resume: import_zod13.z.boolean().optional(),
|
|
17507
17681
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17508
|
-
refresh:
|
|
17682
|
+
refresh: import_zod13.z.boolean().optional(),
|
|
17509
17683
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17510
|
-
restore:
|
|
17684
|
+
restore: import_zod13.z.boolean().optional(),
|
|
17511
17685
|
/**
|
|
17512
17686
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17513
17687
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17514
17688
|
*/
|
|
17515
|
-
revert:
|
|
17689
|
+
revert: import_zod13.z.string().min(1).max(64).optional()
|
|
17516
17690
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17517
17691
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17518
17692
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -17636,6 +17810,70 @@ function createGenerateHandlers(opts) {
|
|
|
17636
17810
|
job.phase = "done";
|
|
17637
17811
|
job.finishedAt = now().toISOString();
|
|
17638
17812
|
}
|
|
17813
|
+
async function submitGroups(job, ctx, groups) {
|
|
17814
|
+
job.phase = "submitting";
|
|
17815
|
+
for (const group of groups) {
|
|
17816
|
+
const provider = opts.providerFor(job.project ?? void 0, group.provider);
|
|
17817
|
+
const ceiling = remainingFor(group, groups.length, opts.budget, spent);
|
|
17818
|
+
if (provider.balance) {
|
|
17819
|
+
const balance = await provider.balance();
|
|
17820
|
+
say(job, `balance: ${formatCost(balance.unit, balance.remaining)} remaining (${group.provider})`);
|
|
17821
|
+
if (balance.unit !== group.costUnit) {
|
|
17822
|
+
throw new Error(
|
|
17823
|
+
`Provider ${group.provider} estimate unit ${group.costUnit} does not match balance unit ${balance.unit}.`
|
|
17824
|
+
);
|
|
17825
|
+
}
|
|
17826
|
+
if (balance.unit !== "free" && group.cost > balance.remaining) {
|
|
17827
|
+
throw new Error(
|
|
17828
|
+
`${group.provider} needs ${formatCost(balance.unit, group.cost)} but only ${formatCost(balance.unit, balance.remaining)} remain.`
|
|
17829
|
+
);
|
|
17830
|
+
}
|
|
17831
|
+
}
|
|
17832
|
+
const res = await submit(provider, ctx.loaded, group.actionable, ctx.lock, ctx.lockPath, {
|
|
17833
|
+
budget: ceiling,
|
|
17834
|
+
onProgress: (line) => say(job, line),
|
|
17835
|
+
spacingMs: opts.submitSpacingMs
|
|
17836
|
+
});
|
|
17837
|
+
spent[group.provider] = (spent[group.provider] ?? 0) + res.spent;
|
|
17838
|
+
units[group.provider] = res.unit;
|
|
17839
|
+
job.spent[group.provider] = (job.spent[group.provider] ?? 0) + res.spent;
|
|
17840
|
+
job.counts.submitted += res.submitted;
|
|
17841
|
+
job.counts.failed += res.failed;
|
|
17842
|
+
say(job, `${group.provider}: submitted ${res.submitted}, failed ${res.failed}, estimated ${formatCost(res.unit, res.spent)}`);
|
|
17843
|
+
}
|
|
17844
|
+
}
|
|
17845
|
+
async function generateInWaves(job, ctx, specs, groups) {
|
|
17846
|
+
for (let wave = 1; ; wave++) {
|
|
17847
|
+
const submittedBefore = job.counts.submitted;
|
|
17848
|
+
await submitGroups(job, ctx, groups);
|
|
17849
|
+
await settle(job, ctx, specs);
|
|
17850
|
+
if (job.phase !== "done" || job.counts.submitted === submittedBefore) return;
|
|
17851
|
+
const finishedAt = job.finishedAt;
|
|
17852
|
+
const finish = () => {
|
|
17853
|
+
job.phase = "done";
|
|
17854
|
+
job.finishedAt = finishedAt;
|
|
17855
|
+
};
|
|
17856
|
+
job.phase = "queued";
|
|
17857
|
+
job.finishedAt = null;
|
|
17858
|
+
const next = await opts.loadProject(job.project ?? void 0);
|
|
17859
|
+
const byKey = new Map(next.specs.map((spec) => [lockKey(spec.styleId, spec.assetId), spec]));
|
|
17860
|
+
const nextSpecs = job.keys.map((key) => byKey.get(key)).filter((spec) => Boolean(spec));
|
|
17861
|
+
const plan = await buildPlan(nextSpecs, next.lock);
|
|
17862
|
+
if (!plan.actionable.length) return finish();
|
|
17863
|
+
for (const group of plan.groups) {
|
|
17864
|
+
const remaining = remainingFor(group, plan.groups.length, opts.budget, spent);
|
|
17865
|
+
if (group.cost > remaining) {
|
|
17866
|
+
say(job, `wave ${wave + 1} would spend ${formatCost(group.costUnit, group.cost)} on ${group.provider} but only ${formatCost(group.costUnit, remaining)} of this session's budget remain; stopping here`);
|
|
17867
|
+
return finish();
|
|
17868
|
+
}
|
|
17869
|
+
}
|
|
17870
|
+
say(job, `wave ${wave + 1}: ${plan.actionable.length} asset(s) whose parents are now on disk: ` + plan.groups.map((g) => `${g.provider} ${formatCost(g.costUnit, g.cost)}`).join("; "));
|
|
17871
|
+
contexts.set(job.id, next);
|
|
17872
|
+
ctx = next;
|
|
17873
|
+
specs = nextSpecs;
|
|
17874
|
+
groups = plan.groups;
|
|
17875
|
+
}
|
|
17876
|
+
}
|
|
17639
17877
|
async function run(job, ctx, specs, groups) {
|
|
17640
17878
|
try {
|
|
17641
17879
|
if (job.mode === "refresh") {
|
|
@@ -17663,36 +17901,8 @@ function createGenerateHandlers(opts) {
|
|
|
17663
17901
|
return;
|
|
17664
17902
|
}
|
|
17665
17903
|
if (job.mode === "generate") {
|
|
17666
|
-
job
|
|
17667
|
-
|
|
17668
|
-
const provider = opts.providerFor(job.project ?? void 0, group.provider);
|
|
17669
|
-
const ceiling = remainingFor(group, groups.length, opts.budget, spent);
|
|
17670
|
-
if (provider.balance) {
|
|
17671
|
-
const balance = await provider.balance();
|
|
17672
|
-
say(job, `balance: ${formatCost(balance.unit, balance.remaining)} remaining (${group.provider})`);
|
|
17673
|
-
if (balance.unit !== group.costUnit) {
|
|
17674
|
-
throw new Error(
|
|
17675
|
-
`Provider ${group.provider} estimate unit ${group.costUnit} does not match balance unit ${balance.unit}.`
|
|
17676
|
-
);
|
|
17677
|
-
}
|
|
17678
|
-
if (balance.unit !== "free" && group.cost > balance.remaining) {
|
|
17679
|
-
throw new Error(
|
|
17680
|
-
`${group.provider} needs ${formatCost(balance.unit, group.cost)} but only ${formatCost(balance.unit, balance.remaining)} remain.`
|
|
17681
|
-
);
|
|
17682
|
-
}
|
|
17683
|
-
}
|
|
17684
|
-
const res = await submit(provider, ctx.loaded, group.actionable, ctx.lock, ctx.lockPath, {
|
|
17685
|
-
budget: ceiling,
|
|
17686
|
-
onProgress: (line) => say(job, line),
|
|
17687
|
-
spacingMs: opts.submitSpacingMs
|
|
17688
|
-
});
|
|
17689
|
-
spent[group.provider] = (spent[group.provider] ?? 0) + res.spent;
|
|
17690
|
-
units[group.provider] = res.unit;
|
|
17691
|
-
job.spent[group.provider] = (job.spent[group.provider] ?? 0) + res.spent;
|
|
17692
|
-
job.counts.submitted += res.submitted;
|
|
17693
|
-
job.counts.failed += res.failed;
|
|
17694
|
-
say(job, `${group.provider}: submitted ${res.submitted}, failed ${res.failed}, estimated ${formatCost(res.unit, res.spent)}`);
|
|
17695
|
-
}
|
|
17904
|
+
await generateInWaves(job, ctx, specs, groups);
|
|
17905
|
+
return;
|
|
17696
17906
|
}
|
|
17697
17907
|
await settle(job, ctx, specs);
|
|
17698
17908
|
} catch (error) {
|
|
@@ -17822,19 +18032,19 @@ function createGenerateHandlers(opts) {
|
|
|
17822
18032
|
}
|
|
17823
18033
|
|
|
17824
18034
|
// src/pipeline/salvage.ts
|
|
17825
|
-
var
|
|
17826
|
-
var
|
|
17827
|
-
var
|
|
18035
|
+
var import_promises28 = require("fs/promises");
|
|
18036
|
+
var import_node_fs30 = require("fs");
|
|
18037
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
17828
18038
|
function providerClaimId(provider, objectId) {
|
|
17829
18039
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
17830
18040
|
}
|
|
17831
18041
|
async function loadClaims(lockPaths, opts = {}) {
|
|
17832
18042
|
const claimed = /* @__PURE__ */ new Set();
|
|
17833
18043
|
for (const p of lockPaths) {
|
|
17834
|
-
if (!(0,
|
|
18044
|
+
if (!(0, import_node_fs30.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17835
18045
|
let parsed;
|
|
17836
18046
|
try {
|
|
17837
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
18047
|
+
parsed = parseLock(JSON.parse(await (0, import_promises28.readFile)(p, "utf8")));
|
|
17838
18048
|
} catch {
|
|
17839
18049
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17840
18050
|
}
|
|
@@ -17938,20 +18148,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
17938
18148
|
return matchStyleByPattern(prompt, manifest);
|
|
17939
18149
|
}
|
|
17940
18150
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
17941
|
-
const own =
|
|
18151
|
+
const own = import_node_path35.default.resolve(ownManifestPath);
|
|
17942
18152
|
const siblingManifestPaths = [
|
|
17943
18153
|
.../* @__PURE__ */ new Set([
|
|
17944
18154
|
...workspaceManifestPaths,
|
|
17945
|
-
...claimPaths.map((c) =>
|
|
18155
|
+
...claimPaths.map((c) => import_node_path35.default.join(import_node_path35.default.dirname(import_node_path35.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
17946
18156
|
])
|
|
17947
18157
|
];
|
|
17948
18158
|
const siblings = [];
|
|
17949
18159
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
17950
|
-
if (
|
|
17951
|
-
if (!(0,
|
|
18160
|
+
if (import_node_path35.default.resolve(siblingManifestPath) === own) continue;
|
|
18161
|
+
if (!(0, import_node_fs30.existsSync)(siblingManifestPath)) continue;
|
|
17952
18162
|
try {
|
|
17953
18163
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
17954
|
-
siblings.push({ label:
|
|
18164
|
+
siblings.push({ label: import_node_path35.default.basename(import_node_path35.default.dirname(siblingManifestPath)), manifest });
|
|
17955
18165
|
} catch {
|
|
17956
18166
|
}
|
|
17957
18167
|
}
|
|
@@ -18071,8 +18281,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18071
18281
|
}
|
|
18072
18282
|
|
|
18073
18283
|
// src/pick/salvage-server.ts
|
|
18074
|
-
var
|
|
18075
|
-
var
|
|
18284
|
+
var import_promises29 = require("fs/promises");
|
|
18285
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
18076
18286
|
|
|
18077
18287
|
// src/pick/salvage-sheet.ts
|
|
18078
18288
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18238,7 +18448,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18238
18448
|
});
|
|
18239
18449
|
const html = renderSalvageSheet(orphans, {
|
|
18240
18450
|
styleId: ctx.styleId,
|
|
18241
|
-
importDir:
|
|
18451
|
+
importDir: import_node_path36.default.relative(process.cwd(), ctx.importDir) || "."
|
|
18242
18452
|
});
|
|
18243
18453
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18244
18454
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18294,10 +18504,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18294
18504
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18295
18505
|
decodePng(buf);
|
|
18296
18506
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18297
|
-
const rel =
|
|
18298
|
-
const outFile =
|
|
18299
|
-
await (0,
|
|
18300
|
-
await (0,
|
|
18507
|
+
const rel = import_node_path36.default.join("_salvaged", `${assetId}.png`);
|
|
18508
|
+
const outFile = import_node_path36.default.resolve(ctx.importDir, rel);
|
|
18509
|
+
await (0, import_promises29.mkdir)(import_node_path36.default.dirname(outFile), { recursive: true });
|
|
18510
|
+
await (0, import_promises29.writeFile)(outFile, buf);
|
|
18301
18511
|
ctx.manifest.assets[assetId] = {
|
|
18302
18512
|
prompt: orphan.prompt,
|
|
18303
18513
|
promptByStyle: {},
|
|
@@ -18329,7 +18539,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18329
18539
|
sourceUrl: durableSource,
|
|
18330
18540
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18331
18541
|
outputs: [{
|
|
18332
|
-
path: portableOutputPath(outFile,
|
|
18542
|
+
path: portableOutputPath(outFile, import_node_path36.default.dirname(ctx.manifestPath)),
|
|
18333
18543
|
sha256: sha256(buf)
|
|
18334
18544
|
}],
|
|
18335
18545
|
submittedAt: orphan.createdAt,
|
|
@@ -18353,9 +18563,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18353
18563
|
}
|
|
18354
18564
|
}
|
|
18355
18565
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18356
|
-
const raw = JSON.parse(await (0,
|
|
18566
|
+
const raw = JSON.parse(await (0, import_promises29.readFile)(ctx.manifestPath, "utf8"));
|
|
18357
18567
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18358
|
-
await (0,
|
|
18568
|
+
await (0, import_promises29.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18359
18569
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18360
18570
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18361
18571
|
return result;
|