import os import gradio as gr from pinecone import Pinecone, ServerlessSpec from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext, Settings from llama_index.vector_stores.pinecone import PineconeVectorStore from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.llms.openai import OpenAI # --- System Prompt --- SYSTEM_PROMPT = """You are Shifu, a polite and professional Healthy Recipe assistant. Answer ONLY using the information found in the indexed recipe document(s). If the answer is not in the document(s), say: "I couldn’t find that in the document." Keep responses concise, helpful, and friendly. """ # ===== CONFIG ===== PINECONE_API_KEY = os.getenv("PINECONE_API_KEY") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") if not PINECONE_API_KEY or not OPENAI_API_KEY: raise RuntimeError("Missing PINECONE_API_KEY or OPENAI_API_KEY") DATA_DIR = "data" BANNER_PATH = os.path.join(DATA_DIR, "shafaq_banner.png") if not os.path.exists(BANNER_PATH): raise RuntimeError("Banner not found at: data/shafaq_banner.png") EMBED_MODEL = "text-embedding-3-small" LLM_MODEL = "gpt-4o-mini" TOP_K = 4 # ===== LlamaIndex & Pinecone ===== Settings.embed_model = OpenAIEmbedding(model=EMBED_MODEL, api_key=OPENAI_API_KEY) Settings.llm = OpenAI(model=LLM_MODEL, api_key=OPENAI_API_KEY, system_prompt=SYSTEM_PROMPT) pc = Pinecone(api_key=PINECONE_API_KEY) def ensure_index(name: str, dim: int = 1536): names = [i["name"] for i in pc.list_indexes()] if name not in names: pc.create_index( name=name, dimension=dim, metric="cosine", spec=ServerlessSpec(cloud="aws", region="us-east-1"), ) return pc.Index(name) pinecone_index = ensure_index("shafaq-recipes-index", dim=1536) vector_store = PineconeVectorStore(pinecone_index=pinecone_index) def bootstrap_index(): if not os.path.isdir(DATA_DIR): raise RuntimeError("No 'data/' directory found.") docs = SimpleDirectoryReader(DATA_DIR).load_data() if not docs: raise RuntimeError("No documents found in data/.") storage_ctx = StorageContext.from_defaults(vector_store=vector_store) VectorStoreIndex.from_documents(docs, storage_context=storage_ctx, show_progress=True) bootstrap_index() def answer(query: str) -> str: if not query.strip(): return "Please enter a question." index = VectorStoreIndex.from_vector_store(vector_store) resp = index.as_query_engine(similarity_top_k=TOP_K).query(query) return str(resp) FAQS = [ "", "How long do these breakfast recipes last in the fridge?", "Can I freeze any of these recipes?", "Are there gluten-free breakfast options?", "How can I make these breakfasts dairy-free?", "What can I use instead of eggs?", "Can I use different veggies or toppings?", "I don’t have almond or coconut milk. What else can I use?", "How can I add more flavor to these recipes?", "What if I don’t have protein powder?", "Can I prep these meals ahead of time?", ] def use_faq(selected_faq: str, free_text: str): prompt = (selected_faq or "").strip() or (free_text or "").strip() if not prompt: return "", "Please select a FAQ or type your question." return prompt, answer(prompt) # ===== UI ===== CSS = """ .header { text-align: left; } .logo img { width:100%; max-width: 320px; border-radius: 12px; } .title { font-weight: 700; font-size: 1.8rem; color: #41644A; margin-bottom: 4px; } .subnote { font-size: 1rem; color: #6C757D; margin-bottom: 20px; } .section-title { font-weight: 600; margin-top: 12px; } """ with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="green", secondary_hue="orange")) as demo: with gr.Row(): with gr.Column(scale=1, min_width=350): gr.Image(value=BANNER_PATH, show_label=False, elem_classes=["logo"]) with gr.Column(scale=2): gr.Markdown("""
Healthy Recipe Recommender based on what’s in your kitchen 🍳🥑