first (part 3: src directory)
Deploy Iddaai Backend / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-04-16 15:12:27 +03:00
parent 2f0b85a0c7
commit 182f4aae16
125 changed files with 22552 additions and 0 deletions
@@ -0,0 +1,109 @@
import { Injectable, Logger } from '@nestjs/common';
import { GeminiService } from '../gemini/gemini.service';
import { PredictionCardDto } from './dto/prediction-card.dto';
const SYSTEM_PROMPT = `Sen profesyonel bir spor analisti ve sosyal medya içerik üreticisisin.
Verilen maç tahmin verisini kullanarak kısa, etkili ve ilgi çekici sosyal medya postları yazıyorsun.
KURALLAR:
- Türkçe yaz
- Maximum 250 karakter (X/Twitter uyumlu)
- Emoji kullan ama abartma (2-4 emoji yeterli)
- Skor tahminini vurgula
- Güven yüzdesini belirt
- İlgili hashtag'leri ekle (#PremierLeague, #SüperLig vb.)
- KESİNLİKLE "kesin kazanır", "garanti" gibi ifadeler KULLANMA
- "Tahminimiz", "Beklentimiz", "Analizimiz" gibi ifadeler kullan
- Farklı maçlar için farklı tarzda yaz, tekdüze olma
- Son satıra her zaman hashtag'leri koy`;
@Injectable()
export class CaptionGeneratorService {
private readonly logger = new Logger(CaptionGeneratorService.name);
constructor(private readonly geminiService: GeminiService) {}
/**
* Generate a social media caption for a match prediction using Gemini AI.
*/
async generateCaption(card: PredictionCardDto): Promise<string> {
if (!this.geminiService.isAvailable()) {
this.logger.warn('Gemini not available, using template caption');
return this.generateFallbackCaption(card);
}
const prompt = this.buildPrompt(card);
try {
const { text } = await this.geminiService.generateText(prompt, {
systemPrompt: SYSTEM_PROMPT,
temperature: 0.8,
maxTokens: 300,
});
// Ensure hashtags are present
const caption = this.ensureHashtags(text, card);
this.logger.log(
`Caption generated for ${card.homeTeam} vs ${card.awayTeam}`,
);
return caption;
} catch (error) {
this.logger.error('Gemini caption generation failed', error);
return this.generateFallbackCaption(card);
}
}
private buildPrompt(card: PredictionCardDto): string {
const topPicksText = card.topPicks
.map(
(p, i) =>
`${i + 1}. ${p.market} (${p.marketEn}) — ${p.pick} — Güven: %${p.confidence} — Oran: ${p.odds}`,
)
.join('\n');
return `Aşağıdaki maç tahmin verisini kullanarak bir sosyal medya postu oluştur:
MAÇ: ${card.homeTeam} vs ${card.awayTeam}
LİG: ${card.leagueName}
TARİH: ${card.matchDate}
İLK YARI SKOR TAHMİNİ: ${card.htScore}
MAÇ SONU SKOR TAHMİNİ: ${card.ftScore}
SKOR GÜVEN: %${card.scoreConfidence}
RİSK SEVİYESİ: ${card.riskLevel}
EN İYİ TAHMİNLER:
${topPicksText}
Sadece post metnini yaz, başka hiçbir şey ekleme.`;
}
private ensureHashtags(text: string, card: PredictionCardDto): string {
// If no hashtags in text, add them
if (!text.includes('#')) {
const leagueTag = card.leagueName
.replace(/\s+/g, '')
.replace(/[^a-zA-Z0-9üöçşğıİÜÖÇŞĞ]/g, '');
const homeTag = card.homeTeam.replace(/\s+/g, '');
const awayTag = card.awayTeam.replace(/\s+/g, '');
text += `\n\n#${leagueTag} #${homeTag} #${awayTag}`;
}
return text.trim();
}
/**
* Fallback caption when Gemini is not available.
*/
private generateFallbackCaption(card: PredictionCardDto): string {
const topPick = card.topPicks[0];
const leagueTag = card.leagueName
.replace(/\s+/g, '')
.replace(/[^a-zA-Z0-9üöçşğıİÜÖÇŞĞ]/g, '');
return `${card.homeTeam} vs ${card.awayTeam}
🎯 Tahminimiz: ${card.ftScore} (İY: ${card.htScore})
📊 Güven: %${card.scoreConfidence}
${topPick ? `🔥 ${topPick.market}: ${topPick.pick} (%${topPick.confidence})` : ''}
#${leagueTag} #SuggestBet #Bahis`.trim();
}
}