@nodaro/prompts 1.15.0 → 1.17.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
@@ -2719,9 +2719,9 @@ var PEOPLE = [
2719
2719
  { id: "bust-small", label: "Small", dimension: "bust", description: "Small bust", promptHint: "small bust", term: "small bust", adultOnly: true },
2720
2720
  { id: "bust-average", label: "Average", dimension: "bust", description: "Average bust", promptHint: "" },
2721
2721
  { id: "bust-full", label: "Full", dimension: "bust", description: "Full bust", promptHint: "full bust", term: "full bust", adultOnly: true },
2722
- { id: "bust-very-full", label: "Very Full", dimension: "bust", description: "Very full bust", promptHint: "very full bust", term: "very full bust", adultOnly: true },
2722
+ { id: "bust-very-full", label: "Very Full", dimension: "bust", description: "Very full bust", promptHint: "a fuller bust", term: "fuller bust", adultOnly: true },
2723
2723
  // ----- Waist -----
2724
- { id: "waist-defined", label: "Defined", dimension: "waist", description: "Defined, cinched waist", promptHint: "defined waist", term: "defined waist", adultOnly: true },
2724
+ { id: "waist-defined", label: "Defined", dimension: "waist", description: "Defined, cinched waist", promptHint: "a defined waistline", term: "defined waistline", adultOnly: true },
2725
2725
  { id: "waist-average", label: "Average", dimension: "waist", description: "Average waist", promptHint: "" },
2726
2726
  { id: "waist-straight", label: "Straight", dimension: "waist", description: "Straight, undefined waist", promptHint: "straight waistline", term: "straight waistline", adultOnly: true },
2727
2727
  // ----- Hips -----
@@ -2729,7 +2729,7 @@ var PEOPLE = [
2729
2729
  { id: "hips-balanced", label: "Balanced", dimension: "hips", description: "Balanced hips", promptHint: "" },
2730
2730
  { id: "hips-wide", label: "Wide", dimension: "hips", description: "Wide hips", promptHint: "wide hips", term: "wide hips", adultOnly: true },
2731
2731
  // ----- Silhouette (overall body shape; optional, no neutral) -----
2732
- { id: "silhouette-hourglass", label: "Hourglass", dimension: "silhouette", description: "Balanced bust + hips, defined waist", promptHint: "hourglass silhouette", term: "hourglass silhouette", adultOnly: true },
2732
+ { id: "silhouette-hourglass", label: "Hourglass", dimension: "silhouette", description: "Balanced bust + hips, defined waist", promptHint: "an hourglass figure", term: "hourglass figure", adultOnly: true },
2733
2733
  { id: "silhouette-rectangular", label: "Rectangular", dimension: "silhouette", description: "Straight up-and-down silhouette", promptHint: "rectangular silhouette", term: "rectangular silhouette" },
2734
2734
  { id: "silhouette-pear", label: "Pear", dimension: "silhouette", description: "Hips wider than shoulders", promptHint: "pear-shaped silhouette", term: "pear-shaped silhouette", adultOnly: true },
2735
2735
  { id: "silhouette-inverted", label: "Inverted Triangle", dimension: "silhouette", description: "Broad shoulders, narrow hips", promptHint: "inverted-triangle silhouette", term: "inverted-triangle silhouette" },
@@ -2818,11 +2818,11 @@ var PEOPLE = [
2818
2818
  { id: "lips-small", label: "Small", dimension: "lip-shape", description: "Petite mouth", promptHint: "a small petite mouth", term: "small petite mouth" },
2819
2819
  // -------------------- Lip State (what the lips are doing / wearing) --------------------
2820
2820
  { id: "lip-state-chapped", label: "Chapped", dimension: "lip-state", description: "Cracked, dry, weather-worn lips", promptHint: "with chapped, cracked, weather-worn dry lips", term: "chapped dry lips" },
2821
- { id: "lip-state-glossy", label: "Glossy", dimension: "lip-state", description: "High-shine, wet-look lips", promptHint: "with high-shine glossy wet-look lips", term: "glossy wet-look lips", adultOnly: true },
2821
+ { id: "lip-state-glossy", label: "Glossy", dimension: "lip-state", description: "High-shine, wet-look lips", promptHint: "with a glossy lip finish", term: "glossy lips", adultOnly: true },
2822
2822
  { id: "lip-state-bare", label: "Bare", dimension: "lip-state", description: "Natural, untreated, no makeup", promptHint: "with bare, natural, untreated lips", term: "bare natural lips" },
2823
2823
  { id: "lip-state-bold-red", label: "Bold Red", dimension: "lip-state", description: "Saturated red lipstick statement", promptHint: "with a bold, saturated red lipstick statement", term: "bold red lipstick" },
2824
- { id: "lip-state-bitten", label: "Bitten", dimension: "lip-state", description: "Slight playful lip-bite, lower lip caught", promptHint: "playfully biting the lower lip", term: "biting the lower lip", adultOnly: true },
2825
- { id: "lip-state-parted", label: "Parted", dimension: "lip-state", description: "Lips slightly parted, breath of air", promptHint: "with lips slightly parted, taking a soft breath", term: "lips slightly parted", adultOnly: true },
2824
+ { id: "lip-state-bitten", label: "Bitten", dimension: "lip-state", description: "Slight playful lip-bite, lower lip caught", promptHint: "lightly biting the lower lip", term: "biting lower lip", adultOnly: true },
2825
+ { id: "lip-state-parted", label: "Parted", dimension: "lip-state", description: "Lips slightly parted, breath of air", promptHint: "with lips relaxed and slightly open, as if mid-sentence", term: "lips slightly open", adultOnly: true },
2826
2826
  { id: "lip-state-pursed", label: "Pursed", dimension: "lip-state", description: "Lips pressed and pushed forward", promptHint: "with lips pursed, pressed and pushed forward", term: "pursed lips" },
2827
2827
  { id: "lip-state-bold-black", label: "Bold Black", dimension: "lip-state", description: "Saturated black lipstick statement (goth / avant-garde)", promptHint: "with a bold, saturated black lipstick statement, goth / avant-garde", term: "bold black lipstick" },
2828
2828
  { id: "lip-state-burgundy", label: "Burgundy", dimension: "lip-state", description: "Deep wine-red lipstick", promptHint: "with deep wine-red burgundy lipstick", term: "burgundy lipstick" },
@@ -2935,9 +2935,9 @@ var PEOPLE = [
2935
2935
  { id: "eyes-black", label: "Black", dimension: "eye-color", description: "Truly black iris (deeper than brown)", promptHint: "jet-black eyes with a truly black iris, deeper than brown", term: "jet-black eyes" },
2936
2936
  // -------------------- Eye State (what the eyes are doing / where they look) --------------------
2937
2937
  { id: "eye-state-closed", label: "Closed", dimension: "eye-state", description: "Eyes fully closed, peaceful", promptHint: "with eyes fully closed in a peaceful expression", term: "eyes fully closed" },
2938
- { id: "eye-state-half-lidded", label: "Half-lidded", dimension: "eye-state", description: "Heavy-lidded sleepy gaze", promptHint: "with heavy half-lidded sleepy eyes", term: "half-lidded sleepy eyes", adultOnly: true },
2938
+ { id: "eye-state-half-lidded", label: "Half-lidded", dimension: "eye-state", description: "Heavy-lidded sleepy gaze", promptHint: "with drowsy, partly closed eyes, the lids sitting low over the iris", term: "drowsy, partly closed eyes", adultOnly: true },
2939
2939
  { id: "eye-state-wide-eyed", label: "Wide-eyed", dimension: "eye-state", description: "Eyes wide open, alert / surprised", promptHint: "with eyes wide open, alert and surprised" },
2940
- { id: "eye-state-staring-camera", label: "Staring at Camera", dimension: "eye-state", description: "Direct unbroken eye contact with the lens", promptHint: "staring directly at the camera with unbroken eye contact" },
2940
+ { id: "eye-state-staring-camera", label: "Staring at Camera", dimension: "eye-state", description: "Direct unbroken eye contact with the lens", promptHint: "looking directly into the camera", term: "direct gaze to camera" },
2941
2941
  { id: "eye-state-gazing-away", label: "Gazing Away", dimension: "eye-state", description: "Looking off-camera, contemplative", promptHint: "gazing off-camera with a contemplative expression", term: "gazing off-camera" },
2942
2942
  { id: "eye-state-gazing-up", label: "Gazing Up", dimension: "eye-state", description: "Eyes raised toward something above", promptHint: "with eyes gazing upward toward something above" },
2943
2943
  { id: "eye-state-gazing-down", label: "Gazing Down", dimension: "eye-state", description: "Eyes downcast", promptHint: "with eyes downcast, gazing softly downward" },
@@ -2954,8 +2954,8 @@ var PEOPLE = [
2954
2954
  { id: "texture-smooth", label: "Smooth", dimension: "skin-texture", description: "Flawless, silky smooth skin", promptHint: "with flawless, silky smooth skin", term: "flawless smooth skin" },
2955
2955
  { id: "texture-wrinkled", label: "Wrinkled", dimension: "skin-texture", description: "Aged, deeply lined skin", promptHint: "with deep wrinkles and aged skin texture", term: "deeply wrinkled aged skin" },
2956
2956
  { id: "texture-goosebumps", label: "Goosebumps", dimension: "skin-texture", description: "Raised goosebumps on skin", promptHint: "with goosebumps raised on the skin" },
2957
- { id: "texture-dewy", label: "Dewy", dimension: "skin-texture", description: "Glowing, dewy fresh skin", promptHint: "with dewy, glowing skin and a fresh sheen", term: "dewy glowing skin" },
2958
- { id: "texture-glistening", label: "Glistening", dimension: "skin-texture", description: "Sweat or oil sheen", promptHint: "with glistening skin, sweat or oil catching the light", term: "glistening skin", adultOnly: true },
2957
+ { id: "texture-dewy", label: "Dewy", dimension: "skin-texture", description: "Glowing, dewy fresh skin", promptHint: "with dewy, luminous skin", term: "dewy skin" },
2958
+ { id: "texture-glistening", label: "Glistening", dimension: "skin-texture", description: "Sweat or oil sheen", promptHint: "with a light glistening sheen on the skin", term: "glistening sheen", adultOnly: true },
2959
2959
  { id: "texture-weathered", label: "Weathered", dimension: "skin-texture", description: "Sun-aged rough skin", promptHint: "with weathered, sun-worn rough skin", term: "weathered sun-worn skin" },
2960
2960
  { id: "texture-porcelain", label: "Porcelain", dimension: "skin-texture", description: "Flawless near-translucent porcelain", promptHint: "with flawless, near-translucent porcelain skin", term: "porcelain skin" },
2961
2961
  { id: "texture-sun-kissed", label: "Sun-kissed", dimension: "skin-texture", description: "Warm tan with healthy glow", promptHint: "with sun-kissed skin, warmly tanned with a healthy glow", term: "sun-kissed skin" },
@@ -2967,8 +2967,8 @@ var PEOPLE = [
2967
2967
  { id: "texture-oily", label: "Oily / Shiny", dimension: "skin-texture", description: "Slight oil sheen on T-zone", promptHint: "with a slight oily sheen across the T-zone", term: "oily shiny skin", adultOnly: true },
2968
2968
  { id: "texture-matte", label: "Matte", dimension: "skin-texture", description: "Poreless matte finish", promptHint: "with a poreless, matte skin finish", term: "matte skin finish" },
2969
2969
  { id: "texture-blemished", label: "Blemished", dimension: "skin-texture", description: "Visible blemishes, real-skin imperfections", promptHint: "with visible blemishes and natural real-skin imperfections", term: "blemished skin" },
2970
- { id: "texture-baby-soft", label: "Baby-soft", dimension: "skin-texture", description: "Smooth, fine-pored youthful skin", promptHint: "with baby-soft, fine-pored, youthful smooth skin", term: "baby-soft skin" },
2971
- { id: "texture-shower-fresh-wet", label: "Shower-Fresh Wet", dimension: "skin-texture", description: "Just-out-of-shower wet skin with water beads", promptHint: "with just-out-of-the-shower wet skin, water beading on the surface and rolling in slow droplets down the curves of the body", term: "shower-fresh wet skin", adultOnly: true },
2970
+ { id: "texture-baby-soft", label: "Baby-soft", dimension: "skin-texture", description: "Smooth, fine-pored youthful skin", promptHint: "with soft, fine-pored skin", term: "soft fine-pored skin" },
2971
+ { id: "texture-shower-fresh-wet", label: "Shower-Fresh Wet", dimension: "skin-texture", description: "Just-out-of-shower wet skin with water beads", promptHint: "with fresh, water-dappled skin as if just out of the shower", term: "water-dappled skin", adultOnly: true },
2972
2972
  { id: "texture-acne-scarred", label: "Acne-scarred", dimension: "skin-texture", description: "Visible acne scarring (distinct from blemished \u2014 healed scar pattern)", promptHint: "with visible acne scarring, healed pitted-skin texture and uneven surface from past breakouts", term: "acne-scarred skin" },
2973
2973
  // -------------------- Distinctive Features --------------------
2974
2974
  { id: "feature-glasses", label: "Glasses", dimension: "distinctive-features", description: "Wears glasses", promptHint: "wearing glasses", term: "wearing glasses" },
@@ -2997,9 +2997,9 @@ var PEOPLE = [
2997
2997
  { id: "feature-ear-piercings", label: "Ear Piercings", dimension: "distinctive-features", description: "Multiple stacked ear piercings (cartilage, helix, conch)", promptHint: "with multiple stacked ear piercings \u2014 cartilage, helix, and conch", term: "multiple stacked ear piercings" },
2998
2998
  { id: "feature-lip-piercing", label: "Lip Piercing", dimension: "distinctive-features", description: "Lip ring / labret stud", promptHint: "with a lip piercing, a small ring or labret stud at the lip" },
2999
2999
  { id: "feature-nostril-piercing", label: "Nostril Piercing", dimension: "distinctive-features", description: "Single nostril stud or ring", promptHint: "with a single nostril piercing, a small stud or ring at the nostril" },
3000
- { id: "feature-bare-shoulders", label: "Bare Shoulders", dimension: "distinctive-features", description: "Bare shoulders exposed", promptHint: "with bare shoulders exposed, the line of the collarbone and shoulder muscles uncovered", adultOnly: true },
3001
- { id: "feature-collarbone-visible", label: "Collarbone Visible", dimension: "distinctive-features", description: "Prominent collarbone catching light", promptHint: "with a prominent collarbone clearly defined and catching the light", term: "visible collarbone", adultOnly: true },
3002
- { id: "feature-midriff-visible", label: "Midriff Visible", dimension: "distinctive-features", description: "Exposed midriff between top and bottom", promptHint: "wearing a cropped style with the midriff visible", term: "visible midriff", adultOnly: true },
3000
+ { id: "feature-bare-shoulders", label: "Bare Shoulders", dimension: "distinctive-features", description: "Bare shoulders exposed", promptHint: "with the shoulders uncovered", term: "shoulders uncovered", adultOnly: true },
3001
+ { id: "feature-collarbone-visible", label: "Collarbone Visible", dimension: "distinctive-features", description: "Prominent collarbone catching light", promptHint: "with an open neckline that leaves the collarbones uncovered", term: "open neckline, collarbones uncovered", adultOnly: true },
3002
+ { id: "feature-midriff-visible", label: "Midriff Visible", dimension: "distinctive-features", description: "Exposed midriff between top and bottom", promptHint: "with a cropped hemline", term: "cropped hemline", adultOnly: true },
3003
3003
  { id: "feature-navel-visible", label: "Navel Visible", dimension: "distinctive-features", description: "Visible navel on bare stomach", promptHint: "with the navel visible", term: "visible navel", adultOnly: true },
3004
3004
  { id: "feature-elongated-neck", label: "Elongated Neck", dimension: "distinctive-features", description: "Long swan-like neck", promptHint: "with an elongated, swan-like neck, long and gracefully extended", term: "elongated swan-like neck" },
3005
3005
  { id: "feature-under-eye-circles", label: "Under-Eye Circles", dimension: "distinctive-features", description: "Subtle dark circles under the eyes (distinct from puffy eye-bags)", promptHint: "with subtle dark circles under the eyes", term: "dark under-eye circles" },
@@ -3326,7 +3326,7 @@ function emitIndependentFragments(value, fragmentFor) {
3326
3326
  const navel = ids.includes("feature-navel-visible");
3327
3327
  for (const id of ids) {
3328
3328
  if (midriff && navel && (id === "feature-midriff-visible" || id === "feature-navel-visible")) {
3329
- if (id === "feature-midriff-visible" && fragmentFor(id) !== "") out.push("wearing a cropped style, midriff and navel visible");
3329
+ if (id === "feature-midriff-visible" && fragmentFor(id) !== "") out.push("with a cropped hemline and the navel showing");
3330
3330
  continue;
3331
3331
  }
3332
3332
  const fragment = fragmentFor(id);
@@ -3591,7 +3591,7 @@ var POSES = [
3591
3591
  { id: "throwing", label: "Throwing", category: "action", description: "Mid-throw motion", promptHint: "caught mid-throw, body coiled and releasing", term: "caught mid-throw" },
3592
3592
  { id: "leaping", label: "Leaping", category: "action", description: "Leaping forward dynamically", promptHint: "leaping forward dynamically with body extended" },
3593
3593
  { id: "dramatic-action", label: "Dramatic Action", category: "action", description: "Exaggerated action pose", promptHint: "in a dramatic, exaggerated action pose full of motion", term: "dramatic exaggerated action pose" },
3594
- { id: "biting-lip", label: "Biting Lip", category: "action", description: "Slight playful lip-bite", promptHint: "biting the lower lip with a subtle playful expression", term: "biting the lower lip", adultOnly: true },
3594
+ { id: "biting-lip", label: "Biting Lip", category: "action", description: "Slight playful lip-bite", promptHint: "lightly biting the lower lip, a subtle playful expression", term: "biting lower lip", adultOnly: true },
3595
3595
  { id: "mid-laugh", label: "Mid-Laugh", category: "action", description: "Caught mid-laugh, head back", promptHint: "caught mid-laugh with head tipped back, eyes crinkled" },
3596
3596
  { id: "pointing-at-camera", label: "Pointing at Camera", category: "action", description: "Pointing directly at camera", promptHint: "pointing one finger directly at the camera, arm extended", term: "pointing directly at the camera" },
3597
3597
  { id: "tongue-out", label: "Sticking Tongue Out", category: "action", description: "Playful tongue-out expression", promptHint: "sticking the tongue out playfully" },
@@ -4019,10 +4019,10 @@ var STYLINGS = [
4019
4019
  { id: "footwear-espadrilles", label: "Espadrilles", dimension: "footwear", description: "Woven jute-sole canvas summer shoe", promptHint: "in woven jute-sole espadrilles with a canvas upper, a casual summer shoe" },
4020
4020
  // -------------------- Wardrobe State (modifier — composes with any garment) --------------------
4021
4021
  { id: "state-oversized", label: "Oversized", dimension: "wardrobe-state", description: "Loose, oversized fit", promptHint: "the clothing loose and oversized, draping freely well past the body", term: "oversized fit" },
4022
- { id: "state-fitted", label: "Fitted", dimension: "wardrobe-state", description: "Form-fitting silhouette", promptHint: "the clothing fitted and form-conscious, hugging the contours of the body", term: "form-fitting clothing", adultOnly: true },
4022
+ { id: "state-fitted", label: "Fitted", dimension: "wardrobe-state", description: "Form-fitting silhouette", promptHint: "tailored, close-fitting clothing", term: "close-fitting clothing", adultOnly: true },
4023
4023
  { id: "state-cropped", label: "Cropped", dimension: "wardrobe-state", description: "Top cropped at midriff", promptHint: "the top cropped above the midriff with the stomach visible", term: "cropped top", adultOnly: true },
4024
4024
  { id: "state-sheer", label: "Sheer", dimension: "wardrobe-state", description: "Translucent fabric", promptHint: "the fabric translucent and sheer, hinting at the silhouette underneath", term: "sheer clothing", adultOnly: true },
4025
- { id: "state-wet", label: "Wet", dimension: "wardrobe-state", description: "Soaked, water-clinging", promptHint: "the clothing soaked and wet, the fabric clinging to the body and dripping water", term: "wet clothing", adultOnly: true },
4025
+ { id: "state-wet", label: "Wet", dimension: "wardrobe-state", description: "Soaked, water-clinging", promptHint: "the clothing soaked through and dripping", term: "soaked clothing", adultOnly: true },
4026
4026
  { id: "state-ripped", label: "Ripped", dimension: "wardrobe-state", description: "Torn and frayed", promptHint: "the fabric torn and ripped at the seams, with frayed edges", term: "ripped clothing" },
4027
4027
  { id: "state-distressed", label: "Distressed", dimension: "wardrobe-state", description: "Worn, faded, weathered", promptHint: "the clothing distressed and weathered, with faded color and worn-down edges", term: "distressed clothing" },
4028
4028
  { id: "state-vintage", label: "Vintage", dimension: "wardrobe-state", description: "Worn, retro character", promptHint: "the clothing carrying vintage character \u2014 softened color, broken-in fit, retro silhouette", term: "vintage clothing" },
@@ -4132,16 +4132,23 @@ function collectStylingFragments(data, fragmentFor) {
4132
4132
  const boldRedAlready = Array.isArray(lipStateRaw) ? lipStateRaw.includes("lip-state-bold-red") : lipStateRaw === "lip-state-bold-red";
4133
4133
  const floored = isMinorAge(data);
4134
4134
  const emit = floored ? (id) => getStyling(id)?.adultOnly === true ? "" : fragmentFor(id) : fragmentFor;
4135
+ const featureRaw = data.distinctiveFeature;
4136
+ const midriffPicked = Array.isArray(featureRaw) ? featureRaw.includes("feature-midriff-visible") : featureRaw === "feature-midriff-visible";
4137
+ const midriffAlready = midriffPicked && !floored;
4138
+ const cropTopPicked = data.top === "top-crop-top";
4139
+ const croppedSuppressed = (id) => id === "top-crop-top" && midriffAlready || id === "state-cropped" && (midriffAlready || cropTopPicked);
4135
4140
  for (const dimension of STYLING_DIMENSION_ORDER) {
4136
4141
  const field = STYLING_FIELD_BY_DIMENSION[dimension];
4137
4142
  const raw = data[field];
4138
4143
  if (dimension === "makeup" && boldRedAlready && raw === "makeup-bold-lips") continue;
4139
4144
  if (typeof raw === "string" && raw.length > 0) {
4145
+ if (croppedSuppressed(raw)) continue;
4140
4146
  const fragment = emit(raw);
4141
4147
  if (fragment) out.push(fragment);
4142
4148
  } else if (Array.isArray(raw)) {
4143
4149
  for (const item of raw) {
4144
4150
  if (typeof item !== "string" || item.length === 0) continue;
4151
+ if (croppedSuppressed(item)) continue;
4145
4152
  const fragment = emit(item);
4146
4153
  if (fragment) out.push(fragment);
4147
4154
  }
@@ -10337,32 +10344,6 @@ function resolveBrandInput(brand) {
10337
10344
  return brand;
10338
10345
  }
10339
10346
 
10340
- // src/prompt-templates.ts
10341
- var DEFAULT_TEMPLATES = {
10342
- "character-description": "Include character '{name}': {description}.",
10343
- "object-description": "Include object '{name}': {description}.",
10344
- "location-description": "Include location '{name}': {description}.",
10345
- "face-description": "Include the exact face and facial features of '{name}' from the reference image. Maintain perfect likeness and facial identity.",
10346
- // `-generation` templates drive standalone entity generation routes
10347
- // (`/v1/generate-character`, `/v1/generate-face`, etc.). Duplicated between
10348
- // frontend and backend historically; consolidated here so the backend DAG
10349
- // orchestrator produces the same prompt as a single-node HTTP call.
10350
- "character-generation": "Create a full-body character portrait: {description}. Style: {style}. Gender: {gender}. High quality, detailed, consistent lighting, neutral background.",
10351
- "object-generation": "Create a product photo of: {description}. Category: {category}. Clean background, professional studio lighting, high detail.",
10352
- "location-generation": "Create a cinematic scene of: {description}. Category: {category}. Atmospheric lighting, high detail, wide angle.",
10353
- "face-generation": "Create a professional close-up face portrait headshot: {description}. Style: {style}. Looking directly at camera, sharp focus on facial features, clean background, studio lighting, high resolution. Maintain exact facial identity and features from the reference image.",
10354
- "generate-image-wrapper": "{userPrompt}\n{assetDescriptions}"
10355
- };
10356
- function resolveTemplate(key, userTemplates, flowTemplates) {
10357
- return flowTemplates?.[key] ?? userTemplates?.[key] ?? DEFAULT_TEMPLATES[key] ?? "";
10358
- }
10359
- function applyTemplate(template, vars) {
10360
- return Object.entries(vars).reduce(
10361
- (result, [key, value]) => result.replaceAll(`{${key}}`, value || ""),
10362
- template
10363
- );
10364
- }
10365
-
10366
10347
  // src/prompt-hint-join.ts
10367
10348
  var PROMPT_HINT_SEPARATOR = ". ";
10368
10349
  function joinPromptHints(userPrompt, hints) {
@@ -10574,6 +10555,87 @@ function sectionedClauseCosts(userPrompt, clauses, structuredFragment) {
10574
10555
  }
10575
10556
  return costs;
10576
10557
  }
10558
+
10559
+ // src/described-references.ts
10560
+ var CHARACTER_BLOCK_HEADER = "Use these characters:";
10561
+ function referenceDescriptionLine(subject, description) {
10562
+ const s = subject.trim();
10563
+ const d = description?.trim();
10564
+ if (!s || !d) return "";
10565
+ return `${s} \u2014 ${d}.`;
10566
+ }
10567
+ function renderDescribedReferenceLines(refs) {
10568
+ if (!refs || refs.length === 0) return [];
10569
+ const lines = [];
10570
+ const seen = /* @__PURE__ */ new Set();
10571
+ for (const r of refs) {
10572
+ const key = r.name?.trim().toLowerCase();
10573
+ if (!key || seen.has(key)) continue;
10574
+ const line = referenceDescriptionLine(r.name, r.description);
10575
+ if (!line) continue;
10576
+ seen.add(key);
10577
+ lines.push(line);
10578
+ }
10579
+ return lines;
10580
+ }
10581
+ function renderReferenceCaptionLines(videoCaptions, audioCaptions, counts) {
10582
+ const lines = [];
10583
+ const render = (captions, kind, cap) => {
10584
+ if (!captions) return;
10585
+ for (let i = 0; i < captions.length && i < cap; i++) {
10586
+ const text = captions[i]?.trim();
10587
+ if (!text) continue;
10588
+ lines.push(`@${kind}_${i + 1}: ${text}.`);
10589
+ }
10590
+ };
10591
+ render(videoCaptions, "video", counts.video);
10592
+ render(audioCaptions, "audio", counts.audio);
10593
+ return lines;
10594
+ }
10595
+ function appendReferenceLines(prompt, lines, format) {
10596
+ if (lines.length === 0) return prompt;
10597
+ if (format === "hybrid") return insertBeforeStyleSection(prompt, lines);
10598
+ const bullets = lines.map((l) => `- ${l}`).join("\n");
10599
+ if (prompt.startsWith(`${CHARACTER_BLOCK_HEADER}
10600
+ `)) {
10601
+ const splitIdx = prompt.indexOf("\n\n");
10602
+ if (splitIdx === -1) return `${prompt}
10603
+ ${bullets}`;
10604
+ return `${prompt.slice(0, splitIdx)}
10605
+ ${bullets}${prompt.slice(splitIdx)}`;
10606
+ }
10607
+ const block = `${CHARACTER_BLOCK_HEADER}
10608
+ ${bullets}`;
10609
+ return prompt ? `${block}
10610
+
10611
+ ${prompt}` : block;
10612
+ }
10613
+
10614
+ // src/prompt-templates.ts
10615
+ var DEFAULT_TEMPLATES = {
10616
+ "character-description": "Include character '{name}': {description}.",
10617
+ "object-description": "Include object '{name}': {description}.",
10618
+ "location-description": "Include location '{name}': {description}.",
10619
+ "face-description": "Include the exact face and facial features of '{name}' from the reference image. Maintain perfect likeness and facial identity.",
10620
+ // `-generation` templates drive standalone entity generation routes
10621
+ // (`/v1/generate-character`, `/v1/generate-face`, etc.). Duplicated between
10622
+ // frontend and backend historically; consolidated here so the backend DAG
10623
+ // orchestrator produces the same prompt as a single-node HTTP call.
10624
+ "character-generation": "Create a full-body character portrait: {description}. Style: {style}. Gender: {gender}. High quality, detailed, consistent lighting, neutral background.",
10625
+ "object-generation": "Create a product photo of: {description}. Category: {category}. Clean background, professional studio lighting, high detail.",
10626
+ "location-generation": "Create a cinematic scene of: {description}. Category: {category}. Atmospheric lighting, high detail, wide angle.",
10627
+ "face-generation": "Create a professional close-up face portrait headshot: {description}. Style: {style}. Looking directly at camera, sharp focus on facial features, clean background, studio lighting, high resolution. Maintain exact facial identity and features from the reference image.",
10628
+ "generate-image-wrapper": "{userPrompt}\n{assetDescriptions}"
10629
+ };
10630
+ function resolveTemplate(key, userTemplates, flowTemplates) {
10631
+ return flowTemplates?.[key] ?? userTemplates?.[key] ?? DEFAULT_TEMPLATES[key] ?? "";
10632
+ }
10633
+ function applyTemplate(template, vars) {
10634
+ return Object.entries(vars).reduce(
10635
+ (result, [key, value]) => result.replaceAll(`{${key}}`, value || ""),
10636
+ template
10637
+ );
10638
+ }
10577
10639
  function composeIdentityDescPart(subject, canonicalDesc, elementInjection) {
10578
10640
  const parts = [];
10579
10641
  const c = canonicalDesc?.trim();
@@ -10582,6 +10644,10 @@ function composeIdentityDescPart(subject, canonicalDesc, elementInjection) {
10582
10644
  if (e) parts.push(e);
10583
10645
  return parts.length > 0 ? `${subject} \u2014 ${parts.join(". ")}` : subject;
10584
10646
  }
10647
+ function pushOverrideDirective(out, ref, binding) {
10648
+ const line = referenceDescriptionLine(binding, ref.descriptionOverride);
10649
+ if (line) out.push(line);
10650
+ }
10585
10651
  function resolveCharacterMentions(prompt, tokens, refs) {
10586
10652
  const bySlug = /* @__PURE__ */ new Map();
10587
10653
  const byVariant = /* @__PURE__ */ new Map();
@@ -10622,7 +10688,7 @@ function resolveCharacterMentions(prompt, tokens, refs) {
10622
10688
  const includeCanonicalDesc = effectiveMode === "identical" || effectiveMode === "face-pose";
10623
10689
  const descPart = composeIdentityDescPart(
10624
10690
  subject,
10625
- includeCanonicalDesc ? match.characterCanonicalDescription : void 0,
10691
+ match.descriptionOverride?.trim() || (includeCanonicalDesc ? match.characterCanonicalDescription : void 0),
10626
10692
  match.elementInjection
10627
10693
  );
10628
10694
  directiveLines.push(`- ${descPart}.${directive ? ` ${directive}` : ""}`);
@@ -10700,6 +10766,7 @@ function resolveCharacterMentionsHybrid(prompt, tokens, refs, existingUrls) {
10700
10766
  const binding = bindingFor(m.url);
10701
10767
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10702
10768
  if (lock) lockLines.push(lock);
10769
+ pushOverrideDirective(elementDirectives, ref, binding);
10703
10770
  const inject = ref.elementInjection?.trim();
10704
10771
  if (inject) elementDirectives.push(inject);
10705
10772
  }
@@ -10773,7 +10840,8 @@ function resolveLocationMentions(prompt, tokens, refs) {
10773
10840
  const subject = `Image ${t.imageIndex} (${displayName})`;
10774
10841
  const includeCanonicalDesc = effectiveMode === "identical";
10775
10842
  const canonicalDesc = match.locationCanonicalDescription?.trim();
10776
- const descPart = includeCanonicalDesc && canonicalDesc ? `${subject} \u2014 ${canonicalDesc}` : subject;
10843
+ const shownDesc = match.descriptionOverride?.trim() || (includeCanonicalDesc ? canonicalDesc : void 0);
10844
+ const descPart = shownDesc ? `${subject} \u2014 ${shownDesc}` : subject;
10777
10845
  directiveLines.push(`- ${descPart}.${directive ? ` ${directive}` : ""}`);
10778
10846
  if (t.bucket && t.variant && match.locationVariantDisplayName && match.locationVariantDisplayName !== "canonical") {
10779
10847
  directiveLines.push(` (in this image: ${match.locationVariantDisplayName})`);
@@ -10841,6 +10909,7 @@ function resolveLocationMentionsHybrid(prompt, tokens, refs, existingUrls) {
10841
10909
  const binding = bindingFor(m.url);
10842
10910
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10843
10911
  if (lock) lockLines.push(lock);
10912
+ pushOverrideDirective(elementDirectives, ref, binding);
10844
10913
  const inject = ref.elementInjection?.trim();
10845
10914
  if (inject) elementDirectives.push(inject);
10846
10915
  }
@@ -10890,10 +10959,11 @@ function resolveImageMentionsHybrid(prompt, tokens, refs, existingUrls) {
10890
10959
  const binding = bindingFor(m.url);
10891
10960
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10892
10961
  if (lock) lockLines.push(lock);
10962
+ pushOverrideDirective(elementDirectives, ref, binding);
10893
10963
  const inject = ref.elementInjection?.trim();
10894
10964
  if (inject) elementDirectives.push(inject);
10895
10965
  }
10896
- return { prompt: resolvedPrompt, additionalUrls, lockLines, elementDirectives };
10966
+ return { prompt: resolvedPrompt, additionalUrls, mentionedUrls: seenUrls, lockLines, elementDirectives };
10897
10967
  }
10898
10968
  function resolveEntityMentionsHybrid(prompt, tokens, refs, existingUrls) {
10899
10969
  const bySlug = /* @__PURE__ */ new Map();
@@ -10944,6 +11014,7 @@ function resolveEntityMentionsHybrid(prompt, tokens, refs, existingUrls) {
10944
11014
  const binding = bindingFor(m.url);
10945
11015
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
10946
11016
  if (lock) lockLines.push(lock);
11017
+ pushOverrideDirective(elementDirectives, ref, binding);
10947
11018
  const inject = ref.elementInjection?.trim();
10948
11019
  if (inject) elementDirectives.push(inject);
10949
11020
  }
@@ -10979,7 +11050,7 @@ function buildCanonicalFallback(refs, mentionedSlugs, startIndex) {
10979
11050
  const subject = `Image ${cursor} (${displayName})`;
10980
11051
  const descPart = composeIdentityDescPart(
10981
11052
  subject,
10982
- includeCanonicalDesc ? r.characterCanonicalDescription : void 0,
11053
+ r.descriptionOverride?.trim() || (includeCanonicalDesc ? r.characterCanonicalDescription : void 0),
10983
11054
  r.elementInjection
10984
11055
  );
10985
11056
  directiveLines.push(`- ${descPart}.${directive ? ` ${directive}` : ""}`);
@@ -10995,7 +11066,7 @@ function buildExtraRefDirectives(refs, emittedCharacterPositions, startIndex) {
10995
11066
  for (const r of refs) {
10996
11067
  if (!r.isExtraRef) continue;
10997
11068
  if (!r.url) continue;
10998
- const description = (r.description ?? r.variantDescription ?? "").trim();
11069
+ const description = (r.descriptionOverride ?? "").trim() || (r.description ?? r.variantDescription ?? "").trim();
10999
11070
  if (r.source === "wired-character" && r.characterSlug) {
11000
11071
  const effectiveMode = r.defaultUsageMode ?? shared.DEFAULT_USAGE_MODE;
11001
11072
  const earlierPos = positionsByChar.get(r.characterSlug);
@@ -11059,6 +11130,7 @@ function renderCanonicalFallbackHybrid(refs, letterForUrl) {
11059
11130
  phrases.push(shared.roleToPhrase(shared.resolveDefaultRole(r.defaultRole, r.defaultUsageMode, r.source), binding));
11060
11131
  const lock = buildIdentityLockLine(r, binding);
11061
11132
  if (lock) lockLines.push(lock);
11133
+ pushOverrideDirective(elementDirectives, r, binding);
11062
11134
  const inject = r.elementInjection?.trim();
11063
11135
  if (inject) elementDirectives.push(inject);
11064
11136
  }
@@ -11073,7 +11145,7 @@ function renderExtraRefsHybrid(extraRefs, letterForUrl, seedLetterByChar) {
11073
11145
  if (!r.url) continue;
11074
11146
  const letter = letterForUrl(r.url);
11075
11147
  const binding = `reference image ${letter}`;
11076
- const description = (r.description ?? r.variantDescription ?? "").trim();
11148
+ const description = (r.descriptionOverride ?? "").trim() || (r.description ?? r.variantDescription ?? "").trim();
11077
11149
  if (r.source === "wired-character" && r.characterSlug) {
11078
11150
  const earlier = firstLetterByChar.get(r.characterSlug);
11079
11151
  if (earlier !== void 0 && earlier !== letter) {
@@ -11110,10 +11182,11 @@ function renderLocationCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, covere
11110
11182
  phrases.push(shared.roleToPhrase(resolveLocationRole(null, null, r), binding));
11111
11183
  const lock = buildIdentityLockLine(r, binding);
11112
11184
  if (lock) lockLines.push(lock);
11185
+ pushOverrideDirective(elementDirectives, r, binding);
11113
11186
  const inject = r.elementInjection?.trim();
11114
11187
  if (inject) elementDirectives.push(inject);
11115
11188
  }
11116
- return { phrases, lockLines, elementDirectives };
11189
+ return { phrases, lockLines, elementDirectives, renderedUrls: seenUrls };
11117
11190
  }
11118
11191
  function renderObjectCreatureCanonicalHybrid(nonCharacterRefs, finalIndexByUrl, coveredUrls) {
11119
11192
  const phrases = [];
@@ -11130,10 +11203,24 @@ function renderObjectCreatureCanonicalHybrid(nonCharacterRefs, finalIndexByUrl,
11130
11203
  phrases.push(shared.roleToPhrase(shared.defaultRoleForSource(r.source), binding));
11131
11204
  const lock = buildIdentityLockLine(r, binding);
11132
11205
  if (lock) lockLines.push(lock);
11206
+ pushOverrideDirective(elementDirectives, r, binding);
11133
11207
  const inject = r.elementInjection?.trim();
11134
11208
  if (inject) elementDirectives.push(inject);
11135
11209
  }
11136
- return { phrases, lockLines, elementDirectives };
11210
+ return { phrases, lockLines, elementDirectives, renderedUrls: seenUrls };
11211
+ }
11212
+ function renderUntoldOverridesHybrid(nonCharacterRefs, finalIndexByUrl, toldUrls) {
11213
+ const lines = [];
11214
+ const sweptUrls = /* @__PURE__ */ new Set();
11215
+ for (const r of nonCharacterRefs) {
11216
+ if (!r.url || toldUrls.has(r.url) || sweptUrls.has(r.url)) continue;
11217
+ const slot = finalIndexByUrl.get(r.url);
11218
+ if (!slot) continue;
11219
+ const before = lines.length;
11220
+ pushOverrideDirective(lines, r, `reference image ${slotToLetter(slot)}`);
11221
+ if (lines.length > before) sweptUrls.add(r.url);
11222
+ }
11223
+ return lines;
11137
11224
  }
11138
11225
  function buildTileIdForUrl(urls, refs, prompt, sourceNodeIdById) {
11139
11226
  const knownSlugs = Array.from(
@@ -11306,8 +11393,10 @@ function buildImagePromptInternal(config2, marks) {
11306
11393
  );
11307
11394
  const suppressedCanonicalLocationIds = config2.suppressedCanonicalLocationIds ?? [];
11308
11395
  const isHybrid = config2.referenceFormat === "hybrid";
11396
+ const describedLines = renderDescribedReferenceLines(config2.describedReferences);
11309
11397
  let hybridBodyConverged = false;
11310
11398
  const mentionedEntityUrls = /* @__PURE__ */ new Set();
11399
+ const mentionedImageUrls = /* @__PURE__ */ new Set();
11311
11400
  if (config2.skipCharacterMentions) {
11312
11401
  config2 = {
11313
11402
  ...config2,
@@ -11425,6 +11514,7 @@ function buildImagePromptInternal(config2, marks) {
11425
11514
  resolved.additionalUrls = [...resolved.additionalUrls, ...hi.additionalUrls];
11426
11515
  hybridImageLockLines = hi.lockLines;
11427
11516
  hybridImageElementDirectives = hi.elementDirectives;
11517
+ for (const u of hi.mentionedUrls) mentionedImageUrls.add(u);
11428
11518
  if (hi.additionalUrls.length > 0) hybridBodyConverged = true;
11429
11519
  }
11430
11520
  }
@@ -11555,9 +11645,13 @@ ${promptForNext}`;
11555
11645
  referenceImageUrls = config2.referenceImageUrls || [];
11556
11646
  }
11557
11647
  }
11558
- if (connectedReferences) {
11648
+ if (connectedReferences || describedLines.length > 0) {
11649
+ const structuredRefs = connectedReferences ?? [];
11559
11650
  let prompt2 = config2.prompt;
11560
- const nonCharacterRefs = connectedReferences.filter(
11651
+ if (!isHybrid && describedLines.length > 0) {
11652
+ prompt2 = appendReferenceLines(prompt2, describedLines, "legacy");
11653
+ }
11654
+ const nonCharacterRefs = structuredRefs.filter(
11561
11655
  (r) => r.source !== "wired-character" && r.isExtraRef !== true
11562
11656
  );
11563
11657
  const nonCharacterUrls = nonCharacterRefs.map((r) => r.url).filter((u) => Boolean(u));
@@ -11587,11 +11681,23 @@ ${promptForNext}`;
11587
11681
  const canonLockBlock = canonLockLines.length > 0 ? `${canonLockLines.join("\n")}
11588
11682
 
11589
11683
  ` : "";
11684
+ const toldOverrideUrls = /* @__PURE__ */ new Set([
11685
+ ...locCanon.renderedUrls,
11686
+ ...objCanon.renderedUrls,
11687
+ ...mentionedEntityUrls,
11688
+ ...mentionedImageUrls
11689
+ ]);
11690
+ const untoldOverrides = renderUntoldOverridesHybrid(
11691
+ nonCharacterRefs,
11692
+ finalIndexByUrl,
11693
+ toldOverrideUrls
11694
+ );
11590
11695
  const canonTrailingLines = [
11591
11696
  ...locCanon.phrases,
11592
11697
  ...objCanon.phrases,
11593
11698
  ...locCanon.elementDirectives,
11594
- ...objCanon.elementDirectives
11699
+ ...objCanon.elementDirectives,
11700
+ ...untoldOverrides
11595
11701
  ];
11596
11702
  const composedScene = `${canonLockBlock}${insertBeforeStyleSection(scene, canonTrailingLines)}`;
11597
11703
  prompt2 = config2.referenceLockSnippet ? `${config2.referenceLockSnippet}
@@ -11618,6 +11724,9 @@ ${directives}`;
11618
11724
  }
11619
11725
  }
11620
11726
  }
11727
+ if (isHybrid && describedLines.length > 0) {
11728
+ prompt2 = appendReferenceLines(prompt2, describedLines, "hybrid");
11729
+ }
11621
11730
  const styleText2 = style?.trim();
11622
11731
  const styleLine2 = styleText2 && !isDeniedStyleId(styleText2) ? `Style: ${getStylePromptHint(styleText2) || styleText2}` : "";
11623
11732
  const negPrompt2 = negativePrompt?.trim();
@@ -11658,7 +11767,7 @@ ${directives}`;
11658
11767
  const reordered = applyReferenceOrder(
11659
11768
  assembledUrls,
11660
11769
  prompt2,
11661
- connectedReferences,
11770
+ structuredRefs,
11662
11771
  referenceOrder,
11663
11772
  sourceNodeIdById
11664
11773
  );
@@ -11783,7 +11892,10 @@ function collectIdentities(prompt, refs, meta3) {
11783
11892
  label,
11784
11893
  fidelity: m?.fidelity ?? defaultFidelityForSource(ref?.source),
11785
11894
  customText: m?.customText?.trim() || void 0,
11786
- description: ref?.description,
11895
+ // Per-use override ahead of the ref's own description; the directive
11896
+ // builder's location-canonical fallback only fires when BOTH are absent,
11897
+ // so an override outranks the stored location wording too.
11898
+ description: ref?.descriptionOverride?.trim() || ref?.description,
11787
11899
  source: ref?.source,
11788
11900
  locationCanonicalDescription: ref?.locationCanonicalDescription,
11789
11901
  locationSlug: ref?.locationSlug,
@@ -11891,7 +12003,7 @@ function buildNonCharacterDirectives(prompt, nonCharacterRefs, identityMeta, fin
11891
12003
  // wired-object falls through to the strict "match exactly" verb.
11892
12004
  label: ref.source === "wired-location" ? "location" : ref.source === "wired-creature" ? "creature" : "object",
11893
12005
  fidelity: defaultFidelityForSource(ref.source),
11894
- description: ref.description?.trim() || void 0,
12006
+ description: ref.descriptionOverride?.trim() || ref.description?.trim() || void 0,
11895
12007
  source: ref.source,
11896
12008
  locationCanonicalDescription: ref.locationCanonicalDescription,
11897
12009
  locationSlug: ref.locationSlug,
@@ -12418,6 +12530,8 @@ function resolveVideoCharacterMentionsHybrid(prompt, tokens, refs, offset) {
12418
12530
  const binding = bindingFor(m.url);
12419
12531
  const lock = buildIdentityLockLine(withForcedIdentityLock(ref, lockOverrideByUrl.get(m.url)), binding);
12420
12532
  if (lock) lockLines.push(lock);
12533
+ const overrideLine = referenceDescriptionLine(binding, ref.descriptionOverride);
12534
+ if (overrideLine) elementDirectives.push(overrideLine);
12421
12535
  const inject = ref.elementInjection?.trim();
12422
12536
  if (inject) elementDirectives.push(inject);
12423
12537
  }
@@ -12443,6 +12557,13 @@ function resolveVideoReferenceCore(args) {
12443
12557
  video: args.videoRefCount ?? 0,
12444
12558
  audio: args.audioRefCount ?? 0
12445
12559
  });
12560
+ const describedAndCaptionLines = [
12561
+ ...renderDescribedReferenceLines(args.describedReferences),
12562
+ ...renderReferenceCaptionLines(args.videoCaptions, args.audioCaptions, {
12563
+ video: args.videoRefCount ?? 0,
12564
+ audio: args.audioRefCount ?? 0
12565
+ })
12566
+ ];
12446
12567
  let wiredCharRefs = [...args.wiredCharRefs];
12447
12568
  const suppressedSlugs = new Set(args.suppressedCanonicalCharacterIds ?? []);
12448
12569
  if (suppressedSlugs.size > 0) {
@@ -12467,14 +12588,15 @@ function resolveVideoReferenceCore(args) {
12467
12588
  const slotByRefId = /* @__PURE__ */ new Map();
12468
12589
  if (wiredCharRefs.length === 0 && !hasExtras) {
12469
12590
  const counts = tokenCounts(leadingRefUrls.length);
12591
+ const resolved2 = resolveReferenceTokens(
12592
+ resolveRefIdTokens(args.prompt, { slotById: slotByRefId, nameById: nameByRefId, imageCount: counts.image }),
12593
+ counts
12594
+ );
12470
12595
  return {
12471
- // tokenCounts(leadingRefUrls.length) → image count == offset (no assets here):
12472
- // leadingRefUrls mode counts the leading refs; ordinalOffset mode counts the
12473
- // caller-owned leading refs the offset stands in for.
12474
- prompt: resolveReferenceTokens(
12475
- resolveRefIdTokens(args.prompt, { slotById: slotByRefId, nameById: nameByRefId, imageCount: counts.image }),
12476
- counts
12477
- ),
12596
+ // Described references and rail captions are the ONE thing this branch can
12597
+ // still contribute: they need no seat, so a request that carries only them
12598
+ // (a story landing before any entity exists) reaches the model here.
12599
+ prompt: describedAndCaptionLines.length > 0 ? appendReferenceLines(resolved2 ?? "", describedAndCaptionLines, args.hybridRoles === true ? "hybrid" : "legacy") : resolved2,
12478
12600
  additionalUrls: [...leadingRefUrls]
12479
12601
  };
12480
12602
  }
@@ -12532,6 +12654,8 @@ function resolveVideoReferenceCore(args) {
12532
12654
  canonicalPhrases.push(shared.roleToPhrase(shared.resolveDefaultRole(r.defaultRole, r.defaultUsageMode, r.source), binding));
12533
12655
  const lock = buildIdentityLockLine(r, binding);
12534
12656
  if (lock) canonicalLockLines.push(lock);
12657
+ const overrideLine = referenceDescriptionLine(binding, r.descriptionOverride);
12658
+ if (overrideLine) canonicalElementDirectives.push(overrideLine);
12535
12659
  const inject = r.elementInjection?.trim();
12536
12660
  if (inject) canonicalElementDirectives.push(inject);
12537
12661
  continue;
@@ -12548,7 +12672,10 @@ function resolveVideoReferenceCore(args) {
12548
12672
  const directive = shared.usageModeDirective(effectiveMode);
12549
12673
  const includeCanonicalDesc = effectiveMode === "identical" || effectiveMode === "face-pose";
12550
12674
  const descBodyParts = [];
12551
- if (includeCanonicalDesc && r.characterCanonicalDescription?.trim()) {
12675
+ const canonicalOverride = r.descriptionOverride?.trim();
12676
+ if (canonicalOverride) {
12677
+ descBodyParts.push(canonicalOverride);
12678
+ } else if (includeCanonicalDesc && r.characterCanonicalDescription?.trim()) {
12552
12679
  descBodyParts.push(r.characterCanonicalDescription.trim());
12553
12680
  }
12554
12681
  if (r.elementInjection?.trim()) descBodyParts.push(r.elementInjection.trim());
@@ -12565,7 +12692,7 @@ function resolveVideoReferenceCore(args) {
12565
12692
  if (!ex.url) continue;
12566
12693
  position += 1;
12567
12694
  if (ex.id && !slotByRefId.has(ex.id)) slotByRefId.set(ex.id, position);
12568
- const desc = (ex.description ?? "").trim();
12695
+ const desc = (ex.descriptionOverride ?? "").trim() || (ex.description ?? "").trim();
12569
12696
  if (ex.characterSlug) {
12570
12697
  const meta3 = args.lookupCharacterBySlug?.(ex.characterSlug);
12571
12698
  const charDefaultMode = meta3?.defaultUsageMode;
@@ -12709,6 +12836,11 @@ ${finalPrompt}` : block;
12709
12836
  merged.push(u);
12710
12837
  }
12711
12838
  }
12839
+ finalPrompt = appendReferenceLines(
12840
+ finalPrompt,
12841
+ describedAndCaptionLines,
12842
+ hybrid ? "hybrid" : "legacy"
12843
+ );
12712
12844
  finalPrompt = resolveRefIdTokens(finalPrompt, {
12713
12845
  slotById: slotByRefId,
12714
12846
  nameById: nameByRefId,
@@ -12923,6 +13055,7 @@ function assembleImageInput(input) {
12923
13055
  prompt: composePromptText(input.userPrompt, pieces, keptHintClauses),
12924
13056
  provider: input.provider,
12925
13057
  ...input.connectedReferences !== void 0 ? { connectedReferences: input.connectedReferences } : {},
13058
+ ...input.describedReferences !== void 0 ? { describedReferences: input.describedReferences } : {},
12926
13059
  // Manual uploads / direct refs ride the builder's reference-URL channel so
12927
13060
  // the per-provider reference gate filters them alongside bound entities.
12928
13061
  // Omit the field entirely when absent so the builder's default ([]) kicks
@@ -13355,8 +13488,44 @@ function getAdultOnlyEntries() {
13355
13488
  function getAdultOnlyIds() {
13356
13489
  return new Set(getAdultOnlyEntries().map((e) => e.id));
13357
13490
  }
13491
+ var RETIRED_ADULT_ONLY_HINT_STRINGS = [
13492
+ // bust-very-full
13493
+ "very full bust",
13494
+ // silhouette-hourglass
13495
+ "hourglass silhouette",
13496
+ // waist-defined
13497
+ "defined waist",
13498
+ // lip-state-glossy
13499
+ "with high-shine glossy wet-look lips",
13500
+ // lip-state-parted
13501
+ "with lips slightly parted, taking a soft breath",
13502
+ // lip-state-bitten
13503
+ "playfully biting the lower lip",
13504
+ // eye-state-half-lidded
13505
+ "with heavy half-lidded sleepy eyes",
13506
+ // texture-glistening
13507
+ "with glistening skin, sweat or oil catching the light",
13508
+ // texture-shower-fresh-wet
13509
+ "with just-out-of-the-shower wet skin, water beading on the surface and rolling in slow droplets down the curves of the body",
13510
+ // feature-bare-shoulders
13511
+ "with bare shoulders exposed, the line of the collarbone and shoulder muscles uncovered",
13512
+ // feature-collarbone-visible
13513
+ "with a prominent collarbone clearly defined and catching the light",
13514
+ // feature-midriff-visible
13515
+ "wearing a cropped style with the midriff visible",
13516
+ // state-fitted — the 2026-07-30 incident clause
13517
+ "the clothing fitted and form-conscious, hugging the contours of the body",
13518
+ // state-wet
13519
+ "the clothing soaked and wet, the fabric clinging to the body and dripping water",
13520
+ // pose `biting-lip` (the lip-state-bitten twin)
13521
+ "biting the lower lip with a subtle playful expression",
13522
+ // the hard-coded midriff+navel fold in emitIndependentFragments — not any
13523
+ // entry's hint, so retiring it ADDS a needle for a clause that really is
13524
+ // emitted verbatim.
13525
+ "wearing a cropped style, midriff and navel visible"
13526
+ ];
13358
13527
  function getAdultOnlyHintStrings() {
13359
- const out = /* @__PURE__ */ new Set();
13528
+ const out = new Set(RETIRED_ADULT_ONLY_HINT_STRINGS);
13360
13529
  for (const e of getAdultOnlyEntries()) {
13361
13530
  if (e.promptHint) out.add(e.promptHint.toLowerCase());
13362
13531
  }
@@ -34759,6 +34928,7 @@ exports.REFERENCE_RULES_MULTI_PERSON = REFERENCE_RULES_MULTI_PERSON;
34759
34928
  exports.REF_BINDING = REF_BINDING;
34760
34929
  exports.RENDER_QUALITIES = RENDER_QUALITIES;
34761
34930
  exports.RENDER_QUALITY_IDS = RENDER_QUALITY_IDS;
34931
+ exports.RETIRED_ADULT_ONLY_HINT_STRINGS = RETIRED_ADULT_ONLY_HINT_STRINGS;
34762
34932
  exports.SCENE_FRAME_RULE = SCENE_FRAME_RULE;
34763
34933
  exports.SCENE_PROMPT_MAX_LENGTH = SCENE_PROMPT_MAX_LENGTH;
34764
34934
  exports.SETTINGS = SETTINGS;
@@ -34822,6 +34992,7 @@ exports.WARDROBE_FIELD_BY_DIMENSION = WARDROBE_FIELD_BY_DIMENSION;
34822
34992
  exports.__resetCatalogIdGuardForTests = __resetCatalogIdGuardForTests;
34823
34993
  exports.appendField = appendField;
34824
34994
  exports.appendMusicMeta = appendMusicMeta;
34995
+ exports.appendReferenceLines = appendReferenceLines;
34825
34996
  exports.applyMinorAgeFloorToPickerValues = applyMinorAgeFloorToPickerValues;
34826
34997
  exports.applyPickerJson = applyPickerJson;
34827
34998
  exports.applyPromptAffixes = applyPromptAffixes;
@@ -35148,11 +35319,14 @@ exports.promptPartSeparator = promptPartSeparator;
35148
35319
  exports.readDirectionFields = readDirectionFields;
35149
35320
  exports.readStructuredFields = readStructuredFields;
35150
35321
  exports.readSubjectFields = readSubjectFields;
35322
+ exports.referenceDescriptionLine = referenceDescriptionLine;
35151
35323
  exports.referenceRulesBlock = referenceRulesBlock;
35152
35324
  exports.registerCatalogPack = registerCatalogPack;
35153
35325
  exports.registerPersonPack = registerPersonPack;
35326
+ exports.renderDescribedReferenceLines = renderDescribedReferenceLines;
35154
35327
  exports.renderDirectionHintClauses = renderDirectionHintClauses;
35155
35328
  exports.renderDirectionHints = renderDirectionHints;
35329
+ exports.renderReferenceCaptionLines = renderReferenceCaptionLines;
35156
35330
  exports.renderStructuredFields = renderStructuredFields;
35157
35331
  exports.renderStyleSection = renderStyleSection;
35158
35332
  exports.renderSubjectHints = renderSubjectHints;