Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Système de Classification Textuelle</title> | |
| <script src="https://cdn.tailwindcss.com"></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 #cbd5e0; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #4f46e5; | |
| background-color: #eef2ff; | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| .category-chip { | |
| transition: all 0.2s ease; | |
| } | |
| .category-chip:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-3xl font-bold text-indigo-700 mb-2"> | |
| <i class="fas fa-project-diagram mr-2"></i>Système de Classification Textuelle | |
| </h1> | |
| <p class="text-gray-600 max-w-2xl mx-auto"> | |
| Un outil simple et transparent pour classifier automatiquement vos données textuelles | |
| </p> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <!-- Process Steps --> | |
| <div class="flex justify-between px-6 py-4 border-b"> | |
| <div class="step flex-1 text-center relative" data-step="1"> | |
| <div class="w-10 h-10 mx-auto rounded-full bg-indigo-600 text-white flex items-center justify-center font-bold mb-2"> | |
| 1 | |
| </div> | |
| <p class="text-sm font-medium text-indigo-600">Import des données</p> | |
| <div class="absolute bottom-0 left-0 right-0 h-1 bg-indigo-600"></div> | |
| </div> | |
| <div class="step flex-1 text-center relative" data-step="2"> | |
| <div class="w-10 h-10 mx-auto rounded-full bg-gray-200 text-gray-600 flex items-center justify-center font-bold mb-2"> | |
| 2 | |
| </div> | |
| <p class="text-sm font-medium text-gray-500">Configuration</p> | |
| <div class="absolute bottom-0 left-0 right-0 h-1 bg-gray-200"></div> | |
| </div> | |
| <div class="step flex-1 text-center relative" data-step="3"> | |
| <div class="w-10 h-10 mx-auto rounded-full bg-gray-200 text-gray-600 flex items-center justify-center font-bold mb-2"> | |
| 3 | |
| </div> | |
| <p class="text-sm font-medium text-gray-500">Classification</p> | |
| <div class="absolute bottom-0 left-0 right-0 h-1 bg-gray-200"></div> | |
| </div> | |
| <div class="step flex-1 text-center relative" data-step="4"> | |
| <div class="w-10 h-10 mx-auto rounded-full bg-gray-200 text-gray-600 flex items-center justify-center font-bold mb-2"> | |
| 4 | |
| </div> | |
| <p class="text-sm font-medium text-gray-500">Résultats</p> | |
| <div class="absolute bottom-0 left-0 right-0 h-1 bg-gray-200"></div> | |
| </div> | |
| </div> | |
| <!-- Step 1 Content --> | |
| <div id="step-1" class="p-6 fade-in"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800"> | |
| <i class="fas fa-file-import mr-2 text-indigo-500"></i>Importez vos données | |
| </h2> | |
| <div class="mb-6"> | |
| <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer"> | |
| <div class="mx-auto w-16 h-16 bg-indigo-100 rounded-full flex items-center justify-center mb-4"> | |
| <i class="fas fa-cloud-upload-alt text-indigo-500 text-2xl"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-700 mb-1">Glissez-déposez votre fichier</h3> | |
| <p class="text-gray-500 mb-4">ou cliquez pour sélectionner</p> | |
| <input type="file" id="fileInput" class="hidden" accept=".csv,.xlsx,.xls"> | |
| <button id="selectFileBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition"> | |
| <i class="fas fa-folder-open mr-2"></i>Sélectionner un fichier | |
| </button> | |
| <p class="text-xs text-gray-400 mt-3">Formats supportés: CSV, XLSX, XLS</p> | |
| </div> | |
| </div> | |
| <div id="filePreview" class="hidden border rounded-lg overflow-hidden"> | |
| <div class="bg-gray-50 px-4 py-3 border-b flex justify-between items-center"> | |
| <div> | |
| <span id="fileName" class="font-medium"></span> | |
| <span id="fileSize" class="text-sm text-gray-500 ml-2"></span> | |
| </div> | |
| <button id="removeFileBtn" class="text-red-500 hover:text-red-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-100"> | |
| <tr> | |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Colonnes détectées</th> | |
| </tr> | |
| </thead> | |
| <tbody id="columnsList" class="bg-white divide-y divide-gray-200"> | |
| <!-- Columns will be added here by JS --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="flex justify-end mt-6"> | |
| <button id="nextStep1" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| Suivant <i class="fas fa-arrow-right ml-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Step 2 Content --> | |
| <div id="step-2" class="hidden p-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800"> | |
| <i class="fas fa-cog mr-2 text-indigo-500"></i>Configurez la classification | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Colonne à classifier</label> | |
| <select id="textColumnSelect" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
| <option value="">Sélectionnez une colonne</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Méthode de classification</label> | |
| <select id="classificationMethod" class="w-full border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
| <option value="keywords">Mots-clés</option> | |
| <option value="ai">IA (recommandé)</option> | |
| <option value="regex">Expressions régulières</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div id="keywordsConfig" class="mb-6"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <label class="block text-sm font-medium text-gray-700">Catégories et mots-clés</label> | |
| <button id="addCategoryBtn" class="text-sm text-indigo-600 hover:text-indigo-800 flex items-center"> | |
| <i class="fas fa-plus-circle mr-1"></i> Ajouter une catégorie | |
| </button> | |
| </div> | |
| <div id="categoriesContainer" class="space-y-3"> | |
| <div class="category-group border rounded-lg p-4 bg-gray-50"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <input type="text" placeholder="Nom de la catégorie" class="category-name flex-1 border-b border-gray-300 focus:border-indigo-500 focus:outline-none bg-transparent py-1"> | |
| <button class="remove-category text-red-500 hover:text-red-700 ml-2"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| <div class="flex flex-wrap gap-2"> | |
| <div class="keyword-chip flex items-center bg-white px-3 py-1 rounded-full text-sm shadow-sm"> | |
| <span>exemple</span> | |
| <button class="remove-keyword ml-1 text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-times text-xs"></i> | |
| </button> | |
| </div> | |
| <input type="text" placeholder="Ajouter un mot-clé" class="keyword-input flex-1 min-w-[100px] border-b border-gray-300 focus:border-indigo-500 focus:outline-none bg-transparent py-1"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="aiConfig" class="hidden mb-6"> | |
| <div class="bg-blue-50 border border-blue-200 rounded-lg p-4"> | |
| <div class="flex"> | |
| <div class="flex-shrink-0"> | |
| <i class="fas fa-robot text-blue-500 text-xl"></i> | |
| </div> | |
| <div class="ml-3"> | |
| <h3 class="text-sm font-medium text-blue-800">Classification par IA</h3> | |
| <div class="mt-2 text-sm text-blue-700"> | |
| <p>Notre système d'IA analysera automatiquement le contenu textuel et proposera des catégories pertinentes. Vous pourrez ajuster les résultats après la classification initiale.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="regexConfig" class="hidden mb-6"> | |
| <div class="bg-purple-50 border border-purple-200 rounded-lg p-4"> | |
| <div class="flex"> | |
| <div class="flex-shrink-0"> | |
| <i class="fas fa-code text-purple-500 text-xl"></i> | |
| </div> | |
| <div class="ml-3"> | |
| <h3 class="text-sm font-medium text-purple-800">Expressions régulières</h3> | |
| <div class="mt-2 text-sm text-purple-700"> | |
| <p>Définissez des motifs complexes pour classifier vos données. Requiert une connaissance des expressions régulières.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4"> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Ajouter une règle Regex</label> | |
| <div class="flex space-x-2"> | |
| <input type="text" placeholder="Nom de la catégorie" class="flex-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
| <input type="text" placeholder="Expression régulière" class="flex-1 border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> | |
| <button class="px-3 py-1 bg-indigo-600 text-white rounded-md hover:bg-indigo-700"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-between mt-6"> | |
| <button id="prevStep2" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition"> | |
| <i class="fas fa-arrow-left mr-2"></i> Précédent | |
| </button> | |
| <button id="nextStep2" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| Suivant <i class="fas fa-arrow-right ml-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Step 3 Content --> | |
| <div id="step-3" class="hidden p-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800"> | |
| <i class="fas fa-tasks mr-2 text-indigo-500"></i>Classification en cours | |
| </h2> | |
| <div class="bg-gray-50 rounded-lg p-6 text-center"> | |
| <div class="w-24 h-24 mx-auto mb-6 relative"> | |
| <div class="absolute inset-0 flex items-center justify-center"> | |
| <i class="fas fa-cog text-indigo-400 text-4xl animate-spin"></i> | |
| </div> | |
| <svg class="w-full h-full" viewBox="0 0 100 100"> | |
| <circle cx="50" cy="50" r="45" fill="none" stroke="#e5e7eb" stroke-width="10"/> | |
| <circle id="progressCircle" cx="50" cy="50" r="45" fill="none" stroke="#4f46e5" stroke-width="10" stroke-dasharray="283" stroke-dashoffset="283" stroke-linecap="round" transform="rotate(-90 50 50)"/> | |
| </svg> | |
| </div> | |
| <h3 id="progressText" class="text-lg font-medium text-gray-700 mb-2">Préparation de la classification...</h3> | |
| <p id="progressSubtext" class="text-gray-500 mb-4">Veuillez patienter pendant le traitement de vos données</p> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5 mb-6"> | |
| <div id="progressBar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <div id="progressDetails" class="hidden text-left bg-white p-4 rounded-lg border mb-4"> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm font-medium">Lignes traitées</span> | |
| <span id="processedRows" class="text-sm">0/0</span> | |
| </div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm font-medium">Catégories identifiées</span> | |
| <span id="identifiedCategories" class="text-sm">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-sm font-medium">Confiance moyenne</span> | |
| <span id="averageConfidence" class="text-sm">0%</span> | |
| </div> | |
| </div> | |
| <button id="cancelProcessBtn" class="px-4 py-2 bg-red-100 text-red-600 rounded-md hover:bg-red-200 transition"> | |
| <i class="fas fa-stop-circle mr-2"></i>Annuler | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Step 4 Content --> | |
| <div id="step-4" class="hidden p-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800"> | |
| <i class="fas fa-chart-bar mr-2 text-indigo-500"></i>Résultats de la classification | |
| </h2> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6"> | |
| <div class="bg-white border rounded-lg p-4 shadow-sm"> | |
| <h3 class="text-sm font-medium text-gray-500 mb-2">Résumé</h3> | |
| <div class="space-y-3"> | |
| <div> | |
| <p class="text-xs text-gray-400">Fichier analysé</p> | |
| <p id="resultFileName" class="font-medium">donnees.csv</p> | |
| </div> | |
| <div> | |
| <p class="text-xs text-gray-400">Lignes classifiées</p> | |
| <p id="resultTotalRows" class="font-medium">1,245</p> | |
| </div> | |
| <div> | |
| <p class="text-xs text-gray-400">Catégories identifiées</p> | |
| <p id="resultTotalCategories" class="font-medium">8</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white border rounded-lg p-4 shadow-sm"> | |
| <h3 class="text-sm font-medium text-gray-500 mb-2">Distribution des catégories</h3> | |
| <div id="categoryDistributionChart" class="h-40"> | |
| <!-- Chart will be rendered here --> | |
| <div class="flex items-center justify-center h-full text-gray-400"> | |
| <i class="fas fa-chart-pie text-3xl mr-2"></i> | |
| <span>Graphique de distribution</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white border rounded-lg p-4 shadow-sm"> | |
| <h3 class="text-sm font-medium text-gray-500 mb-2">Actions</h3> | |
| <div class="space-y-2"> | |
| <button id="exportResultsBtn" class="w-full flex items-center justify-between px-3 py-2 bg-indigo-50 text-indigo-600 rounded hover:bg-indigo-100"> | |
| <span><i class="fas fa-file-export mr-2"></i>Exporter les résultats</span> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| <button id="adjustCategoriesBtn" class="w-full flex items-center justify-between px-3 py-2 bg-blue-50 text-blue-600 rounded hover:bg-blue-100"> | |
| <span><i class="fas fa-edit mr-2"></i>Ajuster les catégories</span> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| <button id="newClassificationBtn" class="w-full flex items-center justify-between px-3 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200"> | |
| <span><i class="fas fa-redo mr-2"></i>Nouvelle classification</span> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white border rounded-lg overflow-hidden mb-6"> | |
| <div class="bg-gray-50 px-4 py-3 border-b flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <span class="font-medium">Résultats détaillés</span> | |
| <span class="ml-2 text-xs bg-indigo-100 text-indigo-800 px-2 py-1 rounded-full">1,245 éléments</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <div class="relative"> | |
| <input type="text" placeholder="Rechercher..." class="pl-8 pr-3 py-1 border rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500"> | |
| <i class="fas fa-search absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400"></i> | |
| </div> | |
| <select class="border rounded-md text-sm focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option>Trier par catégorie</option> | |
| <option>Trier par confiance</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-100"> | |
| <tr> | |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Texte original</th> | |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Catégorie</th> | |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Confiance</th> | |
| <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody id="resultsTable" class="bg-white divide-y divide-gray-200"> | |
| <!-- Sample row --> | |
| <tr> | |
| <td class="px-4 py-3 text-sm max-w-xs truncate">Ceci est un exemple de texte qui a été classifié dans la catégorie "Exemple"</td> | |
| <td class="px-4 py-3"> | |
| <span class="px-2 py-1 bg-indigo-100 text-indigo-800 text-xs rounded-full">Exemple</span> | |
| </td> | |
| <td class="px-4 py-3"> | |
| <div class="flex items-center"> | |
| <div class="w-16 bg-gray-200 rounded-full h-1.5 mr-2"> | |
| <div class="bg-green-500 h-1.5 rounded-full" style="width: 95%"></div> | |
| </div> | |
| <span class="text-xs">95%</span> | |
| </div> | |
| </td> | |
| <td class="px-4 py-3 text-sm"> | |
| <button class="text-indigo-600 hover:text-indigo-900 mr-2"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="text-red-600 hover:text-red-900"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </td> | |
| </tr> | |
| <!-- More rows will be added here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="bg-gray-50 px-4 py-3 border-t flex items-center justify-between"> | |
| <div class="text-sm text-gray-500"> | |
| Affichage de <span class="font-medium">1</span> à <span class="font-medium">10</span> sur <span class="font-medium">1,245</span> résultats | |
| </div> | |
| <div class="flex space-x-1"> | |
| <button class="px-3 py-1 border rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"> | |
| Précédent | |
| </button> | |
| <button class="px-3 py-1 border rounded-md text-sm font-medium bg-indigo-600 text-white"> | |
| 1 | |
| </button> | |
| <button class="px-3 py-1 border rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"> | |
| 2 | |
| </button> | |
| <button class="px-3 py-1 border rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"> | |
| Suivant | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-end mt-6"> | |
| <button id="finishBtn" class="px-6 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition"> | |
| Terminer <i class="fas fa-check ml-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Help Section --> | |
| <div class="mt-8 bg-indigo-50 rounded-xl p-6"> | |
| <div class="flex items-start"> | |
| <div class="flex-shrink-0"> | |
| <i class="fas fa-question-circle text-indigo-500 text-2xl"></i> | |
| </div> | |
| <div class="ml-4"> | |
| <h3 class="text-lg font-medium text-indigo-800">Besoin d'aide ?</h3> | |
| <p class="mt-1 text-sm text-indigo-700"> | |
| Consultez notre <a href="#" class="font-medium underline">guide d'utilisation</a> ou contactez notre <a href="#" class="font-medium underline">support technique</a> pour toute question sur le processus de classification. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const steps = document.querySelectorAll('.step'); | |
| const stepContents = { | |
| 1: document.getElementById('step-1'), | |
| 2: document.getElementById('step-2'), | |
| 3: document.getElementById('step-3'), | |
| 4: document.getElementById('step-4') | |
| }; | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const selectFileBtn = document.getElementById('selectFileBtn'); | |
| const filePreview = document.getElementById('filePreview'); | |
| const fileName = document.getElementById('fileName'); | |
| const fileSize = document.getElementById('fileSize'); | |
| const columnsList = document.getElementById('columnsList'); | |
| const removeFileBtn = document.getElementById('removeFileBtn'); | |
| const nextStep1 = document.getElementById('nextStep1'); | |
| const prevStep2 = document.getElementById('prevStep2'); | |
| const nextStep2 = document.getElementById('nextStep2'); | |
| const textColumnSelect = document.getElementById('textColumnSelect'); | |
| const classificationMethod = document.getElementById('classificationMethod'); | |
| const keywordsConfig = document.getElementById('keywordsConfig'); | |
| const aiConfig = document.getElementById('aiConfig'); | |
| const regexConfig = document.getElementById('regexConfig'); | |
| const categoriesContainer = document.getElementById('categoriesContainer'); | |
| const addCategoryBtn = document.getElementById('addCategoryBtn'); | |
| const progressText = document.getElementById('progressText'); | |
| const progressSubtext = document.getElementById('progressSubtext'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressCircle = document.getElementById('progressCircle'); | |
| const progressDetails = document.getElementById('progressDetails'); | |
| const cancelProcessBtn = document.getElementById('cancelProcessBtn'); | |
| // Variables | |
| let currentStep = 1; | |
| let file = null; | |
| // Initialize | |
| updateStepIndicator(); | |
| // Event Listeners | |
| selectFileBtn.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFileSelect); | |
| removeFileBtn.addEventListener('click', () => { | |
| file = null; | |
| filePreview.classList.add('hidden'); | |
| nextStep1.disabled = true; | |
| fileInput.value = ''; | |
| }); | |
| 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; | |
| handleFileSelect({ target: fileInput }); | |
| } | |
| }); | |
| nextStep1.addEventListener('click', () => navigateToStep(2)); | |
| prevStep2.addEventListener('click', () => navigateToStep(1)); | |
| nextStep2.addEventListener('click', () => navigateToStep(3)); | |
| classificationMethod.addEventListener('change', updateClassificationMethodUI); | |
| addCategoryBtn.addEventListener('click', addNewCategory); | |
| // Delegated event listeners for dynamic elements | |
| categoriesContainer.addEventListener('click', (e) => { | |
| // Remove category | |
| if (e.target.closest('.remove-category')) { | |
| e.target.closest('.category-group').remove(); | |
| checkStep2Completion(); | |
| } | |
| // Remove keyword | |
| if (e.target.closest('.remove-keyword')) { | |
| e.target.closest('.keyword-chip').remove(); | |
| } | |
| }); | |
| categoriesContainer.addEventListener('keydown', (e) => { | |
| // Add keyword on Enter | |
| if (e.target.classList.contains('keyword-input') && e.key === 'Enter' && e.target.value.trim()) { | |
| const keyword = e.target.value.trim(); | |
| const keywordChip = document.createElement('div'); | |
| keywordChip.className = 'keyword-chip flex items-center bg-white px-3 py-1 rounded-full text-sm shadow-sm'; | |
| keywordChip.innerHTML = ` | |
| <span>${keyword}</span> | |
| <button class="remove-keyword ml-1 text-gray-400 hover:text-red-500"> | |
| <i class="fas fa-times text-xs"></i> | |
| </button> | |
| `; | |
| e.target.before(keywordChip); | |
| e.target.value = ''; | |
| checkStep2Completion(); | |
| } | |
| // Check completion on category name change | |
| if (e.target.classList.contains('category-name')) { | |
| setTimeout(checkStep2Completion, 0); | |
| } | |
| }); | |
| // Functions | |
| function handleFileSelect(event) { | |
| file = event.target.files[0]; | |
| if (!file) return; | |
| fileName.textContent = file.name; | |
| fileSize.textContent = formatFileSize(file.size); | |
| // Simulate reading file columns (in a real app, you'd parse the file) | |
| const simulatedColumns = ['ID', 'Texte', 'Date', 'Auteur', 'Référence']; | |
| columnsList.innerHTML = simulatedColumns.map(col => ` | |
| <tr> | |
| <td class="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">${col}</td> | |
| </tr> | |
| `).join(''); | |
| filePreview.classList.remove('hidden'); | |
| nextStep1.disabled = false; | |
| // Populate column select for step 2 | |
| textColumnSelect.innerHTML = '<option value="">Sélectionnez une colonne</option>' + | |
| simulatedColumns.map(col => `<option value="${col}">${col}</option>`).join(''); | |
| } | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| function navigateToStep(step) { | |
| // Validation before leaving current step | |
| if (currentStep === 2 && step === 3 && !validateStep2()) { | |
| return; | |
| } | |
| // Special handling for step 3 (processing) | |
| if (step === 3) { | |
| startProcessing(); | |
| } | |
| // Hide current step | |
| stepContents[currentStep].classList.add('hidden'); | |
| // Show new step | |
| stepContents[step].classList.remove('hidden'); | |
| stepContents[step].classList.add('fade-in'); | |
| // Update current step | |
| currentStep = step; | |
| updateStepIndicator(); | |
| } | |
| function updateStepIndicator() { | |
| steps.forEach(step => { | |
| const stepNum = parseInt(step.dataset.step); | |
| const circle = step.querySelector('div'); | |
| const text = step.querySelector('p'); | |
| const line = step.querySelector('.absolute'); | |
| if (stepNum < currentStep) { | |
| // Completed step | |
| circle.classList.remove('bg-gray-200', 'text-gray-600'); | |
| circle.classList.add('bg-indigo-600', 'text-white'); | |
| text.classList.remove('text-gray-500'); | |
| text.classList.add('text-indigo-600'); | |
| line.classList.remove('bg-gray-200'); | |
| line.classList.add('bg-indigo-600'); | |
| } else if (stepNum === currentStep) { | |
| // Current step | |
| circle.classList.remove('bg-gray-200', 'text-gray-600'); | |
| circle.classList.add('bg-indigo-600', 'text-white'); | |
| text.classList.remove('text-gray-500'); | |
| text.classList.add('text-indigo-600'); | |
| line.classList.remove('bg-indigo-600'); | |
| line.classList.add('bg-gray-200'); | |
| } else { | |
| // Future step | |
| circle.classList.remove('bg-indigo-600', 'text-white'); | |
| circle.classList.add('bg-gray-200', 'text-gray-600'); | |
| text.classList.remove('text-indigo-600'); | |
| text.classList.add('text-gray-500'); | |
| line.classList.remove('bg-indigo-600'); | |
| line.classList.add('bg-gray-200'); | |
| } | |
| }); | |
| } | |
| function updateClassificationMethodUI() { | |
| const method = classificationMethod.value; | |
| keywordsConfig.classList.add('hidden'); | |
| aiConfig.classList.add('hidden'); | |
| regexConfig.classList.add('hidden'); | |
| if (method === 'keywords') { | |
| keywordsConfig.classList.remove('hidden'); | |
| } else if (method === 'ai') { | |
| aiConfig.classList.remove('hidden'); | |
| } else if (method === 'regex') { | |
| regexConfig.classList.remove('hidden'); | |
| } | |
| checkStep2Completion(); | |
| } | |
| function addNewCategory() { | |
| const categoryGroup = document.createElement('div'); | |
| categoryGroup.className = 'category-group border rounded-lg p-4 bg-gray-50'; | |
| categoryGroup.innerHTML = ` | |
| <div class="flex justify-between items-center mb-3"> | |
| <input type="text" placeholder="Nom de la catégorie" class="category-name flex-1 border-b border-gray-300 focus:border-indigo-500 focus:outline-none bg-transparent py-1"> | |
| <button class="remove-category text-red-500 hover:text-red-700 ml-2"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| <div class="flex flex-wrap gap-2"> | |
| <input type="text" placeholder="Ajouter un mot-clé" class="keyword-input flex-1 min-w-[100px] border-b border-gray-300 focus:border-indigo-500 focus:outline-none bg-transparent py-1"> | |
| </div> | |
| `; | |
| categoriesContainer.appendChild(categoryGroup); | |
| // Focus on the new category name input | |
| categoryGroup.querySelector('.category-name').focus(); | |
| } | |
| function checkStep2Completion() { | |
| let isComplete = true; | |
| // Check if text column is selected | |
| if (!textColumnSelect.value) { | |
| isComplete = false; | |
| } | |
| // Check if categories are properly defined (for keyword method) | |
| if (classificationMethod.value === 'keywords') { | |
| const categoryGroups = categoriesContainer.querySelectorAll('.category-group'); | |
| if (categoryGroups.length === 0) { | |
| isComplete = false; | |
| } else { | |
| categoryGroups.forEach(group => { | |
| const name = group.querySelector('.category-name').value.trim(); | |
| const keywords = group.querySelectorAll('.keyword-chip'); | |
| if (!name || keywords.length === 0) { | |
| isComplete = false; | |
| } | |
| }); | |
| } | |
| } | |
| nextStep2.disabled = !isComplete; | |
| } | |
| function validateStep2() { | |
| // In a real app, this would do more thorough validation | |
| return !nextStep2.disabled; | |
| } | |
| function startProcessing() { | |
| progressText.textContent = "Classification en cours..."; | |
| progressSubtext.textContent = "Analyse des données textuelles"; | |
| progressDetails.classList.add('hidden'); | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 5; | |
| if (progress > 100) progress = 100; | |
| progressBar.style.width = `${progress}%`; | |
| progressCircle.style.strokeDashoffset = 283 * (1 - progress/100); | |
| // Update progress details | |
| if (progress > 30 && progress < 70) { | |
| progressText.textContent = "Identification des catégories..."; | |
| progressSubtext.textContent = "Analyse des motifs récurrents"; | |
| } else if (progress >= 70) { | |
| progressText.textContent = "Finalisation des résultats..."; | |
| progressSubtext.textContent = "Préparation des données pour l'affichage"; | |
| progressDetails.classList.remove('hidden'); | |
| document.getElementById('processedRows').textContent = | |
| `${Math.floor(progress * 12.45)}/1245`; | |
| document.getElementById('identifiedCategories').textContent = | |
| `${Math.floor(progress / 15)}`; | |
| document.getElementById('averageConfidence').textContent = | |
| `${Math.min(95, Math.floor(70 + progress / 3))}%`; | |
| } | |
| if (progress === 100) { | |
| clearInterval(interval); | |
| setTimeout(() => { | |
| navigateToStep(4); | |
| }, 1000); | |
| } | |
| }, 300); | |
| cancelProcessBtn.addEventListener('click', () => { | |
| clearInterval(interval); | |
| navigateToStep(2); | |
| }, { once: true }); | |
| } | |
| // Initialize step 2 validation | |
| textColumnSelect.addEventListener('change', checkStep2Completion); | |
| }); | |
| </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=simondh/classifications" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |