kuro223 commited on
Commit
89db25a
·
1 Parent(s): 0a19f08
Files changed (2) hide show
  1. app.py +20 -16
  2. templates/index.html +71 -55
app.py CHANGED
@@ -192,25 +192,24 @@ def upload_file():
192
  try:
193
  if 'file' not in request.files:
194
  return jsonify({'error': 'No file uploaded'}), 400
195
-
196
  file = request.files['file']
197
  if file.filename == '':
198
  return jsonify({'error': 'No file selected'}), 400
199
-
200
  # Lire le fichier
201
  file_bytes = file.read()
202
  mime_type = file.content_type or mimetypes.guess_type(file.filename)[0]
203
-
204
  # Encoder en base64 pour le stockage temporaire
205
  file_b64 = base64.b64encode(file_bytes).decode()
206
-
207
  return jsonify({
208
  'success': True,
209
- 'filename': file.filename,
210
  'mime_type': mime_type,
211
  'data': file_b64
212
  })
213
-
214
  except Exception as e:
215
  return jsonify({'error': str(e)}), 500
216
 
@@ -219,15 +218,20 @@ def chat_with_file():
219
  try:
220
  data = request.get_json()
221
  message = data.get('message', '')
222
- file_data = data.get('file_data')
223
  thinking_enabled = data.get('thinking_enabled', True)
224
  conversation_id = data.get('conversation_id', 'default')
225
-
226
- # Ajouter le message de l'utilisateur à l'historique (avec indication de fichier)
227
- display_message = message if message else 'Analyse ce fichier'
228
- if file_data:
229
- display_message += f" [Fichier: {file_data.get('filename', 'inconnu')}]"
230
- add_message_to_history(conversation_id, 'user', display_message, has_file=True, file_data=file_data)
 
 
 
 
 
231
 
232
  # Configuration du thinking
233
  config_dict = DEFAULT_CONFIG.copy()
@@ -251,10 +255,10 @@ def chat_with_file():
251
 
252
  chat = conversations[conversation_id]
253
 
254
- # Préparation du contenu avec fichier
255
  contents = [message]
256
-
257
- if file_data:
258
  file_bytes = base64.b64decode(file_data['data'])
