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