@nodaro/prompts 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -10217,6 +10217,218 @@ function applyTemplate(template, vars) {
10217
10217
  template
10218
10218
  );
10219
10219
  }
10220
+
10221
+ // src/prompt-hint-join.ts
10222
+ var PROMPT_HINT_SEPARATOR = ". ";
10223
+ function joinPromptHints(userPrompt, hints) {
10224
+ if (hints.length === 0) return userPrompt;
10225
+ return [userPrompt.trim(), ...hints].filter((p) => p.length > 0).join(PROMPT_HINT_SEPARATOR);
10226
+ }
10227
+
10228
+ // src/direction-registry.ts
10229
+ var perId = (full, compact) => (ids, mode) => {
10230
+ const out = [];
10231
+ for (const id of ids) {
10232
+ const frag = mode === "compact" ? compact(id) : full(id);
10233
+ if (frag.length > 0) out.push(frag);
10234
+ }
10235
+ return out;
10236
+ };
10237
+ var viaListBuilder = (build) => (ids, mode) => build(ids.length === 1 ? ids[0] : [...ids], mode).filter((s) => s.length > 0);
10238
+ var viaStringBuilder = (build) => (ids, mode) => {
10239
+ const s = build(ids.length === 1 ? ids[0] : [...ids], mode);
10240
+ return s.length > 0 ? [s] : [];
10241
+ };
10242
+ var viaMood = (ids, mode) => buildMoodHints({ mood: ids.length === 1 ? ids[0] : [...ids] }, mode).filter(
10243
+ (s) => s.length > 0
10244
+ );
10245
+ var framing = perId(getFramingPromptHint, getFramingTerm);
10246
+ var lighting = perId(getLightingPromptHint, getLightingTerm);
10247
+ var exposure = perId(getExposurePromptHint, getExposureTerm);
10248
+ var temporal = perId(getTemporalPromptHint, getTemporalTerm);
10249
+ var DIRECTION_FIELDS = [
10250
+ { key: "cameraMotion", surface: "video", family: "motion", maxPicks: 1, render: perId(getCameraMotionPromptHint, getCameraMotionTerm) },
10251
+ // Composition (the Framing catalog, one row per category).
10252
+ { key: "shotSize", surface: "both", family: "look", maxPicks: 1, render: framing },
10253
+ { key: "angle", surface: "both", family: "look", maxPicks: 1, render: framing },
10254
+ { key: "coverage", surface: "both", family: "look", maxPicks: 1, render: framing },
10255
+ { key: "composition", surface: "both", family: "look", maxPicks: 2, render: framing },
10256
+ { key: "vantage", surface: "both", family: "look", maxPicks: 1, render: framing },
10257
+ { key: "pose", surface: "both", family: "look", maxPicks: 1, render: perId(getPosePromptHint, getPoseTerm) },
10258
+ { key: "compositionEffect", surface: "both", family: "look", maxPicks: 1, render: perId(getCompositionEffectPromptHint, getCompositionEffectTerm) },
10259
+ // Camera.
10260
+ { key: "cameraFormat", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getCameraFormatPromptHint, getCameraFormatTerm) },
10261
+ { key: "lens", surface: "both", family: "look", maxPicks: 1, render: perId(getLensPromptHint, getLensTerm) },
10262
+ // Exposure (stills only — a video's exposure rides its own temporal levers).
10263
+ { key: "aperture", surface: "image", family: "look", maxPicks: 1, render: exposure },
10264
+ { key: "shutterSpeed", surface: "image", family: "look", maxPicks: 1, render: exposure },
10265
+ { key: "isoValue", surface: "image", family: "look", maxPicks: 1, render: exposure },
10266
+ // Light (the Lighting catalog, one row per category).
10267
+ { key: "timeOfDay", surface: "both", family: "look", maxPicks: 1, render: lighting },
10268
+ { key: "lightingStyle", surface: "both", family: "look", maxPicks: 2, render: lighting },
10269
+ { key: "lightingDirection", surface: "both", family: "look", maxPicks: 1, render: lighting },
10270
+ { key: "lightingRatio", surface: "both", family: "look", maxPicks: 1, render: lighting },
10271
+ { key: "colorTemperature", surface: "both", family: "look", maxPicks: 1, render: lighting },
10272
+ { key: "colorLook", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getColorLookPromptHint, getColorLookTerm) },
10273
+ { key: "atmosphere", surface: "both", family: "look", maxPicks: 2, render: viaListBuilder(buildAtmosphereHints) },
10274
+ { key: "postProcess", surface: "image", family: "look", maxPicks: 2, render: viaListBuilder(buildPostProcessHints) },
10275
+ // Style.
10276
+ { key: "style", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getStylePromptHint, getStyleTerm) },
10277
+ { key: "mood", surface: "both", family: "look", maxPicks: 2, render: viaMood },
10278
+ { key: "aesthetic", surface: "both", family: "look", maxPicks: 2, render: viaStringBuilder(buildAestheticHints) },
10279
+ { key: "photoGenre", surface: "image", family: "look", maxPicks: 1, render: perId(getPhotoGenrePromptHint, getPhotoGenreTerm) },
10280
+ { key: "photographer", surface: "image", family: "look", maxPicks: 2, render: viaStringBuilder(buildPhotographerHints) },
10281
+ { key: "renderQuality", surface: "image", family: "look", maxPicks: 1, render: perId(getRenderQualityPromptHint, getRenderQualityTerm) },
10282
+ // Scene.
10283
+ { key: "setting", surface: "both", family: "look", maxPicks: 1, render: perId(getSettingPromptHint, getSettingTerm) },
10284
+ { key: "era", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getEraPromptHint, getEraTerm) },
10285
+ { key: "backdrop", surface: "both", family: "look", maxPicks: 1, render: perId(getBackdropPromptHint, getBackdropTerm) },
10286
+ // Motion & time.
10287
+ { key: "actionFx", surface: "video", family: "motion", maxPicks: 2, render: viaListBuilder(buildActionFxHints) },
10288
+ { key: "temporalSpeed", surface: "video", family: "motion", maxPicks: 1, render: temporal },
10289
+ { key: "temporalFreeze", surface: "video", family: "motion", maxPicks: 1, render: temporal },
10290
+ { key: "temporalDirection", surface: "video", family: "motion", maxPicks: 1, render: temporal },
10291
+ { key: "temporalShutter", surface: "video", family: "motion", maxPicks: 1, render: temporal },
10292
+ { key: "transition", surface: "video", family: "motion", maxPicks: 2, render: perId(getTransitionPromptHint, getTransitionTerm) },
10293
+ { key: "loopSubject", surface: "video", family: "motion", maxPicks: 1, render: perId(getLoopSubjectPromptHint, getLoopSubjectTerm) },
10294
+ // ── LEGACY BLOCK — see the table doc above. Placed LAST, in today's exact
10295
+ // `composePromptText` order, so every pre-registry caller is byte-identical.
10296
+ { key: "framingId", surface: "both", family: "look", maxPicks: 1, render: framing },
10297
+ { key: "framingAngleId", surface: "both", family: "look", maxPicks: 1, render: framing },
10298
+ { key: "lightingId", surface: "both", family: "look", maxPicks: 1, render: lighting },
10299
+ { key: "lensId", surface: "both", family: "look", maxPicks: 1, render: perId(getLensPromptHint, getLensTerm) },
10300
+ { key: "cameraFormatId", surface: "both", family: "look", styleGroup: "film", maxPicks: 1, render: perId(getCameraFormatPromptHint, getCameraFormatTerm) }
10301
+ ];
10302
+ var DIRECTION_SPECS = DIRECTION_FIELDS;
10303
+ var DIRECTION_KEYS = DIRECTION_FIELDS.map((f) => f.key);
10304
+ var FILM_STYLE_KEYS = DIRECTION_SPECS.filter(
10305
+ (f) => f.styleGroup === "film"
10306
+ ).map((f) => f.key);
10307
+ var IMAGE_HINT_MODE_DEFAULT = "full";
10308
+ var VIDEO_HINT_MODE_DEFAULT = { look: "full", motion: "compact" };
10309
+ function modeForFamily(mode, family) {
10310
+ return typeof mode === "string" ? mode : family === "motion" ? mode.motion : mode.look;
10311
+ }
10312
+ var DIRECTION_ARRAY_CEILING = 8;
10313
+ var DIRECTION_ID_MAX_CHARS = 100;
10314
+ function normalizeDirectionIds(value, maxPicks) {
10315
+ const ids = [];
10316
+ if (typeof value === "string") {
10317
+ if (value) ids.push(value);
10318
+ } else if (Array.isArray(value)) {
10319
+ for (const v of value) {
10320
+ if (ids.length >= maxPicks) break;
10321
+ if (typeof v === "string" && v && !ids.includes(v)) ids.push(v);
10322
+ }
10323
+ }
10324
+ return ids.slice(0, maxPicks);
10325
+ }
10326
+ function directionFieldsForSurface(surface) {
10327
+ return DIRECTION_SPECS.filter((f) => f.surface === "both" || f.surface === surface);
10328
+ }
10329
+ function renderDirectionHintClauses(direction, opts) {
10330
+ if (!direction) return [];
10331
+ const mode = opts.mode ?? "full";
10332
+ const out = [];
10333
+ const seen = /* @__PURE__ */ new Set();
10334
+ for (const spec of DIRECTION_SPECS) {
10335
+ if (spec.surface !== "both" && spec.surface !== opts.surface) continue;
10336
+ const ids = normalizeDirectionIds(
10337
+ direction[spec.key],
10338
+ spec.maxPicks
10339
+ );
10340
+ if (ids.length === 0) continue;
10341
+ for (const hint of spec.render(ids, modeForFamily(mode, spec.family))) {
10342
+ if (hint.length > 0 && !seen.has(hint)) {
10343
+ seen.add(hint);
10344
+ out.push({
10345
+ key: spec.key,
10346
+ family: spec.family,
10347
+ ...spec.styleGroup !== void 0 ? { styleGroup: spec.styleGroup } : {},
10348
+ text: hint
10349
+ });
10350
+ }
10351
+ }
10352
+ }
10353
+ return out;
10354
+ }
10355
+ function renderDirectionHints(direction, opts) {
10356
+ return renderDirectionHintClauses(direction, opts).map((c) => c.text);
10357
+ }
10358
+
10359
+ // src/prompt-style-section.ts
10360
+ var STYLE_SECTION_HEADER = "[style]:";
10361
+ var STYLE_SECTION_GAP = "\n\n";
10362
+ var STYLE_SECTION_OPENING = `${STYLE_SECTION_GAP}${STYLE_SECTION_HEADER}
10363
+ `;
10364
+ function splitStyleSection(prompt) {
10365
+ const at = prompt.lastIndexOf(STYLE_SECTION_OPENING);
10366
+ if (at >= 0) {
10367
+ return { body: prompt.slice(0, at), section: prompt.slice(at + STYLE_SECTION_GAP.length) };
10368
+ }
10369
+ return prompt.startsWith(`${STYLE_SECTION_HEADER}
10370
+ `) ? { body: "", section: prompt } : { body: prompt, section: "" };
10371
+ }
10372
+ function insertBeforeStyleSection(prompt, lines) {
10373
+ if (lines.length === 0) return prompt;
10374
+ const block = lines.join("\n");
10375
+ const { body, section } = splitStyleSection(prompt);
10376
+ if (section.length === 0) return `${prompt}
10377
+ ${block}`;
10378
+ return `${body.length > 0 ? `${body}
10379
+ ${block}` : block}${STYLE_SECTION_GAP}${section}`;
10380
+ }
10381
+ function endsInsideStyleSection(prompt) {
10382
+ const at = prompt.lastIndexOf(STYLE_SECTION_GAP);
10383
+ const lastBlock = at >= 0 ? prompt.slice(at + STYLE_SECTION_GAP.length) : prompt;
10384
+ return lastBlock.startsWith(`${STYLE_SECTION_HEADER}
10385
+ `);
10386
+ }
10387
+ function styleSlotFor(row) {
10388
+ if (row.family === "motion") return "body";
10389
+ return row.styleGroup === "film" ? "film" : "scene";
10390
+ }
10391
+ function asBodyClauses(texts) {
10392
+ return texts.map((text) => ({ text, slot: "body" }));
10393
+ }
10394
+ function partitionStyleClauses(direction, opts) {
10395
+ return renderDirectionHintClauses(direction, opts).map((clause) => ({
10396
+ text: clause.text,
10397
+ slot: styleSlotFor(clause)
10398
+ }));
10399
+ }
10400
+ function styleSectionFromClauses(clauses) {
10401
+ const line = (slot) => clauses.filter((c) => c.slot === slot).map((c) => c.text).join(PROMPT_HINT_SEPARATOR);
10402
+ const lines = [line("film"), line("scene")].filter((l) => l.length > 0);
10403
+ return lines.length === 0 ? "" : `${STYLE_SECTION_HEADER}
10404
+ ${lines.join("\n")}`;
10405
+ }
10406
+ function renderStyleSection(direction, opts) {
10407
+ return styleSectionFromClauses(partitionStyleClauses(direction, opts));
10408
+ }
10409
+ function composeSectionedPrompt(userPrompt, clauses, structuredFragment) {
10410
+ const bodyHints = [
10411
+ ...clauses.filter((c) => c.slot === "body").map((c) => c.text),
10412
+ structuredFragment
10413
+ ].filter((p) => p.length > 0);
10414
+ const section = styleSectionFromClauses(clauses);
10415
+ if (section.length === 0) {
10416
+ return bodyHints.length === 0 ? userPrompt : joinPromptHints(userPrompt ?? "", bodyHints);
10417
+ }
10418
+ const body = bodyHints.length > 0 ? joinPromptHints(userPrompt ?? "", bodyHints) : (userPrompt ?? "").trim();
10419
+ return body.length > 0 ? `${body}${STYLE_SECTION_GAP}${section}` : section;
10420
+ }
10421
+ function sectionedClauseCosts(userPrompt, clauses, structuredFragment) {
10422
+ const lengthAt = (kept) => composeSectionedPrompt(userPrompt, clauses.slice(0, kept), structuredFragment)?.length ?? 0;
10423
+ const costs = [];
10424
+ let below = lengthAt(0);
10425
+ for (let i = 0; i < clauses.length; i++) {
10426
+ const at = lengthAt(i + 1);
10427
+ costs.push(at - below);
10428
+ below = at;
10429
+ }
10430
+ return costs;
10431
+ }
10220
10432
  function composeIdentityDescPart(subject, canonicalDesc, elementInjection) {
10221
10433
  const parts = [];
10222
10434
  const c = canonicalDesc?.trim();
@@ -10906,6 +11118,12 @@ function reconcileBodySegments(body, bodySegments) {
10906
11118
  }
10907
11119
  return [{ text: body, origin: "user" }];
10908
11120
  }
