Docfile commited on
Commit
b728b86
·
verified ·
1 Parent(s): 10ad7a5

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +62 -589
templates/index.html CHANGED
@@ -3,606 +3,79 @@
3
  <head>
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>
316
- const messageInput = document.getElementById('messageInput');
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>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mariam AI!</title>
 
 
 
 
7
  <style>
8
+ body { font-family: sans-serif; margin: 20px; background-color: #f0f2f6; }
9
+ .chat-container { max-width: 800px; margin: auto; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); display: flex; flex-direction: column; height: 85vh; }
10
+ .chat-header { background-color: #4CAF50; color: white; padding: 15px; text-align: center; border-top-left-radius: 8px; border-top-right-radius: 8px; }
11
+ .chat-messages { flex-grow: 1; overflow-y: auto; padding: 20px; border-bottom: 1px solid #ddd; }
12
+ .message { margin-bottom: 15px; padding: 10px 15px; border-radius: 15px; max-width: 80%; word-wrap: break-word; }
13
+ .message.user { background-color: #DCF8C6; align-self: flex-end; margin-left: auto; border-bottom-right-radius: 0; }
14
+ .message.assistant { background-color: #f1f1f1; align-self: flex-start; border-bottom-left-radius: 0; white-space: pre-wrap; /* Respecte les sauts de ligne et espaces */ }
15
+ .message strong { font-weight: bold; display: block; margin-bottom: 5px; }
16
+ .chat-input { display: flex; padding: 15px; border-top: 1px solid #ddd; background-color: #f8f9fa; }
17
+ .chat-input input[type="text"] { flex-grow: 1; padding: 10px; border: 1px solid #ccc; border-radius: 20px; margin-right: 10px; }
18
+ .chat-input button { padding: 10px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 20px; cursor: pointer; }
19
+ .chat-input button:hover { background-color: #45a049; }
20
+ .settings { padding: 15px; border-top: 1px solid #ddd; background-color: #f8f9fa; font-size: 0.9em; display: flex; justify-content: space-between; align-items: center; }
21
+ .settings label { margin-right: 10px;}
22
+ .error { color: red; text-align: center; padding: 10px; }
23
+ .spinner { text-align: center; padding: 10px; color: #555; font-style: italic;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  </style>
25
  </head>
26
+ <body>
 
27
 
28
+ <div class="chat-container">
29
+ <div class="chat-header">
30
+ <h1>Mariam AI!</h1>
31
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ <div class="chat-messages" id="chat-messages">
34
+ {% for message in chat_history %}
35
+ <div class="message {{ message.role }}">
36
+ <strong>{{ message.role|capitalize }}</strong>
37
+ {{ message.text }}
38
+ </div>
39
+ {% endfor %}
40
+ <!-- Zone pour afficher "Recherche web en cours..." -->
41
+ {% if processing_message and web_search_active %}
42
+ <div class="spinner">Recherche web en cours...</div>
43
+ {% endif %}
44
+ <!-- Zone pour afficher "Génération de la réponse..." -->
45
+ {% if processing_message %}
46
+ <div class="spinner">Génération de la réponse...</div>
47
+ {% endif %}
48
+ </div>
 
 
 
 
 
 
 
 
49
 
50
+ {% if error %}
51
+ <div class="error">{{ error }}</div>
52
+ {% endif %}
 
 
 
 
 
 
 
 
 
 
53
 
54
+ <form method="POST" action="{{ url_for('chat') }}" enctype="multipart/form-data">
55
+ <div class="settings">
56
+ <div>
57
+ <label for="web_search_toggle">
58
+ <input type="checkbox" id="web_search_toggle" name="web_search" value="true" {% if web_search_active %}checked{% endif %}>
59
+ Activer la recherche web
 
 
 
 
 
60
  </label>
61
  </div>
62
+ <div>
63
+ <label for="file_upload">Uploader (jpg, png, pdf, txt):</label>
64
+ <input type="file" id="file_upload" name="file" accept=".jpg,.jpeg,.png,.pdf,.txt"> <!-- Limiter les types ici -->
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  </div>
66
  </div>
67
+ <div class="chat-input">
68
+ <input type="text" name="prompt" placeholder="Posez votre question..." required autofocus>
69
+ <button type="submit">Envoyer</button>
70
+ </div>
71
+ </form>
72
+ </div>
73
+
74
+ <script>
75
+ // Scroll vers le bas automatiquement
76
+ const chatMessages = document.getElementById('chat-messages');
77
+ chatMessages.scrollTop = chatMessages.scrollHeight;
78
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  </body>
81
  </html>