File size: 6,136 Bytes
cc276cc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | /**
* @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<string>();
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<string, number>();
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<string>();
// 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,
};
}
|