/** * 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 ? `

⚠️ Tractatus Violations Detected

${validation.violations.map(v => `
${v.type}: ${v.message}
Instruction: ${v.instruction}
`).join('')}
` : ''; const warningsHtml = validation.warnings.length > 0 ? `

⚠ Warnings

${validation.warnings.map(w => `
${w.message}
`).join('')}
` : ''; const modal = `

Blog Draft Preview

${violationsHtml} ${warningsHtml}

Title

${draft.title || 'Untitled'}

Subtitle

${draft.subtitle || 'No subtitle'}

Excerpt

${draft.excerpt || 'No excerpt'}

Content Preview

${draft.content ? marked(draft.content.substring(0, 1000)) + '...' : 'No content'}

Tags

${(draft.tags || []).map(tag => ` ${tag} `).join('')}

Word Count

${draft.word_count || 'Unknown'}

Tractatus Angle

${draft.tractatus_angle || 'Not specified'}

Sources

    ${(draft.sources || ['No sources provided']).map(source => `
  • ${source}
  • `).join('')}

🤖 Governance Notice

Policy: ${governance.policy}
Validation: ${validation.recommendation}
Queue ID: ${queue_id}
This draft has been queued for human review and approval before publication.

`; const container = document.getElementById('modal-container'); container.innerHTML = modal; // Close modal handlers container.querySelectorAll('.close-modal').forEach(btn => { btn.addEventListener('click', () => { container.innerHTML = ''; }); }); // View queue handler container.querySelector('.view-queue').addEventListener('click', () => { container.innerHTML = ''; document.querySelector('a[href="#queue"]').click(); }); } // Load draft queue async function loadDraftQueue() { const queueDiv = document.getElementById('draft-queue'); queueDiv.innerHTML = '
Loading queue...
'; try { const response = await apiCall('/api/admin/moderation-queue?type=BLOG_POST_DRAFT'); if (response.ok) { const data = await response.json(); const queue = data.queue || []; if (queue.length === 0) { queueDiv.innerHTML = '
No pending drafts
'; return; } queueDiv.innerHTML = queue.map(item => `

${item.data.draft?.title || item.data.topic}

${item.data.draft?.subtitle || ''}

Audience: ${item.data.audience} Length: ${item.data.length} Created: ${new Date(item.created_at).toLocaleDateString()}
${item.data.validation?.violations.length > 0 ? `
${item.data.validation.violations.length} violation(s)
` : ''}
${item.priority}
`).join(''); // Add review handlers queueDiv.querySelectorAll('.review-draft').forEach(btn => { btn.addEventListener('click', () => { const queueId = btn.dataset.queueId; const item = queue.find(q => q._id === queueId); if (item) { showReviewModal(item); } }); }); } else { queueDiv.innerHTML = '
Failed to load queue
'; } } catch (error) { console.error('Failed to load draft queue:', error); queueDiv.innerHTML = '
Error loading queue
'; } } // Show review modal function showReviewModal(item) { const { draft, validation } = item.data; const modal = `

Review Draft

${draft.title}

${draft.subtitle}

${marked(draft.content || '')}
`; const container = document.getElementById('modal-container'); container.innerHTML = modal; // Close modal handler container.querySelectorAll('.close-modal').forEach(btn => { btn.addEventListener('click', () => { container.innerHTML = ''; }); }); // Approve/Reject handlers (to be implemented) container.querySelector('.approve-draft')?.addEventListener('click', () => { alert('Approve functionality to be implemented'); }); container.querySelector('.reject-draft')?.addEventListener('click', () => { alert('Reject functionality to be implemented'); }); } // Load editorial guidelines async function loadEditorialGuidelines() { try { const response = await apiCall('/api/blog/editorial-guidelines'); if (response.ok) { const data = await response.json(); const guidelines = data.guidelines; // Standards const standardsDiv = document.getElementById('editorial-standards'); standardsDiv.innerHTML = `
Tone
${guidelines.tone}
Voice
${guidelines.voice}
Style
${guidelines.style}
`; // Forbidden patterns const patternsDiv = document.getElementById('forbidden-patterns'); patternsDiv.innerHTML = guidelines.forbiddenPatterns.map(p => `
  • ${p}
  • `).join(''); // Principles const principlesDiv = document.getElementById('core-principles'); principlesDiv.innerHTML = guidelines.principles.map(p => `
  • ${p}
  • `).join(''); } } catch (error) { console.error('Failed to load guidelines:', error); } } // Logout function initLogout() { document.getElementById('logout-btn').addEventListener('click', () => { localStorage.removeItem('adminToken'); window.location.href = '/admin/login.html'; }); } // Refresh queue button function initRefresh() { document.getElementById('refresh-queue-btn')?.addEventListener('click', () => { loadDraftQueue(); }); } // Marked.js simple implementation (fallback) function marked(text) { // Simple markdown to HTML conversion return text .replace(/### (.*)/g, '

    $1

    ') .replace(/## (.*)/g, '

    $1

    ') .replace(/# (.*)/g, '

    $1

    ') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/\n\n/g, '

    ') .replace(/\n/g, '
    '); } // Initialize document.addEventListener('DOMContentLoaded', () => { if (!checkAuth()) return; initNavigation(); initDraftForm(); initLogout(); initRefresh(); loadStatistics(); });