import os import streamlit as st import pandas as pd import plotly.express as px import plotly.graph_objects as go from stravalib.client import Client import google.generativeai as genai from dotenv import load_dotenv from datetime import datetime, timedelta # Load environment variables load_dotenv() # API Keys and Secrets STRAVA_CLIENT_ID = os.getenv('STRAVA_CLIENT_ID') STRAVA_CLIENT_SECRET = os.getenv('STRAVA_CLIENT_SECRET') GEMINI_API_KEY = os.getenv('GEMINI_API_KEY') # Strava API setup client = Client() # Gemini API setup genai.configure(api_key=GEMINI_API_KEY) model = genai.GenerativeModel('gemini-pro') # Streamlit app st.set_page_config(page_title="Strava Run Analysis", layout="wide") # Custom CSS for better UI st.markdown(""" """, unsafe_allow_html=True) st.title("🏃♂️ Strava Run Analysis") # Strava manual authentication if 'access_token' not in st.session_state: st.write("To use this app, you need to authorize it with Strava. Follow these steps:") st.write("1. Click the button below to go to Strava's authorization page:") auth_url = f"https://www.strava.com/oauth/authorize?client_id={STRAVA_CLIENT_ID}&response_type=code&redirect_uri=http://localhost&approval_prompt=force&scope=read_all,profile:read_all,activity:read_all" st.markdown(f"", unsafe_allow_html=True) st.write("2. Log in to Strava if needed and click 'Authorize'") st.write("3. After authorizing, you'll be redirected to a page that may show an error. This is expected!") st.write("4. Copy the 'code' parameter from the URL of that page.") st.write("5. Paste that code below:") auth_code = st.text_input("Paste the authorization code here:") if auth_code: try: token_response = client.exchange_code_for_token( client_id=STRAVA_CLIENT_ID, client_secret=STRAVA_CLIENT_SECRET, code=auth_code ) st.session_state.access_token = token_response['access_token'] st.success("Authorization successful! Refreshing the app...") st.rerun() except Exception as e: st.error(f"An error occurred: {str(e)}. Please try authorizing again.") if 'access_token' in st.session_state: client.access_token = st.session_state.access_token try: athlete = client.get_athlete() st.write(f"Welcome, {athlete.firstname} {athlete.lastname}! 👋") except Exception as e: st.error(f"Error fetching athlete data: {str(e)}. Please try reauthorizing.") if st.button("Reauthorize"): del st.session_state.access_token st.rerun() # Fetch running activities @st.cache_data(ttl=3600) def fetch_run_activities(): activities = list(client.get_activities(limit=200)) runs = [] for activity in activities: if activity.type == 'Run': run_data = { 'name': activity.name, 'distance': float(activity.distance.num) / 1000 if activity.distance else None, # Convert to km 'moving_time': None, 'total_elevation_gain': float(activity.total_elevation_gain) if activity.total_elevation_gain else None, 'average_speed': float(activity.average_speed) * 3.6 if activity.average_speed else None, # Convert to km/h 'average_heartrate': float(activity.average_heartrate) if activity.average_heartrate else None, 'start_date': activity.start_date.replace(tzinfo=None) if activity.start_date else None } # Safely handle moving_time try: if activity.moving_time: run_data['moving_time'] = activity.moving_time.total_seconds() / 60 # Convert to minutes except AttributeError: pass # If moving_time is not accessible, leave it as None runs.append(run_data) df = pd.DataFrame(runs) # Calculate pace only if both moving_time and distance are available df['pace'] = df.apply(lambda row: row['moving_time'] / row['distance'] if row['moving_time'] and row['distance'] else None, axis=1) return df try: df = fetch_run_activities() except Exception as e: st.error(f"Error fetching activities: {str(e)}. Please try again later.") st.stop() # Basic stats st.markdown("
Total Runs
Total Distance
Total Time
Total Elevation Gain