// LionGuard 2 Frontend JavaScript // State management const state = { selectedModel: 'lionguard-2.1', selectedModelGC: 'lionguard-2.1', currentTextId: '', chatHistories: { no_moderation: [], openai_moderation: [], lionguard: [] } }; // Utility functions function showLoading(button) { button.disabled = true; button.classList.add('loading'); const originalText = button.textContent; button.textContent = 'Loading...'; return originalText; } function hideLoading(button, originalText) { button.disabled = false; button.classList.remove('loading'); button.textContent = originalText; } function getScoreLevel(score) { if (score < 0.4) { return { className: 'good', icon: '', title: 'Low risk' }; } if (score < 0.7) { return { className: 'warn', icon: '', title: 'Needs review' }; } return { className: 'bad', icon: '', title: 'High risk' }; } function formatScore(score) { const percentage = Math.round(score * 100); const { className, icon, title } = getScoreLevel(score); return `${icon} ${percentage}%`; } function renderCategoryMeter(score) { const filledSegments = Math.min(10, Math.round(score * 10)); const { className } = getScoreLevel(score); const segments = Array.from({ length: 10 }, (_, index) => { const isFilled = index < filledSegments; const filledClass = isFilled ? `filled ${className}` : ''; return ``; }).join(''); return `
${segments}
`; } // Tab switching function initTabs() { const tabs = document.querySelectorAll('.tab[data-tab]'); const tabContents = document.querySelectorAll('.tab-content'); const dropdownToggle = document.querySelector('.dropdown-toggle'); const demoTabs = ['detector', 'chat']; const updateDropdownState = (targetTab) => { if (!dropdownToggle) return; if (demoTabs.includes(targetTab)) { dropdownToggle.classList.add('active'); } else { dropdownToggle.classList.remove('active'); } }; tabs.forEach(tab => { tab.addEventListener('click', () => { const targetTab = tab.dataset.tab; // Update tabs tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Update content tabContents.forEach(content => { content.classList.remove('active'); if (content.id === `${targetTab}-content`) { content.classList.add('active'); } }); updateDropdownState(targetTab); // Smooth scroll to top when switching tabs window.scrollTo({ top: 0, behavior: 'smooth' }); }); }); const initialActiveTab = document.querySelector('.tab[data-tab].active'); if (initialActiveTab) { updateDropdownState(initialActiveTab.dataset.tab); } } function initNavDropdown() { const dropdown = document.querySelector('.nav-dropdown'); if (!dropdown) return; const toggle = dropdown.querySelector('.dropdown-toggle'); const dropdownTabs = dropdown.querySelectorAll('.dropdown-item[data-tab]'); const closeDropdown = () => { dropdown.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); }; const toggleDropdown = (event) => { event.stopPropagation(); const isOpen = dropdown.classList.toggle('open'); toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); }; toggle.addEventListener('click', toggleDropdown); dropdownTabs.forEach(tab => { tab.addEventListener('click', () => { closeDropdown(); }); }); document.addEventListener('click', (event) => { if (!dropdown.contains(event.target)) { closeDropdown(); } }); } // Model selection for Classifier function initModelSelector() { const select = document.getElementById('model-select'); if (!select) return; select.value = state.selectedModel; select.addEventListener('change', () => { state.selectedModel = select.value; }); } // Model selection for Guardrail Comparison function initModelSelectorGC() { const select = document.getElementById('model-select-gc'); if (!select) return; select.value = state.selectedModelGC; select.addEventListener('change', () => { state.selectedModelGC = select.value; }); } // Classifier: Analyze text async function analyzeText() { const textInput = document.getElementById('text-input'); const analyzeBtn = document.getElementById('analyze-btn'); const binaryResult = document.getElementById('binary-result'); const categoryResults = document.getElementById('category-results'); const feedbackSection = document.getElementById('feedback-section'); const feedbackMessage = document.getElementById('feedback-message'); const text = textInput.value.trim(); if (!text) { alert('Please enter some text to analyze'); return; } const originalText = showLoading(analyzeBtn); feedbackMessage.textContent = ''; feedbackMessage.className = 'feedback-message'; try { const response = await fetch('/moderate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text, model: state.selectedModel }) }); if (!response.ok) { throw new Error('Failed to analyze text'); } const data = await response.json(); // Display binary result const verdictClass = data.binary_verdict; const verdictText = verdictClass.charAt(0).toUpperCase() + verdictClass.slice(1); const verdictIcons = { 'pass': '', 'warn': '', 'fail': '' }; binaryResult.innerHTML = `
${verdictIcons[verdictClass]}
Overall

${verdictText}

