Spaces:
Running
Running
<html lang="pt-BR"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Identificador de Plásticos - ReciclaTech</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"> | |
<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4/dist/tesseract.min.js"></script> | |
<style> | |
.sidebar { | |
width: 280px; | |
transition: all 0.3s; | |
} | |
.sidebar-collapsed { | |
width: 80px; | |
} | |
.main-content { | |
margin-left: 280px; | |
transition: all 0.3s; | |
} | |
.main-content-expanded { | |
margin-left: 80px; | |
} | |
.drop-zone { | |
border: 2px dashed #ccc; | |
border-radius: 8px; | |
padding: 40px; | |
text-align: center; | |
cursor: pointer; | |
transition: all 0.3s; | |
} | |
.drop-zone.highlight { | |
border-color: #3b82f6; | |
background-color: rgba(59, 130, 246, 0.1); | |
} | |
.plastic-badge { | |
font-size: 12px; | |
padding: 2px 8px; | |
border-radius: 10px; | |
font-weight: bold; | |
} | |
.plastic-PP { background-color: #0077B6; color: white; } | |
.plastic-ABS { background-color: #E63946; color: white; } | |
.plastic-PC { background-color: #1D3557; color: white; } | |
.plastic-PVC { background-color: #457B9D; color: white; } | |
.plastic-PET { background-color: #2A9D8F; color: white; } | |
.plastic-HIPS { background-color: #F4A261; color: white; } | |
.plastic-PS { background-color: #E9C46A; color: white; } | |
.plastic-OTHER { background-color: #6A4C93; color: white; } | |
.progress-bar { | |
height: 8px; | |
background-color: #e0e0e0; | |
border-radius: 4px; | |
overflow: hidden; | |
} | |
.progress-bar-fill { | |
height: 100%; | |
background-color: #3b82f6; | |
transition: width 0.3s ease; | |
} | |
.composite-warning { | |
animation: pulse 2s infinite; | |
} | |
@keyframes pulse { | |
0% { border-left-color: #f59e0b; } | |
50% { border-left-color: #fbbf24; } | |
100% { border-left-color: #f59e0b; } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 font-sans flex"> | |
<!-- Sidebar --> | |
<div id="sidebar" class="sidebar bg-gray-800 text-white h-screen fixed"> | |
<div class="p-4 flex items-center justify-between border-b border-gray-700"> | |
<div class="flex items-center space-x-2"> | |
<i class="fas fa-recycle text-2xl text-blue-400"></i> | |
<span id="logo-text" class="font-bold text-lg">ReciclaTech</span> | |
</div> | |
<button id="toggle-sidebar" class="text-gray-400 hover:text-white"> | |
<i class="fas fa-bars"></i> | |
</button> | |
</div> | |
<nav class="p-4"> | |
<div class="mb-8"> | |
<div class="text-gray-400 text-xs uppercase font-bold mb-2" id="nav-section-title">Menu Principal</div> | |
<ul class="space-y-2"> | |
<li> | |
<a href="#" class="flex items-center space-x-3 p-2 rounded bg-gray-700 text-white"> | |
<i class="fas fa-home w-6 text-center"></i> | |
<span id="nav-dashboard" class="font-medium">Dashboard</span> | |
</a> | |
</li> | |
<li> | |
<a href="#" class="flex items-center space-x-3 p-2 rounded hover:bg-gray-700 text-gray-300 hover:text-white"> | |
<i class="fas fa-tasks w-6 text-center"></i> | |
<span id="nav-processos" class="font-medium">Processos</span> | |
</a> | |
</li> | |
<li> | |
<a href="#" class="flex items-center space-x-3 p-2 rounded hover:bg-gray-700 text-gray-300 hover:text-white"> | |
<i class="fas fa-warehouse w-6 text-center"></i> | |
<span id="nav-estoque" class="font-medium">Estoque</span> | |
</a> | |
</li> | |
</ul> | |
</div> | |
<div> | |
<div class="text-gray-400 text-xs uppercase font-bold mb-2" id="tools-section-title">Ferramentas</div> | |
<ul class="space-y-2"> | |
<li> | |
<a href="#" class="flex items-center space-x-3 p-2 rounded bg-blue-900 text-white"> | |
<i class="fas fa-camera w-6 text-center"></i> | |
<span id="nav-identificador" class="font-medium">Identificador</span> | |
</a> | |
</li> | |
<li> | |
<a href="#" class="flex items-center space-x-3 p-2 rounded hover:bg-gray-700 text-gray-300 hover:text-white"> | |
<i class="fas fa-barcode w-6 text-center"></i> | |
<span id="nav-etiquetas" class="font-medium">Etiquetas</span> | |
</a> | |
</li> | |
</ul> | |
</div> | |
</nav> | |
<div class="absolute bottom-0 left-0 right-0 p-4 border-t border-gray-700"> | |
<button id="updatePricesBtn" class="flex items-center space-x-3 p-2 rounded hover:bg-gray-700 text-gray-300 hover:text-white w-full text-left"> | |
<i class="fas fa-sync-alt w-6 text-center"></i> | |
<span id="nav-update" class="font-medium">Atualizar Preços</span> | |
</button> | |
</div> | |
</div> | |
<!-- Main Content --> | |
<div id="main-content" class="main-content flex-1 p-6"> | |
<div class="bg-white rounded-lg shadow-md p-6"> | |
<!-- Header --> | |
<div class="flex justify-between items-center mb-6"> | |
<div> | |
<h1 class="text-2xl font-bold text-gray-800">Identificador de Plásticos</h1> | |
<p class="text-gray-600">Classifique plásticos de lixo eletrônico por símbolos e códigos</p> | |
</div> | |
<div class="flex space-x-3"> | |
<button id="helpBtn" class="bg-blue-50 hover:bg-blue-100 text-blue-700 px-4 py-2 rounded-lg font-medium flex items-center"> | |
<i class="fas fa-question-circle mr-2"></i> Ajuda | |
</button> | |
</div> | |
</div> | |
<!-- Tool Section --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<!-- Upload Section --> | |
<div class="lg:col-span-2"> | |
<div class="bg-gray-50 rounded-lg p-6"> | |
<h2 class="text-lg font-semibold mb-4">Enviar imagem para análise</h2> | |
<div id="dropZone" class="drop-zone mb-6"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
<h3 class="text-lg font-medium mb-1">Arraste e solte a imagem aqui</h3> | |
<p class="text-gray-500 mb-4">Formatos: JPG, PNG, WEBP</p> | |
<button id="selectFileBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-medium transition"> | |
Selecionar arquivo | |
</button> | |
<input type="file" id="fileInput" accept="image/*" class="hidden"> | |
</div> | |
<div id="previewContainer" class="hidden"> | |
<div class="flex justify-between items-center mb-3"> | |
<h3 class="text-lg font-medium">Pré-visualização</h3> | |
<button id="cancelBtn" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> Cancelar | |
</button> | |
</div> | |
<div class="border border-gray-200 rounded-lg p-4"> | |
<img id="imagePreview" src="#" alt="Pré-visualização" class="max-w-full h-auto rounded mx-auto"> | |
</div> | |
<div class="mt-4 grid grid-cols-2 gap-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Peso (kg)</label> | |
<input type="number" id="weightInput" step="0.01" min="0" placeholder="Opcional" class="w-full p-2 border rounded"> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Origem</label> | |
<select id="sourceInput" class="w-full p-2 border rounded"> | |
<option value="">Selecione...</option> | |
<option value="carcaca">Carcaça</option> | |
<option value="componente">Componente Interno</option> | |
<option value="fio">Fios/Cabos</option> | |
<option value="outro">Outro</option> | |
</select> | |
</div> | |
</div> | |
<button id="analyzeBtn" class="mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-3 rounded-lg font-semibold transition"> | |
<i class="fas fa-search mr-2"></i> Analisar Material | |
</button> | |
</div> | |
<div id="loadingIndicator" class="hidden text-center py-8"> | |
<div class="inline-block animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500 mb-4"></div> | |
<p class="text-gray-600">Analisando o material plástico...</p> | |
<div class="progress-bar mt-2"> | |
<div id="analysisProgress" class="progress-bar-fill" style="width: 0%"></div> | |
</div> | |
<p id="progressText" class="text-sm text-gray-500 mt-2">Iniciando análise...</p> | |
</div> | |
</div> | |
</div> | |
<!-- Results Section --> | |
<div> | |
<div class="bg-gray-50 rounded-lg p-6 h-full"> | |
<h2 class="text-lg font-semibold mb-4">Resultados da Análise</h2> | |
<div id="resultsContainer" class="hidden"> | |
<div id="resultsContent" class="space-y-4"> | |
<!-- Results will be inserted here by JavaScript --> | |
</div> | |
<div id="confirmationSection" class="mt-6 hidden"> | |
<h3 class="font-semibold mb-2">Confirmar identificação</h3> | |
<div class="flex space-x-3"> | |
<button id="confirmCorrectBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 rounded-lg font-medium"> | |
<i class="fas fa-check mr-2"></i> Correto | |
</button> | |
<button id="confirmWrongBtn" class="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg font-medium"> | |
<i class="fas fa-times mr-2"></i> Incorreto | |
</button> | |
</div> | |
</div> | |
<div id="storageSection" class="mt-6 hidden"> | |
<h3 class="font-semibold mb-2">Armazenar resultado</h3> | |
<div class="flex space-x-3"> | |
<button id="storeResultBtn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg font-medium"> | |
<i class="fas fa-save mr-2"></i> Salvar | |
</button> | |
<button id="discardResultBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white py-2 rounded-lg font-medium"> | |
<i class="fas fa-trash mr-2"></i> Descartar | |
</button> | |
</div> | |
</div> | |
</div> | |
<div id="emptyState" class="text-center py-8"> | |
<i class="fas fa-microscope text-4xl text-gray-300 mb-3"></i> | |
<h3 class="text-gray-500 font-medium">Nenhuma análise realizada</h3> | |
<p class="text-sm text-gray-400 mt-1">Envie uma imagem para identificar o plástico</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Recent Analyses --> | |
<div class="mt-8"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-lg font-semibold">Análises Recentes</h2> | |
<button id="clearHistoryBtn" class="text-sm text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-trash-alt mr-1"></i> Limpar histórico | |
</button> | |
</div> | |
<div class="bg-gray-50 rounded-lg p-4 overflow-x-auto"> | |
<table class="min-w-full divide-y divide-gray-200"> | |
<thead class="bg-gray-100"> | |
<tr> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Data</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Material</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Origem</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Valor</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ações</th> | |
</tr> | |
</thead> | |
<tbody id="recentAnalysesTable" class="bg-white divide-y divide-gray-200"> | |
<!-- Recent analyses will be inserted here by JavaScript --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Help Modal --> | |
<div id="helpModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-lg p-6 max-w-2xl max-h-[90vh] overflow-y-auto"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-bold">Ajuda - Identificador de Plásticos</h3> | |
<button id="closeHelpModal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="space-y-4"> | |
<div> | |
<h4 class="font-semibold text-lg mb-2">Como usar:</h4> | |
<ol class="list-decimal pl-5 space-y-2"> | |
<li>Tire uma foto nítida do símbolo de reciclagem ou código do plástico</li> | |
<li>Envie a imagem arrastando ou clicando no botão</li> | |
<li>Adicione o peso (opcional) para cálculo de valor</li> | |
<li>Confirme o resultado e armazene para histórico</li> | |
</ol> | |
</div> | |
<div> | |
<h4 class="font-semibold text-lg mb-2">Códigos reconhecidos:</h4> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<div class="bg-gray-50 p-3 rounded-lg"> | |
<span class="plastic-badge plastic-PET">PET</span> | |
<p class="mt-1 text-sm">1 - Politereftalato de Etileno (Garrafas, fibras)</p> | |
</div> | |
<div class="bg-gray-50 p-3 rounded-lg"> | |
<span class="plastic-badge plastic-PP">PP</span> | |
<p class="mt-1 text-sm">5 - Polipropileno (Embalagens, autopeças)</p> | |
</div> | |
<div class="bg-gray-50 p-3 rounded-lg"> | |
<span class="plastic-badge plastic-ABS">ABS</span> | |
<p class="mt-1 text-sm">Acrilonitrila Butadieno Estireno (Eletrônicos)</p> | |
</div> | |
<div class="bg-gray-50 p-3 rounded-lg"> | |
<span class="plastic-badge plastic-PS">PS</span> | |
<p class="mt-1 text-sm">6 - Poliestireno (Embalagens, isolamento)</p> | |
</div> | |
</div> | |
</div> | |
<div class="bg-blue-50 border-l-4 border-blue-400 p-4"> | |
<h4 class="font-semibold text-lg mb-2">Dicas para melhor reconhecimento:</h4> | |
<ul class="list-disc pl-5 space-y-1"> | |
<li>Fotografe em boa luz e com o símbolo em foco</li> | |
<li>Para peças mistas, fotografe cada código separadamente</li> | |
<li>Limpe a peça antes de fotografar para melhor visibilidade</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Enhanced Plastic Database with recycling codes | |
const plasticos_db = { | |
"PET": { | |
"nome": "Politereftalato de Etileno", | |
"codigo": 1, | |
"reciclavel": true, | |
"valor_kg": 1.80, | |
"grupo": "Verde", | |
"densidade": "1.38-1.39 g/cm³", | |
"descricao": "Comum em garrafas de bebidas and fibras têxteis. Reciclável, mas não reutilizável para alimentos.", | |
"aplicacao": "Garrafas, fibras, embalagens alimentícias", | |
"updateUrl": "https://api.reciclagem.com/precos/PET" | |
}, | |
"PP": { | |
"nome": "Polipropileno", | |
"codigo": 5, | |
"reciclavel": true, | |
"valor_kg": 1.60, | |
"grupo": "Azul", | |
"densidade": "0.89-0.91 g/cm³", | |
"descricao": "Usado em embalagens alimentícias, seringas and autopeças. Resistente a altas temperaturas.", | |
"aplicacao": "Embalagens, autopeças, utensílios médicos", | |
"updateUrl": "https://api.reciclagem.com/precos/PP" | |
}, | |
"ABS": { | |
"nome": "Acrilonitrila Butadieno Estireno", | |
"codigo": null, | |
"reciclavel": true, | |
"valor_kg": 2.40, | |
"grupo": "Vermelho", | |
"densidade": "1.04-1.06 g/cm³", | |
"descricao": "Presente em teclados, carcaças de eletrônicos and brinquedos. Resistente ao impacto.", | |
"aplicacao": "Eletrônicos, brinquedos, peças automotivas", | |
"updateUrl": "https://api.reciclagem.com/precos/ABS" | |
}, | |
"PS": { | |
"nome": "Poliestireno", | |
"codigo": 6, | |
"reciclavel": true, | |
"valor_kg": 1.20, | |
"grupo": "Amarelo", | |
"densidade": "1.04-1.07 g/cm³", | |
"descricao": "Usado em embalagens, copos descartáveis and isolamento térmico.", | |
"aplicacao": "Embalagens, isolamento, utensílios descartáveis", | |
"updateUrl": "https://api.reciclagem.com/precos/PS" | |
}, | |
"OTHER": { | |
"nome": "Outros Plásticos", | |
"codigo": 7, | |
"reciclavel": false, | |
"valor_kg": 0.80, | |
"grupo": "Preto", | |
"densidade": "Varia", | |
"descricao": "Mistura de plásticos ou tipos não classificados. Pode conter BPA.", | |
"aplicacao": "Variada", | |
"updateUrl": "https://api.reciclagem.com/precos/OTHER" | |
} | |
}; | |
// Map numeric codes to plastic types | |
const codeToPlastic = { | |
1: "PET", | |
2: "HDPE", | |
5: "PP", | |
6: "PS", | |
7: "OTHER" | |
}; | |
// Variables | |
let imageToAnalyze; | |
let currentAnalysis = null; | |
let recentAnalyses = JSON.parse(localStorage.getItem('recentAnalyses')) || []; | |
// DOM elements | |
const sidebar = document.getElementById('sidebar'); | |
const mainContent = document.getElementById('main-content'); | |
const toggleSidebarBtn = document.getElementById('toggle-sidebar'); | |
const dropZone = document.getElementById('dropZone'); | |
const fileInput = document.getElementById('fileInput'); | |
const selectFileBtn = document.getElementById('selectFileBtn'); | |
const previewContainer = document.getElementById('previewContainer'); | |
const imagePreview = document.getElementById('imagePreview'); | |
const analyzeBtn = document.getElementById('analyzeBtn'); | |
const cancelBtn = document.getElementById('cancelBtn'); | |
const loadingIndicator = document.getElementById('loadingIndicator'); | |
const resultsContainer = document.getElementById('resultsContainer'); | |
const resultsContent = document.getElementById('resultsContent'); | |
const emptyState = document.getElementById('emptyState'); | |
const confirmationSection = document.getElementById('confirmationSection'); | |
const storageSection = document.getElementById('storageSection'); | |
const confirmCorrectBtn = document.getElementById('confirmCorrectBtn'); | |
const confirmWrongBtn = document.getElementById('confirmWrongBtn'); | |
const storeResultBtn = document.getElementById('storeResultBtn'); | |
const discardResultBtn = document.getElementById('discardResultBtn'); | |
const weightInput = document.getElementById('weightInput'); | |
const sourceInput = document.getElementById('sourceInput'); | |
const analysisProgress = document.getElementById('analysisProgress'); | |
const progressText = document.getElementById('progressText'); | |
const recentAnalysesTable = document.getElementById('recentAnalysesTable'); | |
const clearHistoryBtn = document.getElementById('clearHistoryBtn'); | |
const updatePricesBtn = document.getElementById('updatePricesBtn'); | |
const helpBtn = document.getElementById('helpBtn'); | |
const helpModal = document.getElementById('helpModal'); | |
const closeHelpModal = document.getElementById('closeHelpModal'); | |
// Initialize the app | |
document.addEventListener('DOMContentLoaded', () => { | |
updateRecentAnalysesTable(); | |
setupEventListeners(); | |
}); | |
function setupEventListeners() { | |
// Toggle sidebar | |
toggleSidebarBtn.addEventListener('click', toggleSidebar); | |
// File selection events - CORREÇÃO AQUI | |
selectFileBtn.addEventListener('click', function() { | |
fileInput.click(); | |
}); | |
fileInput.addEventListener('change', function(e) { | |
if (this.files && this.files[0]) { | |
handleFile(this.files[0]); | |
} | |
}); | |
analyzeBtn.addEventListener('click', analyzeImage); | |
cancelBtn.addEventListener('click', resetTool); | |
// Confirmation and storage events | |
confirmCorrectBtn.addEventListener('click', () => confirmAnalysis(true)); | |
confirmWrongBtn.addEventListener('click', () => confirmAnalysis(false)); | |
storeResultBtn.addEventListener('click', storeAnalysis); | |
discardResultBtn.addEventListener('click', resetTool); | |
// Drag and drop events | |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
dropZone.addEventListener(eventName, preventDefaults, false); | |
}); | |
['dragenter', 'dragover'].forEach(eventName => { | |
dropZone.addEventListener(eventName, highlight, false); | |
}); | |
['dragleave', 'drop'].forEach(eventName => { | |
dropZone.addEventListener(eventName, unhighlight, false); | |
}); | |
dropZone.addEventListener('drop', handleDrop, false); | |
// Other buttons | |
clearHistoryBtn.addEventListener('click', clearHistory); | |
updatePricesBtn.addEventListener('click', updatePrices); | |
helpBtn.addEventListener('click', () => helpModal.classList.remove('hidden')); | |
closeHelpModal.addEventListener('click', () => helpModal.classList.add('hidden')); | |
} | |
function toggleSidebar() { | |
sidebar.classList.toggle('sidebar-collapsed'); | |
mainContent.classList.toggle('main-content-expanded'); | |
// Toggle text elements in sidebar | |
document.querySelectorAll('[id$="-text"], [id^="nav-"], [id$="-title"]').forEach(el => { | |
el.classList.toggle('hidden'); | |
}); | |
} | |
function preventDefaults(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
function highlight() { | |
dropZone.classList.add('highlight'); | |
} | |
function unhighlight() { | |
dropZone.classList.remove('highlight'); | |
} | |
function handleDrop(e) { | |
const dt = e.dataTransfer; | |
const files = dt.files; | |
if (files.length > 0 && files[0].type.match('image.*')) { | |
handleFile(files[0]); | |
} | |
} | |
function handleFile(file) { | |
const reader = new FileReader(); | |
reader.onload = function(event) { | |
imagePreview.src = event.target.result; | |
imageToAnalyze = imagePreview; | |
previewContainer.classList.remove('hidden'); | |
dropZone.classList.add('hidden'); | |
emptyState.classList.add('hidden'); | |
resultsContainer.classList.add('hidden'); | |
}; | |
reader.readAsDataURL(file); | |
} | |
// Update progress bar and text | |
function updateProgress(percent, text) { | |
analysisProgress.style.width = `${percent}%`; | |
progressText.textContent = text; | |
} | |
// Analyze the image | |
async function analyzeImage() { | |
if (!imageToAnalyze) return; | |
try { | |
// Show loading indicator | |
previewContainer.classList.add('hidden'); | |
loadingIndicator.classList.remove('hidden'); | |
resultsContainer.classList.add('hidden'); | |
// Get weight and source if provided | |
const weight = weightInput.value ? parseFloat(weightInput.value) : null; | |
const source = sourceInput.value || null; | |
// Step 1: Perform OCR to find plastic codes | |
updateProgress(30, 'Procurando códigos de plástico na imagem...'); | |
const ocrResult = await performOCR(imagePreview); | |
// Step 2: Identify plastic type based on found codes | |
updateProgress(70, 'Identificando tipo de plástico...'); | |
const plasticTypes = identifyPlasticTypes(ocrResult.foundCodes, ocrResult.text); | |
// Get current time for the analysis | |
const now = new Date(); | |
const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
const dateString = now.toLocaleDateString(); | |
// Store current analysis | |
currentAnalysis = { | |
plasticTypes: plasticTypes, | |
weight: weight, | |
source: source, | |
image: imagePreview.src, | |
confirmed: false, | |
stored: false, | |
date: dateString, | |
time: timeString, | |
identifiedBy: 'OCR', | |
confidence: plasticTypes.length > 0 ? 0.95 : 0.70 | |
}; | |
// Display results | |
displayResults(currentAnalysis); | |
updateProgress(100, 'Análise concluída!'); | |
// Small delay to show 100% progress | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
} catch (error) { | |
console.error('Error analyzing image:', error); | |
showError('Erro ao analisar a imagem. Por favor, tente novamente com uma foto mais nítida.'); | |
resetTool(); | |
} | |
} | |
// Perform OCR on the image | |
async function performOCR(imageElement) { | |
try { | |
const { data: { text } } = await Tesseract.recognize( | |
imageElement, | |
'por+eng', // Portuguese and English | |
{ | |
logger: m => { | |
if (m.status === 'recognizing text') { | |
updateProgress(30 + (m.progress * 40), 'Lendo códigos na imagem...'); | |
} | |
} | |
} | |
); | |
// Check for common plastic codes (PP, ABS, PC, etc.) | |
const plasticCodes = text.match(/\b(PP|ABS|PC|PVC|PET|HIPS|PMMA|PA|POM|PBT|PSU|PEI|PEEK|PTFE)\b/gi) || []; | |
// Also check for numeric codes (1-7) | |
const numericCodes = text.match(/\b[1-7]\b/g) || []; | |
// Combine all found codes (uppercase) | |
const allCodes = [ | |
...plasticCodes.map(code => code.toUpperCase()), | |
...numericCodes.map(code => codeToPlastic[code] || null).filter(Boolean) | |
]; | |
return { | |
text: text, | |
foundCodes: [...new Set(allCodes)] // Remove duplicates | |
}; | |
} catch (error) { | |
console.error('OCR error:', error); | |
return { | |
text: '', | |
foundCodes: [] | |
}; | |
} | |
} | |
// Identify plastic types based on found codes | |
function identifyPlasticTypes(foundCodes, ocrText) { | |
// If no codes found, try to identify from text description | |
if (foundCodes.length === 0) { | |
const textIdentification = identifyFromText(ocrText); | |
if (textIdentification) { | |
return [textIdentification]; | |
} | |
return [{ | |
type: 'OTHER', | |
...plasticos_db['OTHER'], | |
badgeClass: 'plastic-OTHER', | |
confidence: 0.70 | |
}]; | |
} | |
// Return all identified plastics | |
return foundCodes.map(code => { | |
const plasticInfo = plasticos_db[code] || plasticos_db['OTHER']; | |
return { | |
type: code, | |
...plasticInfo, | |
badgeClass: `plastic-${code}`, | |
confidence: 0.95 | |
}; | |
}); | |
} | |
// Try to identify plastic from text description | |
function identifyFromText(text) { | |
const lowerText = text.toLowerCase(); | |
// Check for common plastic descriptions | |
if (lowerText.includes('polipropileno') || lowerText.includes('polyprop')) { | |
return { | |
type: 'PP', | |
...plasticos_db['PP'], | |
badgeClass: 'plastic-PP', | |
confidence: 0.85 | |
}; | |
} | |
if (lowerText.includes('poliestireno') || lowerText.includes('polystyrene')) { | |
return { | |
type: 'PS', | |
...plasticos_db['PS'], | |
badgeClass: 'plastic-PS', | |
confidence: 0.85 | |
}; | |
} | |
return null; | |
} | |
// Display the analysis results | |
function displayResults(analysis) { | |
loadingIndicator.classList.add('hidden'); | |
emptyState.classList.add('hidden'); | |
// Calculate total value if weight is provided | |
const totalValue = analysis.weight && analysis.plasticTypes.length > 0 ? | |
(analysis.weight * analysis.plasticTypes[0].valor_kg).toFixed(2) : | |
null; | |
// Create results HTML | |
let html = ''; | |
// Show warning for composite materials | |
if (analysis.plasticTypes.length > 1) { | |
html += ` | |
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-4 composite-warning"> | |
<div class="flex"> | |
<div class="flex-shrink-0"> | |
<i class="fas fa-exclamation-triangle text-yellow-500"></i> | |
</div> | |
<div class="ml-3"> | |
<p class="text-sm text-yellow-700"> | |
<strong>Material composto encontrado!</strong><br> | |
Esta peça contém múltiplos plásticos: | |
${analysis.plasticTypes.map(t => t.type).join(' + ')}. | |
Separe manualmente se possível. | |
</p> | |
</div> | |
</div> | |
</div> | |
`; | |
} | |
// Show info for each plastic type found | |
analysis.plasticTypes.forEach(plasticType => { | |
const plasticValue = analysis.weight ? | |
(analysis.weight * plasticType.valor_kg).toFixed(2) : | |
null; | |
html += ` | |
<div class="bg-white border border-gray-200 rounded-lg p-4 mb-4"> | |
<div class="flex justify-between items-start mb-3"> | |
<div> | |
<span class="${plasticType.badgeClass} plastic-badge">${plasticType.type}</span> | |
${plasticType.codigo ? `<span class="ml-2 text-sm text-gray-500">Código: ${plasticType.codigo}</span>` : ''} | |
</div> | |
<div class="text-sm text-gray-500">${analysis.date} ${analysis.time}</div> | |
</div> | |
<h3 class="font-bold text-lg mb-1">${plasticType.nome}</h3> | |
<p class="text-sm text-gray-600 mb-3">${plasticType.descricao}</p> | |
<div class="grid grid-cols-2 gap-4 mb-3"> | |
<div> | |
<div class="text-xs text-gray-500">Reciclável</div> | |
<div class="font-medium">${plasticType.reciclavel ? 'Sim' : 'Não'}</div> | |
</div> | |
<div> | |
<div class="text-xs text-gray-500">Valor por kg</div> | |
<div class="font-medium">R$ ${plasticType.valor_kg.toFixed(2)}</div> | |
</div> | |
<div> | |
<div class="text-xs text-gray-500">Densidade</div> | |
<div class="font-medium">${plasticType.densidade}</div> | |
</div> | |
${analysis.weight ? ` | |
<div> | |
<div class="text-xs text-gray-500">Valor estimado</div> | |
<div class="font-medium">R$ ${plasticValue}</div> | |
</div> | |
` : ''} | |
</div> | |
<div class="flex items-center mb-2"> | |
<div class="w-full mr-2"> | |
<div class="progress-bar"> | |
<div class="progress-bar-fill" style="width: ${Math.round(plasticType.confidence * 100)}%"></div> | |
</div> | |
</div> | |
<span class="text-sm font-medium">${Math.round(plasticType.confidence * 100)}%</span> | |
</div> | |
<div class="text-xs text-gray-500 mb-3">Aplicação comum: ${plasticType.aplicacao}</div> | |
</div> | |
`; | |
}); | |
// Add storage recommendations | |
if (analysis.plasticTypes.length > 0) { | |
const mainPlastic = analysis.plasticTypes[0]; | |
html += ` | |
<div class="bg-white border border-gray-200 rounded-lg p-4"> | |
<h4 class="font-semibold mb-2">Próximos passos recomendados</h4> | |
<ul class="space-y-2 text-sm"> | |
<li class="flex items-start"> | |
<i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
<span>Separar na área de plásticos <strong>${mainPlastic.grupo}</strong> (${mainPlastic.type})</span> | |
</li> | |
<li class="flex items-start"> | |
<i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
<span>${mainPlastic.reciclavel ? 'Encaminhar para reciclagem' : 'Descarte especial necessário'}</span> | |
</li> | |
${analysis.weight ? ` | |
<li class="flex items-start"> | |
<i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
<span>Valor estimado: R$ ${totalValue}</span> | |
</li> | |
` : ''} | |
${analysis.source ? ` | |
<li class="flex items-start"> | |
<i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
<span>Origem: ${getSourceName(analysis.source)}</span> | |
</li> | |
` : ''} | |
</ul> | |
</div> | |
`; | |
} | |
resultsContent.innerHTML = html; | |
resultsContainer.classList.remove('hidden'); | |
confirmationSection.classList.remove('hidden'); | |
storageSection.classList.add('hidden'); | |
} | |
function getSourceName(sourceKey) { | |
const sources = { | |
'carcaca': 'Carcaça', | |
'componente': 'Componente Interno', | |
'fio': 'Fios/Cabos', | |
'outro': 'Outro' | |
}; | |
return sources[sourceKey] || sourceKey; | |
} | |
// Confirm analysis (correct or incorrect) | |
function confirmAnalysis(isCorrect) { | |
if (!currentAnalysis) return; | |
currentAnalysis.confirmed = isCorrect; | |
if (isCorrect) { | |
// Show storage options | |
confirmationSection.classList.add('hidden'); | |
storageSection.classList.remove('hidden'); | |
// Update results to show confirmation | |
const confirmationBadge = document.createElement('div'); | |
confirmationBadge.className = 'bg-green-100 text-green-800 text-xs px-2 py-1 rounded mb-3 flex items-center'; | |
confirmationBadge.innerHTML = `<i class="fas fa-check-circle mr-1"></i> Identificação confirmada como correta`; | |
resultsContent.prepend(confirmationBadge); | |
} else { | |
// Allow user to try again | |
showError('Identificação marcada como incorreta. Por favor, revise ou tente novamente.'); | |
confirmationSection.classList.add('hidden'); | |
// Add button to try again | |
const tryAgainBtn = document.createElement('button'); | |
tryAgainBtn.className = 'mt-4 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg font-medium'; | |
tryAgainBtn.innerHTML = '<i class="fas fa-redo mr-2"></i> Tentar novamente'; | |
tryAgainBtn.addEventListener('click', resetTool); | |
resultsContent.appendChild(tryAgainBtn); | |
} | |
} | |
// Store the analysis result | |
function storeAnalysis() { | |
if (!currentAnalysis) return; | |
currentAnalysis.stored = true; | |
currentAnalysis.storedAt = new Date().toISOString(); | |
// Add to recent analyses | |
recentAnalyses.unshift({ | |
...currentAnalysis, | |
// Store only the first plastic type for the table view | |
mainPlastic: currentAnalysis.plasticTypes[0] | |
}); | |
// Keep only last 50 analyses | |
if (recentAnalyses.length > 50) { | |
recentAnalyses = recentAnalyses.slice(0, 50); | |
} | |
// Save to localStorage | |
localStorage.setItem('recentAnalyses', JSON.stringify(recentAnalyses)); | |
// Update recent analyses table | |
updateRecentAnalysesTable(); | |
// Show success message | |
const storageBadge = document.createElement('div'); | |
storageBadge.className = 'bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded mb-3 flex items-center'; | |
storageBadge.innerHTML = `<i class="fas fa-check-circle mr-1"></i> Resultado armazenado com sucesso`; | |
resultsContent.prepend(storageBadge); | |
// Hide storage section | |
storageSection.classList.add('hidden'); | |
// Add button to analyze another image | |
const newAnalysisBtn = document.createElement('button'); | |
newAnalysisBtn.className = 'mt-4 w-full bg-green-600 hover:bg-green-700 text-white py-2 rounded-lg font-medium'; | |
newAnalysisBtn.innerHTML = '<i class="fas fa-camera mr-2"></i> Analisar outro material'; | |
newAnalysisBtn.addEventListener('click', resetTool); | |
resultsContent.appendChild(newAnalysisBtn); | |
} | |
// Update recent analyses table | |
function updateRecentAnalysesTable() { | |
let html = ''; | |
recentAnalyses.slice(0, 10).forEach(analysis => { | |
const plastic = analysis.mainPlastic || analysis.plasticTypes[0] || plasticos_db['OTHER']; | |
const totalValue = analysis.weight ? | |
(analysis.weight * plastic.valor_kg).toFixed(2) : | |
'-'; | |
html += ` | |
<tr> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${analysis.date}</td> | |
<td class="px-6 py-4 whitespace-nowrap"> | |
<span class="${plastic.badgeClass} plastic-badge">${plastic.type}</span> | |
<span class="ml-2 text-xs text-gray-500">${plastic.nome}</span> | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
${analysis.source ? getSourceName(analysis.source) : '-'} | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
${totalValue === '-' ? '-' : `R$ ${totalValue}`} | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
<button class="text-blue-600 hover:text-blue-800 mr-3" onclick="viewAnalysis('${analysis.storedAt}')"> | |
<i class="fas fa-eye"></i> | |
</button> | |
<button class="text-green-600 hover:text-green-800" onclick="printLabel('${analysis.storedAt}')"> | |
<i class="fas fa-tag"></i> | |
</button> | |
</td> | |
</tr> | |
`; | |
}); | |
if (recentAnalyses.length === 0) { | |
html = ` | |
<tr> | |
<td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500"> | |
Nenhuma análise recente encontrada | |
</td> | |
</tr> | |
`; | |
} | |
recentAnalysesTable.innerHTML = html; | |
} | |
// View a stored analysis | |
window.viewAnalysis = function(storedAt) { | |
const analysis = recentAnalyses.find(a => a.storedAt === storedAt); | |
if (analysis) { | |
currentAnalysis = analysis; | |
imagePreview.src = analysis.image; | |
previewContainer.classList.remove('hidden'); | |
dropZone.classList.add('hidden'); | |
displayResults(analysis); | |
// Scroll to results | |
resultsContainer.scrollIntoView({ behavior: 'smooth' }); | |
} | |
}; | |
// Print label for a stored analysis | |
window.printLabel = function(storedAt) { | |
const analysis = recentAnalyses.find(a => a.storedAt === storedAt); | |
if (analysis) { | |
const plastic = analysis.mainPlastic || analysis.plasticTypes[0] || plasticos_db['OTHER']; | |
const win = window.open('', '_blank'); | |
win.document.write(` | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Etiqueta - ${plastic.type}</title> | |
<style> | |
body { font-family: Arial, sans-serif; margin: 0; padding: 10mm; } | |
.label { width: 80mm; height: 50mm; border: 1px solid #ccc; padding: 5mm; } | |
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; color: white; font-weight: bold; } | |
h2 { margin: 0 0 5px 0; } | |
p { margin: 2px 0; font-size: 14px; } | |
</style> | |
</head> | |
<body> | |
<div class="label"> | |
<h2>ReciclaTech</h2> | |
<p><span class="badge" style="background: ${getComputedStyle(document.querySelector(`.${plastic.badgeClass}`)).backgroundColor}">${plastic.type}</span></p> | |
<p><strong>${plastic.nome}</strong></p> | |
<p>${analysis.date} ${analysis.time}</p> | |
${analysis.weight ? `<p>Peso: ${analysis.weight} kg</p>` : ''} | |
${analysis.source ? `<p>Origem: ${getSourceName(analysis.source)}</p>` : ''} | |
<p>Valor/kg: R$ ${plastic.valor_kg.toFixed(2)}</p> | |
</div> | |
<script> | |
setTimeout(() => { window.print(); window.close(); }, 200); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Diegobrons/s3" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |