Germinal commited on
Commit
484977e
·
verified ·
1 Parent(s): cb69733

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +329 -639
index.html CHANGED
@@ -3,710 +3,400 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>S.I.E. PRO - Forensic Analytics</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
11
  <style>
 
12
  body {
13
  font-family: 'Inter', sans-serif;
14
  }
15
- .gradient-bg {
16
- background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
17
- }
18
- .card-hover:hover {
19
- transform: translateY(-2px);
20
- transition: transform 0.2s ease-in-out;
21
- }
22
  .loading-spinner {
23
- border: 3px solid #f3f3f3;
24
- border-top: 3px solid #3b82f6;
25
  border-radius: 50%;
26
- width: 20px;
27
- height: 20px;
 
28
  animation: spin 1s linear infinite;
29
  }
30
  @keyframes spin {
31
  0% { transform: rotate(0deg); }
32
  100% { transform: rotate(360deg); }
33
  }
 
 
 
 
 
 
 
34
  </style>
35
  </head>
36
- <body class="bg-slate-900 text-slate-200 min-h-screen">
37
- <div id="app" class="max-w-7xl mx-auto p-4">
38
- <!-- Header -->
39
- <div class="gradient-bg rounded-lg p-6 mb-6 shadow-lg">
40
- <div class="flex justify-between items-center">
41
- <div class="flex items-center space-x-3">
42
- <div class="bg-white p-2 rounded-lg">
43
- <svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
44
- <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
45
- </svg>
46
- </div>
47
- <div>
48
- <h1 class="text-xl font-bold text-white">S.I.E. PRO Forensic Analytics</h1>
49
- <p class="text-sm text-blue-100">Análise Inteligente de Agentes Públicos</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  </div>
 
 
 
51
  </div>
52
- <div class="text-right">
53
- <div id="status" class="text-xs text-blue-200 mb-1">Aguardando Autenticação...</div>
54
- <div id="auth-status" class="text-xs text-blue-200">Desconectado</div>
55
- </div>
56
- </div>
57
- </div>
58
 
59
- <!-- Main Content -->
60
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
61
- <!-- Search Panel -->
62
- <div class="lg:col-span-1 space-y-6">
63
- <div class="bg-slate-800 rounded-lg p-6 shadow-lg">
64
- <h2 class="text-lg font-semibold mb-4 text-blue-300">Pesquisa Forense</h2>
65
- <form id="searchForm" class="space-y-4">
66
- <div>
67
- <label class="block text-sm font-medium mb-1 text-slate-300">Tipo de Localização</label>
68
- <select id="locationType" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
69
- <option value="municipio">Município</option>
70
- <option value="cidade">Cidade</option>
71
- <option value="estado">Estado</option>
72
- </select>
73
- </div>
74
 
75
- <div>
76
- <label class="block text-sm font-medium mb-1 text-slate-300">Nome da Localização</label>
77
- <div class="relative">
78
- <input type="text" id="query" placeholder="Digite o nome..." class="w-full px-4 py-2 pl-10 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500" required>
79
- <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
80
- <svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
82
- </svg>
83
- </div>
84
- </div>
85
- </div>
86
 
87
- <div class="grid grid-cols-2 gap-4">
88
- <div>
89
- <label class="block text-sm font-medium mb-1 text-slate-300">Data Inicial</label>
90
- <input type="date" id="startDate" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
91
- </div>
92
- <div>
93
- <label class="block text-sm font-medium mb-1 text-slate-300">Data Final</label>
94
- <input type="date" id="endDate" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
95
- </div>
96
- </div>
97
 
98
- <button type="submit" id="btnSearch" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition duration-200 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed">
99
- <span id="btnText">Pesquisar Agentes</span>
100
- <span id="btnSpinner" class="hidden ml-2 loading-spinner"></span>
101
- </button>
102
- </form>
103
- </div>
 
 
 
 
 
 
104
 
105
- <!-- Advanced Filters -->
106
- <div class="bg-slate-800 rounded-lg p-6 shadow-lg">
107
- <h2 class="text-lg font-semibold mb-4 text-blue-300">Filtros Avançados</h2>
108
- <div class="space-y-4">
109
  <div>
110
- <label class="block text-sm font-medium mb-1 text-slate-300">Status</label>
111
- <select id="statusFilter" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white">
112
- <option value="all">Todos</option>
113
- <option value="ativo">Ativos</option>
114
- <option value="inativo">Inativos</option>
115
- </select>
 
 
 
116
  </div>
117
 
118
  <div>
119
- <label class="block text-sm font-medium mb-1 text-slate-300">Nível de Risco</label>
120
- <select id="riskFilter" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white">
121
- <option value="all">Todos</option>
122
- <option value="low">Baixo (0-33)</option>
123
- <option value="medium">Médio (34-66)</option>
124
- <option value="high">Alto (67-100)</option>
 
 
 
125
  </select>
126
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- <button id="btnApplyFilters" class="w-full bg-slate-600 hover:bg-slate-700 text-white font-medium py-2 px-4 rounded-md transition duration-200">
129
- Aplicar Filtros
130
- </button>
 
131
  </div>
132
- </div>
133
- </div>
134
 
135
- <!-- Results Panel -->
136
- <div class="lg:col-span-2 space-y-6">
137
- <!-- Stats Cards -->
138
- <div id="statsContainer" class="grid grid-cols-1 md:grid-cols-3 gap-4 hidden">
139
- <div class="bg-slate-800 rounded-lg p-4 shadow-lg card-hover">
140
- <div class="flex items-center justify-between">
141
  <div>
