Docfile commited on
Commit
d6ba638
·
verified ·
1 Parent(s): 5ad49c1

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +864 -268
templates/index.html CHANGED
@@ -1,278 +1,874 @@
1
  <!DOCTYPE html>
2
  <html lang="fr">
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
- <!-- Tailwind CSS via CDN -->
8
- <script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
9
- <!-- Font Awesome pour les icônes -->
10
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
- <!-- Google Fonts -->
12
- <link rel="preconnect" href="https://fonts.googleapis.com">
13
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
15
- <!-- Favicon (Emoji amélioré) -->
16
- <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✨</text></svg>">
17
- <script>
18
- tailwind.config = {
19
- darkMode: 'class',
20
- theme: {
21
- extend: {
22
- fontFamily: {
23
- sans: ['Inter', 'system-ui', 'sans-serif'],
24
- mono: ['"JetBrains Mono"', 'monospace']
25
- },
26
- colors: {
27
- primary: { 50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc', 400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1', 800: '#075985', 900: '#0c4a6e' },
28
- secondary: { 50: '#f8fafc', 100: '#f1f5f9', 200: '#e2e8f0', 300: '#cbd5e1', 400: '#94a3b8', 500: '#64748b', 600: '#475569', 700: '#334155', 800: '#1e293b', 900: '#0f172a' },
29
- accent: { 50: '#fdf4ff', 100: '#fae8ff', 200: '#f5d0fe', 300: '#f0abfc', 400: '#e879f9', 500: '#d946ef', 600: '#c026d3', 700: '#a21caf', 800: '#86198f', 900: '#701a75' }
30
- },
31
- animation: { 'bounce-slow': 'bounce 2s infinite', 'pulse-slow': 'pulse 3s infinite', 'typing': 'typing 1.2s steps(3) infinite' },
32
- keyframes: { typing: { '0%': { width: '0.15em' }, '50%': { width: '0.7em' }, '100%': { width: '0.15em' } } }
33
- }
34
- }
35
- }
36
- </script>
37
- <style>
38
- html { scroll-behavior: smooth; overflow-x: hidden; }
39
- body { font-family: 'Inter', sans-serif; transition: background-color 0.3s ease, color 0.3s ease; overflow-x: hidden; }
40
- .chat-layout { min-height: calc(100vh - 64px); display: grid; grid-template-rows: 1fr auto; }
41
- ::-webkit-scrollbar { width: 5px; height: 5px; }
42
- ::-webkit-scrollbar-track { background: transparent; }
43
- ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 5px; }
44
- .dark ::-webkit-scrollbar-thumb { background: #475569; }
45
- .message-bubble { position: relative; max-width: 85%; border-radius: 1rem; padding: 0.875rem 1rem; line-height: 1.5; animation: message-fade-in 0.3s ease-out; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); }
46
- .user-message { border-bottom-right-radius: 0.125rem; align-self: flex-end; background: linear-gradient(to bottom right, #3b82f6, #2563eb); color: white; }
47
- .assistant-message { border-bottom-left-radius: 0.125rem; align-self: flex-start; }
48
- .dark .assistant-message { background-color: #1e293b; color: #e2e8f0; border-color: #334155; }
49
- @keyframes message-fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
50
- @keyframes pulse-fade { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
51
- .typing-indicator { display: inline-flex; align-items: center; margin-left: 0.5rem; }
52
- .typing-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: currentColor; opacity: 0.7; margin: 0 0.1rem; }
53
- .typing-dot:nth-child(1) { animation: pulse-fade 1.2s 0s infinite; }
54
- .typing-dot:nth-child(2) { animation: pulse-fade 1.2s 0.2s infinite; }
55
- .typing-dot:nth-child(3) { animation: pulse-fade 1.2s 0.4s infinite; }
56
- .dark body { background-color: #0f172a; color: #e2e8f0; }
57
- .tooltip .tooltip-text { visibility: hidden; width: max-content; max-width: 200px; background-color: #1e293b; color: #f8fafc; text-align: center; border-radius: 6px; padding: 0.375rem 0.625rem; position: absolute; z-index: 1; bottom: 125%; left: 50%; transform: translateX(-50%); opacity: 0; transition: opacity 0.3s, visibility 0.3s; font-size: 0.75rem; }
58
- .dark .tooltip .tooltip-text { background-color: #475569; }
59
- .tooltip:hover .tooltip-text { visibility: visible; opacity: 1; }
60
- .copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; opacity: 0; transition: opacity 0.2s ease; }
61
- .message-bubble:hover .copy-btn { opacity: 1; }
62
- .chip { display: inline-flex; align-items: center; background: #e0f2fe; border-radius: 9999px; padding: 0.25rem 0.75rem; font-size: 0.75rem; font-weight: 500; color: #0369a1; }
63
- .dark .chip { background: #0c4a6e; color: #7dd3fc; }
64
- .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #cbd5e1; transition: .4s; border-radius: 1.25rem; }
65
- .toggle-slider:before { position: absolute; content: ""; height: 0.875rem; width: 0.875rem; left: 0.25rem; bottom: 0.1875rem; background-color: white; transition: .4s; border-radius: 50%; }
66
- input:checked + .toggle-slider { background-color: #0ea5e9; }
67
- input:checked + .toggle-slider:before { transform: translateX(1.125rem); }
68
- .chat-input { transition: all 0.3s ease; border-color: #e2e8f0; }
69
- .dark .chat-input { background-color: #1e293b; color: #f1f5f9; border-color: #334155; }
70
- pre { position: relative; background-color: #f8fafc; border-radius: 0.5rem; margin: 1rem 0; padding: 1.25rem 1rem; overflow-x: auto; }
71
- .dark pre { background-color: #1e293b; }
72
- code { font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; }
73
- pre:hover .code-copy-btn { opacity: 0.7; }
74
- .table-wrapper { width: 100%; overflow-x: auto; margin: 1rem 0; border: 1px solid #e2e8f0; border-radius: 0.5rem; }
75
- .dark .table-wrapper { border-color: #334155; }
76
- .prose table { width: 100%; border-collapse: collapse; }
77
- .chat-textarea { resize: none; min-height: 44px; max-height: 200px; overflow-y: auto; line-height: 1.5; width: 100%; border-radius: 9999px; padding-top: 0.625rem; padding-bottom: 0.625rem; padding-left: 1rem; padding-right: 3rem; }
78
- .input-wrapper { position: relative; display: flex; align-items: flex-end; }
79
- .send-button-wrapper { position: absolute; right: 0.5rem; bottom: 0.5rem; }
80
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  </head>
82
- <body class="bg-gray-50 text-gray-900 antialiased">
83
- <!-- Header -->
84
- <header class="bg-gradient-to-r from-primary-600 to-primary-800 text-white py-3 px-4 shadow-md sticky top-0 z-10">
85
- <div class="max-w-4xl mx-auto flex justify-between items-center">
86
- <div class="flex items-center space-x-2">
87
- <img src="https://mariam-241.vercel.app/static/image/logoboma.png" alt="Logo Mariam AI" class="h-8 sm:h-10 object-contain">
88
- <h1 class="text-xl font-bold">Mariam AI</h1>
89
- </div>
90
- <div class="flex items-center space-x-2 sm:space-x-4">
91
- <button id="theme-toggle" class="p-2 rounded-full hover:bg-primary-700/50 transition-colors duration-200 tooltip" aria-label="Changer de thème">
92
- <i class="fa-solid fa-moon dark:hidden"></i>
93
- <i class="fa-solid fa-sun hidden dark:inline"></i>
94
- <span class="tooltip-text">Mode clair/sombre</span>
95
- </button>
96
- <form action="/clear" method="POST" id="clear-form">
97
- <button type="submit" class="flex items-center bg-red-500 hover:bg-red-600 text-white text-xs font-semibold py-1.5 px-3 rounded-full transition duration-200 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-opacity-75 tooltip">
98
- <i class="fa-solid fa-trash-can mr-1.5"></i>
99
- <span class="hidden sm:inline">Effacer</span>
100
- <span class="tooltip-text">Effacer la conversation</span>
101
- </button>
102
- </form>
103
- </div>
104
- </div>
105
- </header>
106
- <!-- Main Container -->
107
- <main class="max-w-4xl mx-auto chat-layout">
108
- <!-- Conteneur des messages -->
109
- <section id="chat-messages" class="flex flex-col space-y-6 p-4 overflow-y-auto">
110
- <div id="history-loading" class="text-center py-10">
111
- <div class="inline-flex items-center px-4 py-2 bg-primary-50 text-primary-700 rounded-lg dark:bg-primary-900/30 dark:text-primary-300">
112
- <svg class="animate-spin h-5 w-5 mr-3" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
113
- <span>Chargement de la conversation...</span>
114
- </div>
115
- </div>
116
- <div id="loading-indicator" class="flex items-start space-x-2 hidden">
117
- <div class="w-8 h-8 rounded-full bg-primary-100 flex items-center justify-center flex-shrink-0 dark:bg-primary-900/50"><span class="text-lg">✨</span></div>
118
- <div class="message-bubble assistant-message bg-secondary-50 text-secondary-900 border border-secondary-200 flex items-center">
119
- <span>Mariam réfléchit</span><div class="typing-indicator"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div>
120
- </div>
121
- </div>
122
- </section>
123
- <!-- Conteneur du bas -->
124
- <div class="border-t border-gray-200 dark:border-gray-700">
125
- <div id="error-message" class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 dark:bg-red-900/30 dark:text-red-300 dark:border-red-600 hidden" role="alert">
126
- <div class="flex"><div class="flex-shrink-0"><i class="fa-solid fa-circle-exclamation"></i></div><div class="ml-3"><p class="text-sm font-medium" id="error-text"></p></div><button class="ml-auto" id="dismiss-error"><i class="fa-solid fa-xmark"></i></button></div>
127
- </div>
128
- <div id="preview-area" class="px-4 py-2 bg-gray-50 dark:bg-gray-800/50 hidden"><div id="file-preview" class="hidden"></div></div>
129
- <div class="flex items-center justify-between flex-wrap gap-y-2 px-4 py-2 bg-gray-100 dark:bg-gray-800/80 text-sm text-gray-600 dark:text-gray-300">
130
- <div class="flex items-center space-x-4 flex-wrap gap-y-2">
131
- <label class="flex items-center cursor-pointer tooltip">
132
- <span class="mr-2 text-xs sm:text-sm font-medium"><i class="fa-solid fa-globe mr-1.5"></i><span class="hidden sm:inline">Recherche Web</span></span>
133
- <label class="relative inline-flex items-center cursor-pointer"><input type="checkbox" id="web_search_toggle" name="web_search" value="true" class="sr-only peer"><div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div></label>
134
- <span class="tooltip-text">Activer la recherche web pour cette question</span>
135
- </label>
136
- <label class="flex items-center cursor-pointer tooltip">
137
- <span class="mr-2 text-xs sm:text-sm font-medium text-accent-700 dark:text-accent-300"><i class="fa-solid fa-brain mr-1.5"></i><span class="hidden sm:inline">Avancé</span><span id="advanced-cooldown-timer" class="text-xs ml-1 hidden"></span></span>
138
- <label class="relative inline-flex items-center cursor-pointer"><input type="checkbox" id="advanced_reasoning_toggle" name="advanced_reasoning" value="true" class="sr-only peer"><div class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-purple-300 dark:peer-focus:ring-purple-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-purple-600"></div></label>
139
- <span class="tooltip-text">Utiliser le modèle Pro (plus puissant, 1 fois/min)</span>
140
- </label>
141
- </div>
142
- <div class="flex items-center space-x-2">
143
- <label for="file_upload" class="cursor-pointer flex items-center text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 tooltip">
144
- <i class="fa-solid fa-paperclip"></i>
145
- <span class="ml-1.5 hidden sm:inline">Fichier</span>
146
- <!-- CHANGED: Ajout des types de fichiers audio dans `accept` -->
147
- <input type="file" id="file_upload" name="file" class="hidden" accept=".txt,.pdf,.png,.jpg,.jpeg,.mp3,.wav,.aac,.ogg,.flac">
148
- <!-- CHANGED: Mise à jour du tooltip -->
149
- <span class="tooltip-text">Joindre (texte, pdf, image, audio)</span>
150
- </label>
151
- <div id="file-chip" class="chip hidden">
152
- <i class="fa-solid fa-file chip-icon"></i><span id="file-name" class="truncate max-w-[100px] sm:max-w-[120px]"></span><i id="clear-file" class="fa-solid fa-xmark ml-1 cursor-pointer opacity-70 hover:opacity-100"></i>
153
- </div>
154
- </div>
155
- </div>
156
- <!-- Formulaire de chat -->
157
- <form id="chat-form" class="p-3 sm:p-4 bg-white dark:bg-gray-900">
158
- <div class="input-wrapper">
159
- <textarea id="prompt" name="prompt" class="chat-input chat-textarea w-full border focus:outline-none text-sm sm:text-base"
160
- <!-- CHANGED: Mise à jour du placeholder -->
161
- placeholder="Posez une question, collez une URL, ou joignez un fichier..."
162
- autocomplete="off" rows="1"></textarea>
163
- <div class="send-button-wrapper">
164
- <button type="submit" id="send-button" class="bg-primary-500 hover:bg-primary-600 disabled:bg-primary-300 text-white rounded-full w-9 h-9 transition focus:outline-none focus:ring-2 focus:ring-primary-400 flex items-center justify-center" title="Envoyer le message">
165
- <i class="fa-solid fa-paper-plane text-sm"></i>
166
- </button>
167
- </div>
168
  </div>
169
- <div class="text-xs text-center mt-2 text-gray-400 dark:text-gray-500">
170
- Appuyez sur <kbd class="px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 rounded border border-gray-300 dark:border-gray-700">Entrée</kbd> pour envoyer • <kbd class="px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 rounded border border-gray-300 dark:border-gray-700">Shift+Entrée</kbd> pour une nouvelle ligne
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  </div>
172
- </form>
173
  </div>
174
- </main>
175
- <!-- Scripts -->
176
- <script>
177
- document.addEventListener('DOMContentLoaded', () => {
178
- // ... (le début du script reste identique) ...
179
- const chatForm = document.getElementById('chat-form');
180
- const promptInput = document.getElementById('prompt');
181
- const chatMessages = document.getElementById('chat-messages');
182
- const loadingIndicator = document.getElementById('loading-indicator');
183
- const historyLoadingIndicator = document.getElementById('history-loading');
184
- const errorMessageDiv = document.getElementById('error-message');
185
- const errorTextP = document.getElementById('error-text');
186
- const dismissErrorBtn = document.getElementById('dismiss-error');
187
- const fileUpload = document.getElementById('file_upload');
188
- const fileChip = document.getElementById('file-chip');
189
- const fileNameSpan = document.getElementById('file-name');
190
- const clearFileButton = document.getElementById('clear-file');
191
- const filePreview = document.getElementById('file-preview');
192
- const previewArea = document.getElementById('preview-area');
193
- const sendButton = document.getElementById('send-button');
194
- const clearForm = document.getElementById('clear-form');
195
- const webSearchToggle = document.getElementById('web_search_toggle');
196
- const advancedToggle = document.getElementById('advanced_reasoning_toggle');
197
- const advancedCooldownTimerSpan = document.getElementById('advanced-cooldown-timer');
198
- const themeToggleBtn = document.getElementById('theme-toggle');
199
-
200
- const API_CHAT_ENDPOINT = '/api/chat';
201
- const API_HISTORY_ENDPOINT = '/api/history';
202
- const CLEAR_ENDPOINT = '/clear';
203
- const COOLDOWN_DURATION = 60 * 1000;
204
- let advancedToggleCooldownEndTime = 0;
205
- let isComposing = false;
206
-
207
- // ... (fonctions initializeTheme, toggleTheme, adjustTextareaHeight, scrollToBottom, etc. restent identiques) ...
208
-
209
- // ...
210
-
211
- // NEW: Mise à jour de getFileIcon pour gérer les types audio
212
- function getFileIcon(fileType) {
213
- if (!fileType) return 'fa-file';
214
- if (fileType.includes('pdf')) return 'fa-file-pdf';
215
- if (fileType.includes('text')) return 'fa-file-lines';
216
- if (fileType.startsWith('audio/')) return 'fa-file-audio'; // Ajout pour l'audio
217
- return 'fa-file';
218
- }
219
-
220
- // La logique de prévisualisation des fichiers est mise à jour pour utiliser getFileIcon
221
- fileUpload.addEventListener('change', () => {
222
- if (fileUpload.files.length > 0) {
223
- const file = fileUpload.files[0];
224
- const name = file.name;
225
- fileNameSpan.textContent = name;
226
- fileNameSpan.title = name;
227
- fileChip.classList.remove('hidden');
228
- filePreview.innerHTML = '';
229
- if (file.type.startsWith('image/')) {
230
- const reader = new FileReader();
231
- reader.onload = (e) => {
232
- filePreview.innerHTML = `<div class="file-preview"><img src="${e.target.result}" alt="Prévisualisation: ${escapeHtml(name)}"></div>`;
233
- filePreview.classList.remove('hidden');
234
- previewArea.classList.remove('hidden');
235
- };
236
- reader.readAsDataURL(file);
237
- } else { // Gère texte, pdf, audio, etc.
238
- filePreview.innerHTML = `
239
- <div class="flex items-center justify-center p-3">
240
- <div class="bg-gray-100 dark:bg-gray-800 p-3 rounded-lg text-center">
241
- <i class="fa-solid ${getFileIcon(file.type)} text-3xl text-gray-500 dark:text-gray-400 mb-2"></i>
242
- <p class="text-xs text-gray-500 dark:text-gray-400">${formatFileSize(file.size)}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  </div>
244
- </div>`;
245
- filePreview.classList.remove('hidden');
246
- previewArea.classList.remove('hidden');
247
- }
248
- } else {
249
- clearFileInput();
250
- }
251
- });
252
-
253
- // Le reste du script JavaScript reste majoritairement le même.
254
- // Les fonctions de gestion du formulaire, de l'historique, des erreurs
255
- // n'ont pas besoin de modification car le backend gère la nouvelle complexité.
256
- // La logique de soumission du formulaire est déjà correcte car elle utilise FormData,
257
- // qui envoie les valeurs des cases à cocher et les fichiers sans problème.
258
-
259
- // collez ici l'intégralité de votre script JS existant
260
- // à partir de `function initializeTheme() {` jusqu'à la fin
261
- // pour que l'éditeur puisse remplacer le fichier en entier.
262
- // (J'ai seulement montré les parties modifiées pour la clarté)
263
-
264
- // ... (coller le reste du script JS ici)
265
-
266
- // Exemple de ce qu'il faut coller :
267
- function initializeTheme() {
268
- if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
269
- document.documentElement.classList.add('dark');
270
- } else {
271
- document.documentElement.classList.remove('dark');
272
- }
273
- }
274
- // ... jusqu'à la fin de la balise <script> ...
275
- });
276
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  </body>
278
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="fr">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Chatbot Gemini Avancé</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ height: 100vh;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .container {
22
+ display: flex;
23
+ height: 100vh;
24
+ max-width: 1400px;
25
+ margin: 0 auto;
26
+ background: rgba(255, 255, 255, 0.95);
27
+ box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
28
+ backdrop-filter: blur(8px);
29
+ }
30
+
31
+ .sidebar {
32
+ width: 300px;
33
+ background: rgba(255, 255, 255, 0.1);
34
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
35
+ padding: 20px;
36
+ overflow-y: auto;
37
+ }
38
+
39
+ .sidebar h2 {
40
+ color: #333;
41
+ margin-bottom: 20px;
42
+ font-size: 1.3em;
43
+ text-align: center;
44
+ }
45
+
46
+ .control-group {
47
+ margin-bottom: 20px;
48
+ padding: 15px;
49
+ background: rgba(255, 255, 255, 0.8);
50
+ border-radius: 10px;
51
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
52
+ }
53
+
54
+ .control-group h3 {
55
+ color: #444;
56
+ margin-bottom: 10px;
57
+ font-size: 1em;
58
+ border-bottom: 2px solid #667eea;
59
+ padding-bottom: 5px;
60
+ }
61
+
62
+ .form-group {
63
+ margin-bottom: 15px;
64
+ }
65
+
66
+ label {
67
+ display: block;
68
+ margin-bottom: 5px;
69
+ font-weight: 500;
70
+ color: #555;
71
+ }
72
+
73
+ select, input[type="file"] {
74
+ width: 100%;
75
+ padding: 8px;
76
+ border: 2px solid #ddd;
77
+ border-radius: 5px;
78
+ font-size: 14px;
79
+ transition: border-color 0.3s;
80
+ }
81
+
82
+ select:focus, input[type="file"]:focus {
83
+ border-color: #667eea;
84
+ outline: none;
85
+ }
86
+
87
+ .checkbox-group {
88
+ display: flex;
89
+ align-items: center;
90
+ margin-bottom: 10px;
91
+ }
92
+
93
+ .checkbox-group input[type="checkbox"] {
94
+ margin-right: 8px;
95
+ transform: scale(1.2);
96
+ }
97
+
98
+ .checkbox-group label {
99
+ margin-bottom: 0;
100
+ cursor: pointer;
101
+ }
102
+
103
+ .btn {
104
+ background: linear-gradient(45deg, #667eea, #764ba2);
105
+ color: white;
106
+ border: none;
107
+ padding: 10px 15px;
108
+ border-radius: 5px;
109
+ cursor: pointer;
110
+ font-size: 14px;
111
+ transition: transform 0.2s, box-shadow 0.2s;
112
+ width: 100%;
113
+ }
114
+
115
+ .btn:hover {
116
+ transform: translateY(-2px);
117
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
118
+ }
119
+
120
+ .btn-danger {
121
+ background: linear-gradient(45deg, #ff6b6b, #ee5a52);
122
+ }
123
+
124
+ .btn-danger:hover {
125
+ box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
126
+ }
127
+
128
+ .main-content {
129
+ flex: 1;
130
+ display: flex;
131
+ flex-direction: column;
132
+ height: 100vh;
133
+ }
134
+
135
+ .chat-header {
136
+ background: rgba(255, 255, 255, 0.9);
137
+ padding: 20px;
138
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
139
+ text-align: center;
140
+ }
141
+
142
+ .chat-header h1 {
143
+ color: #333;
144
+ font-size: 1.5em;
145
+ margin-bottom: 5px;
146
+ }
147
+
148
+ .chat-status {
149
+ color: #666;
150
+ font-size: 0.9em;
151
+ }
152
+
153
+ .chat-container {
154
+ flex: 1;
155
+ overflow-y: auto;
156
+ padding: 20px;
157
+ background: rgba(255, 255, 255, 0.05);
158
+ }
159
+
160
+ .message {
161
+ margin-bottom: 20px;
162
+ display: flex;
163
+ animation: slideIn 0.3s ease-out;
164
+ }
165
+
166
+ @keyframes slideIn {
167
+ from {
168
+ opacity: 0;
169
+ transform: translateY(20px);
170
+ }
171
+ to {
172
+ opacity: 1;
173
+ transform: translateY(0);
174
+ }
175
+ }
176
+
177
+ .message.user {
178
+ justify-content: flex-end;
179
+ }
180
+
181
+ .message-content {
182
+ max-width: 70%;
183
+ padding: 15px 20px;
184
+ border-radius: 18px;
185
+ position: relative;
186
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
187
+ }
188
+
189
+ .message.user .message-content {
190
+ background: linear-gradient(135deg, #667eea, #764ba2);
191
+ color: white;
192
+ border-bottom-right-radius: 5px;
193
+ }
194
+
195
+ .message.assistant .message-content {
196
+ background: rgba(255, 255, 255, 0.9);
197
+ color: #333;
198
+ border-bottom-left-radius: 5px;
199
+ border: 1px solid rgba(255, 255, 255, 0.2);
200
+ }
201
+
202
+ .message-time {
203
+ font-size: 0.8em;
204
+ opacity: 0.7;
205
+ margin-top: 5px;
206
+ }
207
+
208
+ .thinking-section {
209
+ background: rgba(255, 235, 59, 0.1);
210
+ border: 1px solid rgba(255, 235, 59, 0.3);
211
+ border-radius: 10px;
212
+ padding: 10px;
213
+ margin-bottom: 10px;
214
+ font-style: italic;
215
+ color: #666;
216
+ }
217
+
218
+ .thinking-section::before {
219
+ content: "🤔 Réflexion du modèle:";
220
+ font-weight: bold;
221
+ color: #f57f17;
222
+ display: block;
223
+ margin-bottom: 5px;
224
+ }
225
+
226
+ .input-container {
227
+ padding: 20px;
228
+ background: rgba(255, 255, 255, 0.9);
229
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
230
+ }
231
+
232
+ .input-row {
233
+ display: flex;
234
+ gap: 10px;
235
+ align-items: flex-end;
236
+ }
237
+
238
+ .message-input {
239
+ flex: 1;
240
+ padding: 12px 15px;
241
+ border: 2px solid #ddd;
242
+ border-radius: 25px;
243
+ font-size: 16px;
244
+ resize: none;
245
+ min-height: 50px;
246
+ max-height: 120px;
247
+ transition: border-color 0.3s;
248
+ }
249
+
250
+ .message-input:focus {
251
+ border-color: #667eea;
252
+ outline: none;
253
+ }
254
+
255
+ .send-btn {
256
+ background: linear-gradient(45deg, #667eea, #764ba2);
257
+ color: white;
258
+ border: none;
259
+ border-radius: 50%;
260
+ width: 50px;
261
+ height: 50px;
262
+ cursor: pointer;
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ transition: transform 0.2s, box-shadow 0.2s;
267
+ }
268
+
269
+ .send-btn:hover:not(:disabled) {
270
+ transform: scale(1.1);
271
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
272
+ }
273
+
274
+ .send-btn:disabled {
275
+ background: #ccc;
276
+ cursor: not-allowed;
277
+ }
278
+
279
+ .file-upload-area {
280
+ margin-bottom: 10px;
281
+ padding: 10px;
282
+ border: 2px dashed #ddd;
283
+ border-radius: 10px;
284
+ text-align: center;
285
+ transition: border-color 0.3s;
286
+ }
287
+
288
+ .file-upload-area.dragover {
289
+ border-color: #667eea;
290
+ background: rgba(102, 126, 234, 0.1);
291
+ }
292
+
293
+ .uploaded-file {
294
+ background: rgba(102, 126, 234, 0.1);
295
+ border: 1px solid #667eea;
296
+ border-radius: 5px;
297
+ padding: 5px 10px;
298
+ margin: 5px 0;
299
+ display: flex;
300
+ justify-content: space-between;
301
+ align-items: center;
302
+ }
303
+
304
+ .remove-file {
305
+ background: #ff6b6b;
306
+ color: white;
307
+ border: none;
308
+ border-radius: 50%;
309
+ width: 20px;
310
+ height: 20px;
311
+ cursor: pointer;
312
+ font-size: 12px;
313
+ }
314
+
315
+ .typing-indicator {
316
+ display: none;
317
+ padding: 10px 20px;
318
+ background: rgba(255, 255, 255, 0.9);
319
+ border-radius: 18px;
320
+ margin-bottom: 20px;
321
+ width: fit-content;
322
+ }
323
+
324
+ .typing-indicator.show {
325
+ display: block;
326
+ animation: slideIn 0.3s ease-out;
327
+ }
328
+
329
+ .typing-dots {
330
+ display: flex;
331
+ gap: 4px;
332
+ }
333
+
334
+ .typing-dots span {
335
+ width: 8px;
336
+ height: 8px;
337
+ background: #667eea;
338
+ border-radius: 50%;
339
+ animation: typing 1.4s infinite;
340
+ }
341
+
342
+ .typing-dots span:nth-child(2) {
343
+ animation-delay: 0.2s;
344
+ }
345
+
346
+ .typing-dots span:nth-child(3) {
347
+ animation-delay: 0.4s;
348
+ }
349
+
350
+ @keyframes typing {
351
+ 0%, 60%, 100% {
352
+ transform: translateY(0);
353
+ opacity: 0.4;
354
+ }
355
+ 30% {
356
+ transform: translateY(-10px);
357
+ opacity: 1;
358
+ }
359
+ }
360
+
361
+ .error-message {
362
+ background: #ffebee;
363
+ color: #c62828;
364
+ border: 1px solid #ffcdd2;
365
+ border-radius: 5px;
366
+ padding: 10px;
367
+ margin: 10px 0;
368
+ }
369
+
370
+ /* Responsive Design */
371
+ @media (max-width: 768px) {
372
+ .container {
373
+ flex-direction: column;
374
+ }
375
+
376
+ .sidebar {
377
+ width: 100%;
378
+ height: auto;
379
+ max-height: 40vh;
380
+ order: 2;
381
+ }
382
+
383
+ .main-content {
384
+ order: 1;
385
+ height: 60vh;
386
+ }
387
+
388
+ .message-content {
389
+ max-width: 85%;
390
+ }
391
+ }
392
+
393
+ /* Code styling */
394
+ pre {
395
+ background: #f5f5f5;
396
+ border-radius: 5px;
397
+ padding: 10px;
398
+ overflow-x: auto;
399
+ margin: 10px 0;
400
+ }
401
+
402
+ code {
403
+ background: #f5f5f5;
404
+ padding: 2px 4px;
405
+ border-radius: 3px;
406
+ font-family: 'Courier New', monospace;
407
+ }
408
+ </style>
409
  </head>
410
+ <body>
411
+ <div class="container">
412
+ <!-- Sidebar -->
413
+ <div class="sidebar">
414
+ <h2>🤖 Configuration</h2>
415
+
416
+ <!-- Modèle -->
417
+ <div class="control-group">
418
+ <h3>Modèle</h3>
419
+ <div class="form-group">
420
+ <label for="model-select">Modèle Gemini:</label>
421
+ <select id="model-select">
422
+ {% for name, value in models.items() %}
423
+ <option value="{{ value }}" {% if name == "Gemini 2.5 Flash" %}selected{% endif %}>{{ name }}</option>
424
+ {% endfor %}
425
+ </select>
426
+ </div>
427
+ </div>
428
+
429
+ <!-- Thinking Options -->
430
+ <div class="control-group">
431
+ <h3>🧠 Options de Réflexion</h3>
432
+ <div class="checkbox-group">
433
+ <input type="checkbox" id="thinking-enabled" checked>
434
+ <label for="thinking-enabled">Activer le raisonnement</label>
435
+ </div>
436
+ <div class="checkbox-group">
437
+ <input type="checkbox" id="include-thoughts">
438
+ <label for="include-thoughts">Afficher les pensées</label>
439
+ </div>
440
+ <p style="font-size: 0.8em; color: #666; margin-top: 5px;">
441
+ 🔧 Tous les outils avancés sont actifs par défaut:<br>
442
+ • Exécution de code<br>
443
+ • Recherche Google<br>
444
+ Contexte URL
445
+ </p>
446
+ </div>
447
+
448
+ <!-- File Upload -->
449
+ <div class="control-group">
450
+ <h3>📁 Upload de fichier</h3>
451
+ <div class="file-upload-area" id="file-upload-area">
452
+ <p>Glissez un fichier ici ou cliquez pour sélectionner</p>
453
+ <input type="file" id="file-input" style="display: none;" accept=".png,.jpg,.jpeg,.pdf,.mp4,.mov,.txt,.csv,.json">
454
+ </div>
455
+ <div id="uploaded-files"></div>
456
+ </div>
457
+
458
+ <!-- Actions -->
459
+ <div class="control-group">
460
+ <h3>Actions</h3>
461
+ <button class="btn btn-danger" onclick="resetChat()">🗑️ Réinitialiser</button>
462
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  </div>
464
+
465
+ <!-- Main Content -->
466
+ <div class="main-content">
467
+ <!-- Header -->
468
+ <div class="chat-header">
469
+ <h1>💬 Chatbot Gemini Avancé</h1>
470
+ <div class="chat-status" id="chat-status">Prêt à discuter</div>
471
+ </div>
472
+
473
+ <!-- Chat Container -->
474
+ <div class="chat-container" id="chat-container">
475
+ <div class="message assistant">
476
+ <div class="message-content">
477
+ <div>👋 Bonjour ! Je suis votre assistant Gemini avec toutes les fonctionnalités avancées activées :</div>
478
+ <div style="margin-top: 10px;">
479
+ ✅ <strong>Exécution de code Python</strong><br>
480
+ ✅ <strong>Recherche Google en temps réel</strong><br>
481
+ ✅ <strong>Analyse d'URLs</strong><br>
482
+ ✅ <strong>Traitement de fichiers (images, PDF, vidéos, textes)</strong><br>
483
+ ✅ <strong>Raisonnement avancé avec Thinking</strong>
484
+ </div>
485
+ <div style="margin-top: 10px; font-style: italic;">
486
+ Comment puis-je vous aider aujourd'hui ?
487
+ </div>
488
+ <div class="message-time">Just now</div>
489
+ </div>
490
+ </div>
491
+ </div>
492
+
493
+ <!-- Typing Indicator -->
494
+ <div class="typing-indicator" id="typing-indicator">
495
+ <div class="typing-dots">
496
+ <span></span>
497
+ <span></span>
498
+ <span></span>
499
+ </div>
500
+ </div>
501
+
502
+ <!-- Input Area -->
503
+ <div class="input-container">
504
+ <div class="input-row">
505
+ <textarea
506
+ id="message-input"
507
+ class="message-input"
508
+ placeholder="Tapez votre message... (Shift+Entrée pour nouvelle ligne)"
509
+ rows="1"></textarea>
510
+ <button id="send-btn" class="send-btn">
511
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
512
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
513
+ </svg>
514
+ </button>
515
+ </div>
516
+ </div>
517
  </div>
 
518
  </div>
519
+
520
+ <script>
521
+ // Variables globales
522
+ let currentFile = null;
523
+ let isWaitingResponse = false;
524
+
525
+ // Éléments DOM
526
+ const chatContainer = document.getElementById('chat-container');
527
+ const messageInput = document.getElementById('message-input');
528
+ const sendBtn = document.getElementById('send-btn');
529
+ const typingIndicator = document.getElementById('typing-indicator');
530
+ const fileInput = document.getElementById('file-input');
531
+ const fileUploadArea = document.getElementById('file-upload-area');
532
+ const uploadedFilesDiv = document.getElementById('uploaded-files');
533
+ const chatStatus = document.getElementById('chat-status');
534
+
535
+ // Configuration du drag & drop
536
+ fileUploadArea.addEventListener('click', () => fileInput.click());
537
+ fileUploadArea.addEventListener('dragover', (e) => {
538
+ e.preventDefault();
539
+ fileUploadArea.classList.add('dragover');
540
+ });
541
+ fileUploadArea.addEventListener('dragleave', () => {
542
+ fileUploadArea.classList.remove('dragover');
543
+ });
544
+ fileUploadArea.addEventListener('drop', (e) => {
545
+ e.preventDefault();
546
+ fileUploadArea.classList.remove('dragover');
547
+ const files = e.dataTransfer.files;
548
+ if (files.length > 0) {
549
+ handleFileUpload(files[0]);
550
+ }
551
+ });
552
+
553
+ // Configuration de l'input file
554
+ fileInput.addEventListener('change', (e) => {
555
+ if (e.target.files.length > 0) {
556
+ handleFileUpload(e.target.files[0]);
557
+ }
558
+ });
559
+
560
+ // Auto-resize textarea
561
+ messageInput.addEventListener('input', function() {
562
+ this.style.height = 'auto';
563
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
564
+ });
565
+
566
+ // Gestion des touches
567
+ messageInput.addEventListener('keydown', (e) => {
568
+ if (e.key === 'Enter' && !e.shiftKey) {
569
+ e.preventDefault();
570
+ sendMessage();
571
+ }
572
+ });
573
+
574
+ // Bouton d'envoi
575
+ sendBtn.addEventListener('click', sendMessage);
576
+
577
+ // Fonctions utilitaires
578
+ function formatTime() {
579
+ return new Date().toLocaleTimeString('fr-FR', {
580
+ hour: '2-digit',
581
+ minute: '2-digit'
582
+ });
583
+ }
584
+
585
+ function scrollToBottom() {
586
+ chatContainer.scrollTop = chatContainer.scrollHeight;
587
+ }
588
+
589
+ function showTyping() {
590
+ typingIndicator.classList.add('show');
591
+ scrollToBottom();
592
+ }
593
+
594
+ function hideTyping() {
595
+ typingIndicator.classList.remove('show');
596
+ }
597
+
598
+ function updateChatStatus(status) {
599
+ chatStatus.textContent = status;
600
+ }
601
+
602
+ // Gestion de l'upload de fichiers
603
+ async function handleFileUpload(file) {
604
+ const formData = new FormData();
605
+ formData.append('file', file);
606
+
607
+ try {
608
+ updateChatStatus('Upload du fichier...');
609
+ const response = await fetch('/upload_file', {
610
+ method: 'POST',
611
+ body: formData
612
+ });
613
+
614
+ const result = await response.json();
615
+
616
+ if (result.success) {
617
+ currentFile = {
618
+ id: result.file_id,
619
+ name: result.filename,
620
+ type: result.mime_type
621
+ };
622
+
623
+ displayUploadedFile(result.filename, result.mime_type);
624
+ updateChatStatus('Fichier uploadé avec succès');
625
+ } else {
626
+ showError('Erreur lors de l\'upload: ' + result.error);
627
+ updateChatStatus('Erreur d\'upload');
628
+ }
629
+ } catch (error) {
630
+ console.error('Erreur upload:', error);
631
+ showError('Erreur lors de l\'upload du fichier');
632
+ updateChatStatus('Erreur d\'upload');
633
+ }
634
+ }
635
+
636
+ function displayUploadedFile(filename, mimeType) {
637
+ const fileIcon = getFileIcon(mimeType);
638
+ uploadedFilesDiv.innerHTML = `
639
+ <div class="uploaded-file">
640
+ <span>${fileIcon} ${filename}</span>
641
+ <button class="remove-file" onclick="removeFile()">×</button>
642
  </div>
643
+ `;
644
+ }
645
+
646
+ function getFileIcon(mimeType) {
647
+ if (mimeType.startsWith('image/')) return '🖼️';
648
+ if (mimeType === 'application/pdf') return '📄';
649
+ if (mimeType.startsWith('video/')) return '🎥';
650
+ if (mimeType.includes('text')) return '📝';
651
+ return '📎';
652
+ }
653
+
654
+ function removeFile() {
655
+ currentFile = null;
656
+ uploadedFilesDiv.innerHTML = '';
657
+ fileInput.value = '';
658
+ updateChatStatus('Fichier retiré');
659
+ }
660
+
661
+ // Gestion des messages
662
+ function addMessage(content, isUser, hasThoughts = false, thoughtsContent = '') {
663
+ const messageDiv = document.createElement('div');
664
+ messageDiv.className = `message ${isUser ? 'user' : 'assistant'}`;
665
+
666
+ let messageHTML = `
667
+ <div class="message-content">
668
+ ${hasThoughts ? `<div class="thinking-section">${thoughtsContent}</div>` : ''}
669
+ <div>${formatMessage(content)}</div>
670
+ <div class="message-time">${formatTime()}</div>
671
+ </div>
672
+ `;
673
+
674
+ messageDiv.innerHTML = messageHTML;
675
+ chatContainer.appendChild(messageDiv);
676
+ scrollToBottom();
677
+ return messageDiv;
678
+ }
679
+
680
+ function formatMessage(content) {
681
+ // Conversion basique markdown vers HTML
682
+ return content
683
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
684
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
685
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
686
+ .replace(/```([^```]+)```/g, '<pre><code>$1</code></pre>')
687
+ .replace(/\n/g, '<br>');
688
+ }
689
+
690
+ function showError(message) {
691
+ const errorDiv = document.createElement('div');
692
+ errorDiv.className = 'error-message';
693
+ errorDiv.textContent = message;
694
+ chatContainer.appendChild(errorDiv);
695
+ scrollToBottom();
696
+
697
+ setTimeout(() => {
698
+ errorDiv.remove();
699
+ }, 5000);
700
+ }
701
+
702
+ // Envoi de messages
703
+ async function sendMessage() {
704
+ if (isWaitingResponse) return;
705
+
706
+ const message = messageInput.value.trim();
707
+ if (!message) return;
708
+
709
+ const model = document.getElementById('model-select').value;
710
+ const thinkingEnabled = document.getElementById('thinking-enabled').checked;
711
+ const includeThoughts = document.getElementById('include-thoughts').checked;
712
+
713
+ // Afficher le message de l'utilisateur
714
+ addMessage(message, true);
715
+
716
+ // Nettoyer l'input
717
+ messageInput.value = '';
718
+ messageInput.style.height = 'auto';
719
+
720
+ // Désactiver l'envoi
721
+ isWaitingResponse = true;
722
+ sendBtn.disabled = true;
723
+ showTyping();
724
+ updateChatStatus('Génération de la réponse...');
725
+
726
+ try {
727
+ const endpoint = currentFile ? '/send_message_with_file' : '/send_message';
728
+ const requestBody = {
729
+ message: message,
730
+ model: model,
731
+ thinking_enabled: thinkingEnabled,
732
+ include_thoughts: includeThoughts
733
+ };
734
+
735
+ if (currentFile) {
736
+ requestBody.file_id = currentFile.id;
737
+ }
738
+
739
+ const response = await fetch(endpoint, {
740
+ method: 'POST',
741
+ headers: {
742
+ 'Content-Type': 'application/json',
743
+ },
744
+ body: JSON.stringify(requestBody)
745
+ });
746
+
747
+ if (!response.ok) {
748
+ throw new Error('Erreur de réseau');
749
+ }
750
+
751
+ const reader = response.body.getReader();
752
+ const decoder = new TextDecoder();
753
+
754
+ let assistantMessage = null;
755
+ let fullResponse = '';
756
+ let thoughtsContent = '';
757
+
758
+ hideTyping();
759
+
760
+ while (true) {
761
+ const { done, value } = await reader.read();
762
+ if (done) break;
763
+
764
+ const chunk = decoder.decode(value);
765
+ const lines = chunk.split('\n');
766
+
767
+ for (const line of lines) {
768
+ if (line.startsWith('data: ')) {
769
+ try {
770
+ const data = JSON.parse(line.slice(6));
771
+
772
+ if (data.error) {
773
+ showError(data.error);
774
+ break;
775
+ }
776
+
777
+ if (data.type === 'thought') {
778
+ thoughtsContent += data.content;
779
+ } else if (data.type === 'text') {
780
+ if (!assistantMessage) {
781
+ assistantMessage = addMessage('', false, includeThoughts, thoughtsContent);
782
+ }
783
+
784
+ fullResponse += data.content;
785
+
786
+ // Mettre à jour le contenu du message
787
+ const messageContent = assistantMessage.querySelector('.message-content > div:last-child');
788
+ if (messageContent) {
789
+ messageContent.innerHTML = formatMessage(fullResponse) + '<div class="message-time">' + formatTime() + '</div>';
790
+ }
791
+
792
+ scrollToBottom();
793
+ } else if (data.type === 'end') {
794
+ // Message terminé
795
+ updateChatStatus('Prêt à discuter');
796
+ }
797
+ } catch (e) {
798
+ console.error('Erreur parsing JSON:', e);
799
+ }
800
+ }
801
+ }
802
+ }
803
+
804
+ // Nettoyer le fichier après envoi
805
+ if (currentFile) {
806
+ removeFile();
807
+ }
808
+
809
+ } catch (error) {
810
+ console.error('Erreur:', error);
811
+ hideTyping();
812
+ showError('Erreur lors de l\'envoi du message: ' + error.message);
813
+ updateChatStatus('Erreur de communication');
814
+ } finally {
815
+ isWaitingResponse = false;
816
+ sendBtn.disabled = false;
817
+ }
818
+ }
819
+
820
+ // Reset du chat
821
+ async function resetChat() {
822
+ if (!confirm('Êtes-vous sûr de vouloir réinitialiser la conversation ?')) {
823
+ return;
824
+ }
825
+
826
+ try {
827
+ const response = await fetch('/reset_chat', {
828
+ method: 'POST',
829
+ headers: {
830
+ 'Content-Type': 'application/json',
831
+ }
832
+ });
833
+
834
+ const result = await response.json();
835
+
836
+ if (result.success) {
837
+ // Nettoyer l'interface
838
+ chatContainer.innerHTML = `
839
+ <div class="message assistant">
840
+ <div class="message-content">
841
+ <div>👋 Nouvelle conversation démarrée ! Toutes les fonctionnalités avancées sont toujours actives.</div>
842
+ <div class="message-time">${formatTime()}</div>
843
+ </div>
844
+ </div>
845
+ `;
846
+
847
+ removeFile();
848
+ updateChatStatus('Conversation réinitialisée');
849
+ } else {
850
+ showError('Erreur lors de la réinitialisation');
851
+ }
852
+ } catch (error) {
853
+ console.error('Erreur reset:', error);
854
+ showError('Erreur lors de la réinitialisation');
855
+ }
856
+ }
857
+
858
+ // Initialisation
859
+ document.addEventListener('DOMContentLoaded', function() {
860
+ updateChatStatus('Prêt à discuter');
861
+ messageInput.focus();
862
+ });
863
+
864
+ // Gestion de la reconnexion
865
+ window.addEventListener('online', () => {
866
+ updateChatStatus('Reconnecté');
867
+ });
868
+
869
+ window.addEventListener('offline', () => {
870
+ updateChatStatus('Hors ligne');
871
+ });
872
+ </script>
873
  </body>
874
  </html>