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}")