Docfile commited on
Commit
295c8a0
·
verified ·
1 Parent(s): 0a6012f

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +105 -73
templates/index.html CHANGED
@@ -131,7 +131,7 @@
131
  <div class="lg:col-span-2">
132
  <div class="bg-white/80 backdrop-blur-sm border border-slate-200 rounded-2xl shadow-lg p-6 sticky top-8 fade-in-up" style="animation-delay: 200ms;">
133
  <div class="flex justify-between items-center mb-4">
134
- <h2 class="text-xl font-bold text-gray-800">Historique des Tâches</h2>
135
  <button id="clearHistoryBtn" class="text-sm font-medium text-red-600 hover:text-red-800 transition-colors disabled:text-red-300 disabled:cursor-not-allowed">
136
  Tout effacer
137
  </button>
@@ -139,7 +139,7 @@
139
  <div id="historyList" class="space-y-3 max-h-[600px] overflow-y-auto pr-2">
140
  <div id="historyEmptyState" class="text-center py-16">
141
  <svg class="w-16 h-16 mx-auto text-slate-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0A2.25 2.25 0 013.75 7.5h16.5a2.25 2.25 0 012.25 2.25m-18.75 0h18.75c.621 0 1.125.504 1.125 1.125v.158c0 .53-.223.998-.586 1.328l-5.454 4.849a3.75 3.75 0 01-5.304 0L4.336 12.36a1.125 1.125 0 01-.586-1.328v-.158c0-.621.504-1.125 1.125-1.125z" /></svg>
142
- <p class="mt-4 text-slate-500 font-medium">Vos tâches apparaîtront ici.</p>
143
  </div>
144
  </div>
145
  </div>
@@ -172,17 +172,43 @@
172
  const historyList = document.getElementById('historyList');
173
  const historyEmptyState = document.getElementById('historyEmptyState');
174
  const clearHistoryBtn = document.getElementById('clearHistoryBtn');
175
-
176
  let currentTaskId = null;
177
  const taskPollers = {};
178
-
179
- // --- UI Helper Functions ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  function showLoading(isLoading) {
181
  submitBtn.disabled = isLoading;
182
  btnText.textContent = isLoading ? 'Lancement...' : 'Lancer la synthèse';
183
  btnSpinner.classList.toggle('hidden', !isLoading);
184
  }
185
-
186
  function displayTaskResult(task) {
187
  if (task.status === 'completed' && task.summary) {
188
  summaryContent.innerHTML = marked.parse(task.summary);
@@ -192,7 +218,9 @@
192
  errorCard.classList.add('hidden');
193
  resultCard.classList.remove('hidden');
194
  resultCard.classList.add('fade-in-up');
195
- window.scrollTo({ top: resultCard.offsetTop - 80, behavior: 'smooth' });
 
 
196
  } else if (task.status === 'failed' && task.error) {
197
  displayError(task.error);
198
  resultCard.classList.add('hidden');
@@ -201,13 +229,13 @@
201
  errorCard.classList.add('hidden');
202
  }
203
  }
204
-
205
  function displayError(message) {
206
  errorMessage.textContent = message;
207
  errorCard.classList.remove('hidden');
208
  errorCard.classList.add('fade-in-up');
209
  }
