// Writing Styles Service - Extended with 15+ personality tones // Path: src/modules/content/services/writing-styles.service.ts import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '../../../database/prisma.service'; import { ContentLanguage } from '@prisma/client'; export interface WritingStyleConfig { name: string; description?: string; tone: WritingTone; voice: 'first_person' | 'second_person' | 'third_person'; vocabulary: 'simple' | 'intermediate' | 'advanced' | 'technical'; sentenceLength: 'short' | 'medium' | 'long' | 'varied'; emojiUsage: 'none' | 'minimal' | 'moderate' | 'heavy'; hashtagStyle: 'none' | 'minimal' | 'topic_based' | 'trending'; structurePreference: 'paragraphs' | 'bullets' | 'numbered' | 'mixed'; engagementStyle: 'educational' | 'storytelling' | 'data_driven' | 'conversational' | 'provocative'; signatureElements?: string[]; avoidPhrases?: string[]; preferredPhrases?: string[]; language?: ContentLanguage; } // 15+ Writing Tones export type WritingTone = | 'storyteller' // Narrative-driven, emotionally engaging | 'narrator' // Documentary style, observational | 'sarcastic' // Witty, ironic, sharp humor | 'inspirational' // Motivational, uplifting | 'professional' // Business-like, polished | 'casual' // Relaxed, conversational | 'friendly' // Warm, approachable | 'authoritative' // Expert, commanding | 'playful' // Fun, light-hearted | 'provocative' // Controversial, challenging | 'empathetic' // Understanding, supportive | 'analytical' // Data-focused, logical | 'humorous' // Comedy-driven, entertaining | 'minimalist' // Concise, direct | 'dramatic' // Intense, emotional | 'educational'; // Teaching, informative export const WRITING_TONES: Record = { storyteller: { emoji: '📖', description: 'Narrative-driven, weaves stories to make points memorable', promptHint: 'Write like a master storyteller, using narrative techniques, character arcs, and emotional hooks', }, narrator: { emoji: '🎙️', description: 'Documentary-style, observational and descriptive', promptHint: 'Write like a documentary narrator, observing and describing with clarity and depth', }, sarcastic: { emoji: '😏', description: 'Witty, ironic, with sharp humor', promptHint: 'Write with sarcastic wit, using irony and clever observations that make readers think', }, inspirational: { emoji: '✨', description: 'Motivational and uplifting content', promptHint: 'Write to inspire and motivate, using powerful language that uplifts the reader', }, professional: { emoji: '💼', description: 'Business-like, polished and authoritative', promptHint: 'Write in a professional, business-appropriate tone with credibility and expertise', }, casual: { emoji: '😊', description: 'Relaxed and conversational', promptHint: 'Write casually, like talking to a friend over coffee', }, friendly: { emoji: '🤝', description: 'Warm, approachable and welcoming', promptHint: 'Write in a warm, friendly manner that makes readers feel comfortable and welcomed', }, authoritative: { emoji: '👔', description: 'Expert voice with commanding presence', promptHint: 'Write with authority and expertise, establishing credibility and trust', }, playful: { emoji: '🎉', description: 'Fun, light-hearted and entertaining', promptHint: 'Write playfully with humor, wordplay, and a light touch', }, provocative: { emoji: '🔥', description: 'Controversial, thought-provoking', promptHint: 'Write to challenge assumptions and provoke thought, with bold statements', }, empathetic: { emoji: '💙', description: 'Understanding and supportive', promptHint: 'Write with empathy, acknowledging struggles and offering understanding', }, analytical: { emoji: '📊', description: 'Data-focused and logical', promptHint: 'Write analytically, using data, logic, and structured arguments', }, humorous: { emoji: '😂', description: 'Comedy-driven, entertaining', promptHint: 'Write with humor, jokes, and entertainment value', }, minimalist: { emoji: '🎯', description: 'Concise, direct, no fluff', promptHint: 'Write minimally, every word counts, eliminate all unnecessary words', }, dramatic: { emoji: '🎭', description: 'Intense, emotional, theatrical', promptHint: 'Write dramatically with intensity, building tension and emotional impact', }, educational: { emoji: '📚', description: 'Teaching-focused, informative', promptHint: 'Write to educate, explain concepts clearly with examples and structure', }, }; // Preset Writing Styles (combining tone + other settings) export const PRESET_STYLES: Record = { master_storyteller: { name: 'Master Storyteller', description: 'Narrative-driven, emotionally engaging content with story arcs', tone: 'storyteller', voice: 'first_person', vocabulary: 'intermediate', sentenceLength: 'varied', emojiUsage: 'minimal', hashtagStyle: 'none', structurePreference: 'paragraphs', engagementStyle: 'storytelling', preferredPhrases: ['Let me tell you...', 'Picture this:', 'Here\'s what happened:'], }, sharp_sarcastic: { name: 'Sharp & Sarcastic', description: 'Witty observations with ironic humor', tone: 'sarcastic', voice: 'first_person', vocabulary: 'intermediate', sentenceLength: 'short', emojiUsage: 'minimal', hashtagStyle: 'none', structurePreference: 'paragraphs', engagementStyle: 'provocative', preferredPhrases: ['Oh sure,', 'Because obviously,', 'Shocking, I know.'], avoidPhrases: ['To be honest', 'Actually'], }, documentary_narrator: { name: 'Documentary Narrator', description: 'Observational, descriptive, cinematic', tone: 'narrator', voice: 'third_person', vocabulary: 'advanced', sentenceLength: 'long', emojiUsage: 'none', hashtagStyle: 'none', structurePreference: 'paragraphs', engagementStyle: 'storytelling', }, motivational_coach: { name: 'Motivational Coach', description: 'Inspiring, action-oriented, empowering', tone: 'inspirational', voice: 'second_person', vocabulary: 'simple', sentenceLength: 'short', emojiUsage: 'moderate', hashtagStyle: 'minimal', structurePreference: 'bullets', engagementStyle: 'conversational', preferredPhrases: ['You can do this!', 'Here\'s the truth:', 'Your time is now.'], }, data_analyst: { name: 'Data Analyst', description: 'Facts, figures, and logical conclusions', tone: 'analytical', voice: 'first_person', vocabulary: 'technical', sentenceLength: 'medium', emojiUsage: 'none', hashtagStyle: 'topic_based', structurePreference: 'numbered', engagementStyle: 'data_driven', preferredPhrases: ['The data shows:', 'Research indicates:', 'Here are the numbers:'], }, friendly_teacher: { name: 'Friendly Teacher', description: 'Educational, patient, encouraging', tone: 'educational', voice: 'second_person', vocabulary: 'simple', sentenceLength: 'short', emojiUsage: 'moderate', hashtagStyle: 'topic_based', structurePreference: 'numbered', engagementStyle: 'educational', preferredPhrases: ['Let me explain:', 'Think of it this way:', 'Here\'s a simple example:'], }, corporate_executive: { name: 'Corporate Executive', description: 'Professional, strategic, leadership-focused', tone: 'professional', voice: 'first_person', vocabulary: 'advanced', sentenceLength: 'medium', emojiUsage: 'none', hashtagStyle: 'topic_based', structurePreference: 'mixed', engagementStyle: 'data_driven', }, stand_up_comedian: { name: 'Stand-up Comedian', description: 'Funny, self-deprecating, observational humor', tone: 'humorous', voice: 'first_person', vocabulary: 'simple', sentenceLength: 'varied', emojiUsage: 'moderate', hashtagStyle: 'none', structurePreference: 'paragraphs', engagementStyle: 'conversational', }, thought_provocateur: { name: 'Thought Provocateur', description: 'Bold statements, contrarian views, challenges assumptions', tone: 'provocative', voice: 'first_person', vocabulary: 'advanced', sentenceLength: 'varied', emojiUsage: 'none', hashtagStyle: 'none', structurePreference: 'paragraphs', engagementStyle: 'provocative', preferredPhrases: ['Unpopular opinion:', 'Hot take:', 'Everyone is wrong about:'], }, zen_minimalist: { name: 'Zen Minimalist', description: 'Every word matters, no fluff, pure clarity', tone: 'minimalist', voice: 'second_person', vocabulary: 'simple', sentenceLength: 'short', emojiUsage: 'none', hashtagStyle: 'none', structurePreference: 'bullets', engagementStyle: 'educational', }, }; @Injectable() export class WritingStylesService { private readonly logger = new Logger(WritingStylesService.name); constructor(private readonly prisma: PrismaService) { } /** * Get all available tones */ getTones(): typeof WRITING_TONES { return WRITING_TONES; } /** * Get all preset styles */ getPresets(): typeof PRESET_STYLES { return PRESET_STYLES; } /** * Create a custom writing style */ async create(userId: string, config: WritingStyleConfig) { return this.prisma.writingStyle.create({ data: { userId, name: config.name, type: 'CUSTOM', tone: config.tone, vocabulary: Array.isArray(config.vocabulary) ? config.vocabulary : [config.vocabulary], sentenceLength: config.sentenceLength, emojiUsage: config.emojiUsage, hashtagStyle: config.hashtagStyle, structurePreference: config.structurePreference, engagementStyle: config.engagementStyle, signatureElements: config.signatureElements || [], avoidWords: config.avoidPhrases || [], preferredPhrases: config.preferredPhrases || [], isDefault: false, }, }); } /** * Get writing style by ID */ async getById(id: string) { // Check if it's a preset if (id.startsWith('preset-')) { const presetKey = id.replace('preset-', ''); return PRESET_STYLES[presetKey] ? { id, ...PRESET_STYLES[presetKey] } : null; } return this.prisma.writingStyle.findUnique({ where: { id }, }); } /** * Get user's default writing style */ async getDefault(userId: string) { const userDefault = await this.prisma.writingStyle.findFirst({ where: { userId, isDefault: true }, }); return userDefault || { id: 'preset-master_storyteller', ...PRESET_STYLES.master_storyteller }; } /** * Get all user's writing styles (custom + presets) */ async getAll(userId: string) { const customStyles = await this.prisma.writingStyle.findMany({ where: { userId }, orderBy: { createdAt: 'desc' }, }); const presets = Object.entries(PRESET_STYLES).map(([key, style]) => ({ id: `preset-${key}`, ...style, isPreset: true, })); return [...customStyles, ...presets]; } /** * Set default writing style */ async setDefault(userId: string, styleId: string) { await this.prisma.writingStyle.updateMany({ where: { userId, isDefault: true }, data: { isDefault: false }, }); return this.prisma.writingStyle.update({ where: { id: styleId }, data: { isDefault: true }, }); } /** * Generate AI prompt for style */ generatePrompt(style: WritingStyleConfig, language?: ContentLanguage): string { const toneInfo = WRITING_TONES[style.tone]; let prompt = ` WRITING STYLE INSTRUCTIONS: ${toneInfo.promptHint} STYLE PARAMETERS: - Tone: ${style.tone} (${toneInfo.description}) - Voice: ${style.voice.replace('_', ' ')} - Vocabulary: ${style.vocabulary} - Sentence length: ${style.sentenceLength} - Emoji usage: ${style.emojiUsage} - Structure: ${style.structurePreference} - Engagement: ${style.engagementStyle} `; if (style.preferredPhrases?.length) { prompt += `\n- Use phrases like: ${style.preferredPhrases.join(', ')}`; } if (style.avoidPhrases?.length) { prompt += `\n- Avoid phrases: ${style.avoidPhrases.join(', ')}`; } if (language) { prompt += `\n\nWRITE CONTENT IN: ${language}`; } return prompt.trim(); } /** * Apply writing style transformations */ applyStyle(content: string, style: WritingStyleConfig): string { let styledContent = content; if (style.emojiUsage === 'none') { styledContent = styledContent.replace(/[\u{1F300}-\u{1F9FF}]/gu, ''); } if (style.structurePreference === 'bullets') { styledContent = this.convertToBullets(styledContent); } else if (style.structurePreference === 'numbered') { styledContent = this.convertToNumbered(styledContent); } return styledContent; } private convertToBullets(content: string): string { const sentences = content.split(/(?<=[.!?])\s+/); return sentences.map((s) => `• ${s.trim()}`).join('\n'); } private convertToNumbered(content: string): string { const sentences = content.split(/(?<=[.!?])\s+/); return sentences.map((s, i) => `${i + 1}. ${s.trim()}`).join('\n'); } }