// ==================================== 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 = 'Loading...') { 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 = 'Select...') { const select = document.getElementById(selectId); if (select) { select.innerHTML = ``; 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 = ` ${option} `; 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) + " (All)"; } 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 = `All`; 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 = `${opt}`; 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} (All)`; } else { labelElement.textContent = `${filterType} (${selectedCount} selected)`; } } /** * 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('Getting all available 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('Error while getting meetings:', error); alert('Error while getting 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('Getting TDocs List...'); 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('Error while getting TDocs:', error); alert('Error while getting 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 = ` ${row.TDoc || ''} ${row.Title || ''} ${row.Type || ''} ${row['TDoc Status'] || ''} ${row['Agenda item description'] || ''} ${row.URL ? 'Lien' : 'N/A'} `; 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} total documents`; document.getElementById('selected-count').textContent = `${selected} selected documents`; } /** * 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('Downloading TDocs...'); 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('Please select at least one 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('Error while downloading 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 (const aItem of agendaItems) { filename += `_${aItem}`; } } // empty set means "Tous" is selected if (docStatuses) { for (const 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('Please select at least one document'); return; } showLoadingOverlay('Extracting requirements...'); 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(`Extracting requirements... (${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('Error while extracting requirements', error); alert('Error while extracting 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 = `

${docReq.document}

${docReq.context}

`; container.appendChild(docDiv); }); } /** * Catégorise les requirements */ async function categorizeRequirements(max_categories) { if (!formattedRequirements || formattedRequirements.length === 0) { alert('No requirement available to categorize'); return; } showLoadingOverlay('Categorizing requirements...'); 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('Error while categorizing requirements:', error); alert('Error while categorizing 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'); if (!container) { console.error('Container element with ID "categorized-requirements-list" not found.'); return; } container.innerHTML = ''; categorizedData.forEach((category, categoryIndex) => { const categoryDiv = document.createElement('div'); categoryDiv.className = 'collapse collapse-arrow mb-2 border border-gray-200 rounded-lg bg-white shadow-sm'; // Generate unique IDs for all checkboxes const globalCheckboxId = `global-checkbox-${categoryIndex}`; // Create the HTML for the individual requirement items within a category const requirementsHTML = category.requirements.map((req, reqIndex) => { const checkboxId = `checkbox-${categoryIndex}-${reqIndex}`; return `
`; }).join(''); // Set the innerHTML for the entire collapsible category component categoryDiv.innerHTML = `
${requirementsHTML}
`; container.appendChild(categoryDiv); }); // --- Event Listeners --- // Stop click propagation on the checkbox wrapper to isolate it from the collapse trigger container.querySelectorAll('.checkbox-and-title-wrapper').forEach(wrapper => { wrapper.addEventListener('click', (e) => { e.stopPropagation(); }); }); // Handle "select all" logic when the global checkbox is changed 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 the global checkbox state when any individual item checkbox is changed 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); const someChecked = Array.from(itemCheckboxes).some(cb => cb.checked); globalCheckbox.checked = allChecked; // Set indeterminate state for better UX if (allChecked) { globalCheckbox.indeterminate = false; } else if (someChecked) { globalCheckbox.indeterminate = true; } else { globalCheckbox.indeterminate = false; } }); }); } /* * 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('Please enter a search query'); return; } if (!formattedRequirements || formattedRequirements.length === 0) { alert('No available requirements for search'); return; } showLoadingOverlay('Searching...'); 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('Error while searching:', error); alert('Error while searching 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 = '

Aucun résultat trouvé pour cette requête.

'; 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 = `
${result.document}
${result.context}
${result.requirement}
`; 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 = `

${categoryTitle}

Version ${currentVersion}
`; // 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 = `

Problem Description

${solution['Problem Description'] || 'Aucune description du problème disponible.'}

`; // 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 = `

Solution Description

${solution['Solution Description'] || 'Aucune description de solution disponible.'}

`; // 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 = `

Critique & Analysis

`; // Sous-sections de critique if (criticism.technical_challenges && criticism.technical_challenges.length > 0) { critiqueContent += `
Technical Challenges:
`; } if (criticism.weaknesses && criticism.weaknesses.length > 0) { critiqueContent += `
Weaknesses:
`; } if (criticism.limitations && criticism.limitations.length > 0) { critiqueContent += `
Limitations:
`; } 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: ` 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);