Lucas ARRIESSE
commited on
Commit
·
75ac1c4
1
Parent(s):
e4ce2a0
Reword routes + prepare for private gen
Browse files- api/solutions.py +24 -21
- prompts/criticize.txt +3 -3
- prompts/refine_solution.txt +4 -4
- schemas.py +26 -17
- static/js/app.js +12 -12
api/solutions.py
CHANGED
|
@@ -7,7 +7,7 @@ from jinja2 import Environment
|
|
| 7 |
from litellm.router import Router
|
| 8 |
from dependencies import INSIGHT_FINDER_BASE_URL, get_http_client, get_llm_router, get_prompt_templates
|
| 9 |
from typing import Awaitable, Callable, TypeVar
|
| 10 |
-
from schemas import _RefinedSolutionModel,
|
| 11 |
|
| 12 |
# Router for solution generation and critique
|
| 13 |
router = APIRouter(tags=["solution generation and critique"])
|
|
@@ -34,10 +34,14 @@ async def retry_until(
|
|
| 34 |
|
| 35 |
# =================================================== Search solutions ============================================================================
|
| 36 |
|
| 37 |
-
@router.post("/search_solutions")
|
| 38 |
-
async def search_solutions_if(req: SolutionSearchV2Request, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router), http_client: AsyncClient = Depends(get_http_client)) -> SolutionSearchResponse:
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
# process requirements into insight finder format
|
| 42 |
fmt_completion = await llm_router.acompletion("gemini-v2", messages=[
|
| 43 |
{
|
|
@@ -67,32 +71,32 @@ async def search_solutions_if(req: SolutionSearchV2Request, prompt_env: Environm
|
|
| 67 |
"category": cat.model_dump(),
|
| 68 |
"technologies": technologies.model_dump()["technologies"],
|
| 69 |
"user_constraints": req.user_constraints,
|
| 70 |
-
"response_schema":
|
| 71 |
})}
|
| 72 |
-
], response_format=
|
| 73 |
|
| 74 |
-
format_solution_model =
|
| 75 |
format_solution.choices[0].message.content)
|
| 76 |
|
| 77 |
final_solution = SolutionModel(
|
| 78 |
-
|
| 79 |
-
|
| 80 |
cat.requirements[i].requirement for i in format_solution_model.requirement_ids
|
| 81 |
],
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
)
|
| 87 |
|
| 88 |
# ========================================================================================================================================
|
| 89 |
|
| 90 |
return final_solution
|
| 91 |
|
| 92 |
-
tasks = await asyncio.gather(*[
|
| 93 |
final_solutions = [sol for sol in tasks if not isinstance(sol, Exception)]
|
| 94 |
|
| 95 |
-
return
|
| 96 |
|
| 97 |
|
| 98 |
@router.post("/criticize_solution", response_model=CritiqueResponse)
|
|
@@ -123,8 +127,8 @@ async def criticize_solution(params: CriticizeSolutionsRequest, prompt_env: Envi
|
|
| 123 |
|
| 124 |
# =================================================================== Refine solution ====================================
|
| 125 |
|
| 126 |
-
@router.post("/refine_solutions", response_model=
|
| 127 |
-
async def refine_solutions(params: CritiqueResponse, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router)) ->
|
| 128 |
"""Refines the previously critiqued solutions."""
|
| 129 |
|
| 130 |
async def __refine_solution(crit: SolutionCriticism):
|
|
@@ -143,12 +147,11 @@ async def refine_solutions(params: CritiqueResponse, prompt_env: Environment = D
|
|
| 143 |
|
| 144 |
# copy previous solution model
|
| 145 |
refined_solution = crit.solution.model_copy(deep=True)
|
| 146 |
-
refined_solution.
|
| 147 |
-
refined_solution.
|
| 148 |
|
| 149 |
return refined_solution
|
| 150 |
|
| 151 |
refined_solutions = await asyncio.gather(*[__refine_solution(crit) for crit in params.critiques], return_exceptions=False)
|
| 152 |
|
| 153 |
-
return
|
| 154 |
-
|
|
|
|
| 7 |
from litellm.router import Router
|
| 8 |
from dependencies import INSIGHT_FINDER_BASE_URL, get_http_client, get_llm_router, get_prompt_templates
|
| 9 |
from typing import Awaitable, Callable, TypeVar
|
| 10 |
+
from schemas import _RefinedSolutionModel, _BootstrappedSolutionModel, _SolutionCriticismOutput, CriticizeSolutionsRequest, CritiqueResponse, InsightFinderConstraintsList, ReqGroupingCategory, ReqGroupingRequest, ReqGroupingResponse, ReqSearchLLMResponse, ReqSearchRequest, ReqSearchResponse, SolutionCriticism, SolutionModel, SolutionBootstrapResponse, SolutionBootstrapRequest, TechnologyData
|
| 11 |
|
| 12 |
# Router for solution generation and critique
|
| 13 |
router = APIRouter(tags=["solution generation and critique"])
|
|
|
|
| 34 |
|
| 35 |
# =================================================== Search solutions ============================================================================
|
| 36 |
|
|
|
|
|
|
|
| 37 |
|
| 38 |
+
@router.post("/bootstrap_solutions")
|
| 39 |
+
async def bootstrap_solutions(req: SolutionBootstrapRequest, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router), http_client: AsyncClient = Depends(get_http_client)) -> SolutionBootstrapResponse:
|
| 40 |
+
"""
|
| 41 |
+
Boostraps a solution for each of the passed in requirements categories using Insight Finder's API.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
async def _bootstrap_solution_inner(cat: ReqGroupingCategory):
|
| 45 |
# process requirements into insight finder format
|
| 46 |
fmt_completion = await llm_router.acompletion("gemini-v2", messages=[
|
| 47 |
{
|
|
|
|
| 71 |
"category": cat.model_dump(),
|
| 72 |
"technologies": technologies.model_dump()["technologies"],
|
| 73 |
"user_constraints": req.user_constraints,
|
| 74 |
+
"response_schema": _BootstrappedSolutionModel.model_json_schema()
|
| 75 |
})}
|
| 76 |
+
], response_format=_BootstrappedSolutionModel)
|
| 77 |
|
| 78 |
+
format_solution_model = _BootstrappedSolutionModel.model_validate_json(
|
| 79 |
format_solution.choices[0].message.content)
|
| 80 |
|
| 81 |
final_solution = SolutionModel(
|
| 82 |
+
context="",
|
| 83 |
+
requirements=[
|
| 84 |
cat.requirements[i].requirement for i in format_solution_model.requirement_ids
|
| 85 |
],
|
| 86 |
+
problem_description=format_solution_model.problem_description,
|
| 87 |
+
solution_description=format_solution_model.solution_description,
|
| 88 |
+
references=[],
|
| 89 |
+
category_id=cat.id,
|
| 90 |
)
|
| 91 |
|
| 92 |
# ========================================================================================================================================
|
| 93 |
|
| 94 |
return final_solution
|
| 95 |
|
| 96 |
+
tasks = await asyncio.gather(*[_bootstrap_solution_inner(cat) for cat in req.categories], return_exceptions=True)
|
| 97 |
final_solutions = [sol for sol in tasks if not isinstance(sol, Exception)]
|
| 98 |
|
| 99 |
+
return SolutionBootstrapResponse(solutions=final_solutions)
|
| 100 |
|
| 101 |
|
| 102 |
@router.post("/criticize_solution", response_model=CritiqueResponse)
|
|
|
|
| 127 |
|
| 128 |
# =================================================================== Refine solution ====================================
|
| 129 |
|
| 130 |
+
@router.post("/refine_solutions", response_model=SolutionBootstrapResponse)
|
| 131 |
+
async def refine_solutions(params: CritiqueResponse, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router)) -> SolutionBootstrapResponse:
|
| 132 |
"""Refines the previously critiqued solutions."""
|
| 133 |
|
| 134 |
async def __refine_solution(crit: SolutionCriticism):
|
|
|
|
| 147 |
|
| 148 |
# copy previous solution model
|
| 149 |
refined_solution = crit.solution.model_copy(deep=True)
|
| 150 |
+
refined_solution.problem_description = req_model.problem_description
|
| 151 |
+
refined_solution.solution_description = req_model.solution_description
|
| 152 |
|
| 153 |
return refined_solution
|
| 154 |
|
| 155 |
refined_solutions = await asyncio.gather(*[__refine_solution(crit) for crit in params.critiques], return_exceptions=False)
|
| 156 |
|
| 157 |
+
return SolutionBootstrapResponse(solutions=refined_solutions)
|
|
|
prompts/criticize.txt
CHANGED
|
@@ -8,9 +8,9 @@ Here are the solutions:
|
|
| 8 |
<solutions>
|
| 9 |
{% for solution in solutions %}
|
| 10 |
## Solution
|
| 11 |
-
- Context: {{solution["
|
| 12 |
-
- Problem description: {{solution["
|
| 13 |
-
- Solution description: {{solution["
|
| 14 |
---
|
| 15 |
{% endfor -%}
|
| 16 |
</solutions>
|
|
|
|
| 8 |
<solutions>
|
| 9 |
{% for solution in solutions %}
|
| 10 |
## Solution
|
| 11 |
+
- Context: {{solution["context"]}}
|
| 12 |
+
- Problem description: {{solution["problem_description"]}}
|
| 13 |
+
- Solution description: {{solution["solution_description"]}}
|
| 14 |
---
|
| 15 |
{% endfor -%}
|
| 16 |
</solutions>
|
prompts/refine_solution.txt
CHANGED
|
@@ -7,18 +7,18 @@ No need to include that the solution is refined.
|
|
| 7 |
Here is the solution:
|
| 8 |
<solution>
|
| 9 |
# Solution Context:
|
| 10 |
-
{{solution['
|
| 11 |
|
| 12 |
# Requirements solved by the solution
|
| 13 |
-
{% for req in solution['
|
| 14 |
- {{req}}
|
| 15 |
{% endfor %}
|
| 16 |
|
| 17 |
# Problem description associated to the solution
|
| 18 |
-
{{solution['
|
| 19 |
|
| 20 |
# Description of the solution
|
| 21 |
-
{{solution['
|
| 22 |
</solution>
|
| 23 |
|
| 24 |
Here is the criticism:
|
|
|
|
| 7 |
Here is the solution:
|
| 8 |
<solution>
|
| 9 |
# Solution Context:
|
| 10 |
+
{{solution['context']}}
|
| 11 |
|
| 12 |
# Requirements solved by the solution
|
| 13 |
+
{% for req in solution['requirements'] -%}
|
| 14 |
- {{req}}
|
| 15 |
{% endfor %}
|
| 16 |
|
| 17 |
# Problem description associated to the solution
|
| 18 |
+
{{solution['problem_description']}}
|
| 19 |
|
| 20 |
# Description of the solution
|
| 21 |
+
{{solution['solution_description']}}
|
| 22 |
</solution>
|
| 23 |
|
| 24 |
Here is the criticism:
|
schemas.py
CHANGED
|
@@ -1,39 +1,48 @@
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
from typing import Any, List, Dict, Optional
|
| 3 |
|
|
|
|
| 4 |
class MeetingsRequest(BaseModel):
|
| 5 |
working_group: str
|
| 6 |
|
|
|
|
| 7 |
class MeetingsResponse(BaseModel):
|
| 8 |
meetings: Dict[str, str]
|
| 9 |
# --------------------------------------
|
| 10 |
|
|
|
|
| 11 |
class DataRequest(BaseModel):
|
| 12 |
working_group: str
|
| 13 |
meeting: str
|
| 14 |
|
|
|
|
| 15 |
class DataResponse(BaseModel):
|
| 16 |
data: List[Dict[Any, Any]]
|
| 17 |
|
| 18 |
# --------------------------------------
|
| 19 |
|
|
|
|
| 20 |
class DocInfo(BaseModel):
|
| 21 |
document: str
|
| 22 |
url: str
|
| 23 |
|
|
|
|
| 24 |
class RequirementsRequest(BaseModel):
|
| 25 |
documents: List[DocInfo]
|
| 26 |
|
|
|
|
| 27 |
class DocRequirements(BaseModel):
|
| 28 |
document: str
|
| 29 |
context: str
|
| 30 |
requirements: List[str]
|
| 31 |
|
|
|
|
| 32 |
class RequirementsResponse(BaseModel):
|
| 33 |
requirements: List[DocRequirements]
|
| 34 |
|
| 35 |
# --------------------------------------
|
| 36 |
|
|
|
|
| 37 |
class RequirementInfo(BaseModel):
|
| 38 |
req_id: int = Field(..., description="The ID of this requirement")
|
| 39 |
context: str = Field(..., description="Context for the requirement.")
|
|
@@ -71,21 +80,22 @@ class ReqGroupingCategory(BaseModel):
|
|
| 71 |
|
| 72 |
|
| 73 |
class SolutionModel(BaseModel):
|
| 74 |
-
|
| 75 |
description="Full context provided for this category.")
|
| 76 |
-
|
| 77 |
-
description="List of each requirement as string.")
|
| 78 |
-
|
| 79 |
description="Description of the problem being solved.")
|
| 80 |
-
|
| 81 |
description="Detailed description of the solution.")
|
| 82 |
-
|
| 83 |
..., description="References to documents used for the solution.")
|
| 84 |
-
|
|
|
|
| 85 |
..., description="ID of the requirements category the solution is based on")
|
| 86 |
|
| 87 |
-
class Config:
|
| 88 |
-
|
| 89 |
|
| 90 |
|
| 91 |
# ============================================================= Categorize requirements endpoint
|
|
@@ -115,14 +125,14 @@ class _ReqGroupingOutput(BaseModel):
|
|
| 115 |
..., description="List of grouping categories")
|
| 116 |
|
| 117 |
|
| 118 |
-
# ===================================================================
|
| 119 |
|
| 120 |
-
class
|
| 121 |
solution: SolutionModel
|
| 122 |
|
| 123 |
|
| 124 |
-
class
|
| 125 |
-
""""Internal model used for solutions
|
| 126 |
requirement_ids: List[int] = Field(...,
|
| 127 |
description="List of each requirement ID addressed by the solution")
|
| 128 |
problem_description: str = Field(...,
|
|
@@ -131,13 +141,12 @@ class _SearchedSolutionModel(BaseModel):
|
|
| 131 |
description="Detailed description of the solution.")
|
| 132 |
|
| 133 |
|
| 134 |
-
class
|
| 135 |
-
"""Response model for solution
|
| 136 |
solutions: list[SolutionModel]
|
| 137 |
|
| 138 |
|
| 139 |
-
class
|
| 140 |
-
"""Response of a requirement grouping call."""
|
| 141 |
categories: List[ReqGroupingCategory]
|
| 142 |
user_constraints: Optional[str] = Field(
|
| 143 |
default=None, description="Additional user constraints to respect when generating the solutions.")
|
|
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
from typing import Any, List, Dict, Optional
|
| 3 |
|
| 4 |
+
|
| 5 |
class MeetingsRequest(BaseModel):
|
| 6 |
working_group: str
|
| 7 |
|
| 8 |
+
|
| 9 |
class MeetingsResponse(BaseModel):
|
| 10 |
meetings: Dict[str, str]
|
| 11 |
# --------------------------------------
|
| 12 |
|
| 13 |
+
|
| 14 |
class DataRequest(BaseModel):
|
| 15 |
working_group: str
|
| 16 |
meeting: str
|
| 17 |
|
| 18 |
+
|
| 19 |
class DataResponse(BaseModel):
|
| 20 |
data: List[Dict[Any, Any]]
|
| 21 |
|
| 22 |
# --------------------------------------
|
| 23 |
|
| 24 |
+
|
| 25 |
class DocInfo(BaseModel):
|
| 26 |
document: str
|
| 27 |
url: str
|
| 28 |
|
| 29 |
+
|
| 30 |
class RequirementsRequest(BaseModel):
|
| 31 |
documents: List[DocInfo]
|
| 32 |
|
| 33 |
+
|
| 34 |
class DocRequirements(BaseModel):
|
| 35 |
document: str
|
| 36 |
context: str
|
| 37 |
requirements: List[str]
|
| 38 |
|
| 39 |
+
|
| 40 |
class RequirementsResponse(BaseModel):
|
| 41 |
requirements: List[DocRequirements]
|
| 42 |
|
| 43 |
# --------------------------------------
|
| 44 |
|
| 45 |
+
|
| 46 |
class RequirementInfo(BaseModel):
|
| 47 |
req_id: int = Field(..., description="The ID of this requirement")
|
| 48 |
context: str = Field(..., description="Context for the requirement.")
|
|
|
|
| 80 |
|
| 81 |
|
| 82 |
class SolutionModel(BaseModel):
|
| 83 |
+
context: str = Field(...,
|
| 84 |
description="Full context provided for this category.")
|
| 85 |
+
requirements: List[str] = Field(...,
|
| 86 |
+
description="List of each requirement covered by the solution as a string.")
|
| 87 |
+
problem_description: str = Field(...,
|
| 88 |
description="Description of the problem being solved.")
|
| 89 |
+
solution_description: str = Field(...,
|
| 90 |
description="Detailed description of the solution.")
|
| 91 |
+
references: list[dict] = Field(
|
| 92 |
..., description="References to documents used for the solution.")
|
| 93 |
+
|
| 94 |
+
category_id: int = Field(
|
| 95 |
..., description="ID of the requirements category the solution is based on")
|
| 96 |
|
| 97 |
+
# class Config:
|
| 98 |
+
# validate_by_name = True # Enables alias handling on input/output
|
| 99 |
|
| 100 |
|
| 101 |
# ============================================================= Categorize requirements endpoint
|
|
|
|
| 125 |
..., description="List of grouping categories")
|
| 126 |
|
| 127 |
|
| 128 |
+
# =================================================================== bootstrap solution response
|
| 129 |
|
| 130 |
+
class _SolutionBootstrapOutput(BaseModel):
|
| 131 |
solution: SolutionModel
|
| 132 |
|
| 133 |
|
| 134 |
+
class _BootstrappedSolutionModel(BaseModel):
|
| 135 |
+
""""Internal model used for solutions bootstrapped using """
|
| 136 |
requirement_ids: List[int] = Field(...,
|
| 137 |
description="List of each requirement ID addressed by the solution")
|
| 138 |
problem_description: str = Field(...,
|
|
|
|
| 141 |
description="Detailed description of the solution.")
|
| 142 |
|
| 143 |
|
| 144 |
+
class SolutionBootstrapResponse(BaseModel):
|
| 145 |
+
"""Response model for solution bootstrapping"""
|
| 146 |
solutions: list[SolutionModel]
|
| 147 |
|
| 148 |
|
| 149 |
+
class SolutionBootstrapRequest(BaseModel):
|
|
|
|
| 150 |
categories: List[ReqGroupingCategory]
|
| 151 |
user_constraints: Optional[str] = Field(
|
| 152 |
default=None, description="Additional user constraints to respect when generating the solutions.")
|
static/js/app.js
CHANGED
|
@@ -756,7 +756,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 756 |
const criticism = item.criticism;
|
| 757 |
|
| 758 |
// Récupérer le titre de la catégorie
|
| 759 |
-
const categoryTitle = categorizedRequirements.categories.find(c => c.id == solution['
|
| 760 |
|
| 761 |
// Container pour chaque solution
|
| 762 |
const solutionCard = document.createElement('div');
|
|
@@ -777,7 +777,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 777 |
<h3 class="text-sm font-semibold text-gray-800">${categoryTitle}</h3>
|
| 778 |
<div class="flex items-center space-x-2 bg-white px-3 py-1 rounded-full border">
|
| 779 |
<button class="version-btn-left w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-100 transition-colors ${currentVersion === 1 ? 'opacity-50 cursor-not-allowed' : ''}"
|
| 780 |
-
data-solution-index="${solution['
|
| 781 |
${currentVersion === 1 ? 'disabled' : ''}>
|
| 782 |
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 783 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
@@ -785,7 +785,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 785 |
</button>
|
| 786 |
<span class="text-xs font-medium text-gray-600 min-w-[60px] text-center version-indicator">Version ${currentVersion}</span>
|
| 787 |
<button class="version-btn-right w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-100 transition-colors ${currentVersion === totalVersions ? 'opacity-50 cursor-not-allowed' : ''}"
|
| 788 |
-
data-solution-index="${solution['
|
| 789 |
${currentVersion === totalVersions ? 'disabled' : ''}>
|
| 790 |
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 791 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
@@ -795,7 +795,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 795 |
</div>
|
| 796 |
<!--
|
| 797 |
<button class="delete-btn text-red-500 hover:text-red-700 transition-colors"
|
| 798 |
-
data-solution-index="${solution['
|
| 799 |
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 800 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5-3h4m-4 0a1 1 0 00-1 1v1h6V5a1 1 0 00-1-1m-4 0h4" />
|
| 801 |
</svg>
|
|
@@ -807,10 +807,10 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 807 |
// Contenu de l'accordéon
|
| 808 |
const content = document.createElement('div');
|
| 809 |
content.className = `accordion-content px-4 py-3 space-y-3`;
|
| 810 |
-
content.id = `content-${solution['
|
| 811 |
|
| 812 |
// Vérifier l'état d'ouverture précédent
|
| 813 |
-
const isOpen = accordionStates[solution['
|
| 814 |
console.log(isOpen);
|
| 815 |
if (!isOpen)
|
| 816 |
content.classList.add('hidden');
|
|
@@ -825,13 +825,13 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 825 |
</svg>
|
| 826 |
Problem Description
|
| 827 |
</h4>
|
| 828 |
-
<p class="text-xs text-gray-700 leading-relaxed">${solution[
|
| 829 |
`;
|
| 830 |
|
| 831 |
// Section Problem requirements
|
| 832 |
const reqsSection = document.createElement('div');
|
| 833 |
reqsSection.className = "bg-gray-50 border-l-2 border-red-400 p-3 rounded-r-md";
|
| 834 |
-
const reqItemsUl = solution["
|
| 835 |
reqsSection.innerHTML = `
|
| 836 |
<h4 class="text-sm font-semibold text-gray-800 mb-2 flex items-center">
|
| 837 |
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
@@ -862,10 +862,10 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 862 |
solutionSection.appendChild(solContents);
|
| 863 |
|
| 864 |
try {
|
| 865 |
-
solContents.innerHTML = marked.parse(solution['
|
| 866 |
}
|
| 867 |
catch (e) {
|
| 868 |
-
solContents.innerHTML = `<p class="text-xs text-gray-700 leading-relaxed">${solution['
|
| 869 |
}
|
| 870 |
|
| 871 |
|
|
@@ -956,7 +956,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 956 |
});
|
| 957 |
|
| 958 |
// create source reference pills
|
| 959 |
-
solution['
|
| 960 |
const pillLink = createEl('a', {
|
| 961 |
href: source.url,
|
| 962 |
target: '_blank',
|
|
@@ -1055,7 +1055,7 @@ async function generateSolutions(selected_categories, user_constraints = null) {
|
|
| 1055 |
let input_req = structuredClone(selected_categories);
|
| 1056 |
input_req.user_constraints = user_constraints;
|
| 1057 |
|
| 1058 |
-
let response = await fetch("/solutions/
|
| 1059 |
let responseObj = await response.json()
|
| 1060 |
return responseObj;
|
| 1061 |
}
|
|
|
|
| 756 |
const criticism = item.criticism;
|
| 757 |
|
| 758 |
// Récupérer le titre de la catégorie
|
| 759 |
+
const categoryTitle = categorizedRequirements.categories.find(c => c.id == solution['category_id']).title;
|
| 760 |
|
| 761 |
// Container pour chaque solution
|
| 762 |
const solutionCard = document.createElement('div');
|
|
|
|
| 777 |
<h3 class="text-sm font-semibold text-gray-800">${categoryTitle}</h3>
|
| 778 |
<div class="flex items-center space-x-2 bg-white px-3 py-1 rounded-full border">
|
| 779 |
<button class="version-btn-left w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-100 transition-colors ${currentVersion === 1 ? 'opacity-50 cursor-not-allowed' : ''}"
|
| 780 |
+
data-solution-index="${solution['category_id']}"
|
| 781 |
${currentVersion === 1 ? 'disabled' : ''}>
|
| 782 |
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 783 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
|
|
| 785 |
</button>
|
| 786 |
<span class="text-xs font-medium text-gray-600 min-w-[60px] text-center version-indicator">Version ${currentVersion}</span>
|
| 787 |
<button class="version-btn-right w-6 h-6 flex items-center justify-center rounded-full hover:bg-gray-100 transition-colors ${currentVersion === totalVersions ? 'opacity-50 cursor-not-allowed' : ''}"
|
| 788 |
+
data-solution-index="${solution['category_id']}"
|
| 789 |
${currentVersion === totalVersions ? 'disabled' : ''}>
|
| 790 |
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 791 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
|
|
| 795 |
</div>
|
| 796 |
<!--
|
| 797 |
<button class="delete-btn text-red-500 hover:text-red-700 transition-colors"
|
| 798 |
+
data-solution-index="${solution['category_id']}" id="solution-delete-btn">
|
| 799 |
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 800 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5-3h4m-4 0a1 1 0 00-1 1v1h6V5a1 1 0 00-1-1m-4 0h4" />
|
| 801 |
</svg>
|
|
|
|
| 807 |
// Contenu de l'accordéon
|
| 808 |
const content = document.createElement('div');
|
| 809 |
content.className = `accordion-content px-4 py-3 space-y-3`;
|
| 810 |
+
content.id = `content-${solution['category_id']}`;
|
| 811 |
|
| 812 |
// Vérifier l'état d'ouverture précédent
|
| 813 |
+
const isOpen = accordionStates[solution['category_id']] || false;
|
| 814 |
console.log(isOpen);
|
| 815 |
if (!isOpen)
|
| 816 |
content.classList.add('hidden');
|
|
|
|
| 825 |
</svg>
|
| 826 |
Problem Description
|
| 827 |
</h4>
|
| 828 |
+
<p class="text-xs text-gray-700 leading-relaxed">${solution["problem_description"] || 'Aucune description du problème disponible.'}</p>
|
| 829 |
`;
|
| 830 |
|
| 831 |
// Section Problem requirements
|
| 832 |
const reqsSection = document.createElement('div');
|
| 833 |
reqsSection.className = "bg-gray-50 border-l-2 border-red-400 p-3 rounded-r-md";
|
| 834 |
+
const reqItemsUl = solution["requirements"].map(req => `<li>${req}</li>`).join('');
|
| 835 |
reqsSection.innerHTML = `
|
| 836 |
<h4 class="text-sm font-semibold text-gray-800 mb-2 flex items-center">
|
| 837 |
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
|
|
| 862 |
solutionSection.appendChild(solContents);
|
| 863 |
|
| 864 |
try {
|
| 865 |
+
solContents.innerHTML = marked.parse(solution['solution_description']);
|
| 866 |
}
|
| 867 |
catch (e) {
|
| 868 |
+
solContents.innerHTML = `<p class="text-xs text-gray-700 leading-relaxed">${solution['solution_description'] || 'No available solution description'}</p>`;
|
| 869 |
}
|
| 870 |
|
| 871 |
|
|
|
|
| 956 |
});
|
| 957 |
|
| 958 |
// create source reference pills
|
| 959 |
+
solution['references'].forEach(source => {
|
| 960 |
const pillLink = createEl('a', {
|
| 961 |
href: source.url,
|
| 962 |
target: '_blank',
|
|
|
|
| 1055 |
let input_req = structuredClone(selected_categories);
|
| 1056 |
input_req.user_constraints = user_constraints;
|
| 1057 |
|
| 1058 |
+
let response = await fetch("/solutions/bootstrap_solutions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input_req) })
|
| 1059 |
let responseObj = await response.json()
|
| 1060 |
return responseObj;
|
| 1061 |
}
|