liarMP4 / frontend /src /App.tsx
GlazedDon0t's picture
fina p3
5dae8fe
import React, { useState, useRef, useEffect } from 'react';
import {
AlertCircle, Play, Upload, List, Database, BarChart2, ExternalLink,
Users, MessageSquare, TrendingUp, ShieldCheck, UserCheck, Search, PlusCircle,
StopCircle, RefreshCw, CheckCircle2, PenTool, ClipboardCheck, Info, Clock, FileText,
Tag, Home, Cpu, FlaskConical, Target, Trash2, ArrowUpRight, CheckSquare, Square,
Layers, Activity, Zap, BrainCircuit, Network, Archive, Plus, Edit3, RotateCcw,
Bot, Trophy, HelpCircle, Settings, Calculator, ChevronDown, ChevronUp
} from 'lucide-react';
function App() {
const[activeTab, setActiveTab] = useState('home');
const[logs, setLogs] = useState<string>('System Ready.\n');
const [isProcessing, setIsProcessing] = useState(false);
const logContainerRef = useRef<HTMLDivElement>(null);
// Processing Config State
const[modelProvider, setModelProvider] = useState('nrp');
const[apiKey, setApiKey] = useState('');
const[baseUrl, setBaseUrl] = useState('https://ellm.nrp-nautilus.io/v1'); // NRP Default
const[modelName, setModelName] = useState('qwen3'); // Default
const[projectId, setProjectId] = useState('');
const [location, setLocation] = useState('us-central1');
const[includeComments, setIncludeComments] = useState(false);
const[reasoningMethod, setReasoningMethod] = useState('cot');
const[promptTemplate, setPromptTemplate] = useState('standard');
const[customQuery, setCustomQuery] = useState('');
const [maxRetries, setMaxRetries] = useState(1);
const[availablePrompts, setAvailablePrompts] = useState<any[]>([]);
const[useSearch, setUseSearch] = useState(false);
const[useCode, setUseCode] = useState(false);
// Data States
const[queueList, setQueueList] = useState<any[]>([]);
const [selectedQueueItems, setSelectedQueueItems] = useState<Set<string>>(new Set());
const[expandedQueueItems, setExpandedQueueItems] = useState<Set<string>>(new Set());
const[lastQueueIndex, setLastQueueIndex] = useState<number | null>(null);
const[singleLinkInput, setSingleLinkInput] = useState('');
const [profileList, setProfileList] = useState<any[]>([]);
const[selectedProfile, setSelectedProfile] = useState<any>(null);
const [profilePosts, setProfilePosts] = useState<any[]>([]);
const[integrityBoard, setIntegrityBoard] = useState<any[]>([]);
const[datasetList, setDatasetList] = useState<any[]>([]);
const[selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
const[lastDatasetIndex, setLastDatasetIndex] = useState<number | null>(null);
const [benchmarks, setBenchmarks] = useState<any>(null);
const[leaderboard, setLeaderboard] = useState<any[]>([]);
const[refreshTrigger, setRefreshTrigger] = useState(0);
// Tags
const[configuredTags, setConfiguredTags] = useState<any>({});
// Manual Labeling State
const[manualLink, setManualLink] = useState('');
const [manualCaption, setManualCaption] = useState('');
const[manualTags, setManualTags] = useState('');
const[manualReasoning, setManualReasoning] = useState('');
const[manualScores, setManualScores] = useState({
visual: 5, audio: 5, source: 5, logic: 5, emotion: 5,
va: 5, vc: 5, ac: 5, final: 50
});
const[showRubric, setShowRubric] = useState(false);
const[aiReference, setAiReference] = useState<any>(null);
const[labelBrowserMode, setLabelBrowserMode] = useState<'queue' | 'dataset'>('queue');
const[labelFilter, setLabelFilter] = useState('');
// Agent Chat State
const [agentInput, setAgentInput] = useState('');
const[agentMessages, setAgentMessages] = useState<any[]>([]);
const[agentThinking, setAgentThinking] = useState(false);
const [agentEndpoint, setAgentEndpoint] = useState('/a2a');
const[agentMethod, setAgentMethod] = useState('agent.process');
const [agentConfig, setAgentConfig] = useState({ use_search: true, use_code: false });
// Resampling configuration
const[resampleCount, setResampleCount] = useState<number>(1);
// Drag Selection references
const isDraggingQueueRef = useRef(false);
const isDraggingDatasetRef = useRef(false);
// Quick Demo State
const[demoLink, setDemoLink] = useState('');
const[demoLogs, setDemoLogs] = useState('');
const[demoIsProcessing, setDemoIsProcessing] = useState(false);
const[demoResult, setDemoResult] = useState<any>(null);
const[showDemoConfig, setShowDemoConfig] = useState(false);
const demoLogContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleMouseUp = () => {
isDraggingQueueRef.current = false;
isDraggingDatasetRef.current = false;
};
window.addEventListener('mouseup', handleMouseUp);
return () => window.removeEventListener('mouseup', handleMouseUp);
},[]);
const handleTagsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setManualTags(e.target.value);
};
useEffect(() => {
const load = async (url: string, setter: any) => {
try { const res = await fetch(url); const d = await res.json(); setter(Array.isArray(d) ? d : (d.status==='no_data'?null:d)); } catch(e) {}
};
load('/config/prompts', setAvailablePrompts);
load('/config/tags', setConfiguredTags);
if (activeTab === 'home') {
load('/benchmarks/stats', setBenchmarks);
load('/benchmarks/leaderboard', setLeaderboard);
}
if (activeTab === 'queue') {
load('/queue/list', setQueueList);
setSelectedQueueItems(new Set());
setLastQueueIndex(null);
}
if (activeTab === 'profiles') load('/profiles/list', setProfileList);
if (activeTab === 'analytics') load('/analytics/account_integrity', setIntegrityBoard);
if (activeTab === 'dataset' || activeTab === 'manual' || activeTab === 'groundtruth') load('/dataset/list', setDatasetList);
if (activeTab === 'manual') load('/queue/list', setQueueList);
setSelectedItems(new Set());
setLastDatasetIndex(null);
},[activeTab, refreshTrigger]);
useEffect(() => {
if (logContainerRef.current) logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
}, [logs]);
useEffect(() => {
if (demoLogContainerRef.current) demoLogContainerRef.current.scrollTop = demoLogContainerRef.current.scrollHeight;
},[demoLogs]);
useEffect(() => {
if (activeTab === 'agent' && agentMessages.length === 0) {
callAgent(agentMethod, {
input: "hello",
agent_config: { provider: modelProvider === 'gcloud' ? 'vertex' : modelProvider, api_key: apiKey, project_id: projectId }
})
.then(res => res.json())
.then(data => {
if (data.result && data.result.text) {
setAgentMessages([{role: 'agent', content: data.result.text}]);
}
})
.catch(e => console.error("Initial Agent Ping Failed", e));
}
}, [activeTab]);
const existingTags = React.useMemo(() => {
const tags = new Set<string>();
Object.keys(configuredTags).forEach(t => tags.add(t));
if (datasetList && Array.isArray(datasetList)) {
datasetList.forEach(item => {
if (item.tags && typeof item.tags === 'string') {
item.tags.split(/[,\n|;]+/).forEach((t: string) => {
const clean = t.trim().replace(/^['"]|['"]$/g, '');
if (clean.length > 1) tags.add(clean);
});
}
});
}
return Array.from(tags).sort();
},[datasetList, configuredTags]);
const toggleTag = (tag: string) => {
let current = manualTags.split(',').map(t => t.trim()).filter(Boolean);
if (current.includes(tag)) {
current = current.filter(t => t !== tag);
} else {
current.push(tag);
}
setManualTags(current.join(', '));
};
const loadProfilePosts = async (username: string) => {
const res = await fetch(`/profiles/${username}/posts`);
const data = await res.json();
setProfilePosts(data);
setSelectedProfile(username);
};
const sendToManualLabeler = (link: string, text: string) => {
setManualLink(link);
setManualCaption(text);
setManualScores({
visual: 5, audio: 5, source: 5, logic: 5, emotion: 5,
va: 5, vc: 5, ac: 5, final: 50
});
setManualReasoning('');
setManualTags('');
setAiReference(null);
const ref = datasetList.find(d =>
d.source !== 'Manual' &&
d.source !== 'manual_promoted' &&
(d.link === link)
);
setAiReference(ref || null);
setActiveTab('manual');
};
const loadFromBrowser = (item: any, mode: 'queue' | 'dataset') => {
setManualLink(item.link);
const ref = datasetList.find(d =>
d.source !== 'Manual' &&
d.source !== 'manual_promoted' &&
(d.id === item.id || d.link === item.link)
);
setAiReference(ref || null);
if (mode === 'dataset') {
setManualCaption(item.caption || '');
setManualTags(item.tags || '');
setManualReasoning(item.reasoning || item.final_reasoning || '');
setManualScores({
visual: parseInt(item.visual_integrity_score || item.visual_score) || 5,
audio: parseInt(item.audio_integrity_score || item.audio_score) || 5,
source: parseInt(item.source_credibility_score || item.source_score) || 5,
logic: parseInt(item.logical_consistency_score || item.logic_score) || 5,
emotion: parseInt(item.emotional_manipulation_score || item.emotion_score) || 5,
va: parseInt(item.video_audio_score || item.align_video_audio) || 5,
vc: parseInt(item.video_caption_score || item.align_video_caption) || 5,
ac: parseInt(item.audio_caption_score || item.align_audio_caption) || 5,
final: parseInt(item.final_veracity_score) || 50
});
} else {
setManualCaption(''); setManualReasoning(''); setManualTags('');
setManualScores({
visual: 5, audio: 5, source: 5, logic: 5, emotion: 5,
va: 5, vc: 5, ac: 5, final: 50
});
}
};
const editSelectedLabel = () => {
if (selectedItems.size !== 1) return alert("Please select exactly one item to edit.");
const id = Array.from(selectedItems)[0];
const item = datasetList.find(d => d.id === id);
if(!item) return;
loadFromBrowser(item, 'dataset');
setActiveTab('manual');
};
const toggleSelection = (e: React.MouseEvent, id: string, index: number, list: any[]) => {
let newSet = new Set(selectedItems);
if (e.shiftKey && lastDatasetIndex !== null && lastDatasetIndex !== index) {
const start = Math.min(lastDatasetIndex, index);
const end = Math.max(lastDatasetIndex, index);
for (let i = start; i <= end; i++) {
newSet.add(list[i].id);
}
} else {
if (newSet.has(id)) newSet.delete(id);
else newSet.add(id);
}
setSelectedItems(newSet);
setLastDatasetIndex(index);
};
const toggleQueueSelection = (e: React.MouseEvent, link: string, index: number, list: any[]) => {
let newSet = new Set(selectedQueueItems);
if (e.shiftKey && lastQueueIndex !== null && lastQueueIndex !== index) {
const start = Math.min(lastQueueIndex, index);
const end = Math.max(lastQueueIndex, index);
for (let i = start; i <= end; i++) {
newSet.add(list[i].link);
}
} else {
if (newSet.has(link)) newSet.delete(link);
else newSet.add(link);
}
setSelectedQueueItems(newSet);
setLastQueueIndex(index);
};
const toggleQueueExpand = (e: React.MouseEvent, link: string) => {
e.stopPropagation();
const newSet = new Set(expandedQueueItems);
if (newSet.has(link)) newSet.delete(link);
else newSet.add(link);
setExpandedQueueItems(newSet);
};
const promoteSelected = async () => {
if (selectedItems.size === 0) return alert("No items selected.");
if (!confirm(`Promote ${selectedItems.size} items to Ground Truth?`)) return;
try {
const res = await fetch('/manual/promote', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ids: Array.from(selectedItems) })
});
const d = await res.json();
if(d.status === 'success') {
alert(`Successfully promoted ${d.promoted_count} items.`);
setSelectedItems(new Set());
setRefreshTrigger(p => p+1);
} else alert("Promotion failed: " + d.message);
} catch(e: any) { alert("Network error: " + e.toString()); }
};
const verifySelected = async () => {
if (selectedItems.size === 0) return alert("No items selected.");
if (!confirm(`Queue ${selectedItems.size} Ground Truth items for AI Verification Pipeline?`)) return;
try {
const res = await fetch('/manual/verify_queue', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ids: Array.from(selectedItems), resample_count: resampleCount })
});
const d = await res.json();
if(d.status === 'success') {
alert(d.message);
setSelectedItems(new Set());
setActiveTab('queue');
setRefreshTrigger(p => p+1);
} else alert("Error: " + d.message);
} catch(e) { alert("Network error."); }
};
const deleteSelected = async () => {
if (selectedItems.size === 0) return alert("No items selected.");
if (!confirm(`Delete ${selectedItems.size} items from Ground Truth? Irreversible.`)) return;
try {
const res = await fetch('/manual/delete', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ids: Array.from(selectedItems) })
});
const d = await res.json();
if(d.status === 'success') {
alert(`Deleted ${d.deleted_count} items.`);
setSelectedItems(new Set());
setRefreshTrigger(p => p+1);
} else alert("Error deleting: " + d.message);
} catch(e) { alert("Network error."); }
};
const deleteDataEntries = async () => {
if (selectedItems.size === 0) return alert("No items selected.");
if (!confirm(`Delete ${selectedItems.size} items? This cannot be undone.`)) return;
const selectedArray = Array.from(selectedItems);
const manualIds = selectedArray.filter(id => datasetList.find(d => d.id === id)?.source === 'Manual');
const aiIds = selectedArray.filter(id => {
const item = datasetList.find(d => d.id === id);
return item?.source === 'AI' || !item?.source;
});
try {
let msg = "";
if (manualIds.length > 0) {
const res = await fetch('/manual/delete', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ids: manualIds })
});
const d = await res.json();
if (d.status === 'success') msg += `Deleted ${d.deleted_count} Manual items. `;
else msg += `Failed Manual delete: ${d.message}. `;
}
if (aiIds.length > 0) {
const res = await fetch('/dataset/delete', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ ids: aiIds })
});
const d = await res.json();
if (d.status === 'success') msg += `Deleted ${d.deleted_count} AI items. `;
else msg += `Failed AI delete: ${d.message}. `;
}
alert(msg || "Done.");
setSelectedItems(new Set());
setRefreshTrigger(p => p + 1);
} catch (e) {
alert("Network error: " + e);
}
};
const submitManualLabel = async () => {
if(!manualLink) return alert("Link is required.");
const payload = {
link: manualLink, caption: manualCaption, tags: manualTags, reasoning: manualReasoning,
visual_integrity_score: manualScores.visual, audio_integrity_score: manualScores.audio,
source_credibility_score: manualScores.source, logical_consistency_score: manualScores.logic,
emotional_manipulation_score: manualScores.emotion,
video_audio_score: manualScores.va, video_caption_score: manualScores.vc, audio_caption_score: manualScores.ac,
final_veracity_score: manualScores.final,
classification: "Manual Verified"
};
try {
const res = await fetch('/manual/save', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) });
const d = await res.json();
if(d.status === 'success') {
alert("Label Saved! Data updated.");
setRefreshTrigger(p => p+1);
} else alert("Error saving label: " + d.message);
} catch(e: any) { alert("Network error: " + e.toString()); }
};
const queueUnlabeledPosts = async () => {
const unlabeled = profilePosts.filter(p => !p.is_labeled).map(p => p.link);
if(unlabeled.length === 0) return alert("All posts already labeled!");
const csvContent = "link\n" + unlabeled.join("\n");
const blob = new Blob([csvContent], { type: 'text/csv' });
const fd = new FormData(); fd.append("file", blob, "batch_upload.csv");
try {
await fetch('/queue/upload_csv', { method: 'POST', body: fd });
alert(`Queued ${unlabeled.length} links.`); setRefreshTrigger(p => p+1);
} catch (e) { alert("Error uploading."); }
};
const addSingleLink = async () => {
if(!singleLinkInput) return;
try {
const res = await fetch('/queue/add', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ link: singleLinkInput })
});
const d = await res.json();
if(d.status === 'success') {
setSingleLinkInput('');
setRefreshTrigger(p => p+1);
} else { alert(d.message); }
} catch(e) { alert("Error adding link"); }
};
const addSingleLinkDirect = async (link: string) => {
if(!link) return;
try {
const res = await fetch('/queue/add', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ link: link })
});
const d = await res.json();
if(d.status === 'success') {
setRefreshTrigger(p => p+1);
} else { alert(d.message); }
} catch(e) { alert("Error adding link"); }
};
const clearProcessed = async () => {
if(!confirm("Remove all 'Processed' items from the queue?")) return;
try {
const res = await fetch('/queue/clear_processed', { method: 'POST' });
const d = await res.json();
alert(`Removed ${d.removed_count} processed items.`);
setRefreshTrigger(p => p+1);
} catch(e) { alert("Error clearing queue."); }
};
const requeueItems = async () => {
if(selectedQueueItems.size === 0) return alert("No items selected.");
if(!confirm(`Requeue ${selectedQueueItems.size} items? Their status will reset to Pending for processing.`)) return;
try {
const res = await fetch('/queue/requeue', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ links: Array.from(selectedQueueItems) })
});
const d = await res.json();
if(d.status === 'success') {
alert(`Requeued ${d.count} items.`);
setSelectedQueueItems(new Set());
setRefreshTrigger(p => p+1);
}
} catch(e) { alert("Error requeuing items."); }
};
const deleteQueueItems = async () => {
if(selectedQueueItems.size === 0) return alert("No items selected.");
if(!confirm(`Remove ${selectedQueueItems.size} items from queue?`)) return;
try {
const res = await fetch('/queue/delete', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ links: Array.from(selectedQueueItems) })
});
const d = await res.json();
if(d.status === 'success') {
alert(`Removed ${d.count} items.`);
setSelectedQueueItems(new Set());
setRefreshTrigger(p => p+1);
}
} catch(e) { alert("Error deleting queue items."); }
};
const stopProcessing = async () => {
if(!confirm("Stop batch processing?")) return;
await fetch('/queue/stop', { method: 'POST' });
setLogs(prev => prev + '\n[SYSTEM] Stop Signal Sent.\n');
};
const startProcessing = async () => {
if (isProcessing) return;
setIsProcessing(true);
setLogs(prev => prev + '\n[SYSTEM] Starting Queue Processing...\n');
const fd = new FormData();
const activeProvider = modelProvider === 'gcloud' ? 'vertex' : modelProvider;
fd.append('model_selection', activeProvider);
fd.append('gemini_api_key', apiKey);
fd.append('gemini_model_name', modelName);
fd.append('vertex_project_id', projectId);
fd.append('vertex_location', location);
fd.append('vertex_model_name', modelName);
fd.append('vertex_api_key', apiKey);
// Provide generic NRP configs
fd.append('nrp_api_key', apiKey);
fd.append('nrp_model_name', modelName);
fd.append('nrp_base_url', baseUrl);
fd.append('include_comments', includeComments.toString());
fd.append('reasoning_method', reasoningMethod);
fd.append('prompt_template', promptTemplate);
fd.append('custom_query', customQuery);
fd.append('max_reprompts', maxRetries.toString());
fd.append('use_search', useSearch.toString());
fd.append('use_code', useCode.toString());
try {
const res = await fetch('/queue/run', { method: 'POST', body: fd });
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value.includes('event: close')) { setIsProcessing(false); break; }
const clean = value.replace(/data: /g, '').trim();
if (clean) setLogs(prev => prev + clean + '\n');
}
} catch (e) { setIsProcessing(false); }
setRefreshTrigger(p => p+1);
};
const runDemo = async () => {
if (!demoLink) return;
setDemoIsProcessing(true);
setDemoLogs('[SYSTEM] Preparing pipeline for single execution...\n');
setDemoResult(null);
try {
// 1. Ensure the link is added and forcefully requeued so it is 'Pending'
await fetch('/queue/add', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ link: demoLink })
});
await fetch('/queue/requeue', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ links: [demoLink] })
});
// 2. Setup FormData exactly like normal queue
const fd = new FormData();
const activeProvider = modelProvider === 'gcloud' ? 'vertex' : modelProvider;
fd.append('model_selection', activeProvider);
fd.append('gemini_api_key', apiKey);
fd.append('gemini_model_name', modelName);
fd.append('vertex_project_id', projectId);
fd.append('vertex_location', location);
fd.append('vertex_model_name', modelName);
fd.append('vertex_api_key', apiKey);
fd.append('nrp_api_key', apiKey);
fd.append('nrp_model_name', modelName);
fd.append('nrp_base_url', baseUrl);
fd.append('include_comments', includeComments.toString());
fd.append('reasoning_method', reasoningMethod);
fd.append('prompt_template', promptTemplate);
fd.append('custom_query', customQuery);
fd.append('max_reprompts', maxRetries.toString());
fd.append('use_search', useSearch.toString());
fd.append('use_code', useCode.toString());
setDemoLogs(prev => prev + '[SYSTEM] Sending analysis payload to model server...\n');
// 3. Read Stream
const runRes = await fetch('/queue/run', { method: 'POST', body: fd });
const reader = runRes.body!.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value.includes('event: close')) break;
const clean = value.replace(/data: /g, '').trim();
if (clean) setDemoLogs(prev => prev + clean + '\n');
}
// 4. Look up result from dataset
setDemoLogs(prev => prev + '\n[SYSTEM] Fetching structured object result...\n');
const dsRes = await fetch('/dataset/list');
const dsList = await dsRes.json();
const normalize = (l: string) => l.split('?')[0].replace('https://', '').replace('http://', '').replace('www.', '').replace(/\/$/, '');
const targetLink = normalize(demoLink);
// Sort descending to ensure we get the latest processed run
dsList.sort((a: any, b: any) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
const match = dsList.find((d: any) => normalize(d.link || '') === targetLink && d.source !== 'Manual');
if (match) {
setDemoResult(match);
setDemoLogs(prev => prev + '[SYSTEM] Result rendered successfully.\n');
} else {
setDemoLogs(prev => prev + '[SYSTEM] Error: Could not find parsed result. Processing might have failed.\n');
}
} catch (err: any) {
setDemoLogs(prev => prev + `\n[ERROR] ${err.message}\n`);
} finally {
setDemoIsProcessing(false);
setRefreshTrigger(p => p+1);
}
}
const callAgent = async (method: string, payloadParams: any) => {
return fetch(agentEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: "2.0",
method: method,
params: payloadParams,
id: Date.now()
})
});
};
const sendAgentMessage = async () => {
if (!agentInput.trim() || agentThinking) return;
setAgentMessages(prev =>[...prev, {role: 'user', content: agentInput}]);
const currentInput = agentInput;
setAgentInput('');
setAgentThinking(true);
try {
const fullAgentConfig = {
...agentConfig,
provider: modelProvider === 'gcloud' ? 'vertex' : modelProvider,
api_key: apiKey,
base_url: baseUrl,
project_id: projectId,
location: location,
model_name: modelName,
reasoning_method: reasoningMethod,
prompt_template: promptTemplate
};
let res = await callAgent(agentMethod, { input: currentInput, agent_config: fullAgentConfig });
let data = await res.json();
if (data.error && data.error.code === -32601 && agentMethod === 'agent.process') {
res = await callAgent('agent.generate', { input: currentInput, agent_config: fullAgentConfig });
data = await res.json();
if (!data.error) {
setAgentMethod('agent.generate');
}
}
let reply = "Agent sent no text.";
if (data.error) {
reply = `Agent Error: ${data.error.message || JSON.stringify(data.error)}`;
} else if (data.result) {
if (typeof data.result === 'string') reply = data.result;
else if (data.result.text) reply = data.result.text;
else if (data.result.content) reply = data.result.content;
else reply = JSON.stringify(data.result);
if (data.result.update_config) {
const cfg = data.result.update_config;
if (cfg.provider) setModelProvider(cfg.provider);
if (cfg.api_key) setApiKey(cfg.api_key);
if (cfg.project_id) setProjectId(cfg.project_id);
}
}
setAgentMessages(prev =>[...prev, {role: 'agent', content: reply}]);
if (currentInput.toLowerCase().includes("queue") || currentInput.includes("http")) {
setTimeout(() => setRefreshTrigger(p => p+1), 2000);
}
} catch (e: any) {
setAgentMessages(prev =>[...prev, {role: 'agent', content: `Connection Error: ${e.message}.`}]);
} finally {
setAgentThinking(false);
}
};
return (
<div className="flex h-screen w-full bg-[#09090b] text-slate-200 font-sans overflow-hidden">
<datalist id="modelSuggestions">
<option value="gemini-1.5-pro" />
<option value="gemini-1.5-flash" />
<option value="gemini-2.0-flash" />
<option value="qwen3" />
<option value="gpt-oss" />
<option value="kimi" />
<option value="glm-4.7" />
<option value="minimax-m2" />
<option value="glm-v" />
<option value="gemma3" />
</datalist>
{/* SIDEBAR */}
<div className="w-[280px] flex flex-col border-r border-slate-800/60 bg-[#0c0c0e]">
<div className="h-16 flex items-center px-6 border-b border-slate-800/60">
<h1 className="text-sm font-bold text-white">vChat <span className="text-slate-500">Manager</span></h1>
</div>
<div className="flex-1 p-4 space-y-1">
{[
{id:'home', l:'Home & Benchmarks', i:Home},
{id:'agent', l:'Agent Nexus', i:Bot},
{id:'queue', l:'Ingest Queue', i:List},
{id:'profiles', l:'User Profiles', i:Users},
{id:'manual', l:'Labeling Studio', i:PenTool},
{id:'dataset', l:'Data Manager', i:Archive},
{id:'groundtruth', l:'Ground Truth (Verified)', i:ShieldCheck},
{id:'analytics', l:'Analytics', i:BarChart2}
].map(t => (
<button key={t.id} onClick={() => setActiveTab(t.id)}
className={`w-full flex items-center gap-3 px-4 py-3 text-xs font-medium rounded-lg ${activeTab===t.id ? 'bg-indigo-600/20 text-indigo-300' : 'text-slate-500 hover:bg-white/5'}`}>
<t.i className="w-4 h-4" /> {t.l}
</button>
))}
</div>
</div>
{/* MAIN CONTENT */}
<div className="flex-1 flex flex-col bg-[#09090b] overflow-hidden">
<div className="h-16 border-b border-slate-800/60 flex items-center px-8 bg-[#09090b]">
<span className="text-sm font-bold text-white uppercase tracking-wider">{activeTab}</span>
</div>
<div className="flex-1 p-6 overflow-hidden flex flex-col">
{/* HOME TAB */}
{activeTab === 'home' && (
<div className="h-full overflow-y-auto space-y-8 max-w-5xl pr-2">
{/* QUICK DEMO SECTION */}
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-6 shadow-sm">
<div className="flex justify-between items-start mb-4">
<div>
<h2 className="text-xl font-bold text-white flex items-center gap-2">
<Zap className="w-5 h-5 text-amber-400" /> Try LiarMP4 - Quick Demo
</h2>
<p className="text-sm text-slate-400 mt-1">
Test the Multimodal Factuality Pipeline on a single video URL.
</p>
</div>
<button
onClick={() => setShowDemoConfig(!showDemoConfig)}
className="text-xs font-bold text-slate-500 hover:text-slate-300 flex items-center gap-1 bg-slate-950 px-3 py-1.5 rounded border border-slate-800"
>
<Settings className="w-4 h-4" />
{showDemoConfig ? "Hide Config" : "Show Config"}
</button>
</div>
{showDemoConfig && (
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800 mb-4 grid grid-cols-2 gap-6 animate-in fade-in zoom-in-95 duration-200">
<div className="space-y-3">
<label className="text-[10px] text-slate-500 uppercase font-bold block border-b border-slate-800 pb-1">LLM Provider Config</label>
<select value={modelProvider} onChange={e => setModelProvider(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white">
<option value="vertex">Vertex AI (Enterprise)</option>
<option value="gemini">Gemini API (Public)</option>
<option value="gcloud">Google Cloud (Project + API Key)</option>
<option value="nrp">NRP (Nautilus Envoy Gateway)</option>
</select>
{modelProvider === 'nrp' && (
<>
<input value={baseUrl} onChange={e => setBaseUrl(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="Base URL"/>
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="API Token"/>
</>
)}
{modelProvider === 'gemini' && (
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="API Key"/>
)}
{(modelProvider === 'vertex' || modelProvider === 'gcloud') && (
<>
<input value={projectId} onChange={e => setProjectId(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="Project ID"/>
<input value={location} onChange={e => setLocation(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="Location"/>
{modelProvider === 'gcloud' && <input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="API Key"/>}
</>
)}
<input list="modelSuggestions" value={modelName} onChange={e => setModelName(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white" placeholder="Model Name (e.g. gemini-1.5-pro)"/>
</div>
<div className="space-y-3">
<label className="text-[10px] text-slate-500 uppercase font-bold block border-b border-slate-800 pb-1">Inference Strategy</label>
<select value={reasoningMethod} onChange={e => setReasoningMethod(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white">
<option value="none">Direct (No CoT)</option>
<option value="cot">Standard Chain of Thought</option>
<option value="fcot">Fractal Chain of Thought</option>
</select>
<select value={promptTemplate} onChange={e => setPromptTemplate(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white">
{availablePrompts.length > 0 ? availablePrompts.map(p => (
<option key={p.id} value={p.id}>{p.name}</option>
)) : <option value="standard">Standard</option>}
</select>
<label className="text-[10px] text-slate-500 uppercase font-bold block border-b border-slate-800 pb-1 mt-3">Agentic Tools</label>
<label className="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
<input type="checkbox" className="accent-indigo-500" checked={useSearch} onChange={e => setUseSearch(e.target.checked)} />
Enable Web Search Retrieval
</label>
<label className="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
<input type="checkbox" className="accent-indigo-500" checked={useCode} onChange={e => setUseCode(e.target.checked)} />
Enable Code Execution
</label>
</div>
</div>
)}
<div className="flex gap-2 mb-4">
<input
type="text"
value={demoLink}
onChange={(e) => setDemoLink(e.target.value)}
placeholder="Enter X/Twitter Video URL here..."
className="flex-1 bg-slate-950 border border-slate-700 rounded-lg p-3 text-sm text-white focus:outline-none focus:border-indigo-500"
disabled={demoIsProcessing}
/>
<button
onClick={runDemo}
disabled={demoIsProcessing || !demoLink}
className="bg-indigo-600 hover:bg-indigo-500 disabled:bg-slate-700 text-white px-6 py-3 rounded-lg font-bold flex items-center gap-2 transition"
>
{demoIsProcessing ? <RefreshCw className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4 fill-white" />}
{demoIsProcessing ? "Processing..." : "Analyze"}
</button>
</div>
{(demoIsProcessing || demoLogs) && !demoResult && (
<div className="bg-black border border-slate-800 rounded-lg p-4 font-mono text-[10px] text-emerald-500 h-64 overflow-y-auto whitespace-pre-wrap animate-in fade-in duration-300" ref={demoLogContainerRef}>
{demoLogs || "Initializing pipeline..."}
</div>
)}
{demoResult && (
<div className="mt-6 border-t border-slate-800 pt-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex justify-between items-start mb-6">
<div>
<h3 className="text-xl font-bold text-white flex items-center gap-2">
<ShieldCheck className="w-6 h-6 text-emerald-400" />
<span className="text-indigo-400 uppercase tracking-wider text-[16px] bg-indigo-500/10 px-2 py-0.5 rounded border border-indigo-500/20">[{demoResult.config_model || 'AI'}]
</span>
Analysis Complete
</h3>
<div className="text-xs text-slate-400 mt-2 font-mono">
ID: {demoResult.id} | Prompt: {demoResult.config_prompt} | Reasoning: {demoResult.config_reasoning}
</div>
</div>
<div className="bg-slate-950 border border-slate-800 px-6 py-2 rounded-lg text-center">
<div className="text-[10px] uppercase text-slate-500 font-bold mb-1">Final Score</div>
<div className={`text-4xl font-bold font-mono ${demoResult.final_veracity_score < 50 ? 'text-red-400' : 'text-emerald-400'}`}>
{demoResult.final_veracity_score}
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-6 mb-6">
<div className="col-span-2 space-y-4">
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800">
<div className="text-xs font-bold text-indigo-400 uppercase mb-2">Video Context</div>
<div className="text-sm text-slate-300 italic leading-relaxed">"{demoResult.caption || 'No specific context found'}"</div>
</div>
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800">
<div className="text-xs font-bold text-amber-400 uppercase mb-2">AI Reasoning</div>
<div className="text-sm text-slate-300 whitespace-pre-wrap leading-relaxed">{demoResult.reasoning || 'No reasoning provided'}</div>
</div>
{demoResult.tags && (
<div className="flex flex-wrap gap-2">
{demoResult.tags.split(',').map((t: string) => (
<span key={t} className="px-2 py-1 bg-slate-800 border border-slate-700 rounded text-xs text-slate-400 font-mono">{t.trim()}</span>
))}
</div>
)}
</div>
<div className="space-y-4">
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800">
<div className="text-xs font-bold text-sky-400 uppercase mb-3">Veracity Vectors</div>
<div className="space-y-2">
{[
{ label: 'Visual Integrity', score: parseFloat(demoResult.visual_score) || 0 },
{ label: 'Audio Integrity', score: parseFloat(demoResult.audio_score) || 0 },
{ label: 'Source Credibility', score: parseFloat(demoResult.source_score) || 0 },
{ label: 'Logical Consistency', score: parseFloat(demoResult.logic_score) || 0 },
{ label: 'Emotional Manip.', score: parseFloat(demoResult.emotion_score) || 0 },
].map((v) => (
<div key={v.label} className="flex justify-between items-center text-xs">
<span className="text-slate-400 w-32 truncate">{v.label}</span>
<div className="flex items-center gap-2 flex-1 ml-2">
<div className="flex-1 h-1.5 bg-slate-800 rounded-full overflow-hidden">
<div className={`h-full ${v.score < 5 ? 'bg-red-400' : v.score < 8 ? 'bg-amber-400' : 'bg-emerald-400'}`} style={{ width: `${(v.score / 10) * 100}%` }} />
</div>
<span className="font-mono text-slate-300 w-6 text-right">{v.score}</span>
</div>
</div>
))}
</div>
</div>
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800">
<div className="text-xs font-bold text-pink-400 uppercase mb-3">Modality Alignment</div>
<div className="space-y-2">
{[
{ label: 'Video ↔ Audio', score: parseFloat(demoResult.align_video_audio) || 0 },
{ label: 'Video ↔ Caption', score: parseFloat(demoResult.align_video_caption) || 0 },
{ label: 'Audio ↔ Caption', score: parseFloat(demoResult.align_audio_caption) || 0 },
].map((v) => (
<div key={v.label} className="flex justify-between items-center text-xs">
<span className="text-slate-400 w-32 truncate">{v.label}</span>
<div className="flex items-center gap-2 flex-1 ml-2">
<div className="flex-1 h-1.5 bg-slate-800 rounded-full overflow-hidden">
<div className={`h-full ${v.score < 5 ? 'bg-red-400' : v.score < 8 ? 'bg-amber-400' : 'bg-emerald-400'}`} style={{ width: `${(v.score / 10) * 100}%` }} />
</div>
<span className="font-mono text-slate-300 w-6 text-right">{v.score}</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="flex justify-end">
<button onClick={() => {setDemoResult(null); setDemoLogs(''); setDemoLink('');}} className="text-xs text-slate-500 hover:text-white flex items-center gap-1 border border-slate-800 px-3 py-1.5 rounded bg-slate-950">
<RotateCcw className="w-3 h-3"/> Reset Demo
</button>
</div>
</div>
)}
</div>
{/* EXISTING HOME TAB CONTENT */}
<div className="grid grid-cols-3 gap-6">
<div className="col-span-2 bg-slate-900/50 border border-slate-800 rounded-xl p-6">
<h2 className="text-xl font-bold text-white mb-4">Philosophy & Methodology</h2>
<p className="text-sm text-slate-400 mb-4">
The goal of this research is to test various predictive models, generative AI models, prompting techniques, and agents against a rigorous <strong>Ground Truth</strong> standard.
</p>
<div className="grid grid-cols-2 gap-4 mt-6">
<div className="p-4 bg-black/50 border border-slate-800 rounded-lg">
<div className="flex items-center gap-2 text-indigo-400 font-bold text-xs uppercase mb-2">
<Calculator className="w-3 h-3"/> AI Generated Label Score
</div>
<div className="text-[10px] font-mono text-slate-400 leading-relaxed">
Formula to calculate the overall veracity score for each post:<br/><br/>
<code className="text-indigo-300 bg-indigo-950/30 p-1 rounded block mb-2">S_final = ((w1*V_vis + w2*V_aud + w3*V_src) / 3) * min(1, M_vc / 5) * 10</code>
<span className="text-slate-500 italic mt-2 block">Applies the Recontextualization Penalty based on Alignment metrics. Stored in dataset.csv.</span>
</div>
</div>
<div className="p-4 bg-black/50 border border-slate-800 rounded-lg">
<div className="flex items-center gap-2 text-emerald-400 font-bold text-xs uppercase mb-2">
<Target className="w-3 h-3"/> AI Config Accuracy Formula
</div>
<div className="text-[10px] font-mono text-slate-400 leading-relaxed">
Formula to calculate the overall score for how accurate the config combination is (based on difference against Ground Truth):<br/><br/>
<code className="text-emerald-300 bg-emerald-950/30 p-1 rounded block mb-1">Composite MAE = Mean Absolute Error across all 8 sub-vectors</code>
<code className="text-emerald-300 bg-emerald-950/30 p-1 rounded block">Binary Accuracy = Σ(Predict_bin == GT_bin) / N * 100</code>
<span className="text-slate-500 italic mt-2 block">Evaluates agent parameters & prompt efficacy against comprehensive factors.</span>
</div>
</div>
</div>
</div>
<div className="bg-indigo-900/20 border border-indigo-500/30 rounded-xl p-6 flex flex-col justify-center items-center">
<div className="text-xs uppercase text-indigo-400 font-bold mb-2">Ground Truth Accuracy</div>
{benchmarks ? (
<>
<div className="text-5xl font-mono font-bold text-white mb-2">{benchmarks.accuracy_percent}%</div>
<div className="text-xs text-slate-500 mb-1">Comp MAE: {benchmarks.mae} points</div>
<div className="text-[10px] text-emerald-400 font-bold mb-2">Tag Acc: {benchmarks.tag_accuracy_percent}%</div>
<div className="text-xs text-slate-500 mt-2">{benchmarks.count} verified samples</div>
</>
) : (
<span className="text-slate-600">No data found</span>
)}
</div>
</div>
{/* Configuration Leaderboard */}
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-6">
<h3 className="text-sm font-bold text-white uppercase mb-4 flex items-center gap-2">
<Trophy className="w-4 h-4 text-amber-400"/> Automated Hill Climbing (Leaderboard)
</h3>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-slate-400">
<thead className="bg-slate-950 text-slate-500 uppercase">
<tr>
<th className="p-3">Type</th>
<th className="p-3">Model</th>
<th className="p-3">Prompt</th>
<th className="p-3">Reasoning</th>
<th className="p-3 text-center">Tools</th>
<th className="p-3 text-center">FCoT Depth</th>
<th className="p-3 text-right text-emerald-400">Accuracy</th>
<th className="p-3 text-right">Comp. MAE</th>
<th className="p-3 text-right">Tag Acc</th>
<th className="p-3"></th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
{leaderboard && leaderboard.map((row, i) => (
<tr key={i} className="hover:bg-white/5">
<td className="p-3 font-mono text-xs">{row.type === 'GenAI' ? <span className="text-indigo-400">GenAI</span> : <span className="text-pink-400">Pred</span>}</td>
<td className="p-3 font-mono text-white">{row.model}</td>
<td className="p-3">{row.prompt}</td>
<td className="p-3 uppercase text-[10px]">{row.reasoning}</td>
<td className="p-3 text-center text-sky-400 font-mono text-[10px]">{row.tools || 'None'}</td>
<td className="p-3 text-center text-slate-400 font-mono">{row.fcot_depth ?? 0}</td>
<td className="p-3 text-right font-bold text-emerald-400">{row.accuracy}%</td>
<td className="p-3 text-right font-mono text-amber-400">{row.comp_mae}</td>
<td className="p-3 text-right">{row.tag_acc}%</td>
<td className="p-3 text-center">
<div className="group relative">
<HelpCircle className="w-4 h-4 text-slate-600 cursor-help"/>
<div className="absolute right-0 bottom-6 w-64 p-3 bg-black border border-slate-700 rounded shadow-xl hidden group-hover:block z-50 text-[10px] whitespace-pre-wrap text-left">
<div className="font-bold mb-1 text-slate-400">Config Params</div>
<div>{row.params}</div>
<div className="mt-2 pt-2 border-t border-slate-800 text-slate-400 font-bold">Samples: {row.samples}</div>
</div>
</div>
</td>
</tr>
))}
{(!leaderboard || leaderboard.length === 0) && (
<tr><td colSpan={10} className="p-4 text-center text-slate-600">No benchmark data available.</td></tr>
)}
</tbody>
</table>
</div>
</div>
{/* Detailed Vector Accuracies */}
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-6 mt-6 mb-8">
<h3 className="text-sm font-bold text-white uppercase mb-4 flex items-center gap-2">
<BarChart2 className="w-4 h-4 text-sky-400"/> Detailed Vector Error Analysis (MAE)
</h3>
<div className="overflow-x-auto">
<table className="w-full text-left text-xs text-slate-400">
<thead className="bg-slate-950 text-slate-500 uppercase">
<tr>
<th className="p-3">Model</th>
<th className="p-3">Prompt</th>
<th className="p-3">Reasoning</th>
<th className="p-3">Tools / Techniques</th>
<th className="p-3 text-right">Vis</th>
<th className="p-3 text-right">Aud</th>
<th className="p-3 text-right">Src</th>
<th className="p-3 text-right">Log</th>
<th className="p-3 text-right">Emo</th>
<th className="p-3 text-right">V-A</th>
<th className="p-3 text-right">V-C</th>
<th className="p-3 text-right">A-C</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
{leaderboard && leaderboard.map((row, i) => (
<tr key={i} className="hover:bg-white/5">
<td className="p-3 font-mono text-white">{row.model}</td>
<td className="p-3">{row.prompt}</td>
<td className="p-3 uppercase text-[10px]">{row.reasoning}</td>
<td className="p-3 text-sky-400 font-mono text-[10px]">{row.tools || 'None'}</td>
<td className="p-3 text-right font-mono">{row.err_visual_score ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_audio_score ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_source_score ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_logic_score ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_emotion_score ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_align_video_audio ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_align_video_caption ?? '-'}</td>
<td className="p-3 text-right font-mono">{row.err_align_audio_caption ?? '-'}</td>
</tr>
))}
{(!leaderboard || leaderboard.length === 0) && (
<tr><td colSpan={12} className="p-4 text-center text-slate-600">No detailed benchmark data available.</td></tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)}
{/* AGENT NEXUS TAB */}
{activeTab === 'agent' && (
<div className="flex h-full gap-6">
<div className="w-1/3 overflow-y-auto pr-2 flex flex-col gap-4">
<div className="bg-slate-900/50 border border-slate-800 rounded-xl p-6 flex flex-col">
<h2 className="text-lg font-bold text-white flex items-center gap-2 mb-4">
<BrainCircuit className="w-5 h-5 text-indigo-400"/> Agent Configuration
</h2>
<div className="text-xs text-slate-400 mb-6">
This interface interacts with the <strong>LiarMP4 Agent</strong> running on the Google Cloud Agent Development Kit (ADK) via the A2A Protocol. You can instruct the agent to change configs directly in chat.
</div>
<div className="bg-slate-950 p-4 rounded border border-slate-800">
<div className="text-[10px] uppercase text-slate-500 font-bold mb-2 flex items-center gap-2"><Settings className="w-3 h-3"/> Connection</div>
<div className="space-y-2">
<label className="text-[10px] text-slate-400">Agent Endpoint URL</label>
<input
value={agentEndpoint}
onChange={e => setAgentEndpoint(e.target.value)}
className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white font-mono placeholder-slate-600"
placeholder="e.g. /a2a"
/>
</div>
</div>
</div>
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-800">
<div className="text-[10px] uppercase text-slate-500 font-bold mb-2 flex items-center gap-2"><Settings className="w-3 h-3"/> Inference Config</div>
<div className="space-y-2">
<label className="text-[10px] text-slate-500">Provider</label>
<select value={modelProvider} onChange={e => setModelProvider(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white">
<option value="vertex">Vertex AI (Enterprise)</option>
<option value="gemini">Gemini API (Public)</option>
<option value="gcloud">Google Cloud (Project + API Key)</option>
<option value="nrp">NRP (Nautilus Envoy Gateway)</option>
</select>
{modelProvider === 'vertex' && (
<>
<input value={projectId} onChange={e => setProjectId(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="gcp-project-id"/>
<input value={location} onChange={e => setLocation(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="us-central1"/>
</>
)}
{modelProvider === 'gemini' && (
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="API Key"/>
)}
{modelProvider === 'gcloud' && (
<>
<input value={projectId} onChange={e => setProjectId(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="Project Name / ID"/>
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="API Key"/>
<input value={location} onChange={e => setLocation(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="us-central1"/>
</>
)}
{modelProvider === 'nrp' && (
<>
<div className="space-y-1">
<input value={baseUrl} onChange={e => setBaseUrl(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="https://ellm.nrp-nautilus.io/v1"/>
</div>
<div className="space-y-1">
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="NRP API Token"/>
</div>
</>
)}
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Model Name</label>
<input list="modelSuggestions" value={modelName} onChange={e => setModelName(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="Model Name"/>
</div>
<div className="space-y-1 mt-2">
<label className="text-[10px] text-slate-500">Reasoning Method</label>
<select value={reasoningMethod} onChange={e => setReasoningMethod(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white">
<option value="none">Direct (No CoT)</option>
<option value="cot">Standard Chain of Thought</option>
<option value="fcot">Fractal Chain of Thought</option>
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Prompt Persona</label>
<select value={promptTemplate} onChange={e => setPromptTemplate(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white">
{availablePrompts.length > 0 ? availablePrompts.map(p => (
<option key={p.id} value={p.id}>{p.name}</option>
)) : <option value="standard">Standard</option>}
</select>
</div>
</div>
</div>
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-800">
<div className="text-[10px] uppercase text-slate-500 font-bold mb-2 flex items-center gap-2"><Zap className="w-3 h-3"/> Agent Capabilities</div>
<ul className="text-xs text-slate-400 space-y-1 pl-4 list-disc">
<li>Process raw video & audio modalities via A2A</li>
<li>Fetch & analyze comment sentiment and community context</li>
<li>Run full Factuality pipeline (FCoT) & Generate Veracity Vectors</li>
<li>Automatically save raw AI Labeled JSON files & sync to Data Manager</li>
<li>Verify and compare AI outputs against Ground Truth</li>
<li>Reprompt dynamically for missing scores or incomplete data</li>
</ul>
</div>
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-800">
<div className="text-[10px] uppercase text-slate-500 font-bold mb-2 flex items-center gap-2"><Cpu className="w-3 h-3"/> Active Tools</div>
<div className="space-y-2">
<label className="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
<input type="checkbox" className="accent-indigo-500" checked={agentConfig.use_search} onChange={e => setAgentConfig({...agentConfig, use_search: e.target.checked})} />
Enable Google Search Retrieval
</label>
<label className="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
<input type="checkbox" className="accent-indigo-500" checked={agentConfig.use_code} onChange={e => setAgentConfig({...agentConfig, use_code: e.target.checked})} />
Enable Code Execution Environment
</label>
</div>
</div>
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-800 mb-6">
<div className="text-[10px] uppercase text-slate-500 font-bold mb-2 flex items-center gap-2"><MessageSquare className="w-3 h-3"/> Quick Commands</div>
<div className="flex flex-col gap-2">
<button onClick={() => setAgentInput("Set provider to gemini")} className="text-left text-[10px] text-indigo-400 hover:text-indigo-300 bg-indigo-900/20 p-2 rounded border border-indigo-500/30">"Set provider to Gemini"</button>
<button onClick={() => setAgentInput("Run full pipeline on ")} className="text-left text-[10px] text-indigo-400 hover:text-indigo-300 bg-indigo-900/20 p-2 rounded border border-indigo-500/30">"Run full pipeline on [URL]"</button>
</div>
</div>
</div>
<div className="flex-1 bg-slate-900/50 border border-slate-800 rounded-xl flex flex-col overflow-hidden">
<div className="p-4 border-b border-slate-800 bg-slate-950/50">
<div className="text-xs font-bold text-white">Agent Interaction (A2A)</div>
</div>
<div className="flex-1 p-4 overflow-y-auto space-y-4">
{agentMessages.map((m, i) => (
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] p-3 rounded-lg text-xs ${m.role === 'user' ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-300'} whitespace-pre-wrap`}>
{m.content}
</div>
</div>
))}
{agentThinking && (
<div className="flex justify-start">
<div className="max-w-[80%] p-3 rounded-lg text-xs bg-slate-800 text-slate-300 animate-pulse">
Processing request through pipeline...
</div>
</div>
)}
</div>
<div className="p-4 bg-slate-950 border-t border-slate-800 flex gap-2">
<input
value={agentInput}
onChange={e => setAgentInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && sendAgentMessage()}
className="flex-1 bg-slate-900 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-500"
placeholder="Message the agent (e.g., 'Analyze this video: https://...')"
disabled={agentThinking}
/>
<button onClick={sendAgentMessage} disabled={agentThinking} className="bg-indigo-600 hover:bg-indigo-500 disabled:bg-slate-700 text-white p-2 rounded">
<ArrowUpRight className="w-4 h-4"/>
</button>
</div>
</div>
</div>
)}
{/* QUEUE TAB */}
{activeTab === 'queue' && (
<div className="flex h-full gap-6">
<div className="w-[300px] bg-slate-900/50 border border-slate-800 rounded-xl p-4 flex flex-col gap-4 overflow-y-auto">
<div className="bg-slate-950 border border-slate-800 rounded p-3">
<label className="text-[10px] text-slate-500 uppercase font-bold mb-2 block">Quick Ingest</label>
<div className="flex gap-2">
<input
value={singleLinkInput}
onChange={e => setSingleLinkInput(e.target.value)}
className="flex-1 bg-slate-900 border border-slate-700 rounded p-1.5 text-xs text-white placeholder-slate-600"
placeholder="https://x.com/..."
/>
<button onClick={addSingleLink} className="bg-indigo-600 hover:bg-indigo-500 text-white rounded p-1.5">
<Plus className="w-4 h-4" />
</button>
</div>
</div>
<div className="text-xs font-bold text-indigo-400 uppercase">Config</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Provider</label>
<select value={modelProvider} onChange={e => setModelProvider(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white">
<option value="vertex">Vertex AI (Enterprise)</option>
<option value="gemini">Gemini API (Public)</option>
<option value="gcloud">Google Cloud (Project + API Key)</option>
<option value="nrp">NRP (Nautilus Envoy Gateway)</option>
</select>
</div>
{modelProvider === 'vertex' && (
<>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Project ID</label>
<input value={projectId} onChange={e => setProjectId(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="gcp-project-id"/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Location</label>
<input value={location} onChange={e => setLocation(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="us-central1"/>
</div>
</>
)}
{modelProvider === 'gemini' && (
<div className="space-y-1">
<label className="text-[10px] text-slate-500">API Key</label>
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="AIzaSy..."/>
</div>
)}
{modelProvider === 'gcloud' && (
<>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Project Name / ID</label>
<input value={projectId} onChange={e => setProjectId(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="gcp-project-id"/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">API Key</label>
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="AIzaSy..."/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Location</label>
<input value={location} onChange={e => setLocation(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="us-central1"/>
</div>
</>
)}
{modelProvider === 'nrp' && (
<>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">API Base URL</label>
<input value={baseUrl} onChange={e => setBaseUrl(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="https://ellm.nrp-nautilus.io/v1"/>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">API Key</label>
<input type="password" value={apiKey} onChange={e => setApiKey(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white placeholder-slate-600" placeholder="NRP API Token"/>
</div>
</>
)}
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Model Name</label>
<input list="modelSuggestions" value={modelName} onChange={e => setModelName(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white" placeholder="Model Name"/>
</div>
<div className="space-y-1 mt-2">
<label className="text-[10px] text-slate-500">Reasoning Method</label>
<select value={reasoningMethod} onChange={e => setReasoningMethod(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white">
<option value="none">Direct (No CoT)</option>
<option value="cot">Standard Chain of Thought</option>
<option value="fcot">Fractal Chain of Thought</option>
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] text-slate-500">Prompt Persona</label>
<select value={promptTemplate} onChange={e => setPromptTemplate(e.target.value)} className="w-full bg-slate-950 border border-slate-700 rounded p-2 text-xs text-white">
{availablePrompts.length > 0 ? availablePrompts.map(p => (
<option key={p.id} value={p.id}>{p.name}</option>
)) : <option value="standard">Standard</option>}
</select>
</div>
<div className="space-y-2 mt-2">
<label className="text-[10px] text-slate-500 uppercase font-bold block border-b border-slate-800 pb-1">Agentic Tools</label>
<label className="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
<input type="checkbox" className="accent-indigo-500" checked={useSearch} onChange={e => setUseSearch(e.target.checked)} />
Enable Web Search Retrieval
</label>
<label className="flex items-center gap-2 text-xs text-slate-300 cursor-pointer">
<input type="checkbox" className="accent-indigo-500" checked={useCode} onChange={e => setUseCode(e.target.checked)} />
Enable Code Execution
</label>
</div>
{/* Process Controls */}
{isProcessing ? (
<button onClick={stopProcessing} className="w-full py-2 bg-red-600 hover:bg-red-500 text-white rounded font-bold text-xs flex items-center justify-center gap-2 animate-pulse">
<StopCircle className="w-3 h-3"/> STOP PROCESSING
</button>
) : (
<button onClick={startProcessing} className="w-full py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded font-bold text-xs flex items-center justify-center gap-2">
<Play className="w-3 h-3"/> Start Batch
</button>
)}
<div className="flex gap-2">
<button onClick={clearProcessed} className="flex-1 py-2 bg-slate-700 hover:bg-slate-600 text-slate-300 rounded font-bold text-[10px] flex items-center justify-center gap-1">
<Trash2 className="w-3 h-3"/> Clear Done
</button>
<button onClick={requeueItems} className="flex-1 py-2 bg-sky-900/50 hover:bg-sky-900 text-sky-300 border border-sky-900 rounded font-bold text-[10px] flex items-center justify-center gap-1">
<RotateCcw className="w-3 h-3"/> Requeue Sel.
</button>
<button onClick={deleteQueueItems} className="flex-1 py-2 bg-red-900/50 hover:bg-red-900 text-red-300 border border-red-900 rounded font-bold text-[10px] flex items-center justify-center gap-1">
<Trash2 className="w-3 h-3"/> Delete Sel.
</button>
</div>
</div>
<div className="flex-1 flex flex-col gap-4 overflow-hidden">
<div className="flex-1 bg-slate-900/30 border border-slate-800 rounded-xl overflow-auto">
<table className="w-full text-left text-xs text-slate-400 select-none">
<thead className="bg-slate-950 sticky top-0">
<tr>
<th className="p-3 w-8"><Square className="w-4 h-4 text-slate-600"/></th>
<th className="p-3">Type</th>
<th className="p-3">Link</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody>
{queueList.map((q, i, arr) => (
<React.Fragment key={i}>
<tr
className={`border-t border-slate-800/50 hover:bg-white/5 ${selectedQueueItems.has(q.link) ? 'bg-indigo-900/20' : ''}`}
onMouseDown={(e) => {
isDraggingQueueRef.current = true;
toggleQueueSelection(e, q.link, i, arr);
}}
onMouseEnter={() => {
if (isDraggingQueueRef.current && !selectedQueueItems.has(q.link)) {
setSelectedQueueItems(prev => new Set(prev).add(q.link));
setLastQueueIndex(i);
}
}}
>
<td className="p-3 cursor-pointer">
{selectedQueueItems.has(q.link) ? <CheckSquare className="w-4 h-4 text-indigo-400"/> : <Square className="w-4 h-4 text-slate-600"/>}
</td>
<td className="p-3 font-bold text-[10px]">{q.task_type === 'Verify' ? <span className="text-amber-400">VERIFY</span> : <span className="text-slate-500">INGEST</span>}</td>
<td className="p-3">
<div className="flex items-center gap-2">
<span className="text-sky-500 font-mono break-all">{q.link}</span>
{q.comments && q.comments.length > 0 && (
<button
onClick={(e) => toggleQueueExpand(e, q.link)}
className="px-2 py-1 bg-slate-800 rounded text-[10px] text-slate-300 hover:bg-slate-700 flex items-center gap-1 z-10"
>
{expandedQueueItems.has(q.link) ? <ChevronUp className="w-3 h-3"/> : <ChevronDown className="w-3 h-3"/>}
{q.comments.length} Comments
</button>
)}
</div>
</td>
<td className="p-3">
{q.status === 'Processed' ?
<span className="text-emerald-500 flex items-center gap-1"><CheckCircle2 className="w-3 h-3"/> Done</span> :
q.status === 'Error' ?
<span className="text-red-500 flex items-center gap-1"><AlertCircle className="w-3 h-3"/> Error</span> :
<span className="text-amber-500">Pending</span>
}
</td>
</tr>
{expandedQueueItems.has(q.link) && q.comments && q.comments.length > 0 && (
<tr className="bg-slate-900/40">
<td colSpan={4} className="p-0">
<div className="px-12 py-3 border-l-2 border-indigo-500 ml-4 space-y-2">
{q.comments.map((c: any, ci: number) => (
<div key={ci} className="flex flex-col gap-1 p-2 bg-slate-900 border border-slate-800 rounded">
<div className="flex justify-between items-center">
<div className="text-[10px] font-bold text-indigo-400">{c.author}</div>
<div className="flex gap-2">
{c.link && <a href={c.link} target="_blank" rel="noreferrer" className="text-[10px] text-sky-400 hover:underline">View Post</a>}
{c.link && <button onClick={() => addSingleLinkDirect(c.link)} className="text-[10px] bg-slate-800 hover:bg-slate-700 text-slate-300 px-2 py-0.5 rounded flex items-center gap-1"><Plus className="w-3 h-3"/> Queue Comment</button>}
</div>
</div>
<div className="text-xs text-slate-400 line-clamp-2">{c.text}</div>
</div>
))}
</div>
</td>
</tr>
)}
</React.Fragment>
))}
</tbody>
</table>
</div>
<div ref={logContainerRef} className="h-40 bg-black border border-slate-800 rounded-xl p-3 font-mono text-[10px] text-emerald-500 overflow-y-auto whitespace-pre-wrap">{logs}</div>
</div>
</div>
)}
{/* PROFILES TAB */}
{activeTab === 'profiles' && (
<div className="flex h-full gap-6">
<div className="w-1/3 bg-slate-900/50 border border-slate-800 rounded-xl overflow-hidden flex flex-col">
<div className="p-3 bg-slate-950 border-b border-slate-800 text-xs font-bold text-slate-400">Scraped Accounts</div>
<div className="flex-1 overflow-auto">
{profileList.map((p, i) => (
<div key={i} onClick={() => loadProfilePosts(p.username)}
className={`p-3 border-b border-slate-800/50 cursor-pointer hover:bg-white/5 ${selectedProfile===p.username ? 'bg-indigo-900/20 border-l-2 border-indigo-500': ''}`}>
<div className="text-sm font-bold text-white">@{p.username}</div>
<div className="text-[10px] text-slate-500">{p.posts_count} posts stored</div>
</div>
))}
</div>
</div>
<div className="flex-1 bg-slate-900/50 border border-slate-800 rounded-xl overflow-hidden flex flex-col">
<div className="p-3 bg-slate-950 border-b border-slate-800 flex justify-between items-center">
<span className="text-xs font-bold text-slate-400">Post History {selectedProfile ? `(@${selectedProfile})` : ''}</span>
{selectedProfile && (
<button onClick={queueUnlabeledPosts} className="px-3 py-1 bg-indigo-600 hover:bg-indigo-500 text-white rounded text-[10px] flex items-center gap-1 transition hover:scale-105">
<PlusCircle className="w-3 h-3"/> Auto-Label
</button>
)}
</div>
<div className="flex-1 overflow-auto">
<table className="w-full text-left text-xs text-slate-400">
<thead className="bg-slate-900/80 sticky top-0"><tr><th className="p-3">Date</th><th className="p-3">Text</th><th className="p-3">Status</th><th className="p-3">Action</th></tr></thead>
<tbody className="divide-y divide-slate-800">
{profilePosts.map((row, i) => (
<tr key={i} className="hover:bg-white/5">
<td className="p-3 whitespace-nowrap text-slate-500">{row.timestamp?.split('T')[0]}</td>
<td className="p-3 truncate max-w-[300px]">{row.text}</td>
<td className="p-3">{row.is_labeled ? <span className="text-emerald-500 flex items-center gap-1"><ShieldCheck className="w-3 h-3"/> Labeled</span> : <span className="text-slate-600">Unlabeled</span>}</td>
<td className="p-3 flex gap-2">
<button onClick={() => sendToManualLabeler(row.link, row.text)} className="text-indigo-400 hover:text-indigo-300 flex items-center gap-1 bg-indigo-900/30 px-2 py-1 rounded hover:bg-indigo-900/50"><PenTool className="w-3 h-3"/> Manual</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
{/* DATASET TAB (Data Manager) */}
{activeTab === 'dataset' && (
<div className="h-full overflow-auto bg-slate-900/50 border border-slate-800 rounded-xl flex flex-col">
<div className="p-4 border-b border-slate-800 flex justify-between items-center bg-slate-950">
<span className="font-bold text-slate-400 text-sm flex items-center gap-2">
<Archive className="w-4 h-4"/> Data Manager (Unified)
</span>
<div className="flex gap-2 items-center">
<div className="text-[10px] text-slate-500 bg-slate-900 px-2 py-1 rounded border border-slate-800 flex gap-2">
<span>Total: {datasetList.length}</span>
<span>AI: {datasetList.filter(d => d.source === 'AI').length}</span>
<span>Manual: {datasetList.filter(d => d.source === 'Manual').length}</span>
</div>
{selectedItems.size === 1 && (
<button onClick={editSelectedLabel} className="bg-sky-600 text-white text-xs px-3 py-1 rounded font-bold hover:bg-sky-500 flex items-center gap-2">
<Edit3 className="w-3 h-3"/> Edit Label
</button>
)}
<button onClick={deleteDataEntries} className="bg-red-600 text-white text-xs px-3 py-1 rounded font-bold hover:bg-red-500 flex items-center gap-2">
<Trash2 className="w-3 h-3"/> Delete Selected
</button>
<button onClick={promoteSelected} className="bg-emerald-600 text-white text-xs px-3 py-1 rounded font-bold hover:bg-emerald-500 flex items-center gap-2">
<ShieldCheck className="w-3 h-3"/> Add Selected to Ground Truth
</button>
</div>
</div>
<div className="flex-1 overflow-auto">
<table className="w-full text-left text-xs text-slate-400 select-none">
<thead className="bg-slate-950 sticky top-0">
<tr>
<th className="p-3 w-8"><Square className="w-4 h-4 text-slate-600"/></th>
<th className="p-3">Source</th>
<th className="p-3">ID</th>
<th className="p-3">Config</th>
<th className="p-3">Caption</th>
<th className="p-3">Score</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
{datasetList.map((row, i, arr) => (
<tr
key={i}
className={`hover:bg-white/5 ${selectedItems.has(row.id) ? 'bg-indigo-900/20' : ''} ${row.source==='Manual'?'bg-emerald-900/5':''}`}
onMouseDown={(e) => {
isDraggingDatasetRef.current = true;
toggleSelection(e, row.id, i, arr);
}}
onMouseEnter={() => {
if (isDraggingDatasetRef.current && !selectedItems.has(row.id)) {
setSelectedItems(prev => new Set(prev).add(row.id));
setLastDatasetIndex(i);
}
}}
>
<td className="p-3 cursor-pointer">
{selectedItems.has(row.id) ? <CheckSquare className="w-4 h-4 text-indigo-400"/> : <Square className="w-4 h-4 text-slate-600"/>}
</td>
<td className="p-3">
{row.source === 'Manual' ? (
<span className="text-emerald-400 text-[10px] font-bold border border-emerald-900 bg-emerald-900/20 px-1 rounded">MANUAL</span>
) : (
<div className="flex flex-col gap-1">
<span className="text-indigo-400 text-[10px] font-bold border border-indigo-900 bg-indigo-900/20 px-1 rounded w-fit">AI</span>
</div>
)}
</td>
<td className="p-3 font-mono text-slate-500">{row.id}</td>
<td className="p-3 text-[10px] text-slate-400">
{row.source !== 'Manual' ? (
<>
<div className="font-bold text-slate-300">{row.config_model || 'N/A'}</div>
<div>{row.config_prompt} | {row.config_reasoning}</div>
</>
) : (
<span className="text-slate-600">N/A</span>
)}
</td>
<td className="p-3 truncate max-w-[300px]" title={row.caption}>{row.caption}</td>
<td className="p-3 font-bold text-white">{row.final_veracity_score}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* GROUND TRUTH TAB */}
{activeTab === 'groundtruth' && (
<div className="h-full overflow-auto bg-slate-900/50 border border-slate-800 rounded-xl flex flex-col">
<div className="p-4 border-b border-slate-800 flex justify-between items-center bg-slate-950">
<span className="font-bold text-emerald-400 text-sm flex items-center gap-2"><ShieldCheck className="w-4 h-4"/> Verified Ground Truth CSV</span>
<div className="flex gap-2">
<span className="text-xs text-slate-500 py-1 mr-4">{datasetList.filter(d => d.source === 'Manual').length} Verified Items</span>
<div className="flex items-center gap-2 mr-2">
<label className="text-[10px] uppercase text-slate-500 font-bold">Resamples:</label>
<input
type="number" min="1" max="50"
value={resampleCount}
onChange={e => setResampleCount(parseInt(e.target.value) || 1)}
className="w-16 bg-slate-900 border border-slate-700 rounded p-1 text-xs text-white"
/>
</div>
<button onClick={verifySelected} className="bg-indigo-600 text-white text-xs px-3 py-1 rounded font-bold hover:bg-indigo-500 flex items-center gap-2">
<RotateCcw className="w-3 h-3"/> Verify Scores (Re-Queue AI Run)
</button>
<button onClick={deleteSelected} className="bg-red-600 text-white text-xs px-3 py-1 rounded font-bold hover:bg-red-500 flex items-center gap-2">
<Trash2 className="w-3 h-3"/> Delete Selected
</button>
</div>
</div>
<div className="flex-1 overflow-auto">
<table className="w-full text-left text-xs text-slate-400 select-none">
<thead className="bg-slate-950 sticky top-0">
<tr>
<th className="p-3 w-8"><Square className="w-4 h-4 text-slate-600"/></th>
<th className="p-3">ID</th>
<th className="p-3">Caption</th>
<th className="p-3">Score</th>
<th className="p-3">Source</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800">
{datasetList.filter(d => d.source === 'Manual').map((row, i, arr) => (
<tr
key={i}
className={`hover:bg-white/5 ${selectedItems.has(row.id) ? 'bg-red-900/10' : ''}`}
onMouseDown={(e) => {
isDraggingDatasetRef.current = true;
toggleSelection(e, row.id, i, arr);
}}
onMouseEnter={() => {
if (isDraggingDatasetRef.current && !selectedItems.has(row.id)) {
setSelectedItems(prev => new Set(prev).add(row.id));
setLastDatasetIndex(i);
}
}}
>
<td className="p-3 cursor-pointer">
{selectedItems.has(row.id) ? <CheckSquare className="w-4 h-4 text-red-400"/> : <Square className="w-4 h-4 text-slate-600"/>}
</td>
<td className="p-3 font-mono text-emerald-400">{row.id}</td>
<td className="p-3 truncate max-w-[300px]" title={row.caption}>{row.caption}</td>
<td className="p-3 font-bold text-white">{row.final_veracity_score}</td>
<td className="p-3 text-[10px] uppercase text-slate-500">{row.source}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* MANUAL LABELING STUDIO */}
{activeTab === 'manual' && (
<div className="flex h-full gap-6 relative">
{/* Rubric Overlay */}
{showRubric && (
<div className="absolute inset-0 z-50 bg-[#09090b]/95 backdrop-blur-sm p-8 flex justify-center items-center">
<div className="bg-slate-900 border border-slate-800 w-full max-w-4xl h-[90vh] rounded-xl flex flex-col shadow-2xl">
<div className="p-6 border-b border-slate-800 flex justify-between items-center">
<h2 className="text-xl font-bold text-white">Labeling Guide & Rubric</h2>
<button onClick={() => setShowRubric(false)} className="text-slate-400 hover:text-white"><Trash2 className="w-6 h-6 rotate-45"/></button>
</div>
<div className="flex-1 overflow-y-auto p-8 prose prose-invert max-w-none">
<h3>Core Scoring Philosophy</h3>
<p><strong>1</strong> = Malicious/Fabricated. <strong>5</strong> = Unknown/Generic. <strong>10</strong> = Authentic/Verified.</p>
<h4>A. Visual Integrity</h4>
<ul>
<li><strong>1-2 (Deepfake):</strong> AI-generated or spatially altered.</li>
<li><strong>3-4 (Deceptive):</strong> Real footage, misleading edit (speed/crop).</li>
<li><strong>5-6 (Context):</strong> Real footage, false context or stock B-roll.</li>
<li><strong>9-10 (Raw):</strong> Verified raw footage/metadata.</li>
</ul>
</div>
</div>
</div>
)}
<div className="w-[280px] bg-slate-900/50 border border-slate-800 rounded-xl flex flex-col overflow-hidden">
<div className="flex border-b border-slate-800">
<button onClick={() => setLabelBrowserMode('queue')} className={`flex-1 py-3 text-xs font-bold ${labelBrowserMode==='queue'?'text-indigo-400 border-b-2 border-indigo-500':''}`}>Queue</button>
<button onClick={() => setLabelBrowserMode('dataset')} className={`flex-1 py-3 text-xs font-bold ${labelBrowserMode==='dataset'?'text-indigo-400 border-b-2 border-indigo-500':''}`}>Reviewed</button>
</div>
<div className="p-2 border-b border-slate-800">
<input value={labelFilter} onChange={e => setLabelFilter(e.target.value)} placeholder="Filter..." className="w-full bg-slate-950 border border-slate-700 rounded px-2 py-1 text-xs text-white"/>
</div>
<div className="flex-1 overflow-auto">
{(labelBrowserMode==='queue' ? queueList : datasetList)
.filter(i => (i.link || '').includes(labelFilter) || (i.id || '').includes(labelFilter))
.map((item, i) => (
<div key={i} onClick={() => loadFromBrowser(item, labelBrowserMode)}
className={`p-3 border-b border-slate-800/50 cursor-pointer hover:bg-white/5 ${manualLink===item.link?'bg-indigo-900/20 border-l-2 border-indigo-500':''}`}>
<div className="text-[10px] text-indigo-400 font-mono mb-1 truncate">{item.id || 'Pending'}</div>
<div className="text-xs text-slate-300 truncate">{item.link}</div>
</div>
))}
</div>
</div>
{/* Main Workspace with Split View Support */}
<div className="flex-1 bg-slate-900/50 border border-slate-800 rounded-xl overflow-hidden flex">
{/* THE FORM */}
<div className="flex-1 p-6 overflow-y-auto">
<div className="flex justify-between items-center mb-6 pb-4 border-b border-slate-800">
<h2 className="text-lg font-bold text-white flex items-center gap-2"><PenTool className="w-5 h-5"/> Studio</h2>
<div className="flex gap-2">
<button onClick={() => setShowRubric(true)} className="bg-slate-800 hover:bg-slate-700 text-indigo-400 px-3 py-2 rounded-lg font-bold text-xs flex gap-2 items-center"><Info className="w-4 h-4"/> Reference Guide</button>
<a href={manualLink} target="_blank" rel="noreferrer" className={`bg-slate-800 text-white px-3 py-2 rounded-lg font-bold flex gap-2 ${!manualLink && 'opacity-50 pointer-events-none'}`}><ExternalLink className="w-4 h-4"/> Open</a>
<button onClick={submitManualLabel} className="bg-emerald-600 text-white px-6 py-2 rounded-lg font-bold flex gap-2"><ClipboardCheck className="w-4 h-4"/> Save & Add to GT</button>
</div>
</div>
<div className="space-y-6">
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800">
<div className="mb-4">
<label className="text-xs uppercase text-slate-500 font-bold">Link</label>
<input value={manualLink} onChange={e => setManualLink(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-sm text-indigo-400 font-mono mt-1"/>
</div>
<div>
<label className="text-xs uppercase text-slate-500 font-bold">Caption</label>
<textarea value={manualCaption} onChange={e => setManualCaption(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-sm text-slate-300 mt-1 h-20"/>
</div>
</div>
<div className="grid grid-cols-2 gap-8">
<div>
<h3 className="text-sm font-bold text-indigo-400 uppercase mb-4 border-b border-slate-800 pb-2">Veracity Vectors</h3>
{['visual', 'audio', 'source', 'logic', 'emotion'].map(k => (
<div key={k} className="mb-4">
<div className="flex justify-between text-xs mb-1">
<span className="capitalize text-slate-300 font-bold">{k}</span>
<span className="text-indigo-400 font-mono font-bold">{(manualScores as any)[k]}/10</span>
</div>
<input type="range" min="1" max="10" value={(manualScores as any)[k]} onChange={e => setManualScores({...manualScores,[k]: parseInt(e.target.value)})} className="w-full accent-indigo-500"/>
</div>
))}
</div>
<div>
<h3 className="text-sm font-bold text-emerald-400 uppercase mb-4 border-b border-slate-800 pb-2">Modality Alignment</h3>
{['va', 'vc', 'ac'].map(k => (
<div key={k} className="mb-4">
<div className="flex justify-between text-xs mb-1">
<span className="capitalize text-slate-300 font-bold">{k.replace('v', 'Video-').replace('a', 'Audio-').replace('c', 'Caption')}</span>
<span className="text-emerald-400 font-mono font-bold">{(manualScores as any)[k]}/10</span>
</div>
<input type="range" min="1" max="10" value={(manualScores as any)[k]} onChange={e => setManualScores({...manualScores,[k]: parseInt(e.target.value)})} className="w-full accent-emerald-500"/>
</div>
))}
</div>
</div>
<div className="bg-slate-950 p-4 rounded-lg border border-slate-800">
<div className="flex justify-between items-center mb-4">
<h3 className="text-sm font-bold text-white uppercase">Final Veracity Score</h3>
<span className="text-2xl font-bold font-mono text-emerald-400">{manualScores.final}</span>
</div>
<input type="range" min="0" max="100" value={manualScores.final} onChange={e => setManualScores({...manualScores, final: parseInt(e.target.value)})} className="w-full accent-white mb-6"/>
<div className="mb-4">
<label className="text-xs uppercase text-slate-500 font-bold">Reasoning</label>
<textarea value={manualReasoning} onChange={e => setManualReasoning(e.target.value)} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-sm text-slate-300 mt-1 h-24" placeholder="Justification..."/>
</div>
<div>
<label className="text-xs uppercase text-slate-500 font-bold">Tags</label>
<input value={manualTags} onChange={handleTagsChange} className="w-full bg-slate-900 border border-slate-700 rounded p-2 text-sm text-slate-300 mt-1" placeholder="politics, satire..."/>
{existingTags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-2">
{existingTags.map(t => (
<button key={t} onClick={() => toggleTag(t)} className="px-2 py-1 bg-slate-800 hover:bg-slate-700 text-[10px] rounded text-slate-400 border border-slate-700">{t}</button>
))}
</div>
)}
</div>
</div>
</div>
</div>
{/* AI REFERENCE PANEL */}
{aiReference && (
<div className="w-[300px] bg-slate-950 border-l border-slate-800 p-4 overflow-y-auto">
<h3 className="text-xs font-bold text-indigo-400 uppercase mb-4 flex items-center gap-2">
<BrainCircuit className="w-4 h-4"/> AI Reference
</h3>
<div className="text-[10px] text-slate-500 mb-2 font-mono break-all">ID: {aiReference.id}</div>
<div className="mb-6 bg-slate-900 p-3 rounded border border-slate-800">
<div className="text-xs text-slate-400 font-bold uppercase mb-1">AI Score</div>
<div className={`text-2xl font-mono font-bold ${aiReference.final_veracity_score < 50 ? 'text-red-400' : 'text-emerald-400'}`}>
{aiReference.final_veracity_score}/100
</div>
</div>
<div className="mb-4">
<div className="text-xs text-slate-400 font-bold uppercase mb-1">Reasoning</div>
<div className="text-xs text-slate-400 whitespace-pre-wrap leading-relaxed p-2 bg-slate-900 rounded border border-slate-800/50">
{aiReference.reasoning || "No reasoning provided."}
</div>
</div>
<div className="mb-4">
<div className="text-xs text-slate-400 font-bold uppercase mb-1">Configuration</div>
<div className="text-[10px] space-y-1">
<div className="flex justify-between"><span className="text-slate-500">Model:</span> <span className="text-slate-300">{aiReference.config_model || 'Unknown'}</span></div>
<div className="flex justify-between"><span className="text-slate-500">Prompt:</span> <span className="text-slate-300">{aiReference.config_prompt || 'Unknown'}</span></div>
<div className="flex justify-between"><span className="text-slate-500">Reasoning:</span> <span className="text-slate-300">{aiReference.config_reasoning || 'Unknown'}</span></div>
</div>
</div>
{aiReference.raw_toon && (
<div className="mt-4 pt-4 border-t border-slate-800">
<details>
<summary className="text-[10px] cursor-pointer text-indigo-400 hover:text-indigo-300 font-bold">Show Raw AI-Labeled Data (JSON/TOON)</summary>
<pre className="text-[9px] text-slate-500 whitespace-pre-wrap mt-2 bg-black p-2 rounded border border-slate-800 overflow-x-auto">
{aiReference.raw_toon}
</pre>
</details>
</div>
)}
</div>
)}
</div>
</div>
)}
{activeTab === 'analytics' && (
<div className="h-full overflow-auto">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-bold text-indigo-400 uppercase flex items-center gap-2">
<UserCheck className="w-4 h-4"/> Account Integrity Leaderboard
</h3>
<button onClick={() => setRefreshTrigger(p => p+1)} className="text-xs text-slate-500 hover:text-white flex gap-1 items-center"><RefreshCw className="w-3 h-3"/> Refresh</button>
</div>
<div className="bg-slate-900/50 border border-slate-800 rounded-xl overflow-hidden">
<table className="w-full text-left text-xs text-slate-400">
<thead className="bg-slate-950"><tr><th className="p-4">User</th><th className="p-4">Avg Veracity</th><th className="p-4">Samples</th><th className="p-4">Rating</th></tr></thead>
<tbody className="divide-y divide-slate-800">
{integrityBoard.map((row, i) => (
<tr key={i} className="hover:bg-white/5">
<td className="p-4 font-bold text-white">@{row.username}</td>
<td className="p-4 text-indigo-300 font-mono text-lg">{row.avg_veracity}</td>
<td className="p-4 text-slate-500">{row.posts_labeled} posts</td>
<td className="p-4">
{row.avg_veracity > 70 ? <span className="text-emerald-500 bg-emerald-500/10 px-2 py-1 rounded font-bold">High Trust</span> :
<span className="text-amber-500 bg-amber-500/10 px-2 py-1 rounded font-bold">Mixed</span>}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
</div>
</div>
)
}
export default App