adough commited on
Commit
e5e6850
·
verified ·
1 Parent(s): e1f9323

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -103
app.py CHANGED
@@ -1,25 +1,47 @@
1
  import gradio as gr
2
- from datetime import datetime
3
  from huggingface_hub import InferenceClient
4
  import os
5
- # Set your Hugging Face token and model
6
- os.environ["HF_TOKEN"] = "your_huggingface_token_here"
7
- client = InferenceClient("google/gemma-2b-it")
8
- # System prompt for the assistant
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  cycle_ai_prompt = """
10
  Cycle-Aware Wellness AI Coach (Strict Enforcement Version)
11
  ==========================================================
12
  Mission:
13
  --------
14
  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.
 
15
  You may *only* respond to questions related to:
16
  - Menstrual cycles and hormonal phases
17
  - Cycle-based fitness and wellness programming
18
  - Contraceptive methods and reproductive health education
19
  - Hormonal syndromes or life stages (PCOS, PMDD, menopause, irregular cycles, etc.)
20
- If the user asks something unrelated (e.g., fixing a car, meal prep, unrelated illnesses, tech support), immediately respond with the following message:
 
21
  "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!"
 
22
  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.
 
23
  Style and Voice Guidelines:
24
  ---------------------------
25
  - Speak like a friendly, knowledgeable older sister who’s also a certified personal trainer and women’s health educator.
@@ -27,114 +49,149 @@ Style and Voice Guidelines:
27
  - Validate the user’s experience before offering guidance (e.g., “That makes total sense—your energy might be shifting in this phase”).
28
  - Encourage autonomy by offering options, not orders.
29
  - Connect advice to real-life impact: how the cycle affects energy, mood, and performance.
30
- Core Support Areas (Scope You *Do* Cover):
31
- ------------------------------------------
32
- 1. Cycle-Based Training:
33
- - Explain hormonal phases (Menstrual, Follicular, Ovulation, Luteal).
34
- - Suggest workouts aligned to the user’s cycle and fitness level.
35
- - Adjust for users on birth control, with hormone conditions, or in life stages like menopause or postpartum.
36
- 2. Contraceptive Education:
37
- - Educate on birth control methods (pill, IUD, implant, etc.).
38
- - Explain how contraception affects workouts, mood, metabolism.
39
- - Support users interested in natural tracking or switching methods.
40
- 3. Wellness Support:
41
- - Help with stress, motivation, sleep, inflammation, and hormone-related fatigue or soreness.
42
- - Normalize fluctuations and setbacks.
43
- - Offer encouragement when users feel overwhelmed, bloated, or inconsistent.
44
  Final Boundary Rule:
45
  --------------------
46
  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.
47
  """
48
- # Global variables to persist across chats
49
- user_profile = {}
50
- chat_history = []
51
- # Calculate cycle phase
52
- def calculate_cycle_phase(start_date, end_date):
53
- today = datetime.today().date()
54
- start = datetime.strptime(start_date, "%Y-%m-%d").date()
55
- end = datetime.strptime(end_date, "%Y-%m-%d").date()
56
- cycle_length = 28 # Average cycle
57
- days_since_start = (today - start).days
58
- if days_since_start < 0:
59
- return ":warning: Your entered period start date is in the future."
60
- day_in_cycle = days_since_start % cycle_length
61
- period_length = (end - start).days
62
- if day_in_cycle <= period_length:
63
- return "Menstrual phase :drop_of_blood:"
64
- elif day_in_cycle <= 13:
65
- return "Follicular phase :seedling:"
66
- elif day_in_cycle <= 16:
67
- return "Ovulation phase :dizzy:"
68
- else:
69
- return "Luteal phase :full_moon:"
70
- # Save user info
71
- def save_user_profile(name, age, level, period_start, period_end):
72
- user_profile.update({
73
- "name": name,
74
- "age": age,
75
- "level": level,
76
- "period_start": period_start,
77
- "period_end": period_end
78
- })
79
- phase = calculate_cycle_phase(period_start, period_end)
80
- welcome = f":wave: Hi {name}! Based on your last period, you're currently in the **{phase}**.\n\nHow can I support your wellness today?"
81
- return welcome
82
- # Main chatbot function
83
  def respond(message, history):
