@nodaro/prompts 1.38.0 → 1.40.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.
Files changed (130) hide show
  1. package/dist/chunk-2EW5B7ZV.cjs +86 -0
  2. package/dist/chunk-2EW5B7ZV.cjs.map +1 -0
  3. package/dist/chunk-2P26SZX5.cjs +414 -0
  4. package/dist/chunk-2P26SZX5.cjs.map +1 -0
  5. package/dist/{chunk-7XKHDXCL.cjs → chunk-3U7X6JZN.cjs} +13 -12
  6. package/dist/chunk-3U7X6JZN.cjs.map +1 -0
  7. package/dist/chunk-4GKTOVFM.cjs +8 -0
  8. package/dist/chunk-4GKTOVFM.cjs.map +1 -0
  9. package/dist/{chunk-HRBBNODV.js → chunk-6GVFJLDL.js} +4 -3
  10. package/dist/chunk-6GVFJLDL.js.map +1 -0
  11. package/dist/{chunk-LVSH7K7R.cjs → chunk-C3NHNECH.cjs} +165 -88
  12. package/dist/chunk-C3NHNECH.cjs.map +1 -0
  13. package/dist/{chunk-CEPVQQVO.js → chunk-C46256GZ.js} +43 -5
  14. package/dist/chunk-C46256GZ.js.map +1 -0
  15. package/dist/{chunk-JKCR6WRZ.cjs → chunk-DQFQJECU.cjs} +43 -5
  16. package/dist/chunk-DQFQJECU.cjs.map +1 -0
  17. package/dist/{chunk-QDEWBBUR.js → chunk-DUXX6CBX.js} +12 -143
  18. package/dist/chunk-DUXX6CBX.js.map +1 -0
  19. package/dist/chunk-EAYEZXWT.js +388 -0
  20. package/dist/chunk-EAYEZXWT.js.map +1 -0
  21. package/dist/chunk-GILUVQQJ.js +6 -0
  22. package/dist/chunk-GILUVQQJ.js.map +1 -0
  23. package/dist/{chunk-KIYNOOPJ.js → chunk-GTDKDD7Y.js} +12 -6
  24. package/dist/chunk-GTDKDD7Y.js.map +1 -0
  25. package/dist/{chunk-QBEZMR2F.js → chunk-HQTHDTHN.js} +137 -62
  26. package/dist/chunk-HQTHDTHN.js.map +1 -0
  27. package/dist/{chunk-TLVX5PXD.cjs → chunk-IUCNJAHW.cjs} +13 -7
  28. package/dist/chunk-IUCNJAHW.cjs.map +1 -0
  29. package/dist/{chunk-ZFIXQRBZ.cjs → chunk-LXPKTI2K.cjs} +12 -83
  30. package/dist/chunk-LXPKTI2K.cjs.map +1 -0
  31. package/dist/{chunk-CVPB2N3Z.cjs → chunk-LZ53F5BN.cjs} +103 -5
  32. package/dist/chunk-LZ53F5BN.cjs.map +1 -0
  33. package/dist/{chunk-4FDNVNJE.js → chunk-N6VMPG5S.js} +12 -79
  34. package/dist/chunk-N6VMPG5S.js.map +1 -0
  35. package/dist/{chunk-LK2CZK5U.cjs → chunk-PWWM7Q3T.cjs} +7 -7
  36. package/dist/{chunk-LK2CZK5U.cjs.map → chunk-PWWM7Q3T.cjs.map} +1 -1
  37. package/dist/{chunk-N7TOWXDJ.js → chunk-QDUTMOF4.js} +3 -3
  38. package/dist/{chunk-N7TOWXDJ.js.map → chunk-QDUTMOF4.js.map} +1 -1
  39. package/dist/{chunk-NO6XHX4R.js → chunk-RB2F5JQF.js} +100 -7
  40. package/dist/chunk-RB2F5JQF.js.map +1 -0
  41. package/dist/{chunk-MENBHEAY.cjs → chunk-WRM52ZBN.cjs} +19 -161
  42. package/dist/chunk-WRM52ZBN.cjs.map +1 -0
  43. package/dist/chunk-YS6HOWMW.js +80 -0
  44. package/dist/chunk-YS6HOWMW.js.map +1 -0
  45. package/dist/entries/assembly.cjs +34 -25
  46. package/dist/entries/assembly.d.cts +121 -37
  47. package/dist/entries/assembly.d.ts +121 -37
  48. package/dist/entries/assembly.js +5 -4
  49. package/dist/entries/doctrine.cjs +46 -46
  50. package/dist/entries/doctrine.d.cts +3 -3
  51. package/dist/entries/doctrine.d.ts +3 -3
  52. package/dist/entries/doctrine.js +1 -1
  53. package/dist/entries/picker-catalogs.cjs +42 -42
  54. package/dist/entries/picker-catalogs.js +3 -3
  55. package/dist/entries/presets.cjs +49 -26
  56. package/dist/entries/presets.d.cts +53 -1
  57. package/dist/entries/presets.d.ts +53 -1
  58. package/dist/entries/presets.js +4 -1
  59. package/dist/entries/prompt-builder.cjs +135 -98
  60. package/dist/entries/prompt-builder.d.cts +22 -11
  61. package/dist/entries/prompt-builder.d.ts +22 -11
  62. package/dist/entries/prompt-builder.js +4 -3
  63. package/dist/entries/prompt-fields.cjs +41 -40
  64. package/dist/entries/prompt-fields.d.cts +2 -1
  65. package/dist/entries/prompt-fields.d.ts +2 -1
  66. package/dist/entries/prompt-fields.js +2 -1
  67. package/dist/index.cjs +339 -273
  68. package/dist/index.d.cts +4 -4
  69. package/dist/index.d.ts +4 -4
  70. package/dist/index.js +11 -9
  71. package/dist/{subject-registry-BUswuEVD.d.cts → subject-registry-CQi_CZfJ.d.cts} +78 -9
  72. package/dist/{subject-registry-D7V8PEG-.d.ts → subject-registry-DFiGx49v.d.ts} +78 -9
  73. package/package.json +2 -2
  74. package/src/__tests__/assemble-image-input-style-references.test.ts +173 -0
  75. package/src/__tests__/catalog-id-guard.test.ts +30 -0
  76. package/src/__tests__/factory-preset-previews.test.ts +123 -0
  77. package/src/__tests__/factory-preset-references.test.ts +222 -0
  78. package/src/__tests__/factory-presets-prototype-keys.test.ts +43 -0
  79. package/src/__tests__/frame-reference-slots.test.ts +43 -0
  80. package/src/__tests__/gemini-omni-inputs.test.ts +12 -10
  81. package/src/__tests__/provider-prompt-doctrine.test.ts +14 -0
  82. package/src/__tests__/user-direction-refs.test.ts +180 -0
  83. package/src/__tests__/veo-i2v-inputs.test.ts +16 -11
  84. package/src/assemble-image-input.ts +114 -13
  85. package/src/catalog-id-guard.ts +18 -2
  86. package/src/color-names.ts +192 -0
  87. package/src/direction-registry.ts +66 -5
  88. package/src/entries/assembly.ts +1 -0
  89. package/src/entries/prompt-builder.ts +2 -0
  90. package/src/factory-presets/generate-image.ts +29 -0
  91. package/src/factory-presets/index.ts +6 -1
  92. package/src/factory-presets/preview-files.generated.ts +4 -0
  93. package/src/factory-presets/previews.ts +40 -0
  94. package/src/factory-presets/references.ts +81 -0
  95. package/src/factory-presets/shared-image.ts +4 -0
  96. package/src/factory-presets/types.ts +8 -0
  97. package/src/frame-reference-slots.ts +92 -0
  98. package/src/gemini-omni-inputs.ts +23 -22
  99. package/src/image-reference-doctrine.ts +3 -3
  100. package/src/image-token-pattern.ts +13 -0
  101. package/src/node-prompt-fields.ts +5 -3
  102. package/src/own-entry.ts +11 -0
  103. package/src/prompt-builder.ts +4 -5
  104. package/src/prompt-style-section.ts +3 -3
  105. package/src/prompt-wizard-categories.ts +2 -0
  106. package/src/provider-prompt-doctrine.ts +45 -3
  107. package/src/ref-binding.ts +28 -3
  108. package/src/resolve-prompt.ts +2 -1
  109. package/src/seedance-2-inputs.ts +15 -40
  110. package/src/user-direction-refs.ts +110 -0
  111. package/src/veo-i2v-inputs.ts +24 -17
  112. package/src/video-reference-resolver.ts +1 -1
  113. package/dist/chunk-4FDNVNJE.js.map +0 -1
  114. package/dist/chunk-7XKHDXCL.cjs.map +0 -1
  115. package/dist/chunk-CEPVQQVO.js.map +0 -1
  116. package/dist/chunk-CVPB2N3Z.cjs.map +0 -1
  117. package/dist/chunk-HRBBNODV.js.map +0 -1
  118. package/dist/chunk-JKCR6WRZ.cjs.map +0 -1
  119. package/dist/chunk-KIYNOOPJ.js.map +0 -1
  120. package/dist/chunk-LVSH7K7R.cjs.map +0 -1
  121. package/dist/chunk-MENBHEAY.cjs.map +0 -1
  122. package/dist/chunk-NO6XHX4R.js.map +0 -1
  123. package/dist/chunk-QBEZMR2F.js.map +0 -1
  124. package/dist/chunk-QDEWBBUR.js.map +0 -1
  125. package/dist/chunk-TLVX5PXD.cjs.map +0 -1
  126. package/dist/chunk-UJKFHPZ7.js +0 -37
  127. package/dist/chunk-UJKFHPZ7.js.map +0 -1
  128. package/dist/chunk-W5SCZIOB.cjs +0 -43
  129. package/dist/chunk-W5SCZIOB.cjs.map +0 -1
  130. package/dist/chunk-ZFIXQRBZ.cjs.map +0 -1
