Docfile commited on
Commit
2482b18
·
verified ·
1 Parent(s): 92666fa

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +177 -379
templates/index.html CHANGED
@@ -3,371 +3,184 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title> AI Assistant - Flat Black & White</title>
7
- <style>
8
- /* --- RESET ET CONFIGURATION GLOBALE --- */
9
- * {
10
- margin: 0;
11
- padding: 0;
12
- box-sizing: border-box;
13
- }
14
-
15
- html {
16
- scroll-behavior: smooth;
17
- }
18
-
19
- body {
20
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
- background-color: #121212; /* Fond sombre principal */
22
- color: #f1f1f1; /* Couleur de texte par défaut */
23
- min-height: 100vh;
24
- display: flex;
25
- justify-content: center;
26
- align-items: center;
27
- padding: 0; /* Pas de padding sur le body pour le mobile */
28
- }
29
-
30
- /* --- CONTENEUR PRINCIPAL DU CHAT --- */
31
- .chat-container {
32
- width: 100%;
33
- max-width: 800px;
34
- height: 100vh; /* Prend toute la hauteur par défaut (mobile-first) */
35
- background-color: #1e1e1e; /* Fond du conteneur légèrement plus clair */
36
- display: flex;
37
- flex-direction: column;
38
- overflow: hidden;
39
- }
40
-
41
- /* --- EN-TÊTE --- */
42
- .header {
43
- background-color: #1e1e1e;
44
- color: #ffffff;
45
- padding: 20px;
46
- text-align: center;
47
- border-bottom: 1px solid #333; /* Séparateur plat */
48
- flex-shrink: 0;
49
- }
50
-
51
- .header h1 {
52
- font-size: 22px;
53
- font-weight: 600;
54
- margin-bottom: 5px;
55
  }
56
-
57
- .header p {
58
- font-size: 14px;
59
- color: #aaa;
 
60
  }
61
-
62
- .controls {
63
- display: flex;
64
- justify-content: center;
65
- align-items: center;
66
- gap: 20px;
67
- margin-top: 15px;
68
  }
69
-
70
- .thinking-toggle {
71
- display: flex;
72
- align-items: center;
73
- gap: 10px;
74
- font-size: 14px;
75
- color: #ccc;
76
  }
77
-
78
- .switch {
79
- position: relative;
80
- display: inline-block;
81
- width: 50px;
82
- height: 28px;
83
  }
84
 
85
- .switch input { opacity: 0; width: 0; height: 0; }
86
-
87
- .slider {
88
- position: absolute;
89
- cursor: pointer;
90
- top: 0;
91
- left: 0;
92
- right: 0;
93
- bottom: 0;
94
- background-color: #444;
95
- transition: .2s;
96
- border-radius: 28px;
97
- }
98
-
99
- .slider:before {
100
- position: absolute;
101
- content: "";
102
- height: 20px;
103
- width: 20px;
104
- left: 4px;
105
- bottom: 4px;
106
- background-color: #f1f1f1;
107
- transition: .2s;
108
- border-radius: 50%;
109
  }
