Spaces:
Build error
Build error
| 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 { motion, AnimatePresence } from 'framer-motion'; | |
| import AskExpertPanel from './AskExpertPanel'; | |
| const models = [ | |
| { id: 'multi', label: 'Multi-Model Consensus (Safe Average)', provider: 'consensus' }, | |
| { id: 'groq/openai/gpt-oss-120b', label: 'Groq OSS-120B (Powerhouse)', provider: 'groq' }, | |
| { id: 'groq/llama-4-70b-versatile', label: 'Llama 4 70B (Fast Versatile)', provider: 'groq' }, | |
| { id: 'kimi/kimi-3.0', label: 'Kimi 3.0 (NVIDIA Thinking)', provider: 'moonshot' }, | |
| { id: 'qwen/qwen-max-2', label: 'Qwen Max 2 (Analytical Depth)', provider: 'dashscope' }, | |
| { id: 'zai/glm-5', label: 'Z.ai GLM-5 (Reasoning Pro)', provider: 'zai' }, | |
| { id: 'deepseek/r2', label: 'DeepSeek R2 (Logic & Strategy Beast)', provider: 'deepseek' }, | |
| { id: 'openrouter/anthropic/claude-4-sonnet', label: 'Claude 4 Sonnet (via OpenRouter)', provider: 'openrouter' }, | |
| { id: 'openrouter/openai/gpt-5o-mini', label: 'GPT-5o Mini (via OpenRouter)', provider: 'openrouter' }, | |
| { id: 'openrouter/google/gemini-3-pro', label: 'Gemini 3 Pro (via OpenRouter)', provider: 'openrouter' }, | |
| { id: 'gemini/gemini-2.5-pro-preview-06-05', label: 'Gemini 2.5 Pro (Direct)', provider: 'gemini' }, | |
| { id: 'hf/NousResearch/Hermes-4-Llama-4-70B', label: 'Hermes-4 Llama 4 70B (HF)', provider: 'hf' }, | |
| ]; | |
| 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 |