Spaces:
Sleeping
Sleeping
| // ==================================== Variables globales ======================================== | |
| let requirements = []; | |
| // Filtres | |
| let selectedType = ""; // "" = Tous | |
| let selectedStatus = new Set(); // valeurs cochées (hors "Tous") | |
| let selectedAgenda = new Set(); | |
| // Generation de solutions | |
| let accordionStates = {}; | |
| let formattedRequirements = []; | |
| let categorizedRequirements = []; | |
| let solutionsCriticizedVersions = []; | |
| // checksum pour vérifier si les requirements séléctionnés ont changé | |
| let lastSelectedRequirementsChecksum = null; | |
| // les requirements ont ils été extraits au moins une fois ? | |
| let hasRequirementsExtracted = false; | |
| // ============================================================================= | |
| // FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS | |
| // ============================================================================= | |
| /** | |
| * Active/désactive des éléments par leurs IDs | |
| * @param {string[]} elementIds - Liste des IDs des éléments à activer | |
| * @param {boolean} enabled - true pour activer, false pour désactiver | |
| */ | |
| function toggleElementsEnabled(elementIds, enabled = true) { | |
| elementIds.forEach(id => { | |
| const element = document.getElementById(id); | |
| if (element) { | |
| if (enabled) { | |
| element.removeAttribute('disabled'); | |
| } else { | |
| element.setAttribute('disabled', 'true'); | |
| } | |
| } | |
| }); | |
| } | |
| /** | |
| * Affiche/masque des conteneurs par leurs IDs | |
| * @param {string[]} containerIds - Liste des IDs des conteneurs à afficher | |
| * @param {boolean} visible - true pour afficher, false pour masquer | |
| */ | |
| function toggleContainersVisibility(containerIds, visible = true) { | |
| containerIds.forEach(id => { | |
| const container = document.getElementById(id); | |
| if (container) { | |
| if (visible) { | |
| container.classList.remove('hidden'); | |
| } else { | |
| container.classList.add('hidden'); | |
| } | |
| } | |
| }); | |
| } | |
| /** | |
| * Affiche le loading overlay avec un message personnalisé | |
| * @param {string} message - Message à afficher | |
| */ | |
| function showLoadingOverlay(message = 'Chargement en cours...') { | |
| document.getElementById('progress-text').textContent = message; | |
| toggleContainersVisibility(['loading-overlay'], true); | |
| } | |
| /** | |
| * Masque le loading overlay | |
| */ | |
| function hideLoadingOverlay() { | |
| toggleContainersVisibility(['loading-overlay'], false); | |
| } | |
| /** | |
| * Réinitialise un select et ajoute des options | |
| * @param {string} selectId - ID du select | |
| * @param {Object} options - Objet avec les options {value: text} | |
| * @param {string} defaultText - Texte par défaut | |
| */ | |
| function populateSelect(selectId, options, defaultText = 'Sélectionner...') { | |
| const select = document.getElementById(selectId); | |
| if (select) { | |
| select.innerHTML = `<option value="">${defaultText}</option>`; | |
| Object.entries(options).forEach(([text, value]) => { | |
| const option = document.createElement('option'); | |
| option.value = value; | |
| option.textContent = text; | |
| select.appendChild(option); | |
| }); | |
| } | |
| } | |
| function populateCheckboxDropdown(optionsContainerId, options, filterType, labelId, selectionSet) { | |
| const container = document.getElementById(optionsContainerId); | |
| container.innerHTML = ''; | |
| selectionSet.clear(); // reset all | |
| // Ajoute chaque option | |
| options.forEach(option => { | |
| const safeId = `${filterType}-${encodeURIComponent(option).replace(/[%\s]/g, '_')}`; | |
| const label = document.createElement('label'); | |
| label.className = "flex items-center gap-2 cursor-pointer py-1"; | |
| label.innerHTML = ` | |
| <input type="checkbox" class="${filterType}-checkbox option-checkbox" id="${safeId}" value="${option}"> | |
| <span>${option}</span> | |
| `; | |
| label.querySelector('input').addEventListener('change', function () { | |
| if (this.checked) { | |
| selectionSet.add(this.value); | |
| } else { | |
| selectionSet.delete(this.value); | |
| } | |
| // Gestion du label "Tous" | |
| updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); | |
| // Gestion du "Tous" global | |
| const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`); | |
| if (allBox && allBox.checked) allBox.checked = false; | |
| // Si plus rien n'est coché, recoche "Tous" | |
| if (selectionSet.size === 0 && allBox) allBox.checked = true; | |
| applyFilters(); | |
| }); | |
| container.appendChild(label); | |
| }); | |
| // Réinitialise le label | |
| updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); | |
| // Gestion de "Tous" | |
| const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`); | |
| if (allBox) { | |
| allBox.addEventListener('change', function () { | |
| if (this.checked) { | |
| // Décoche tout le reste | |
| selectionSet.clear(); | |
| container.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); | |
| this.checked = true; // reste coché | |
| updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); | |
| applyFilters(); | |
| } | |
| }); | |
| } | |
| } | |
| function updateCheckboxDropdownLabel(type, labelId, set, totalCount) { | |
| const label = document.getElementById(labelId); | |
| if (!set.size) { | |
| label.textContent = type.charAt(0).toUpperCase() + type.slice(1) + " (Tous)"; | |
| } else if (set.size === 1) { | |
| label.textContent = [...set][0]; | |
| } else { | |
| label.textContent = `${type.charAt(0).toUpperCase() + type.slice(1)} (${set.size}/${totalCount})`; | |
| } | |
| } | |
| function updateSelectedFilters(filterType, value, isChecked) { | |
| if (isChecked) { | |
| selectedFilters[filterType].add(value); | |
| } else { | |
| selectedFilters[filterType].delete(value); | |
| } | |
| } | |
| function populateDaisyDropdown(menuId, options, labelId, onSelect) { | |
| const menu = document.getElementById(menuId); | |
| menu.innerHTML = ''; | |
| // Option "Tous" | |
| const liAll = document.createElement('li'); | |
| liAll.innerHTML = `<a data-value="">Tous</a>`; | |
| liAll.querySelector('a').onclick = e => { | |
| e.preventDefault(); | |
| document.getElementById(labelId).textContent = "Type"; | |
| onSelect(""); | |
| }; | |
| menu.appendChild(liAll); | |
| // Ajoute chaque option | |
| options.forEach(opt => { | |
| const li = document.createElement('li'); | |
| li.innerHTML = `<a data-value="${opt}">${opt}</a>`; | |
| li.querySelector('a').onclick = e => { | |
| e.preventDefault(); | |
| document.getElementById(labelId).textContent = opt; | |
| onSelect(opt); | |
| }; | |
| menu.appendChild(li); | |
| }); | |
| } | |
| function updateFilterLabel(filterType) { | |
| const selectedCount = selectedFilters[filterType].size; | |
| const labelElement = document.getElementById(`${filterType}-filter-label`); | |
| if (selectedCount === 0) { | |
| labelElement.textContent = `${filterType} (Tous)`; | |
| } else { | |
| labelElement.textContent = `${filterType} (${selectedCount} sélectionné${selectedCount > 1 ? 's' : ''})`; | |
| } | |
| } | |
| /** | |
| * Extrait les données du tableau selon un mapping | |
| * @param {Object} mapping - Mapping des colonnes {columnName: propertyName} | |
| * @returns {Array} Données extraites | |
| */ | |
| function extractTableData(mapping) { | |
| const tbody = document.querySelector('#data-table tbody'); | |
| const rows = tbody.querySelectorAll('tr'); | |
| const data = []; | |
| rows.forEach(row => { | |
| const checkboxes = row.querySelectorAll('input[type="checkbox"]:checked'); | |
| if (checkboxes.length > 0) { | |
| const rowData = {}; | |
| Object.entries(mapping).forEach(([columnName, propertyName]) => { | |
| const cell = row.querySelector(`td[data-column="${columnName}"]`); | |
| if (cell) { | |
| if (columnName == "URL") { | |
| rowData[propertyName] = cell.querySelector('a').getAttribute('href'); | |
| } else { | |
| rowData[propertyName] = cell.textContent.trim(); | |
| } | |
| } | |
| }); | |
| data.push(rowData); | |
| } | |
| }); | |
| return data; | |
| } | |
| const TABS = { | |
| 'doc-table-tab': 'doc-table-tab-contents', | |
| 'requirements-tab': 'requirements-tab-contents', | |
| 'solutions-tab': 'solutions-tab-contents', | |
| 'query-tab': 'query-tab-contents' | |
| }; | |
| /** | |
| * Bascule l'affichage sur le nouveau tab | |
| * @param {*} newTab | |
| */ | |
| function switchTab(newTab) { | |
| // Remove active tab style from all tabs | |
| Object.keys(TABS).forEach(tabId => { | |
| const tabElement = document.getElementById(tabId); | |
| if (tabElement) { | |
| tabElement.classList.remove("tab-active"); | |
| } | |
| }); | |
| // Hide all tab contents | |
| Object.values(TABS).forEach(contentId => { | |
| const contentElement = document.getElementById(contentId); | |
| if (contentElement) { | |
| contentElement.classList.add("hidden"); | |
| } | |
| }); | |
| // Activate the new tab if it exists in the mapping | |
| if (newTab in TABS) { | |
| const newTabElement = document.getElementById(newTab); | |
| const newContentElement = document.getElementById(TABS[newTab]); | |
| if (newTabElement) newTabElement.classList.add("tab-active"); | |
| if (newContentElement) newContentElement.classList.remove("hidden"); | |
| } | |
| } | |
| /** | |
| * Bascule l'affichage vers la tab uniquement si les requirements sont | |
| */ | |
| function enableTabSwitching() { | |
| Object.keys(TABS).forEach(tabId => { | |
| const tab = document.getElementById(tabId); | |
| if (tab) | |
| tab.classList.remove("tab-disabled"); | |
| }) | |
| } | |
| /** | |
| * Change l'état d'activation du number box de choix de nb de catégories. | |
| */ | |
| function debounceAutoCategoryCount(state) { | |
| document.getElementById('category-count').disabled = state; | |
| } | |
| // ============================================================================= | |
| // FONCTIONS MÉTIER | |
| // ============================================================================= | |
| /** | |
| * Récupère la liste des meetings pour un working group | |
| */ | |
| async function getMeetings() { | |
| const workingGroup = document.getElementById('working-group-select').value; | |
| if (!workingGroup) return; | |
| showLoadingOverlay('Récupération des meetings...'); | |
| toggleElementsEnabled(['get-meetings-btn'], false); | |
| try { | |
| const response = await fetch('/get_meetings', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ working_group: workingGroup }) | |
| }); | |
| const data = await response.json(); | |
| populateSelect('meeting-select', data.meetings, 'Select a meeting'); | |
| toggleContainersVisibility(['meeting-container'], true); | |
| } catch (error) { | |
| console.error('Erreur lors de la récupération des meetings:', error); | |
| alert('Erreur lors de la récupération des meetings'); | |
| } finally { | |
| hideLoadingOverlay(); | |
| toggleElementsEnabled(['get-meetings-btn'], true); | |
| } | |
| } | |
| /** | |
| * Récupère la liste des TDocs pour un meeting | |
| */ | |
| async function getTDocs() { | |
| const workingGroup = document.getElementById('working-group-select').value; | |
| const meeting = document.getElementById('meeting-select').value; | |
| if (!workingGroup || !meeting) return; | |
| showLoadingOverlay('Récupération de la liste des TDocs...'); | |
| toggleElementsEnabled(['get-tdocs-btn'], false); | |
| try { | |
| const response = await fetch('/get_dataframe', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ working_group: workingGroup, meeting: meeting }) | |
| }); | |
| const data = await response.json(); | |
| populateDataTable(data.data); | |
| setupFilters(data.data); | |
| toggleContainersVisibility([ | |
| 'filters-container', | |
| 'action-buttons-container', | |
| 'doc-table-tab-contents', | |
| // 'data-table-container', | |
| // 'data-table-info-container' | |
| ], true); | |
| hasRequirementsExtracted = false; | |
| } catch (error) { | |
| console.error('Erreur lors de la récupération des TDocs:', error); | |
| alert('Erreur lors de la récupération des TDocs'); | |
| } finally { | |
| hideLoadingOverlay(); | |
| toggleElementsEnabled(['get-tdocs-btn'], true); | |
| } | |
| } | |
| /** | |
| * Remplit le tableau de données | |
| * @param {Array} data - Données à afficher | |
| */ | |
| function populateDataTable(data) { | |
| const tbody = document.querySelector('#data-table tbody'); | |
| tbody.innerHTML = ''; | |
| data.forEach(row => { | |
| const tr = document.createElement('tr'); | |
| tr.setAttribute('data-type', row.Type || ''); | |
| tr.setAttribute('data-status', row['TDoc Status'] || ''); | |
| tr.setAttribute('data-agenda', row['Agenda item description'] || ''); | |
| tr.innerHTML = ` | |
| <td class="px-4 py-2"> | |
| <input type="checkbox" class="row-checkbox"> | |
| </td> | |
| <td class="px-4 py-2" data-column="TDoc">${row.TDoc || ''}</td> | |
| <td class="px-4 py-2" data-column="Title">${row.Title || ''}</td> | |
| <td class="px-4 py-2" data-column="Type">${row.Type || ''}</td> | |
| <td class="px-4 py-2" data-column="Status">${row['TDoc Status'] || ''}</td> | |
| <td class="px-4 py-2" data-column="Agenda">${row['Agenda item description'] || ''}</td> | |
| <td class="px-4 py-2" data-column="URL"> | |
| <a href="${row.URL || '#'}" target="_blank" class="text-blue-500 hover:underline"> | |
| ${row.URL ? 'Lien' : 'N/A'} | |
| </a> | |
| </td> | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| setupTableEvents(); | |
| updateSelectedAndDisplayedCount(); | |
| } | |
| function setupFilters(data) { | |
| // Extrait les valeurs uniques | |
| const types = [...new Set(data.map(item => item.Type).filter(Boolean))]; | |
| const statuses = [...new Set(data.map(item => item['TDoc Status']).filter(Boolean))]; | |
| const agendaItems = [...new Set(data.map(item => item['Agenda item description']).filter(Boolean))]; | |
| // Type (sélection unique DaisyUI) | |
| populateDaisyDropdown('doc-type-filter-menu', types, 'doc-type-filter-label', type => { | |
| selectedType = type; | |
| applyFilters(); | |
| }); | |
| // Status (checkbox multiselect) | |
| populateCheckboxDropdown('status-options', statuses, 'status', 'status-filter-label', selectedStatus); | |
| // Agenda (checkbox multiselect) | |
| populateCheckboxDropdown('agenda-options', agendaItems, 'agenda', 'agenda-filter-label', selectedAgenda); | |
| // Initialisation des labels (optionnel) | |
| document.getElementById('doc-type-filter-label').textContent = 'Type'; | |
| document.getElementById('status-filter-label').textContent = 'Status (Tous)'; | |
| document.getElementById('agenda-filter-label').textContent = 'Agenda Item (Tous)'; | |
| } | |
| /** | |
| * Configure les événements des filtres | |
| */ | |
| function setupFilterEvents() { | |
| ['doc-type-filter', 'doc-status-filter', 'agenda-item-filter'].forEach(filterId => { | |
| document.getElementById(filterId).addEventListener('change', applyFilters); | |
| }); | |
| } | |
| function updateSelectedAndDisplayedCount() { | |
| // Lignes visibles (après filtrage) | |
| const rows = document.querySelectorAll('#data-table tbody tr'); | |
| let displayed = 0, selected = 0; | |
| rows.forEach(row => { | |
| // display: none signifie caché par le filtre | |
| if (row.style.display === '' || row.style.display === undefined) { | |
| displayed++; | |
| const cb = row.querySelector('.row-checkbox'); | |
| if (cb && cb.checked) selected++; | |
| } | |
| }); | |
| document.getElementById('displayed-count').textContent = | |
| `${displayed} document${displayed !== 1 ? 's' : ''} affiché${displayed > 1 ? 's' : ''}`; | |
| document.getElementById('selected-count').textContent = | |
| `${selected} document${selected !== 1 ? 's' : ''} sélectionné${selected > 1 ? 's' : ''}`; | |
| } | |
| /** | |
| * Applique les filtres au tableau | |
| */ | |
| function applyFilters() { | |
| const rows = document.querySelectorAll('#data-table tbody tr'); | |
| rows.forEach(row => { | |
| const typeVal = row.getAttribute('data-type'); | |
| const statusVal = row.getAttribute('data-status'); | |
| const agendaVal = row.getAttribute('data-agenda'); | |
| const typeMatch = !selectedType || typeVal === selectedType; | |
| const statusMatch = !selectedStatus.size || selectedStatus.has(statusVal); | |
| const agendaMatch = !selectedAgenda.size || selectedAgenda.has(agendaVal); | |
| row.style.display = (typeMatch && statusMatch && agendaMatch) ? '' : 'none'; | |
| }); | |
| updateSelectedAndDisplayedCount?.(); | |
| } | |
| /** | |
| * Configure les événements du tableau | |
| */ | |
| function setupTableEvents() { | |
| document.getElementById('select-all-checkbox').addEventListener('change', function () { | |
| const checkboxes = document.querySelectorAll('.row-checkbox'); | |
| checkboxes.forEach(checkbox => { | |
| // Ne coche que les visibles | |
| if (checkbox.closest('tr').style.display === '' || checkbox.closest('tr').style.display === undefined) { | |
| checkbox.checked = this.checked; | |
| } | |
| }); | |
| updateSelectedAndDisplayedCount(); | |
| }); | |
| // Listener sur chaque ligne | |
| const rowCheckboxes = document.querySelectorAll('.row-checkbox'); | |
| rowCheckboxes.forEach(cb => cb.addEventListener('change', updateSelectedAndDisplayedCount)); | |
| // Compteur initial | |
| updateSelectedAndDisplayedCount(); | |
| } | |
| /** | |
| * Télécharge les TDocs sélectionnés | |
| */ | |
| async function downloadTDocs() { | |
| showLoadingOverlay('Téléchargement des TDocs en cours...'); | |
| toggleElementsEnabled(['download-tdocs-btn', 'extract-requirements-btn'], false); | |
| try { | |
| // Extraire les données du tableau avec TDoc et URL | |
| const selectedData = extractTableData({ 'TDoc': 'document', 'URL': 'url' }); | |
| if (selectedData.length === 0) { | |
| alert('Veuillez sélectionner au moins un document'); | |
| return; | |
| } | |
| // Transformer au format requis: [{tdoc_id: url}, ...] | |
| const documents = selectedData.map(obj => obj.document) | |
| const response = await fetch('/download_tdocs', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ documents: documents }) | |
| }); | |
| const blob = await response.blob(); | |
| downloadBlob(blob, generateDownloadFilename()); | |
| } catch (error) { | |
| console.error(error); | |
| alert('Erreur lors du téléchargement des TDocs'); | |
| } finally { | |
| hideLoadingOverlay(); | |
| toggleElementsEnabled(['download-tdocs-btn', 'extract-requirements-btn'], true); | |
| } | |
| } | |
| /** | |
| * Génère un nom de fichier pour le téléchargement | |
| * @returns {string} Nom du fichier | |
| */ | |
| function generateDownloadFilename() { | |
| let filename = document.getElementById('meeting-select').value || 'documents'; | |
| const agendaItems = selectedAgenda; | |
| const docStatuses = selectedStatus | |
| const docType = selectedType; | |
| // empty set means "Tous" is selected | |
| if (agendaItems) { | |
| for (aItem of agendaItems) { | |
| filename += `_${aItem}`; | |
| } | |
| } | |
| // empty set means "Tous" is selected | |
| if (docStatuses) { | |
| for (docStatus of docStatuses) { | |
| filename += `_${docStatus}`; | |
| } | |
| } | |
| // empty means "Tous" | |
| if (docType && docType !== "") { | |
| filename = `${docType}_${filename}`; | |
| } | |
| if (hasRequirementsExtracted) { | |
| filename = `requirements_${filename}`; | |
| } | |
| return `${filename}.zip`; | |
| } | |
| /** | |
| * Télécharge un blob | |
| * @param {Blob} blob - Blob à télécharger | |
| * @param {string} filename - Nom du fichier | |
| */ | |
| function downloadBlob(blob, filename) { | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| window.URL.revokeObjectURL(url); | |
| } | |
| /** | |
| * Extrait les requirements des documents sélectionnés | |
| */ | |
| async function extractRequirements() { | |
| const selectedData = extractTableData({ 'TDoc': 'document', 'URL': 'url' }); | |
| if (selectedData.length === 0) { | |
| alert('Veuillez sélectionner au moins un document'); | |
| return; | |
| } | |
| showLoadingOverlay('Extraction des requirements en cours...'); | |
| toggleElementsEnabled(['extract-requirements-btn'], false); | |
| try { | |
| const response = await postWithSSE('/generate_requirements/sse', { documents: selectedData }, { | |
| onMessage: (msg) => { | |
| console.log("SSE message:"); | |
| console.log(msg); | |
| showLoadingOverlay(`Extraction des requirements en cours... (${msg.processed_docs}/${msg.total_docs})`); | |
| }, | |
| onError: (err) => { | |
| console.error(`Error while fetching requirements: ${err}`); | |
| throw err; | |
| } | |
| }); | |
| // const response = await fetch('/generate_requirements/', { | |
| // method: 'POST', | |
| // headers: { 'Content-Type': 'application/json' }, | |
| // body: req | |
| // }); | |
| const data = response.data; // data in the SSE message contains the requirements response | |
| requirements = data.requirements; | |
| let req_id = 0; | |
| data.requirements.forEach(obj => { | |
| obj.requirements.forEach(req => { | |
| formattedRequirements.push({ | |
| req_id, | |
| "document": obj.document, | |
| "context": obj.context, | |
| "requirement": req | |
| }) | |
| req_id++; | |
| }) | |
| }) | |
| displayRequirements(requirements); | |
| toggleContainersVisibility(['requirements-container', 'query-requirements-container'], true); | |
| toggleContainersVisibility(['categorize-requirements-btn'], true); | |
| // we got some requirements to the other tabs can be enabled | |
| enableTabSwitching(); | |
| // set the number of fetched requirements | |
| document.getElementById('requirements-tab-badge').innerText = requirements.length; | |
| hasRequirementsExtracted = true; | |
| } catch (error) { | |
| console.error('Erreur lors de l\'extraction des requirements:', error); | |
| alert('Erreur lors de l\'extraction des requirements'); | |
| } finally { | |
| hideLoadingOverlay(); | |
| toggleElementsEnabled(['extract-requirements-btn'], true); | |
| } | |
| } | |
| /** | |
| * Affiche les requirements | |
| * @param {Array} requirementsData - Données des requirements | |
| */ | |
| function displayRequirements(requirementsData) { | |
| const container = document.getElementById('requirements-list'); | |
| container.innerHTML = ''; | |
| requirementsData.forEach((docReq, docIndex) => { | |
| const docDiv = document.createElement('div'); | |
| docDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white'; | |
| docDiv.innerHTML = ` | |
| <h3 class="text-lg font-semibold mb-2">${docReq.document}</h3> | |
| <p class="text-gray-600 mb-3">${docReq.context}</p> | |
| <ul class="list-disc list-inside space-y-1"> | |
| ${docReq.requirements.map((req, reqIndex) => | |
| `<li class="text-sm" data-req-id="${docIndex}-${reqIndex}">${req}</li>` | |
| ).join('')} | |
| </ul> | |
| `; | |
| container.appendChild(docDiv); | |
| }); | |
| } | |
| /** | |
| * Catégorise les requirements | |
| */ | |
| async function categorizeRequirements(max_categories) { | |
| if (!formattedRequirements || formattedRequirements.length === 0) { | |
| alert('Aucun requirement à catégoriser'); | |
| return; | |
| } | |
| showLoadingOverlay('Catégorisation des requirements en cours...'); | |
| toggleElementsEnabled(['categorize-requirements-btn'], false); | |
| try { | |
| const response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/reqs/categorize_requirements', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ requirements: formattedRequirements, "max_n_categories": max_categories }) | |
| }); | |
| const data = await response.json(); | |
| categorizedRequirements = data; | |
| displayCategorizedRequirements(categorizedRequirements.categories); | |
| clearAllSolutions(); | |
| // Masquer le container de query et afficher les catégories et boutons solutions | |
| // toggleContainersVisibility(['query-requirements-container'], false); | |
| toggleContainersVisibility(['categorized-requirements-container', 'solutions-action-buttons-container'], true); | |
| } catch (error) { | |
| console.error('Erreur lors de la catégorisation des requirements:', error); | |
| alert('Erreur lors de la catégorisation des requirements'); | |
| } finally { | |
| hideLoadingOverlay(); | |
| toggleElementsEnabled(['categorize-requirements-btn'], true); | |
| } | |
| } | |
| /** | |
| * Affiche les requirements catégorisés | |
| * @param {Array} categorizedData - Données des requirements catégorisés | |
| */ | |
| function displayCategorizedRequirements(categorizedData) { | |
| const container = document.getElementById('categorized-requirements-list'); | |
| container.innerHTML = ''; | |
| categorizedData.forEach((category, categoryIndex) => { | |
| const categoryDiv = document.createElement('div'); | |
| categoryDiv.tabIndex = 0; | |
| categoryDiv.className = 'collapse collapse-arrow mb-2 border border-gray-200 rounded-lg bg-white'; | |
| // Generate unique IDs for checkboxes | |
| const globalCheckboxId = `global-checkbox-${categoryIndex}`; | |
| const requirementsHTML = category.requirements.map((req, reqIndex) => { | |
| const checkboxId = `checkbox-${categoryIndex}-${reqIndex}`; | |
| return ` | |
| <div class="p-2 bg-gray-50 rounded border-l-4 border-blue-400 flex items-start gap-2" data-category-index="${categoryIndex}" data-cat-req-id="${reqIndex}"> | |
| <input type="checkbox" class="item-checkbox" id="${checkboxId}" data-category-index="${categoryIndex}" /> | |
| <label for="${checkboxId}" class="flex-1"> | |
| <div class="text-sm font-medium text-gray-700">${req.document}</div> | |
| <div class="text-sm text-gray-600">${req.requirement}</div> | |
| </label> | |
| </div>`; | |
| }).join(''); | |
| categoryDiv.innerHTML = ` | |
| <div class="collapse-title font-semibold flex items-center gap-2"> | |
| <input type="checkbox" class="global-checkbox" id="${globalCheckboxId}" data-category-index="${categoryIndex}" /> | |
| <label for="${globalCheckboxId}" class="text-lg font-semibold text-blue-600">${category.title}</label> | |
| </div> | |
| <input type="checkbox" /> | |
| <div class="collapse-content text-sm"> | |
| <div class="space-y-2"> | |
| ${requirementsHTML} | |
| </div> | |
| </div> | |
| `; | |
| container.appendChild(categoryDiv); | |
| }); | |
| // Event delegation to handle global checkbox logic | |
| container.querySelectorAll('.global-checkbox').forEach(globalCheckbox => { | |
| globalCheckbox.addEventListener('change', (e) => { | |
| const categoryIndex = e.target.dataset.categoryIndex; | |
| const itemCheckboxes = container.querySelectorAll(`.item-checkbox[data-category-index="${categoryIndex}"]`); | |
| itemCheckboxes.forEach(checkbox => { | |
| checkbox.checked = e.target.checked; | |
| }); | |
| }); | |
| }); | |
| // Update global checkbox state when individual checkboxes change | |
| container.querySelectorAll('.item-checkbox').forEach(itemCheckbox => { | |
| itemCheckbox.addEventListener('change', (e) => { | |
| const categoryIndex = e.target.dataset.categoryIndex; | |
| const itemCheckboxes = container.querySelectorAll(`.item-checkbox[data-category-index="${categoryIndex}"]`); | |
| const globalCheckbox = container.querySelector(`.global-checkbox[data-category-index="${categoryIndex}"]`); | |
| const allChecked = Array.from(itemCheckboxes).every(cb => cb.checked); | |
| globalCheckbox.checked = allChecked; | |
| }); | |
| }); | |
| } | |
| /* | |
| * Copie la liste de requirements complète dans le presse papier | |
| */ | |
| function copyAllRequirementsAsMarkdown() { | |
| const formatted = requirements.map(doc => { | |
| const header = `Document: ${doc.document}\nContext: ${doc.context}\nRequirements:\n`; | |
| const reqs = doc.requirements.map((req, i) => ` ${i + 1}. ${req}`).join('\n'); | |
| return `${header}${reqs}`; | |
| }).join('\n\n'); | |
| navigator.clipboard.writeText(formatted) | |
| .then(() => { | |
| console.log('Requirements copied to clipboard.'); | |
| alert("Requirements copied to clipboard"); | |
| }) | |
| .catch(err => { | |
| console.error('Failed to copy requirements:', err); | |
| }); | |
| } | |
| /* | |
| * Copie les requirements séléctionnés en markdown | |
| */ | |
| function copySelectedRequirementsAsMarkdown() { | |
| const selected = getSelectedRequirementsByCategory(); | |
| if (!selected || !selected.categories || selected.categories.length === 0) { | |
| alert("No selected requirements to copy."); | |
| return; | |
| } | |
| const lines = []; | |
| selected.categories.forEach(category => { | |
| lines.push(`### ${category.title}`); | |
| category.requirements.forEach(req => { | |
| lines.push(`- ${req.requirement} (${req.document})`); | |
| }); | |
| lines.push(''); // Add an empty line after each category | |
| }); | |
| const markdownText = lines.join('\n'); | |
| navigator.clipboard.writeText(markdownText).then(() => { | |
| console.log("Markdown copied to clipboard."); | |
| alert("Selected requirements copied to clipboard"); | |
| }).catch(err => { | |
| console.error("Failed to copy markdown:", err); | |
| }); | |
| } | |
| /* | |
| * Recupère tous les requirements séléctionnés par catégorie dans l'interface. | |
| */ | |
| function getSelectedRequirementsByCategory() { | |
| const container = document.getElementById('categorized-requirements-list'); | |
| const selected_category_ids = []; | |
| const categoryDivs = container.querySelectorAll('.collapse'); | |
| categoryDivs.forEach((categoryDiv, categoryIndex) => { | |
| // Find all checked item checkboxes within this category | |
| const checkedItems = categoryDiv.querySelectorAll(`.item-checkbox[data-category-index="${categoryIndex}"]:checked`); | |
| if (checkedItems.length > 0) { | |
| // Extract requirement indexes from their parent div's data attribute | |
| const checkedReqIndexes = Array.from(checkedItems).map(checkbox => { | |
| const itemDiv = checkbox.closest('[data-cat-req-id]'); | |
| return parseInt(itemDiv.dataset.catReqId, 10); | |
| }); | |
| selected_category_ids.push({ | |
| categoryIndex, | |
| checkedReqIndexes | |
| }); | |
| } | |
| }); | |
| /// Compute a checksum to check if checked requirements changed between two generations of solutions. | |
| let totalChecksum = 0; | |
| for (const { categoryIndex, checkedReqIndexes } of selected_category_ids) { | |
| const catChecksum = checkedReqIndexes.reduce( | |
| (sum, val, i) => sum + (val + 1) * (i + 1) ** 2, | |
| 0 | |
| ); | |
| totalChecksum += (categoryIndex + 1) * catChecksum; // include category index for entropy | |
| } | |
| /// Reconstruct the schema based on the selected ids. | |
| let selected_categories = { | |
| categories: selected_category_ids.map(({ categoryIndex, checkedReqIndexes }) => { | |
| const category = categorizedRequirements.categories[categoryIndex]; | |
| const requirements = checkedReqIndexes.map(i => category.requirements[i]); | |
| return { | |
| id: categoryIndex, | |
| title: category.title, | |
| requirements, | |
| }; | |
| }), | |
| requirements_checksum: totalChecksum, | |
| }; | |
| return selected_categories; | |
| } | |
| async function searchRequirements() { | |
| const query = document.getElementById('query-input').value.trim(); | |
| if (!query) { | |
| alert('Veuillez entrer une requête de recherche'); | |
| return; | |
| } | |
| if (!formattedRequirements || formattedRequirements.length === 0) { | |
| alert('Aucun requirement disponible pour la recherche'); | |
| return; | |
| } | |
| showLoadingOverlay('Recherche en cours...'); | |
| toggleElementsEnabled(['search-requirements-btn'], false); | |
| try { | |
| // Préparer les requirements pour la recherche | |
| const response = await fetch('/get_reqs_from_query', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| query: query, | |
| requirements: formattedRequirements | |
| }) | |
| }); | |
| const data = await response.json(); | |
| displaySearchResults(data.requirements); | |
| } catch (error) { | |
| console.error('Erreur lors de la recherche:', error); | |
| alert('Erreur lors de la recherche des requirements'); | |
| } finally { | |
| hideLoadingOverlay(); | |
| toggleElementsEnabled(['search-requirements-btn'], true); | |
| } | |
| } | |
| /** | |
| * Affiche les résultats de recherche | |
| * @param {Array} results - Résultats de la recherche | |
| */ | |
| function displaySearchResults(results) { | |
| const container = document.getElementById('query-results'); | |
| container.innerHTML = ''; | |
| if (results.length === 0) { | |
| container.innerHTML = '<p class="text-gray-500">Aucun résultat trouvé pour cette requête.</p>'; | |
| return; | |
| } | |
| const resultsDiv = document.createElement('div'); | |
| resultsDiv.className = 'space-y-3'; | |
| results.forEach((result, index) => { | |
| const resultDiv = document.createElement('div'); | |
| resultDiv.className = 'p-3 bg-blue-50 border border-blue-200 rounded-lg'; | |
| resultDiv.innerHTML = ` | |
| <div class="text-sm font-medium text-blue-800">${result.document}</div> | |
| <div class="text-sm text-gray-600 mb-1">${result.context}</div> | |
| <div class="text-sm">${result.requirement}</div> | |
| `; | |
| resultsDiv.appendChild(resultDiv); | |
| }); | |
| container.appendChild(resultsDiv); | |
| } | |
| function createSolutionAccordion(solutionCriticizedHistory, containerId, versionIndex = 0, categoryIndex = null) { | |
| const container = document.getElementById(containerId); | |
| if (!container) { | |
| console.error(`Container with ID "${containerId}" not found`); | |
| return; | |
| } | |
| // Si categoryIndex est spécifié, ne mettre à jour que cette catégorie | |
| if (categoryIndex !== null) { | |
| updateSingleAccordion(solutionCriticizedHistory, containerId, versionIndex, categoryIndex); | |
| return; | |
| } | |
| // Vider le container seulement si on recrée tout | |
| container.innerHTML = ''; | |
| // Récupérer les données de la version actuelle directement | |
| const currentVersionData = solutionCriticizedHistory[versionIndex]; | |
| // Créer l'accordéon principal | |
| const accordion = document.createElement('div'); | |
| accordion.className = 'space-y-2'; | |
| accordion.id = 'main-accordion'; | |
| // Afficher seulement les solutions de la version actuelle | |
| currentVersionData.critiques.forEach((item, index) => { | |
| createSingleAccordionItem(item, index, versionIndex, solutionCriticizedHistory, accordion); | |
| }); | |
| // Ajouter l'accordéon au container | |
| container.appendChild(accordion); | |
| } | |
| function createSingleAccordionItem(item, index, versionIndex, solutionCriticizedHistory, accordion) { | |
| const solution = item.solution; | |
| const criticism = item.criticism; | |
| // Récupérer le titre de la catégorie | |
| const categoryTitle = categorizedRequirements.categories.find(c => c.id == solution['Category_Id']).title; | |
| // const categoryTitle = document.querySelector(`#category-${solution['Category_Id']} h2`)?.textContent || `Catégorie ${solution['Category_Id'] + 1}`; | |
| // Container pour chaque solution | |
| const solutionCard = document.createElement('div'); | |
| solutionCard.className = 'border border-gray-200 rounded-md shadow-sm solution-accordion'; | |
| solutionCard.id = `accordion-item-${index}`; | |
| // En-tête de l'accordéon avec navigation | |
| const header = document.createElement('div'); | |
| header.className = 'bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors duration-200'; | |
| solutionCard.setAttribute('solution-accordion-id', `${index}`) | |
| const currentVersion = versionIndex + 1; | |
| const totalVersions = solutionCriticizedHistory.length; | |
| header.innerHTML = ` | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center space-x-3"> | |
| <h3 class="text-sm font-semibold text-gray-800">${categoryTitle}</h3> | |
| <div class="flex items-center space-x-2 bg-white px-3 py-1 rounded-full border"> | |
| <button class="version-btn-left w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-100 transition-colors ${currentVersion === 1 ? 'opacity-50 cursor-not-allowed' : ''}" | |
| data-solution-index="${solution['Category_Id']}" | |
| ${currentVersion === 1 ? 'disabled' : ''}> | |
| <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path> | |
| </svg> | |
| </button> | |
| <span class="text-xs font-medium text-gray-600 min-w-[60px] text-center version-indicator">Version ${currentVersion}</span> | |
| <button class="version-btn-right w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-100 transition-colors ${currentVersion === totalVersions ? 'opacity-50 cursor-not-allowed' : ''}" | |
| data-solution-index="${solution['Category_Id']}" | |
| ${currentVersion === totalVersions ? 'disabled' : ''}> | |
| <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- | |
| <button class="delete-btn text-red-500 hover:text-red-700 transition-colors" | |
| data-solution-index="${solution['Category_Id']}" id="solution-delete-btn"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5-3h4m-4 0a1 1 0 00-1 1v1h6V5a1 1 0 00-1-1m-4 0h4" /> | |
| </svg> | |
| </button> | |
| --> | |
| </div>`; | |
| // Contenu de l'accordéon | |
| const content = document.createElement('div'); | |
| content.className = `accordion-content px-4 py-3 space-y-3`; | |
| content.id = `content-${solution['Category_Id']}`; | |
| // Vérifier l'état d'ouverture précédent | |
| const isOpen = accordionStates[solution['Category_Id']] || false; | |
| console.log(isOpen); | |
| if (!isOpen) | |
| content.classList.add('hidden'); | |
| // Section Problem Description | |
| const problemSection = document.createElement('div'); | |
| problemSection.className = 'bg-red-50 border-l-2 border-red-400 p-3 rounded-r-md'; | |
| problemSection.innerHTML = ` | |
| <h4 class="text-sm font-semibold text-red-800 mb-2 flex items-center"> | |
| <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> | |
| <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path> | |
| </svg> | |
| Problem Description | |
| </h4> | |
| <p class="text-xs text-gray-700 leading-relaxed">${solution['Problem Description'] || 'Aucune description du problème disponible.'}</p> | |
| `; | |
| // Section Solution Description | |
| const solutionSection = document.createElement('div'); | |
| solutionSection.className = 'bg-green-50 border-l-2 border-green-400 p-3 rounded-r-md'; | |
| solutionSection.innerHTML = ` | |
| <h4 class="text-sm font-semibold text-green-800 mb-2 flex items-center"> | |
| <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> | |
| <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> | |
| </svg> | |
| Solution Description | |
| </h4> | |
| <p class="text-xs text-gray-700 leading-relaxed">${solution['Solution Description'] || 'Aucune description de solution disponible.'}</p> | |
| `; | |
| // Section Critique | |
| const critiqueSection = document.createElement('div'); | |
| critiqueSection.className = 'bg-yellow-50 border-l-2 border-yellow-400 p-3 rounded-r-md'; | |
| let critiqueContent = ` | |
| <h4 class="text-sm font-semibold text-yellow-800 mb-2 flex items-center"> | |
| <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> | |
| <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path> | |
| </svg> | |
| Critique & Analysis | |
| </h4> | |
| `; | |
| // Sous-sections de critique | |
| if (criticism.technical_challenges && criticism.technical_challenges.length > 0) { | |
| critiqueContent += ` | |
| <div class="mb-2"> | |
| <h5 class="text-xs font-semibold text-yellow-700 mb-1">Technical Challenges:</h5> | |
| <ul class="list-disc list-inside space-y-0.5 text-xs text-gray-700 ml-3"> | |
| ${criticism.technical_challenges.map(challenge => `<li>${challenge}</li>`).join('')} | |
| </ul> | |
| </div> | |
| `; | |
| } | |
| if (criticism.weaknesses && criticism.weaknesses.length > 0) { | |
| critiqueContent += ` | |
| <div class="mb-2"> | |
| <h5 class="text-xs font-semibold text-yellow-700 mb-1">Weaknesses:</h5> | |
| <ul class="list-disc list-inside space-y-0.5 text-xs text-gray-700 ml-3"> | |
| ${criticism.weaknesses.map(weakness => `<li>${weakness}</li>`).join('')} | |
| </ul> | |
| </div> | |
| `; | |
| } | |
| if (criticism.limitations && criticism.limitations.length > 0) { | |
| critiqueContent += ` | |
| <div class="mb-2"> | |
| <h5 class="text-xs font-semibent text-yellow-700 mb-1">Limitations:</h5> | |
| <ul class="list-disc list-inside space-y-0.5 text-xs text-gray-700 ml-3"> | |
| ${criticism.limitations.map(limitation => `<li>${limitation}</li>`).join('')} | |
| </ul> | |
| </div> | |
| `; | |
| } | |
| critiqueSection.innerHTML = critiqueContent; | |
| // ===================================== Section sources ================================ | |
| createEl = (tag, properties) => { | |
| const element = document.createElement(tag); | |
| Object.assign(element, properties); | |
| return element; | |
| }; | |
| // conteneur des sources | |
| const sourcesSection = createEl('div', { | |
| className: 'bg-gray-50 border-l-2 border-gray-400 p-3 rounded-r-md' | |
| }); | |
| const heading = createEl('h4', { | |
| className: 'text-sm font-semibold text-black mb-2 flex items-center', | |
| innerHTML: ` | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| class="w-4 h-4 mr-1" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| stroke="currentColor" | |
| stroke-width="2"> | |
| <path | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" | |
| /> | |
| </svg> | |
| Sources | |
| ` | |
| }); | |
| const pillContainer = createEl('div', { | |
| className: 'flex flex-wrap mt-1' | |
| }); | |
| // create reference pills | |
| solution['References'].forEach(source => { | |
| const pillLink = createEl('a', { | |
| href: source.url, | |
| target: '_blank', | |
| rel: 'noopener noreferrer', | |
| className: 'inline-block bg-gray-100 text-black text-xs font-medium mr-2 mb-2 px-3 py-1 rounded-full hover:bg-gray-400 transition-colors', | |
| textContent: source.name | |
| }); | |
| pillContainer.appendChild(pillLink); | |
| }); | |
| sourcesSection.append(heading, pillContainer); | |
| // ====================================================================================== | |
| // Ajouter les sections au contenu | |
| content.appendChild(problemSection); | |
| content.appendChild(solutionSection); | |
| content.appendChild(critiqueSection); | |
| content.appendChild(sourcesSection); | |
| // Événement de clic pour l'accordéon (exclure les boutons de navigation) | |
| header.addEventListener('click', (e) => { | |
| // Ne pas déclencher l'accordéon si on clique sur les boutons de navigation | |
| if (e.target.closest('.version-btn-left') || e.target.closest('.version-btn-right') || e.target.closest('#solution-delete-btn')) { | |
| return; | |
| } | |
| // handling open state | |
| const isCurrentlyOpen = !content.classList.contains('hidden'); | |
| accordionStates[solution['Category_Id']] = isCurrentlyOpen; | |
| if (isCurrentlyOpen) | |
| content.classList.add('hidden'); | |
| else | |
| content.classList.remove('hidden'); | |
| }); | |
| // Delete solution accordion button | |
| // header.querySelector('#solution-delete-btn')?.addEventListener('click', (e) => { | |
| // e.stopPropagation(); | |
| // solutionCard.remove(); | |
| // }); | |
| // Événements de navigation pour cette catégorie spécifique | |
| header.querySelector('.version-btn-left')?.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| updateSingleAccordion(solutionCriticizedHistory, 'accordion-container', versionIndex - 1, index); | |
| }); | |
| header.querySelector('.version-btn-right')?.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| updateSingleAccordion(solutionCriticizedHistory, 'accordion-container', versionIndex + 1, index); | |
| }); | |
| // Assembler la carte de solution | |
| solutionCard.appendChild(header); | |
| solutionCard.appendChild(content); | |
| accordion.appendChild(solutionCard); | |
| } | |
| function updateSingleAccordion(solutionCriticizedHistory, containerId, newVersionIndex, categoryIndex) { | |
| // Vérifier les limites de version | |
| if (newVersionIndex < 0 || newVersionIndex >= solutionCriticizedHistory.length) { | |
| return; | |
| } | |
| const accordionItem = document.getElementById(`accordion-item-${categoryIndex}`); | |
| if (!accordionItem) return; | |
| const newData = solutionCriticizedHistory[newVersionIndex]; | |
| const newItem = newData.critiques[categoryIndex]; | |
| if (!newItem) return; | |
| // Mettre à jour le contenu de cette catégorie spécifique | |
| const tempContainer = document.createElement('div'); | |
| createSingleAccordionItem(newItem, categoryIndex, newVersionIndex, solutionCriticizedHistory, tempContainer); | |
| // Remplacer l'ancien item par le nouveau | |
| accordionItem.parentNode.replaceChild(tempContainer.firstChild, accordionItem); | |
| } | |
| // Fonction d'initialisation simplifiée | |
| function initializeSolutionAccordion(solutionCriticizedHistory, containerId, startVersion = 0) { | |
| // Réinitialiser les états d'accordéon | |
| accordionStates = {}; | |
| createSolutionAccordion(solutionCriticizedHistory, containerId, startVersion); | |
| document.getElementById(containerId).classList.remove('hidden') | |
| } | |
| // Supprime toutes les accordéons de solutions générées | |
| //FIXME: À terme, ne devrait pas exister | |
| function clearAllSolutions() { | |
| accordionStates = {} | |
| solutionsCriticizedVersions = [] | |
| document.querySelectorAll('.solution-accordion').forEach(a => a.remove()); | |
| } | |
| async function generateSolutions(selected_categories, user_constraints) { | |
| console.log(selected_categories); | |
| let input_req = structuredClone(selected_categories); | |
| input_req.user_constraints = user_constraints; | |
| let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/search_solutions_gemini/v2', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input_req) }) | |
| let responseObj = await response.json() | |
| return responseObj; | |
| } | |
| async function generateCriticisms(solutions) { | |
| let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) }) | |
| let responseObj = await response.json() | |
| solutionsCriticizedVersions.push(responseObj) | |
| } | |
| async function refineSolutions(critiques) { | |
| let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) }) | |
| let responseObj = await response.json() | |
| await generateCriticisms(responseObj) | |
| } | |
| async function workflow(steps = 1) { | |
| let soluce; | |
| showLoadingOverlay('Génération des solutions & critiques ....'); | |
| const selected_requirements = getSelectedRequirementsByCategory(); | |
| const user_constraints = document.getElementById('additional-gen-instr').value; | |
| console.log(user_constraints); | |
| // check if the selected requirements changed since last workflow usage | |
| const requirements_changed = selected_requirements.requirements_checksum != (lastSelectedRequirementsChecksum ?? -1); | |
| for (let step = 1; step <= steps; step++) { | |
| if (requirements_changed) { | |
| clearAllSolutions(); | |
| console.log("Requirements checksum changed. Cleaning up"); | |
| lastSelectedRequirementsChecksum = selected_requirements.requirements_checksum; | |
| } | |
| if (solutionsCriticizedVersions.length == 0) { | |
| soluce = await generateSolutions(selected_requirements, user_constraints ? user_constraints : null); | |
| await generateCriticisms(soluce) | |
| } else { | |
| let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1]; | |
| await refineSolutions(prevSoluce) | |
| } | |
| } | |
| hideLoadingOverlay(); | |
| initializeSolutionAccordion(solutionsCriticizedVersions, "solutions-list") | |
| } | |
| // ============================================================================= | |
| // INITIALISATION DES ÉVÉNEMENTS | |
| // ============================================================================= | |
| document.addEventListener('DOMContentLoaded', function () { | |
| // Événements des boutons principaux | |
| // document.getElementById('get-meetings-btn').addEventListener('click', getMeetings); | |
| document.getElementById('working-group-select').addEventListener('change', (ev) => { | |
| getMeetings(); | |
| }); | |
| document.getElementById('get-tdocs-btn').addEventListener('click', getTDocs); | |
| document.getElementById('download-tdocs-btn').addEventListener('click', downloadTDocs); | |
| document.getElementById('extract-requirements-btn').addEventListener('click', extractRequirements); | |
| document.getElementById('categorize-requirements-btn').addEventListener('click', () => { | |
| const category_count_auto_detect = document.getElementById('auto-detect-toggle').checked; | |
| const n_categories = document.getElementById('category-count').value; | |
| categorizeRequirements(category_count_auto_detect ? null : n_categories); | |
| }); | |
| // Événement pour la recherche | |
| document.getElementById('search-requirements-btn').addEventListener('click', searchRequirements); | |
| // Événements pour les boutons de solutions (à implémenter plus tard) | |
| document.getElementById('get-solutions-btn').addEventListener('click', () => { | |
| const n_steps = document.getElementById('solution-gen-nsteps').value; | |
| workflow(n_steps); | |
| }); | |
| document.getElementById('get-solutions-step-btn').addEventListener('click', () => { | |
| workflow(); | |
| }); | |
| }); | |
| // dseactiver le choix du nb de catégories lorsqu'en mode auto | |
| document.getElementById('auto-detect-toggle').addEventListener('change', (ev) => { debounceAutoCategoryCount(ev.target.checked) }); | |
| debounceAutoCategoryCount(true); | |
| // focus l'input d'instructions de gen additionelles | |
| document.getElementById("additional-gen-instr-btn").addEventListener('click', (ev) => { | |
| document.getElementById('additional-gen-instr').focus() | |
| }) | |
| document.getElementById('copy-reqs-btn').addEventListener('click', (ev) => { | |
| copySelectedRequirementsAsMarkdown(); | |
| }); | |
| document.getElementById('copy-all-reqs-btn').addEventListener('click', copyAllRequirementsAsMarkdown); |