259
  file_part = types.Part.from_bytes(
260
  data=file_bytes,
 
192
  try:
193
  if 'file' not in request.files:
194
  return jsonify({'error': 'No file uploaded'}), 400
195
+
196
  file = request.files['file']
197
  if file.filename == '':
198
  return jsonify({'error': 'No file selected'}), 400
199
+
200
  # Lire le fichier
201
  file_bytes = file.read()
202
  mime_type = file.content_type or mimetypes.guess_type(file.filename)[0]
203
+
204
  # Encoder en base64 pour le stockage temporaire
205
  file_b64 = base64.b64encode(file_bytes).decode()
206
+
207
  return jsonify({
208
  'success': True,
 
209
  'mime_type': mime_type,
210
  'data': file_b64
211
  })
212
+
213
  except Exception as e:
214
  return jsonify({'error': str(e)}), 500
215
 
 
218
  try:
219
  data = request.get_json()
220
  message = data.get('message', '')
221
+ file_data_list = data.get('file_data', [])
222
  thinking_enabled = data.get('thinking_enabled', True)
223
  conversation_id = data.get('conversation_id', 'default')
224
+
225
+ # Ensure file_data_list is a list
226
+ if not isinstance(file_data_list, list):
227
+ file_data_list = [file_data_list]
228
+
229
+ # Ajouter le message de l'utilisateur à l'historique (avec indication de fichiers)
230
+ display_message = message if message else 'Analyse ces fichiers'
231
+ if file_data_list:
232
+ file_count = len(file_data_list)
233
+ display_message += f" [{file_count} fichier{'s' if file_count > 1 else ''}]"
234
+ add_message_to_history(conversation_id, 'user', display_message, has_file=len(file_data_list) > 0, file_data=file_data_list)
235
 
236
  # Configuration du thinking
237
  config_dict = DEFAULT_CONFIG.copy()
 
255
 
256
  chat = conversations[conversation_id]
257
 
258
+ # Préparation du contenu avec fichiers
259
  contents = [message]
260
+
261
+ for file_data in file_data_list:
262
  file_bytes = base64.b64decode(file_data['data'])
263
  file_part = types.Part.from_bytes(
264
  data=file_bytes,
templates/index.html CHANGED
@@ -130,7 +130,7 @@
130
  <div class="p-4 md:p-6 bg-dark-card border-t border-dark-border flex-shrink-0">
131
  <div class="mb-3 p-3 bg-dark-elevated rounded-lg text-sm hidden" id="filePreview"></div>
132
  <div class="flex gap-3 items-end">
133
- <input type="file" id="fileInput" class="hidden" accept="image/*,video/*,.pdf,.txt,.csv,.json" onchange="handleFileSelect(event)">
134
  <button class="bg-gray-700 text-white h-12 w-12 rounded-full hover:bg-gray-600 transition-colors flex-shrink-0 flex items-center justify-center text-xl" onclick="document.getElementById('fileInput').click()" title="Joindre un fichier">
135
  📎
136
  </button>
@@ -152,7 +152,7 @@
152
  </div>
153
 
154
  <script>
155
- let currentFile = null;
156
  let conversationId = 'session_' + Date.now();
157
 
158
  // Configuration de marked pour le markdown
@@ -174,61 +174,72 @@
174
  }
175
 
176
  function handleFileSelect(event) {
177
- const file = event.target.files[0];
178
- if (!file) return;
179
 
180
  const filePreview = document.getElementById('filePreview');
181
- const formData = new FormData();
182
- formData.append('file', file);
183
-
184
- // Show immediate preview for images
185
- if (file.type.startsWith('image/')) {
186
- const reader = new FileReader();
187
- reader.onload = function(e) {
188
- filePreview.innerHTML = `
189
- <div class="flex items-center gap-3">
190
- <img src="${e.target.result}" alt="${file.name}" class="w-16 h-16 object-cover rounded-lg border border-gray-600">
191
- <div class="flex-1">
192
- <div class="text-gray-300 text-sm font-medium">${file.name}</div>
 
 
193
  <div class="text-gray-500 text-xs">${(file.size / 1024).toFixed(1)} KB</div>
194
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  <button onclick="removeFile()" class="bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded transition-colors">✕</button>
196
  </div>
197
  `;
198
  filePreview.classList.remove('hidden');
199
- };
200
- reader.readAsDataURL(file);
201
- } else {
202
- filePreview.innerHTML = `
203
- <div class="flex justify-between items-center">
204
- <span class="text-gray-300">📎 ${file.name}</span>
205
- <button onclick="removeFile()" class="bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded transition-colors">✕</button>
206
- </div>
207
- `;
208
- filePreview.classList.remove('hidden');
209
- }
210
-
211
- fetch('/upload', {
212
- method: 'POST',
213
- body: formData
214
- })
215
- .then(response => response.json())
216
- .then(data => {
217
- if (data.success) {
218
- currentFile = data;
219
- } else {
220
- showError('Erreur lors du téléchargement: ' + data.error);
221
- filePreview.classList.add('hidden');
222
  }
223
- })
224
- .catch(error => {
225
- showError('Erreur lors du téléchargement: ' + error.message);
226
- filePreview.classList.add('hidden');
227
- });
228
  }
229
 
230
  function removeFile() {
231
- currentFile = null;
232
  document.getElementById('filePreview').classList.add('hidden');
233
  document.getElementById('fileInput').value = '';
234
  }
@@ -237,7 +248,7 @@
237
  const messageInput = document.getElementById('messageInput');
238
  const message = messageInput.value.trim();
239
 
240
- if (!message && !currentFile) return;
241
 
242
  const sendButton = document.getElementById('sendButton');
243
  const typingIndicator = document.getElementById('typingIndicator');
@@ -245,8 +256,8 @@
245
  messageInput.disabled = true;
246
  sendButton.disabled = true;
247
 
248
- if (message || currentFile) {
249
- addMessage('user', message, currentFile);
250
  }
251
 
252
  typingIndicator.classList.remove('hidden');
@@ -256,15 +267,15 @@
256
  autoResize(messageInput);
257
 
258
  const thinkingEnabled = document.getElementById('thinkingToggle').checked;
259
- const endpoint = currentFile ? '/chat_with_file' : '/chat';
260
  const payload = {
261
- message: message || 'Analyse ce fichier',
262
  thinking_enabled: thinkingEnabled,
263
  conversation_id: conversationId
264
  };
265
 
266
- if (currentFile) {
267
- payload.file_data = currentFile;
268
  }
269
 
270
  fetch(endpoint, {
@@ -358,9 +369,14 @@
358
 
359
  let messageContent = '';
360
 
361
- if (fileData && fileData.mime_type && fileData.mime_type.startsWith('image/')) {
362
- const imageUrl = `data:${fileData.mime_type};base64,${fileData.data}`;
363
- messageContent += `<img src="${imageUrl}" alt="${fileData.filename}" class="max-w-full h-auto rounded-lg mb-2 border border-gray-600">`;
 
 
 
 
 
364
  }
365
 
366
  if (content) {
 
130
  <div class="p-4 md:p-6 bg-dark-card border-t border-dark-border flex-shrink-0">
131
  <div class="mb-3 p-3 bg-dark-elevated rounded-lg text-sm hidden" id="filePreview"></div>
132
  <div class="flex gap-3 items-end">
133
+ <input type="file" id="fileInput" class="hidden" accept="image/*,video/*,.pdf,.txt,.csv,.json" multiple onchange="handleFileSelect(event)">
134
  <button class="bg-gray-700 text-white h-12 w-12 rounded-full hover:bg-gray-600 transition-colors flex-shrink-0 flex items-center justify-center text-xl" onclick="document.getElementById('fileInput').click()" title="Joindre un fichier">
135
  📎
136
  </button>
 
152
  </div>
153
 
154
  <script>
155
+ let currentFiles = [];
156
  let conversationId = 'session_' + Date.now();
157
 
158
  // Configuration de marked pour le markdown
 
174
  }
175
 
176
  function handleFileSelect(event) {
177
+ const files = Array.from(event.target.files);
178
+ if (files.length === 0) return;
179
 
180
  const filePreview = document.getElementById('filePreview');
181
+ currentFiles = [];
182
+ let previews = [];
183
+
184
+ files.forEach((file, index) => {
185
+ const formData = new FormData();
186
+ formData.append('file', file);
187
+
188
+ // Show immediate preview for images
189
+ if (file.type.startsWith('image/')) {
190
+ const reader = new FileReader();
191
+ reader.onload = function(e) {
192
+ previews[index] = `
193
+ <div class="flex items-center gap-3">
194
+ <img src="${e.target.result}" class="w-16 h-16 object-cover rounded-lg border border-gray-600">
195
  <div class="text-gray-500 text-xs">${(file.size / 1024).toFixed(1)} KB</div>
196
  </div>
197
+ `;
198
+ updatePreview();
199
+ };
200
+ reader.readAsDataURL(file);
201
+ } else {
202
+ previews[index] = `
203
+ <div class="flex items-center gap-3">
204
+ <span class="text-gray-300">📎</span>
205
+ <div class="text-gray-500 text-xs">${(file.size / 1024).toFixed(1)} KB</div>
206
+ </div>
207
+ `;
208
+ updatePreview();
209
+ }
210
+
211
+ fetch('/upload', {
212
+ method: 'POST',
213
+ body: formData
214
+ })
215
+ .then(response => response.json())
216
+ .then(data => {
217
+ if (data.success) {
218
+ currentFiles.push(data);
219
+ } else {
220
+ showError('Erreur lors du téléchargement: ' + data.error);
221
+ }
222
+ })
223
+ .catch(error => {
224
+ showError('Erreur lors du téléchargement: ' + error.message);
225
+ });
226
+ });
227
+
228
+ function updatePreview() {
229
+ if (previews.filter(p => p).length === files.length) {
230
+ filePreview.innerHTML = `
231
+ <div class="flex flex-wrap gap-3 items-center">
232
+ ${previews.join('')}
233
  <button onclick="removeFile()" class="bg-gray-600 hover:bg-gray-500 text-white px-3 py-1 rounded transition-colors">✕</button>
234
  </div>
235
  `;
236
  filePreview.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
238
+ }
 
 
 
 
239
  }
240
 
241
  function removeFile() {
242
+ currentFiles = [];
243
  document.getElementById('filePreview').classList.add('hidden');
244
  document.getElementById('fileInput').value = '';
245
  }
 
248
  const messageInput = document.getElementById('messageInput');
249
  const message = messageInput.value.trim();
250
 
251
+ if (!message && currentFiles.length === 0) return;
252
 
253
  const sendButton = document.getElementById('sendButton');
254
  const typingIndicator = document.getElementById('typingIndicator');
 
256
  messageInput.disabled = true;
257
  sendButton.disabled = true;
258
 
259
+ if (message || currentFiles.length > 0) {
260
+ addMessage('user', message, currentFiles);
261
  }
262
 
263
  typingIndicator.classList.remove('hidden');
 
267
  autoResize(messageInput);
268
 
269
  const thinkingEnabled = document.getElementById('thinkingToggle').checked;
270
+ const endpoint = currentFiles.length > 0 ? '/chat_with_file' : '/chat';
271
  const payload = {
272
+ message: message || 'Analyse ces fichiers',
273
  thinking_enabled: thinkingEnabled,
274
  conversation_id: conversationId
275
  };
276
 
277
+ if (currentFiles.length > 0) {
278
+ payload.file_data = currentFiles;
279
  }
280
 
281
  fetch(endpoint, {
 
369
 
370
  let messageContent = '';
371
 
372
+ if (fileData) {
373
+ const files = Array.isArray(fileData) ? fileData : [fileData];
374
+ files.forEach(file => {
375
+ if (file.mime_type && file.mime_type.startsWith('image/')) {
376
+ const imageUrl = `data:${file.mime_type};base64,${file.data}`;
377
+ messageContent += `<img src="${imageUrl}" class="max-w-full h-auto rounded-lg mb-2 border border-gray-600">`;
378
+ }
379
+ });
380
  }
381
 
382
  if (content) {