CrypticallyRequie's picture
🐳 22/05 - 17:15 - try again please
620b6ec verified
Raw
History Blame Contribute Delete
12.1 kB
// Initialize Lucide icons
function refreshIcons() {
lucide.createIcons();
}
refreshIcons();
// Mobile menu
const mobileBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
mobileBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
// Scroll reveal observer
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));
// Animated counters
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));
// Playground Logic
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();
});
});
// Pre-select some suites for better UX
['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; // 40-90 base
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;
}
// UI State: Loading
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...`
];
// Simulate progress
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);
// Generate and render results
const data = generateResults(agentName, Array.from(selectedSuites));
renderResults(data, agentName);
resultsLoading.classList.add('hidden');
resultsContent.classList.remove('hidden');
resultsContent.classList.add('flex');
// Reset button state
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}`;
// Overall badge
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'}`;
// Category bars
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);
});
// Findings list
const list = document.getElementById('findings-list');
list.innerHTML = '';
data.results.forEach(suite => {
// Suite header
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');
});
// Add reveal class to major sections for scroll animations
document.querySelectorAll('section h2, section .grid').forEach((el, i) => {
if (!el.closest('#playground')) {
el.classList.add('reveal');
el.style.transitionDelay = `${i * 50}ms`;
}
});