chatbot / app.py
jacs10's picture
new code
18fb44e verified
raw
history blame
9.16 kB
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()