/** * Media Triage Admin UI * AI-powered media inquiry triage with human oversight */ // Auth check const token = localStorage.getItem('admin_token'); const user = JSON.parse(localStorage.getItem('admin_user') || '{}'); if (!token) { window.location.href = '/admin/login.html'; } // Display admin name document.getElementById('admin-name').textContent = user.email || 'Admin'; // Logout document.getElementById('logout-btn').addEventListener('click', () => { localStorage.removeItem('admin_token'); localStorage.removeItem('admin_user'); window.location.href = '/admin/login.html'; }); /** * API request helper with automatic auth header injection */ async function apiRequest(endpoint, options = {}) { const response = await fetch(endpoint, { ...options, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', ...options.headers } }); if (response.status === 401) { localStorage.removeItem('admin_token'); window.location.href = '/admin/login.html'; return; } return response.json(); } // State management let inquiries = []; let currentInquiry = null; let filters = { status: 'new', urgency: '', values: '', sortBy: 'received_at' }; /** * Load and display dashboard statistics */ async function loadStatistics() { try { const response = await apiRequest('/api/media/inquiries'); if (!response.success) { console.error('Invalid stats response:', response); return; } const allInquiries = response.inquiries || []; // Calculate statistics const newInquiries = allInquiries.filter(i => !i.ai_triage); const triaged = allInquiries.filter(i => i.ai_triage && i.status === 'pending'); const responded = allInquiries.filter(i => i.status === 'responded'); const valuesInvolved = allInquiries.filter(i => i.ai_triage?.involves_values); document.getElementById('stat-total').textContent = allInquiries.length; document.getElementById('stat-new').textContent = newInquiries.length; document.getElementById('stat-triaged').textContent = triaged.length; document.getElementById('stat-values').textContent = valuesInvolved.length; document.getElementById('stat-responded').textContent = responded.length; } catch (error) { console.error('Failed to load statistics:', error); showToast('Failed to load statistics', 'error'); } } /** * Load and render inquiries based on current filters */ async function loadInquiries() { const container = document.getElementById('inquiries-container'); try { // Show loading state container.innerHTML = `

Loading media inquiries...

`; const response = await apiRequest('/api/media/inquiries'); if (!response.success) { throw new Error('Failed to load inquiries'); } inquiries = response.inquiries || []; // Apply filters let filtered = inquiries.filter(inquiry => { // Status filter if (filters.status === 'new' && inquiry.ai_triage) return false; if (filters.status === 'triaged' && (!inquiry.ai_triage || inquiry.status === 'responded')) return false; if (filters.status === 'responded' && inquiry.status !== 'responded') return false; // Urgency filter if (filters.urgency && inquiry.ai_triage?.urgency !== filters.urgency) return false; // Values filter if (filters.values === 'true' && !inquiry.ai_triage?.involves_values) return false; if (filters.values === 'false' && inquiry.ai_triage?.involves_values) return false; return true; }); // Apply sorting filtered.sort((a, b) => { switch (filters.sortBy) { case 'received_at': return new Date(b.created_at) - new Date(a.created_at); case 'urgency_score': const scoreA = a.ai_triage?.urgency_score || 0; const scoreB = b.ai_triage?.urgency_score || 0; return scoreB - scoreA; case 'deadline': if (!a.inquiry.deadline && !b.inquiry.deadline) return 0; if (!a.inquiry.deadline) return 1; if (!b.inquiry.deadline) return -1; return new Date(a.inquiry.deadline) - new Date(b.inquiry.deadline); default: return 0; } }); // Update results count document.getElementById('filter-results').textContent = `Showing ${filtered.length} inquir${filtered.length !== 1 ? 'ies' : 'y'}`; // Render inquiries if (filtered.length === 0) { container.innerHTML = `

No inquiries found

Try adjusting your filters.

`; return; } // Render inquiry cards container.innerHTML = filtered.map(inquiry => renderInquiryCard(inquiry)).join(''); } catch (error) { console.error('Failed to load inquiries:', error); container.innerHTML = `

Failed to load inquiries. Please try again.