210
-
211
  function getFileIcon(filename, sizeClass = 'w-8 h-8') {
212
  if (filename.toLowerCase().includes('youtube')) {
213
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="${sizeClass} text-red-600"><path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path></svg>`;
@@ -222,14 +250,12 @@
222
  }
223
  return iconSvg;
224
  }
225
-
226
  function resetFileSelection() {
227
  fileInput.value = '';
228
  fileInfo.classList.add('hidden');
229
  fileInfo.classList.remove('flex');
230
  dropzone.classList.remove('hidden');
231
  }
232
-
233
  function handleFileSelect(file) {
234
  if (!file) return;
235
  youtubeUrlInput.value = '';
@@ -240,9 +266,9 @@
240
  fileInfo.classList.remove('hidden');
241
  fileInfo.classList.add('flex');
242
  }
243
-
244
  // --- Task & History Management ---
245
-
246
  function getStatusBadge(status) {
247
  switch (status) {
248
  case 'completed': return `<span class="status-badge bg-green-100 text-green-800">Terminé</span>`;
@@ -278,31 +304,27 @@
278
  </div>`;
279
  }
280
 
281
- async function loadTasks() {
282
- try {
283
- const response = await fetch('/tasks');
284
- const tasks = await response.json();
285
-
286
- historyEmptyState.classList.toggle('hidden', tasks.length > 0);
287
- clearHistoryBtn.disabled = tasks.length === 0;
288
-
289
- historyList.innerHTML = '';
290
- for(const task of tasks) {
291
- historyList.insertAdjacentHTML('beforeend', createHistoryItemHTML(task));
292
- if (task.status === 'pending' || task.status === 'processing') {
293
- pollTaskStatus(task.task_id);
294
- }
295
  }
296
- } catch (err) {
297
- console.error('Error loading history:', err);
298
- historyList.innerHTML = '<p class="text-red-500 text-center py-8">Impossible de charger l\'historique.</p>';
299
- }
300
  }
301
 
302
  function updateTaskInHistoryUI(task) {
303
  const taskElement = document.querySelector(`.task-item[data-task-id="${task.task_id}"]`);
304
  if (!taskElement) return;
305
-
306
  taskElement.querySelector('.status-badge').outerHTML = getStatusBadge(task.status);
307
  const progressBar = taskElement.querySelector('.progress-bar');
308
  const progressContainer = taskElement.querySelector('.progress-container');
@@ -315,64 +337,70 @@
315
  progressContainer?.classList.remove('hidden');
316
  }
317
  }
318
-
319
  function pollTaskStatus(taskId) {
320
  if (taskPollers[taskId]) return;
321
-
322
  taskPollers[taskId] = setInterval(async () => {
323
  try {
324
  const response = await fetch(`/task/${taskId}`);
325
  if (!response.ok) {
326
- // Stop polling if task not found (e.g., deleted)
327
  if (response.status === 404) clearInterval(taskPollers[taskId]);
328
  return;
329
  }
330
- const task = await response.json();
331
- updateTaskInHistoryUI(task);
332
-
333
- if (task.status === 'completed' || task.status === 'failed') {
 
 
 
 
 
 
 
 
334
  clearInterval(taskPollers[taskId]);
335
  delete taskPollers[taskId];
336
- // If it's the most recent task, display its final state
337
- const firstTaskInList = historyList.querySelector('.task-item');
338
- if (firstTaskInList && firstTaskInList.dataset.taskId === taskId) {
339
- displayTaskResult(task);
340
  }
341
  }
342
  } catch (err) {
343
  console.error(`Error polling task ${taskId}:`, err);
344
- clearInterval(taskPollers[taskId]); // Stop on network error
345
  delete taskPollers[taskId];
346
  }
347
- }, 2000); // Poll every 2 seconds
348
  }
349
-
350
- window.viewTask = async function(taskId) {
351
- const response = await fetch(`/task/${taskId}`);
352
- const task = await response.json();
353
- displayTaskResult(task);
 
 
354
  }
355
-
356
- window.deleteTask = async function(taskId) {
357
  if (confirm('Voulez-vous vraiment supprimer cette tâche ?')) {
358
- await fetch(`/delete-task/${taskId}`, { method: 'DELETE' });
359
- document.querySelector(`.task-item[data-task-id="${taskId}"]`)?.remove();
360
- if (historyList.children.length === 1) { // 1 is the empty state
361
- historyEmptyState.classList.remove('hidden');
362
- clearHistoryBtn.disabled = true;
363
- }
364
  }
365
  }
366
-
367
  clearHistoryBtn.addEventListener('click', async () => {
368
  if (confirm('Êtes-vous sûr de vouloir effacer tout l\'historique ? Cette action est irréversible.')) {
369
- await fetch('/clear-tasks', { method: 'POST' });
370
- loadTasks(); // Reload to show empty state
371
  resultCard.classList.add('hidden');
 
372
  }
373
  });
