generated from fahricansecer/boilerplate-be
@@ -269,10 +269,17 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
const fallbackModel = 'gemini-3.1-flash-image-preview';
|
||||
|
||||
try {
|
||||
this.logger.log(`🎨 Görsel üretiliyor: "${prompt.substring(0, 100)}..." [${aspectRatio}]`);
|
||||
this.logger.log(
|
||||
`🎨 Görsel üretiliyor: "${prompt.substring(0, 100)}..." [${aspectRatio}]`,
|
||||
);
|
||||
|
||||
// En-boy oranına göre yönlendirmeyi zorla
|
||||
const orientation = aspectRatio === '9:16' ? '(VERTICAL / PORTRAIT)' : aspectRatio === '16:9' ? '(HORIZONTAL / LANDSCAPE)' : '(SQUARE)';
|
||||
const orientation =
|
||||
aspectRatio === '9:16'
|
||||
? '(VERTICAL / PORTRAIT)'
|
||||
: aspectRatio === '16:9'
|
||||
? '(HORIZONTAL / LANDSCAPE)'
|
||||
: '(SQUARE)';
|
||||
// Gemini modelleri ana konunun (subject) prompt'un en başında olmasını tercih eder.
|
||||
// Jenerik stil kelimelerini sonuna ekliyoruz ki ana konu (prompt) kaybolmasın.
|
||||
const enhancedPrompt = isIllustration
|
||||
@@ -283,23 +290,36 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
for (let attempt = 1; attempt <= 2; attempt++) {
|
||||
try {
|
||||
this.logger.log(`🔄 Katman 1 (deneme ${attempt}/2): ${primaryModel}`);
|
||||
const result = await this.tryGenerateContentImage(primaryModel, enhancedPrompt);
|
||||
const result = await this.tryGenerateContentImage(
|
||||
primaryModel,
|
||||
enhancedPrompt,
|
||||
);
|
||||
if (result && result.buffer.length > 0) {
|
||||
this.logger.log(`✅ Görsel üretildi (${primaryModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`);
|
||||
this.logger.log(
|
||||
`✅ Görsel üretildi (${primaryModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`,
|
||||
);
|
||||
return { buffer: result.buffer, mimeType: result.mimeType };
|
||||
}
|
||||
|
||||
|
||||
const reason = result?.errorReason || 'null response';
|
||||
this.logger.warn(`⚠️ ${primaryModel} deneme ${attempt}: görsel döndürmedi (${reason})`);
|
||||
|
||||
if (['IMAGE_OTHER', 'SAFETY', 'PROHIBITED_CONTENT'].includes(reason)) {
|
||||
this.logger.warn(`🚫 Güvenlik/Politika filtresi tetiklendi (${reason}). Denemeler iptal ediliyor.`);
|
||||
this.logger.warn(
|
||||
`⚠️ ${primaryModel} deneme ${attempt}: görsel döndürmedi (${reason})`,
|
||||
);
|
||||
|
||||
if (
|
||||
['IMAGE_OTHER', 'SAFETY', 'PROHIBITED_CONTENT'].includes(reason)
|
||||
) {
|
||||
this.logger.warn(
|
||||
`🚫 Güvenlik/Politika filtresi tetiklendi (${reason}). Denemeler iptal ediliyor.`,
|
||||
);
|
||||
break; // Fail fast for safety blocks
|
||||
}
|
||||
|
||||
|
||||
if (attempt < 2) await this.sleep(2000);
|
||||
} catch (err1: any) {
|
||||
this.logger.warn(`⚠️ ${primaryModel} deneme ${attempt} hata: ${err1.message?.substring(0, 200)}`);
|
||||
this.logger.warn(
|
||||
`⚠️ ${primaryModel} deneme ${attempt} hata: ${err1.message?.substring(0, 200)}`,
|
||||
);
|
||||
if (attempt < 2) await this.sleep(2000);
|
||||
}
|
||||
}
|
||||
@@ -307,18 +327,33 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
// ── Katman 2: gemini-3.1-flash-image-preview (Nano Banana 2) ──
|
||||
try {
|
||||
this.logger.log(`🔄 Katman 2: ${fallbackModel}`);
|
||||
const result = await this.tryGenerateContentImage(fallbackModel, enhancedPrompt);
|
||||
const result = await this.tryGenerateContentImage(
|
||||
fallbackModel,
|
||||
enhancedPrompt,
|
||||
);
|
||||
if (result && result.buffer.length > 0) {
|
||||
this.logger.log(`✅ Görsel üretildi (${fallbackModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`);
|
||||
this.logger.log(
|
||||
`✅ Görsel üretildi (${fallbackModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`,
|
||||
);
|
||||
return { buffer: result.buffer, mimeType: result.mimeType };
|
||||
}
|
||||
this.logger.warn(`⚠️ ${fallbackModel}: görsel döndürmedi (${result?.errorReason || 'null response'})`);
|
||||
|
||||
if (['IMAGE_OTHER', 'SAFETY', 'PROHIBITED_CONTENT'].includes(result?.errorReason || '')) {
|
||||
this.logger.warn(`🚫 Katman 2 Güvenlik/Politika filtresi tetiklendi. Katman 3'e geçiliyor.`);
|
||||
this.logger.warn(
|
||||
`⚠️ ${fallbackModel}: görsel döndürmedi (${result?.errorReason || 'null response'})`,
|
||||
);
|
||||
|
||||
if (
|
||||
['IMAGE_OTHER', 'SAFETY', 'PROHIBITED_CONTENT'].includes(
|
||||
result?.errorReason || '',
|
||||
)
|
||||
) {
|
||||
this.logger.warn(
|
||||
`🚫 Katman 2 Güvenlik/Politika filtresi tetiklendi. Katman 3'e geçiliyor.`,
|
||||
);
|
||||
}
|
||||
} catch (err2: any) {
|
||||
this.logger.warn(`⚠️ ${fallbackModel} hata: ${err2.message?.substring(0, 200)}`);
|
||||
this.logger.warn(
|
||||
`⚠️ ${fallbackModel} hata: ${err2.message?.substring(0, 200)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ── Katman 3: Imagen 4 Fast (generateImages API) ──
|
||||
@@ -335,20 +370,31 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
});
|
||||
|
||||
if (response.generatedImages?.[0]?.image?.imageBytes) {
|
||||
const buffer = Buffer.from(response.generatedImages[0].image.imageBytes, 'base64');
|
||||
const buffer = Buffer.from(
|
||||
response.generatedImages[0].image.imageBytes,
|
||||
'base64',
|
||||
);
|
||||
const mimeType = 'image/jpeg';
|
||||
this.logger.log(`✅ Görsel üretildi (Imagen 4): ${(buffer.length / 1024).toFixed(1)} KB`);
|
||||
this.logger.log(
|
||||
`✅ Görsel üretildi (Imagen 4): ${(buffer.length / 1024).toFixed(1)} KB`,
|
||||
);
|
||||
return { buffer, mimeType };
|
||||
}
|
||||
this.logger.warn(`⚠️ Imagen 4: görsel döndürmedi. Üretilen görsel sayısı: ${response.generatedImages?.length || 0}`);
|
||||
this.logger.warn(
|
||||
`⚠️ Imagen 4: görsel döndürmedi. Üretilen görsel sayısı: ${response.generatedImages?.length || 0}`,
|
||||
);
|
||||
} catch (err3: any) {
|
||||
this.logger.warn(`⚠️ Imagen 4 hata: ${err3.message?.substring(0, 200)}`);
|
||||
this.logger.warn(
|
||||
`⚠️ Imagen 4 hata: ${err3.message?.substring(0, 200)}`,
|
||||
);
|
||||
}
|
||||
|
||||
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}`);
|
||||
this.logger.error(
|
||||
`Gemini görsel üretim hatası: ${error instanceof Error ? error.message : error}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -360,7 +406,11 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
private async tryGenerateContentImage(
|
||||
model: string,
|
||||
prompt: string,
|
||||
): Promise<{ buffer: Buffer; mimeType: string; errorReason?: string } | null> {
|
||||
): Promise<{
|
||||
buffer: Buffer;
|
||||
mimeType: string;
|
||||
errorReason?: string;
|
||||
} | null> {
|
||||
const response = await this.client!.models.generateContent({
|
||||
model,
|
||||
contents: prompt,
|
||||
@@ -374,12 +424,18 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
// Safety filter veya boş yanıt kontrolü
|
||||
if (!candidate?.content?.parts || candidate.content.parts.length === 0) {
|
||||
const finishReason = candidate?.finishReason || 'UNKNOWN';
|
||||
this.logger.warn(`⚠️ ${model}: boş yanıt (finishReason: ${finishReason})`);
|
||||
return { buffer: Buffer.from([]), mimeType: '', errorReason: finishReason };
|
||||
this.logger.warn(
|
||||
`⚠️ ${model}: boş yanıt (finishReason: ${finishReason})`,
|
||||
);
|
||||
return {
|
||||
buffer: Buffer.from([]),
|
||||
mimeType: '',
|
||||
errorReason: finishReason,
|
||||
};
|
||||
}
|
||||
|
||||
const imagePart = candidate.content.parts.find(
|
||||
(p: any) => p.inlineData?.mimeType?.startsWith('image/'),
|
||||
const imagePart = candidate.content.parts.find((p: any) =>
|
||||
p.inlineData?.mimeType?.startsWith('image/'),
|
||||
);
|
||||
|
||||
if (imagePart?.inlineData?.data) {
|
||||
@@ -391,16 +447,26 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
// Text-only response geldi (görsel yok)
|
||||
const textParts = candidate.content.parts.filter((p: any) => p.text);
|
||||
if (textParts.length > 0) {
|
||||
this.logger.warn(`⚠️ ${model}: sadece text döndü, görsel yok. Text: "${textParts[0].text?.substring(0, 100)}"`);
|
||||
return { buffer: Buffer.from([]), mimeType: '', errorReason: 'TEXT_ONLY' };
|
||||
this.logger.warn(
|
||||
`⚠️ ${model}: sadece text döndü, görsel yok. Text: "${textParts[0].text?.substring(0, 100)}"`,
|
||||
);
|
||||
return {
|
||||
buffer: Buffer.from([]),
|
||||
mimeType: '',
|
||||
errorReason: 'TEXT_ONLY',
|
||||
};
|
||||
}
|
||||
|
||||
return { buffer: Buffer.from([]), mimeType: '', errorReason: 'NO_IMAGE_DATA' };
|
||||
return {
|
||||
buffer: Buffer.from([]),
|
||||
mimeType: '',
|
||||
errorReason: 'NO_IMAGE_DATA',
|
||||
};
|
||||
}
|
||||
|
||||
/** Basit uyku fonksiyonu — retry aralarında kullanılır */
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,4 +501,3 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
return this.generateImage(prompt, '16:9');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user