import streamlit as st import pickle import requests from bs4 import BeautifulSoup # === Custom CSS for better styling === def load_css(): st.markdown(""" """, unsafe_allow_html=True) # === Load TF-IDF Vectorizer === @st.cache_resource def load_vectorizer(): with open("saved_models/tfidf_vectorizer.pkl", "rb") as f: return pickle.load(f) # === Load XGBoost Model === @st.cache_resource def load_model(): with open("saved_models/XGBoost_model.pkl", "rb") as f: return pickle.load(f) # === Prediksi === def predict_allergen(model, vectorizer, input_text): X_input = vectorizer.transform([input_text]) prediction = model.predict(X_input) return prediction[0] # === Scraping bahan dari Cookpad === def get_ingredients_from_cookpad(url): headers = {"User-Agent": "Mozilla/5.0"} try: response = requests.get(url, headers=headers) if response.status_code != 200: return None, "Gagal mengambil halaman." soup = BeautifulSoup(response.text, "html.parser") ingredient_div = soup.find("div", class_="ingredient-list") if not ingredient_div: return None, "Tidak menemukan elemen bahan." ingredients = [] for item in ingredient_div.find_all("li"): amount = item.find("bdi") name = item.find("span") if amount and name: ingredients.append(f"{amount.get_text(strip=True)} {name.get_text(strip=True)}") else: ingredients.append(item.get_text(strip=True)) return ingredients, None except Exception as e: return None, f"Terjadi kesalahan: {str(e)}" # === Display results with custom styling === def display_results(results): st.markdown("### ๐ŸŽฏ Hasil Analisis Alergen") positive_results = [] negative_results = [] for allergen, status in results.items(): if status == 1: positive_results.append(allergen) else: negative_results.append(allergen) # Display positive results (allergens detected) if positive_results: st.markdown("#### โš ๏ธ **Alergen Terdeteksi:**") for allergen in positive_results: st.markdown(f'
๐Ÿšจ {allergen}
', unsafe_allow_html=True) # Display negative results (safe allergens) if negative_results: st.markdown("#### โœ… **Aman dari Alergen:**") for allergen in negative_results: st.markdown(f'
โœ“ {allergen}
', unsafe_allow_html=True) # Show summary if not positive_results: st.markdown('
๐ŸŽ‰ Tidak ada alergen berbahaya terdeteksi!
', unsafe_allow_html=True) # === Main UI === def main(): st.set_page_config( page_title="Deteksi Alergen Makanan", page_icon="๐Ÿฅ˜", layout="wide", initial_sidebar_state="expanded" ) # Load custom CSS load_css() # Header st.markdown("""

๐Ÿฅ˜ Deteksi Alergen Makanan

Analisis kandungan alergen dalam resep makanan dengan teknologi AI

""", unsafe_allow_html=True) # Sidebar info with st.sidebar: st.markdown("### ๐Ÿ“‹ Informasi Alergen") st.markdown(""" **Alergen yang dapat dideteksi:** - ๐Ÿฅ› Susu - ๐Ÿฅœ Kacang - ๐Ÿฅš Telur - ๐Ÿฆ Makanan Laut - ๐ŸŒพ Gandum """) st.markdown("### ๐Ÿ’ก Tips Penggunaan") st.markdown(""" - Masukkan bahan dengan detail - Gunakan nama bahan dalam bahasa Indonesia - Untuk URL Cookpad, pastikan link valid - Maksimal 20 URL per analisis """) # Main content col1, col2, col3 = st.columns([1, 6, 1]) with col2: # Input method selection st.markdown("### ๐Ÿ”ง Pilih Metode Input") input_mode = st.radio( "", ["๐Ÿ“ Input Manual", "๐Ÿ”— URL Cookpad"], horizontal=True ) # Load model components try: vectorizer = load_vectorizer() model = load_model() labels = ['Susu', 'Kacang', 'Telur', 'Makanan Laut', 'Gandum'] except Exception as e: st.error(f"โŒ Gagal memuat model: {str(e)}") st.stop() st.markdown("---") if input_mode == "๐Ÿ“ Input Manual": st.markdown("### ๐Ÿ“ Masukkan Bahan Makanan") # Info card st.markdown("""
๐Ÿ’ก Petunjuk: Masukkan daftar bahan makanan yang ingin dianalisis. Pisahkan setiap bahan dengan koma atau baris baru.
""", unsafe_allow_html=True) input_text = st.text_area( "", height=150, placeholder="Contoh: telur, susu, tepung terigu, garam, mentega..." ) col_btn1, col_btn2, col_btn3 = st.columns([2, 2, 2]) with col_btn2: if st.button("๐Ÿ” Analisis Alergen", use_container_width=True): if not input_text.strip(): st.warning("โš ๏ธ Mohon masukkan bahan makanan terlebih dahulu.") else: with st.spinner("๐Ÿ”„ Sedang menganalisis..."): pred = predict_allergen(model, vectorizer, input_text) results = dict(zip(labels, pred)) st.success("โœ… Analisis selesai!") display_results(results) elif input_mode == "๐Ÿ”— URL Cookpad": st.markdown("### ๐Ÿ”— Analisis dari URL Cookpad") # Info card st.markdown("""
๐Ÿ’ก Petunjuk: Masukkan hingga 20 URL resep dari Cookpad. Setiap URL harus dalam baris terpisah.
""", unsafe_allow_html=True) urls_input = st.text_area( "", placeholder="https://cookpad.com/id/resep/...\nhttps://cookpad.com/id/resep/...", height=200 ) urls = [url.strip() for url in urls_input.splitlines() if url.strip()] if len(urls) > 20: st.warning("โš ๏ธ Maksimal hanya bisa memproses 20 URL. Menggunakan 20 URL pertama.") urls = urls[:20] if urls: st.info(f"๐Ÿ“Š Siap memproses {len(urls)} URL") if st.button("๐Ÿ” Analisis dari URL", use_container_width=True): if not urls: st.warning("โš ๏ธ Mohon masukkan minimal satu URL.") else: # Progress bar progress_bar = st.progress(0) status_text = st.empty() for i, url in enumerate(urls): # Update progress progress = (i + 1) / len(urls) progress_bar.progress(progress) status_text.markdown(f'
Memproses resep {i+1} dari {len(urls)}
', unsafe_allow_html=True) ingredients, error = get_ingredients_from_cookpad(url) with st.expander(f"๐Ÿ“– Resep #{i+1}", expanded=False): st.markdown(f"**URL:** {url}") if error: st.error(f"โŒ {error}") else: st.success("โœ… Bahan berhasil diambil!") # Display ingredients in a nice format st.markdown("**๐Ÿงพ Daftar Bahan:**") for ing in ingredients: st.markdown(f'
โ€ข {ing}
', unsafe_allow_html=True) # Predict allergens joined_ingredients = " ".join(ingredients) pred = predict_allergen(model, vectorizer, joined_ingredients) results = dict(zip(labels, pred)) st.markdown("---") display_results(results) # Clear progress indicators progress_bar.empty() status_text.empty() st.success("๐ŸŽ‰ Semua resep telah dianalisis!") # Footer st.markdown(""" """, unsafe_allow_html=True) if __name__ == "__main__": main()