Spaces:
Build error
Build error
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Forensic Person Search System</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); | |
| } | |
| .card-glow { | |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.1); | |
| transition: all 0.3s ease; | |
| } | |
| .card-glow:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 30px rgba(59, 130, 246, 0.2); | |
| } | |
| .gradient-border { | |
| background: linear-gradient(45deg, #3b82f6, #8b5cf6); | |
| padding: 1px; | |
| border-radius: 0.75rem; | |
| } | |
| .gradient-border-content { | |
| background: #0f172a; | |
| border-radius: 0.625rem; | |
| } | |
| .timeline-item::before { | |
| content: ''; | |
| position: absolute; | |
| left: 10px; | |
| top: 0; | |
| bottom: 0; | |
| width: 2px; | |
| background: linear-gradient(to bottom, #3b82f6, #8b5cf6); | |
| } | |
| .timeline-dot { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: linear-gradient(45deg, #3b82f6, #8b5cf6); | |
| position: absolute; | |
| left: 5px; | |
| } | |
| .loading-spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid rgba(59, 130, 246, 0.2); | |
| border-top-color: #3b82f6; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .network-graph { | |
| background: radial-gradient(circle at center, rgba(59, 130, 246, 0.1) 0%, transparent 70%); | |
| } | |
| .risk-meter { | |
| background: linear-gradient(to right, #ef4444, #f59e0b, #10b981); | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen p-4 md:p-6"> | |
| <div class="max-w-7xl mx-auto"> | |
| <!-- Header --> | |
| <header class="flex justify-between items-center mb-8"> | |
| <div class="flex items-center space-x-3"> | |
| <div class="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center"> | |
| <i class="fas fa-search text-white text-lg"></i> | |
| </div> | |
| <h1 class="text-2xl font-bold text-white">Forensic Search System</h1> | |
| </div> | |
| <div class="text-sm text-slate-400"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="hover:text-blue-400 transition-colors"> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <div id="app" class="space-y-6"> | |
| <!-- Status Bar --> | |
| <div id="status" class="text-xs text-slate-500 mb-4 flex items-center space-x-2"> | |
| <span class="w-2 h-2 rounded-full bg-yellow-500 animate-pulse"></span> | |
| <span>Aguardando Autenticação...</span> | |
| </div> | |
| <!-- Search Form --> | |
| <div class="gradient-border"> | |
| <div class="gradient-border-content p-6"> | |
| <form id="searchForm" class="space-y-4"> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-slate-300 mb-1">CPF</label> | |
| <input type="text" id="cpf" placeholder="000.000.000-00" | |
| class="w-full px-4 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-slate-300 mb-1">Rede Social</label> | |
| <select id="socialNetwork" class="w-full px-4 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| <option value="facebook">Facebook</option> | |
| <option value="twitter">Twitter/X</option> | |
| <option value="instagram">Instagram</option> | |
| <option value="linkedin">LinkedIn</option> | |
| <option value="tiktok">TikTok</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-slate-300 mb-1">Perfil</label> | |
| <input type="text" id="profileUrl" placeholder="https://social.com/username" | |
| class="w-full px-4 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
| </div> | |
| </div> | |
| <div class="flex justify-end"> | |
| <button id="btnSearch" type="submit" class="bg-gradient-to-r from-blue-600 to-purple-600 text-white px-6 py-2 rounded-lg font-medium hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"> | |
| <i class="fas fa-search mr-2"></i> | |
| Iniciar Pesquisa Forense | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Results Dashboard --> | |
| <div id="resultsDashboard" class="hidden space-y-6"> | |
| <!-- Summary Cards --> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| <div class="card-glow bg-slate-800 p-6 rounded-xl"> | |
| <div class="flex items-center space-x-3 mb-4"> | |
| <div class="w-12 h-12 bg-blue-600 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-user text-white"></i> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white">Perfil Analisado</h3> | |
| <p id="profileName" class="text-slate-300">-</p> | |
| </div> | |
| </div> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Postagens Analisadas</span> | |
| <span id="postsCount" class="text-lg font-bold text-white">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Período Coberto</span> | |
| <span id="periodCovered" class="text-lg font-bold text-white">-</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-glow bg-slate-800 p-6 rounded-xl"> | |
| <div class="flex items-center space-x-3 mb-4"> | |
| <div class="w-12 h-12 bg-purple-600 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-chart-line text-white"></i> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white">Atividade</h3> | |
| <p class="text-slate-300">Métricas de Engajamento</p> | |
| </div> | |
| </div> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Média Diária</span> | |
| <span id="dailyAvg" class="text-lg font-bold text-white">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Pico de Atividade</span> | |
| <span id="peakActivity" class="text-lg font-bold text-white">-</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-glow bg-slate-800 p-6 rounded-xl"> | |
| <div class="flex items-center space-x-3 mb-4"> | |
| <div class="w-12 h-12 bg-red-600 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-exclamation-triangle text-white"></i> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white">Riscos Detectados</h3> | |
| <p class="text-slate-300">Avaliação de Perigo</p> | |
| </div> | |
| </div> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Nível de Risco</span> | |
| <span id="riskLevel" class="text-lg font-bold text-red-400">Baixo</span> | |
| </div> | |
| <div class="w-full h-2 bg-slate-700 rounded-full mt-2"> | |
| <div id="riskMeter" class="h-full rounded-full risk-meter" style="width: 20%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-glow bg-slate-800 p-6 rounded-xl"> | |
| <div class="flex items-center space-x-3 mb-4"> | |
| <div class="w-12 h-12 bg-green-600 rounded-full flex items-center justify-center"> | |
| <i class="fas fa-network-wired text-white"></i> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white">Conexões</h3> | |
| <p class="text-slate-300">Rede Social</p> | |
| </div> | |
| </div> | |
| <div class="space-y-2"> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Seguidores</span> | |
| <span id="followers" class="text-lg font-bold text-white">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm text-slate-400">Seguindo</span> | |
| <span id="following" class="text-lg font-bold text-white">0</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Detailed Analysis --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Timeline --> | |
| <div class="lg:col-span-2 space-y-4"> | |
| <div class="gradient-border"> | |
| <div class="gradient-border-content p-6"> | |
| <h2 class="text-xl font-bold text-white mb-4 flex items-center"> | |
| <i class="fas fa-clock mr-2"></i> | |
| Linha do Tempo | |
| </h2> | |
| <div id="timeline" class="space-y-6"> | |
| <!-- Timeline items will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Content Analysis --> | |
| <div class="gradient-border"> | |
| <div class="gradient-border-content p-6"> | |
| <h2 class="text-xl font-bold text-white mb-4 flex items-center"> | |
| <i class="fas fa-chart-pie mr-2"></i> | |
| Análise de Conteúdo | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white mb-2">Tópicos Principais</h3> | |
| <div id="topicsChart" class="h-48 bg-slate-800 rounded-lg flex items-center justify-center"> | |
| <span class="text-slate-400">Gráfico de tópicos</span> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white mb-2">Sentimento</h3> | |
| <div id="sentimentChart" class="h-48 bg-slate-800 rounded-lg flex items-center justify-center"> | |
| <span class="text-slate-400">Gráfico de sentimento</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Network Graph --> | |
| <div class="space-y-4"> | |
| <div class="gradient-border"> | |
| <div class="gradient-border-content p-6"> | |
| <h2 class="text-xl font-bold text-white mb-4 flex items-center"> | |
| <i class="fas fa-project-diagram mr-2"></i> | |
| Rede de Conexões | |
| </h2> | |
| <div id="networkGraph" class="h-64 bg-slate-800 rounded-lg network-graph flex items-center justify-center"> | |
| <span class="text-slate-400">Visualização de rede</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Risk Assessment --> | |
| <div class="gradient-border"> | |
| <div class="gradient-border-content p-6"> | |
| <h2 class="text-xl font-bold text-white mb-4 flex items-center"> | |
| <i class="fas fa-shield-alt mr-2"></i> | |
| Avaliação de Risco | |
| </h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white mb-2">Alertas</h3> | |
| <div id="riskAlerts" class="space-y-2"> | |
| <!-- Risk alerts will be added here --> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-white mb-2">Recomendações</h3> | |
| <div id="recommendations" class="space-y-2"> | |
| <!-- Recommendations will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading State --> | |
| <div id="loadingState" class="hidden text-center py-12"> | |
| <div class="loading-spinner mx-auto mb-4"></div> | |
| <h3 class="text-xl font-semibold text-white mb-2">Processando Análise Forense</h3> | |
| <p class="text-slate-400">Estamos coletando e analisando dados dos últimos 5 anos...</p> | |
| <div class="mt-4 text-sm text-slate-500"> | |
| <p>Esta operação pode levar alguns minutos dependendo da quantidade de dados.</p> | |
| <p>Por favor, não feche esta janela.</p> | |
| </div> | |
| </div> | |
| <!-- Error State --> | |
| <div id="errorState" class="hidden bg-red-900/20 border border-red-500 rounded-lg p-4 text-red-300"> | |
| <div class="flex items-start space-x-3"> | |
| <i class="fas fa-exclamation-circle text-red-400 mt-1"></i> | |
| <div> | |
| <h3 class="font-semibold text-white mb-2">Erro na Análise</h3> | |
| <p id="errorMessage">Ocorreu um erro ao processar sua solicitação.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Configuration | |
| const PLUGIN_ID = "forensic-search-system-v1"; | |
| let authToken = null; | |
| let currentAnalysis = null; | |
| // DOM Elements | |
| const statusElement = document.getElementById('status'); | |
| const searchForm = document.getElementById('searchForm'); | |
| const btnSearch = document.getElementById('btnSearch'); | |
| const resultsDashboard = document.getElementById('resultsDashboard'); | |
| const loadingState = document.getElementById('loadingState'); | |
| const errorState = document.getElementById('errorState'); | |
| // 1. Authentication Handshake | |
| window.addEventListener('message', (event) => { | |
| if (event.data.type === 'AUTH_TOKEN') { | |
| authToken = event.data.token; | |
| statusElement.innerHTML = ` | |
| <span class="w-2 h-2 rounded-full bg-green-500"></span> | |
| <span>Autenticado com sucesso</span> | |
| `; | |
| btnSearch.disabled = false; | |
| } | |
| }); | |
| // 2. Form Submission | |
| searchForm.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| if (!authToken) { | |
| showError("Token de autenticação não disponível"); | |
| return; | |
| } | |
| // Get form values | |
| const cpf = document.getElementById('cpf').value; | |
| const socialNetwork = document.getElementById('socialNetwork').value; | |
| const profileUrl = document.getElementById('profileUrl').value; | |
| if (!cpf || !profileUrl) { | |
| showError("Por favor, preencha todos os campos obrigatórios"); | |
| return; | |
| } | |
| // Show loading state | |
| resultsDashboard.classList.add('hidden'); | |
| loadingState.classList.remove('hidden'); | |
| errorState.classList.add('hidden'); | |
| try { | |
| // Call AI Gateway | |
| const analysis = await callAIGateway(cpf, socialNetwork, profileUrl); | |
| currentAnalysis = analysis; | |
| renderResults(analysis); | |
| } catch (error) { | |
| showError(error.message || "Falha ao processar a análise"); | |
| } | |
| }); | |
| // 3. AI Gateway Call | |
| async function callAIGateway(cpf, socialNetwork, profileUrl) { | |
| try { | |
| const response = await fetch('/api/client/plugin/ai', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${authToken}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| plugin_id: PLUGIN_ID, | |
| user_prompt: `Analise forensicamente o perfil ${profileUrl} na rede social ${socialNetwork} associado ao CPF ${cpf}. Colete dados dos últimos 5 anos e gere um relatório detalhado com: | |
| 1. Informações básicas do perfil | |
| 2. Análise de atividade (postagens, engajamento) | |
| 3. Linha do tempo de eventos significativos | |
| 4. Análise de conteúdo (tópicos, sentimento) | |
| 5. Rede de conexões | |
| 6. Avaliação de risco | |
| 7. Alertas e recomendações | |
| Formate a resposta como JSON com a seguinte estrutura: | |
| { | |
| "profile": {...}, | |
| "activity": {...}, | |
| "timeline": [...], | |
| "contentAnalysis": {...}, | |
| "network": {...}, | |
| "riskAssessment": {...} | |
| }` | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Erro na API: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return JSON.parse(data.response); // Assuming the AI returns JSON in the response field | |
| } catch (error) { | |
| console.error("Error calling AI Gateway:", error); | |
| throw error; | |
| } | |
| } | |
| // 4. Render Results | |
| function renderResults(analysis) { | |
| // Hide loading state | |
| loadingState.classList.add('hidden'); | |
| // Populate summary cards | |
| document.getElementById('profileName').textContent = analysis.profile.name || '-'; | |
| document.getElementById('postsCount').textContent = analysis.activity.postsCount || '0'; | |
| document.getElementById('periodCovered').textContent = analysis.activity.period || '-'; | |
| document.getElementById('dailyAvg').textContent = analysis.activity.dailyAverage || '0'; | |
| document.getElementById('peakActivity').textContent = analysis.activity.peakDate || '-'; | |
| // Risk assessment | |
| const riskLevel = analysis.riskAssessment.level || 'Baixo'; | |
| const riskPercentage = getRiskPercentage(riskLevel); | |
| document.getElementById('riskLevel').textContent = riskLevel; | |
| document.getElementById('riskMeter').style.width = `${riskPercentage}%`; | |
| // Network | |
| document.getElementById('followers').textContent = analysis.network.followers || '0'; | |
| document.getElementById('following').textContent = analysis.network.following || '0'; | |
| // Render timeline | |
| renderTimeline(analysis.timeline); | |
| // Render risk alerts and recommendations | |
| renderAlerts(analysis.riskAssessment.alerts); | |
| renderRecommendations(analysis.riskAssessment.recommendations); | |
| // Show results | |
| resultsDashboard.classList.remove('hidden'); | |
| } | |
| // 5. Helper Functions | |
| function getRiskPercentage(level) { | |
| const levels = { | |
| 'Baixo': 20, | |
| 'Médio': 50, | |
| 'Alto': 80, | |
| 'Crítico': 100 | |
| }; | |
| return levels[level] || 20; | |
| } | |
| function renderTimeline(items) { | |
| const timelineElement = document.getElementById('timeline'); | |
| timelineElement.innerHTML = ''; | |
| if (!items || items.length === 0) { | |
| timelineElement.innerHTML = '<p class="text-slate-400">Nenhum evento significativo encontrado.</p>'; | |
| return; | |
| } | |
| items.forEach(item => { | |
| const timelineItem = document.createElement('div'); | |
| timelineItem.className = 'relative pl-8 pb-4'; | |
| timelineItem.innerHTML = ` | |
| <div class="timeline-dot"></div> | |
| <div class="bg-slate-800 p-4 rounded-lg border border-slate-700"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h4 class="font-semibold text-white">${item.title}</h4> | |
| <span class="text-xs text-slate-400">${item.date}</span> | |
| </div> | |
| <p class="text-slate-300 mb-3">${item.description}</p> | |
| ${item.tags ? ` | |
| <div class="flex flex-wrap gap-2"> | |
| ${item.tags.map(tag => ` | |
| <span class="px-2 py-1 bg-slate-700 text-xs rounded-full text-slate-300">${tag}</span> | |
| `).join('')} | |
| </div> | |
| ` : ''} | |
| </div> | |
| `; | |
| timelineElement.appendChild(timelineItem); | |
| }); | |
| } | |
| function renderAlerts(alerts) { | |
| const alertsElement = document.getElementById('riskAlerts'); | |
| alertsElement.innerHTML = ''; | |
| if (!alerts || alerts.length === 0) { | |
| alertsElement.innerHTML = '<p class="text-slate-400 text-sm">Nenhum alerta encontrado.</p>'; | |
| return; | |
| } | |
| alerts.forEach(alert => { | |
| const alertElement = document.createElement('div'); | |
| alertElement.className = 'bg-red-900/30 border border-red-500 rounded-lg p-3'; | |
| alertElement.innerHTML = ` | |
| <div class="flex items-start space-x-3"> | |
| <i class="fas fa-exclamation-triangle text-red-400 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-white">${alert.title}</h4> | |
| <p class="text-red-300 text-sm">${alert.description}</p> | |
| ${alert.severity ? ` | |
| <span class="text-xs text-red-400 mt-1 block">Severidade: ${alert.severity}</span> | |
| ` : ''} | |
| </div> | |
| </div> | |
| `; | |
| alertsElement.appendChild(alertElement); | |
| }); | |
| } | |
| function renderRecommendations(recommendations) { | |
| const recElement = document.getElementById('recommendations'); | |
| recElement.innerHTML = ''; | |
| if (!recommendations || recommendations.length === 0) { | |
| recElement.innerHTML = '<p class="text-slate-400 text-sm">Nenhuma recomendação.</p>'; | |
| return; | |
| } | |
| recommendations.forEach(rec => { | |
| const recElementItem = document.createElement('div'); | |
| recElementItem.className = 'bg-blue-900/30 border border-blue-500 rounded-lg p-3'; | |
| recElementItem.innerHTML = ` | |
| <div class="flex items-start space-x-3"> | |
| <i class="fas fa-lightbulb text-blue-400 mt-1"></i> | |
| <div> | |
| <h4 class="font-semibold text-white">${rec.title}</h4> | |
| <p class="text-blue-300 text-sm">${rec.description}</p> | |
| </div> | |
| </div> | |
| `; | |
| recElement.appendChild(recElementItem); | |
| }); | |
| } | |
| function showError(message) { | |
| loadingState.classList.add('hidden'); | |
| errorState.classList.remove('hidden'); | |
| document.getElementById('errorMessage').textContent = message; | |
| } | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Check if we're in an iframe | |
| if (window.self !== window.top) { | |
| // Send ready message to parent | |
| window.parent.postMessage({ type: 'PLUGIN_READY', pluginId: PLUGIN_ID }, '*'); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |