generated from fahricansecer/boilerplate-be
@@ -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 {}
|
||||||
|
|||||||
Reference in New Issue
Block a user