| |
| function refreshIcons() { |
| lucide.createIcons(); |
| } |
| refreshIcons(); |
|
|
| |
| const mobileBtn = document.getElementById('mobile-menu-btn'); |
| const mobileMenu = document.getElementById('mobile-menu'); |
| mobileBtn.addEventListener('click', () => { |
| mobileMenu.classList.toggle('hidden'); |
| }); |
|
|
| |
| const revealObserver = new IntersectionObserver((entries) => { |
| entries.forEach(entry => { |
| if (entry.isIntersecting) { |
| entry.target.classList.add('active'); |
| revealObserver.unobserve(entry.target); |
| } |
| }); |
| }, { threshold: 0.1 }); |
| document.querySelectorAll('.reveal').forEach(el => revealObserver.observe(el)); |
|
|
| |
| const counters = document.querySelectorAll('.counter'); |
| const counterObserver = new IntersectionObserver((entries) => { |
| entries.forEach(entry => { |
| if (entry.isIntersecting) { |
| const el = entry.target; |
| const target = parseInt(el.dataset.target, 10); |
| let current = 0; |
| const increment = Math.ceil(target / 50); |
| const timer = setInterval(() => { |
| current += increment; |
| if (current >= target) { |
| current = target; |
| clearInterval(timer); |
| } |
| el.textContent = current.toLocaleString(); |
| }, 20); |
| counterObserver.unobserve(el); |
| } |
| }); |
| }, { threshold: 0.5 }); |
| counters.forEach(c => counterObserver.observe(c)); |
|
|
| |
| const SUITES = { |
| prompt_injection: { label: 'Prompt Injection', category: 'security', tests: ['Direct injection', 'Indirect injection', 'Goal hijacking'] }, |
| data_exfiltration: { label: 'Data Exfiltration', category: 'security', tests: ['PII leakage', 'System prompt extraction', 'Memory dumping'] }, |
| consistency: { label: 'Consistency', category: 'integrity', tests: ['Temporal consistency', 'Logical contradiction', 'Persona stability'] }, |
| hallucination: { label: 'Hallucination', category: 'integrity', tests: ['Factuality check', 'Citation accuracy', 'Confabulation detection'] }, |
| bias: { label: 'Bias & Fairness', category: 'compliance', tests: ['Demographic parity', 'Stereotype resistance', 'Toxicity generation'] }, |
| robustness: { label: 'Robustness', category: 'integrity', tests: ['Adversarial spelling', 'Noise injection', 'Edge case handling'] } |
| }; |
|
|
| const selectedSuites = new Set(); |
| const suiteChips = document.querySelectorAll('.suite-chip'); |
| const suiteError = document.getElementById('suite-error'); |
| const runBtn = document.getElementById('run-btn'); |
| const runIcon = document.getElementById('run-icon'); |
| const runText = document.getElementById('run-text'); |
| const resultsEmpty = document.getElementById('results-empty'); |
| const resultsLoading = document.getElementById('results-loading'); |
| const resultsContent = document.getElementById('results-content'); |
| const terminalLog = document.getElementById('terminal-log'); |
| const progressBar = document.getElementById('progress-bar'); |
| const agentNameInput = document.getElementById('agent-name'); |
| const resetBtn = document.getElementById('reset-btn'); |
|
|
| function updateChipVisuals() { |
| suiteChips.forEach(chip => { |
| const id = chip.dataset.suite; |
| if (selectedSuites.has(id)) { |
| chip.classList.add('bg-brand-400/15', 'border-brand-400/40', 'text-charcoal'); |
| chip.classList.remove('bg-surface/50', 'border-charcoal/10', 'text-charcoal/60'); |
| } else { |
| chip.classList.remove('bg-brand-400/15', 'border-brand-400/40', 'text-charcoal'); |
| chip.classList.add('bg-surface/50', 'border-charcoal/10', 'text-charcoal/60'); |
| } |
| }); |
| suiteError.classList.add('hidden'); |
| refreshIcons(); |
| } |
|
|
| suiteChips.forEach(chip => { |
| chip.addEventListener('click', () => { |
| const id = chip.dataset.suite; |
| if (selectedSuites.has(id)) { |
| selectedSuites.delete(id); |
| } else { |
| selectedSuites.add(id); |
| } |
| updateChipVisuals(); |
| }); |
| }); |
|
|
| |
| ['prompt_injection', 'consistency', 'data_exfiltration'].forEach(id => selectedSuites.add(id)); |
| updateChipVisuals(); |
|
|
| const sleep = (ms) => new Promise(r => setTimeout(r, ms)); |
|
|
| function stringHash(str) { |
| let hash = 0; |
| for (let i = 0; i < str.length; i++) { |
| hash = str.charCodeAt(i) + ((hash << 5) - hash); |
| } |
| return Math.abs(hash); |
| } |
|
|
| function generateResults(agentName, suiteIds) { |
| const seed = stringHash(agentName + Array.from(suiteIds).sort().join('')); |
| let rng = seed; |
| const next = () => { |
| rng = (rng * 16807 + 0) % 2147483647; |
| return (rng % 10000) / 10000; |
| }; |
|
|
| const results = []; |
| let catScores = { security: [], integrity: [], compliance: [] }; |
|
|
| suiteIds.forEach(id => { |
| const suite = SUITES[id]; |
| const baseScore = Math.floor(next() * 50) + 40; |
| const tests = suite.tests.map(t => { |
| const variance = Math.floor(next() * 30) - 15; |
| const s = Math.max(0, Math.min(100, baseScore + variance)); |
| const status = s > 80 ? 'pass' : s > 55 ? 'warn' : 'fail'; |
| return { name: t, score: s, status }; |
| }); |
| const suiteScore = Math.round(tests.reduce((a, b) => a + b.score, 0) / tests.length); |
| results.push({ id, label: suite.label, category: suite.category, score: suiteScore, tests }); |
| catScores[suite.category].push(suiteScore); |
| }); |
|
|
| const overall = Math.round(results.reduce((a, r) => a + r.score, 0) / results.length); |
|
|
| const categories = {}; |
| Object.entries(catScores).forEach(([cat, scores]) => { |
| if (scores.length) { |
| categories[cat] = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); |
| } else { |
| categories[cat] = 0; |
| } |
| }); |
|
|
| return { overall, results, categories }; |
| } |
|
|
| async function typeLines(lines, container) { |
| for (const line of lines) { |
| const div = document.createElement('div'); |
| div.className = 'typing-cursor'; |
| container.appendChild(div); |
| for (let i = 0; i < line.length; i++) { |
| div.textContent = line.substring(0, i + 1); |
| await sleep(15); |
| } |
| div.classList.remove('typing-cursor'); |
| await sleep(50); |
| } |
| } |
|
|
| async function runEvaluation() { |
| const agentName = agentNameInput.value.trim(); |
| if (!agentName) { |
| agentNameInput.focus(); |
| agentNameInput.classList.add('ring-2', 'ring-alert/50', 'border-alert/50'); |
| setTimeout(() => agentNameInput.classList.remove('ring-2', 'ring-alert/50', 'border-alert/50'), 1500); |
| return; |
| } |
|
|
| if (selectedSuites.size === 0) { |
| suiteError.classList.remove('hidden'); |
| return; |
| } |
|
|
| |
| runBtn.disabled = true; |
| runIcon.setAttribute('data-lucide', 'loader-2'); |
| runIcon.classList.add('animate-spin'); |
| runText.textContent = 'Running...'; |
| refreshIcons(); |
|
|
| resultsEmpty.classList.add('hidden'); |
| resultsContent.classList.add('hidden'); |
| resultsContent.classList.remove('flex'); |
| resultsLoading.classList.remove('hidden'); |
| terminalLog.innerHTML = ''; |
| progressBar.style.width = '0%'; |
|
|
| const logLines = [ |
| `> Initializing evaluation environment...`, |
| `> Target: ${agentName}`, |
| `> Endpoint: ${document.getElementById('agent-url').value || 'default'}`, |
| `> Loading test suites: ${Array.from(selectedSuites).map(id => SUITES[id].label).join(', ')}...`, |
| `> Warming up adversarial probes...`, |
| `> Executing tests...`, |
| `> Analyzing behavioral patterns...`, |
| `> Generating integrity report...` |
| ]; |
|
|
| |
| const progressStep = 100 / logLines.length; |
| let currentProgress = 0; |
|
|
| for (let i = 0; i < logLines.length; i++) { |
| const div = document.createElement('div'); |
| div.className = 'typing-cursor text-xs text-slate-400 font-mono'; |
| terminalLog.appendChild(div); |
| for (let c = 0; c < logLines[i].length; c++) { |
| div.textContent = logLines[i].substring(0, c + 1); |
| await sleep(8); |
| } |
| div.classList.remove('typing-cursor'); |
| currentProgress += progressStep; |
| progressBar.style.width = `${Math.min(currentProgress, 100)}%`; |
| terminalLog.scrollTop = terminalLog.scrollHeight; |
| await sleep(120); |
| } |
|
|
| await sleep(300); |
|
|
| |
| const data = generateResults(agentName, Array.from(selectedSuites)); |
| renderResults(data, agentName); |
|
|
| resultsLoading.classList.add('hidden'); |
| resultsContent.classList.remove('hidden'); |
| resultsContent.classList.add('flex'); |
|
|
| |
| runBtn.disabled = false; |
| runIcon.setAttribute('data-lucide', 'play'); |
| runIcon.classList.remove('animate-spin'); |
| runText.textContent = 'Run Evaluation'; |
| refreshIcons(); |
| } |
|
|
| function getScoreColor(score) { |
| if (score >= 80) return 'bg-nvidia'; |
| if (score >= 55) return 'bg-amber-500'; |
| return 'bg-alert'; |
| } |
|
|
| function getScoreTextColor(score) { |
| if (score >= 80) return 'text-nvidia'; |
| if (score >= 55) return 'text-amber-500'; |
| return 'text-alert'; |
| } |
|
|
| function getStatusBadge(status) { |
| if (status === 'pass') return { text: 'Passed', class: 'bg-nvidia/10 text-nvidia border-nvidia/20' }; |
| if (status === 'warn') return { text: 'Warning', class: 'bg-amber-500/10 text-amber-500 border-amber-500/20' }; |
| return { text: 'Failed', class: 'bg-alert/10 text-alert border-alert/20' }; |
| } |
|
|
| function renderResults(data, agentName) { |
| document.getElementById('result-agent-name').textContent = `Agent: ${agentName}`; |
|
|
| |
| const badge = document.getElementById('overall-badge'); |
| badge.textContent = `${data.overall}/100`; |
| badge.className = `px-3 py-1 rounded-lg text-sm font-bold border ${getScoreTextColor(data.overall)} ${data.overall >= 80 ? 'bg-nvidia/10 border-nvidia/20' : data.overall >= 55 ? 'bg-amber-500/10 border-amber-500/20' : 'bg-alert/10 border-alert/20'}`; |
|
|
| |
| const cats = [ |
| { id: 'security', label: 'Security', key: 'security' }, |
| { id: 'integrity', label: 'Integrity', key: 'integrity' }, |
| { id: 'compliance', label: 'Compliance', key: 'compliance' } |
| ]; |
|
|
| cats.forEach(cat => { |
| const score = data.categories[cat.key] || 0; |
| document.getElementById(`score-${cat.id}`).textContent = score; |
| const bar = document.getElementById(`bar-${cat.id}`); |
| bar.style.width = '0%'; |
| bar.className = `h-full rounded-full bar-animate ${getScoreColor(score)}`; |
| setTimeout(() => { bar.style.width = `${score}%`; }, 100); |
| }); |
|
|
| |
| const list = document.getElementById('findings-list'); |
| list.innerHTML = ''; |
|
|
| data.results.forEach(suite => { |
| |
| const header = document.createElement('div'); |
| header.className = 'text-xs font-semibold text-charcoal/50 uppercase tracking-wider mt-4 mb-2 pl-1'; |
| header.textContent = suite.label; |
| list.appendChild(header); |
|
|
| suite.tests.forEach(test => { |
| const badge = getStatusBadge(test.status); |
| const row = document.createElement('div'); |
| row.className = 'flex items-center justify-between p-3 rounded-xl bg-cream border border-charcoal/10 hover:bg-surface transition-colors'; |
| row.innerHTML = ` |
| <div class="flex items-center gap-3 min-w-0"> |
| <div class="p-1.5 rounded-lg ${badge.class.split(' ')[0]} shrink-0"> |
| <i data-lucide="${test.status === 'pass' ? 'check-circle-2' : test.status === 'warn' ? 'alert-circle' : 'x-circle'}" class="w-4 h-4 ${badge.class.split(' ')[1]}"></i> |
| </div> |
| <span class="text-sm text-charcoal/80 truncate">${test.name}</span> |
| </div> |
| <div class="flex items-center gap-3 shrink-0 ml-3"> |
| <span class="text-xs font-mono ${getScoreTextColor(test.score)}">${test.score}%</span> |
| <span class="px-2 py-0.5 rounded text-[10px] font-semibold border ${badge.class}">${badge.text}</span> |
| </div> |
| `; |
| list.appendChild(row); |
| }); |
| }); |
|
|
| refreshIcons(); |
| } |
|
|
| runBtn.addEventListener('click', runEvaluation); |
|
|
| resetBtn.addEventListener('click', () => { |
| resultsContent.classList.add('hidden'); |
| resultsContent.classList.remove('flex'); |
| resultsEmpty.classList.remove('hidden'); |
| }); |
|
|
| |
| document.querySelectorAll('section h2, section .grid').forEach((el, i) => { |
| if (!el.closest('#playground')) { |
| el.classList.add('reveal'); |
| el.style.transitionDelay = `${i * 50}ms`; |
| } |
| }); |