84
  if not isinstance(history, list):
85
  history = []
86
- # Build conversation with system + chat history
87
  messages = [{"role": "system", "content": cycle_ai_prompt}]
88
- # Append user profile to context as assistant message
89
- if user_profile:
90
- profile_intro = (
91
- f"User Info:\n"
92
- f"- Name: {user_profile.get('name')}\n"
93
- f"- Age: {user_profile.get('age')}\n"
94
- f"- Experience Level: {user_profile.get('level')}\n"
95
- f"- Period Start: {user_profile.get('period_start')}\n"
96
- f"- Period End: {user_profile.get('period_end')}\n"
97
- f"- Current Phase: {calculate_cycle_phase(user_profile.get('period_start'), user_profile.get('period_end'))}"
98
- )
99
- messages.append({"role": "assistant", "content": profile_intro})
100
- for turn in history:
101
- if isinstance(turn, list) and len(turn) == 2:
102
- messages.append({"role": "user", "content": turn[0]})
103
- messages.append({"role": "assistant", "content": turn[1]})
104
  messages.append({"role": "user", "content": message})
105
- # Get response from model
106
- try:
107
- response = client.chat_completion(messages=messages, max_tokens=500, temperature=0.5)
108
- assistant_reply = response["choices"][0]["message"]["content"].strip()
109
- except Exception as e:
110
- assistant_reply = f":warning: Something went wrong: {e}"
111
- history.append((message, assistant_reply))
112
- return history, ""
113
- # Gradio UI
114
- with gr.Blocks() as app:
115
- gr.Markdown("# 🩷 Menstrual Cycle & Wellness Chatbot")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  with gr.Row():
117
  with gr.Column(scale=1):
118
- gr.Markdown("### :woman_standing: Your Profile")
119
- name = gr.Textbox(label="Name")
120
- age = gr.Textbox(label="Age")
121
- level = gr.Dropdown(["Beginner", "Intermediate", "Expert"], label="Experience Level")
122
- period_start = gr.Textbox(label="Period Start Date (YYYY-MM-DD)")
123
- period_end = gr.Textbox(label="Period End Date (YYYY-MM-DD)")
124
- save_btn = gr.Button("Save Profile")
125
- profile_output = gr.Markdown()
 
126
  with gr.Column(scale=2):
127
- chatbot = gr.Chatbot(label="Cycle Chat").style(height=400)
128
- user_msg = gr.Textbox(placeholder="Ask about your workouts, cycle, birth control, etc…")
129
- send_btn = gr.Button("Send")
130
- save_btn.click(
131
- fn=save_user_profile,
132
- inputs=[name, age, level, period_start, period_end],
133
- outputs=profile_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  )
135
- send_btn.click(
136
- fn=respond,
137
- inputs=[user_msg, chatbot],
138
- outputs=[chatbot, user_msg]
 
139
  )
140
- app.launch()
 
 
 
1
  import gradio as gr
 
2
  from huggingface_hub import InferenceClient
3
  import os
