Lucas ARRIESSE commited on
Commit
75074de
·
1 Parent(s): 50d44e1
Files changed (2) hide show
  1. index.html +99 -69
  2. static/script.js +233 -63
index.html CHANGED
@@ -57,68 +57,7 @@
57
  </button>
58
  </div>
59
 
60
- <!-- Filters -->
61
- <div id="filters-container" class="mb-6 hidden">
62
- <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
63
- <!-- Type Filter Dropdown -->
64
- <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
65
- <div tabindex="0" role="button" class="btn w-full justify-between" id="doc-type-filter-btn">
66
- <span id="doc-type-filter-label">Type</span>
67
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24"
68
- stroke="currentColor">
69
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
70
- </svg>
71
- </div>
72
- <ul tabindex="0"
73
- class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-full min-w-[200px] max-h-60 overflow-y-auto"
74
- id="doc-type-filter-menu">
75
- <!-- Rempli par JS -->
76
- </ul>
77
- </div>
78
 
79
- <!-- Status Filter Dropdown -->
80
- <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
81
- <div tabindex="0" role="button" class="btn w-full justify-between" id="status-dropdown-btn">
82
- <span id="status-filter-label">Status (Tous)</span>
83
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24"
84
- stroke="currentColor">
85
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
86
- </svg>
87
- </div>
88
- <ul tabindex="0"
89
- class="dropdown-content z-[1] p-2 shadow bg-base-100 rounded-box w-full max-h-60 overflow-y-auto">
90
- <li class="border-b pb-2 mb-2">
91
- <label class="flex items-center gap-2 cursor-pointer">
92
- <input type="checkbox" class="status-checkbox" value="all" checked>
93
- <span class="font-semibold">Tous</span>
94
- </label>
95
- </li>
96
- <div id="status-options" class="flex flex-col gap-1"></div>
97
- </ul>
98
- </div>
99
-
100
- <!-- Agenda Item Filter Dropdown -->
101
- <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
102
- <div tabindex="0" role="button" class="btn w-full justify-between" id="agenda-dropdown-btn">
103
- <span id="agenda-filter-label">Agenda Item (Tous)</span>
104
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24"
105
- stroke="currentColor">
106
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
107
- </svg>
108
- </div>
109
- <ul tabindex="0"
110
- class="dropdown-content z-[1] p-2 shadow bg-base-100 rounded-box w-full max-h-60 overflow-y-auto">
111
- <li class="border-b pb-2 mb-2">
112
- <label class="flex items-center gap-2 cursor-pointer">
113
- <input type="checkbox" class="agenda-checkbox" value="all" checked>
114
- <span class="font-semibold">Tous</span>
115
- </label>
116
- </li>
117
- <div id="agenda-options" class="flex flex-col gap-1"></div>
118
- </ul>
119
- </div>
120
- </div>
121
- </div>
122
 
123
  <!-- Add an horizontal separation -->
124
  <hr>
@@ -139,8 +78,74 @@
139
  </div>
140
 
141
  <div id="doc-table-tab-contents" class="hidden">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  <!-- Data Table Informations -->
143
- <div class="flex justify-between items-center mb-2 pt-10" id="data-table-info-container">
144
  <!-- Left side: buttons -->
145
  <div class="flex gap-2 items-center">
146
  <div class="tooltip" data-tip="Extract requirements from selected documents">
@@ -155,7 +160,7 @@
155
  </svg>Extract Requirements
156
  </button>
157
  </div>
158
- <button id="download-tdocs-btn" class="text-sm rounded px-3 py-1 shadow">
159
  📦 Download Selected TDocs
160
  </button>
161
  </div>
@@ -249,22 +254,47 @@
249
  </div>
250
  </div>
251
  <div id="categorized-requirements-container pt-10" class="mb-6">
252
- <h2 class="text-2xl font-bold mb-4 pt-10">Requirements categories list</h2>
 
 
 
 
 
 
 
 
 
 
253
  <div id="categorized-requirements-list"></div>
254
  </div>
255
 
256
  <!-- Solutions Action Buttons -->
257
  <div id="solutions-action-buttons-container" class="mb-6 hidden">
258
- <div class="flex flex-wrap gap-2 justify-center">
259
- <button id="get-solutions-btn"
260
- class="px-4 py-2 bg-indigo-500 text-white rounded-md hover:bg-indigo-600">
261
- Get Solutions
262
- </button>
 
 
 