374
-
375
-
376
  // --- Event Listeners ---
377
 
378
  fileInput.addEventListener('change', (e) => handleFileSelect(e.target.files[0]));
@@ -386,13 +414,10 @@
386
  handleFileSelect(file);
387
  });
388
  removeFileBtn.addEventListener('click', resetFileSelection);
389
-
390
  youtubeUrlInput.addEventListener('input', () => {
391
- if (youtubeUrlInput.value.trim() !== '') {
392
- resetFileSelection();
393
- }
394
  });
395
-
396
  uploadForm.addEventListener('submit', async (e) => {
397
  e.preventDefault();
398
  if (!fileInput.files[0] && !youtubeUrlInput.value.trim()) {
@@ -402,7 +427,7 @@
402
  showLoading(true);
403
  resultCard.classList.add('hidden');
404
  errorCard.classList.add('hidden');
405
-
406
  const formData = new FormData(uploadForm);
407
 
408
  try {
@@ -410,8 +435,15 @@
410
  const data = await response.json();
411
  if (!response.ok || data.error) throw new Error(data.error || `Erreur serveur: ${response.status}`);
412
 
413
- // Add new task to top of history and start polling
414
- await loadTasks(); // Reload entire list to get the new task on top
 
 
 
 
 
 
 
415
 
416
  } catch (err) {
417
  displayError(err.message);
@@ -434,10 +466,10 @@
434
  setTimeout(() => { copyBtnText.textContent = 'Copier'; }, 2000);
435
  });
436
  });
437
-
438
  // Initial Load
439
  loadTasks();
440
-
441
  </script>
442
  </body>
443
  </html>
 
131
  <div class="lg:col-span-2">
132
  <div class="bg-white/80 backdrop-blur-sm border border-slate-200 rounded-2xl shadow-lg p-6 sticky top-8 fade-in-up" style="animation-delay: 200ms;">
133
  <div class="flex justify-between items-center mb-4">
134
+ <h2 class="text-xl font-bold text-gray-800">Mon Historique</h2>
135
  <button id="clearHistoryBtn" class="text-sm font-medium text-red-600 hover:text-red-800 transition-colors disabled:text-red-300 disabled:cursor-not-allowed">
136
  Tout effacer
137
  </button>
 
139
  <div id="historyList" class="space-y-3 max-h-[600px] overflow-y-auto pr-2">
140
  <div id="historyEmptyState" class="text-center py-16">
141
  <svg class="w-16 h-16 mx-auto text-slate-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0A2.25 2.25 0 013.75 7.5h16.5a2.25 2.25 0 012.25 2.25m-18.75 0h18.75c.621 0 1.125.504 1.125 1.125v.158c0 .53-.223.998-.586 1.328l-5.454 4.849a3.75 3.75 0 01-5.304 0L4.336 12.36a1.125 1.125 0 01-.586-1.328v-.158c0-.621.504-1.125 1.125-1.125z" /></svg>
142
+ <p class="mt-4 text-slate-500 font-medium">Vos synthèses apparaîtront ici.</p>
143
  </div>
144
  </div>
145
  </div>
 
172
  const historyList = document.getElementById('historyList');
173
  const historyEmptyState = document.getElementById('historyEmptyState');
174
  const clearHistoryBtn = document.getElementById('clearHistoryBtn');
175
+
176
  let currentTaskId = null;
177
  const taskPollers = {};
