// Server-side mirror of search.html's ROLE_BANDS regex table. // Each band carries a *visual scene* — clothing + immediate backdrop — // so ComfyUI produces role-coherent headshots instead of interchangeable // studio portraits. The front-end sends the raw role string in the // query (?role=Forklift%20Operator); the server resolves it to a band // and looks up the scene here. export type RoleBand = | "warehouse" | "production" | "trades" | "driver" | "lead"; export interface SceneDef { band: RoleBand; // Free-form clause inserted into the diffusion prompt AFTER // "[age]-year-old [race] [gender] [role], ". Should describe what // they're wearing and what is immediately behind them. Keep under // ~25 words — SDXL Turbo loses focus on longer prompts and starts // hallucinating cartoon hands. scene: string; } const RE_BANDS: { re: RegExp; band: RoleBand }[] = [ { re: /forklift|warehouse|associate|material\s*handler|loader|loading|packag|shipping|logistics|inventory|sanitation|janit/i, band: "warehouse" }, { re: /production|assembl|quality/i, band: "production" }, { re: /welder|weld|electric|maint(enance)?\s*tech|cnc|machine\s*op|hvac|plumb|carpenter|mason|tool\s*&\s*die/i, band: "trades" }, { re: /driver|truck|haul|cdl/i, band: "driver" }, { re: /line\s*lead|supervisor|foreman|coordinator|lead\b/i, band: "lead" }, ]; export function roleBand(role: string): RoleBand { const r = (role || "").trim(); if (!r) return "warehouse"; for (const b of RE_BANDS) if (b.re.test(r)) return b.band; return "warehouse"; } // TODO J — refine these. Each `scene` string lands directly in the // diffusion prompt. Tone target: a coordinator glances at the card // and recognizes the role from the photo before reading the role pill. // // Things that work well in SDXL Turbo at 8 steps: // - One concrete clothing item ("high-visibility yellow vest") // - One concrete prop ("hard hat hanging from belt", "tablet in hand") // - One blurred background element ("warehouse pallet aisle behind", // "factory machinery softly out of focus") // - Avoid: text/logos (rendered as scribble), specific brands, hands // holding tools (often distorts), full-body language ("standing", // "leaning") — model is trained on portrait crops. // // Each scene now bakes "monochrome black and white photography" into // the prompt so the model produces native B&W output rather than us // applying CSS grayscale post-hoc. SDXL Turbo handles B&W natively // with strong tonal range — better than desaturating a color render. export const SCENES: Record = { warehouse: { band: "warehouse", scene: "wearing a high-visibility safety vest over a t-shirt, hard hat visible, blurred warehouse pallet aisle behind, soft natural light, monochrome black and white photography, fine film grain, documentary portrait style", }, production: { band: "production", scene: "wearing a work shirt with safety glasses on forehead, blurred factory machinery softly out of focus behind, fluorescent overhead lighting, monochrome black and white photography, fine film grain, documentary portrait style", }, trades: { band: "trades", scene: "wearing a heavy-duty work shirt with rolled sleeves, blurred workshop tool wall behind, focused tungsten lighting, monochrome black and white photography, fine film grain, documentary portrait style", }, driver: { band: "driver", scene: "wearing a polo shirt, lanyard with ID badge visible, blurred truck cab or loading dock behind, daylight, monochrome black and white photography, fine film grain, documentary portrait style", }, lead: { band: "lead", scene: "wearing a button-down shirt, tablet held casually at chest level, blurred warehouse floor in soft focus behind, professional lighting, monochrome black and white photography, fine film grain, documentary portrait style", }, }; // v2 — baked B&W + 1024×1024 render canvas (4× pixels of v1). Larger // source means downsampling to a 40px avatar packs more detail per // displayed pixel, hiding the diffusion-y micro-textures that read as // "AI generated" at small sizes. Server route reads pool from // data/headshots_role_pool/{SCENES_VERSION}/... so v1 stays available // for rollback / A-B comparison. export const SCENES_VERSION = "v2"; // Default render dimensions used by both the on-demand /headshots/ // generate/:key route and the offline render_role_pool.py script. v1 // used 512²; v2 doubles to 1024² (linear 2× = 4× pixels = ~3× GPU // time on SDXL Turbo). export const FACE_RENDER_DIM = 1024;