Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions src/client/modules/Traits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
];
1 change: 1 addition & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
17 changes: 17 additions & 0 deletions src/server/handlers/portrait-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
57 changes: 55 additions & 2 deletions src/server/services/image-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,7 +28,7 @@ interface ImageData {
height?: number;
model?: string;
provider?: string;
}
};

function getImageProvider(): ImageProvider {
if (settings.PORTKEY_API_KEY) {
Expand Down Expand Up @@ -306,6 +307,58 @@ class ImageService {
throw new Error('Flux generation timeout');
}

async blurBackground(imageBase64: string): Promise<ImageData> {
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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check whether data.images is really and array and having item in the first index?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

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<never> {
return new Promise((_, reject) => {
setTimeout(() => {
Expand Down
6 changes: 2 additions & 4 deletions src/server/services/prompt-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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 =
Expand Down