yasmine110 commited on
Commit
6c048df
·
verified ·
1 Parent(s): 25e8b77

Update frontend/script.js

Browse files
Files changed (1) hide show
  1. frontend/script.js +244 -92
frontend/script.js CHANGED
@@ -1,152 +1,304 @@
1
  // Configuration
2
- const MAX_FILE_SIZE_MB = 10; // Taille max en Mo
3
- const PROCESSING_TIMEOUT = 300000; // 5 min en ms
 
4
 
5
- document.getElementById('uploadForm').addEventListener('submit', async function(e) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  e.preventDefault();
7
 
8
- const fileInput = document.getElementById('document');
9
- const targetLang = document.getElementById('targetLang').value;
10
- const loading = document.getElementById('loading');
11
- const resultArea = document.getElementById('resultArea');
12
- const downloadBtn = document.getElementById('downloadBtn');
13
- const sizeWarning = document.getElementById('sizeWarning');
14
-
15
- if (fileInput.files.length === 0) {
16
- alert('Veuillez sélectionner un fichier');
17
- return;
18
- }
19
-
20
  const file = fileInput.files[0];
21
-
22
- // Vérification taille fichier
23
- if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
24
- alert(`Fichier trop volumineux (> ${MAX_FILE_SIZE_MB} Mo)`);
25
  return;
26
  }
27
-
28
- // Afficher les éléments UI
29
- loading.style.display = 'block';
 
 
 
30
  resultArea.style.display = 'none';
31
- downloadBtn.style.display = 'none';
32
- sizeWarning.style.display = file.size > 5 * 1024 * 1024 ? 'block' : 'none'; // Avertissement si >5Mo
33
 
34
  try {
35
- // 1. Traduction standard avec timeout
36
- const controller = new AbortController();
37
- const timeoutId = setTimeout(() => controller.abort(), PROCESSING_TIMEOUT);
38
 
39
- const formData = new FormData();
40
- formData.append('file', file);
41
- formData.append('target_lang', targetLang);
42
 
43
- const translateResponse = await fetch('/api/translate', {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  method: 'POST',
45
  body: formData,
46
  signal: controller.signal
47
  });
48
-
49
  clearTimeout(timeoutId);
50
 
51
- if (!translateResponse.ok) {
52
- const error = await translateResponse.json().catch(() => ({ detail: translateResponse.statusText }));
53
- throw new Error(error.detail || 'Erreur inconnue');
54
  }
55
-
56
- const data = await translateResponse.json();
57
-
58
- // Afficher les résultats
59
- document.getElementById('filename').textContent = data.filename;
60
- document.getElementById('originalText').textContent = data.original_text;
61
- document.getElementById('translatedText').textContent = data.translated_text;
62
-
63
- // 2. Préparer le téléchargement
64
- downloadBtn.style.display = 'block';
65
- downloadBtn.onclick = () => downloadTranslated(file, targetLang);
66
-
67
- resultArea.style.display = 'block';
68
  } catch (error) {
69
- console.error('Erreur:', error);
70
- if (error.name === 'AbortError') {
71
- alert(`Délai dépassé (${PROCESSING_TIMEOUT/1000}s). Essayez avec un fichier plus court.`);
72
- } else {
73
- alert(`Erreur : ${error.message || 'Une erreur est survenue'}`);
74
- }
75
  } finally {
76
- loading.style.display = 'none';
 
 
77
  }
78
- });
79
 
