Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NaweliMed Pro - Traducteur PDF Multilingue</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .dropzone { | |
| border: 2px dashed #3b82f6; | |
| border-radius: 0.5rem; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #10b981; | |
| background-color: #f0fdf4; | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| #pdfViewer { | |
| border: 1px solid #e5e7eb; | |
| border-radius: 0.5rem; | |
| overflow: hidden; | |
| } | |
| .language-flag { | |
| width: 24px; | |
| height: 16px; | |
| display: inline-block; | |
| margin-right: 8px; | |
| background-size: cover; | |
| border-radius: 2px; | |
| } | |
| .logo { | |
| background: linear-gradient(135deg, #3b82f6 0%, #10b981 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <div class="flex items-center justify-center mb-2"> | |
| <i class="fas fa-file-pdf text-4xl mr-3 logo"></i> | |
| <h1 class="text-4xl font-bold logo">NaweliMed Pro</h1> | |
| </div> | |
| <p class="text-gray-600">Solution professionnelle de traduction de documents médicaux</p> | |
| </header> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8"> | |
| <div class="p-6"> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <!-- PDF Upload Section --> | |
| <div class="space-y-4"> | |
| <h2 class="text-xl font-semibold text-gray-800 flex items-center"> | |
| <i class="fas fa-file-upload mr-2 text-blue-500"></i> Importer un PDF | |
| </h2> | |
| <div id="dropzone" class="dropzone p-8 text-center cursor-pointer hover:bg-blue-50 transition-colors duration-200"> | |
| <div class="flex flex-col items-center justify-center space-y-2"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> | |
| </svg> | |
| <p class="text-gray-600">Glissez-déposez votre fichier PDF ici ou cliquez pour sélectionner</p> | |
| <p class="text-sm text-gray-500">Taille maximale: 10MB</p> | |
| </div> | |
| <input type="file" id="fileInput" class="hidden" accept=".pdf"> | |
| </div> | |
| <div id="fileInfo" class="hidden bg-blue-50 p-3 rounded-lg border border-blue-100"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <p class="font-medium text-blue-800 flex items-center"> | |
| <i class="fas fa-file-pdf mr-2"></i> | |
| <span id="fileName"></span> | |
| </p> | |
| <p class="text-sm text-blue-600 ml-6" id="fileSize"></p> | |
| </div> | |
| <button id="removeFile" class="text-red-500 hover:text-red-700"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | |
| <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Language Selection Section --> | |
| <div class="space-y-4"> | |
| <h2 class="text-xl font-semibold text-gray-800 flex items-center"> | |
| <i class="fas fa-language mr-2 text-blue-500"></i> Sélection des langues | |
| </h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1 flex items-center"> | |
| <i class="fas fa-arrow-down mr-2 text-gray-500"></i> Langue source | |
| </label> | |
| <select id="sourceLang" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="fr"> | |
| <span class="language-flag" style="background-image: url('https://flagcdn.com/16x12/fr.png')"></span> | |
| Français | |
| </option> | |
| <option value="en"> | |
| <span class="language-flag" style="background-image: url('https://flagcdn.com/16x12/gb.png')"></span> | |
| Anglais | |
| </option> | |
| <option value="nl"> | |
| <span class="language-flag" style="background-image: url('https://flagcdn.com/16x12/nl.png')"></span> | |
| Néerlandais | |
| </option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1 flex items-center"> | |
| <i class="fas fa-arrow-up mr-2 text-gray-500"></i> Langue cible | |
| </label> | |
| <select id="targetLang" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="en"> | |
| <span class="language-flag" style="background-image: url('https://flagcdn.com/16x12/gb.png')"></span> | |
| Anglais | |
| </option> | |
| <option value="fr"> | |
| <span class="language-flag" style="background-image: url('https://flagcdn.com/16x12/fr.png')"></span> | |
| Français | |
| </option> | |
| <option value="nl"> | |
| <span class="language-flag" style="background-image: url('https://flagcdn.com/16x12/nl.png')"></span> | |
| Néerlandais | |
| </option> | |
| </select> | |
| </div> | |
| <div class="flex items-center"> | |
| <input id="preserveFormatting" type="checkbox" class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"> | |
| <label for="preserveFormatting" class="ml-2 block text-sm text-gray-700"> | |
| <i class="fas fa-text-height mr-1"></i> Conserver la mise en forme | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- PDF Preview Section --> | |
| <div id="pdfPreviewSection" class="hidden mt-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
| <i class="fas fa-eye mr-2 text-blue-500"></i> Aperçu du PDF | |
| </h2> | |
| <div id="pdfViewer" class="w-full h-64 bg-gray-100 flex items-center justify-center rounded-lg"> | |
| <p class="text-gray-500 flex flex-col items-center"> | |
| <i class="fas fa-file-pdf text-4xl mb-2 text-gray-400"></i> | |
| Aperçu du PDF sera affiché ici | |
| </p> | |
| </div> | |
| <div class="mt-2 flex justify-between items-center"> | |
| <div class="text-sm text-gray-500"> | |
| <i class="fas fa-file-alt mr-1"></i> Pages: <span id="pageCount">0</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="prevPage" class="px-3 py-1 bg-gray-200 rounded-md text-gray-700 hover:bg-gray-300 transition-colors disabled:opacity-50 flex items-center"> | |
| <i class="fas fa-chevron-left mr-1"></i> Précédent | |
| </button> | |
| <span class="px-3 py-1 text-gray-700 flex items-center"> | |
| <i class="fas fa-file mr-1"></i> Page <span id="currentPage">1</span> | |
| </span> | |
| <button id="nextPage" class="px-3 py-1 bg-gray-200 rounded-md text-gray-700 hover:bg-gray-300 transition-colors disabled:opacity-50 flex items-center"> | |
| Suivant <i class="fas fa-chevron-right ml-1"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Action Buttons --> | |
| <div class="mt-6 flex flex-col sm:flex-row justify-end space-y-2 sm:space-y-0 sm:space-x-3"> | |
| <button id="translateBtn" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center"> | |
| <i class="fas fa-language mr-2"></i> Traduire le document | |
| </button> | |
| <button id="exportBtn" class="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center"> | |
| <i class="fas fa-file-excel mr-2"></i> Exporter en Excel | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Progress Bar --> | |
| <div id="progressContainer" class="hidden bg-gray-100 border-t border-gray-200"> | |
| <div class="px-6 py-3"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-blue-700 flex items-center"> | |
| <i class="fas fa-spinner fa-spin mr-2"></i> Progression | |
| </span> | |
| <span class="text-sm font-medium text-blue-700" id="progressPercent">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="progressBar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Translation Results --> | |
| <div id="resultsSection" class="hidden bg-white rounded-xl shadow-lg overflow-hidden mt-8"> | |
| <div class="p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
| <i class="fas fa-list-check mr-2 text-blue-500"></i> Résultats de la traduction | |
| </h2> | |
| <div class="overflow-x-auto"> | |
| <table id="resultsTable" class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | |
| <i class="fas fa-file-alt mr-1"></i> Page | |
| </th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | |
| <i class="fas fa-font mr-1"></i> Texte original | |
| </th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> | |
| <i class="fas fa-exchange-alt mr-1"></i> Traduction | |
| </th> | |
| </tr> | |
| </thead> | |
| <tbody id="resultsBody" class="bg-white divide-y divide-gray-200"> | |
| <!-- Results will be inserted here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="mt-12 text-center text-gray-500 text-sm"> | |
| <p>© 2023 NaweliMed Pro - Solution professionnelle de traduction médicale</p> | |
| <p class="mt-1">Développé avec <i class="fas fa-heart text-red-500"></i> pour les professionnels de santé</p> | |
| </footer> | |
| </div> | |
| <script> | |
| // Initialize PDF.js worker | |
| pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js'; | |
| // DOM Elements | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileInfo = document.getElementById('fileInfo'); | |
| const fileName = document.getElementById('fileName'); | |
| const fileSize = document.getElementById('fileSize'); | |
| const removeFile = document.getElementById('removeFile'); | |
| const translateBtn = document.getElementById('translateBtn'); | |
| const exportBtn = document.getElementById('exportBtn'); | |
| const pdfPreviewSection = document.getElementById('pdfPreviewSection'); | |
| const pdfViewer = document.getElementById('pdfViewer'); | |
| const pageCount = document.getElementById('pageCount'); | |
| const currentPage = document.getElementById('currentPage'); | |
| const prevPage = document.getElementById('prevPage'); | |
| const nextPage = document.getElementById('nextPage'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| const resultsSection = document.getElementById('resultsSection'); | |
| const resultsBody = document.getElementById('resultsBody'); | |
| const sourceLang = document.getElementById('sourceLang'); | |
| const targetLang = document.getElementById('targetLang'); | |
| // State variables | |
| let pdfDoc = null; | |
| let currentPageNum = 1; | |
| let pdfTextContent = []; | |
| let translatedContent = []; | |
| let isTranslating = false; | |
| // Event Listeners | |
| dropzone.addEventListener('click', () => fileInput.click()); | |
| dropzone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropzone.classList.add('active'); | |
| }); | |
| dropzone.addEventListener('dragleave', () => { | |
| dropzone.classList.remove('active'); | |
| }); | |
| dropzone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropzone.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFileUpload(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', () => { | |
| if (fileInput.files.length) { | |
| handleFileUpload(fileInput.files[0]); | |
| } | |
| }); | |
| removeFile.addEventListener('click', resetFileInput); | |
| prevPage.addEventListener('click', () => { | |
| if (currentPageNum > 1) { | |
| currentPageNum--; | |
| renderPage(); | |
| } | |
| }); | |
| nextPage.addEventListener('click', () => { | |
| if (currentPageNum < pdfDoc.numPages) { | |
| currentPageNum++; | |
| renderPage(); | |
| } | |
| }); | |
| translateBtn.addEventListener('click', translateDocument); | |
| exportBtn.addEventListener('click', exportToExcel); | |
| // Functions | |
| function handleFileUpload(file) { | |
| if (file.type !== 'application/pdf') { | |
| alert('Veuillez sélectionner un fichier PDF valide.'); | |
| return; | |
| } | |
| if (file.size > 10 * 1024 * 1024) { | |
| alert('Le fichier est trop volumineux. Taille maximale: 10MB'); | |
| return; | |
| } | |
| // Display file info | |
| fileName.textContent = file.name; | |
| fileSize.textContent = `${(file.size / 1024 / 1024).toFixed(2)} MB`; | |
| fileInfo.classList.remove('hidden'); | |
| // Enable translate button | |
| translateBtn.disabled = false; | |
| // Load PDF for preview | |
| const fileReader = new FileReader(); | |
| fileReader.onload = function() { | |
| const typedArray = new Uint8Array(this.result); | |
| loadPdf(typedArray); | |
| }; | |
| fileReader.readAsArrayBuffer(file); | |
| } | |
| function loadPdf(data) { | |
| pdfjsLib.getDocument(data).promise.then(function(pdf) { | |
| pdfDoc = pdf; | |
| pageCount.textContent = pdf.numPages; | |
| currentPageNum = 1; | |
| renderPage(); | |
| pdfPreviewSection.classList.remove('hidden'); | |
| // Disable prev button initially | |
| prevPage.disabled = true; | |
| nextPage.disabled = pdf.numPages <= 1; | |
| }).catch(function(error) { | |
| console.error('Error loading PDF:', error); | |
| alert('Erreur lors du chargement du PDF. Veuillez réessayer.'); | |
| }); | |
| } | |
| function renderPage() { | |
| pdfDoc.getPage(currentPageNum).then(function(page) { | |
| const viewport = page.getViewport({ scale: 1.0 }); | |
| const canvas = document.createElement('canvas'); | |
| const context = canvas.getContext('2d'); | |
| canvas.height = viewport.height; | |
| canvas.width = viewport.width; | |
| // Clear previous content | |
| pdfViewer.innerHTML = ''; | |
| pdfViewer.appendChild(canvas); | |
| // Render PDF page | |
| page.render({ | |
| canvasContext: context, | |
| viewport: viewport | |
| }); | |
| // Update page controls | |
| currentPage.textContent = currentPageNum; | |
| prevPage.disabled = currentPageNum <= 1; | |
| nextPage.disabled = currentPageNum >= pdfDoc.numPages; | |
| // Extract text content | |
| page.getTextContent().then(function(textContent) { | |
| const textItems = textContent.items.map(item => item.str).join(' '); | |
| if (!pdfTextContent[currentPageNum - 1]) { | |
| pdfTextContent[currentPageNum - 1] = textItems; | |
| } | |
| }); | |
| }); | |
| } | |
| function resetFileInput() { | |
| fileInput.value = ''; | |
| fileInfo.classList.add('hidden'); | |
| pdfPreviewSection.classList.add('hidden'); | |
| resultsSection.classList.add('hidden'); | |
| translateBtn.disabled = true; | |
| exportBtn.disabled = true; | |
| pdfDoc = null; | |
| pdfTextContent = []; | |
| translatedContent = []; | |
| } | |
| function translateDocument() { | |
| if (isTranslating) return; | |
| const source = sourceLang.value; | |
| const target = targetLang.value; | |
| if (source === target) { | |
| alert('Les langues source et cible doivent être différentes.'); | |
| return; | |
| } | |
| isTranslating = true; | |
| translateBtn.disabled = true; | |
| progressContainer.classList.remove('hidden'); | |
| progressBar.style.width = '0%'; | |
| progressPercent.textContent = '0%'; | |
| // Simulate translation progress (in a real app, this would be API calls) | |
| let progress = 0; | |
| translatedContent = []; | |
| const totalPages = pdfDoc.numPages; | |
| const progressInterval = setInterval(() => { | |
| progress += Math.random() * 10; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(progressInterval); | |
| // Simulate translation results | |
| for (let i = 0; i < pdfTextContent.length; i++) { | |
| const originalText = pdfTextContent[i] || `Contenu de la page ${i + 1}`; | |
| let translatedText = ''; | |
| // Simple mock translation based on language pairs | |
| if (source === 'fr' && target === 'en') { | |
| translatedText = `[English translation of: ${originalText.substring(0, 30)}...]`; | |
| } else if (source === 'fr' && target === 'nl') { | |
| translatedText = `[Dutch translation of: ${originalText.substring(0, 30)}...]`; | |
| } else if (source === 'en' && target === 'fr') { | |
| translatedText = `[Traduction française de: ${originalText.substring(0, 30)}...]`; | |
| } else if (source === 'en' && target === 'nl') { | |
| translatedText = `[Nederlandse vertaling van: ${originalText.substring(0, 30)}...]`; | |
| } else if (source === 'nl' && target === 'fr') { | |
| translatedText = `[Traduction française de: ${originalText.substring(0, 30)}...]`; | |
| } else if (source === 'nl' && target === 'en') { | |
| translatedText = `[English translation of: ${originalText.substring(0, 30)}...]`; | |
| } | |
| translatedContent.push({ | |
| page: i + 1, | |
| original: originalText, | |
| translation: translatedText | |
| }); | |
| } | |
| displayResults(); | |
| isTranslating = false; | |
| exportBtn.disabled = false; | |
| } | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${Math.round(progress)}%`; | |
| }, 300); | |
| } | |
| function displayResults() { | |
| resultsBody.innerHTML = ''; | |
| translatedContent.forEach(item => { | |
| const row = document.createElement('tr'); | |
| const pageCell = document.createElement('td'); | |
| pageCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500'; | |
| pageCell.textContent = item.page; | |
| const originalCell = document.createElement('td'); | |
| originalCell.className = 'px-6 py-4 text-sm text-gray-900'; | |
| originalCell.textContent = item.original.length > 100 | |
| ? item.original.substring(0, 100) + '...' | |
| : item.original; | |
| const translationCell = document.createElement('td'); | |
| translationCell.className = 'px-6 py-4 text-sm text-gray-900'; | |
| translationCell.textContent = item.translation.length > 100 | |
| ? item.translation.substring(0, 100) + '...' | |
| : item.translation; | |
| row.appendChild(pageCell); | |
| row.appendChild(originalCell); | |
| row.appendChild(translationCell); | |
| resultsBody.appendChild(row); | |
| }); | |
| resultsSection.classList.remove('hidden'); | |
| } | |
| function exportToExcel() { | |
| if (translatedContent.length === 0) { | |
| alert('Aucune donnée à exporter. Veuillez d\'abord traduire le document.'); | |
| return; | |
| } | |
| // Prepare worksheet | |
| const wsData = [ | |
| ['Page', 'Texte original', 'Traduction'], | |
| ...translatedContent.map(item => [item.page, item.original, item.translation]) | |
| ]; | |
| const ws = XLSX.utils.aoa_to_sheet(wsData); | |
| // Create workbook | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, 'Traduction'); | |
| // Generate file name | |
| const fileName = `NaweliMed_Pro_traduction_${sourceLang.value}_to_${targetLang.value}_${new Date().toISOString().slice(0, 10)}.xlsx`; | |
| // Export | |
| XLSX.writeFile(wb, fileName); | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Dannylova31/hellotra" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |