feat: Add multi-file batch upload with sequential processing

Implements comprehensive batch upload system with real-time progress tracking:

Backend Infrastructure:
- Add batch_jobs global dict for batch orchestration
- Add BatchFileInfo and BatchJob TypedDicts to utils/types.py
- Create run_batch_sequential() worker function with thread.join() synchronization
- Modify /upload POST route to detect single vs multi-file uploads
- Add 3 batch API routes: /upload/batch/progress, /status, /result
- Add timestamp_to_date Jinja2 template filter

Frontend:
- Update upload.html with 'multiple' attribute and file counter
- Create upload_batch_progress.html: Real-time dashboard with SSE per file
- Create upload_batch_result.html: Final summary with statistics

Architecture:
- Backward compatible: single-file upload unchanged
- Sequential processing: one file after another (respects API limits)
- N parallel SSE connections: one per file for real-time progress
- Polling mechanism to discover job IDs as files start processing
- 1-hour timeout per file with error handling and continuation

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-08 22:41:52 +01:00
parent 7a7a2b8e19
commit b70b796ef8
5 changed files with 819 additions and 37 deletions

View File

@@ -17,7 +17,7 @@
<form method="post" enctype="multipart/form-data">
<!-- Sélection du fichier -->
<div class="form-group">
<label class="form-label" for="file">📎 Sélectionnez votre fichier</label>
<label class="form-label" for="file">📎 Sélectionnez votre fichier (ou plusieurs)</label>
<input
type="file"
name="file"
@@ -25,9 +25,13 @@
class="form-control"
accept=".pdf,.docx,.md"
required
multiple
onchange="updateOptionsForFileType()"
>
<div class="caption mt-1">Formats acceptés : PDF (.pdf), Word (.docx) ou Markdown (.md) • Max 50 MB</div>
<div class="caption mt-1">
Formats acceptés : PDF (.pdf), Word (.docx) ou Markdown (.md) • Max 50 MB par fichier<br>
<span id="file-count-info" style="font-weight: 600; color: #2196F3;"></span>
</div>
</div>
<!-- Configuration recommandée (par défaut) -->
@@ -217,6 +221,18 @@ function updateModelOptions() {
function updateOptionsForFileType() {
const fileInput = document.getElementById('file');
const fileCountInfo = document.getElementById('file-count-info');
const fileCount = fileInput.files.length;
// Update file count display
if (fileCount === 0) {
fileCountInfo.textContent = '';
} else if (fileCount === 1) {
fileCountInfo.textContent = '';
} else {
fileCountInfo.textContent = `📦 ${fileCount} fichiers sélectionnés (traitement séquentiel : un fichier après l'autre)`;
}
const fileName = fileInput.files[0]?.name || '';
const isWord = fileName.toLowerCase().endsWith('.docx');
const isPDF = fileName.toLowerCase().endsWith('.pdf');
@@ -238,7 +254,7 @@ function updateOptionsForFileType() {
wordPipelineInfo.style.display = 'none';
markdownPipelineInfo.style.display = 'none';
// Afficher selon le type
// Afficher selon le type (basé sur le premier fichier)
if (isWord) {
wordInfo.style.display = 'block';
wordPipelineInfo.style.display = 'block';