/** * @fileoverview A more intelligent, local, rule-based algorithm for summarizing chats. * This acts as a "primitive AI" for summarization, running entirely in the browser. */ import type { Message, User } from './types'; export interface LocalSummary { participants: string[]; topic: string; summaryPoints: string[]; } // Simple list of common "stop words" to ignore when finding topics. const STOP_WORDS_EN = new Set(['a', 'an', 'the', 'is', 'are', 'was', 'were', 'in', 'on', 'at', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'and', 'but', 'or', 'so', 'to', 'of', 'for', 'with', 'about', 'as', 'that', 'this', 'what', 'when', 'where', 'why', 'how', 'do', 'does', 'did']); const STOP_WORDS_AR = new Set(['أنا', 'أنت', 'هو', 'هي', 'نحن', 'هم', 'و', 'أو', 'في', 'على', 'من', 'عن', 'ما', 'ماذا', 'كيف', 'متى', 'أين', 'هل', 'كان', 'يكون', 'قال', 'قالت', 'هذا', 'هذه', 'ذلك', 'تلك']); // Keywords that indicate an important message const KEYWORD_SCORES = { // English 'agree': 5, 'decision': 5, 'plan': 5, 'important': 5, 'because': 4, 'question': 4, 'next step': 5, 'finally': 4, 'conclusion': 5, 'i will': 3, 'we should': 3, // Arabic 'موافقة': 5, 'قرار': 5, 'خطة': 5, 'مهم': 5, 'لأن': 4, 'سؤال': 4, 'الخطوة التالية': 5, 'أخيرا': 4, 'خلاصة': 5, 'سأقوم': 3, 'يجب علينا': 3, 'اتفقنا': 5, }; interface ScoredMessage { message: Message; score: number; } /** * Scores a message based on its content to determine its importance. * @param message The message to score. * @param previousMessage The message that came before it. * @returns A numerical score. */ function getMessageScore(message: Message, previousMessage?: Message): number { if (!message.text || message.isSystemMessage) return 0; let score = 0; const text = message.text.toLowerCase(); // 1. Keyword scoring for (const keyword in KEYWORD_SCORES) { if (text.includes(keyword)) { score += KEYWORD_SCORES[keyword as keyof typeof KEYWORD_SCORES]; } } // 2. Question scoring if (text.includes('?')) { score += 5; // Questions are very important } // 3. Answer scoring (if it follows a question) if (previousMessage && previousMessage.text?.includes('?')) { score += 6; // Answers are even more important } // 4. Length scoring (longer messages are likely more detailed) if (text.length > 100) { score += 3; } else if (text.length > 50) { score += 2; } return score; } /** * Generates a more intelligent summary of a chat history locally. * @param messages The array of messages in the chat. * @param currentUser The currently logged-in user. * @returns A LocalSummary object. */ export function generateLocalSummary(messages: Message[], currentUser: User): LocalSummary { if (messages.length === 0) { return { participants: [], topic: 'No conversation yet', summaryPoints: ['The chat is empty.'], }; } // 1. Identify Participants const participantSet = new Set(); messages.forEach(msg => { if (msg.sender === currentUser.uid) { participantSet.add(currentUser.displayName); } else { participantSet.add(msg.senderDisplayName); } }); const participants = Array.from(participantSet); // 2. Score all messages for importance const scoredMessages: ScoredMessage[] = []; for (let i = 0; i < messages.length; i++) { const message = messages[i]; const previousMessage = i > 0 ? messages[i - 1] : undefined; scoredMessages.push({ message, score: getMessageScore(message, previousMessage), }); } // Sort messages by score to find the most important ones const sortedScoredMessages = scoredMessages.sort((a, b) => b.score - a.score); // 3. Determine Topic from the most important messages const wordCounts = new Map(); const stopWords = new Set([...STOP_WORDS_EN, ...STOP_WORDS_AR]); // Analyze top 5 most important messages for topic words sortedScoredMessages.slice(0, 5).forEach(({ message }) => { if (!message.text) return; const words = message.text.toLowerCase().split(/\s+/); words.forEach(word => { const cleanWord = word.replace(/[.,!?"'()؟]/g, ''); if (cleanWord.length > 3 && !stopWords.has(cleanWord)) { wordCounts.set(cleanWord, (wordCounts.get(cleanWord) || 0) + 1); } }); }); const sortedWords = Array.from(wordCounts.entries()).sort((a, b) => b[1] - a[1]); const topic = sortedWords.length > 0 ? sortedWords.slice(0, 2).map(entry => entry[0]).join(' & ') : 'General discussion'; // 4. Generate Summary Points from the top scored messages const summaryPoints: string[] = []; const usedMessageIds = new Set(); // Take top 3-4 most important messages to form the summary for (const scoredMsg of sortedScoredMessages) { if (summaryPoints.length >= 4) break; if (scoredMsg.score > 0 && scoredMsg.message.id && !usedMessageIds.has(scoredMsg.message.id)) { const point = `${scoredMsg.message.senderDisplayName} discussed: "${scoredMsg.message.text!.substring(0, 50)}...".`; summaryPoints.push(point); usedMessageIds.add(scoredMsg.message.id); } } // Fallback if no "important" messages were found if (summaryPoints.length === 0 && messages.length > 0) { const firstMessage = messages[0]; const lastMessage = messages[messages.length - 1]; summaryPoints.push(`Conversation started by ${firstMessage.senderDisplayName}.`); if (lastMessage.id !== firstMessage.id) { summaryPoints.push(`The last message was from ${lastMessage.senderDisplayName}.`); } } return { participants, topic, summaryPoints, }; }