142
- <p class="text-sm text-slate-400">Total de Agentes</p>
143
- <p id="totalAgents" class="text-2xl font-bold text-white">0</p>
 
144
  </div>
145
- <div class="bg-blue-600 p-2 rounded-full">
146
- <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
147
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
148
- </svg>
149
  </div>
150
  </div>
151
  </div>
152
 
153
- <div class="bg-slate-800 rounded-lg p-4 shadow-lg card-hover">
154
- <div class="flex items-center justify-between">
155
- <div>
156
- <p class="text-sm text-slate-400">Agentes Ativos</p>
157
- <p id="activeAgents" class="text-2xl font-bold text-green-400">0</p>
158
- </div>
159
- <div class="bg-green-600 p-2 rounded-full">
160
- <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
161
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
162
  </svg>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  </div>
164
  </div>
165
- </div>
166
 
167
- <div class="bg-slate-800 rounded-lg p-4 shadow-lg card-hover">
168
- <div class="flex items-center justify-between">
169
- <div>
170
- <p class="text-sm text-slate-400">Risco Médio</p>
171
- <p id="avgRisk" class="text-2xl font-bold text-yellow-400">0%</p>
172
- </div>
173
- <div class="bg-yellow-600 p-2 rounded-full">
174
- <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
175
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
176
  </svg>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </div>
178
  </div>
179
- </div>
180
- </div>
181
 
182
- <!-- Results List -->
183
- <div class="bg-slate-800 rounded-lg p-6 shadow-lg">
184
- <div class="flex justify-between items-center mb-4">
185
- <h2 class="text-lg font-semibold text-blue-300">Resultados da Análise</h2>
186
- <div class="flex space-x-2">
187
- <button id="btnExport" class="px-3 py-1 bg-slate-700 hover:bg-slate-600 text-sm rounded-md transition duration-200 disabled:opacity-50">
188
- Exportar
189
- </button>
190
- <button id="btnDashboard" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-sm rounded-md transition duration-200">
191
- Ver Dashboard
192
- </button>
 
 
 
 
 
 
 
 
 
 
 
193
  </div>
194
  </div>
195
 
196
- <div id="resultsContainer" class="space-y-4">
197
- <!-- Results will be inserted here -->
198
- </div>
199
-
200
- <div id="loadingResults" class="text-center py-8 hidden">
201
- <div class="mx-auto w-12 h-12 loading-spinner mb-4"></div>
202
- <p class="text-slate-400">Processando análise com IA...</p>
203
- </div>
204
-
205
- <div id="noResults" class="text-center py-8 hidden">
206
- <svg class="mx-auto w-12 h-12 text-slate-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
207
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
208
- </svg>
209
- <p class="text-slate-400">Nenhum resultado encontrado</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  </div>
211
- </div>
212
- </div>
213
- </div>
214
- </div>
215
 
216
- <!-- Dashboard Modal -->
217
- <div id="dashboardModal" class="fixed inset-0 bg-black bg-opacity-75 z-50 hidden overflow-y-auto">
218
- <div class="flex items-center justify-center min-h-screen p-4">
219
- <div class="bg-slate-800 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] overflow-hidden">
220
- <div class="bg-slate-900 p-4 flex justify-between items-center border-b border-slate-700">
221
- <h2 class="text-lg font-semibold text-white">Dashboard Analítico</h2>
222
- <button id="closeDashboard" class="text-slate-400 hover:text-white">
223
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
224
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
225
- </svg>
226
- </button>
227
- </div>
228
- <div class="p-6 overflow-y-auto max-h-[70vh]">
229
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
230
- <div class="bg-slate-700 rounded-lg p-4">
231
- <h3 class="text-sm font-medium mb-2 text-blue-300">Distribuição por Status</h3>
232
- <canvas id="statusChart"></canvas>
233
- </div>
234
- <div class="bg-slate-700 rounded-lg p-4">
235
- <h3 class="text-sm font-medium mb-2 text-blue-300">Nível de Risco</h3>
236
- <canvas id="riskChart"></canvas>
237
  </div>
238
  </div>
239
  </div>