11121
+ function controlSuffixes(body, styleLine, avoidLine) {
11122
+ const separator = (text) => endsInsideStyleSection(text) ? STYLE_SECTION_GAP : "\n";
11123
+ const styleSuffix = styleLine ? `${separator(body)}${styleLine}` : "";
11124
+ const avoidSuffix = avoidLine ? `${separator(body + styleSuffix)}${avoidLine}` : "";
11125
+ return { styleSuffix, avoidSuffix };
11126
+ }
10909
11127
  function buildImagePrompt(config2) {
10910
11128
  return buildImagePromptInternal(config2);
10911
11129
  }
@@ -11182,9 +11400,7 @@ ${promptForNext}`;
11182
11400
  const lockBlock = allLockLines.length > 0 ? `${allLockLines.join("\n")}
11183
11401
 
11184
11402
  ` : "";
11185
- const trailingBlock = trailingLines.length > 0 ? `
11186
- ${trailingLines.join("\n")}` : "";
11187
- promptForNext = `${lockBlock}${promptForNext}${trailingBlock}`;
11403
+ promptForNext = `${lockBlock}${insertBeforeStyleSection(promptForNext, trailingLines)}`;
11188
11404
  }
11189
11405
  config2 = {
11190
11406
  ...config2,
@@ -11232,9 +11448,7 @@ ${trailingLines.join("\n")}` : "";
11232
11448
  ...locCanon.elementDirectives,
