generated from fahricansecer/boilerplate-be
Initial commit
This commit is contained in:
331
src/modules/skriptai/services/analysis.service.ts
Normal file
331
src/modules/skriptai/services/analysis.service.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user