240
- </div>
241
- </div>
242
- </div>
243
-
244
- <script>
245
- // Plugin Configuration
246
- const PLUGIN_ID = "sie-pro-forensic-analytics";
247
- let authToken = null;
248
- let currentResults = [];
249
-
250
- // DOM Elements
251
- const statusElement = document.getElementById('status');
252
- const authStatusElement = document.getElementById('auth-status');
253
- const btnSearch = document.getElementById('btnSearch');
254
- const btnText = document.getElementById('btnText');
255
- const btnSpinner = document.getElementById('btnSpinner');
256
- const searchForm = document.getElementById('searchForm');
257
- const resultsContainer = document.getElementById('resultsContainer');
258
- const loadingResults = document.getElementById('loadingResults');
259
- const noResults = document.getElementById('noResults');
260
- const statsContainer = document.getElementById('statsContainer');
261
- const btnDashboard = document.getElementById('btnDashboard');
262
- const btnExport = document.getElementById('btnExport');
263
- const btnApplyFilters = document.getElementById('btnApplyFilters');
264
- const dashboardModal = document.getElementById('dashboardModal');
265
- const closeDashboard = document.getElementById('closeDashboard');
266
-
267
- // Authentication Handshake
268
- window.addEventListener('message', (event) => {
269
- if (event.data.type === 'AUTH_TOKEN') {
270
- authToken = event.data.token;
271
- statusElement.textContent = 'Autenticado';
272
- authStatusElement.textContent = 'Conectado ao S.I.E. PRO';
273
- authStatusElement.className = 'text-xs text-green-400';
274
- btnSearch.disabled = false;
275
- console.log('Plugin authenticated successfully');
276
- }
277
- });
278
-
279
- // Initialize the plugin
280
- function init() {
281
- // Set up event listeners
282
- searchForm.addEventListener('submit', handleSearch);
283
- btnDashboard.addEventListener('click', showDashboard);
284
- closeDashboard.addEventListener('click', hideDashboard);
285
- btnApplyFilters.addEventListener('click', applyFilters);
286
- btnExport.addEventListener('click', exportData);
287
-
288
- // Check if we're in an iframe
289
- if (window.self !== window.top) {
290
- // Send ready message to parent
291
- window.parent.postMessage({ type: 'PLUGIN_READY', pluginId: PLUGIN_ID }, '*');
292
- }
293
-
294
- // Disable buttons initially
295
- btnSearch.disabled = true;
296
- btnDashboard.disabled = true;
297
- btnExport.disabled = true;
298
- }
299
-
300
- // Handle search form submission
301
- async function handleSearch(e) {
302
- e.preventDefault();
303
-
304
- if (!authToken) {
305
- alert('Plugin não autenticado. Por favor, aguarde a autenticação.');
306
- return;
307
- }
308
-
309
- // Show loading state
310
- btnText.textContent = 'Processando...';
311
- btnSpinner.classList.remove('hidden');
312
- btnSearch.disabled = true;
313
- loadingResults.classList.remove('hidden');
314
- resultsContainer.innerHTML = '';
315
- noResults.classList.add('hidden');
316
- statsContainer.classList.add('hidden');
317
-
318
- // Get form data
319
- const formData = new FormData(searchForm);
320
- const queryData = {
321
- locationType: formData.get('locationType'),
322
- query: formData.get('query'),
323
- startDate: formData.get('startDate') || null,
324
- endDate: formData.get('endDate') || null
325
- };
326
-
327
- try {
328
- // Call the AI Gateway
329
- const results = await callAIGateway(queryData);
330
- currentResults = results;
331
-
332
- // Display results
333
- displayResults(results);
334
- updateStats(results);
335
-
336
- // Enable dashboard button if we have results
337
- btnDashboard.disabled = results.length === 0;
338
- btnExport.disabled = results.length === 0;
339
- } catch (error) {
340
- console.error('Error in search:', error);
341
- showError('Ocorreu um erro ao processar a pesquisa. Por favor, tente novamente.');
342
- } finally {
343
- // Reset button state
344
- btnText.textContent = 'Pesquisar Agentes';
345
- btnSpinner.classList.add('hidden');
346
- btnSearch.disabled = false;
347
- loadingResults.classList.add('hidden');
348
- }
349
- }
350
-
351
- // Call the AI Gateway
352
- async function callAIGateway(queryData) {
353
- const prompt = buildAIPrompt(queryData);
354
-
355
- try {
356
- const response = await axios.post('/api/client/plugin/ai', {
357
- plugin_id: PLUGIN_ID,
358
- user_prompt: prompt
359
- }, {
360
- headers: {
361
- 'Authorization': `Bearer ${authToken}`,
362
- 'Content-Type': 'application/json'
363
  }
364
  });
 
365
 
366
- // Parse the AI response
367
- return parseAIResponse(response.data, queryData.query, queryData.locationType);
368
- } catch (error) {
369
- console.error('AI Gateway Error:', error);
370
- throw new Error('Falha na comunicação com o gateway de IA');
371
- }
372
- }
373
-
374
- // Build the AI prompt
375
- function buildAIPrompt(queryData) {
376
- return `
377
- Analise os seguintes agentes públicos para possíveis irregularidades ou pontos de atenção:
378
-
379
- Localização: ${queryData.query} (${queryData.locationType})
380
- Período: ${queryData.startDate ? `${queryData.startDate} a ${queryData.endDate}` : 'Não especificado'}
381
-
382
- Por favor, forneça uma análise detalhada para cada agente público encontrado nesta localização, incluindo:
383
- 1. Nome completo
384
- 2. Cargo atual
385
- 3. Status (ativo/inativo)
386
- 4. Análise forense breve (máx 150 palavras)
387
- 5. 2-3 recomendações para investigação adicional
388
- 6. Score de risco (0-100)
389
-
390
- Formate a resposta como JSON com a seguinte estrutura:
391
- [
392
- {
393
- "name": "Nome do Agente",
394
- "position": "Cargo",
395
- "status": "ativo/inativo",
396
- "analysis": "Análise detalhada...",
397
- "recommendations": ["Recomendação 1", "Recomendação 2"],
398
- "riskScore": 45
399
- }
400
- ]
401
-
402
- Se não encontrar agentes públicos, retorne um array vazio: []
403
- `;
404
- }
405
-
406
- // Parse the AI response
407
- function parseAIResponse(aiResponse, location, locationType) {
408
- try {
409
- // Try to parse as JSON first
410
- const parsed = typeof aiResponse === 'string' ? JSON.parse(aiResponse) : aiResponse;
411
-
412
- if (Array.isArray(parsed)) {
413
- return parsed.map(agent => ({
414
- ...agent,
415
- location: `${location} - ${locationType === 'estado' ? 'Estado' : 'Município'}`
416
- }));
417
- }
418
-
419
- // If not JSON, try to extract information from text
420
- return extractFromText(aiResponse, location, locationType);
421
- } catch (e) {
422
- console.warn('Could not parse AI response as JSON, attempting text extraction');
423
- return extractFromText(aiResponse, location, locationType);
424
- }
425
- }
426
-
427
- // Fallback text extraction
428
- function extractFromText(text, location, locationType) {
429
- // This is a simplified extraction - in a real scenario, you'd want more robust parsing
430
- const agents = [];
431
-
432
- // Look for patterns in the text
433
- const nameMatches = text.match(/Nome: (.+)/g) || [];
434
- const positionMatches = text.match(/Cargo: (.+)/g) || [];
435
- const statusMatches = text.match(/Status: (ativo|inativo)/g) || [];
436
-
437
- for (let i = 0; i < nameMatches.length; i++) {
438
- agents.push({
439
- name: nameMatches[i] ? nameMatches[i].replace('Nome: ', '') : `Agente ${i+1}`,
440
- position: positionMatches[i] ? positionMatches[i].replace('Cargo: ', '') : 'Cargo não especificado',
441
- status: statusMatches[i] ? statusMatches[i].replace('Status: ', '') : 'ativo',
442
- analysis: "Análise não disponível - dados incompletos",
443
- recommendations: ["Verificar documentos oficiais", "Revisar histórico de atividades"],
444
- riskScore: Math.floor(Math.random() * 100),
445
- location: `${location} - ${locationType === 'estado' ? 'Estado' : 'Município'}`
446
- });
447
- }
448
-
449
- return agents.length > 0 ? agents : [];
450
- }
451
-
452
- // Display results
453
- function displayResults(results) {
454
- if (results.length === 0) {
455
- noResults.classList.remove('hidden');
456
- return;
457
- }
458
-
459
- resultsContainer.innerHTML = results.map((agent, index) => `
460
- <div class="bg-slate-700 rounded-lg p-4 shadow-md card-hover">
461
- <div class="flex flex-col md:flex-row md:items-center md:justify-between mb-3">
462
- <div class="flex items-center space-x-3 mb-3 md:mb-0">
463
- <div class="bg-blue-600 text-white rounded-full h-10 w-10 flex items-center justify-center font-bold text-lg">
464
- ${agent.name.charAt(0)}
465
- </div>
466
- <div>
467
- <h3 class="font-semibold text-white">${agent.name}</h3>
468
- <p class="text-sm text-slate-300">${agent.position}</p>
469
- </div>
470
- </div>
471
- <div class="text-right">
472
- <p class="text-sm text-slate-400">${agent.location}</p>
473
- <span class="inline-block px-2 py-1 rounded-full text-xs font-medium ${
474
- agent.status === 'ativo' ? 'bg-green-500 text-white' : 'bg-red-500 text-white'
475
- }">
476
- ${agent.status}
477
- </span>
478
- </div>
479
- </div>
480
-
481
- <div class="border-t border-slate-600 pt-3">
482
- <div class="mb-3">
483
- <h4 class="font-medium text-blue-300 mb-1 flex items-center">
484
- <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
485
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V7a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
486
- </svg>
487
- Análise Forense
488
- </h4>
489
- <p class="text-sm text-slate-300">${agent.analysis || 'Análise não disponível'}</p>
490
- </div>
491
-
492
- <div class="mb-3">
493
- <h4 class="font-medium text-blue-300 mb-1 flex items-center">
494
- <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
495
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
496
- </svg>
497
- Recomendações
498
- </h4>
499
- <ul class="list-disc list-inside text-sm text-slate-300 space-y-1">
500
- ${agent.recommendations.map(rec => `<li>${rec}</li>`).join('')}
501
- </ul>
502
- </div>
503
-
504
- <div>
505
- <h4 class="font-medium text-blue-300 mb-1 flex items-center">
506
- <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
507
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
508
- </svg>
509
- Nível de Risco
510
- </h4>
511
- <div class="flex items-center">
512
- <div class="w-full bg-slate-600 rounded-full h-2.5">
513
- <div class="bg-gradient-to-r from-red-500 via-yellow-500 to-green-500 h-full rounded-full" style="width: ${agent.riskScore}%"></div>
514
- </div>
515
- <span class="text-sm font-medium text-white ml-2">${agent.riskScore}%</span>
516
- </div>
517
- </div>
518
- </div>
519
- </div>
520
- `).join('');
521
-
522
- statsContainer.classList.remove('hidden');
523
- }
524
-
525
- // Update stats
526
- function updateStats(results) {
527
- document.getElementById('totalAgents').textContent = results.length;
528
- document.getElementById('activeAgents').textContent = results.filter(r => r.status === 'ativo').length;
529
-
530
- const avgRisk = results.reduce((sum, r) => sum + r.riskScore, 0) / results.length;
531
- document.getElementById('avgRisk').textContent = `${Math.round(avgRisk)}%`;
532
- }
533
-
534
- // Show dashboard
535
- function showDashboard() {
536
- if (currentResults.length === 0) return;
537
-
538
- // Create charts
539
- createCharts(currentResults);
540
- dashboardModal.classList.remove('hidden');
541
- }
542
-
543
- // Hide dashboard
544
- function hideDashboard() {
545
- dashboardModal.classList.add('hidden');
546
- }
547
-
548
- // Create charts
549
- function createCharts(results) {
550
- // Status Chart
551
- const statusCounts = results.reduce((acc, agent) => {
552
- acc[agent.status] = (acc[agent.status] || 0) + 1;
553
- return acc;
554
- }, {});
555
-
556
- new Chart(document.getElementById('statusChart'), {
557
- type: 'doughnut',
558
- data: {
559
- labels: Object.keys(statusCounts),
560
- datasets: [{
561
- data: Object.values(statusCounts),
562
- backgroundColor: ['#10b981', '#ef4444'],
563
- borderWidth: 0
564
- }]
565
- },
566
- options: {
567
- responsive: true,
568
- maintainAspectRatio: false,
569
- plugins: {
570
- legend: {
571
- position: 'bottom',
572
- labels: {
573
- color: '#94a3b8'
574
- }
575
- }
576
- }
577
  }
578
- });
579
 
580
- // Risk Chart
581
- const riskRanges = {
582
- 'Baixo (0-33)': results.filter(r => r.riskScore <= 33).length,
583
- 'Médio (34-66)': results.filter(r => r.riskScore > 33 && r.riskScore <= 66).length,
584
- 'Alto (67-100)': results.filter(r => r.riskScore > 66).length
585
- };
586
 
587
- new Chart(document.getElementById('riskChart'), {
588
- type: 'bar',
589
- data: {
590
- labels: Object.keys(riskRanges),
591
- datasets: [{
592
- label: 'Número de Agentes',
593
- data: Object.values(riskRanges),
594
- backgroundColor: ['#10b981', '#f59e0b', '#ef4444'],
595
- borderWidth: 0
596
- }]
597
- },
598
- options: {
599
- responsive: true,
600
- maintainAspectRatio: false,
601
- plugins: {
602
- legend: {
603
- display: false
604
- }
605
- },
606
- scales: {
607
- y: {
608
- beginAtZero: true,
609
- ticks: {
610
- color: '#94a3b8'
611
- },
612
- grid: {
613
- color: '#334155'
614
- }
615
  },
616
- x: {
617
- ticks: {
618
- color: '#94a3b8'
619
- },
620
- grid: {
621
- color: '#334155'
622
- }
623
- }
 
 
 
 
 
 
 
 
 
 
624
  }
625
- }
626
- });
627
- }
628
 
