import gradio as gr from huggingface_hub import InferenceClient import os from sentence_transformers import SentenceTransformer import torch import numpy as np from datetime import datetime, timedelta # Load knowledge base with open("knowledge.txt", "r", encoding="utf-8") as f: knowledge_text = f.read() chunks = [chunk.strip() for chunk in knowledge_text.split("\n\n") if chunk.strip()] embedder = SentenceTransformer('all-MiniLM-L6-v2') chunk_embeddings = embedder.encode(chunks, convert_to_tensor=True) def get_relevant_context(query, top_k=3): query_embedding = embedder.encode(query, convert_to_tensor=True) query_embedding = query_embedding / query_embedding.norm() norm_chunk_embeddings = chunk_embeddings / chunk_embeddings.norm(dim=1, keepdim=True) similarities = torch.matmul(norm_chunk_embeddings, query_embedding) top_k_indices = torch.topk(similarities, k=top_k).indices.cpu().numpy() context = "\n\n".join([chunks[i]] for i in top_k_indices) return context client = InferenceClient("google/gemma-2-2b-it") cycle_ai_prompt = """ Cycle-Aware Wellness AI Coach (Strict Enforcement Version) ========================================================== Mission: -------- You are a compassionate and knowledgeable wellness coach who specializes in fitness aligned with the menstrual cycle and evidence-based contraceptive education. Your mission is to empower women to understand their bodies, support their fitness and reproductive health, and make informed, cycle-aware choices. You may *only* respond to questions related to: - Menstrual cycles and hormonal phases - Cycle-based fitness and wellness programming - Contraceptive methods and reproductive health education - Hormonal syndromes or life stages (PCOS, PMDD, menopause, irregular cycles, etc.) If the user asks something unrelated (e.g., fixing a car, meal prep, unrelated illnesses, tech support), immediately respond with: "I'm here to help with cycle-based fitness and contraceptive wellness. That question’s outside my scope, but I’d love to support you with anything related to your body, cycle, or health goals!" You must *never* attempt to answer off-topic requests, even if the user insists or rephrases. Always redirect the conversation back to wellness, hormones, fitness, or reproductive health. Style and Voice Guidelines: --------------------------- - Speak like a friendly, knowledgeable older sister who’s also a certified personal trainer and women’s health educator. - Be warm, inclusive, and body-positive. Avoid judgment, shame, or clinical coldness. - Validate the user’s experience before offering guidance (e.g., “That makes total sense—your energy might be shifting in this phase”). - Encourage autonomy by offering options, not orders. - Connect advice to real-life impact: how the cycle affects energy, mood, and performance. When the user asks about workouts, always ask a follow-up: "What kind of equipment or space do you have access to—like a gym, home weights, or just bodyweight? I’ll tailor a workout for you based on that!" Final Boundary Rule: -------------------- Strictly decline all unrelated questions. Your only purpose is cycle-aware fitness and reproductive wellness coaching. Do not give general medical, tech, cooking, legal, or life advice. """ def determine_cycle_phase(start_date_str): try: start_date = datetime.strptime(start_date_str, "%Y-%m-%d") days_since = (datetime.now() - start_date).days % 28 if days_since < 5: return "Menstrual Phase", "💗 Time to rest and recover. Gentle movement like stretching or walking is great." elif days_since < 13: return "Follicular Phase", "💪 Your energy’s building—go for strength training or cardio!" elif days_since < 16: return "Ovulation Phase", "🔥 Peak power! Try high-intensity workouts or social activities." else: return "Luteal Phase", "🌙 Wind down. Opt for lighter training, yoga, or bodyweight exercises." except: return "Unknown Phase", "Couldn't parse the date. Please use YYYY-MM-DD." def respond(message, history): if not isinstance(history, list): history = [] messages = [{"role": "system", "content": cycle_ai_prompt}] for entry in history: if isinstance(entry, dict): messages.append(entry) messages.append({"role": "user", "content": message}) response = client.chat_completion( messages, max_tokens=500, temperature=0.1 ) print("DEBUG RESPONSE:", response) # <-- Add this to inspect # Now adapt based on actual response try: assistant_reply = response['choices'][0]['message']['content'].strip() except Exception as e: assistant_reply = f"⚠️ There was an error processing the response: {e}" new_history = history + [ {"role": "user", "content": message}, {"role": "assistant", "content": assistant_reply} ] return new_history, new_history, "" def update_chatbot(user_message, history): return respond(user_message, history) def set_user_info(name, age, level, period_start_date, period_end_date): phase, tip = determine_cycle_phase(period_start_date) greeting = f"Hi {name}! I'm here to help you with cycle-aware fitness and wellness.\n\nYou’re {age} years old, training at a {level.lower()} level, and your last period started on {period_start_date} and ended on {period_end_date}.\n\nRight now, you’re likely in your **{phase}**. {tip} 💞\n\nAsk me anything about your body, cycle, or contraceptive health!" return name, [{"role": "assistant", "content": greeting}] def button_click(question, history): new_history, updated_history, _ = respond(question, history) return new_history, updated_history with gr.Blocks( css=""" .gradio-container { background: linear-gradient(135deg, #F6D365 0%, #FDA085 50%, #FF8FA0 100%); font-family: 'Quicksand', sans-serif; } .message.user { background-color: lightpink; border-radius: 20px; padding: 10px; margin: 5px; max-width: 75%; align-self: flex-end; } .message.bot { background-color: lightcoral; border-radius: 20px; padding: 10px; margin: 5px; max-width: 75%; align-self: flex-start; } .chat-interface { background-color: peachpuff; border-radius: 12px; padding: 15px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); } #banner-image { background-color: transparent; margin-bottom: -100px; } """, theme=gr.themes.Soft( primary_hue="pink", secondary_hue="orange", neutral_hue="yellow", spacing_size="lg", radius_size="lg", font=[gr.themes.GoogleFont("Quicksand"), "sans-serif"], font_mono=[gr.themes.GoogleFont("IBM Plex Mono"), "monospace"] ) ) as demo: gr.Image( value="/content/Untitled design.png", show_label=False, show_share_button = False, show_download_button = False, elem_id="banner-image") name_state = gr.State("") chat_history = gr.State([]) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### Tell me about yourself") name_input = gr.Textbox(label="Name", placeholder="Your name…") age_input = gr.Textbox(label="Age", placeholder="Your age…") level_input = gr.Dropdown(choices=["Beginner", "Intermediate", "Expert"], label="Training Level") period_start_input = gr.Textbox(label="Last Period Start Date", placeholder="YYYY-MM-DD") period_end_input = gr.Textbox(label="Last Period End Date", placeholder="YYYY-MM-DD") set_btn = gr.Button("Set Info") gr.Markdown("_After the greeting appears, start chatting →_") with gr.Column(scale=2): chatbot = gr.Chatbot(label="Chat", type="messages") user_text = gr.Textbox(placeholder="Ask me something…", label="") with gr.Row(): workout_btn = gr.Button("Prompt: Workouts") contraceptive_btn = gr.Button("Prompt: Contraceptives") set_btn.click( fn=set_user_info, inputs=[name_input, age_input, level_input, period_start_input, period_end_input], outputs=[name_state, chatbot], show_progress=False, ) user_text.submit( fn=update_chatbot, inputs=[user_text, chat_history], outputs=[chatbot, chat_history, user_text] ) workout_btn.click( fn=lambda history: button_click("What workout routine would you recommend based on my cycle phase?", history), inputs=[chat_history], outputs=[chatbot, chat_history] ) contraceptive_btn.click( fn=lambda history: button_click("Can you explain the different contraceptive options and their benefits?", history), inputs=[chat_history], outputs=[chatbot, chat_history] ) if __name__ == '__main__': demo.launch()