Germinal commited on
Commit
cb69733
·
verified ·
1 Parent(s): 9c04413

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +712 -0
index.html ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
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>