11233
11449
  ...objCanon.elementDirectives
11234
11450
  ];
11235
- const canonTrailingBlock = canonTrailingLines.length > 0 ? `
11236
- ${canonTrailingLines.join("\n")}` : "";
11237
- const composedScene = `${canonLockBlock}${scene}${canonTrailingBlock}`;
11451
+ const composedScene = `${canonLockBlock}${insertBeforeStyleSection(scene, canonTrailingLines)}`;
11238
11452
  prompt2 = config2.referenceLockSnippet ? `${config2.referenceLockSnippet}
11239
11453
  ${composedScene}` : composedScene;
11240
11454
  if (marks) marks.directivesPrefix = "";
@@ -11260,29 +11474,29 @@ ${directives}`;
11260
11474
  }
11261
11475
  }
11262
11476
  const styleText2 = style?.trim();
11263
- const styleSuffix2 = styleText2 ? `
11264
- Style: ${getStylePromptHint(styleText2) || styleText2}` : "";
11477
+ const styleLine2 = styleText2 ? `Style: ${getStylePromptHint(styleText2) || styleText2}` : "";
11265
11478
  const negPrompt2 = negativePrompt?.trim();
11266
11479
  let nativeNegativePrompt2;
11267
- let avoidSuffix2 = "";
11480
+ let avoidLine2 = "";
11268
11481
  if (negPrompt2) {
11269
11482
  if (shared.NATIVE_NEGATIVE_PROMPT_MODELS.has(provider)) {
11270
11483
  nativeNegativePrompt2 = negPrompt2.slice(0, shared.getMaxNegativePromptChars(provider));
11271
11484
  } else {
11272
- avoidSuffix2 = `
11273
- Avoid: ${negPrompt2}`;
11485
+ avoidLine2 = `Avoid: ${negPrompt2}`;
11274
11486
  }
11275
11487
  }
11276
- if (marks) {
11277
- marks.styleSuffix = styleSuffix2;
11278
- marks.avoidSuffix = avoidSuffix2;
11279
- }
11280
11488
  const maxLen2 = shared.getMaxImagePromptChars(provider);
11281
- const reserved2 = styleSuffix2.length + avoidSuffix2.length;
11489
+ const preCut2 = controlSuffixes(prompt2, styleLine2, avoidLine2);
11490
+ const reserved2 = preCut2.styleSuffix.length + preCut2.avoidSuffix.length;
11282
11491
  if (prompt2.length + reserved2 > maxLen2) {
11283
11492
  if (marks) marks.overflowChars += prompt2.length + reserved2 - maxLen2;
11284
11493
  prompt2 = prompt2.slice(0, Math.max(0, maxLen2 - reserved2 - 3)) + "...";
11285
11494
  }
11495
+ const { styleSuffix: styleSuffix2, avoidSuffix: avoidSuffix2 } = controlSuffixes(prompt2, styleLine2, avoidLine2);
11496
+ if (marks) {
11497
+ marks.styleSuffix = styleSuffix2;
11498
+ marks.avoidSuffix = avoidSuffix2;
11499
+ }
11286
11500
  if (marks) marks.bodyBeforeSuffixes = prompt2.slice(marks.directivesPrefix.length);
11287
11501
  prompt2 = prompt2 + styleSuffix2 + avoidSuffix2;
11288
11502
  if (prompt2.length > maxLen2) {
@@ -11334,35 +11548,37 @@ Avoid: ${negPrompt2}`;
11334
11548
  let prompt = config2.prompt;
