og-arin commited on
Commit
3baf452
Β·
verified Β·
1 Parent(s): 75504d3

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +318 -0
app.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FitByte β€” Personalized AI Fitness Coach
3
+ ========================================
4
+ Tech Stack : Groq API (LLaMA 3.3 70B) | Gradio | Hugging Face Spaces
5
+ Concepts : System Prompt Engineering, Prompt Chaining, Dynamic Prompts,
6
+ BMI Calculation, Input Validation, Secure API Key Handling
7
+ """
8
+
9
+ import os
10
+ import gradio as gr
11
+ from groq import Groq
12
+
13
+ # ─────────────────────────────────────────────
14
+ # API SETUP (Colab β†’ HF Spaces compatible)
15
+ # ─────────────────────────────────────────────
16
+ def get_api_key() -> str:
17
+ try:
18
+ from google.colab import userdata
19
+ return userdata.get("GROQ_API_KEY")
20
+ except Exception:
21
+ key = os.getenv("GROQ_API_KEY")
22
+ if not key:
23
+ raise EnvironmentError("GROQ_API_KEY not found in environment.")
24
+ return key
25
+
26
+ try:
27
+ client = Groq(api_key=get_api_key())
28
+ API_READY = True
29
+ except Exception as e:
30
+ API_READY = False
31
+ API_ERROR = str(e)
32
+
33
+
34
+ # ─────────────────────────────────────────────
35
+ # BMI CALCULATION
36
+ # ─────────────────────────────────────────────
37
+ def calculate_bmi(weight_kg: float, height_cm: float) -> tuple:
38
+ if height_cm <= 0 or weight_kg <= 0:
39
+ raise ValueError("Weight and height must be positive numbers.")
40
+ height_m = height_cm / 100
41
+ bmi = round(weight_kg / (height_m ** 2), 1)
42
+ if bmi < 18.5:
43
+ category = "Underweight"
44
+ elif bmi < 25.0:
45
+ category = "Normal weight"
46
+ elif bmi < 30.0:
47
+ category = "Overweight"
48
+ else:
49
+ category = "Obese"
50
+ return bmi, category
51
+
52
+
53
+ # ─────────────────────────────────────────────
54
+ # PROMPT ENGINEERING
55
+ # ─────────────────────────────────────────────
56
+ COACHING_MODES = {
57
+ "Motivational Coach πŸ”₯": (
58
+ "You are an energetic, motivational fitness coach. "
59
+ "Use powerful, encouraging language. Include motivational quotes. "
60
+ "Make the user feel unstoppable. Use emojis sparingly but effectively."
61
+ ),
62
+ "Scientific Advisor πŸ”¬": (
63
+ "You are a sports scientist and certified nutritionist. "
64
+ "Use precise, evidence-based language. Cite physiological principles "
65
+ "(e.g., progressive overload, TDEE, macronutrient ratios). "
66
+ "Be clinical but clear. No fluff."
67
+ ),
68
+ "Friendly Buddy 😊": (
69
+ "You are the user's supportive gym buddy. "
70
+ "Keep the tone casual, warm, and relatable. "
71
+ "Use simple language. Make fitness feel approachable and fun."
72
+ ),
73
+ "Strict Drill Sergeant πŸ’ͺ": (
74
+ "You are a no-nonsense military fitness trainer. "
75
+ "Be direct, demanding, and results-focused. "
76
+ "No excuses. Short, punchy sentences. Push the user hard."
77
+ ),
78
+ }
79
+
80
+ def build_system_prompt(mode: str) -> str:
81
+ base = COACHING_MODES.get(mode, COACHING_MODES["Motivational Coach πŸ”₯"])
82
+ return (
83
+ f"{base}\n\n"
84
+ "Always structure your response with these clearly labeled sections:\n"
85
+ "1. **BMI Analysis** β€” Interpret their BMI and what it means for them.\n"
86
+ "2. **Weekly Workout Plan** β€” Plan with specific exercises, sets, reps.\n"
87
+ "3. **Daily Meal Plan** β€” Breakfast, Lunch, Dinner, Snacks with approximate calories.\n"
88
+ "4. **Key Tips** β€” 3 personalized tips based on their goal and fitness level.\n"
89
+ "5. **Motivational Closing** β€” End with an inspiring one-liner.\n\n"
90
+ "Use markdown formatting with bold headers. Be specific, not generic.\n\n"
91
+ "IMPORTANT: Build the meal plan strictly around the user's available foods and cuisine. "
92
+ "Do NOT suggest foods they cannot access or afford. Use locally available, budget-friendly alternatives."
93
+ )
94
+
95
+ def build_user_prompt(
96
+ name, age, gender, body_condition,
97
+ weight, height, bmi, bmi_cat,
98
+ goal, fitness_level, dietary_pref,
99
+ cuisine_pref, food_context,
100
+ workout_days, workout_duration,
101
+ health_conditions
102
+ ) -> str:
103
+ conditions_str = health_conditions.strip() if health_conditions else "None reported"
104
+ food_str = food_context.strip() if food_context else "No specific constraints"
105
+ return (
106
+ f"Create a fully personalized fitness plan for the following individual:\n\n"
107
+ f"**Personal Details:**\n"
108
+ f"- Name: {name}\n"
109
+ f"- Age: {age} years | Gender: {gender}\n"
110
+ f"- Weight: {weight} kg | Height: {height} cm\n"
111
+ f"- BMI: {bmi} ({bmi_cat}) | Body Condition: {body_condition}\n\n"
112
+ f"**Goals & Preferences:**\n"
113
+ f"- Primary Goal: {goal}\n"
114
+ f"- Current Fitness Level: {fitness_level}\n"
115
+ f"- Dietary Type: {dietary_pref}\n"
116
+ f"- Cuisine / Food Region: {cuisine_pref}\n"
117
+ f"- Food Availability & Budget: {food_str}\n"
118
+ f"- Workout Days per Week: {workout_days}\n"
119
+ f"- Workout Duration per Day: {workout_duration} minutes\n"
120
+ f"- Health Conditions / Injuries: {conditions_str}\n\n"
121
+ f"Generate a realistic, safe, and highly personalized plan. "
122
+ f"Account for their BMI category, body condition, and health conditions "
123
+ f"when recommending exercises and diet."
124
+ )
125
+
126
+
127
+ # ─────────────────────────────────────────────
128
+ # GROQ API CALL
129
+ # ─────────────────────────────────────────────
130
+ def call_groq(system_prompt: str, user_prompt: str) -> str:
131
+ response = client.chat.completions.create(
132
+ model="llama-3.3-70b-versatile",
133
+ messages=[
134
+ {"role": "system", "content": system_prompt},
135
+ {"role": "user", "content": user_prompt}
136
+ ],
137
+ max_tokens=2048
138
+ )
139
+ return response.choices[0].message.content
140
+
141
+
142
+ # ─────────────────────────────────────────────
143
+ # MAIN ORCHESTRATION
144
+ # ─────────────────────────────────────────────
145
+ def generate_fitness_plan(
146
+ name, age, gender, body_condition,
147
+ weight, height,
148
+ goal, fitness_level, dietary_pref,
149
+ cuisine_pref, food_context,
150
+ workout_days, workout_duration,
151
+ health_conditions, coaching_mode
152
+ ):
153
+ if not name.strip():
154
+ return "❌ Error", "Please enter your name.", ""
155
+ if not (10 <= int(age) <= 100):
156
+ return "❌ Error", "Age must be between 10 and 100.", ""
157
+ if weight <= 0 or height <= 0:
158
+ return "❌ Error", "Weight and height must be positive values.", ""
159
+ if not API_READY:
160
+ return "❌ API Error", f"Groq API not configured: {API_ERROR}", ""
161
+
162
+ try:
163
+ bmi, bmi_cat = calculate_bmi(weight, height)
164
+ bmi_display = f"**BMI: {bmi}** β€” {bmi_cat}"
165
+
166
+ system_prompt = build_system_prompt(coaching_mode)
167
+ user_prompt = build_user_prompt(
168
+ name, age, gender, body_condition,
169
+ weight, height, bmi, bmi_cat,
170
+ goal, fitness_level, dietary_pref,
171
+ cuisine_pref, food_context,
172
+ int(workout_days), int(workout_duration),
173
+ health_conditions
174
+ )
175
+
176
+ plan = call_groq(system_prompt, user_prompt)
177
+
178
+ download_text = (
179
+ f"FITBYTE β€” PERSONALIZED FITNESS PLAN\n"
180
+ f"Name: {name}\n"
181
+ f"{'='*50}\n\n"
182
+ f"BMI: {bmi} ({bmi_cat})\n"
183
+ f"Goal: {goal} | Level: {fitness_level} | Mode: {coaching_mode}\n"
184
+ f"{'='*50}\n\n"
185
+ + plan
186
+ )
187
+
188
+ return bmi_display, plan, download_text
189
+
190
+ except ValueError as ve:
191
+ return "❌ Input Error", str(ve), ""
192
+ except Exception as e:
193
+ return "❌ Error", f"Something went wrong: {str(e)}", ""
194
+
195
+
196
+ def save_plan(download_text: str):
197
+ if not download_text:
198
+ return None
199
+ filepath = "/tmp/fitbyte_plan.txt"
200
+ with open(filepath, "w") as f:
201
+ f.write(download_text)
202
+ return filepath
203
+
204
+
205
+ # ─────────────────────────────────────────────
206
+ # GRADIO UI
207
+ # ─────────────────────────────────────────────
208
+ CSS = """
209
+ footer { display: none !important; }
210
+ """
211
+
212
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="emerald"), css=CSS, title="FitByte") as demo:
213
+
214
+ gr.Markdown("""
215
+ # πŸ‹οΈ FitByte β€” Personalized AI Fitness Coach
216
+ > **Powered by Groq (LLaMA 3.3 70B)** | Built with Gradio | Deployed on Hugging Face Spaces
217
+ """)
218
+
219
+ with gr.Row():
220
+
221
+ with gr.Column(scale=1):
222
+ gr.Markdown("### πŸ‘€ Personal Info")
223
+ name = gr.Textbox(label="Full Name", placeholder="e.g. Rohit Sharma")
224
+ age = gr.Number(label="Age", value=21, minimum=10, maximum=100)
225
+ gender = gr.Radio(["Male", "Female", "Other"], label="Gender", value="Male")
226
+ body_condition = gr.Dropdown(
227
+ ["Not Sure", "Skinny", "Skinny-Fat", "Average", "Overweight/Fat", "Muscular"],
228
+ label="Current Body Condition", value="Average"
229
+ )
230
+
231
+ gr.Markdown("### πŸ“ Body Metrics")
232
+ weight = gr.Number(label="Weight (kg)", value=70)
233
+ height = gr.Number(label="Height (cm)", value=175)
234
+
235
+ gr.Markdown("### 🎯 Fitness Profile")
236
+ goal = gr.Dropdown(
237
+ ["Weight Loss", "Muscle Gain", "Maintain Weight", "Improve Stamina", "Flexibility & Mobility"],
238
+ label="Primary Goal", value="Muscle Gain"
239
+ )
240
+ fitness_level = gr.Dropdown(
241
+ ["Beginner (0–6 months)", "Intermediate (6 months–2 years)", "Advanced (2+ years)"],
242
+ label="Fitness Level", value="Beginner (0–6 months)"
243
+ )
244
+ dietary_pref = gr.Dropdown(
245
+ ["No Preference", "Vegetarian", "Eggetarian", "Vegan", "Non-Vegetarian", "Keto", "High Protein"],
246
+ label="Dietary Preference", value="No Preference"
247
+ )
248
+ workout_days = gr.Slider(minimum=2, maximum=7, step=1, value=4, label="Workout Days per Week")
249
+ workout_duration = gr.Slider(minimum=20, maximum=120, step=10, value=45, label="Workout Duration per Day (minutes)")
250
+
251
+ gr.Markdown("### 🍽️ Food & Cuisine")
252
+ cuisine_pref = gr.Dropdown(
253
+ ["No Preference", "Indian", "South Indian", "Middle Eastern", "East Asian", "Mediterranean", "Western"],
254
+ label="Cuisine / Region", value="No Preference"
255
+ )
256
+ food_context = gr.Textbox(
257
+ label="Food Availability & Budget (optional)",
258
+ placeholder="e.g. I eat chapati, dal, sabzi daily. No bread or pasta. Tight budget.",
259
+ lines=2
260
+ )
261
+
262
+ gr.Markdown("### βš•οΈ Health")
263
+ health_conditions = gr.Textbox(
264
+ label="Health Conditions / Injuries (optional)",
265
+ placeholder="e.g. Lower back pain, knee injury..."
266
+ )
267
+
268
+ gr.Markdown("### πŸ€– Coaching Style")
269
+ coaching_mode = gr.Radio(
270
+ list(COACHING_MODES.keys()),
271
+ label="Select Your Coach",
272
+ value="Motivational Coach πŸ”₯"
273
+ )
274
+
275
+ generate_btn = gr.Button("⚑ Generate My Plan", variant="primary", size="lg")
276
+
277
+ with gr.Column(scale=2):
278
+ gr.Markdown("### πŸ“Š Your BMI")
279
+ bmi_output = gr.Markdown(value="_Your BMI will appear here._")
280
+
281
+ gr.Markdown("### πŸ“‹ Your Personalized Plan")
282
+ plan_output = gr.Markdown(value="_Fill in your details and hit Generate._", height=600)
283
+
284
+ with gr.Row():
285
+ download_state = gr.State("")
286
+ download_btn = gr.Button("πŸ’Ύ Download My Plan", variant="secondary")
287
+ download_file = gr.File(label="Your Plan (TXT)", visible=False)
288
+
289
+ gr.Markdown("""
290
+ ---
291
+ **πŸ“Œ Disclaimer:** AI-generated for educational purposes. Consult a certified trainer and nutritionist before starting any fitness program.
292
+ """)
293
+
294
+ generate_btn.click(
295
+ fn=generate_fitness_plan,
296
+ inputs=[
297
+ name, age, gender, body_condition,
298
+ weight, height,
299
+ goal, fitness_level, dietary_pref,
300
+ cuisine_pref, food_context,
301
+ workout_days, workout_duration,
302
+ health_conditions, coaching_mode
303
+ ],
304
+ outputs=[bmi_output, plan_output, download_state]
305
+ )
306
+
307
+ download_btn.click(
308
+ fn=save_plan,
309
+ inputs=[download_state],
310
+ outputs=[download_file]
311
+ ).then(
312
+ fn=lambda: gr.File(visible=True),
313
+ outputs=[download_file]
314
+ )
315
+
316
+
317
+ if __name__ == "__main__":
318
+ demo.launch()