178
+ const LOCAL_STORAGE_KEY = 'aiSummarizerTasks';
179
+
180
+ // --- Fonctions utilitaires pour le LocalStorage ---
181
+
182
+ /**
183
+ * Récupère les tâches depuis le localStorage.
184
+ * @returns {Array} La liste des tâches.
185
+ */
186
+ function getLocalTasks() {
187
+ const tasksJSON = localStorage.getItem(LOCAL_STORAGE_KEY);
188
+ try {
189
+ return tasksJSON ? JSON.parse(tasksJSON) : [];
190
+ } catch (e) {
191
+ console.error("Erreur de parsing des tâches locales:", e);
192
+ return [];
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Sauvegarde la liste des tâches dans le localStorage.
198
+ * @param {Array} tasks La liste des tâches à sauvegarder.
199
+ */
200
+ function saveLocalTasks(tasks) {
201
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(tasks));
202
+ }
203
+
204
+
205
+ // --- Fonctions UI ---
206
  function showLoading(isLoading) {
207
  submitBtn.disabled = isLoading;
208
  btnText.textContent = isLoading ? 'Lancement...' : 'Lancer la synthèse';
209
  btnSpinner.classList.toggle('hidden', !isLoading);
210
  }
211
+
212
  function displayTaskResult(task) {
213
  if (task.status === 'completed' && task.summary) {
214
  summaryContent.innerHTML = marked.parse(task.summary);
 
218
  errorCard.classList.add('hidden');
219
  resultCard.classList.remove('hidden');
220
  resultCard.classList.add('fade-in-up');
221
+ if (resultCard.getBoundingClientRect().top < 0 || resultCard.getBoundingClientRect().bottom > window.innerHeight) {
222
+ window.scrollTo({ top: resultCard.offsetTop - 80, behavior: 'smooth' });
223
+ }
224
  } else if (task.status === 'failed' && task.error) {
225
  displayError(task.error);
226
  resultCard.classList.add('hidden');
 
229
  errorCard.classList.add('hidden');
230
  }
231
  }
232
+
233
  function displayError(message) {
234
  errorMessage.textContent = message;
235
  errorCard.classList.remove('hidden');
236
  errorCard.classList.add('fade-in-up');
237
  }
238
+
239
  function getFileIcon(filename, sizeClass = 'w-8 h-8') {
240
  if (filename.toLowerCase().includes('youtube')) {
241
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="${sizeClass} text-red-600"><path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path></svg>`;
 
250
  }
251
  return iconSvg;
252
  }
 
253
  function resetFileSelection() {
254
  fileInput.value = '';
255
  fileInfo.classList.add('hidden');
256
  fileInfo.classList.remove('flex');
257
  dropzone.classList.remove('hidden');
258
  }
 
259
  function handleFileSelect(file) {
260
  if (!file) return;
261
  youtubeUrlInput.value = '';
 
266
  fileInfo.classList.remove('hidden');
267
  fileInfo.classList.add('flex');
268
  }
269
+
270
  // --- Task & History Management ---
271
+
272
  function getStatusBadge(status) {
273
  switch (status) {
274
  case 'completed': return `<span class="status-badge bg-green-100 text-green-800">Terminé</span>`;
 
304
  </div>`;
305
  }
306
 
307
+ function renderHistory(tasks) {
308
+ historyEmptyState.classList.toggle('hidden', tasks.length > 0);
309
+ clearHistoryBtn.disabled = tasks.length === 0;
310
+ historyList.innerHTML = tasks.map(createHistoryItemHTML).join('');
311
+ }
312
+
313
+ function loadTasks() {
314
+ const tasks = getLocalTasks();
315
+ renderHistory(tasks);
316
+
317
+ tasks.forEach(task => {
318
+ if (task.status === 'pending' || task.status === 'processing') {
319
+ pollTaskStatus(task.task_id);
 
320
  }
321
+ });
 
 
 
322
  }
323
 
324
  function updateTaskInHistoryUI(task) {
325
  const taskElement = document.querySelector(`.task-item[data-task-id="${task.task_id}"]`);
326
  if (!taskElement) return;
327
+
328
  taskElement.querySelector('.status-badge').outerHTML = getStatusBadge(task.status);
329
  const progressBar = taskElement.querySelector('.progress-bar');
330
  const progressContainer = taskElement.querySelector('.progress-container');
 
337
  progressContainer?.classList.remove('hidden');
338
  }
339
  }
340
+
341
  function pollTaskStatus(taskId) {
342
  if (taskPollers[taskId]) return;
343
+
344
  taskPollers[taskId] = setInterval(async () => {
345
  try {
346
  const response = await fetch(`/task/${taskId}`);
347
  if (!response.ok) {
 
348
  if (response.status === 404) clearInterval(taskPollers[taskId]);
349
  return;
350
  }
351
+ const updatedTask = await response.json();
352
+
353
+ let tasks = getLocalTasks();
354
+ const taskIndex = tasks.findIndex(t => t.task_id === taskId);
355
+ if (taskIndex !== -1) {
356
+ tasks[taskIndex] = updatedTask;
357
+ saveLocalTasks(tasks);
358
+ }
359
+
360
+ updateTaskInHistoryUI(updatedTask);
361
+
362
+ if (updatedTask.status === 'completed' || updatedTask.status === 'failed') {
363
  clearInterval(taskPollers[taskId]);
364
  delete taskPollers[taskId];
365
+ if (taskIndex === 0) {
366
+ displayTaskResult(updatedTask);
 
 
367
  }
368
  }
369
  } catch (err) {
370
  console.error(`Error polling task ${taskId}:`, err);
371
+ clearInterval(taskPollers[taskId]);
372
  delete taskPollers[taskId];
373
  }
374
+ }, 3000);
375
  }
376
+
377
+ window.viewTask = function(taskId) {
378
+ const tasks = getLocalTasks();
379
+ const task = tasks.find(t => t.task_id === taskId);
380
+ if (task) {
381
+ displayTaskResult(task);
382
+ }
383
  }
384
+
385
+ window.deleteTask = function(taskId) {
386
  if (confirm('Voulez-vous vraiment supprimer cette tâche ?')) {
387
+ let tasks = getLocalTasks();
388
+ tasks = tasks.filter(t => t.task_id !== taskId);
389
+ saveLocalTasks(tasks);
390
+ loadTasks();
 
 
391
  }
392
  }
393
+
394
  clearHistoryBtn.addEventListener('click', async () => {
395
  if (confirm('Êtes-vous sûr de vouloir effacer tout l\'historique ? Cette action est irréversible.')) {
396
+ saveLocalTasks([]);
397
+ loadTasks();
398
  resultCard.classList.add('hidden');
399
+ errorCard.classList.add('hidden');
400
  }
401
  });
402
+
403
+
404
  // --- Event Listeners ---
405
 
406
  fileInput.addEventListener('change', (e) => handleFileSelect(e.target.files[0]));
 
414
  handleFileSelect(file);
415
  });
