generated from fahricansecer/boilerplate-be
+1
-1
@@ -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),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<
|
||||
|
||||
Reference in New Issue
Block a user