4
+ from sentence_transformers import SentenceTransformer
5
+ import torch
6
+ import numpy as np
7
+ from datetime import datetime, timedelta
8
+
9
+ # Load knowledge base
10
+ with open("knowledge.txt", "r", encoding="utf-8") as f:
11
+ knowledge_text = f.read()
12
+ chunks = [chunk.strip() for chunk in knowledge_text.split("\n\n") if chunk.strip()]
13
+ embedder = SentenceTransformer('all-MiniLM-L6-v2')
14
+ chunk_embeddings = embedder.encode(chunks, convert_to_tensor=True)
15
+
16
+ def get_relevant_context(query, top_k=3):
17
+ query_embedding = embedder.encode(query, convert_to_tensor=True)
18
+ query_embedding = query_embedding / query_embedding.norm()
19
+ norm_chunk_embeddings = chunk_embeddings / chunk_embeddings.norm(dim=1, keepdim=True)
20
+ similarities = torch.matmul(norm_chunk_embeddings, query_embedding)
21
+ top_k_indices = torch.topk(similarities, k=top_k).indices.cpu().numpy()
22
+ context = "\n\n".join([chunks[i] for i in top_k_indices])
23
+ return context
24
+
25
+ client = InferenceClient("google/gemma-2-2b-it")
26
+
27
  cycle_ai_prompt = """
28
  Cycle-Aware Wellness AI Coach (Strict Enforcement Version)
29
  ==========================================================
30
  Mission:
31
  --------
32
  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.
33
+
34
  You may *only* respond to questions related to:
35
  - Menstrual cycles and hormonal phases
36
  - Cycle-based fitness and wellness programming
37
  - Contraceptive methods and reproductive health education
38
  - Hormonal syndromes or life stages (PCOS, PMDD, menopause, irregular cycles, etc.)
39
+
40
+ If the user asks something unrelated (e.g., fixing a car, meal prep, unrelated illnesses, tech support), immediately respond with:
41
  "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!"
42
+
43
  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.
44
+
45
  Style and Voice Guidelines:
46
  ---------------------------
47
  - Speak like a friendly, knowledgeable older sister who’s also a certified personal trainer and women’s health educator.
 
49
  - Validate the user’s experience before offering guidance (e.g., “That makes total sense—your energy might be shifting in this phase”).
50
  - Encourage autonomy by offering options, not orders.
51
  - Connect advice to real-life impact: how the cycle affects energy, mood, and performance.
52
+
53
+ When the user asks about workouts, always ask a follow-up:
54
+ "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!"
55
+
 
 
 
 
 
 
 
 
 
 
56
  Final Boundary Rule:
57
  --------------------
58
  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.
59
  """
60
+
61
+ def determine_cycle_phase(start_date_str):
62
+ try:
63
+ start_date = datetime.strptime(start_date_str, "%Y-%m-%d")
64
+ days_since = (datetime.now() - start_date).days % 28
65
+ if days_since < 5:
66
+ return "Menstrual Phase", "💗 Time to rest and recover. Gentle movement like stretching or walking is great."
67
+ elif days_since < 13:
68
+ return "Follicular Phase", "💪 Your energy’s building—go for strength training or cardio!"
69
+ elif days_since < 16:
70
+ return "Ovulation Phase", "🔥 Peak power! Try high-intensity workouts or social activities."
71
+ else:
72
+ return "Luteal Phase", "🌙 Wind down. Opt for lighter training, yoga, or bodyweight exercises."
73
+ except:
74
+ return "Unknown Phase", "Couldn't parse the date. Please use YYYY-MM-DD."
75
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def respond(message, history):
77
  if not isinstance(history, list):
78
  history = []
79
+
80
  messages = [{"role": "system", "content": cycle_ai_prompt}]
