om4r932 commited on
Commit
0d2c020
·
1 Parent(s): ac3ccd5

Push reworked MVP

Browse files
Files changed (3) hide show
  1. app.py +3 -3
  2. index.html +129 -115
  3. static/script.js +564 -359
app.py CHANGED
@@ -359,7 +359,7 @@ def find_requirements_from_problem_description(req: ReqSearchRequest):
359
  requirements = req.requirements
360
  query = req.query
361
 
362
- requirements_text = "\n".join([f"[Selection ID: {r.i} | Document: {r.document} | Context: {r.context} | Requirement: {r.requirement}]" for r in requirements])
363
 
364
  print("Called the LLM")
365
  resp_ai = llm_router.completion(
@@ -369,9 +369,9 @@ def find_requirements_from_problem_description(req: ReqSearchRequest):
369
  )
370
  print("Answered")
371
  print(resp_ai.choices[0].message.content)
372
-
373
  out_llm = ReqSearchLLMResponse.model_validate_json(resp_ai.choices[0].message.content).selected
374
- if max(out_llm) > len(out_llm) - 1:
375
  raise HTTPException(status_code=500, detail="LLM error : Generated a wrong index, please try again.")
376
 
377
  return ReqSearchResponse(requirements=[requirements[i] for i in out_llm])
 
359
  requirements = req.requirements
360
  query = req.query
361
 
362
+ requirements_text = "\n".join([f"[Selection ID: {r.req_id} | Document: {r.document} | Context: {r.context} | Requirement: {r.requirement}]" for r in requirements])
363
 
364
  print("Called the LLM")
365
  resp_ai = llm_router.completion(
 
369
  )
370
  print("Answered")
371
  print(resp_ai.choices[0].message.content)
372
+
373
  out_llm = ReqSearchLLMResponse.model_validate_json(resp_ai.choices[0].message.content).selected
374
+ if max(out_llm) > len(requirements) - 1:
375
  raise HTTPException(status_code=500, detail="LLM error : Generated a wrong index, please try again.")
376
 
377
  return ReqSearchResponse(requirements=[requirements[i] for i in out_llm])
index.html CHANGED
@@ -1,148 +1,162 @@
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">
7
  <title>Requirements Extractor</title>
8
- <link href="https://cdn.jsdelivr.net/npm/daisyui@3.9.4/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>
22
- <option>SA4</option>
23
- <option>SA5</option>
24
- <option>SA6</option>
25
- <option>CT1</option>
26
- <option>CT2</option>
27
- <option>CT3</option>
28
- <option>CT4</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>
 
37
  </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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  </div>
56
 
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>
67
  </div>
68
  </div>
69
 
70
- <center>
71
- <span class="loading loading-bars loading-xl hidden" id="loadingBar"></span>
72
- <p class="hidden" id="progressText"></p>
73
- </center>
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- <!-- Tableau des données -->
76
- <div class="max-h-[65vh] overflow-y-auto mt-12" id="dataFrameDiv">
77
- <table class="table table-zebra w-full" id="dataFrame">
78
- <thead class="sticky top-0 bg-base-200 z-10">
79
- <tr class="bg-base-200">
80
- <th>TDoc</th>
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>
88
- <tbody>
89
- </tbody>
90
  </table>
91
  </div>
92
 
93
- <div id="categorizeReqContainer" class="hidden">
94
- <div class="w-full max-w-[100%] mx-auto p-6" id="carousels">
95
- <h1 class="text-xl font-bold mb-8 text-center">Requirements categories list</h1>
96
-
97
- </div>
98
- <div class="w-full max-w-[100%] mx-auto px-6 pb-6" id="catReqActions">
99
- <div class="flex items-center justify-center gap-4 flex-wrap">
100
- <!-- Checkbox et Input Limite -->
101
- <div class="flex items-center gap-2">
102
- <input
103
- type="number"
104
- id="limitInput"
105
- placeholder="10"
106
- min="1"
107
- max="9999"
108
- class="w-20 px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
109
- >
110
- </div>
111
-
112
- <!-- Boutons -->
113
 
114
- <button
115
- id="getSolutions"
116
- class="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors duration-200"
117
- >
118
- Get solutions
119
- </button>
120
-
121
- <button
122
- id="getSolutionsStepByStep"
123
- class="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors duration-200"
124
- >
125
- Get solutions (Step-by-step)
126
- </button>
127
- </div>
128
  </div>
 
129
  </div>
130
 
131
- <div id="criticizeSoluceContainer" class="hidden">
132
-
 
 
133
  </div>
134
 
135
- <center>
136
- <div id="buttons">
137
- <p id="reqStatus" class="mt-6 hidden">Requirements extracted</p>
138
- <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
139
- <button class="btn mt-6 hidden" id="getReqs">Extract Requirements</button>
140
- <button class="btn mt-6 hidden" id="downloadZip">Download TDocs</button>
141
- <button class="btn mt-6 hidden" id="searchReq">Query requirements</button>
142
- <button class="btn mt-6 hidden" id="categorizeReq">Categorize requirements</button>
143
- </div>
144
  </div>
145
- </center>
 
 
 
 
 
 
146
  </div>
147
 
148
  <script src="/static/script.js"></script>
 
1
  <!DOCTYPE html>
2
+ <html lang="fr">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Requirements Extractor</title>
 
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  </head>
9
+ <body class="bg-gray-100 min-h-screen">
10
+ <!-- Loading Overlay -->
11
+ <div id="loading-overlay" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
12
+ <div class="bg-white p-6 rounded-lg shadow-lg text-center">
13
+ <div id="loading-bar" class="w-64 h-4 bg-gray-200 rounded-full mb-4">
14
+ <div class="h-full bg-blue-500 rounded-full animate-pulse"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  </div>
16
+ <p id="progress-text" class="text-gray-700">Chargement en cours...</p>
17
  </div>
18
+ </div>
 
 
 
 
 
19
 
20
+ <div class="container mx-auto p-6">
21
+ <h1 class="text-3xl font-bold text-center mb-8">Requirements Extractor</h1>
 
 
22
 
23
+ <!-- Working Group Selection -->
24
+ <div id="working-group-container" class="mb-6">
25
+ <label for="working-group-select" class="block text-sm font-medium text-gray-700 mb-2">Working Group</label>
26
+ <select id="working-group-select" class="w-full p-2 border border-gray-300 rounded-md">
27
+ <option value="">Select a working group</option>
28
+ <option value="SA1">SA1</option>
29
+ <option value="SA2">SA2</option>
30
+ <option value="SA3">SA3</option>
31
+ <option value="SA4">SA4</option>
32
+ <option value="SA5">SA5</option>
33
+ <option value="SA6">SA6</option>
34
+ <option value="CT1">CT1</option>
35
+ <option value="CT2">CT2</option>
36
+ <option value="CT3">CT3</option>
37
+ <option value="CT4">CT4</option>
38
+ <option value="CT5">CT5</option>
39
+ <option value="CT6">CT6</option>
40
+ </select>
41
+ <button id="get-meetings-btn" class="mt-2 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
42
+ Get Meetings
43
+ </button>
44
  </div>
45
 
