import { useState, useCallback, useRef } from 'react'; import { useDropzone } from 'react-dropzone'; import axios from 'axios'; import { Search, CheckCircle, AlertTriangle, Globe, ShieldCheck, FileText, Upload, X, Brain, Loader2 } from 'lucide-react'; import AskExpertPanel from './AskExpertPanel'; const models = [ { id: 'multi', label: 'Multi-Model Consensus (Safe Average)', provider: 'consensus' }, { id: 'groq/llama-3.1-70b-versatile', label: 'Groq Llama 3.1 70B (Fast)', provider: 'groq' }, { id: 'openrouter/anthropic/claude-3.5-sonnet', label: 'Claude 3.5 Sonnet (via OpenRouter)', provider: 'openrouter' }, { id: 'deepseek/deepseek-chat', label: 'DeepSeek Chat (Logic)', provider: 'deepseek' }, { id: 'gemini/gemini-1.5-pro', label: 'Gemini 1.5 Pro', provider: 'gemini' }, ]; export default function VerificationCore() { const [query, setQuery] = useState(''); const [file, setFile] = useState(null); const [filePreview, setFilePreview] = useState(null); const [fileContent, setFileContent] = useState(''); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [selectedMode, setSelectedMode] = useState('multi'); const [bendMode, setBendMode] = useState(false); const [searchStatus, setSearchStatus] = useState(''); const [progress, setProgress] = useState(0); const abortControllerRef = useRef(null); const onDrop = useCallback(async (acceptedFiles) => { const uploadedFile = acceptedFiles[0]; if (!uploadedFile) return; if (uploadedFile.size > 20 * 1024 * 1024) { setError('File size exceeds 20MB limit'); return; } setFile(uploadedFile); setError(null); setFileContent(''); setFilePreview(null); try { if (uploadedFile.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = () => setFilePreview(reader.result); reader.readAsDataURL(uploadedFile); } else if (uploadedFile.type === 'application/pdf') { const formData = new FormData(); formData.append('file', uploadedFile); const response = await axios.post('/api/parse-pdf', formData); setFileContent(response.data.text); } else if (uploadedFile.type.startsWith('text/') || ['.md', '.csv', '.json'].some(ext => uploadedFile.name.toLowerCase().endsWith(ext))) { const text = await uploadedFile.text(); setFileContent(text); } } catch (err) { setError('Failed to process file: ' + err.message); } }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'image/*': ['.jpeg', '.jpg', '.png', '.webp', '.gif'], 'application/pdf': ['.pdf'], 'text/*': ['.txt', '.md', '.csv', '.json'], }, maxFiles: 1, maxSize: 20 * 1024 * 1024, }); const removeFile = () => { setFile(null); setFilePreview(null); setFileContent(''); setError(null); }; const handleVerify = async (e) => { e.preventDefault(); if (!query.trim() && !fileContent) { setError('Please enter a query or upload a file'); return; } setLoading(true); setError(null); setResults([]); setProgress(0); abortControllerRef.current = new AbortController(); try { let finalPrompt = query.trim(); let contextData = ''; if (fileContent) { contextData = `\n\n[FILE CONTENT]:\n${fileContent.slice(0, 15000)}${fileContent.length > 15000 ? '... (truncated)' : ''}`; finalPrompt += contextData; } if (bendMode && /current|latest|today|news|update|verify|fact-check/i.test(query)) { setSearchStatus('Searching web for latest context...'); try { const searchRes = await axios.post('/api/websearch', { query: query.slice(0, 100), maxResults: 5 }, { signal: abortControllerRef.current.signal }); const searchResults = searchRes.data.results || []; if (searchResults.length > 0) { const searchContext = searchResults.map((r, i) => `[${i + 1}] ${r.title}: ${r.snippet}`).join('\n'); finalPrompt += `\n\n[WEB SEARCH RESULTS]:\n${searchContext}`; } setSearchStatus('Web context integrated'); } catch (err) { setSearchStatus('Web search failed, continuing without'); } } setProgress(30); const progressInterval = setInterval(() => setProgress(p => Math.min(p + 10, 90)), 1000); const response = await axios.post('/api/inference', { prompt: finalPrompt, model: selectedMode, bendMode, systemPrompt: bendMode ? 'You are in BEND MODE (hypothetical/strategic analysis). Prefix all outputs with [HYPOTHETICAL SCENARIO]. Provide analytical, strategic insights without real-world action.' : 'You are a fact-checking and verification assistant. Provide accurate, sourced information with confidence scores.', }, { signal: abortControllerRef.current.signal }); clearInterval(progressInterval); setProgress(100); const resultData = { id: Date.now().toString(), model: selectedMode === 'multi' ? 'Consensus Engine' : models.find(m => m.id === selectedMode)?.label, content: response.data.result, confidence: response.data.confidence || 0.85, timestamp: new Date().toISOString(), sources: response.data.sources || [], isHypothetical: bendMode, query: query.trim(), }; setResults([resultData]); const history = JSON.parse(localStorage.getItem('verificationHistory') || '[]'); history.unshift(resultData); localStorage.setItem('verificationHistory', JSON.stringify(history.slice(0, 50))); } catch (err) { if (err.name === 'AbortError') { setError('Verification cancelled by user'); } else { setError('Verification failed: ' + (err.message || 'Unknown error')); } } finally { setLoading(false); setProgress(0); setTimeout(() => setSearchStatus(''), 3000); } }; const handleCancel = () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; return (

Verification Core

AI-powered claim verification with multi-model consensus

Bend Mode

Hypothetical/strategic analysis without real-world constraints