@@ -5,6 +5,12 @@ var workflow = require('@nodaro/shared/workflow');
5
5
  var nodes = require('@nodaro/shared/nodes');
6
6
  var social = require('@nodaro/shared/social');
7
7
 
8
+ // src/own-entry.ts
9
+ function ownEntry(record, key) {
10
+ return key !== void 0 && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
11
+ }
12
+
13
+ // src/resolve-prompt.ts
8
14
  var present = (s) => typeof s === "string" && s.trim().length > 0;
9
15
  var rr = (s, m) => m.size > 0 ? workflow.resolveNodeRefs(s, m) : s;
10
16
  var GLUE_PUNCTUATION = /* @__PURE__ */ new Set([",", ".", ";", ":", "!", "?", ")"]);
@@ -76,7 +82,7 @@ function computeNodePrompt(nodeType, data, { override, wired, refMap, appendWire
76
82
  if (nodeType === "text-to-speech") {
77
83
  typed = data.textSource === "direct" || !present(wired) ? [data.directText] : [];
78
84
  } else {
79
- const fields = NODE_PROMPT_CANDIDATE_FIELDS[nodeType] ?? ["prompt"];
85
+ const fields = ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? ["prompt"];
80
86
  typed = fields.map((f) => data[f]);
81
87
  }
82
88
  return resolvePrompt({ override, typed, wired, refMap, appendWired, affixes: nodes.readPromptAffixes(data) });
@@ -92,28 +98,6 @@ function computeScriptTopic(data, { override, wired, refMap }) {
92
98
  return applyPromptAffixes(core === void 0 ? "" : rr(core, refMap), nodes.readPromptAffixes(data), refMap);
93
99
  }
94
100
 
95
- // src/audio-tags.ts
96
- function removeBracketTags(text) {
97
- let out = "";
98
- let from = 0;
99
- let open = text.indexOf("[");
100
- while (open !== -1) {
101
- const close = text.indexOf("]", open + 1);
102
- if (close === -1) break;
103
- if (close === open + 1) {
104
- open = text.indexOf("[", open + 1);
105
- continue;
106
- }
107
- out += text.slice(from, open);
108
- from = close + 1;
109
- open = text.indexOf("[", from);
110
- }
111
- return out + text.slice(from);
112
- }
113
- function stripAudioTags(text) {
114
- return removeBracketTags(text).replace(/\s{2,}/g, " ").trim();
115
- }
116
-
117
101
  // src/node-prompt-fields.ts
118
102
  var NODE_PROMPT_FIELDS = {
119
103
  // ── Image ──
@@ -188,7 +172,7 @@ var NODE_PROMPT_FIELDS = {
188
172
  "edit-plan": { prompt: "instructions", promptLabel: "Editing instructions", media: "video", inline: false }
189
173
  };
190
174
  function getPromptFields(nodeType) {
191
- return nodeType ? NODE_PROMPT_FIELDS[nodeType] : void 0;
175
+ return nodeType ? ownEntry(NODE_PROMPT_FIELDS, nodeType) : void 0;
192
176
  }
193
177
  function nodeHasPromptField(nodeType) {
194
178
  return getPromptFields(nodeType) !== void 0;
@@ -212,75 +196,20 @@ function promptFieldCarriesAffixes(nodeType, promptField) {
212
196
  const core = promptAffixCoreField(nodeType);
213
197
  if (core === void 0) return false;
214
198
  if (promptField === void 0) return getPromptFields(nodeType).prompt === core;
215
- return promptField === core || (NODE_PROMPT_CANDIDATE_FIELDS[nodeType] ?? []).includes(promptField);
199
+ return promptField === core || (ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? []).includes(promptField);
216
200
  }
217
201
  var PROMPT_AFFIX_NODE_TYPES = new Set(
218
202
  Object.entries(NODE_PROMPT_FIELDS).filter(([, s]) => s.affixes !== false).map(([t]) => t)
219
203
  );
220
- var WIRED_OUTPUT_NODE_TYPES = /* @__PURE__ */ new Set(["webhook-output", ...social.SOCIAL_POST_NODE_TYPES]);
221
- var TEXT_REQUIRED_NODE_TYPES = /* @__PURE__ */ new Set([
222
- "llm-chat",
223
- "ai-writer",
224
- "generate-script",
225
- "text-to-speech",
226
- "generate-music",
227
- "text-to-audio",
228
- // Save to Collection refuses an empty record ("nothing to save"): a feed
229
- // with nothing new wired straight into it must skip it, not fail the run.
230
- "collection-write",
231
- // Generate Image: an empty prompt is a legal request in general (references
232
- // alone), but when the text it was WIRED produced nothing this run — the skip
233
- // rule's precondition — an empty prompt is a paid picture of nothing.
234
- "generate-image"
235
- ]);
236
- var withoutAffixes = (data) => ({ ...data, promptPrefix: void 0, promptSuffix: void 0 });
237
- var present2 = (s) => typeof s === "string" && s.trim().length > 0;
238
- function computeAiWriterInput(data, { override, wired }) {
239
- return [override, wired, data.userInput, data.prompt].find(present2) ?? "";
240
- }
241
- function computeNodeSendText(nodeType, data, args) {
242
- const bare = withoutAffixes(data);
243
- switch (nodeType) {
244
- case "llm-chat":
245
- return computeLlmChatFields(bare, {
246
- override: args.override,
247
- wiredUserInput: args.wired,
248
- wiredSystemPrompt: args.wiredSystemPrompt,
249
- refMap: args.refMap
250
- }).userInput;
251
- case "ai-writer":
252
- return computeAiWriterInput(bare, args);
253
- case "generate-script":
254
- return computeScriptTopic(bare, { override: args.override, wired: args.wired, refMap: args.refMap });
255
- case "generate-music": {
256
- const prompt = computeNodePrompt(nodeType, bare, { override: args.override, wired: args.wired, refMap: args.refMap });
257
- return present2(prompt) ? prompt : present2(data.lyrics) ? data.lyrics : "";
258
- }
259
- case "text-to-speech":
260
- case "text-to-audio":
261
- case "generate-image":
262
- return computeNodePrompt(nodeType, bare, { override: args.override, wired: args.wired, refMap: args.refMap });
263
- case "collection-write": {
264
- const typed = [data.title, data.text, data.link].map((v) => typeof v === "string" ? workflow.resolveNodeRefs(v, args.refMap) : v).find((v) => present2(v));
265
- return [args.override, args.wired, typed].find(present2) ?? "";
266
- }
267
- default:
268
- return void 0;
269
- }
270
- }
271
204
 
272
205
  exports.NODE_PROMPT_CANDIDATE_FIELDS = NODE_PROMPT_CANDIDATE_FIELDS;
273
206
  exports.NODE_PROMPT_FIELDS = NODE_PROMPT_FIELDS;
274
207
  exports.PROMPT_AFFIX_CORE_FIELD_OVERRIDES = PROMPT_AFFIX_CORE_FIELD_OVERRIDES;
275
208
  exports.PROMPT_AFFIX_NODE_TYPES = PROMPT_AFFIX_NODE_TYPES;
276
- exports.TEXT_REQUIRED_NODE_TYPES = TEXT_REQUIRED_NODE_TYPES;
277
- exports.WIRED_OUTPUT_NODE_TYPES = WIRED_OUTPUT_NODE_TYPES;
278
209
  exports.applyPromptAffixes = applyPromptAffixes;
279
210
  exports.composeNegative = composeNegative;
280
- exports.computeAiWriterInput = computeAiWriterInput;
281
211
  exports.computeLlmChatFields = computeLlmChatFields;
282
212
  exports.computeNodePrompt = computeNodePrompt;
283
- exports.computeNodeSendText = computeNodeSendText;
284
213
  exports.computeScriptTopic = computeScriptTopic;
285
214
  exports.getPromptFields = getPromptFields;
286
215
  exports.getSnippetMedia = getSnippetMedia;
@@ -288,10 +217,10 @@ exports.joinPromptParts = joinPromptParts;
288
217
  exports.nodeHasInlinePrompt = nodeHasInlinePrompt;
289
218
  exports.nodeHasPromptField = nodeHasPromptField;
290
219
  exports.nodeSupportsPromptAffixes = nodeSupportsPromptAffixes;
220
+ exports.ownEntry = ownEntry;
291
221
  exports.promptAffixCoreField = promptAffixCoreField;
292
222
  exports.promptFieldCarriesAffixes = promptFieldCarriesAffixes;
293
223
  exports.promptPartSeparator = promptPartSeparator;
294
224
  exports.resolvePrompt = resolvePrompt;
295
- exports.stripAudioTags = stripAudioTags;
296
- //# sourceMappingURL=chunk-ZFIXQRBZ.cjs.map
297
- //# sourceMappingURL=chunk-ZFIXQRBZ.cjs.map
225
+ //# sourceMappingURL=chunk-LXPKTI2K.cjs.map
226
+ //# sourceMappingURL=chunk-LXPKTI2K.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/own-entry.ts","../src/resolve-prompt.ts","../src/node-prompt-fields.ts"],"names":["resolveNodeRefs","joinSentences","SOCIAL_POST_NODE_TYPES","readPromptAffixes"],"mappings":"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","file":"chunk-LXPKTI2K.cjs","sourcesContent":["/**\n * A registry keyed by node type is a plain object, and the key is\n * caller-supplied (`?nodeType=` on a public route, a preset row's `node_type`,\n * workflow JSON). A plain `record[key] ?? fallback` reads an Object.prototype\n * member for `\"constructor\"`, `\"toString\"` or `\"__proto__\"` — a function or\n * the prototype itself, which the caller then maps or iterates and throws on.\n * This reads an OWN entry only, so such a name is \"no such type\".\n */\nexport function ownEntry<T>(record: Readonly<Record<string, T>>, key: string | undefined): T | undefined {\n return key !== undefined && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined\n}\n","import { resolveNodeRefs } from \"@nodaro/shared/workflow\"\nimport { readPromptAffixes, type PromptAffixes } from \"@nodaro/shared/nodes\"\nimport { SOCIAL_POST_NODE_TYPES } from \"@nodaro/shared/social\"\nimport { joinSentences } from \"./hint-join.js\"\nimport { ownEntry } from \"./own-entry.js\"\n\nexport interface ResolvePromptArgs {\n override?: string\n typed?: ReadonlyArray<string | undefined>\n wired?: string\n refMap: ReadonlyMap<string, string>\n /** Opt-in (generate-image / generate-video only): APPEND the wired\n * (connected-prompt) value to the TYPED base instead of treating wired as a\n * fallback, so a connected prompt auto-injects alongside the typed prompt. An\n * `override` (list fan-out item) still fully replaces — no wired append.\n * Off/undefined = exact legacy precedence (override > typed > wired) for every\n * other node type. */\n appendWired?: boolean\n /** Pre/post text wrapped around WHICHEVER core precedence produced (override,\n * typed, wired, or the appendWired combination). Applied last. */\n affixes?: PromptAffixes\n}\nconst present = (s?: string): s is string => typeof s === \"string\" && s.trim().length > 0\nconst rr = (s: string, m: ReadonlyMap<string, string>) => (m.size > 0 ? resolveNodeRefs(s, m) : s)\n\n// ---------------------------------------------------------------------------\n// Prompt affixes (pre/post text) — see docs/prompt-pre-post-text.md (join rule, empty-core, no-op guarantee)\n// ---------------------------------------------------------------------------\n\nconst GLUE_PUNCTUATION = new Set([\",\", \".\", \";\", \":\", \"!\", \"?\", \")\"])\n\n/** The ONE join rule between two non-blank prompt parts: a single space unless\n * the boundary already has whitespace on either side or the right part opens\n * with sentence punctuation. Shared by `joinPromptParts` and the editor's\n * Final-view segment builder so preview and run agree byte-for-byte. */\nexport function promptPartSeparator(left: string, right: string): \"\" | \" \" {\n if (left.length === 0 || right.length === 0) return \"\"\n if (/\\s$/.test(left) || /^\\s/.test(right)) return \"\"\n if (GLUE_PUNCTUATION.has(right[0])) return \"\"\n return \" \"\n}\n\n/** Join prompt parts with `promptPartSeparator`; blank parts are dropped,\n * non-blank parts are used verbatim (no trimming). */\nexport function joinPromptParts(parts: ReadonlyArray<string | undefined>): string {\n let out = \"\"\n for (const part of parts) {\n if (!present(part)) continue\n out = out.length === 0 ? part : out + promptPartSeparator(out, part) + part\n }\n return out\n}\n\n/**\n * Wrap `core` with the node's pre/post text. `{Label}` refs are resolved in the\n * AFFIXES only — the core arrives exactly as the caller's precedence produced it.\n * NO-OP GUARANTEE: with no non-blank affix the core is returned unchanged (same\n * reference) — every existing workflow stays byte-identical.\n * EMPTY CORE: a blank core with affixes yields the joined affixes alone.\n */\nexport function applyPromptAffixes(core: string, affixes: PromptAffixes | undefined, refMap: ReadonlyMap<string, string>): string\nexport function applyPromptAffixes(core: string | undefined, affixes: PromptAffixes | undefined, refMap: ReadonlyMap<string, string>): string | undefined\nexport function applyPromptAffixes(\n core: string | undefined,\n affixes: PromptAffixes | undefined,\n refMap: ReadonlyMap<string, string>,\n): string | undefined {\n const prefix = present(affixes?.prefix) ? rr(affixes!.prefix!, refMap) : undefined\n const suffix = present(affixes?.suffix) ? rr(affixes!.suffix!, refMap) : undefined\n if (prefix === undefined && suffix === undefined) return core\n return joinPromptParts([prefix, core, suffix])\n}\n\n/** SINGLE SOURCE OF TRUTH for prompt precedence across both DAG engines:\n * override (list fan-out) > first present typed candidate > wired > \"\".\n * \"present\" = non-empty after trim. {Label} refs are resolved on the chosen\n * branch via the shared resolveNodeRefs. With `appendWired`, the chosen base\n * AND the wired value are both emitted, joined as sentences (`joinSentences`:\n * \". \", or a space after one that already ends). Affixes wrap the result. */\nexport function resolvePrompt({ override, typed = [], wired, refMap, appendWired, affixes }: ResolvePromptArgs): string {\n return applyPromptAffixes(resolveCore({ override, typed, wired, refMap, appendWired }), affixes, refMap)\n}\n\nfunction resolveCore({ override, typed = [], wired, refMap, appendWired }: Omit<ResolvePromptArgs, \"affixes\">): string {\n // appendWired: a connected prompt APPENDS to the TYPED base. An `override`\n // (list fan-out item) still fully REPLACES — it never receives a wired append,\n // so per-item fan-out prompts are unchanged.\n if (appendWired && !present(override)) {\n const base = typed.find(present)\n return joinSentences([base, wired].filter(present).map((s) => rr(s, refMap)))\n }\n if (present(override)) return rr(override, refMap)\n for (const t of typed) if (present(t)) return rr(t, refMap)\n if (present(wired)) return rr(wired, refMap)\n return \"\"\n}\n\n/** Compose a final NEGATIVE prompt from a TYPED base + a WIRED (connected\n * negative-handle) value: both are emitted, joined as sentences (`joinSentences`, mirrors `appendWired`\n * for the positive prompt). Pure join — the caller resolves `{label}` refs on\n * the typed value first, and the wired value is already a resolved output that\n * the input-resolver has filtered (referenced / Inject-Negative-off dropped).\n * Generate-image / generate-video only; empty parts are dropped. */\nexport function composeNegative(typed?: string, wired?: string): string {\n return joinSentences([typed, wired])\n}\n\n/** Ordered typed-candidate fields per node type — the precedence source of\n * truth (NOT NODE_MAPPABLE_FIELDS, which is field-mapping eligibility, omits\n * video-retake, and orders llm-chat wrong). */\nexport const NODE_PROMPT_CANDIDATE_FIELDS: Readonly<Record<string, readonly string[]>> = {\n \"generate-image\": [\"prompt\"],\n // motionPrompt is here so generate-video re-typed to text-to-video keeps a\n // prompt stored in data.motionPrompt (the inline picker's legacy field). Safe\n // for a standalone text-to-video node — it never carries data.motionPrompt.\n \"text-to-video\": [\"prompt\", \"motionPrompt\"],\n \"video-to-video\": [\"prompt\"],\n \"generate-music\": [\"prompt\"],\n \"speech-to-video\": [\"prompt\"],\n \"cinematic-avatar\": [\"prompt\"],\n \"extend-video\": [\"prompt\"],\n \"video-retake\": [\"prompt\"],\n \"suno-replace-section\": [\"prompt\"],\n \"image-to-video\": [\"prompt\", \"motionPrompt\"],\n \"generate-video\": [\"prompt\", \"motionPrompt\"],\n \"text-to-audio\": [\"prompt\", \"text\"],\n // Social posts: the orchestrator + frontend executor match per-platform node\n // types (node.type is \"instagram-post\", \"telegram-post\", …), NOT a unified\n // \"social-publish\" type — so every platform needs its own [\"caption\"] entry.\n // Derived from the shared SOCIAL_POST_NODE_TYPES single source of truth so a\n // new platform can't drift out of the precedence map.\n ...Object.fromEntries(\n [...SOCIAL_POST_NODE_TYPES].map((t) => [t, [\"caption\"] as readonly string[]]),\n ),\n // Kept for any caller that passes the aggregate type instead of a platform.\n \"social-publish\": [\"caption\"],\n}\n\nexport interface ComputeNodePromptArgs {\n override?: string\n wired?: string\n refMap: ReadonlyMap<string, string>\n /** See ResolvePromptArgs.appendWired — generate-image / generate-video only. */\n appendWired?: boolean\n}\n/** Resolve a single-prompt node's final prompt (typed-primary). Both engines\n * call this so field-selection + precedence are structurally identical. */\nexport function computeNodePrompt(\n nodeType: string,\n data: Record<string, unknown>,\n { override, wired, refMap, appendWired }: ComputeNodePromptArgs,\n): string {\n let typed: ReadonlyArray<string | undefined>\n if (nodeType === \"text-to-speech\") {\n // data.text is a phantom field on TTS; only directText (gated) is real.\n //\n // The gate is a PREFERENCE, not a lock: when textSource is \"connected\" we\n // still fall back to typed text if nothing is wired. Writers flip the gate\n // (PromptFieldSpec.promptGate), but data reaches nodes from places no\n // writer touches — workflows saved before that fix, JSON imports, MCP\n // writes, templates — and there the text sat visible in the node while the\n // run failed with \"no text found\" (founder, 2026-08-14). Coerce rather\n // than reject, same principle as normalizeModelInput.\n typed =\n data.textSource === \"direct\" || !present(wired)\n ? [data.directText as string | undefined]\n : []\n } else {\n const fields = ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? [\"prompt\"]\n typed = fields.map((f) => data[f] as string | undefined)\n }\n return resolvePrompt({ override, typed, wired, refMap, appendWired, affixes: readPromptAffixes(data) })\n}\n\nexport interface LlmChatFieldArgs {\n override?: string\n wiredUserInput?: string\n wiredSystemPrompt?: string\n refMap: ReadonlyMap<string, string>\n}\n/** llm-chat resolves TWO independent fields. override applies to userInput only. */\nexport function computeLlmChatFields(\n data: Record<string, unknown>,\n { override, wiredUserInput, wiredSystemPrompt, refMap }: LlmChatFieldArgs,\n): { userInput: string; systemPrompt: string } {\n return {\n userInput: resolvePrompt({ override, typed: [data.userInput as string | undefined], wired: wiredUserInput, refMap, affixes: readPromptAffixes(data) }),\n systemPrompt: resolvePrompt({ typed: [data.systemPrompt as string | undefined], wired: wiredSystemPrompt, refMap }),\n }\n}\n\n/** generate-script's topic. Both engines call this, so a wired Text node reads\n * the same on a single-node run and a server run.\n *\n * Precedence is override (a list fan-out item) > wired > `data.prompt`. Wired\n * comes BEFORE the typed field, the reverse of `computeNodePrompt`, because\n * `data.prompt` has no editor: the panel's text field is the style guide. A\n * value there was written by the old connect-time `{Label}` auto-fill or by an\n * agent, and it is kept only as the fallback that saved workflows rely on. */\nexport function computeScriptTopic(\n data: Record<string, unknown>,\n { override, wired, refMap }: Omit<ComputeNodePromptArgs, \"appendWired\">,\n): string {\n const core = [override, wired, data.prompt as string | undefined].find(present)\n return applyPromptAffixes(core === undefined ? \"\" : rr(core, refMap), readPromptAffixes(data), refMap)\n}\n","/**\n * Single source of truth: which data field(s) hold a node's user-editable\n * prompt text. Drives the quick-edit Prompt modal so it can work generically\n * across every AI node without each call site hardcoding a field name.\n *\n * The registry LIVES HERE (`@nodaro/prompts`) so the backend orchestrator, the\n * `/v1/nodes` registry, the prompt-affix totality tests and the docs tooling\n * read the SAME list the editor does. `frontend/src/lib/prompt-fields.ts` is a\n * pure re-export of this module — frontend code keeps importing\n * `@/lib/prompt-fields` unchanged.\n *\n * Most nodes store the prompt in `data.prompt`, but several don't\n * (`text-prompt` → `text`, `image-to-text` → `customPrompt`, …) — this map is\n * where that knowledge lives, once.\n *\n * INVARIANT: every node that exposes a user-editable prompt MUST have an entry\n * here, or the quick-edit modal silently does nothing for it. A guard test\n * (`prompt-fields.test.ts`) keeps this in sync with the node set.\n *\n * This module is intentionally pure data (no React/lucide imports) so it can be\n * pulled into the app-runtime bundle and any test without dragging in the icon\n * library. The string `icon` kind is mapped to a concrete lucide component in\n * `prompt-edit-button.tsx` (`getPromptIcon`), the only place that renders it.\n */\n\nimport type { SnippetMedia } from \"./factory-snippets/types.js\"\nimport { NODE_PROMPT_CANDIDATE_FIELDS } from \"./resolve-prompt.js\"\nimport { ownEntry } from \"./own-entry.js\"\n\n/** Which lucide glyph a node's prompt affordance uses. Kept as a string so this\n * module stays icon-library-free; mapped to a component in the strip button. */\nexport type PromptIconKind = \"pencil\" | \"paintbrush\"\n\nexport interface PromptFieldSpec {\n /** Data key holding the primary prompt (e.g. \"prompt\", \"text\", \"customPrompt\"). */\n readonly prompt: string\n /** Data key holding the negative prompt, when the node supports one. */\n readonly negative?: string\n /** Label override for the primary field (defaults to \"Prompt\"). */\n readonly promptLabel?: string\n /** Icon for the prompt affordance (strip button + modal title). Defaults to a\n * pencil; image-editing nodes use a paintbrush to read as \"edit\". */\n readonly icon?: PromptIconKind\n /** Node modality for prompt-snippet scoping — drives which snippet pool the\n * \"/\" menu and Snippets button show for this node's prompt fields. REQUIRED\n * so a new node cannot forget to declare it (compile error). */\n readonly media: SnippetMedia\n /** True when this node renders a media-result preview body and therefore\n * participates in inline-prompt mode (the `InlineNodePrompt` editor on the\n * node face, centralized in `BaseNode`). REQUIRED (like `media`) so a new\n * prompt node must consciously choose — compile error if omitted. The set of\n * `inline: true` types is guarded in `prompt-fields.test.ts`. */\n readonly inline: boolean\n /** Some nodes only READ their prompt field when a sibling discriminator\n * selects it — TTS keeps `directText` but resolves it only while\n * `textSource === \"direct\"`, defaulting to `\"connected\"`. Writing the text\n * without flipping the discriminator saved the value and then failed with\n * \"no text found\" (founder hit it live, 2026-08-14). Declare the pair here\n * and every writer flips it automatically. */\n readonly promptGate?: { readonly field: string; readonly value: string }\n /** Prompt pre/post text (`promptPrefix` / `promptSuffix`) support. Defaults to\n * ON for every prompt node; set `false` ONLY for a node whose \"prompt\" is not\n * a model prompt (the plain Text input node). Read via `nodeSupportsPromptAffixes`. */\n readonly affixes?: false\n}\n\nexport const NODE_PROMPT_FIELDS: Readonly<Record<string, PromptFieldSpec>> = {\n // ── Image ──\n // (`edit-image` / `image-to-image` are legacy types consolidated into\n // `modify-image`; they're not in the creatable node set, so a node of that\n // type never mounts and needs no entry here. The guard test enforces that.)\n \"generate-image\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"image\", inline: true },\n \"modify-image\": { prompt: \"prompt\", negative: \"negativePrompt\", icon: \"paintbrush\", media: \"image\", inline: true },\n \"generate-mask\": { prompt: \"prompt\", promptLabel: \"What to mask\", media: \"image\", inline: true },\n // ── Video ──\n \"generate-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n // Trimmed multi-segment stitch sibling of generate-video — no negativePrompt field.\n \"generate-video-pro\": { prompt: \"prompt\", media: \"video\", inline: true },\n // Span-replace sibling of generate-video-pro — no negativePrompt field.\n \"edit-video-pro\": { prompt: \"prompt\", media: \"video\", inline: true },\n \"text-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"image-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", promptLabel: \"Motion prompt\", media: \"video\", inline: true },\n \"video-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"switchx\": { prompt: \"prompt\", promptLabel: \"Look prompt\", media: \"video\", inline: true },\n \"extend-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"speech-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"motion-transfer\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n // Cinematic Avatar (HeyGen) — generative prompt (NOT a verbatim script),\n // so it participates in the quick-edit Prompt modal like other AI video nodes.\n \"cinematic-avatar\": { prompt: \"prompt\", media: \"video\", inline: true },\n \"video-sfx\": { prompt: \"prompt\", negative: \"negativePrompt\", promptLabel: \"Sound prompt\", media: \"audio\", inline: true },\n \"video-retake\": { prompt: \"prompt\", promptLabel: \"Retake prompt\", media: \"video\", inline: true },\n // Video Mask (SAM 3) — the subject to track, like Generate Mask's \"what to mask\".\n \"video-mask\": { prompt: \"prompt\", promptLabel: \"What to mask\", media: \"video\", inline: true },\n // ── Audio / music ──\n \"generate-music\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-generate\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"text-to-audio\": { prompt: \"prompt\", media: \"audio\", inline: true },\n // ── Text / LLM (no media-result preview body → no inline editor) ──\n \"text-prompt\": { prompt: \"text\", promptLabel: \"Text\", media: \"text\", inline: false, affixes: false },\n \"image-to-text\": { prompt: \"customPrompt\", promptLabel: \"Question\", media: \"text\", inline: false },\n \"llm-chat\": { prompt: \"userInput\", promptLabel: \"Prompt\", media: \"text\", inline: false },\n // ── Speech / voice ──\n \"text-to-speech\": { prompt: \"directText\", promptLabel: \"Text\", media: \"audio\", inline: true, promptGate: { field: \"textSource\", value: \"direct\" } },\n \"voice-design\": { prompt: \"voiceDescription\", promptLabel: \"Voice description\", media: \"audio\", inline: true },\n \"voice-remix\": { prompt: \"voiceDescription\", promptLabel: \"Voice description\", media: \"audio\", inline: true },\n \"lip-sync\": { prompt: \"prompt\", media: \"audio\", inline: true },\n // ── Suno (music) ──\n \"suno-cover\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-extend\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-replace-section\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-upload-extend\": { prompt: \"prompt\", media: \"audio\", inline: true },\n // NOTE: `suno-add-vocals` is intentionally absent — its node has no\n // user-editable prompt (SunoAddVocalsData declares only `model`; the config\n // panel + backend route take taskId/audioId/model, never a prompt). A stale\n // entry here rendered a phantom Prompt editor in the quick-edit modal that\n // wrote to a `data.prompt` key nothing ever reads (the dead-field class the\n // guard test now catches via defaultData ownership).\n \"suno-lyrics\": { prompt: \"prompt\", media: \"audio\", inline: false },\n \"suno-style-boost\": { prompt: \"content\", promptLabel: \"Style\", media: \"audio\", inline: false },\n // ── Composition / FX (compact, no media-result preview → no inline editor) ──\n \"image-critic\": { prompt: \"prompt\", promptLabel: \"Criteria\", media: \"image\", inline: false },\n \"motion-graphics\": { prompt: \"motionPrompt\", promptLabel: \"Motion prompt\", media: \"video\", inline: false },\n \"pro-3d-render\": { prompt: \"scenePrompt\", promptLabel: \"Scene\", media: \"text\", inline: false },\n \"generate-3d-scene\": { prompt: \"scenePrompt\", promptLabel: \"Scene\", media: \"text\", inline: false },\n \"edit-3d-scene\": { prompt: \"editPrompt\", promptLabel: \"Edit instruction\", media: \"text\", inline: false },\n \"3d-title\": { prompt: \"titlePrompt\", promptLabel: \"Title\", media: \"text\", inline: false },\n // ── Script / alignment (their primary text field) ──\n \"generate-script\": { prompt: \"styleGuide\", promptLabel: \"Style guide\", media: \"text\", inline: false },\n \"forced-alignment\": { prompt: \"transcript\", promptLabel: \"Transcript\", media: \"audio\", inline: false },\n // Video Analysis — its focus hint is the editable prompt (renders a JSON scene\n // table, not a media preview → no inline editor).\n \"video-analysis\": { prompt: \"analysisFocus\", promptLabel: \"Analysis focus\", media: \"video\", inline: false },\n // Edit Plan (podcast editing) — its free-text editing instructions are the\n // editable prompt; the affixes wrap them. Emits an EDL (JSON), not a media\n // preview, so no inline editor.\n \"edit-plan\": { prompt: \"instructions\", promptLabel: \"Editing instructions\", media: \"video\", inline: false },\n}\n\n/** The prompt-field spec for a node type, or undefined if it has none (an\n * Object.prototype member name such as `constructor` has none). */\nexport function getPromptFields(nodeType: string | undefined): PromptFieldSpec | undefined {\n return nodeType ? ownEntry(NODE_PROMPT_FIELDS, nodeType) : undefined\n}\n\n/** True when this node type has a registered, quick-editable prompt field. */\nexport function nodeHasPromptField(nodeType: string | undefined): boolean {\n return getPromptFields(nodeType) !== undefined\n}\n\n/** Snippet modality for a node type (drives the snippet pool), or undefined\n * when the node has no prompt field. */\nexport function getSnippetMedia(nodeType: string | undefined): SnippetMedia | undefined {\n return getPromptFields(nodeType)?.media\n}\n\n/** True when this node type renders the inline on-node prompt editor — the\n * media-preview nodes (image/video/audio result body). Single source for\n * BaseNode's centralized `InlineNodePrompt` rendering and the gold nodes'\n * `showInline` derivation (`useInlinePromptActive`). */\nexport function nodeHasInlinePrompt(nodeType: string | undefined): boolean {\n return getPromptFields(nodeType)?.inline === true\n}\n\n/** True when the node type carries `promptPrefix` / `promptSuffix` — every\n * registered prompt node except explicit opt-outs (`affixes: false`). The single\n * predicate the config panel, Final view, `/v1/nodes` registry, totality tests\n * and docs tooling all gate on. */\nexport function nodeSupportsPromptAffixes(nodeType: string | undefined): boolean {\n const spec = getPromptFields(nodeType)\n return spec !== undefined && spec.affixes !== false\n}\n\n/**\n * Where the RUN-TIME prompt lives when it differs from the editor's prompt\n * field (spec §7). `generate-script`'s editable prompt is its `styleGuide`, but\n * the run wraps the topic `prompt` with the affixes — so previewing the style\n * guide must NOT show them.\n */\nexport const PROMPT_AFFIX_CORE_FIELD_OVERRIDES: Readonly<Record<string, string>> = { \"generate-script\": \"prompt\" }\n\n/** The data key whose value the run wraps with promptPrefix/promptSuffix, or\n * undefined when the node has no affixes. */\nexport function promptAffixCoreField(nodeType: string | undefined): string | undefined {\n if (!nodeSupportsPromptAffixes(nodeType)) return undefined\n return PROMPT_AFFIX_CORE_FIELD_OVERRIDES[nodeType!] ?? getPromptFields(nodeType)!.prompt\n}\n\n/**\n * True when a preview of `promptField` on this node must show the affixes —\n * i.e. that key is what the run actually wraps. THE gate for the editor's Final\n * view, so a sibling field (llm-chat's `systemPrompt`, generate-script's\n * `styleGuide`) never renders a wrap the run doesn't perform.\n *\n * Two ways a key qualifies:\n * - it IS the core field ({@link promptAffixCoreField}), or\n * - it is a run-time FALLBACK candidate for the type\n * ({@link NODE_PROMPT_CANDIDATE_FIELDS}) — `computeNodePrompt` picks the\n * first present candidate and wraps THAT, so i2v's legacy `motionPrompt`\n * genuinely receives the affixes when `data.prompt` is empty.\n *\n * The two rules never collide: no override type declares candidates (guarded in\n * `node-prompt-fields.test.ts`).\n *\n * `undefined` means \"the node's primary prompt field\" — the common call.\n */\nexport function promptFieldCarriesAffixes(nodeType: string | undefined, promptField: string | undefined): boolean {\n const core = promptAffixCoreField(nodeType)\n if (core === undefined) return false\n if (promptField === undefined) return getPromptFields(nodeType)!.prompt === core\n return promptField === core || (ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? []).includes(promptField)\n}\n\n/** The affix-capable node types, derived from the registry (never hand-listed). */\nexport const PROMPT_AFFIX_NODE_TYPES: ReadonlySet<string> = new Set(\n Object.entries(NODE_PROMPT_FIELDS).filter(([, s]) => s.affixes !== false).map(([t]) => t),\n)\n"]}
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var chunkLXPKTI2K_cjs = require('./chunk-LXPKTI2K.cjs');
4
+ var chunk4GKTOVFM_cjs = require('./chunk-4GKTOVFM.cjs');
3
5
  var chunkNAFD6MGH_cjs = require('./chunk-NAFD6MGH.cjs');
4
6
  var models = require('@nodaro/shared/models');
5
7
 
@@ -137,7 +139,9 @@ function stylizedSubjectFor(nodeType) {
137
139
  name: p.name,
138
140
  description: p.description,
139
141
  group: "Stylized Subject",
140
- data: { provider: editProviderFor(nodeType), ...p.data }
142
+ data: { provider: editProviderFor(nodeType), ...p.data },
143
+ // Restyles the person in the connected photo ("the reference image").
144
+ minReferences: 1
141
145
  }));
142
146
  }
143
147
  var IMAGE_EDITS = [
@@ -261,7 +265,9 @@ function editsFor(nodeType) {
261
265
  name: p.name,
262
266
  description: p.description,
263
267
  group: "Edits",
264
- data: { provider: editProviderFor(nodeType), ...p.data }
268
+ data: { provider: editProviderFor(nodeType), ...p.data },
269
+ // Every edit operates on the connected photo.
270
+ minReferences: 1
265
271
  }));