629
- // Apply filters
630
- function applyFilters() {
631
- const statusFilter = document.getElementById('statusFilter').value;
632
- const riskFilter = document.getElementById('riskFilter').value;
633
-
634
- let filteredResults = [...currentResults];
635
-
636
- // Apply status filter
637
- if (statusFilter !== 'all') {
638
- filteredResults = filteredResults.filter(r => r.status === statusFilter);
639
- }
640
-
641
- // Apply risk filter
642
- if (riskFilter !== 'all') {
643
- const ranges = {
644
- 'low': [0, 33],
645
- 'medium': [34, 66],
646
- 'high': [67, 100]
647
- };
648
-
649
- filteredResults = filteredResults.filter(r => {
650
- const [min, max] = ranges[riskFilter];
651
- return r.riskScore >= min && r.riskScore <= max;
652
- });
653
- }
654
-
655
- displayResults(filteredResults);
656
- updateStats(filteredResults);
657
- }
658
-
659
- // Export data
660
- function exportData() {
661
- if (currentResults.length === 0) return;
662
-
663
- const csvData = [
664
- ['Nome', 'Cargo', 'Localização', 'Status', 'Score de Risco', 'Análise', 'Recomendações'],
665
- ...currentResults.map(agent => [
666
- `"${agent.name}"`,
667
- `"${agent.position}"`,
668
- `"${agent.location}"`,
669
- `"${agent.status}"`,
670
- agent.riskScore,
671
- `"${agent.analysis.replace(/\"/g, '""')}"`,
672
- `"${agent.recommendations.join('; ')}"`
673
- ])
674
- ].join('\n');
675
-
676
- const blob = new Blob([csvData], { type: 'text/csv' });
677
- const url = URL.createObjectURL(blob);
678
- const a = document.createElement('a');
679
- a.href = url;
680
- a.download = `analise-forense-${new Date().toISOString().split('T')[0]}.csv`;
681
- document.body.appendChild(a);
682
- a.click();
683
- document.body.removeChild(a);
684
- URL.revokeObjectURL(url);
685
- }
686
 
