Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- client/pages/dashboard.py +37 -16
- client/pages/home.py +69 -23
- client/pages/nutri.py +166 -66
- client/pages/nutri_old.py +64 -25
- client/pages/sign_in.py +8 -8
- client/pages/sign_up.py +41 -41
- client/pages/user.py +90 -74
- client/pages/user__course_list.py +17 -19
- client/pages/user__favoris.py +7 -6
- client/pages/user__info_perso.py +51 -23
- client/pages/user__mealplan.py +64 -25
client/pages/dashboard.py
CHANGED
@@ -9,7 +9,7 @@ from server.db.db import (
|
|
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
|
@@ -32,7 +32,8 @@ st.markdown(
|
|
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;
|
@@ -101,7 +102,9 @@ st.markdown("""
|
|
101 |
color: #0086b3;
|
102 |
}
|
103 |
</style>
|
104 |
-
""",
|
|
|
|
|
105 |
|
106 |
|
107 |
# Créer des colonnes pour disposer les cards
|
@@ -109,49 +112,67 @@ col1, col2, col3, col4 = st.columns(4)
|
|
109 |
|
110 |
# Card 1
|
111 |
with col1:
|
112 |
-
st.markdown(
|
|
|
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 |
-
""",
|
|
|
|
|
118 |
|
119 |
# Card 2
|
120 |
with col2:
|
121 |
-
st.markdown(
|
|
|
122 |
<div class="card">
|
123 |
<div class="card-title">⏱️ Latence moyenne</div>
|
124 |
<div class="card-value">{average_latency}s</div>
|
125 |
</div>
|
126 |
-
""",
|
|
|
|
|
127 |
|
128 |
# Card 3
|
129 |
with col3:
|
130 |
-
st.markdown(
|
|
|
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 |
-
""",
|
|
|
|
|
136 |
|
137 |
# Card 4
|
138 |
with col4:
|
139 |
-
st.markdown(
|
|
|
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 |
-
""",
|
|
|
|
|
145 |
|
146 |
# Graphique des requêtes par jour
|
147 |
st.markdown("### 📅 Nombre de requêtes par jour")
|
148 |
|
149 |
-
fig = go.Figure(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
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
|
|
|
32 |
)
|
33 |
|
34 |
# Ajouter le CSS pour les cards avec animations et un design moderne
|
35 |
+
st.markdown(
|
36 |
+
"""
|
37 |
<style>
|
38 |
.title-container {
|
39 |
background-color: #6A5ACD;
|
|
|
102 |
color: #0086b3;
|
103 |
}
|
104 |
</style>
|
105 |
+
""",
|
106 |
+
unsafe_allow_html=True,
|
107 |
+
)
|
108 |
|
109 |
|
110 |
# Créer des colonnes pour disposer les cards
|
|
|
112 |
|
113 |
# Card 1
|
114 |
with col1:
|
115 |
+
st.markdown(
|
116 |
+
f"""
|
117 |
<div class="card">
|
118 |
<div class="card-title">🍲 Nombre de recettes suggerées</div>
|
119 |
<div class="card-value">{total_recipes}</div>
|
120 |
</div>
|
121 |
+
""",
|
122 |
+
unsafe_allow_html=True,
|
123 |
+
)
|
124 |
|
125 |
# Card 2
|
126 |
with col2:
|
127 |
+
st.markdown(
|
128 |
+
f"""
|
129 |
<div class="card">
|
130 |
<div class="card-title">⏱️ Latence moyenne</div>
|
131 |
<div class="card-value">{average_latency}s</div>
|
132 |
</div>
|
133 |
+
""",
|
134 |
+
unsafe_allow_html=True,
|
135 |
+
)
|
136 |
|
137 |
# Card 3
|
138 |
with col3:
|
139 |
+
st.markdown(
|
140 |
+
f"""
|
141 |
<div class="card">
|
142 |
<div class="card-title">💸 Coût total des requêtes</div>
|
143 |
<div class="card-value">${total_cost}</div>
|
144 |
</div>
|
145 |
+
""",
|
146 |
+
unsafe_allow_html=True,
|
147 |
+
)
|
148 |
|
149 |
# Card 4
|
150 |
with col4:
|
151 |
+
st.markdown(
|
152 |
+
f"""
|
153 |
<div class="card">
|
154 |
<div class="card-title">🌱 Impact écologique estimé</div>
|
155 |
<div class="card-value">{total_impact} kg CO2</div>
|
156 |
</div>
|
157 |
+
""",
|
158 |
+
unsafe_allow_html=True,
|
159 |
+
)
|
160 |
|
161 |
# Graphique des requêtes par jour
|
162 |
st.markdown("### 📅 Nombre de requêtes par jour")
|
163 |
|
164 |
+
fig = go.Figure(
|
165 |
+
data=[
|
166 |
+
go.Scatter(
|
167 |
+
x=df_requests["date"],
|
168 |
+
y=df_requests["nombre_requetes"],
|
169 |
+
mode="lines+markers",
|
170 |
+
)
|
171 |
+
]
|
172 |
+
)
|
173 |
|
174 |
fig.update_layout(
|
175 |
+
xaxis_title="Date", yaxis_title="Nombre de requêtes", template="plotly_dark"
|
|
|
|
|
176 |
)
|
177 |
|
178 |
+
st.plotly_chart(fig)
|
client/pages/home.py
CHANGED
@@ -3,7 +3,8 @@ import os
|
|
3 |
|
4 |
# def home_page():
|
5 |
|
6 |
-
st.markdown(
|
|
|
7 |
<style>
|
8 |
/*
|
9 |
body, .stApp {
|
@@ -99,39 +100,50 @@ st.markdown("""
|
|
99 |
}
|
100 |
|
101 |
</style>
|
102 |
-
""",
|
|
|
|
|
103 |
|
104 |
-
st.markdown(
|
|
|
105 |
<h2 class="welcome-title">
|
106 |
Bienvenue sur NutriGénie <span class="user-name">{st.session_state['user']}</span> 🍽️!
|
107 |
</h2>
|
108 |
-
""",
|
|
|
|
|
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 |
-
""",
|
|
|
|
|
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 |
-
""",
|
|
|
|
|
135 |
|
136 |
# Fonctionnalités disposées horizontalement par paires
|
137 |
col1, col2 = st.columns(2)
|
@@ -139,7 +151,8 @@ col1, col2 = st.columns(2)
|
|
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>
|
@@ -154,11 +167,14 @@ with col1:
|
|
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 |
-
""",
|
|
|
|
|
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>
|
@@ -173,7 +189,9 @@ with col2:
|
|
173 |
</div>
|
174 |
<p>Obtenez des suggestions de repas en fonction de vos goûts et de vos besoins nutritionnels.</p>
|
175 |
</div>
|
176 |
-
""",
|
|
|
|
|
177 |
|
178 |
# Présentation des membres de l'équipe
|
179 |
st.markdown("<hr>", unsafe_allow_html=True) # Ajoute une ligne de séparation
|
@@ -181,13 +199,38 @@ st.markdown("<hr>", unsafe_allow_html=True) # Ajoute une ligne de séparation
|
|
181 |
st.subheader("Rencontrez notre équipe 👩🍳👨🍳")
|
182 |
|
183 |
# Définition des 5 membres
|
184 |
-
base_path = os.path.join("client","assets")
|
185 |
membres = [
|
186 |
-
{
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
]
|
192 |
|
193 |
# Création des colonnes pour chaque membre
|
@@ -196,9 +239,12 @@ cols = st.columns(5)
|
|
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(
|
|
|
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 |
-
""",
|
|
|
|
|
|
3 |
|
4 |
# def home_page():
|
5 |
|
6 |
+
st.markdown(
|
7 |
+
"""
|
8 |
<style>
|
9 |
/*
|
10 |
body, .stApp {
|
|
|
100 |
}
|
101 |
|
102 |
</style>
|
103 |
+
""",
|
104 |
+
unsafe_allow_html=True,
|
105 |
+
)
|
106 |
|
107 |
+
st.markdown(
|
108 |
+
f"""
|
109 |
<h2 class="welcome-title">
|
110 |
Bienvenue sur NutriGénie <span class="user-name">{st.session_state['user']}</span> 🍽️!
|
111 |
</h2>
|
112 |
+
""",
|
113 |
+
unsafe_allow_html=True,
|
114 |
+
)
|
115 |
|
116 |
+
st.markdown(
|
117 |
+
"""
|
118 |
<br>
|
119 |
<div class="presentation-text">
|
120 |
" 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é. "
|
121 |
</div>
|
122 |
<br>
|
123 |
+
""",
|
124 |
+
unsafe_allow_html=True,
|
125 |
+
)
|
126 |
|
127 |
+
logo_path = os.path.join("client", "assets", "logo.png")
|
128 |
|
129 |
# centrer le logo
|
130 |
cola, colb, colc = st.columns(3)
|
131 |
|
132 |
with cola:
|
133 |
+
pass
|
134 |
with colb:
|
135 |
st.image(logo_path, use_container_width=True, caption=None)
|
136 |
with colc:
|
137 |
+
pass
|
138 |
|
139 |
+
st.markdown(
|
140 |
+
"""
|
141 |
<br>
|
142 |
<h3 style="color:#2a4b47; text-align:center;">🔧 Fonctionnalités principales de l'application :</h3>
|
143 |
<br>
|
144 |
+
""",
|
145 |
+
unsafe_allow_html=True,
|
146 |
+
)
|
147 |
|
148 |
# Fonctionnalités disposées horizontalement par paires
|
149 |
col1, col2 = st.columns(2)
|
|
|
151 |
with col1:
|
152 |
|
153 |
# Fonctionnalités 1 et 2
|
154 |
+
st.markdown(
|
155 |
+
"""
|
156 |
<div class="features">
|
157 |
<div style="display: flex; align-items: center;">
|
158 |
<span class="feature-icon">🍽️</span>
|
|
|
167 |
</div>
|
168 |
<p>Consultez l'historique de vos repas consommés et suivez vos habitudes alimentaires au fil du temps.</p>
|
169 |
</div>
|
170 |
+
""",
|
171 |
+
unsafe_allow_html=True,
|
172 |
+
)
|
173 |
|
174 |
with col2:
|
175 |
# Fonctionnalités 3 et 4
|
176 |
+
st.markdown(
|
177 |
+
"""
|
178 |
<div class="features">
|
179 |
<div style="display: flex; align-items: center;">
|
180 |
<span class="feature-icon">🛒</span>
|
|
|
189 |
</div>
|
190 |
<p>Obtenez des suggestions de repas en fonction de vos goûts et de vos besoins nutritionnels.</p>
|
191 |
</div>
|
192 |
+
""",
|
193 |
+
unsafe_allow_html=True,
|
194 |
+
)
|
195 |
|
196 |
# Présentation des membres de l'équipe
|
197 |
st.markdown("<hr>", unsafe_allow_html=True) # Ajoute une ligne de séparation
|
|
|
199 |
st.subheader("Rencontrez notre équipe 👩🍳👨🍳")
|
200 |
|
201 |
# Définition des 5 membres
|
202 |
+
base_path = os.path.join("client", "assets")
|
203 |
membres = [
|
204 |
+
{
|
205 |
+
"nom": "Souraya",
|
206 |
+
"role": "M2 SISE",
|
207 |
+
"photo": f"{os.path.join(base_path,'membre1.jpg')}",
|
208 |
+
"emoji_role": "👩💻",
|
209 |
+
},
|
210 |
+
{
|
211 |
+
"nom": "Bertrand",
|
212 |
+
"role": "M2 SISE",
|
213 |
+
"photo": f"{os.path.join(base_path,'membre2.jpg')}",
|
214 |
+
"emoji_role": "👩💻",
|
215 |
+
},
|
216 |
+
{
|
217 |
+
"nom": "Cyril",
|
218 |
+
"role": "M2 SISE",
|
219 |
+
"photo": f"{os.path.join(base_path,'membre3.jpg')}",
|
220 |
+
"emoji_role": "👩💻",
|
221 |
+
},
|
222 |
+
{
|
223 |
+
"nom": "Linh Nhi",
|
224 |
+
"role": "M2 SISE",
|
225 |
+
"photo": f"{os.path.join(base_path,'membre4.jpg')}",
|
226 |
+
"emoji_role": "👩💻",
|
227 |
+
},
|
228 |
+
{
|
229 |
+
"nom": "Daniella",
|
230 |
+
"role": "M2 SISE",
|
231 |
+
"photo": f"{os.path.join(base_path,'membre5.jpg')}",
|
232 |
+
"emoji_role": "👩💻",
|
233 |
+
},
|
234 |
]
|
235 |
|
236 |
# Création des colonnes pour chaque membre
|
|
|
239 |
for i, membre in enumerate(membres):
|
240 |
with cols[i]:
|
241 |
st.image(membre["photo"], use_container_width=True, caption=None)
|
242 |
+
st.markdown(
|
243 |
+
f"""
|
244 |
<div class="team-member">
|
245 |
<div class="team-name">{membre['nom']}</div>
|
246 |
<div class="team-name-role">{membre['emoji_role']} {membre['role']}</div>
|
247 |
</div>
|
248 |
+
""",
|
249 |
+
unsafe_allow_html=True,
|
250 |
+
)
|
client/pages/nutri.py
CHANGED
@@ -8,7 +8,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,
|
@@ -16,9 +16,10 @@ from server.db.dbmanager import (
|
|
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 |
|
@@ -34,12 +35,12 @@ 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(
|
|
|
|
|
43 |
print("✅ MistralAPI initialisé avec succès.")
|
44 |
|
45 |
mistral = st.session_state.mistral_instance # Récupérer l'instance stockée
|
@@ -48,7 +49,9 @@ mistral = st.session_state.mistral_instance # Récupérer l'instance stockée
|
|
48 |
try:
|
49 |
guardrail = Guardrail()
|
50 |
except Exception as e:
|
51 |
-
st.error(
|
|
|
|
|
52 |
st.stop()
|
53 |
|
54 |
# 🔹 Chargement de la base de données
|
@@ -56,7 +59,9 @@ 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(
|
|
|
|
|
60 |
|
61 |
# 🔹 Sidebar : Bouton "➕ Nouveau chat" en haut
|
62 |
st.sidebar.title("🗂️ Historique")
|
@@ -64,7 +69,7 @@ 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
|
@@ -72,20 +77,29 @@ 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[
|
76 |
-
title = conversation[
|
77 |
key = f"conversation_{id_conversation}_{index}" # Clé unique
|
78 |
|
79 |
-
col1, col2 = st.sidebar.columns(
|
|
|
|
|
80 |
|
81 |
with col1:
|
82 |
-
if
|
|
|
|
|
|
|
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(
|
|
|
|
|
|
|
|
|
89 |
st.rerun()
|
90 |
|
91 |
with col2:
|
@@ -107,7 +121,9 @@ for message in st.session_state.messages:
|
|
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(
|
|
|
|
|
111 |
|
112 |
|
113 |
# 🔹 Interface utilisateur - Zone d'entrée utilisateur
|
@@ -120,10 +136,14 @@ if prompt := st.chat_input("Dîtes quelque-chose"):
|
|
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(
|
124 |
-
|
|
|
|
|
|
|
|
|
125 |
st.stop()
|
126 |
-
|
127 |
# 🔸 Vérifier la sécurité du message
|
128 |
is_safe = guardrail.analyze_query(prompt)
|
129 |
|
@@ -134,21 +154,44 @@ if prompt := st.chat_input("Dîtes quelque-chose"):
|
|
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(
|
|
|
|
|
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(
|
|
|
|
|
141 |
if current_title == "Nouvelle conversation":
|
142 |
new_title = mistral.auto_wrap(text=prompt, temperature=0.5)
|
143 |
-
update_conversation_title(
|
|
|
|
|
144 |
|
145 |
# 🔸 Ajouter le message à l'historique et l'enregistrer dans la base de données
|
146 |
-
st.session_state.messages.append(
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
152 |
st.stop() # Arrêter l'exécution ici pour ne PAS envoyer à Mistral
|
153 |
|
154 |
retries = 0
|
@@ -165,30 +208,42 @@ if prompt := st.chat_input("Dîtes quelque-chose"):
|
|
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(
|
169 |
-
|
170 |
-
|
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 |
|
@@ -196,47 +251,69 @@ if prompt := st.chat_input("Dîtes quelque-chose"):
|
|
196 |
if word in response.lower():
|
197 |
try:
|
198 |
# 🔹 Extraire plusieurs titres de recettes
|
199 |
-
suggested_recipes = mistral.extract_multiple_recipes(
|
|
|
|
|
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 = [
|
207 |
-
|
|
|
|
|
|
|
|
|
208 |
if new_recipes:
|
209 |
-
st.session_state["chatbot_suggestions"].extend(
|
210 |
-
|
211 |
-
|
|
|
|
|
|
|
|
|
212 |
# 🔹 Sauvegarder les suggestions dans la BDD
|
213 |
-
save_chatbot_suggestions(
|
|
|
|
|
214 |
except Exception as e:
|
215 |
-
print(
|
|
|
|
|
216 |
|
217 |
break # On ne veut ajouter qu'une seule suggestion par réponse
|
218 |
-
|
219 |
|
|
|
|
|
220 |
|
221 |
-
|
222 |
-
|
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(
|
|
|
|
|
233 |
time.sleep(wait_time)
|
234 |
else:
|
235 |
-
st.error(
|
236 |
-
|
|
|
|
|
|
|
|
|
237 |
st.stop()
|
238 |
|
239 |
-
if response
|
|
|
|
|
|
|
|
|
|
|
240 |
break
|
241 |
|
242 |
if retries >= max_retries:
|
@@ -256,15 +333,38 @@ if prompt := st.chat_input("Dîtes quelque-chose"):
|
|
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
|
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(
|
270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
16 |
update_conversation_title,
|
17 |
delete_conversation,
|
18 |
load_chatbot_suggestions,
|
19 |
+
save_chatbot_suggestions,
|
20 |
)
|
21 |
import logging
|
22 |
+
|
23 |
logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler()])
|
24 |
logger = logging.getLogger(__name__)
|
25 |
|
|
|
35 |
st.session_state.messages = [] # Initialise l'historique des messages
|
36 |
|
37 |
|
|
|
|
|
38 |
# 🔹 Initialisation unique de MistralAPI
|
39 |
if "mistral_instance" not in st.session_state:
|
40 |
print("🔄 Initialisation de MistralAPI...")
|
41 |
+
st.session_state.mistral_instance = MistralAPI(
|
42 |
+
model=st.session_state["mistral_model"]
|
43 |
+
)
|
44 |
print("✅ MistralAPI initialisé avec succès.")
|
45 |
|
46 |
mistral = st.session_state.mistral_instance # Récupérer l'instance stockée
|
|
|
49 |
try:
|
50 |
guardrail = Guardrail()
|
51 |
except Exception as e:
|
52 |
+
st.error(
|
53 |
+
f"❌ Guardrail introuvable. Veuillez relancer l'appli ou contacter l'équipe de développement. Détails : {e}"
|
54 |
+
)
|
55 |
st.stop()
|
56 |
|
57 |
# 🔹 Chargement de la base de données
|
|
|
59 |
user_id = st.session_state["user_id"]
|
60 |
|
61 |
if "chatbot_suggestions" not in st.session_state:
|
62 |
+
st.session_state["chatbot_suggestions"] = load_chatbot_suggestions(
|
63 |
+
db_manager, user_id
|
64 |
+
)
|
65 |
|
66 |
# 🔹 Sidebar : Bouton "➕ Nouveau chat" en haut
|
67 |
st.sidebar.title("🗂️ Historique")
|
|
|
69 |
title = "Nouvelle conversation"
|
70 |
new_conversation_id = create_conversation(db_manager, title, user_id)
|
71 |
st.session_state.id_conversation = new_conversation_id
|
72 |
+
st.session_state.messages = []
|
73 |
st.rerun()
|
74 |
|
75 |
# 🔹 Charger l'historique des conversations
|
|
|
77 |
|
78 |
# 🔹 Sidebar : Affichage de l'historique des conversations avec bouton de suppression
|
79 |
for index, conversation in enumerate(conversation_history):
|
80 |
+
id_conversation = conversation["id_conversation"]
|
81 |
+
title = conversation["title"]
|
82 |
key = f"conversation_{id_conversation}_{index}" # Clé unique
|
83 |
|
84 |
+
col1, col2 = st.sidebar.columns(
|
85 |
+
[0.8, 0.2]
|
86 |
+
) # 🔹 Disposition pour aligner le bouton de suppression
|
87 |
|
88 |
with col1:
|
89 |
+
if (
|
90 |
+
"id_conversation" in st.session_state
|
91 |
+
and st.session_state.id_conversation == id_conversation
|
92 |
+
):
|
93 |
st.button(f"🟢 {title}", key=key, disabled=True)
|
94 |
else:
|
95 |
if st.button(title, key=key):
|
96 |
st.session_state.id_conversation = id_conversation
|
97 |
st.session_state.messages = load_messages(db_manager, id_conversation)
|
98 |
+
update_conversation(
|
99 |
+
db_manager,
|
100 |
+
id_conversation=st.session_state.id_conversation,
|
101 |
+
id_utilisateur=user_id,
|
102 |
+
)
|
103 |
st.rerun()
|
104 |
|
105 |
with col2:
|
|
|
121 |
elif message["role"] == "assistant":
|
122 |
with st.chat_message("assistant", avatar="client/assets/avatar_bot_big.jpg"):
|
123 |
st.markdown(message["content"])
|
124 |
+
st.caption(
|
125 |
+
f"📅 {timestamp} {latency_text}"
|
126 |
+
) # 🔹 Ajout de l'heure et de la latence
|
127 |
|
128 |
|
129 |
# 🔹 Interface utilisateur - Zone d'entrée utilisateur
|
|
|
136 |
# 🔸 Vérifier si le message est dans une langue supportée par le guardrail
|
137 |
is_supported = guardrail.analyze_language(prompt)
|
138 |
if not is_supported:
|
139 |
+
st.warning(
|
140 |
+
"""
|
141 |
+
⚠️ Votre message n'est pas rédigé dans les langues actuellement supportées (FR, EN, DE, ES).
|
142 |
+
Si votre message est pourtant dans une des langues supportées, le reformuler ou l'allonger peut être utile.
|
143 |
+
"""
|
144 |
+
)
|
145 |
st.stop()
|
146 |
+
|
147 |
# 🔸 Vérifier la sécurité du message
|
148 |
is_safe = guardrail.analyze_query(prompt)
|
149 |
|
|
|
154 |
if st.session_state.id_conversation is None:
|
155 |
# Générer un titre basé sur le premier message
|
156 |
title = mistral.auto_wrap(text=prompt, temperature=0.5)
|
157 |
+
st.session_state.id_conversation = create_conversation(
|
158 |
+
db_manager, title, user_id
|
159 |
+
)
|
160 |
else:
|
161 |
# Vérifier si le titre est encore "Nouvelle conversation" et le mettre à jour si nécessaire
|
162 |
+
current_title = get_conversation_title(
|
163 |
+
db_manager, st.session_state.id_conversation
|
164 |
+
)
|
165 |
if current_title == "Nouvelle conversation":
|
166 |
new_title = mistral.auto_wrap(text=prompt, temperature=0.5)
|
167 |
+
update_conversation_title(
|
168 |
+
db_manager, st.session_state.id_conversation, new_title
|
169 |
+
)
|
170 |
|
171 |
# 🔸 Ajouter le message à l'historique et l'enregistrer dans la base de données
|
172 |
+
st.session_state.messages.append(
|
173 |
+
{
|
174 |
+
"role": "user",
|
175 |
+
"content": prompt,
|
176 |
+
"temps_traitement": None,
|
177 |
+
"total_cout": None,
|
178 |
+
"impact_eco": None,
|
179 |
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
180 |
+
}
|
181 |
+
)
|
182 |
+
save_message(
|
183 |
+
db_manager,
|
184 |
+
st.session_state.id_conversation,
|
185 |
+
role="user",
|
186 |
+
content=prompt,
|
187 |
+
temps_traitement=None,
|
188 |
+
total_cout=None,
|
189 |
+
impact_eco=None,
|
190 |
+
)
|
191 |
|
192 |
# 🔸 Si le message est interdit, afficher l'alerte mais NE PAS l'envoyer à Mistral
|
193 |
if not is_safe:
|
194 |
+
st.warning("⚠️ Votre message ne respecte pas l'usage de notre chatbot.")
|
195 |
st.stop() # Arrêter l'exécution ici pour ne PAS envoyer à Mistral
|
196 |
|
197 |
retries = 0
|
|
|
208 |
start_time = time.time() # 🔹 Début du chronomètre
|
209 |
|
210 |
print("🔄 Génération de réponse en cours...")
|
211 |
+
stream_response = mistral.stream(
|
212 |
+
st.session_state.messages, temperature=0.5
|
213 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
|
215 |
# Compteur pour les tokens de sortie
|
216 |
+
# Comment devenir une meilleure personne dans la vie ?
|
217 |
output_tokens = 0
|
218 |
+
temp_stream = [
|
219 |
+
chunk.data.choices[0].delta.content for chunk in stream_response
|
220 |
+
]
|
221 |
+
# Calculer les tokens pour ce morceau de réponse
|
222 |
+
temp_output_token = sum(
|
223 |
+
[mistral.count_output_tokens(chunk) for chunk in temp_stream]
|
224 |
+
)
|
225 |
+
reponse = " ".join(temp_stream)
|
226 |
+
|
227 |
+
if response == "Injection":
|
228 |
+
st.warning(
|
229 |
+
"⚠️ Votre message ne respecte pas l'usage de notre chatbot."
|
230 |
+
)
|
231 |
+
# guardrail.incremental_learning(prompt, 1) # 1 car injection. Le tuning ne se fait que sur les injections
|
232 |
+
st.stop()
|
233 |
+
# end_time = time.time() # 🔹 Fin du chronomètre
|
234 |
+
break
|
235 |
+
else: # on réinitialise
|
236 |
+
response = ""
|
237 |
+
for chunk in temp_stream:
|
238 |
+
response += chunk
|
239 |
+
response_placeholder.markdown(response)
|
240 |
+
# # Calculer les tokens pour ce morceau de réponse
|
241 |
+
# output_tokens += mistral.count_output_tokens(chunk.data.choices[0].delta.content)
|
242 |
+
|
243 |
+
time.sleep(0.03)
|
244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
# 🔹 Vérifier si la réponse contient une suggestion de recette
|
246 |
+
|
247 |
# 🔹 Vérifier si la réponse contient des suggestions de recettes
|
248 |
keywords = ["recette", "plat", "préparer", "ingrédients"]
|
249 |
|
|
|
251 |
if word in response.lower():
|
252 |
try:
|
253 |
# 🔹 Extraire plusieurs titres de recettes
|
254 |
+
suggested_recipes = mistral.extract_multiple_recipes(
|
255 |
+
text=response, temperature=0.3
|
256 |
+
)
|
257 |
|
258 |
# Vérifier et initialiser la liste des suggestions
|
259 |
if "chatbot_suggestions" not in st.session_state:
|
260 |
st.session_state["chatbot_suggestions"] = []
|
261 |
|
262 |
# Ajouter uniquement les recettes qui ne sont pas déjà stockées
|
263 |
+
new_recipes = [
|
264 |
+
recipe
|
265 |
+
for recipe in suggested_recipes
|
266 |
+
if recipe not in st.session_state["chatbot_suggestions"]
|
267 |
+
]
|
268 |
+
|
269 |
if new_recipes:
|
270 |
+
st.session_state["chatbot_suggestions"].extend(
|
271 |
+
new_recipes
|
272 |
+
) # Ajouter plusieurs recettes
|
273 |
+
print(
|
274 |
+
f"✅ {len(new_recipes)} nouvelles suggestions ajoutées."
|
275 |
+
)
|
276 |
+
|
277 |
# 🔹 Sauvegarder les suggestions dans la BDD
|
278 |
+
save_chatbot_suggestions(
|
279 |
+
db_manager, user_id, new_recipes
|
280 |
+
)
|
281 |
except Exception as e:
|
282 |
+
print(
|
283 |
+
f"❌ Erreur lors de l'extraction des suggestions : {e}"
|
284 |
+
)
|
285 |
|
286 |
break # On ne veut ajouter qu'une seule suggestion par réponse
|
|
|
287 |
|
288 |
+
# end_time = time.time() # 🔹 Fin du chronomètre
|
289 |
+
# latency = round(end_time - start_time, 2) # 🔹 Calcul de la latence
|
290 |
|
291 |
+
# print(f"✅ Réponse générée en {latency} secondes.")
|
292 |
+
# print(f"✅ Nombre de tokens de sortie : {output_tokens}")
|
|
|
|
|
|
|
|
|
293 |
except Exception as e:
|
294 |
if hasattr(e, "status_code") and e.status_code == 429:
|
295 |
retries += 1
|
296 |
+
wait_time = 2 ** retries
|
297 |
stream_response = None
|
298 |
+
print(
|
299 |
+
f"⚠️ Rate limit atteint. Nouvel essai dans {wait_time} secondes..."
|
300 |
+
)
|
301 |
time.sleep(wait_time)
|
302 |
else:
|
303 |
+
st.error(
|
304 |
+
f"❌ Erreur : Impossible de traiter votre demande. Détails : {str(e)}"
|
305 |
+
)
|
306 |
+
response_placeholder.markdown(
|
307 |
+
"❌ Erreur lors de la génération de la réponse."
|
308 |
+
)
|
309 |
st.stop()
|
310 |
|
311 |
+
if response != "": # On sort de la boucle
|
312 |
+
end_time = time.time() # 🔹 Fin du chronomètre
|
313 |
+
latency = round(end_time - start_time, 2) # 🔹 Calcul de la latence
|
314 |
+
|
315 |
+
print(f"✅ Réponse générée en {latency} secondes.")
|
316 |
+
print(f"✅ Nombre de tokens de sortie : {output_tokens}")
|
317 |
break
|
318 |
|
319 |
if retries >= max_retries:
|
|
|
333 |
print(f"✅ Coût total de la requête : {total_cost} USD")
|
334 |
|
335 |
# Facteur d'émission (en grammes de CO₂ par token)
|
336 |
+
EMISSIONS_PER_TOKEN = 0.00005 # estimation
|
337 |
|
338 |
# Calcul de l'empreinte carbone pour les tokens d'entrée et de sortie
|
339 |
input_emissions = input_tokens * EMISSIONS_PER_TOKEN
|
340 |
output_emissions = output_tokens * EMISSIONS_PER_TOKEN
|
341 |
total_emissions = input_emissions + output_emissions
|
342 |
print(f"🌍 Impact écologique total de la requête : {total_emissions:.4f} g CO₂")
|
|
|
343 |
|
344 |
# 🔹 Enregistrer la réponse de l'assistant
|
345 |
+
st.session_state.messages.append(
|
346 |
+
{
|
347 |
+
"role": "assistant",
|
348 |
+
"content": response,
|
349 |
+
"temps_traitement": latency,
|
350 |
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
351 |
+
}
|
352 |
+
)
|
353 |
+
save_message(
|
354 |
+
db_manager,
|
355 |
+
st.session_state.id_conversation,
|
356 |
+
role="assistant",
|
357 |
+
content=response,
|
358 |
+
temps_traitement=latency,
|
359 |
+
total_cout=total_cost,
|
360 |
+
impact_eco=total_emissions,
|
361 |
+
)
|
362 |
+
|
363 |
+
if response == "Injection":
|
364 |
+
guardrail.incremental_learning(
|
365 |
+
prompt, [1]
|
366 |
+
) # 1 car injection. Le tuning ne se fait que sur les injections
|
367 |
+
print(
|
368 |
+
"🤖 Entraînement du guardrail à reconnaître le prompt comme dangereux effectué avec succès"
|
369 |
+
)
|
370 |
+
st.stop()
|
client/pages/nutri_old.py
CHANGED
@@ -4,10 +4,11 @@ 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():
|
@@ -19,9 +20,14 @@ def nutri_page():
|
|
19 |
st.sidebar.title("Historique")
|
20 |
|
21 |
for conversation_id, _, title in conversation_history:
|
22 |
-
if
|
|
|
|
|
|
|
23 |
# Bouton désactivé pour la conversation active
|
24 |
-
st.sidebar.button(
|
|
|
|
|
25 |
else:
|
26 |
# Bouton actif pour les autres conversations
|
27 |
if st.sidebar.button(title, key=f"conversation_{conversation_id}"):
|
@@ -51,11 +57,11 @@ def nutri_page():
|
|
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(
|
|
|
|
|
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"):
|
@@ -64,30 +70,41 @@ def nutri_page():
|
|
64 |
max_retries = 3
|
65 |
while retries < max_retries:
|
66 |
try:
|
67 |
-
title = mistral.auto_wrap(
|
|
|
|
|
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(
|
|
|
|
|
74 |
time.sleep(wait_time)
|
75 |
else:
|
76 |
# Gérer d'autres types d'erreurs
|
77 |
-
st.error(
|
|
|
|
|
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(
|
|
|
|
|
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(
|
90 |
-
|
|
|
|
|
|
|
|
|
91 |
with st.chat_message("user"):
|
92 |
st.markdown(prompt)
|
93 |
|
@@ -98,7 +115,9 @@ def nutri_page():
|
|
98 |
try:
|
99 |
guardrail = Guardrail()
|
100 |
except Exception as e:
|
101 |
-
st.error(
|
|
|
|
|
102 |
st.stop()
|
103 |
# is_supported = await guardrail.analyze_language(prompt)
|
104 |
# if not is_supported:
|
@@ -106,43 +125,63 @@ def nutri_page():
|
|
106 |
# st.stop()
|
107 |
is_safe = guardrail.analyze_query(prompt)
|
108 |
if not is_safe:
|
109 |
-
st.warning(
|
|
|
|
|
110 |
st.stop()
|
111 |
|
112 |
####################
|
113 |
###### PROMPT ######
|
114 |
####################
|
115 |
|
116 |
-
with st.chat_message("assistant", avatar
|
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(
|
|
|
|
|
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(
|
|
|
|
|
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(
|
|
|
|
|
135 |
time.sleep(wait_time)
|
136 |
else:
|
137 |
# Gestion d'autres types d'erreurs
|
138 |
-
st.error(
|
139 |
-
|
|
|
|
|
|
|
|
|
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(
|
146 |
-
|
|
|
|
|
|
|
|
|
147 |
st.session_state.messages.append({"role": "assistant", "content": response})
|
148 |
-
save_message(
|
|
|
|
|
|
|
|
|
|
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 |
|
14 |
def nutri_page():
|
|
|
20 |
st.sidebar.title("Historique")
|
21 |
|
22 |
for conversation_id, _, title in conversation_history:
|
23 |
+
if (
|
24 |
+
"conversation_id" in st.session_state
|
25 |
+
and st.session_state.conversation_id == conversation_id
|
26 |
+
):
|
27 |
# Bouton désactivé pour la conversation active
|
28 |
+
st.sidebar.button(
|
29 |
+
f"🟢 {title}", key=f"conversation_{conversation_id}", disabled=True
|
30 |
+
)
|
31 |
else:
|
32 |
# Bouton actif pour les autres conversations
|
33 |
if st.sidebar.button(title, key=f"conversation_{conversation_id}"):
|
|
|
57 |
with st.chat_message("user"): # Utilisez votre avatar utilisateur
|
58 |
st.markdown(message["content"])
|
59 |
elif message["role"] == "assistant":
|
60 |
+
with st.chat_message(
|
61 |
+
"assistant", avatar="client/assets/avatar_bot_big.jpg"
|
62 |
+
): # Avatar personnalisé pour l'assistant
|
63 |
st.markdown(message["content"])
|
64 |
|
|
|
|
|
65 |
# Initialisation de Mistral
|
66 |
mistral = MistralAPI(model=st.session_state["mistral_model"])
|
67 |
if prompt := st.chat_input("Dîtes quelque-chose"):
|
|
|
70 |
max_retries = 3
|
71 |
while retries < max_retries:
|
72 |
try:
|
73 |
+
title = mistral.auto_wrap(
|
74 |
+
prompt
|
75 |
+
) # Utiliser le début du message comme titre
|
76 |
except Exception as e:
|
77 |
# Vérifier explicitement si l'erreur est une 429 (rate limit exceeded)
|
78 |
if hasattr(e, "status_code") and e.status_code == 429:
|
79 |
retries += 1
|
80 |
wait_time = 2 ** retries # Temps d'attente exponentiel
|
81 |
+
st.warning(
|
82 |
+
f"Limite de requêtes atteinte (429). Nouvel essai dans {wait_time} secondes..."
|
83 |
+
)
|
84 |
time.sleep(wait_time)
|
85 |
else:
|
86 |
# Gérer d'autres types d'erreurs
|
87 |
+
st.error(
|
88 |
+
f"Erreur : Impossible de traiter votre demande (déetails : {str(e)})"
|
89 |
+
)
|
90 |
st.stop()
|
91 |
if title is not None:
|
92 |
break
|
93 |
# Si tous les retries échouent, retourner un message d'erreur
|
94 |
if title is None:
|
95 |
+
st.error(
|
96 |
+
"Impossible d'obtenir une réponse. Limite de requêtes atteinte après plusieurs tentatives."
|
97 |
+
)
|
98 |
st.stop()
|
99 |
|
|
|
100 |
st.session_state.conversation_id = create_conversation(title=title)
|
101 |
st.session_state.messages.append({"role": "user", "content": prompt})
|
102 |
+
save_message(
|
103 |
+
conversation_id=st.session_state.conversation_id,
|
104 |
+
role="user",
|
105 |
+
content=prompt,
|
106 |
+
)
|
107 |
+
|
108 |
with st.chat_message("user"):
|
109 |
st.markdown(prompt)
|
110 |
|
|
|
115 |
try:
|
116 |
guardrail = Guardrail()
|
117 |
except Exception as e:
|
118 |
+
st.error(
|
119 |
+
f"Guardrail introuvable. Veuillez relancer le conteneur pour recréer le guardrail. Détails : {e}"
|
120 |
+
)
|
121 |
st.stop()
|
122 |
# is_supported = await guardrail.analyze_language(prompt)
|
123 |
# if not is_supported:
|
|
|
125 |
# st.stop()
|
126 |
is_safe = guardrail.analyze_query(prompt)
|
127 |
if not is_safe:
|
128 |
+
st.warning(
|
129 |
+
"Le prompt semble violer nos considérations éthiques. Nous vous invitons à poser une autre question."
|
130 |
+
)
|
131 |
st.stop()
|
132 |
|
133 |
####################
|
134 |
###### PROMPT ######
|
135 |
####################
|
136 |
|
137 |
+
with st.chat_message("assistant", avatar="client/assets/avatar_bot_big.jpg"):
|
138 |
retries = 0
|
139 |
max_retries = 3
|
140 |
while retries < max_retries:
|
141 |
response = ""
|
142 |
response_placeholder = st.empty()
|
143 |
try:
|
144 |
+
stream_response = mistral.stream(
|
145 |
+
st.session_state.messages
|
146 |
+
) # Utiliser le début du message comme titr
|
147 |
# Traiter la réponse en streaming
|
148 |
for chunk in stream_response:
|
149 |
response += chunk.data.choices[0].delta.content
|
150 |
response_placeholder.markdown(response)
|
151 |
+
time.sleep(
|
152 |
+
0.03
|
153 |
+
) # Petit délai pour simuler le flux en temps réel
|
154 |
except Exception as e:
|
155 |
if hasattr(e, "status_code") and e.status_code == 429:
|
156 |
# Gestion explicite de l'erreur 429 (Rate Limit Exceeded)
|
157 |
retries += 1
|
158 |
wait_time = 2 ** retries # Délai exponentiel : 2, 4, 8 secondes
|
159 |
+
st.warning(
|
160 |
+
f"Limite de requêtes atteinte (429). Nouvel essai dans {wait_time} secondes..."
|
161 |
+
)
|
162 |
time.sleep(wait_time)
|
163 |
else:
|
164 |
# Gestion d'autres types d'erreurs
|
165 |
+
st.error(
|
166 |
+
f"Erreur : Impossible de traiter votre demande (détails : {str(e)})"
|
167 |
+
)
|
168 |
+
response_placeholder.markdown(
|
169 |
+
"Erreur lors de la génération de la réponse."
|
170 |
+
)
|
171 |
st.stop()
|
172 |
if stream_response is not None:
|
173 |
break # Si le streaming réussit, on sort de la boucle
|
174 |
# Si toutes les tentatives échouent, message d'erreur final
|
175 |
if retries >= max_retries:
|
176 |
+
st.error(
|
177 |
+
"Impossible d'obtenir une réponse. Limite de requêtes atteinte après plusieurs tentatives."
|
178 |
+
)
|
179 |
+
response = (
|
180 |
+
"Erreur : Limite de requêtes atteinte après plusieurs tentatives."
|
181 |
+
)
|
182 |
st.session_state.messages.append({"role": "assistant", "content": response})
|
183 |
+
save_message(
|
184 |
+
conversation_id=st.session_state.conversation_id,
|
185 |
+
role="assistant",
|
186 |
+
content=response,
|
187 |
+
)
|
client/pages/sign_in.py
CHANGED
@@ -3,6 +3,7 @@ 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(
|
@@ -43,12 +44,12 @@ def sign_in(navigate_to):
|
|
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:
|
@@ -64,16 +65,15 @@ def sign_in(navigate_to):
|
|
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 =
|
72 |
-
print("user",user)
|
73 |
|
74 |
if user:
|
75 |
user = user[0]
|
76 |
-
user_id = user["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
|
@@ -92,7 +92,7 @@ def sign_in(navigate_to):
|
|
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 |
|
@@ -130,4 +130,4 @@ def sign_in(navigate_to):
|
|
130 |
|
131 |
if st.button("Pas de compte ? Inscrivez-vous."):
|
132 |
navigate_to("inscription")
|
133 |
-
"""
|
|
|
3 |
import os
|
4 |
from dotenv import load_dotenv
|
5 |
|
6 |
+
|
7 |
def sign_in(navigate_to):
|
8 |
|
9 |
st.markdown(
|
|
|
44 |
|
45 |
</style>
|
46 |
""",
|
47 |
+
unsafe_allow_html=True,
|
48 |
)
|
49 |
|
50 |
logo_path = "assets/logo.png"
|
51 |
|
52 |
+
# centrer le logo
|
53 |
col1, col2, col3 = st.columns([1.5, 1.5, 1])
|
54 |
|
55 |
with col2:
|
|
|
65 |
login = st.text_input("👤 Pseudo")
|
66 |
password = st.text_input("🔒 Mot de passe", type="password")
|
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 = ?", (login,))
|
72 |
+
print("user", user[0]["id_utilisateur"])
|
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
|
|
|
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 |
|
|
|
130 |
|
131 |
if st.button("Pas de compte ? Inscrivez-vous."):
|
132 |
navigate_to("inscription")
|
133 |
+
"""
|
client/pages/sign_up.py
CHANGED
@@ -1,41 +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 |
-
|
9 |
-
|
10 |
-
st.
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
"
|
35 |
-
"mot_de_passe": hashed_password
|
36 |
-
|
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}")
|
|
|
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 |
+
|
9 |
+
def sign_up(navigate_to):
|
10 |
+
db_manager = st.session_state.get("db_manager")
|
11 |
+
st.title("Inscription")
|
12 |
+
|
13 |
+
# Champs obligatoires
|
14 |
+
login = st.text_input("Pseudo")
|
15 |
+
email = st.text_input("Email")
|
16 |
+
password = st.text_input("Mot de passe", type="password")
|
17 |
+
confirm_password = st.text_input("Confirmer le mot de passe", type="password")
|
18 |
+
|
19 |
+
# Lien pour rediriger vers la page de connexion
|
20 |
+
if st.button("Déjà un compte ? connectez-vous."):
|
21 |
+
navigate_to("connexion")
|
22 |
+
|
23 |
+
# Vérification des champs obligatoires
|
24 |
+
if st.button("Créer un compte"):
|
25 |
+
if not login or not email or not password or not confirm_password:
|
26 |
+
st.error("Tous les champs sont obligatoires.")
|
27 |
+
elif password != confirm_password:
|
28 |
+
st.error("Les mots de passe ne correspondent pas.")
|
29 |
+
else:
|
30 |
+
hashed_password = generate_password_hash(password)
|
31 |
+
try:
|
32 |
+
# Insérer l'utilisateur dans la base de données
|
33 |
+
db_manager.insert_data_from_dict(
|
34 |
+
"utilisateurs",
|
35 |
+
[{"login": login, "email": email, "mot_de_passe": hashed_password}],
|
36 |
+
)
|
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
CHANGED
@@ -1,74 +1,90 @@
|
|
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 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
font-
|
15 |
-
font-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
font-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
font-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
"""
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
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 |
+
"""
|
11 |
+
<style>
|
12 |
+
/* Style global pour les headers */
|
13 |
+
h3 {
|
14 |
+
font-size: 20px;
|
15 |
+
font-family: New Icon;
|
16 |
+
font-weight: 700;
|
17 |
+
}
|
18 |
+
|
19 |
+
h2 {
|
20 |
+
font-size: 2rem;
|
21 |
+
color: #2a4b47;
|
22 |
+
}
|
23 |
+
|
24 |
+
.welcome-title {
|
25 |
+
font-size: 2.5rem;
|
26 |
+
font-weight: 700;
|
27 |
+
color: #2a4b47;
|
28 |
+
text-align: center;
|
29 |
+
animation: fadeIn 2s ease-out;
|
30 |
+
}
|
31 |
+
|
32 |
+
@keyframes fadeIn {
|
33 |
+
0% { opacity: 0; }
|
34 |
+
100% { opacity: 1; }
|
35 |
+
}
|
36 |
+
|
37 |
+
.user-name {
|
38 |
+
color: #4e7a63;
|
39 |
+
font-size: 3rem;
|
40 |
+
font-weight: bold;
|
41 |
+
animation: nameAnimation 2s ease-out;
|
42 |
+
font-family: New Icon;
|
43 |
+
}
|
44 |
+
</style>
|
45 |
+
""",
|
46 |
+
unsafe_allow_html=True,
|
47 |
+
)
|
48 |
+
|
49 |
+
# Affichage du message de bienvenue
|
50 |
+
st.markdown(
|
51 |
+
f"""
|
52 |
+
<h2 class="welcome-title">
|
53 |
+
Bienvenue sur NutriGénie <span class="user-name">{st.session_state['user']}</span> 🍽️!
|
54 |
+
</h2>
|
55 |
+
""",
|
56 |
+
unsafe_allow_html=True,
|
57 |
+
)
|
58 |
+
|
59 |
+
# Définition des onglets horizontaux
|
60 |
+
tabs = st.tabs(
|
61 |
+
[
|
62 |
+
"🧑💼 Informations personnelles ",
|
63 |
+
"🍽️ Meal Plan",
|
64 |
+
"🛒 Liste des courses",
|
65 |
+
"⭐ Favoris",
|
66 |
+
]
|
67 |
+
)
|
68 |
+
|
69 |
+
# Onglet 1 : Informations personnelles
|
70 |
+
with tabs[0]:
|
71 |
+
st.markdown(
|
72 |
+
'<h3 class="stHeader">🧑💼 Informations personnelles</h3>',
|
73 |
+
unsafe_allow_html=True,
|
74 |
+
)
|
75 |
+
info_perso() # Charger la page `info_perso.py`
|
76 |
+
|
77 |
+
# Onglet 2 : Meal Plan
|
78 |
+
with tabs[1]:
|
79 |
+
st.markdown('<h3 class="stHeader">🍽️ Meal Plan</h3>', unsafe_allow_html=True)
|
80 |
+
mealplan() # Charger la page `mealplan.py`
|
81 |
+
|
82 |
+
# Onglet 3 : Liste des courses
|
83 |
+
with tabs[2]:
|
84 |
+
st.markdown('<h3 class="stHeader">🛒 Liste des courses</h3>', unsafe_allow_html=True)
|
85 |
+
course_list() # Charger la page `course_list.py`
|
86 |
+
|
87 |
+
# Onglet 4 : Favoris
|
88 |
+
with tabs[3]:
|
89 |
+
st.markdown('<h3 class="stHeader">⭐ Favoris</h3>', unsafe_allow_html=True)
|
90 |
+
favoris() # Charger la page `favoris.py`
|
client/pages/user__course_list.py
CHANGED
@@ -1,19 +1,17 @@
|
|
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 |
-
|
|
|
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")
|
|
|
|
client/pages/user__favoris.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
st.
|
6 |
-
st.
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
|
4 |
+
def favoris():
|
5 |
+
st.subheader("Favoris")
|
6 |
+
st.write("Voici vos recettes ou restaurants favoris.")
|
7 |
+
st.button("Ajouter aux favoris")
|
client/pages/user__info_perso.py
CHANGED
@@ -1,5 +1,6 @@
|
|
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")
|
@@ -8,68 +9,95 @@ def info_perso():
|
|
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[
|
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 =
|
|
|
|
|
|
|
|
|
35 |
objectifs_nutritionnels = st.selectbox(
|
36 |
-
"Objectifs nutritionnels",
|
37 |
["Prise de masse", "Tonification", "Perdre du poids", "Vide"],
|
38 |
-
index=[
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
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(
|
|
|
|
|
49 |
with col2:
|
50 |
# Traitement de la valeur vide pour l'activité physique
|
51 |
-
activite_physique_val =
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
56 |
)
|
57 |
with col3:
|
58 |
-
objectif_calorique = st.text_input(
|
|
|
|
|
59 |
|
60 |
# Bouton pour sauvegarder toutes les informations
|
61 |
if st.button("Tout mettre à jour"):
|
62 |
table_name = "utilisateurs"
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
condition = "id_utilisateur = %s"
|
68 |
|
69 |
# Rassembler les paramètres à passer à la méthode
|
70 |
-
params = (
|
71 |
-
|
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 |
|
|
|
1 |
import streamlit as st
|
2 |
|
3 |
+
|
4 |
def info_perso():
|
5 |
# Récupérer le gestionnaire de base de données
|
6 |
db_manager = st.session_state.get("db_manager")
|
|
|
9 |
st.error("Erreur : DBManager n'est pas initialisé.")
|
10 |
else:
|
11 |
# Récupérer les informations de l'utilisateur connecté
|
12 |
+
user_id = st.session_state["user_id"]
|
13 |
query = f"SELECT * FROM utilisateurs WHERE id_utilisateur = {user_id}"
|
14 |
user_info = db_manager.query(query)
|
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 |
# Deuxième formulaire (Objectifs nutritionnels, Poids, Taille)
|
30 |
col1, col2, col3 = st.columns(3)
|
31 |
with col1:
|
32 |
+
|
33 |
+
objectifs_nutritionnels_val = (
|
34 |
+
user_info["objectifs_nutritionnels"]
|
35 |
+
if user_info["objectifs_nutritionnels"]
|
36 |
+
else "Vide"
|
37 |
+
)
|
38 |
objectifs_nutritionnels = st.selectbox(
|
39 |
+
"Objectifs nutritionnels",
|
40 |
["Prise de masse", "Tonification", "Perdre du poids", "Vide"],
|
41 |
+
index=[
|
42 |
+
"Prise de masse",
|
43 |
+
"Tonification",
|
44 |
+
"Perdre du poids",
|
45 |
+
"Vide",
|
46 |
+
].index(objectifs_nutritionnels_val),
|
47 |
)
|
48 |
with col2:
|
49 |
poids = st.number_input("Poids (kg)", value=user_info["poids"], step=1)
|
50 |
with col3:
|
51 |
+
taille = st.number_input(
|
52 |
+
"Taille (cm)", value=user_info["taille"], step=1
|
53 |
+
)
|
54 |
|
55 |
# Troisième formulaire (Régime particulier, Activité physique, Objectif calorique)
|
56 |
col1, col2, col3 = st.columns(3)
|
57 |
with col1:
|
58 |
+
regime_particulier = st.text_area(
|
59 |
+
"Régime particulier", user_info["regime_particulier"]
|
60 |
+
)
|
61 |
with col2:
|
62 |
# Traitement de la valeur vide pour l'activité physique
|
63 |
+
activite_physique_val = (
|
64 |
+
user_info["activite_physique"]
|
65 |
+
if user_info["activite_physique"]
|
66 |
+
else "Vide"
|
67 |
+
)
|
68 |
activite_physique = st.selectbox(
|
69 |
"Activité physique",
|
70 |
["Sédentaire", "Légère", "Modérée", "Intense", "Vide"],
|
71 |
+
index=["Sédentaire", "Légère", "Modérée", "Intense", "Vide"].index(
|
72 |
+
activite_physique_val
|
73 |
+
),
|
74 |
)
|
75 |
with col3:
|
76 |
+
objectif_calorique = st.text_input(
|
77 |
+
"Objectif calorique", user_info["objectif_calorique"]
|
78 |
+
)
|
79 |
|
80 |
# Bouton pour sauvegarder toutes les informations
|
81 |
if st.button("Tout mettre à jour"):
|
82 |
table_name = "utilisateurs"
|
83 |
+
|
84 |
+
set_clause = """login = ?, email = ?, objectifs_nutritionnels = ?, poids = ?, taille = ?,
|
85 |
+
regime_particulier = ?, activite_physique = ?, objectif_calorique = ?"""
|
86 |
+
condition = "id_utilisateur = ?"
|
|
|
87 |
|
88 |
# Rassembler les paramètres à passer à la méthode
|
89 |
+
params = (
|
90 |
+
nom,
|
91 |
+
email,
|
92 |
+
objectifs_nutritionnels,
|
93 |
+
poids,
|
94 |
+
taille,
|
95 |
+
regime_particulier,
|
96 |
+
activite_physique,
|
97 |
+
objectif_calorique,
|
98 |
+
user_id,
|
99 |
+
)
|
100 |
+
|
101 |
# Appel à la méthode update_data avec les bons paramètres
|
102 |
db_manager.update_data(table_name, set_clause, condition, params)
|
103 |
|
client/pages/user__mealplan.py
CHANGED
@@ -1,10 +1,8 @@
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
from datetime import datetime, timedelta
|
4 |
-
from server.db.dbmanager import
|
5 |
-
|
6 |
-
load_chatbot_suggestions
|
7 |
-
)
|
8 |
db_manager = get_db_manager()
|
9 |
user_id = st.session_state["user_id"]
|
10 |
|
@@ -12,22 +10,35 @@ user_id = st.session_state["user_id"]
|
|
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(
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
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(
|
|
|
|
|
|
|
|
|
31 |
selected_week = int(selected_week_label.split(" ")[1])
|
32 |
|
33 |
# 📌 Génération des 7 jours de la semaine sélectionnée
|
@@ -40,25 +51,36 @@ def mealplan():
|
|
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": "",
|
|
|
|
|
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(
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
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(
|
|
|
|
|
62 |
|
63 |
# 📌 **Prévisualisation du repas existant**
|
64 |
existing_meal = st.session_state["meal_plan"][selected_day][selected_meal]
|
@@ -66,8 +88,10 @@ def mealplan():
|
|
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(
|
70 |
-
|
|
|
|
|
71 |
|
72 |
col1, col2 = st.columns([0.7, 0.3])
|
73 |
with col1:
|
@@ -77,12 +101,16 @@ def mealplan():
|
|
77 |
|
78 |
if submitted:
|
79 |
st.session_state["meal_plan"][selected_day][selected_meal] = meal_input
|
80 |
-
st.session_state[
|
|
|
|
|
81 |
st.rerun()
|
82 |
|
83 |
if deleted:
|
84 |
st.session_state["meal_plan"][selected_day][selected_meal] = ""
|
85 |
-
st.session_state[
|
|
|
|
|
86 |
st.rerun()
|
87 |
|
88 |
# 🤖 Ajout des suggestions du chatbot
|
@@ -90,19 +118,30 @@ def mealplan():
|
|
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(
|
94 |
-
|
|
|
95 |
|
96 |
if st.session_state["chatbot_suggestions"]:
|
97 |
with st.form("chatbot_form"):
|
98 |
-
selected_recipe = st.selectbox(
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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][
|
105 |
-
|
|
|
|
|
|
|
|
|
106 |
st.rerun()
|
107 |
else:
|
108 |
st.write("⚠️ Aucune suggestion du chatbot pour le moment.")
|
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
from datetime import datetime, timedelta
|
4 |
+
from server.db.dbmanager import get_db_manager, load_chatbot_suggestions
|
5 |
+
|
|
|
|
|
6 |
db_manager = get_db_manager()
|
7 |
user_id = st.session_state["user_id"]
|
8 |
|
|
|
10 |
def get_week_dates(year, week):
|
11 |
"""Retourne les dates du Lundi au Dimanche pour une semaine donnée."""
|
12 |
first_day_of_year = datetime(year, 1, 1)
|
13 |
+
first_monday = first_day_of_year + timedelta(
|
14 |
+
days=(7 - first_day_of_year.weekday())
|
15 |
+
) # Trouver le premier lundi
|
16 |
+
start_date = first_monday + timedelta(
|
17 |
+
weeks=week - 1
|
18 |
+
) # Trouver le début de la semaine sélectionnée
|
19 |
+
return [
|
20 |
+
(start_date + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(7)
|
21 |
+
] # 7 jours (Lundi → Dimanche)
|
22 |
+
|
23 |
|
24 |
def mealplan():
|
25 |
# 📆 Sélection de l'année et de la semaine
|
26 |
current_year = datetime.now().year
|
27 |
current_week = datetime.now().isocalendar()[1]
|
28 |
|
29 |
+
year_options = list(
|
30 |
+
range(current_year, current_year - 3, -1)
|
31 |
+
) # Années en ordre décroissant
|
32 |
col1, col2 = st.columns([0.5, 0.5]) # Réduction de la largeur des sélecteurs
|
33 |
with col1:
|
34 |
selected_year = st.selectbox("📅 Année", year_options, index=0)
|
35 |
with col2:
|
36 |
week_options = [f"Semaine {i}" for i in range(1, 53)]
|
37 |
+
selected_week_label = st.selectbox(
|
38 |
+
"📆 Semaine",
|
39 |
+
week_options,
|
40 |
+
index=week_options.index(f"Semaine {current_week}"),
|
41 |
+
)
|
42 |
selected_week = int(selected_week_label.split(" ")[1])
|
43 |
|
44 |
# 📌 Génération des 7 jours de la semaine sélectionnée
|
|
|
51 |
for date in week_dates:
|
52 |
if date not in st.session_state["meal_plan"]:
|
53 |
st.session_state["meal_plan"][date] = {
|
54 |
+
"Petit-déjeuner": "",
|
55 |
+
"Déjeuner": "",
|
56 |
+
"Dîner": "",
|
57 |
}
|
58 |
|
59 |
# 📋 Affichage du planning pour 7 jours
|
60 |
st.subheader(f"📆 Planning des repas - {selected_week_label} ({selected_year})")
|
61 |
+
df = pd.DataFrame.from_dict(
|
62 |
+
{date: st.session_state["meal_plan"][date] for date in week_dates},
|
63 |
+
orient="index",
|
64 |
+
)
|
65 |
+
df.index = pd.to_datetime(df.index).strftime(
|
66 |
+
"%A %d %B"
|
67 |
+
) # Affichage clair des jours
|
68 |
st.dataframe(df, use_container_width=True)
|
69 |
|
70 |
# ✅ **Message de validation persistant**
|
71 |
if "validation_msg" in st.session_state:
|
72 |
st.success(st.session_state["validation_msg"])
|
73 |
+
st.session_state.pop(
|
74 |
+
"validation_msg"
|
75 |
+
) # Supprimer après affichage pour éviter la persistance infinie
|
76 |
|
77 |
# 🍽️ **Modification et Suppression d'un Repas**
|
78 |
st.subheader("✍️ Modifier un repas")
|
79 |
|
80 |
selected_day = st.selectbox("📅 Choisissez un jour", week_dates)
|
81 |
+
selected_meal = st.selectbox(
|
82 |
+
"🍽️ Sélectionnez le repas", ["Petit-déjeuner", "Déjeuner", "Dîner"]
|
83 |
+
)
|
84 |
|
85 |
# 📌 **Prévisualisation du repas existant**
|
86 |
existing_meal = st.session_state["meal_plan"][selected_day][selected_meal]
|
|
|
88 |
st.markdown(f"🔹 **Repas actuel pour {selected_meal} :**\n> {existing_meal}")
|
89 |
|
90 |
with st.form("meal_form"):
|
91 |
+
meal_input = st.text_area(
|
92 |
+
f"Ajoutez un plat pour {selected_meal} ({selected_day})",
|
93 |
+
value=existing_meal,
|
94 |
+
) # Pré-rempli avec le repas actuel
|
95 |
|
96 |
col1, col2 = st.columns([0.7, 0.3])
|
97 |
with col1:
|
|
|
101 |
|
102 |
if submitted:
|
103 |
st.session_state["meal_plan"][selected_day][selected_meal] = meal_input
|
104 |
+
st.session_state[
|
105 |
+
"validation_msg"
|
106 |
+
] = f"✅ {selected_meal} ajouté/modifié pour le {selected_day} avec succès !"
|
107 |
st.rerun()
|
108 |
|
109 |
if deleted:
|
110 |
st.session_state["meal_plan"][selected_day][selected_meal] = ""
|
111 |
+
st.session_state[
|
112 |
+
"validation_msg"
|
113 |
+
] = f"❌ {selected_meal} supprimé pour le {selected_day} !"
|
114 |
st.rerun()
|
115 |
|
116 |
# 🤖 Ajout des suggestions du chatbot
|
|
|
118 |
|
119 |
# 🔹 Charger les suggestions enregistrées si elles ne sont pas déjà en mémoire
|
120 |
if "chatbot_suggestions" not in st.session_state:
|
121 |
+
st.session_state["chatbot_suggestions"] = load_chatbot_suggestions(
|
122 |
+
db_manager, user_id
|
123 |
+
)
|
124 |
|
125 |
if st.session_state["chatbot_suggestions"]:
|
126 |
with st.form("chatbot_form"):
|
127 |
+
selected_recipe = st.selectbox(
|
128 |
+
"🔍 Sélectionnez une recette", st.session_state["chatbot_suggestions"]
|
129 |
+
)
|
130 |
+
selected_day_for_recipe = st.selectbox(
|
131 |
+
"📅 Assigner à quel jour ?", week_dates
|
132 |
+
)
|
133 |
+
selected_meal_for_recipe = st.selectbox(
|
134 |
+
"🍽️ Assigner à quel repas ?", ["Petit-déjeuner", "Déjeuner", "Dîner"]
|
135 |
+
)
|
136 |
|
137 |
add_recipe = st.form_submit_button("➕ Ajouter la recette au planning")
|
138 |
if add_recipe:
|
139 |
+
st.session_state["meal_plan"][selected_day_for_recipe][
|
140 |
+
selected_meal_for_recipe
|
141 |
+
] += f"\n- {selected_recipe}"
|
142 |
+
st.session_state[
|
143 |
+
"validation_msg"
|
144 |
+
] = f"✅ Recette ajoutée à {selected_meal_for_recipe} ({selected_day_for_recipe}) avec succès !"
|
145 |
st.rerun()
|
146 |
else:
|
147 |
st.write("⚠️ Aucune suggestion du chatbot pour le moment.")
|