FitPlan_AI22 / app.py
Rajaluxanaa's picture
Update app.py
5b377cf verified
import streamlit as st
import pandas as pd
from datetime import datetime
from model_api import query_model
from prompt_builder import build_prompt
import time
# Page configuration
st.set_page_config(
page_title="FitGenius AI - Personalized Workout Planner",
page_icon="πŸ‹οΈ",
layout="centered",
initial_sidebar_state="auto"
)
# Simplified Custom CSS with Shorthand
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* { font-family: 'Inter', sans-serif; }
.stApp {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.main {
padding: 2rem 1rem;
background: rgba(255,255,255,0.95);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
margin: 1rem auto;
max-width: 1200px;
}
/* Header */
.header-container {
text-align: center;
padding: 2rem 1rem;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 15px;
margin-bottom: 2rem;
color: white;
animation: fadeInDown 0.8s ease-out;
}
.header-container h1 {
font-size: clamp(1.8rem, 5vw, 2.5rem);
font-weight: 700;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
/* Animations */
@keyframes fadeInDown {
from { opacity: 0; transform: translateY(-30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Form Elements */
.form-container, .profile-card {
background: white;
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
margin: 1rem 0;
animation: fadeInUp 0.6s ease-out;
}
/* Input Styling - Shorthand */
.stTextInput input, .stNumberInput input, .stSelectbox select {
border-radius: 10px !important;
border: 2px solid #e0e0e0 !important;
padding: 0.75rem !important;
transition: all 0.3s ease !important;
}
.stTextInput input:focus, .stNumberInput input:focus, .stSelectbox select:focus {
border-color: #667eea !important;
box-shadow: 0 0 0 3px rgba(102,126,234,0.1) !important;
}
/* Button Styling - Shorthand */
.stButton button {
background: linear-gradient(135deg, #667eea, #764ba2) !important;
color: white !important;
font-weight: 600 !important;
padding: 0.75rem 2rem !important;
border-radius: 10px !important;
border: none !important;
transition: all 0.3s ease !important;
text-transform: uppercase !important;
letter-spacing: 1px !important;
box-shadow: 0 4px 15px rgba(102,126,234,0.4) !important;
}
.stButton button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102,126,234,0.6) !important;
}
/* Progress Steps - Simplified */
.progress-steps {
display: flex;
justify-content: space-between;
margin: 2rem 0;
padding: 0 1rem;
}
.step {
flex: 1;
text-align: center;
position: relative;
}
.step:not(:last-child):after {
content: '';
position: absolute;
top: 25px;
right: -50%;
width: 100%;
height: 3px;
background: #e0e0e0;
z-index: 0;
}
.step.completed:not(:last-child):after {
background: linear-gradient(90deg, #667eea, #764ba2);
}
.step-circle {
width: 50px;
height: 50px;
background: white;
border: 3px solid #e0e0e0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 0.5rem;
font-weight: 700;
color: #999;
position: relative;
z-index: 1;
transition: all 0.3s ease;
font-size: 1.2rem;
}
.step.active .step-circle {
background: linear-gradient(135deg, #667eea, #764ba2);
border-color: transparent;
color: white;
transform: scale(1.1);
box-shadow: 0 0 20px rgba(102,126,234,0.5);
}
.step.completed .step-circle {
background: #2ecc71;
border-color: #27ae60;
color: white;
}
.step-label { font-size: 0.9rem; color: #666; }
.step.active .step-label { color: #667eea; font-weight: 700; }
.step.completed .step-label { color: #27ae60; }
/* BMI Card */
.bmi-card {
background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
padding: 2rem;
border-radius: 15px;
text-align: center;
margin: 1rem 0;
animation: fadeInUp 0.8s ease-out;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.bmi-card h3 {
color: #333;
font-size: 1.2rem;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.bmi-value {
font-size: 4rem !important;
font-weight: 800 !important;
margin: 0.5rem 0 !important;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
line-height: 1.2;
}
.bmi-status {
font-size: 1.5rem !important;
font-weight: 600 !important;
padding: 0.5rem 1.5rem !important;
border-radius: 50px !important;
display: inline-block !important;
margin-top: 0.5rem !important;
}
/* BMI Categories - Shorthand */
.category-underweight { background: linear-gradient(135deg, #3498db, #2980b9); color: white; }
.category-normal { background: linear-gradient(135deg, #2ecc71, #27ae60); color: white; }
.category-overweight { background: linear-gradient(135deg, #f39c12, #e67e22); color: white; }
.category-obese { background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; }
/* Metric Cards - Shorthand */
.metric-card {
background: white;
padding: 1.5rem;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
text-align: center;
transition: all 0.3s ease;
border: 1px solid #f0f0f0;
}
.metric-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0,0,0,0.1); }
.metric-icon { font-size: 2rem; margin-bottom: 0.5rem; }
.metric-label { color: #666; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 1px; }
.metric-value { color: #333; font-size: 1.5rem; font-weight: 700; }
/* Workout Plan */
.workout-plan {
background: white;
padding: 2.5rem;
border-radius: 15px;
margin: 1.5rem 0;
box-shadow: 0 15px 40px rgba(0,0,0,0.1);
border-left: 5px solid #667eea;
animation: fadeInUp 0.8s ease-out;
line-height: 1.8;
}
/* Step Instructions */
.step-instructions {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 1rem 1.5rem;
border-radius: 8px;
margin: 1rem 0;
color: #333;
}
.step-instructions ol { margin: 0.5rem 0; padding-left: 1.5rem; }
.step-instructions li { margin: 0.3rem 0; }
/* Success Message */
.success-message {
background: linear-gradient(135deg, #2ecc71, #27ae60);
color: white;
padding: 1rem;
border-radius: 10px;
text-align: center;
animation: slideIn 0.5s ease-out;
margin: 1rem 0;
}
@keyframes slideIn {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Mobile Responsive - Shorthand */
@media (max-width: 768px) {
.main { padding: 1rem; margin: 0.5rem; }
.bmi-value { font-size: 3rem !important; }
.bmi-status { font-size: 1.2rem !important; }
.step-circle { width: 40px; height: 40px; font-size: 1rem; }
.step-label { font-size: 0.8rem; }
.workout-plan { padding: 1.5rem; }
}
/* Utility Classes */
.text-center { text-align: center; }
.mt-2 { margin-top: 2rem; }
.mb-2 { margin-bottom: 2rem; }
.p-2 { padding: 2rem; }
/* Tab Styling */
.stTabs [data-baseweb="tab-list"] { gap: 2rem; padding: 0.5rem; }
.stTabs [data-baseweb="tab"] {
padding: 0.5rem 1.5rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.stTabs [aria-selected="true"] {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
</style>
""", unsafe_allow_html=True)
# Initialize session state
if 'page' not in st.session_state:
st.session_state.page = 1
st.session_state.profile_created = False
st.session_state.profile_data = {}
st.session_state.workout_plan = None
# Header
st.markdown("""
<div class="header-container">
<h1>πŸ‹οΈ FitGenius AI</h1>
<p>Your Personal AI-Powered Fitness Assistant</p>
</div>
""", unsafe_allow_html=True)
# Progress Steps
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
step1_class = 'active' if st.session_state.page == 1 else ('completed' if st.session_state.profile_created else '')
step2_class = 'active' if st.session_state.page == 2 else ('completed' if st.session_state.page > 2 else '')
st.markdown(f"""
<div class="progress-steps">
<div class="step {step1_class}"><div class="step-circle">1</div><div class="step-label">Profile</div></div>
<div class="step {step2_class}"><div class="step-circle">2</div><div class="step-label">Plan</div></div>
</div>
""", unsafe_allow_html=True)
# Navigation functions
def next_page(): st.session_state.page = 2; st.rerun()
def prev_page(): st.session_state.page = 1; st.rerun()
# Page 1: Profile Form
if st.session_state.page == 1:
st.markdown("""
<div class="step-instructions">
<strong>πŸ“ Step 1: Complete Your Profile</strong>
<ol><li>Fill in your details</li><li>Select goals & equipment</li><li>Generate plan</li></ol>
</div>
""", unsafe_allow_html=True)
def validate_inputs(name, age, height, weight, gender):
errors = []
if not name or not name.strip(): errors.append("❌ Name required")
if not gender: errors.append("❌ Gender required")
if age is None or age <= 0 or age > 120: errors.append("❌ Valid age required (1-120)")
elif age < 18: errors.append("❌ Must be 18+")
if height is None or height <= 0: errors.append("❌ Valid height required")
if weight is None or weight <= 0: errors.append("❌ Valid weight required")
return errors
with st.form(key=f"fitness_form_{st.session_state.page}"):
st.markdown('<div class="form-container">', unsafe_allow_html=True)
st.markdown('### πŸ“‹ Personal Info')
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Full Name *", placeholder="John Doe")
age = st.number_input("Age *", 1, 120, None, placeholder="25", step=1)
weight = st.number_input("Weight (kg) *", 0.1, 500.0, None, placeholder="70", step=0.1)
with col2:
height = st.number_input("Height (cm) *", 1.0, 300.0, None, placeholder="175", step=0.1)
gender = st.selectbox("Gender *", ["Male", "Female", "Other"])
st.markdown('### πŸ’ͺ Fitness Details')
col3, col4 = st.columns(2)
with col3:
fitness_goal = st.selectbox("Goal *", ["Build Muscle", "Weight Loss", "Strength Gain", "Abs Building", "Flexible"])
fitness_level = st.radio("Level *", ["Beginner", "Intermediate", "Advanced"], horizontal=True)
with col4:
equipment = st.multiselect("Equipment *",
["Dumbbells", "Resistance Band", "Yoga Mat", "Kettlebells", "Barbell", "Treadmill", "Exercise Bike", "No Equipment"])
if st.form_submit_button("🎯 Generate My AI Workout Plan", use_container_width=True):
errors = validate_inputs(name, age, height, weight, gender)
if not equipment: errors.append("❌ Select equipment")
if errors:
for e in errors: st.error(e)
else:
with st.spinner("πŸ€– Creating your plan..."):
try:
for i in range(100): time.sleep(0.01); st.progress(i + 1)
prompt, bmi, status = build_prompt(name, age, gender, height, weight, fitness_goal, fitness_level, equipment)
st.session_state.workout_plan = query_model(prompt)
st.session_state.profile_data = {
'name': name, 'age': age, 'gender': gender, 'height_cm': height,
'weight': weight, 'bmi': bmi, 'bmi_status': status,
'fitness_goal': fitness_goal, 'fitness_level': fitness_level,
'equipment': equipment, 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M")
}
st.session_state.profile_created = True
time.sleep(0.5); next_page()
except Exception as e: st.error(f"⚠️ Error: {str(e)}")
# Page 2: Results
elif st.session_state.page == 2 and st.session_state.profile_created:
data = st.session_state.profile_data
st.markdown(f"""
<div class="step-instructions"><strong>πŸŽ‰ Step 2: Your Plan</strong> - Welcome, {data['name']}!</div>
<div class="success-message">βœ… Your personalized fitness plan is ready!</div>
""", unsafe_allow_html=True)
# BMI Card
st.markdown(f"""
<div class="bmi-card">
<h3>πŸ“Š BMI</h3>
<div class="bmi-value">{data['bmi']:.1f}</div>
<div class="bmi-status category-{data['bmi_status'].lower().replace(' ', '')}">{data['bmi_status']}</div>
</div>
""", unsafe_allow_html=True)
# Metrics
cols = st.columns(4)
metrics = [("πŸ“", "Height", f"{data['height_cm']} cm"), ("βš–οΈ", "Weight", f"{data['weight']} kg"),
("🎯", "Goal", data['fitness_goal']), ("πŸ“Š", "Level", data['fitness_level'])]
for col, (icon, label, value) in zip(cols, metrics):
with col: st.markdown(f'<div class="metric-card"><div class="metric-icon">{icon}</div><div class="metric-label">{label}</div><div class="metric-value">{value}</div></div>', unsafe_allow_html=True)
# Workout Plan
st.markdown('### πŸ€– AI-Generated Workout Plan')
tab1, tab2 = st.tabs(["πŸ“‹ Formatted", "πŸ“ Raw"])
with tab1:
st.markdown(f'<div class="workout-plan">{st.session_state.workout_plan}</div>', unsafe_allow_html=True)
c1, c2 = st.columns(2)
with c1:
st.download_button("πŸ“₯ Download Plan", st.session_state.workout_plan,
f"workout_{data['name'].replace(' ', '_')}.txt", use_container_width=True)
with c2:
if st.button("πŸ”„ New Plan", use_container_width=True):
with st.spinner("πŸ€– Generating..."):
p,_,_ = build_prompt(data['name'], data['age'], data['gender'], data['height_cm'],
data['weight'], data['fitness_goal'], data['fitness_level'], data['equipment'])
st.session_state.workout_plan = query_model(p); st.rerun()
with tab2:
st.text_area("Raw text:", st.session_state.workout_plan, height=400)
# Actions
st.markdown("---")
c1, c2, c3 = st.columns(3)
with c1: st.button("β—€ Back", on_click=prev_page, use_container_width=True)
with c2:
if st.button("πŸ†• New Profile", use_container_width=True):
for k in ['profile_created', 'profile_data', 'workout_plan']:
st.session_state[k] = {} if k == 'profile_data' else (False if k == 'profile_created' else None)
st.session_state.page = 1; st.rerun()
with c3:
report = f"""FITNESS REPORT
Generated: {data['timestamp']}
Name: {data['name']} | Age: {data['age']} | Gender: {data['gender']}
Height: {data['height_cm']}cm | Weight: {data['weight']}kg | BMI: {data['bmi']:.1f} ({data['bmi_status']})
Goal: {data['fitness_goal']} | Level: {data['fitness_level']} | Equipment: {', '.join(data['equipment'])}
WORKOUT PLAN:
{st.session_state.workout_plan}"""
st.download_button("πŸ“₯ Export", report, f"report_{data['name'].replace(' ', '_')}.txt", use_container_width=True)
# Sidebar
with st.sidebar:
st.markdown("""
<div style="background: white; padding: 2rem; border-radius: 15px; text-align: center;">
<img src="https://cdn-icons-png.flaticon.com/512/3043/3043650.png" width="80">
<h3 style="color: #333;">FitGenius AI</h3>
</div>
<div style="background: white; padding: 1.5rem; border-radius: 15px; margin: 1rem 0;">
<h4>🎯 Steps:</h4>
<ol style="color: #666;">
<li>Fill profile</li>
<li>Set goals</li>
<li>Get AI plan</li>
<li>Start training</li>
</ol>
</div>
<div style="background: white; padding: 1.5rem; border-radius: 15px;">
<h4>πŸ“Š BMI:</h4>
<p><span style="color:#3498db;">●</span> Underweight: <18.5</p>
<p><span style="color:#2ecc71;">●</span> Normal: 18.5-24.9</p>
<p><span style="color:#f39c12;">●</span> Overweight: 25-29.9</p>
<p><span style="color:#e74c3c;">●</span> Obese: β‰₯30</p>
</div>
<div style="background: #fff3cd; padding: 1rem; border-radius: 15px; margin: 1rem 0;">
<p style="color:#856404; font-size:0.9rem; margin:0;">⚠️ Consult a professional before starting</p>
</div>
""", unsafe_allow_html=True)
# Footer
st.markdown("""
<div style="text-align: center; padding: 2rem; color: white;">
<p>πŸ‹οΈ FitGenius AI - Your Personal Trainer</p>
<p style="font-size:0.8rem; opacity:0.8;">Β© 2024 All rights reserved</p>
</div>
""", unsafe_allow_html=True)