`; showToast('Failed to load inquiries', 'error'); } } /** * Render a single inquiry as an HTML card */ function renderInquiryCard(inquiry) { const receivedDate = new Date(inquiry.created_at).toLocaleDateString(); const receivedTime = new Date(inquiry.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); // Status badge let statusBadge = ''; if (!inquiry.ai_triage) { statusBadge = 'New'; } else if (inquiry.status === 'responded') { statusBadge = 'Responded'; } else { statusBadge = 'Triaged'; } // Urgency badge let urgencyBadge = ''; if (inquiry.ai_triage?.urgency) { const urgencyColors = { high: 'bg-red-100 text-red-800', medium: 'bg-yellow-100 text-yellow-800', low: 'bg-green-100 text-green-800' }; const urgencyColor = urgencyColors[inquiry.ai_triage.urgency] || 'bg-gray-100 text-gray-800'; urgencyBadge = ` Urgency: ${inquiry.ai_triage.urgency.toUpperCase()} `; } // Values warning badge let valuesBadge = ''; if (inquiry.ai_triage?.involves_values) { valuesBadge = ` Values-Sensitive `; } // Border color for values-sensitive inquiries const borderClass = inquiry.ai_triage?.involves_values ? 'border-red-300 border-2' : 'border-gray-200'; return `

${escapeHtml(inquiry.inquiry.subject)}

From: ${escapeHtml(inquiry.contact.name)} (${escapeHtml(inquiry.contact.outlet)})

Received: ${receivedDate} at ${receivedTime}

${statusBadge} ${urgencyBadge} ${valuesBadge}

${escapeHtml(inquiry.inquiry.message)}

${inquiry.ai_triage ? `

AI Triage Analysis

Urgency Score: ${inquiry.ai_triage.urgency_score}/100
Sensitivity: ${inquiry.ai_triage.topic_sensitivity}
Response Time: ${inquiry.ai_triage.suggested_response_time} hours
${inquiry.ai_triage.involves_values ? `

⚠️ BoundaryEnforcer: ${escapeHtml(inquiry.ai_triage.boundary_enforcement)}

` : ''}
` : ''}
${!inquiry.ai_triage ? ` ` : inquiry.status !== 'responded' ? ` ` : ` `}
`; } /** * Show inquiry details modal */ async function showInquiryDetails(inquiryId) { try { const response = await apiRequest(`/api/media/inquiries/${inquiryId}`); if (!response.success) { throw new Error('Failed to load inquiry details'); } currentInquiry = response.inquiry; const modal = document.getElementById('details-modal'); const content = document.getElementById('details-modal-content'); // Build detailed view let html = `

Contact Information

Name: ${escapeHtml(currentInquiry.contact.name)}
Email: ${escapeHtml(currentInquiry.contact.email)}
Outlet: ${escapeHtml(currentInquiry.contact.outlet)}
${currentInquiry.contact.phone ? `
Phone: ${escapeHtml(currentInquiry.contact.phone)}
` : ''}

Inquiry Details

Subject:

${escapeHtml(currentInquiry.inquiry.subject)}

Message:

${escapeHtml(currentInquiry.inquiry.message)}

${currentInquiry.inquiry.deadline ? `
Deadline:

${new Date(currentInquiry.inquiry.deadline).toLocaleString()}

` : ''} ${currentInquiry.inquiry.topic_areas?.length ? `
Topic Areas:

${currentInquiry.inquiry.topic_areas.map(t => escapeHtml(t)).join(', ')}

` : ''}
`; // AI Triage Results (if available) if (currentInquiry.ai_triage) { html += `

AI Triage Analysis

Urgency: ${currentInquiry.ai_triage.urgency.toUpperCase()} (Score: ${currentInquiry.ai_triage.urgency_score}/100)

${escapeHtml(currentInquiry.ai_triage.urgency_reasoning)}

Topic Sensitivity: ${currentInquiry.ai_triage.topic_sensitivity.toUpperCase()}

${escapeHtml(currentInquiry.ai_triage.sensitivity_reasoning)}

${currentInquiry.ai_triage.involves_values ? '⚠️ ' : '✓ '}Values Involvement: ${currentInquiry.ai_triage.involves_values ? 'YES' : 'NO'}

${escapeHtml(currentInquiry.ai_triage.values_reasoning)}

${escapeHtml(currentInquiry.ai_triage.boundary_enforcement)}

${currentInquiry.ai_triage.suggested_talking_points?.length ? `

Suggested Talking Points:

` : ''} ${currentInquiry.ai_triage.draft_response ? `

AI-Generated Draft Response (Human Review Required):

${escapeHtml(currentInquiry.ai_triage.draft_response)}

${escapeHtml(currentInquiry.ai_triage.draft_response_reasoning)}

` : ''}

Framework Compliance: BoundaryEnforcer: ${currentInquiry.ai_triage.framework_compliance?.boundary_enforcer_checked ? '✓' : '✗'} | Human Approval Required: ${currentInquiry.ai_triage.framework_compliance?.human_approval_required ? '✓' : '✗'} | Reasoning Transparent: ${currentInquiry.ai_triage.framework_compliance?.reasoning_transparent ? '✓' : '✗'}