81
+ for entry in history:
82
+ if isinstance(entry, dict):
83
+ messages.append(entry)
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  messages.append({"role": "user", "content": message})
85
+
86
+ response = client.chat_completion(
87
+ messages,
88
+ max_tokens=500,
89
+ temperature=0.1
90
+ )
91
+
92
+ assistant_reply = response['choices'][0]['message']['content'].strip()
93
+ new_history = history + [
94
+ {"role": "user", "content": message},
95
+ {"role": "assistant", "content": assistant_reply}
96
+ ]
97
+ return new_history, new_history, ""
98
+
99
+ def update_chatbot(user_message, history):
100
+ return respond(user_message, history)
101
+
102
+ def set_user_info(name, age, level, period_start_date, period_end_date):
103
+ phase, tip = determine_cycle_phase(period_start_date)
104
+ 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!"
105
+ return name, [{"role": "assistant", "content": greeting}]
106
+
107
+ def button_click(question, history):
108
+ new_history, updated_history, _ = respond(question, history)
109
+ return new_history, updated_history
110
+
111
+ with gr.Blocks(
112
+ css="""
113
+ .gradio-container {
114
+ background: linear-gradient(135deg, #F6D365 0%, #FDA085 50%, #FF8FA0 100%);
115
+ font-family: 'Quicksand', sans-serif;
116
+ }
117
+ .message.user {
118
+ background-color: lightpink;
119
+ border-radius: 20px;
120
+ padding: 10px;
121
+ margin: 5px;
122
+ max-width: 75%;
123
+ align-self: flex-end;
124
+ }
125
+ .message.bot {
126
+ background-color: lightcoral;
127
+ border-radius: 20px;
128
+ padding: 10px;
129
+ margin: 5px;
130
+ max-width: 75%;
131
+ align-self: flex-start;
132
+ }
133
+ .chat-interface {
134
+ background-color: peachpuff;
135
+ border-radius: 12px;
136
+ padding: 15px;
137
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
138
+ }
139
+ """,
140
+ theme=gr.themes.Soft(
141
+ primary_hue="pink",
142
+ secondary_hue="orange",
143
+ neutral_hue="yellow",
144
+ spacing_size="lg",
145
+ radius_size="lg",
146
+ font=[gr.themes.GoogleFont("Quicksand"), "sans-serif"],
147
+ font_mono=[gr.themes.GoogleFont("IBM Plex Mono"), "monospace"]
148
+ )
149
+ ) as demo:
150
+ name_state = gr.State("")
151
+ chat_history = gr.State([])
152
+
153
  with gr.Row():
154
  with gr.Column(scale=1):
155
+ gr.Markdown("### Tell me about yourself")
156
+ name_input = gr.Textbox(label="Name", placeholder="Your name…")
157
+ age_input = gr.Textbox(label="Age", placeholder="Your age…")
158
+ level_input = gr.Dropdown(choices=["Beginner", "Intermediate", "Expert"], label="Training Level")
159
+ period_start_input = gr.Textbox(label="Last Period Start Date", placeholder="YYYY-MM-DD")
160
+ period_end_input = gr.Textbox(label="Last Period End Date", placeholder="YYYY-MM-DD")
161
+ set_btn = gr.Button("Set Info")
162
+ gr.Markdown("_After the greeting appears, start chatting →_")
163
+
164
  with gr.Column(scale=2):
165
+ chatbot = gr.Chatbot(label="Chat", type="messages")
166
+ user_text = gr.Textbox(placeholder="Ask me something…", label="")
167
+ with gr.Row():
168
+ workout_btn = gr.Button("Prompt: Workouts")
169
+ contraceptive_btn = gr.Button("Prompt: Contraceptives")
170
+
171
+ set_btn.click(
172
+ fn=set_user_info,
173
+ inputs=[name_input, age_input, level_input, period_start_input, period_end_input],
174
+ outputs=[name_state, chatbot],
175
+ show_progress=False,
176
+ )
177
+
178
+ user_text.submit(
179
+ fn=update_chatbot,
180
+ inputs=[user_text, chat_history],
181
+ outputs=[chatbot, chat_history, user_text]
182
+ )
183
+
184
+ workout_btn.click(
185
+ fn=lambda history: button_click("What workout routine would you recommend based on my cycle phase?", history),
186
+ inputs=[chat_history],
187
+ outputs=[chatbot, chat_history]
188
  )
189
+
190
+ contraceptive_btn.click(
191
+ fn=lambda history: button_click("Can you explain the different contraceptive options and their benefits?", history),
192
+ inputs=[chat_history],
193
+ outputs=[chatbot, chat_history]
194
  )
195
+
196
+ if __name__ == '__main__':
197
+ demo.launch()