687
- // Show error
688
- function showError(message) {
689
- resultsContainer.innerHTML = `
690
- <div class="bg-red-900/50 border border-red-500 rounded-lg p-4">
691
- <div class="flex">
692
- <div class="flex-shrink-0">
693
- <svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
694
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
695
- </svg>
696
- </div>
697
- <div class="ml-3">
698
- <p class="text-sm text-red-300">${message}</p>
699
- </div>
700
- </div>
701
  </div>
702
- `;
703
- }
704
-
705
- // Initialize the plugin
706
- init();
707
 
708
- // Built with anycoder
709
- console.log('S.I.E. PRO Forensic Analytics Plugin initialized');
710
  </script>
711
  </body>
712
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pesquisa Forense de Pessoas</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
9
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
10
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
11
  <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
13
  body {
14
  font-family: 'Inter', sans-serif;
15
  }
 
 
 
 
 
 
 
16
  .loading-spinner {
17
+ border: 4px solid rgba(255, 255, 255, 0.1);
 
18
  border-radius: 50%;
19
+ border-top: 4px solid #3b82f6;
20
+ width: 40px;
21
+ height: 40px;
22
  animation: spin 1s linear infinite;
23
  }
24
  @keyframes spin {
25
  0% { transform: rotate(0deg); }
26
  100% { transform: rotate(360deg); }
27
  }