110
-
111
- input:checked + .slider { background-color: #777; }
112
- input:checked + .slider:before { transform: translateX(22px); }
113
-
114
- .btn-reset {
115
- background: transparent;
116
- border: 1px solid #555;
117
- color: #ccc;
118
- padding: 8px 16px;
119
- border-radius: 20px;
120
- cursor: pointer;
121
- font-size: 14px;
122
- transition: background-color 0.2s ease;
123
  }
124
 
125
- .btn-reset:hover { background-color: #333; }
126
-
127
- /* --- ZONE DES MESSAGES --- */
128
- .messages {
129
- flex: 1;
130
- overflow-y: auto;
131
- padding: 20px 15px;
132
- display: flex;
133
- flex-direction: column;
134
- gap: 15px;
135
  }
136
-
137
- .message {
138
- max-width: 85%;
139
- padding: 12px 18px;
140
- border-radius: 18px;
141
- font-size: 16px;
142
- line-height: 1.5;
143
  animation: fadeIn 0.3s ease;
144
  }
145
 
146
- .message.user {
147
- background-color: #3a3a3a; /* Couleur message utilisateur */
148
- color: #f1f1f1;
149
- align-self: flex-end;
150
- border-bottom-right-radius: 4px;
151
- }
152
-
153
- .message.assistant {
154
- background-color: #2c2c2c; /* Couleur message assistant */
155
- color: #e0e0e0;
156
- align-self: flex-start;
157
- border-bottom-left-radius: 4px;
158
- }
159
-
160
- .message ul { margin-top: 10px; padding-left: 20px; }
161
-
162
- .thoughts {
163
- background: #252525;
164
- color: #aaa;
165
- border-left: 3px solid #666; /* Indicateur visuel plat */
166
- margin: 10px 0;
167
- padding: 15px;
168
- border-radius: 8px;
169
- font-style: italic;
170
- font-size: 14px;
171
- }
172
-
173
- .thoughts-header {
174
  font-weight: 600;
175
- margin-bottom: 8px;
176
- display: flex;
177
- align-items: center;
178
- gap: 8px;
179
- color: #ccc;
180
- }
181
-
182
- /* --- ZONE DE SAISIE --- */
183
- .input-container {
184
- padding: 15px;
185
- background-color: #1e1e1e;
186
- border-top: 1px solid #333;
187
- flex-shrink: 0;
188
- }
189
-
190
- .input-row { display: flex; gap: 10px; align-items: flex-end; }
191
- .input-wrapper { flex: 1; position: relative; }
192
-
193
- .message-input {
194
- width: 100%;
195
- min-height: 48px;
196
- padding: 12px 55px 12px 20px;
197
- border: 1px solid #444;
198
- border-radius: 24px;
199
- font-size: 16px;
200
- resize: none;
201
- outline: none;
202
- transition: border-color 0.2s ease;
203
- font-family: inherit;
204
- background-color: #2c2c2c;
205
- color: #f1f1f1;
206
- }
207
-
208
- .message-input::placeholder { color: #777; }
209
- .message-input:focus { border-color: #888; }
210
-
211
- .send-button {
212
- position: absolute;
213
- right: 4px;
214
- top: 50%;
215
- transform: translateY(-50%);
216
- background-color: #333;
217
- border: none;
218
- border-radius: 50%;
219
- width: 40px;
220
- height: 40px;
221
- color: #f1f1f1;
222
- cursor: pointer;
223
- transition: background-color 0.2s ease;
224
- display: flex;
225
- align-items: center;
226
- justify-content: center;
227
- font-size: 20px; /* Taille de l'icône */
228
  }
229
-
230
- .send-button:hover:not(:disabled) { background-color: #444; }
231
- .send-button:disabled { background: #252525; color: #555; cursor: not-allowed; }
232
-
233
- .file-input { display: none; }
234
-
235
- .file-button {
 
 
 
 
 
 
 
 
 
 
236
  background: #333;
237
- border: none;
238
- color: #f1f1f1;
239
- height: 48px;
240
- width: 48px;
241
- border-radius: 50%;
242
- cursor: pointer;
243
- font-size: 20px; /* Taille de l'icône */
244
- transition: background-color 0.2s ease;
245
- flex-shrink: 0;
246
  }
247
-
248
- .file-button:hover { background-color: #444; }
249
-
250
- .file-preview {
251
- margin-bottom: 10px;
252
- padding: 10px;
253
- background: #2c2c2c;
254
- border-radius: 10px;
255
- font-size: 14px;
256
- display: none;
257
  }
258
-
259
- /* --- INDICATEURS ET NOTIFICATIONS --- */
260
- .typing-indicator {
261
- display: none;
262
- align-items: center;
263
- gap: 8px;
264
- color: #999;
265
  font-style: italic;
266
- padding: 0 15px 10px;
267
- }
268
-
269
- .typing-dots span {
270
- width: 8px; height: 8px; border-radius: 50%;
271
- background: #666;
272
- animation: typing 1.4s infinite ease-in-out;
273
- }
274
- .typing-dots span:nth-child(1) { animation-delay: 0s; }
275
- .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
276
- .typing-dots span:nth-child(3) { animation-delay: 0.4s; }
277
-
278
- .error {
279
- background: #4d1c20; color: #ffacb6;
280
- border-left: 3px solid #e53935;
281
- padding: 15px; border-radius: 8px; margin: 10px 0;
282
- }
283
- .success {
284
- background: #1a4331; color: #a6f1c4;
285
- border-left: 3px solid #2e7d32;
286
- padding: 15px; border-radius: 8px; margin: 10px 0;
287
- }
288
-
289
- /* --- STYLE DE LA SCROLLBAR (THÈME SOMBRE) --- */
290
- .messages::-webkit-scrollbar { width: 8px; }
291
- .messages::-webkit-scrollbar-track { background: #1e1e1e; }
292
- .messages::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; }
293
- .messages::-webkit-scrollbar-thumb:hover { background: #777; }
294
-
295
- /* --- ANIMATIONS --- */
296
- @keyframes typing {
297
- 0%, 60%, 100% { transform: scale(0.8); opacity: 0.5; }
298
- 30% { transform: scale(1.2); opacity: 1; }
299
- }
300
-
301
- @keyframes fadeIn {
302
- from { opacity: 0; transform: translateY(10px); }
303
- to { opacity: 1; transform: translateY(0); }
304
- }
305
-
306
- /* --- RESPONSIVE DESIGN (pour les écrans plus larges) --- */
307
- @media (min-width: 768px) {
308
- body {
309
- padding: 20px; /* Ajoute de l'espace autour sur grand écran */
310
- }
311
- .chat-container {
312
- height: 90vh; /* Hauteur limitée sur grand écran */
313
- border-radius: 12px;
314
- border: 1px solid #333;
315
- }
316
  }
317
  </style>
318
  </head>
319
- <body>
320
- <div class="chat-container">
321
- <div class="header">
322
- <h1>Mariam AI Assistant</h1>
323
- <p>❤️</p>
324
- <div class="controls">
325
- <div class="thinking-toggle">
326
- <span>réflexion Mode:</span>
327
- <label class="switch">
328
- <input type="checkbox" id="thinkingToggle" checked>
329
- <span class="slider"></span>
330
- </label>
 
 
331
  </div>
332
- <button class="btn-reset" onclick="resetConversation()">
333
- 🔄 Reset
334
- </button>
335
  </div>
336
  </div>
337
 
338
- <div class="messages" id="messages">
339
- <div class="message assistant">
340
- <div>👋 bonjour, sur quoi allons nous travailler aujourdhui ?</div>
341
-
342
- </div>
343
  </div>
344
 
345
- <div class="typing-indicator" id="typingIndicator">
 
346
  <span>L'assistant réfléchit</span>
347
- <div class="typing-dots">
348
- <span></span>
349
- <span></span>
350
- <span></span>
351
  </div>
352
  </div>
353
 
354
- <div class="input-container">
355
- <div class="file-preview" id="filePreview"></div>
356
- <div class="input-row">
357
- <input type="file" id="fileInput" class="file-input" accept="image/*,video/*,.pdf,.txt,.csv,.json" onchange="handleFileSelect(event)">
358
- <button class="file-button" onclick="document.getElementById('fileInput').click()" title="Joindre un fichier">
 
 
 
 
 
 
 
 
 
 
 
359
  📎
360
  </button>
361
- <div class="input-wrapper">
362
  <textarea
363
  id="messageInput"
364
- class="message-input"
365
  placeholder="Tapez votre message ici..."
366
  rows="1"
367
  onkeydown="handleKeyDown(event)"
368
  oninput="autoResize(this)"
369
  ></textarea>
370
- <button class="send-button" id="sendButton" onclick="sendMessage()" title="Envoyer">
 
 
 
 
 
371
 
372
  </button>
373
  </div>
@@ -375,14 +188,19 @@
375
  </div>
376
  </div>
377
 
378
- <!-- SCRIPT ORIGINAL ET FONCTIONNEL -->
379
  <script>
380
  let currentFile = null;
381
  let conversationId = 'session_' + Date.now();
382
 
 
 
 
 
 
 
383
  function autoResize(textarea) {
384
  textarea.style.height = 'auto';
385
- textarea.style.height = Math.min(textarea.scrollHeight, 150) + 'px';
386
  }
387
 
388
  function handleKeyDown(event) {
@@ -409,12 +227,12 @@
409
  if (data.success) {
410
  currentFile = data;
411
  filePreview.innerHTML = `
412
- <div style="display: flex; justify-content: space-between; align-items: center;">
413
  <span>📎 ${data.filename}</span>
414
- <button onclick="removeFile()" style="background: #555; color: white; border: none; padding: 5px 10px; border-radius: 5px; cursor: pointer;">✕</button>
415
  </div>
416
  `;
417
- filePreview.style.display = 'block';
418
  } else {
419
  showError('Erreur lors du téléchargement: ' + data.error);
420
  }
@@ -426,7 +244,7 @@
426
 
427
  function removeFile() {
428
  currentFile = null;
429
- document.getElementById('filePreview').style.display = 'none';
430
  document.getElementById('fileInput').value = '';
431
  }
432
 
@@ -446,16 +264,15 @@
446
  addMessage('user', message);
447
  }
448
 
449
- typingIndicator.style.display = 'flex';
450
 
451
  messageInput.value = '';
452
  autoResize(messageInput);
453
 
454
- const thinkingEnabled = document.getElementById('thinkingToggle').checked;
455
  const endpoint = currentFile ? '/chat_with_file' : '/chat';
456
  const payload = {
457
  message: message || 'Analyse ce fichier',
458
- thinking_enabled: thinkingEnabled,
459
  conversation_id: conversationId
460
  };
461
 
@@ -477,12 +294,11 @@
477
  const decoder = new TextDecoder();
478
  let buffer = '';
479
  let messageElement = null;
480
- let thoughtsElement = null;
481
 
482
  function processStream() {
483
  return reader.read().then(({ done, value }) => {
484
  if (done) {
485
- // La fin du stream est gérée par le message 'end'
486
  return;
487
  }
488
 
@@ -499,24 +315,21 @@
499
  if (!messageElement) {
500
  messageElement = addMessage('assistant', '');
501
  }
502
- messageElement.innerHTML += formatChunk(data.content);
503
- } else if (data.type === 'thought' && thinkingEnabled) {
504
- if (!thoughtsElement) {
505
- thoughtsElement = addThoughts('');
506
- thoughtsElement.innerHTML = `<div class="thoughts-header">🧠 Réflexion de l'assistant:</div><div class="thought-content"></div>`;
507
- }
508
- thoughtsElement.querySelector('.thought-content').innerHTML += formatChunk(data.content);
509
  } else if (data.type === 'error') {
510
  showError(data.content);
511
  } else if (data.type === 'end') {
512
- typingIndicator.style.display = 'none';
513
  messageInput.disabled = false;
514
  sendButton.disabled = false;
515
  messageInput.focus();
516
  removeFile();
517
- return; // Fin du traitement du stream
518
  }
519
- // Scroll vers le bas à chaque nouveau chunk
520
  const messagesContainer = document.getElementById('messages');
521
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
522
 
@@ -531,7 +344,7 @@
531
  return processStream();
532
  })
533
  .catch(error => {
534
- typingIndicator.style.display = 'none';
535
  messageInput.disabled = false;
536
  sendButton.disabled = false;
537
  showError('Erreur: ' + error.message);
@@ -542,52 +355,37 @@
542
  function addMessage(role, content) {
543
  const messagesContainer = document.getElementById('messages');
544
  const messageDiv = document.createElement('div');
545
- messageDiv.className = `message ${role}`;
546
- messageDiv.innerHTML = formatMessage(content);
 
 
 
 
 
 
 
547
  messagesContainer.appendChild(messageDiv);
548
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
549
  return messageDiv;
550
  }
551
 
552
- function addThoughts(content) {
553
- const messagesContainer = document.getElementById('messages');
554
- const thoughtsDiv = document.createElement('div');
555
- thoughtsDiv.className = 'thoughts';
556
- thoughtsDiv.innerHTML = content;
557
- messagesContainer.appendChild(thoughtsDiv);
558
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
559
- return thoughtsDiv;
560
  }
561
 
562
  function escapeHtml(unsafe) {
563
  return unsafe
564
- .replace(/&/g, "&amp;")
565
- .replace(/</g, "&lt;")
566
- .replace(/>/g, "&gt;")
567
- .replace(/"/g, "&quot;")
568
- .replace(/'/g, "&#039;");
569
- }
570
-
571
- function formatChunk(chunk) {
572
- // Pour le streaming, on échappe le HTML et on remplace juste les sauts de ligne
573
- return escapeHtml(chunk).replace(/\n/g, '<br>');
574
  }
575
 
576
- function formatMessage(content) {
577
- // Cette fonction est utilisée pour le message initial de l'utilisateur
578
- // Elle peut gérer plus de markdown car elle traite le message en une seule fois
579
- return escapeHtml(content)
580
- .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
581
- .replace(/\*(.*?)\*/g, '<em>$1</em>')
582
- .replace(/`(.*?)`/g, '<code style="background: #444; padding: 2px 5px; border-radius: 4px; font-family: monospace;">$1</code>')
583
- .replace(/\n/g, '<br>');
584
- }
585
-
586
-
587
  function showError(message) {
588
  const messagesContainer = document.getElementById('messages');
589
  const errorDiv = document.createElement('div');
590
- errorDiv.className = 'error';
591
  errorDiv.textContent = message;
592
  messagesContainer.appendChild(errorDiv);
593
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
@@ -603,7 +401,7 @@
603
  .then(response => {
604
  if (response.ok) {
605
  document.getElementById('messages').innerHTML = `
606
- <div class="message assistant">
607
  <div>👋 Conversation réinitialisée ! Comment puis-je vous aider ?</div>
608
  </div>
609
  `;
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mariam AI Assistant</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/5.1.1/marked.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/DOMPurify/3.0.3/purify.min.js"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ 'dark': {
16
+ 100: '#f1f1f1',
17
+ 200: '#e0e0e0',
18
+ 300: '#ccc',
19
+ 400: '#999',
20
+ 500: '#777',
21
+ 600: '#555',
22
+ 700: '#444',
23
+ 800: '#333',
24
+ 900: '#2c2c2c',
25
+ 950: '#1e1e1e'
26
+ }
27
+ }
28
+ }
29
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
+ </script>
32
+ <style>
33
+ /* Custom scrollbar styles */
34
+ .custom-scrollbar::-webkit-scrollbar {
35
+ width: 6px;
36
  }
37
+ .custom-scrollbar::-webkit-scrollbar-track {
38
+ background: #1e1e1e;
 
 
 
 
 
39
  }
40
+ .custom-scrollbar::-webkit-scrollbar-thumb {
41
+ background: #555;
42
+ border-radius: 3px;
 
 
 
 
43
  }
44
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
45
+ background: #777;
 
 
 
 
46
  }
47
 
48
+ /* Animation for typing dots */
49
+ @keyframes typing {
50
+ 0%, 60%, 100% {
51
+ transform: scale(0.8);
52
+ opacity: 0.5;
53
+ }
54
+ 30% {
55
+ transform: scale(1.2);
56
+ opacity: 1;
57
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  }
59
+ .typing-dot:nth-child(1) { animation-delay: 0s; }
60
+ .typing-dot:nth-child(2) { animation-delay: 0.2s; }
61
+ .typing-dot:nth-child(3) { animation-delay: 0.4s; }
62
+ .typing-dot {
63
+ animation: typing 1.4s infinite ease-in-out;
 
 
 
 
 
 
 
 
64
  }
65
 
66
+ /* Fade in animation */
67
+ @keyframes fadeIn {
68
+ from { opacity: 0; transform: translateY(10px); }
69
+ to { opacity: 1; transform: translateY(0); }
 
 
 
 
 
 
70
  }
71
+ .fade-in {
 
 
 
 
 
 
72
  animation: fadeIn 0.3s ease;
73
  }
74
 
75
+ /* Markdown styles */
76
+ .markdown-content h1, .markdown-content h2, .markdown-content h3 {
77
+ margin: 1em 0 0.5em 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  font-weight: 600;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
+ .markdown-content h1 { font-size: 1.5em; }
81
+ .markdown-content h2 { font-size: 1.3em; }
82
+ .markdown-content h3 { font-size: 1.1em; }
83
+ .markdown-content p { margin: 0.5em 0; }
84
+ .markdown-content ul, .markdown-content ol {
85
+ margin: 0.5em 0;
86
+ padding-left: 1.5em;
87
+ }
88
+ .markdown-content li { margin: 0.2em 0; }
89
+ .markdown-content code {
90
+ background: #444;
91
+ padding: 2px 6px;
92
+ border-radius: 4px;
93
+ font-family: 'Courier New', monospace;
94
+ font-size: 0.9em;
95
+ }
96
+ .markdown-content pre {
97
  background: #333;
98
+ padding: 1em;
99
+ border-radius: 8px;
100
+ margin: 1em 0;
101
+ overflow-x: auto;
 
 
 
 
 
102
  }
103
+ .markdown-content pre code {
104
+ background: none;
105
+ padding: 0;
 
 
 
 
 
 
 
106
  }
107
+ .markdown-content blockquote {
108
+ border-left: 3px solid #666;
109
+ padding-left: 1em;
110
+ margin: 1em 0;
111
+ color: #ccc;
 
 
112
  font-style: italic;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  }
114
  </style>
115
  </head>
116
+ <body class="bg-gray-900 text-gray-100 min-h-screen flex justify-center items-center p-0 md:p-5">
117
+ <div class="w-full max-w-4xl h-screen md:h-[90vh] bg-dark-950 flex flex-col md:rounded-xl md:border md:border-dark-800 overflow-hidden">
118
+ <!-- Header -->
119
+ <div class="bg-dark-950 px-6 py-5 border-b border-dark-800 flex-shrink-0">
120
+ <div class="text-center">
121
+ <h1 class="text-xl font-semibold mb-1">Mariam AI Assistant</h1>
122
+ <p class="text-sm text-dark-300">❤️</p>
123
+ <div class="flex justify-center items-center mt-4">
124
+ <button
125
+ class="bg-transparent border border-dark-600 text-dark-300 px-4 py-2 rounded-full text-sm hover:bg-dark-800 transition-colors duration-200"
126
+ onclick="resetConversation()"
127
+ >
128
+ 🔄 Reset
129
+ </button>
130
  </div>
 
 
 
131
  </div>
132
  </div>
133
 
134
+ <!-- Messages -->
135
+ <div id="messages" class="flex-1 overflow-y-auto px-4 py-5 space-y-4 custom-scrollbar">
136
+ <div class="max-w-[85%] bg-dark-900 text-dark-200 p-4 rounded-2xl rounded-bl-sm fade-in">
137
+ <div>👋 Bonjour, sur quoi allons-nous travailler aujourd'hui ?</div>
138
+ </div>
139
  </div>
140
 
141
+ <!-- Typing Indicator -->
142
+ <div id="typingIndicator" class="hidden px-4 pb-3 flex items-center gap-3 text-dark-400 italic">
143
  <span>L'assistant réfléchit</span>
144
+ <div class="flex gap-1">
145
+ <span class="w-2 h-2 bg-dark-600 rounded-full typing-dot"></span>
146
+ <span class="w-2 h-2 bg-dark-600 rounded-full typing-dot"></span>
147
+ <span class="w-2 h-2 bg-dark-600 rounded-full typing-dot"></span>
148
  </div>
149
  </div>
150
 
151
+ <!-- Input Container -->
152
+ <div class="bg-dark-950 p-4 border-t border-dark-800 flex-shrink-0">
153
+ <div id="filePreview" class="hidden mb-3 p-3 bg-dark-900 rounded-lg text-sm"></div>
154
+ <div class="flex gap-3 items-end">
155
+ <input
156
+ type="file"
157
+ id="fileInput"
158
+ class="hidden"
159
+ accept="image/*,video/*,.pdf,.txt,.csv,.json"
160
+ onchange="handleFileSelect(event)"
161
+ >
162
+ <button
163
+ class="bg-dark-800 hover:bg-dark-700 text-gray-100 h-12 w-12 rounded-full flex items-center justify-center text-xl transition-colors duration-200 flex-shrink-0"
164
+ onclick="document.getElementById('fileInput').click()"
165
+ title="Joindre un fichier"
166
+ >
167
  📎
168
  </button>
169
+ <div class="flex-1 relative">
170
  <textarea
171
  id="messageInput"
172
+ class="w-full min-h-[48px] max-h-32 px-5 py-3 pr-14 bg-dark-900 text-gray-100 border border-dark-700 rounded-full resize-none outline-none focus:border-dark-500 transition-colors duration-200 placeholder-dark-400"
173
  placeholder="Tapez votre message ici..."
174
  rows="1"
175
  onkeydown="handleKeyDown(event)"
176
  oninput="autoResize(this)"
177
  ></textarea>
178
+ <button
179
+ id="sendButton"
180
+ class="absolute right-1 top-1/2 -translate-y-1/2 bg-dark-800 hover:bg-dark-700 disabled:bg-dark-900 disabled:text-dark-600 text-gray-100 w-10 h-10 rounded-full flex items-center justify-center text-lg transition-colors duration-200"
181
+ onclick="sendMessage()"
182
+ title="Envoyer"
183
+ >
184
 
185
  </button>
186
  </div>
 
188
  </div>
189
  </div>
190
 
 
191
  <script>
192
  let currentFile = null;
193
  let conversationId = 'session_' + Date.now();
194
 
195
+ // Configuration de marked pour le rendu markdown
196
+ marked.setOptions({
197
+ breaks: true,
198
+ gfm: true
199
+ });
200
+
201
  function autoResize(textarea) {
202
  textarea.style.height = 'auto';
203
+ textarea.style.height = Math.min(textarea.scrollHeight, 128) + 'px';
204
  }
205
 
206
  function handleKeyDown(event) {
 
227
  if (data.success) {
228
  currentFile = data;
229
  filePreview.innerHTML = `
230
+ <div class="flex justify-between items-center">
231
  <span>📎 ${data.filename}</span>
232
+ <button onclick="removeFile()" class="bg-dark-600 hover:bg-dark-500 text-white px-3 py-1 rounded text-sm transition-colors duration-200">✕</button>
233
  </div>
234
  `;
235
+ filePreview.classList.remove('hidden');
236
  } else {
237
  showError('Erreur lors du téléchargement: ' + data.error);
238
  }
 
244
 
245
  function removeFile() {
246
  currentFile = null;
247
+ document.getElementById('filePreview').classList.add('hidden');
248
  document.getElementById('fileInput').value = '';
249
  }
250
 
 
264
  addMessage('user', message);
265
  }
266
 
267
+ typingIndicator.classList.remove('hidden');
268
 
269
  messageInput.value = '';
270
  autoResize(messageInput);
271
 
 
272
  const endpoint = currentFile ? '/chat_with_file' : '/chat';
273
  const payload = {
274
  message: message || 'Analyse ce fichier',
275
+ thinking_enabled: false, // Désactivé pour une meilleure UX
276
  conversation_id: conversationId
277
  };
278
 
 
294
  const decoder = new TextDecoder();
295
  let buffer = '';
296
  let messageElement = null;
297
+ let contentBuffer = '';
298
 
299
  function processStream() {
300
  return reader.read().then(({ done, value }) => {
301
  if (done) {
 
302
  return;
303
  }
304
 
 
315
  if (!messageElement) {
316
  messageElement = addMessage('assistant', '');
317
  }
318
+ contentBuffer += data.content;
319
+ // Rendu markdown en temps réel
320
+ const htmlContent = DOMPurify.sanitize(marked.parse(contentBuffer));
321
+ messageElement.innerHTML = `<div class="markdown-content">${htmlContent}</div>`;
 
 
 
322
  } else if (data.type === 'error') {
323
  showError(data.content);
324
  } else if (data.type === 'end') {
325
+ typingIndicator.classList.add('hidden');
326
  messageInput.disabled = false;
327
  sendButton.disabled = false;
328
  messageInput.focus();
329
  removeFile();
330
+ return;
331
  }
332
+ // Scroll vers le bas
333
  const messagesContainer = document.getElementById('messages');
334
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
335
 
 
344
  return processStream();
345
  })
346
  .catch(error => {
347
+ typingIndicator.classList.add('hidden');
348
  messageInput.disabled = false;
349
  sendButton.disabled = false;
350
  showError('Erreur: ' + error.message);
 
355
  function addMessage(role, content) {
356
  const messagesContainer = document.getElementById('messages');
357
  const messageDiv = document.createElement('div');
358
+
359
+ if (role === 'user') {
360
+ messageDiv.className = 'max-w-[85%] bg-dark-700 text-gray-100 p-4 rounded-2xl rounded-br-sm ml-auto fade-in';
361
+ messageDiv.innerHTML = formatUserMessage(content);
362
+ } else {
363
+ messageDiv.className = 'max-w-[85%] bg-dark-900 text-dark-200 p-4 rounded-2xl rounded-bl-sm fade-in';
364
+ messageDiv.innerHTML = content || '<div class="markdown-content"></div>';
365
+ }
366
+
367
  messagesContainer.appendChild(messageDiv);
368
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
369
  return messageDiv;
370
  }
371
 
372
+ function formatUserMessage(content) {
373
+ return escapeHtml(content).replace(/\n/g, '<br>');
 
 
 
 
 
 
374
  }
375
 
376
  function escapeHtml(unsafe) {
377
  return unsafe
378
+ .replace(/&/g, "&amp;")
379
+ .replace(/</g, "&lt;")
380
+ .replace(/>/g, "&gt;")
381
+ .replace(/"/g, "&quot;")
382
+ .replace(/'/g, "&#039;");
 
 
 
 
 
383
  }
384
 
 
 
 
 
 
 
 
 
 
 
 
385
  function showError(message) {
386
  const messagesContainer = document.getElementById('messages');
387
  const errorDiv = document.createElement('div');
388
+ errorDiv.className = 'bg-red-900/50 text-red-200 border-l-4 border-red-500 p-4 rounded-lg fade-in';
389
  errorDiv.textContent = message;
390
  messagesContainer.appendChild(errorDiv);
391
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
 
401
  .then(response => {
402
  if (response.ok) {
403
  document.getElementById('messages').innerHTML = `
404
+ <div class="max-w-[85%] bg-dark-900 text-dark-200 p-4 rounded-2xl rounded-bl-sm fade-in">
405
  <div>👋 Conversation réinitialisée ! Comment puis-je vous aider ?</div>
406
  </div>
407
  `;