agent-verif / test-ui.html
claude's playground
feat(agent verf): Add multi-provider LLM infrastructure with dual-mode support
0a6602d
Raw
History Blame Contribute Delete
13.3 kB
<!DOCTYPE html>
<!--
============================================================================
agent verf - Test UI
Version: 0.1.0
Last Updated: 2026-01-13
Simple HTML interface for testing the verification API.
Open this file directly in your browser.
============================================================================
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>agent verf - Test UI</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
verdict: {
true: '#22c55e',
false: '#ef4444',
misleading: '#f59e0b',
satire: '#8b5cf6',
unverified: '#6b7280',
disputed: '#f97316',
},
brand: {
primary: '#3b82f6',
secondary: '#1e40af',
},
},
},
},
}
</script>
<style>
.spinner {
animation: spin 1s linear infinite;
border-radius: 50%;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- Header -->
<header class="border-b border-gray-200 bg-white">
<div class="max-w-3xl mx-auto px-4 py-4">
<a href="#" class="flex items-center gap-2">
<span class="text-2xl">🔍</span>
<span class="text-xl font-bold text-gray-900">agent verf</span>
<span class="text-xs bg-gray-100 px-2 py-1 rounded">TEST UI</span>
</a>
</div>
</header>
<!-- Main Content -->
<main class="max-w-3xl mx-auto px-4 py-8">
<!-- Input Section -->
<div id="input-section">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-900 mb-3">Verify Any Content</h1>
<p class="text-gray-600">Paste a URL to get an AI-powered fact-check</p>
</div>
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6">
<form id="verify-form" class="space-y-4">
<div>
<label for="url" class="block text-sm font-medium text-gray-700 mb-2">
Paste a URL to verify
</label>
<input
type="url"
id="url"
placeholder="https://twitter.com/... or any URL"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none"
required
>
</div>
<button
type="submit"
id="submit-btn"
class="w-full py-3 px-4 rounded-lg font-medium text-white bg-brand-primary hover:bg-brand-secondary transition-colors"
>
Verify Content
</button>
</form>
<p id="error-msg" class="mt-3 text-red-500 text-sm hidden"></p>
</div>
</div>
<!-- Progress Section -->
<div id="progress-section" class="hidden text-center">
<div class="spinner w-16 h-16 mx-auto mb-6"></div>
<h2 class="text-2xl font-bold text-gray-900 mb-4">Verifying Content</h2>
<div class="w-full bg-gray-200 rounded-full h-3 mb-4 max-w-md mx-auto">
<div id="progress-bar" class="bg-brand-primary h-3 rounded-full transition-all duration-500" style="width: 10%"></div>
</div>
<p id="progress-step" class="text-gray-600">Starting...</p>
<div class="mt-6 flex justify-center gap-2">
<div id="dot-1" class="w-3 h-3 rounded-full bg-gray-300"></div>
<div id="dot-2" class="w-3 h-3 rounded-full bg-gray-300"></div>
<div id="dot-3" class="w-3 h-3 rounded-full bg-gray-300"></div>
<div id="dot-4" class="w-3 h-3 rounded-full bg-gray-300"></div>
</div>
</div>
<!-- Result Section -->
<div id="result-section" class="hidden">
<!-- Verdict Header -->
<div class="text-center mb-8">
<span id="verdict-badge" class="inline-flex items-center px-4 py-2 rounded-full font-bold text-lg"></span>
<p id="verdict-meta" class="mt-2 text-gray-500"></p>
</div>
<!-- Summary -->
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-900 mb-3">Summary</h2>
<p id="verdict-summary" class="text-gray-700"></p>
</div>
<!-- Key Findings -->
<div id="findings-card" class="bg-white rounded-lg shadow-md border border-gray-200 p-6 mb-6 hidden">
<h2 class="text-lg font-semibold text-gray-900 mb-3">Key Findings</h2>
<ul id="findings-list" class="space-y-2"></ul>
</div>
<!-- Evidence -->
<div id="evidence-card" class="bg-white rounded-lg shadow-md border border-gray-200 p-6 mb-6 hidden">
<h2 class="text-lg font-semibold text-gray-900 mb-3">Evidence</h2>
<div id="evidence-list" class="space-y-4"></div>
</div>
<!-- Actions -->
<div class="flex justify-center gap-4 mt-8">
<button onclick="location.reload()" class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg font-medium text-gray-900">
Verify Another
</button>
</div>
</div>
</main>
<script>
const API_URL = 'http://localhost:8080';
const VERDICT_STYLES = {
TRUE: { bg: 'bg-green-100', text: 'text-green-700', icon: '✓', label: 'True' },
FALSE: { bg: 'bg-red-100', text: 'text-red-700', icon: '✗', label: 'False' },
MISLEADING: { bg: 'bg-amber-100', text: 'text-amber-700', icon: '⚠', label: 'Misleading' },
SATIRE: { bg: 'bg-purple-100', text: 'text-purple-700', icon: '🎭', label: 'Satire' },
UNVERIFIED: { bg: 'bg-gray-100', text: 'text-gray-700', icon: '?', label: 'Unverified' },
DISPUTED: { bg: 'bg-orange-100', text: 'text-orange-700', icon: '⚡', label: 'Disputed' },
};
const STEP_LABELS = {
extraction: 'Extracting content...',
triage: 'Analyzing content...',
evidence: 'Gathering evidence...',
synthesizer: 'Generating verdict...',
done: 'Complete!',
};
// Form submission
document.getElementById('verify-form').addEventListener('submit', async (e) => {
e.preventDefault();
const url = document.getElementById('url').value;
try {
showProgress();
// Submit verification
const response = await fetch(`${API_URL}/api/v1/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, scan_mode: 'quick_scan' }),
});
if (!response.ok) throw new Error('Failed to submit');
const data = await response.json();
pollStatus(data.request_id);
} catch (err) {
showError(err.message);
}
});
function showProgress() {
document.getElementById('input-section').classList.add('hidden');
document.getElementById('progress-section').classList.remove('hidden');
}
function showError(msg) {
document.getElementById('input-section').classList.remove('hidden');
document.getElementById('progress-section').classList.add('hidden');
const errorEl = document.getElementById('error-msg');
errorEl.textContent = msg;
errorEl.classList.remove('hidden');
}
async function pollStatus(requestId) {
try {
const response = await fetch(`${API_URL}/api/v1/status/${requestId}`);
const data = await response.json();
// Update progress UI
const progress = data.progress || 0;
document.getElementById('progress-bar').style.width = `${Math.max(progress * 100, 10)}%`;
document.getElementById('progress-step').textContent = STEP_LABELS[data.current_step] || 'Processing...';
// Update dots
const steps = ['extraction', 'triage', 'evidence', 'synthesizer'];
steps.forEach((step, i) => {
const dot = document.getElementById(`dot-${i + 1}`);
if (data.current_step === step) {
dot.className = 'w-3 h-3 rounded-full bg-brand-primary animate-pulse';
} else if (progress > (i + 1) * 0.25) {
dot.className = 'w-3 h-3 rounded-full bg-brand-primary';
} else {
dot.className = 'w-3 h-3 rounded-full bg-gray-300';
}
});
if (data.status === 'completed') {
fetchReceipt(requestId);
} else if (data.status === 'failed') {
showError(data.error || 'Verification failed');
} else {
setTimeout(() => pollStatus(requestId), 1000);
}
} catch (err) {
showError('Failed to check status');
}
}
async function fetchReceipt(requestId) {
try {
const response = await fetch(`${API_URL}/api/v1/receipt/${requestId}`);
const receipt = await response.json();
showResult(receipt);
} catch (err) {
showError('Failed to fetch results');
}
}
function showResult(receipt) {
document.getElementById('progress-section').classList.add('hidden');
document.getElementById('result-section').classList.remove('hidden');
const verdict = receipt.verdict;
const style = VERDICT_STYLES[verdict.status] || VERDICT_STYLES.UNVERIFIED;
// Verdict badge
const badge = document.getElementById('verdict-badge');
badge.className = `inline-flex items-center px-4 py-2 rounded-full font-bold text-lg ${style.bg} ${style.text}`;
badge.innerHTML = `<span class="mr-2">${style.icon}</span>${style.label}`;
// Meta info
document.getElementById('verdict-meta').textContent =
`${Math.round(verdict.confidence * 100)}% confidence · ${receipt.sources_checked} sources checked`;
// Summary
document.getElementById('verdict-summary').textContent = verdict.summary;
// Key findings
if (verdict.key_findings && verdict.key_findings.length > 0) {
const findingsCard = document.getElementById('findings-card');
const findingsList = document.getElementById('findings-list');
findingsCard.classList.remove('hidden');
findingsList.innerHTML = verdict.key_findings.map(f =>
`<li class="flex items-start gap-2">
<span class="text-brand-primary">•</span>
<span class="text-gray-700">${f}</span>
</li>`
).join('');
}
// Evidence
if (receipt.evidence && receipt.evidence.length > 0) {
const evidenceCard = document.getElementById('evidence-card');
const evidenceList = document.getElementById('evidence-list');
evidenceCard.classList.remove('hidden');
evidenceList.innerHTML = receipt.evidence.map(ev => {
const source = ev.sources[0];
return `<div class="border-l-2 border-gray-200 pl-4">
<p class="text-gray-700">${ev.finding}</p>
${source ? `<p class="mt-1 text-sm text-gray-500">
Source: <a href="${source.url}" target="_blank" class="text-brand-primary hover:underline">${source.domain}</a>
(authority: ${Math.round(source.authority_score * 100)}%)
</p>` : ''}
</div>`;
}).join('');
}
}
</script>
</body>
</html>