Spaces:
Runtime error
Runtime error
Upload 12 files
Browse files- SMART_DEPLOYMENT.md +101 -0
- app.py +5 -1
- athletic_performance.py +68 -29
SMART_DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧠 Smart Deployment System
|
| 2 |
+
|
| 3 |
+
## 🎯 How It Works
|
| 4 |
+
|
| 5 |
+
Your `app.py` now automatically detects what to deploy:
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
📁 Repository
|
| 9 |
+
├── app.py ← Smart entry point
|
| 10 |
+
├── flask_api.py ← Flask routes
|
| 11 |
+
├── athletic_performance.py ← Core logic
|
| 12 |
+
├── requirements.txt ← Both Gradio + Flask deps
|
| 13 |
+
└── Dockerfile (optional) ← Controls deployment mode
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
## 🔄 Deployment Modes
|
| 17 |
+
|
| 18 |
+
### 🔌 **Flask API Mode**
|
| 19 |
+
**Triggered when:**
|
| 20 |
+
- ✅ Dockerfile exists in repo
|
| 21 |
+
- ✅ Running in Docker container
|
| 22 |
+
- ✅ `DEPLOYMENT_MODE=flask` env var
|
| 23 |
+
|
| 24 |
+
**What you get:**
|
| 25 |
+
- Clean REST API endpoints
|
| 26 |
+
- `/ai-coaching` with favorite sports
|
| 27 |
+
- JSON responses with percentiles + tips
|
| 28 |
+
- No eventId complexity
|
| 29 |
+
|
| 30 |
+
### 🎨 **Gradio UI Mode**
|
| 31 |
+
**Triggered when:**
|
| 32 |
+
- ❌ No Dockerfile in repo
|
| 33 |
+
- ✅ `DEPLOYMENT_MODE=gradio` env var
|
| 34 |
+
- ✅ Default fallback mode
|
| 35 |
+
|
| 36 |
+
**What you get:**
|
| 37 |
+
- Interactive web interface
|
| 38 |
+
- Sports selection checkboxes
|
| 39 |
+
- Built-in API via Gradio
|
| 40 |
+
- User-friendly forms
|
| 41 |
+
|
| 42 |
+
## 🚀 Usage Examples
|
| 43 |
+
|
| 44 |
+
### **Deploy Flask API:**
|
| 45 |
+
1. Keep `Dockerfile` in your repo
|
| 46 |
+
2. Push to HuggingFace Spaces
|
| 47 |
+
3. Choose Docker SDK
|
| 48 |
+
4. → Automatically runs Flask API! 🔌
|
| 49 |
+
|
| 50 |
+
### **Deploy Gradio UI:**
|
| 51 |
+
1. Remove/rename `Dockerfile`
|
| 52 |
+
2. Push to HuggingFace Spaces
|
| 53 |
+
3. Choose Gradio SDK
|
| 54 |
+
4. → Automatically runs Gradio UI! 🎨
|
| 55 |
+
|
| 56 |
+
### **Switch Modes:**
|
| 57 |
+
- **Want Flask?** → Add `Dockerfile` back
|
| 58 |
+
- **Want Gradio?** → Remove `Dockerfile`
|
| 59 |
+
- **Force mode?** → Set `DEPLOYMENT_MODE` env var
|
| 60 |
+
|
| 61 |
+
## 🎯 Benefits
|
| 62 |
+
|
| 63 |
+
✅ **One Codebase** - Same repo for both deployments
|
| 64 |
+
✅ **Auto-Detection** - No manual configuration
|
| 65 |
+
✅ **Easy Switching** - Just add/remove Dockerfile
|
| 66 |
+
✅ **Same Features** - Favorite sports work in both modes
|
| 67 |
+
✅ **Flexible** - Override with environment variables
|
| 68 |
+
|
| 69 |
+
## 📋 HuggingFace Spaces Setup
|
| 70 |
+
|
| 71 |
+
### For Flask API:
|
| 72 |
+
```
|
| 73 |
+
1. Create new Space
|
| 74 |
+
2. SDK: Docker ✅
|
| 75 |
+
3. Upload files WITH Dockerfile
|
| 76 |
+
4. → Flask API runs automatically
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### For Gradio UI:
|
| 80 |
+
```
|
| 81 |
+
1. Create new Space
|
| 82 |
+
2. SDK: Gradio ✅
|
| 83 |
+
3. Upload files WITHOUT Dockerfile
|
| 84 |
+
4. → Gradio UI runs automatically
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## 🔧 Environment Variables
|
| 88 |
+
|
| 89 |
+
- `DEPLOYMENT_MODE=flask` → Force Flask mode
|
| 90 |
+
- `DEPLOYMENT_MODE=gradio` → Force Gradio mode
|
| 91 |
+
- `PORT=7860` → Custom port (default: 7860)
|
| 92 |
+
- `GEMINI_API_KEY=xxx` → Default API key
|
| 93 |
+
|
| 94 |
+
## 🎉 Result
|
| 95 |
+
|
| 96 |
+
**One smart repository that automatically adapts to your deployment needs!**
|
| 97 |
+
|
| 98 |
+
🔌 **Flask**: Clean API for mobile apps
|
| 99 |
+
🎨 **Gradio**: Beautiful UI for web users
|
| 100 |
+
🧠 **Smart**: Chooses automatically
|
| 101 |
+
⚡ **Features**: Favorite sports + concise AI coaching in both modes!
|
app.py
CHANGED
|
@@ -257,7 +257,11 @@ def get_ai_coaching_recommendations(youtube_url, video_file, user_height_cm, use
|
|
| 257 |
|
| 258 |
## 🏆 AI Expert Coaching Analysis
|
| 259 |
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
---
|
| 263 |
*Analysis powered by Google Gemini AI*
|
|
|
|
| 257 |
|
| 258 |
## 🏆 AI Expert Coaching Analysis
|
| 259 |
|
| 260 |
+
**📊 Performance Percentiles:**
|
| 261 |
+
{chr(10).join([f"- **{sport}**: {percentile}th percentile" for sport, percentile in ai_result.get('analysis', {}).get('sports', {}).items()])}
|
| 262 |
+
|
| 263 |
+
**💡 Improvement Tips:**
|
| 264 |
+
{chr(10).join([f"{i+1}. {tip}" for i, tip in enumerate(ai_result.get('analysis', {}).get('tips', []))])}
|
| 265 |
|
| 266 |
---
|
| 267 |
*Analysis powered by Google Gemini AI*
|
athletic_performance.py
CHANGED
|
@@ -971,7 +971,7 @@ def get_ai_sports_coaching_analysis(jump_height_cm, user_height_cm, gender, favo
|
|
| 971 |
# Format favorite sports list
|
| 972 |
sports_list = ", ".join(favorite_sports) if favorite_sports else "None specified"
|
| 973 |
|
| 974 |
-
prompt = f"""You are a sports coach. Based on this athlete's jump performance, provide
|
| 975 |
|
| 976 |
ATHLETE DATA:
|
| 977 |
- Gender: {gender}
|
|
@@ -979,21 +979,25 @@ ATHLETE DATA:
|
|
| 979 |
- Jump Height: {jump_height_cm:.2f} cm
|
| 980 |
- Sports: {sports_list}
|
| 981 |
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
| 994 |
-
|
| 995 |
-
|
| 996 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
|
| 998 |
# Prepare the API request - try Gemini 1.5 Pro as fallback
|
| 999 |
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent"
|
|
@@ -1044,21 +1048,56 @@ Keep it simple for beginner athletes. Use basic English. No long explanations.""
|
|
| 1044 |
result = response.json()
|
| 1045 |
|
| 1046 |
if 'candidates' in result and len(result['candidates']) > 0:
|
| 1047 |
-
|
| 1048 |
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
"
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1060 |
}
|
| 1061 |
-
}
|
| 1062 |
else:
|
| 1063 |
return {"error": f"No response generated from AI. Response: {result}"}
|
| 1064 |
|
|
|
|
| 971 |
# Format favorite sports list
|
| 972 |
sports_list = ", ".join(favorite_sports) if favorite_sports else "None specified"
|
| 973 |
|
| 974 |
+
prompt = f"""You are a sports coach. Based on this athlete's jump performance, provide analysis in JSON format.
|
| 975 |
|
| 976 |
ATHLETE DATA:
|
| 977 |
- Gender: {gender}
|
|
|
|
| 979 |
- Jump Height: {jump_height_cm:.2f} cm
|
| 980 |
- Sports: {sports_list}
|
| 981 |
|
| 982 |
+
RESPOND WITH VALID JSON IN THIS EXACT FORMAT:
|
| 983 |
+
{{
|
| 984 |
+
"sports": {{
|
| 985 |
+
"Basketball": 75,
|
| 986 |
+
"Volleyball": 80
|
| 987 |
+
}},
|
| 988 |
+
"tips": [
|
| 989 |
+
"Focus on proper landing technique to reduce knee strain",
|
| 990 |
+
"Add plyometric exercises to your training routine",
|
| 991 |
+
"Strengthen your leg muscles with squats and lunges",
|
| 992 |
+
"Practice explosive movements for better power development"
|
| 993 |
+
]
|
| 994 |
+
}}
|
| 995 |
+
|
| 996 |
+
RULES:
|
| 997 |
+
- "sports" should contain percentile rankings (0-100) only for sports in their list: {sports_list}
|
| 998 |
+
- "tips" must be exactly 4 practical improvement suggestions
|
| 999 |
+
- Use simple English for beginner athletes
|
| 1000 |
+
- Return ONLY valid JSON, no extra text"""
|
| 1001 |
|
| 1002 |
# Prepare the API request - try Gemini 1.5 Pro as fallback
|
| 1003 |
url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent"
|
|
|
|
| 1048 |
result = response.json()
|
| 1049 |
|
| 1050 |
if 'candidates' in result and len(result['candidates']) > 0:
|
| 1051 |
+
ai_response_text = result['candidates'][0]['content']['parts'][0]['text']
|
| 1052 |
|
| 1053 |
+
try:
|
| 1054 |
+
# Parse the JSON response from AI
|
| 1055 |
+
ai_analysis_json = json.loads(ai_response_text)
|
| 1056 |
+
|
| 1057 |
+
# Validate the expected structure
|
| 1058 |
+
if not isinstance(ai_analysis_json, dict):
|
| 1059 |
+
raise ValueError("Response is not a JSON object")
|
| 1060 |
+
|
| 1061 |
+
# Extract required fields with defaults
|
| 1062 |
+
sports_percentiles = ai_analysis_json.get('sports', {})
|
| 1063 |
+
tips = ai_analysis_json.get('tips', [])
|
| 1064 |
+
|
| 1065 |
+
# Ensure tips is a list
|
| 1066 |
+
if not isinstance(tips, list):
|
| 1067 |
+
tips = []
|
| 1068 |
+
|
| 1069 |
+
# Ensure sports is a dict
|
| 1070 |
+
if not isinstance(sports_percentiles, dict):
|
| 1071 |
+
sports_percentiles = {}
|
| 1072 |
+
|
| 1073 |
+
return {
|
| 1074 |
+
"success": True,
|
| 1075 |
+
"analysis": {
|
| 1076 |
+
"sports": sports_percentiles,
|
| 1077 |
+
"tips": tips
|
| 1078 |
+
},
|
| 1079 |
+
"athlete_profile": {
|
| 1080 |
+
"gender": gender,
|
| 1081 |
+
"height_cm": user_height_cm,
|
| 1082 |
+
"jump_height_cm": jump_height_cm,
|
| 1083 |
+
"relative_jump_height": relative_jump_height,
|
| 1084 |
+
"flight_time_s": flight_time_s,
|
| 1085 |
+
"peak_power_watts": peak_power_watts,
|
| 1086 |
+
"rfd": rfd
|
| 1087 |
+
}
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
except json.JSONDecodeError as e:
|
| 1091 |
+
# Fallback: if JSON parsing fails, try to extract some information
|
| 1092 |
+
return {
|
| 1093 |
+
"error": f"AI returned invalid JSON. Raw response: {ai_response_text[:200]}...",
|
| 1094 |
+
"parsing_error": str(e)
|
| 1095 |
+
}
|
| 1096 |
+
except Exception as e:
|
| 1097 |
+
return {
|
| 1098 |
+
"error": f"Error processing AI response: {str(e)}",
|
| 1099 |
+
"raw_response": ai_response_text[:200]
|
| 1100 |
}
|
|
|
|
| 1101 |
else:
|
| 1102 |
return {"error": f"No response generated from AI. Response: {result}"}
|
| 1103 |
|