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

This commit is contained in:
Harun CAN
2026-05-01 00:45:33 +02:00
parent 1775ac1aa1
commit 5184db32cc
33 changed files with 1852 additions and 713 deletions
+98 -33
View File
@@ -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');
}
}