diff --git a/.env.template b/.env.template index 369ccb0..46c5859 100644 --- a/.env.template +++ b/.env.template @@ -12,6 +12,10 @@ MAXMIND_LICENSE_KEY= PORTKEY_API_KEY= GEMINI_API_KEY= +# DeepInfra API (optional) +# Used for background blur when "Background Blur" trait is selected +DEEPINFRA_API_KEY= + SENTRY_DSN= # Google Cloud Storage (optional) diff --git a/src/client/modules/Traits.ts b/src/client/modules/Traits.ts index a4533cb..5528049 100644 --- a/src/client/modules/Traits.ts +++ b/src/client/modules/Traits.ts @@ -98,4 +98,11 @@ export const TRAITS_OPTIONS: Trait[] = [ icon: '👨', category: 'age', }, + { + id: 'background-blur', + name: 'Background Blur', + description: 'Blur the background for professional focus', + icon: '🔘', + category: 'effects', + }, ]; diff --git a/src/server/config.ts b/src/server/config.ts index 46fefaa..610861e 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -16,5 +16,6 @@ export const settings = { SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY || '', GCS_BUCKET_NAME: process.env.GCS_BUCKET_NAME || '', GCS_PROJECT_ID: process.env.GCS_PROJECT_ID || '', + DEEPINFRA_API_KEY: process.env.DEEPINFRA_API_KEY || '', ALLOW_FACE_UPLOAD: process.env.ALLOW_FACE_UPLOAD === 'true', } as const; diff --git a/src/server/handlers/portrait-handler.ts b/src/server/handlers/portrait-handler.ts index a0db8f9..a0cea92 100644 --- a/src/server/handlers/portrait-handler.ts +++ b/src/server/handlers/portrait-handler.ts @@ -71,6 +71,23 @@ export const handlePortraitGeneration = async ( const imageData = await imageService.generate(prompt, resizedPath); + const hasBackgroundBlur = parsedTraits.includes('background-blur'); + + if (hasBackgroundBlur && imageData.b64_json) { + console.log('🔘 Background blur trait detected - applying blur...'); + try { + const blurredImageData = await imageService.blurBackground( + imageData.b64_json + ); + Object.assign(imageData, blurredImageData); + } catch (blurError) { + console.warn( + '⚠️ Background blur failed, returning original image:', + blurError + ); + } + } + res.json({ success: true, image: { diff --git a/src/server/services/image-service.ts b/src/server/services/image-service.ts index d5c1041..e35e8ce 100644 --- a/src/server/services/image-service.ts +++ b/src/server/services/image-service.ts @@ -15,10 +15,11 @@ const portkey = new Portkey({ const genAI = new GoogleGenAI({ apiKey: settings.GEMINI_API_KEY }); const IMAGE_GENERATION_TIMEOUT = 120000; +const BLUR_BACKGROUND_TIMEOUT = 30000; type ImageProvider = 'portkey' | 'google-genai' | 'flux'; -interface ImageData { +type ImageData = { url?: string; filename?: string; fileSize?: number; @@ -27,7 +28,7 @@ interface ImageData { height?: number; model?: string; provider?: string; -} +}; function getImageProvider(): ImageProvider { if (settings.PORTKEY_API_KEY) { @@ -306,6 +307,58 @@ class ImageService { throw new Error('Flux generation timeout'); } + async blurBackground(imageBase64: string): Promise { + if (!settings.DEEPINFRA_API_KEY) { + throw new Error('DEEPINFRA_API_KEY not configured'); + } + + console.log('🔘 Applying background blur via DeepInfra Bria API...'); + + const response = await Promise.race([ + fetch( + 'https://api.deepinfra.com/v1/inference/Bria/blur_background?version=0ObxGWB8', + { + method: 'POST', + headers: { + Authorization: `Bearer ${settings.DEEPINFRA_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + image: `data:image/png;base64,${imageBase64}`, + scale: 5, + }), + } + ), + this.createTimeoutPromise(BLUR_BACKGROUND_TIMEOUT), + ]); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `DeepInfra API error: ${response.status} ${response.statusText} - ${errorText}` + ); + } + + const data = (await response.json()) as { images: string[] }; + + if (!Array.isArray(data.images) || data.images.length < 1) { + throw new Error('No blurred image URL returned from DeepInfra API'); + } + + const imageUrl = data.images[0]; + const imageResponse = await fetch(imageUrl); + const imageBuffer = await imageResponse.arrayBuffer(); + const blurredBase64 = Buffer.from(imageBuffer).toString('base64'); + const fileData = await this.saveImageFile(blurredBase64, 'bria-blur'); + console.log('✅ Background blur applied successfully'); + return { + ...fileData, + b64_json: blurredBase64, + width: 1024, + height: 1024, + }; + } + private createTimeoutPromise(timeoutMs: number): Promise { return new Promise((_, reject) => { setTimeout(() => { diff --git a/src/server/services/prompt-service.ts b/src/server/services/prompt-service.ts index 75a8dd8..5a31e6a 100644 --- a/src/server/services/prompt-service.ts +++ b/src/server/services/prompt-service.ts @@ -43,6 +43,7 @@ const TRAIT_MAPPINGS = { beard: 'with beard', 'young-adult': 'young adult appearance', 'middle-aged': 'middle-aged appearance', + 'background-blur': '', }; const PROMPT_TEMPLATE = @@ -169,10 +170,7 @@ etc.`; }): string { const styleIds = Array.isArray(imageStyle) ? imageStyle : [imageStyle]; const stylePromptParts = styleIds - .map( - (id) => - STYLE_PROMPTS[id as keyof typeof STYLE_PROMPTS] - ) + .map((id) => STYLE_PROMPTS[id as keyof typeof STYLE_PROMPTS]) .filter(Boolean); const stylePrompt =