Docfile commited on
Commit
a5874c3
·
verified ·
1 Parent(s): 47af5a5

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +446 -200
templates/index.html CHANGED
@@ -4,171 +4,312 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Assistant IA</title>
7
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
 
 
9
  <style>
10
  :root {
11
- --primary-blue: #1E40AF;
12
- --light-blue: #EFF6FF;
13
- --accent-blue: #3B82F6;
 
 
 
 
 
14
  }
15
 
16
  body {
17
  background: linear-gradient(135deg, var(--light-blue) 0%, #ffffff 100%);
18
  min-height: 100vh;
 
19
  }
20
 
 
21
  .glass-effect {
22
- background: rgba(255, 255, 255, 0.9);
23
- backdrop-filter: blur(10px);
24
- border: 1px solid rgba(255, 255, 255, 0.2);
 
 
25
  }
26
 
27
- .message {
28
- transition: transform 0.3s ease;
 
 
 
 
 
29
  }
30
 
31
- .message:hover {
32
- transform: translateY(-2px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
 
35
  .quick-action {
36
  background: linear-gradient(135deg, var(--primary-blue) 0%, var(--accent-blue) 100%);
37
- transition: all 0.3s ease;
 
 
38
  }
39
-
40
  .quick-action:hover {
41
- transform: translateY(-2px);
42
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
 
 
 
 
 
 
 
 
 
 
43
  }
44
 
 
45
  .custom-input {
46
- border: 2px solid #E5E7EB;
47
  transition: all 0.3s ease;
 
 
 
 
 
48
  }
49
-
50
  .custom-input:focus {
51
  border-color: var(--accent-blue);
52
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
53
  outline: none;
54
  }
 
 
 
 
 
55
 
 
56
  @keyframes spin {
57
  0% { transform: rotate(0deg); }
58
  100% { transform: rotate(360deg); }
59
  }
60
-
61
  .loading-spinner {
62
- border-top-color: var(--accent-blue);
 
63
  animation: spin 1s linear infinite;
64
  }
 
 
 
65
 
66
- .file-upload-preview {
67
- background: rgba(59, 130, 246, 0.1);
 
68
  border: 2px dashed var(--accent-blue);
69
  transition: all 0.3s ease;
70
  }
 
 
 
 
 
 
 
 
71
 
72
- .file-upload-preview:hover {
73
- background: rgba(59, 130, 246, 0.2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
  </style>
76
  </head>
77
- <body class="font-sans">
78
- <div class="container mx-auto px-4 py-8 max-w-6xl">
 
79
  <!-- Header -->
80
- <header class="glass-effect rounded-2xl p-6 mb-8 flex justify-between items-center">
81
- <div class="flex items-center space-x-4">
82
- <div class="w-12 h-12 rounded-full bg-blue-600 flex items-center justify-center">
83
- <svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
84
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
85
  </svg>
86
  </div>
87
  <div>
88
- <h1 class="text-2xl font-bold text-blue-900">Assistant IA</h1>
89
- <p class="text-blue-600">En ligne</p>
90
  </div>
91
  </div>
92
- <div class="flex items-center space-x-4">
93
- <label class="flex items-center space-x-2 text-blue-900">
94
- <input type="checkbox" id="webSearchToggle" class="w-4 h-4 text-blue-600 focus:ring-blue-500">
95
  <span>Recherche web</span>
96
  </label>
97
- <button onclick="clearChat()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all duration-300">
98
- Effacer
 
99
  </button>
100
  </div>
101
  </header>
102
 
103
- <!-- Quick Actions -->
104
- <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-8">
105
- <button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2">
106
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
107
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
108
- </svg>
109
  <span>Recherche</span>
110
  </button>
111
- <button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2">
112
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
113
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
114
- </svg>
115
  <span>Brainstorm</span>
116
  </button>
117
- <button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2">
118
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
119
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
120
- </svg>
121
  <span>Analyse</span>
122
  </button>
123
- <button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2">
124
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
125
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
126
- </svg>
127
  <span>Images</span>
128
  </button>
129
- <button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2">
130
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
131
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
132
- </svg>
133
  <span>Code</span>
134
  </button>
135
  </div>
136
 
137
  <!-- Chat Messages -->
138
- <div id="chatMessages" class="glass-effect rounded-2xl p-6 mb-8 h-[500px] overflow-y-auto space-y-4">
139
- <!-- Messages will be inserted here dynamically -->
 
 
 
 
 
 
 
 
 
140
  </div>
141
 
142
  <!-- Input Area -->
143
- <div class="glass-effect rounded-2xl p-6">
 
144
  <div class="mb-4">
145
- <input type="file" id="fileUpload" class="hidden" accept=".jpg,.jpeg,.png,.pdf,.txt,.mp3,.mp4">
146
- <label for="fileUpload" class="file-upload-preview p-4 rounded-xl flex items-center justify-center cursor-pointer hover:bg-blue-50">
147
- <svg class="w-6 h-6 text-blue-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
148
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
149
  </svg>
150
- <span id="fileName" class="text-blue-600">Déposer un fichier</span>
 
151
  </label>
152
  </div>
153
-
154
- <div class="flex space-x-4">
 
155
  <div class="relative flex-grow">
156
- <input type="text" id="messageInput"
157
- class="custom-input w-full rounded-xl px-6 py-4 text-blue-900 placeholder-blue-400"
158
- placeholder="Écrivez votre message...">
 
 
 
 
159
  </div>
160
- <button onclick="sendMessage()" class="quick-action px-8 py-4 rounded-xl flex items-center justify-center">
161
- <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
162
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
163
- </svg>
164
  </button>
165
  </div>
166
  </div>
 
 
 
 
167
  </div>
168
 
169
  <!-- Loading Overlay -->
170
- <div id="loadingOverlay" class="fixed inset-0 bg-white bg-opacity-75 backdrop-filter backdrop-blur-sm hidden flex items-center justify-center z-50">
171
- <div class="loading-spinner w-16 h-16 border-4 border-blue-200 border-t-blue-600 rounded-full"></div>
 
172
  </div>
173
 
174
  <script>
@@ -176,187 +317,292 @@
176
  const chatMessages = document.getElementById('chatMessages');
177
  const webSearchToggle = document.getElementById('webSearchToggle');
178
  const fileUpload = document.getElementById('fileUpload');
179
- const fileName = document.getElementById('fileName');
 
180
  const loadingOverlay = document.getElementById('loadingOverlay');
 
 
 
181
 
182
- function addMessage(content, isUser = false) {
183
- const messageDiv = document.createElement('div');
184
- messageDiv.className = `message flex ${isUser ? 'justify-end' : 'justify-start'}`;
185
 
186
- const innerDiv = document.createElement('div');
187
- innerDiv.className = `max-w-[75%] p-4 rounded-xl ${isUser ? 'bg-blue-600 text-white' : 'bg-white text-blue-900'} shadow-lg`;
188
- innerDiv.innerHTML = marked.parse(content);
 
 
 
189
 
190
- messageDiv.appendChild(innerDiv);
191
- chatMessages.appendChild(messageDiv);
192
- chatMessages.scrollTop = chatMessages.scrollHeight;
 
 
 
193
  }
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  async function sendMessage() {
196
- const message = messageInput.value.trim();
197
- if (!message) return;
 
 
 
 
 
 
 
 
 
 
198
 
199
- addMessage(message, true);
200
- messageInput.value = '';
201
- showLoading();
202
 
203
  try {
204
  const response = await fetch('/send_message', {
205
  method: 'POST',
206
  headers: {
207
- 'Content-Type': 'application/json'
 
208
  },
209
  body: JSON.stringify({
210
- message: message,
211
  web_search: webSearchToggle.checked
 
212
  })
213
  });
214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  const data = await response.json();
216
- hideLoading();
217
  if (data.error) {
218
- addMessage(`Erreur: ${data.error}`);
 
 
 
 
 
219
  } else {
220
- addMessage(data.response);
221
  }
 
222
  } catch (error) {
223
  hideLoading();
224
- addMessage(`Erreur: ${error.message}`);
 
225
  }
226
  }
227
 
228
- messageInput.addEventListener('keypress', (e) => {
229
- if (e.key === 'Enter' && !e.shiftKey) {
230
- e.preventDefault();
231
- sendMessage();
 
 
 
 
 
 
 
232
  }
233
- });
234
 
235
- fileUpload.addEventListener('change', async (e) => {
236
- const file = e.target.files[0];
237
- if (!file) return;
238
 
239
- fileName.textContent = file.name;
 
 
 
 
240
  const formData = new FormData();
241
  formData.append('file', file);
242
 
243
- showLoading();
244
-
245
  try {
246
  const response = await fetch('/upload', {
247
  method: 'POST',
248
  body: formData
 
249
  });
250
 
251
- const data = await response.json();
252
  hideLoading();
253
- if (data.error) {
254
- addMessage(`Erreur: ${data.error}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  } else {
256
- addMessage(`Fichier téléchargé: ${file.name}`);
 
 
257
  }
 
258
  } catch (error) {
259
  hideLoading();
260
- addMessage(`Erreur: ${error.message}`);
 
 
 
 
 
261
  }
262
- });
263
 
264
  async function clearChat() {
 
 
 
 
265
  try {
266
- showLoading();
267
- await fetch('/clear_chat', { method: 'POST' });
268
- chatMessages.innerHTML = '';
269
  hideLoading();
270
- fileName.textContent = 'Déposer un fichier';
271
- fileUpload.value = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  } catch (error) {
273
  hideLoading();
274
- addMessage(`Erreur: ${error.message}`);
 
 
275
  }
276
  }
277
 
278
- function showLoading() {
279
- loadingOverlay.classList.remove('hidden');
280
- }
281
-
282
- function hideLoading() {
283
- loadingOverlay.classList.add('hidden');
284
  }
285
 
286
- document.querySelectorAll('.quick-action').forEach(button => {
287
- button.addEventListener('click', () => {
288
- const text = button.querySelector('span').textContent.trim();
289
- let prompt = '';
290
-
291
- switch (text) {
292
- case 'Recherche':
293
- prompt = "Effectuez une recherche sur ";
294
- break;
295
- case 'Brainstorm':
296
- prompt = "Donnez-moi des idées sur ";
297
- break;
298
- case 'Analyse':
299
- prompt = "Analysez les données suivantes : ";
300
- break;
301
- case 'Images':
302
- prompt = "Créez une image de ";
303
- break;
304
- case 'Code':
305
- prompt = "Écrivez du code pour ";
306
- break;
307
- default:
308
- prompt = "";
309
- }
310
-
311
- messageInput.value = prompt;
312
- messageInput.focus();
313
- // Place le curseur à la fin du texte
314
- messageInput.setSelectionRange(prompt.length, prompt.length);
315
- });
316
- });
317
-
318
- // Fonction pour initialiser l'interface
319
- function initializeChat() {
320
- // Vérifier si des messages existent dans le localStorage
321
- const savedMessages = localStorage.getItem('chatMessages');
322
- if (savedMessages) {
323
- const messages = JSON.parse(savedMessages);
324
- messages.forEach(msg => {
325
- addMessage(msg.content, msg.isUser);
326
- });
327
- }
328
-
329
- // Focus sur l'input au chargement
330
- messageInput.focus();
331
- }
332
-
333
- // Fonction pour sauvegarder les messages
334
- function saveMessage(content, isUser) {
335
- const savedMessages = localStorage.getItem('chatMessages') || '[]';
336
- const messages = JSON.parse(savedMessages);
337
- messages.push({ content, isUser });
338
- localStorage.setItem('chatMessages', JSON.stringify(messages));
339
- }
340
-
341
- // Fonction pour formater les messages avec Markdown
342
- function formatMessage(content) {
343
- return marked.parse(content);
344
- }
345
-
346
- // Gestion des erreurs globale
347
- window.addEventListener('error', function(e) {
348
- console.error('Erreur globale:', e.error);
349
- hideLoading();
350
- addMessage('Une erreur est survenue. Veuillez réessayer.', false);
351
  });
352
 
353
- // Gestion de la déconnexion
354
- window.addEventListener('offline', function() {
355
- addMessage('La connexion internet a été perdue. Vérifiez votre connexion.', false);
356
- });
357
 
358
- // Initialisation de l'interface au chargement
359
- document.addEventListener('DOMContentLoaded', initializeChat);
360
  </script>
 
361
  </body>
362
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Assistant IA</title>
7
+ <!-- Using Tailwind via CDN -->
8
+ <script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
9
+ <!-- Using Marked.js via CDN -->
10
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
  <style>
12
  :root {
13
+ --primary-blue: #1E40AF; /* blue-800 */
14
+ --light-blue: #EFF6FF; /* blue-50 */
15
+ --accent-blue: #3B82F6; /* blue-500 */
16
+ --text-dark: #111827; /* gray-900 */
17
+ --text-light: #4B5563; /* gray-600 */
18
+ --bg-user: #2563EB; /* blue-600 */
19
+ --bg-assistant: #FFFFFF;
20
+ --border-color: #E5E7EB; /* gray-200 */
21
  }
22
 
23
  body {
24
  background: linear-gradient(135deg, var(--light-blue) 0%, #ffffff 100%);
25
  min-height: 100vh;
26
+ font-family: 'Inter', sans-serif; /* Consider adding Inter font via Google Fonts */
27
  }
28
 
29
+ /* Enhanced Glass Effect */
30
  .glass-effect {
31
+ background: rgba(255, 255, 255, 0.8); /* Slightly less transparent */
32
+ backdrop-filter: blur(12px) saturate(180%); /* Increased blur and saturation */
33
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
34
+ border: 1px solid rgba(230, 230, 230, 0.3); /* Softer border */
35
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15); /* Softer shadow */
36
  }
37
 
38
+ /* Message Styling */
39
+ .message-container {
40
+ display: flex;
41
+ margin-bottom: 1rem;
42
+ opacity: 0;
43
+ transform: translateY(10px);
44
+ animation: fadeIn 0.5s ease forwards;
45
  }
46
 
47
+ @keyframes fadeIn {
48
+ to {
49
+ opacity: 1;
50
+ transform: translateY(0);
51
+ }
52
+ }
53
+
54
+ .message-bubble {
55
+ max-width: 75%;
56
+ padding: 0.75rem 1rem; /* 12px 16px */
57
+ border-radius: 1rem; /* large */
58
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
59
+ word-wrap: break-word; /* Ensure long words break */
60
+ overflow-wrap: break-word; /* Modern standard */
61
+ }
62
+
63
+ .message-container.user {
64
+ justify-content: flex-end;
65
+ }
66
+ .message-container.assistant {
67
+ justify-content: flex-start;
68
+ }
69
+
70
+ .message-bubble.user {
71
+ background-color: var(--bg-user);
72
+ color: white;
73
+ border-bottom-right-radius: 0.25rem; /* small */
74
+ }
75
+ .message-bubble.assistant {
76
+ background-color: var(--bg-assistant);
77
+ color: var(--text-dark);
78
+ border: 1px solid var(--border-color);
79
+ border-bottom-left-radius: 0.25rem; /* small */
80
+ }
81
+ /* Styling for code blocks generated by marked.js */
82
+ .message-bubble pre {
83
+ background-color: #f3f4f6; /* gray-100 */
84
+ color: #1f2937; /* gray-800 */
85
+ padding: 1em;
86
+ border-radius: 0.5rem; /* md */
87
+ overflow-x: auto;
88
+ font-family: 'Courier New', Courier, monospace;
89
+ font-size: 0.9em;
90
+ margin: 0.5em 0;
91
+ }
92
+ .message-bubble code:not(pre code) { /* Inline code */
93
+ background-color: #e5e7eb; /* gray-200 */
94
+ padding: 0.2em 0.4em;
95
+ border-radius: 0.25rem;
96
+ font-size: 0.9em;
97
+ }
98
+ .message-bubble ul, .message-bubble ol {
99
+ padding-left: 1.5rem;
100
+ margin-top: 0.5rem;
101
+ margin-bottom: 0.5rem;
102
+ }
103
+ .message-bubble li > p { /* Prevent extra margins inside list items */
104
+ margin-bottom: 0.25rem;
105
+ }
106
+ .message-bubble blockquote {
107
+ border-left: 4px solid var(--accent-blue);
108
+ padding-left: 1rem;
109
+ margin-left: 0;
110
+ color: var(--text-light);
111
+ font-style: italic;
112
  }
113
 
114
+ /* Quick Actions */
115
  .quick-action {
116
  background: linear-gradient(135deg, var(--primary-blue) 0%, var(--accent-blue) 100%);
117
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
118
+ color: white;
119
+ font-weight: 500;
120
  }
 
121
  .quick-action:hover {
122
+ transform: translateY(-3px) scale(1.03);
123
+ box-shadow: 0 6px 15px rgba(59, 130, 246, 0.35);
124
+ }
125
+ .quick-action:active {
126
+ transform: translateY(-1px) scale(0.98);
127
+ box-shadow: 0 3px 8px rgba(59, 130, 246, 0.25);
128
+ }
129
+ .quick-action svg {
130
+ transition: transform 0.2s ease-in-out;
131
+ }
132
+ .quick-action:hover svg {
133
+ transform: rotate(-5deg) scale(1.1);
134
  }
135
 
136
+ /* Input Area */
137
  .custom-input {
138
+ border: 2px solid var(--border-color);
139
  transition: all 0.3s ease;
140
+ color: var(--text-dark);
141
+ padding-right: 3rem; /* Space for potential icons inside input */
142
+ }
143
+ .custom-input::placeholder {
144
+ color: #9ca3af; /* gray-400 */
145
  }
 
146
  .custom-input:focus {
147
  border-color: var(--accent-blue);
148
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
149
  outline: none;
150
  }
151
+ /* Send Button */
152
+ #sendButton {
153
+ /* Inherits quick-action styles */
154
+ min-width: 80px; /* Ensure button doesn't shrink too much */
155
+ }
156
 
157
+ /* Loading Spinner */
158
  @keyframes spin {
159
  0% { transform: rotate(0deg); }
160
  100% { transform: rotate(360deg); }
161
  }
 
162
  .loading-spinner {
163
+ border: 4px solid rgba(59, 130, 246, 0.2); /* Lighter border */
164
+ border-top-color: var(--accent-blue); /* Spinner color */
165
  animation: spin 1s linear infinite;
166
  }
167
+ #loadingOverlay {
168
+ transition: opacity 0.3s ease-in-out;
169
+ }
170
 
171
+ /* File Upload Preview */
172
+ .file-upload-area {
173
+ background: rgba(59, 130, 246, 0.05);
174
  border: 2px dashed var(--accent-blue);
175
  transition: all 0.3s ease;
176
  }
177
+ .file-upload-area:hover {
178
+ background: rgba(59, 130, 246, 0.1);
179
+ border-color: var(--primary-blue);
180
+ }
181
+ #fileName {
182
+ font-size: 0.9rem;
183
+ font-style: italic;
184
+ }
185
 
186
+ /* Scrollbar */
187
+ #chatMessages::-webkit-scrollbar {
188
+ width: 8px;
189
+ }
190
+ #chatMessages::-webkit-scrollbar-track {
191
+ background: var(--light-blue);
192
+ border-radius: 10px;
193
+ }
194
+ #chatMessages::-webkit-scrollbar-thumb {
195
+ background-color: var(--accent-blue);
196
+ border-radius: 10px;
197
+ border: 2px solid var(--light-blue);
198
+ }
199
+ #chatMessages {
200
+ scrollbar-width: thin;
201
+ scrollbar-color: var(--accent-blue) var(--light-blue);
202
  }
203
  </style>
204
  </head>
205
+ <body class="text-gray-800">
206
+ <div class="container mx-auto px-4 py-8 max-w-4xl"> <!-- Centered & Max Width -->
207
+
208
  <!-- Header -->
209
+ <header class="glass-effect rounded-2xl p-4 md:p-6 mb-6 flex flex-col sm:flex-row justify-between items-center">
210
+ <div class="flex items-center space-x-3 mb-4 sm:mb-0">
211
+ <div class="w-12 h-12 rounded-full bg-gradient-to-br from-blue-600 to-blue-800 flex items-center justify-center shadow-md">
212
+ <svg class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
213
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
214
  </svg>
215
  </div>
216
  <div>
217
+ <h1 class="text-xl md:text-2xl font-bold text-blue-900">Assistant IA</h1>
218
+ <p class="text-sm text-green-600 font-medium">En ligne</p>
219
  </div>
220
  </div>
221
+ <div class="flex items-center space-x-3">
222
+ <label class="flex items-center space-x-2 text-sm text-blue-900 cursor-pointer hover:text-blue-700">
223
+ <input type="checkbox" id="webSearchToggle" class="form-checkbox h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
224
  <span>Recherche web</span>
225
  </label>
226
+ <button id="clearChatButton" title="Effacer la conversation" class="bg-red-500 hover:bg-red-600 text-white px-3 py-2 rounded-lg transition-colors duration-200 text-sm font-medium flex items-center space-x-1">
227
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
228
+ <span>Effacer</span>
229
  </button>
230
  </div>
231
  </header>
232
 
233
+ <!-- Quick Actions (Optional: Keep or remove based on preference) -->
234
+ <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3 mb-6">
235
+ <!-- Example Quick Action Button -->
236
+ <button data-prompt="Effectuez une recherche sur " class="quick-action p-3 rounded-xl flex flex-col sm:flex-row items-center justify-center space-y-1 sm:space-y-0 sm:space-x-2 text-xs sm:text-sm">
237
+ <svg class="w-5 h-5 mb-1 sm:mb-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
 
238
  <span>Recherche</span>
239
  </button>
240
+ <button data-prompt="Donnez-moi des idées sur " class="quick-action p-3 rounded-xl flex flex-col sm:flex-row items-center justify-center space-y-1 sm:space-y-0 sm:space-x-2 text-xs sm:text-sm">
241
+ <svg class="w-5 h-5 mb-1 sm:mb-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
 
 
242
  <span>Brainstorm</span>
243
  </button>
244
+ <button data-prompt="Analysez les données suivantes : " class="quick-action p-3 rounded-xl flex flex-col sm:flex-row items-center justify-center space-y-1 sm:space-y-0 sm:space-x-2 text-xs sm:text-sm">
245
+ <svg class="w-5 h-5 mb-1 sm:mb-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
 
 
246
  <span>Analyse</span>
247
  </button>
248
+ <button data-prompt="Créez une image de " class="quick-action p-3 rounded-xl flex flex-col sm:flex-row items-center justify-center space-y-1 sm:space-y-0 sm:space-x-2 text-xs sm:text-sm">
249
+ <svg class="w-5 h-5 mb-1 sm:mb-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
 
 
250
  <span>Images</span>
251
  </button>
252
+ <button data-prompt="Écrivez du code pour " class="quick-action p-3 rounded-xl flex flex-col sm:flex-row items-center justify-center space-y-1 sm:space-y-0 sm:space-x-2 text-xs sm:text-sm">
253
+ <svg class="w-5 h-5 mb-1 sm:mb-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>
 
 
254
  <span>Code</span>
255
  </button>
256
  </div>
257
 
258
  <!-- Chat Messages -->
259
+ <div id="chatMessages" class="glass-effect rounded-2xl p-4 md:p-6 mb-6 h-[50vh] md:h-[60vh] overflow-y-auto space-y-4">
260
+ <!-- Messages Rendered by Server (Jinja2) -->
261
+ {% for message in messages %}
262
+ <div class="message-container {{ message.role }}"> {# Use role for class #}
263
+ <div class="message-bubble {{ message.role }}">
264
+ {# Use marked.parse and sanitize output #}
265
+ {{ message.content | safe }} {# Assume backend sends sanitized HTML or use JS #}
266
+ </div>
267
+ </div>
268
+ {% endfor %}
269
+ <!-- New messages will be appended here by JavaScript -->
270
  </div>
271
 
272
  <!-- Input Area -->
273
+ <div class="glass-effect rounded-2xl p-4 md:p-6">
274
+ <!-- File Upload Area -->
275
  <div class="mb-4">
276
+ <input type="file" id="fileUpload" class="hidden" accept=".jpg,.jpeg,.png,.gif,.webp,.heic,.heif,.pdf,.txt,.mp3,.wav,.ogg,.mp4,.mov,.avi">
277
+ <label for="fileUpload" class="file-upload-area p-4 rounded-xl flex flex-col items-center justify-center cursor-pointer hover:bg-blue-50 text-center">
278
+ <svg class="w-8 h-8 text-blue-500 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
279
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
280
  </svg>
281
+ <span id="fileName" class="text-blue-600 text-sm">Cliquez ou déposez un fichier ici (Max 100Mo)</span>
282
+ <span id="uploadStatus" class="text-xs text-gray-500 mt-1"></span>
283
  </label>
284
  </div>
285
+
286
+ <!-- Text Input & Send Button -->
287
+ <div class="flex items-center space-x-3">
288
  <div class="relative flex-grow">
289
+ <textarea id="messageInput"
290
+ class="custom-input w-full rounded-xl px-4 py-3 pr-12 text-sm md:text-base resize-none border-gray-300 focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
291
+ placeholder="Écrivez votre message... (Shift+Enter pour nouvelle ligne)"
292
+ rows="1"
293
+ style="min-height: 48px; max-height: 150px;"
294
+ oninput="this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px';"
295
+ ></textarea>
296
  </div>
297
+ <button id="sendButton" title="Envoyer le message" class="quick-action px-4 py-3 rounded-xl flex items-center justify-center self-end transition-transform duration-150 ease-in-out">
298
+ <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
299
+ <span class="sr-only">Envoyer</span>
 
300
  </button>
301
  </div>
302
  </div>
303
+
304
+ <footer class="text-center mt-6 text-xs text-gray-500">
305
+ Propulsé par Gemini | Conçu par Youssouf
306
+ </footer>
307
  </div>
308
 
309
  <!-- Loading Overlay -->
310
+ <div id="loadingOverlay" class="fixed inset-0 bg-white bg-opacity-75 backdrop-filter backdrop-blur-sm hidden items-center justify-center z-50" style="display: none;">
311
+ <div class="loading-spinner w-16 h-16 rounded-full"></div>
312
+ <span id="loadingText" class="ml-4 text-blue-800 font-medium">Chargement...</span>
313
  </div>
314
 
315
  <script>
 
317
  const chatMessages = document.getElementById('chatMessages');
318
  const webSearchToggle = document.getElementById('webSearchToggle');
319
  const fileUpload = document.getElementById('fileUpload');
320
+ const fileNameLabel = document.getElementById('fileName');
321
+ const uploadStatusLabel = document.getElementById('uploadStatus');
322
  const loadingOverlay = document.getElementById('loadingOverlay');
323
+ const loadingText = document.getElementById('loadingText');
324
+ const sendButton = document.getElementById('sendButton');
325
+ const clearChatButton = document.getElementById('clearChatButton');
326
 
327
+ let currentFileName = null; // Track uploaded file name
 
 
328
 
329
+ // --- Utility Functions ---
330
+ function showLoading(text = "Chargement...") {
331
+ loadingText.textContent = text;
332
+ loadingOverlay.style.display = 'flex'; // Use style.display
333
+ loadingOverlay.style.opacity = 1;
334
+ }
335
 
336
+ function hideLoading() {
337
+ // Add a small delay for smoother transition if needed
338
+ loadingOverlay.style.opacity = 0;
339
+ setTimeout(() => {
340
+ loadingOverlay.style.display = 'none';
341
+ }, 300); // Match CSS transition duration
342
  }
343
 
344
+ function sanitizeHTML(str) {
345
+ // Basic sanitizer (consider a more robust library like DOMPurify for production)
346
+ const temp = document.createElement('div');
347
+ temp.textContent = str;
348
+ return temp.innerHTML;
349
+ }
350
+
351
+ function addMessage(content, role = 'assistant') {
352
+ const messageContainer = document.createElement('div');
353
+ messageContainer.className = `message-container ${role}`; // 'user' or 'assistant'
354
+
355
+ const messageBubble = document.createElement('div');
356
+ messageBubble.className = `message-bubble ${role}`;
357
+
358
+ // Use marked.parse for Markdown rendering
359
+ // Ensure marked is loaded before this script runs
360
+ if (typeof marked === 'undefined') {
361
+ console.error("Marked.js library is not loaded.");
362
+ messageBubble.textContent = content; // Fallback to plain text
363
+ } else {
364
+ // Basic sanitization - FOR PRODUCTION, USE A ROBUST LIBRARY (e.g., DOMPurify)
365
+ // marked.parse might return HTML, ensure it's safe before inserting
366
+ const rawHtml = marked.parse(content);
367
+ // Simple placeholder for sanitization concept:
368
+ // const sanitizedHtml = yourSanitizationFunction(rawHtml);
369
+ // For now, we'll trust the backend/marked output for this example, but BE CAREFUL.
370
+ messageBubble.innerHTML = rawHtml; // Potentially unsafe if content isn't trusted
371
+ }
372
+
373
+ messageContainer.appendChild(messageBubble);
374
+ chatMessages.appendChild(messageContainer);
375
+
376
+ // Smooth scroll to the bottom
377
+ chatMessages.scrollTo({ top: chatMessages.scrollHeight, behavior: 'smooth' });
378
+ }
379
+
380
+ // --- Event Listeners ---
381
+ sendButton.addEventListener('click', sendMessage);
382
+ messageInput.addEventListener('keypress', (e) => {
383
+ if (e.key === 'Enter' && !e.shiftKey) {
384
+ e.preventDefault(); // Prevent default Enter behavior (new line)
385
+ sendMessage();
386
+ }
387
+ });
388
+
389
+ clearChatButton.addEventListener('click', clearChat);
390
+
391
+ fileUpload.addEventListener('change', handleFileUpload);
392
+
393
+ document.querySelectorAll('.quick-action[data-prompt]').forEach(button => {
394
+ button.addEventListener('click', () => {
395
+ const promptText = button.getAttribute('data-prompt');
396
+ messageInput.value = promptText;
397
+ messageInput.focus();
398
+ // Auto-resize textarea after setting value
399
+ messageInput.style.height = 'auto';
400
+ messageInput.style.height = (messageInput.scrollHeight) + 'px';
401
+ // Optional: Place cursor at the end
402
+ messageInput.setSelectionRange(promptText.length, promptText.length);
403
+ });
404
+ });
405
+
406
+
407
+ // --- Core Functions ---
408
  async function sendMessage() {
409
+ const messageText = messageInput.value.trim();
410
+ if (!messageText && !currentFileName) { // Don't send empty messages unless a file is context
411
+ console.log("Empty message, not sending.");
412
+ return;
413
+ }
414
+
415
+ // Add user message optimistically to UI
416
+ // Use the raw text, backend response will be formatted
417
+ addMessage(sanitizeHTML(messageText), 'user');
418
+ const userMessageToSend = messageText; // Keep the original text for sending
419
+ messageInput.value = ''; // Clear input
420
+ messageInput.style.height = '48px'; // Reset textarea height
421
 
422
+ showLoading("Envoi en cours...");
 
 
423
 
424
  try {
425
  const response = await fetch('/send_message', {
426
  method: 'POST',
427
  headers: {
428
+ 'Content-Type': 'application/json',
429
+ 'Accept': 'application/json' // Explicitly accept JSON
430
  },
431
  body: JSON.stringify({
432
+ message: userMessageToSend,
433
  web_search: webSearchToggle.checked
434
+ // File info is now handled server-side via session
435
  })
436
  });
437
 
438
+ hideLoading(); // Hide loading indicator once response headers are received
439
+
440
+ if (!response.ok) {
441
+ // Try to get error message from JSON response body
442
+ let errorMsg = `Erreur HTTP: ${response.status}`;
443
+ try {
444
+ const errorData = await response.json();
445
+ errorMsg = `Erreur: ${errorData.error || response.statusText}`;
446
+ } catch (jsonError) {
447
+ // If response is not JSON, use statusText
448
+ console.warn("Could not parse error response as JSON:", jsonError);
449
+ }
450
+ addMessage(errorMsg, 'assistant'); // Display error in chat
451
+ console.error("Send message error:", errorMsg);
452
+ return; // Stop processing on error
453
+ }
454
+
455
  const data = await response.json();
456
+
457
  if (data.error) {
458
+ addMessage(`Erreur: ${sanitizeHTML(data.error)}`, 'assistant');
459
+ } else if (data.response) {
460
+ addMessage(data.response, 'assistant'); // Add AI response (already Markdown processed if backend does it, or process here)
461
+ // If files were processed, reset the file indicator?
462
+ // Decide based on whether files persist across messages
463
+ // resetFileUploadUI(); // Uncomment if needed
464
  } else {
465
+ addMessage("Réponse inattendue du serveur.", 'assistant');
466
  }
467
+
468
  } catch (error) {
469
  hideLoading();
470
+ console.error('Erreur lors de l\'envoi du message:', error);
471
+ addMessage(`Erreur de connexion ou de traitement: ${sanitizeHTML(error.message)}`, 'assistant');
472
  }
473
  }
474
 
475
+ async function handleFileUpload(event) {
476
+ const file = event.target.files[0];
477
+ if (!file) return;
478
+
479
+ // Basic validation (optional: add more checks like type if needed)
480
+ const maxSize = 100 * 1024 * 1024; // 100MB
481
+ if (file.size > maxSize) {
482
+ uploadStatusLabel.textContent = `Fichier trop volumineux (Max ${maxSize / 1024 / 1024}Mo).`;
483
+ uploadStatusLabel.style.color = 'red';
484
+ fileUpload.value = ''; // Clear the file input
485
+ return;
486
  }
 
487
 
 
 
 
488
 
489
+ fileNameLabel.textContent = `Fichier sélectionné : ${file.name}`;
490
+ uploadStatusLabel.textContent = 'Upload en cours...';
491
+ uploadStatusLabel.style.color = 'gray';
492
+ showLoading("Téléversement du fichier...");
493
+
494
  const formData = new FormData();
495
  formData.append('file', file);
496
 
 
 
497
  try {
498
  const response = await fetch('/upload', {
499
  method: 'POST',
500
  body: formData
501
+ // No 'Content-Type' header needed, browser sets it for FormData
502
  });
503
 
 
504
  hideLoading();
505
+ const data = await response.json();
506
+
507
+ if (!response.ok || data.error) {
508
+ const errorMsg = data.error || `Erreur HTTP ${response.status}`;
509
+ uploadStatusLabel.textContent = `Échec: ${sanitizeHTML(errorMsg)}`;
510
+ uploadStatusLabel.style.color = 'red';
511
+ fileNameLabel.textContent = 'Cliquez ou déposez un fichier ici (Max 100Mo)'; // Reset label
512
+ fileUpload.value = ''; // Clear the file input
513
+ currentFileName = null;
514
+ console.error("File upload error:", errorMsg);
515
+ } else if (data.success) {
516
+ uploadStatusLabel.textContent = 'Prêt à être envoyé avec votre message.';
517
+ uploadStatusLabel.style.color = 'green';
518
+ currentFileName = data.filename; // Store filename indicating success
519
+ // Optionally add a message to chat confirming upload?
520
+ // addMessage(`Fichier "${sanitizeHTML(data.filename)}" prêt.`, 'system'); // Example system message
521
  } else {
522
+ uploadStatusLabel.textContent = 'Réponse serveur inattendue.';
523
+ uploadStatusLabel.style.color = 'red';
524
+ currentFileName = null;
525
  }
526
+
527
  } catch (error) {
528
  hideLoading();
529
+ console.error('Erreur lors de l\'upload:', error);
530
+ uploadStatusLabel.textContent = `Erreur: ${sanitizeHTML(error.message)}`;
531
+ uploadStatusLabel.style.color = 'red';
532
+ fileNameLabel.textContent = 'Cliquez ou déposez un fichier ici (Max 100Mo)'; // Reset label
533
+ fileUpload.value = ''; // Clear the file input
534
+ currentFileName = null;
535
  }
536
+ }
537
 
538
  async function clearChat() {
539
+ if (!confirm("Êtes-vous sûr de vouloir effacer toute la conversation et les fichiers uploadés ?")) {
540
+ return;
541
+ }
542
+ showLoading("Effacement...");
543
  try {
544
+ const response = await fetch('/clear_chat', { method: 'POST' });
 
 
545
  hideLoading();
546
+ if (!response.ok) {
547
+ throw new Error(`Erreur serveur: ${response.status}`);
548
+ }
549
+ const data = await response.json();
550
+ if (data.success) {
551
+ chatMessages.innerHTML = ''; // Clear messages on frontend
552
+ resetFileUploadUI();
553
+ messageInput.value = ''; // Clear input field
554
+ messageInput.style.height = '48px'; // Reset textarea height
555
+ console.log("Chat cleared successfully.");
556
+ // Optionally add a confirmation message
557
+ // addMessage("Conversation effacée.", "system");
558
+ } else {
559
+ throw new Error("Le serveur n'a pas confirmé l'effacement.");
560
+ }
561
  } catch (error) {
562
  hideLoading();
563
+ console.error('Erreur lors de l\'effacement:', error);
564
+ // Maybe display error in a non-chat way (e.g., alert or status bar)
565
+ alert(`Erreur lors de l'effacement : ${error.message}`);
566
  }
567
  }
568
 
569
+ function resetFileUploadUI() {
570
+ fileNameLabel.textContent = 'Cliquez ou déposez un fichier ici (Max 100Mo)';
571
+ uploadStatusLabel.textContent = '';
572
+ fileUpload.value = ''; // Clear the actual file input
573
+ currentFileName = null;
 
574
  }
575
 
576
+ // --- Initial Setup ---
577
+ // Scroll to bottom on initial load (after server-rendered messages)
578
+ window.addEventListener('load', () => {
579
+ chatMessages.scrollTop = chatMessages.scrollHeight;
580
+ // Initial resize check for text area in case of cached content
581
+ messageInput.style.height = 'auto';
582
+ messageInput.style.height = (messageInput.scrollHeight) + 'px';
583
+ messageInput.focus(); // Focus input on load
584
+
585
+ // Initial rendering of messages from server uses Jinja template.
586
+ // We need to apply marked AFTER the page loads to the server-rendered content.
587
+ document.querySelectorAll('.message-bubble').forEach(bubble => {
588
+ // Re-parse content that came from the server template if it wasn't pre-rendered as HTML
589
+ // This assumes the server sent raw markdown in message.content
590
+ // If the server sends pre-rendered HTML via `| safe`, this step might not be needed
591
+ // or could cause double-rendering issues. Adjust based on backend output.
592
+
593
+ // Let's assume backend sends raw markdown for consistency with JS added messages:
594
+ if (typeof marked !== 'undefined') {
595
+ const currentContent = bubble.innerHTML; // Get the raw content from template
596
+ bubble.innerHTML = marked.parse(currentContent); // Parse and replace
597
+ }
598
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  });
600
 
601
+ // Optional: Add online/offline status indicators
602
+ window.addEventListener('offline', () => addMessage('⚠️ Vous êtes hors ligne. Vérifiez votre connexion.', 'system'));
603
+ window.addEventListener('online', () => addMessage('✅ Vous êtes de nouveau en ligne.', 'system'));
 
604
 
 
 
605
  </script>
606
+
607
  </body>
608
  </html>