AI Model: ${currentInquiry.ai_triage.ai_model} | Triaged: ${new Date(currentInquiry.ai_triage.triaged_at).toLocaleString()}

`; } // Cultural Sensitivity Check (Phase 1: inst_081 pluralism) if (currentInquiry.response?.cultural_sensitivity) { const cultural = currentInquiry.response.cultural_sensitivity; const riskColors = { 'LOW': { bg: 'green', text: 'green' }, 'MEDIUM': { bg: 'yellow', text: 'yellow' }, 'HIGH': { bg: 'red', text: 'red' } }; const colors = riskColors[cultural.risk_level] || riskColors['LOW']; html += `

🌍 Cultural Sensitivity Analysis

${cultural.risk_level === 'HIGH' ? '⚠️ ' : cultural.risk_level === 'MEDIUM' ? '⚡ ' : '✓ '}Risk Level: ${cultural.risk_level}

Recommended Action: ${cultural.recommended_action}

${cultural.risk_level === 'HIGH' ? `

🚨 Human review recommended before sending (inst_081 pluralism)

` : ''}
${cultural.concerns?.length > 0 ? `

Cultural Concerns (${cultural.concerns.length}):

` : ''} ${cultural.suggestions?.length > 0 ? `

Suggested Adaptations (${cultural.suggestions.length}):

` : ''}

Framework: PluralisticDeliberationOrchestrator (inst_081) | Checked: ${new Date(cultural.checked_at).toLocaleString()}

Note: AI flags cultural concerns but never blocks. Human decides final approach.

`; } content.innerHTML = html; // Update modal buttons visibility const triageBtn = document.getElementById('details-modal-triage-btn'); const respondBtn = document.getElementById('details-modal-respond-btn'); if (!currentInquiry.ai_triage) { triageBtn.classList.remove('hidden'); respondBtn.classList.add('hidden'); } else if (currentInquiry.status !== 'responded') { triageBtn.classList.add('hidden'); respondBtn.classList.remove('hidden'); } else { triageBtn.classList.add('hidden'); respondBtn.classList.add('hidden'); } modal.classList.remove('hidden'); } catch (error) { console.error('Failed to load inquiry details:', error); showToast('Failed to load inquiry details', 'error'); } } /** * Run AI triage on inquiry */ async function runTriage(inquiryId) { try { showToast('Running AI triage analysis...', 'info'); const response = await apiRequest(`/api/media/inquiries/${inquiryId}/triage`, { method: 'POST' }); if (response.success) { showToast('AI triage completed successfully', 'success'); await loadInquiries(); await loadStatistics(); // Refresh details modal if open if (currentInquiry && currentInquiry._id === inquiryId) { await showInquiryDetails(inquiryId); } } else { showToast(response.message || 'Failed to run triage', 'error'); } } catch (error) { console.error('Triage error:', error); showToast('Failed to run AI triage', 'error'); } } /** * Show response modal */ function showResponseModal(inquiryId) { if (!currentInquiry || currentInquiry._id !== inquiryId) { // Load inquiry first showInquiryDetails(inquiryId).then(() => { openResponseModal(); }); } else { openResponseModal(); } } function openResponseModal() { const modal = document.getElementById('response-modal'); const contactInfo = document.getElementById('response-contact-info'); const aiDraftSection = document.getElementById('ai-draft-section'); const aiDraftContent = document.getElementById('ai-draft-content'); const valuesWarning = document.getElementById('values-warning'); const valuesWarningText = document.getElementById('values-warning-text'); const responseContent = document.getElementById('response-content'); // Populate contact info contactInfo.innerHTML = `

To: ${escapeHtml(currentInquiry.contact.name)} (${escapeHtml(currentInquiry.contact.email)})
Outlet: ${escapeHtml(currentInquiry.contact.outlet)}
Subject: ${escapeHtml(currentInquiry.inquiry.subject)}