46
+ <!-- Meeting Selection -->
47
+ <div id="meeting-container" class="mb-6 hidden">
48
+ <label for="meeting-select" class="block text-sm font-medium text-gray-700 mb-2">Meeting</label>
49
+ <select id="meeting-select" class="w-full p-2 border border-gray-300 rounded-md">
50
+ <option value="">Select a meeting</option>
51
+ </select>
52
+ <button id="get-tdocs-btn" class="mt-2 px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600">
53
+ Get TDocs
54
+ </button>
55
+ </div>
56
+
57
+ <!-- Filters -->
58
+ <div id="filters-container" class="mb-6 hidden">
59
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
60
+ <div>
61
+ <label for="doc-type-filter" class="block text-sm font-medium text-gray-700 mb-2">Type</label>
62
+ <select id="doc-type-filter" class="w-full p-2 border border-gray-300 rounded-md">
63
+ <option value="">Tous</option>
64
+ </select>
65
+ </div>
66
+ <div>
67
+ <label for="doc-status-filter" class="block text-sm font-medium text-gray-700 mb-2">Status</label>
68
+ <select id="doc-status-filter" class="w-full p-2 border border-gray-300 rounded-md">
69
+ <option value="">Tous</option>
70
+ </select>
71
+ </div>
72
+ <div>
73
+ <label for="agenda-item-filter" class="block text-sm font-medium text-gray-700 mb-2">Agenda Item</label>
74
+ <select id="agenda-item-filter" class="w-full p-2 border border-gray-300 rounded-md">
75
+ <option value="">Tous</option>
76
+ </select>
77
  </div>
78
  </div>
79
  </div>
80
 
81
+ <!-- Action Buttons -->
82
+ <div id="action-buttons-container" class="mb-6 hidden">
83
+ <div class="flex flex-wrap gap-2">
84
+ <button id="download-tdocs-btn" class="px-4 py-2 bg-purple-500 text-white rounded-md hover:bg-purple-600">
85
+ Download TDocs
86
+ </button>
87
+ <button id="extract-requirements-btn" class="px-4 py-2 bg-orange-500 text-white rounded-md hover:bg-orange-600">
88
+ Extract Requirements
89
+ </button>
90
+ <button id="find-requirements-btn" class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 hidden">
91
+ Find Requirements
92
+ </button>
93
+ <button id="categorize-requirements-btn" class="px-4 py-2 bg-yellow-500 text-white rounded-md hover:bg-yellow-600 hidden">
94
+ Categorize Requirements
95
+ </button>
96
+ </div>
97
+ </div>
98
 
99
+ <!-- Data Table -->
100
+ <div id="data-table-container" class="mb-6 hidden">
101
+ <table id="data-table" class="w-full bg-white rounded-lg shadow overflow-hidden">
102
+ <thead class="bg-gray-50">
103
+ <tr>
104
+ <th class="px-4 py-2 text-left">
105
+ <input type="checkbox" id="select-all-checkbox">
106
+ </th>
107
+ <th class="px-4 py-2 text-left">TDoc</th>
108
+ <th class="px-4 py-2 text-left">Title</th>
109
+ <th class="px-4 py-2 text-left">Type</th>
110
+ <th class="px-4 py-2 text-left">Status</th>
111
+ <th class="px-4 py-2 text-left">Agenda Item</th>
112
+ <th class="px-4 py-2 text-left">URL</th>
113
  </tr>
114
  </thead>
115
+ <tbody></tbody>
 
116
  </table>
117
  </div>
118
 
119
+ <!-- Requirements Container -->
120
+ <div id="requirements-container" class="mb-6 hidden">
121
+ <h2 class="text-2xl font-bold mb-4">Requirements extracted</h2>
122
+ <div id="requirements-list"></div>
123
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ <!-- Query Requirements -->
126
+ <div id="query-requirements-container" class="mb-6 hidden">
127
+ <h2 class="text-2xl font-bold mb-4">Query requirements</h2>
128
+ <div class="flex gap-2">
129
+ <input type="text" id="query-input" class="flex-1 p-2 border border-gray-300 rounded-md" placeholder="Enter your query...">
130
+ <button id="search-requirements-btn" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">
131
+ Search
132
+ </button>
 
 
 
 
 
 
133
  </div>
134
+ <div id="query-results" class="mt-4"></div>
135
  </div>
136
 
137
+ <!-- Categorized Requirements Container -->
138
+ <div id="categorized-requirements-container" class="mb-6 hidden">
139
+ <h2 class="text-2xl font-bold mb-4">Requirements categories list</h2>
140
+ <div id="categorized-requirements-list"></div>
141
  </div>
142
 
143
+ <!-- Solutions Action Buttons -->
144
+ <div id="solutions-action-buttons-container" class="mb-6 hidden">
145
+ <div class="flex flex-wrap gap-2 justify-center">
146
+ <button id="get-solutions-btn" class="px-4 py-2 bg-indigo-500 text-white rounded-md hover:bg-indigo-600">
147
+ Get Solutions
148
+ </button>
149
+ <button id="get-solutions-step-btn" class="px-4 py-2 bg-pink-500 text-white rounded-md hover:bg-pink-600">
150
+ Get Solutions (Step-by-step)
151
+ </button>
152
  </div>
153
+ </div>
154
+
155
+ <!-- Solutions Container -->
156
+ <div id="solutions-container" class="mb-6 hidden">
157
+ <h2 class="text-2xl font-bold mb-4">Solutions</h2>
158
+ <div id="solutions-list"></div>
159
+ </div>
160
  </div>
161
 
162
  <script src="/static/script.js"></script>
static/script.js CHANGED
@@ -1,233 +1,394 @@
1
- let requirements;
2
- let categorizedRequirements;
 
 
 
3
  let solutionsCriticizedVersions = [];
4
  let isRequirements = false;
5
 