${data.binary_percentage}/100
`; // Display category results const categoryHTML = data.categories.map(cat => `
${cat.emoji} ${cat.name}
${renderCategoryMeter(cat.max_score)}
${formatScore(cat.max_score)}
`).join(''); categoryResults.innerHTML = `
${categoryHTML}
`; // Show feedback section state.currentTextId = data.text_id; feedbackSection.style.display = 'block'; } catch (error) { console.error('Error:', error); binaryResult.innerHTML = `
❌ Error analyzing text: ${error.message}
`; } finally { hideLoading(analyzeBtn, originalText); } } // Classifier: Submit feedback async function submitFeedback(agree) { const feedbackMessage = document.getElementById('feedback-message'); if (!state.currentTextId) { feedbackMessage.textContent = 'No analysis to provide feedback on'; feedbackMessage.className = 'feedback-message info'; return; } try { const response = await fetch('/send_feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text_id: state.currentTextId, agree: agree }) }); if (!response.ok) { throw new Error('Failed to submit feedback'); } const data = await response.json(); feedbackMessage.textContent = data.message; feedbackMessage.className = 'feedback-message success'; } catch (error) { console.error('Error:', error); feedbackMessage.textContent = 'Error submitting feedback'; feedbackMessage.className = 'feedback-message info'; } } // Guardrail Comparison: Render chat messages function renderChatMessages(containerId, messages) { const container = document.getElementById(containerId); container.innerHTML = messages.map(msg => `
${msg.content}
`).join(''); // Scroll to bottom container.scrollTop = container.scrollHeight; } // Guardrail Comparison: Send message async function sendMessage() { const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); const message = messageInput.value.trim(); if (!message) { alert('Please enter a message'); return; } const originalText = showLoading(sendBtn); try { const response = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, model: state.selectedModelGC, histories: state.chatHistories }) }); if (!response.ok) { throw new Error('Failed to send message'); } const data = await response.json(); // Update state state.chatHistories = data.histories; // Render all chat panels renderChatMessages('chat-no-mod', data.histories.no_moderation); renderChatMessages('chat-openai', data.histories.openai_moderation); renderChatMessages('chat-lionguard', data.histories.lionguard); // Clear input messageInput.value = ''; } catch (error) { console.error('Error:', error); alert('Error sending message: ' + error.message); } finally { hideLoading(sendBtn, originalText); } } // Guardrail Comparison: Clear all chats function clearAllChats() { state.chatHistories = { no_moderation: [], openai_moderation: [], lionguard: [] }; document.getElementById('chat-no-mod').innerHTML = ''; document.getElementById('chat-openai').innerHTML = ''; document.getElementById('chat-lionguard').innerHTML = ''; } // Initialize event listeners function initEventListeners() { // Classifier tab const analyzeBtn = document.getElementById('analyze-btn'); const textInput = document.getElementById('text-input'); const thumbsUpBtn = document.getElementById('thumbs-up'); const thumbsDownBtn = document.getElementById('thumbs-down'); analyzeBtn.addEventListener('click', analyzeText); textInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && e.ctrlKey) { analyzeText(); } }); thumbsUpBtn.addEventListener('click', () => submitFeedback(true)); thumbsDownBtn.addEventListener('click', () => submitFeedback(false)); // Guardrail Comparison tab const sendBtn = document.getElementById('send-btn'); const messageInput = document.getElementById('message-input'); const clearBtn = document.getElementById('clear-btn'); sendBtn.addEventListener('click', sendMessage); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); sendMessage(); } }); clearBtn.addEventListener('click', clearAllChats); } // Dark mode toggle function initThemeToggle() { const themeToggle = document.getElementById('theme-toggle'); if (!themeToggle) return; const themeIcon = themeToggle.querySelector('.theme-icon'); const updateIcon = (isDark) => { themeToggle.setAttribute('aria-pressed', isDark ? 'true' : 'false'); if (themeIcon) { // Toggle class for boxicons themeIcon.className = isDark ? 'bx bx-moon theme-icon' : 'bx bx-sun theme-icon'; themeIcon.textContent = ''; // clear text content } }; const savedTheme = localStorage.getItem('theme') || 'light'; const shouldStartDark = savedTheme === 'dark'; if (shouldStartDark) { document.body.classList.add('dark-mode'); } updateIcon(shouldStartDark); themeToggle.addEventListener('click', () => { document.body.classList.toggle('dark-mode'); const isDark = document.body.classList.contains('dark-mode'); updateIcon(isDark); localStorage.setItem('theme', isDark ? 'dark' : 'light'); }); } // Code snippet tabs for Get Started function initCodeTabs() { const tabs = document.querySelectorAll('.code-tab'); const blocks = document.querySelectorAll('.code-block'); if (!tabs.length) return; tabs.forEach(tab => { tab.addEventListener('click', () => { // Remove active class from all tabs tabs.forEach(t => t.classList.remove('active')); // Add active class to clicked tab tab.classList.add('active'); // Hide all blocks blocks.forEach(b => b.classList.remove('active')); // Show target block const targetId = `code-${tab.dataset.code}`; const targetBlock = document.getElementById(targetId); if (targetBlock) { targetBlock.classList.add('active'); } }); }); } // Copy Code Functionality function initCopyButton() { const copyBtn = document.getElementById('copy-code-btn'); if (!copyBtn) return; copyBtn.addEventListener('click', async () => { // Find active code block const activeBlock = document.querySelector('.code-block.active code'); if (!activeBlock) return; const textToCopy = activeBlock.textContent; try { await navigator.clipboard.writeText(textToCopy); // Provide feedback const originalHtml = copyBtn.innerHTML; copyBtn.innerHTML = ` Copied!`; copyBtn.classList.add('success'); setTimeout(() => { copyBtn.innerHTML = originalHtml; copyBtn.classList.remove('success'); }, 2000); } catch (err) { console.error('Failed to copy:', err); copyBtn.innerHTML = ` Failed`; setTimeout(() => { copyBtn.innerHTML = ` Copy`; }, 2000); } }); } // Initialize app document.addEventListener('DOMContentLoaded', () => { initTabs(); initNavDropdown(); initModelSelector(); initModelSelectorGC(); initEventListeners(); initThemeToggle(); initCodeTabs(); initCopyButton(); console.log('LionGuard 2 app initialized'); });