@koda-sl/baker-cli 0.439.0-dev.33cf4ca99 → 0.439.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -22
- package/dist/{chunk-APTNJ2EY.js → chunk-6PP3MBGZ.js} +320 -133
- package/dist/chunk-6PP3MBGZ.js.map +1 -0
- package/dist/{chunk-UKHD423A.js → chunk-7THIX4WW.js} +8 -5
- package/dist/{chunk-UKHD423A.js.map → chunk-7THIX4WW.js.map} +1 -1
- package/dist/{chunk-5DJPTQU5.js → chunk-EURJVJQY.js} +2 -2
- package/dist/{chunk-PR3CXBAT.js → chunk-F7LXMR73.js} +13 -4
- package/dist/chunk-F7LXMR73.js.map +1 -0
- package/dist/{chunk-32UJLGZX.js → chunk-QLNGHVRT.js} +11 -3
- package/dist/chunk-QLNGHVRT.js.map +1 -0
- package/dist/{chunk-KP33GIDZ.js → chunk-VNROEZTR.js} +4 -4
- package/dist/cli.js +15941 -12517
- package/dist/cli.js.map +1 -1
- package/dist/{client-JM3QBGR2.js → client-TCDMWAL3.js} +4 -4
- package/dist/engine/index.d.ts +3 -6
- package/dist/engine/index.js +3 -3
- package/dist/{env-WIAWLTDN.js → env-ZPCFZJN5.js} +4 -2
- package/dist/output-CW4JBWIM.js +22 -0
- package/dist/{shared-DJTAT7CM.js → shared-6I2TP4YQ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-32UJLGZX.js.map +0 -1
- package/dist/chunk-APTNJ2EY.js.map +0 -1
- package/dist/chunk-PR3CXBAT.js.map +0 -1
- package/dist/output-UGMWOFQR.js +0 -18
- /package/dist/{chunk-5DJPTQU5.js.map → chunk-EURJVJQY.js.map} +0 -0
- /package/dist/{chunk-KP33GIDZ.js.map → chunk-VNROEZTR.js.map} +0 -0
- /package/dist/{client-JM3QBGR2.js.map → client-TCDMWAL3.js.map} +0 -0
- /package/dist/{env-WIAWLTDN.js.map → env-ZPCFZJN5.js.map} +0 -0
- /package/dist/{output-UGMWOFQR.js.map → output-CW4JBWIM.js.map} +0 -0
- /package/dist/{shared-DJTAT7CM.js.map → shared-6I2TP4YQ.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debugLogHttp,
|
|
3
3
|
readBodyForLog
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EURJVJQY.js";
|
|
5
5
|
import {
|
|
6
6
|
API_KEY_FORMAT_MESSAGE,
|
|
7
7
|
DIRECT_ROUTE,
|
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
refuseNonPublicUrl,
|
|
23
23
|
requireConnection,
|
|
24
24
|
shouldEscalate
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-F7LXMR73.js";
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
|
|
28
28
|
var require_safe_stable_stringify = __commonJS({
|
|
29
|
-
"
|
|
29
|
+
"../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
30
30
|
"use strict";
|
|
31
31
|
var { hasOwnProperty } = Object.prototype;
|
|
32
32
|
var stringify = configure2();
|
|
@@ -1408,7 +1408,7 @@ function resolveAdaptFormats(params) {
|
|
|
1408
1408
|
return params.formats ?? [];
|
|
1409
1409
|
}
|
|
1410
1410
|
|
|
1411
|
-
//
|
|
1411
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1412
1412
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1413
1413
|
var configure = import__.default.configure;
|
|
1414
1414
|
var wrapper_default = import__.default;
|
|
@@ -3594,23 +3594,6 @@ function resolveConcurrency(...candidates) {
|
|
|
3594
3594
|
}
|
|
3595
3595
|
return DEFAULT_CONCURRENCY;
|
|
3596
3596
|
}
|
|
3597
|
-
async function mapWithConcurrency(items, limit, fn) {
|
|
3598
|
-
const results = new Array(items.length);
|
|
3599
|
-
let next = 0;
|
|
3600
|
-
const worker = async () => {
|
|
3601
|
-
while (next < items.length) {
|
|
3602
|
-
const index = next++;
|
|
3603
|
-
try {
|
|
3604
|
-
results[index] = { status: "fulfilled", value: await fn(items[index], index) };
|
|
3605
|
-
} catch (reason) {
|
|
3606
|
-
results[index] = { status: "rejected", reason };
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
};
|
|
3610
|
-
const workers = Array.from({ length: Math.min(limit, items.length) }, worker);
|
|
3611
|
-
await Promise.all(workers);
|
|
3612
|
-
return results;
|
|
3613
|
-
}
|
|
3614
3597
|
|
|
3615
3598
|
// src/engine/lib/json-walk.ts
|
|
3616
3599
|
function isContainerObject(value) {
|
|
@@ -6706,6 +6689,7 @@ function textFidelityDirective(strings2, hasFont) {
|
|
|
6706
6689
|
return [
|
|
6707
6690
|
exact,
|
|
6708
6691
|
"Render NO other words anywhere in the image \u2014 no invented labels, captions, sub-headers, or filler text.",
|
|
6692
|
+
"Any bill, quote, receipt, screen or price tag in the scene shows no readable prices or amounts beyond these strings: its figures are blurred or out of focus.",
|
|
6709
6693
|
"For any brand name or uncommon word, lock its letters exactly as written (spell it out letter-by-letter if that helps the characters render correctly).",
|
|
6710
6694
|
"Keep all type crisp, legible, and large enough to read cleanly; no garbled, warped, or duplicated characters, and no unreadable microtext."
|
|
6711
6695
|
].join("\n");
|
|
@@ -6770,22 +6754,32 @@ var Viewer = z7.object({
|
|
|
6770
6754
|
/** Who will see the ad, in the words a marketer would use. */
|
|
6771
6755
|
who: z7.string().trim().min(1),
|
|
6772
6756
|
market: Market.optional(),
|
|
6773
|
-
/** What they already know about the brand and the offer. */
|
|
6774
|
-
knows: z7.string().optional(),
|
|
6757
|
+
/** What they already know about the brand and the offer; a list (as _definition.md keeps it) reads as one line. */
|
|
6758
|
+
knows: z7.preprocess((v) => Array.isArray(v) ? v.map(String).join("; ") || void 0 : v, z7.string().optional()),
|
|
6775
6759
|
temperature: z7.enum(["cold", "warm", "hot"]).optional(),
|
|
6776
6760
|
/** Where they meet it (feed, Stories, Reels). */
|
|
6777
6761
|
placement: z7.string().optional()
|
|
6778
6762
|
}).strict();
|
|
6779
|
-
var
|
|
6763
|
+
var withoutFormatName = (v) => {
|
|
6764
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || typeof v.concept !== "string")
|
|
6765
|
+
return v;
|
|
6766
|
+
const { concept: _format, ...scene } = v;
|
|
6767
|
+
return scene;
|
|
6768
|
+
};
|
|
6769
|
+
var SceneFields = z7.object({
|
|
6780
6770
|
description: z7.string().trim().min(1),
|
|
6781
|
-
/** A photograph, a designed graphic, or an illustration. */
|
|
6782
|
-
look: z7.
|
|
6771
|
+
/** A photograph, a designed graphic, or an illustration; plain synonyms ("photography") read as them. */
|
|
6772
|
+
look: z7.preprocess(
|
|
6773
|
+
(v) => typeof v !== "string" ? v : /^photo/i.test(v.trim()) ? "photo" : /^illustrat/i.test(v.trim()) ? "illustration" : /^(design|graphic)/i.test(v.trim()) ? "designed" : v,
|
|
6774
|
+
z7.enum(["photo", "designed", "illustration"])
|
|
6775
|
+
),
|
|
6783
6776
|
/** How a photo was taken, in plain words (handheld at standing height, window light, faint glare). */
|
|
6784
6777
|
capture: z7.string().optional(),
|
|
6785
6778
|
mood: z7.string().optional(),
|
|
6786
6779
|
/** How the frame is arranged, by what fills each part of it. */
|
|
6787
6780
|
layout: z7.string().optional()
|
|
6788
6781
|
}).strict();
|
|
6782
|
+
var Scene = z7.preprocess(withoutFormatName, SceneFields);
|
|
6789
6783
|
var Brand = z7.object({
|
|
6790
6784
|
name: z7.string().optional(),
|
|
6791
6785
|
/** The words the brand's logo prints (its wordmark), so a check reads them as the logo, never as copy. */
|
|
@@ -6857,13 +6851,19 @@ var AdSpecSchema = z7.object({
|
|
|
6857
6851
|
* editor's line numbers, a product's own label, a platform's badge. The render may draw them
|
|
6858
6852
|
* and the review never calls them extra words.
|
|
6859
6853
|
*/
|
|
6860
|
-
scene_text: z7.
|
|
6854
|
+
scene_text: z7.preprocess(
|
|
6855
|
+
(v) => Array.isArray(v) ? v.map((w) => w && typeof w === "object" && "text" in w ? w.text : w) : v,
|
|
6856
|
+
z7.array(z7.string().trim().min(1)).default([])
|
|
6857
|
+
),
|
|
6861
6858
|
/** The formats the ad is made for; the canvas delivers them. */
|
|
6862
6859
|
formats: z7.array(z7.string()).optional(),
|
|
6863
6860
|
/** An optional note on direction for this concept. */
|
|
6864
6861
|
direction: z7.string().optional(),
|
|
6865
6862
|
/** The device the ad is built on, and why it works on a stranger. Judged, never drawn. */
|
|
6866
|
-
mechanism: z7.
|
|
6863
|
+
mechanism: z7.preprocess((v) => {
|
|
6864
|
+
const list = v && typeof v === "object" && !Array.isArray(v) ? [v] : v;
|
|
6865
|
+
return Array.isArray(list) ? list.map((m) => typeof m === "string" ? { device: m } : m) : list;
|
|
6866
|
+
}, z7.array(Mechanism).default([])),
|
|
6867
6867
|
/** Why the reference won and what this ad keeps of it (schema /3). */
|
|
6868
6868
|
concept: Concept.optional(),
|
|
6869
6869
|
/**
|
|
@@ -6931,6 +6931,49 @@ function unwrittenProblems(spec) {
|
|
|
6931
6931
|
}
|
|
6932
6932
|
return problems;
|
|
6933
6933
|
}
|
|
6934
|
+
var QUOTED = /"([^"]{2,})"|“([^”]{2,})”|(?<![\p{L}\p{N}])['‘]([^'’]{2,}?)['’](?![\p{L}\p{N}])/gu;
|
|
6935
|
+
var FIGURE = /[£$€]\s?\d[\d.,]*|\d+(?:[.,]\d+)?\s?%/g;
|
|
6936
|
+
var comparable = (text2) => text2.normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").toLowerCase().replace(/[^\p{L}\p{N}£$€%]+/gu, " ").trim();
|
|
6937
|
+
function sceneWordProblems(spec) {
|
|
6938
|
+
const held = [...spec.copy.map((l) => l.text), ...spec.scene_text].map(comparable);
|
|
6939
|
+
const holds = (text2) => {
|
|
6940
|
+
const want = comparable(text2);
|
|
6941
|
+
return want.length === 0 || held.some((h) => h.includes(want));
|
|
6942
|
+
};
|
|
6943
|
+
const scene = [spec.scene.description, spec.scene.layout ?? ""].join(" ");
|
|
6944
|
+
const quoted = [...scene.matchAll(QUOTED)].map((m) => (m[1] ?? m[2] ?? m[3] ?? "").trim()).filter((q) => !holds(q));
|
|
6945
|
+
const figures = (scene.match(FIGURE) ?? []).map((f) => f.trim()).filter((f) => !holds(f));
|
|
6946
|
+
const unheld = [.../* @__PURE__ */ new Set([...quoted, ...figures])];
|
|
6947
|
+
if (unheld.length === 0) return [];
|
|
6948
|
+
return [
|
|
6949
|
+
`The scene asks the picture to show ${unheld.map((w) => `"${w}"`).join(", ")}, which the copy does not hold: the picture would print it and the run would then spend a round erasing it. Put each one the ad needs in \`copy\` as an \`in_scene\` line with \`on\` (the bill, the phone), or describe that surface without words (blank, blurred, face-down).`
|
|
6950
|
+
];
|
|
6951
|
+
}
|
|
6952
|
+
var FIELD_SHAPES = {
|
|
6953
|
+
copy: '"copy": [{"text": "Book your free survey", "lettering": "designed", "role": "cta"}]',
|
|
6954
|
+
scene: '"scene": {"description": "a family on a sunny roof terrace", "look": "photo"}',
|
|
6955
|
+
colors: '"colors": [{"object": "the button", "name": "deep green", "hex": "#1F5C3A"}]',
|
|
6956
|
+
references: '"references": [{"id": "PRODUCT_1", "role": "product", "what": "the solar panel kit"}]',
|
|
6957
|
+
third_party_brands: '"third_party_brands": [{"name": "Trustpilot", "role": "proof", "logo": "BADGE_1"}]',
|
|
6958
|
+
mechanism: '"mechanism": [{"device": "a before and after", "why_it_works": "the change is seen, not told"}]',
|
|
6959
|
+
viewer: '"viewer": {"who": "homeowners with high energy bills", "knows": "nothing about us yet"}'
|
|
6960
|
+
};
|
|
6961
|
+
var SPEC_SHAPES_HINT = `prompt.json shapes: ${["copy", "colors", "viewer"].map((f) => FIELD_SHAPES[f]).join("; ")}.`;
|
|
6962
|
+
function adSpecErrorText(e) {
|
|
6963
|
+
if (!(e instanceof z7.ZodError)) return e instanceof Error ? e.message : String(e);
|
|
6964
|
+
const items = /* @__PURE__ */ new Map();
|
|
6965
|
+
for (const issue of e.issues) {
|
|
6966
|
+
const [field2, index, key] = issue.path;
|
|
6967
|
+
const at2 = typeof index === "number" ? `${String(field2)}[${index}]` : issue.path.map(String).join(".") || "the spec";
|
|
6968
|
+
const what = issue.code === "unrecognized_keys" ? `no field ${issue.keys.map((k) => `"${k}"`).join(", ")}` : key !== void 0 && typeof index === "number" ? `"${String(key)}" ${issue.message.replace(/^Invalid input: /, "")}` : issue.message.replace(/^Invalid input: /, "");
|
|
6969
|
+
items.set(at2, [...items.get(at2) ?? [], what]);
|
|
6970
|
+
}
|
|
6971
|
+
const fields = [...new Set(e.issues.map((i) => String(i.path[0] ?? "")))].filter((f) => FIELD_SHAPES[f]);
|
|
6972
|
+
return [
|
|
6973
|
+
...[...items].map(([at2, whats]) => `${at2}: ${[...new Set(whats)].join("; ")}`),
|
|
6974
|
+
...fields.map((f) => `Shape: ${FIELD_SHAPES[f]}`)
|
|
6975
|
+
].join("\n");
|
|
6976
|
+
}
|
|
6934
6977
|
function readAdSpec(raw) {
|
|
6935
6978
|
if (isAdSpec(raw)) return AdSpecSchema.parse(raw);
|
|
6936
6979
|
const advertiser = raw?.reference?.advertiser;
|
|
@@ -10138,7 +10181,7 @@ function checkOrphanNodes(ctx) {
|
|
|
10138
10181
|
path: `nodes[${ctx.idToIndex.get(n.id)}]`,
|
|
10139
10182
|
code: STAGE_CODES.ORPHAN_NODE,
|
|
10140
10183
|
severity: "warning",
|
|
10141
|
-
message: `node "${n.id}" (${n.type}) is not reachable from output "${out.node}"
|
|
10184
|
+
message: `node "${n.id}" (${n.type}) is not reachable from output "${out.node}": a run skips it, so nothing it would make is used; wire it in or remove it`
|
|
10142
10185
|
});
|
|
10143
10186
|
}
|
|
10144
10187
|
}
|
|
@@ -10209,6 +10252,17 @@ function unwrap(schema) {
|
|
|
10209
10252
|
}
|
|
10210
10253
|
|
|
10211
10254
|
// src/engine/engine/executor.ts
|
|
10255
|
+
function throwRunOutcome(signal, failures, held) {
|
|
10256
|
+
if (signal?.aborted) throw new RunAbortedError("signal", "run aborted by signal");
|
|
10257
|
+
if (failures.length === 1 && failures[0]) throw failures[0].reason;
|
|
10258
|
+
if (failures.length > 1) throw new LayerExecutionError([...failures]);
|
|
10259
|
+
if (held.length > 0) {
|
|
10260
|
+
throw new RunAbortedError(
|
|
10261
|
+
"checkpoint",
|
|
10262
|
+
`stopped before ${held.join(", ")} \u2014 everything up to here is done and cached, so continuing this run id pays for none of it again`
|
|
10263
|
+
);
|
|
10264
|
+
}
|
|
10265
|
+
}
|
|
10212
10266
|
function heldByCheckpoint(canvas, layer, kinds) {
|
|
10213
10267
|
if (!kinds?.size) return [];
|
|
10214
10268
|
return layer.filter((id) => {
|
|
@@ -10297,45 +10351,63 @@ var Engine = class {
|
|
|
10297
10351
|
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir, node_runs: nodeRuns };
|
|
10298
10352
|
}
|
|
10299
10353
|
async runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns, needsBytes) {
|
|
10300
|
-
const
|
|
10354
|
+
const order = topologicalLayers(graph).flat();
|
|
10301
10355
|
const limit = resolveConcurrency(opts.concurrency);
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
duration_ms: r.durationMs,
|
|
10319
|
-
credits: r.credits
|
|
10320
|
-
};
|
|
10321
|
-
nodeRuns.push(run);
|
|
10322
|
-
this.emitProgress(opts, { kind: "node_settled", run, outputs: outputs[nodeId] ?? {} });
|
|
10323
|
-
}
|
|
10324
|
-
});
|
|
10325
|
-
});
|
|
10326
|
-
const failures = [];
|
|
10327
|
-
settled.forEach((result, i) => {
|
|
10328
|
-
const nodeId = layer[i];
|
|
10329
|
-
if (result.status === "rejected" && nodeId) {
|
|
10330
|
-
if (result.reason instanceof RunAbortedError) return;
|
|
10331
|
-
failures.push({ nodeId, reason: result.reason });
|
|
10332
|
-
this.emitProgress(opts, { kind: "node_failed", node_id: nodeId, error: failureDetail(result.reason) });
|
|
10356
|
+
if (opts.signal?.aborted) throw new RunAbortedError("signal", "run aborted before dispatch");
|
|
10357
|
+
const waitingOn = new Map(order.map((id) => [id, new Set(graph.get(id) ?? [])]));
|
|
10358
|
+
const running = /* @__PURE__ */ new Map();
|
|
10359
|
+
const failures = [];
|
|
10360
|
+
const held = [];
|
|
10361
|
+
const dispatch = (nodeId) => {
|
|
10362
|
+
waitingOn.delete(nodeId);
|
|
10363
|
+
this.emitProgress(opts, { kind: "node_start", node_id: nodeId });
|
|
10364
|
+
const settled = this.executeOne(canvas, nodeId, outputs, runId, writer, opts, needsBytes.has(nodeId)).then(
|
|
10365
|
+
(r) => {
|
|
10366
|
+
this.recordRun(canvas, nodeId, r, counters, nodeRuns, outputs, opts);
|
|
10367
|
+
for (const deps of waitingOn.values()) deps.delete(nodeId);
|
|
10368
|
+
},
|
|
10369
|
+
(reason) => {
|
|
10370
|
+
failures.push({ nodeId, reason });
|
|
10371
|
+
this.emitProgress(opts, { kind: "node_failed", node_id: nodeId, error: failureDetail(reason) });
|
|
10333
10372
|
}
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10373
|
+
);
|
|
10374
|
+
running.set(
|
|
10375
|
+
nodeId,
|
|
10376
|
+
settled.finally(() => running.delete(nodeId))
|
|
10377
|
+
);
|
|
10378
|
+
};
|
|
10379
|
+
const isHeld = (nodeId) => heldByCheckpoint(canvas, [nodeId], opts.stop_before_kinds).length > 0;
|
|
10380
|
+
for (; ; ) {
|
|
10381
|
+
const ready = opts.signal?.aborted ? [] : order.filter((id) => waitingOn.get(id)?.size === 0);
|
|
10382
|
+
for (const nodeId of ready) {
|
|
10383
|
+
if (running.size >= limit) break;
|
|
10384
|
+
if (isHeld(nodeId)) {
|
|
10385
|
+
waitingOn.delete(nodeId);
|
|
10386
|
+
held.push(nodeId);
|
|
10387
|
+
} else dispatch(nodeId);
|
|
10388
|
+
}
|
|
10389
|
+
if (running.size === 0) break;
|
|
10390
|
+
await Promise.race(running.values());
|
|
10338
10391
|
}
|
|
10392
|
+
const rank = new Map(order.map((id, i) => [id, i]));
|
|
10393
|
+
failures.sort((a, b) => (rank.get(a.nodeId) ?? 0) - (rank.get(b.nodeId) ?? 0));
|
|
10394
|
+
throwRunOutcome(opts.signal, failures, held);
|
|
10395
|
+
}
|
|
10396
|
+
/** A settled node counted, recorded and announced. */
|
|
10397
|
+
recordRun(canvas, nodeId, r, counters, nodeRuns, outputs, opts) {
|
|
10398
|
+
if (r.cached) counters.cachedNodes++;
|
|
10399
|
+
counters.totalCredits += r.credits;
|
|
10400
|
+
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
10401
|
+
if (!node) return;
|
|
10402
|
+
const run = {
|
|
10403
|
+
node_id: nodeId,
|
|
10404
|
+
node_type: node.type,
|
|
10405
|
+
cached: r.cached,
|
|
10406
|
+
duration_ms: r.durationMs,
|
|
10407
|
+
credits: r.credits
|
|
10408
|
+
};
|
|
10409
|
+
nodeRuns.push(run);
|
|
10410
|
+
this.emitProgress(opts, { kind: "node_settled", run, outputs: outputs[nodeId] ?? {} });
|
|
10339
10411
|
}
|
|
10340
10412
|
/** Progress consumers are observers only — an exception there must never fail the run. */
|
|
10341
10413
|
emitProgress(opts, event) {
|
|
@@ -10427,20 +10499,6 @@ var Engine = class {
|
|
|
10427
10499
|
this.logOutputWarnings(node.id, outputsObj);
|
|
10428
10500
|
return { cached: false, credits, durationMs: elapsed };
|
|
10429
10501
|
}
|
|
10430
|
-
/**
|
|
10431
|
-
* Every reason a layer must not dispatch, checked before any of its nodes starts, so a stop
|
|
10432
|
-
* never leaves a layer half billed: a signal, the cost cap and a checkpoint.
|
|
10433
|
-
*/
|
|
10434
|
-
assertLayerMayRun(canvas, layer, opts) {
|
|
10435
|
-
if (opts.signal?.aborted) throw new RunAbortedError("signal", "run aborted before layer dispatch");
|
|
10436
|
-
const held = heldByCheckpoint(canvas, layer, opts.stop_before_kinds);
|
|
10437
|
-
if (held.length > 0) {
|
|
10438
|
-
throw new RunAbortedError(
|
|
10439
|
-
"checkpoint",
|
|
10440
|
-
`stopped before ${held.join(", ")} \u2014 everything up to here is done and cached, so continuing this run id pays for none of it again`
|
|
10441
|
-
);
|
|
10442
|
-
}
|
|
10443
|
-
}
|
|
10444
10502
|
/** A cache hit replayed as this node's result: its outputs written, and free. */
|
|
10445
10503
|
async replayCached(node, cacheKey, outputs, writer) {
|
|
10446
10504
|
const cacheT0 = Date.now();
|
|
@@ -14605,6 +14663,18 @@ function boxesOfLine(line, boxes) {
|
|
|
14605
14663
|
return b.kind === "text" && read.length > 0 && words.includes(` ${read} `);
|
|
14606
14664
|
});
|
|
14607
14665
|
}
|
|
14666
|
+
var BUTTON_PAD = 0.7;
|
|
14667
|
+
var isButtonLine = (line) => /^(cta|button|call_to_action)$/.test(roleKey2(line.role));
|
|
14668
|
+
function buttonShape(box) {
|
|
14669
|
+
const pad = BUTTON_PAD * (box.y1 - box.y0);
|
|
14670
|
+
return {
|
|
14671
|
+
...box,
|
|
14672
|
+
x0: Math.max(0, box.x0 - pad),
|
|
14673
|
+
y0: Math.max(0, box.y0 - pad),
|
|
14674
|
+
x1: Math.min(1, box.x1 + pad),
|
|
14675
|
+
y1: Math.min(1, box.y1 + pad)
|
|
14676
|
+
};
|
|
14677
|
+
}
|
|
14608
14678
|
function shownBoxes(boxes, lines, clear) {
|
|
14609
14679
|
const linesOfBox = /* @__PURE__ */ new Map();
|
|
14610
14680
|
for (const line of lines) {
|
|
@@ -14988,7 +15058,7 @@ async function videoDuration(video) {
|
|
|
14988
15058
|
return null;
|
|
14989
15059
|
}
|
|
14990
15060
|
}
|
|
14991
|
-
async function askGateway(model, parts, timeoutMs, spent) {
|
|
15061
|
+
async function askGateway(model, parts, timeoutMs, spent, reasoning) {
|
|
14992
15062
|
const key = openRouterKey();
|
|
14993
15063
|
if (!key) return null;
|
|
14994
15064
|
try {
|
|
@@ -14998,7 +15068,8 @@ async function askGateway(model, parts, timeoutMs, spent) {
|
|
|
14998
15068
|
body: JSON.stringify({
|
|
14999
15069
|
model,
|
|
15000
15070
|
messages: [{ role: "user", content: parts }],
|
|
15001
|
-
usage: { include: true }
|
|
15071
|
+
usage: { include: true },
|
|
15072
|
+
...reasoning ? { reasoning } : {}
|
|
15002
15073
|
}),
|
|
15003
15074
|
signal: AbortSignal.timeout(timeoutMs)
|
|
15004
15075
|
});
|
|
@@ -15047,7 +15118,7 @@ async function askAboutStills(images, prompt, onSpend) {
|
|
|
15047
15118
|
if (!hasOpenRouterKey()) return null;
|
|
15048
15119
|
return await ask(images, prompt, (usage) => onSpend?.(usage));
|
|
15049
15120
|
}
|
|
15050
|
-
async function askAboutImages(model, images, prompt, onSpend, timeoutMs = 6e4) {
|
|
15121
|
+
async function askAboutImages(model, images, prompt, onSpend, timeoutMs = 6e4, reasoning) {
|
|
15051
15122
|
if (!hasOpenRouterKey()) return null;
|
|
15052
15123
|
return await askGateway(
|
|
15053
15124
|
model,
|
|
@@ -15058,7 +15129,8 @@ async function askAboutImages(model, images, prompt, onSpend, timeoutMs = 6e4) {
|
|
|
15058
15129
|
)
|
|
15059
15130
|
],
|
|
15060
15131
|
timeoutMs,
|
|
15061
|
-
(usage) => onSpend?.(usage)
|
|
15132
|
+
(usage) => onSpend?.(usage),
|
|
15133
|
+
reasoning
|
|
15062
15134
|
);
|
|
15063
15135
|
}
|
|
15064
15136
|
async function askAboutText(model, prompt, onSpend) {
|
|
@@ -15222,7 +15294,9 @@ async function readTextBoxes(image, opts = {}) {
|
|
|
15222
15294
|
return { status: "not_checked" };
|
|
15223
15295
|
}
|
|
15224
15296
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15225
|
-
const answer = await askAboutImages(TEXT_BOX_READER_MODEL, [jpeg], TEXT_BOX_PROMPT, opts.onSpend
|
|
15297
|
+
const answer = await askAboutImages(TEXT_BOX_READER_MODEL, [jpeg], TEXT_BOX_PROMPT, opts.onSpend, 6e4, {
|
|
15298
|
+
effort: "minimal"
|
|
15299
|
+
});
|
|
15226
15300
|
const boxes = textBoxesFromReader(answer);
|
|
15227
15301
|
if (boxes) return { status: "read", boxes };
|
|
15228
15302
|
}
|
|
@@ -15312,6 +15386,8 @@ var StaticFinding = z21.object({
|
|
|
15312
15386
|
});
|
|
15313
15387
|
var ABSENCE_NEVER_A_FINDING = /\b(?:missing|no|lacks?|without)\s+(?:an?\s+|any\s+|clear\s+|visible\s+)?(?:call to action|cta)\b|\b(?:does not|doesn't) (?:show|tell) the viewer what to do/i;
|
|
15314
15388
|
var ALWAYS_BLOCKING = /^(?:SUBJECT|PERSON):/i;
|
|
15389
|
+
var ABOUT_OWN_COPY = /^COPY:|["“«]|\b(?:headline|subhead\w*|button|cta|tagline|copy)\b/i;
|
|
15390
|
+
var BLURRED_FIGURES = /\b(?:blur\w*|obscur\w*|unreadable|illegible|smudg\w*)\b(?=[^.]*\b(?:figures?|numbers?|digits?|amounts?|prices?|savings?|values?)\b)|\b(?:figures?|numbers?|digits?|amounts?|prices?|savings?|values?)\b(?=[^.]*\b(?:blur\w*|obscur\w*|unreadable|illegible|smudg\w*)\b)/i;
|
|
15315
15391
|
function classifyStaticFindings(json) {
|
|
15316
15392
|
const findings = json?.findings;
|
|
15317
15393
|
if (!Array.isArray(findings)) return [];
|
|
@@ -15320,7 +15396,7 @@ function classifyStaticFindings(json) {
|
|
|
15320
15396
|
if (!parsed.success) return [];
|
|
15321
15397
|
const { severity, format, what } = parsed.data;
|
|
15322
15398
|
if (ABSENCE_NEVER_A_FINDING.test(what)) return [];
|
|
15323
|
-
const graded = ALWAYS_BLOCKING.test(what) ? "blocking" : severity;
|
|
15399
|
+
const graded = ALWAYS_BLOCKING.test(what) ? "blocking" : BLURRED_FIGURES.test(what) && !ABOUT_OWN_COPY.test(what) ? "warning" : severity;
|
|
15324
15400
|
return [{ severity: graded, what: format?.trim() ? `${format.trim()}: ${what}` : what }];
|
|
15325
15401
|
});
|
|
15326
15402
|
}
|
|
@@ -15331,7 +15407,7 @@ var CopyRead = z21.object({
|
|
|
15331
15407
|
seen: z21.string().nullable()
|
|
15332
15408
|
});
|
|
15333
15409
|
function comparableCopy(text2) {
|
|
15334
|
-
return text2.replace(/[‘’‚′]/g, "'").replace(/[“”„″]/g, '"').replace(/[‐-―−]/g, "-").replace(/…/g, "...").replace(/\s+/g, " ").trim().toLowerCase();
|
|
15410
|
+
return text2.replace(/[‘’‚′]/g, "'").replace(/[“”„″]/g, '"').replace(/[‐-―−]/g, "-").replace(/…/g, "...").replace(/[«»]/g, '"').replace(/\s+/g, " ").trim().replace(/^"([^"]*)"$/, "$1").trim().toLowerCase();
|
|
15335
15411
|
}
|
|
15336
15412
|
function copyFindings(json, copy, formats, scrolled = {}) {
|
|
15337
15413
|
if (copy.length === 0) return [];
|
|
@@ -15706,7 +15782,7 @@ function contextItems(opts) {
|
|
|
15706
15782
|
opts.thumbnails ? "7. THUMBNAIL: at thumbnail size the lead line (the largest words) cannot be read. Judge only whether those words can be read, nothing else. That is blocking: most people only ever see the ad that small." : "7. THUMBNAIL: the lead line (the largest words) would be unreadable on a phone feed. That is blocking."
|
|
15707
15783
|
],
|
|
15708
15784
|
sizes: opts.formats.length > 1 ? [
|
|
15709
|
-
|
|
15785
|
+
`12. SIZES: one size loses what the others are built on: the letter sheet, the email or chat window, the post card, the comic's panels and their order, the product the ad is about, or a size that shows something the others do not (blocking; start the finding with "SIZES:" and report it on the size that differs). Rearranged for its shape is fine.`
|
|
15710
15786
|
] : []
|
|
15711
15787
|
};
|
|
15712
15788
|
}
|
|
@@ -15759,6 +15835,7 @@ function mostOfALine(chunk, copy) {
|
|
|
15759
15835
|
});
|
|
15760
15836
|
}
|
|
15761
15837
|
var CLAIM = /[%€$£]|\b(?:free|gratis|gratuit[oa]?s?|off|sale|oferta|descuento|rebajas?|promo)\b/i;
|
|
15838
|
+
var PRICE = /[€$£]\s?\d|\d\s?[€$£]/;
|
|
15762
15839
|
var COPY_SIZE_SHARE = 0.8;
|
|
15763
15840
|
function extraWordFindings(format, extra, boxes, copyBoxes) {
|
|
15764
15841
|
if (extra.length === 0) return [];
|
|
@@ -15776,7 +15853,8 @@ function extraWordFindings(format, extra, boxes, copyBoxes) {
|
|
|
15776
15853
|
const printed = (chunk) => usual > 0 && underCopy.includes(chunk) && (!CLAIM.test(chunk) || scenery(chunk));
|
|
15777
15854
|
const quote = (chunks) => chunks.map((e) => `"${e}"`).join(", ");
|
|
15778
15855
|
const asCopy = extra.filter((chunk) => !printed(chunk));
|
|
15779
|
-
const
|
|
15856
|
+
const prices = extra.filter((chunk) => printed(chunk) && PRICE.test(chunk));
|
|
15857
|
+
const inScene = extra.filter((chunk) => printed(chunk) && !prices.includes(chunk));
|
|
15780
15858
|
return [
|
|
15781
15859
|
...asCopy.length > 0 ? [
|
|
15782
15860
|
{
|
|
@@ -15785,6 +15863,13 @@ function extraWordFindings(format, extra, boxes, copyBoxes) {
|
|
|
15785
15863
|
what: `${format}: the picture adds words that aren't in the copy: ${quote(asCopy)}. Remove them, or add them to the copy.`
|
|
15786
15864
|
}
|
|
15787
15865
|
] : [],
|
|
15866
|
+
...prices.length > 0 ? [
|
|
15867
|
+
{
|
|
15868
|
+
severity: "blocking",
|
|
15869
|
+
// `static-autofix.ts` SCENE_PRICES reads this sentence to blur them.
|
|
15870
|
+
what: `${format}: the picture shows prices the copy does not hold: ${quote(prices)}. Make them unreadable.`
|
|
15871
|
+
}
|
|
15872
|
+
] : [],
|
|
15788
15873
|
...inScene.length > 0 ? [
|
|
15789
15874
|
{
|
|
15790
15875
|
severity: "warning",
|
|
@@ -15849,7 +15934,8 @@ function corroborateEdgeClaims(findings, boxesByFormat, lines) {
|
|
|
15849
15934
|
const element = claimedBoxes(f.what, boxes, buttons);
|
|
15850
15935
|
if (element.length === 0) return f;
|
|
15851
15936
|
const named = claimedEdges(f.what);
|
|
15852
|
-
const
|
|
15937
|
+
const pad = BUTTON.test(f.what) && !LOGO_CLAIM.test(f.what) ? BUTTON_PAD : 0;
|
|
15938
|
+
const atEdge = element.some((b) => named.some((edge) => clearanceTo(b, edge) - pad * (b.y1 - b.y0) <= AT_EDGE));
|
|
15853
15939
|
return atEdge ? f : { ...f, severity: "warning" };
|
|
15854
15940
|
});
|
|
15855
15941
|
}
|
|
@@ -15857,9 +15943,11 @@ function claimedEdges(what) {
|
|
|
15857
15943
|
const edges = EDGE_WORD.filter(([, word]) => word.test(what)).map(([edge]) => edge);
|
|
15858
15944
|
return edges.length > 0 ? edges : ["top", "bottom", "left", "right"];
|
|
15859
15945
|
}
|
|
15946
|
+
var LOGO_CLAIM = /\b(?:logos?|wordmark|mark)\b/i;
|
|
15947
|
+
var BUTTON = /\b(button|cta|call[- ]to[- ]action)\b/i;
|
|
15860
15948
|
function claimedBoxes(what, boxes, buttons) {
|
|
15861
|
-
if (
|
|
15862
|
-
if (
|
|
15949
|
+
if (LOGO_CLAIM.test(what)) return boxes.filter((b) => b.kind === "logo");
|
|
15950
|
+
if (BUTTON.test(what)) return buttons.flatMap((l) => boxesOfLine(l, boxes));
|
|
15863
15951
|
return ABOUT_COPY.test(what) ? boxes.filter((b) => b.kind === "text") : [];
|
|
15864
15952
|
}
|
|
15865
15953
|
async function bytesOf(source) {
|
|
@@ -15989,6 +16077,22 @@ function everySizeChecks(opts) {
|
|
|
15989
16077
|
onSpend: opts.onSpend
|
|
15990
16078
|
};
|
|
15991
16079
|
}
|
|
16080
|
+
async function confirmBlocking(findings, stills, formats, ask2) {
|
|
16081
|
+
const claims = findings.filter((f) => f.severity === "blocking" && !CLIPPED.test(f.what));
|
|
16082
|
+
if (claims.length === 0 || stills.length === 0) return [...findings];
|
|
16083
|
+
const prompt = [
|
|
16084
|
+
`These are the finished sizes of one ad, in order: ${formats.map((f, i) => `Image ${i + 1} is the ${f}`).join("; ")}.`,
|
|
16085
|
+
"A reviewer reported each claim below. Look again at the size each one names (and, for a claim comparing sizes, at the others) and answer true only when the defect is plainly there for someone looking at it on a phone.",
|
|
16086
|
+
"Answer false when the thing is not there, when it is an ordinary part of the scene (a second house, a neighbour's roof, a reflection), or when the sizes it compares show the same thing.",
|
|
16087
|
+
...claims.map((c, i) => `${i + 1}. ${c.what}`),
|
|
16088
|
+
`Reply with only this JSON: {"answers":[true|false, ...]}, one answer per claim, in order.`
|
|
16089
|
+
].join("\n");
|
|
16090
|
+
const answer = await ask2([...stills], prompt).catch(() => null);
|
|
16091
|
+
const answers = answer?.answers;
|
|
16092
|
+
if (!Array.isArray(answers) || answers.length !== claims.length) return [...findings];
|
|
16093
|
+
const unconfirmed = new Set(claims.filter((_, i) => answers[i] === false));
|
|
16094
|
+
return findings.map((f) => unconfirmed.has(f) ? { ...f, severity: "warning" } : f);
|
|
16095
|
+
}
|
|
15992
16096
|
async function reviewRenderedStatic(opts) {
|
|
15993
16097
|
const { measured, originals, stills, thumbs } = await loadDelivered(opts.images);
|
|
15994
16098
|
if (measured.length === 0) return null;
|
|
@@ -16019,7 +16123,14 @@ async function reviewRenderedStatic(opts) {
|
|
|
16019
16123
|
if (answer === null) return unreviewed(shape, measuredText);
|
|
16020
16124
|
const text2 = read ? measuredText : copyFindings(answer, opts.copy, formats, opts.scrolled);
|
|
16021
16125
|
const boxesByFormat = read ? Object.fromEntries(checks.flatMap((c, i) => c.status === "checked" ? [[formats[i] ?? "", c.boxes]] : [])) : {};
|
|
16022
|
-
|
|
16126
|
+
const judged = corroborateEdgeClaims(classifyStaticFindings(answer), boxesByFormat, lines);
|
|
16127
|
+
const confirmed = await confirmBlocking(
|
|
16128
|
+
judged,
|
|
16129
|
+
stills,
|
|
16130
|
+
formats,
|
|
16131
|
+
(images, prompt) => askAboutStills(images, prompt, opts.onSpend)
|
|
16132
|
+
);
|
|
16133
|
+
return [...shape, ...text2, ...confirmed];
|
|
16023
16134
|
}
|
|
16024
16135
|
|
|
16025
16136
|
// src/engine/nodes/local/staticNative.ts
|
|
@@ -16263,7 +16374,9 @@ function boxesToPlace(boxes, format, opts = {}) {
|
|
|
16263
16374
|
const past = (box) => bandViolations([box], format, { placements })[0];
|
|
16264
16375
|
const cut = (box) => (past(box)?.clearance ?? 1) <= FRAME_CUT;
|
|
16265
16376
|
const logoWords = opts.copy.filter((l) => roleKey2(l.role) === "logo").map((l) => l.text);
|
|
16266
|
-
|
|
16377
|
+
const read = withWordmark(boxes, logoWords);
|
|
16378
|
+
const buttons = new Set(opts.copy.filter(isButtonLine).flatMap((line) => boxesOfLine(line, read)));
|
|
16379
|
+
return shownBoxes(read, opts.copy, (box) => !past(box)).filter((shown) => !shown.supporting || cut(shown.box)).map((shown) => buttons.has(shown.box) ? buttonShape(shown.box) : shown.box);
|
|
16267
16380
|
}
|
|
16268
16381
|
function planSafeFit(boxes, format, opts = {}) {
|
|
16269
16382
|
const area = blockingArea(format, opts.placements);
|
|
@@ -16591,12 +16704,64 @@ import { z as z26 } from "zod";
|
|
|
16591
16704
|
// src/engine/nodes/local/lib/variant-prefilter.ts
|
|
16592
16705
|
import { createHash as createHash3 } from "crypto";
|
|
16593
16706
|
import sharp8 from "sharp";
|
|
16594
|
-
function
|
|
16707
|
+
function coverOf(from, to) {
|
|
16595
16708
|
const scale = Math.max(to.width / from.width, to.height / from.height);
|
|
16596
|
-
const
|
|
16597
|
-
const
|
|
16598
|
-
|
|
16599
|
-
|
|
16709
|
+
const width = from.width * scale;
|
|
16710
|
+
const height = from.height * scale;
|
|
16711
|
+
return { scale, width, height, centre: { left: (width - to.width) / 2, top: (height - to.height) / 2 } };
|
|
16712
|
+
}
|
|
16713
|
+
function framingOf(from, to) {
|
|
16714
|
+
let frame;
|
|
16715
|
+
return {
|
|
16716
|
+
place: (boxes) => {
|
|
16717
|
+
frame = frameKeeping(boxes, from, to);
|
|
16718
|
+
return coverFitBoxes(boxes, from, to, frame);
|
|
16719
|
+
},
|
|
16720
|
+
moved: () => {
|
|
16721
|
+
const { centre } = coverOf(from, to);
|
|
16722
|
+
return frame && (Math.abs(frame.left - centre.left) >= 1 || Math.abs(frame.top - centre.top) >= 1) ? frame : void 0;
|
|
16723
|
+
}
|
|
16724
|
+
};
|
|
16725
|
+
}
|
|
16726
|
+
async function cutToFrame(bytes, frame, to) {
|
|
16727
|
+
const { width = 0, height = 0 } = await sharp8(bytes).metadata();
|
|
16728
|
+
const { scale } = coverOf({ width, height }, to);
|
|
16729
|
+
const w = Math.min(width, Math.round(to.width / scale));
|
|
16730
|
+
const h = Math.min(height, Math.round(to.height / scale));
|
|
16731
|
+
const left = Math.min(width - w, Math.max(0, Math.round(frame.left / scale)));
|
|
16732
|
+
const top = Math.min(height - h, Math.max(0, Math.round(frame.top / scale)));
|
|
16733
|
+
return sharp8(bytes).extract({ left, top, width: w, height: h }).png().toBuffer();
|
|
16734
|
+
}
|
|
16735
|
+
var FRAME_MARGIN = 0.02;
|
|
16736
|
+
function frameKeeping(boxes, from, to) {
|
|
16737
|
+
const cover = coverOf(from, to);
|
|
16738
|
+
if (boxes.length === 0) return cover.centre;
|
|
16739
|
+
const along = (offset2, extent, frame, lo, hi) => {
|
|
16740
|
+
const low = lo * extent;
|
|
16741
|
+
const high = hi * extent;
|
|
16742
|
+
const room = extent - frame;
|
|
16743
|
+
const margin = FRAME_MARGIN * frame;
|
|
16744
|
+
for (const m of [margin, 0]) {
|
|
16745
|
+
const min = Math.max(0, high + m - frame);
|
|
16746
|
+
const max = Math.min(room, low - m);
|
|
16747
|
+
if (min <= max) return Math.min(max, Math.max(min, offset2));
|
|
16748
|
+
}
|
|
16749
|
+
return offset2;
|
|
16750
|
+
};
|
|
16751
|
+
const x0 = Math.min(...boxes.map((b) => b.x0));
|
|
16752
|
+
const x1 = Math.max(...boxes.map((b) => b.x1));
|
|
16753
|
+
const y0 = Math.min(...boxes.map((b) => b.y0));
|
|
16754
|
+
const y1 = Math.max(...boxes.map((b) => b.y1));
|
|
16755
|
+
return {
|
|
16756
|
+
left: along(cover.centre.left, cover.width, to.width, x0, x1),
|
|
16757
|
+
top: along(cover.centre.top, cover.height, to.height, y0, y1)
|
|
16758
|
+
};
|
|
16759
|
+
}
|
|
16760
|
+
function coverFitBoxes(boxes, from, to, frame) {
|
|
16761
|
+
const cover = coverOf(from, to);
|
|
16762
|
+
const { left: offX, top: offY } = frame ?? cover.centre;
|
|
16763
|
+
const x = (f) => (f * cover.width - offX) / to.width;
|
|
16764
|
+
const y = (f) => (f * cover.height - offY) / to.height;
|
|
16600
16765
|
return boxes.map((b) => ({ ...b, x0: x(b.x0), y0: y(b.y0), x1: x(b.x1), y1: y(b.y1) }));
|
|
16601
16766
|
}
|
|
16602
16767
|
var NO_LOGO = "The brand's logo was not found on it.";
|
|
@@ -16650,34 +16815,45 @@ async function prefilterVariants(variants, spec, formats, opts = {}) {
|
|
|
16650
16815
|
});
|
|
16651
16816
|
const format = plan.master.format;
|
|
16652
16817
|
const grade = plan.masterShape === "relayout" ? plan.placements ? { placements: plan.placements } : {} : { band: plan.band };
|
|
16818
|
+
const want = { lines, buttons, ...brand ? { brand } : {}, logo, format, grade };
|
|
16653
16819
|
const checked = await Promise.all(
|
|
16654
|
-
variants.map(
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
try {
|
|
16658
|
-
const bytes = await bytesOf(source);
|
|
16659
|
-
const { width = 0, height = 0 } = await sharp8(bytes).metadata();
|
|
16660
|
-
const check = await checkStaticImage(bytes, {
|
|
16661
|
-
format,
|
|
16662
|
-
lines,
|
|
16663
|
-
allowed: brand ? [brand] : [],
|
|
16664
|
-
...grade,
|
|
16665
|
-
toFrame: (boxes) => coverFitBoxes(boxes, { width, height }, plan.master.size),
|
|
16666
|
-
...opts.read ? { read: opts.read } : {},
|
|
16667
|
-
...opts.onSpend ? { onSpend: opts.onSpend } : {}
|
|
16668
|
-
});
|
|
16669
|
-
if (check.status === "not_checked") return { index: variant.index, blocking: null };
|
|
16670
|
-
const prefix = `${format}: `;
|
|
16671
|
-
const blocking = check.findings.filter((f) => f.severity === "blocking").map((f) => f.what.startsWith(prefix) ? f.what.slice(prefix.length) : f.what);
|
|
16672
|
-
const want = { lines, buttons, ...brand ? { brand } : {}, logo, format, grade };
|
|
16673
|
-
return { index: variant.index, blocking, ...rankOf(check.boxes, blocking, want) };
|
|
16674
|
-
} catch {
|
|
16675
|
-
return { index: variant.index, blocking: null };
|
|
16676
|
-
}
|
|
16677
|
-
})
|
|
16820
|
+
variants.map(
|
|
16821
|
+
(variant) => checkVariant(variant, { format, size: plan.master.size, lines, brand, grade, want, opts })
|
|
16822
|
+
)
|
|
16678
16823
|
);
|
|
16679
16824
|
const outcome = prefilterOutcome(checked);
|
|
16680
|
-
|
|
16825
|
+
const frames = Object.fromEntries(checked.flatMap((c) => "frame" in c && c.frame ? [[c.index, c.frame]] : []));
|
|
16826
|
+
return {
|
|
16827
|
+
...outcome,
|
|
16828
|
+
kept: seededOrder(outcome.kept, variants),
|
|
16829
|
+
...Object.keys(frames).length > 0 ? { frames, masterSize: plan.master.size } : {}
|
|
16830
|
+
};
|
|
16831
|
+
}
|
|
16832
|
+
async function checkVariant(variant, c) {
|
|
16833
|
+
const { format, lines, brand, grade, want, opts } = c;
|
|
16834
|
+
const source = variant.path ?? variant.url;
|
|
16835
|
+
if (!source) return { index: variant.index, blocking: null };
|
|
16836
|
+
try {
|
|
16837
|
+
const bytes = await bytesOf(source);
|
|
16838
|
+
const { width = 0, height = 0 } = await sharp8(bytes).metadata();
|
|
16839
|
+
const framing = framingOf({ width, height }, c.size);
|
|
16840
|
+
const check = await checkStaticImage(bytes, {
|
|
16841
|
+
format,
|
|
16842
|
+
lines,
|
|
16843
|
+
allowed: brand ? [brand] : [],
|
|
16844
|
+
...grade,
|
|
16845
|
+
toFrame: framing.place,
|
|
16846
|
+
...opts.read ? { read: opts.read } : {},
|
|
16847
|
+
...opts.onSpend ? { onSpend: opts.onSpend } : {}
|
|
16848
|
+
});
|
|
16849
|
+
if (check.status === "not_checked") return { index: variant.index, blocking: null };
|
|
16850
|
+
const prefix = `${format}: `;
|
|
16851
|
+
const blocking = check.findings.filter((f) => f.severity === "blocking").map((f) => f.what.startsWith(prefix) ? f.what.slice(prefix.length) : f.what);
|
|
16852
|
+
const frame = framing.moved();
|
|
16853
|
+
return { index: variant.index, blocking, ...rankOf(check.boxes, blocking, want), ...frame ? { frame } : {} };
|
|
16854
|
+
} catch {
|
|
16855
|
+
return { index: variant.index, blocking: null };
|
|
16856
|
+
}
|
|
16681
16857
|
}
|
|
16682
16858
|
function seededOrder(indexes, variants) {
|
|
16683
16859
|
const shaOf = new Map(variants.map((v) => [v.index, v.sha256]));
|
|
@@ -16753,7 +16929,7 @@ async function readSpec(ref) {
|
|
|
16753
16929
|
}
|
|
16754
16930
|
var staticPrefilterNode = defineNode({
|
|
16755
16931
|
id: "static_prefilter",
|
|
16756
|
-
version: "1.
|
|
16932
|
+
version: "1.1.0",
|
|
16757
16933
|
category: "image",
|
|
16758
16934
|
location: "local",
|
|
16759
16935
|
// Reads each variant's pixels itself, from its file or its durable URL, so the variants
|
|
@@ -16786,7 +16962,8 @@ var staticPrefilterNode = defineNode({
|
|
|
16786
16962
|
images: z26.array(ImageRef).min(1),
|
|
16787
16963
|
// Which variant (its place among the inputs) each of `images` is, so the judge's "Image k" maps back.
|
|
16788
16964
|
order: z26.array(z26.number().int().min(0)).optional(),
|
|
16789
|
-
// The picture every size is made from: the checks' first, or on a tie the judge's.
|
|
16965
|
+
// The picture every size is made from: the checks' first, or on a tie the judge's. Cut to the
|
|
16966
|
+
// master's frame where every word stays whole when its centre would cut one, else as rendered.
|
|
16790
16967
|
image: ImageRef.optional(),
|
|
16791
16968
|
// Which variant it is (its place among the inputs).
|
|
16792
16969
|
chosen: z26.number().int().min(0).optional(),
|
|
@@ -16799,12 +16976,12 @@ var staticPrefilterNode = defineNode({
|
|
|
16799
16976
|
}).strict(),
|
|
16800
16977
|
outputKinds: { images: "image", image: "image" },
|
|
16801
16978
|
cost: () => ({ credits: 0, seconds_estimate: 1 }),
|
|
16802
|
-
async execute({ inputs, params }) {
|
|
16803
|
-
const variants = inputs.images.map((
|
|
16979
|
+
async execute({ inputs, params, ctx }) {
|
|
16980
|
+
const variants = inputs.images.map((image2, index) => ({
|
|
16804
16981
|
index,
|
|
16805
|
-
sha256:
|
|
16806
|
-
...
|
|
16807
|
-
...
|
|
16982
|
+
sha256: image2.sha256,
|
|
16983
|
+
...image2.path ? { path: image2.path } : {},
|
|
16984
|
+
...image2.url ? { url: image2.url } : {}
|
|
16808
16985
|
}));
|
|
16809
16986
|
const usage = [];
|
|
16810
16987
|
const result = await prefilterVariants(variants, params.checks ?? await readSpec(inputs.spec), params.formats, {
|
|
@@ -16820,10 +16997,11 @@ var staticPrefilterNode = defineNode({
|
|
|
16820
16997
|
{ model: params.judge_model, prompt: params.judge_prompt, onSpend: (u) => usage.push(u) }
|
|
16821
16998
|
) : null;
|
|
16822
16999
|
const chosen = tie?.chosen ?? shown[0] ?? 0;
|
|
17000
|
+
const image = await framed(inputs.images[chosen], result.frames?.[chosen], result.masterSize, ctx);
|
|
16823
17001
|
return {
|
|
16824
17002
|
images: kept.length > 0 ? kept : inputs.images,
|
|
16825
17003
|
order: shown,
|
|
16826
|
-
...
|
|
17004
|
+
...image ? { image } : {},
|
|
16827
17005
|
chosen,
|
|
16828
17006
|
...tie ? { reasoning: tie.reasoning } : {},
|
|
16829
17007
|
dropped: result.dropped,
|
|
@@ -16832,6 +17010,12 @@ var staticPrefilterNode = defineNode({
|
|
|
16832
17010
|
};
|
|
16833
17011
|
}
|
|
16834
17012
|
});
|
|
17013
|
+
async function framed(picture, frame, size2, ctx) {
|
|
17014
|
+
const source = picture?.path ?? picture?.url;
|
|
17015
|
+
if (!picture || !frame || !size2 || !source) return picture;
|
|
17016
|
+
const bytes = await cutToFrame(await bytesOf(source), frame, size2);
|
|
17017
|
+
return await ctx.assets.ingestBytes({ bytes, kind: "image", mime: "image/png" });
|
|
17018
|
+
}
|
|
16835
17019
|
|
|
16836
17020
|
// src/engine/nodes/local/staticSafeFit.ts
|
|
16837
17021
|
import { z as z27 } from "zod";
|
|
@@ -18484,6 +18668,9 @@ export {
|
|
|
18484
18668
|
conceptSummary,
|
|
18485
18669
|
conceptProblems,
|
|
18486
18670
|
unwrittenProblems,
|
|
18671
|
+
sceneWordProblems,
|
|
18672
|
+
SPEC_SHAPES_HINT,
|
|
18673
|
+
adSpecErrorText,
|
|
18487
18674
|
readAdSpec,
|
|
18488
18675
|
withRenderedLettering,
|
|
18489
18676
|
conceptClassOf,
|
|
@@ -18571,4 +18758,4 @@ export {
|
|
|
18571
18758
|
defaultRegistry,
|
|
18572
18759
|
createEngineFromEnv
|
|
18573
18760
|
};
|
|
18574
|
-
//# sourceMappingURL=chunk-
|
|
18761
|
+
//# sourceMappingURL=chunk-6PP3MBGZ.js.map
|