diff --git a/apps/figgos/apps/backend/src/generation/gemini.service.ts b/apps/figgos/apps/backend/src/generation/gemini.service.ts index 6761259b3..fa8baa3c3 100644 --- a/apps/figgos/apps/backend/src/generation/gemini.service.ts +++ b/apps/figgos/apps/backend/src/generation/gemini.service.ts @@ -43,10 +43,11 @@ export class GeminiService { name: string, description: string, rarity: FigureRarity, - language: FigureLanguage + language: FigureLanguage, + hasFace: boolean = false ): Promise { const statRange = STAT_RANGES[rarity]; - const userPrompt = buildProfileUserPrompt(name, description, rarity, statRange, language); + const userPrompt = buildProfileUserPrompt(name, description, rarity, statRange, language, hasFace); this.logger.log(`Generating profile for "${name}" (${rarity})...`); diff --git a/apps/figgos/apps/backend/src/generation/generation.service.ts b/apps/figgos/apps/backend/src/generation/generation.service.ts index 8d48faa31..75171328b 100644 --- a/apps/figgos/apps/backend/src/generation/generation.service.ts +++ b/apps/figgos/apps/backend/src/generation/generation.service.ts @@ -45,7 +45,8 @@ export class GenerationService { input.name, input.description, input.rarity, - input.language + input.language, + !!input.faceImage ); // Save profile immediately (even if image gen fails, we keep the text) diff --git a/apps/figgos/apps/backend/src/generation/prompts.ts b/apps/figgos/apps/backend/src/generation/prompts.ts index 5671b9db1..fb546e1a6 100644 --- a/apps/figgos/apps/backend/src/generation/prompts.ts +++ b/apps/figgos/apps/backend/src/generation/prompts.ts @@ -22,13 +22,18 @@ export function buildProfileUserPrompt( description: string, rarity: string, statRange: { min: number; max: number }, - language: FigureLanguage + language: FigureLanguage, + hasFace: boolean = false ): string { const langInstruction = language === 'de' ? '\n\nIMPORTANT: Generate ALL text content (subtitle, backstory, items, specialAttack) in German. Only the visualDescription should remain in English (it feeds into an image generation prompt).' : ''; + const faceInstruction = hasFace + ? '\n\nA reference photo of the person will be used for the figure\'s face. In the visualDescription, do NOT describe facial features, skin tone, or face shape — only describe clothing, pose, expression mood, and body. The face will come from the photo.' + : ''; + return `Generate a full character profile for this collectible figure: **Name:** ${name} @@ -37,7 +42,7 @@ export function buildProfileUserPrompt( **Stats range for ${rarity}:** each stat (attack, defense, special) must be between ${statRange.min} and ${statRange.max}. -Generate: subtitle, 3 items, backstory, stats, special attack, and a detailed visual description of the figure.${langInstruction}`; +Generate: subtitle, 3 items, backstory, stats, special attack, and a detailed visual description of the figure.${langInstruction}${faceInstruction}`; } // ══════════════════════════════════════════════════════════════ @@ -201,11 +206,11 @@ export function buildImagePrompt( .map((item) => ` - ${item}`) .join('\n'); - const faceInstruction = hasFace - ? `\n\nCRITICAL — FACE TRANSFER: The provided reference photo shows the person's real face. The miniature figure MUST have this EXACT face — same facial structure, same features, same expression — but rendered in the miniature figure style. Preserve the likeness perfectly while matching the figure's aesthetic.` + const faceBlock = hasFace + ? `\n\nFACE REFERENCE: The attached photo shows the person this figure is based on. The figure's face must closely resemble this person — preserve their identity, face shape, and overall appearance. Adapt clothing and body to match the character description, but keep the face true to the photo.` : ''; - return `Product photograph of a premium collectible figure in sealed blister packaging on a pure white background. Package fills 95% of frame. + return `Product photograph of a premium collectible figure in sealed blister packaging on a pure white background. Package fills 95% of frame.${faceBlock} ${style.card} Hanging hole at top center. Clear plastic blister with molded compartments. @@ -216,7 +221,7 @@ Name in ${style.textStyle}: "${name.toUpperCase()}" large at the top. "${subtitl In the left compartment stands the figure: ${visualDescription} ${REALISM_BLOCK} -${style.vibe}${faceInstruction} +${style.vibe} Three accessories in separate molded blister compartments on the right side, stacked vertically: ${itemsText}