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

This commit is contained in:
Harun CAN
2026-05-06 10:58:50 +02:00
parent a40619ef33
commit 3d36926fe9
4 changed files with 351 additions and 3 deletions
@@ -0,0 +1,48 @@
export class UploadedFileDto {
name: string;
content: string;
}
export class AnalyzeContentDto {
transcripts: UploadedFileDto[];
comments: UploadedFileDto[];
tone: string;
duration: string;
speakerName: string;
topicFocus: string;
targetAudience: string;
}
export class StrategyResultDto {
title: string;
psychologicalTheme?: string;
inspiredByGap?: string;
hook?: string;
thumbnailConcept?: string;
segments: {
type: string;
duration: string;
description: string;
keyPoints: string[];
neuroObjective?: string;
}[];
interviewQuestions: string[];
selectedComments: {
username?: string;
text: string;
insightValue?: string;
sourceFile?: string;
}[];
commercialAnalysis: {
suitableIndustries: string[];
brandSafetyScore: number;
suggestedBrands: string[];
monetizationPotential: string;
};
chartData?: { topic: string; emotionalArousal: number }[];
}
export class CommercialAnalysisDto {
title: string;
industries: string[];
}
@@ -0,0 +1,255 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { GoogleGenAI, Type } from '@google/genai';
import { AnalyzeContentDto, StrategyResultDto, CommercialAnalysisDto } from './dto/tube-strategist.dto';
@Injectable()
export class TubeStrategistService {
private readonly logger = new Logger(TubeStrategistService.name);
private ai: GoogleGenAI;
constructor(private readonly configService: ConfigService) {
const apiKey =
this.configService.get<string>('gemini.apiKey') ||
process.env.GOOGLE_API_KEY;
this.ai = new GoogleGenAI({ apiKey });
}
private async withRetry<T>(fn: () => Promise<T>, retries = 3, delay = 2000): Promise<T> {
try {
return await fn();
} catch (error: any) {
if (
retries > 0 &&
(error.status === 429 || error.toString().includes('500'))
) {
await new Promise((resolve) => setTimeout(resolve, delay));
return this.withRetry(fn, retries - 1, delay * 1.5);
}
throw error;
}
}
async analyzeContent(dto: AnalyzeContentDto): Promise<StrategyResultDto> {
const transcriptText = dto.transcripts
.map((t) => `[DOSYA: ${t.name}]\n${t.content.substring(0, 10000)}`)
.join('\n\n');
const commentText = dto.comments
.map((c) => `[DOSYA: ${c.name}]\n${c.content.substring(0, 5000)}`)
.join('\n\n');
const schema: any = {
type: Type.OBJECT,
properties: {
title: { type: Type.STRING },
psychologicalTheme: { type: Type.STRING },
inspiredByGap: { type: Type.STRING },
hook: { type: Type.STRING },
thumbnailConcept: { type: Type.STRING },
segments: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
type: { type: Type.STRING },
duration: {
type: Type.STRING,
description: 'Dakika damgası. Örn: 0-5. dk, 5-12. dk',
},
description: { type: Type.STRING },
keyPoints: { type: Type.ARRAY, items: { type: Type.STRING } },
neuroObjective: { type: Type.STRING },
},
},
},
interviewQuestions: { type: Type.ARRAY, items: { type: Type.STRING } },
selectedComments: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
username: { type: Type.STRING },
text: { type: Type.STRING },
insightValue: { type: Type.STRING },
sourceFile: {
type: Type.STRING,
description: 'Yorumun alındığı dosya adı',
},
},
},
},
commercialAnalysis: {
type: Type.OBJECT,
properties: {
suitableIndustries: { type: Type.ARRAY, items: { type: Type.STRING } },
brandSafetyScore: { type: Type.NUMBER },
suggestedBrands: { type: Type.ARRAY, items: { type: Type.STRING } },
monetizationPotential: { type: Type.STRING },
},
},
chartData: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
topic: { type: Type.STRING },
emotionalArousal: { type: Type.NUMBER },
},
},
},
},
required: [
'title',
'segments',
'interviewQuestions',
'commercialAnalysis',
'selectedComments',
],
};
const prompt = `
ROL: YouTube Strateji Uzmanı.
KREATİF TALİMAT: Mevcut verileri sadece kopyalama. Verilerdeki boşlukları kullanarak DAHA ÖNCE YAPILMAMIŞ, özgün ve sıradışı bir video fikri üret.
KURALLAR:
1. ÜSLUP VE RUH: Tüm içerik "${dto.tone}" tonunda olmalı.
2. ZAMANLAMA: Videonun süresi ${dto.duration}. Segmentleri matematiksel olarak böl (Örn: 0-5. dk, 5-12. dk, 12-20. dk vb.). Her segment 3-8 dk arası olsun.
3. SORULAR: Tam 20 tane derin, sarsıcı mülakat sorusu üret.
4. YORUMLAR: Seçtiğin her yorumun hangi dosyadan geldiğini 'sourceFile' alanına yaz.
VERİLER:
TRANSKRİPTLER: ${transcriptText}
YORUMLAR: ${commentText}
Detaylar: Kitle: ${dto.targetAudience}, Sunucu: ${dto.speakerName}, Konu: ${dto.topicFocus}
`;
return await this.withRetry(async () => {
const response = await this.ai.models.generateContent({
model: 'gemini-3-pro-preview',
contents: prompt,
config: {
responseMimeType: 'application/json',
responseSchema: schema,
thinkingConfig: { thinkingBudget: 16000 },
},
});
return JSON.parse(response.text || '{}');
});
}
async generateSeoReport(strategy: StrategyResultDto): Promise<any> {
const schema: any = {
type: Type.OBJECT,
properties: {
mainKeywords: { type: Type.ARRAY, items: { type: Type.STRING } },
tags: {
type: Type.ARRAY,
items: { type: Type.STRING },
description: 'Sadece kelimeler, başında # olmadan',
},
optimizedTitle: { type: Type.STRING },
metaDescription: { type: Type.STRING },
competitorGap: { type: Type.STRING },
alternativeTitles: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
title: { type: Type.STRING },
neuroScore: { type: Type.NUMBER },
psychologicalAngle: { type: Type.STRING },
},
},
},
},
};
const prompt = `
Video: "${strategy.title}".
Görev: Profesyonel YouTube SEO analizi yap.
ÖNEMLİ: 'tags' dizisine sadece virgülle ayrılacak saf kelimeleri yaz, başında # olmasın.
5 tane alternatif başlık üret ve her birine 0-100 arası başarı (neuro) puanı ver.
`;
const response = await this.ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: prompt,
config: { responseMimeType: 'application/json', responseSchema: schema },
});
return JSON.parse(response.text || '{}');
}
async generateNeuroReport(strategy: StrategyResultDto): Promise<any> {
const schema: any = {
type: Type.OBJECT,
properties: {
eyeTrackingFocus: { type: Type.STRING },
colorPsychology: { type: Type.STRING },
dopamineTriggers: { type: Type.ARRAY, items: { type: Type.STRING } },
limbicSystemGoal: { type: Type.STRING },
attentionSpans: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: { phase: { type: Type.STRING }, score: { type: Type.NUMBER } },
},
},
},
};
const response = await this.ai.models.generateContent({
model: 'gemini-3-pro-preview',
contents: `Video Konsepti: "${strategy.title}". Bu video için aşırı detaylı nöro-pazarlama analizi yap. İzleyicinin beyninde oluşacak dopamin döngüsünü kurgula.`,
config: { responseMimeType: 'application/json', responseSchema: schema },
});
return JSON.parse(response.text || '{}');
}
async generateMarketingReport(strategy: StrategyResultDto): Promise<any> {
const schema: any = {
type: Type.OBJECT,
properties: {
targetPersonas: { type: Type.ARRAY, items: { type: Type.STRING } },
socialMediaHooks: {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: { platform: { type: Type.STRING }, text: { type: Type.STRING } },
},
},
emailSubjectLines: { type: Type.ARRAY, items: { type: Type.STRING } },
viralHooks: { type: Type.ARRAY, items: { type: Type.STRING } },
},
};
const response = await this.ai.models.generateContent({
model: 'gemini-3-pro-preview',
contents: `Video Konsepti: "${strategy.title}". Bu videoyu viral yapmak için pazarlama stratejisi ve persona analizi üret.`,
config: { responseMimeType: 'application/json', responseSchema: schema },
});
return JSON.parse(response.text || '{}');
}
async generateDeepCommercialAnalysis(dto: CommercialAnalysisDto): Promise<any> {
const schema: any = {
type: Type.OBJECT,
properties: {
targetBrands: { type: Type.ARRAY, items: { type: Type.STRING } },
emailDraft: { type: Type.STRING },
estimatedRevenue: { type: Type.STRING },
negotiationTip: { type: Type.STRING },
},
};
const response = await this.ai.models.generateContent({
model: 'gemini-3-pro-preview',
contents: `Video: "${dto.title}". Sektörler: ${dto.industries.join(
', ',
)}. Türkiye'den 5 gerçek marka seç ve her birine özel mail taslağı oluştur.`,
config: {
responseMimeType: 'application/json',
responseSchema: schema,
},
});
return JSON.parse(response.text || '{}');
}
}
@@ -1,14 +1,19 @@
import { Controller, Post, Body, Get, Param, UseGuards, HttpCode, HttpStatus, Req } from '@nestjs/common'; import { Controller, Post, Body, Get, Param, UseGuards, HttpCode, HttpStatus, Req } from '@nestjs/common';
import { YoutubeToolsService } from './youtube-tools.service'; import { YoutubeToolsService } from './youtube-tools.service';
import { TubeStrategistService } from './tube-strategist.service';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/auth.guards'; import { JwtAuthGuard } from '../auth/guards/auth.guards';
import { AnalyzeContentDto, CommercialAnalysisDto, StrategyResultDto } from './dto/tube-strategist.dto';
@ApiTags('youtube-tools') @ApiTags('youtube-tools')
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Controller('youtube-tools') @Controller('youtube-tools')
export class YoutubeToolsController { export class YoutubeToolsController {
constructor(private readonly youtubeToolsService: YoutubeToolsService) {} constructor(
private readonly youtubeToolsService: YoutubeToolsService,
private readonly tubeStrategistService: TubeStrategistService
) {}
@Post('analyze') @Post('analyze')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@@ -65,4 +70,43 @@ export class YoutubeToolsController {
async generateSeoImage(@Body('prompt') prompt: string) { async generateSeoImage(@Body('prompt') prompt: string) {
return this.youtubeToolsService.generateSeoImage(prompt); return this.youtubeToolsService.generateSeoImage(prompt);
} }
// ==========================================
// TUBE STRATEGIST ENDPOINTS
// ==========================================
@Post('strategist/analyze')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Tube Strategist: Ana İçerik Stratejisi Analizi' })
async strategistAnalyze(@Body() dto: AnalyzeContentDto) {
return this.tubeStrategistService.analyzeContent(dto);
}
@Post('strategist/seo')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Tube Strategist: SEO Raporu Üretimi' })
async strategistSeo(@Body() dto: StrategyResultDto) {
return this.tubeStrategistService.generateSeoReport(dto);
}
@Post('strategist/neuro')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Tube Strategist: Nöro-Pazarlama Raporu Üretimi' })
async strategistNeuro(@Body() dto: StrategyResultDto) {
return this.tubeStrategistService.generateNeuroReport(dto);
}
@Post('strategist/marketing')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Tube Strategist: Pazarlama & Viral Raporu Üretimi' })
async strategistMarketing(@Body() dto: StrategyResultDto) {
return this.tubeStrategistService.generateMarketingReport(dto);
}
@Post('strategist/commercial')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Tube Strategist: Ticari Sponsorluk Taslağı Üretimi' })
async strategistCommercial(@Body() dto: CommercialAnalysisDto) {
return this.tubeStrategistService.generateDeepCommercialAnalysis(dto);
}
} }
@@ -1,12 +1,13 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { YoutubeToolsController } from './youtube-tools.controller'; import { YoutubeToolsController } from './youtube-tools.controller';
import { YoutubeToolsService } from './youtube-tools.service'; import { YoutubeToolsService } from './youtube-tools.service';
import { TubeStrategistService } from './tube-strategist.service';
import { GeminiModule } from '../gemini/gemini.module'; import { GeminiModule } from '../gemini/gemini.module';
@Module({ @Module({
imports: [GeminiModule], imports: [GeminiModule],
controllers: [YoutubeToolsController], controllers: [YoutubeToolsController],
providers: [YoutubeToolsService], providers: [YoutubeToolsService, TubeStrategistService],
exports: [YoutubeToolsService], exports: [YoutubeToolsService, TubeStrategistService],
}) })
export class YoutubeToolsModule {} export class YoutubeToolsModule {}