`; // Show AI draft if available if (currentInquiry.ai_triage?.draft_response) { aiDraftSection.classList.remove('hidden'); aiDraftContent.textContent = currentInquiry.ai_triage.draft_response; } else { aiDraftSection.classList.add('hidden'); } // Show values warning if applicable if (currentInquiry.ai_triage?.involves_values) { valuesWarning.classList.remove('hidden'); valuesWarningText.textContent = currentInquiry.ai_triage.values_reasoning; } else { valuesWarning.classList.add('hidden'); } // Clear response content responseContent.value = ''; modal.classList.remove('hidden'); // Close details modal if open document.getElementById('details-modal').classList.add('hidden'); } /** * Send response to inquiry */ async function sendResponse() { const responseContent = document.getElementById('response-content').value.trim(); if (!responseContent) { showToast('Please enter a response', 'warning'); return; } if (!currentInquiry) { showToast('No inquiry selected', 'error'); return; } try { showToast('Sending response...', 'info'); const response = await apiRequest(`/api/media/inquiries/${currentInquiry._id}/respond`, { method: 'POST', body: JSON.stringify({ content: responseContent }) }); if (response.success) { showToast('Response sent successfully', 'success'); document.getElementById('response-modal').classList.add('hidden'); await loadInquiries(); await loadStatistics(); } else { showToast(response.message || 'Failed to send response', 'error'); } } catch (error) { console.error('Send response error:', error); showToast('Failed to send response', 'error'); } } // Event listeners for modals document.getElementById('close-details-modal').addEventListener('click', () => { document.getElementById('details-modal').classList.add('hidden'); }); document.getElementById('details-modal-close-btn').addEventListener('click', () => { document.getElementById('details-modal').classList.add('hidden'); }); document.getElementById('details-modal-triage-btn').addEventListener('click', () => { if (currentInquiry) { runTriage(currentInquiry._id); } }); document.getElementById('details-modal-respond-btn').addEventListener('click', () => { if (currentInquiry) { openResponseModal(); } }); document.getElementById('close-response-modal').addEventListener('click', () => { document.getElementById('response-modal').classList.add('hidden'); }); document.getElementById('response-modal-cancel-btn').addEventListener('click', () => { document.getElementById('response-modal').classList.add('hidden'); }); document.getElementById('send-response-btn').addEventListener('click', () => { sendResponse(); }); document.getElementById('use-draft-btn').addEventListener('click', () => { const draft = document.getElementById('ai-draft-content').textContent; document.getElementById('response-content').value = draft; showToast('Draft copied to response editor', 'success'); }); // Filter handlers document.getElementById('filter-status').addEventListener('change', (e) => { filters.status = e.target.value; loadInquiries(); }); document.getElementById('filter-urgency').addEventListener('change', (e) => { filters.urgency = e.target.value; loadInquiries(); }); document.getElementById('filter-values').addEventListener('change', (e) => { filters.values = e.target.value; loadInquiries(); }); document.getElementById('sort-by').addEventListener('change', (e) => { filters.sortBy = e.target.value; loadInquiries(); }); document.getElementById('clear-filters-btn').addEventListener('click', () => { filters = { status: 'new', urgency: '', values: '', sortBy: 'received_at' }; document.getElementById('filter-status').value = 'new'; document.getElementById('filter-urgency').value = ''; document.getElementById('filter-values').value = ''; document.getElementById('sort-by').value = 'received_at'; loadInquiries(); }); // Delegate click events for dynamically created buttons document.getElementById('inquiries-container').addEventListener('click', (e) => { const btn = e.target.closest('button'); if (!btn) return; const inquiryId = btn.dataset.inquiryId; if (!inquiryId) return; if (btn.classList.contains('view-details-btn')) { showInquiryDetails(inquiryId); } else if (btn.classList.contains('run-triage-btn')) { runTriage(inquiryId); } else if (btn.classList.contains('respond-btn')) { showResponseModal(inquiryId); } }); /** * Show a toast notification message */ function showToast(message, type = 'info') { const container = document.getElementById('toast-container'); const colors = { success: 'bg-green-500', error: 'bg-red-500', warning: 'bg-yellow-500', info: 'bg-blue-500' }; const toast = document.createElement('div'); toast.className = `${colors[type]} text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-2 transition-all duration-300 ease-in-out`; toast.style.opacity = '0'; toast.style.transform = 'translateX(100px)'; const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.className = 'ml-4 text-white hover:text-gray-200'; closeBtn.addEventListener('click', () => toast.remove()); const messageSpan = document.createElement('span'); messageSpan.textContent = message; toast.appendChild(messageSpan); toast.appendChild(closeBtn); container.appendChild(toast); // Trigger animation setTimeout(() => { toast.style.opacity = '1'; toast.style.transform = 'translateX(0)'; }, 10); // Auto-remove after 5 seconds setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(100px)'; setTimeout(() => toast.remove(), 300); }, 5000); } // Utility functions function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Initialize on page load loadStatistics(); loadInquiries();