diff --git a/.gitignore b/.gitignore index 8a5d4a6..21650ab 100644 --- a/.gitignore +++ b/.gitignore @@ -215,4 +215,10 @@ marimo/_lsp/ __marimo__/ # Streamlit -.streamlit/secrets.toml \ No newline at end of file +.streamlit/secrets.toml + +# Replit +.replit +replit.nix +replit.md +BuildInPublic/ \ No newline at end of file diff --git a/.sample.env b/.sample.env deleted file mode 100644 index ac74257..0000000 --- a/.sample.env +++ /dev/null @@ -1 +0,0 @@ -OPENAI_API_KEY='' \ No newline at end of file diff --git a/ai_personal_trainer.zip b/ai_personal_trainer.zip deleted file mode 100644 index 0dda8d6..0000000 Binary files a/ai_personal_trainer.zip and /dev/null differ diff --git a/api.py b/api.py deleted file mode 100644 index 41af6e1..0000000 --- a/api.py +++ /dev/null @@ -1,47 +0,0 @@ -# personal_trainer_agent.py -from fastapi import FastAPI, Request -from pydantic import BaseModel -import openai -from dotenv import load_dotenv -import os -load_dotenv() -client = openai.OpenAI() - -# Initialize FastAPI app -app = FastAPI() - -# ✅ Replace with your actual API key - -# Define data structure for user inputs -class UserInfo(BaseModel): - goal: str - experience: str - days_per_week: int - equipment: str - -@app.post("/generate_plan/") -async def generate_plan(user: UserInfo): - """ - Basic AI Agent: - - Takes user's fitness info - - Generates a simple gym plan - """ - prompt = f""" - You are a personal fitness trainer. - Create a 1-week gym plan for someone with: - - Goal: {user.goal} - - Experience level: {user.experience} - - Days per week available: {user.days_per_week} - - Available equipment: {user.equipment} - - Return the plan as a simple, structured list (Day 1, Day 2, ...). - """ - - response = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": prompt}], - temperature=0.7 - ) - - plan = response.choices[0].message.content - return {"gym_plan": plan} diff --git a/auth.py b/auth.py new file mode 100644 index 0000000..24f1488 --- /dev/null +++ b/auth.py @@ -0,0 +1,56 @@ +import streamlit as st +import requests +import uuid + +st.title("Login/Signup") +username = st.text_input("Username") + +if "session_id" in st.session_state and "username" in st.session_state: + st.switch_page("pages/frontend.py") + +# Logging in +if st.button("Login"): + if username: + st.session_state.session_id = str(uuid.uuid4()) + API_BASE = "http://localhost:8000/login/" + response = requests.post(API_BASE, + json={ + "username": username, + "session_id": st.session_state.session_id + }) + + if response.status_code == 200: + if response.json()["exists"]: + st.session_state.username = username + st.success("Logged in successfully!") + st.switch_page("pages/frontend.py") + else: + st.warning("User not found. Please sign up.") + else: + st.error("Failed to connect to the server.") + else: + st.error("Username is required") + +# Signing up +if st.button("Signup"): + if username: + st.session_state.session_id = str(uuid.uuid4()) + API_BASE = "http://localhost:8000/signup/" + response = requests.post(API_BASE, + json={ + "username": username, + "session_id": st.session_state.session_id + }) + if response.status_code == 200: + if response.json()["exists"]: + st.warning( + "Username already exists. Please login or choose a different username." + ) + else: + st.session_state.username = username + st.success("Signed up successfully!") + st.switch_page("pages/frontend.py") + else: + st.error("Failed to connect to the server.") + else: + st.error("Username is required") diff --git a/backend/personal_trainer_agent.py b/backend/personal_trainer_agent.py deleted file mode 100644 index 01bd007..0000000 --- a/backend/personal_trainer_agent.py +++ /dev/null @@ -1,67 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel -from dotenv import load_dotenv -from openai import OpenAI -import os - -# Chemin vers le fichier .env (même dossier que ce script) -dotenv_path = os.path.join(os.path.dirname(__file__), ".env") -load_dotenv(dotenv_path) - -# Vérifier que la clé est présente -api_key = os.getenv("OPENAI_API_KEY") -if not api_key: - raise ValueError("Veuillez définir votre clé OpenAI dans le fichier .env !") - -print("Clé API détectée :", api_key is not None) # juste pour vérifier - -# Créer le client OpenAI avec la clé -client = OpenAI(api_key=api_key) - -# Création de l'application FastAPI -app = FastAPI() - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - -# Modèle pour les entrées utilisateur -class UserInput(BaseModel): - goal: str - experience: str - days: int - equipment: str - -# Route pour générer le plan -@app.post("/generate_plan") -def generate_plan(data: UserInput): - if data.days <= 0: - return {"error": "Le nombre de jours doit être supérieur à 0"} - - prompt = f""" - You are an expert fitness coach. - Create a weekly workout plan: - - Goal: {data.goal} - - Experience: {data.experience} - - Days/week: {data.days} - - Equipment: {data.equipment} - """ - - try: - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": "You are an expert fitness coach."}, - {"role": "user", "content": prompt} - ] - ) - plan_text = response.choices[0].message["content"] - return {"plan": plan_text} - - except Exception as e: - return {"error": str(e)} diff --git a/dbSQLAlchemy.py b/dbSQLAlchemy.py new file mode 100644 index 0000000..c2c2e79 --- /dev/null +++ b/dbSQLAlchemy.py @@ -0,0 +1,93 @@ +from sqlalchemy import Column, String, DateTime, create_engine, Integer, ForeignKey +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from datetime import datetime + +#connect to db or create if not exists +engine = create_engine('sqlite:///my_database.db', echo=True) +Base = declarative_base() + +class User(Base): + __tablename__ = "users" + + username = Column(String, primary_key=True) + session_id = Column(String, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + +class ChatMessage(Base): + __tablename__ = "chat_messages" + + id = Column(Integer, primary_key=True, autoincrement=True) + username = Column(String, ForeignKey("users.username"), nullable=False) + role = Column(String, nullable=False) + content = Column(String, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + + +class UserPreferences(Base): + __tablename__ = "user_preferences" + username = Column(String, ForeignKey("users.username"), primary_key=True) + preferences_json = Column(String, nullable=False) + updated_at = Column(DateTime, default=datetime.utcnow) + age = Column(Integer) + gender = Column(String) + + +# Create tables +Base.metadata.create_all(bind=engine) + +#Session factory to interact with db +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +session = SessionLocal() + +# CRUD helpers +def get_user(db_session, username: str): + return db_session.query(User).filter_by(username=username).first() + + +def create_user(db_session, username: str, session_id: str): + user = User(username=username, session_id=session_id) + db_session.add(user) + db_session.commit() + db_session.refresh(user) + return user + + +def update_session(db_session, username: str, session_id: str): + user = get_user(db_session, username) + if user: + user.session_id = session_id + db_session.commit() + db_session.refresh(user) + return username + + +def create_chat_message(db_session, username: str, role: str, content: str): + chat_message = ChatMessage(username=username, role=role, content=content) + db_session.add(chat_message) + db_session.commit() + db_session.refresh(chat_message) + + + +def create_user_preferences(db_session, username: str, preferences_json: str): + user_preferences = UserPreferences(username=username, preferences_json=preferences_json) + db_session.add(user_preferences) + db_session.commit() + db_session.refresh(user_preferences) + return user_preferences + +def get_user_preferences(db_session, username: str): + return db_session.query(UserPreferences).filter_by(username=username).first() + +def update_user_preferences(db_session, username: str, preferences_json: str): + user_preferences = get_user_preferences(db_session, username) + if user_preferences: + user_preferences.preferences_json = preferences_json + db_session.commit() + db_session.refresh(user_preferences) + return user_preferences + return None + +def get_chat_messages(db_session, username: str): + return db_session.query(ChatMessage).filter_by(username=username).order_by(ChatMessage.created_at.asc()).all() diff --git a/frontend.py b/frontend.py deleted file mode 100644 index 7548d8d..0000000 --- a/frontend.py +++ /dev/null @@ -1,38 +0,0 @@ -import streamlit as st -import requests - -# --- Streamlit page setup --- -st.set_page_config(page_title="AI Personal Trainer 💪", page_icon="🏋️") -st.title("💪 Your AI Personal Trainer") -st.write("Answer a few questions and get your personalized gym plan instantly.") - -# --- User input form --- -with st.form("trainer_form"): - goal = st.text_input("🎯 What’s your fitness goal? (e.g. build muscle, lose fat, stay fit)") - experience = st.selectbox("🔥 Your experience level:", ["Beginner", "Intermediate", "Advanced"]) - days_per_week = st.slider("📅 How many days per week can you train?", 1, 7, 4) - equipment = st.text_area("🏋️ What equipment do you have? (e.g. dumbbells, resistance bands, gym access)") - submitted = st.form_submit_button("Generate Plan 🚀") - -# --- Backend API URL --- -API_URL = "http://127.0.0.1:8000/generate_plan/" # update if deployed - -# --- Call the FastAPI backend --- -if submitted: - if not goal or not equipment: - st.warning("⚠️ Please fill in all fields before submitting.") - else: - with st.spinner("Generating your gym plan..."): - response = requests.post(API_URL, json={ - "goal": goal, - "experience": experience, - "days_per_week": days_per_week, - "equipment": equipment - }) - - if response.status_code == 200: - plan = response.json()["gym_plan"] - st.success("🏋️ Here’s your personalized plan:") - st.markdown(plan) - else: - st.error("❌ Something went wrong. Make sure the FastAPI server is running.") diff --git a/frontend/step2_streamlit_app.py b/frontend/step2_streamlit_app.py deleted file mode 100644 index 99fef1a..0000000 --- a/frontend/step2_streamlit_app.py +++ /dev/null @@ -1,28 +0,0 @@ -import streamlit as st -import requests - -st.set_page_config(page_title="AI Trainer", page_icon="💪", layout="centered") - -st.title("💪 AI Personal Trainer Agent") -st.write("Fill the form and get your personalized gym plan instantly.") - -goal = st.selectbox("🎯 Your Goal", ["Lose weight", "Build muscle", "Endurance", "Get toned"]) -experience = st.selectbox("📊 Experience Level", ["Beginner", "Intermediate", Advanced"]) -days = st.slider("📅 Training Days per Week", 1, 7, 3) -equipment = st.text_input("🏋️ Equipment Available") - -if st.button("Generate Plan 🚀"): - data = { - "goal": goal, - "experience": experience, - "days": days, - "equipment": equipment - } - - response = requests.post("http://localhost:8000/generate_plan", json=data) - - if response.status_code == 200: - st.success("Your Personalized Plan:") - st.write(response.json()["plan"]) - else: - st.error("Error connecting to backend.") diff --git a/main.py b/main.py index 30324f9..2f3c447 100644 --- a/main.py +++ b/main.py @@ -1,82 +1,181 @@ -import openai -from dotenv import load_dotenv -import os from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel -from typing import List +from dotenv import load_dotenv +import os +from groq import Groq +import dbSQLAlchemy as db +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_groq import ChatGroq +from langchain_core.runnables.history import RunnableWithMessageHistory +from langchain_core.messages import AIMessage, HumanMessage +from langchain_core.runnables import RunnableSequence +from pydantic import SecretStr +import json -# Load environment variables load_dotenv() -client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY")) - -# ------------------- -# CLI: original script -# ------------------- -def main(): - print("💪 Welcome to your AI Personal Trainer!") - goal = input("What's your fitness goal? ") - experience = input("What's your experience level (beginner/intermediate/advanced)? ") - days = input("How many days per week can you train? ") - equipment = input("What equipment do you have? ") - - prompt = f""" - You are a personal trainer. Create a 1-week gym plan for someone with: - Goal: {goal} - Experience: {experience} - Days per week: {days} - Equipment: {equipment} - Return it as a structured text plan (Day 1, Day 2...). - """ - - # UPDATED TO NEW API - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": prompt}], - temperature=0.7 - ) - - print("\n🏋️ Your Gym Plan:\n") - print(response.choices[0].message.content) - -# ------------------- -# Web API: FastAPI -# ------------------- -app = FastAPI(title="AI Personal Trainer API") - -# Home route so you don't get 404 -@app.get("/") -def home(): - return {"message": "API is running — go to /docs"} - -class UserData(BaseModel): +key = os.getenv("GroqAPI") +if not key: + raise ValueError("Groq API key not found in environment variables.") +client = Groq(api_key=key) +llm = ChatGroq(temperature=0.7, model="llama-3.1-8b-instant", api_key=SecretStr(key)) + +# Build LangChain memory +def build_memory(messages: list): + history = [ + HumanMessage(content=m["content"]) if m["role"] == "user" + else AIMessage(content=m["content"]) + for m in messages + ] + + return history + + +# Build prompt template + +def build_prompt_template(): + return ChatPromptTemplate.from_messages([ + ( + "system", + """You are an AI personal trainer. + + User preferences: + - Goal: {goal} + - Experience: {experience} + - Days per week: {days_per_week} + - Equipment: {equipment} + - Coaching tone: {tone} + + Adapt all responses to these preferences.""" + ), + MessagesPlaceholder(variable_name="chat_history"), + ("human", "{input}") + ]) + + + +class UserInput(BaseModel): + username: str + age: int + gender: str goal: str experience: str days_per_week: int - equipment: List[str] = [] - -@app.post("/generate_plan") -def generate_plan(user: UserData): - prompt = f""" - You are a personal trainer. Create a 1-week gym plan for someone with: - Goal: {user.goal} - Experience: {user.experience} - Days per week: {user.days_per_week} - Equipment: {', '.join(user.equipment)} - Return it as a structured text plan (Day 1, Day 2...). - """ - - # CORRECT NEW API CALL - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": prompt}], - temperature=0.7 - ) - - return {"plan": response.choices[0].message.content} - -# ------------------- -# Run CLI if script is executed directly -# ------------------- -if __name__ == "__main__": - main() + equipment: str + tone: str + weight: int + height: int + +class ChatInput(BaseModel): + username: str + message: str + +class AuthInput(BaseModel): + username: str + session_id: str + + +app = FastAPI() + + +@app.post("/login/") +def login(data: AuthInput): + if data.username: + user = db.get_user(db.session, data.username) + if user: + db.update_session(db.session, data.username, data.session_id) + return {"exists": True} + else: + return {"exists": False} + +@app.post("/signup/") +def signup(data: AuthInput): + if data.username: + user = db.get_user(db.session, data.username) + if user: + return {"exists": True} + else: + db.create_user(db.session, data.username, data.session_id) + return {"exists": False} + +@app.post("/update_preferences/") +def update_preferences(data: UserInput): + preferences_json = { + "age": data.age, + "gender": data.gender, + "goal": data.goal, + "experience": data.experience, + "days_per_week": data.days_per_week, + "equipment": data.equipment, + "tone": data.tone, + "weight": data.weight, + "height": data.height + } + if not db.get_user_preferences(db.session, data.username): + db.create_user_preferences(db.session, data.username, json.dumps(preferences_json)) + else: + db.update_user_preferences(db.session, data.username, json.dumps(preferences_json)) + +@app.get("/chat_history/{username}") +def get_chat_history(username: str): + messages = db.get_chat_messages(db.session, username) + return {"messages": [ + {"role": msg.role, "content": msg.content} for msg in messages + ] + } + + +@app.post("/chat/") +def chat(data: ChatInput): + print("connected to fastapi") + # 1. Load user preferences + user_preferences = db.get_user_preferences(db.session, data.username) + messages = db.get_chat_messages(db.session, data.username) + # 2. Build memory + print("got preferences and history") + memory = build_memory([ + {"role": msg.role, "content": msg.content} + for msg in messages + ]) + print("memory built") + + # 3. Build prompt template + prompt = build_prompt_template() + print("prompt built") + # 4. Combine prompt and LLM into a RunnableSequence + chain = RunnableSequence(prompt, llm) + print("chain built") + if not user_preferences: + return {"response": "Please set your preferences first."} + + preferences = json.loads(str(user_preferences.preferences_json)) + print("Memory:", memory) + # 5. Invoke chain with context + response = chain.invoke({ + "goal": + preferences["goal"], + "experience": + preferences["experience"], + "days_per_week": + preferences["days_per_week"], + "equipment": + preferences["equipment"], + "tone": + preferences["tone"], + "chat_history": memory, + "input": data.message + }) + + assistant_reply = str(response.content) + + # 6. Save messages to DB + db.create_chat_message(db.session, data.username, "user", data.message) + db.create_chat_message(db.session, data.username, "assistant", assistant_reply) + + + return {"response": assistant_reply} + + + + diff --git a/pages/frontend.py b/pages/frontend.py new file mode 100644 index 0000000..8d93c77 --- /dev/null +++ b/pages/frontend.py @@ -0,0 +1,158 @@ +import streamlit as st +import requests + +st.set_page_config(page_title="AI Personal Trainer", page_icon="💪") +st.title("💪 Your AI Personal Trainer") + +# Prepare backend API URLs +API_BASE = "http://localhost:8000" +PREFERENCES_API = f"{API_BASE}/update_preferences/" +CHAT_API = f"{API_BASE}/chat/" +HISTORY_API = f"{API_BASE}/chat_history/" + +# Check if user is logged in +if "username" not in st.session_state or "session_id" not in st.session_state: + st.error("⚠️ You must be logged in to access this page.") + st.switch_page("auth.py") + +# Prepare chat history container +if "messages" not in st.session_state: + st.session_state.messages = [] + +# Create tabs +tab_preferences, tab_chat = st.tabs(["⚙️ Preferences", "💬 Chatbot"]) + +# First tab: Preferences +with tab_preferences: + st.subheader("⚙️ Personal Preferences") + + with st.form("preferences_form"): + age = st.number_input("👶 Age", min_value=18, max_value=60, value=20) + gender = st.radio("👤 Gender", ["Male", "Female"]) + weight = st.number_input("🏋️ Weight (kg)", + min_value=30, + max_value=150, + value=70) + height = st.number_input("📏 Height (cm)", + min_value=140, + max_value=220, + value=170) + goal = st.text_input("🎯 Fitness goal", + placeholder="Build muscle, lose fat, stay fit...") + + experience = st.selectbox("🔥 Experience level", + ["Beginner", "Intermediate", "Advanced"]) + + tone = st.selectbox("🗣️ Coaching tone", + ["Motivational", "Strict", "Friendly", "Neutral"]) + + days_per_week = st.slider("📅 Training days per week", 1, 7, 4) + + equipment = st.text_area( + "🏋️ Available equipment", + placeholder="Dumbbells, resistance bands, gym access...") + + save_preferences = st.form_submit_button("💾 Save Preferences") + + # Save preferences to backend + if save_preferences: + if not goal or not equipment: + st.warning("⚠️ Please fill in all required fields.") + else: + with st.spinner("Saving your preferences..."): + response = requests.post(PREFERENCES_API, + json={ + "username": + st.session_state.username, + "goal": goal, + "experience": experience, + "tone": tone, + "days_per_week": days_per_week, + "equipment": equipment, + "gender": gender, + "age": age, + "weight": weight, + "height": height + }) + + if response.status_code == 200: + st.success("✅ Preferences saved successfully!") + else: + st.error("❌ Failed to save preferences.") + +# Second tab: Chatbot +with tab_chat: + st.subheader("💬 Chat with your AI Trainer") + + # Load chat history from backend + if not st.session_state.messages: + history_response = requests.get( + f"{HISTORY_API}/{st.session_state.username}") + + if history_response.status_code == 200: + st.session_state.messages = history_response.json()["messages"] + + # Display chat history + for msg in st.session_state.messages: + if msg["role"] == "user": + with st.chat_message("user"): + st.markdown( + f"
" + f"{msg['content']}
", + unsafe_allow_html=True) + else: + with st.chat_message("assistant"): + st.markdown( + f"
" + f"{msg['content']}
", + unsafe_allow_html=True) + + # Chat input + col1, col2 = st.columns([5, 1]) + + with col1: + user_input = st.text_input( + "Type your message", + placeholder="Ask about workouts, nutrition, recovery...") + + with col2: + send_clicked = st.button("Send ➤") + + if send_clicked and user_input: + # Show user message + st.session_state.messages.append({ + "role": "user", + "content": user_input + }) + + with st.chat_message("user"): + st.markdown( + f"
" + f"{user_input}
", + unsafe_allow_html=True) + + # Call backend + with st.chat_message("assistant"): + with st.spinner("Thinking..."): + st.write("⏳ Generating response...") + response = requests.post(CHAT_API, + json={ + "username": + st.session_state.username, + "message": user_input + }) + st.write(response.status_code) + if response.status_code == 200: + assistant_reply = response.json()["response"] + else: + assistant_reply = "❌ Something went wrong." + + st.markdown( + f"
" + f"{assistant_reply}
", + unsafe_allow_html=True) + + st.session_state.messages.append({ + "role": "assistant", + "content": assistant_reply + }) diff --git a/requirement.txt/fastapi.txt b/requirement.txt/fastapi.txt deleted file mode 100644 index 8e559cf..0000000 --- a/requirement.txt/fastapi.txt +++ /dev/null @@ -1,6 +0,0 @@ -fastapi -uvicorn -openai -python-dotenv -streamlit -requests diff --git a/requirements.txt b/requirements.txt index c40678e..02c1649 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,10 @@ streamlit pandas transformers dotenv +openai +pydantic +python-dotenv +requests +groq +langchain-core +langchain-groq diff --git a/run.py b/run.py new file mode 100644 index 0000000..a3bccd5 --- /dev/null +++ b/run.py @@ -0,0 +1,25 @@ +import subprocess +import sys +import time + +backend_process = subprocess.Popen( + [sys.executable, "-m", "uvicorn", "main:app", "--host", "localhost", "--port", "8000", "--reload"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT +) + +time.sleep(2) + +frontend_process = subprocess.Popen( + [sys.executable, "-m", "streamlit", "run", "auth.py", + "--server.port", "5000", + "--server.address", "0.0.0.0", + "--server.headless", "true", + "--browser.gatherUsageStats", "false"], +) + +try: + frontend_process.wait() +except KeyboardInterrupt: + backend_process.terminate() + frontend_process.terminate() diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..f2ef606 --- /dev/null +++ b/start.sh @@ -0,0 +1,8 @@ +# start.sh +#!/bin/bash + +# Start FastAPI in the background on port 8000 (adjust if needed, but 8000 is common) +uvicorn main:app --host 0.0.0.0 --port 8000 & + +# Start Streamlit in the foreground (Replit will show this in the web view) +streamlit run auth.py --server.port 8081 --server.address 0.0.0.0 \ No newline at end of file