export const CAPTION_STYLES = ['descriptive', 'klein_prompt', 'delta', 'tags'] export const DELTA_LOCKED_LOOK = 'short light-brown bob, pink tank, light blue shorts, open green Slytherin robe with red-gold trim, barefoot' export function captionStylePrompt(style) { switch (style) { case 'klein_prompt': return 'Write one self-contained image generation prompt for Flux Klein. Include subject, pose, clothing, setting, lighting, and camera. No preamble or quotes.' case 'delta': return `The character's locked baseline look is: ${DELTA_LOCKED_LOOK}. Describe ONLY what changed from that baseline in this image. Do not restate the locked look.` case 'tags': return 'List comma-separated descriptive tags for this image. Tags only, no sentences.' case 'descriptive': default: return 'Describe this image in clear, detailed prose. Cover subject, appearance, clothing, pose, setting, and lighting.' } } export function validateCaptionRequest(raw) { const fail = message => { throw Object.assign(new Error(message), { statusCode: 400 }) } const body = raw || {} const style = String(body.captionStyle || body.style || 'descriptive') if (!CAPTION_STYLES.includes(style)) fail('Unknown caption style.') const folderId = String(body.folderId || '') if (!folderId) fail('Choose a folder.') const stillId = String(body.stillId || body.imageAId || '') const sourcePath = String(body.sourcePath || '') if (!stillId && !sourcePath) fail('Choose a still to describe.') return { mode: 'caption', folderId, stillId, sourcePath, captionStyle: style, promptSections: { action: `Describe ยท ${style}` }, compiledPrompt: captionStylePrompt(style), engine: 'caption', imageStyles: { positive: [], negative: [] }, hideOutput: false, lockFace: false, lockOutfit: false, hideInput: false, identityStillId: '', imageAId: stillId, imageBId: '', maskId: '', startClipId: '', endStillId: '', guides: [], batch: [], settings: { aspect: 'auto', fps: 24, width: 0, height: 0, steps: 1, cfg: 0, seedMode: 'random', seed: 0, duration: 0, turbo: true, loraStack: [] } } }