28
+ .card-hover {
29
+ transition: all 0.3s ease;
30
+ }
31
+ .card-hover:hover {
32
+ transform: translateY(-5px);
33
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
34
+ }
35
  </style>
36
  </head>
37
+ <body class="bg-slate-900 text-slate-200 min-h-screen p-4 font-sans">
38
+ <div id="root"></div>
39
+
40
+ <script type="text/babel">
41
+ const PLUGIN_ID = "forensic-person-search-v1";
42
+ let authToken = null;
43
+
44
+ // Componentes React
45
+ const AuthStatus = () => {
46
+ const [status, setStatus] = React.useState("Aguardando Autenticação...");
47
+ const [isAuthenticated, setIsAuthenticated] = React.useState(false);
48
+
49
+ React.useEffect(() => {
50
+ window.addEventListener('message', (event) => {
51
+ if (event.data.type === 'AUTH_TOKEN') {
52
+ authToken = event.data.token;
53
+ setStatus("Conectado e Autenticado ✅");
54
+ setIsAuthenticated(true);
55
+ }
56
+ });
57
+ }, []);
58
+
59
+ return (
60
+ <div className="flex justify-between items-center mb-6">
61
+ <div className="flex items-center space-x-2">
62
+ <div className="w-3 h-3 rounded-full bg-blue-500 animate-pulse"></div>
63
+ <span className="text-xs text-slate-400">{status}</span>
64
  </div>
65
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener noreferrer" className="text-xs text-blue-400 hover:text-blue-300 transition-colors">
66
+ Built with anycoder
67
+ </a>
68
  </div>
69
+ );
70
+ };
 
 
 
 
71
 
