Sahm269 commited on
Commit
7bd7bef
·
verified ·
1 Parent(s): 8bb4c20

Upload 22 files

Browse files
.gitattributes CHANGED
@@ -34,3 +34,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
37
+ client/assets/avatar_bot_big.jpg filter=lfs diff=lfs merge=lfs -text
38
+ client/assets/avatar_bot_medium.jpg filter=lfs diff=lfs merge=lfs -text
39
+ client/assets/logo.png filter=lfs diff=lfs merge=lfs -text
40
+ client/assets/membre1.jpg filter=lfs diff=lfs merge=lfs -text
41
+ client/assets/membre2.jpg filter=lfs diff=lfs merge=lfs -text
42
+ client/assets/membre3.jpg filter=lfs diff=lfs merge=lfs -text
client/__init__.py ADDED
File without changes
client/assets/avatar_bot_big.jpg ADDED

Git LFS Details

  • SHA256: 21d767c9ccdc22263cf5a87b437e47001ec30a9930c698f99874428530248ff8
  • Pointer size: 131 Bytes
  • Size of remote file: 760 kB
client/assets/avatar_bot_medium.jpg ADDED

Git LFS Details

  • SHA256: d6978063c70568c67735c7130f4e889a04448b0fdd2079650a0effa68d9d7e1f
  • Pointer size: 131 Bytes
  • Size of remote file: 107 kB
client/assets/avatar_bot_small.jpg ADDED
client/assets/logo.png ADDED

Git LFS Details

  • SHA256: ec2063eb9218ca8a66c543661499ffbc4948b7e4c70dc414d72f3340747bc2e6
  • Pointer size: 131 Bytes
  • Size of remote file: 771 kB
client/assets/membre1.jpg ADDED

Git LFS Details

  • SHA256: 7324c3a85b3db72ea0803373d95651c40ca56ca4a6dc40e1ec89d1cbfee2faa6
  • Pointer size: 131 Bytes
  • Size of remote file: 140 kB
client/assets/membre2.jpg ADDED

Git LFS Details

  • SHA256: 999511f03af05bd53cdf73523242504ed66b02689e328368ffc7f67df338011c
  • Pointer size: 131 Bytes
  • Size of remote file: 131 kB
client/assets/membre3.jpg ADDED

Git LFS Details

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