Sportans / main.py
Mr-Thop's picture
Update main.py (#23)
5411016
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import List
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from models import RegisterRequest, LoginRequest, UserResponse
from auth import register_user, authenticate_user
from schemas import ChatRequest, ChatResponse, CoachRequest, CoachResponse
from chatbot import generate_chat_reply
from schemas import EventCreate, EventResponse
from events_service import add_event, get_all_events, get_events_by_date
from typing import List
from datetime import date
from schemas import FitnessRequest, FitnessResponse
from services import (
calculate_bmi,
calculate_bmr,
diet_recommendations,
fitness_recommendations,
)
from coach_routes import router as coach_router
from player_routes import router as player_router
from stats_routes import router as stats_router
from feedback_routes import router as feedback_router
from cv_model import router as model
from tools import get_nearby_sports_events
# Pose Feedback Services
from pose_feedback import PoseFeedbackService, TTSService, VoiceCommandService
app = FastAPI(title="Sportans Backend")
# ------------------ POSE FEEDBACK SERVICES ------------------
pose_feedback_service = PoseFeedbackService()
tts_service = TTSService()
voice_service = VoiceCommandService()
# ------------------ MODELS ------------------
class Mentor(BaseModel):
name: str
imageUrl: str
class TeamMember(BaseModel):
name: str
class AboutResponse(BaseModel):
title: str
teamName: str
description: List[str]
mentor: Mentor
teamMembers: List[TeamMember]
teamImageUrl: str
# ------------------ STATIC FILES ------------------
app.mount("/static", StaticFiles(directory="images"), name="static")
# ------------------ ROUTES ------------------
@app.get("/about", response_model=AboutResponse)
async def about_us():
return {
"title": "About Us",
"teamName": "Team Sportans",
"description": [
"In our first year of studies, we embarked on a remarkable journey to create this project, a journey that served as a testament to our collective ideas and boundless creativity.",
"We wish to extend our deepest and most sincere gratitude to our mentor, Nandhini K. Her unwavering guidance and steadfast support were invaluable.",
"There were countless late nights spent debugging code and endless iterations as we sought perfection.",
"As we present our project to the world, we are grateful for your time and hope it proves inspiring and informative."
],
"mentor": {
"name": "Anupkumar Bongale",
"imageUrl": "/static/mentor.jpg"
},
"teamMembers": [
{"name": "Karthikeshwar Karne Reddy"},
{"name": "Karan Chauhan"},
{"name": "Krrish Sinha"}
],
"teamImageUrl": "/static/members.jpg"
}
@app.post("/register")
async def register(data: RegisterRequest):
user_id, error = register_user(data)
if error:
raise HTTPException(status_code=400, detail=error)
return {"message": "Registration successful", "userId": user_id}
@app.post("/login", response_model=UserResponse)
async def login(data: LoginRequest):
user = authenticate_user(data)
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
return user
# -------- Sporta Bot --------
@app.post("/ai/chat", response_model=ChatResponse)
async def sporta_bot_chat(data: ChatRequest):
try:
reply, updated_history = generate_chat_reply(
username=data.username,
message=data.message,
history=[h.dict() for h in data.history]
)
return {
"reply": reply,
"history": updated_history
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
#-------- Sporta Coach --------
@app.post("/ai/coach", response_model=CoachResponse)
async def sporta_coach(data: CoachRequest):
try:
plan = generate_coaching_plan(data)
return {"plan": plan}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
#-------Nearby Events-------
@app.get("/nearbyevents")
def get_events(city: str):
try:
result = get_nearby_sports_events.run(city)
return {
"status": "success",
"city": city,
"events": result
}
except Exception as e:
return {
"status": "error",
"message": str(e)
}
# -------- Add Event --------
@app.post("/events")
async def create_event(event: EventCreate):
add_event(event)
return {"message": "Event added successfully"}
# -------- Get All Events --------
@app.get("/events", response_model=List[EventResponse])
async def fetch_events():
rows = get_all_events()
return rows
# -------- Search Events By Date --------
@app.get("/events/search", response_model=List[EventResponse])
async def search_events(eventDate: date):
rows = get_events_by_date(eventDate)
return rows
@app.post("/fitness", response_model=FitnessResponse)
def get_fitness_data(data: FitnessRequest):
bmi = calculate_bmi(data.weight, data.height)
bmr = calculate_bmr(data.weight, data.height, data.age, data.gender)
return FitnessResponse(
bmi=bmi,
bmr=bmr,
fitness_recommendations=fitness_recommendations(bmi),
diet_recommendations=diet_recommendations(bmr),
)
app.include_router(coach_router)
app.include_router(player_router)
app.include_router(stats_router)
app.include_router(feedback_router)
app.include_router(model)
# ------------------ POSE FEEDBACK WEBSOCKET ------------------
@app.websocket("/ws/pose-feedback")
async def pose_feedback_websocket(websocket: WebSocket):
"""
Real-time pose feedback via WebSocket
Receives pose keypoints and provides voice feedback based on:
1. User voice request "Am I doing right?"
2. Critical safety issues
3. Holding pose for 15+ seconds
Anti-spam: Max 1 feedback per 10 seconds, top 2 corrections only
"""
await websocket.accept()
try:
while True:
# Receive data from frontend
data = await websocket.receive_json()
action = data.get("action")
if action == "analyze_pose":
# Frontend sends pose data every frame
keypoints = data.get("keypoints")
pose_name = data.get("pose_name")
if not keypoints or not pose_name:
await websocket.send_json({
"type": "error",
"message": "Missing keypoints or pose_name"
})
continue
# Check if feedback should be given
result = pose_feedback_service.should_give_feedback(
keypoints=keypoints,
pose_name=pose_name,
voice_trigger=False
)
if result:
# Generate voice audio
audio_bytes = tts_service.text_to_speech(result["feedback_text"])
# Send text feedback first
await websocket.send_json({
"type": "feedback",
"text": result["feedback_text"],
"corrections": result["corrections"],
"reason": result["reason"]
})
# Send audio bytes
await websocket.send_bytes(audio_bytes)
else:
# No feedback needed
await websocket.send_json({
"type": "no_feedback"
})
elif action == "voice_command":
# Frontend sends audio when user speaks
keypoints = data.get("keypoints")
pose_name = data.get("pose_name")
audio_data = data.get("audio") # Base64 or bytes
if not keypoints or not pose_name:
await websocket.send_json({
"type": "error",
"message": "Missing keypoints or pose_name"
})
continue
# Check if user said trigger phrase like "Am I doing right?"
is_trigger = voice_service.listen_for_trigger(audio_data)
if is_trigger:
# User explicitly requested feedback
result = pose_feedback_service.should_give_feedback(
keypoints=keypoints,
pose_name=pose_name,
voice_trigger=True
)
if result:
audio_bytes = tts_service.text_to_speech(result["feedback_text"])
await websocket.send_json({
"type": "feedback",
"text": result["feedback_text"],
"corrections": result["corrections"],
"reason": "voice_requested"
})
await websocket.send_bytes(audio_bytes)
else:
# No corrections needed
audio_bytes = tts_service.text_to_speech("Great job! Your pose looks good.")
await websocket.send_json({
"type": "feedback",
"text": "Great job! Your pose looks good.",
"corrections": [],
"reason": "voice_requested"
})
await websocket.send_bytes(audio_bytes)
elif action == "reset":
# Reset feedback state (new session)
pose_feedback_service.reset_state()
await websocket.send_json({
"type": "reset_complete"
})
except WebSocketDisconnect:
print("Client disconnected from pose feedback")
except Exception as e:
print(f"WebSocket error: {e}")
try:
await websocket.send_json({
"type": "error",
"message": str(e)
})
except:
pass