/
linariell
/
Honeysuckle
Обзор
Документация
Войти
/
linariell
/
Honeysuckle
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
index.html
242 строки
13 KB
Polina Vitkovskaya
fixing OCR
20 май 2026, 04:47
20 май 2026, 04:47
bae406c
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Honeysuckle AutoVision Pro</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <style> .table-container::-webkit-scrollbar { width: 6px; } .table-container::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } .preview-img { width: 100%; height: 60px; object-fit: contain; border-radius: 0.5rem; border: 1px solid #e2e8f0; background: #fff; } #mainImage { transition: transform 0.3s ease-in-out; } </style> </head> <body class="bg-slate-50 font-sans min-h-screen flex flex-col"> <nav class="bg-white border-b p-4 shadow-sm"> <div class="max-w-7xl mx-auto flex justify-between items-center"> <h1 class="text-xl font-bold italic"> 🌿 Honeysuckle <span class="text-green-600">AI</span> <span class="text-[10px] bg-slate-100 px-2 py-1 rounded text-slate-500 ml-2 uppercase tracking-tighter">Auto-Scale Edition</span> </h1> <div class="text-xs text-slate-400 font-mono" id="storageStatus">Записей: 0</div> </div> </nav> <main class="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-6 p-6 w-full"> <!-- ЛЕВАЯ КОЛОНКА --> <div class="lg:col-span-8 space-y-6"> <!-- Окно просмотра фото --> <div id="image-container" class="shadow-inner relative flex items-center justify-center bg-[#111] rounded-2xl overflow-hidden h-[60vh] border-2 border-slate-300"> <!-- НОВАЯ КНОПКА ПОВОРОТА --> <button id="rotateBtn" onclick="rotateImage()" class="hidden absolute top-4 right-4 z-10 bg-slate-900/50 hover:bg-slate-900/80 backdrop-blur border border-white/20 text-white px-4 py-2 rounded-lg text-xs font-bold transition shadow-lg"> ↻ ПОВЕРНУТЬ </button> <img id="mainImage" class="max-w-full max-h-full object-contain" src="" style="display:none;" /> <div id="placeholder" class="text-slate-500 text-center"> <span class="text-5xl block mb-3 opacity-50">📸</span> <p class="font-bold">Нет изображения</p> <p class="text-sm mt-1">Загрузите фото для автоматического анализа</p> </div> </div> <!-- Таблица --> <div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden"> <div class="p-4 border-b bg-slate-50 flex justify-between items-center"> <h2 class="font-bold text-slate-700">📊 Журнал измерений</h2> <div class="flex gap-2"> <button onclick="exportToExcel()" class="text-xs bg-white border border-slate-300 px-3 py-1.5 rounded-lg hover:bg-slate-100">Экспорт .xlsx</button> <button onclick="clearHistory()" class="text-xs bg-red-50 text-red-600 px-3 py-1.5 rounded-lg hover:bg-red-100">Очистить</button> </div> </div> <div class="table-container overflow-y-auto max-h-[300px]"> <table class="w-full text-left text-sm"> <thead class="bg-slate-50 sticky top-0"> <tr> <th class="p-3 border-b">Образец</th> <th class="p-3 border-b">Площадь (мм²)</th> <th class="p-3 border-b">Площадь (см²)</th> <th class="p-3 border-b text-right">Действие</th> </tr> </thead> <tbody id="historyBody"></tbody> </table> </div> </div> </div> <!-- ПРАВАЯ КОЛОНКА --> <div class="lg:col-span-4 space-y-4"> <div class="bg-white p-4 rounded-2xl shadow-sm border border-slate-100 space-y-3"> <input type="file" id="imageInput" class="hidden" accept="image/*"> <button onclick="document.getElementById('imageInput').click()" class="w-full bg-slate-800 hover:bg-black text-white py-4 rounded-xl font-bold transition shadow-md"> 📁 ВЫБРАТЬ ФОТО </button> <button id="analyzeBtn" class="w-full bg-green-600 hover:bg-green-700 disabled:bg-slate-200 disabled:text-slate-400 text-white py-4 rounded-xl text-lg font-bold shadow-md transition" disabled> 🔬 АНАЛИЗИРОВАТЬ </button> </div> <div id="resultsCard" class="hidden bg-white p-6 rounded-2xl shadow-sm border border-slate-200 space-y-4 border-l-4 border-l-green-500"> <div class="bg-blue-50 p-3 rounded-lg border border-blue-100 flex items-center justify-between"> <p id="resLabel" class="text-lg font-mono font-bold text-blue-900 leading-none">---</p> <button onclick="renameSample()" class="text-blue-400 hover:text-blue-600 transition">✏️</button> </div> <div class="text-center space-y-2"> <div> <span class="text-6xl font-black text-slate-800" id="resArea">0</span> <span class="text-slate-400 font-bold">мм²</span> </div> <div> <span class="text-3xl font-black text-green-600" id="resAreaCm">0</span> <span class="text-slate-400 font-bold text-sm">см²</span> </div> </div> <button id="saveBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 rounded-lg font-bold transition"> ➕ СОХРАНИТЬ В ЖУРНАЛ </button> <div class="grid grid-cols-3 gap-2 pt-4 border-t"> <div class="text-center"> <p class="text-[9px] text-slate-400 mb-1 uppercase font-bold">Маска</p> <img id="preMask" class="preview-img"> </div> <div class="text-center"> <p class="text-[9px] text-slate-400 mb-1 uppercase font-bold">Сетка</p> <img id="preGrid" class="preview-img"> </div> <div class="text-center"> <p class="text-[9px] text-slate-400 mb-1 uppercase font-bold">Этикетка</p> <img id="preLabel" class="preview-img"> </div> </div> </div> </div> </main> <script> const analyzeBtn = document.getElementById("analyzeBtn"); const imageInput = document.getElementById("imageInput"); const mainImage = document.getElementById("mainImage"); const rotateBtn = document.getElementById("rotateBtn"); const saveBtn = document.getElementById("saveBtn"); let fileData, currentResult = null; let currentRotation = 0; // Состояние поворота картинки let history = JSON.parse(localStorage.getItem('honeysuckle_history') || '[]'); // Функция вращения (работает по часовой стрелке) function rotateImage() { currentRotation = (currentRotation + 90) % 360; // Учитываем небольшой масштаб при повороте 90/270, чтобы изображение не вылазило за края контейнера const scale = (currentRotation === 90 || currentRotation === 270) ? 0.85 : 1; mainImage.style.transform = `rotate(${currentRotation}deg) scale(${scale})`; } imageInput.onchange = e => { if (!e.target.files[0]) return; fileData = e.target.files[0]; // Сбрасываем поворот при загрузке нового фото currentRotation = 0; mainImage.style.transform = `rotate(0deg) scale(1)`; mainImage.src = URL.createObjectURL(fileData); mainImage.style.display = 'block'; document.getElementById('placeholder').style.display = 'none'; rotateBtn.classList.remove('hidden'); // Показываем кнопку вращения analyzeBtn.disabled = false; document.getElementById("resultsCard").classList.add("hidden"); }; analyzeBtn.onclick = async () => { analyzeBtn.innerText = "⏳ ИДЕТ АНАЛИЗ..."; analyzeBtn.disabled = true; const form = new FormData(); form.append("file", fileData); form.append("rotation", currentRotation); // Отправляем градус поворота на сервер! try { const res = await fetch("/analyze", { method: "POST", body: form }); const data = await res.json(); if (data.status === "error") { alert(data.message); return; } currentResult = data; document.getElementById("resLabel").innerText = data.label; document.getElementById("resArea").innerText = data.area_mm2; document.getElementById("resAreaCm").innerText = data.area_cm2; document.getElementById("preMask").src = "data:image/jpeg;base64," + data.previews.mask; document.getElementById("preGrid").src = "data:image/jpeg;base64," + data.previews.grid; document.getElementById("preLabel").src = "data:image/jpeg;base64," + data.previews.label_crop; // Результат (обрезанный лист) уже ровный, поэтому сбрасываем визуальный поворот контейнера mainImage.style.transform = `rotate(0deg) scale(1)`; currentRotation = 0; mainImage.src = "data:image/jpeg;base64," + data.previews.roi; rotateBtn.classList.add('hidden'); // Прячем кнопку вращения, т.к. кадр уже вырезан document.getElementById("resultsCard").classList.remove("hidden"); } catch(err) { alert("Ошибка связи с сервером. Проверьте консоль Python."); } finally { analyzeBtn.innerText = "🔬 АНАЛИЗИРОВАТЬ"; analyzeBtn.disabled = false; } }; // --- ЛОГИКА ТАБЛИЦЫ --- function updateHistoryTable() { const body = document.getElementById("historyBody"); body.innerHTML = history.length ? "" : '<tr><td colspan="4" class="p-8 text-center text-slate-400 italic">Журнал пуст</td></tr>'; history.forEach((item, i) => { body.innerHTML += `<tr class="hover:bg-slate-50 border-b border-slate-100 last:border-0"> <td class="p-3"><div class="font-bold text-slate-800">${item.label}</div><div class="text-[10px] text-slate-400">${item.date}</div></td> <td class="p-3 font-black text-slate-600">${item.area} <span class="text-[10px] font-normal">мм²</span></td> <td class="p-3 font-black text-green-600">${item.area_cm2} <span class="text-[10px] font-normal">см²</span></td> <td class="p-3 text-right"><button onclick="deleteEntry(${i})" class="text-slate-300 hover:text-red-500 transition text-xl">🗑️</button></td> </tr>`; }); document.getElementById("storageStatus").innerText = `Записей: ${history.length}`; } saveBtn.onclick = () => { history.unshift({ label: currentResult.label, area: currentResult.area_mm2, area_cm2: currentResult.area_cm2, date: new Date().toLocaleString() }); localStorage.setItem('honeysuckle_history', JSON.stringify(history)); updateHistoryTable(); saveBtn.innerText = "✅ ГОТОВО"; setTimeout(() => saveBtn.innerText = "➕ СОХРАНИТЬ В ЖУРНАЛ", 2000); }; function deleteEntry(i) { history.splice(i, 1); localStorage.setItem('honeysuckle_history', JSON.stringify(history)); updateHistoryTable(); } function clearHistory() { if(confirm("Очистить?")) { history = []; localStorage.clear(); updateHistoryTable(); } } function renameSample() { const n = prompt("Имя:", document.getElementById("resLabel").innerText); if(n) { document.getElementById("resLabel").innerText = n; currentResult.label = n; } } function exportToExcel() { const ws = XLSX.utils.json_to_sheet(history); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Data"); XLSX.writeFile(wb, "Honeysuckle_Results.xlsx"); } updateHistoryTable(); </script> </body> </html>