Spaces:
Sleeping
Sleeping
shaileshjadhavSS
commited on
Commit
·
cd570f9
1
Parent(s):
cbf81e5
Added UI and backend for bot
Browse files- .gitignore +7 -0
- app.py +265 -0
- model/prompts.py +45 -0
- model/utils.py +45 -0
- requirements.txt +70 -0
- settings/__init__.py +1 -0
- settings/base.py +24 -0
- user_auth/__init__.py +0 -0
- user_auth/auth_manager.py +23 -0
- user_auth/user.py +18 -0
- user_auth/user_manager.py +59 -0
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
**/__pycache__/
|
| 2 |
+
*.env
|
| 3 |
+
*.jpg
|
| 4 |
+
*.jpeg
|
| 5 |
+
*.mp3
|
| 6 |
+
app.log
|
| 7 |
+
*.png
|
app.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import streamlit as st
|
| 3 |
+
from streamlit_chat import message
|
| 4 |
+
from model.utils import create_model, setup_logger, generate_content
|
| 5 |
+
import google.generativeai as genai
|
| 6 |
+
import base64
|
| 7 |
+
from settings.base import setup_logger, GOOGLE_API_KEY
|
| 8 |
+
import json
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
|
| 11 |
+
from user_auth.user_manager import UserManager
|
| 12 |
+
from user_auth.auth_manager import AuthManager
|
| 13 |
+
|
| 14 |
+
# Set up logging
|
| 15 |
+
logger = setup_logger()
|
| 16 |
+
|
| 17 |
+
# Initialize session state variables
|
| 18 |
+
if 'history' not in st.session_state:
|
| 19 |
+
st.session_state.history = []
|
| 20 |
+
if 'chat_session' not in st.session_state:
|
| 21 |
+
st.session_state.chat_session = None
|
| 22 |
+
if 'authenticated' not in st.session_state:
|
| 23 |
+
st.session_state.authenticated = False
|
| 24 |
+
if 'email' not in st.session_state:
|
| 25 |
+
st.session_state.email = ""
|
| 26 |
+
if 'profile' not in st.session_state:
|
| 27 |
+
st.session_state.profile = None
|
| 28 |
+
if 'model' not in st.session_state:
|
| 29 |
+
st.session_state.model = None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def save_chat_history(user_name, chat_history):
|
| 34 |
+
"""
|
| 35 |
+
Save chat history to a file in a user-specific directory with a timestamp.
|
| 36 |
+
"""
|
| 37 |
+
base_dir = "chat_history"
|
| 38 |
+
user_dir = os.path.join(base_dir, user_name)
|
| 39 |
+
os.makedirs(user_dir, exist_ok=True)
|
| 40 |
+
|
| 41 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 42 |
+
file_path = os.path.join(user_dir, f"session_{timestamp}.json")
|
| 43 |
+
|
| 44 |
+
# Prepare a serializable version of chat_history
|
| 45 |
+
serializable_history = []
|
| 46 |
+
for entry in chat_history:
|
| 47 |
+
if entry['role'] == 'user':
|
| 48 |
+
user_entry = {
|
| 49 |
+
'role': 'user',
|
| 50 |
+
'parts': entry['parts'][1] if len(entry['parts']) > 1 else entry['parts'][0]
|
| 51 |
+
}
|
| 52 |
+
serializable_history.append(user_entry)
|
| 53 |
+
elif entry['role'] == 'model':
|
| 54 |
+
model_entry = {
|
| 55 |
+
'role': 'model',
|
| 56 |
+
'parts': entry['parts']
|
| 57 |
+
}
|
| 58 |
+
serializable_history.append(model_entry)
|
| 59 |
+
|
| 60 |
+
with open(file_path, "w") as f:
|
| 61 |
+
json.dump(serializable_history, f, indent=4)
|
| 62 |
+
|
| 63 |
+
logger.info(f"Chat history saved to {file_path}")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def authenticate_user(email, password):
|
| 67 |
+
"""
|
| 68 |
+
Authenticate user using AuthManager.
|
| 69 |
+
"""
|
| 70 |
+
user_manager = UserManager()
|
| 71 |
+
auth_manager = AuthManager(user_manager)
|
| 72 |
+
authenticated = auth_manager.authenticate_user(email, password)
|
| 73 |
+
return authenticated
|
| 74 |
+
|
| 75 |
+
def conversation_chat(file_path, text_prompt):
|
| 76 |
+
"""
|
| 77 |
+
Handle the conversation with the chat session using the provided file path and text prompt.
|
| 78 |
+
"""
|
| 79 |
+
temp_dir = "temp"
|
| 80 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
user_input = text_prompt if text_prompt else ""
|
| 84 |
+
|
| 85 |
+
if file_path:
|
| 86 |
+
temp_file_path = os.path.join(temp_dir, file_path.name)
|
| 87 |
+
with open(temp_file_path, "wb") as f:
|
| 88 |
+
f.write(file_path.read()) # Write the uploaded file to temp location
|
| 89 |
+
|
| 90 |
+
logger.info(f"Successfully saved uploaded file: {file_path.name}")
|
| 91 |
+
|
| 92 |
+
file = genai.upload_file(path=temp_file_path, display_name=file_path.name)
|
| 93 |
+
user_entry = {
|
| 94 |
+
"role": "user",
|
| 95 |
+
"parts": [file, user_input]
|
| 96 |
+
}
|
| 97 |
+
else:
|
| 98 |
+
user_entry = {
|
| 99 |
+
"role": "user",
|
| 100 |
+
"parts": [user_input]
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
st.session_state.history.append(user_entry)
|
| 104 |
+
|
| 105 |
+
response_text = generate_content(st.session_state.model, st.session_state.history)
|
| 106 |
+
|
| 107 |
+
bot_entry = {
|
| 108 |
+
"role": "model",
|
| 109 |
+
"parts": response_text
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
st.session_state.history.append(bot_entry)
|
| 113 |
+
|
| 114 |
+
logger.info("Conversation successfully processed")
|
| 115 |
+
|
| 116 |
+
except Exception as e:
|
| 117 |
+
logger.error(f"Error processing file: {e}")
|
| 118 |
+
st.session_state.history.append("Error")
|
| 119 |
+
|
| 120 |
+
def display_chat():
|
| 121 |
+
"""
|
| 122 |
+
Display chat input and responses.
|
| 123 |
+
"""
|
| 124 |
+
profile = st.session_state.profile
|
| 125 |
+
st.title("Wellness Bot 🦾🤖")
|
| 126 |
+
|
| 127 |
+
chat_container = st.container()
|
| 128 |
+
upload_container = st.container()
|
| 129 |
+
|
| 130 |
+
clear_chat_button = st.button('Clear Chat')
|
| 131 |
+
if st.button('Logout'):
|
| 132 |
+
|
| 133 |
+
# Save chat history before logging out
|
| 134 |
+
save_chat_history(st.session_state.email, st.session_state.history)
|
| 135 |
+
|
| 136 |
+
st.session_state.authenticated = False
|
| 137 |
+
st.session_state.email = ""
|
| 138 |
+
st.session_state.history = []
|
| 139 |
+
st.session_state.chat_session = None
|
| 140 |
+
st.session_state.profile = None
|
| 141 |
+
st.session_state.model = None
|
| 142 |
+
st.rerun()
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
with upload_container:
|
| 146 |
+
with st.form(key='chat_form', clear_on_submit=True):
|
| 147 |
+
file_path = st.file_uploader("Upload an image or audio of a meal:", type=["jpg", "jpeg", "png", "mpeg", "mp3", "wav", "ogg", "mp4"])
|
| 148 |
+
text_prompt = st.text_input("Type Here...")
|
| 149 |
+
submit_button = st.form_submit_button(label='Send ⬆️')
|
| 150 |
+
|
| 151 |
+
if submit_button:
|
| 152 |
+
conversation_chat(file_path, text_prompt)
|
| 153 |
+
|
| 154 |
+
if clear_chat_button:
|
| 155 |
+
st.session_state.history = []
|
| 156 |
+
st.session_state.chat_session = None
|
| 157 |
+
|
| 158 |
+
with chat_container:
|
| 159 |
+
|
| 160 |
+
message(f"Hey {profile['name']}! I'm here to assist you with your meals. Let's make healthy eating a breeze. Feel free to upload an image/video/audio of your meal or ask any questions about nutrition, dietary needs, and meal planning. Together, we'll achieve your goals and ensure you stay healthy and happy.", avatar_style="bottts")
|
| 161 |
+
for i, entry in enumerate(st.session_state.history):
|
| 162 |
+
if entry['role'] == 'user':
|
| 163 |
+
if len(entry['parts']) > 1:
|
| 164 |
+
uploaded_file = entry['parts'][0]
|
| 165 |
+
if uploaded_file.mime_type.startswith("image/"):
|
| 166 |
+
file_name= uploaded_file.display_name
|
| 167 |
+
with open(os.path.join("temp", file_name), "rb") as file:
|
| 168 |
+
encoded_img = base64.b64encode(file.read()).decode('utf-8')
|
| 169 |
+
img_html = f'<img src="data:image/png;base64,{encoded_img}" width="200" style="margin-top: 5px;"/>'
|
| 170 |
+
st.markdown(f"""
|
| 171 |
+
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px;">
|
| 172 |
+
<div style="max-width: 300px; background-color: #E8E8E8; border-radius: 10px; padding: 10px; position: relative;">
|
| 173 |
+
<div style="text-align: right;">
|
| 174 |
+
{img_html}
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
""", unsafe_allow_html=True)
|
| 179 |
+
else:
|
| 180 |
+
message(uploaded_file.display_name, is_user=True, key=f"{i}_user", avatar_style="adventurer")
|
| 181 |
+
|
| 182 |
+
if entry['parts'][1] != "":
|
| 183 |
+
message(entry['parts'][1], is_user=True, key=f"{i}_user", avatar_style="adventurer")
|
| 184 |
+
else:
|
| 185 |
+
message(entry['parts'][0], is_user=True, key=f"{i}_user", avatar_style="adventurer")
|
| 186 |
+
elif entry['role'] == 'model':
|
| 187 |
+
message(entry['parts'], key=str(i), avatar_style="bottts")
|
| 188 |
+
|
| 189 |
+
def main():
|
| 190 |
+
"""
|
| 191 |
+
Main function to run the Streamlit app.
|
| 192 |
+
"""
|
| 193 |
+
user_manager = UserManager()
|
| 194 |
+
auth_manager = AuthManager(user_manager)
|
| 195 |
+
st.set_page_config(page_title="Wellness Bot 🦾🤖", page_icon=":fork_and_knife:")
|
| 196 |
+
|
| 197 |
+
if st.session_state.authenticated:
|
| 198 |
+
if st.session_state.profile is None or st.session_state.model is None:
|
| 199 |
+
st.session_state.profile = user_manager.get_user(st.session_state.email).profile
|
| 200 |
+
st.session_state.model = create_model(GOOGLE_API_KEY, st.session_state.profile)
|
| 201 |
+
|
| 202 |
+
display_chat()
|
| 203 |
+
else:
|
| 204 |
+
st.title("Wellness Bot 🦾🤖 \n\nLogin/SignUp")
|
| 205 |
+
|
| 206 |
+
tab1, tab2 = st.tabs(["Login", "Sign Up"])
|
| 207 |
+
|
| 208 |
+
with tab1:
|
| 209 |
+
st.header("Login")
|
| 210 |
+
email = st.text_input("Email", key="login_email")
|
| 211 |
+
password = st.text_input("Password", type="password", key="login_password")
|
| 212 |
+
if st.button("Login"):
|
| 213 |
+
if auth_manager.authenticate_user(email, password):
|
| 214 |
+
st.success("Login successful!")
|
| 215 |
+
st.session_state.authenticated = True
|
| 216 |
+
st.session_state.email = email
|
| 217 |
+
st.rerun()
|
| 218 |
+
else:
|
| 219 |
+
st.error("Invalid email or password")
|
| 220 |
+
|
| 221 |
+
with tab2:
|
| 222 |
+
st.header("Create Account")
|
| 223 |
+
email = st.text_input("Email", key="signup_email")
|
| 224 |
+
password = st.text_input("Password", type="password", key="signup_password")
|
| 225 |
+
confirm_password = st.text_input("Confirm Password", type="password", key="signup_confirm_password")
|
| 226 |
+
|
| 227 |
+
name = st.text_input("Name")
|
| 228 |
+
age = st.number_input("Age", min_value=1, max_value=120)
|
| 229 |
+
gender = st.selectbox("Gender", ["Male", "Female", "Other"])
|
| 230 |
+
height = st.number_input("Height (cm)", min_value=30, max_value=300)
|
| 231 |
+
weight = st.number_input("Weight (kg)", min_value=1, max_value=500)
|
| 232 |
+
location = st.text_input("Location")
|
| 233 |
+
allergies = st.text_input("Allergies (comma-separated)")
|
| 234 |
+
spec_diet_pref = st.text_input("Special Dietary Preferences")
|
| 235 |
+
primary_goal = st.text_area("Primary Goal")
|
| 236 |
+
health_condition = st.text_area("Health Condition")
|
| 237 |
+
activity_level = st.selectbox("Activity Level", ["Sedentary", "Lightly Active", "Moderately Active", "Very Active"])
|
| 238 |
+
daily_calorie_intake = st.number_input("Daily Calorie Intake", min_value=1, max_value=10000)
|
| 239 |
+
|
| 240 |
+
if st.button("Sign Up"):
|
| 241 |
+
if password != confirm_password:
|
| 242 |
+
st.error("Passwords do not match")
|
| 243 |
+
else:
|
| 244 |
+
profile = {
|
| 245 |
+
"name": name,
|
| 246 |
+
"age": age,
|
| 247 |
+
"gender": gender,
|
| 248 |
+
"height": height,
|
| 249 |
+
"weight": weight,
|
| 250 |
+
"location": location,
|
| 251 |
+
"allergies": allergies.split(','),
|
| 252 |
+
"spec_diet_pref": spec_diet_pref,
|
| 253 |
+
"primary_goal": primary_goal,
|
| 254 |
+
"health_condition": health_condition,
|
| 255 |
+
"activity_level": activity_level,
|
| 256 |
+
"daily_calorie_intake": daily_calorie_intake
|
| 257 |
+
}
|
| 258 |
+
success, message = user_manager.create_user(email, password, profile)
|
| 259 |
+
if success:
|
| 260 |
+
st.success(message)
|
| 261 |
+
else:
|
| 262 |
+
st.error(message)
|
| 263 |
+
|
| 264 |
+
if __name__ == "__main__":
|
| 265 |
+
main()
|
model/prompts.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def chatbot_prompt(profile):
|
| 2 |
+
age = profile.get('age', 'unknown')
|
| 3 |
+
gender = profile.get('gender', 'unknown')
|
| 4 |
+
location = profile.get('location', 'unknown')
|
| 5 |
+
spec_diet_pref = profile.get('spec_diet_pref', 'unknown')
|
| 6 |
+
primary_goal = profile.get('primary_goal', 'unknown')
|
| 7 |
+
health_condition = profile.get('health_condition', 'unknown')
|
| 8 |
+
activity_level = profile.get('activity_level', 'unknown')
|
| 9 |
+
daily_calorie_intake = profile.get('daily_calorie_intake', 'unknown')
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
return(f"""You are a highly knowledgeable nutritionist. You provide expert advice on meal planning, dietary needs, nutritional values, and healthy eating habits.
|
| 13 |
+
You avoid answering any questions that are not related to meals, food, nutrition, or diet.
|
| 14 |
+
Keep your responses focused on offering practical and evidence-based nutritional guidance.
|
| 15 |
+
Keep your tone motivational and supportive.
|
| 16 |
+
|
| 17 |
+
when you provide your expert advice always consider user details which are as follows and explain in what way it is tailored for the user:
|
| 18 |
+
Age: {age},Gender: {gender},Location: {location}, Special Diet Preference: {spec_diet_pref}, Primary Goal: {primary_goal},
|
| 19 |
+
Health Condition: {health_condition} Activity Level: {activity_level}, Daily Calorie Intake: {daily_calorie_intake} calories
|
| 20 |
+
|
| 21 |
+
Chat Flow:
|
| 22 |
+
1. Ask the user which meal of the day is uploading (breakfast, lunch, dinner, or snack).
|
| 23 |
+
2. Identify, mention what you could identified and ask required follow up questions as needed to provide a proper response
|
| 24 |
+
a. if user uploads an image:
|
| 25 |
+
- analyze the image to identify the meal name, ingredients, and detailed and necessary nutritional information including portion of each nutrient with its metric. \n\nMeal Analysis:\n\nMeal Name:\n[Identified Meal Name]\n\nIdentified Ingredient:\nIngredient 1\nIngredient 2\nIngredient 3\n...\n\nNutritional value Information (per serving)
|
| 26 |
+
- If the image is not of a meal, respond with 'Please add a valid image.' If the image or meal details are unclear or incomplete, ask specific questions for clarification. If the image appears fake, respond with 'Please add an original meal image.'
|
| 27 |
+
b. if user uploads audio:
|
| 28 |
+
- transcribe the audio and identify the meal related information mentioned and reply to the user the transcribed text and get a confirmation.
|
| 29 |
+
- respond with the identified meal name, ingredients and thier quantity (if not mentioned ask the user to provide), provide the necessary nutritional information in detail including portion of each nutrient with its metric. \n\nMeal Analysis:\n\nMeal Name:\n[Identified Meal Name]\n\nIdentified Ingredient:\nIngredient 1\nIngredient 2\nIngredient 3\n...\n\nNutritional value Information (per serving)
|
| 30 |
+
- If the audio is not of a meal, respond with 'Please provide a valid meal description.' If the audio or meal details are unclear or incomplete, ask specific questions for clarification. If the audio appears fake, respond with 'Please provide proper clear audio.'
|
| 31 |
+
c. if user uploads video:
|
| 32 |
+
- analyse the video uploaded and identify the meal related information mentioned or visible in the frames and reply to the user the transcription and get a confirmation.
|
| 33 |
+
- If the video has audio, transcribe the audio and identify all the meal related information mentioned (it could be the meal name or the ingredients) and reply to the user the transcribed text and get a confirmation.
|
| 34 |
+
- respond with the identified meal name, ingredients, and detailed and necessary nutritional information including portion of each nutrient with its metric. \n\nMeal Analysis:\n\nMeal Name:\n[Identified Meal Name]\n\nIdentified Ingredient:\nIngredient 1\nIngredient 2\nIngredient 3\n...\n\nNutritional Information (per serving)
|
| 35 |
+
- if the video is not a meal or the meal details are unclear or incomplete or if the video has no audio or the audio does not mention any meal , ask specific questions for clarification. If the video appears fake, respond with 'Please provide a valid meal video.'
|
| 36 |
+
d. if user begins the conversation with a text input of their meal
|
| 37 |
+
- Ask additional questions to gather information required to provide a proper response.
|
| 38 |
+
3. Additional Information Needed:\\n(If applicable) Please provide more details on:\\n- each ingredient clarification(size, type/kind)\\n- Specific cooking instructions (e.g., fried, baked)
|
| 39 |
+
4. Please confirm if the identified details are correct.
|
| 40 |
+
-If correct, rewrite the identified details in a structured format and suggest a healthy alternative meal with detailed nutritional information and explanation why it is a better alternative.
|
| 41 |
+
4. Based on identified ingredients and nutritional information, suggest better alternatives cosidering the user's profile information, that would help improve the user's health or achiever goals, with detailed nutritional information and explanation why is this a better alternative. Healthy Alternative Suggestion:\\n\\n\"\n Ingredients:\\nIngredient 1\\nIngredient 2\\nIngredient 3\\n...\\n\\n\"\n Nutritional Information (per serving):\\n\\n
|
| 42 |
+
5. Then suggest the next healthy meal with a detailed ingredient list , recipe and nutritional value information per serving, explanation on how the suggested meal is beneficial for the user's health or goals. Also, help User keep track of their calorie intake by providing a calorie intake breakdown of the current meal ,suggested next meal and their future meals of the day\"\n
|
| 43 |
+
|
| 44 |
+
Every time you provide something, please confirm from the user, If any details are incorrect, please ask for additional information and always consider user details provided.\"\n""")
|
| 45 |
+
|
model/utils.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from model.prompts import chatbot_prompt
|
| 4 |
+
from settings.base import setup_logger
|
| 5 |
+
|
| 6 |
+
logger = setup_logger()
|
| 7 |
+
|
| 8 |
+
def create_model(api_key, profile):
|
| 9 |
+
"""
|
| 10 |
+
Create a GenerativeModel instance using the provided API key.
|
| 11 |
+
"""
|
| 12 |
+
generation_config = {
|
| 13 |
+
"temperature": 0.8,
|
| 14 |
+
"top_p": 0.95,
|
| 15 |
+
"top_k": 64,
|
| 16 |
+
"max_output_tokens": 8192,
|
| 17 |
+
"response_mime_type": "text/plain",
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
genai.configure(api_key=api_key)
|
| 22 |
+
model = genai.GenerativeModel(
|
| 23 |
+
model_name="gemini-1.5-pro",
|
| 24 |
+
generation_config=generation_config,
|
| 25 |
+
system_instruction= chatbot_prompt(profile),
|
| 26 |
+
)
|
| 27 |
+
logger.info("Model created successfully")
|
| 28 |
+
return model
|
| 29 |
+
except Exception as e:
|
| 30 |
+
logger.error(f"Error creating model: {e}")
|
| 31 |
+
return None
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def generate_content(model, message):
|
| 35 |
+
"""
|
| 36 |
+
Send a message to the chat session and get the response.
|
| 37 |
+
"""
|
| 38 |
+
try:
|
| 39 |
+
# msg = f"Query:{message}/n/n Every time you provide something please confirm from the user if the identified details are correct. If they are correct, suggest the next healthy meal with the detailed recipe. If any details are incorrect, please ask for additional information."
|
| 40 |
+
response = model.generate_content(message)
|
| 41 |
+
logger.info(f"Generated content for message")
|
| 42 |
+
return response.text
|
| 43 |
+
except Exception as e:
|
| 44 |
+
logger.error(f"Error generating content for message: {e}")
|
| 45 |
+
return f"Error generating content: {e}"
|
requirements.txt
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
altair==5.3.0
|
| 2 |
+
annotated-types==0.7.0
|
| 3 |
+
attrs==23.2.0
|
| 4 |
+
bcrypt==4.1.3
|
| 5 |
+
blinker==1.8.2
|
| 6 |
+
cachelib==0.13.0
|
| 7 |
+
cachetools==5.3.3
|
| 8 |
+
certifi==2024.6.2
|
| 9 |
+
charset-normalizer==3.3.2
|
| 10 |
+
click==8.1.7
|
| 11 |
+
Flask==3.0.3
|
| 12 |
+
Flask-Session==0.8.0
|
| 13 |
+
gitdb==4.0.11
|
| 14 |
+
GitPython==3.1.43
|
| 15 |
+
google-ai-generativelanguage==0.6.6
|
| 16 |
+
google-api-core==2.19.1
|
| 17 |
+
google-api-python-client==2.134.0
|
| 18 |
+
google-auth==2.30.0
|
| 19 |
+
google-auth-httplib2==0.2.0
|
| 20 |
+
google-generativeai==0.7.1
|
| 21 |
+
googleapis-common-protos==1.63.2
|
| 22 |
+
grpcio==1.64.1
|
| 23 |
+
grpcio-status==1.62.2
|
| 24 |
+
httplib2==0.22.0
|
| 25 |
+
idna==3.7
|
| 26 |
+
itsdangerous==2.2.0
|
| 27 |
+
Jinja2==3.1.4
|
| 28 |
+
jsonschema==4.22.0
|
| 29 |
+
jsonschema-specifications==2023.12.1
|
| 30 |
+
markdown-it-py==3.0.0
|
| 31 |
+
MarkupSafe==2.1.5
|
| 32 |
+
mdurl==0.1.2
|
| 33 |
+
msgspec==0.18.6
|
| 34 |
+
numpy==2.0.0
|
| 35 |
+
packaging==24.1
|
| 36 |
+
pandas==2.2.2
|
| 37 |
+
pillow==10.3.0
|
| 38 |
+
proto-plus==1.24.0
|
| 39 |
+
protobuf==4.25.3
|
| 40 |
+
psycopg2-binary==2.9.9
|
| 41 |
+
pyarrow==16.1.0
|
| 42 |
+
pyasn1==0.6.0
|
| 43 |
+
pyasn1_modules==0.4.0
|
| 44 |
+
pydantic==2.7.4
|
| 45 |
+
pydantic_core==2.18.4
|
| 46 |
+
pydeck==0.9.1
|
| 47 |
+
Pygments==2.18.0
|
| 48 |
+
pyparsing==3.1.2
|
| 49 |
+
python-dateutil==2.9.0.post0
|
| 50 |
+
pytz==2024.1
|
| 51 |
+
referencing==0.35.1
|
| 52 |
+
requests==2.32.3
|
| 53 |
+
rich==13.7.1
|
| 54 |
+
rpds-py==0.18.1
|
| 55 |
+
rsa==4.9
|
| 56 |
+
six==1.16.0
|
| 57 |
+
smmap==5.0.1
|
| 58 |
+
streamlit==1.36.0
|
| 59 |
+
streamlit-chat==0.1.1
|
| 60 |
+
tenacity==8.4.2
|
| 61 |
+
toml==0.10.2
|
| 62 |
+
toolz==0.12.1
|
| 63 |
+
tornado==6.4.1
|
| 64 |
+
tqdm==4.66.4
|
| 65 |
+
typing_extensions==4.12.2
|
| 66 |
+
tzdata==2024.1
|
| 67 |
+
uritemplate==4.1.1
|
| 68 |
+
urllib3==2.2.2
|
| 69 |
+
watchdog==4.0.1
|
| 70 |
+
Werkzeug==3.0.3
|
settings/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from settings.base import *
|
settings/base.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
|
| 4 |
+
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
|
| 5 |
+
|
| 6 |
+
DB_NAME = os.environ.get("DB_NAME")
|
| 7 |
+
DB_USER = os.environ.get("DB_USER")
|
| 8 |
+
DB_PASSWORD = os.environ.get("DB_PASSWORD")
|
| 9 |
+
DB_HOST = os.environ.get("DB_HOST")
|
| 10 |
+
DB_PORT = os.environ.get("DB_PORT")
|
| 11 |
+
|
| 12 |
+
def setup_logger():
|
| 13 |
+
logger = logging.getLogger('app_logger')
|
| 14 |
+
logger.setLevel(logging.DEBUG)
|
| 15 |
+
|
| 16 |
+
# Avoid adding multiple handlers if the logger already has handlers
|
| 17 |
+
if not logger.hasHandlers():
|
| 18 |
+
fh = logging.FileHandler('debug.log', mode='w')
|
| 19 |
+
fh.setLevel(logging.DEBUG)
|
| 20 |
+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
| 21 |
+
fh.setFormatter(formatter)
|
| 22 |
+
logger.addHandler(fh)
|
| 23 |
+
|
| 24 |
+
return logger
|
user_auth/__init__.py
ADDED
|
File without changes
|
user_auth/auth_manager.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from user_auth.user_manager import UserManager
|
| 2 |
+
import logging
|
| 3 |
+
from settings.base import setup_logger
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
logger = setup_logger()
|
| 7 |
+
|
| 8 |
+
class AuthManager:
|
| 9 |
+
def __init__(self, user_manager):
|
| 10 |
+
self.user_manager = user_manager
|
| 11 |
+
|
| 12 |
+
def authenticate_user(self, email, password):
|
| 13 |
+
user = self.user_manager.get_user(email)
|
| 14 |
+
if user:
|
| 15 |
+
logger.info(f'Attempting to authenticate user: {user.email}')
|
| 16 |
+
if user.check_password(password):
|
| 17 |
+
logger.info(f'User authenticated successfully: {user.email}')
|
| 18 |
+
return True
|
| 19 |
+
else:
|
| 20 |
+
logger.error(f'Invalid password for user: {user.email}')
|
| 21 |
+
else:
|
| 22 |
+
logger.error(f'User not found: {email}')
|
| 23 |
+
return False
|
user_auth/user.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import bcrypt
|
| 2 |
+
|
| 3 |
+
class User:
|
| 4 |
+
def __init__(self, email, password, profile):
|
| 5 |
+
self.email = email
|
| 6 |
+
self.password_hash = self._hash_password(password)
|
| 7 |
+
self.password = password
|
| 8 |
+
self.profile = profile
|
| 9 |
+
|
| 10 |
+
def _hash_password(self, password):
|
| 11 |
+
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
|
| 12 |
+
return hashed_password
|
| 13 |
+
|
| 14 |
+
def check_password(self, password):
|
| 15 |
+
entered_password = password.encode('utf-8')
|
| 16 |
+
stored_pwd = self.password.encode('utf-8')
|
| 17 |
+
return bcrypt.checkpw(entered_password, stored_pwd)
|
| 18 |
+
|
user_auth/user_manager.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import psycopg2
|
| 2 |
+
from user_auth.user import User
|
| 3 |
+
from settings.base import setup_logger, DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
logger = setup_logger()
|
| 7 |
+
|
| 8 |
+
class UserManager:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.conn = psycopg2.connect(
|
| 11 |
+
dbname=DB_NAME, user=DB_USER, password=DB_PASSWORD, host=DB_HOST, port=DB_PORT
|
| 12 |
+
)
|
| 13 |
+
self.cursor = self.conn.cursor()
|
| 14 |
+
|
| 15 |
+
def create_user(self, email, password, profile):
|
| 16 |
+
if self.get_user(email):
|
| 17 |
+
return False, "User already exists!"
|
| 18 |
+
user = User(email, password, profile)
|
| 19 |
+
try:
|
| 20 |
+
self.cursor.execute(
|
| 21 |
+
"""
|
| 22 |
+
INSERT INTO users (username, password, name, age, gender, height, weight, location, allergies, spec_diet_pref, primary_goal, health_condition, activity_level, daily_calorie_intake)
|
| 23 |
+
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
| 24 |
+
""",
|
| 25 |
+
(email, user.password_hash.decode("utf-8"), profile['name'], profile['age'], profile['gender'], profile['height'], profile['weight'], profile['location'], profile['allergies'], profile['spec_diet_pref'], profile['primary_goal'], profile['health_condition'], profile['activity_level'], profile['daily_calorie_intake'])
|
| 26 |
+
)
|
| 27 |
+
self.conn.commit()
|
| 28 |
+
logger.info(f'User created successfully: {email}')
|
| 29 |
+
return True, "Account created successfully!"
|
| 30 |
+
except Exception as e:
|
| 31 |
+
self.conn.rollback()
|
| 32 |
+
logger.error(f'Error creating user: {e}')
|
| 33 |
+
return False, str(e)
|
| 34 |
+
|
| 35 |
+
def get_user(self, email):
|
| 36 |
+
self.cursor.execute("SELECT * FROM users WHERE username = %s", (email,))
|
| 37 |
+
result = self.cursor.fetchone()
|
| 38 |
+
if result:
|
| 39 |
+
profile = {
|
| 40 |
+
"name": result[3],
|
| 41 |
+
"age": result[4],
|
| 42 |
+
"gender": result[5],
|
| 43 |
+
"height": result[6],
|
| 44 |
+
"weight": result[7],
|
| 45 |
+
"location": result[8],
|
| 46 |
+
"allergies": result[9],
|
| 47 |
+
"spec_diet_pref": result[10],
|
| 48 |
+
"primary_goal": result[11],
|
| 49 |
+
"health_condition": result[12],
|
| 50 |
+
"activity_level": result[13],
|
| 51 |
+
"daily_calorie_intake": result[14]
|
| 52 |
+
}
|
| 53 |
+
logger.info(f'User retrieved from database: {result[1]}')
|
| 54 |
+
return User(email=result[1], password=result[1], profile=profile)
|
| 55 |
+
return None
|
| 56 |
+
|
| 57 |
+
def close(self):
|
| 58 |
+
self.cursor.close()
|
| 59 |
+
self.conn.close()
|