11335
11549
  if (charDescs.length > 0) {
11336
11550
  const wrapperTemplate = resolveTemplate("generate-image-wrapper", userTemplates, flowTemplates);
11337
- prompt = applyTemplate(wrapperTemplate, {
11338
- userPrompt: prompt,
11551
+ const { body, section } = splitStyleSection(prompt);
11552
+ const wrapped = applyTemplate(wrapperTemplate, {
11553
+ userPrompt: body,
11339
11554
  assetDescriptions: charDescs.join(" ")
11340
11555
  });
11556
+ prompt = section.length > 0 ? `${wrapped}${STYLE_SECTION_GAP}${section}` : wrapped;
11341
11557
  }
11342
11558
  const styleText = style?.trim();
11343
- const styleSuffix = styleText ? `
11344
- Style: ${getStylePromptHint(styleText) || styleText}` : "";
11559
+ const styleLine = styleText ? `Style: ${getStylePromptHint(styleText) || styleText}` : "";
11345
11560
  const negPrompt = negativePrompt?.trim();
11346
11561
  let nativeNegativePrompt;
11347
- let avoidSuffix = "";
11562
+ let avoidLine = "";
11348
11563
  if (negPrompt) {
11349
11564
  if (shared.NATIVE_NEGATIVE_PROMPT_MODELS.has(provider)) {
11350
11565
  nativeNegativePrompt = negPrompt.slice(0, shared.getMaxNegativePromptChars(provider));
11351
11566
  } else {
11352
- avoidSuffix = `
11353
- Avoid: ${negPrompt}`;
11567
+ avoidLine = `Avoid: ${negPrompt}`;
11354
11568
  }
11355
11569
  }
11356
- if (marks) {
11357
- marks.styleSuffix = styleSuffix;
11358
- marks.avoidSuffix = avoidSuffix;
11359
- }
11360
11570
  const maxLen = shared.getMaxImagePromptChars(provider);
11361
- const reserved = styleSuffix.length + avoidSuffix.length;
11571
+ const preCut = controlSuffixes(prompt, styleLine, avoidLine);
11572
+ const reserved = preCut.styleSuffix.length + preCut.avoidSuffix.length;
11362
11573
  if (prompt.length + reserved > maxLen) {
11363
11574
  if (marks) marks.overflowChars += prompt.length + reserved - maxLen;
11364
11575
  prompt = prompt.slice(0, Math.max(0, maxLen - reserved - 3)) + "...";
11365
11576
  }
11577
+ const { styleSuffix, avoidSuffix } = controlSuffixes(prompt, styleLine, avoidLine);
11578
+ if (marks) {
11579
+ marks.styleSuffix = styleSuffix;
11580
+ marks.avoidSuffix = avoidSuffix;
11581
+ }
11366
11582
  if (marks) marks.bodyBeforeSuffixes = prompt;
11367
11583
  prompt = prompt + styleSuffix + avoidSuffix;
11368
11584
  if (prompt.length > maxLen) {
@@ -11589,7 +11805,12 @@ function capitalizeLineInitial(line) {
11589
11805
  return line.replace(/[a-zA-Z]/, (c) => c.toUpperCase());
11590
11806
  }
11591
11807
  function buildHybridScene(prompt, refs, finalIndexByUrl) {
11592
- return prompt.split("\n").map((line) => capitalizeLineInitial(expandImageRefTokensHybrid(line, refs, finalIndexByUrl))).join("\n");
11808
+ const lines = prompt.split("\n");
11809
+ const sectionAt = lines.indexOf(STYLE_SECTION_HEADER);
11810
+ return lines.map((line, i) => {
11811
+ const expanded = expandImageRefTokensHybrid(line, refs, finalIndexByUrl);
11812
+ return sectionAt >= 0 && i >= sectionAt ? expanded : capitalizeLineInitial(expanded);
11813
+ }).join("\n");
11593
11814
  }
11594
11815
  function buildIdentityDirectives(prompt, refs, meta3 = [], suppressedCanonicalLocationIds) {
11595
11816
  return collectIdentities(prompt, refs, meta3).map((id) => buildIdentityDirective(id, suppressedCanonicalLocationIds)).filter((s) => s.length > 0).join("\n");
@@ -11807,125 +12028,6 @@ function renderLens(l) {
11807
12028
  if (l.aperture) bits.push(`f/${l.aperture}`);
11808
12029
  return bits.length > 0 ? `Lens: ${bits.join(", ")}.` : "";
11809
12030
  }
11810
-
11811
- // src/direction-registry.ts
11812
- var perId = (full, compact) => (ids, mode) => {
11813
- const out = [];
11814
- for (const id of ids) {
11815
- const frag = mode === "compact" ? compact(id) : full(id);
11816
- if (frag.length > 0) out.push(frag);
11817
- }
11818
- return out;
11819
- };
11820
- var viaListBuilder = (build) => (ids, mode) => build(ids.length === 1 ? ids[0] : [...ids], mode).filter((s) => s.length > 0);
11821
- var viaStringBuilder = (build) => (ids, mode) => {
11822
- const s = build(ids.length === 1 ? ids[0] : [...ids], mode);
11823
- return s.length > 0 ? [s] : [];
11824
- };
11825
- var viaMood = (ids, mode) => buildMoodHints({ mood: ids.length === 1 ? ids[0] : [...ids] }, mode).filter(
11826
- (s) => s.length > 0
11827
- );
11828
- var framing = perId(getFramingPromptHint, getFramingTerm);
11829
- var lighting = perId(getLightingPromptHint, getLightingTerm);
11830
- var exposure = perId(getExposurePromptHint, getExposureTerm);
11831
- var temporal = perId(getTemporalPromptHint, getTemporalTerm);
11832
- var DIRECTION_FIELDS = [
11833
- { key: "cameraMotion", surface: "video", family: "motion", maxPicks: 1, render: perId(getCameraMotionPromptHint, getCameraMotionTerm) },
11834
- // Composition (the Framing catalog, one row per category).
11835
- { key: "shotSize", surface: "both", family: "look", maxPicks: 1, render: framing },
11836
- { key: "angle", surface: "both", family: "look", maxPicks: 1, render: framing },
11837
- { key: "coverage", surface: "both", family: "look", maxPicks: 1, render: framing },
11838
- { key: "composition", surface: "both", family: "look", maxPicks: 2, render: framing },
11839
- { key: "vantage", surface: "both", family: "look", maxPicks: 1, render: framing },
11840
- { key: "pose", surface: "both", family: "look", maxPicks: 1, render: perId(getPosePromptHint, getPoseTerm) },
11841
- { key: "compositionEffect", surface: "both", family: "look", maxPicks: 1, render: perId(getCompositionEffectPromptHint, getCompositionEffectTerm) },
11842
- // Camera.
11843
- { key: "cameraFormat", surface: "both", family: "look", maxPicks: 1, render: perId(getCameraFormatPromptHint, getCameraFormatTerm) },
11844
- { key: "lens", surface: "both", family: "look", maxPicks: 1, render: perId(getLensPromptHint, getLensTerm) },
11845
- // Exposure (stills only — a video's exposure rides its own temporal levers).
11846
- { key: "aperture", surface: "image", family: "look", maxPicks: 1, render: exposure },
11847
- { key: "shutterSpeed", surface: "image", family: "look", maxPicks: 1, render: exposure },
11848
- { key: "isoValue", surface: "image", family: "look", maxPicks: 1, render: exposure },
11849
- // Light (the Lighting catalog, one row per category).
11850
- { key: "timeOfDay", surface: "both", family: "look", maxPicks: 1, render: lighting },
11851
- { key: "lightingStyle", surface: "both", family: "look", maxPicks: 2, render: lighting },
11852
- { key: "lightingDirection", surface: "both", family: "look", maxPicks: 1, render: lighting },
11853
- { key: "lightingRatio", surface: "both", family: "look", maxPicks: 1, render: lighting },
11854
- { key: "colorTemperature", surface: "both", family: "look", maxPicks: 1, render: lighting },
11855
- { key: "colorLook", surface: "both", family: "look", maxPicks: 1, render: perId(getColorLookPromptHint, getColorLookTerm) },
11856
- { key: "atmosphere", surface: "both", family: "look", maxPicks: 2, render: viaListBuilder(buildAtmosphereHints) },
11857
- { key: "postProcess", surface: "image", family: "look", maxPicks: 2, render: viaListBuilder(buildPostProcessHints) },
11858
- // Style.
11859
- { key: "style", surface: "both", family: "look", maxPicks: 1, render: perId(getStylePromptHint, getStyleTerm) },
11860
- { key: "mood", surface: "both", family: "look", maxPicks: 2, render: viaMood },
11861
- { key: "aesthetic", surface: "both", family: "look", maxPicks: 2, render: viaStringBuilder(buildAestheticHints) },
11862
- { key: "photoGenre", surface: "image", family: "look", maxPicks: 1, render: perId(getPhotoGenrePromptHint, getPhotoGenreTerm) },
11863
- { key: "photographer", surface: "image", family: "look", maxPicks: 2, render: viaStringBuilder(buildPhotographerHints) },
11864
- { key: "renderQuality", surface: "image", family: "look", maxPicks: 1, render: perId(getRenderQualityPromptHint, getRenderQualityTerm) },
11865
- // Scene.
11866
- { key: "setting", surface: "both", family: "look", maxPicks: 1, render: perId(getSettingPromptHint, getSettingTerm) },
11867
- { key: "era", surface: "both", family: "look", maxPicks: 1, render: perId(getEraPromptHint, getEraTerm) },
11868
- { key: "backdrop", surface: "both", family: "look", maxPicks: 1, render: perId(getBackdropPromptHint, getBackdropTerm) },
11869
- // Motion & time.
11870
- { key: "actionFx", surface: "video", family: "motion", maxPicks: 2, render: viaListBuilder(buildActionFxHints) },
11871
- { key: "temporalSpeed", surface: "video", family: "motion", maxPicks: 1, render: temporal },
11872
- { key: "temporalFreeze", surface: "video", family: "motion", maxPicks: 1, render: temporal },
11873
- { key: "temporalDirection", surface: "video", family: "motion", maxPicks: 1, render: temporal },
11874
- { key: "temporalShutter", surface: "video", family: "motion", maxPicks: 1, render: temporal },
11875
- { key: "transition", surface: "video", family: "motion", maxPicks: 2, render: perId(getTransitionPromptHint, getTransitionTerm) },
11876
- { key: "loopSubject", surface: "video", family: "motion", maxPicks: 1, render: perId(getLoopSubjectPromptHint, getLoopSubjectTerm) },
11877
- // ── LEGACY BLOCK — see the table doc above. Placed LAST, in today's exact
11878
- // `composePromptText` order, so every pre-registry caller is byte-identical.
11879
- { key: "framingId", surface: "both", family: "look", maxPicks: 1, render: framing },
11880
- { key: "framingAngleId", surface: "both", family: "look", maxPicks: 1, render: framing },
11881
- { key: "lightingId", surface: "both", family: "look", maxPicks: 1, render: lighting },
11882
- { key: "lensId", surface: "both", family: "look", maxPicks: 1, render: perId(getLensPromptHint, getLensTerm) },
11883
- { key: "cameraFormatId", surface: "both", family: "look", maxPicks: 1, render: perId(getCameraFormatPromptHint, getCameraFormatTerm) }
11884
- ];
11885
- var DIRECTION_KEYS = DIRECTION_FIELDS.map((f) => f.key);
11886
- var IMAGE_HINT_MODE_DEFAULT = "full";
11887
- var VIDEO_HINT_MODE_DEFAULT = { look: "full", motion: "compact" };
11888
- function modeForFamily(mode, family) {
11889
- return typeof mode === "string" ? mode : family === "motion" ? mode.motion : mode.look;
11890
- }
11891
- var DIRECTION_ARRAY_CEILING = 8;
11892
- var DIRECTION_ID_MAX_CHARS = 100;
11893
- function normalizeDirectionIds(value, maxPicks) {
11894
- const ids = [];
11895
- if (typeof value === "string") {
11896
- if (value) ids.push(value);
11897
- } else if (Array.isArray(value)) {
11898
- for (const v of value) {
11899
- if (ids.length >= maxPicks) break;
11900
- if (typeof v === "string" && v && !ids.includes(v)) ids.push(v);
11901
- }
11902
- }
11903
- return ids.slice(0, maxPicks);
11904
- }
11905
- function directionFieldsForSurface(surface) {
11906
- return DIRECTION_FIELDS.filter((f) => f.surface === "both" || f.surface === surface);
11907
- }
11908
- function renderDirectionHints(direction, opts) {
11909
- if (!direction) return [];
11910
- const mode = opts.mode ?? "full";
11911
- const out = [];
11912
- const seen = /* @__PURE__ */ new Set();
11913
- for (const spec of DIRECTION_FIELDS) {
11914
- if (spec.surface !== "both" && spec.surface !== opts.surface) continue;
11915
- const ids = normalizeDirectionIds(
11916
- direction[spec.key],
11917
- spec.maxPicks
11918
- );
11919
- if (ids.length === 0) continue;
11920
- for (const hint of spec.render(ids, modeForFamily(mode, spec.family))) {
11921
- if (hint.length > 0 && !seen.has(hint)) {
11922
- seen.add(hint);
11923
- out.push(hint);
11924
- }
11925
- }
11926
- }
11927
- return out;
11928
- }
11929
12031
  var viaFragmentBuilder = (build) => (subject, mode) => {
11930
12032
  const clause = build(subject, mode).filter((s) => s.length > 0).join(", ");
11931
12033
  return clause.length > 0 ? [clause] : [];
@@ -12050,20 +12152,13 @@ function renderSubjectHints(subject, opts) {
12050
12152
  return out;
12051
12153
  }
12052
12154
 
12053
- // src/prompt-hint-join.ts
12054
- var PROMPT_HINT_SEPARATOR = ". ";
12055
- function joinPromptHints(userPrompt, hints) {
12056
- if (hints.length === 0) return userPrompt;
12057
- return [userPrompt.trim(), ...hints].filter((p) => p.length > 0).join(PROMPT_HINT_SEPARATOR);
12058
- }
12059
-
12060
12155
  // src/hint-shedding.ts
12061
- function keepableDirectionHints(hintClauses, kept, deficit) {
12156
+ function keepableDirectionHints(hintClauses, kept, deficit, clauseCosts) {
12062
12157
  let remaining = deficit;
12063
12158
  let next = kept;
12064
12159
  while (next > 0 && remaining > 0) {
12065
12160
  next -= 1;
12066
- remaining -= hintClauses[next].length + PROMPT_HINT_SEPARATOR.length;
12161
+ remaining -= clauseCosts?.[next] ?? hintClauses[next].length + PROMPT_HINT_SEPARATOR.length;
12067
12162
  }
12068
12163
  return next;
12069
12164
  }
@@ -12417,9 +12512,7 @@ function resolveVideoReferenceCore(args) {
12417
12512
  const lockBlock = lockLines.length > 0 ? `${lockLines.join("\n")}
12418
12513
 
12419
12514
  ` : "";
12420
- const trailingBlock = trailingLines.length > 0 ? `
12421
- ${trailingLines.join("\n")}` : "";
12422
- finalPrompt = `${lockBlock}${finalPrompt}${trailingBlock}`;
12515
+ finalPrompt = `${lockBlock}${insertBeforeStyleSection(finalPrompt, trailingLines)}`;
12423
12516
  } else {
12424
12517
  const allFallbackLines = [...fallbackDirectiveLines, ...extraDirectiveLines];
12425
12518
  if (allFallbackLines.length > 0) {
@@ -12656,24 +12749,26 @@ function assembleSunoInput(input) {
12656
12749
  function renderImageHintPieces(subject, direction, structured) {
12657
12750
  return {
12658
12751
  hintClauses: [
12659
- ...renderSubjectHints(subject, {
12660
- surface: "image",
12661
- mode: SUBJECT_IMAGE_HINT_MODE_DEFAULT
12662
- }),
12663
- ...renderDirectionHints(direction, {
12752
+ ...asBodyClauses(
12753
+ renderSubjectHints(subject, {
12754
+ surface: "image",
12755
+ mode: SUBJECT_IMAGE_HINT_MODE_DEFAULT
12756
+ })
12757
+ ),
12758
+ ...partitionStyleClauses(direction, {
12664
12759
  surface: "image",
12665
12760
  mode: IMAGE_HINT_MODE_DEFAULT
12666
12761
  })
12667
- ].filter((p) => p.length > 0),
12762
+ ].filter((c) => c.text.length > 0),
12668
12763
  structuredFragment: structured ? renderStructuredFields(structured) : ""
12669
12764
  };
12670
12765
  }
12671
12766
  function composePromptText(userPrompt, pieces, keptHintClauses) {
12672
- const hints = [
12673
- ...pieces.hintClauses.slice(0, keptHintClauses),
12767
+ return composeSectionedPrompt(
12768
+ userPrompt,
12769
+ pieces.hintClauses.slice(0, keptHintClauses),
12674
12770
  pieces.structuredFragment
12675
- ].filter((p) => p.length > 0);
12676
- return joinPromptHints(userPrompt, hints);
12771
+ );
12677
12772
  }
12678
12773
  function assembleImageInput(input) {
12679
12774
  const pieces = renderImageHintPieces(input.subject, input.direction, input.structured);
@@ -12703,9 +12798,17 @@ function assembleImageInput(input) {
12703
12798
  });
12704
12799
  let kept = pieces.hintClauses.length;
12705
12800
  let fitted = assembleWith(kept);
12706
- while (fitted.overflowChars > 0 && kept > 0) {
12707
- kept = keepableDirectionHints(pieces.hintClauses, kept, fitted.overflowChars);
12708
- fitted = assembleWith(kept);
12801
+ if (fitted.overflowChars > 0) {
12802
+ const costs = sectionedClauseCosts(
12803
+ input.userPrompt,
12804
+ pieces.hintClauses,
12805
+ pieces.structuredFragment
12806
+ );
12807
+ const texts = pieces.hintClauses.map((c) => c.text);
12808
+ while (fitted.overflowChars > 0 && kept > 0) {
12809
+ kept = keepableDirectionHints(texts, kept, fitted.overflowChars, costs);
12810
+ fitted = assembleWith(kept);
12811
+ }
12709
12812
  }
12710
12813
  const { overflowChars, ...result } = fitted;
12711
12814
  if (input.throwOnEmpty && !result.prompt.trim()) {
@@ -12719,31 +12822,30 @@ function assembleImageInput(input) {
12719
12822
  // src/assemble-video-input.ts
12720
12823
  function composeVideoPromptText(userPrompt, direction, structured, opts) {
12721
12824
  const hintClauses = [
12722
- ...renderSubjectHints(opts?.subject, {
12723
- surface: "video",
12724
- mode: opts?.subjectHintMode ?? SUBJECT_VIDEO_HINT_MODE_DEFAULT
12725
- }),
12726
- ...renderDirectionHints(direction, {
12825
+ ...asBodyClauses(
12826
+ renderSubjectHints(opts?.subject, {
12827
+ surface: "video",
12828
+ mode: opts?.subjectHintMode ?? SUBJECT_VIDEO_HINT_MODE_DEFAULT
12829
+ })
12830
+ ),
12831
+ ...partitionStyleClauses(direction, {
12727
12832
  surface: "video",
12728
12833
  mode: opts?.hintMode ?? VIDEO_HINT_MODE_DEFAULT
12729
12834
  })
12730
- ].filter((p) => p.length > 0);
12835
+ ].filter((c) => c.text.length > 0);
12731
12836
  const structuredFragment = structured ? renderStructuredFields(structured) : "";
12732
- const composeWith = (kept2) => {
12733
- const hints = [...hintClauses.slice(0, kept2), structuredFragment].filter(
12734
- (p) => p.length > 0
12735
- );
12736
- if (hints.length === 0) return userPrompt;
12737
- return joinPromptHints(userPrompt ?? "", hints);
12738
- };
12837
+ const composeWith = (kept2) => composeSectionedPrompt(userPrompt, hintClauses.slice(0, kept2), structuredFragment);
12739
12838
  const cap = opts?.cap;
12740
12839
  if (cap === void 0) return composeWith(hintClauses.length);
12741
12840
  const frame = opts?.frame ?? ((body2) => body2);
12742
12841
  let kept = hintClauses.length;
12743
12842
  let body = composeWith(kept);
12744
12843
  let framedLength = frame(body)?.length ?? 0;
12844
+ if (framedLength <= cap) return body;
12845
+ const costs = sectionedClauseCosts(userPrompt, hintClauses, structuredFragment);
12846
+ const texts = hintClauses.map((c) => c.text);
12745
12847
  while (framedLength > cap && kept > 0) {
12746
- kept = keepableDirectionHints(hintClauses, kept, framedLength - cap);
12848
+ kept = keepableDirectionHints(texts, kept, framedLength - cap, costs);
12747
12849
  body = composeWith(kept);
12748
12850
  framedLength = frame(body)?.length ?? 0;
12749
12851
  }
@@ -34121,6 +34223,7 @@ exports.EXPOSURE_SETTINGS = EXPOSURE_SETTINGS;
34121
34223
  exports.FACTORY_PRESETS = FACTORY_PRESETS;
34122
34224
  exports.FACTORY_SNIPPETS = FACTORY_SNIPPETS;
34123
34225
  exports.FILM_STILL_PREFIX = FILM_STILL_PREFIX;
34226
+ exports.FILM_STYLE_KEYS = FILM_STYLE_KEYS;
34124
34227
  exports.FRAMINGS = FRAMINGS;
34125
34228
  exports.FRAMING_CATEGORY_LABELS = FRAMING_CATEGORY_LABELS;
34126
34229
  exports.FRAMING_CATEGORY_ORDER = FRAMING_CATEGORY_ORDER;
@@ -34231,6 +34334,8 @@ exports.SNIPPET_MEDIA_VALUES = SNIPPET_MEDIA_VALUES;
34231
34334
  exports.STYLES = STYLES;
34232
34335
  exports.STYLE_IDS = STYLE_IDS;
34233
34336
  exports.STYLE_PRESETS = STYLE_PRESETS;
34337
+ exports.STYLE_SECTION_GAP = STYLE_SECTION_GAP;
34338
+ exports.STYLE_SECTION_HEADER = STYLE_SECTION_HEADER;
34234
34339
  exports.STYLINGS = STYLINGS;
34235
34340
  exports.STYLING_DIMENSION_LABELS = STYLING_DIMENSION_LABELS;
34236
34341
  exports.STYLING_DIMENSION_ORDER = STYLING_DIMENSION_ORDER;
@@ -34283,6 +34388,7 @@ exports.applyPickerJson = applyPickerJson;
34283
34388
  exports.applyPromptAffixes = applyPromptAffixes;
34284
34389
  exports.applyReferenceOrderToVideo = applyReferenceOrderToVideo;
34285
34390
  exports.applyTemplate = applyTemplate;
34391
+ exports.asBodyClauses = asBodyClauses;
34286
34392
  exports.assembleImageInput = assembleImageInput;
34287
34393
  exports.assembleSunoInput = assembleSunoInput;
34288
34394
  exports.buildActionFxHints = buildActionFxHints;
@@ -34356,6 +34462,7 @@ exports.composeCameraMotionTermFromConnections = composeCameraMotionTermFromConn
34356
34462
  exports.composeCharacterFxHintFromConnections = composeCharacterFxHintFromConnections;
34357
34463
  exports.composeNegative = composeNegative;
34358
34464
  exports.composePickerCatalogs = composePickerCatalogs;
34465
+ exports.composeSectionedPrompt = composeSectionedPrompt;
34359
34466
  exports.composeSoundHintFromConnections = composeSoundHintFromConnections;
34360
34467
  exports.composeTransitionHintFromConnections = composeTransitionHintFromConnections;
34361
34468
  exports.composeVideoPromptText = composeVideoPromptText;
@@ -34364,6 +34471,7 @@ exports.computeNodePrompt = computeNodePrompt;
34364
34471
  exports.computePackSidecarCoverage = computePackSidecarCoverage;
34365
34472
  exports.deriveTerm = deriveTerm;
34366
34473
  exports.directionFieldsForSurface = directionFieldsForSurface;
34474
+ exports.endsInsideStyleSection = endsInsideStyleSection;
34367
34475
  exports.expandImagePositionRefs = expandImagePositionRefs;
34368
34476
  exports.expandImageRefTokens = expandImageRefTokens;
34369
34477
  exports.filmStillPrefix = filmStillPrefix;
@@ -34550,6 +34658,7 @@ exports.getWardrobePromptHint = getWardrobePromptHint;
34550
34658
  exports.groupFactoryPresets = groupFactoryPresets;
34551
34659
  exports.hasUpstreamCharacter = hasUpstreamCharacter;
34552
34660
  exports.identityRefsSentence = identityRefsSentence;
34661
+ exports.insertBeforeStyleSection = insertBeforeStyleSection;
34553
34662
  exports.isAnalyzablePicker = isAnalyzablePicker;
34554
34663
  exports.isInstrumentalVocal = isInstrumentalVocal;
34555
34664
  exports.isSuspiciousDerivedTerm = isSuspiciousDerivedTerm;
@@ -34565,6 +34674,7 @@ exports.nodeHasInlinePrompt = nodeHasInlinePrompt;
34565
34674
  exports.nodeHasPromptField = nodeHasPromptField;
34566
34675
  exports.nodeSupportsPromptAffixes = nodeSupportsPromptAffixes;
34567
34676
  exports.normalizeSubjectFields = normalizeSubjectFields;
34677
+ exports.partitionStyleClauses = partitionStyleClauses;
34568
34678
  exports.personPacksVersion = personPacksVersion;
34569
34679
  exports.pickerFanoutTargets = pickerFanoutTargets;
34570
34680
  exports.projectAllCatalogs = projectAllCatalogs;
@@ -34579,8 +34689,10 @@ exports.readSubjectFields = readSubjectFields;
34579
34689
  exports.referenceRulesBlock = referenceRulesBlock;
34580
34690
  exports.registerCatalogPack = registerCatalogPack;
34581
34691
  exports.registerPersonPack = registerPersonPack;
34692
+ exports.renderDirectionHintClauses = renderDirectionHintClauses;
34582
34693
  exports.renderDirectionHints = renderDirectionHints;
34583
34694
  exports.renderStructuredFields = renderStructuredFields;
34695
+ exports.renderStyleSection = renderStyleSection;
34584
34696
  exports.renderSubjectHints = renderSubjectHints;
34585
34697
  exports.resetCatalogPacks = resetCatalogPacks;
34586
34698
  exports.resetPersonPacks = resetPersonPacks;
@@ -34596,6 +34708,10 @@ exports.resolveTemplate = resolveTemplate;
34596
34708
  exports.resolveTerm = resolveTerm;
34597
34709
  exports.resolveVeoI2vInputs = resolveVeoI2vInputs;
34598
34710
  exports.resolveVideoReferenceCore = resolveVideoReferenceCore;
34711
+ exports.sectionedClauseCosts = sectionedClauseCosts;
34712
+ exports.splitStyleSection = splitStyleSection;
34713
+ exports.styleSectionFromClauses = styleSectionFromClauses;
34714
+ exports.styleSlotFor = styleSlotFor;
34599
34715
  exports.subjectFieldsForSurface = subjectFieldsForSurface;
34600
34716
  exports.summarizePickerCatalogs = summarizePickerCatalogs;
34601
34717
  exports.toIdentityLockMode = toIdentityLockMode;