/** * Title Suggestion Service * Generates intelligent title suggestions based on note content */ import { createOpenAI } from '@ai-sdk/openai' import { generateText } from 'ai' import { LanguageDetectionService } from './language-detection.service' // Helper to get AI model for text generation function getTextGenerationModel() { const apiKey = process.env.OPENAI_API_KEY if (!apiKey) { throw new Error('OPENAI_API_KEY not configured for title generation') } const openai = createOpenAI({ apiKey }) return openai('gpt-4o-mini') } export interface TitleSuggestion { title: string confidence: number // 0-100 reasoning?: string // Why this title was suggested } export class TitleSuggestionService { private languageDetection: LanguageDetectionService constructor() { this.languageDetection = new LanguageDetectionService() } /** * Generate 3 title suggestions for a note * Uses interface language (from user settings) for prompts */ async generateSuggestions(noteContent: string): Promise { // Detect language of the note content const { language: contentLanguage } = await this.languageDetection.detectLanguage(noteContent) try { const model = getTextGenerationModel() // System prompt - explains what to do const systemPrompt = `You are an expert title generator for a note-taking application. Your task is to generate 3 distinct, engaging titles that capture the essence of the user's note. Requirements: - Generate EXACTLY 3 titles - Each title should be 3-8 words - Titles should be concise but descriptive - Each title should have a different style: 1. Direct/Summary style - What the note is about 2. Question style - Posing a question the note addresses 3. Creative/Metaphorical style - Using imagery or analogy - Return titles in the SAME LANGUAGE as the user's note - Be helpful and avoid generic titles like "My Note" or "Untitled" Output Format (JSON): { "suggestions": [ { "title": "...", "confidence": 85, "reasoning": "..." }, { "title": "...", "confidence": 80, "reasoning": "..." }, { "title": "...", "confidence": 75, "reasoning": "..." } ] }` // User prompt with language context const userPrompt = `Generate 3 title suggestions for this note: ${noteContent} Note language detected: ${contentLanguage} Respond with titles in ${contentLanguage} (same language as the note).` const { text } = await generateText({ model, system: systemPrompt, prompt: userPrompt, temperature: 0.7 }) // Parse JSON response const response = JSON.parse(text) if (!response.suggestions || !Array.isArray(response.suggestions)) { throw new Error('Invalid response format') } // Validate and limit to exactly 3 suggestions const suggestions = response.suggestions .slice(0, 3) .filter((s: any) => s.title && typeof s.title === 'string') .map((s: any) => ({ title: s.title.trim(), confidence: Math.min(100, Math.max(0, s.confidence || 75)), reasoning: s.reasoning || '' })) // Ensure we always return exactly 3 suggestions while (suggestions.length < 3) { suggestions.push({ title: this.generateFallbackTitle(noteContent, contentLanguage), confidence: 60, reasoning: 'Generated fallback title' }) } return suggestions } catch (error) { console.error('Error generating title suggestions:', error) // Fallback to simple extraction return this.generateFallbackSuggestions(noteContent, contentLanguage) } } /** * Generate fallback title from first few meaningful words */ private generateFallbackTitle(content: string, language: string): string { const words = content.split(/\s+/).filter(w => w.length > 3) if (words.length === 0) { return language === 'fr' ? 'Note sans titre' : 'Untitled Note' } // Take first 3-5 meaningful words const titleWords = words.slice(0, Math.min(5, words.length)) return titleWords.join(' ').charAt(0).toUpperCase() + titleWords.join(' ').slice(1) } /** * Generate fallback suggestions when AI fails */ private generateFallbackSuggestions(content: string, language: string): TitleSuggestion[] { const baseTitle = this.generateFallbackTitle(content, language) return [ { title: baseTitle, confidence: 70, reasoning: 'Extracted from note content' }, { title: language === 'fr' ? `Réflexions sur ${baseTitle.toLowerCase()}` : `Thoughts on ${baseTitle.toLowerCase()}`, confidence: 65, reasoning: 'Contextual variation' }, { title: language === 'fr' ? `${baseTitle}: Points clés` : `${baseTitle}: Key Points`, confidence: 60, reasoning: 'Summary style' } ] } /** * Save selected title to note metadata */ async recordFeedback( noteId: string, selectedTitle: string, allSuggestions: TitleSuggestion[] ): Promise { // This will be implemented in Phase 3 when we add feedback collection // For now, we just log it // TODO: In Phase 3, save to AiFeedback table for: // - Improving future suggestions // - Building user preference model // - Computing confidence scores } } // Singleton instance export const titleSuggestionService = new TitleSuggestionService()