416
  removeFileBtn.addEventListener('click', resetFileSelection);
 
417
  youtubeUrlInput.addEventListener('input', () => {
418
+ if (youtubeUrlInput.value.trim() !== '') resetFileSelection();
 
 
419
  });
420
+
421
  uploadForm.addEventListener('submit', async (e) => {
422
  e.preventDefault();
423
  if (!fileInput.files[0] && !youtubeUrlInput.value.trim()) {
 
427
  showLoading(true);
428
  resultCard.classList.add('hidden');
429
  errorCard.classList.add('hidden');
430
+
431
  const formData = new FormData(uploadForm);
432
 
433
  try {
 
435
  const data = await response.json();
436
  if (!response.ok || data.error) throw new Error(data.error || `Erreur serveur: ${response.status}`);
437
 
438
+ const taskResponse = await fetch(`/task/${data.task_id}`);
439
+ const newTask = await taskResponse.json();
440
+
441
+ let tasks = getLocalTasks();
442
+ tasks.unshift(newTask);
443
+ saveLocalTasks(tasks);
444
+
445
+ renderHistory(tasks);
446
+ pollTaskStatus(newTask.task_id);
447
 
448
  } catch (err) {
449
  displayError(err.message);
 
466
  setTimeout(() => { copyBtnText.textContent = 'Copier'; }, 2000);
467
  });
468
  });
469
+
470
  // Initial Load
471
  loadTasks();
472
+
473
  </script>
474
  </body>
475
  </html>