generated from fahricansecer/boilerplate-be
332 lines
9.5 KiB
TypeScript
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;
|
|
}
|
|
}
|