main
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-04-12 15:14:49 +02:00
parent 23eed2982c
commit 5a52370fe2
12 changed files with 152 additions and 64 deletions
+1 -1
View File
@@ -118,7 +118,7 @@ import {
useFactory: (configService: ConfigService) => ({
fallbackLanguage: configService.get('i18n.fallbackLanguage', 'en'),
loaderOptions: {
path: path.join(__dirname, '..', 'i18n'),
path: path.join(__dirname, 'i18n'),
watch: configService.get('app.isDevelopment', true),
},
}),
+77 -39
View File
@@ -242,6 +242,11 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
/**
* Gemini Image Generation API ile görsel üret.
* 3 katmanlı fallback mimarisi:
* 1) gemini-2.5-flash-image (Nano Banana — hızlı, stabil)
* 2) gemini-3.1-flash-image-preview (Nano Banana 2 — en yeni)
* 3) Imagen 4 Fast (generateImages API)
*
* Raspberry Pi 5 bellek koruması için buffer olarak döner.
*
* @param prompt - İngilizce görsel açıklaması
@@ -256,63 +261,66 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
throw new Error('Gemini AI is not available. Check your configuration.');
}
const imageModel = this.configService.get<string>(
'gemini.imageModel',
'gemini-2.0-flash-preview-image-generation',
);
// Güncel model sıralaması (Nisan 2026):
// - gemini-2.5-flash-image: Nano Banana — stabil, hızlı
// - gemini-3.1-flash-image-preview: Nano Banana 2 — en yeni, yüksek kalite
const primaryModel = 'gemini-2.5-flash-image';
const fallbackModel = 'gemini-3.1-flash-image-preview';
try {
this.logger.debug(`🎨 Görsel üretiliyor: "${prompt.substring(0, 80)}..." [${aspectRatio}]`);
const enhancedPrompt = `Generate a high-quality image for this description: ${prompt}. Style: photorealistic, cinematic lighting, detailed. Aspect ratio: ${aspectRatio}.`;
const enhancedPrompt = `Generate a high-quality image for this description: ${prompt}. Style: photorealistic, cinematic lighting, detailed. Aspect ratio: ${aspectRatio}. IMPORTANT: Generate only the image, no text response needed.`;
// 1) First try the stable Imagen-4 Fast API
// ── Katman 1: gemini-2.5-flash-image (Nano Banana) ──
try {
this.logger.debug(`🔄 Katman 1: ${primaryModel} deneniyor...`);
const result = await this.tryGenerateContentImage(primaryModel, enhancedPrompt);
if (result) {
this.logger.log(`✅ Görsel üretildi (${primaryModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`);
return result;
}
} catch (err1: any) {
this.logger.warn(`⚠️ ${primaryModel} başarısız: ${err1.message?.substring(0, 120)}`);
}
// ── Katman 2: gemini-3.1-flash-image-preview (Nano Banana 2) ──
try {
this.logger.debug(`🔄 Katman 2: ${fallbackModel} deneniyor...`);
const result = await this.tryGenerateContentImage(fallbackModel, enhancedPrompt);
if (result) {
this.logger.log(`✅ Görsel üretildi (${fallbackModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`);
return result;
}
} catch (err2: any) {
this.logger.warn(`⚠️ ${fallbackModel} başarısız: ${err2.message?.substring(0, 120)}`);
}
// ── Katman 3: Imagen 4 Fast (generateImages API) ──
try {
this.logger.debug(`🔄 Katman 3: Imagen 4 Fast deneniyor...`);
const response = await this.client!.models.generateImages({
model: 'imagen-4.0-fast-generate-001',
prompt: enhancedPrompt,
config: {
numberOfImages: 1,
aspectRatio: aspectRatio,
outputMimeType: 'image/jpeg',
personGeneration: 'ALLOW_ALL' as any
}
numberOfImages: 1,
aspectRatio: aspectRatio,
outputMimeType: 'image/jpeg',
personGeneration: 'ALLOW_ALL' as any,
},
});
if (response.generatedImages?.[0]?.image?.imageBytes) {
const buffer = Buffer.from(response.generatedImages[0].image.imageBytes, 'base64');
const mimeType = 'image/jpeg';
this.logger.log(`✅ Görsel üretildi (Imagen): ${(buffer.length / 1024).toFixed(1)} KB [${mimeType}]`);
this.logger.log(`✅ Görsel üretildi (Imagen 4): ${(buffer.length / 1024).toFixed(1)} KB`);
return { buffer, mimeType };
}
} catch (imagenError: any) {
this.logger.warn(`Imagen API error, falling back to generateContent... ${imagenError.message}`);
} catch (err3: any) {
this.logger.warn(`⚠️ Imagen 4 başarısız: ${err3.message?.substring(0, 120)}`);
}
// 2) Fallback to Gemini Flash image modalities (experimental feature)
const response = await this.client!.models.generateContent({
model: imageModel,
contents: enhancedPrompt,
config: {
responseModalities: ['IMAGE', 'TEXT'],
},
});
// Gemini image generation modeli, inlineData olarak görsel döner
const candidate = response.candidates?.[0];
const imagePart = candidate?.content?.parts?.find(
(p: any) => p.inlineData?.mimeType?.startsWith('image/'),
);
if (imagePart?.inlineData?.data) {
const buffer = Buffer.from(imagePart.inlineData.data, 'base64');
const mimeType = imagePart.inlineData.mimeType || 'image/png';
this.logger.log(`✅ Görsel üretildi (Flash): ${(buffer.length / 1024).toFixed(1)} KB [${mimeType}]`);
return { buffer, mimeType };
}
this.logger.warn('Gemini görsel üretemedi — response içinde image part bulunamadı');
this.logger.error('❌ Tüm görsel üretim katmanları başarısız oldu');
return null;
} catch (error) {
this.logger.error(`Gemini görsel üretim hatası: ${error instanceof Error ? error.message : error}`);
@@ -320,6 +328,36 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
}
}
/**
* generateContent API ile görsel üretim denemesi.
* responseModalities: ['IMAGE', 'TEXT'] kullanarak inlineData içinden resim çıkarır.
*/
private async tryGenerateContentImage(
model: string,
prompt: string,
): Promise<{ buffer: Buffer; mimeType: string } | null> {
const response = await this.client!.models.generateContent({
model,
contents: prompt,
config: {
responseModalities: ['IMAGE', 'TEXT'],
},
});
const candidate = response.candidates?.[0];
const imagePart = candidate?.content?.parts?.find(
(p: any) => p.inlineData?.mimeType?.startsWith('image/'),
);
if (imagePart?.inlineData?.data) {
const buffer = Buffer.from(imagePart.inlineData.data, 'base64');
const mimeType = imagePart.inlineData.mimeType || 'image/png';
return { buffer, mimeType };
}
return null;
}
/**
* Sahne bazlı görsel üret — visualPrompt ve video stili kullanarak.
*
+1 -1
View File
@@ -8,7 +8,7 @@ import {
ApiResponse,
createSuccessResponse,
} from '../../common/types/api-response.type';
import { User } from '@prisma/client/wasm';
import { User } from '@prisma/client';
import { plainToInstance } from 'class-transformer';
import { UserResponseDto } from './dto/user.dto';
+1 -1
View File
@@ -3,7 +3,7 @@ import * as bcrypt from 'bcrypt';
import { PrismaService } from '../../database/prisma.service';
import { BaseService } from '../../common/base';
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
import { User } from '@prisma/client/wasm';
import { User } from '@prisma/client';
@Injectable()
export class UsersService extends BaseService<