om4r932 commited on
Commit
546fbbe
·
1 Parent(s): 03e159d

Separate JS and HTML + add Categorize reqs

Browse files
Files changed (3) hide show
  1. app.py +10 -5
  2. index.html +20 -259
  3. static/script.js +364 -0
app.py CHANGED
@@ -2,6 +2,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
2
  import json
3
  import traceback
4
  from fastapi import FastAPI, BackgroundTasks, HTTPException
 
5
  from schemas import *
6
  from fastapi.middleware.cors import CORSMiddleware
7
  from fastapi.responses import FileResponse, StreamingResponse
@@ -38,6 +39,7 @@ warnings.filterwarnings("ignore")
38
  from bs4 import BeautifulSoup
39
 
40
  app = FastAPI(title="Requirements Extractor")
 
41
  app.add_middleware(CORSMiddleware, allow_credentials=True, allow_headers=["*"], allow_methods=["*"], allow_origins=["*"])
42
  llm_router = Router(model_list=[{"model_name": "gemini-v1", "litellm_params": {"model": "gemini/gemini-2.0-flash", "api_key": os.environ.get("GEMINI"), "max_retries": 10, "rpm": 15}},
43
  {"model_name": "gemini-v2", "litellm_params": {"model": "gemini/gemini-2.5-flash", "api_key": os.environ.get("GEMINI"), "max_retries": 10, "rpm": 10}}]
@@ -188,7 +190,9 @@ def get_meetings(req: MeetingsRequest):
188
  working_group = req.working_group
189
  tsg = re.sub(r"\d+", "", working_group)
190
  wg_number = re.search(r"\d", working_group).group(0)
 
191
  url = "https://www.3gpp.org/ftp/tsg_" + tsg
 
192
  resp = requests.get(url, verify=False)
193
  soup = BeautifulSoup(resp.text, "html.parser")
194
  meeting_folders = []
@@ -196,17 +200,18 @@ def get_meetings(req: MeetingsRequest):
196
  wg_folders = [item.get_text() for item in soup.select("tr td a")]
197
  selected_folder = None
198
  for folder in wg_folders:
199
- if str(wg_number) in folder:
200
  selected_folder = folder
201
  break
202
 
203
  url += "/" + selected_folder
 
204
 
205
  if selected_folder:
206
  resp = requests.get(url, verify=False)
207
  soup = BeautifulSoup(resp.text, "html.parser")
208
- meeting_folders = [item.get_text() for item in soup.select("tr td a") if item.get_text().startswith("TSG")]
209
- all_meetings = [working_group + "#" + meeting.split("_", 1)[1].replace("_", " ").replace("-", " ") for meeting in meeting_folders]
210
 
211
  return MeetingsResponse(meetings=dict(zip(all_meetings, meeting_folders)))
212
 
@@ -304,7 +309,7 @@ async def gen_reqs(req: RequirementsRequest, background_tasks: BackgroundTasks):
304
  async with limiter_mapping[model_used]:
305
  resp_ai = await llm_router.acompletion(
306
  model=model_used,
307
- messages=[{"role":"user","content": 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."}],
308
  response_format=RequirementsResponse
309
  )
310
  return RequirementsResponse.model_validate_json(resp_ai.choices[0].message.content).requirements
@@ -315,7 +320,7 @@ async def gen_reqs(req: RequirementsRequest, background_tasks: BackgroundTasks):
315
  async with limiter_mapping[model_used]:
316
  resp_ai = await llm_router.acompletion(
317
  model=model_used,
318
- messages=[{"role":"user","content": 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."}],
319
  response_format=RequirementsResponse
320
  )
321
  return RequirementsResponse.model_validate_json(resp_ai.choices[0].message.content).requirements
 
2
  import json
3
  import traceback
4
  from fastapi import FastAPI, BackgroundTasks, HTTPException
5
+ from fastapi.staticfiles import StaticFiles
6
  from schemas import *
7
  from fastapi.middleware.cors import CORSMiddleware
8
  from fastapi.responses import FileResponse, StreamingResponse
 
39
  from bs4 import BeautifulSoup
40
 
41
  app = FastAPI(title="Requirements Extractor")
42
+ app.mount("/static", StaticFiles(directory="static"), name="static")
43
  app.add_middleware(CORSMiddleware, allow_credentials=True, allow_headers=["*"], allow_methods=["*"], allow_origins=["*"])
44
  llm_router = Router(model_list=[{"model_name": "gemini-v1", "litellm_params": {"model": "gemini/gemini-2.0-flash", "api_key": os.environ.get("GEMINI"), "max_retries": 10, "rpm": 15}},
45
  {"model_name": "gemini-v2", "litellm_params": {"model": "gemini/gemini-2.5-flash", "api_key": os.environ.get("GEMINI"), "max_retries": 10, "rpm": 10}}]
 
190
  working_group = req.working_group
191
  tsg = re.sub(r"\d+", "", working_group)
192
  wg_number = re.search(r"\d", working_group).group(0)
193
+ print(tsg, wg_number)
194
  url = "https://www.3gpp.org/ftp/tsg_" + tsg
195
+ print(url)
196
  resp = requests.get(url, verify=False)
197
  soup = BeautifulSoup(resp.text, "html.parser")
198
  meeting_folders = []
 
200
  wg_folders = [item.get_text() for item in soup.select("tr td a")]
201
  selected_folder = None
202
  for folder in wg_folders:
203
+ if "wg" + str(wg_number) in folder.lower():
204
  selected_folder = folder
205
  break
206
 
207
  url += "/" + selected_folder
208
+ print(url)
209
 
210
  if selected_folder:
211
  resp = requests.get(url, verify=False)
212
  soup = BeautifulSoup(resp.text, "html.parser")
213
+ meeting_folders = [item.get_text() for item in soup.select("tr td a") if item.get_text().startswith("TSG") or (item.get_text().startswith("CT") and "-" in item.get_text())]
214
+ all_meetings = [working_group + "#" + meeting.split("_", 1)[1].replace("_", " ").replace("-", " ") if meeting.startswith('TSG') else meeting.replace("-","#") for meeting in meeting_folders]
215
 
216
  return MeetingsResponse(meetings=dict(zip(all_meetings, meeting_folders)))
217
 
 
309
  async with limiter_mapping[model_used]:
310
  resp_ai = await llm_router.acompletion(
311
  model=model_used,
312
+ messages=[{"role":"user","content": 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"}],
313
  response_format=RequirementsResponse
314
  )
315
  return RequirementsResponse.model_validate_json(resp_ai.choices[0].message.content).requirements
 
320
  async with limiter_mapping[model_used]:
321
  resp_ai = await llm_router.acompletion(
322
  model=model_used,
323
+ messages=[{"role":"user","content": 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"}],
324
  response_format=RequirementsResponse
325
  )
326
  return RequirementsResponse.model_validate_json(resp_ai.choices[0].message.content).requirements
index.html CHANGED
@@ -1,5 +1,6 @@
1
  <!DOCTYPE html>
2
  <html lang="fr" data-theme="light">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -7,13 +8,14 @@
7
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.css" rel="stylesheet">
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  </head>
 
10
  <body class="p-8 bg-base-100">
11
  <div class="container mx-auto">
12
  <h1 class="text-4xl font-bold text-center mb-8">Requirements Extractor</h1>
13
- <div id="dataFrameForm">
14
  <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
15
  <select class="select select-bordered" id="workingGroupSelect">
16
- <option disabled selected value="">Working Group</option>
17
  <option>SA1</option>
18
  <option>SA2</option>
19
  <option>SA3</option>
@@ -27,8 +29,8 @@
27
  <option>CT5</option>
28
  <option>CT6</option>
29
  </select>
30
- <select class="select select-bordered" id="meetingSelect" disabled>
31
- <option disabled selected value="">Select a working group</option>
32
  </select>
33
  <button class="btn" id="getTDocs">Get TDocs</button>
34
  </div>
@@ -36,17 +38,17 @@
36
  <div class="hidden" id="filters">
37
  <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
38
  <select class="select select-bordered" id="docType">
39
- <option disabled selected value="">Type</option>
40
  <option>Tous</option>
41
  </select>
42
 
43
  <select class="select select-bordered" id="docStatus">
44
- <option disabled selected value="">Status</option>
45
  <option>Tous</option>
46
  </select>
47
 
48
  <select class="select select-bordered" id="agendaItem">
49
- <option disabled selected value="">Agenda Item</option>
50
  <option>Tous</option>
51
  </select>
52
  </div>
@@ -55,8 +57,10 @@
55
  <div class="flex justify-center mt-12 min-h-[10vh] hidden" id="queryReqForm">
56
  <div class="w-full max-w-md">
57
  <div class="grid grid-cols-1 gap-4">
58
- <textarea placeholder="Enter your problem description here ..." class="w-full mx-auto px-4 py-2 border rounded" id="problemDescription"></textarea>
59
- <button class="w-1/2 mx-auto px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700" id="queryReq">
 
 
60
  Find requirements
61
  </button>
62
  </div>
@@ -77,7 +81,7 @@
77
  <th>Title</th>
78
  <th>Type</th>
79
  <th>Status</th>
80
- <th>Agenda Item N°</th>
81
  <th>URL</th>
82
  </tr>
83
  </thead>
@@ -86,6 +90,11 @@
86
  </table>
87
  </div>
88
 
 
 
 
 
 
89
  <center>
90
  <div id="buttons">
91
  <p id="reqStatus" class="mt-6 hidden">Requirements extracted</p>
@@ -99,254 +108,6 @@
99
  </center>
100
  </div>
101
 
102
- <script>
103
- let requirements;
104
-
105
- function downloadTDocs(){
106
- const data = tableToGenBody({"TDoc": "doc"});
107
- const dataSet = [...new Set(data.map(item => item.doc))];
108
- console.log(dataSet);
109
- let body = {"documents": dataSet, "meeting": document.getElementById('meetingSelect').value};
110
- if (document.getElementById('agendaItem').value != "" | document.getElementById('agendaItem').value != "Tous"){
111
- body['agenda_item'] = document.getElementById('agendaItem').value;
112
- }
113
- fetch('/download_tdocs', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(body)})
114
- .then(resp => resp.blob())
115
- .then(blob => {
116
- const url = window.URL.createObjectURL(blob);
117
- const a = document.createElement("a");
118
- a.href = url;
119
- let dl_name = `${document.getElementById('meetingSelect').value}`;
120
- if (document.getElementById('agendaItem').value != "" | document.getElementById('agendaItem').value != "Tous"){dl_name = dl_name + `_${document.getElementById('agendaItem').value}`};
121
- if (document.getElementById('docStatus').value != "" | document.getElementById('docStatus').value != "Tous"){dl_name = dl_name + `_${document.getElementById('docStatus').value}`};
122
- if (document.getElementById('docType').value != "" | document.getElementById('docType').value != "Tous"){dl_name = `${document.getElementById('docType').value}_${dl_name}`};
123
- if (document.getElementById('queryReqForm').classList.contains('hidden')){dl_name = `requirements_${dl_name}_${url.split('/').pop()}`}
124
- dl_name = dl_name + ".zip";
125
- a.download = dl_name;
126
- document.body.appendChild(a);
127
- a.click();
128
- a.remove();
129
- window.URL.revokeObjectURL(url); // libération mémoire
130
- })
131
- }
132
-
133
- function getDataFrame(){
134
- document.getElementById("loadingBar").classList.remove("hidden");
135
- const wg = document.getElementById('workingGroupSelect').value;
136
- const meeting = document.getElementById('meetingSelect').value;
137
- document.getElementById('docType').innerHTML = `
138
- <option disabled selected value="">Type</option>
139
- <option>Tous</option>
140
- `
141
-
142
- document.getElementById('docStatus').innerHTML = `
143
- <option disabled selected value="">Status</option>
144
- <option>Tous</option>
145
- `
146
-
147
- document.getElementById('agendaItem').innerHTML = `
148
- <option disabled selected value="">Agenda Item</option>
149
- <option>Tous</option>
150
- `
151
- const dataFrame = document.getElementById("dataFrame");
152
- document.getElementById("progressText").classList.remove('hidden')
153
- document.getElementById("progressText").innerHTML = "Loading ...";
154
- document.getElementById("loadingBar").classList.remove("hidden")
155
- fetch("/get_dataframe", {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({"working_group": wg, "meeting": meeting})})
156
- .then(resp => resp.json())
157
- .then(data => {
158
- document.getElementById("filters").classList.remove("hidden");
159
- document.getElementById("loadingBar").classList.add("hidden");
160
- document.getElementById("downloadZip").classList.remove("hidden");
161
- document.getElementById("getReqs").classList.remove("hidden");
162
- const dataframeBody = dataFrame.querySelector("tbody");
163
- dataframeBody.innerHTML = "";
164
- const setType = new Set();
165
- const setAgenda = new Set();
166
- const setStatus = new Set();
167
- data.data.forEach(row => {
168
- const tr = document.createElement("tr");
169
- tr.setAttribute("data-type", row['Type']);
170
- tr.setAttribute("data-status", row["TDoc Status"]);
171
- tr.setAttribute("data-agenda", row["Agenda item description"]);
172
- tr.innerHTML = `
173
- <td>${row["TDoc"]}</td>
174
- <td>${row["Title"]}</td>
175
- <td>${row["Type"]}</td>
176
- <td>${row["TDoc Status"]}</td>
177
- <td>${row["Agenda item description"]}</td>
178
- <td>
179
- <a href="${row["URL"]}" class="link">${row["URL"]}</a>
180
- </td>
181
- `;
182
- dataframeBody.appendChild(tr);
183
- setType.add(row["Type"]);
184
- setAgenda.add(row["Agenda item description"]);
185
- setStatus.add(row["TDoc Status"]);
186
- })
187
-
188
- setType.forEach(tdoctype => {
189
- const option = document.createElement("option");
190
- option.textContent = tdoctype;
191
- option.value = tdoctype;
192
- document.getElementById('docType').appendChild(option);
193
- })
194
-
195
- setAgenda.forEach(agenda => {
196
- const option = document.createElement("option");
197
- option.textContent = agenda;
198
- option.value = agenda;
199
- document.getElementById('agendaItem').appendChild(option);
200
- })
201
-
202
- setStatus.forEach(status => {
203
- const option = document.createElement("option");
204
- option.textContent = status;
205
- option.value = status;
206
- document.getElementById('docStatus').appendChild(option);
207
- })
208
- })
209
-
210
- document.getElementById("progressText").classList.add('hidden')
211
- document.getElementById("loadingBar").classList.add("hidden")
212
- }
213
-
214
- function filterTable() {
215
- const type = document.getElementById('docType').value
216
- const status = document.getElementById('docStatus').value
217
- const agenda = document.getElementById('agendaItem').value
218
-
219
- document.querySelectorAll('#dataFrame tbody tr').forEach(row => {
220
- const showRow =
221
- (type === 'Tous' || row.dataset.type === type || type === "") &&
222
- (status === 'Tous' || row.dataset.status === status || status === "") &&
223
- (agenda === 'Tous' || row.dataset.agenda === agenda || agenda === "")
224
-
225
- row.style.display = showRow ? '' : 'none'
226
- })
227
- }
228
-
229
- function getMeetings(){
230
- const workingGroup = document.getElementById("workingGroupSelect").value;
231
- document.getElementById("meetingSelect").setAttribute('disabled', 'true')
232
- document.getElementById("meetingSelect").innerHTML = "<option>Loading...</option>"
233
- document.getElementById("getTDocs").setAttribute('disabled', 'true')
234
- fetch("/get_meetings", {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({"working_group": workingGroup})})
235
- .then(resp => resp.json())
236
- .then(data => {
237
- document.getElementById("meetingSelect").innerHTML = "";
238
- document.getElementById("meetingSelect").removeAttribute("disabled");
239
- document.getElementById("getTDocs").removeAttribute("disabled")
240
- for(const [key, value] of Object.entries(data.meetings)){
241
- const option = document.createElement("option");
242
- option.textContent = key;
243
- option.value = value;
244
- document.getElementById('meetingSelect').appendChild(option);
245
- }
246
- })
247
- }
248
-
249
- function generateRequirements(){
250
- const bodyreq = tableToGenBody({"TDoc": "document", "URL": "url"});
251
- document.getElementById("progressText").classList.remove('hidden');
252
- document.getElementById("progressText").innerHTML = "Generating requirements, please wait, it may take a while ...";
253
- document.getElementById("loadingBar").classList.remove("hidden");
254
-
255
- fetch("/generate_requirements", {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({"documents": bodyreq})})
256
- .then(resp => resp.json())
257
- .then(data => {
258
- document.getElementById("loadingBar").classList.add("hidden");
259
- document.getElementById("progressText").classList.add("hidden");
260
- document.getElementById("reqStatus").classList.remove("hidden");
261
- document.getElementById("getReqs").classList.add("hidden");
262
- document.getElementById("searchReq").classList.remove("hidden");
263
- document.getElementById("categorizeReq").classList.remove("hidden");
264
- requirements = [];
265
- data.requirements.forEach(obj => {
266
- obj.requirements.forEach(req => {
267
- requirements.push({"document": obj.document, "context": obj.context, "requirement": req})
268
- })
269
- })
270
- })
271
- }
272
-
273
- function queryRequirements(){
274
- fetch("/get_reqs_from_query", {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({query: document.getElementById("problemDescription").value, requirements})})
275
- .then(resp => resp.json())
276
- .then(data => {
277
- const dataFrame = document.getElementById("dataFrameDiv");
278
- const dataFrameHead = dataFrame.querySelector("thead");
279
- const dataFrameBody = dataFrame.querySelector("tbody");
280
- document.getElementById("buttons").classList.remove("hidden");
281
- document.getElementById("searchReq").classList.add("hidden");
282
- document.getElementById("categorizeReq").classList.add("hidden");
283
- document.getElementById("getReqs").classList.add("hidden");
284
- document.getElementById("reqStatus").classList.add("hidden");
285
- document.getElementById("downloadZip").classList.remove("hidden");
286
-
287
- dataFrame.classList.remove("hidden");
288
-
289
- dataFrameHead.innerHTML = `
290
- <th>TDoc</th>
291
- <th>Context</th>
292
- <th>Requirement</th>
293
- `;
294
-
295
- dataFrameBody.innerHTML = "";
296
-
297
- data.requirements.forEach(req => {
298
- const tr = document.createElement("tr");
299
- tr.innerHTML = `
300
- <td>${req["document"]}</td>
301
- <td>${req["context"]}</td>
302
- <td>${req["requirement"]}</td>
303
- `;
304
- dataFrameBody.appendChild(tr);
305
- })
306
- })
307
- }
308
-
309
- function tableToGenBody(columnsMap) {
310
- // columnsMap : { "NomHeaderDansTable": "nom_voulu", ... }
311
- const dataFrame = document.getElementById("dataFrame");
312
- const headers = Array.from(dataFrame.querySelectorAll('thead th')).map(th => th.innerText.trim());
313
-
314
- // Indices des colonnes à extraire
315
- const selectedIndices = headers
316
- .map((header, idx) => columnsMap[header] ? idx : -1)
317
- .filter(idx => idx !== -1);
318
-
319
- return Array.from(dataFrame.querySelectorAll('tbody tr'))
320
- .filter(row => getComputedStyle(row).display !== 'none')
321
- .map(row => {
322
- const cells = Array.from(row.querySelectorAll('td'));
323
- const obj = {};
324
- selectedIndices.forEach(idx => {
325
- const originalHeader = headers[idx];
326
- const newKey = columnsMap[originalHeader];
327
- obj[newKey] = cells[idx].innerText.trim();
328
- });
329
- return obj;
330
- });
331
- }
332
-
333
- // Écouteurs d'événements pour les filtres
334
-
335
- document.getElementById('docType').addEventListener('change', filterTable)
336
- document.getElementById('docStatus').addEventListener('change', filterTable)
337
- document.getElementById('agendaItem').addEventListener('change', filterTable)
338
- document.getElementById("workingGroupSelect").addEventListener('change', getMeetings)
339
- document.getElementById('getTDocs').addEventListener('click', getDataFrame)
340
- document.getElementById("getReqs").addEventListener("click", generateRequirements);
341
- document.getElementById("downloadZip").addEventListener('click', downloadTDocs)
342
- document.getElementById("queryReq").addEventListener("click", queryRequirements)
343
- document.getElementById('searchReq').addEventListener('click', ()=>{
344
- document.getElementById('dataFrameForm').classList.add('hidden');
345
- document.getElementById('filters').classList.add('hidden');
346
- document.getElementById('queryReqForm').classList.remove('hidden');
347
- document.getElementById('dataFrameDiv').classList.add('hidden');
348
- document.getElementById('buttons').classList.add('hidden');
349
- })
350
- </script>
351
  </body>
352
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="fr" data-theme="light">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.css" rel="stylesheet">
9
  <script src="https://cdn.tailwindcss.com"></script>
10
  </head>
11
+
12
  <body class="p-8 bg-base-100">
13
  <div class="container mx-auto">
14
  <h1 class="text-4xl font-bold text-center mb-8">Requirements Extractor</h1>
15
+ <div class="" id="dataFrameForm">
16
  <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
17
  <select class="select select-bordered" id="workingGroupSelect">
18
+ <option disabled="" selected="" value="">Working Group</option>
19
  <option>SA1</option>
20
  <option>SA2</option>
21
  <option>SA3</option>
 
29
  <option>CT5</option>
30
  <option>CT6</option>
31
  </select>
32
+ <select class="select select-bordered" id="meetingSelect" disabled="">
33
+ <option disabled="" selected="" value="">Select a working group</option>
34
  </select>
35
  <button class="btn" id="getTDocs">Get TDocs</button>
36
  </div>
 
38
  <div class="hidden" id="filters">
39
  <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
40
  <select class="select select-bordered" id="docType">
41
+ <option disabled="" selected="" value="">Type</option>
42
  <option>Tous</option>
43
  </select>
44
 
45
  <select class="select select-bordered" id="docStatus">
46
+ <option disabled="" selected="" value="">Status</option>
47
  <option>Tous</option>
48
  </select>
49
 
50
  <select class="select select-bordered" id="agendaItem">
51
+ <option disabled="" selected="" value="">Agenda Item</option>
52
  <option>Tous</option>
53
  </select>
54
  </div>
 
57
  <div class="flex justify-center mt-12 min-h-[10vh] hidden" id="queryReqForm">
58
  <div class="w-full max-w-md">
59
  <div class="grid grid-cols-1 gap-4">
60
+ <textarea placeholder="Enter your problem description here ..."
61
+ class="w-full mx-auto px-4 py-2 border rounded" id="problemDescription"></textarea>
62
+ <button class="w-1/2 mx-auto px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
63
+ id="queryReq">
64
  Find requirements
65
  </button>
66
  </div>
 
81
  <th>Title</th>
82
  <th>Type</th>
83
  <th>Status</th>
84
+ <th>Agenda Item</th>
85
  <th>URL</th>
86
  </tr>
87
  </thead>
 
90
  </table>
91
  </div>
92
 
93
+ <div class="w-full max-w-[100%] mx-auto p-6 hidden" id="carousels">
94
+ <h1 class="text-xl font-bold mb-8 text-center">Liste des catégories de requirements</h1>
95
+
96
+ </div>
97
+
98
  <center>
99
  <div id="buttons">
100
  <p id="reqStatus" class="mt-6 hidden">Requirements extracted</p>
 
108
  </center>
109
  </div>
110
 
111
+ <script src="/static/script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  </body>
113
  </html>
static/script.js ADDED
@@ -0,0 +1,364 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let requirements;
2
+
3
+ function downloadTDocs() {
4
+ const data = tableToGenBody({
5
+ "TDoc": "doc"
6
+ });
7
+ const dataSet = [...new Set(data.map(item => item.doc))];
8
+ console.log(dataSet);
9
+ let body = {
10
+ "documents": dataSet,
11
+ "meeting": document.getElementById('meetingSelect').value
12
+ };
13
+ if (document.getElementById('agendaItem').value != "" | document.getElementById('agendaItem').value !=
14
+ "Tous") {
15
+ body['agenda_item'] = document.getElementById('agendaItem').value;
16
+ }
17
+ fetch('/download_tdocs', {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json"
21
+ },
22
+ body: JSON.stringify(body)
23
+ })
24
+ .then(resp => resp.blob())
25
+ .then(blob => {
26
+ const url = window.URL.createObjectURL(blob);
27
+ const a = document.createElement("a");
28
+ a.href = url;
29
+ let dl_name = `${document.getElementById('meetingSelect').value}`;
30
+ if (document.getElementById('agendaItem').value != "" | document.getElementById('agendaItem')
31
+ .value != "Tous") {
32
+ dl_name = dl_name + `_${document.getElementById('agendaItem').value}`
33
+ };
34
+ if (document.getElementById('docStatus').value != "" | document.getElementById('docStatus')
35
+ .value != "Tous") {
36
+ dl_name = dl_name + `_${document.getElementById('docStatus').value}`
37
+ };
38
+ if (document.getElementById('docType').value != "" | document.getElementById('docType').value !=
39
+ "Tous") {
40
+ dl_name = `${document.getElementById('docType').value}_${dl_name}`
41
+ };
42
+ if (document.getElementById('queryReqForm').classList.contains('hidden')) {
43
+ dl_name = `requirements_${dl_name}_${url.split('/').pop()}`
44
+ }
45
+ dl_name = dl_name + ".zip";
46
+ a.download = dl_name;
47
+ document.body.appendChild(a);
48
+ a.click();
49
+ a.remove();
50
+ window.URL.revokeObjectURL(url); // libération mémoire
51
+ })
52
+ }
53
+
54
+ function getDataFrame() {
55
+ document.getElementById("loadingBar").classList.remove("hidden");
56
+ const wg = document.getElementById('workingGroupSelect').value;
57
+ const meeting = document.getElementById('meetingSelect').value;
58
+ document.getElementById('docType').innerHTML = `
59
+ <option disabled selected value="">Type</option>
60
+ <option>Tous</option>
61
+ `
62
+
63
+ document.getElementById('docStatus').innerHTML = `
64
+ <option disabled selected value="">Status</option>
65
+ <option>Tous</option>
66
+ `
67
+
68
+ document.getElementById('agendaItem').innerHTML = `
69
+ <option disabled selected value="">Agenda Item</option>
70
+ <option>Tous</option>
71
+ `
72
+ const dataFrame = document.getElementById("dataFrame");
73
+ document.getElementById("progressText").classList.remove('hidden')
74
+ document.getElementById("progressText").innerHTML = "Loading ...";
75
+ document.getElementById("loadingBar").classList.remove("hidden")
76
+ fetch("/get_dataframe", {
77
+ method: "POST",
78
+ headers: {
79
+ "Content-Type": "application/json"
80
+ },
81
+ body: JSON.stringify({
82
+ "working_group": wg,
83
+ "meeting": meeting
84
+ })
85
+ })
86
+ .then(resp => resp.json())
87
+ .then(data => {
88
+ document.getElementById("filters").classList.remove("hidden");
89
+ document.getElementById("loadingBar").classList.add("hidden");
90
+ document.getElementById("downloadZip").classList.remove("hidden");
91
+ document.getElementById("getReqs").classList.remove("hidden");
92
+ const dataframeBody = dataFrame.querySelector("tbody");
93
+ dataframeBody.innerHTML = "";
94
+ const setType = new Set();
95
+ const setAgenda = new Set();
96
+ const setStatus = new Set();
97
+ data.data.forEach(row => {
98
+ const tr = document.createElement("tr");
99
+ tr.setAttribute("data-type", row['Type']);
100
+ tr.setAttribute("data-status", row["TDoc Status"]);
101
+ tr.setAttribute("data-agenda", row["Agenda item description"]);
102
+ tr.innerHTML = `
103
+ <td>${row["TDoc"]}</td>
104
+ <td>${row["Title"]}</td>
105
+ <td>${row["Type"]}</td>
106
+ <td>${row["TDoc Status"]}</td>
107
+ <td>${row["Agenda item description"]}</td>
108
+ <td>
109
+ <a href="${row["URL"]}" class="link">${row["URL"]}</a>
110
+ </td>
111
+ `;
112
+ dataframeBody.appendChild(tr);
113
+ setType.add(row["Type"]);
114
+ setAgenda.add(row["Agenda item description"]);
115
+ setStatus.add(row["TDoc Status"]);
116
+ })
117
+
118
+ setType.forEach(tdoctype => {
119
+ const option = document.createElement("option");
120
+ option.textContent = tdoctype;
121
+ option.value = tdoctype;
122
+ document.getElementById('docType').appendChild(option);
123
+ })
124
+
125
+ setAgenda.forEach(agenda => {
126
+ const option = document.createElement("option");
127
+ option.textContent = agenda;
128
+ option.value = agenda;
129
+ document.getElementById('agendaItem').appendChild(option);
130
+ })
131
+
132
+ setStatus.forEach(status => {
133
+ const option = document.createElement("option");
134
+ option.textContent = status;
135
+ option.value = status;
136
+ document.getElementById('docStatus').appendChild(option);
137
+ })
138
+ })
139
+
140
+ document.getElementById("progressText").classList.add('hidden')
141
+ document.getElementById("loadingBar").classList.add("hidden")
142
+ }
143
+
144
+ function filterTable() {
145
+ const type = document.getElementById('docType').value
146
+ const status = document.getElementById('docStatus').value
147
+ const agenda = document.getElementById('agendaItem').value
148
+
149
+ document.querySelectorAll('#dataFrame tbody tr').forEach(row => {
150
+ const showRow =
151
+ (type === 'Tous' || row.dataset.type === type || type === "") &&
152
+ (status === 'Tous' || row.dataset.status === status || status === "") &&
153
+ (agenda === 'Tous' || row.dataset.agenda === agenda || agenda === "")
154
+
155
+ row.style.display = showRow ? '' : 'none'
156
+ })
157
+ }
158
+
159
+ function getMeetings() {
160
+ const workingGroup = document.getElementById("workingGroupSelect").value;
161
+ document.getElementById("meetingSelect").setAttribute('disabled', 'true')
162
+ document.getElementById("meetingSelect").innerHTML = "<option>Loading...</option>"
163
+ document.getElementById("getTDocs").setAttribute('disabled', 'true')
164
+ fetch("/get_meetings", {
165
+ method: "POST",
166
+ headers: {
167
+ "Content-Type": "application/json"
168
+ },
169
+ body: JSON.stringify({
170
+ "working_group": workingGroup
171
+ })
172
+ })
173
+ .then(resp => resp.json())
174
+ .then(data => {
175
+ document.getElementById("meetingSelect").innerHTML = "";
176
+ document.getElementById("meetingSelect").removeAttribute("disabled");
177
+ document.getElementById("getTDocs").removeAttribute("disabled")
178
+ for (const [key, value] of Object.entries(data.meetings)) {
179
+ const option = document.createElement("option");
180
+ option.textContent = key;
181
+ option.value = value;
182
+ document.getElementById('meetingSelect').appendChild(option);
183
+ }
184
+ })
185
+ }
186
+
187
+ function generateRequirements() {
188
+ const bodyreq = tableToGenBody({
189
+ "TDoc": "document",
190
+ "URL": "url"
191
+ });
192
+ document.getElementById("progressText").classList.remove('hidden');
193
+ document.getElementById("progressText").innerHTML =
194
+ "Generating requirements, please wait, it may take a while ...";
195
+ document.getElementById("loadingBar").classList.remove("hidden");
196
+
197
+ fetch("/generate_requirements", {
198
+ method: "POST",
199
+ headers: {
200
+ "Content-Type": "application/json"
201
+ },
202
+ body: JSON.stringify({
203
+ "documents": bodyreq
204
+ })
205
+ })
206
+ .then(resp => resp.json())
207
+ .then(data => {
208
+ document.getElementById("loadingBar").classList.add("hidden");
209
+ document.getElementById("progressText").classList.add("hidden");
210
+ document.getElementById("reqStatus").classList.remove("hidden");
211
+ document.getElementById("getReqs").classList.add("hidden");
212
+ document.getElementById("searchReq").classList.remove("hidden");
213
+ document.getElementById("categorizeReq").classList.remove("hidden");
214
+ requirements = [];
215
+ data.requirements.forEach(obj => {
216
+ obj.requirements.forEach(req => {
217
+ requirements.push({
218
+ "document": obj.document,
219
+ "context": obj.context,
220
+ "requirement": req
221
+ })
222
+ })
223
+ })
224
+ })
225
+ }
226
+
227
+ function queryRequirements() {
228
+ fetch("/get_reqs_from_query", {
229
+ method: "POST",
230
+ headers: {
231
+ "Content-Type": "application/json"
232
+ },
233
+ body: JSON.stringify({
234
+ query: document.getElementById("problemDescription").value,
235
+ requirements
236
+ })
237
+ })
238
+ .then(resp => resp.json())
239
+ .then(data => {
240
+ const dataFrame = document.getElementById("dataFrameDiv");
241
+ const dataFrameHead = dataFrame.querySelector("thead");
242
+ const dataFrameBody = dataFrame.querySelector("tbody");
243
+ document.getElementById("buttons").classList.remove("hidden");
244
+ document.getElementById("searchReq").classList.add("hidden");
245
+ document.getElementById("categorizeReq").classList.add("hidden");
246
+ document.getElementById("getReqs").classList.add("hidden");
247
+ document.getElementById("reqStatus").classList.add("hidden");
248
+ document.getElementById("downloadZip").classList.remove("hidden");
249
+
250
+ dataFrame.classList.remove("hidden");
251
+
252
+ dataFrameHead.innerHTML = `
253
+ <th>TDoc</th>
254
+ <th>Context</th>
255
+ <th>Requirement</th>
256
+ `;
257
+
258
+ dataFrameBody.innerHTML = "";
259
+
260
+ data.requirements.forEach(req => {
261
+ const tr = document.createElement("tr");
262
+ tr.innerHTML = `
263
+ <td>${req["document"]}</td>
264
+ <td>${req["context"]}</td>
265
+ <td>${req["requirement"]}</td>
266
+ `;
267
+ dataFrameBody.appendChild(tr);
268
+ })
269
+ })
270
+ }
271
+
272
+ function tableToGenBody(columnsMap) {
273
+ // columnsMap : { "NomHeaderDansTable": "nom_voulu", ... }
274
+ const dataFrame = document.getElementById("dataFrame");
275
+ const headers = Array.from(dataFrame.querySelectorAll('thead th')).map(th => th.innerText.trim());
276
+
277
+ // Indices des colonnes à extraire
278
+ const selectedIndices = headers
279
+ .map((header, idx) => columnsMap[header] ? idx : -1)
280
+ .filter(idx => idx !== -1);
281
+
282
+ return Array.from(dataFrame.querySelectorAll('tbody tr'))
283
+ .filter(row => getComputedStyle(row).display !== 'none')
284
+ .map(row => {
285
+ const cells = Array.from(row.querySelectorAll('td'));
286
+ const obj = {};
287
+ selectedIndices.forEach(idx => {
288
+ const originalHeader = headers[idx];
289
+ const newKey = columnsMap[originalHeader];
290
+ obj[newKey] = cells[idx].innerText.trim();
291
+ });
292
+ return obj;
293
+ });
294
+ }
295
+
296
+ function createCard(cardTitle, cardSub, cardText) {
297
+ return `
298
+ <div class="flex-none w-80 bg-white rounded-lg shadow-md p-6 mr-4 border border-gray-200 hover:shadow-lg transition-shadow duration-300">
299
+ <h3 class="text-lg font-semibold text-gray-800 mb-2">${cardTitle}</h3>
300
+ <p class="text-sm text-gray-600 mb-3 font-medium">${cardSub}</p>
301
+ <p class="text-gray-700 text-sm leading-relaxed">${cardText}</p>
302
+ </div>
303
+ `;
304
+ }
305
+
306
+ function createCarousel(carouselTitle, cards) {
307
+ let cardsHTML = cards.join("\n");
308
+ return `
309
+ <div class="mb-8">
310
+ <h2 class="text-xl font-bold text-gray-800 mb-4">${carouselTitle}</h2>
311
+ <div class="overflow-x-auto scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-200">
312
+ <div class="flex pb-4">
313
+ ${cardsHTML}
314
+ </div>
315
+ </div>
316
+ </div>
317
+ `;
318
+ }
319
+
320
+ function categorizeRequirements() {
321
+ fetch("https://game4all-reqroup.hf.space/categorize_requirements", {
322
+ method: "POST",
323
+ headers: {
324
+ "Content-Type": "application/json"
325
+ },
326
+ body: JSON.stringify({
327
+ requirements
328
+ })
329
+ })
330
+ .then(resp => resp.json())
331
+ .then(data => {
332
+ document.getElementById('dataFrameForm').classList.add('hidden');
333
+ document.getElementById('filters').classList.add('hidden');
334
+ document.getElementById('carousels').classList.remove('hidden');
335
+ document.getElementById('dataFrameDiv').classList.add('hidden');
336
+ document.getElementById('buttons').classList.add('hidden');
337
+ data.categories.forEach(cat => {
338
+ let reqCards = [];
339
+ cat.requirements.forEach(reqContent => { // Correction ici
340
+ reqCards.push(createCard(reqContent.document, reqContent.context, reqContent.requirement))
341
+ });
342
+ document.getElementById('carousels').innerHTML += createCarousel(cat.title, reqCards);
343
+ })
344
+ })
345
+ }
346
+
347
+ // Écouteurs d'événements pour les filtres
348
+
349
+ document.getElementById('docType').addEventListener('change', filterTable)
350
+ document.getElementById('docStatus').addEventListener('change', filterTable)
351
+ document.getElementById('agendaItem').addEventListener('change', filterTable)
352
+ document.getElementById("workingGroupSelect").addEventListener('change', getMeetings)
353
+ document.getElementById('getTDocs').addEventListener('click', getDataFrame)
354
+ document.getElementById("getReqs").addEventListener("click", generateRequirements);
355
+ document.getElementById('categorizeReq').addEventListener('click', categorizeRequirements);
356
+ document.getElementById("downloadZip").addEventListener('click', downloadTDocs)
357
+ document.getElementById("queryReq").addEventListener("click", queryRequirements)
358
+ document.getElementById('searchReq').addEventListener('click', () => {
359
+ document.getElementById('dataFrameForm').classList.add('hidden');
360
+ document.getElementById('filters').classList.add('hidden');
361
+ document.getElementById('queryReqForm').classList.remove('hidden');
362
+ document.getElementById('dataFrameDiv').classList.add('hidden');
363
+ document.getElementById('buttons').classList.add('hidden');
364
+ })