/** * Blog Curation Admin UI * Tractatus Framework - AI-assisted content generation with human oversight */ // Get auth token from localStorage function getAuthToken() { return localStorage.getItem('adminToken'); } // Check authentication function checkAuth() { const token = getAuthToken(); if (!token) { window.location.href = '/admin/login.html'; return false; } return true; } // API call helper async function apiCall(endpoint, options = {}) { const token = getAuthToken(); const defaultOptions = { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }; const response = await fetch(endpoint, { ...defaultOptions, ...options }); if (response.status === 401) { localStorage.removeItem('adminToken'); window.location.href = '/admin/login.html'; throw new Error('Unauthorized'); } return response; } // Navigation function initNavigation() { const navLinks = document.querySelectorAll('.nav-link'); const sections = { 'draft': document.getElementById('draft-section'), 'queue': document.getElementById('queue-section'), 'guidelines': document.getElementById('guidelines-section') }; navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const target = link.getAttribute('href').substring(1); // Update active link navLinks.forEach(l => l.classList.remove('active', 'bg-gray-100', 'text-blue-600')); link.classList.add('active', 'bg-gray-100', 'text-blue-600'); // Show target section Object.values(sections).forEach(section => section.classList.add('hidden')); if (sections[target]) { sections[target].classList.remove('hidden'); // Load data for specific sections if (target === 'queue') { loadDraftQueue(); } else if (target === 'guidelines') { loadEditorialGuidelines(); } } }); }); // Set first link as active navLinks[0].classList.add('active', 'bg-gray-100', 'text-blue-600'); } // Load statistics async function loadStatistics() { try { // Load pending drafts const queueResponse = await apiCall('/api/admin/moderation-queue?type=BLOG_POST_DRAFT'); if (queueResponse.ok) { const queueData = await queueResponse.json(); document.getElementById('stat-pending-drafts').textContent = queueData.queue?.length || 0; } // Load published posts const postsResponse = await apiCall('/api/blog/admin/posts?status=published&limit=1000'); if (postsResponse.ok) { const postsData = await postsResponse.json(); document.getElementById('stat-published-posts').textContent = postsData.pagination?.total || 0; } } catch (error) { console.error('Failed to load statistics:', error); } } // Draft form submission function initDraftForm() { const form = document.getElementById('draft-form'); const btn = document.getElementById('draft-btn'); const status = document.getElementById('draft-status'); form.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); const data = { topic: formData.get('topic'), audience: formData.get('audience'), length: formData.get('length') || 'medium', focus: formData.get('focus') || undefined }; // UI feedback btn.disabled = true; btn.textContent = 'Generating...'; status.textContent = 'Calling Claude API...'; status.className = 'text-sm text-blue-600'; try { const response = await apiCall('/api/blog/draft-post', { method: 'POST', body: JSON.stringify(data) }); const result = await response.json(); if (response.ok) { // Success - show draft in modal status.textContent = '✓ Draft generated! Opening preview...'; status.className = 'text-sm text-green-600'; setTimeout(() => { showDraftModal(result); form.reset(); status.textContent = ''; }, 1000); } else { // Error status.textContent = `✗ Error: ${result.message}`; status.className = 'text-sm text-red-600'; } } catch (error) { status.textContent = `✗ Error: ${error.message}`; status.className = 'text-sm text-red-600'; } finally { btn.disabled = false; btn.textContent = 'Generate Draft'; } }); } // Show draft modal function showDraftModal(result) { const { draft, validation, governance, queue_id } = result; const violationsHtml = validation.violations.length > 0 ? `
${draft.title || 'Untitled'}
${draft.subtitle || 'No subtitle'}
${draft.excerpt || 'No excerpt'}
${draft.word_count || 'Unknown'}
${draft.tractatus_angle || 'Not specified'}
Policy: ${governance.policy}
Validation: ${validation.recommendation}
Queue ID: ${queue_id}
This draft has been queued for human review and approval before publication.
${item.data.draft?.subtitle || ''}
${draft.subtitle}
${marked(draft.content || '')}')
.replace(/\n/g, '
');
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
if (!checkAuth()) return;
initNavigation();
initDraftForm();
initLogout();
initRefresh();
loadStatistics();
});