Lucas ARRIESSE commited on
Commit
50d44e1
·
1 Parent(s): ed24fa8

UI work + add back sources logging

Browse files
Files changed (4) hide show
  1. app.py +8 -1
  2. index.html +145 -87
  3. schemas.py +2 -2
  4. static/script.js +207 -79
app.py CHANGED
@@ -291,8 +291,11 @@ def get_change_request_dataframe(req: DataRequest):
291
 
292
  @app.post("/download_tdocs")
293
  def download_tdocs(req: DownloadRequest):
 
294
  documents = req.documents
295
 
 
 
296
  def process_document(doc: str):
297
  doc_id = doc
298
  url = requests.post(
@@ -337,10 +340,13 @@ def download_tdocs(req: DownloadRequest):
337
 
338
  @app.post("/generate_requirements", response_model=RequirementsResponse)
339
  async def gen_reqs(req: RequirementsRequest, background_tasks: BackgroundTasks):
 
 
340
  documents = req.documents
341
  n_docs = len(documents)
342
 
343
- logging.info("Generating requirements for documents: {}".format([doc.document for doc in documents]))
 
344
 
345
  def prompt(doc_id, full):
346
  return f"Here's the document whose ID is {doc_id} : {full}\n\nExtract all requirements and group them by context, returning a list of objects where each object includes a document ID, a concise description of the context where the requirements apply (not a chapter title or copied text), and a list of associated requirements; always return the result as a list, even if only one context is found. Remove the errors"
@@ -424,6 +430,7 @@ def find_requirements_from_problem_description(req: ReqSearchRequest):
424
 
425
  out_llm = ReqSearchLLMResponse.model_validate_json(
426
  resp_ai.choices[0].message.content).selected
 
427
  if max(out_llm) > len(requirements) - 1:
428
  raise HTTPException(
429
  status_code=500, detail="LLM error : Generated a wrong index, please try again.")
 
291
 
292
  @app.post("/download_tdocs")
293
  def download_tdocs(req: DownloadRequest):
294
+ """Download the specified TDocs and zips them in a single archive"""
295
  documents = req.documents
296
 
297
+ logging.info(f"Downloading TDocs: {documents}")
298
+
299
  def process_document(doc: str):
300
  doc_id = doc
301
  url = requests.post(
 
340
 
341
  @app.post("/generate_requirements", response_model=RequirementsResponse)
342
  async def gen_reqs(req: RequirementsRequest, background_tasks: BackgroundTasks):
343
+ """Extract requirements from the specified TDocs using a LLM"""
344
+
345
  documents = req.documents
346
  n_docs = len(documents)
347
 
348
+ logging.info("Generating requirements for documents: {}".format(
349
+ [doc.document for doc in documents]))
350
 
351
  def prompt(doc_id, full):
352
  return f"Here's the document whose ID is {doc_id} : {full}\n\nExtract all requirements and group them by context, returning a list of objects where each object includes a document ID, a concise description of the context where the requirements apply (not a chapter title or copied text), and a list of associated requirements; always return the result as a list, even if only one context is found. Remove the errors"
 
430
 
431
  out_llm = ReqSearchLLMResponse.model_validate_json(
432
  resp_ai.choices[0].message.content).selected
433
+
434
  if max(out_llm) > len(requirements) - 1:
435
  raise HTTPException(
436
  status_code=500, detail="LLM error : Generated a wrong index, please try again.")
index.html CHANGED
@@ -5,13 +5,13 @@
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Requirements Extractor</title>
8
- <link href="https://cdnjs.cloudflare.com/ajax/libs/daisyui/5.0.43/daisyui.css" rel="stylesheet" type="text/css" />
9
- <script src="https://cdn.tailwindcss.com"></script>
10
  </head>
11
 
12
  <body class="bg-gray-100 min-h-screen">
13
  <!-- Loading Overlay -->
14
- <div id="loading-overlay" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
15
  <div class="bg-white p-6 rounded-lg shadow-lg text-center">
16
  <div id="loading-bar" class="w-64 h-4 bg-gray-200 rounded-full mb-4">
17
  <div class="h-full bg-blue-500 rounded-full animate-pulse"></div>
@@ -96,6 +96,7 @@
96
  <div id="status-options" class="flex flex-col gap-1"></div>
97
  </ul>
98
  </div>
 
99
  <!-- Agenda Item Filter Dropdown -->
100
  <div class="dropdown dropdown-bottom dropdown-center w-full mb-4">
101
  <div tabindex="0" role="button" class="btn w-full justify-between" id="agenda-dropdown-btn">
@@ -119,104 +120,161 @@
119
  </div>
120
  </div>
121
 
122
- <!-- Action Buttons -->
123
- <div id="action-buttons-container" class="mb-6 hidden">
124
- <div class="flex flex-wrap gap-2">
125
- <button id="download-tdocs-btn"
126
- class="px-4 py-2 bg-purple-500 text-white rounded-md hover:bg-purple-600">
127
- Download TDocs
128
- </button>
129
- <button id="extract-requirements-btn"
130
- class="px-4 py-2 bg-orange-500 text-white rounded-md hover:bg-orange-600">
131
- Extract Requirements
132
- </button>
133
- <button id="find-requirements-btn"
134
- class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 hidden">
135
- Find Requirements
136
- </button>
137
- <button id="categorize-requirements-btn"
138
- class="px-4 py-2 bg-yellow-500 text-white rounded-md hover:bg-yellow-600 hidden">
139
- Categorize Requirements
140
- </button>
141
- </div>
142
- </div>
143
- <!-- Data Table Informations -->
144
- <div class="flex justify-end mb-2 gap-2 items-center">
145
- <span id="displayed-count" class="text-sm text-gray-700 bg-white rounded px-3 py-1 shadow">
146
- 0 document affiché
147
- </span>
148
- <span id="selected-count" class="text-sm text-blue-700 bg-blue-50 rounded px-3 py-1 shadow">
149
- 0 document sélectionné
150
- </span>
151
  </div>
152
- <!-- Data Table -->
153
- <div id="data-table-container" class="mb-6 hidden">
154
- <table id="data-table" class="w-full bg-white rounded-lg shadow overflow-hidden">
155
- <thead class="bg-gray-50">
156
- <tr>
157
- <th class="px-4 py-2 text-left">
158
- <input type="checkbox" id="select-all-checkbox">
159
- </th>
160
- <th class="px-4 py-2 text-left">TDoc</th>
161
- <th class="px-4 py-2 text-left">Title</th>
162
- <th class="px-4 py-2 text-left">Type</th>
163
- <th class="px-4 py-2 text-left">Status</th>
164
- <th class="px-4 py-2 text-left">Agenda Item</th>
165
- <th class="px-4 py-2 text-left">URL</th>
166
- </tr>
167
- </thead>
168
- <tbody></tbody>
169
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  </div>
171
 
172
- <!-- Requirements Container -->
173
- <div id="requirements-container" class="mb-6 hidden">
174
- <h2 class="text-2xl font-bold mb-4">Requirements extracted</h2>
175
- <div id="requirements-list"></div>
 
 
176
  </div>
177
 
 
178
  <!-- Query Requirements -->
179
- <div id="query-requirements-container" class="mb-6 hidden">
180
- <h2 class="text-2xl font-bold mb-4">Query requirements</h2>
181
- <div class="flex gap-2">
182
- <input type="text" id="query-input" class="flex-1 p-2 border border-gray-300 rounded-md"
183
- placeholder="Enter your query...">
184
- <button id="search-requirements-btn"
185
- class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
186
- Search
187
- </button>
 
 
 
188
  </div>
189
- <div id="query-results" class="mt-4"></div>
190
  </div>
191
 
192
- <!-- Categorized Requirements Container -->
193
- <div id="categorized-requirements-container" class="mb-6 hidden">
194
- <h2 class="text-2xl font-bold mb-4">Requirements categories list</h2>
195
- <div id="categorized-requirements-list"></div>
196
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
- <!-- Solutions Action Buttons -->
199
- <div id="solutions-action-buttons-container" class="mb-6 hidden">
200
- <div class="flex flex-wrap gap-2 justify-center">
201
- <button id="get-solutions-btn"
202
- class="px-4 py-2 bg-indigo-500 text-white rounded-md hover:bg-indigo-600">
203
- Get Solutions
204
- </button>
205
- <button id="get-solutions-step-btn"
206
- class="px-4 py-2 bg-pink-500 text-white rounded-md hover:bg-pink-600">
207
- Get Solutions (Step-by-step)
208
- </button>
 
209
  </div>
210
- </div>
211
 
212
- <!-- Solutions Container -->
213
- <div id="solutions-container" class="mb-6 hidden">
214
- <h2 class="text-2xl font-bold mb-4">Solutions</h2>
215
- <div id="solutions-list"></div>
 
216
  </div>
217
- </div>
218
 
219
- <script src="/static/script.js"></script>
220
  </body>
221
 
222
  </html>
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Requirements Extractor</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
9
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
10
  </head>
11
 
12
  <body class="bg-gray-100 min-h-screen">
13
  <!-- Loading Overlay -->
14
+ <div id="loading-overlay" class="fixed inset-0 bg-black opacity-50 flex items-center justify-center z-50 hidden">
15
  <div class="bg-white p-6 rounded-lg shadow-lg text-center">
16
  <div id="loading-bar" class="w-64 h-4 bg-gray-200 rounded-full mb-4">
17
  <div class="h-full bg-blue-500 rounded-full animate-pulse"></div>
 
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">
 
120
  </div>
121
  </div>
122
 
123
+ <!-- Add an horizontal separation -->
124
+ <hr>
125
+ <!-- Tab list for subsections -->
126
+ <div role="tablist" class="tabs tabs-border" id="tab-container">
127
+ <a role="tab" class="tab tab-active" id="doc-table-tab" onclick="switchTab('doc-table-tab')">📝
128
+ Documents</a>
129
+ <a role="tab" class="tab tab-disabled" id="requirements-tab" onclick="switchTab('requirements-tab')">
130
+ <div class="flex items-center gap-1">
131
+ <div class="badge badge-neutral badge-outline badge-xs" id="requirements-tab-badge">0</div>
132
+ <span>Requirements</span>
133
+ </div>
134
+ </a>
135
+ <a role="tab" class="tab tab-disabled" id="solutions-tab" onclick="switchTab('solutions-tab')">Group and
136
+ Solve</a>
137
+ <a role="tab" class="tab tab-disabled" id="query-tab" onclick="switchTab('query-tab')">🔎 Find relevant
138
+ requirements</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
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">
147
+ <button id="extract-requirements-btn"
148
+ class="bg-orange-300 text-white text-sm rounded px-3 py-1 shadow hover:bg-orange-600">
149
+ <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true"
150
+ xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
151
+ viewBox="0 0 24 24">
152
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
153
+ stroke-width="2"
154
+ d="M9 8h6m-6 4h6m-6 4h6M6 3v18l2-2 2 2 2-2 2 2 2-2 2 2V3l-2 2-2-2-2 2-2-2-2 2-2-2Z" />
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>
162
+
163
+ <!-- Right side: document counts -->
164
+ <div class="flex gap-2 items-center">
165
+ <span id="displayed-count" class="text-sm text-gray-700 bg-white rounded px-3 py-1 shadow">
166
+ 0 document affiché
167
+ </span>
168
+ <span id="selected-count" class="text-sm text-blue-700 bg-blue-50 rounded px-3 py-1 shadow">
169
+ 0 document sélectionné
170
+ </span>
171
+ </div>
172
+ </div>
173
+
174
+
175
+ <!-- Data Table -->
176
+ <div id="data-table-container" class="mb-6">
177
+ <table id="data-table" class="w-full bg-white rounded-lg shadow overflow-hidden">
178
+ <thead class="bg-gray-50">
179
+ <tr>
180
+ <th class="px-4 py-2 text-left">
181
+ <input type="checkbox" id="select-all-checkbox">
182
+ </th>
183
+ <th class="px-4 py-2 text-left">TDoc</th>
184
+ <th class="px-4 py-2 text-left">Title</th>
185
+ <th class="px-4 py-2 text-left">Type</th>
186
+ <th class="px-4 py-2 text-left">Status</th>
187
+ <th class="px-4 py-2 text-left">Agenda Item</th>
188
+ <th class="px-4 py-2 text-left">URL</th>
189
+ </tr>
190
+ </thead>
191
+ <tbody></tbody>
192
+ </table>
193
+ </div>
194
  </div>
195
 
196
+ <div id="requirements-tab-contents" class="hidden pt-10">
197
+ <!-- Requirement list container -->
198
+ <div id="requirements-container" class="mb-6">
199
+ <h2 class="text-2xl font-bold mb-4">Extracted requirement list</h2>
200
+ <div id="requirements-list"></div>
201
+ </div>
202
  </div>
203
 
204
+
205
  <!-- Query Requirements -->
206
+ <div id="query-tab-contents" class="hidden pt-10">
207
+ <div id="query-requirements-container" class="mb-6">
208
+ <h2 class="text-2xl font-bold mb-4">Find relevant requirements for a query</h2>
209
+ <div class="flex gap-2">
210
+ <input type="text" id="query-input" class="flex-1 p-2 border border-gray-300 rounded-md"
211
+ placeholder="Enter your query...">
212
+ <button id="search-requirements-btn"
213
+ class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
214
+ Search
215
+ </button>
216
+ </div>
217
+ <div id="query-results" class="mt-4"></div>
218
  </div>
 
219
  </div>
220
 
221
+ <!-- Solution tab -->
222
+ <div id="solutions-tab-contents" class="hidden pt-10">
223
+ <!--Header de catégorisation des requirements-->
224
+ <div class="w-full mx-auto mt-4 p-4 bg-base-100 rounded-box shadow-lg border border-base-200">
225
+ <div class="flex flex-wrap justify-begin items-center gap-6">
226
+
227
+ <!-- Number input -->
228
+ <div class="form-control flex flex-col items-center justify-center">
229
+ <label class="label" for="category-count">
230
+ <span class="label-text text-base-content"># Categories</span>
231
+ </label>
232
+ <input id="category-count" type="number" class="input input-bordered w-24" min="1" value="3" />
233
+ </div>
234
+
235
+ <!-- Auto detect toggle -->
236
+ <div class="form-control flex flex-col items-center justify-center">
237
+ <label class="label">
238
+ <span class="label-text text-base-content">Number of categories</span>
239
+ </label>
240
+ <input type="checkbox" class="toggle toggle-primary" id="auto-detect-toggle"
241
+ onchange="toggleAutoDetect(this)" />
242
+ <div class="text-xs mt-1 text-base-content">Manual | Auto detect</div>
243
+ </div>
244
+
245
+ <!-- Action Button -->
246
+ <button id="categorize-requirements-btn" class="btn btn-primary btn-l self-center">
247
+ Categorize
248
+ </button>
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 -->
271
+ <div id="solutions-container" class="mb-6">
272
+ <h2 class="text-2xl font-bold mb-4">Solutions</h2>
273
+ <div id="solutions-list"></div>
274
+ </div>
275
  </div>
 
276
 
277
+ <script src="/static/script.js"></script>
278
  </body>
279
 
280
  </html>
schemas.py CHANGED
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel
2
  from typing import Any, List, Dict, Optional
3
 
4
  class MeetingsRequest(BaseModel):
@@ -51,4 +51,4 @@ class ReqSearchResponse(BaseModel):
51
  # --------------------------------------
52
 
53
  class DownloadRequest(BaseModel):
54
- documents: List[str]
 
1
+ from pydantic import BaseModel, Field
2
  from typing import Any, List, Dict, Optional
3
 
4
  class MeetingsRequest(BaseModel):
 
51
  # --------------------------------------
52
 
53
  class DownloadRequest(BaseModel):
54
+ documents: List[str] = Field(description="List of document IDs to download")
static/script.js CHANGED
@@ -98,7 +98,7 @@ function populateCheckboxDropdown(optionsContainerId, options, filterType, label
98
  <input type="checkbox" class="${filterType}-checkbox option-checkbox" id="${safeId}" value="${option}">
99
  <span>${option}</span>
100
  `;
101
- label.querySelector('input').addEventListener('change', function() {
102
  if (this.checked) {
103
  selectionSet.add(this.value);
104
  } else {
@@ -122,7 +122,7 @@ function populateCheckboxDropdown(optionsContainerId, options, filterType, label
122
  // Gestion de "Tous"
123
  const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`);
124
  if (allBox) {
125
- allBox.addEventListener('change', function() {
126
  if (this.checked) {
127
  // Décoche tout le reste
128
  selectionSet.clear();
@@ -147,11 +147,11 @@ function updateCheckboxDropdownLabel(type, labelId, set, totalCount) {
147
  }
148
 
149
  function updateSelectedFilters(filterType, value, isChecked) {
150
- if (isChecked) {
151
- selectedFilters[filterType].add(value);
152
- } else {
153
- selectedFilters[filterType].delete(value);
154
- }
155
  }
156
 
157
  function populateDaisyDropdown(menuId, options, labelId, onSelect) {
@@ -181,14 +181,14 @@ function populateDaisyDropdown(menuId, options, labelId, onSelect) {
181
  }
182
 
183
  function updateFilterLabel(filterType) {
184
- const selectedCount = selectedFilters[filterType].size;
185
- const labelElement = document.getElementById(`${filterType}-filter-label`);
186
-
187
- if (selectedCount === 0) {
188
- labelElement.textContent = `${filterType} (Tous)`;
189
- } else {
190
- labelElement.textContent = `${filterType} (${selectedCount} sélectionné${selectedCount > 1 ? 's' : ''})`;
191
- }
192
  }
193
 
194
  /**
@@ -208,7 +208,7 @@ function extractTableData(mapping) {
208
  Object.entries(mapping).forEach(([columnName, propertyName]) => {
209
  const cell = row.querySelector(`td[data-column="${columnName}"]`);
210
  if (cell) {
211
- if(columnName == "URL") {
212
  rowData[propertyName] = cell.querySelector('a').getAttribute('href');
213
  } else {
214
  rowData[propertyName] = cell.textContent.trim();
@@ -222,6 +222,64 @@ function extractTableData(mapping) {
222
  return data;
223
  }
224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  // =============================================================================
226
  // FONCTIONS MÉTIER
227
  // =============================================================================
@@ -261,7 +319,7 @@ async function getMeetings() {
261
  async function getTDocs() {
262
  const workingGroup = document.getElementById('working-group-select').value;
263
  const meeting = document.getElementById('meeting-select').value;
264
-
265
  if (!workingGroup || !meeting) return;
266
 
267
  showLoadingOverlay('Récupération de la liste des TDocs...');
@@ -277,13 +335,15 @@ async function getTDocs() {
277
  const data = await response.json();
278
  populateDataTable(data.data);
279
  setupFilters(data.data);
280
-
281
  toggleContainersVisibility([
282
  'filters-container',
283
  'action-buttons-container',
284
- 'data-table-container'
 
 
285
  ], true);
286
-
287
  isRequirements = false;
288
  } catch (error) {
289
  console.error('Erreur lors de la récupération des TDocs:', error);
@@ -406,7 +466,7 @@ function applyFilters() {
406
  * Configure les événements du tableau
407
  */
408
  function setupTableEvents() {
409
- document.getElementById('select-all-checkbox').addEventListener('change', function() {
410
  const checkboxes = document.querySelectorAll('.row-checkbox');
411
  checkboxes.forEach(checkbox => {
412
  // Ne coche que les visibles
@@ -466,7 +526,7 @@ async function downloadTDocs() {
466
  */
467
  function generateDownloadFilename() {
468
  let filename = document.getElementById('meeting-select').value || 'documents';
469
-
470
  const agendaItem = document.getElementById('agenda-item-filter').value;
471
  const docStatus = document.getElementById('doc-status-filter').value;
472
  const docType = document.getElementById('doc-type-filter').value;
@@ -527,21 +587,27 @@ async function extractRequirements() {
527
  requirements = data.requirements;
528
  let req_id = 0;
529
  data.requirements.forEach(obj => {
530
- obj.requirements.forEach(req => {
531
- formattedRequirements.push({
532
- req_id,
533
- "document": obj.document,
534
- "context": obj.context,
535
- "requirement": req
536
- })
537
- req_id++;
538
  })
 
539
  })
 
540
  displayRequirements(requirements);
541
-
542
  toggleContainersVisibility(['requirements-container', 'query-requirements-container'], true);
543
- toggleContainersVisibility(['find-requirements-btn', 'categorize-requirements-btn', 'get-solutions-btn', 'get-solutions-step-btn'], true);
544
-
 
 
 
 
 
 
545
  isRequirements = true;
546
  } catch (error) {
547
  console.error('Erreur lors de l\'extraction des requirements:', error);
@@ -563,17 +629,17 @@ function displayRequirements(requirementsData) {
563
  requirementsData.forEach((docReq, docIndex) => {
564
  const docDiv = document.createElement('div');
565
  docDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white';
566
-
567
  docDiv.innerHTML = `
568
  <h3 class="text-lg font-semibold mb-2">${docReq.document}</h3>
569
  <p class="text-gray-600 mb-3">${docReq.context}</p>
570
  <ul class="list-disc list-inside space-y-1">
571
- ${docReq.requirements.map((req, reqIndex) =>
572
- `<li class="text-sm" data-req-id="${docIndex}-${reqIndex}">${req}</li>`
573
- ).join('')}
574
  </ul>
575
  `;
576
-
577
  container.appendChild(docDiv);
578
  });
579
  }
@@ -586,6 +652,7 @@ async function categorizeRequirements(max_categories) {
586
  alert('Aucun requirement à catégoriser');
587
  return;
588
  }
 
589
 
590
  showLoadingOverlay('Catégorisation des requirements en cours...');
591
  toggleElementsEnabled(['categorize-requirements-btn'], false);
@@ -600,11 +667,11 @@ async function categorizeRequirements(max_categories) {
600
  const data = await response.json();
601
  categorizedRequirements = data;
602
  displayCategorizedRequirements(categorizedRequirements.categories);
603
-
604
  // Masquer le container de query et afficher les catégories et boutons solutions
605
- toggleContainersVisibility(['query-requirements-container'], false);
606
  toggleContainersVisibility(['categorized-requirements-container', 'solutions-action-buttons-container'], true);
607
-
608
  } catch (error) {
609
  console.error('Erreur lors de la catégorisation des requirements:', error);
610
  alert('Erreur lors de la catégorisation des requirements');
@@ -624,20 +691,26 @@ function displayCategorizedRequirements(categorizedData) {
624
 
625
  categorizedData.forEach((category, categoryIndex) => {
626
  const categoryDiv = document.createElement('div');
627
- categoryDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white';
628
-
 
 
629
  categoryDiv.innerHTML = `
 
630
  <h3 class="text-lg font-semibold mb-2 text-blue-600">${category.title}</h3>
 
 
631
  <div class="space-y-2">
632
- ${category.requirements.map((req, reqIndex) =>
633
- `<div class="p-2 bg-gray-50 rounded border-l-4 border-blue-400" data-cat-req-id="${categoryIndex}-${reqIndex}">
634
  <div class="text-sm font-medium text-gray-700">${req.document}</div>
635
  <div class="text-sm text-gray-600">${req.requirement}</div>
636
- </div>`
637
- ).join('')}
638
  </div>
 
639
  `;
640
-
641
  container.appendChild(categoryDiv);
642
  });
643
  }
@@ -662,7 +735,7 @@ async function searchRequirements() {
662
  const response = await fetch('/get_reqs_from_query', {
663
  method: 'POST',
664
  headers: { 'Content-Type': 'application/json' },
665
- body: JSON.stringify({
666
  query: query,
667
  requirements: formattedRequirements
668
  })
@@ -670,7 +743,7 @@ async function searchRequirements() {
670
 
671
  const data = await response.json();
672
  displaySearchResults(data.requirements);
673
-
674
  } catch (error) {
675
  console.error('Erreur lors de la recherche:', error);
676
  alert('Erreur lors de la recherche des requirements');
@@ -699,13 +772,13 @@ function displaySearchResults(results) {
699
  results.forEach((result, index) => {
700
  const resultDiv = document.createElement('div');
701
  resultDiv.className = 'p-3 bg-blue-50 border border-blue-200 rounded-lg';
702
-
703
  resultDiv.innerHTML = `
704
  <div class="text-sm font-medium text-blue-800">${result.document}</div>
705
  <div class="text-sm text-gray-600 mb-1">${result.context}</div>
706
  <div class="text-sm">${result.requirement}</div>
707
  `;
708
-
709
  resultsDiv.appendChild(resultDiv);
710
  });
711
 
@@ -760,10 +833,10 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
760
  // En-tête de l'accordéon avec navigation
761
  const header = document.createElement('div');
762
  header.className = 'bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors duration-200';
763
-
764
  const currentVersion = versionIndex + 1;
765
  const totalVersions = solutionCriticizedHistory.length;
766
-
767
  header.innerHTML = `
768
  <div class="flex justify-between items-center">
769
  <div class="flex items-center space-x-3">
@@ -834,7 +907,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
834
  // Section Critique
835
  const critiqueSection = document.createElement('div');
836
  critiqueSection.className = 'bg-yellow-50 border-l-2 border-yellow-400 p-3 rounded-r-md';
837
-
838
  let critiqueContent = `
839
  <h4 class="text-sm font-semibold text-yellow-800 mb-2 flex items-center">
840
  <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
@@ -880,10 +953,64 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
880
 
881
  critiqueSection.innerHTML = critiqueContent;
882
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
883
  // Ajouter les sections au contenu
884
  content.appendChild(problemSection);
885
  content.appendChild(solutionSection);
886
  content.appendChild(critiqueSection);
 
887
 
888
  // Événement de clic pour l'accordéon (exclure les boutons de navigation)
889
  header.addEventListener('click', (e) => {
@@ -891,7 +1018,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
891
  if (e.target.closest('.version-btn-left') || e.target.closest('.version-btn-right')) {
892
  return;
893
  }
894
-
895
  const icon = header.querySelector('.accordion-icon');
896
  const isCurrentlyOpen = !content.classList.contains('hidden');
897
 
@@ -934,13 +1061,13 @@ function updateSingleAccordion(solutionCriticizedHistory, containerId, newVersio
934
 
935
  const newData = solutionCriticizedHistory[newVersionIndex];
936
  const newItem = newData.critiques[categoryIndex];
937
-
938
  if (!newItem) return;
939
 
940
  // Mettre à jour le contenu de cette catégorie spécifique
941
  const tempContainer = document.createElement('div');
942
  createSingleAccordionItem(newItem, categoryIndex, newVersionIndex, solutionCriticizedHistory, tempContainer);
943
-
944
  // Remplacer l'ancien item par le nouveau
945
  accordionItem.parentNode.replaceChild(tempContainer.firstChild, accordionItem);
946
  }
@@ -953,29 +1080,29 @@ function initializeSolutionAccordion(solutionCriticizedHistory, containerId, sta
953
  document.getElementById(containerId).classList.remove('hidden')
954
  }
955
 
956
- async function generateSolutions(){
957
- 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)})
958
  let responseObj = await response.json()
959
  return responseObj;
960
  }
961
 
962
- async function generateCriticisms(solutions){
963
- let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/criticize_solution', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(solutions)})
964
  let responseObj = await response.json()
965
  solutionsCriticizedVersions.push(responseObj)
966
  }
967
 
968
- async function refineSolutions(critiques){
969
- let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/refine_solution', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(critiques)})
970
  let responseObj = await response.json()
971
  await generateCriticisms(responseObj)
972
  }
973
 
974
- async function workflow(steps = 1){
975
  let soluce;
976
  showLoadingOverlay('Génération des solutions & critiques ....');
977
- for(let step = 1; step <= steps; step++){
978
- if(solutionsCriticizedVersions.length == 0){
979
  soluce = await generateSolutions();
980
  await generateCriticisms(soluce)
981
  } else {
@@ -991,25 +1118,21 @@ async function workflow(steps = 1){
991
  // INITIALISATION DES ÉVÉNEMENTS
992
  // =============================================================================
993
 
994
- document.addEventListener('DOMContentLoaded', function() {
995
  // Événements des boutons principaux
996
  document.getElementById('get-meetings-btn').addEventListener('click', getMeetings);
997
  document.getElementById('get-tdocs-btn').addEventListener('click', getTDocs);
998
  document.getElementById('download-tdocs-btn').addEventListener('click', downloadTDocs);
999
  document.getElementById('extract-requirements-btn').addEventListener('click', extractRequirements);
1000
- document.getElementById('categorize-requirements-btn').addEventListener('click', ()=>{categorizeRequirements(8)});
1001
-
1002
- // Événement pour le bouton "Find Requirements"
1003
- document.getElementById('find-requirements-btn').addEventListener('click', function() {
1004
- // Afficher le container de requête
1005
- toggleContainersVisibility(['query-requirements-container'], true);
1006
- // Scroll vers le container de requête
1007
- document.getElementById('query-requirements-container').scrollIntoView({ behavior: 'smooth' });
1008
  });
1009
-
1010
  // Événement pour la recherche
1011
  document.getElementById('search-requirements-btn').addEventListener('click', searchRequirements);
1012
-
1013
  // Événements pour les boutons de solutions (à implémenter plus tard)
1014
  document.getElementById('get-solutions-btn').addEventListener('click', () => {
1015
  alert('Fonctionnalité à implémenter');
@@ -1017,4 +1140,9 @@ document.addEventListener('DOMContentLoaded', function() {
1017
  document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
1018
  workflow();
1019
  });
1020
- });
 
 
 
 
 
 
98
  <input type="checkbox" class="${filterType}-checkbox option-checkbox" id="${safeId}" value="${option}">
99
  <span>${option}</span>
100
  `;
101
+ label.querySelector('input').addEventListener('change', function () {
102
  if (this.checked) {
103
  selectionSet.add(this.value);
104
  } else {
 
122
  // Gestion de "Tous"
123
  const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`);
124
  if (allBox) {
125
+ allBox.addEventListener('change', function () {
126
  if (this.checked) {
127
  // Décoche tout le reste
128
  selectionSet.clear();
 
147
  }
148
 
149
  function updateSelectedFilters(filterType, value, isChecked) {
150
+ if (isChecked) {
151
+ selectedFilters[filterType].add(value);
152
+ } else {
153
+ selectedFilters[filterType].delete(value);
154
+ }
155
  }
156
 
157
  function populateDaisyDropdown(menuId, options, labelId, onSelect) {
 
181
  }
182
 
183
  function updateFilterLabel(filterType) {
184
+ const selectedCount = selectedFilters[filterType].size;
185
+ const labelElement = document.getElementById(`${filterType}-filter-label`);
186
+
187
+ if (selectedCount === 0) {
188
+ labelElement.textContent = `${filterType} (Tous)`;
189
+ } else {
190
+ labelElement.textContent = `${filterType} (${selectedCount} sélectionné${selectedCount > 1 ? 's' : ''})`;
191
+ }
192
  }
193
 
194
  /**
 
208
  Object.entries(mapping).forEach(([columnName, propertyName]) => {
209
  const cell = row.querySelector(`td[data-column="${columnName}"]`);
210
  if (cell) {
211
+ if (columnName == "URL") {
212
  rowData[propertyName] = cell.querySelector('a').getAttribute('href');
213
  } else {
214
  rowData[propertyName] = cell.textContent.trim();
 
222
  return data;
223
  }
224
 
225
+ const TABS = {
226
+ 'doc-table-tab': 'doc-table-tab-contents',
227
+ 'requirements-tab': 'requirements-tab-contents',
228
+ 'solutions-tab': 'solutions-tab-contents',
229
+ 'query-tab': 'query-tab-contents'
230
+ };
231
+
232
+ /**
233
+ * Bascule l'affichage sur le nouveau tab
234
+ * @param {*} newTab
235
+ */
236
+ function switchTab(newTab) {
237
+ // Remove active tab style from all tabs
238
+ Object.keys(TABS).forEach(tabId => {
239
+ const tabElement = document.getElementById(tabId);
240
+ if (tabElement) {
241
+ tabElement.classList.remove("tab-active");
242
+ }
243
+ });
244
+
245
+ // Hide all tab contents
246
+ Object.values(TABS).forEach(contentId => {
247
+ const contentElement = document.getElementById(contentId);
248
+ if (contentElement) {
249
+ contentElement.classList.add("hidden");
250
+ }
251
+ });
252
+
253
+ // Activate the new tab if it exists in the mapping
254
+ if (newTab in TABS) {
255
+ const newTabElement = document.getElementById(newTab);
256
+ const newContentElement = document.getElementById(TABS[newTab]);
257
+
258
+ if (newTabElement) newTabElement.classList.add("tab-active");
259
+ if (newContentElement) newContentElement.classList.remove("hidden");
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Bascule l'affichage vers la tab uniquement si les requirements sont
265
+ */
266
+ function enableTabSwitching() {
267
+ Object.keys(TABS).forEach(tabId => {
268
+ const tab = document.getElementById(tabId);
269
+ if (tab)
270
+ tab.classList.remove("tab-disabled");
271
+ })
272
+ }
273
+
274
+
275
+ /**
276
+ * Change l'état d'activation du number box de choix de nb de catégories.
277
+ */
278
+ function debounceAutoCategoryCount(state) {
279
+ document.getElementById('category-count').disabled = state;
280
+ }
281
+
282
+
283
  // =============================================================================
284
  // FONCTIONS MÉTIER
285
  // =============================================================================
 
319
  async function getTDocs() {
320
  const workingGroup = document.getElementById('working-group-select').value;
321
  const meeting = document.getElementById('meeting-select').value;
322
+
323
  if (!workingGroup || !meeting) return;
324
 
325
  showLoadingOverlay('Récupération de la liste des TDocs...');
 
335
  const data = await response.json();
336
  populateDataTable(data.data);
337
  setupFilters(data.data);
338
+
339
  toggleContainersVisibility([
340
  'filters-container',
341
  'action-buttons-container',
342
+ 'doc-table-tab-contents',
343
+ // 'data-table-container',
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);
 
466
  * Configure les événements du tableau
467
  */
468
  function setupTableEvents() {
469
+ document.getElementById('select-all-checkbox').addEventListener('change', function () {
470
  const checkboxes = document.querySelectorAll('.row-checkbox');
471
  checkboxes.forEach(checkbox => {
472
  // Ne coche que les visibles
 
526
  */
527
  function generateDownloadFilename() {
528
  let filename = document.getElementById('meeting-select').value || 'documents';
529
+
530
  const agendaItem = document.getElementById('agenda-item-filter').value;
531
  const docStatus = document.getElementById('doc-status-filter').value;
532
  const docType = document.getElementById('doc-type-filter').value;
 
587
  requirements = data.requirements;
588
  let req_id = 0;
589
  data.requirements.forEach(obj => {
590
+ obj.requirements.forEach(req => {
591
+ formattedRequirements.push({
592
+ req_id,
593
+ "document": obj.document,
594
+ "context": obj.context,
595
+ "requirement": req
 
 
596
  })
597
+ req_id++;
598
  })
599
+ })
600
  displayRequirements(requirements);
601
+
602
  toggleContainersVisibility(['requirements-container', 'query-requirements-container'], true);
603
+ toggleContainersVisibility(['categorize-requirements-btn'], true);
604
+
605
+ // we got some requirements to the other tabs can be enabled
606
+ enableTabSwitching();
607
+
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);
 
629
  requirementsData.forEach((docReq, docIndex) => {
630
  const docDiv = document.createElement('div');
631
  docDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white';
632
+
633
  docDiv.innerHTML = `
634
  <h3 class="text-lg font-semibold mb-2">${docReq.document}</h3>
635
  <p class="text-gray-600 mb-3">${docReq.context}</p>
636
  <ul class="list-disc list-inside space-y-1">
637
+ ${docReq.requirements.map((req, reqIndex) =>
638
+ `<li class="text-sm" data-req-id="${docIndex}-${reqIndex}">${req}</li>`
639
+ ).join('')}
640
  </ul>
641
  `;
642
+
643
  container.appendChild(docDiv);
644
  });
645
  }
 
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
  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);
673
  toggleContainersVisibility(['categorized-requirements-container', 'solutions-action-buttons-container'], true);
674
+
675
  } catch (error) {
676
  console.error('Erreur lors de la catégorisation des requirements:', error);
677
  alert('Erreur lors de la catégorisation des requirements');
 
691
 
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
  }
 
735
  const response = await fetch('/get_reqs_from_query', {
736
  method: 'POST',
737
  headers: { 'Content-Type': 'application/json' },
738
+ body: JSON.stringify({
739
  query: query,
740
  requirements: formattedRequirements
741
  })
 
743
 
744
  const data = await response.json();
745
  displaySearchResults(data.requirements);
746
+
747
  } catch (error) {
748
  console.error('Erreur lors de la recherche:', error);
749
  alert('Erreur lors de la recherche des requirements');
 
772
  results.forEach((result, index) => {
773
  const resultDiv = document.createElement('div');
774
  resultDiv.className = 'p-3 bg-blue-50 border border-blue-200 rounded-lg';
775
+
776
  resultDiv.innerHTML = `
777
  <div class="text-sm font-medium text-blue-800">${result.document}</div>
778
  <div class="text-sm text-gray-600 mb-1">${result.context}</div>
779
  <div class="text-sm">${result.requirement}</div>
780
  `;
781
+
782
  resultsDiv.appendChild(resultDiv);
783
  });
784
 
 
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">
 
907
  // Section Critique
908
  const critiqueSection = document.createElement('div');
909
  critiqueSection.className = 'bg-yellow-50 border-l-2 border-yellow-400 p-3 rounded-r-md';
910
+
911
  let critiqueContent = `
912
  <h4 class="text-sm font-semibold text-yellow-800 mb-2 flex items-center">
913
  <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
 
953
 
954
  critiqueSection.innerHTML = critiqueContent;
955
 
956
+ // ===================================== Section sources ================================
957
+
958
+ createEl = (tag, properties) => {
959
+ const element = document.createElement(tag);
960
+ Object.assign(element, properties);
961
+ return element;
962
+ };
963
+
964
+ // conteneur des sources
965
+ const sourcesSection = createEl('div', {
966
+ className: 'bg-gray-50 border-l-2 border-gray-400 p-3 rounded-r-md'
967
+ });
968
+
969
+ const heading = createEl('h4', {
970
+ className: 'text-sm font-semibold text-black mb-2 flex items-center',
971
+ innerHTML: `
972
+ <svg
973
+ xmlns="http://www.w3.org/2000/svg"
974
+ class="w-4 h-4 mr-1"
975
+ fill="none"
976
+ viewBox="0 0 24 24"
977
+ stroke="currentColor"
978
+ stroke-width="2">
979
+ <path
980
+ stroke-linecap="round"
981
+ stroke-linejoin="round"
982
+ d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
983
+ />
984
+ </svg>
985
+ Sources
986
+ `
987
+ });
988
+
989
+ const pillContainer = createEl('div', {
990
+ className: 'flex flex-wrap mt-1'
991
+ });
992
+
993
+ // create reference pills
994
+ solution['References'].forEach(source => {
995
+ const pillLink = createEl('a', {
996
+ href: source.url,
997
+ target: '_blank',
998
+ rel: 'noopener noreferrer',
999
+ 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',
1000
+ textContent: source.name
1001
+ });
1002
+ pillContainer.appendChild(pillLink);
1003
+ });
1004
+
1005
+ sourcesSection.append(heading, pillContainer);
1006
+
1007
+ // ======================================================================================
1008
+
1009
  // Ajouter les sections au contenu
1010
  content.appendChild(problemSection);
1011
  content.appendChild(solutionSection);
1012
  content.appendChild(critiqueSection);
1013
+ content.appendChild(sourcesSection);
1014
 
1015
  // Événement de clic pour l'accordéon (exclure les boutons de navigation)
1016
  header.addEventListener('click', (e) => {
 
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
 
 
1061
 
1062
  const newData = solutionCriticizedHistory[newVersionIndex];
1063
  const newItem = newData.critiques[categoryIndex];
1064
+
1065
  if (!newItem) return;
1066
 
1067
  // Mettre à jour le contenu de cette catégorie spécifique
1068
  const tempContainer = document.createElement('div');
1069
  createSingleAccordionItem(newItem, categoryIndex, newVersionIndex, solutionCriticizedHistory, tempContainer);
1070
+
1071
  // Remplacer l'ancien item par le nouveau
1072
  accordionItem.parentNode.replaceChild(tempContainer.firstChild, accordionItem);
1073
  }
 
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
  }
1088
 
1089
+ async function generateCriticisms(solutions) {
1090
+ let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) })
1091
  let responseObj = await response.json()
1092
  solutionsCriticizedVersions.push(responseObj)
1093
  }
1094
 
1095
+ async function refineSolutions(critiques) {
1096
+ let response = await fetch('https://organizedprogrammers-reqxtract-api.hf.space/solution/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) })
1097
  let responseObj = await response.json()
1098
  await generateCriticisms(responseObj)
1099
  }
1100
 
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 {
 
1118
  // INITIALISATION DES ÉVÉNEMENTS
1119
  // =============================================================================
1120
 
1121
+ document.addEventListener('DOMContentLoaded', function () {
1122
  // Événements des boutons principaux
1123
  document.getElementById('get-meetings-btn').addEventListener('click', getMeetings);
1124
  document.getElementById('get-tdocs-btn').addEventListener('click', getTDocs);
1125
  document.getElementById('download-tdocs-btn').addEventListener('click', downloadTDocs);
1126
  document.getElementById('extract-requirements-btn').addEventListener('click', extractRequirements);
1127
+ document.getElementById('categorize-requirements-btn').addEventListener('click', () => {
1128
+ const category_count_auto_detect = document.getElementById('auto-detect-toggle').checked;
1129
+ const n_categories = document.getElementById('category-count').value;
1130
+ categorizeRequirements(category_count_auto_detect ? null : n_categories);
 
 
 
 
1131
  });
1132
+
1133
  // Événement pour la recherche
1134
  document.getElementById('search-requirements-btn').addEventListener('click', searchRequirements);
1135
+
1136
  // Événements pour les boutons de solutions (à implémenter plus tard)
1137
  document.getElementById('get-solutions-btn').addEventListener('click', () => {
1138
  alert('Fonctionnalité à implémenter');
 
1140
  document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
1141
  workflow();
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);