80
- // Téléchargement optimisé
81
- async function downloadTranslated(file, targetLang) {
82
- const loading = document.getElementById('loading');
83
- const progressBar = document.createElement('div');
84
- progressBar.innerHTML = `
85
- <div class="progress mt-2">
86
- <div class="progress-bar progress-bar-striped progress-bar-animated"
87
- style="width: 0%"></div>
88
- </div>
89
- `;
90
- loading.appendChild(progressBar);
91
- loading.style.display = 'block';
 
 
 
92
 
 
 
 
93
  try {
94
- // Configuration du timeout
95
- const controller = new AbortController();
96
- const timeoutId = setTimeout(() => controller.abort(), PROCESSING_TIMEOUT);
97
-
98
  const formData = new FormData();
99
  formData.append('file', file);
100
  formData.append('target_lang', targetLang);
101
-
102
- // Mise à jour visuelle
103
- progressBar.querySelector('.progress-bar').style.width = "30%";
104
-
105
  const response = await fetch('/api/download_translated', {
106
  method: 'POST',
107
  body: formData,
108
  signal: controller.signal
109
  });
110
-
111
  clearTimeout(timeoutId);
112
- progressBar.querySelector('.progress-bar').style.width = "70%";
113
 
114
  if (!response.ok) {
115
  const error = await response.json().catch(() => ({ detail: response.statusText }));
116
- throw new Error(error.detail || 'Erreur de téléchargement');
117
  }
118
 
119
- // Vérification du type MIME
120
  const blob = await response.blob();
121
  if (!blob.type.includes('openxmlformats-officedocument.wordprocessingml')) {
122
  throw new Error("Format de fichier invalide");
123
  }
124
 
125
- // Téléchargement
126
- progressBar.querySelector('.progress-bar').style.width = "100%";
127
  const url = URL.createObjectURL(blob);
128
  const a = document.createElement('a');
129
  a.href = url;
130
- a.download = `TRADUIT_${file.name.replace(/\.[^/.]+$/, "")}.docx`;
131
  document.body.appendChild(a);
132
  a.click();
133
 
134
- // Nettoyage
135
  setTimeout(() => {
136
  document.body.removeChild(a);
137
  URL.revokeObjectURL(url);
138
- progressBar.remove();
139
  }, 100);
140
 
 
 
141
  } catch (error) {
142
  console.error('Erreur de téléchargement:', error);
143
- if (error.name === 'AbortError') {
144
- alert(`Délai dépassé (${PROCESSING_TIMEOUT/1000}s). Essayez avec un fichier plus court.`);
145
- } else {
146
- alert(`Échec du téléchargement : ${error.message}`);
147
- }
148
  } finally {
149
- loading.style.display = 'none';
150
- progressBar.remove();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
 
1
  // Configuration
2
+ const MAX_FILE_SIZE_MB = 15; // Augmenté à 15 Mo
3
+ const PROCESSING_TIMEOUT = 600000; // 10 min en ms (augmenté)
4
+ const SUPPORTED_FORMATS = ['pdf', 'docx', 'xlsx', 'pptx']; // Formats supportés
5
 
6
+ // Éléments UI
7
+ const uploadForm = document.getElementById('uploadForm');
8
+ const fileInput = document.getElementById('document');
9
+ const targetLangSelect = document.getElementById('targetLang');
10
+ const loadingElement = document.getElementById('loading');
11
+ const resultArea = document.getElementById('resultArea');
12
+ const downloadBtn = document.getElementById('downloadBtn');
13
+ const sizeWarning = document.getElementById('sizeWarning');
14
+ const progressContainer = document.getElementById('progressContainer');
15
+ const progressBar = document.getElementById('progressBar');
16
+ const progressPercent = document.getElementById('progressPercent');
17
+ const copyTranslatedBtn = document.getElementById('copyTranslatedBtn');
18
+ const newTranslationBtn = document.getElementById('newTranslationBtn');
19
+
20
+ // Initialisation
21
+ document.addEventListener('DOMContentLoaded', () => {
22
+ initEventListeners();
23
+ checkFileSupport();
24
+ });
25
+
26
+ function initEventListeners() {
27
+ uploadForm.addEventListener('submit', handleFormSubmit);
28
+ fileInput.addEventListener('change', handleFileSelect);
29
+ copyTranslatedBtn.addEventListener('click', copyTranslatedText);
30
+ newTranslationBtn.addEventListener('click', resetForm);
31
+ }
32
+
33
+ function checkFileSupport() {
34
+ const fileSupportInfo = document.createElement('div');
35
+ fileSupportInfo.className = 'small text-muted mt-2';
36
+ fileSupportInfo.innerHTML = `Formats supportés: ${SUPPORTED_FORMATS.join(', ').toUpperCase()}`;
37
+ fileInput.parentNode.insertBefore(fileSupportInfo, fileInput.nextSibling);
38
+ }
39
+
40
+ async function handleFormSubmit(e) {
41
  e.preventDefault();
42
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  const file = fileInput.files[0];
44
+ const targetLang = targetLangSelect.value;
45
+
46
+ if (!file) {
47
+ showToast('Veuillez sélectionner un fichier', 'warning');
48
  return;
49
  }
50
+
51
+ // Validation du fichier
52
+ if (!validateFile(file)) return;
53
+
54
+ // UI Loading state
55
+ setLoadingState(true, 'Préparation du document...');
56
  resultArea.style.display = 'none';
57
+ sizeWarning.style.display = file.size > 5 * 1024 * 1024 ? 'block' : 'none';
 
58
 
59
  try {
60
+ // Traduction avec suivi de progression
61
+ const data = await translateFile(file, targetLang);
 
62
 
63
+ // Affichage des résultats
64
+ displayResults(data, file);
65
+ showToast('Traduction terminée avec succès!', 'success');
66
 
67
+ } catch (error) {
68
+ handleTranslationError(error);
69
+ } finally {
70
+ setLoadingState(false);
71
+ }
72
+ }
73
+
74
+ function validateFile(file) {
75
+ const fileExtension = file.name.split('.').pop().toLowerCase();
76
+
77
+ if (!SUPPORTED_FORMATS.includes(fileExtension)) {
78
+ showToast(`Format non supporté: ${fileExtension.toUpperCase()}`, 'error');
79
+ return false;
80
+ }
81
+
82
+ if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
83
+ showToast(`Fichier trop volumineux (> ${MAX_FILE_SIZE_MB} Mo)`, 'error');
84
+ return false;
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ function handleFileSelect(e) {
91
+ const file = e.target.files[0];
92
+ if (!file) return;
93
+
94
+ const fileInfo = document.getElementById('fileInfo');
95
+ const sizeMB = (file.size / (1024 * 1024)).toFixed(2);
96
+
97
+ fileInfo.innerHTML = `
98
+ <i class="bi bi-file-earmark-text me-1"></i>
99
+ <strong>${file.name}</strong> • ${sizeMB} MB
100
+ `;
101
+
102
+ // Animation feedback
103
+ document.querySelector('.card').classList.add('animate__animated', 'animate__pulse');
104
+ setTimeout(() => {
105
+ document.querySelector('.card').classList.remove('animate__animated', 'animate__pulse');
106
+ }, 1000);
107
+
108
+ // Avertissement pour fichiers volumineux
109
+ sizeWarning.style.display = file.size > 5 * 1024 * 1024 ? 'block' : 'none';
110
+ if (file.size > 5 * 1024 * 1024) {
111
+ document.getElementById('warningText').textContent =
112
+ `Fichier volumineux (${sizeMB} MB) - Le traitement peut prendre plusieurs minutes`;
113
+ }
114
+ }
115
+
116
+ async function translateFile(file, targetLang) {
117
+ const formData = new FormData();
118
+ formData.append('file', file);
119
+ formData.append('target_lang', targetLang);
120
+
121
+ // Configuration du timeout et suivi de progression
122
+ const controller = new AbortController();
123
+ const timeoutId = setTimeout(() => controller.abort(), PROCESSING_TIMEOUT);
124
+
125
+ // Affichage de la progression
126
+ progressContainer.style.display = 'block';
127
+ updateProgress(10, 'Analyse du document...');
128
+
129
+ try {
130
+ const response = await fetch('/api/translate', {
131
  method: 'POST',
132
  body: formData,
133
  signal: controller.signal
134
  });
135
+
136
  clearTimeout(timeoutId);
137
 
138
+ if (!response.ok) {
139
+ const error = await response.json().catch(() => ({ detail: response.statusText }));
140
+ throw new Error(error.detail || 'Erreur lors de la traduction');
141
  }
142
+
143
+ updateProgress(100, 'Traduction terminée');
144
+ return await response.json();
145
+
 
 
 
 
 
 
 
 
 
146
  } catch (error) {
147
+ throw error;
 
 
 
 
 
148
  } finally {
149
+ setTimeout(() => {
150
+ progressContainer.style.display = 'none';
151
+ }, 1000);
152
  }
153
+ }
154
 
155
+ function displayResults(data, file) {
156
+ document.getElementById('filename').textContent = data.filename;
157
+ document.getElementById('originalText').textContent = data.original_text;
158
+ document.getElementById('translatedText').textContent = data.translated_text;
159
+
160
+ const sizeMB = (file.size / (1024 * 1024)).toFixed(2);
161
+ document.getElementById('fileSize').textContent = `(${sizeMB} MB)`;
162
+
163
+ document.getElementById('infoText').textContent =
164
+ `Document traduit en ${targetLangSelect.options[targetLangSelect.selectedIndex].text}`;
165
+
166
+ // Préparer le téléchargement
167
+ downloadBtn.onclick = () => downloadTranslated(file, targetLangSelect.value);
168
+ resultArea.style.display = 'block';
169
+ }
170
 
171
+ async function downloadTranslated(file, targetLang) {
172
+ setLoadingState(true, 'Préparation du téléchargement...');
173
+
174
  try {
 
 
 
 
175
  const formData = new FormData();
176
  formData.append('file', file);
177
  formData.append('target_lang', targetLang);
178
+
179
+ const controller = new AbortController();
180
+ const timeoutId = setTimeout(() => controller.abort(), PROCESSING_TIMEOUT);
181
+
182
  const response = await fetch('/api/download_translated', {
183
  method: 'POST',
184
  body: formData,
185
  signal: controller.signal
186
  });
187
+
188
  clearTimeout(timeoutId);
 
189
 
190
  if (!response.ok) {
191
  const error = await response.json().catch(() => ({ detail: response.statusText }));
192
+ throw new Error(error.detail || 'Erreur lors du téléchargement');
193
  }
194
 
 
195
  const blob = await response.blob();
196
  if (!blob.type.includes('openxmlformats-officedocument.wordprocessingml')) {
197
  throw new Error("Format de fichier invalide");
198
  }
199
 
 
 
200
  const url = URL.createObjectURL(blob);
201
  const a = document.createElement('a');
202
  a.href = url;
203
+ a.download = `TRADUCTION_${file.name.replace(/\.[^/.]+$/, "")}.docx`;
204
  document.body.appendChild(a);
205
  a.click();
206
 
 
207
  setTimeout(() => {
208
  document.body.removeChild(a);
209
  URL.revokeObjectURL(url);
 
210
  }, 100);
211
 
212
+ showToast('Téléchargement terminé!', 'success');
213
+
214
  } catch (error) {
215
  console.error('Erreur de téléchargement:', error);
216
+ showToast(`Échec du téléchargement: ${error.message}`, 'error');
 
 
 
 
217
  } finally {
218
+ setLoadingState(false);
219
+ }
220
+ }
221
+
222
+ function copyTranslatedText() {
223
+ const translatedText = document.getElementById('translatedText').textContent;
224
+ navigator.clipboard.writeText(translatedText)
225
+ .then(() => showToast('Texte copié dans le presse-papiers!', 'success'))
226
+ .catch(err => showToast('Échec de la copie', 'error'));
227
+ }
228
+
229
+ function resetForm() {
230
+ uploadForm.reset();
231
+ document.getElementById('fileInfo').innerHTML = 'Aucun fichier sélectionné';
232
+ resultArea.style.display = 'none';
233
+ sizeWarning.style.display = 'none';
234
+ showToast('Prêt pour une nouvelle traduction', 'info');
235
+ }
236
+
237
+ function setLoadingState(isLoading, message = '') {
238
+ if (isLoading) {
239
+ loadingElement.style.display = 'block';
240
+ document.getElementById('loadingText').innerHTML = `
241
+ <i class="bi bi-gear-fill animate-spin me-2"></i>
242
+ ${message}
243
+ `;
244
+ } else {
245
+ loadingElement.style.display = 'none';
246
  }
247
+ }
248
+
249
+ function updateProgress(percent, message) {
250
+ progressBar.style.width = `${percent}%`;
251
+ progressPercent.textContent = `${percent}%`;
252
+ if (message) {
253
+ document.getElementById('loadingText').innerHTML = `
254
+ <i class="bi bi-gear-fill animate-spin me-2"></i>
255
+ ${message} (${percent}%)
256
+ `;
257
+ }
258
+ }
259
+
260
+ function handleTranslationError(error) {
261
+ console.error('Erreur:', error);
262
+
263
+ let errorMessage = 'Une erreur est survenue';
264
+ if (error.name === 'AbortError') {
265
+ errorMessage = `Délai dépassé (${PROCESSING_TIMEOUT/1000}s). Essayez avec un fichier plus court.`;
266
+ } else {
267
+ errorMessage = error.message || error.toString();
268
+ }
269
+
270
+ showToast(errorMessage, 'error');
271
+ }
272
+
273
+ function showToast(message, type = 'info') {
274
+ const toast = document.getElementById('toastNotification');
275
+ const toastMessage = document.getElementById('toastMessage');
276
+
277
+ // Configuration du style selon le type
278
+ const typeStyles = {
279
+ success: { bg: '#2ecc71', icon: 'bi-check-circle-fill' },
280
+ error: { bg: '#e74c3c', icon: 'bi-exclamation-circle-fill' },
281
+ warning: { bg: '#f39c12', icon: 'bi-exclamation-triangle-fill' },
282
+ info: { bg: '#3498db', icon: 'bi-info-circle-fill' }
283
+ };
284
+
285
+ const style = typeStyles[type] || typeStyles.info;
286
+
287
+ toast.style.backgroundColor = style.bg;
288
+ toastMessage.innerHTML = `
289
+ <i class="bi ${style.icon} me-2"></i>
290
+ ${message}
291
+ `;
292
+
293
+ // Affichage avec animation
294
+ toast.style.display = 'flex';
295
+ toast.style.animation = 'fadeIn 0.5s';
296
+
297
+ // Masquage après 5 secondes
298
+ setTimeout(() => {
299
+ toast.style.animation = 'fadeIn 0.5s reverse';
300
+ setTimeout(() => {
301
+ toast.style.display = 'none';
302
+ }, 500);
303
+ }, 5000);
304
  }