/** * PIOE - Personal Intelligence & Opportunity Engine * Frontend JavaScript Application */ class PIOEApp { constructor() { this.currentCategory = null; this.currentDomain = null; this.minScore = 0; this.opportunities = []; this.init(); } init() { this.bindEvents(); this.loadStats(); this.loadOpportunities(); } bindEvents() { // Navigation items document.querySelectorAll('.nav-item[data-view]').forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); this.setActiveNav(item); this.handleViewChange(item.dataset.view); }); }); // Category filters document.querySelectorAll('.nav-item[data-category]').forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); this.setActiveNav(item); this.currentCategory = item.dataset.category; this.loadOpportunities(); this.showFeedView(); }); }); // Domain filter document.getElementById('domain-filter').addEventListener('change', (e) => { this.currentDomain = e.target.value || null; this.loadOpportunities(); }); // Score filter document.getElementById('score-filter').addEventListener('change', (e) => { this.minScore = parseFloat(e.target.value) || 0; this.loadOpportunities(); }); // Run ingestion document.getElementById('run-ingestion').addEventListener('click', (e) => { e.preventDefault(); this.runIngestion(); }); // View stats document.getElementById('view-stats').addEventListener('click', (e) => { e.preventDefault(); this.showStatsModal(); }); // Modal close document.querySelector('.modal-close').addEventListener('click', () => { this.closeModal(); }); document.querySelector('.modal-backdrop').addEventListener('click', () => { this.closeModal(); }); // PIOE 2.0: AI Chat document.getElementById('open-chat')?.addEventListener('click', (e) => { e.preventDefault(); this.toggleChat(); }); } // PIOE 2.0: Chat Methods toggleChat() { const panel = document.getElementById('chat-panel'); panel.classList.toggle('active'); } async sendChatMessage() { const input = document.getElementById('chat-input'); const messagesContainer = document.getElementById('chat-messages'); const message = input.value.trim(); if (!message) return; // Add user message to chat messagesContainer.innerHTML += `
`; input.value = ''; messagesContainer.scrollTop = messagesContainer.scrollHeight; // Add loading indicator const loadingId = `loading-${Date.now()}`; messagesContainer.innerHTML += ` `; messagesContainer.scrollTop = messagesContainer.scrollHeight; try { const response = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }) }); const data = await response.json(); // Remove loading indicator document.getElementById(loadingId)?.remove(); // Build response HTML let responseHtml = `${this.escapeHtml(data.response || 'No response')}
`; // Add matched opportunities if any if (data.opportunities && data.opportunities.length > 0) { responseHtml += `[TIP] ${this.escapeHtml(data.suggested_action)}
`; } messagesContainer.innerHTML += ` `; } catch (error) { document.getElementById(loadingId)?.remove(); messagesContainer.innerHTML += ` `; } messagesContainer.scrollTop = messagesContainer.scrollHeight; } setActiveNav(activeItem) { document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); }); activeItem.classList.add('active'); } handleViewChange(view) { if (view === 'feed') { this.currentCategory = null; this.loadOpportunities(); this.showFeedView(); this.updateHeader('Opportunity Feed', 'High-signal opportunities detected by PIOE'); } else if (view === 'digest') { this.loadDigest('daily'); this.showDigestView(); this.updateHeader('Daily Brief', 'Your personalized intelligence report'); } else if (view === 'urgent') { this.loadDigest('urgent'); this.showDigestView(); this.updateHeader('Urgent Opportunities', 'Deadlines approaching soon'); } } updateHeader(title, subtitle) { document.getElementById('page-title').textContent = title; document.getElementById('page-subtitle').textContent = subtitle; } showFeedView() { document.getElementById('opportunity-feed').style.display = 'flex'; document.getElementById('digest-view').style.display = 'none'; } showDigestView() { document.getElementById('opportunity-feed').style.display = 'none'; document.getElementById('digest-view').style.display = 'block'; } async loadStats() { try { const response = await fetch('/api/stats'); const stats = await response.json(); document.getElementById('total-count').textContent = stats.total_opportunities || 0; document.getElementById('new-count').textContent = stats.new_opportunities || 0; document.getElementById('hackathon-count').textContent = stats.by_category?.hackathon || 0; document.getElementById('internship-count').textContent = stats.by_category?.internship || 0; } catch (error) { console.error('Failed to load stats:', error); } } async loadOpportunities() { const feed = document.getElementById('opportunity-feed'); feed.innerHTML = '${this.escapeHtml(opp.raw_text?.slice(0, 200) || '')}
Error loading digest: ${error.message}
`; } } markdownToHtml(md) { return md .replace(/^### (.*$)/gim, '$1') .replace(/\[(.*?)\]\((.*?)\)/g, '$1') .replace(/^---$/gim, '
⏰ Deadline: ${new Date(opp.deadline).toLocaleDateString()}
` : ''}${this.escapeHtml(opp.raw_text || 'No description available.')}
`; modal.classList.add('active'); } async getGuidance(opportunityId) { const container = document.getElementById('guidance-container'); const content = document.getElementById('guidance-content'); container.style.display = 'block'; content.innerHTML = '🔄 Analyzing opportunity...
'; try { const response = await fetch(`/api/opportunities/${opportunityId}/guidance`); const data = await response.json(); const g = data.guidance; content.innerHTML = `${g.networking_tips}
${g.differentiation_angle}
${g.why || 'Personalized guidance based on your profile'}
Failed to get guidance: ${error.message}
`; } } closeModal() { document.getElementById('detail-modal').classList.remove('active'); } async updateStatus(id, status) { try { await fetch(`/api/opportunities/${id}/status`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }) }); // Visual feedback this.showNotification(`Status updated to ${status}`); } catch (error) { console.error('Failed to update status:', error); } } async runIngestion() { this.showNotification('Starting ingestion... This may take a few minutes.'); try { await fetch('/api/ingest/run', { method: 'POST' }); this.showNotification('Ingestion started! Refresh in a few minutes to see new opportunities.'); } catch (error) { this.showNotification('Failed to start ingestion: ' + error.message); } } async showStatsModal() { try { const response = await fetch('/api/stats'); const stats = await response.json(); const body = document.getElementById('modal-body'); body.innerHTML = `