import {
toggleElementsEnabled, toggleContainersVisibility, showLoadingOverlay, hideLoadingOverlay, populateSelect,
populateCheckboxDropdown, populateDaisyDropdown, extractTableData, switchTab, enableTabSwitching, debounceAutoCategoryCount,
bindTabs, checkPrivateLLMInfoAvailable, moveSolutionToDrafts, buildSolutionSubCategories, handleDraftRefine, renderDraftUI, populateLLMModelSelect,
displayFullAssessment, handleSaveConfigFields, handleLoadConfigFields, handleFTOAnalysis, handleClearConfig, handleExportDrafts
} from "./ui.js";
import { postWithSSE } from "./sse.js";
// ==================================== Variables globales ========================================
let requirements = [];
// Filtres
let selectedType = ""; // "" = Tous
let selectedStatus = new Set(); // valeurs cochées (hors "Tous")
let selectedAgenda = new Set();
// Requirements
let formattedRequirements = [];
let categorizedRequirements = [];
// les requirements ont ils été extraits au moins une fois ?
let hasRequirementsExtracted = false;
// Generation de solutions
let solutionAccordionStates = {};
let solutionsCriticizedVersions = [];
// checksum pour vérifier si les requirements séléctionnés ont changé
let lastSelectedRequirementsChecksum = null;
// =============================================================================
// 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('/docs/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('/docs/get_meeting_docs', {
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',
], true);
switchTab('doc-table-tab');
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, applyFilters);
// Agenda (checkbox multiselect)
populateCheckboxDropdown('agenda-options', agendaItems, 'agenda', 'agenda-filter-label', selectedAgenda, applyFilters);
// 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)';
}
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
*/
export 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 pCR / CR / draftCR dans TDocs sélectionnés.
* Le JS filtre les tdocs sélectionnés pour recup uniquement les xxxxxCR
*/
async function downloadTDocs() {
showLoadingOverlay('Downloading TDocs...');
toggleElementsEnabled(['download-tdocs-btn', 'extract-requirements-btn'], false);
try {
// Extraire les données du tableau avec le format suivant pour la requete backend
// { document: "nom_doc", url: "url_doc", type: "type_de_doc"}
const selectedData = extractTableData({ 'TDoc': 'document', 'URL': 'url', 'Type': "type" });
if (selectedData.length === 0) {
alert('Please select at least one document');
return;
}
// on prend tout
const documents = selectedData;
const response = await fetch('/docs/download_docs', {
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', 'Type': 'type' });
console.log("Selected docs data");
console.log(selectedData);
if (selectedData.length === 0) {
alert('Please select at least one document');
return;
}
// ne prendre que les documents avec un type qui contient xxxxCR en minuscules
const documents = selectedData.filter(d => d.type.toLowerCase().includes("cr"));
showLoadingOverlay('Extracting requirements...');
toggleElementsEnabled(['extract-requirements-btn'], false);
try {
const response = await postWithSSE('/docs/extract_requirements/sse', { documents: documents }, {
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 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}
${docReq.requirements.map((req, reqIndex) =>
`- ${req}
`
).join('')}
`;
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('/requirements/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);
clearAllBootstrapSolutions();
// 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 = `
`;
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(() => {
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('/requirements/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);
}
// =========================================== Solution bootstrapping ==============================================
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;
// 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']}`;
// enable solution drafting if private LLM info is present to enable solution private drafting
if (checkPrivateLLMInfoAvailable()) {
// Boutons pour passer la solution en draft
const body_btn_div = document.createElement('div');
body_btn_div.className = "flex justify-end";
const draft_btn = document.createElement('button');
draft_btn.className = "btn btn-secondary rounded-full";
draft_btn.innerText = "✏ Draft solution"
body_btn_div.appendChild(draft_btn);
content.appendChild(body_btn_div);
draft_btn.addEventListener('click', _ => {
// alert(`Drafting solution ${solution['category_id']} ${versionIndex}`)
moveSolutionToDrafts(solution);
});
}
// Vérifier l'état d'ouverture précédent
const isOpen = solutionAccordionStates[solution['category_id']] || false;
console.log(isOpen);
if (!isOpen)
content.classList.add('hidden');
// 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:
${criticism.technical_challenges.map(challenge => `- ${challenge}
`).join('')}
`;
}
if (criticism.weaknesses && criticism.weaknesses.length > 0) {
critiqueContent += `
Weaknesses:
${criticism.weaknesses.map(weakness => `- ${weakness}
`).join('')}
`;
}
if (criticism.limitations && criticism.limitations.length > 0) {
critiqueContent += `
Limitations:
${criticism.limitations.map(limitation => `- ${limitation}
`).join('')}
`;
}
critiqueSection.innerHTML = critiqueContent;
// ======================================================================================
for (let item of buildSolutionSubCategories(solution))
content.appendChild(item);
content.appendChild(critiqueSection);
// É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');
solutionAccordionStates[solution['category_id']] = isCurrentlyOpen;
if (isCurrentlyOpen)
content.classList.add('hidden');
else
content.classList.remove('hidden');
});
// É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
solutionAccordionStates = {};
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 clearAllBootstrapSolutions() {
solutionAccordionStates = {}
solutionsCriticizedVersions = []
document.querySelectorAll('.solution-accordion').forEach(a => a.remove());
}
async function generateBootstrapSolutions(selected_categories, user_constraints = null) {
console.log(selected_categories);
let input_req = structuredClone(selected_categories);
input_req.user_constraints = user_constraints;
let response = await fetch("/solutions/bootstrap_solutions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input_req) })
let responseObj = await response.json()
return responseObj;
}
async function generateBootstrapCriticisms(solutions) {
let response = await fetch('/solutions/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) })
let responseObj = await response.json()
solutionsCriticizedVersions.push(responseObj)
}
async function refineBootstrapSolutions(critiques) {
let response = await fetch('/solutions/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) })
let responseObj = await response.json()
await generateBootstrapCriticisms(responseObj)
}
async function boostrapWorkflow(steps = 1) {
let soluce;
showLoadingOverlay('Boostrapping solutions ....');
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) {
clearAllBootstrapSolutions();
console.log("Requirements checksum changed. Cleaning up");
lastSelectedRequirementsChecksum = selected_requirements.requirements_checksum;
}
if (solutionsCriticizedVersions.length == 0) {
soluce = await generateBootstrapSolutions(selected_requirements, user_constraints ? user_constraints : null);
await generateBootstrapCriticisms(soluce)
} else {
let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
await refineBootstrapSolutions(prevSoluce)
}
}
hideLoadingOverlay();
initializeSolutionAccordion(solutionsCriticizedVersions, "solutions-list")
}
// =============================================================================
// INITIALISATION DES ÉVÉNEMENTS
// =============================================================================
document.addEventListener('DOMContentLoaded', function () {
// Bind tous les tabs
bindTabs();
// Événements des boutons principaux
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 bootstrappées
document.getElementById('get-solutions-btn').addEventListener('click', () => {
const n_steps = document.getElementById('solution-gen-nsteps').value;
boostrapWorkflow(n_steps);
});
document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
boostrapWorkflow(1);
});
// render l'ui de draft vide
renderDraftUI();
handleLoadConfigFields();
});
// 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()
})
// copy requirements
document.getElementById('copy-reqs-btn').addEventListener('click', (ev) => {
copySelectedRequirementsAsMarkdown();
});
// copy all requirements
document.getElementById('copy-all-reqs-btn').addEventListener('click', copyAllRequirementsAsMarkdown);
// =============== settings events ================
document.getElementById('settings-save-btn').addEventListener('click', handleSaveConfigFields);
document.getElementById('settings-clear-btn').addEventListener('click', handleClearConfig);
// button to fetch llm models
document.getElementById('settings-fetch-models').addEventListener('click', _ => {
const url = document.getElementById('settings-provider-url').value;
const token = document.getElementById('settings-provider-token').value;
populateLLMModelSelect('settings-provider-model', url, token).catch(e => alert("Error while fetching models: " + e))
});
// ================== solution drafting events =============
// button to open full assessment modal
document.getElementById('read-assessment-button').addEventListener('click', _ => {
displayFullAssessment();
});
// Events des boutons pour le drafting de solutions
document.getElementById('refine-btn').addEventListener('click', handleDraftRefine);
document.getElementById('fto-analysis-btn').addEventListener('click', handleFTOAnalysis);
document.getElementById('export-timeline-btn').addEventListener('click', handleExportDrafts)