pixelkiln 0.74.0 → 0.76.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 +139 -14
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +51 -0
- package/dist/client/gallery.js +1021 -537
- package/dist/index.cjs +34 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +19 -2
- package/docs/CLI.md +3 -1
- package/docs/GENERATORS.md +3 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6139,6 +6139,25 @@ function cleanupResults(images, sent, operation) {
|
|
|
6139
6139
|
}
|
|
6140
6140
|
return images.map((image) => Buffer.from(image.base64, "base64"));
|
|
6141
6141
|
}
|
|
6142
|
+
function parseProFlashQuote(raw) {
|
|
6143
|
+
const body = raw && typeof raw === "object" ? raw : {};
|
|
6144
|
+
const nested = [body, body.usage, body.cost, body.estimate].filter((v) => Boolean(v) && typeof v === "object");
|
|
6145
|
+
const pick = (...names) => {
|
|
6146
|
+
for (const obj of nested) {
|
|
6147
|
+
for (const name of names) {
|
|
6148
|
+
const value = obj[name];
|
|
6149
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
return null;
|
|
6153
|
+
};
|
|
6154
|
+
const image = pick("image", "image_generations", "first_image", "first_image_generations");
|
|
6155
|
+
const rotations = pick("rotations", "rotation", "rotations_generations", "rotation_generations");
|
|
6156
|
+
const total = pick("total", "total_generations", "generations");
|
|
6157
|
+
if (total !== null) return { image, rotations, total };
|
|
6158
|
+
if (image !== null || rotations !== null) return { image, rotations, total: (image ?? 0) + (rotations ?? 0) };
|
|
6159
|
+
throw new Error(`GET /pro-flash/cost returned no price: ${JSON.stringify(raw).slice(0, 200)}`);
|
|
6160
|
+
}
|
|
6142
6161
|
var PixelLabError = class extends ProviderError {
|
|
6143
6162
|
constructor(message6, status, body) {
|
|
6144
6163
|
super("pixellab", message6, { status });
|
|
@@ -6197,6 +6216,19 @@ var PixelLabClient = class {
|
|
|
6197
6216
|
plan: raw.subscription?.plan ?? "unknown"
|
|
6198
6217
|
};
|
|
6199
6218
|
}
|
|
6219
|
+
/**
|
|
6220
|
+
* `GET /pro-flash/cost`: PixelLab's own quote for one Pro Flash job, split
|
|
6221
|
+
* into the first image and the v3 rotations. Free, and provisional by
|
|
6222
|
+
* PixelLab's own description; the billed amount on the finished job is
|
|
6223
|
+
* the real one. The response schema is undocumented (`{}` in the spec),
|
|
6224
|
+
* so this reads it leniently: a total if one is given, else the sum of
|
|
6225
|
+
* the parts, and refuses an answer with no number in it.
|
|
6226
|
+
*/
|
|
6227
|
+
async proFlashCost(args) {
|
|
6228
|
+
const query = new URLSearchParams({ operation: args.operation, width: String(args.width), height: String(args.height) });
|
|
6229
|
+
if (args.nDirections !== void 0) query.set("n_directions", String(args.nDirections));
|
|
6230
|
+
return parseProFlashQuote(await this.request(`/pro-flash/cost?${query}`));
|
|
6231
|
+
}
|
|
6200
6232
|
/**
|
|
6201
6233
|
* Square objects that persist indefinitely.
|
|
6202
6234
|
*
|
|
@@ -7333,6 +7365,65 @@ function proFlashImageCost(width, height) {
|
|
|
7333
7365
|
const side = Math.max(width, height);
|
|
7334
7366
|
return side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
7335
7367
|
}
|
|
7368
|
+
function proFlashQuoteQuery(spec) {
|
|
7369
|
+
if (spec.provider !== "pixellab" || spec.mirror) return null;
|
|
7370
|
+
if (spec.revision) {
|
|
7371
|
+
if (spec.revision.engine !== "pro-flash") return null;
|
|
7372
|
+
return {
|
|
7373
|
+
operation: spec.revision.mode === "inpaint" ? "inpaint" : "edit",
|
|
7374
|
+
width: spec.revision.sourceWidth ?? spec.width,
|
|
7375
|
+
height: spec.revision.sourceHeight ?? spec.height,
|
|
7376
|
+
part: "total"
|
|
7377
|
+
};
|
|
7378
|
+
}
|
|
7379
|
+
if (spec.generator === "imageProFlash") return { operation: "create", width: spec.width, height: spec.height, part: "total" };
|
|
7380
|
+
const character = spec.character;
|
|
7381
|
+
if (spec.generator === "character" && character?.kind === "base" && character.mode === "pro-flash") {
|
|
7382
|
+
const south = character.reference?.south;
|
|
7383
|
+
return south ? { operation: "character", width: south.width, height: south.height, nDirections: 8, part: "rotations" } : { operation: "character", width: spec.width, height: spec.height, nDirections: 8, part: "total" };
|
|
7384
|
+
}
|
|
7385
|
+
const object = spec.objectPro;
|
|
7386
|
+
if (spec.generator === "objectPro" && object?.kind === "base") {
|
|
7387
|
+
const south = object.reference;
|
|
7388
|
+
return south ? { operation: "object", width: south.width, height: south.height, nDirections: object.directions, part: "rotations" } : { operation: "object", width: spec.width, height: spec.height, nDirections: object.directions, part: "total" };
|
|
7389
|
+
}
|
|
7390
|
+
return null;
|
|
7391
|
+
}
|
|
7392
|
+
function createProFlashQuoter(client2, opts = {}) {
|
|
7393
|
+
const ttl = opts.ttlMs ?? 10 * 6e4;
|
|
7394
|
+
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
7395
|
+
const now = opts.now ?? Date.now;
|
|
7396
|
+
const cache = /* @__PURE__ */ new Map();
|
|
7397
|
+
return async (spec) => {
|
|
7398
|
+
const query = proFlashQuoteQuery(spec);
|
|
7399
|
+
if (!query) return null;
|
|
7400
|
+
const key = `${query.operation}:${query.width}x${query.height}:${query.nDirections ?? ""}`;
|
|
7401
|
+
let hit = cache.get(key);
|
|
7402
|
+
if (!hit || now() - hit.at > ttl) {
|
|
7403
|
+
let timer;
|
|
7404
|
+
const answer = Promise.race([
|
|
7405
|
+
client2.proFlashCost({
|
|
7406
|
+
operation: query.operation,
|
|
7407
|
+
width: query.width,
|
|
7408
|
+
height: query.height,
|
|
7409
|
+
...query.nDirections !== void 0 ? { nDirections: query.nDirections } : {}
|
|
7410
|
+
}),
|
|
7411
|
+
new Promise((_, reject) => {
|
|
7412
|
+
timer = setTimeout(() => reject(new Error(`PixelLab's Pro Flash quote did not answer within ${timeoutMs / 1e3}s`)), timeoutMs);
|
|
7413
|
+
})
|
|
7414
|
+
]).finally(() => clearTimeout(timer));
|
|
7415
|
+
hit = { at: now(), answer };
|
|
7416
|
+
cache.set(key, hit);
|
|
7417
|
+
answer.catch(() => {
|
|
7418
|
+
if (cache.get(key) === hit) cache.delete(key);
|
|
7419
|
+
});
|
|
7420
|
+
}
|
|
7421
|
+
const quote = await hit.answer;
|
|
7422
|
+
if (query.part === "total") return quote.total;
|
|
7423
|
+
if (quote.rotations === null) throw new Error("PixelLab's Pro Flash quote gave no rotations figure for a base drawn from a sprite");
|
|
7424
|
+
return quote.rotations;
|
|
7425
|
+
};
|
|
7426
|
+
}
|
|
7336
7427
|
function characterAnimationName(spec) {
|
|
7337
7428
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
7338
7429
|
}
|
|
@@ -14208,31 +14299,43 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
14208
14299
|
(absolute, original) => ({ manifestPath: absolute, sha256: sha256(original), changed: false })
|
|
14209
14300
|
);
|
|
14210
14301
|
}
|
|
14211
|
-
async function priceManifestEdit(manifestPath, edit) {
|
|
14302
|
+
async function priceManifestEdit(manifestPath, edit, opts = {}) {
|
|
14212
14303
|
return withCandidate(
|
|
14213
14304
|
manifestPath,
|
|
14214
14305
|
edit,
|
|
14215
14306
|
async ({ absolute, specs }) => {
|
|
14216
14307
|
const before = new Map((await resolveSpecs(await loadManifest(absolute))).map((spec) => [`${spec.styleId}/${spec.assetId}`, spec.specHash]));
|
|
14308
|
+
const priced = specs.filter((spec) => before.get(`${spec.styleId}/${spec.assetId}`) !== spec.specHash);
|
|
14309
|
+
let quoteError;
|
|
14310
|
+
const live = await Promise.all(priced.map(async (spec) => {
|
|
14311
|
+
if (!opts.quote) return null;
|
|
14312
|
+
try {
|
|
14313
|
+
return await opts.quote(spec);
|
|
14314
|
+
} catch (error) {
|
|
14315
|
+
quoteError ??= error instanceof Error ? error.message : String(error);
|
|
14316
|
+
return null;
|
|
14317
|
+
}
|
|
14318
|
+
}));
|
|
14217
14319
|
const items = [];
|
|
14218
14320
|
const totals = {};
|
|
14219
|
-
|
|
14321
|
+
priced.forEach((spec, index) => {
|
|
14220
14322
|
const key = `${spec.styleId}/${spec.assetId}`;
|
|
14221
|
-
const
|
|
14222
|
-
|
|
14323
|
+
const quoted = live[index];
|
|
14324
|
+
const cost = quoted ?? spec.cost;
|
|
14223
14325
|
items.push({
|
|
14224
14326
|
key,
|
|
14225
14327
|
styleId: spec.styleId,
|
|
14226
14328
|
assetId: spec.assetId,
|
|
14227
|
-
change:
|
|
14329
|
+
change: before.has(key) ? "changed" : "new",
|
|
14228
14330
|
provider: spec.provider,
|
|
14229
|
-
cost
|
|
14331
|
+
cost,
|
|
14230
14332
|
costUnit: spec.costUnit,
|
|
14231
|
-
candidates: spec.candidates
|
|
14333
|
+
candidates: spec.candidates,
|
|
14334
|
+
...quoted !== null && quoted !== void 0 ? { quote: "live", estimate: spec.cost } : {}
|
|
14232
14335
|
});
|
|
14233
|
-
totals[spec.costUnit] = (totals[spec.costUnit] ?? 0) +
|
|
14234
|
-
}
|
|
14235
|
-
return { items, totals };
|
|
14336
|
+
totals[spec.costUnit] = (totals[spec.costUnit] ?? 0) + cost;
|
|
14337
|
+
});
|
|
14338
|
+
return quoteError ? { items, totals, quoteError } : { items, totals };
|
|
14236
14339
|
},
|
|
14237
14340
|
() => ({ items: [], totals: {} })
|
|
14238
14341
|
);
|
|
@@ -14895,7 +14998,15 @@ function createGalleryPriceHandler(opts) {
|
|
|
14895
14998
|
} catch (error) {
|
|
14896
14999
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
14897
15000
|
}
|
|
14898
|
-
|
|
15001
|
+
let quote = null;
|
|
15002
|
+
let quoteError;
|
|
15003
|
+
try {
|
|
15004
|
+
quote = opts.quoteFor ? await opts.quoteFor(parsed.data.project) : null;
|
|
15005
|
+
} catch (error) {
|
|
15006
|
+
quoteError = error instanceof Error ? error.message : String(error);
|
|
15007
|
+
}
|
|
15008
|
+
const price = await priceManifestEdit(manifestPath, parsed.data, quote ? { quote } : {});
|
|
15009
|
+
return quoteError && !price.quoteError ? { ...price, quoteError } : price;
|
|
14899
15010
|
};
|
|
14900
15011
|
}
|
|
14901
15012
|
|
|
@@ -17879,7 +17990,10 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17879
17990
|
};
|
|
17880
17991
|
const loadProject = async (project) => {
|
|
17881
17992
|
const ctx = await access2.loadProject(project);
|
|
17882
|
-
|
|
17993
|
+
loadProjectEnv(path34.dirname(ctx.loaded.path));
|
|
17994
|
+
return ctx;
|
|
17995
|
+
};
|
|
17996
|
+
const loadProjectEnv = (dir) => {
|
|
17883
17997
|
const declared = readEnvFiles(dir);
|
|
17884
17998
|
const credentialNames = new Set(availableProviders().flatMap((id) => providerCredentialEnvs(providerFactory(id))));
|
|
17885
17999
|
for (const [name, value] of Object.entries(declared)) {
|
|
@@ -17890,7 +18004,18 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17890
18004
|
}
|
|
17891
18005
|
}
|
|
17892
18006
|
loadEnvFiles(dir);
|
|
17893
|
-
|
|
18007
|
+
};
|
|
18008
|
+
const quoters = /* @__PURE__ */ new Map();
|
|
18009
|
+
const quoteFor = async (project) => {
|
|
18010
|
+
loadProjectEnv(path34.dirname(path34.resolve(await access2.manifestFor(project))));
|
|
18011
|
+
const key = process.env.PIXELLAB_API_KEY;
|
|
18012
|
+
if (!key) return null;
|
|
18013
|
+
let quoter = quoters.get(key);
|
|
18014
|
+
if (!quoter) {
|
|
18015
|
+
quoter = createProFlashQuoter(new PixelLabClient(key));
|
|
18016
|
+
quoters.set(key, quoter);
|
|
18017
|
+
}
|
|
18018
|
+
return quoter;
|
|
17894
18019
|
};
|
|
17895
18020
|
const server = await serveGallery({
|
|
17896
18021
|
load: () => {
|
|
@@ -17913,7 +18038,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17913
18038
|
),
|
|
17914
18039
|
...args.edit ? { edit: createGalleryEditHandler({ manifestFor: access2.manifestFor, loadProject: access2.loadProject, reload, onProgress: log }) } : {},
|
|
17915
18040
|
...budget ? { generate: createGenerateHandlers({ loadProject, providerFor, budget, reload, onProgress: log }) } : {},
|
|
17916
|
-
...args.edit ? { price: createGalleryPriceHandler({ manifestFor: access2.manifestFor }) } : {},
|
|
18041
|
+
...args.edit ? { price: createGalleryPriceHandler({ manifestFor: access2.manifestFor, quoteFor }) } : {},
|
|
17917
18042
|
// Estimating acts on the author's behalf and feeds a manifest edit, so it follows the write gate.
|
|
17918
18043
|
...args.edit ? { skeleton: createGallerySkeletonHandlers({ loadProject, limit: args.estimateLimit, onProgress: log }) } : {},
|
|
17919
18044
|
// The editor exists to write hand edits back, so it follows the write gate.
|