Spaces:
Sleeping
Sleeping
| // 1. ZDJĘCIA Z DATASETU | |
| const hardcodedMeals = [ | |
| { name: "Stek z warzywami", image: "/test_images/stek_z_warzywami.jpg", macros: { kcal: 525, p: 48, f: 35, c: 6 } }, | |
| { name: "Mini pizza salami", image: "/test_images/mini_pizza_salami.jpg", macros: { kcal: 132, p: 6, f: 6, c: 15 } }, | |
| { name: "Jajecznica z brokułem", image: "/test_images/jajecznica_z_brokulem.jpg", macros: { kcal: 296, p: 19, f: 21, c: 7 } }, | |
| { name: "Mix sałat", image: "/test_images/miska_salaty.jpg", macros: { kcal: 20, p: 1, f: 0, c: 4 } }, | |
| { name: "Bekon", image: "/test_images/bekon.jpg", macros: { kcal: 470, p: 32, f: 36, c: 1 } }, | |
| { name: "Owsianka z owocami", image: "/test_images/owsianka_z_owocami.jpg", macros: { kcal: 155, p: 5, f: 3, c: 29 } } | |
| ]; | |
| // 2. ZDJĘCIA SPOZA DATASETU | |
| const externalMeals = [ | |
| { name: "Burger drwala", image: "/test_images/burger_drwala.png", macros: { kcal: 915, p: 39, f: 52, c: 70 } }, | |
| { name: "Kotelty drobiowe z ziemniakami i sałatą", image: "/test_images/kotelty_z_ziemniakami.jpeg", macros: { kcal: 590, p: 61, f: 11, c: 63 } }, | |
| { name: "Spaghetti bolognese", image: "/test_images/spaghetti-bolognese.jpg", macros: { kcal: 405, p: 21, f: 17, c: 43 } }, | |
| { name: "SUPER bowl", image: "/test_images/super_bowl.png", macros: { kcal: 716, p: 33, f: 42, c: 60 } } | |
| ]; | |
| // 3. POSIŁKI UŻYTKOWNIKÓW | |
| let userMeals = JSON.parse(localStorage.getItem('savedUserMeals')) || []; | |
| let currentFile = null; | |
| let currentTruth = null; | |
| let activeTab = null; | |
| // Słownik do przywracania oryginalnych nazw przycisków | |
| const btnOriginalText = { | |
| test: "🗂️ Dataset", | |
| external: "🌍 Spoza Datasetu", | |
| user: "👤 Użytkownik" | |
| }; | |
| window.onload = () => {}; | |
| function openSidebar(tab) { | |
| const sidebar = document.getElementById('sidebar'); | |
| const btns = { | |
| test: document.getElementById('btnTest'), | |
| external: document.getElementById('btnExternal'), | |
| user: document.getElementById('btnUser') | |
| }; | |
| // Resetujemy wszystkie przyciski do stanu domyślnego | |
| for (let key in btns) { | |
| btns[key].classList.remove('open'); | |
| btns[key].classList.remove('shifted'); // Usuwamy przesunięcie | |
| btns[key].style.backgroundColor = "#333"; | |
| btns[key].style.color = "white"; | |
| btns[key].innerText = btnOriginalText[key]; | |
| } | |
| // Jeśli kliknięto aktywny panel -> zamknij i przerwij (wszystko wraca na prawo) | |
| if (activeTab === tab && sidebar.classList.contains('open')) { | |
| sidebar.classList.remove('open'); | |
| activeTab = null; | |
| return; | |
| } | |
| // Otwarcie nowego panelu | |
| sidebar.classList.add('open'); | |
| activeTab = tab; | |
| // Otwarcie nowego panelu | |
| sidebar.classList.add('open'); | |
| activeTab = tab; | |
| // JEŚLI TO TELEFON - ZAMKNIJ PRZYCISKI PO WYBORZE | |
| if (window.innerWidth <= 800) { | |
| document.getElementById('menuContainer').classList.remove('show'); | |
| } | |
| // Przesuwamy WSZYSTKIE przyciski na lewo (do krawędzi panelu) | |
| for (let key in btns) { | |
| btns[key].classList.add('shifted'); | |
| } | |
| // Stylizacja TYLKO aktywnego przycisku i zmiana tekstu na emotkę | |
| btns[tab].classList.add('open'); | |
| btns[tab].style.backgroundColor = "#00ff88"; | |
| btns[tab].style.color = "#121212"; | |
| btns[tab].innerText = "▶︎▶︎▶︎"; // Emotka zastępująca ">>> Zamknij" | |
| // Wybór odpowiedniej zawartości do wyświetlenia | |
| if (tab === 'test') { | |
| renderList(hardcodedMeals, 'test'); | |
| } else if (tab === 'external') { | |
| renderList(externalMeals, 'external'); | |
| } else { | |
| renderList(userMeals, 'user'); | |
| } | |
| } | |
| function renderList(mealsArray, type) { | |
| const list = document.getElementById('mealsList'); | |
| const form = document.getElementById('addMealForm'); | |
| const title = document.getElementById('sidebarTitle'); | |
| const desc = document.getElementById('sidebarDesc'); | |
| list.innerHTML = ""; | |
| if (type === 'test') { | |
| title.innerText = "Baza Datasetu"; | |
| desc.innerText = "Oryginalne posiłki wycięte ze zbioru testowego."; | |
| form.style.display = 'none'; | |
| if(mealsArray.length === 0) list.innerHTML = "<p style='color:#555;'>Brak zdjęć w skrypcie.</p>"; | |
| } else if (type === 'external') { | |
| title.innerText = "Spoza Datasetu"; | |
| desc.innerText = "Testy na zdjęciach spoza zbioru."; | |
| form.style.display = 'none'; | |
| if(mealsArray.length === 0) list.innerHTML = "<p style='color:#555;'>Brak zewnętrznych zdjęć.</p>"; | |
| } else { | |
| title.innerText = "Posiłki Użytkownika"; | |
| desc.innerText = "Zapisywane lokalnie w przeglądarce."; | |
| form.style.display = 'block'; | |
| if(mealsArray.length === 0) list.innerHTML = "<p style='color:#555; text-align:center;'>Dodaj pierwszy posiłek poniżej!</p>"; | |
| } | |
| mealsArray.forEach((meal, index) => { | |
| list.innerHTML += ` | |
| <div class="meal-card" onclick="loadMealIntoAnalyzer(${index}, '${type}')"> | |
| <img src="${meal.image}" alt="Zdjęcie"> | |
| <div class="meal-card-title">${meal.name}</div> | |
| <div class="meal-card-macro"> | |
| 🔥 ${meal.macros.kcal} kcal | 🍗 ${meal.macros.p}g | 🥑 ${meal.macros.f}g | 🍞 ${meal.macros.c}g | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| } | |
| function previewNewImage(event) { | |
| if(event.target.files.length === 0) return; | |
| currentFile = event.target.files[0]; | |
| currentTruth = null; | |
| setupPreview(URL.createObjectURL(currentFile), "Tryb: Nieznane zdjęcie (Z dysku)"); | |
| } | |
| async function loadMealIntoAnalyzer(index, type) { | |
| let meal; | |
| if (type === 'test') meal = hardcodedMeals[index]; | |
| else if (type === 'external') meal = externalMeals[index]; | |
| else meal = userMeals[index]; | |
| currentTruth = meal.macros; | |
| try { | |
| const res = await fetch(meal.image); | |
| const blob = await res.blob(); | |
| currentFile = new File([blob], "test_image.jpg", { type: "image/jpeg" }); | |
| setupPreview(meal.image, `Tryb: Ground Truth (${meal.name})`); | |
| if(window.innerWidth < 800) openSidebar(activeTab); | |
| } catch (err) { | |
| alert("Błąd ładowania! Upewnij się, że zdjęcie jest fizycznie w folderze."); | |
| } | |
| } | |
| function setupPreview(imageSrc, modeText) { | |
| const preview = document.getElementById('preview'); | |
| const modeInd = document.getElementById('modeIndicator'); | |
| preview.src = imageSrc; | |
| preview.style.display = 'block'; | |
| document.getElementById('analyzeBtn').disabled = false; | |
| document.getElementById('resultsPanel').style.display = 'none'; | |
| modeInd.innerText = modeText; | |
| modeInd.style.color = currentTruth ? "#ffaa00" : "#aaa"; | |
| } | |
| async function analyzeImage() { | |
| if (!currentFile) return; | |
| const btn = document.getElementById('analyzeBtn'); | |
| btn.disabled = true; | |
| btn.innerText = "⏳ ONNX analizuje..."; | |
| const formData = new FormData(); | |
| formData.append("file", currentFile); | |
| try { | |
| const response = await fetch('/predict-macro', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| displayResults(data.predictions); | |
| } catch (error) { | |
| alert("Błąd serwera! Upewnij się, że FastAPI działa."); | |
| console.error(error); | |
| } finally { | |
| btn.disabled = false; | |
| btn.innerText = "Analizuj Makro"; | |
| } | |
| } | |
| function displayResults(preds) { | |
| document.getElementById('res-kcal').innerText = preds.kcal + " kcal"; | |
| document.getElementById('res-protein').innerText = preds.protein_g + " g"; | |
| document.getElementById('res-fat').innerText = preds.fat_g + " g"; | |
| document.getElementById('res-carb').innerText = preds.carb_g + " g"; | |
| const truthCols = document.querySelectorAll('.truth-col'); | |
| if (currentTruth) { | |
| truthCols.forEach(col => col.style.display = 'table-cell'); | |
| document.getElementById('truth-kcal').innerText = currentTruth.kcal + " kcal"; | |
| document.getElementById('truth-protein').innerText = currentTruth.p + " g"; | |
| document.getElementById('truth-fat').innerText = currentTruth.f + " g"; | |
| document.getElementById('truth-carb').innerText = currentTruth.c + " g"; | |
| document.getElementById('diff-kcal').innerText = Math.abs((preds.kcal - currentTruth.kcal).toFixed(1)) + " kcal"; | |
| document.getElementById('diff-protein').innerText = Math.abs((preds.protein_g - currentTruth.p).toFixed(1)) + " g"; | |
| document.getElementById('diff-fat').innerText = Math.abs((preds.fat_g - currentTruth.f).toFixed(1)) + " g"; | |
| document.getElementById('diff-carb').innerText = Math.abs((preds.carb_g - currentTruth.c).toFixed(1)) + " g"; | |
| } else { | |
| truthCols.forEach(col => col.style.display = 'none'); | |
| } | |
| document.getElementById('resultsPanel').style.display = 'block'; | |
| } | |
| function addUserMeal() { | |
| const fileInput = document.getElementById('newMealImage'); | |
| const name = document.getElementById('newMealName').value; | |
| const kcal = document.getElementById('newMealKcal').value; | |
| const p = document.getElementById('newMealP').value; | |
| const f = document.getElementById('newMealF').value; | |
| const c = document.getElementById('newMealC').value; | |
| if(!fileInput.files[0] || !name || !kcal) { | |
| alert("Wypełnij nazwę, zdjęcie i chociaż kalorie!"); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const newMeal = { | |
| name: name, | |
| image: e.target.result, | |
| macros: { kcal: kcal, p: p||0, f: f||0, c: c||0 } | |
| }; | |
| userMeals.push(newMeal); | |
| localStorage.setItem('savedUserMeals', JSON.stringify(userMeals)); | |
| document.getElementById('newMealName').value = ""; | |
| fileInput.value = ""; | |
| renderList(userMeals, 'user'); | |
| }; | |
| reader.readAsDataURL(fileInput.files[0]); | |
| } | |
| // Obsługa menu na telefonach | |
| function toggleMobileMenu() { | |
| document.getElementById('menuContainer').classList.toggle('show'); | |
| } |