Sahm269 commited on
Commit
5b2d4e7
·
verified ·
1 Parent(s): 0daf169

Upload 12 files

Browse files
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
- """, unsafe_allow_html=True)
 
 
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(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)
 
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
- """, 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)
@@ -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
- """, 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>
@@ -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
- """, 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
@@ -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
- {"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
@@ -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(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)
 
 
 
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(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
@@ -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(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
@@ -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(db_manager, user_id)
 
 
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['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:
@@ -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(f"📅 {timestamp} {latency_text}") # 🔹 Ajout de l'heure et de la latence
 
 
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("⚠️ 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
 
@@ -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(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
@@ -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(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
 
@@ -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(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:
@@ -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 # 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)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 "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}"):
@@ -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("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"):
@@ -64,30 +70,41 @@ def nutri_page():
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
 
@@ -98,7 +115,9 @@ def nutri_page():
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:
@@ -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("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)
 
 
 
 
 
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 = %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
@@ -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
- 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}")
 
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
- <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`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- def favoris():
4
- st.subheader("Favoris")
5
- st.write("Voici vos recettes ou restaurants favoris.")
6
- st.button("Ajouter aux favoris")
 
 
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['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
 
 
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
- get_db_manager,
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(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
@@ -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": "", "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]
@@ -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(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:
@@ -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["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
@@ -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(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.")
 
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.")