266
272
  }
267
273
 
@@ -270,12 +276,17 @@ var sentences = (...parts) => parts.join(" ");
270
276
  var REALISM_MODEL = { provider: "gpt-image-2", resolution: "2K" };
271
277
  var SKIN_STUDY_MODEL = { provider: "seedream-5-pro", quality: "high" };
272
278
  var GENERATE_IMAGE_PRESETS = [
279
+ // `minReferences` marks a preset that works from connected photos its prompt
280
+ // describes in words ("the attached photo"); `{image:N}` tokens count on their
281
+ // own, so a tokenized preset declares nothing. The groups stay all-or-nothing
282
+ // (factory-preset-references.test.ts), so a new preset copies the line along.
273
283
  // ── Reference Sheet ──────────────────────────────────────────────────────
274
284
  {
275
285
  id: "generate-image/character-board",
276
286
  name: "Character Board",
277
287
  description: "Connect a sharp, well-lit photo \u2192 dense character sheet; reuse it as a reference for consistent shots.",
278
288
  group: "Reference Sheet",
289
+ minReferences: 1,
279
290
  data: {
280
291
  provider: "gpt-image-2",
281
292
  aspectRatio: "16:9",
@@ -290,6 +301,7 @@ var GENERATE_IMAGE_PRESETS = [
290
301
  name: "Pose Board",
291
302
  description: "Connect a sharp photo \u2192 animation-ready pose & expression sheet; reuse it as a reference for consistent action.",
292
303
  group: "Reference Sheet",
304
+ minReferences: 1,
293
305
  data: {
294
306
  provider: "gpt-image-2",
295
307
  aspectRatio: "16:9",
@@ -304,6 +316,7 @@ var GENERATE_IMAGE_PRESETS = [
304
316
  name: "Location Board",
305
317
  description: "Connect a photo \u2192 dense location sheet; reuse it as a reference for consistent scenes.",
306
318
  group: "Reference Sheet",
319
+ minReferences: 1,
307
320
  data: {
308
321
  provider: "gpt-image-2",
309
322
  aspectRatio: "16:9",
@@ -318,6 +331,7 @@ var GENERATE_IMAGE_PRESETS = [
318
331
  name: "Product Board",
319
332
  description: "Connect a photo \u2192 dense product sheet; reuse it as a reference for consistent shots.",
320
333
  group: "Reference Sheet",
334
+ minReferences: 1,
321
335
  data: {
322
336
  provider: "gpt-image-2",
323
337
  aspectRatio: "16:9",
@@ -332,6 +346,7 @@ var GENERATE_IMAGE_PRESETS = [
332
346
  name: "Outfit Board",
333
347
  description: "Connect a photo \u2192 dense wardrobe sheet; reuse it as a reference for consistent looks.",
334
348
  group: "Reference Sheet",
349
+ minReferences: 1,
335
350
  data: {
336
351
  provider: "gpt-image-2",
337
352
  aspectRatio: "16:9",
@@ -346,6 +361,7 @@ var GENERATE_IMAGE_PRESETS = [
346
361
  name: "Scene Board",
347
362
  description: "Connect a photo \u2192 set-dressing & props study; reuse it as a reference for consistent sets.",
348
363
  group: "Reference Sheet",
364
+ minReferences: 1,
349
365
  data: {
350
366
  provider: "gpt-image-2",
351
367
  aspectRatio: "16:9",
@@ -360,6 +376,7 @@ var GENERATE_IMAGE_PRESETS = [
360
376
  name: "Creature Board",
361
377
  description: "Connect a sharp photo \u2192 dense creature sheet; reuse it as a reference for consistent shots.",
362
378
  group: "Reference Sheet",
379
+ minReferences: 1,
363
380
  data: {
364
381
  provider: "gpt-image-2",
365
382
  aspectRatio: "16:9",
@@ -374,6 +391,7 @@ var GENERATE_IMAGE_PRESETS = [
374
391
  name: "Vehicle Board",
375
392
  description: "Connect a photo \u2192 dense vehicle sheet; reuse it as a reference for consistent shots.",
376
393
  group: "Reference Sheet",
394
+ minReferences: 1,
377
395
  data: {
378
396
  provider: "gpt-image-2",
379
397
  aspectRatio: "16:9",
@@ -388,6 +406,7 @@ var GENERATE_IMAGE_PRESETS = [
388
406
  name: "Food Board",
389
407
  description: "Connect a photo \u2192 dense dish sheet; reuse it as a reference for consistent food shots.",
390
408
  group: "Reference Sheet",
409
+ minReferences: 1,
391
410
  data: {
392
411
  provider: "gpt-image-2",
393
412
  aspectRatio: "16:9",
@@ -402,6 +421,7 @@ var GENERATE_IMAGE_PRESETS = [
402
421
  name: "Mascot Board",
403
422
  description: "Connect a mascot image \u2192 brand-character sheet with brand applications; reuse it for a consistent mascot.",
404
423
  group: "Reference Sheet",
424
+ minReferences: 1,
405
425
  data: {
406
426
  provider: "gpt-image-2",
407
427
  aspectRatio: "16:9",
@@ -416,6 +436,7 @@ var GENERATE_IMAGE_PRESETS = [
416
436
  name: "Pet Board",
417
437
  description: "Connect a sharp photo \u2192 dense pet sheet; reuse it as a reference for consistent shots.",
418
438
  group: "Reference Sheet",
439
+ minReferences: 1,
419
440
  data: {
420
441
  provider: "gpt-image-2",
421
442
  aspectRatio: "16:9",
@@ -430,6 +451,7 @@ var GENERATE_IMAGE_PRESETS = [
430
451
  name: "Skin Detail Sheet",
431
452
  description: "Connect a sharp face photo \u2192 a six-panel skin reference.",
432
453
  group: "Reference Sheet",
454
+ minReferences: 1,
433
455
  data: {
434
456
  ...SKIN_STUDY_MODEL,
435
457
  aspectRatio: "3:2",
@@ -452,6 +474,7 @@ var GENERATE_IMAGE_PRESETS = [
452
474
  name: "Character Reference Grid",
453
475
  description: "Connect a photo \u2192 clean 4-angle identity grid (no decorations) \u2014 the strongest consistency anchor.",
454
476
  group: "Cast & Consistency",
477
+ minReferences: 1,
455
478
  data: {
456
479
  provider: "gpt-image-2",
457
480
  aspectRatio: "3:4",
@@ -466,6 +489,8 @@ var GENERATE_IMAGE_PRESETS = [
466
489
  name: "Cast Mega Grid",
467
490
  description: "Connect 2\u20134 character refs \u2192 one labeled cast sheet; then reference cast members by name in scenes.",
468
491
  group: "Cast & Consistency",
492
+ // A cast sheet is two characters or more ("Connect 2–4 character refs").
493
+ minReferences: 2,
469
494
  data: {
470
495
  provider: "gpt-image-2",
471
496
  aspectRatio: "3:4",
@@ -479,6 +504,7 @@ var GENERATE_IMAGE_PRESETS = [
479
504
  name: "Cast Scene (by name)",
480
505
  description: "Connect a cast grid \u2192 stage a scene naming the characters; never re-describe their looks.",
481
506
  group: "Cast & Consistency",
507
+ minReferences: 1,
482
508
  data: {
483
509
  provider: "gpt-image-2",
484
510
  aspectRatio: "16:9",
@@ -493,6 +519,7 @@ var GENERATE_IMAGE_PRESETS = [
493
519
  name: "Label Elements",
494
520
  description: "Step 1 \xB7 connect a photo \u2192 the same image with a numbered label on each changeable element.",
495
521
  group: "Edit by Name",
522
+ minReferences: 1,
496
523
  data: {
497
524
  provider: "gpt-image-2",
498
525
  quality: "high",
@@ -505,6 +532,7 @@ var GENERATE_IMAGE_PRESETS = [
505
532
  name: "Apply Named Edit",
506
533
  description: 'Step 2 \xB7 connect the labeled sheet \u2192 type your change in the prompt (e.g. change "drink 1" to blue); the labels are removed from the result.',
507
534
  group: "Edit by Name",
535
+ minReferences: 1,
508
536
  data: {
509
537
  provider: "gpt-image-2",
510
538
  quality: "high",
@@ -1713,6 +1741,7 @@ var GENERATE_IMAGE_PRESETS = [
1713
1741
  name: "Five Ages (Timeless Soul)",
1714
1742
  description: "One person at 5 ages, studio line-up. Needs a reference photo.",
1715
1743
  group: "Portrait Transformations",
1744
+ minReferences: 1,
1716
1745
  data: {
1717
1746
  provider: "gpt-image-2",
1718
1747
  aspectRatio: "3:4",
@@ -1725,6 +1754,7 @@ var GENERATE_IMAGE_PRESETS = [
1725
1754
  name: "Three-Age Triptych",
1726
1755
  description: "Same person at 3 ages (~10 / 40 / 75). Needs a reference photo.",
1727
1756
  group: "Portrait Transformations",
1757
+ minReferences: 1,
1728
1758
  data: {
1729
1759
  provider: "gpt-image-2",
1730
1760
  aspectRatio: "3:4",
@@ -1737,6 +1767,7 @@ var GENERATE_IMAGE_PRESETS = [
1737
1767
  name: "Five Ages \u2014 B&W",
1738
1768
  description: "Monochrome age line-up. Needs a reference photo.",
1739
1769
  group: "Portrait Transformations",
1770
+ minReferences: 1,
1740
1771
  data: {
1741
1772
  provider: "gpt-image-2",
1742
1773
  aspectRatio: "3:4",
@@ -1749,6 +1780,7 @@ var GENERATE_IMAGE_PRESETS = [
1749
1780
  name: "Decade Timeline",
1750
1781
  description: "Same person across the '80s\u20132020s. Needs a reference photo.",
1751
1782
  group: "Portrait Transformations",
1783
+ minReferences: 1,
1752
1784
  data: {
1753
1785
  provider: "gpt-image-2",
1754
1786
  aspectRatio: "3:4",
@@ -1761,6 +1793,7 @@ var GENERATE_IMAGE_PRESETS = [
1761
1793
  name: "Four Seasons",
1762
1794
  description: "Same person in spring / summer / autumn / winter. Needs a reference photo.",
1763
1795
  group: "Portrait Transformations",
1796
+ minReferences: 1,
1764
1797
  data: {
1765
1798
  provider: "gpt-image-2",
1766
1799
  aspectRatio: "3:4",
@@ -1773,6 +1806,7 @@ var GENERATE_IMAGE_PRESETS = [
1773
1806
  name: "Times of Day",
1774
1807
  description: "Same person at dawn / midday / golden hour / night. Needs a reference photo.",
1775
1808
  group: "Portrait Transformations",
1809
+ minReferences: 1,
1776
1810
  data: {
1777
1811
  provider: "gpt-image-2",
1778
1812
  aspectRatio: "3:4",
@@ -1785,6 +1819,7 @@ var GENERATE_IMAGE_PRESETS = [
1785
1819
  name: "Sports Jersey Portrait",
1786
1820
  description: "You in an oversized team jersey, studio editorial. Needs a reference photo.",
1787
1821
  group: "Portrait Transformations",
1822
+ minReferences: 1,
1788
1823
  data: {
1789
1824
  provider: "nano-banana-2-1",
1790
1825
  aspectRatio: "4:5",
@@ -5116,6 +5151,64 @@ function groupFactoryPresets(presets) {
5116
5151
  }
5117
5152
  return buckets;
5118
5153
  }
5154
+ var MAX_PRESET_REFERENCES = Math.max(
5155
+ 1,
5156
+ ...[...models.MODELS_WITH_REFERENCE_IMAGE_SUPPORT].map((provider) => models.imageReferenceLimit(provider))
5157
+ );
5158
+ function promptFieldsOf(nodeType) {
5159
+ const spec = chunkLXPKTI2K_cjs.getPromptFields(nodeType);
5160
+ const candidates = chunkLXPKTI2K_cjs.ownEntry(chunkLXPKTI2K_cjs.NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? [];
5161
+ if (!spec && candidates.length === 0) return [];
5162
+ const fields = new Set(candidates);
5163
+ if (spec) {
5164
+ fields.add(spec.prompt);
5165
+ if (spec.negative) fields.add(spec.negative);
5166
+ }
5167
+ const core = chunkLXPKTI2K_cjs.promptAffixCoreField(nodeType);
5168
+ if (core) fields.add(core);
5169
+ if (chunkLXPKTI2K_cjs.nodeSupportsPromptAffixes(nodeType)) {
5170
+ fields.add("promptPrefix");
5171
+ fields.add("promptSuffix");
5172
+ }
5173
+ return [...fields];
5174
+ }
5175
+ function highestImageToken(text) {
5176
+ let highest = 0;
5177
+ for (const match of text.matchAll(chunk4GKTOVFM_cjs.IMAGE_TOKEN_PATTERN)) {
5178
+ const n = Number.parseInt(match[1] ?? "", 10);
5179
+ if (Number.isInteger(n) && n > highest) highest = n;
5180
+ }
5181
+ return highest;
5182
+ }
5183
+ function presetMinReferences(nodeType, data, declared) {
5184
+ let count = typeof declared === "number" && Number.isInteger(declared) && declared > 0 ? declared : 0;
5185
+ for (const field of promptFieldsOf(nodeType)) {
5186
+ const value = data[field];
5187
+ if (typeof value === "string") count = Math.max(count, highestImageToken(value));
5188
+ }
5189
+ return Math.min(count, MAX_PRESET_REFERENCES);
5190
+ }
5191
+
5192
+ // src/factory-presets/preview-files.generated.ts
5193
+ var FACTORY_PRESET_PREVIEW_FILES = {};
5194
+
5195
+ // src/factory-presets/previews.ts
5196
+ var PRESET_PREVIEW_PATH = "/preset-previews/";
5197
+ function originOf(baseUrl) {
5198
+ try {
5199
+ const url = new URL(baseUrl);
5200
+ if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
5201
+ return url.origin + url.pathname.replace(/\/+$/, "");
5202
+ } catch {
5203
+ return void 0;
5204
+ }
5205
+ }
5206
+ function factoryPresetPreviewUrl(presetId, baseUrl, files = FACTORY_PRESET_PREVIEW_FILES) {
5207
+ const file = Object.prototype.hasOwnProperty.call(files, presetId) ? files[presetId] : void 0;
5208
+ if (!file) return null;
5209
+ const origin = originOf(baseUrl);
5210
+ return origin ? `${origin}${PRESET_PREVIEW_PATH}${file}` : null;
5211
+ }
5119
5212
 
5120
5213
  // src/factory-presets/index.ts
5121
5214
  function forModifyImage(p) {
@@ -5147,7 +5240,7 @@ var FACTORY_PRESETS = {
5147
5240
  "lottie-overlay": LOTTIE_OVERLAY_PRESETS
5148
5241
  };
5149
5242
  function getFactoryPresets(nodeType) {
5150
- return FACTORY_PRESETS[nodeType] ?? [];
5243
+ return chunkLXPKTI2K_cjs.ownEntry(FACTORY_PRESETS, nodeType) ?? [];
5151
5244
  }
5152
5245
 
5153
5246
  // src/caption-treatments.ts
@@ -5532,8 +5625,10 @@ exports.BRAND_PRESET_META = BRAND_PRESET_META;
5532
5625
  exports.CAPTION_LEVERS_BY_LANGUAGE = CAPTION_LEVERS_BY_LANGUAGE;
5533
5626
  exports.CAPTION_SEGMENT_LEVER_KEYS = CAPTION_SEGMENT_LEVER_KEYS;
5534
5627
  exports.FACTORY_PRESETS = FACTORY_PRESETS;
5628
+ exports.FACTORY_PRESET_PREVIEW_FILES = FACTORY_PRESET_PREVIEW_FILES;
5535
5629
  exports.FACTORY_SNIPPETS = FACTORY_SNIPPETS;
5536
5630
  exports.HOOK_PLATE_PRESET_ID = HOOK_PLATE_PRESET_ID;
5631
+ exports.MAX_PRESET_REFERENCES = MAX_PRESET_REFERENCES;
5537
5632
  exports.OBJECT_ANGLE_PRESETS = OBJECT_ANGLE_PRESETS;
5538
5633
  exports.OBJECT_ANGLE_PROMPTS = OBJECT_ANGLE_PROMPTS;
5539
5634
  exports.OBJECT_ASSET_PRESETS = OBJECT_ASSET_PRESETS;
@@ -5542,13 +5637,16 @@ exports.OBJECT_MATERIAL_PRESETS = OBJECT_MATERIAL_PRESETS;
5542
5637
  exports.OBJECT_MATERIAL_PROMPTS = OBJECT_MATERIAL_PROMPTS;
5543
5638
  exports.OBJECT_VARIATION_PRESETS = OBJECT_VARIATION_PRESETS;
5544
5639
  exports.OBJECT_VARIATION_PROMPTS = OBJECT_VARIATION_PROMPTS;
5640
+ exports.PRESET_PREVIEW_PATH = PRESET_PREVIEW_PATH;
5545
5641
  exports.SNIPPET_MEDIA_VALUES = SNIPPET_MEDIA_VALUES;
5546
5642
  exports.STYLE_PRESETS = STYLE_PRESETS;
5643
+ exports.factoryPresetPreviewUrl = factoryPresetPreviewUrl;
5547
5644
  exports.getFactoryPresets = getFactoryPresets;
5548
5645
  exports.getFactorySnippets = getFactorySnippets;
5549
5646
  exports.getStylePreset = getStylePreset;
5550
5647
  exports.groupFactoryPresets = groupFactoryPresets;
5551
5648
  exports.hookPlateCaptionSegments = hookPlateCaptionSegments;
5649
+ exports.presetMinReferences = presetMinReferences;
5552
5650
  exports.resolveBrandInput = resolveBrandInput;
5553
- //# sourceMappingURL=chunk-CVPB2N3Z.cjs.map
5554
- //# sourceMappingURL=chunk-CVPB2N3Z.cjs.map
5651
+ //# sourceMappingURL=chunk-LZ53F5BN.cjs.map
5652
+ //# sourceMappingURL=chunk-LZ53F5BN.cjs.map