6
- function downloadTDocs() {
7
- document.getElementById("getReqs").setAttribute('disabled', 'true')
8
- document.getElementById("downloadZip").setAttribute('disabled', 'true')
9
- document.getElementById("progressText").classList.remove('hidden');
10
- document.getElementById("progressText").innerHTML = "Downloading TDocs, please wait, it may take a while ...";
11
- document.getElementById("loadingBar").classList.remove("hidden");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- const data = tableToGenBody({
14
- "TDoc": "doc"
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  });
16
- const dataSet = [...new Set(data.map(item => item.doc))];
17
- console.log(dataSet);
18
- let body = {
19
- "documents": dataSet,
20
- "meeting": document.getElementById('meetingSelect').value
21
- };
22
- if (document.getElementById('agendaItem').value != "" | document.getElementById('agendaItem').value !=
23
- "Tous") {
24
- body['agenda_item'] = document.getElementById('agendaItem').value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
- fetch('/download_tdocs', {
27
- method: "POST",
28
- headers: {
29
- "Content-Type": "application/json"
30
- },
31
- body: JSON.stringify(body)
32
- })
33
- .then(resp => resp.blob())
34
- .then(blob => {
35
- document.getElementById("getReqs").removeAttribute('disabled')
36
- document.getElementById("downloadZip").removeAttribute('disabled')
37
- document.getElementById("loadingBar").classList.add("hidden");
38
- document.getElementById("progressText").classList.add("hidden");
39
- const url = window.URL.createObjectURL(blob);
40
- const a = document.createElement("a");
41
- a.href = url;
42
- let dl_name = `${document.getElementById('meetingSelect').value}`;
43
- if (document.getElementById('agendaItem').value != "" | document.getElementById('agendaItem')
44
- .value != "Tous") {
45
- dl_name = dl_name + `_${document.getElementById('agendaItem').value}`
46
- };
47
- if (document.getElementById('docStatus').value != "" | document.getElementById('docStatus')
48
- .value != "Tous") {
49
- dl_name = dl_name + `_${document.getElementById('docStatus').value}`
50
- };
51
- if (document.getElementById('docType').value != "" | document.getElementById('docType').value !=
52
- "Tous") {
53
- dl_name = `${document.getElementById('docType').value}_${dl_name}`
54
- };
55
- if (isRequirements) {
56
- dl_name = `requirements_${dl_name}_${url.split('/').pop()}`
57
- }
58
- dl_name = dl_name + ".zip";
59
- a.download = dl_name;
60
- document.body.appendChild(a);
61
- a.click();
62
- a.remove();
63
- window.URL.revokeObjectURL(url); // libération mémoire
64
- })
65
  }
66
 
67
- function getDataFrame() {
68
- isRequirements = false;
69
- document.getElementById("progressText").classList.remove('hidden');
70
- document.getElementById("progressText").innerHTML = "Getting TDoc list of meeting ...";
71
- document.getElementById("loadingBar").classList.remove("hidden");
72
- document.getElementById("getTDocs").setAttribute('disabled', 'true')
73
-
74
- const wg = document.getElementById('workingGroupSelect').value;
75
- const meeting = document.getElementById('meetingSelect').value;
76
-
77
- document.getElementById('docType').innerHTML = `<option disabled selected value="">Type</option><option>Tous</option>`
78
- document.getElementById('docStatus').innerHTML = `<option disabled selected value="">Status</option><option>Tous</option>`
79
- document.getElementById('agendaItem').innerHTML = `<option disabled selected value="">Agenda Item</option><option>Tous</option>`
80
-
81
- const dataFrame = document.getElementById("dataFrame");
82
- fetch("/get_dataframe", {
83
- method: "POST",
84
- headers: {
85
- "Content-Type": "application/json"
86
- },
87
- body: JSON.stringify({
88
- "working_group": wg,
89
- "meeting": meeting
90
- })
91
- })
92
- .then(resp => resp.json())
93
- .then(data => {
94
- document.getElementById("filters").classList.remove("hidden");
95
- document.getElementById("downloadZip").classList.remove("hidden");
96
- document.getElementById("getReqs").classList.remove("hidden");
97
- document.getElementById("getTDocs").removeAttribute("disabled")
98
- document.getElementById("progressText").classList.add('hidden');
99
- document.getElementById("loadingBar").classList.add("hidden");
100
-
101
- const dataframeBody = dataFrame.querySelector("tbody");
102
- dataframeBody.innerHTML = "";
103
- const setType = new Set();
104
- const setAgenda = new Set();
105
- const setStatus = new Set();
106
- data.data.forEach(row => {
107
- const tr = document.createElement("tr");
108
- tr.setAttribute("data-type", row['Type']);
109
- tr.setAttribute("data-status", row["TDoc Status"]);
110
- tr.setAttribute("data-agenda", row["Agenda item description"]);
111
- tr.innerHTML = `
112
- <td>${row["TDoc"]}</td>
113
- <td>${row["Title"]}</td>
114
- <td>${row["Type"]}</td>
115
- <td>${row["TDoc Status"]}</td>
116
- <td>${row["Agenda item description"]}</td>
117
- <td>
118
- <a href="${row["URL"]}" class="link">${row["URL"]}</a>
119
- </td>
120
- `;
121
- dataframeBody.appendChild(tr);
122
- setType.add(row["Type"]);
123
- setAgenda.add(row["Agenda item description"]);
124
- setStatus.add(row["TDoc Status"]);
125
- })
126
 
127
- setType.forEach(tdoctype => {
128
- const option = document.createElement("option");
129
- option.textContent = tdoctype;
130
- option.value = tdoctype;
131
- document.getElementById('docType').appendChild(option);
132
- })
133
 
134
- setAgenda.forEach(agenda => {
135
- const option = document.createElement("option");
136
- option.textContent = agenda;
137
- option.value = agenda;
138
- document.getElementById('agendaItem').appendChild(option);
139
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- setStatus.forEach(status => {
142
- const option = document.createElement("option");
143
- option.textContent = status;
144
- option.value = status;
145
- document.getElementById('docStatus').appendChild(option);
146
- })
147
- })
 
 
 
148
  }
149
 
150
- function filterTable() {
151
- const type = document.getElementById('docType').value
152
- const status = document.getElementById('docStatus').value
153
- const agenda = document.getElementById('agendaItem').value
 
 
 
 
 
 
 
154
 
155
- document.querySelectorAll('#dataFrame tbody tr').forEach(row => {
156
- const showRow =
157
- (type === 'Tous' || row.dataset.type === type || type === "") &&
158
- (status === 'Tous' || row.dataset.status === status || status === "") &&
159
- (agenda === 'Tous' || row.dataset.agenda === agenda || agenda === "")
 
160
 
161
- row.style.display = showRow ? '' : 'none'
162
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  }
164
 
165
- function getMeetings() {
166
- isRequirements = false;
167
- const workingGroup = document.getElementById("workingGroupSelect").value;
168
- document.getElementById("meetingSelect").setAttribute('disabled', 'true')
169
- document.getElementById("meetingSelect").innerHTML = "<option>Loading...</option>"
170
- document.getElementById("getTDocs").setAttribute('disabled', 'true')
171
- fetch("/get_meetings", {
172
- method: "POST",
173
- headers: {
174
- "Content-Type": "application/json"
175
- },
176
- body: JSON.stringify({
177
- "working_group": workingGroup
178
- })
179
- })
180
- .then(resp => resp.json())
181
- .then(data => {
182
- document.getElementById("meetingSelect").innerHTML = "";
183
- for (const [key, value] of Object.entries(data.meetings)) {
184
- const option = document.createElement("option");
185
- option.textContent = key;
186
- option.value = value;
187
- document.getElementById('meetingSelect').appendChild(option);
188
- }
189
- document.getElementById("meetingSelect").removeAttribute("disabled");
190
- document.getElementById("getTDocs").removeAttribute("disabled")
191
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
 
194
- function generateRequirements() {
195
- isRequirements = false;
196
- document.getElementById("getReqs").setAttribute('disabled', 'true')
197
- document.getElementById("downloadZip").setAttribute('disabled', 'true')
198
- document.getElementById("progressText").classList.remove('hidden');
199
- document.getElementById("progressText").innerHTML = "Generating requirements, please wait, it may take a while ...";
200
- document.getElementById("loadingBar").classList.remove("hidden");
201
-
202
- const bodyreq = tableToGenBody({
203
- "TDoc": "document",
204
- "URL": "url"
205
  });
 
206
 
207
- fetch("/generate_requirements", {
208
- method: "POST",
209
- headers: {
210
- "Content-Type": "application/json"
211
- },
212
- body: JSON.stringify({
213
- "documents": bodyreq
214
- })
215
- })
216
- .then(resp => resp.json())
217
- .then(data => {
218
- let req_id = 0;
219
- document.getElementById("loadingBar").classList.add("hidden");
220
- document.getElementById("progressText").classList.add("hidden");
221
- document.getElementById("reqStatus").classList.remove("hidden");
222
- document.getElementById("getReqs").classList.add("hidden");
223
- document.getElementById("searchReq").classList.remove("hidden");
224
- document.getElementById("categorizeReq").classList.remove("hidden");
225
- document.getElementById("getReqs").removeAttribute('disabled')
226
- document.getElementById("downloadZip").removeAttribute('disabled')
227
- requirements = [];
228
- data.requirements.forEach(obj => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  obj.requirements.forEach(req => {
230
- requirements.push({
231
  req_id,
232
  "document": obj.document,
233
  "context": obj.context,
@@ -236,142 +397,180 @@ function generateRequirements() {
236
  req_id++;
237
  })
238
  })
239
- })
 
 
 
 
 
 
 
 
 
 
 
 
240
  }
241
 
242
- function queryRequirements() {
243
- fetch("/get_reqs_from_query", {
244
- method: "POST",
245
- headers: {
246
- "Content-Type": "application/json"
247
- },
248
- body: JSON.stringify({
249
- query: document.getElementById("problemDescription").value,
250
- requirements
251
- })
252
- })
253
- .then(resp => resp.json())
254
- .then(data => {
255
- const dataFrame = document.getElementById("dataFrameDiv");
256
- const dataFrameHead = dataFrame.querySelector("thead");
257
- const dataFrameBody = dataFrame.querySelector("tbody");
258
- document.getElementById("buttons").classList.remove("hidden");
259
- document.getElementById("searchReq").classList.add("hidden");
260
- document.getElementById("categorizeReq").classList.add("hidden");
261
- document.getElementById("getReqs").classList.add("hidden");
262
- document.getElementById("reqStatus").classList.add("hidden");
263
- document.getElementById("downloadZip").classList.remove("hidden");
264
- isRequirements = true;
265
-
266
- dataFrame.classList.remove("hidden");
267
-
268
- dataFrameHead.innerHTML = `
269
- <th>TDoc</th>
270
- <th>Context</th>
271
- <th>Requirement</th>
272
- `;
273
-
274
- dataFrameBody.innerHTML = "";
275
-
276
- data.requirements.forEach(req => {
277
- const tr = document.createElement("tr");
278
- tr.innerHTML = `
279
- <td>${req["document"]}</td>
280
- <td>${req["context"]}</td>
281
- <td>${req["requirement"]}</td>
282
- `;
283
- dataFrameBody.appendChild(tr);
284
- })
285
- })
286
  }
287
 
288
- function tableToGenBody(columnsMap) {
289
- // columnsMap : { "NomHeaderDansTable": "nom_voulu", ... }
290
- const dataFrame = document.getElementById("dataFrame");
291
- const headers = Array.from(dataFrame.querySelectorAll('thead th')).map(th => th.innerText.trim());
292
-
293
- // Indices des colonnes à extraire
294
- const selectedIndices = headers
295
- .map((header, idx) => columnsMap[header] ? idx : -1)
296
- .filter(idx => idx !== -1);
297
-
298
- return Array.from(dataFrame.querySelectorAll('tbody tr'))
299
- .filter(row => getComputedStyle(row).display !== 'none')
300
- .map(row => {
301
- const cells = Array.from(row.querySelectorAll('td'));
302
- const obj = {};
303
- selectedIndices.forEach(idx => {
304
- const originalHeader = headers[idx];
305
- const newKey = columnsMap[originalHeader];
306
- obj[newKey] = cells[idx].innerText.trim();
307
- });
308
- return obj;
309
  });
310
- }
311
 
312
- function createCard(cardTitle, cardSub, cardText) {
313
- return `
314
- <div class="bg-white rounded-lg shadow-md p-6 border border-gray-200 hover:shadow-lg transition-shadow duration-300">
315
- <h3 class="text-lg font-semibold text-gray-800 mb-2">${cardTitle}</h3>
316
- <p class="text-sm text-gray-600 mb-3 font-medium">${cardSub}</p>
317
- <p class="text-gray-700 text-sm leading-relaxed">${cardText}</p>
318
- </div>
319
- `;
 
 
 
 
 
 
 
320
  }
321
 
 
 
 
 
 
 
 
322
 
323
- function createCarousel(carouselTitle, id, cards) {
324
- let cardsHTML = cards.join("\n");
325
- return `
326
- <div class="mb-8" id="category-${id}">
327
- <h2 class="text-xl font-bold text-gray-800 mb-4">${carouselTitle}</h2>
328
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
329
- ${cardsHTML}
 
 
 
 
 
 
330
  </div>
331
- </div>
332
- `;
 
 
333
  }
334
 
335
- function categorizeRequirements(max_categories) {
336
- isRequirements = false;
337
- document.getElementById('categorizeReq').setAttribute('disabled', 'true');
338
- document.getElementById('downloadZip').setAttribute('disabled', 'true');
339
- document.getElementById('searchReq').setAttribute('disabled', 'true');
340
- document.getElementById('progressText').classList.remove('hidden');
341
- document.getElementById('loadingBar').classList.remove('hidden');
342
- document.getElementById('progressText').textContent = "Grouping requirements by their context ...";
343
-
344
- fetch("https://game4all-reqroup.hf.space/reqs/categorize_requirements", {
345
- method: "POST",
346
- headers: {
347
- "Content-Type": "application/json"
348
- },
349
- body: JSON.stringify({
350
- requirements,
351
- "max_n_categories": max_categories
352
- })
353
- })
354
- .then(resp => resp.json())
355
- .then(data => {
356
- categorizedRequirements = data;
357
- document.getElementById('dataFrameForm').classList.add('hidden');
358
- document.getElementById('filters').classList.add('hidden');
359
- document.getElementById('categorizeReqContainer').classList.remove('hidden');
360
- document.getElementById('dataFrameDiv').classList.add('hidden');
361
- document.getElementById('buttons').classList.add('hidden');
362
- document.getElementById('carousels').innerHTML = '<h1 class="text-xl font-bold mb-8 text-center">Requirements categories list</h1>'
363
- data.categories.forEach(cat => {
364
- let reqCards = [];
365
- cat.requirements.forEach(reqContent => { // Correction ici
366
- reqCards.push(createCard(reqContent.document, reqContent.context, reqContent.requirement))
367
- });
368
- document.getElementById('carousels').innerHTML += createCarousel(cat.title, cat.id, reqCards);
369
- })
370
- })
371
  }
372
 
373
- // Variable globale pour tracker les états d'ouverture par catégorie
374
- let accordionStates = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
  function createSolutionAccordion(solutionCriticizedHistory, containerId, versionIndex = 0, categoryIndex = null) {
377
  const container = document.getElementById(containerId);
@@ -611,25 +810,22 @@ function initializeSolutionAccordion(solutionCriticizedHistory, containerId, sta
611
  // Réinitialiser les états d'accordéon
612
  accordionStates = {};
613
  createSolutionAccordion(solutionCriticizedHistory, containerId, startVersion);
614
- document.getElementById("criticizeSoluceContainer").classList.remove('hidden')
615
  }
616
 
617
  async function generateSolutions(){
618
- isRequirements = false;
619
  let response = await fetch('https://game4all-reqroup.hf.space/solution/search_solutions_gemini', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(categorizedRequirements)})
620
  let responseObj = await response.json()
621
  return responseObj;
622
  }
623
 
624
  async function generateCriticisms(solutions){
625
- isRequirements = false;
626
  let response = await fetch('https://game4all-reqroup.hf.space/solution/criticize_solution', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(solutions)})
627
  let responseObj = await response.json()
628
  solutionsCriticizedVersions.push(responseObj)
629
  }
630
 
631
  async function refineSolutions(critiques){
632
- isRequirements = false;
633
  let response = await fetch('https://game4all-reqroup.hf.space/solution/refine_solution', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(critiques)})
634
  let responseObj = await response.json()
635
  await generateCriticisms(responseObj)
@@ -637,6 +833,7 @@ async function refineSolutions(critiques){
637
 
638
  async function workflow(steps = 1){
639
  let soluce;
 
640
  for(let step = 1; step <= steps; step++){
641
  if(solutionsCriticizedVersions.length == 0){
642
  soluce = await generateSolutions();
@@ -646,30 +843,38 @@ async function workflow(steps = 1){
646
  await refineSolutions(prevSoluce)
647
  }
648
  }
649
- initializeSolutionAccordion(solutionsCriticizedVersions, "criticizeSoluceContainer")
 
650
  }
651
 
652
-
653
- // Écouteurs d'événements pour les filtres
654
-
655
- document.getElementById('docType').addEventListener('change', filterTable)
656
- document.getElementById('docStatus').addEventListener('change', filterTable)
657
- document.getElementById('agendaItem').addEventListener('change', filterTable)
658
- document.getElementById("workingGroupSelect").addEventListener('change', getMeetings)
659
- document.getElementById('getTDocs').addEventListener('click', getDataFrame)
660
- document.getElementById("getReqs").addEventListener("click", generateRequirements);
661
- document.getElementById('categorizeReq').addEventListener('click', () => categorizeRequirements(8));
662
- document.getElementById("downloadZip").addEventListener('click', downloadTDocs)
663
- document.getElementById("queryReq").addEventListener("click", queryRequirements)
664
- document.getElementById('getSolutionsStepByStep').addEventListener('click', async () => await workflow())
665
- document.getElementById("getSolutions").addEventListener('click', async () => {
666
- let steps = document.getElementById('limitInput').value != '' ? document.getElementById('limitInput').value : 10;
667
- await workflow(steps)
668
- })
669
- document.getElementById('searchReq').addEventListener('click', () => {
670
- document.getElementById('dataFrameForm').classList.add('hidden');
671
- document.getElementById('filters').classList.add('hidden');
672
- document.getElementById('queryReqForm').classList.remove('hidden');
673
- document.getElementById('dataFrameDiv').classList.add('hidden');
674
- document.getElementById('buttons').classList.add('hidden');
675
- })
 
 
 
 
 
 
 
 
1
+ // Variables globales
2
+ let requirements = [];
3
+ let accordionStates = {};
4
+ let formattedRequirements = [];
5
+ let categorizedRequirements = [];
6
  let solutionsCriticizedVersions = [];
7
  let isRequirements = false;
8
 
9
+ // =============================================================================
10
+ // FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS
11
+ // =============================================================================
12
+
13
+ /**
14
+ * Active/désactive des éléments par leurs IDs
15
+ * @param {string[]} elementIds - Liste des IDs des éléments à activer
16
+ * @param {boolean} enabled - true pour activer, false pour désactiver
17
+ */
18
+ function toggleElementsEnabled(elementIds, enabled = true) {
19
+ elementIds.forEach(id => {
20
+ const element = document.getElementById(id);
21
+ if (element) {
22
+ if (enabled) {
23
+ element.removeAttribute('disabled');
24
+ } else {
25
+ element.setAttribute('disabled', 'true');
26
+ }
27
+ }
28
+ });
29
+ }
30
 
31
+ /**
32
+ * Affiche/masque des conteneurs par leurs IDs
33
+ * @param {string[]} containerIds - Liste des IDs des conteneurs à afficher
34
+ * @param {boolean} visible - true pour afficher, false pour masquer
35
+ */
36
+ function toggleContainersVisibility(containerIds, visible = true) {
37
+ containerIds.forEach(id => {
38
+ const container = document.getElementById(id);
39
+ if (container) {
40
+ if (visible) {
41
+ container.classList.remove('hidden');
42
+ } else {
43
+ container.classList.add('hidden');
44
+ }
45
+ }
46
  });
47
+ }
48
+
49
+ /**
50
+ * Affiche le loading overlay avec un message personnalisé
51
+ * @param {string} message - Message à afficher
52
+ */
53
+ function showLoadingOverlay(message = 'Chargement en cours...') {
54
+ document.getElementById('progress-text').textContent = message;
55
+ toggleContainersVisibility(['loading-overlay'], true);
56
+ }
57
+
58
+ /**
59
+ * Masque le loading overlay
60
+ */
61
+ function hideLoadingOverlay() {
62
+ toggleContainersVisibility(['loading-overlay'], false);
63
+ }
64
+
65
+ /**
66
+ * Réinitialise un select et ajoute des options
67
+ * @param {string} selectId - ID du select
68
+ * @param {Object} options - Objet avec les options {value: text}
69
+ * @param {string} defaultText - Texte par défaut
70
+ */
71
+ function populateSelect(selectId, options, defaultText = 'Sélectionner...') {
72
+ const select = document.getElementById(selectId);
73
+ if (select) {
74
+ select.innerHTML = `<option value="">${defaultText}</option>`;
75
+ Object.entries(options).forEach(([text, value]) => {
76
+ const option = document.createElement('option');
77
+ option.value = value;
78
+ option.textContent = text;
79
+ select.appendChild(option);
80
+ });
81
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
 
84
+ /**
85
+ * Extrait les données du tableau selon un mapping
86
+ * @param {Object} mapping - Mapping des colonnes {columnName: propertyName}
87
+ * @returns {Array} Données extraites
88
+ */
89
+ function extractTableData(mapping) {
90
+ const tbody = document.querySelector('#data-table tbody');
91
+ const rows = tbody.querySelectorAll('tr');
92
+ const data = [];
93
+
94
+ rows.forEach(row => {
95
+ const checkboxes = row.querySelectorAll('input[type="checkbox"]:checked');
96
+ if (checkboxes.length > 0) {
97
+ const rowData = {};
98
+ Object.entries(mapping).forEach(([columnName, propertyName]) => {
99
+ const cell = row.querySelector(`td[data-column="${columnName}"]`);
100
+ if (cell) {
101
+ if(columnName == "URL") {
102
+ rowData[propertyName] = cell.querySelector('a').getAttribute('href');
103
+ } else {
104
+ rowData[propertyName] = cell.textContent.trim();
105
+ }
106
+ }
107
+ });
108
+ data.push(rowData);
109
+ }
110
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ return data;
113
+ }
 
 
 
 
114
 
115
+ // =============================================================================
116
+ // FONCTIONS MÉTIER
117
+ // =============================================================================
118
+
119
+ /**
120
+ * Récupère la liste des meetings pour un working group
121
+ */
122
+ async function getMeetings() {
123
+ const workingGroup = document.getElementById('working-group-select').value;
124
+ if (!workingGroup) return;
125
+
126
+ showLoadingOverlay('Récupération des meetings...');
127
+ toggleElementsEnabled(['get-meetings-btn'], false);
128
+
129
+ try {
130
+ const response = await fetch('/get_meetings', {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify({ working_group: workingGroup })
134
+ });
135
 
136
+ const data = await response.json();
137
+ populateSelect('meeting-select', data.meetings, 'Select a meeting');
138
+ toggleContainersVisibility(['meeting-container'], true);
139
+ } catch (error) {
140
+ console.error('Erreur lors de la récupération des meetings:', error);
141
+ alert('Erreur lors de la récupération des meetings');
142
+ } finally {
143
+ hideLoadingOverlay();
144
+ toggleElementsEnabled(['get-meetings-btn'], true);
145
+ }
146
  }
147
 
148
+ /**
149
+ * Récupère la liste des TDocs pour un meeting
150
+ */
151
+ async function getTDocs() {
152
+ const workingGroup = document.getElementById('working-group-select').value;
153
+ const meeting = document.getElementById('meeting-select').value;
154
+
155
+ if (!workingGroup || !meeting) return;
156
+
157
+ showLoadingOverlay('Récupération de la liste des TDocs...');
158
+ toggleElementsEnabled(['get-tdocs-btn'], false);
159
 
160
+ try {
161
+ const response = await fetch('/get_dataframe', {
162
+ method: 'POST',
163
+ headers: { 'Content-Type': 'application/json' },
164
+ body: JSON.stringify({ working_group: workingGroup, meeting: meeting })
165
+ });
166
 
167
+ const data = await response.json();
168
+ populateDataTable(data.data);
169
+ setupFilters(data.data);
170
+
171
+ toggleContainersVisibility([
172
+ 'filters-container',
173
+ 'action-buttons-container',
174
+ 'data-table-container'
175
+ ], true);
176
+
177
+ isRequirements = false;
178
+ } catch (error) {
179
+ console.error('Erreur lors de la récupération des TDocs:', error);
180
+ alert('Erreur lors de la récupération des TDocs');
181
+ } finally {
182
+ hideLoadingOverlay();
183
+ toggleElementsEnabled(['get-tdocs-btn'], true);
184
+ }
185
  }
186
 
187
+ /**
188
+ * Remplit le tableau de données
189
+ * @param {Array} data - Données à afficher
190
+ */
191
+ function populateDataTable(data) {
192
+ const tbody = document.querySelector('#data-table tbody');
193
+ tbody.innerHTML = '';
194
+
195
+ data.forEach(row => {
196
+ const tr = document.createElement('tr');
197
+ tr.setAttribute('data-type', row.Type || '');
198
+ tr.setAttribute('data-status', row['TDoc Status'] || '');
199
+ tr.setAttribute('data-agenda', row['Agenda item description'] || '');
200
+
201
+ tr.innerHTML = `
202
+ <td class="px-4 py-2">
203
+ <input type="checkbox" class="row-checkbox">
204
+ </td>
205
+ <td class="px-4 py-2" data-column="TDoc">${row.TDoc || ''}</td>
206
+ <td class="px-4 py-2" data-column="Title">${row.Title || ''}</td>
207
+ <td class="px-4 py-2" data-column="Type">${row.Type || ''}</td>
208
+ <td class="px-4 py-2" data-column="Status">${row['TDoc Status'] || ''}</td>
209
+ <td class="px-4 py-2" data-column="Agenda">${row['Agenda item description'] || ''}</td>
210
+ <td class="px-4 py-2" data-column="URL">
211
+ <a href="${row.URL || '#'}" target="_blank" class="text-blue-500 hover:underline">
212
+ ${row.URL ? 'Lien' : 'N/A'}
213
+ </a>
214
+ </td>
215
+ `;
216
+
217
+ tbody.appendChild(tr);
218
+ });
219
+
220
+ setupTableEvents();
221
+ }
222
+
223
+ /**
224
+ * Configure les filtres basés sur les données
225
+ * @param {Array} data - Données pour extraire les valeurs uniques
226
+ */
227
+ function setupFilters(data) {
228
+ const types = [...new Set(data.map(item => item.Type).filter(Boolean))];
229
+ const statuses = [...new Set(data.map(item => item['TDoc Status']).filter(Boolean))];
230
+ const agendaItems = [...new Set(data.map(item => item['Agenda item description']).filter(Boolean))];
231
+
232
+ populateSelect('doc-type-filter', Object.fromEntries(types.map(t => [t, t])), 'Tous');
233
+ populateSelect('doc-status-filter', Object.fromEntries(statuses.map(s => [s, s])), 'Tous');
234
+ populateSelect('agenda-item-filter', Object.fromEntries(agendaItems.map(a => [a, a])), 'Tous');
235
+
236
+ setupFilterEvents();
237
  }
238
 
239
+ /**
240
+ * Configure les événements des filtres
241
+ */
242
+ function setupFilterEvents() {
243
+ ['doc-type-filter', 'doc-status-filter', 'agenda-item-filter'].forEach(filterId => {
244
+ document.getElementById(filterId).addEventListener('change', applyFilters);
 
 
 
 
 
245
  });
246
+ }
247
 
248
+ /**
249
+ * Applique les filtres au tableau
250
+ */
251
+ function applyFilters() {
252
+ const typeFilter = document.getElementById('doc-type-filter').value;
253
+ const statusFilter = document.getElementById('doc-status-filter').value;
254
+ const agendaFilter = document.getElementById('agenda-item-filter').value;
255
+
256
+ const rows = document.querySelectorAll('#data-table tbody tr');
257
+
258
+ rows.forEach(row => {
259
+ const type = row.getAttribute('data-type');
260
+ const status = row.getAttribute('data-status');
261
+ const agenda = row.getAttribute('data-agenda');
262
+
263
+ const typeMatch = !typeFilter || type === typeFilter;
264
+ const statusMatch = !statusFilter || status === statusFilter;
265
+ const agendaMatch = !agendaFilter || agenda === agendaFilter;
266
+
267
+ if (typeMatch && statusMatch && agendaMatch) {
268
+ row.style.display = '';
269
+ } else {
270
+ row.style.display = 'none';
271
+ }
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Configure les événements du tableau
277
+ */
278
+ function setupTableEvents() {
279
+ // Checkbox "Sélectionner tout"
280
+ document.getElementById('select-all-checkbox').addEventListener('change', function() {
281
+ const checkboxes = document.querySelectorAll('.row-checkbox');
282
+ checkboxes.forEach(checkbox => {
283
+ if(checkbox.parentElement.parentElement.style.display != "none"){checkbox.checked = this.checked;}
284
+ });
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Télécharge les TDocs sélectionnés
290
+ */
291
+ async function downloadTDocs() {
292
+ showLoadingOverlay('Téléchargement des TDocs en cours...');
293
+ toggleElementsEnabled(['download-tdocs-btn', 'extract-requirements-btn'], false);
294
+
295
+ try {
296
+ // Extraire les données du tableau avec TDoc et URL
297
+ const selectedData = extractTableData({ 'TDoc': 'document', 'URL': 'url' });
298
+ if (selectedData.length === 0) {
299
+ alert('Veuillez sélectionner au moins un document');
300
+ return;
301
+ }
302
+
303
+ // Transformer au format requis: [{tdoc_id: url}, ...]
304
+ const documents = selectedData.map(obj => obj.document)
305
+
306
+ const response = await fetch('/download_tdocs', {
307
+ method: 'POST',
308
+ headers: { 'Content-Type': 'application/json' },
309
+ body: JSON.stringify({ documents: documents })
310
+ });
311
+
312
+ const blob = await response.blob();
313
+ downloadBlob(blob, generateDownloadFilename());
314
+ } catch (error) {
315
+ console.error('Erreur lors du téléchargement:', error);
316
+ alert('Erreur lors du téléchargement des TDocs');
317
+ } finally {
318
+ hideLoadingOverlay();
319
+ toggleElementsEnabled(['download-tdocs-btn', 'extract-requirements-btn'], true);
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Génère un nom de fichier pour le téléchargement
325
+ * @returns {string} Nom du fichier
326
+ */
327
+ function generateDownloadFilename() {
328
+ let filename = document.getElementById('meeting-select').value || 'documents';
329
+
330
+ const agendaItem = document.getElementById('agenda-item-filter').value;
331
+ const docStatus = document.getElementById('doc-status-filter').value;
332
+ const docType = document.getElementById('doc-type-filter').value;
333
+
334
+ if (agendaItem && agendaItem !== 'Tous') {
335
+ filename += `_${agendaItem}`;
336
+ }
337
+ if (docStatus && docStatus !== 'Tous') {
338
+ filename += `_${docStatus}`;
339
+ }
340
+ if (docType && docType !== 'Tous') {
341
+ filename = `${docType}_${filename}`;
342
+ }
343
+ if (isRequirements) {
344
+ filename = `requirements_${filename}`;
345
+ }
346
+
347
+ return `${filename}.zip`;
348
+ }
349
+
350
+ /**
351
+ * Télécharge un blob
352
+ * @param {Blob} blob - Blob à télécharger
353
+ * @param {string} filename - Nom du fichier
354
+ */
355
+ function downloadBlob(blob, filename) {
356
+ const url = window.URL.createObjectURL(blob);
357
+ const a = document.createElement('a');
358
+ a.href = url;
359
+ a.download = filename;
360
+ document.body.appendChild(a);
361
+ a.click();
362
+ a.remove();
363
+ window.URL.revokeObjectURL(url);
364
+ }
365
+
366
+ /**
367
+ * Extrait les requirements des documents sélectionnés
368
+ */
369
+ async function extractRequirements() {
370
+ const selectedData = extractTableData({ 'TDoc': 'document', 'URL': 'url' });
371
+ if (selectedData.length === 0) {
372
+ alert('Veuillez sélectionner au moins un document');
373
+ return;
374
+ }
375
+
376
+ showLoadingOverlay('Extraction des requirements en cours...');
377
+ toggleElementsEnabled(['extract-requirements-btn'], false);
378
+
379
+ try {
380
+ const response = await fetch('/generate_requirements', {
381
+ method: 'POST',
382
+ headers: { 'Content-Type': 'application/json' },
383
+ body: JSON.stringify({ documents: selectedData })
384
+ });
385
+
386
+ const data = await response.json();
387
+ requirements = data.requirements;
388
+ let req_id = 0;
389
+ data.requirements.forEach(obj => {
390
  obj.requirements.forEach(req => {
391
+ formattedRequirements.push({
392
  req_id,
393
  "document": obj.document,
394
  "context": obj.context,
 
397
  req_id++;
398
  })
399
  })
400
+ displayRequirements(requirements);
401
+
402
+ toggleContainersVisibility(['requirements-container', 'query-requirements-container'], true);
403
+ toggleContainersVisibility(['find-requirements-btn', 'categorize-requirements-btn', 'get-solutions-btn', 'get-solutions-step-btn'], true);
404
+
405
+ isRequirements = true;
406
+ } catch (error) {
407
+ console.error('Erreur lors de l\'extraction des requirements:', error);
408
+ alert('Erreur lors de l\'extraction des requirements');
409
+ } finally {
410
+ hideLoadingOverlay();
411
+ toggleElementsEnabled(['extract-requirements-btn'], true);
412
+ }
413
  }
414
 
415
+ /**
416
+ * Affiche les requirements
417
+ * @param {Array} requirementsData - Données des requirements
418
+ */
419
+ function displayRequirements(requirementsData) {
420
+ const container = document.getElementById('requirements-list');
421
+ container.innerHTML = '';
422
+
423
+ requirementsData.forEach((docReq, docIndex) => {
424
+ const docDiv = document.createElement('div');
425
+ docDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white';
426
+
427
+ docDiv.innerHTML = `
428
+ <h3 class="text-lg font-semibold mb-2">${docReq.document}</h3>
429
+ <p class="text-gray-600 mb-3">${docReq.context}</p>
430
+ <ul class="list-disc list-inside space-y-1">
431
+ ${docReq.requirements.map((req, reqIndex) =>
432
+ `<li class="text-sm" data-req-id="${docIndex}-${reqIndex}">${req}</li>`
433
+ ).join('')}
434
+ </ul>
435
+ `;
436
+
437
+ container.appendChild(docDiv);
438
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  }
440
 
441
+ /**
442
+ * Catégorise les requirements
443
+ */
444
+ async function categorizeRequirements(max_categories) {
445
+ if (!formattedRequirements || formattedRequirements.length === 0) {
446
+ alert('Aucun requirement à catégoriser');
447
+ return;
448
+ }
449
+
450
+ showLoadingOverlay('Catégorisation des requirements en cours...');
451
+ toggleElementsEnabled(['categorize-requirements-btn'], false);
452
+
453
+ try {
454
+ const response = await fetch('https://game4all-reqroup.hf.space/reqs/categorize_requirements', {
455
+ method: 'POST',
456
+ headers: { 'Content-Type': 'application/json' },
457
+ body: JSON.stringify({ requirements: formattedRequirements, "max_n_categories": max_categories })
 
 
 
 
458
  });
 
459
 
460
+ const data = await response.json();
461
+ categorizedRequirements = data;
462
+ displayCategorizedRequirements(categorizedRequirements.categories);
463
+
464
+ // Masquer le container de query et afficher les catégories et boutons solutions
465
+ toggleContainersVisibility(['query-requirements-container'], false);
466
+ toggleContainersVisibility(['categorized-requirements-container', 'solutions-action-buttons-container'], true);
467
+
468
+ } catch (error) {
469
+ console.error('Erreur lors de la catégorisation des requirements:', error);
470
+ alert('Erreur lors de la catégorisation des requirements');
471
+ } finally {
472
+ hideLoadingOverlay();
473
+ toggleElementsEnabled(['categorize-requirements-btn'], true);
474
+ }
475
  }
476
 
477
+ /**
478
+ * Affiche les requirements catégorisés
479
+ * @param {Array} categorizedData - Données des requirements catégorisés
480
+ */
481
+ function displayCategorizedRequirements(categorizedData) {
482
+ const container = document.getElementById('categorized-requirements-list');
483
+ container.innerHTML = '';
484
 
485
+ categorizedData.forEach((category, categoryIndex) => {
486
+ const categoryDiv = document.createElement('div');
487
+ categoryDiv.className = 'mb-6 p-4 border border-gray-200 rounded-lg bg-white';
488
+
489
+ categoryDiv.innerHTML = `
490
+ <h3 class="text-lg font-semibold mb-2 text-blue-600">${category.title}</h3>
491
+ <div class="space-y-2">
492
+ ${category.requirements.map((req, reqIndex) =>
493
+ `<div class="p-2 bg-gray-50 rounded border-l-4 border-blue-400" data-cat-req-id="${categoryIndex}-${reqIndex}">
494
+ <div class="text-sm font-medium text-gray-700">${req.document}</div>
495
+ <div class="text-sm text-gray-600">${req.requirement}</div>
496
+ </div>`
497
+ ).join('')}
498
  </div>
499
+ `;
500
+
501
+ container.appendChild(categoryDiv);
502
+ });
503
  }
504
 
505
+ async function searchRequirements() {
506
+ const query = document.getElementById('query-input').value.trim();
507
+ if (!query) {
508
+ alert('Veuillez entrer une requête de recherche');
509
+ return;
510
+ }
511
+
512
+ if (!formattedRequirements || formattedRequirements.length === 0) {
513
+ alert('Aucun requirement disponible pour la recherche');
514
+ return;
515
+ }
516
+
517
+ showLoadingOverlay('Recherche en cours...');
518
+ toggleElementsEnabled(['search-requirements-btn'], false);
519
+
520
+ try {
521
+ // Préparer les requirements pour la recherche
522
+ const response = await fetch('/get_reqs_from_query', {
523
+ method: 'POST',
524
+ headers: { 'Content-Type': 'application/json' },
525
+ body: JSON.stringify({
526
+ query: query,
527
+ requirements: formattedRequirements
528
+ })
529
+ });
530
+
531
+ const data = await response.json();
532
+ displaySearchResults(data.requirements);
533
+
534
+ } catch (error) {
535
+ console.error('Erreur lors de la recherche:', error);
536
+ alert('Erreur lors de la recherche des requirements');
537
+ } finally {
538
+ hideLoadingOverlay();
539
+ toggleElementsEnabled(['search-requirements-btn'], true);
540
+ }
541
  }
542
 
543
+ /**
544
+ * Affiche les résultats de recherche
545
+ * @param {Array} results - Résultats de la recherche
546
+ */
547
+ function displaySearchResults(results) {
548
+ const container = document.getElementById('query-results');
549
+ container.innerHTML = '';
550
+
551
+ if (results.length === 0) {
552
+ container.innerHTML = '<p class="text-gray-500">Aucun résultat trouvé pour cette requête.</p>';
553
+ return;
554
+ }
555
+
556
+ const resultsDiv = document.createElement('div');
557
+ resultsDiv.className = 'space-y-3';
558
+
559
+ results.forEach((result, index) => {
560
+ const resultDiv = document.createElement('div');
561
+ resultDiv.className = 'p-3 bg-blue-50 border border-blue-200 rounded-lg';
562
+
563
+ resultDiv.innerHTML = `
564
+ <div class="text-sm font-medium text-blue-800">${result.document}</div>
565
+ <div class="text-sm text-gray-600 mb-1">${result.context}</div>
566
+ <div class="text-sm">${result.requirement}</div>
567
+ `;
568
+
569
+ resultsDiv.appendChild(resultDiv);
570
+ });
571
+
572
+ container.appendChild(resultsDiv);
573
+ }
574
 
575
  function createSolutionAccordion(solutionCriticizedHistory, containerId, versionIndex = 0, categoryIndex = null) {
576
  const container = document.getElementById(containerId);
 
810
  // Réinitialiser les états d'accordéon
811
  accordionStates = {};
812
  createSolutionAccordion(solutionCriticizedHistory, containerId, startVersion);
813
+ document.getElementById(containerId).classList.remove('hidden')
814
  }
815
 
816
  async function generateSolutions(){
 
817
  let response = await fetch('https://game4all-reqroup.hf.space/solution/search_solutions_gemini', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(categorizedRequirements)})
818
  let responseObj = await response.json()
819
  return responseObj;
820
  }
821
 
822
  async function generateCriticisms(solutions){
 
823
  let response = await fetch('https://game4all-reqroup.hf.space/solution/criticize_solution', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(solutions)})
824
  let responseObj = await response.json()
825
  solutionsCriticizedVersions.push(responseObj)
826
  }
827
 
828
  async function refineSolutions(critiques){
 
829
  let response = await fetch('https://game4all-reqroup.hf.space/solution/refine_solution', {method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(critiques)})
830
  let responseObj = await response.json()
831
  await generateCriticisms(responseObj)
 
833
 
834
  async function workflow(steps = 1){
835
  let soluce;
836
+ showLoadingOverlay('Génération des solutions & critiques ....');
837
  for(let step = 1; step <= steps; step++){
838
  if(solutionsCriticizedVersions.length == 0){
839
  soluce = await generateSolutions();
 
843
  await refineSolutions(prevSoluce)
844
  }
845
  }
846
+ hideLoadingOverlay();
847
+ initializeSolutionAccordion(solutionsCriticizedVersions, "solutions-list")
848
  }
849
 
850
+ // =============================================================================
851
+ // INITIALISATION DES ÉVÉNEMENTS
852
+ // =============================================================================
853
+
854
+ document.addEventListener('DOMContentLoaded', function() {
855
+ // Événements des boutons principaux
856
+ document.getElementById('get-meetings-btn').addEventListener('click', getMeetings);
857
+ document.getElementById('get-tdocs-btn').addEventListener('click', getTDocs);
858
+ document.getElementById('download-tdocs-btn').addEventListener('click', downloadTDocs);
859
+ document.getElementById('extract-requirements-btn').addEventListener('click', extractRequirements);
860
+ document.getElementById('categorize-requirements-btn').addEventListener('click', ()=>{categorizeRequirements(8)});
861
+
862
+ // Événement pour le bouton "Find Requirements"
863
+ document.getElementById('find-requirements-btn').addEventListener('click', function() {
864
+ // Afficher le container de requête
865
+ toggleContainersVisibility(['query-requirements-container'], true);
866
+ // Scroll vers le container de requête
867
+ document.getElementById('query-requirements-container').scrollIntoView({ behavior: 'smooth' });
868
+ });
869
+
870
+ // Événement pour la recherche
871
+ document.getElementById('search-requirements-btn').addEventListener('click', searchRequirements);
872
+
873
+ // Événements pour les boutons de solutions (à implémenter plus tard)
874
+ document.getElementById('get-solutions-btn').addEventListener('click', () => {
875
+ alert('Fonctionnalité à implémenter');
876
+ });
877
+ document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
878
+ workflow();
879
+ });
880
+ });