263
  <button id="get-solutions-step-btn"
264
  class="px-4 py-2 bg-pink-500 text-white rounded-md hover:bg-pink-600">
265
  Get Solutions (Step-by-step)
266
  </button>
 
 
 
 
 
 
 
 
 
 
 
267
  </div>
 
268
  </div>
269
 
270
  <!-- Solutions Container -->
 
57
  </button>
58
  </div>
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  <!-- Add an horizontal separation -->
63
  <hr>
 
78
  </div>
79
 
80
  <div id="doc-table-tab-contents" class="hidden">
81
+ <!-- Filters -->
82
+ <div id="filters-container" class="mb-6 hidden pt-10">
83
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
84
+ <!-- Type Filter Dropdown -->
85
+ <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
86
+ <div tabindex="0" role="button" class="btn w-full justify-between" id="doc-type-filter-btn">
87
+ <span id="doc-type-filter-label">Type</span>
88
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24"
89
+ stroke="currentColor">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
91
+ d="M19 9l-7 7-7-7" />
92
+ </svg>
93
+ </div>
94
+ <ul tabindex="0"
95
+ class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-full min-w-[200px] max-h-60 overflow-y-auto"
96
+ id="doc-type-filter-menu">
97
+ <!-- Rempli par JS -->
98
+ </ul>
99
+ </div>
100
+
101
+ <!-- Status Filter Dropdown -->
102
+ <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
103
+ <div tabindex="0" role="button" class="btn w-full justify-between" id="status-dropdown-btn">
104
+ <span id="status-filter-label">Status (Tous)</span>
105
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24"
106
+ stroke="currentColor">
107
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
108
+ d="M19 9l-7 7-7-7" />
109
+ </svg>
110
+ </div>
111
+ <ul tabindex="0"
112
+ class="dropdown-content z-[1] p-2 shadow bg-base-100 rounded-box w-full max-h-60 overflow-y-auto">
113
+ <li class="border-b pb-2 mb-2">
114
+ <label class="flex items-center gap-2 cursor-pointer">
115
+ <input type="checkbox" class="status-checkbox" value="all" checked>
116
+ <span class="font-semibold">Tous</span>
117
+ </label>
118
+ </li>
119
+ <div id="status-options" class="flex flex-col gap-1"></div>
120
+ </ul>
121
+ </div>
122
+
123
+ <!-- Agenda Item Filter Dropdown -->
124
+ <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
125
+ <div tabindex="0" role="button" class="btn w-full justify-between" id="agenda-dropdown-btn">
126
+ <span id="agenda-filter-label">Agenda Item (Tous)</span>
127
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-2" fill="none" viewBox="0 0 24 24"
128
+ stroke="currentColor">
129
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
130
+ d="M19 9l-7 7-7-7" />
131
+ </svg>
132
+ </div>
133
+ <ul tabindex="0"
134
+ class="dropdown-content z-[1] p-2 shadow bg-base-100 rounded-box w-full max-h-60 overflow-y-auto">
135
+ <li class="border-b pb-2 mb-2">
136
+ <label class="flex items-center gap-2 cursor-pointer">
137
+ <input type="checkbox" class="agenda-checkbox" value="all" checked>
138
+ <span class="font-semibold">Tous</span>
139
+ </label>
140
+ </li>
141
+ <div id="agenda-options" class="flex flex-col gap-1"></div>
142
+ </ul>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
  <!-- Data Table Informations -->
148
+ <div class="flex justify-between items-center mb-2 pt-5" id="data-table-info-container">
149
  <!-- Left side: buttons -->
150
  <div class="flex gap-2 items-center">
151
  <div class="tooltip" data-tip="Extract requirements from selected documents">
 
160
  </svg>Extract Requirements
161
  </button>
162
  </div>
163
+ <button id="download-tdocs-btn" class="text-sm rounded px-3 py-1 shadow cursor-pointer">
164
  📦 Download Selected TDocs
165
  </button>
166
  </div>
 
254
  </div>
255
  </div>
256
  <div id="categorized-requirements-container pt-10" class="mb-6">
