From 3d36926fe9e2918d069c0bebf063a71f0278d7ae Mon Sep 17 00:00:00 2001 From: Harun CAN Date: Wed, 6 May 2026 10:58:50 +0200 Subject: [PATCH] main --- .../youtube-tools/dto/tube-strategist.dto.ts | 48 ++++ .../youtube-tools/tube-strategist.service.ts | 255 ++++++++++++++++++ .../youtube-tools/youtube-tools.controller.ts | 46 +++- .../youtube-tools/youtube-tools.module.ts | 5 +- 4 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 src/modules/youtube-tools/dto/tube-strategist.dto.ts create mode 100644 src/modules/youtube-tools/tube-strategist.service.ts diff --git a/src/modules/youtube-tools/dto/tube-strategist.dto.ts b/src/modules/youtube-tools/dto/tube-strategist.dto.ts new file mode 100644 index 0000000..ddc5d34 --- /dev/null +++ b/src/modules/youtube-tools/dto/tube-strategist.dto.ts @@ -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[]; +} diff --git a/src/modules/youtube-tools/tube-strategist.service.ts b/src/modules/youtube-tools/tube-strategist.service.ts new file mode 100644 index 0000000..9609713 --- /dev/null +++ b/src/modules/youtube-tools/tube-strategist.service.ts @@ -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('gemini.apiKey') || + process.env.GOOGLE_API_KEY; + this.ai = new GoogleGenAI({ apiKey }); + } + + private async withRetry(fn: () => Promise, retries = 3, delay = 2000): Promise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 || '{}'); + } +} diff --git a/src/modules/youtube-tools/youtube-tools.controller.ts b/src/modules/youtube-tools/youtube-tools.controller.ts index 627b3b8..9757885 100644 --- a/src/modules/youtube-tools/youtube-tools.controller.ts +++ b/src/modules/youtube-tools/youtube-tools.controller.ts @@ -1,14 +1,19 @@ import { Controller, Post, Body, Get, Param, UseGuards, HttpCode, HttpStatus, Req } from '@nestjs/common'; import { YoutubeToolsService } from './youtube-tools.service'; +import { TubeStrategistService } from './tube-strategist.service'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/auth.guards'; +import { AnalyzeContentDto, CommercialAnalysisDto, StrategyResultDto } from './dto/tube-strategist.dto'; @ApiTags('youtube-tools') @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Controller('youtube-tools') export class YoutubeToolsController { - constructor(private readonly youtubeToolsService: YoutubeToolsService) {} + constructor( + private readonly youtubeToolsService: YoutubeToolsService, + private readonly tubeStrategistService: TubeStrategistService + ) {} @Post('analyze') @HttpCode(HttpStatus.OK) @@ -65,4 +70,43 @@ export class YoutubeToolsController { async generateSeoImage(@Body('prompt') prompt: string) { 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); + } } diff --git a/src/modules/youtube-tools/youtube-tools.module.ts b/src/modules/youtube-tools/youtube-tools.module.ts index e4625ac..078fff2 100644 --- a/src/modules/youtube-tools/youtube-tools.module.ts +++ b/src/modules/youtube-tools/youtube-tools.module.ts @@ -1,12 +1,13 @@ import { Module } from '@nestjs/common'; import { YoutubeToolsController } from './youtube-tools.controller'; import { YoutubeToolsService } from './youtube-tools.service'; +import { TubeStrategistService } from './tube-strategist.service'; import { GeminiModule } from '../gemini/gemini.module'; @Module({ imports: [GeminiModule], controllers: [YoutubeToolsController], - providers: [YoutubeToolsService], - exports: [YoutubeToolsService], + providers: [YoutubeToolsService, TubeStrategistService], + exports: [YoutubeToolsService, TubeStrategistService], }) export class YoutubeToolsModule {}