Spaces:
Sleeping
Sleeping
| 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) |