257
+ <div class="flex mb-4 pt-10">
258
+ <div class="justify-begin">
259
+ <h2 class="text-2xl font-bold">Requirements categories list</h2>
260
+ </div>
261
+ <div class="justify-end pl-5">
262
+ <!--Copy reqs button-->
263
+ <button class="btn btn-square" id="copy-reqs-btn" aria-label="Copy">
264
+ 📋
265
+ </button>
266
+ </div>
267
+ </div>
268
  <div id="categorized-requirements-list"></div>
269
  </div>
270
 
271
  <!-- Solutions Action Buttons -->
272
  <div id="solutions-action-buttons-container" class="mb-6 hidden">
273
+ <div class="flex flex-wrap gap-2 justify-center items-center">
274
+ <div class="join">
275
+ <input id="category-count" type="number" class="input join-item w-24" min="1" value="3" />
276
+ <button id="get-solutions-btn"
277
+ class="btn join-item px-4 py-2 bg-indigo-500 text-white rounded-md hover:bg-indigo-600">
278
+ Run multiple steps
279
+ </button>
280
+ </div>
281
  <button id="get-solutions-step-btn"
282
  class="px-4 py-2 bg-pink-500 text-white rounded-md hover:bg-pink-600">
283
  Get Solutions (Step-by-step)
284
  </button>
285
+ <div class="dropdown dropdown-center">
286
+ <div id="additional-gen-instr-btn" tabindex="0" role="button" class="btn m-1">🎨 Additional
287
+ generation constraints</div>
288
+ <div tabindex="0" class="dropdown-content card card-sm bg-base-100 z-1 w-256 shadow-md">
289
+ <div class="card-body">
290
+ <h3>Add additional constraints to follow when generating the solutions</h3>
291
+ <textarea id="additional-gen-instr"
292
+ class="textarea textarea-bordered w-full"></textarea>
293
+ </div>
294
+ </div>
295
+ </div>
296
  </div>
297
+
298
  </div>
299
 
300
  <!-- Solutions Container -->
static/script.js CHANGED
@@ -1,13 +1,20 @@
1
- // Variables globales
2
  let requirements = [];
 
 
3
  let selectedType = ""; // "" = Tous
4
  let selectedStatus = new Set(); // valeurs cochées (hors "Tous")
5
  let selectedAgenda = new Set();
 
 
6
  let accordionStates = {};
7
  let formattedRequirements = [];
8
  let categorizedRequirements = [];
9
  let solutionsCriticizedVersions = [];
10
- let isRequirements = false;
 
 
 
11
 
12
  // =============================================================================
13
  // FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS
@@ -344,7 +351,7 @@ async function getTDocs() {
344
  // 'data-table-info-container'
345
  ], true);
346
 