72
+ const SearchForm = ({ onSearch, isLoading }) => {
73
+ const [cpf, setCpf] = React.useState("");
74
+ const [socialProfile, setSocialProfile] = React.useState("");
75
+ const [years, setYears] = React.useState(5);
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ const handleSubmit = (e) => {
78
+ e.preventDefault();
79
+ onSearch({ cpf, socialProfile, years });
80
+ };
 
 
 
 
 
 
 
81
 
82
+ return (
83
+ <form onSubmit={handleSubmit} className="bg-slate-800 p-6 rounded-lg shadow-lg mb-8">
84
+ <h2 className="text-xl font-semibold mb-6 text-white">Pesquisa Forense</h2>
 
 
 
 
 
 
 
85
 
86
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
87
+ <div>
88
+ <label className="block text-sm font-medium text-slate-300 mb-1">CPF</label>
89
+ <input
90
+ type="text"
91
+ value={cpf}
92
+ onChange={(e) => setCpf(e.target.value)}
93
+ placeholder="000.000.000-00"
94
+ className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
95
+ required
96
+ />
97
+ </div>
98
 
 
 
 
 
99
  <div>
100
+ <label className="block text-sm font-medium text-slate-300 mb-1">Perfil Social</label>
101
+ <input
102
+ type="url"
103
+ value={socialProfile}
104
+ onChange={(e) => setSocialProfile(e.target.value)}
105
+ placeholder="https://rede-social.com/perfil"
106
+ className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
107
+ required
108
+ />
109
  </div>
110
 
111
  <div>
112
+ <label className="block text-sm font-medium text-slate-300 mb-1">Anos de História</label>
113
+ <select
114
+ value={years}
115
+ onChange={(e) => setYears(parseInt(e.target.value))}
116
+ className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
117
+ >
118
+ {[1, 2, 3, 4, 5].map((year) => (
119
+ <option key={year} value={year}>{year} ano{year > 1 ? 's' : ''}</option>
120
+ ))}
121
  </select>
122
  </div>
123
+ </div>
124
+
125
+ <button
126
+ type="submit"
127
+ disabled={isLoading}
128
+ className={`w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded-md transition-colors flex items-center justify-center ${isLoading ? 'opacity-70 cursor-not-allowed' : ''}`}
129
+ >
130
+ {isLoading ? (
131
+ <>
132
+ <div className="loading-spinner mr-2"></div>
133
+ Processando...
134
+ </>
135
+ ) : (
136
+ 'Iniciar Pesquisa Forense'
137
+ )}
138
+ </button>
139
+ </form>
140
+ );
141
+ };
142
+
143
+ const ResultsDashboard = ({ results, error }) => {
144
+ if (error) {
145
+ return (
146
+ <div className="bg-red-900/20 border border-red-500 p-4 rounded-lg text-red-300">
147
+ <h3 className="font-semibold mb-2">Erro na Pesquisa</h3>
148
+ <p>{error}</p>
149
+ </div>
150
+ );
151
+ }
152
 
153
+ if (!results) {
154
+ return (
155
+ <div className="text-center py-12">
156
+ <p className="text-slate-400">Insira os dados e clique em "Iniciar Pesquisa Forense" para começar</p>
157
  </div>
158
+ );
159
+ }
160
 
161
+ return (
162
+ <div className="space-y-6">
163
+ {/* Header com informações básicas */}
164
+ <div className="bg-slate-800 p-6 rounded-lg shadow-lg">
165
+ <div className="flex flex-col md:flex-row justify-between items-start md:items-center">
 
166
  <div>
167
+ <h2 className="text-2xl font-bold text-white mb-2">{results.personalInfo.name || "Desconhecido"}</h2>
168
+ <p className="text-slate-300">CPF: {results.personalInfo.cpf || "Não fornecido"}</p>
169
+ <p className="text-slate-400 text-sm">Perfil analisado: {results.socialProfile}</p>
170
  </div>
171
+ <div className="mt-4 md:mt-0">
172
+ <span className={`px-3 py-1 rounded-full text-xs font-medium ${results.riskLevel === 'ALTO' ? 'bg-red-500' : results.riskLevel === 'MÉDIO' ? 'bg-yellow-500' : 'bg-green-500'}`}>
173
+ Nível de Risco: {results.riskLevel || "Não avaliado"}
174
+ </span>
175
  </div>
176
  </div>
177
  </div>
178
 
179
+ {/* Cards de informações */}
180
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
181
+ {/* Card de Atividade */}
182
+ <div className="bg-slate-800 p-6 rounded-lg shadow-lg card-hover">
183
+ <h3 className="text-lg font-semibold text-white mb-4 flex items-center">
184
+ <svg className="w-5 h-5 mr-2 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
185
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
 
 
186
  </svg>
187
+ Atividade Recente
188
+ </h3>
189
+ <div className="space-y-3">
190
+ <div className="flex justify-between text-sm">
191
+ <span className="text-slate-400">Posts nos últimos {results.years} anos</span>
192
+ <span className="font-medium text-white">{results.activity.postCount || 0}</span>
193
+ </div>
194
+ <div className="flex justify-between text-sm">
195
+ <span className="text-slate-400">Comentários</span>
196
+ <span className="font-medium text-white">{results.activity.commentCount || 0}</span>
197
+ </div>
198
+ <div className="flex justify-between text-sm">
199
+ <span className="text-slate-400">Curtidas</span>
200
+ <span className="font-medium text-white">{results.activity.likeCount || 0}</span>
201
+ </div>
202
  </div>
203
  </div>
 
204
 
205
+ {/* Card de Análise de Sentimento */}
206
+ <div className="bg-slate-800 p-6 rounded-lg shadow-lg card-hover">
207
+ <h3 className="text-lg font-semibold text-white mb-4 flex items-center">
208
+ <svg className="w-5 h-5 mr-2 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
209
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
 
 
 
 
210
  </svg>
211
+ Análise de Sentimento
212
+ </h3>
213
+ <div className="space-y-3">
214
+ <div className="flex items-center justify-between">
215
+ <span className="text-slate-400 text-sm">Positivo</span>
216
+ <div className="w-2/3 bg-slate-700 rounded-full h-2.5">
217
+ <div className="bg-green-500 h-2.5 rounded-full" style={{ width: `${results.sentiment.positive || 0}%` }}></div>
218
+ </div>
219
+ <span className="text-white text-sm ml-2">{results.sentiment.positive || 0}%</span>
220
+ </div>
221
+ <div className="flex items-center justify-between">
222
+ <span className="text-slate-400 text-sm">Neutro</span>
223
+ <div className="w-2/3 bg-slate-700 rounded-full h-2.5">
224
+ <div className="bg-blue-500 h-2.5 rounded-full" style={{ width: `${results.sentiment.neutral || 0}%` }}></div>
225
+ </div>
226
+ <span className="text-white text-sm ml-2">{results.sentiment.neutral || 0}%</span>
227
+ </div>
228
+ <div className="flex items-center justify-between">
229
+ <span className="text-slate-400 text-sm">Negativo</span>
230
+ <div className="w-2/3 bg-slate-700 rounded-full h-2.5">
231
+ <div className="bg-red-500 h-2.5 rounded-full" style={{ width: `${results.sentiment.negative || 0}%` }}></div>
232
+ </div>
233
+ <span className="text-white text-sm ml-2">{results.sentiment.negative || 0}%</span>
234
+ </div>
235
  </div>
236
  </div>
 
 
237
 
238
+ {/* Card de Conexões */}
239
+ <div className="bg-slate-800 p-6 rounded-lg shadow-lg card-hover">
240
+ <h3 className="text-lg font-semibold text-white mb-4 flex items-center">
241
+ <svg className="w-5 h-5 mr-2 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
242
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
243
+ </svg>
244
+ Conexões
245
+ </h3>
246
+ <div className="space-y-3">
247
+ <div className="flex justify-between text-sm">
248
+ <span className="text-slate-400">Amigos/Seguidores</span>
249
+ <span className="font-medium text-white">{results.connections.friends || 0}</span>
250
+ </div>
251
+ <div className="flex justify-between text-sm">
252
+ <span className="text-slate-400">Grupos</span>
253
+ <span className="font-medium text-white">{results.connections.groups || 0}</span>
254
+ </div>
255
+ <div className="flex justify-between text-sm">
256
+ <span className="text-slate-400">Páginas Seguidas</span>
257
+ <span className="font-medium text-white">{results.connections.pages || 0}</span>
258
+ </div>
259
+ </div>
260
  </div>
261
  </div>
262
 
263
+ {/* Tabela de Posts Importantes */}
264
+ <div className="bg-slate-800 p-6 rounded-lg shadow-lg">
265
+ <h3 className="text-lg font-semibold text-white mb-4 flex items-center">
266
+ <svg className="w-5 h-5 mr-2 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
267
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3h3m-3 4h3m-3 4h3m-3 4h3" />
268
+ </svg>
269
+ Posts Importantes (Últimos {results.years} anos)
270
+ </h3>
271
+ <div className="overflow-x-auto">
272
+ <table className="w-full text-sm">
273
+ <thead className="text-slate-400 border-b border-slate-700">
274
+ <tr>
275
+ <th className="px-4 py-2 text-left">Data</th>
276
+ <th className="px-4 py-2 text-left">Conteúdo</th>
277
+ <th className="px-4 py-2 text-left">Sentimento</th>
278
+ <th className="px-4 py-2 text-left">Engajamento</th>
279
+ </tr>
280
+ </thead>
281
+ <tbody>
282
+ {results.importantPosts && results.importantPosts.length > 0 ? (
283
+ results.importantPosts.map((post, index) => (
284
+ <tr key={index} className="border-b border-slate-700 hover:bg-slate-700/50 transition-colors">
285
+ <td className="px-4 py-3 text-slate-300">{new Date(post.date).toLocaleDateString()}</td>
286
+ <td className="px-4 py-3 text-white max-w-xs truncate">{post.content}</td>
287
+ <td className="px-4 py-3">
288
+ <span className={`px-2 py-1 rounded-full text-xs font-medium ${post.sentiment === 'POSITIVO' ? 'bg-green-500' : post.sentiment === 'NEGATIVO' ? 'bg-red-500' : 'bg-blue-500'}`}>
289
+ {post.sentiment}
290
+ </span>
291
+ </td>
292
+ <td className="px-4 py-3 text-white">{post.engagement}</td>
293
+ </tr>
294
+ ))
295
+ ) : (
296
+ <tr>
297
+ <td colSpan="4" className="px-4 py-6 text-center text-slate-400">Nenhum post importante encontrado</td>
298
+ </tr>
299
+ )}
300
+ </tbody>
301
+ </table>
302
+ </div>
303
  </div>
 
 
 
 
304
 
305
+ {/* Resumo da IA */}
306
+ <div className="bg-slate-800 p-6 rounded-lg shadow-lg">
307
+ <h3 className="text-lg font-semibold text-white mb-4 flex items-center">
308
+ <svg className="w-5 h-5 mr-2 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
309
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
310
+ </svg>
311
+ Resumo da Análise Forense (IA Gemini)
312
+ </h3>
313
+ <div className="prose prose-invert max-w-none text-slate-300">
314
+ <p className="whitespace-pre-wrap">{results.aiSummary || "Nenhum resumo disponível. A análise da IA Gemini não retornou resultados."}</p>
 
 
 
 
 
 
 
 
 
 
 
315
  </div>
316
  </div>
317
  </div>
318
+ );
319
+ };
320
+
321
+ const App = () => {
322
+ const [results, setResults] = React.useState(null);
323
+ const [error, setError] = React.useState(null);
324
+ const [isLoading, setIsLoading] = React.useState(false);
325
+ const [isAuthenticated, setIsAuthenticated] = React.useState(false);
326
+
327
+ React.useEffect(() => {
328
+ window.addEventListener('message', (event) => {
329
+ if (event.data.type === 'AUTH_TOKEN') {
330
+ authToken = event.data.token;
331
+ setIsAuthenticated(true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  }
333
  });
334
+ }, []);
335
 
336
+ const handleSearch = async ({ cpf, socialProfile, years }) => {
337
+ if (!authToken) {
338
+ setError("Token de autenticação não disponível. Por favor, recarregue a página.");
339
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
 
341
 
342
+ setIsLoading(true);
343
+ setError(null);
344
+ setResults(null);
 
 
 
345
 
346
+ try {
347
+ const response = await fetch('/api/client/plugin/ai', {
348
+ method: 'POST',
349
+ headers: {
350
+ 'Content-Type': 'application/json',
351
+ 'Authorization': `Bearer ${authToken}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  },
353
+ body: JSON.stringify({
354
+ plugin_id: PLUGIN_ID,
355
+ user_prompt: `Realize uma pesquisa forense detalhada da pessoa com CPF ${cpf} e perfil social ${socialProfile}. Analise os últimos ${years} anos de atividade. Estruture os dados nos seguintes campos:
356
+
357
+ 1. personalInfo: {name, cpf, age, location}
358
+ 2. activity: {postCount, commentCount, likeCount}
359
+ 3. sentiment: {positive, neutral, negative} (percentages)
360
+ 4. connections: {friends, groups, pages}
361
+ 5. importantPosts: [ {date, content, sentiment, engagement} ] (max 10)
362
+ 6. riskLevel: "BAIXO", "MÉDIO" ou "ALTO"
363
+ 7. aiSummary: resumo profissional em português (máx 500 palavras) com insights forenses
364
+
365
+ Use a API do Gemini para estruturar e analisar as informações. Inclua qualquer comportamento suspeito, padrões de atividade e potenciais riscos identificados.`
366
+ })
367
+ });
368
+
369
+ if (!response.ok) {
370
+ throw new Error(`Erro na API: ${response.status} ${response.statusText}`);
371
  }
 
 
 
372
 
373
+ const data = await response.json();
374
+ setResults({
375
+ ...data,
376
+ cpf,
377
+ socialProfile,
378
+ years
379
+ });
380
+
381
+ } catch (err) {
382
+ console.error("Erro na pesquisa:", err);
383
+ setError(err.message || "Falha ao processar a pesquisa forense. Tente novamente.");
384
+ } finally {
385
+ setIsLoading(false);
386
+ }
387
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
+ return (
390
+ <div className="max-w-7xl mx-auto">
391
+ <AuthStatus />
392
+ <SearchForm onSearch={handleSearch} isLoading={isLoading} />
393
+ <ResultsDashboard results={results} error={error} />
 
 
 
 
 
 
 
 
 
394
  </div>
395
+ );
396
+ };
 
 
 
397
 
398
+ // Renderizar o aplicativo React
399
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
400
  </script>
401
  </body>
402
  </html>