Files
SkriptAI-be/src/modules/skriptai/services/analysis.service.ts
2026-03-23 01:59:17 +03:00

332 lines
9.5 KiB
TypeScript

import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../../database/prisma.service';
import { GeminiService } from '../../gemini/gemini.service';
import { NeuroAnalysisResult, YoutubeAudit } from '../types/skriptai.types';
/**
* AnalysisService
*
* Service for AI-powered content analysis including:
* - Neuro Marketing Analysis
* - YouTube Audit
* - Commercial Brief Generation
*
* TR: AI destekli içerik analizi servisi (Nöro Pazarlama, YouTube Denetimi, Ticari Brief).
* EN: Service for AI-powered content analysis.
*/
@Injectable()
export class AnalysisService {
private readonly logger = new Logger(AnalysisService.name);
constructor(
private readonly prisma: PrismaService,
private readonly gemini: GeminiService,
) {}
/**
* Perform Neuro Marketing Analysis on a script
*
* @param projectId - Project ID
* @returns Neuro analysis result
*/
async analyzeNeuroMarketing(projectId: string): Promise<NeuroAnalysisResult> {
const project = await this.prisma.scriptProject.findUnique({
where: { id: projectId },
include: { segments: { orderBy: { sortOrder: 'asc' } } },
});
if (!project) {
throw new NotFoundException(`Project with ID ${projectId} not found`);
}
const fullScript = project.segments
.map((s) => s.narratorScript)
.join('\n\n');
const prompt = `Analyze this script using Consumer Neuroscience and Cialdini's 6 Principles of Persuasion.
Script:
${fullScript.substring(0, 10000)}
Provide:
1. Engagement Score (0-100): How well does it capture attention?
2. Dopamine Score (0-100): Does it create anticipation & reward loops?
3. Clarity Score (0-100): Is the message clear and memorable?
4. Cialdini's Persuasion Metrics (0-100 each):
- Reciprocity: Does it give value first?
- Scarcity: Does it create urgency?
- Authority: Does it establish credibility?
- Consistency: Does it align with viewer beliefs?
- Liking: Is the tone likeable/relatable?
- Social Proof: Does it reference others' actions?
5. Neuro Metrics:
- Attention Hooks: Moments that grab attention
- Emotional Triggers: Points that evoke emotion
- Memory Anchors: Unique/memorable elements
- Action Drivers: CTAs or challenges
6. Suggestions: 3-5 specific improvements
Return JSON: {
"engagementScore": number,
"dopamineScore": number,
"clarityScore": number,
"persuasionMetrics": {
"reciprocity": number,
"scarcity": number,
"authority": number,
"consistency": number,
"liking": number,
"socialProof": number
},
"neuroMetrics": {
"attentionHooks": ["..."],
"emotionalTriggers": ["..."],
"memoryAnchors": ["..."],
"actionDrivers": ["..."]
},
"suggestions": ["..."]
}`;
const resp = await this.gemini.generateJSON<NeuroAnalysisResult>(
prompt,
'{ engagementScore, dopamineScore, clarityScore, persuasionMetrics, neuroMetrics, suggestions }',
);
// Save to project
await this.prisma.scriptProject.update({
where: { id: projectId },
data: { neuroAnalysis: resp.data as any },
});
return resp.data;
}
/**
* Perform YouTube Audit
*
* @param projectId - Project ID
* @returns YouTube audit result
*/
async performYoutubeAudit(projectId: string): Promise<YoutubeAudit> {
const project = await this.prisma.scriptProject.findUnique({
where: { id: projectId },
include: { segments: { orderBy: { sortOrder: 'asc' } } },
});
if (!project) {
throw new NotFoundException(`Project with ID ${projectId} not found`);
}
const fullScript = project.segments
.map((s) => s.narratorScript)
.join('\n\n');
const prompt = `Perform a YouTube Algorithm Audit on this script for topic "${project.topic}".
Script:
${fullScript.substring(0, 10000)}
Analyze and provide:
1. Hook Score (0-100): First 10 seconds effectiveness
2. Pacing Score (0-100): Does it maintain momentum?
3. Viral Potential (0-100): Shareability factor
4. Retention Analysis: 3-5 potential drop-off points with time, issue, suggestion, severity (High/Medium/Low)
5. Thumbnail Concepts: 3 high-CTR thumbnail ideas with:
- Concept name
- Visual description
- Text overlay
- Color psychology
- Emotion target
- AI generation prompt
6. Title Options: 5 clickable titles (curiosity gap, numbers, power words)
7. Community Post: Engaging post to tease the video
8. Pinned Comment: Engagement-driving first comment
9. SEO Description: Optimized video description with keywords
10. Keywords: 10 relevant search keywords
Return JSON: {
"hookScore": number,
"pacingScore": number,
"viralPotential": number,
"retentionAnalysis": [{ "time": "0:30", "issue": "...", "suggestion": "...", "severity": "High" }],
"thumbnails": [{ "conceptName": "...", "visualDescription": "...", "textOverlay": "...", "colorPsychology": "...", "emotionTarget": "...", "aiPrompt": "..." }],
"titles": ["..."],
"communityPost": "...",
"pinnedComment": "...",
"description": "...",
"keywords": ["..."]
}`;
const resp = await this.gemini.generateJSON<YoutubeAudit>(
prompt,
'{ hookScore, pacingScore, viralPotential, retentionAnalysis, thumbnails, titles, communityPost, pinnedComment, description, keywords }',
);
// Save to project
await this.prisma.scriptProject.update({
where: { id: projectId },
data: { youtubeAudit: resp.data as any },
});
return resp.data;
}
/**
* Generate Commercial Brief (Sponsorship Analysis)
*
* @param projectId - Project ID
* @returns Commercial brief with sponsor suggestions
*/
async generateCommercialBrief(projectId: string) {
const project = await this.prisma.scriptProject.findUnique({
where: { id: projectId },
include: { segments: { orderBy: { sortOrder: 'asc' } } },
});
if (!project) {
throw new NotFoundException(`Project with ID ${projectId} not found`);
}
const fullScript = project.segments
.map((s) => s.narratorScript)
.join('\n\n');
const prompt = `Analyze this content for commercial viability and sponsorship opportunities.
Topic: "${project.topic}"
Audience: ${project.targetAudience.join(', ')}
Content Type: ${project.contentType}
Script excerpt:
${fullScript.substring(0, 5000)}
Provide:
1. Viability Score (1-10 scale as string): "8/10"
2. Viability Reason: Why this content is commercially viable
3. Sponsor Suggestions (3-5 potential sponsors):
- Company name
- Industry
- Match reason (why this sponsor fits)
- Email draft (outreach template)
Return JSON: {
"viabilityScore": "8/10",
"viabilityReason": "...",
"sponsors": [
{
"name": "Company Name",
"industry": "Tech/Finance/etc",
"matchReason": "...",
"emailDraft": "..."
}
]
}`;
const resp = await this.gemini.generateJSON<{
viabilityScore: string;
viabilityReason: string;
sponsors: {
name: string;
industry: string;
matchReason: string;
emailDraft: string;
}[];
}>(prompt, '{ viabilityScore, viabilityReason, sponsors }');
// Save to project
await this.prisma.scriptProject.update({
where: { id: projectId },
data: { commercialBrief: resp.data as any },
});
return resp.data;
}
/**
* Generate thumbnails using external image service
*
* @param prompt - Image generation prompt
* @returns Generated image URL
*/
/**
* Generate thumbnails using external image service
* Applies "Nano Banana" prompt enrichment for high-quality results.
*
* @param prompt - Image generation prompt
* @returns Generated image URL
*/
async generateThumbnailImage(prompt: string): Promise<string> {
// Quality boosters (Nano Banana style)
const QUALITY_BOOSTERS = [
'highly detailed',
'8k resolution',
'professional photography',
'studio lighting',
'sharp focus',
'cinematic composition',
'vibrant colors',
'masterpiece',
];
// Enrich prompt with Nano Banana logic
const enrichedPrompt = `${prompt}, ${QUALITY_BOOSTERS.join(', ')}. CRITICAL OBJECTIVE: The result MUST achieve a perfect 10/10 score. Clarity: 10/10. Professionalism: 10/10.`;
// Use Real Nano Banana (Gemini Imagen)
return await this.gemini.generateImage(enrichedPrompt);
}
/**
* Generate visual assets for a project
*
* @param projectId - Project ID
* @param count - Number of assets to generate
* @returns Generated visual assets
*/
async generateVisualAssets(projectId: string, count: number = 5) {
const project = await this.prisma.scriptProject.findUnique({
where: { id: projectId },
});
if (!project) {
throw new NotFoundException(`Project with ID ${projectId} not found`);
}
const prompt = `Generate ${count} specific, simple visual keywords for an image generator about "${project.topic}".
Format: "subject action context style". Keep it English, concise, no special chars.
Return JSON array of strings.`;
const resp = await this.gemini.generateJSON<string[]>(
prompt,
'["keyword1", "keyword2", ...]',
);
// Generate image URLs and save to database
const assets = await Promise.all(
resp.data.map(async (keyword) => {
const url = await this.generateThumbnailImage(keyword);
return this.prisma.visualAsset.create({
data: {
projectId,
url,
desc: keyword,
selected: true,
},
});
}),
);
return assets;
}
}