347
- isRequirements = false;
348
  } catch (error) {
349
  console.error('Erreur lors de la récupération des TDocs:', error);
350
  alert('Erreur lors de la récupération des TDocs');
@@ -540,7 +547,7 @@ function generateDownloadFilename() {
540
  if (docType && docType !== 'Tous') {
541
  filename = `${docType}_${filename}`;
542
  }
543
- if (isRequirements) {
544
  filename = `requirements_${filename}`;
545
  }
546
 
@@ -608,7 +615,7 @@ async function extractRequirements() {
608
  // set the number of fetched requirements
609
  document.getElementById('requirements-tab-badge').innerText = requirements.length;
610
 
611
- isRequirements = true;
612
  } catch (error) {
613
  console.error('Erreur lors de l\'extraction des requirements:', error);
614
  alert('Erreur lors de l\'extraction des requirements');
@@ -652,7 +659,6 @@ async function categorizeRequirements(max_categories) {
652
  alert('Aucun requirement à catégoriser');
653
  return;
654
  }
655
- console.log(max_categories);
656
 
657
  showLoadingOverlay('Catégorisation des requirements en cours...');
658
  toggleElementsEnabled(['categorize-requirements-btn'], false);
@@ -667,6 +673,7 @@ async function categorizeRequirements(max_categories) {
667
  const data = await response.json();
668
  categorizedRequirements = data;
669
  displayCategorizedRequirements(categorizedRequirements.categories);
 
670
 
671
  // Masquer le container de query et afficher les catégories et boutons solutions
672
  // toggleContainersVisibility(['query-requirements-container'], false);
@@ -692,27 +699,148 @@ function displayCategorizedRequirements(categorizedData) {
692
  categorizedData.forEach((category, categoryIndex) => {
693
  const categoryDiv = document.createElement('div');
694
  categoryDiv.tabIndex = 0;
 
695
 
696
- categoryDiv.className = 'collapse collapse-arrow mb-6 p-4 border border-gray-200 rounded-lg bg-white';
697
 
698
- categoryDiv.innerHTML = `
699
- <div class="collapse-title font-semibold">
700
- <h3 class="text-lg font-semibold mb-2 text-blue-600">${category.title}</h3>
701
- </div>
702
- <div class="collapse-content text-sm">
703
- <div class="space-y-2">
704
- ${category.requirements.map((req, reqIndex) =>
705
- `<div class="p-2 bg-gray-50 rounded border-l-4 border-blue-400" data-cat-req-id="${categoryIndex}-${reqIndex}">
 
706
  <div class="text-sm font-medium text-gray-700">${req.document}</div>
707
  <div class="text-sm text-gray-600">${req.requirement}</div>
708
- </div>`
709
- ).join('')}
710
- </div>
 
 
 
 
 
711
  </div>
 
 
 
 
 
 
712
  `;
713
 
714
  container.appendChild(categoryDiv);
715
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
716
  }
717
 
718
  async function searchRequirements() {
@@ -823,47 +951,54 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
823
  const criticism = item.criticism;
824
 
825
  // Récupérer le titre de la catégorie
826
- const categoryTitle = document.querySelector(`#category-${solution['Category_Id']} h2`)?.textContent || `Catégorie ${solution['Category_Id'] + 1}`;
 
827
 
828
  // Container pour chaque solution
829
  const solutionCard = document.createElement('div');
830
- solutionCard.className = 'border border-gray-200 rounded-md shadow-sm';
831
  solutionCard.id = `accordion-item-${index}`;
832
 
833
  // En-tête de l'accordéon avec navigation
834
  const header = document.createElement('div');
835
  header.className = 'bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors duration-200';
 
836
 
837
  const currentVersion = versionIndex + 1;
838
  const totalVersions = solutionCriticizedHistory.length;
839
 
840
  header.innerHTML = `
841
- <div class="flex justify-between items-center">
842
- <div class="flex items-center space-x-3">
843
- <h3 class="text-sm font-semibold text-gray-800">${categoryTitle}</h3>
844
- <div class="flex items-center space-x-2 bg-white px-3 py-1 rounded-full border">
845
- <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' : ''}"
846
- data-solution-index="${solution['Category_Id']}"
847
- ${currentVersion === 1 ? 'disabled' : ''}>
848
- <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
849
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
850
- </svg>
851
- </button>
852
- <span class="text-xs font-medium text-gray-600 min-w-[60px] text-center version-indicator">Version ${currentVersion}</span>
853
- <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' : ''}"
854
- data-solution-index="${solution['Category_Id']}"
855
- ${currentVersion === totalVersions ? 'disabled' : ''}>
856
- <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
857
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
858
- </svg>
859
- </button>
860
- </div>
861
  </div>
862
- <svg class="w-4 h-4 transform transition-transform duration-200 accordion-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
863
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
864
- </svg>
865
  </div>
866
- `;
 
 
 
 
 
 
 
 
 
867
 
868
  // Contenu de l'accordéon
869
  const content = document.createElement('div');
@@ -872,11 +1007,9 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
872
 
873
  // Vérifier l'état d'ouverture précédent
874
  const isOpen = accordionStates[solution['Category_Id']] || false;
875
- if (!isOpen) {
 
876
  content.classList.add('hidden');
877
- } else {
878
- header.querySelector('.accordion-icon').style.transform = 'rotate(180deg)';
879
- }
880
 
881
  // Section Problem Description
882
  const problemSection = document.createElement('div');
@@ -1015,24 +1148,25 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
1015
  // Événement de clic pour l'accordéon (exclure les boutons de navigation)
1016
  header.addEventListener('click', (e) => {
1017
  // Ne pas déclencher l'accordéon si on clique sur les boutons de navigation
1018
- if (e.target.closest('.version-btn-left') || e.target.closest('.version-btn-right')) {
1019
  return;
1020
  }
1021
 
1022
- const icon = header.querySelector('.accordion-icon');
1023
  const isCurrentlyOpen = !content.classList.contains('hidden');
1024
-
1025
- if (isCurrentlyOpen) {
1026
  content.classList.add('hidden');
1027
- icon.style.transform = 'rotate(0deg)';
1028
- accordionStates[solution['Category_Id']] = false;
1029
- } else {
1030
  content.classList.remove('hidden');
1031
- icon.style.transform = 'rotate(180deg)';
1032
- accordionStates[solution['Category_Id']] = true;
1033
- }
1034
  });
1035
 
 
 
 
 
 
 
1036
  // Événements de navigation pour cette catégorie spécifique
1037
  header.querySelector('.version-btn-left')?.addEventListener('click', (e) => {
1038
  e.stopPropagation();
@@ -1080,8 +1214,21 @@ function initializeSolutionAccordion(solutionCriticizedHistory, containerId, sta
1080
  document.getElementById(containerId).classList.remove('hidden')
1081
  }
1082
 
1083
- async function generateSolutions() {
1084
- let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/search_solutions_gemini', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(categorizedRequirements) })
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  let responseObj = await response.json()
1086
  return responseObj;
1087
  }
@@ -1101,9 +1248,24 @@ async function refineSolutions(critiques) {
1101
  async function workflow(steps = 1) {
1102
  let soluce;
1103
  showLoadingOverlay('Génération des solutions & critiques ....');
 
 
 
 
 
 
 
 
 
1104
  for (let step = 1; step <= steps; step++) {
 
 
 
 
 
 
1105
  if (solutionsCriticizedVersions.length == 0) {
1106
- soluce = await generateSolutions();
1107
  await generateCriticisms(soluce)
1108
  } else {
1109
  let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
@@ -1142,7 +1304,15 @@ document.addEventListener('DOMContentLoaded', function () {
1142
  });
1143
  });
1144
 
1145
-
1146
  // dseactiver le choix du nb de catégories lorsqu'en mode auto
1147
  document.getElementById('auto-detect-toggle').addEventListener('change', (ev) => { debounceAutoCategoryCount(ev.target.checked) });
1148
- debounceAutoCategoryCount(true);
 
 
 
 
 
 
 
 
 
 
1
+ // ==================================== Variables globales ========================================
2
  let requirements = [];
3
+
4
+ // Filtres
5
  let selectedType = ""; // "" = Tous
6
  let selectedStatus = new Set(); // valeurs cochées (hors "Tous")
7
  let selectedAgenda = new Set();
8
+
9
+ // Generation de solutions
10
  let accordionStates = {};
11
  let formattedRequirements = [];
12
  let categorizedRequirements = [];
13
  let solutionsCriticizedVersions = [];
14
+ // checksum pour vérifier si les requirements séléctionnés ont changé
15
+ let lastSelectedRequirementsChecksum = null;
16
+ // les requirements ont ils été extraits au moins une fois ?
17
+ let hasRequirementsExtracted = false;
18
 
19
  // =============================================================================
20
  // FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS
 
351
  // 'data-table-info-container'
352
  ], true);
353
 
354
+ hasRequirementsExtracted = false;
355
  } catch (error) {
356
  console.error('Erreur lors de la récupération des TDocs:', error);
357
  alert('Erreur lors de la récupération des TDocs');
 
547
  if (docType && docType !== 'Tous') {
548
  filename = `${docType}_${filename}`;
549
  }
550
+ if (hasRequirementsExtracted) {
551
  filename = `requirements_${filename}`;
552
  }
553
 
 
615
  // set the number of fetched requirements
616
  document.getElementById('requirements-tab-badge').innerText = requirements.length;
617
 
618
+ hasRequirementsExtracted = true;
619
  } catch (error) {
620
  console.error('Erreur lors de l\'extraction des requirements:', error);
621
  alert('Erreur lors de l\'extraction des requirements');
 
659
  alert('Aucun requirement à catégoriser');
660
  return;
661
  }
 
662
 
663
  showLoadingOverlay('Catégorisation des requirements en cours...');
664
  toggleElementsEnabled(['categorize-requirements-btn'], false);
 
673
  const data = await response.json();
674
  categorizedRequirements = data;
675
  displayCategorizedRequirements(categorizedRequirements.categories);
676
+ clearAllSolutions();
677
 
678
  // Masquer le container de query et afficher les catégories et boutons solutions
679
  // toggleContainersVisibility(['query-requirements-container'], false);
 
699
  categorizedData.forEach((category, categoryIndex) => {
700
  const categoryDiv = document.createElement('div');
701
  categoryDiv.tabIndex = 0;
702
+ categoryDiv.className = 'collapse collapse-arrow mb-2 border border-gray-200 rounded-lg bg-white';
703
 
 
704
 
705
+ // Generate unique IDs for checkboxes
706
+ const globalCheckboxId = `global-checkbox-${categoryIndex}`;
707
+
708
+ const requirementsHTML = category.requirements.map((req, reqIndex) => {
709
+ const checkboxId = `checkbox-${categoryIndex}-${reqIndex}`;
710
+ return `
711
+ <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}">
712
+ <input type="checkbox" class="item-checkbox" id="${checkboxId}" data-category-index="${categoryIndex}" />
713
+ <label for="${checkboxId}" class="flex-1">
714
  <div class="text-sm font-medium text-gray-700">${req.document}</div>
715
  <div class="text-sm text-gray-600">${req.requirement}</div>
716
+ </label>
717
+ </div>`;
718
+ }).join('');
719
+
720
+ categoryDiv.innerHTML = `
721
+ <div class="collapse-title font-semibold flex items-center gap-2">
722
+ <input type="checkbox" class="global-checkbox" id="${globalCheckboxId}" data-category-index="${categoryIndex}" />
723
+ <label for="${globalCheckboxId}" class="text-lg font-semibold text-blue-600">${category.title}</label>
724
  </div>
725
+ <input type="checkbox" />
726
+ <div class="collapse-content text-sm">
727
+ <div class="space-y-2">
728
+ ${requirementsHTML}
729
+ </div>
730
+ </div>
731
  `;
732
 
733
  container.appendChild(categoryDiv);
734
  });
735
+
736
+ // Event delegation to handle global checkbox logic
737
+ container.querySelectorAll('.global-checkbox').forEach(globalCheckbox => {
738
+ globalCheckbox.addEventListener('change', (e) => {
739
+ const categoryIndex = e.target.dataset.categoryIndex;
740
+ const itemCheckboxes = container.querySelectorAll(`.item-checkbox[data-category-index="${categoryIndex}"]`);
741
+ itemCheckboxes.forEach(checkbox => {
742
+ checkbox.checked = e.target.checked;
743
+ });
744
+ });
745
+ });
746
+
747
+ // Update global checkbox state when individual checkboxes change
748
+ container.querySelectorAll('.item-checkbox').forEach(itemCheckbox => {
749
+ itemCheckbox.addEventListener('change', (e) => {
750
+ const categoryIndex = e.target.dataset.categoryIndex;
751
+ const itemCheckboxes = container.querySelectorAll(`.item-checkbox[data-category-index="${categoryIndex}"]`);
752
+ const globalCheckbox = container.querySelector(`.global-checkbox[data-category-index="${categoryIndex}"]`);
753
+
754
+ const allChecked = Array.from(itemCheckboxes).every(cb => cb.checked);
755
+ globalCheckbox.checked = allChecked;
756
+ });
757
+ });
758
+ }
759
+
760
+ /*
761
+ * Copie les requirements séléctionnés en markdown
762
+ */
763
+ function copySelectedRequirementsAsMarkdown() {
764
+ const selected = getSelectedRequirementsByCategory();
765
+
766
+ if (!selected || !selected.categories || selected.categories.length === 0) {
767
+ alert("No selected requirements to copy.");
768
+ return;
769
+ }
770
+
771
+ const lines = [];
772
+
773
+ selected.categories.forEach(category => {
774
+ lines.push(`### ${category.title}`);
775
+ category.requirements.forEach(req => {
776
+ lines.push(`- ${req.requirement}`);
777
+ });
778
+ lines.push(''); // Add an empty line after each category
779
+ });
780
+
781
+ const markdownText = lines.join('\n');
782
+
783
+ navigator.clipboard.writeText(markdownText).then(() => {
784
+ console.log("Markdown copied to clipboard.");
785
+ alert("Selected requirements copied to clipboard");
786
+ }).catch(err => {
787
+ console.error("Failed to copy markdown:", err);
788
+ });
789
+ }
790
+
791
+ /*
792
+ * Recupère tous les requirements séléctionnés par catégorie dans l'interface.
793
+ */
794
+ function getSelectedRequirementsByCategory() {
795
+ const container = document.getElementById('categorized-requirements-list');
796
+ const selected_category_ids = [];
797
+
798
+ const categoryDivs = container.querySelectorAll('.collapse');
799
+
800
+ categoryDivs.forEach((categoryDiv, categoryIndex) => {
801
+ // Find all checked item checkboxes within this category
802
+ const checkedItems = categoryDiv.querySelectorAll(`.item-checkbox[data-category-index="${categoryIndex}"]:checked`);
803
+
804
+ if (checkedItems.length > 0) {
805
+ // Extract requirement indexes from their parent div's data attribute
806
+ const checkedReqIndexes = Array.from(checkedItems).map(checkbox => {
807
+ const itemDiv = checkbox.closest('[data-cat-req-id]');
808
+ return parseInt(itemDiv.dataset.catReqId, 10);
809
+ });
810
+
811
+ selected_category_ids.push({
812
+ categoryIndex,
813
+ checkedReqIndexes
814
+ });
815
+ }
816
+ });
817
+
818
+ /// Compute a checksum to check if checked requirements changed between two generations of solutions.
819
+ let totalChecksum = 0;
820
+
821
+ for (const { categoryIndex, checkedReqIndexes } of selected_category_ids) {
822
+ const catChecksum = checkedReqIndexes.reduce(
823
+ (sum, val, i) => sum + (val + 1) * (i + 1) ** 2,
824
+ 0
825
+ );
826
+ totalChecksum += (categoryIndex + 1) * catChecksum; // include category index for entropy
827
+ }
828
+
829
+ /// Reconstruct the schema based on the selected ids.
830
+ let selected_categories = {
831
+ categories: selected_category_ids.map(({ categoryIndex, checkedReqIndexes }) => {
832
+ const category = categorizedRequirements.categories[categoryIndex];
833
+ const requirements = checkedReqIndexes.map(i => category.requirements[i]);
834
+ return {
835
+ id: categoryIndex,
836
+ title: category.title,
837
+ requirements,
838
+ };
839
+ }),
840
+ requirements_checksum: totalChecksum,
841
+ };
842
+
843
+ return selected_categories;
844
  }
845
 
846
  async function searchRequirements() {
 
951
  const criticism = item.criticism;
952
 
953
  // Récupérer le titre de la catégorie
954
+ const categoryTitle = categorizedRequirements.categories.find(c => c.id == solution['Category_Id']).title;
955
+ // const categoryTitle = document.querySelector(`#category-${solution['Category_Id']} h2`)?.textContent || `Catégorie ${solution['Category_Id'] + 1}`;
956
 
957
  // Container pour chaque solution
958
  const solutionCard = document.createElement('div');
959
+ solutionCard.className = 'border border-gray-200 rounded-md shadow-sm solution-accordion';
960
  solutionCard.id = `accordion-item-${index}`;
961
 
962
  // En-tête de l'accordéon avec navigation
963
  const header = document.createElement('div');
964
  header.className = 'bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors duration-200';
965
+ solutionCard.setAttribute('solution-accordion-id', `${index}`)
966
 
967
  const currentVersion = versionIndex + 1;
968
  const totalVersions = solutionCriticizedHistory.length;
969
 
970
  header.innerHTML = `
971
+ <div class="flex justify-between items-center">
972
+ <div class="flex items-center space-x-3">
973
+ <h3 class="text-sm font-semibold text-gray-800">${categoryTitle}</h3>
974
+ <div class="flex items-center space-x-2 bg-white px-3 py-1 rounded-full border">
975
+ <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' : ''}"
976
+ data-solution-index="${solution['Category_Id']}"
977
+ ${currentVersion === 1 ? 'disabled' : ''}>
978
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
979
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
980
+ </svg>
981
+ </button>
982
+ <span class="text-xs font-medium text-gray-600 min-w-[60px] text-center version-indicator">Version ${currentVersion}</span>
983
+ <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' : ''}"
984
+ data-solution-index="${solution['Category_Id']}"
985
+ ${currentVersion === totalVersions ? 'disabled' : ''}>
986
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
987
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
988
+ </svg>
989
+ </button>
 
990
  </div>
 
 
 
991
  </div>
992
+ <!--
993
+ <button class="delete-btn text-red-500 hover:text-red-700 transition-colors"
994
+ data-solution-index="${solution['Category_Id']}" id="solution-delete-btn">
995
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
996
+ <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" />
997
+ </svg>
998
+ </button>
999
+ -->
1000
+ </div>`;
1001
+
1002
 
1003
  // Contenu de l'accordéon
1004
  const content = document.createElement('div');
 
1007
 
1008
  // Vérifier l'état d'ouverture précédent
1009
  const isOpen = accordionStates[solution['Category_Id']] || false;
1010
+ console.log(isOpen);
1011
+ if (!isOpen)
1012
  content.classList.add('hidden');
 
 
 
1013
 
1014
  // Section Problem Description
1015
  const problemSection = document.createElement('div');
 
1148
  // Événement de clic pour l'accordéon (exclure les boutons de navigation)
1149
  header.addEventListener('click', (e) => {
1150
  // Ne pas déclencher l'accordéon si on clique sur les boutons de navigation
1151
+ if (e.target.closest('.version-btn-left') || e.target.closest('.version-btn-right') || e.target.closest('#solution-delete-btn')) {
1152
  return;
1153
  }
1154
 
1155
+ // handling open state
1156
  const isCurrentlyOpen = !content.classList.contains('hidden');
1157
+ accordionStates[solution['Category_Id']] = isCurrentlyOpen;
1158
+ if (isCurrentlyOpen)
1159
  content.classList.add('hidden');
1160
+ else
 
 
1161
  content.classList.remove('hidden');
 
 
 
1162
  });
1163
 
1164
+ // Delete solution accordion button
1165
+ // header.querySelector('#solution-delete-btn')?.addEventListener('click', (e) => {
1166
+ // e.stopPropagation();
1167
+ // solutionCard.remove();
1168
+ // });
1169
+
1170
  // Événements de navigation pour cette catégorie spécifique
1171
  header.querySelector('.version-btn-left')?.addEventListener('click', (e) => {
1172
  e.stopPropagation();
 
1214
  document.getElementById(containerId).classList.remove('hidden')
1215
  }
1216
 
1217
+ // Supprime toutes les accordéons de solutions générées
1218
+ //FIXME: À terme, ne devrait pas exister
1219
+ function clearAllSolutions() {
1220
+ accordionStates = {}
1221
+ solutionsCriticizedVersions = []
1222
+ document.querySelectorAll('.solution-accordion').forEach(a => a.remove());
1223
+ }
1224
+
1225
+ async function generateSolutions(selected_categories, user_constraints) {
1226
+ console.log(selected_categories);
1227
+
1228
+ let input_req = structuredClone(selected_categories);
1229
+ input_req.user_constraints = user_constraints;
1230
+
1231
+ 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) })
1232
  let responseObj = await response.json()
1233
  return responseObj;
1234
  }
 
1248
  async function workflow(steps = 1) {
1249
  let soluce;
1250
  showLoadingOverlay('Génération des solutions & critiques ....');
1251
+
1252
+ const selected_requirements = getSelectedRequirementsByCategory();
1253
+ const user_constraints = document.getElementById('additional-gen-instr').value;
1254
+
1255
+ console.log(user_constraints);
1256
+
1257
+ // check if the selected requirements changed since last workflow usage
1258
+ const requirements_changed = selected_requirements.requirements_checksum != (lastSelectedRequirementsChecksum ?? -1);
1259
+
1260
  for (let step = 1; step <= steps; step++) {
1261
+ if (requirements_changed) {
1262
+ clearAllSolutions();
1263
+ console.log("Requirements checksum changed. Cleaning up");
1264
+ lastSelectedRequirementsChecksum = selected_requirements.requirements_checksum;
1265
+ }
1266
+
1267
  if (solutionsCriticizedVersions.length == 0) {
1268
+ soluce = await generateSolutions(selected_requirements, user_constraints ? user_constraints : null);
1269
  await generateCriticisms(soluce)
1270
  } else {
1271
  let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
 
1304
  });
1305
  });
1306
 
 
1307
  // dseactiver le choix du nb de catégories lorsqu'en mode auto
1308
  document.getElementById('auto-detect-toggle').addEventListener('change', (ev) => { debounceAutoCategoryCount(ev.target.checked) });
1309
+ debounceAutoCategoryCount(true);
1310
+
1311
+ // focus l'input d'instructions de gen additionelles
1312
+ document.getElementById("additional-gen-instr-btn").addEventListener('click', (ev) => {
1313
+ document.getElementById('additional-gen-instr').focus()
1314
+ })
1315
+
1316
+ document.getElementById('copy-reqs-btn').addEventListener('click', (ev) => {
1317
+ copySelectedRequirementsAsMarkdown();
1318
+ });