Spaces:
Sleeping
Sleeping
Upload 22 files
Browse files- .gitattributes +6 -0
- client/__init__.py +0 -0
- client/assets/avatar_bot_big.jpg +3 -0
- client/assets/avatar_bot_medium.jpg +3 -0
- client/assets/avatar_bot_small.jpg +0 -0
- client/assets/logo.png +3 -0
- client/assets/membre1.jpg +3 -0
- client/assets/membre2.jpg +3 -0
- client/assets/membre3.jpg +3 -0
- client/assets/membre4.jpg +0 -0
- client/assets/membre5.jpg +0 -0
- client/pages/__init__.py +0 -0
- client/pages/dashboard.py +157 -0
- client/pages/home.py +204 -0
- client/pages/nutri.py +270 -0
- client/pages/nutri_old.py +148 -0
- client/pages/sign_in.py +133 -0
- client/pages/sign_up.py +41 -0
- client/pages/user.py +74 -0
- client/pages/user__course_list.py +19 -0
- client/pages/user__favoris.py +6 -0
- client/pages/user__info_perso.py +76 -0
- client/pages/user__mealplan.py +113 -0
.gitattributes
CHANGED
@@ -34,3 +34,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
37 |
+
client/assets/avatar_bot_big.jpg filter=lfs diff=lfs merge=lfs -text
|
38 |
+
client/assets/avatar_bot_medium.jpg filter=lfs diff=lfs merge=lfs -text
|
39 |
+
client/assets/logo.png filter=lfs diff=lfs merge=lfs -text
|
40 |
+
client/assets/membre1.jpg filter=lfs diff=lfs merge=lfs -text
|
41 |
+
client/assets/membre2.jpg filter=lfs diff=lfs merge=lfs -text
|
42 |
+
client/assets/membre3.jpg filter=lfs diff=lfs merge=lfs -text
|
client/__init__.py
ADDED
File without changes
|
client/assets/avatar_bot_big.jpg
ADDED
![]() |
Git LFS Details
|
client/assets/avatar_bot_medium.jpg
ADDED
![]() |
Git LFS Details
|
client/assets/avatar_bot_small.jpg
ADDED
![]() |
client/assets/logo.png
ADDED
![]() |
Git LFS Details
|
client/assets/membre1.jpg
ADDED
![]() |
Git LFS Details
|
client/assets/membre2.jpg
ADDED
![]() |
Git LFS Details
|
client/assets/membre3.jpg
ADDED
![]() |
Git LFS Details
|
client/assets/membre4.jpg
ADDED
![]() |
client/assets/membre5.jpg
ADDED
![]() |
client/pages/__init__.py
ADDED
File without changes
|
client/pages/dashboard.py
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import psycopg2
|
3 |
+
import os
|
4 |
+
import plotly.graph_objects as go
|
5 |
+
import pandas as pd
|
6 |
+
from dotenv import load_dotenv
|
7 |
+
from server.db.db import (
|
8 |
+
get_recipes_count,
|
9 |
+
get_average_latency,
|
10 |
+
get_daily_requests,
|
11 |
+
get_total_cost,
|
12 |
+
get_total_impact
|
13 |
+
)
|
14 |
+
|
15 |
+
# Récupérer les données pour afficher sur le dashboard
|
16 |
+
total_recipes = get_recipes_count()
|
17 |
+
average_latency = get_average_latency()
|
18 |
+
total_cost = get_total_cost()
|
19 |
+
total_impact = get_total_impact()
|
20 |
+
|
21 |
+
# Récupérer les données des requêtes par jour
|
22 |
+
df_requests = get_daily_requests()
|
23 |
+
|
24 |
+
# Affichage de la page dashboard avec Streamlit
|
25 |
+
st.markdown(
|
26 |
+
"""
|
27 |
+
<div class="title-container">
|
28 |
+
DASHBOARD
|
29 |
+
</div>
|
30 |
+
""",
|
31 |
+
unsafe_allow_html=True,
|
32 |
+
)
|
33 |
+
|
34 |
+
# Ajouter le CSS pour les cards avec animations et un design moderne
|
35 |
+
st.markdown("""
|
36 |
+
<style>
|
37 |
+
.title-container {
|
38 |
+
background-color: #6A5ACD;
|
39 |
+
border-radius: 10px;
|
40 |
+
color: white;
|
41 |
+
text-align: center;
|
42 |
+
padding: 5px;
|
43 |
+
margin-bottom: 20px;
|
44 |
+
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
45 |
+
font-family: New Icon;
|
46 |
+
font-size: 30px;
|
47 |
+
}
|
48 |
+
|
49 |
+
/* Style pour les cards */
|
50 |
+
.card {
|
51 |
+
border-radius: 15px;
|
52 |
+
padding: 20px;
|
53 |
+
background: linear-gradient(to top, #cae7d4, #a7d7b8);
|
54 |
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
55 |
+
margin-bottom: 20px;
|
56 |
+
text-align: center;
|
57 |
+
transition: transform 0.3s, box-shadow 0.3s;
|
58 |
+
animation: fadeInUp 0.5s ease-out;
|
59 |
+
height: 200px;
|
60 |
+
}
|
61 |
+
|
62 |
+
.card:hover {
|
63 |
+
transform: translateY(-10px);
|
64 |
+
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
|
65 |
+
}
|
66 |
+
|
67 |
+
@keyframes fadeInUp {
|
68 |
+
0% {
|
69 |
+
transform: translateY(20px);
|
70 |
+
opacity: 0;
|
71 |
+
}
|
72 |
+
100% {
|
73 |
+
transform: translateY(0);
|
74 |
+
opacity: 1;
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
.card-title {
|
79 |
+
font-size: 1.3em;
|
80 |
+
color: #2d3e50;
|
81 |
+
margin-bottom: 15px;
|
82 |
+
font-weight: bold;
|
83 |
+
font-family: New Icon;
|
84 |
+
}
|
85 |
+
|
86 |
+
.card-value {
|
87 |
+
font-size: 30px;
|
88 |
+
color: #0099cc;
|
89 |
+
font-weight: bold;
|
90 |
+
}
|
91 |
+
|
92 |
+
/* Add smooth transition for the hover effect */
|
93 |
+
.card .card-title, .card .card-value {
|
94 |
+
transition: color 0.3s ease;
|
95 |
+
}
|
96 |
+
|
97 |
+
.card:hover {
|
98 |
+
color: #FF9A76;
|
99 |
+
}
|
100 |
+
.card:hover .card-value {
|
101 |
+
color: #0086b3;
|
102 |
+
}
|
103 |
+
</style>
|
104 |
+
""", unsafe_allow_html=True)
|
105 |
+
|
106 |
+
|
107 |
+
# Créer des colonnes pour disposer les cards
|
108 |
+
col1, col2, col3, col4 = st.columns(4)
|
109 |
+
|
110 |
+
# Card 1
|
111 |
+
with col1:
|
112 |
+
st.markdown(f"""
|
113 |
+
<div class="card">
|
114 |
+
<div class="card-title">🍲 Nombre de recettes suggerées</div>
|
115 |
+
<div class="card-value">{total_recipes}</div>
|
116 |
+
</div>
|
117 |
+
""", unsafe_allow_html=True)
|
118 |
+
|
119 |
+
# Card 2
|
120 |
+
with col2:
|
121 |
+
st.markdown(f"""
|
122 |
+
<div class="card">
|
123 |
+
<div class="card-title">⏱️ Latence moyenne</div>
|
124 |
+
<div class="card-value">{average_latency}s</div>
|
125 |
+
</div>
|
126 |
+
""", unsafe_allow_html=True)
|
127 |
+
|
128 |
+
# Card 3
|
129 |
+
with col3:
|
130 |
+
st.markdown(f"""
|
131 |
+
<div class="card">
|
132 |
+
<div class="card-title">💸 Coût total des requêtes</div>
|
133 |
+
<div class="card-value">${total_cost}</div>
|
134 |
+
</div>
|
135 |
+
""", unsafe_allow_html=True)
|
136 |
+
|
137 |
+
# Card 4
|
138 |
+
with col4:
|
139 |
+
st.markdown(f"""
|
140 |
+
<div class="card">
|
141 |
+
<div class="card-title">🌱 Impact écologique estimé</div>
|
142 |
+
<div class="card-value">{total_impact} kg CO2</div>
|
143 |
+
</div>
|
144 |
+
""", unsafe_allow_html=True)
|
145 |
+
|
146 |
+
# Graphique des requêtes par jour
|
147 |
+
st.markdown("### 📅 Nombre de requêtes par jour")
|
148 |
+
|
149 |
+
fig = go.Figure(data=[go.Scatter(x=df_requests['date'], y=df_requests['nombre_requetes'], mode='lines+markers')])
|
150 |
+
|
151 |
+
fig.update_layout(
|
152 |
+
xaxis_title="Date",
|
153 |
+
yaxis_title="Nombre de requêtes",
|
154 |
+
template="plotly_dark"
|
155 |
+
)
|
156 |
+
|
157 |
+
st.plotly_chart(fig)
|
client/pages/home.py
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import os
|
3 |
+
|
4 |
+
# def home_page():
|
5 |
+
|
6 |
+
st.markdown("""
|
7 |
+
<style>
|
8 |
+
/*
|
9 |
+
body, .stApp {
|
10 |
+
background: linear-gradient(to right, #cae7d4, #a8d8b9);
|
11 |
+
}*/
|
12 |
+
|
13 |
+
@keyframes gradientAnimation {
|
14 |
+
0% { background-position: 0% 50%; }
|
15 |
+
50% { background-position: 100% 50%; }
|
16 |
+
100% { background-position: 0% 50%; }
|
17 |
+
}
|
18 |
+
|
19 |
+
.presentation {
|
20 |
+
background-color: #ffffff;
|
21 |
+
border-radius: 10px;
|
22 |
+
padding: 2rem;
|
23 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
24 |
+
}
|
25 |
+
|
26 |
+
.left-col, .right-col {
|
27 |
+
border-radius: 12px;
|
28 |
+
overflow: hidden;
|
29 |
+
}
|
30 |
+
|
31 |
+
h2 {
|
32 |
+
font-size: 2rem;
|
33 |
+
color: #2a4b47;
|
34 |
+
}
|
35 |
+
|
36 |
+
.welcome-title {
|
37 |
+
font-size: 2.5rem;
|
38 |
+
font-weight: 700;
|
39 |
+
color: #2a4b47;
|
40 |
+
text-align: center;
|
41 |
+
animation: fadeIn 2s ease-out;
|
42 |
+
}
|
43 |
+
|
44 |
+
@keyframes fadeIn {
|
45 |
+
0% { opacity: 0; }
|
46 |
+
100% { opacity: 1; }
|
47 |
+
}
|
48 |
+
|
49 |
+
.user-name {
|
50 |
+
color: #4e7a63;
|
51 |
+
font-size: 3rem;
|
52 |
+
font-weight: bold;
|
53 |
+
animation: nameAnimation 2s ease-out;
|
54 |
+
}
|
55 |
+
|
56 |
+
.presentation-text {
|
57 |
+
font-size: 1.3rem;
|
58 |
+
color: #4e7a63;
|
59 |
+
text-align: center;
|
60 |
+
font-style: italic;
|
61 |
+
animation: fadeIn 3s ease-out;
|
62 |
+
}
|
63 |
+
|
64 |
+
.feature-icon {
|
65 |
+
font-size: 2rem;
|
66 |
+
margin-right: 1rem;
|
67 |
+
}
|
68 |
+
|
69 |
+
.features {
|
70 |
+
background: linear-gradient(to right, #cae7d4, #a8d8b9);
|
71 |
+
padding: 1.5rem;
|
72 |
+
border-radius: 8px;
|
73 |
+
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
74 |
+
animation: fadeIn 1s ease-out;
|
75 |
+
transition: transform 0.3s ease;
|
76 |
+
margin-bottom: 1rem;
|
77 |
+
height:250px;
|
78 |
+
}
|
79 |
+
|
80 |
+
.features:hover {
|
81 |
+
transform: translateY(-5px);
|
82 |
+
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2);
|
83 |
+
}
|
84 |
+
|
85 |
+
.team-name-role {
|
86 |
+
color: #2a4b47;
|
87 |
+
font-size: 1.1rem;
|
88 |
+
margin-top: 0.5rem;
|
89 |
+
font-weight: 500;
|
90 |
+
font-style: italic;
|
91 |
+
text-align: center;
|
92 |
+
}
|
93 |
+
|
94 |
+
.team-name {
|
95 |
+
color: #2a4b47;
|
96 |
+
font-size: 1.3rem;
|
97 |
+
font-weight: 700;
|
98 |
+
text-align: center;
|
99 |
+
}
|
100 |
+
|
101 |
+
</style>
|
102 |
+
""", unsafe_allow_html=True)
|
103 |
+
|
104 |
+
st.markdown(f"""
|
105 |
+
<h2 class="welcome-title">
|
106 |
+
Bienvenue sur NutriGénie <span class="user-name">{st.session_state['user']}</span> 🍽️!
|
107 |
+
</h2>
|
108 |
+
""", unsafe_allow_html=True)
|
109 |
+
|
110 |
+
st.markdown("""
|
111 |
+
<br>
|
112 |
+
<div class="presentation-text">
|
113 |
+
" Laissez-nous vous guider à travers une expérience culinaire sur-mesure. Découvrez des recettes adaptées à vos préférences et suivez vos habitudes alimentaires en toute simplicité. "
|
114 |
+
</div>
|
115 |
+
<br>
|
116 |
+
""", unsafe_allow_html=True)
|
117 |
+
|
118 |
+
logo_path = os.path.join("client","assets","logo.png")
|
119 |
+
|
120 |
+
# centrer le logo
|
121 |
+
cola, colb, colc = st.columns(3)
|
122 |
+
|
123 |
+
with cola:
|
124 |
+
pass
|
125 |
+
with colb:
|
126 |
+
st.image(logo_path, use_container_width=True, caption=None)
|
127 |
+
with colc:
|
128 |
+
pass
|
129 |
+
|
130 |
+
st.markdown("""
|
131 |
+
<br>
|
132 |
+
<h3 style="color:#2a4b47; text-align:center;">🔧 Fonctionnalités principales de l'application :</h3>
|
133 |
+
<br>
|
134 |
+
""", unsafe_allow_html=True)
|
135 |
+
|
136 |
+
# Fonctionnalités disposées horizontalement par paires
|
137 |
+
col1, col2 = st.columns(2)
|
138 |
+
|
139 |
+
with col1:
|
140 |
+
|
141 |
+
# Fonctionnalités 1 et 2
|
142 |
+
st.markdown("""
|
143 |
+
<div class="features">
|
144 |
+
<div style="display: flex; align-items: center;">
|
145 |
+
<span class="feature-icon">🍽️</span>
|
146 |
+
<h3><strong>Génération de recettes personnalisées</strong></h3>
|
147 |
+
</div>
|
148 |
+
<p>Créez des recettes adaptées à vos préférences et vos besoins alimentaires. Nous générons des suggestions personnalisées pour chaque utilisateur.</p>
|
149 |
+
</div>
|
150 |
+
<div class="features">
|
151 |
+
<div style="display: flex; align-items: center;">
|
152 |
+
<span class="feature-icon">📝</span>
|
153 |
+
<h3><strong>Suivi des repas</strong></h3>
|
154 |
+
</div>
|
155 |
+
<p>Consultez l'historique de vos repas consommés et suivez vos habitudes alimentaires au fil du temps.</p>
|
156 |
+
</div>
|
157 |
+
""", unsafe_allow_html=True)
|
158 |
+
|
159 |
+
with col2:
|
160 |
+
# Fonctionnalités 3 et 4
|
161 |
+
st.markdown("""
|
162 |
+
<div class="features">
|
163 |
+
<div style="display: flex; align-items: center;">
|
164 |
+
<span class="feature-icon">🛒</span>
|
165 |
+
<h3><strong>Liste de courses</strong></h3>
|
166 |
+
</div>
|
167 |
+
<p>Générez automatiquement des listes de courses basées sur les recettes que vous avez choisies. Ne manquez plus d'ingrédients !</p>
|
168 |
+
</div>
|
169 |
+
<div class="features">
|
170 |
+
<div style="display: flex; align-items: center;">
|
171 |
+
<span class="feature-icon">🍴</span>
|
172 |
+
<h3><strong>Suggestions de repas</strong></h3>
|
173 |
+
</div>
|
174 |
+
<p>Obtenez des suggestions de repas en fonction de vos goûts et de vos besoins nutritionnels.</p>
|
175 |
+
</div>
|
176 |
+
""", unsafe_allow_html=True)
|
177 |
+
|
178 |
+
# Présentation des membres de l'équipe
|
179 |
+
st.markdown("<hr>", unsafe_allow_html=True) # Ajoute une ligne de séparation
|
180 |
+
|
181 |
+
st.subheader("Rencontrez notre équipe 👩🍳👨🍳")
|
182 |
+
|
183 |
+
# Définition des 5 membres
|
184 |
+
base_path = os.path.join("client","assets")
|
185 |
+
membres = [
|
186 |
+
{"nom": "Souraya", "role": "M2 SISE", "photo": f"{os.path.join(base_path,'membre1.jpg')}", "emoji_role": "👩💻"},
|
187 |
+
{"nom": "Bertrand", "role": "M2 SISE", "photo": f"{os.path.join(base_path,'membre2.jpg')}", "emoji_role": "👩💻"},
|
188 |
+
{"nom": "Cyril", "role": "M2 SISE", "photo": f"{os.path.join(base_path,'membre3.jpg')}", "emoji_role": "👩💻"},
|
189 |
+
{"nom": "Linh Nhi", "role": "M2 SISE", "photo": f"{os.path.join(base_path,'membre4.jpg')}", "emoji_role": "👩💻"},
|
190 |
+
{"nom": "Daniella", "role": "M2 SISE", "photo": f"{os.path.join(base_path,'membre5.jpg')}", "emoji_role": "👩💻"}
|
191 |
+
]
|
192 |
+
|
193 |
+
# Création des colonnes pour chaque membre
|
194 |
+
cols = st.columns(5)
|
195 |
+
|
196 |
+
for i, membre in enumerate(membres):
|
197 |
+
with cols[i]:
|
198 |
+
st.image(membre["photo"], use_container_width=True, caption=None)
|
199 |
+
st.markdown(f"""
|
200 |
+
<div class="team-member">
|
201 |
+
<div class="team-name">{membre['nom']}</div>
|
202 |
+
<div class="team-name-role">{membre['emoji_role']} {membre['role']}</div>
|
203 |
+
</div>
|
204 |
+
""", unsafe_allow_html=True)
|
client/pages/nutri.py
ADDED
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import os
|
3 |
+
import time
|
4 |
+
from server.mistral.mistralapi import MistralAPI
|
5 |
+
from server.security.prompt_guard import Guardrail
|
6 |
+
import asyncio
|
7 |
+
from typing import List, Dict
|
8 |
+
from datetime import datetime
|
9 |
+
from server.db.dbmanager import (
|
10 |
+
load_conversations,
|
11 |
+
load_messages,
|
12 |
+
update_conversation,
|
13 |
+
create_conversation,
|
14 |
+
save_message,
|
15 |
+
get_conversation_title,
|
16 |
+
update_conversation_title,
|
17 |
+
delete_conversation,
|
18 |
+
load_chatbot_suggestions,
|
19 |
+
save_chatbot_suggestions
|
20 |
+
)
|
21 |
+
import logging
|
22 |
+
logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
|
23 |
+
logger = logging.getLogger(__name__)
|
24 |
+
|
25 |
+
|
26 |
+
# 🔹 Chargement des variables de session pour éviter les rechargements inutiles
|
27 |
+
if "id_conversation" not in st.session_state:
|
28 |
+
st.session_state.id_conversation = None
|
29 |
+
|
30 |
+
if "mistral_model" not in st.session_state:
|
31 |
+
st.session_state["mistral_model"] = "mistral-large-latest"
|
32 |
+
|
33 |
+
if "messages" not in st.session_state:
|
34 |
+
st.session_state.messages = [] # Initialise l'historique des messages
|
35 |
+
|
36 |
+
|
37 |
+
|
38 |
+
|
39 |
+
# 🔹 Initialisation unique de MistralAPI
|
40 |
+
if "mistral_instance" not in st.session_state:
|
41 |
+
print("🔄 Initialisation de MistralAPI...")
|
42 |
+
st.session_state.mistral_instance = MistralAPI(model=st.session_state["mistral_model"])
|
43 |
+
print("✅ MistralAPI initialisé avec succès.")
|
44 |
+
|
45 |
+
mistral = st.session_state.mistral_instance # Récupérer l'instance stockée
|
46 |
+
|
47 |
+
# 🔹 Initialisation de la sécurité (Guardrail)
|
48 |
+
try:
|
49 |
+
guardrail = Guardrail()
|
50 |
+
except Exception as e:
|
51 |
+
st.error(f"❌ Guardrail introuvable. Veuillez relancer l'appli ou contacter l'équipe de développement. Détails : {e}")
|
52 |
+
st.stop()
|
53 |
+
|
54 |
+
# 🔹 Chargement de la base de données
|
55 |
+
db_manager = st.session_state["db_manager"]
|
56 |
+
user_id = st.session_state["user_id"]
|
57 |
+
|
58 |
+
if "chatbot_suggestions" not in st.session_state:
|
59 |
+
st.session_state["chatbot_suggestions"] = load_chatbot_suggestions(db_manager, user_id)
|
60 |
+
|
61 |
+
# 🔹 Sidebar : Bouton "➕ Nouveau chat" en haut
|
62 |
+
st.sidebar.title("🗂️ Historique")
|
63 |
+
if st.sidebar.button("➕ Nouveau chat"):
|
64 |
+
title = "Nouvelle conversation"
|
65 |
+
new_conversation_id = create_conversation(db_manager, title, user_id)
|
66 |
+
st.session_state.id_conversation = new_conversation_id
|
67 |
+
st.session_state.messages = []
|
68 |
+
st.rerun()
|
69 |
+
|
70 |
+
# 🔹 Charger l'historique des conversations
|
71 |
+
conversation_history = load_conversations(db_manager, user_id) or []
|
72 |
+
|
73 |
+
# 🔹 Sidebar : Affichage de l'historique des conversations avec bouton de suppression
|
74 |
+
for index, conversation in enumerate(conversation_history):
|
75 |
+
id_conversation = conversation['id_conversation']
|
76 |
+
title = conversation['title']
|
77 |
+
key = f"conversation_{id_conversation}_{index}" # Clé unique
|
78 |
+
|
79 |
+
col1, col2 = st.sidebar.columns([0.8, 0.2]) # 🔹 Disposition pour aligner le bouton de suppression
|
80 |
+
|
81 |
+
with col1:
|
82 |
+
if "id_conversation" in st.session_state and st.session_state.id_conversation == id_conversation:
|
83 |
+
st.button(f"🟢 {title}", key=key, disabled=True)
|
84 |
+
else:
|
85 |
+
if st.button(title, key=key):
|
86 |
+
st.session_state.id_conversation = id_conversation
|
87 |
+
st.session_state.messages = load_messages(db_manager, id_conversation)
|
88 |
+
update_conversation(db_manager, id_conversation=st.session_state.id_conversation, id_utilisateur=user_id)
|
89 |
+
st.rerun()
|
90 |
+
|
91 |
+
with col2:
|
92 |
+
if st.button("🗑️", key=f"delete_{id_conversation}"): # 🔥 Bouton de suppression
|
93 |
+
delete_conversation(db_manager, id_conversation)
|
94 |
+
st.rerun() # Rafraîchir après suppression
|
95 |
+
|
96 |
+
# 🔹 Affichage des messages précédents dans l'interface
|
97 |
+
for message in st.session_state.messages:
|
98 |
+
timestamp = message["timestamp"]
|
99 |
+
latency = message["temps_traitement"]
|
100 |
+
latency_text = f"⏳ {latency} sec" if latency is not None else ""
|
101 |
+
|
102 |
+
if message["role"] == "user":
|
103 |
+
with st.chat_message("user"):
|
104 |
+
st.markdown(message["content"])
|
105 |
+
st.caption(f"📅 {timestamp} {latency_text}")
|
106 |
+
|
107 |
+
elif message["role"] == "assistant":
|
108 |
+
with st.chat_message("assistant", avatar="client/assets/avatar_bot_big.jpg"):
|
109 |
+
st.markdown(message["content"])
|
110 |
+
st.caption(f"📅 {timestamp} {latency_text}") # 🔹 Ajout de l'heure et de la latence
|
111 |
+
|
112 |
+
|
113 |
+
# 🔹 Interface utilisateur - Zone d'entrée utilisateur
|
114 |
+
if prompt := st.chat_input("Dîtes quelque-chose"):
|
115 |
+
|
116 |
+
# Calcul du nombre de tokens avant d'envoyer le prompt
|
117 |
+
input_tokens = mistral.count_input_tokens([{"content": prompt}])
|
118 |
+
print(f"✅ Nombre de tokens en entrée : {input_tokens}")
|
119 |
+
|
120 |
+
# 🔸 Vérifier si le message est dans une langue supportée par le guardrail
|
121 |
+
is_supported = guardrail.analyze_language(prompt)
|
122 |
+
if not is_supported:
|
123 |
+
st.warning("⚠️ Votre message n'est pas rédigé dans les langues actuellement supportées (FR, EN, DE, ES).")
|
124 |
+
st.warning("Si votre message est pourtant dans une des langues supportées, le reformuler ou l'allonger peut être utile.")
|
125 |
+
st.stop()
|
126 |
+
|
127 |
+
# 🔸 Vérifier la sécurité du message
|
128 |
+
is_safe = guardrail.analyze_query(prompt)
|
129 |
+
|
130 |
+
# 🔸 Afficher immédiatement le message de l'utilisateur
|
131 |
+
with st.chat_message("user"):
|
132 |
+
st.markdown(prompt)
|
133 |
+
|
134 |
+
if st.session_state.id_conversation is None:
|
135 |
+
# Générer un titre basé sur le premier message
|
136 |
+
title = mistral.auto_wrap(text=prompt, temperature=0.5)
|
137 |
+
st.session_state.id_conversation = create_conversation(db_manager, title, user_id)
|
138 |
+
else:
|
139 |
+
# Vérifier si le titre est encore "Nouvelle conversation" et le mettre à jour si nécessaire
|
140 |
+
current_title = get_conversation_title(db_manager, st.session_state.id_conversation)
|
141 |
+
if current_title == "Nouvelle conversation":
|
142 |
+
new_title = mistral.auto_wrap(text=prompt, temperature=0.5)
|
143 |
+
update_conversation_title(db_manager, st.session_state.id_conversation, new_title)
|
144 |
+
|
145 |
+
# 🔸 Ajouter le message à l'historique et l'enregistrer dans la base de données
|
146 |
+
st.session_state.messages.append({"role": "user", "content": prompt, "temps_traitement":None, "total_cout":None, "impact_eco":None, "timestamp":datetime.now().strftime("%Y-%m-%d %H:%M:%S")})
|
147 |
+
save_message(db_manager, st.session_state.id_conversation, role="user", content=prompt, temps_traitement=None, total_cout=None, impact_eco=None)
|
148 |
+
|
149 |
+
# 🔸 Si le message est interdit, afficher l'alerte mais NE PAS l'envoyer à Mistral
|
150 |
+
if not is_safe:
|
151 |
+
st.warning("⚠️ Votre message ne respecte pas nos consignes.")
|
152 |
+
st.stop() # Arrêter l'exécution ici pour ne PAS envoyer à Mistral
|
153 |
+
|
154 |
+
retries = 0
|
155 |
+
max_retries = 3
|
156 |
+
|
157 |
+
# 🔹 Générer une réponse avec Mistral et RAG
|
158 |
+
with st.chat_message("assistant", avatar="client/assets/avatar_bot_big.jpg"):
|
159 |
+
retries = 0
|
160 |
+
max_retries = 3
|
161 |
+
while retries < max_retries:
|
162 |
+
response = ""
|
163 |
+
response_placeholder = st.empty()
|
164 |
+
try:
|
165 |
+
start_time = time.time() # 🔹 Début du chronomètre
|
166 |
+
|
167 |
+
print("🔄 Génération de réponse en cours...")
|
168 |
+
stream_response = mistral.stream(st.session_state.messages, temperature=0.5)
|
169 |
+
|
170 |
+
# for chunk in stream_response:
|
171 |
+
# response += chunk.data.choices[0].delta.content
|
172 |
+
# if response == "Injection":
|
173 |
+
# st.warning("⚠️ Votre message ne respecte pas nos consignes.")
|
174 |
+
# guardrail.incremental_learning(prompt, 1) # 1 car injection. Le tuning ne se fait que sur les injections
|
175 |
+
# st.stop()
|
176 |
+
# else: # on réinitialise
|
177 |
+
# response = ""
|
178 |
+
|
179 |
+
# Compteur pour les tokens de sortie
|
180 |
+
output_tokens = 0
|
181 |
+
|
182 |
+
for chunk in stream_response:
|
183 |
+
response += chunk.data.choices[0].delta.content
|
184 |
+
response_placeholder.markdown(response)
|
185 |
+
# Calculer les tokens pour ce morceau de réponse
|
186 |
+
output_tokens += mistral.count_output_tokens(chunk.data.choices[0].delta.content)
|
187 |
+
|
188 |
+
time.sleep(0.03)
|
189 |
+
|
190 |
+
# 🔹 Vérifier si la réponse contient une suggestion de recette
|
191 |
+
|
192 |
+
# 🔹 Vérifier si la réponse contient des suggestions de recettes
|
193 |
+
keywords = ["recette", "plat", "préparer", "ingrédients"]
|
194 |
+
|
195 |
+
for word in keywords:
|
196 |
+
if word in response.lower():
|
197 |
+
try:
|
198 |
+
# 🔹 Extraire plusieurs titres de recettes
|
199 |
+
suggested_recipes = mistral.extract_multiple_recipes(text=response, temperature=0.3)
|
200 |
+
|
201 |
+
# Vérifier et initialiser la liste des suggestions
|
202 |
+
if "chatbot_suggestions" not in st.session_state:
|
203 |
+
st.session_state["chatbot_suggestions"] = []
|
204 |
+
|
205 |
+
# Ajouter uniquement les recettes qui ne sont pas déjà stockées
|
206 |
+
new_recipes = [recipe for recipe in suggested_recipes if recipe not in st.session_state["chatbot_suggestions"]]
|
207 |
+
|
208 |
+
if new_recipes:
|
209 |
+
st.session_state["chatbot_suggestions"].extend(new_recipes) # Ajouter plusieurs recettes
|
210 |
+
print(f"✅ {len(new_recipes)} nouvelles suggestions ajoutées.")
|
211 |
+
|
212 |
+
# 🔹 Sauvegarder les suggestions dans la BDD
|
213 |
+
save_chatbot_suggestions(db_manager, user_id, new_recipes)
|
214 |
+
except Exception as e:
|
215 |
+
print(f"❌ Erreur lors de l'extraction des suggestions : {e}")
|
216 |
+
|
217 |
+
break # On ne veut ajouter qu'une seule suggestion par réponse
|
218 |
+
|
219 |
+
|
220 |
+
|
221 |
+
|
222 |
+
end_time = time.time() # 🔹 Fin du chronomètre
|
223 |
+
latency = round(end_time - start_time, 2) # 🔹 Calcul de la latence
|
224 |
+
|
225 |
+
print(f"✅ Réponse générée en {latency} secondes.")
|
226 |
+
print(f"✅ Nombre de tokens de sortie : {output_tokens}")
|
227 |
+
except Exception as e:
|
228 |
+
if hasattr(e, "status_code") and e.status_code == 429:
|
229 |
+
retries += 1
|
230 |
+
wait_time = 2 ** retries
|
231 |
+
stream_response = None
|
232 |
+
print(f"⚠️ Rate limit atteint. Nouvel essai dans {wait_time} secondes...")
|
233 |
+
time.sleep(wait_time)
|
234 |
+
else:
|
235 |
+
st.error(f"❌ Erreur : Impossible de traiter votre demande. Détails : {str(e)}")
|
236 |
+
response_placeholder.markdown("❌ Erreur lors de la génération de la réponse.")
|
237 |
+
st.stop()
|
238 |
+
|
239 |
+
if response is not None:
|
240 |
+
break
|
241 |
+
|
242 |
+
if retries >= max_retries:
|
243 |
+
st.error("❌ Impossible d'obtenir une réponse après plusieurs tentatives.")
|
244 |
+
response = "❌ Erreur : Limite de requêtes atteinte."
|
245 |
+
|
246 |
+
# 🔹 Calcul du coût des tokens d'entrée et de sortie
|
247 |
+
input_cost_per_token = 0.0004 # Coût par token d'entrée
|
248 |
+
output_cost_per_token = 0.0005 # Coût par token de sortie
|
249 |
+
|
250 |
+
# Calcul du coût des tokens d'entrée et de sortie
|
251 |
+
input_cost = input_tokens * input_cost_per_token
|
252 |
+
output_cost = output_tokens * output_cost_per_token
|
253 |
+
|
254 |
+
# Calcul du coût total
|
255 |
+
total_cost = input_cost + output_cost
|
256 |
+
print(f"✅ Coût total de la requête : {total_cost} USD")
|
257 |
+
|
258 |
+
# Facteur d'émission (en grammes de CO₂ par token)
|
259 |
+
EMISSIONS_PER_TOKEN = 0.00005 # estimation
|
260 |
+
|
261 |
+
# Calcul de l'empreinte carbone pour les tokens d'entrée et de sortie
|
262 |
+
input_emissions = input_tokens * EMISSIONS_PER_TOKEN
|
263 |
+
output_emissions = output_tokens * EMISSIONS_PER_TOKEN
|
264 |
+
total_emissions = input_emissions + output_emissions
|
265 |
+
print(f"🌍 Impact écologique total de la requête : {total_emissions:.4f} g CO₂")
|
266 |
+
|
267 |
+
|
268 |
+
# 🔹 Enregistrer la réponse de l'assistant
|
269 |
+
st.session_state.messages.append({"role": "assistant", "content": response, "temps_traitement":latency, "timestamp":datetime.now().strftime("%Y-%m-%d %H:%M:%S")})
|
270 |
+
save_message(db_manager, st.session_state.id_conversation, role="assistant", content=response, temps_traitement=latency, total_cout=total_cost, impact_eco=total_emissions)
|
client/pages/nutri_old.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import time
|
3 |
+
from server.mistral.mistralapi import MistralAPI
|
4 |
+
from server.security.prompt_guard import Guardrail
|
5 |
+
from projects.LLM_project.server.db.db_sqlite import (
|
6 |
+
load_conversations,
|
7 |
+
load_messages,
|
8 |
+
update_conversation,
|
9 |
+
create_conversation,
|
10 |
+
save_message)
|
11 |
+
|
12 |
+
|
13 |
+
def nutri_page():
|
14 |
+
# Interface Streamlit
|
15 |
+
# st.set_page_config(page_title="Nutrigénie", layout="wide")
|
16 |
+
# Section pour afficher l'historique de la conversation
|
17 |
+
conversation_history = load_conversations()
|
18 |
+
# st.sidebar.title("Navigation")
|
19 |
+
st.sidebar.title("Historique")
|
20 |
+
|
21 |
+
for conversation_id, _, title in conversation_history:
|
22 |
+
if "conversation_id" in st.session_state and st.session_state.conversation_id == conversation_id:
|
23 |
+
# Bouton désactivé pour la conversation active
|
24 |
+
st.sidebar.button(f"🟢 {title}", key=f"conversation_{conversation_id}", disabled=True)
|
25 |
+
else:
|
26 |
+
# Bouton actif pour les autres conversations
|
27 |
+
if st.sidebar.button(title, key=f"conversation_{conversation_id}"):
|
28 |
+
# Charger la conversation sélectionnée
|
29 |
+
st.session_state.conversation_id = conversation_id
|
30 |
+
st.session_state.messages = load_messages(conversation_id)
|
31 |
+
update_conversation(conversation_id=st.session_state.conversation_id)
|
32 |
+
st.rerun()
|
33 |
+
|
34 |
+
st.title("Parlez au Nutrigénie")
|
35 |
+
# Historique de la conversation
|
36 |
+
if "conversation_id" not in st.session_state:
|
37 |
+
st.session_state.conversation_id = None
|
38 |
+
|
39 |
+
# Affichage des messages précédents
|
40 |
+
if st.session_state.conversation_id:
|
41 |
+
st.session_state.messages = load_messages(st.session_state.conversation_id)
|
42 |
+
|
43 |
+
if "mistral_model" not in st.session_state:
|
44 |
+
st.session_state["mistral_model"] = "mistral-large-latest"
|
45 |
+
|
46 |
+
if "messages" not in st.session_state:
|
47 |
+
st.session_state.messages = []
|
48 |
+
|
49 |
+
for message in st.session_state.messages:
|
50 |
+
if message["role"] == "user":
|
51 |
+
with st.chat_message("user"): # Utilisez votre avatar utilisateur
|
52 |
+
st.markdown(message["content"])
|
53 |
+
elif message["role"] == "assistant":
|
54 |
+
with st.chat_message("assistant", avatar="client/assets/avatar_bot_big.jpg"): # Avatar personnalisé pour l'assistant
|
55 |
+
st.markdown(message["content"])
|
56 |
+
|
57 |
+
|
58 |
+
|
59 |
+
# Initialisation de Mistral
|
60 |
+
mistral = MistralAPI(model=st.session_state["mistral_model"])
|
61 |
+
if prompt := st.chat_input("Dîtes quelque-chose"):
|
62 |
+
if st.session_state.conversation_id is None:
|
63 |
+
retries = 0
|
64 |
+
max_retries = 3
|
65 |
+
while retries < max_retries:
|
66 |
+
try:
|
67 |
+
title = mistral.auto_wrap(prompt) # Utiliser le début du message comme titre
|
68 |
+
except Exception as e:
|
69 |
+
# Vérifier explicitement si l'erreur est une 429 (rate limit exceeded)
|
70 |
+
if hasattr(e, "status_code") and e.status_code == 429:
|
71 |
+
retries += 1
|
72 |
+
wait_time = 2 ** retries # Temps d'attente exponentiel
|
73 |
+
st.warning(f"Limite de requêtes atteinte (429). Nouvel essai dans {wait_time} secondes...")
|
74 |
+
time.sleep(wait_time)
|
75 |
+
else:
|
76 |
+
# Gérer d'autres types d'erreurs
|
77 |
+
st.error(f"Erreur : Impossible de traiter votre demande (déetails : {str(e)})")
|
78 |
+
st.stop()
|
79 |
+
if title is not None:
|
80 |
+
break
|
81 |
+
# Si tous les retries échouent, retourner un message d'erreur
|
82 |
+
if title is None:
|
83 |
+
st.error("Impossible d'obtenir une réponse. Limite de requêtes atteinte après plusieurs tentatives.")
|
84 |
+
st.stop()
|
85 |
+
|
86 |
+
|
87 |
+
st.session_state.conversation_id = create_conversation(title=title)
|
88 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
89 |
+
save_message(conversation_id=st.session_state.conversation_id, role="user", content=prompt)
|
90 |
+
|
91 |
+
with st.chat_message("user"):
|
92 |
+
st.markdown(prompt)
|
93 |
+
|
94 |
+
###################
|
95 |
+
#### Guardrail ####
|
96 |
+
###################
|
97 |
+
|
98 |
+
try:
|
99 |
+
guardrail = Guardrail()
|
100 |
+
except Exception as e:
|
101 |
+
st.error(f"Guardrail introuvable. Veuillez relancer le conteneur pour recréer le guardrail. Détails : {e}")
|
102 |
+
st.stop()
|
103 |
+
# is_supported = await guardrail.analyze_language(prompt)
|
104 |
+
# if not is_supported:
|
105 |
+
# st.warning("To use our bot in a safe manner, you must do the conversation in either english, french, german or spanish. If necessary you may use an online translator.")
|
106 |
+
# st.stop()
|
107 |
+
is_safe = guardrail.analyze_query(prompt)
|
108 |
+
if not is_safe:
|
109 |
+
st.warning("Le prompt semble violer nos considérations éthiques. Nous vous invitons à poser une autre question.")
|
110 |
+
st.stop()
|
111 |
+
|
112 |
+
####################
|
113 |
+
###### PROMPT ######
|
114 |
+
####################
|
115 |
+
|
116 |
+
with st.chat_message("assistant", avatar = "client/assets/avatar_bot_big.jpg"):
|
117 |
+
retries = 0
|
118 |
+
max_retries = 3
|
119 |
+
while retries < max_retries:
|
120 |
+
response = ""
|
121 |
+
response_placeholder = st.empty()
|
122 |
+
try:
|
123 |
+
stream_response = mistral.stream(st.session_state.messages) # Utiliser le début du message comme titr
|
124 |
+
# Traiter la réponse en streaming
|
125 |
+
for chunk in stream_response:
|
126 |
+
response += chunk.data.choices[0].delta.content
|
127 |
+
response_placeholder.markdown(response)
|
128 |
+
time.sleep(0.03) # Petit délai pour simuler le flux en temps réel
|
129 |
+
except Exception as e:
|
130 |
+
if hasattr(e, "status_code") and e.status_code == 429:
|
131 |
+
# Gestion explicite de l'erreur 429 (Rate Limit Exceeded)
|
132 |
+
retries += 1
|
133 |
+
wait_time = 2 ** retries # Délai exponentiel : 2, 4, 8 secondes
|
134 |
+
st.warning(f"Limite de requêtes atteinte (429). Nouvel essai dans {wait_time} secondes...")
|
135 |
+
time.sleep(wait_time)
|
136 |
+
else:
|
137 |
+
# Gestion d'autres types d'erreurs
|
138 |
+
st.error(f"Erreur : Impossible de traiter votre demande (détails : {str(e)})")
|
139 |
+
response_placeholder.markdown("Erreur lors de la génération de la réponse.")
|
140 |
+
st.stop()
|
141 |
+
if stream_response is not None:
|
142 |
+
break # Si le streaming réussit, on sort de la boucle
|
143 |
+
# Si toutes les tentatives échouent, message d'erreur final
|
144 |
+
if retries >= max_retries:
|
145 |
+
st.error("Impossible d'obtenir une réponse. Limite de requêtes atteinte après plusieurs tentatives.")
|
146 |
+
response = "Erreur : Limite de requêtes atteinte après plusieurs tentatives."
|
147 |
+
st.session_state.messages.append({"role": "assistant", "content": response})
|
148 |
+
save_message(conversation_id=st.session_state.conversation_id, role="assistant", content=response)
|
client/pages/sign_in.py
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from werkzeug.security import check_password_hash
|
3 |
+
import os
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
|
6 |
+
def sign_in(navigate_to):
|
7 |
+
|
8 |
+
st.markdown(
|
9 |
+
"""
|
10 |
+
<style>
|
11 |
+
|
12 |
+
@keyframes fadeIn {
|
13 |
+
0% { opacity: 0; }
|
14 |
+
100% { opacity: 1; }
|
15 |
+
}
|
16 |
+
.stImage {
|
17 |
+
animation: fadeIn 1s ease-in;
|
18 |
+
}
|
19 |
+
|
20 |
+
.stTextInput > div > div > input {
|
21 |
+
font-size: 16px;
|
22 |
+
border-radius: 8px;
|
23 |
+
border: 1px solid #6A5ACD;
|
24 |
+
}
|
25 |
+
|
26 |
+
.stButton > button {
|
27 |
+
background-color: #6A5ACD;
|
28 |
+
color: white;
|
29 |
+
font-size: 18px;
|
30 |
+
padding: 10px;
|
31 |
+
border-radius: 8px;
|
32 |
+
border: none;
|
33 |
+
cursor: pointer;
|
34 |
+
transition: 0.3s;
|
35 |
+
}
|
36 |
+
|
37 |
+
|
38 |
+
|
39 |
+
.stAlert {
|
40 |
+
text-align: center;
|
41 |
+
font-weight: bold;
|
42 |
+
}
|
43 |
+
|
44 |
+
</style>
|
45 |
+
""",
|
46 |
+
unsafe_allow_html=True
|
47 |
+
)
|
48 |
+
|
49 |
+
logo_path = "assets/logo.png"
|
50 |
+
|
51 |
+
#centrer le logo
|
52 |
+
col1, col2, col3 = st.columns([1.5, 1.5, 1])
|
53 |
+
|
54 |
+
with col2:
|
55 |
+
if os.path.exists(logo_path):
|
56 |
+
st.image(logo_path, width=150)
|
57 |
+
|
58 |
+
# Récupération du gestionnaire de base de données (déjà stocké en session)
|
59 |
+
db_manager = st.session_state.get("db_manager")
|
60 |
+
|
61 |
+
st.title("Connexion")
|
62 |
+
|
63 |
+
# Champs de connexion
|
64 |
+
login = st.text_input("👤 Pseudo")
|
65 |
+
password = st.text_input("🔒 Mot de passe", type="password")
|
66 |
+
|
67 |
+
|
68 |
+
# Bouton de connexion
|
69 |
+
if st.button("Se connecter"):
|
70 |
+
# Vérification des identifiants en base de données
|
71 |
+
user = db_manager.fetch_by_condition("utilisateurs", "login = %s", (login,))
|
72 |
+
print("user",user)
|
73 |
+
|
74 |
+
if user:
|
75 |
+
user = user[0]
|
76 |
+
user_id = user["id_utilisateur"] # Récupération de l'ID utilisateur
|
77 |
+
hashed_password = user["mot_de_passe"] # Récupération du mot de passe hashé
|
78 |
+
|
79 |
+
# Vérification du mot de passe
|
80 |
+
if check_password_hash(hashed_password, password):
|
81 |
+
# Stocker les infos utilisateur dans la session
|
82 |
+
st.session_state["logged_in"] = True
|
83 |
+
st.session_state["user"] = login
|
84 |
+
st.session_state["user_id"] = user_id
|
85 |
+
|
86 |
+
# Message de confirmation
|
87 |
+
st.success("✅ Connexion réussie !")
|
88 |
+
navigate_to("accueil") # Redirige vers la page d'accueil
|
89 |
+
else:
|
90 |
+
st.error("❌ Mot de passe incorrect.")
|
91 |
+
else:
|
92 |
+
st.error("❌ Utilisateur non trouvé.")
|
93 |
+
|
94 |
+
# Lien vers l'inscription
|
95 |
+
if st.button("Pas de compte ? Inscrivez-vous.") :
|
96 |
+
navigate_to("inscription") # Redirection vers l'inscription
|
97 |
+
|
98 |
+
|
99 |
+
"""
|
100 |
+
# Forcer la connexion pendant le développement (commenter cette partie si nécessaire)
|
101 |
+
if 'logged_in' not in st.session_state or not st.session_state['logged_in']:
|
102 |
+
# Simuler une connexion (utiliser des valeurs par défaut)
|
103 |
+
st.session_state["logged_in"] = True
|
104 |
+
st.session_state["user"] = "dev_user"
|
105 |
+
st.session_state["user_id"] = 1 # ID fictif pour le développement
|
106 |
+
st.success("✅ Connexion simulée pour le développement !")
|
107 |
+
navigate_to("accueil") # Redirige vers la page d'accueil
|
108 |
+
else:
|
109 |
+
# Logique de connexion normale (en production)
|
110 |
+
login = st.text_input("👤 Pseudo")
|
111 |
+
password = st.text_input("🔒 Mot de passe", type="password")
|
112 |
+
|
113 |
+
if st.button("Se connecter"):
|
114 |
+
user = db_manager.fetch_by_condition("utilisateurs", "login = %s", (login,))
|
115 |
+
|
116 |
+
if user:
|
117 |
+
user_id = user[0][0]
|
118 |
+
hashed_password = user[0][2]
|
119 |
+
|
120 |
+
if check_password_hash(hashed_password, password):
|
121 |
+
st.session_state["logged_in"] = True
|
122 |
+
st.session_state["user"] = login
|
123 |
+
st.session_state["user_id"] = user_id
|
124 |
+
st.success("✅ Connexion réussie !")
|
125 |
+
navigate_to("accueil")
|
126 |
+
else:
|
127 |
+
st.error("❌ Mot de passe incorrect.")
|
128 |
+
else:
|
129 |
+
st.error("❌ Utilisateur non trouvé.")
|
130 |
+
|
131 |
+
if st.button("Pas de compte ? Inscrivez-vous."):
|
132 |
+
navigate_to("inscription")
|
133 |
+
"""
|
client/pages/sign_up.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from werkzeug.security import generate_password_hash
|
3 |
+
import os
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
|
6 |
+
import sys
|
7 |
+
|
8 |
+
def sign_up(navigate_to):
|
9 |
+
db_manager = st.session_state.get("db_manager")
|
10 |
+
st.title("Inscription")
|
11 |
+
|
12 |
+
# Champs obligatoires
|
13 |
+
login = st.text_input("Pseudo")
|
14 |
+
email = st.text_input("Email")
|
15 |
+
password = st.text_input("Mot de passe", type="password")
|
16 |
+
confirm_password = st.text_input("Confirmer le mot de passe", type="password")
|
17 |
+
|
18 |
+
# Lien pour rediriger vers la page de connexion
|
19 |
+
if st.button("Déjà un compte ? connectez-vous."):
|
20 |
+
navigate_to("connexion")
|
21 |
+
|
22 |
+
# Vérification des champs obligatoires
|
23 |
+
if st.button("Créer un compte"):
|
24 |
+
if not login or not email or not password or not confirm_password:
|
25 |
+
st.error("Tous les champs sont obligatoires.")
|
26 |
+
elif password != confirm_password:
|
27 |
+
st.error("Les mots de passe ne correspondent pas.")
|
28 |
+
else:
|
29 |
+
hashed_password = generate_password_hash(password)
|
30 |
+
try:
|
31 |
+
# Insérer l'utilisateur dans la base de données
|
32 |
+
db_manager.insert_data_from_dict("utilisateurs", [{
|
33 |
+
"login": login,
|
34 |
+
"email": email,
|
35 |
+
"mot_de_passe": hashed_password
|
36 |
+
}], "id_utilisateur")
|
37 |
+
|
38 |
+
st.success("Compte créé avec succès. Vous pouvez vous connecter.")
|
39 |
+
st.session_state["current_page"] = "connexion"
|
40 |
+
except Exception as e:
|
41 |
+
st.error(f"Erreur lors de l'inscription : {e}")
|
client/pages/user.py
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from client.pages.user__info_perso import info_perso
|
3 |
+
from client.pages.user__mealplan import mealplan
|
4 |
+
from client.pages.user__course_list import course_list
|
5 |
+
from client.pages.user__favoris import favoris
|
6 |
+
|
7 |
+
|
8 |
+
# Appliquer le style personnalisé aux headers
|
9 |
+
st.markdown("""
|
10 |
+
<style>
|
11 |
+
/* Style global pour les headers */
|
12 |
+
h3 {
|
13 |
+
font-size: 20px;
|
14 |
+
font-family: New Icon;
|
15 |
+
font-weight: 700;
|
16 |
+
}
|
17 |
+
|
18 |
+
h2 {
|
19 |
+
font-size: 2rem;
|
20 |
+
color: #2a4b47;
|
21 |
+
}
|
22 |
+
|
23 |
+
.welcome-title {
|
24 |
+
font-size: 2.5rem;
|
25 |
+
font-weight: 700;
|
26 |
+
color: #2a4b47;
|
27 |
+
text-align: center;
|
28 |
+
animation: fadeIn 2s ease-out;
|
29 |
+
}
|
30 |
+
|
31 |
+
@keyframes fadeIn {
|
32 |
+
0% { opacity: 0; }
|
33 |
+
100% { opacity: 1; }
|
34 |
+
}
|
35 |
+
|
36 |
+
.user-name {
|
37 |
+
color: #4e7a63;
|
38 |
+
font-size: 3rem;
|
39 |
+
font-weight: bold;
|
40 |
+
animation: nameAnimation 2s ease-out;
|
41 |
+
font-family: New Icon;
|
42 |
+
}
|
43 |
+
</style>
|
44 |
+
""", unsafe_allow_html=True)
|
45 |
+
|
46 |
+
# Affichage du message de bienvenue
|
47 |
+
st.markdown(f"""
|
48 |
+
<h2 class="welcome-title">
|
49 |
+
Bienvenue sur NutriGénie <span class="user-name">{st.session_state['user']}</span> 🍽️!
|
50 |
+
</h2>
|
51 |
+
""", unsafe_allow_html=True)
|
52 |
+
|
53 |
+
# Définition des onglets horizontaux
|
54 |
+
tabs = st.tabs(["🧑💼 Informations personnelles ", "🍽️ Meal Plan", "🛒 Liste des courses", "⭐ Favoris"])
|
55 |
+
|
56 |
+
# Onglet 1 : Informations personnelles
|
57 |
+
with tabs[0]:
|
58 |
+
st.markdown('<h3 class="stHeader">🧑💼 Informations personnelles</h3>', unsafe_allow_html=True)
|
59 |
+
info_perso() # Charger la page `info_perso.py`
|
60 |
+
|
61 |
+
# Onglet 2 : Meal Plan
|
62 |
+
with tabs[1]:
|
63 |
+
st.markdown('<h3 class="stHeader">🍽️ Meal Plan</h3>', unsafe_allow_html=True)
|
64 |
+
mealplan() # Charger la page `mealplan.py`
|
65 |
+
|
66 |
+
# Onglet 3 : Liste des courses
|
67 |
+
with tabs[2]:
|
68 |
+
st.markdown('<h3 class="stHeader">🛒 Liste des courses</h3>', unsafe_allow_html=True)
|
69 |
+
course_list() # Charger la page `course_list.py`
|
70 |
+
|
71 |
+
# Onglet 4 : Favoris
|
72 |
+
with tabs[3]:
|
73 |
+
st.markdown('<h3 class="stHeader">⭐ Favoris</h3>', unsafe_allow_html=True)
|
74 |
+
favoris() # Charger la page `favoris.py`
|
client/pages/user__course_list.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from server.db.db import get_ingredients
|
3 |
+
|
4 |
+
|
5 |
+
# Page des courses
|
6 |
+
def course_list():
|
7 |
+
st.write("Voici votre liste des courses.")
|
8 |
+
|
9 |
+
ingredients_list = get_ingredients()
|
10 |
+
|
11 |
+
if ingredients_list:
|
12 |
+
for ingredient in ingredients_list:
|
13 |
+
st.write(f"🍎 {ingredient[0]}")
|
14 |
+
else:
|
15 |
+
st.write("Aucun ingrédient trouvé.")
|
16 |
+
|
17 |
+
st.checkbox("Ajouter un nouvel article")
|
18 |
+
|
19 |
+
|
client/pages/user__favoris.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
def favoris():
|
4 |
+
st.subheader("Favoris")
|
5 |
+
st.write("Voici vos recettes ou restaurants favoris.")
|
6 |
+
st.button("Ajouter aux favoris")
|
client/pages/user__info_perso.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
def info_perso():
|
4 |
+
# Récupérer le gestionnaire de base de données
|
5 |
+
db_manager = st.session_state.get("db_manager")
|
6 |
+
|
7 |
+
if db_manager is None:
|
8 |
+
st.error("Erreur : DBManager n'est pas initialisé.")
|
9 |
+
else:
|
10 |
+
# Récupérer les informations de l'utilisateur connecté
|
11 |
+
user_id = st.session_state['user_id']
|
12 |
+
query = f"SELECT * FROM utilisateurs WHERE id_utilisateur = {user_id}"
|
13 |
+
user_info = db_manager.query(query)
|
14 |
+
|
15 |
+
|
16 |
+
if not user_info:
|
17 |
+
st.error("Utilisateur non trouvé.")
|
18 |
+
else:
|
19 |
+
user_info = user_info[0] # Récupérer la première ligne (unique utilisateur)
|
20 |
+
print(user_info) # Récupérer et afficher les informations de l'utilisateur
|
21 |
+
|
22 |
+
# Premier formulaire (Nom, Email, Mot de passe)
|
23 |
+
col1, col2, col3 = st.columns(3)
|
24 |
+
with col1:
|
25 |
+
nom = st.text_input("Nom", user_info["login"])
|
26 |
+
with col2:
|
27 |
+
email = st.text_input("Email", user_info["email"])
|
28 |
+
|
29 |
+
|
30 |
+
# Deuxième formulaire (Objectifs nutritionnels, Poids, Taille)
|
31 |
+
col1, col2, col3 = st.columns(3)
|
32 |
+
with col1:
|
33 |
+
|
34 |
+
objectifs_nutritionnels_val = user_info["objectifs_nutritionnels"] if user_info["objectifs_nutritionnels"] else "Vide"
|
35 |
+
objectifs_nutritionnels = st.selectbox(
|
36 |
+
"Objectifs nutritionnels",
|
37 |
+
["Prise de masse", "Tonification", "Perdre du poids", "Vide"],
|
38 |
+
index=["Prise de masse", "Tonification", "Perdre du poids", "Vide"].index(objectifs_nutritionnels_val)
|
39 |
+
)
|
40 |
+
with col2:
|
41 |
+
poids = st.number_input("Poids (kg)", value=user_info["poids"], step=1)
|
42 |
+
with col3:
|
43 |
+
taille = st.number_input("Taille (cm)", value=user_info["taille"], step=1)
|
44 |
+
|
45 |
+
# Troisième formulaire (Régime particulier, Activité physique, Objectif calorique)
|
46 |
+
col1, col2, col3 = st.columns(3)
|
47 |
+
with col1:
|
48 |
+
regime_particulier = st.text_area("Régime particulier", user_info["regime_particulier"])
|
49 |
+
with col2:
|
50 |
+
# Traitement de la valeur vide pour l'activité physique
|
51 |
+
activite_physique_val = user_info["activite_physique"] if user_info["activite_physique"] else "Vide"
|
52 |
+
activite_physique = st.selectbox(
|
53 |
+
"Activité physique",
|
54 |
+
["Sédentaire", "Légère", "Modérée", "Intense", "Vide"],
|
55 |
+
index=["Sédentaire", "Légère", "Modérée", "Intense", "Vide"].index(activite_physique_val)
|
56 |
+
)
|
57 |
+
with col3:
|
58 |
+
objectif_calorique = st.text_input("Objectif calorique", user_info["objectif_calorique"])
|
59 |
+
|
60 |
+
# Bouton pour sauvegarder toutes les informations
|
61 |
+
if st.button("Tout mettre à jour"):
|
62 |
+
table_name = "utilisateurs"
|
63 |
+
|
64 |
+
|
65 |
+
set_clause = """login = %s, email = %s, objectifs_nutritionnels = %s, poids = %s, taille = %s,
|
66 |
+
regime_particulier = %s, activite_physique = %s, objectif_calorique = %s"""
|
67 |
+
condition = "id_utilisateur = %s"
|
68 |
+
|
69 |
+
# Rassembler les paramètres à passer à la méthode
|
70 |
+
params = (nom, email, objectifs_nutritionnels, poids, taille,
|
71 |
+
regime_particulier, activite_physique, objectif_calorique, user_id)
|
72 |
+
|
73 |
+
# Appel à la méthode update_data avec les bons paramètres
|
74 |
+
db_manager.update_data(table_name, set_clause, condition, params)
|
75 |
+
|
76 |
+
st.success("Informations mises à jour avec succès.")
|
client/pages/user__mealplan.py
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
from server.db.dbmanager import (
|
5 |
+
get_db_manager,
|
6 |
+
load_chatbot_suggestions
|
7 |
+
)
|
8 |
+
db_manager = get_db_manager()
|
9 |
+
user_id = st.session_state["user_id"]
|
10 |
+
|
11 |
+
|
12 |
+
def get_week_dates(year, week):
|
13 |
+
"""Retourne les dates du Lundi au Dimanche pour une semaine donnée."""
|
14 |
+
first_day_of_year = datetime(year, 1, 1)
|
15 |
+
first_monday = first_day_of_year + timedelta(days=(7 - first_day_of_year.weekday())) # Trouver le premier lundi
|
16 |
+
start_date = first_monday + timedelta(weeks=week - 1) # Trouver le début de la semaine sélectionnée
|
17 |
+
return [(start_date + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)] # 7 jours (Lundi → Dimanche)
|
18 |
+
|
19 |
+
def mealplan():
|
20 |
+
# 📆 Sélection de l'année et de la semaine
|
21 |
+
current_year = datetime.now().year
|
22 |
+
current_week = datetime.now().isocalendar()[1]
|
23 |
+
|
24 |
+
year_options = list(range(current_year, current_year - 3, -1)) # Années en ordre décroissant
|
25 |
+
col1, col2 = st.columns([0.5, 0.5]) # Réduction de la largeur des sélecteurs
|
26 |
+
with col1:
|
27 |
+
selected_year = st.selectbox("📅 Année", year_options, index=0)
|
28 |
+
with col2:
|
29 |
+
week_options = [f"Semaine {i}" for i in range(1, 53)]
|
30 |
+
selected_week_label = st.selectbox("📆 Semaine", week_options, index=week_options.index(f"Semaine {current_week}"))
|
31 |
+
selected_week = int(selected_week_label.split(" ")[1])
|
32 |
+
|
33 |
+
# 📌 Génération des 7 jours de la semaine sélectionnée
|
34 |
+
week_dates = get_week_dates(selected_year, selected_week)
|
35 |
+
|
36 |
+
# 📋 Initialisation des repas
|
37 |
+
if "meal_plan" not in st.session_state:
|
38 |
+
st.session_state["meal_plan"] = {}
|
39 |
+
|
40 |
+
for date in week_dates:
|
41 |
+
if date not in st.session_state["meal_plan"]:
|
42 |
+
st.session_state["meal_plan"][date] = {
|
43 |
+
"Petit-déjeuner": "", "Déjeuner": "", "Dîner": ""
|
44 |
+
}
|
45 |
+
|
46 |
+
# 📋 Affichage du planning pour 7 jours
|
47 |
+
st.subheader(f"📆 Planning des repas - {selected_week_label} ({selected_year})")
|
48 |
+
df = pd.DataFrame.from_dict({date: st.session_state["meal_plan"][date] for date in week_dates}, orient="index")
|
49 |
+
df.index = pd.to_datetime(df.index).strftime("%A %d %B") # Affichage clair des jours
|
50 |
+
st.dataframe(df, use_container_width=True)
|
51 |
+
|
52 |
+
# ✅ **Message de validation persistant**
|
53 |
+
if "validation_msg" in st.session_state:
|
54 |
+
st.success(st.session_state["validation_msg"])
|
55 |
+
st.session_state.pop("validation_msg") # Supprimer après affichage pour éviter la persistance infinie
|
56 |
+
|
57 |
+
# 🍽️ **Modification et Suppression d'un Repas**
|
58 |
+
st.subheader("✍️ Modifier un repas")
|
59 |
+
|
60 |
+
selected_day = st.selectbox("📅 Choisissez un jour", week_dates)
|
61 |
+
selected_meal = st.selectbox("🍽️ Sélectionnez le repas", ["Petit-déjeuner", "Déjeuner", "Dîner"])
|
62 |
+
|
63 |
+
# 📌 **Prévisualisation du repas existant**
|
64 |
+
existing_meal = st.session_state["meal_plan"][selected_day][selected_meal]
|
65 |
+
if existing_meal:
|
66 |
+
st.markdown(f"🔹 **Repas actuel pour {selected_meal} :**\n> {existing_meal}")
|
67 |
+
|
68 |
+
with st.form("meal_form"):
|
69 |
+
meal_input = st.text_area(f"Ajoutez un plat pour {selected_meal} ({selected_day})",
|
70 |
+
value=existing_meal) # Pré-rempli avec le repas actuel
|
71 |
+
|
72 |
+
col1, col2 = st.columns([0.7, 0.3])
|
73 |
+
with col1:
|
74 |
+
submitted = st.form_submit_button("✅ Ajouter / Modifier")
|
75 |
+
with col2:
|
76 |
+
deleted = st.form_submit_button("❌ Supprimer le plat")
|
77 |
+
|
78 |
+
if submitted:
|
79 |
+
st.session_state["meal_plan"][selected_day][selected_meal] = meal_input
|
80 |
+
st.session_state["validation_msg"] = f"✅ {selected_meal} ajouté/modifié pour le {selected_day} avec succès !"
|
81 |
+
st.rerun()
|
82 |
+
|
83 |
+
if deleted:
|
84 |
+
st.session_state["meal_plan"][selected_day][selected_meal] = ""
|
85 |
+
st.session_state["validation_msg"] = f"❌ {selected_meal} supprimé pour le {selected_day} !"
|
86 |
+
st.rerun()
|
87 |
+
|
88 |
+
# 🤖 Ajout des suggestions du chatbot
|
89 |
+
st.subheader("🤖 Suggestions du Chatbot")
|
90 |
+
|
91 |
+
# 🔹 Charger les suggestions enregistrées si elles ne sont pas déjà en mémoire
|
92 |
+
if "chatbot_suggestions" not in st.session_state:
|
93 |
+
st.session_state["chatbot_suggestions"] = load_chatbot_suggestions(db_manager, user_id)
|
94 |
+
|
95 |
+
|
96 |
+
if st.session_state["chatbot_suggestions"]:
|
97 |
+
with st.form("chatbot_form"):
|
98 |
+
selected_recipe = st.selectbox("🔍 Sélectionnez une recette", st.session_state["chatbot_suggestions"])
|
99 |
+
selected_day_for_recipe = st.selectbox("📅 Assigner à quel jour ?", week_dates)
|
100 |
+
selected_meal_for_recipe = st.selectbox("🍽️ Assigner à quel repas ?", ["Petit-déjeuner", "Déjeuner", "Dîner"])
|
101 |
+
|
102 |
+
add_recipe = st.form_submit_button("➕ Ajouter la recette au planning")
|
103 |
+
if add_recipe:
|
104 |
+
st.session_state["meal_plan"][selected_day_for_recipe][selected_meal_for_recipe] += f"\n- {selected_recipe}"
|
105 |
+
st.session_state["validation_msg"] = f"✅ Recette ajoutée à {selected_meal_for_recipe} ({selected_day_for_recipe}) avec succès !"
|
106 |
+
st.rerun()
|
107 |
+
else:
|
108 |
+
st.write("⚠️ Aucune suggestion du chatbot pour le moment.")
|
109 |
+
|
110 |
+
# 📤 Exportation du planning en CSV
|
111 |
+
if st.button("📥 Exporter en CSV"):
|
112 |
+
df.to_csv("meal_plan.csv")
|
113 |
+
st.success("✅ Plan exporté en CSV avec succès !")
|