anycoder-2ae81253 / components /VerificationCore.jsx
santiagr7776's picture
Upload components/VerificationCore.jsx with huggingface_hub
9cfe393 verified
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 (
<div className="min-h-screen p-4 sm:p-6 lg:p-8 pb-32">
<div className="max-w-6xl mx-auto space-y-6">
<div className="text-center mb-8 animate-fade-in">
<h1 className="text-4xl font-bold text-gradient mb-2">Verification Core</h1>
<p className="text-slate-600 dark:text-slate-400">AI-powered claim verification with multi-model consensus</p>
</div>
<div className="card space-y-6 animate-slide-up">
<div className="flex items-center justify-between p-4 bg-helios-50 dark:bg-helios-900/20 rounded-lg border border-helios-200 dark:border-helios-800">
<div className="flex items-center space-x-3">
<Brain className={`w-6 h-6 ${bendMode ? 'text-helios-600' : 'text-slate-400'}`} />
<div>
<h3 className="font-semibold text-slate-900 dark:text-white">Bend Mode</h3>
<p className="text-sm text-slate-600 dark:text-slate-400">Hypothetical/strategic analysis without real-world constraints</p>
</div>
</div>
<button
onClick={() => setBendMode(!bendMode)}
className={`relative inline-flex h-7 w-12 items-center rounded-full transition-colors ${
bendMode ? 'bg-helios-600' : 'bg-slate-300 dark:bg-slate-600'
}`}
>
<span className={`inline-block h-5 w-5 transform rounded-full bg-white transition-transform ${
bendMode ? 'translate-x-6' : 'translate-x-1'
}`} />
</button>
</div>
<form onSubmit={handleVerify} className="space-y-4">
<div className="relative">
<textarea
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={bendMode ? "Enter hypothetical scenario or strategic question..." : "Enter claim to verify, question to research, or topic to analyze..."}
className="input-field min-h-[120px] resize-none"
disabled={loading}
/>
<div className="absolute bottom-3 right-3 text-xs text-slate-400">{query.length} chars</div>
</div>
{!file ? (
<div
{...getRootProps()}
className={`border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors ${
isDragActive
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-slate-300 dark:border-slate-600 hover:border-primary-400'
}`}
>
<input {...getInputProps()} />
<Upload className="w-8 h-8 mx-auto mb-2 text-slate-400" />
<p className="text-sm text-slate-600 dark:text-slate-400">Drop files here or click to upload (PDF, Images, Text)</p>
<p className="text-xs text-slate-400 mt-1">Max 20MB</p>
</div>
) : (
<div className="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700/50 rounded-lg animate-fade-in">
<div className="flex items-center space-x-3">
<FileText className="w-5 h-5 text-slate-500" />
<div>
<p className="font-medium text-slate-900 dark:text-white truncate max-w-xs">{file.name}</p>
<p className="text-xs text-s