@nodaro/prompts 1.11.0 → 1.12.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
@@ -9241,7 +9241,7 @@ function objectOptions(arr, phrase) {
9241
9241
  label: e.label,
9242
9242
  description: e.description,
9243
9243
  category: e.subcategory,
9244
- promptHint: phrase(e.label.toLowerCase(), e.description),
9244
+ promptHint: phrase(e),
9245
9245
  // Object entities have no `promptHint` field of their own (it is
9246
9246
  // synthesized above), so the term cannot come from `resolveTerm`'s
9247
9247
  // empty-hint rule: an authored `term` wins, and otherwise the label IS the
@@ -9573,7 +9573,7 @@ var SINGLE_CATALOGS = [
9573
9573
  defaultValue: "dog-golden-retriever",
9574
9574
  categoryOrder: shared.ANIMAL_SUBCATEGORY_ORDER,
9575
9575
  categoryLabels: shared.ANIMAL_SUBCATEGORY_LABELS,
9576
- options: objectOptions(shared.ANIMALS, (label, description) => `featuring a ${label}, ${description}`)
9576
+ options: objectOptions(shared.ANIMALS, (e) => shared.getAnimalPromptHint(e.id))
9577
9577
  },
9578
9578
  {
9579
9579
  nodeType: "vehicle",
@@ -9584,7 +9584,7 @@ var SINGLE_CATALOGS = [
9584
9584
  defaultValue: "sedan",
9585
9585
  categoryOrder: shared.VEHICLE_SUBCATEGORY_ORDER,
9586
9586
  categoryLabels: shared.VEHICLE_SUBCATEGORY_LABELS,
9587
- options: objectOptions(shared.VEHICLES, (label, description) => `featuring a ${label}, ${description}`)
9587
+ options: objectOptions(shared.VEHICLES, (e) => `featuring a ${e.label.toLowerCase()}, ${e.description}`)
9588
9588
  },
9589
9589
  {
9590
9590
  nodeType: "weapon",
@@ -9595,7 +9595,7 @@ var SINGLE_CATALOGS = [
9595
9595
  defaultValue: "katana",
9596
9596
  categoryOrder: shared.WEAPON_SUBCATEGORY_ORDER,
9597
9597
  categoryLabels: shared.WEAPON_SUBCATEGORY_LABELS,
9598
- options: objectOptions(shared.WEAPONS, (label, description) => `with a ${label}, ${description}`)
9598
+ options: objectOptions(shared.WEAPONS, (e) => `with a ${e.label.toLowerCase()}, ${e.description}`)
9599
9599
  },
9600
9600
  {
9601
9601
  nodeType: "furniture",
@@ -9606,7 +9606,7 @@ var SINGLE_CATALOGS = [
9606
9606
  defaultValue: "sofa",
9607
9607
  categoryOrder: shared.FURNITURE_SUBCATEGORY_ORDER,
9608
9608
  categoryLabels: shared.FURNITURE_SUBCATEGORY_LABELS,
9609
- options: objectOptions(shared.FURNITURE, (label, description) => `including a ${label}, ${description}`)
9609
+ options: objectOptions(shared.FURNITURE, (e) => `including a ${e.label.toLowerCase()}, ${e.description}`)
9610
9610
  },
9611
9611
  {
9612
9612
  nodeType: "held-prop",
@@ -9974,13 +9974,16 @@ function resolveBaseHint(type, data, mode = "full") {
9974
9974
  return withCustomText(data, byMode(mode, getLoopSubjectPromptHint, getLoopSubjectTerm)(asStr(data.loopSubject)));
9975
9975
  case "material":
9976
9976
  return withCustomText(data, buildMaterialHints(data.material, mode));
9977
- case "animal": {
9978
- const animal = shared.getAnimal(asStr(data.animal));
9977
+ // Animal is the one Object-entity catalog whose phrasing has a single
9978
+ // owner: `@nodaro/shared`'s `getAnimalPromptHint` / `getAnimalTerm`, which
9979
+ // the picker-catalog funnel calls too. Both getters already return "" on a
9980
+ // miss, so the entry lookup and the `animal ? … : ""` guard are the
9981
+ // getters' job now, not this switch's.
9982
+ case "animal":
9979
9983
  return withCustomText(
9980
9984
  data,
9981
- animal ? byMode(mode, `featuring a ${animal.label.toLowerCase()}, ${animal.description}`, objectEntityTerm(animal)) : ""
9985
+ byMode(mode, shared.getAnimalPromptHint, shared.getAnimalTerm)(asStr(data.animal))
9982
9986
  );
9983
- }
9984
9987
  case "vehicle": {
9985
9988
  const vehicle = shared.getVehicle(asStr(data.vehicle));
9986
9989
  return withCustomText(
@@ -10284,6 +10287,11 @@ ${resolvedPrompt}`;
10284
10287
  }
10285
10288
  return { prompt: resolvedPrompt, additionalUrls, mentionedCharacterSlugs };
10286
10289
  }
10290
+ function spliceMentionPhrase(prompt, offset, tokenLength, phrase) {
10291
+ const before = prompt.slice(0, offset).replace(/(?<=\S)[^\S\r\n]{2,}$/, " ");
10292
+ const after = prompt.slice(offset + tokenLength).replace(/^[^\S\r\n]{2,}/, " ");
10293
+ return before + phrase + after;
10294
+ }
10287
10295
  function resolveCharacterMentionsHybrid(prompt, tokens, refs, existingUrls) {
10288
10296
  const bySlug = /* @__PURE__ */ new Map();
10289
10297
  const byVariant = /* @__PURE__ */ new Map();
@@ -10322,7 +10330,7 @@ function resolveCharacterMentionsHybrid(prompt, tokens, refs, existingUrls) {
10322
10330
  let resolvedPrompt = prompt;
10323
10331
  for (const m of [...matched].sort((a, b) => b.offset - a.offset)) {
10324
10332
  const phrase = shared.roleToPhrase(m.role, bindingFor(m.url));
10325
- resolvedPrompt = resolvedPrompt.slice(0, m.offset) + phrase + resolvedPrompt.slice(m.offset + m.token.length);
10333
+ resolvedPrompt = spliceMentionPhrase(resolvedPrompt, m.offset, m.token.length, phrase);
10326
10334
  }
10327
10335
  const lockLines = [];
10328
10336
  const elementDirectives = [];
@@ -10353,16 +10361,21 @@ function locationModeDirective(mode) {
10353
10361
  }
10354
10362
  }
10355
10363
  function locationModeToRole(mode) {
10356
- switch (mode) {
10357
- case "identical":
10358
- return "background";
10359
- case "style":
10360
- return "style";
10361
- case "layout":
10362
- return "layout";
10363
- default:
10364
- return shared.defaultRoleForSource("wired-location");
10365
- }
10364
+ return roleBearingLocationMode(mode) ?? shared.defaultRoleForSource("wired-location");
10365
+ }
10366
+ function roleBearingLocationMode(mode) {
10367
+ const m = mode?.trim();
10368
+ if (!m) return null;
10369
+ return shared.REFERENCE_ROLE_PRESETS["wired-location"].includes(m) ? m : null;
10370
+ }
10371
+ function resolveLocationRole(tokenRole, tokenUsageMode, ref) {
10372
+ const explicitRole = tokenRole?.trim();
10373
+ if (explicitRole) return shared.normalizeRoleSlug(explicitRole);
10374
+ const modeRole = roleBearingLocationMode(tokenUsageMode);
10375
+ if (modeRole) return modeRole;
10376
+ const nodeRole = ref.defaultRole?.trim();
10377
+ if (nodeRole) return shared.normalizeRoleSlug(nodeRole);
10378
+ return locationModeToRole(ref.defaultUsageMode ?? shared.DEFAULT_LOCATION_USAGE_MODE);
10366
10379
  }
10367
10380
  function resolveLocationMentions(prompt, tokens, refs) {
10368
10381
  const bySlug = /* @__PURE__ */ new Map();
@@ -10444,8 +10457,7 @@ function resolveLocationMentionsHybrid(prompt, tokens, refs, existingUrls) {
10444
10457
  mentionedLocationSlugs.add(t.locationSlug);
10445
10458
  refByUrl.set(match.url, match);
10446
10459
  if (t.lock !== void 0) lockOverrideByUrl.set(match.url, t.lock);
10447
- const mode = t.usageMode ?? match.defaultUsageMode ?? shared.DEFAULT_LOCATION_USAGE_MODE;
10448
- const role = t.role ? shared.normalizeRoleSlug(t.role) : locationModeToRole(mode);
10460
+ const role = resolveLocationRole(t.role, t.usageMode, match);
10449
10461
  matched.push({ token: t.token, offset: t.offset, url: match.url, role });
10450
10462
  }
10451
10463
  const slotByUrl = /* @__PURE__ */ new Map();
@@ -10459,7 +10471,7 @@ function resolveLocationMentionsHybrid(prompt, tokens, refs, existingUrls) {
10459
10471
  let resolvedPrompt = prompt;
10460
10472
  for (const m of [...matched].sort((a, b) => b.offset - a.offset)) {
10461
10473
  const phrase = shared.roleToPhrase(m.role, bindingFor(m.url));
10462
- resolvedPrompt = resolvedPrompt.slice(0, m.offset) + phrase + resolvedPrompt.slice(m.offset + m.token.length);
10474
+ resolvedPrompt = spliceMentionPhrase(resolvedPrompt, m.offset, m.token.length, phrase);
10463
10475
  }
10464
10476
  const lockLines = [];
10465
10477
  const elementDirectives = [];
@@ -10508,7 +10520,7 @@ function resolveImageMentionsHybrid(prompt, tokens, refs, existingUrls) {
10508
10520
  let resolvedPrompt = prompt;
10509
10521
  for (const m of [...matched].sort((a, b) => b.offset - a.offset)) {
10510
10522
  const phrase = shared.roleToPhrase(m.role, bindingFor(m.url));
10511
- resolvedPrompt = resolvedPrompt.slice(0, m.offset) + phrase + resolvedPrompt.slice(m.offset + m.token.length);
10523
+ resolvedPrompt = spliceMentionPhrase(resolvedPrompt, m.offset, m.token.length, phrase);
10512
10524
  }
10513
10525
  const lockLines = [];
10514
10526
  const elementDirectives = [];
@@ -10526,6 +10538,60 @@ function resolveImageMentionsHybrid(prompt, tokens, refs, existingUrls) {
10526
10538
  }
10527
10539
  return { prompt: resolvedPrompt, additionalUrls, lockLines, elementDirectives };
10528
10540
  }
10541
+ function resolveEntityMentionsHybrid(prompt, tokens, refs, existingUrls) {
10542
+ const bySlug = /* @__PURE__ */ new Map();
10543
+ for (const source of ["wired-creature", "wired-object"]) {
10544
+ for (const r of refs) {
10545
+ if (r.source !== source) continue;
10546
+ const slug = shared.entityMentionSlugForRef(r);
10547
+ if (!slug) continue;
10548
+ if (!bySlug.has(slug)) bySlug.set(slug, r);
10549
+ }
10550
+ }
10551
+ const additionalUrls = [];
10552
+ const mentionedUrls = /* @__PURE__ */ new Set();
10553
+ const refByUrl = /* @__PURE__ */ new Map();
10554
+ const lockOverrideByUrl = /* @__PURE__ */ new Map();
10555
+ const matched = [];
10556
+ for (const t of tokens) {
10557
+ const match = bySlug.get(t.entitySlug);
10558
+ if (!match || !match.url) continue;
10559
+ additionalUrls.push(match.url);
10560
+ mentionedUrls.add(match.url);
10561
+ refByUrl.set(match.url, match);
10562
+ if (t.lock !== void 0) lockOverrideByUrl.set(match.url, t.lock);
10563
+ const role = (t.role ?? "").trim() || shared.resolveDefaultRole(match.defaultRole, match.defaultUsageMode, match.source);
10564
+ matched.push({ token: t.token, offset: t.offset, url: match.url, role });
10565
+ }
10566
+ const slotByUrl = /* @__PURE__ */ new Map();
10567
+ for (const u of [...existingUrls, ...additionalUrls]) {
10568
+ if (!slotByUrl.has(u)) slotByUrl.set(u, slotByUrl.size + 1);
10569
+ }
10570
+ const bindingFor = (url2) => {
10571
+ const slot = slotByUrl.get(url2);
10572
+ return slot ? `reference image ${slotToLetter(slot)}` : "the reference image";
10573
+ };
10574
+ let resolvedPrompt = prompt;
10575
+ for (const m of [...matched].sort((a, b) => b.offset - a.offset)) {
10576
+ const phrase = shared.roleToPhrase(m.role, bindingFor(m.url));
10577
+ resolvedPrompt = spliceMentionPhrase(resolvedPrompt, m.offset, m.token.length, phrase);
10578
+ }
10579
+ const lockLines = [];
10580
+ const elementDirectives = [];
10581
+ const seenUrls = /* @__PURE__ */ new Set();
10582
+ for (const m of matched) {
10583
+ if (seenUrls.has(m.url)) continue;
10584
+ seenUrls.add(m.url);
10585
+ const ref = refByUrl.get(m.url);
10586
+ if (!ref) continue;
10587
+ const binding = bindingFor(m.url);
10588
+ const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10589
+ if (lock) lockLines.push(lock);
10590
+ const inject = ref.elementInjection?.trim();
10591
+ if (inject) elementDirectives.push(inject);
10592
+ }
10593
+ return { prompt: resolvedPrompt, additionalUrls, mentionedUrls, lockLines, elementDirectives };
10594
+ }
10529
10595
  function buildCanonicalFallback(refs, mentionedSlugs, startIndex) {
10530
10596
  const directiveLines = [];
10531
10597
  const urls = [];
@@ -10684,7 +10750,7 @@ function renderLocationCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, covere
10684
10750
  if (!slot) continue;
10685
10751
  seenUrls.add(r.url);
10686
10752
  const binding = `reference image ${slotToLetter(slot)}`;
10687
- phrases.push(shared.roleToPhrase(locationModeToRole(r.defaultUsageMode), binding));
10753
+ phrases.push(shared.roleToPhrase(resolveLocationRole(null, null, r), binding));
10688
10754
  const lock = buildIdentityLockLine(r, binding);
10689
10755
  if (lock) lockLines.push(lock);
10690
10756
  const inject = r.elementInjection?.trim();
@@ -10843,6 +10909,20 @@ function reconcileBodySegments(body, bodySegments) {
10843
10909
  function buildImagePrompt(config2) {
10844
10910
  return buildImagePromptInternal(config2);
10845
10911
  }
10912
+ function buildImagePromptWithOverflow(config2) {
10913
+ const marks = newAssemblyMarks();
10914
+ const result = buildImagePromptInternal(config2, marks);
10915
+ return { ...result, overflowChars: marks.overflowChars };
10916
+ }
10917
+ function newAssemblyMarks() {
10918
+ return {
10919
+ directivesPrefix: "",
10920
+ bodyBeforeSuffixes: "",
10921
+ styleSuffix: "",
10922
+ avoidSuffix: "",
10923
+ overflowChars: 0
10924
+ };
10925
+ }
10846
10926
  function buildImagePromptInternal(config2, marks) {
10847
10927
  let {
10848
10928
  provider,
@@ -10864,6 +10944,7 @@ function buildImagePromptInternal(config2, marks) {
10864
10944
  const suppressedCanonicalLocationIds = config2.suppressedCanonicalLocationIds ?? [];
10865
10945
  const isHybrid = config2.referenceFormat === "hybrid";
10866
10946
  let hybridBodyConverged = false;
10947
+ const mentionedEntityUrls = /* @__PURE__ */ new Set();
10867
10948
  if (config2.skipCharacterMentions) {
10868
10949
  config2 = {
10869
10950
  ...config2,
@@ -10910,7 +10991,9 @@ function buildImagePromptInternal(config2, marks) {
10910
10991
  );
10911
10992
  const knownImageSlugs = shared.knownImageSlugsFromRefs(connectedReferences);
10912
10993
  const hasImageMentionTokens = isHybrid && knownImageSlugs.length > 0 && shared.findImageMentionTokens(config2.prompt, knownImageSlugs).length > 0;
10913
- if (knownCharacterSlugs.length > 0 || hasExtraRefs || knownLocationSlugs.length > 0 || hasImageMentionTokens) {
10994
+ const knownEntitySlugs = shared.knownEntitySlugsFromRefs(connectedReferences);
10995
+ const hasEntityMentionTokens = isHybrid && knownEntitySlugs.length > 0 && shared.findEntityMentionTokens(config2.prompt, knownEntitySlugs).length > 0;
10996
+ if (knownCharacterSlugs.length > 0 || hasExtraRefs || knownLocationSlugs.length > 0 || hasImageMentionTokens || hasEntityMentionTokens) {
10914
10997
  const mentionTokens = knownCharacterSlugs.length > 0 ? shared.findCharacterMentionTokens(config2.prompt, knownCharacterSlugs) : [];
10915
10998
  let hybridLockLines = [];
10916
10999
  let hybridElementDirectives = [];
@@ -10982,6 +11065,25 @@ function buildImagePromptInternal(config2, marks) {
10982
11065
  if (hi.additionalUrls.length > 0) hybridBodyConverged = true;
10983
11066
  }
10984
11067
  }
11068
+ let hybridEntityLockLines = [];
11069
+ let hybridEntityElementDirectives = [];
11070
+ if (hasEntityMentionTokens) {
11071
+ const entityTokens = shared.findEntityMentionTokens(resolved.prompt, knownEntitySlugs);
11072
+ if (entityTokens.length > 0) {
11073
+ const he = resolveEntityMentionsHybrid(
11074
+ resolved.prompt,
11075
+ entityTokens,
11076
+ connectedReferences,
11077
+ [...referenceImageUrls || [], ...resolved.additionalUrls]
11078
+ );
11079
+ resolved.prompt = he.prompt;
11080
+ resolved.additionalUrls = [...resolved.additionalUrls, ...he.additionalUrls];
11081
+ hybridEntityLockLines = he.lockLines;
11082
+ hybridEntityElementDirectives = he.elementDirectives;
11083
+ for (const u of he.mentionedUrls) mentionedEntityUrls.add(u);
11084
+ if (he.additionalUrls.length > 0) hybridBodyConverged = true;
11085
+ }
11086
+ }
10985
11087
  const prefixDedupedLength = [
10986
11088
  ...referenceImageUrls || [],
10987
11089
  ...resolved.additionalUrls
@@ -11063,6 +11165,7 @@ ${promptForNext}`;
11063
11165
  ...hybridLockLines,
11064
11166
  ...hybridLocationLockLines,
11065
11167
  ...hybridImageLockLines,
11168
+ ...hybridEntityLockLines,
11066
11169
  ...canonical.lockLines,
11067
11170
  ...extrasRendered.lockLines
11068
11171
  ])];
@@ -11070,6 +11173,7 @@ ${promptForNext}`;
11070
11173
  ...hybridElementDirectives,
11071
11174
  ...hybridLocationElementDirectives,
11072
11175
  ...hybridImageElementDirectives,
11176
+ ...hybridEntityElementDirectives,
11073
11177
  ...canonical.phrases,
11074
11178
  ...canonical.elementDirectives,
11075
11179
  ...extrasRendered.bodyLines,
@@ -11116,7 +11220,8 @@ ${trailingLines.join("\n")}` : "";
11116
11220
  if (coveredUrl) tokenCoveredUrls.add(coveredUrl);
11117
11221
  }
11118
11222
  const locCanon = renderLocationCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, tokenCoveredUrls);
11119
- const objCanon = renderObjectCreatureCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, tokenCoveredUrls);
11223
+ const objCoveredUrls = mentionedEntityUrls.size > 0 ? /* @__PURE__ */ new Set([...tokenCoveredUrls, ...mentionedEntityUrls]) : tokenCoveredUrls;
11224
+ const objCanon = renderObjectCreatureCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, objCoveredUrls);
11120
11225
  const canonLockLines = [...locCanon.lockLines, ...objCanon.lockLines];
11121
11226
  const canonLockBlock = canonLockLines.length > 0 ? `${canonLockLines.join("\n")}
11122
11227
 
@@ -11175,11 +11280,13 @@ Avoid: ${negPrompt2}`;
11175
11280
  const maxLen2 = shared.getMaxImagePromptChars(provider);
11176
11281
  const reserved2 = styleSuffix2.length + avoidSuffix2.length;
11177
11282
  if (prompt2.length + reserved2 > maxLen2) {
11283
+ if (marks) marks.overflowChars += prompt2.length + reserved2 - maxLen2;
11178
11284
  prompt2 = prompt2.slice(0, Math.max(0, maxLen2 - reserved2 - 3)) + "...";
11179
11285
  }
11180
11286
  if (marks) marks.bodyBeforeSuffixes = prompt2.slice(marks.directivesPrefix.length);
11181
11287
  prompt2 = prompt2 + styleSuffix2 + avoidSuffix2;
11182
11288
  if (prompt2.length > maxLen2) {
11289
+ if (marks) marks.overflowChars += prompt2.length - maxLen2;
11183
11290
  prompt2 = prompt2.slice(0, maxLen2 - 3) + "...";
11184
11291
  }
11185
11292
  if (!isHybrid) {
@@ -11253,11 +11360,13 @@ Avoid: ${negPrompt}`;
11253
11360
  const maxLen = shared.getMaxImagePromptChars(provider);
11254
11361
  const reserved = styleSuffix.length + avoidSuffix.length;
11255
11362
  if (prompt.length + reserved > maxLen) {
11363
+ if (marks) marks.overflowChars += prompt.length + reserved - maxLen;
11256
11364
  prompt = prompt.slice(0, Math.max(0, maxLen - reserved - 3)) + "...";
11257
11365
  }
11258
11366
  if (marks) marks.bodyBeforeSuffixes = prompt;
11259
11367
  prompt = prompt + styleSuffix + avoidSuffix;
11260
11368
  if (prompt.length > maxLen) {
11369
+ if (marks) marks.overflowChars += prompt.length - maxLen;
11261
11370
  prompt = prompt.slice(0, maxLen - 3) + "...";
11262
11371
  }
11263
11372
  const allRefs = referenceImageUrls.length > 0 ? referenceImageUrls : ancestorRefs;
@@ -11267,12 +11376,7 @@ Avoid: ${negPrompt}`;
11267
11376
  return { prompt, nativeNegativePrompt, referenceImageUrls: refsToSend };
11268
11377
  }
11269
11378
  function buildImagePromptSegments(config2, bodySegments) {
11270
- const marks = {
11271
- directivesPrefix: "",
11272
- bodyBeforeSuffixes: "",
11273
- styleSuffix: "",
11274
- avoidSuffix: ""
11275
- };
11379
+ const marks = newAssemblyMarks();
11276
11380
  const result = buildImagePromptInternal(config2, marks);
11277
11381
  let segments = [
11278
11382
  ...marks.directivesPrefix ? [{ text: marks.directivesPrefix, origin: "mention" }] : [],
@@ -11462,13 +11566,24 @@ function hybridRolePhrase(label, letter) {
11462
11566
  return `the ${label} from reference image ${letter}`;
11463
11567
  }
11464
11568
  function expandImageRefTokensHybrid(prompt, refs, finalIndexByUrl) {
11465
- return prompt.replace(IMAGE_TOKEN_PATTERN, (match, num, label) => {
11466
- const n = parseInt(num, 10);
11569
+ const splices = [];
11570
+ for (const m of prompt.matchAll(IMAGE_TOKEN_PATTERN)) {
11571
+ const n = parseInt(m[1], 10);
11467
11572
  const ref = refs[n - 1];
11468
11573
  const finalIdx = ref?.url ? finalIndexByUrl.get(ref.url) : void 0;
11469
- if (!finalIdx) return match;
11470
- return hybridRolePhrase((label ?? "").trim(), slotToLetter(finalIdx));
11471
- });
11574
+ if (!finalIdx) continue;
11575
+ splices.push({
11576
+ offset: m.index ?? 0,
11577
+ length: m[0].length,
11578
+ phrase: hybridRolePhrase((m[2] ?? "").trim(), slotToLetter(finalIdx))
11579
+ });
11580
+ }
11581
+ let resolved = prompt;
11582
+ for (let i = splices.length - 1; i >= 0; i--) {
11583
+ const s = splices[i];
11584
+ resolved = spliceMentionPhrase(resolved, s.offset, s.length, s.phrase);
11585
+ }
11586
+ return resolved;
11472
11587
  }
11473
11588
  function capitalizeLineInitial(line) {
11474
11589
  return line.replace(/[a-zA-Z]/, (c) => c.toUpperCase());
@@ -11811,6 +11926,129 @@ function renderDirectionHints(direction, opts) {
11811
11926
  }
11812
11927
  return out;
11813
11928
  }
11929
+ var viaFragmentBuilder = (build) => (subject, mode) => {
11930
+ const clause = build(subject, mode).filter((s) => s.length > 0).join(", ");
11931
+ return clause.length > 0 ? [clause] : [];
11932
+ };
11933
+ var viaListBuilder2 = (build) => (ids, mode) => build(ids.length === 1 ? ids[0] : [...ids], mode).filter((s) => s.length > 0);
11934
+ var viaStringBuilder2 = (build) => (ids, mode) => {
11935
+ const s = build(ids.length === 1 ? ids[0] : [...ids], mode);
11936
+ return s.length > 0 ? [s] : [];
11937
+ };
11938
+ var perId2 = (full, compact) => (ids, mode) => {
11939
+ const out = [];
11940
+ for (const id of ids) {
11941
+ const frag = mode === "compact" ? compact(id) : full(id);
11942
+ if (frag.length > 0) out.push(frag);
11943
+ }
11944
+ return out;
11945
+ };
11946
+ var SUBJECT_FIELDS = [
11947
+ { key: "person", kind: "group", surface: "both", render: viaFragmentBuilder(buildPersonHints) },
11948
+ { key: "styling", kind: "group", surface: "both", render: viaFragmentBuilder(buildStylingHints) },
11949
+ { key: "heldProp", kind: "ids", surface: "both", maxPicks: 2, render: viaListBuilder2(buildHeldPropHints) },
11950
+ { key: "material", kind: "ids", surface: "both", maxPicks: 2, render: viaStringBuilder2(buildMaterialHints) },
11951
+ { key: "animal", kind: "ids", surface: "both", maxPicks: 1, render: perId2(shared.getAnimalPromptHint, shared.getAnimalTerm) }
11952
+ ];
11953
+ var SUBJECT_FOLD_KEYS = SUBJECT_FIELDS.map((f) => f.key);
11954
+ var IDS_ROWS = SUBJECT_FIELDS.filter((f) => f.kind === "ids");
11955
+ var SUBJECT_CUSTOM_AGE_KEY = "customAge";
11956
+ var CUSTOM_AGE_MIN = 0;
11957
+ var CUSTOM_AGE_MAX = 120;
11958
+ var SUBJECT_KEYS = [
11959
+ ...PERSON_DIMENSION_ORDER.map((d) => PERSON_FIELD_BY_DIMENSION[d]),
11960
+ SUBJECT_CUSTOM_AGE_KEY,
11961
+ ...STYLING_DIMENSION_ORDER.map((d) => STYLING_FIELD_BY_DIMENSION[d]),
11962
+ ...IDS_ROWS.map((f) => f.key)
11963
+ ];
11964
+ function getRegisteredSubjectKeys() {
11965
+ const fieldByDimension = getRegisteredPersonFieldByDimension();
11966
+ const personFields = [];
11967
+ for (const dimension of getRegisteredPersonDimensionOrder()) {
11968
+ const field = fieldByDimension[dimension];
11969
+ if (field) personFields.push(field);
11970
+ }
11971
+ return [
11972
+ ...personFields,
11973
+ SUBJECT_CUSTOM_AGE_KEY,
11974
+ ...STYLING_DIMENSION_ORDER.map((d) => STYLING_FIELD_BY_DIMENSION[d]),
11975
+ ...IDS_ROWS.map((f) => f.key)
11976
+ ];
11977
+ }
11978
+ var SUBJECT_IMAGE_HINT_MODE_DEFAULT = "full";
11979
+ var SUBJECT_VIDEO_HINT_MODE_DEFAULT = "compact";
11980
+ var SUBJECT_ARRAY_CEILING = DIRECTION_ARRAY_CEILING;
11981
+ var SUBJECT_ID_MAX_CHARS = DIRECTION_ID_MAX_CHARS;
11982
+ var MAX_SUBJECT_KEYS = 128;
11983
+ var SUBJECT_KEY_MAX_CHARS = 64;
11984
+ function normalizeIds(value, maxPicks) {
11985
+ const ids = [];
11986
+ if (typeof value === "string") {
11987
+ if (value) ids.push(value);
11988
+ } else if (Array.isArray(value)) {
11989
+ for (const v of value) {
11990
+ if (ids.length >= maxPicks) break;
11991
+ if (typeof v === "string" && v && !ids.includes(v)) ids.push(v);
11992
+ }
11993
+ }
11994
+ return ids.slice(0, maxPicks);
11995
+ }
11996
+ function limitByField() {
11997
+ const out = /* @__PURE__ */ new Map();
11998
+ const personFieldByDimension = getRegisteredPersonFieldByDimension();
11999
+ for (const dimension of getRegisteredPersonDimensionOrder()) {
12000
+ const field = personFieldByDimension[dimension];
12001
+ if (field) out.set(field, getPersonDimensionLimit(dimension));
12002
+ }
12003
+ for (const dimension of STYLING_DIMENSION_ORDER) {
12004
+ out.set(
12005
+ STYLING_FIELD_BY_DIMENSION[dimension],
12006
+ getStylingDimensionLimit(dimension)
12007
+ );
12008
+ }
12009
+ for (const row of IDS_ROWS) out.set(row.key, row.maxPicks);
12010
+ return out;
12011
+ }
12012
+ function normalizeSubjectFields(subject) {
12013
+ if (!subject || typeof subject !== "object" || Array.isArray(subject)) return void 0;
12014
+ const src = subject;
12015
+ const limits = limitByField();
12016
+ const out = {};
12017
+ const rawAge = src[SUBJECT_CUSTOM_AGE_KEY];
12018
+ if (typeof rawAge === "number" && Number.isFinite(rawAge)) {
12019
+ out[SUBJECT_CUSTOM_AGE_KEY] = Math.max(
12020
+ CUSTOM_AGE_MIN,
12021
+ Math.min(CUSTOM_AGE_MAX, Math.round(rawAge))
12022
+ );
12023
+ }
12024
+ for (const [field, limit] of limits) {
12025
+ const ids = normalizeIds(src[field], limit);
12026
+ if (ids.length === 0) continue;
12027
+ out[field] = ids.length === 1 ? ids[0] : ids;
12028
+ }
12029
+ return Object.keys(out).length > 0 ? out : void 0;
12030
+ }
12031
+ function subjectFieldsForSurface(surface) {
12032
+ return SUBJECT_FIELDS.filter((f) => f.surface === "both" || f.surface === surface);
12033
+ }
12034
+ function renderSubjectHints(subject, opts) {
12035
+ const bag = normalizeSubjectFields(subject);
12036
+ if (!bag) return [];
12037
+ const mode = opts.mode ?? "full";
12038
+ const out = [];
12039
+ const seen = /* @__PURE__ */ new Set();
12040
+ for (const spec of SUBJECT_FIELDS) {
12041
+ if (spec.surface !== "both" && spec.surface !== opts.surface) continue;
12042
+ const hints = spec.kind === "group" ? spec.render(bag, mode) : spec.render(normalizeIds(bag[spec.key], spec.maxPicks), mode);
12043
+ for (const hint of hints) {
12044
+ if (hint.length > 0 && !seen.has(hint)) {
12045
+ seen.add(hint);
12046
+ out.push(hint);
12047
+ }
12048
+ }
12049
+ }
12050
+ return out;
12051
+ }
11814
12052
 
11815
12053
  // src/prompt-hint-join.ts
11816
12054
  var PROMPT_HINT_SEPARATOR = ". ";
@@ -11819,6 +12057,17 @@ function joinPromptHints(userPrompt, hints) {
11819
12057
  return [userPrompt.trim(), ...hints].filter((p) => p.length > 0).join(PROMPT_HINT_SEPARATOR);
11820
12058
  }
11821
12059
 
12060
+ // src/hint-shedding.ts
12061
+ function keepableDirectionHints(hintClauses, kept, deficit) {
12062
+ let remaining = deficit;
12063
+ let next = kept;
12064
+ while (next > 0 && remaining > 0) {
12065
+ next -= 1;
12066
+ remaining -= hintClauses[next].length + PROMPT_HINT_SEPARATOR.length;
12067
+ }
12068
+ return next;
12069
+ }
12070
+
11822
12071
  // src/ref-binding.ts
11823
12072
  function identityRefsSentence(firstOrdinal, lastOrdinal) {
11824
12073
  return firstOrdinal === lastOrdinal ? `${REF_BINDING.ordinal(firstOrdinal)} is an identity reference for this shot's subjects \u2014 match its subject's exact appearance; it is not a frame.` : `${REF_BINDING.ordinal(firstOrdinal)} through ${REF_BINDING.ordinal(lastOrdinal)} are identity references for this shot's subjects \u2014 match each subject's exact appearance; they are not frames.`;
@@ -12404,17 +12653,32 @@ function assembleSunoInput(input) {
12404
12653
  }
12405
12654
 
12406
12655
  // src/assemble-image-input.ts
12407
- function composePromptText(userPrompt, direction, structured) {
12656
+ function renderImageHintPieces(subject, direction, structured) {
12657
+ return {
12658
+ hintClauses: [
12659
+ ...renderSubjectHints(subject, {
12660
+ surface: "image",
12661
+ mode: SUBJECT_IMAGE_HINT_MODE_DEFAULT
12662
+ }),
12663
+ ...renderDirectionHints(direction, {
12664
+ surface: "image",
12665
+ mode: IMAGE_HINT_MODE_DEFAULT
12666
+ })
12667
+ ].filter((p) => p.length > 0),
12668
+ structuredFragment: structured ? renderStructuredFields(structured) : ""
12669
+ };
12670
+ }
12671
+ function composePromptText(userPrompt, pieces, keptHintClauses) {
12408
12672
  const hints = [
12409
- ...renderDirectionHints(direction, { surface: "image", mode: IMAGE_HINT_MODE_DEFAULT }),
12410
- structured ? renderStructuredFields(structured) : ""
12673
+ ...pieces.hintClauses.slice(0, keptHintClauses),
12674
+ pieces.structuredFragment
12411
12675
  ].filter((p) => p.length > 0);
12412
12676
  return joinPromptHints(userPrompt, hints);
12413
12677
  }
12414
12678
  function assembleImageInput(input) {
12415
- const prompt = composePromptText(input.userPrompt, input.direction, input.structured);
12416
- const result = buildImagePrompt({
12417
- prompt,
12679
+ const pieces = renderImageHintPieces(input.subject, input.direction, input.structured);
12680
+ const assembleWith = (keptHintClauses) => buildImagePromptWithOverflow({
12681
+ prompt: composePromptText(input.userPrompt, pieces, keptHintClauses),
12418
12682
  provider: input.provider,
12419
12683
  ...input.connectedReferences !== void 0 ? { connectedReferences: input.connectedReferences } : {},
12420
12684
  // Manual uploads / direct refs ride the builder's reference-URL channel so
@@ -12437,6 +12701,13 @@ function assembleImageInput(input) {
12437
12701
  ...input.sourceNodeIdById !== void 0 ? { sourceNodeIdById: input.sourceNodeIdById } : {},
12438
12702
  ...input.skipCharacterMentions !== void 0 ? { skipCharacterMentions: input.skipCharacterMentions } : {}
12439
12703
  });
12704
+ let kept = pieces.hintClauses.length;
12705
+ let fitted = assembleWith(kept);
12706
+ while (fitted.overflowChars > 0 && kept > 0) {
12707
+ kept = keepableDirectionHints(pieces.hintClauses, kept, fitted.overflowChars);
12708
+ fitted = assembleWith(kept);
12709
+ }
12710
+ const { overflowChars, ...result } = fitted;
12440
12711
  if (input.throwOnEmpty && !result.prompt.trim()) {
12441
12712
  throw new Error(
12442
12713
  "No prompt \u2014 type one, bind a character, or pick a cinematography direction"
@@ -12447,15 +12718,36 @@ function assembleImageInput(input) {
12447
12718
 
12448
12719
  // src/assemble-video-input.ts
12449
12720
  function composeVideoPromptText(userPrompt, direction, structured, opts) {
12450
- const hints = [
12721
+ const hintClauses = [
12722
+ ...renderSubjectHints(opts?.subject, {
12723
+ surface: "video",
12724
+ mode: opts?.subjectHintMode ?? SUBJECT_VIDEO_HINT_MODE_DEFAULT
12725
+ }),
12451
12726
  ...renderDirectionHints(direction, {
12452
12727
  surface: "video",
12453
12728
  mode: opts?.hintMode ?? VIDEO_HINT_MODE_DEFAULT
12454
- }),
12455
- structured ? renderStructuredFields(structured) : ""
12729
+ })
12456
12730
  ].filter((p) => p.length > 0);
12457
- if (hints.length === 0) return userPrompt;
12458
- return joinPromptHints(userPrompt ?? "", hints);
12731
+ 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
+ };
12739
+ const cap = opts?.cap;
12740
+ if (cap === void 0) return composeWith(hintClauses.length);
12741
+ const frame = opts?.frame ?? ((body2) => body2);
12742
+ let kept = hintClauses.length;
12743
+ let body = composeWith(kept);
12744
+ let framedLength = frame(body)?.length ?? 0;
12745
+ while (framedLength > cap && kept > 0) {
12746
+ kept = keepableDirectionHints(hintClauses, kept, framedLength - cap);
12747
+ body = composeWith(kept);
12748
+ framedLength = frame(body)?.length ?? 0;
12749
+ }
12750
+ return body;
12459
12751
  }
12460
12752
 
12461
12753
  // src/read-node-direction.ts
@@ -12480,6 +12772,31 @@ function readDirectionFields(value) {
12480
12772
  }
12481
12773
  return Object.keys(out).length > 0 ? out : void 0;
12482
12774
  }
12775
+ function readSubjectFields(value) {
12776
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
12777
+ const src = value;
12778
+ const out = {};
12779
+ for (const key of getRegisteredSubjectKeys()) {
12780
+ const v = src[key];
12781
+ if (key === SUBJECT_CUSTOM_AGE_KEY) {
12782
+ if (typeof v === "number" && Number.isFinite(v)) out[key] = v;
12783
+ continue;
12784
+ }
12785
+ if (typeof v === "string") {
12786
+ if (v.length > 0 && v.length <= SUBJECT_ID_MAX_CHARS) out[key] = v;
12787
+ } else if (Array.isArray(v)) {
12788
+ const kept = [];
12789
+ for (const x of v) {
12790
+ if (kept.length >= SUBJECT_ARRAY_CEILING) break;
12791
+ if (typeof x === "string" && x.length > 0 && x.length <= SUBJECT_ID_MAX_CHARS) {
12792
+ kept.push(x);
12793
+ }
12794
+ }
12795
+ if (kept.length > 0) out[key] = kept;
12796
+ }
12797
+ }
12798
+ return Object.keys(out).length > 0 ? out : void 0;
12799
+ }
12483
12800
  var GENDERS = ["man", "woman", "child", "non-binary"];
12484
12801
  var MAX_STRUCTURED_VALUE_CHARS = 200;
12485
12802
  var PERSON_FIELDS2 = {
@@ -33839,6 +34156,7 @@ exports.MATERIAL_IDS = MATERIAL_IDS;
33839
34156
  exports.MAX_SELECTED_BY_DIMENSION = MAX_SELECTED_BY_DIMENSION;
33840
34157
  exports.MAX_SELECTED_BY_FRAMING_CATEGORY = MAX_SELECTED_BY_FRAMING_CATEGORY;
33841
34158
  exports.MAX_SELECTED_BY_STYLING_DIMENSION = MAX_SELECTED_BY_STYLING_DIMENSION;
34159
+ exports.MAX_SUBJECT_KEYS = MAX_SUBJECT_KEYS;
33842
34160
  exports.MOODS = MOODS;
33843
34161
  exports.MOOD_CATEGORY_LABELS = MOOD_CATEGORY_LABELS;
33844
34162
  exports.MOOD_CATEGORY_ORDER = MOOD_CATEGORY_ORDER;
@@ -33918,6 +34236,15 @@ exports.STYLING_DIMENSION_LABELS = STYLING_DIMENSION_LABELS;
33918
34236
  exports.STYLING_DIMENSION_ORDER = STYLING_DIMENSION_ORDER;
33919
34237
  exports.STYLING_FIELD_BY_DIMENSION = STYLING_FIELD_BY_DIMENSION;
33920
34238
  exports.STYLING_IDS = STYLING_IDS;
34239
+ exports.SUBJECT_ARRAY_CEILING = SUBJECT_ARRAY_CEILING;
34240
+ exports.SUBJECT_CUSTOM_AGE_KEY = SUBJECT_CUSTOM_AGE_KEY;
34241
+ exports.SUBJECT_FIELDS = SUBJECT_FIELDS;
34242
+ exports.SUBJECT_FOLD_KEYS = SUBJECT_FOLD_KEYS;
34243
+ exports.SUBJECT_ID_MAX_CHARS = SUBJECT_ID_MAX_CHARS;
34244
+ exports.SUBJECT_IMAGE_HINT_MODE_DEFAULT = SUBJECT_IMAGE_HINT_MODE_DEFAULT;
34245
+ exports.SUBJECT_KEYS = SUBJECT_KEYS;
34246
+ exports.SUBJECT_KEY_MAX_CHARS = SUBJECT_KEY_MAX_CHARS;
34247
+ exports.SUBJECT_VIDEO_HINT_MODE_DEFAULT = SUBJECT_VIDEO_HINT_MODE_DEFAULT;
33921
34248
  exports.TEMPORALS = TEMPORALS;
33922
34249
  exports.TEMPORAL_CATEGORY_LABELS = TEMPORAL_CATEGORY_LABELS;
33923
34250
  exports.TEMPORAL_CATEGORY_ORDER = TEMPORAL_CATEGORY_ORDER;
@@ -33978,6 +34305,7 @@ exports.buildIdentityDirectives = buildIdentityDirectives;
33978
34305
  exports.buildIdentityLockLine = buildIdentityLockLine;
33979
34306
  exports.buildImagePrompt = buildImagePrompt;
33980
34307
  exports.buildImagePromptSegments = buildImagePromptSegments;
34308
+ exports.buildImagePromptWithOverflow = buildImagePromptWithOverflow;
33981
34309
  exports.buildInstrumentationHints = buildInstrumentationHints;
33982
34310
  exports.buildInstrumentationTerms = buildInstrumentationTerms;
33983
34311
  exports.buildLightingHints = buildLightingHints;
@@ -34168,6 +34496,7 @@ exports.getRegisteredPersonDimensionLabels = getRegisteredPersonDimensionLabels;
34168
34496
  exports.getRegisteredPersonDimensionOrder = getRegisteredPersonDimensionOrder;
34169
34497
  exports.getRegisteredPersonFieldByDimension = getRegisteredPersonFieldByDimension;
34170
34498
  exports.getRegisteredPickerCatalogs = getRegisteredPickerCatalogs;
34499
+ exports.getRegisteredSubjectKeys = getRegisteredSubjectKeys;
34171
34500
  exports.getRenderQuality = getRenderQuality;
34172
34501
  exports.getRenderQualityLabel = getRenderQualityLabel;
34173
34502
  exports.getRenderQualityPromptHint = getRenderQualityPromptHint;
@@ -34228,12 +34557,14 @@ exports.isVantageFraming = isVantageFraming;
34228
34557
  exports.isWizardSupported = isWizardSupported;
34229
34558
  exports.joinPromptHints = joinPromptHints;
34230
34559
  exports.joinPromptParts = joinPromptParts;
34560
+ exports.keepableDirectionHints = keepableDirectionHints;
34231
34561
  exports.listPickerCatalogs = listPickerCatalogs;
34232
34562
  exports.migratePersonValue = migratePersonValue;
34233
34563
  exports.modeForFamily = modeForFamily;
34234
34564
  exports.nodeHasInlinePrompt = nodeHasInlinePrompt;
34235
34565
  exports.nodeHasPromptField = nodeHasPromptField;
34236
34566
  exports.nodeSupportsPromptAffixes = nodeSupportsPromptAffixes;
34567
+ exports.normalizeSubjectFields = normalizeSubjectFields;
34237
34568
  exports.personPacksVersion = personPacksVersion;
34238
34569
  exports.pickerFanoutTargets = pickerFanoutTargets;
34239
34570
  exports.projectAllCatalogs = projectAllCatalogs;
@@ -34244,11 +34575,13 @@ exports.promptFieldCarriesAffixes = promptFieldCarriesAffixes;
34244
34575
  exports.promptPartSeparator = promptPartSeparator;
34245
34576
  exports.readDirectionFields = readDirectionFields;
34246
34577
  exports.readStructuredFields = readStructuredFields;
34578
+ exports.readSubjectFields = readSubjectFields;
34247
34579
  exports.referenceRulesBlock = referenceRulesBlock;
34248
34580
  exports.registerCatalogPack = registerCatalogPack;
34249
34581
  exports.registerPersonPack = registerPersonPack;
34250
34582
  exports.renderDirectionHints = renderDirectionHints;
34251
34583
  exports.renderStructuredFields = renderStructuredFields;
34584
+ exports.renderSubjectHints = renderSubjectHints;
34252
34585
  exports.resetCatalogPacks = resetCatalogPacks;
34253
34586
  exports.resetPersonPacks = resetPersonPacks;
34254
34587
  exports.resolveBrandInput = resolveBrandInput;
@@ -34263,6 +34596,7 @@ exports.resolveTemplate = resolveTemplate;
34263
34596
  exports.resolveTerm = resolveTerm;
34264
34597
  exports.resolveVeoI2vInputs = resolveVeoI2vInputs;
34265
34598
  exports.resolveVideoReferenceCore = resolveVideoReferenceCore;
34599
+ exports.subjectFieldsForSurface = subjectFieldsForSurface;
34266
34600
  exports.summarizePickerCatalogs = summarizePickerCatalogs;
34267
34601
  exports.toIdentityLockMode = toIdentityLockMode;
34268
34602
  exports.truncateForField = truncateForField;