import streamlit as st import pandas as pd import requests import time import textwrap import os import glob API_BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8000") CLIENT_ID = os.environ.get("CLIENT_ID", "DEMO_CLIENT_001") APP_PASSWORD = os.environ.get("APP_PASSWORD", "s3poke") # Imposta questa nei Segreti di HF Space st.set_page_config(page_title="Rilevatore di Ingredienti", layout="wide") # CSS moderno e professionale con il font Inter st.markdown(""" """, unsafe_allow_html=True) # Logica di autenticazione def check_password(): if not APP_PASSWORD: return True if "password_correct" not in st.session_state: st.session_state["password_correct"] = False if not st.session_state["password_correct"]: st.markdown('

Accesso Rilevatore di Ingredienti

', unsafe_allow_html=True) st.info("Questo spazio รจ privato. Inserisci la password di accesso.") password = st.text_input("Password", type="password") if st.button("Accedi"): if password == APP_PASSWORD: st.session_state["password_correct"] = True st.rerun() else: st.error("Password errata.") return False return True if not check_password(): st.stop() def _do_configure_api(): config_dir = os.path.join(os.path.dirname(__file__), "csv_config") csv_files = glob.glob(os.path.join(config_dir, "*.csv")) if not csv_files: return {"status": "error", "message": "Nessun CSV di configurazione trovato "} try: df = pd.read_csv(csv_files[0]) required_cols = {"PRODUCT_ID", "DESCRIPTION", "FAMILY", "COMPONENT", "EXTRA DESCRIPTION"} if not required_cols.issubset(set(df.columns)): return {"status": "error", "message": "Colonne richieste mancanti nel CSV."} mask = (df["COMPONENT"].str.lower() == "ingredient") | (df["COMPONENT"].str.lower().str.startswith("proteine")) df_filtered = df[mask].copy() df_filtered["id"] = df_filtered["PRODUCT_ID"].astype(str) df_filtered["description"] = df_filtered["DESCRIPTION"].astype(str) df_filtered["FAMILY"] = df_filtered["FAMILY"].fillna("") df_filtered["EXTRA DESCRIPTION"] = df_filtered["EXTRA DESCRIPTION"].fillna("") df_filtered["extra_description"] = df_filtered["COMPONENT"].astype(str) + " - " + df_filtered["EXTRA DESCRIPTION"].astype(str) products = df_filtered[["id", "description", "extra_description"]].to_dict(orient="records") payload = { "client_id": CLIENT_ID, "context": "Rilevamento degli ingredienti all'interno delle Poke Bowl in Italia.", "products": products } resp = requests.post(f"{API_BASE_URL}/configure", json=payload) if resp.status_code == 200: return {"status": "success", "message": "API configurata con successo."} else: return {"status": "error", "message": f"Errore API {resp.status_code}: {resp.text}"} except Exception as e: return {"status": "error", "message": f"Impossibile configurare l'API: {e}"} @st.cache_resource(show_spinner=False) def configure_api(): return _do_configure_api() # Inizializza la configurazione silenziosamente with st.spinner("Inizializzazione configurazione API..."): config_status = configure_api() st.markdown('

Rilevatore di Ingredienti

', unsafe_allow_html=True) if config_status["status"] == "error": st.error(f"Errore di Inizializzazione: {config_status['message']}") st.info("Controlla i file di configurazione e la connessione API.") st.markdown("### Analisi dell'Immagine") st.markdown("Carica l'immagine di una bowl o seleziona un'immagine di test per identificarne automaticamente gli ingredienti.") # Cerca la cartella delle immagini di test (demo/test_images o ../tests) possible_test_dirs = [ os.path.join(os.path.dirname(__file__), "test_images"), os.path.join(os.path.dirname(__file__), "..", "tests") ] test_images_dir = next((d for d in possible_test_dirs if os.path.exists(d)), None) image_to_analyze_path = None uploaded_img = None if test_images_dir: test_images = [] for ext in ('*.png', '*.jpg', '*.jpeg', '*.webp'): test_images.extend(glob.glob(os.path.join(test_images_dir, ext))) test_images = sorted(test_images) if test_images: selection_mode = st.radio("Scegli la sorgente dell'immagine:", ["Carica la tua", "Seleziona un'immagine di test"], horizontal=True) if selection_mode == "Seleziona un'immagine di test": img_names = [os.path.basename(p) for p in test_images] selected_img_name = st.selectbox("Seleziona un'immagine di test:", img_names) image_to_analyze_path = os.path.join(test_images_dir, selected_img_name) else: uploaded_img = st.file_uploader("Carica Immagine", type=["png", "jpg", "jpeg", "webp"], label_visibility="collapsed") else: uploaded_img = st.file_uploader("Carica Immagine", type=["png", "jpg", "jpeg", "webp"], label_visibility="collapsed") if uploaded_img is not None or image_to_analyze_path is not None: col1, col2 = st.columns([1, 1], gap="large") with col1: if uploaded_img is not None: st.image(uploaded_img, width='stretch') img_name = uploaded_img.name img_bytes = uploaded_img.getvalue() img_type = uploaded_img.type else: with open(image_to_analyze_path, "rb") as f: img_bytes = f.read() st.image(img_bytes, width='stretch') img_name = os.path.basename(image_to_analyze_path) img_type = "image/jpeg" if img_name.lower().endswith(('.jpg', '.jpeg')) else "image/png" with col2: if st.button("Identifica Ingredienti", type="primary", width='stretch'): with st.spinner("Analisi dell'immagine in corso..."): files = {"image": (img_name, img_bytes, img_type)} data = {"client_id": CLIENT_ID} try: resp = requests.post(f"{API_BASE_URL}/predict", files=files, data=data) if resp.status_code == 409: st.info("Configurazione assente in questa istanza. Sincronizzazione in corso... riprova la predizione in automatico.") conf_resp = _do_configure_api() if conf_resp.get("status") == "success": # Ripopolare i file consumati da request.post e riprovare files = {"image": (img_name, img_bytes, img_type)} resp = requests.post(f"{API_BASE_URL}/predict", files=files, data=data) else: st.error(f"Errore durante l'auto-configurazione: {conf_resp.get('message')}") if resp.status_code == 200: predictions = resp.json().get("predictions", []) if not predictions: st.info("Nessun ingrediente rilevato nell'immagine.") else: st.success("Analisi completata") result_container = st.empty() displayed_markdown = "

Componenti Rilevati

" for pred in predictions: score_pct = pred.get("score", 0) * 100 desc = pred.get("description") or "Nessun dettaglio aggiuntivo disponibile" p_id = pred.get("product_id") item_html = textwrap.dedent(f"""
{p_id} {score_pct:.1f}%
{desc}
""") displayed_markdown += item_html result_container.markdown(displayed_markdown, unsafe_allow_html=True) time.sleep(0.08) # Elegante ritardo d'animazione else: st.error(f"Errore API {resp.status_code}: {resp.text}") except Exception as e: st.error(f"Impossibile connettersi all'API: {e}")