PageSourceSearch

https://dynamic-salmiakki-095693.netlify.app/phase3-context-integration.js?v=1781614395

js dynamic-salmiakki-095693.netlify.app collected 2026-10-03 10:40:45 UTC 16,246 bytes, 430 lines download raw bytes

1/**
2 * Phase 3: Unified Frontend-Backend Context Integration
3 * Connects frontend context system with backend context engine for unified reference resolution
4 */
5
6class UnifiedContextManager {
7    constructor() {
8        this.backendSessionId = null;
9        this.frontendContext = null;
10        this.isBackendSyncEnabled = true;
11        this.apiEndpoint = '/api/chat'; // Use main chat endpoint with Wikipedia processing
12        this.lastSyncTimestamp = 0;
13        this.entitySyncEnabled = true;
14        
15        console.log('[Phase3] Initializing unified context manager...');
16        this.initialize();
17    }
18    
19    /**
20     * Initialize unified context system
21     */
22    async initialize() {
23        // Initialize frontend context if not already done
24        if (!window.conversationContext) {
25            window.conversationContext = new ConversationContext();
26        }
27        this.frontendContext = window.conversationContext;
28        
29        // Generate consistent session ID for backend
30        this.backendSessionId = this.generateBackendSessionId();
31        
32        // Sync initial session with backend
33        await this.syncWithBackend();
34        
35        console.log(`[Phase3] Unified context initialized with session: ${this.backendSessionId}`);
36    }
37    
38    /**
39     * Generate backend-compatible session ID
40     */
41    generateBackendSessionId() {
42        // Use frontend session ID if available, otherwise generate new one
43        if (this.frontendContext && this.frontendContext.sessionId) {
44            return `frontend_${this.frontendContext.sessionId}`;
45        }
46        return `unified_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
47    }
48    
49    /**
50     * Sync frontend context with backend
51     */
52    async syncWithBackend() {
53        // Disable backend sync during chat conversations to avoid conflicts
54        // Entity synchronization now happens through the main chat stream
55        console.log('[Phase3] Backend sync disabled during conversations - using chat stream for entity sync');
56        return null;
57    }
58    
59    /**
60     * Build messages array for backend context processing
61     */
62    buildMessagesForBackend() {
63        const messages = [
64            {
65                role: 'system',
66                content: 'You are a helpful assistant that provides information about celebrities and movies.'
67            }
68        ];
69        
70        // Add conversation history from frontend
71        if (this.frontendContext && this.frontendContext.conversationHistory) {
72            this.frontendContext.conversationHistory.forEach(msg => {
73                messages.push({
74                    role: msg.role,
75                    content: msg.content
76                });
77            });
78        }
79        
80        return messages;
81    }
82    
83    /**
84     * Update frontend context with backend insights
85     */
86    updateFrontendFromBackend(backendResult) {
87        if (!backendResult || !backendResult.context) return;
88        
89        const backendContext = backendResult.context;
90        
91        // Sync entity information
92        if (backendContext.mentionedEntities) {
93            backendContext.mentionedEntities.forEach(entity => {
94                // Update frontend entity memory with backend insights
95                const key = entity.text.toLowerCase();
96                if (this.frontendContext.entityMemory.has(key)) {
97                    const existing = this.frontendContext.entityMemory.get(key);
98                    existing.mentions = entity.mentions || existing.mentions;
99                    existing.type = entity.type || existing.type;
100                    existing.lastMention = Date.now();
101                } else {
102                    // Add new entity from backend
103                    this.frontendContext.entityMemory.set(key, {
104                        text: entity.text,
105                        type: entity.type,
106                        mentions: entity.mentions || 1,
107                        firstMention: Date.now(),
108                        lastMention: Date.now(),
109                        contexts: []
110                    });
111                }
112            });
113        }
114        
115        // Sync topic information
116        if (backendContext.activeTopics) {
117            backendContext.activeTopics.forEach(topic => {
118                this.frontendContext.topicMemory.add(topic);
119            });
120        }
121        
122        console.log('[Phase3] Frontend updated with backend context insights');
123    }
124    
125    /**
126     * Extract entities from AI response content to preserve them in memory
127     */
128    extractEntitiesFromResponse(responseContent) {
129        if (!responseContent || !this.entitySyncEnabled) return;
130        
131        // Extract person names (capitalized words that could be names)
132        const personPattern = /\b([A-Z][a-z]+ [A-Z][a-z]+(?:\s[A-Z][a-z]+)*)\b/g;
133        const personMatches = responseContent.match(personPattern) || [];
134        
135        // Extract movie/show titles (quoted text or titles after "in", "from", etc.)
136        const titlePattern = /"([^"]+)"|(?:in|from|movie|film|show)\s+([A-Z][^.!?]*)/gi;
137        const titleMatches = [];
138        let match;
139        while ((match = titlePattern.exec(responseContent)) !== null) {
140            titleMatches.push(match[1] || match[2]);
141        }
142        
143        // Add extracted entities to memory
144        [...personMatches, ...titleMatches].forEach(entityText => {
145            if (entityText && entityText.length > 2) {
146                const key = entityText.toLowerCase();
147                const entityType = personMatches.includes(entityText) ? 'person' : 'title';
148                
149                if (this.frontendContext.entityMemory.has(key)) {
150                    const existing = this.frontendContext.entityMemory.get(key);
151                    existing.mentions += 1;
152                    existing.lastMention = Date.now();
153                } else {
154                    this.frontendContext.entityMemory.set(key, {
155                        text: entityText,
156                        type: entityType,
157                        mentions: 1,
158                        firstMention: Date.now(),
159                        lastMention: Date.now(),
160                        contexts: []
161                    });
162                }
163            }
164        });
165        
166        console.log(`[Phase3] Extracted ${personMatches.length} persons and ${titleMatches.length} titles from AI response`);
167    }
168    
169    /**
170     * Enhanced reference resolution using backend context
171     */
172    async resolveReferencesWithBackend(query) {
173        console.log(`[Phase3] Resolving references for: "${query}"`);
174        
175        // First try frontend resolution
176        let resolvedQuery = this.frontendContext.resolveReferences(query);
177        
178        // If no change, try backend-enhanced resolution
179        if (resolvedQuery === query && this.isBackendSyncEnabled) {
180            try {
181                // Send query to backend for context-enhanced resolution
182                const messages = this.buildMessagesForBackend();
183                messages.push({
184                    role: 'user',
185                    content: query
186                });
187                
188                const response = await fetch(this.apiEndpoint, {
189                    method: 'POST',
190                    headers: {
191                        'Content-Type': 'application/json',
192                        'X-Session-ID': this.backendSessionId
193                    },
194                    body: JSON.stringify({ messages })
195                });
196                
197                if (response.ok) {
198                    const result = await response.json();
199                    
200                    // Check if backend provided better context
201                    if (result.context && result.context.contextSummary) {
202                        // Use backend context to enhance resolution
203                        resolvedQuery = this.enhanceQueryWithBacken
203dContext(query, result.context);
204                    }
205                }
206            } catch (error) {
207                console.warn('[Phase3] Backend reference resolution failed:', error);
208            }
209        }
210        
211        if (resolvedQuery !== query) {
212            console.log(`[Phase3] Resolved: "${query}" -> "${resolvedQuery}"`);
213        }
214        
215        return resolvedQuery;
216    }
217    
218    /**
219     * Enhance query with backend context insights
220     */
221    enhanceQueryWithBackendContext(query, backendContext) {
222        let enhancedQuery = query;
223        
224        // Extract entity information from backend context summary
225        if (backendContext.contextSummary) {
226            const summary = backendContext.contextSummary;
227            
228            // Look for person entities mentioned in context
229            const personMatch = summary.match(/([A-Z][a-z]+ [A-Z][a-z]+) \(person/);
230            if (personMatch) {
231                const personName = personMatch[1];
232                
233                // Replace pronouns with person name
234                enhancedQuery = enhancedQuery.replace(/\bhis\b/gi, `${personName}'s`);
235                enhancedQuery = enhancedQuery.replace(/\bher\b/gi, `${personName}'s`);
236                enhancedQuery = enhancedQuery.replace(/\bhim\b/gi, personName);
237                enhancedQuery = enhancedQuery.replace(/\bshe\b/gi, personName);
238            }
239        }
240        
241        return enhancedQuery;
242    }
243    
244    /**
245     * Add message to both frontend and backend context
246     */
247    async addMessage(role, content, metadata = {}) {
248        // Add to frontend context
249        this.frontendContext.addMessage(role, content, metadata);
250        
251        // Sync with backend periodically
252        if (role === 'user') {
253            await this.syncWithBackend();
254        }
255        
256        console.log(`[Phase3] Added ${role} message to unified context`);
257    }
258    
259    /**
260     * Get unified context for query
261     */
262    async getContextForQuery(query) {
263        // Get frontend context
264        const frontendContext = this.frontendContext.getContextForQuery(query);
265        
266        // Enhance with backend context if available
267        const backendSync = await this.syncWithBackend();
268        
269        return {
270            ...frontendContext,
271            backendSessionId: this.backendSessionId,
272            backendContext: backendSync ? backendSync.context : null,
273            unified: true
274        };
275    }
276    
277    /**
278     * Get session statistics from both systems
279     */
280    getSessionStats() {
281        const frontendStats = {
282            conversationLength: this.frontendContext.conversationHistory.length,
283            entityCount: this.frontendContext.entityMemory.size,
284            topicCount: this.frontendContext.topicMemory.size
285        };
286        
287        return {
288            sessionId: this.backendSessionId,
289            frontend: frontendStats,
290            unified: true
291        };
292    }
293}
294
295// Enhanced sendMessage function for Phase 3
296let originalSendMessage = null;
297let unifiedContextManager = null;
298
299async function phase3EnhancedSendMessage(text) {
300    console.log('[Phase3] Enhanced sendMessage called with:', text);
301    
302    // Handle undefined/null text input
303    if (!text || typeof text !== 'string' || text.trim() === '') {
304        console.warn('[Phase3] Invalid text input, skipping contextual processing');
305        if (originalSendMessage) {
306            return originalSendMessage.call(this, text);
307        }
308        return;
309    }
310    
311    // Initialize unified context manager if not done
312    if (!unifiedContextManager) {
313        unifiedContextManager = new UnifiedContextManager();
314        await unifiedContextManager.initialize();
315    }
316    
317    try {
318        // Periodic cleanup to prevent stale context carryover
319        if (Math.random() < 0.1) { // 10% chance to run cleanup
320            console.log('[Phase3] Running periodic context cleanup');
321            unifiedContextManager.frontendContext.clearStaleContext();
322        }
323        
324        // Extract entities from user query first
325        unifiedContextManager.extractEntitiesFromResponse(text);
326        
327        // DISABLED: Frontend reference resolution causes issues like "his wives" -> "Albert Einsteinss"
328        // The backend OpenAI entity extraction is much more sophisticated and handles this correctly
329        console.log('[Phase3] Skipping frontend reference resolution - using backend AI resolution instead');
330        
331        // Add user message to unified context
332        await unifiedContextManager.addMessage('user', text);
333        
334        // Get context info for display
335        const contextInfo = unifiedContextManager.frontendContext.getContextForQuery(text);
336        console.log('[Phase3] Context info:', contextInfo);
337        
338        // Display Phase 3 status
339        const statusElement = document.getElementById('phase3-status');
340        if (statusElement) {
341            const entityCount = unifiedContextManager.frontendContext.entityMemory.size;
342            const topicCount = unifiedContextManager.frontendContext.topicMemory.size;
343            statusElement.innerHTML = `Phase 3 Active | Session: ${unifiedContextManager.backendSessionId} | Entities: ${entityCount} | Topics: ${topicCount}`;
344        }
345        
346    } catch (error) {
347        console.error('[Phase3] Enhanced sendMessage error:', error);
348    }
349    
350    // Call original sendMessage with potentially resolved text
351    if (originalSendMessage) {
352        return originalSendMessage.call(this, text);
353    }
354}
355
356// Function to enhance AI responses with unified context
357async function phase3EnhanceAIResponse(response) {
358    if (unifiedContextManager && response) {
359        // Add AI response to unified context
360        await unifiedContextManager.addMessage('assistant', response);
361        console.log('[Phase3] Added AI response to unified context');
362        
363        // Extract entities from AI response
364        unifiedContextManager.extractEntitiesFromResponse(response);
365    }
366    return response;
367}
368
369// Initialize Phase 3 contextual integration
370function initializePhase3Integration() {
371    console.log('[Phase3] Initializing unified frontend-backend context integration...');
372    
373    // Wait for main.js to load and sendMessage to be available
374    const checkForSendMessage = () => {
375        if (typeof window.sendMessage === 'function') {
376            console.log('[Phase3] Found sendMessage function, enhancing with unified context...');
377            
378            // Store original function
379            originalSendMessage = window.sendMessage;
380            
381            // Replace with Phase 3 enhanced version
382            window.sendMessage = phase3EnhancedSendMessage;
383            
384            console.log('[Phase3] Unified context integration complete!');
385            
386            /*
387            // Add Phase 3 status indicator
388            const statusDiv = document.createElement('div');
389            statusDiv.id = 'phase3-status';
390            statusDiv.innerHTML = '🚀 Phase 3: Unified Context Active';
391            statusDiv.style.cssText = `
392                position: fixed;
393                top: 10px;
394                right: 10px;
395                background: #e8f0ff;
396                color: #0066cc;
397                padding: 5px 10px;
398                border-radius: 15px;
399                font-size: 12px;
400                z-index: 1000;
401                border: 1px solid #99ccff;
402                opacity: 0.9;
403                font-weight: bold;
404            `;
405            document.body.appendChild(statusDiv);
406            */
407            
408            // Remove old status indicators
409            const oldStatus = document.getElementById('context-status');
410            if (oldStatus) oldStatus.remove();
411            
412        } else {
413            setTimeout(checkForSendMessage, 100);
414        }
415    };
416    
417    checkForSendMessage();
418}
419
420// Auto-initialize when DOM is ready
421if (document.readyState === 'loading') {
422    document.addEventListener('DOMContentLoaded', initializePhase3Integration);
423} else {
424    initializePhase3Integration();
425}
426
427// Export for global access
428window.UnifiedContextManager = UnifiedContextManager;
429window.phase3EnhancedSendMessage = phase3EnhancedSendMessage;
430window.initializePhase3Integration = initializePhase3Integration;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.