Spaces:
Sleeping
Sleeping
Lucas ARRIESSE
commited on
Commit
·
e97be0e
1
Parent(s):
75ac1c4
WIP solution drafting
Browse files- README.md +8 -0
- api/requirements.py +3 -2
- api/solutions.py +41 -4
- app.py +17 -3
- prompts/{synthesize_solution.txt → bootstrap_solution.txt} +0 -1
- prompts/private/README +1 -0
- prompts/private/assess.txt +25 -0
- prompts/private/extract.txt +24 -0
- prompts/private/refine.txt +24 -0
- prompts/search/build_final_report.txt +57 -0
- prompts/search/search_topic.txt +12 -0
- schemas.py +36 -29
- static/index.html +98 -15
- static/js/app.js +74 -140
- static/js/gen.js +239 -0
- static/js/ui-utils.js +374 -8
README.md
CHANGED
|
@@ -10,3 +10,11 @@ short_description: Requirements Extractor
|
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 13 |
+
|
| 14 |
+
## Used libraries
|
| 15 |
+
|
| 16 |
+
**Check requirements.txt for server-side libraries**
|
| 17 |
+
|
| 18 |
+
**Client side libraries**
|
| 19 |
+
- `markedjs` : For rendering markdown content.
|
| 20 |
+
- `zod` : For clientside schema validation.
|
api/requirements.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from fastapi import APIRouter, Depends, HTTPException
|
| 2 |
from jinja2 import Environment
|
| 3 |
from litellm.router import Router
|
|
@@ -22,12 +23,12 @@ def find_requirements_from_problem_description(req: ReqSearchRequest, llm_router
|
|
| 22 |
messages=[{"role": "user", "content": f"Given all the requirements : \n {requirements_text} \n and the problem description \"{query}\", return a list of 'Selection ID' for the most relevant corresponding requirements that reference or best cover the problem. If none of the requirements covers the problem, simply return an empty list"}],
|
| 23 |
response_format=ReqSearchLLMResponse
|
| 24 |
)
|
| 25 |
-
print("Answered")
|
| 26 |
-
print(resp_ai.choices[0].message.content)
|
| 27 |
|
| 28 |
out_llm = ReqSearchLLMResponse.model_validate_json(
|
| 29 |
resp_ai.choices[0].message.content).selected
|
| 30 |
|
|
|
|
|
|
|
| 31 |
if max(out_llm) > len(requirements) - 1:
|
| 32 |
raise HTTPException(
|
| 33 |
status_code=500, detail="LLM error : Generated a wrong index, please try again.")
|
|
|
|
| 1 |
+
import logging
|
| 2 |
from fastapi import APIRouter, Depends, HTTPException
|
| 3 |
from jinja2 import Environment
|
| 4 |
from litellm.router import Router
|
|
|
|
| 23 |
messages=[{"role": "user", "content": f"Given all the requirements : \n {requirements_text} \n and the problem description \"{query}\", return a list of 'Selection ID' for the most relevant corresponding requirements that reference or best cover the problem. If none of the requirements covers the problem, simply return an empty list"}],
|
| 24 |
response_format=ReqSearchLLMResponse
|
| 25 |
)
|
|
|
|
|
|
|
| 26 |
|
| 27 |
out_llm = ReqSearchLLMResponse.model_validate_json(
|
| 28 |
resp_ai.choices[0].message.content).selected
|
| 29 |
|
| 30 |
+
logging.info(f"Found {len(out_llm)} reqs matching case.")
|
| 31 |
+
|
| 32 |
if max(out_llm) > len(requirements) - 1:
|
| 33 |
raise HTTPException(
|
| 34 |
status_code=500, detail="LLM error : Generated a wrong index, please try again.")
|
api/solutions.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
import asyncio
|
| 2 |
import json
|
| 3 |
import logging
|
| 4 |
-
from fastapi import APIRouter, Depends, HTTPException
|
| 5 |
from httpx import AsyncClient
|
| 6 |
-
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, _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"])
|
|
@@ -67,7 +67,7 @@ async def bootstrap_solutions(req: SolutionBootstrapRequest, prompt_env: Environ
|
|
| 67 |
|
| 68 |
format_solution = await llm_router.acompletion("gemini-v2", messages=[{
|
| 69 |
"role": "user",
|
| 70 |
-
"content": await prompt_env.get_template("
|
| 71 |
"category": cat.model_dump(),
|
| 72 |
"technologies": technologies.model_dump()["technologies"],
|
| 73 |
"user_constraints": req.user_constraints,
|
|
@@ -155,3 +155,40 @@ async def refine_solutions(params: CritiqueResponse, prompt_env: Environment = D
|
|
| 155 |
refined_solutions = await asyncio.gather(*[__refine_solution(crit) for crit in params.critiques], return_exceptions=False)
|
| 156 |
|
| 157 |
return SolutionBootstrapResponse(solutions=refined_solutions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import json
|
| 3 |
import logging
|
| 4 |
+
from fastapi import APIRouter, Depends, HTTPException, Response
|
| 5 |
from httpx import AsyncClient
|
| 6 |
+
from jinja2 import Environment, TemplateNotFound
|
| 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, PriorArtSearchRequest, PriorArtSearchResponse, 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"])
|
|
|
|
| 67 |
|
| 68 |
format_solution = await llm_router.acompletion("gemini-v2", messages=[{
|
| 69 |
"role": "user",
|
| 70 |
+
"content": await prompt_env.get_template("bootstrap_solution.txt").render_async(**{
|
| 71 |
"category": cat.model_dump(),
|
| 72 |
"technologies": technologies.model_dump()["technologies"],
|
| 73 |
"user_constraints": req.user_constraints,
|
|
|
|
| 155 |
refined_solutions = await asyncio.gather(*[__refine_solution(crit) for crit in params.critiques], return_exceptions=False)
|
| 156 |
|
| 157 |
return SolutionBootstrapResponse(solutions=refined_solutions)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
@router.post("/search_prior_art")
|
| 161 |
+
async def search_prior_art(req: PriorArtSearchRequest, prompt_env: Environment = Depends(get_prompt_templates), llm_router: Router = Depends(get_llm_router)) -> PriorArtSearchResponse:
|
| 162 |
+
"""Performs a comprehensive prior art search / FTO search against the provided topics for a drafted solution"""
|
| 163 |
+
|
| 164 |
+
sema = asyncio.Semaphore(4)
|
| 165 |
+
|
| 166 |
+
async def __search_topic(topic: str) -> str:
|
| 167 |
+
search_prompt = await prompt_env.get_template("search/search_topic.txt").render_async(**{
|
| 168 |
+
"topic": topic
|
| 169 |
+
})
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
await sema.acquire()
|
| 173 |
+
|
| 174 |
+
search_completion = await llm_router.acompletion(model="gemini-v2", messages=[
|
| 175 |
+
{"role": "user", "content": search_prompt}
|
| 176 |
+
], temperature=0.3, tools=[{"googleSearch": {}}])
|
| 177 |
+
|
| 178 |
+
return {"topic": topic, "content": search_completion.choices[0].message.content}
|
| 179 |
+
finally:
|
| 180 |
+
sema.release()
|
| 181 |
+
|
| 182 |
+
# Dispatch the individual tasks for topic search
|
| 183 |
+
topics = await asyncio.gather(*[__search_topic(top) for top in req.topics], return_exceptions=False)
|
| 184 |
+
|
| 185 |
+
consolidation_prompt = await prompt_env.get_template("search/build_final_report.txt").render_async(**{
|
| 186 |
+
"searches": topics
|
| 187 |
+
})
|
| 188 |
+
|
| 189 |
+
# Then consolidate everything into a single detailed topic
|
| 190 |
+
consolidation_completion = await llm_router.acompletion(model="gemini-v2", messages=[
|
| 191 |
+
{"role": "user", "content": consolidation_prompt}
|
| 192 |
+
], temperature=0.5)
|
| 193 |
+
|
| 194 |
+
return PriorArtSearchResponse(content=consolidation_completion.choices[0].message.content, references=[])
|
app.py
CHANGED
|
@@ -2,13 +2,14 @@ import asyncio
|
|
| 2 |
import logging
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
from typing import Literal
|
|
|
|
| 5 |
import nltk
|
| 6 |
import warnings
|
| 7 |
import os
|
| 8 |
-
from fastapi import Depends, FastAPI, BackgroundTasks, HTTPException, Request
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
import api.solutions
|
| 11 |
-
from dependencies import get_llm_router, init_dependencies
|
| 12 |
import api.docs
|
| 13 |
import api.requirements
|
| 14 |
from api.docs import docx_to_txt
|
|
@@ -40,9 +41,22 @@ app = FastAPI(title="Requirements Extractor", docs_url="/apidocs")
|
|
| 40 |
app.add_middleware(CORSMiddleware, allow_credentials=True, allow_headers=[
|
| 41 |
"*"], allow_methods=["*"], allow_origins=["*"])
|
| 42 |
|
| 43 |
-
# =======================================================================================================================================================================================
|
| 44 |
|
| 45 |
app.include_router(api.docs.router, prefix="/docs")
|
| 46 |
app.include_router(api.requirements.router, prefix="/requirements")
|
| 47 |
app.include_router(api.solutions.router, prefix="/solutions")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
|
|
|
| 2 |
import logging
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
from typing import Literal
|
| 5 |
+
from jinja2 import Environment, TemplateNotFound
|
| 6 |
import nltk
|
| 7 |
import warnings
|
| 8 |
import os
|
| 9 |
+
from fastapi import Depends, FastAPI, BackgroundTasks, HTTPException, Request, Response
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
import api.solutions
|
| 12 |
+
from dependencies import get_llm_router, get_prompt_templates, init_dependencies
|
| 13 |
import api.docs
|
| 14 |
import api.requirements
|
| 15 |
from api.docs import docx_to_txt
|
|
|
|
| 41 |
app.add_middleware(CORSMiddleware, allow_credentials=True, allow_headers=[
|
| 42 |
"*"], allow_methods=["*"], allow_origins=["*"])
|
| 43 |
|
|
|
|
| 44 |
|
| 45 |
app.include_router(api.docs.router, prefix="/docs")
|
| 46 |
app.include_router(api.requirements.router, prefix="/requirements")
|
| 47 |
app.include_router(api.solutions.router, prefix="/solutions")
|
| 48 |
+
|
| 49 |
+
# INTERNAL ROUTE TO RETRIEVE PROMPT TEMPLATES FOR PRIVATE COMPUTE
|
| 50 |
+
@app.get("/prompt/{task}", include_in_schema=True)
|
| 51 |
+
async def retrieve_prompt(task: str, prompt_env: Environment = Depends(get_prompt_templates)):
|
| 52 |
+
"""Retrieves a prompt for client-side private inference"""
|
| 53 |
+
try:
|
| 54 |
+
logging.info(f"Retrieving template for on device private task {task}.")
|
| 55 |
+
prompt, filename, _ = prompt_env.loader.get_source(
|
| 56 |
+
prompt_env, f"private/{task}.txt")
|
| 57 |
+
return prompt
|
| 58 |
+
except TemplateNotFound as _:
|
| 59 |
+
return Response(content="", status_code=404)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
prompts/{synthesize_solution.txt → bootstrap_solution.txt}
RENAMED
|
@@ -26,7 +26,6 @@ Requirements:
|
|
| 26 |
Here are the technologies you may use for the mechanisms that compose the solution:
|
| 27 |
{% for tech in technologies -%}
|
| 28 |
- {{tech["title"]}} : {{tech["purpose"]}}
|
| 29 |
-
* Key Components : {{tech["key_components"]}}
|
| 30 |
{% endfor %}
|
| 31 |
</available_technologies>
|
| 32 |
|
|
|
|
| 26 |
Here are the technologies you may use for the mechanisms that compose the solution:
|
| 27 |
{% for tech in technologies -%}
|
| 28 |
- {{tech["title"]}} : {{tech["purpose"]}}
|
|
|
|
| 29 |
{% endfor %}
|
| 30 |
</available_technologies>
|
| 31 |
|
prompts/private/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Prompts for client-side inference (in `private/`) should only use basic variable templating.
|
prompts/private/assess.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<role> You are a patent committee expert, critical and no sugarcoating. Your criteria of selection of an idea is whether the idea would more likely lead to a patentable solution that add value and can be monetized to/by your business.</role>
|
| 2 |
+
<task>
|
| 3 |
+
Analyze the patentability and added value of the proposed invention. Ensure that it ensures with your business line and patent portfolio.
|
| 4 |
+
Patentability criteria are originality, innovative process, detectable,non-obvious by a person of the art, surprising.
|
| 5 |
+
Evaluate the patent using the following notation criterias while taking into account the business line and portfolio.
|
| 6 |
+
Finally end your analysis by stating whether the idea is a "NO-GO", "CONDITIONAL-GO", "IMMEDIATE-GO" and provide a list of actionnable insights to help refine substantially the idea to better align to the business line if need be.
|
| 7 |
+
</task>
|
| 8 |
+
|
| 9 |
+
<business>
|
| 10 |
+
{{business}}
|
| 11 |
+
</business>
|
| 12 |
+
|
| 13 |
+
<notation_criterias>
|
| 14 |
+
{{notation_criterias}}
|
| 15 |
+
</notation_criterias>
|
| 16 |
+
|
| 17 |
+
<idea>
|
| 18 |
+
**The idea is as follows:**
|
| 19 |
+
|
| 20 |
+
## Problem description
|
| 21 |
+
{{problem_description}}
|
| 22 |
+
|
| 23 |
+
## Solution description
|
| 24 |
+
{{solution_description}}
|
| 25 |
+
</idea>
|
prompts/private/extract.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<role>You are an useful assistant great at summarizing and extracting insight from reports</role>
|
| 2 |
+
<task>Extract from the report you're given the final verdict for the evaluated idea ("NO-GO", "CONDITIONAL-GO", "IMMEDIATE-GO"), summarize the global report feedback
|
| 3 |
+
and extract the actionnable insights.
|
| 4 |
+
</task>
|
| 5 |
+
|
| 6 |
+
<report>
|
| 7 |
+
{{report}}
|
| 8 |
+
</report>
|
| 9 |
+
|
| 10 |
+
<response_format>
|
| 11 |
+
Reply in JSON using the following schema:
|
| 12 |
+
{{response_schema}}
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
Here's an example response:
|
| 16 |
+
{
|
| 17 |
+
"final_verdict": "NO-GO",
|
| 18 |
+
"summary": "<summary>",
|
| 19 |
+
"insights": [
|
| 20 |
+
"Develop the training part",
|
| 21 |
+
"Review the model architechture design"
|
| 22 |
+
]
|
| 23 |
+
}
|
| 24 |
+
</response_format>
|
prompts/private/refine.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<role>You are an expert system designer</role>
|
| 2 |
+
<task>
|
| 3 |
+
Your task is to refine an idea to account for the given insights.
|
| 4 |
+
</task>
|
| 5 |
+
|
| 6 |
+
<idea>
|
| 7 |
+
**The idea is as follows:**
|
| 8 |
+
|
| 9 |
+
## Problem description
|
| 10 |
+
{{problem_description}}
|
| 11 |
+
|
| 12 |
+
## Solution description
|
| 13 |
+
{{solution_description}}
|
| 14 |
+
</idea>
|
| 15 |
+
|
| 16 |
+
<insights>
|
| 17 |
+
Here are the insights:
|
| 18 |
+
{{insights}}
|
| 19 |
+
</insights>
|
| 20 |
+
|
| 21 |
+
<business>
|
| 22 |
+
Here is some business info to help for refining based on the insights:
|
| 23 |
+
{{business_info}}
|
| 24 |
+
</business>
|
prompts/search/build_final_report.txt
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<role>You are a deep researcher agent which excels in prior art analysis for ideas</role>
|
| 2 |
+
<task>
|
| 3 |
+
Your task is to consolidate a list of searches done for multiple topics in a single report detailling the current prior art for a solution.
|
| 4 |
+
- Present a high-level summary of the most relevant prior art identified, mentioning the general approaches and technologies.
|
| 5 |
+
|
| 6 |
+
For each identified piece of prior art (patent or academic document):
|
| 7 |
+
|
| 8 |
+
* **Identification:**
|
| 9 |
+
* **Type:** Patent / Academic Paper
|
| 10 |
+
* **Identifier:** [Patent Number/Publication Number or Paper Title/DOI]
|
| 11 |
+
* **Title:** [Title of Patent/Paper]
|
| 12 |
+
* **Authors/Inventors:** [Names]
|
| 13 |
+
* **Publication Date:** [Date]
|
| 14 |
+
* **Assignee/Publisher:** [Company/Institution/Journal]
|
| 15 |
+
* **Link/Source:** [URL or DOI if applicable]
|
| 16 |
+
|
| 17 |
+
* **Mechanism Description:**
|
| 18 |
+
* Detail the core working principle and mechanism(s) described in the prior art.
|
| 19 |
+
* Explain how it functions and the underlying scientific or engineering concepts.
|
| 20 |
+
* [Specific instructions on detail level - e.g., "Describe at a conceptual level", "Include schematic representations if described", "Focus on the functional blocks"]
|
| 21 |
+
|
| 22 |
+
* **Key Features:**
|
| 23 |
+
* List and describe the significant features of the mechanism.
|
| 24 |
+
* Highlight unique aspects, advantages, and potential applications.
|
| 25 |
+
* [Specific instructions on features to focus on - e.g., "Focus on efficiency metrics", "Describe material choices", "Mention any novel components"]
|
| 26 |
+
|
| 27 |
+
* **Advantages and Disadvantages/Limitations:**
|
| 28 |
+
* Outline the benefits and drawbacks of the described mechanism.
|
| 29 |
+
* Identify any limitations or potential challenges.
|
| 30 |
+
|
| 31 |
+
* **Relevance to topic:**
|
| 32 |
+
* Clearly explain how this prior art relates to the given topic.
|
| 33 |
+
* Quantify or qualify the degree of relevance if possible.
|
| 34 |
+
|
| 35 |
+
- Comparison of Prior Art
|
| 36 |
+
|
| 37 |
+
* If multiple similar prior arts are found, create a comparative analysis.
|
| 38 |
+
* Highlight similarities and differences in mechanisms, features, and performance.
|
| 39 |
+
* Consider presenting this in a table format.
|
| 40 |
+
|
| 41 |
+
- Gaps and Opportunities
|
| 42 |
+
|
| 43 |
+
* Based on the analysis, identify areas where current prior art is lacking or could be improved.
|
| 44 |
+
* Suggest potential opportunities for innovation or further development in relation to topic.
|
| 45 |
+
|
| 46 |
+
- Conclusion
|
| 47 |
+
|
| 48 |
+
* Summarize the key findings of the prior art search.
|
| 49 |
+
* Provide a concluding statement on the landscape of existing technologies related to topic.
|
| 50 |
+
</task>
|
| 51 |
+
|
| 52 |
+
<searches>
|
| 53 |
+
{% for search in searches -%}
|
| 54 |
+
### Topic: {{search['topic']}}
|
| 55 |
+
{{search['content']}}
|
| 56 |
+
{% endfor %}
|
| 57 |
+
</searches>
|
prompts/search/search_topic.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<role>You are a deep researcher agent which excels in prior art analysis for ideas</role>
|
| 2 |
+
<task>
|
| 3 |
+
Your task is to search prior art which will also serve for Freedom-to-Operate analysis on a following given topic.
|
| 4 |
+
Analyze patents and academic documents and detail the found mechanisms and their features.
|
| 5 |
+
</task>
|
| 6 |
+
|
| 7 |
+
<topic>
|
| 8 |
+
**The topic is**
|
| 9 |
+
{{topic}}
|
| 10 |
+
|
| 11 |
+
Use keywords adapted and relevant to the context to perform your searches.
|
| 12 |
+
</topic>
|
schemas.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
-
from typing import Any, List, Dict, Optional
|
| 3 |
|
| 4 |
|
| 5 |
class MeetingsRequest(BaseModel):
|
|
@@ -90,7 +90,7 @@ class SolutionModel(BaseModel):
|
|
| 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 |
|
|
@@ -126,6 +126,30 @@ class _ReqGroupingOutput(BaseModel):
|
|
| 126 |
|
| 127 |
|
| 128 |
# =================================================================== bootstrap solution response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
class _SolutionBootstrapOutput(BaseModel):
|
| 131 |
solution: SolutionModel
|
|
@@ -183,39 +207,22 @@ class CritiqueResponse(BaseModel):
|
|
| 183 |
# ==========================================================================
|
| 184 |
|
| 185 |
class _RefinedSolutionModel(BaseModel):
|
| 186 |
-
"""Internal model used for solution refining"""
|
| 187 |
|
| 188 |
problem_description: str = Field(...,
|
| 189 |
description="New description of the problem being solved.")
|
| 190 |
solution_description: str = Field(...,
|
| 191 |
description="New detailed description of the solution.")
|
| 192 |
|
|
|
|
| 193 |
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
-
# Helpers to extract constraints for Insights Finder
|
| 197 |
|
| 198 |
-
class
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
class InsightFinderConstraintsList(BaseModel):
|
| 204 |
-
constraints: list[InsightFinderConstraintItem]
|
| 205 |
-
|
| 206 |
-
# =================================================
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
class Technology(BaseModel):
|
| 210 |
-
"""Represents a single technology entry with its details."""
|
| 211 |
-
title: str
|
| 212 |
-
purpose: str
|
| 213 |
-
key_components: str
|
| 214 |
-
advantages: str
|
| 215 |
-
limitations: str
|
| 216 |
-
id: int
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
class TechnologyData(BaseModel):
|
| 220 |
-
"""Represents the top-level object containing a list of technologies."""
|
| 221 |
-
technologies: List[Technology]
|
|
|
|
| 1 |
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import Any, List, Dict, Literal, Optional
|
| 3 |
|
| 4 |
|
| 5 |
class MeetingsRequest(BaseModel):
|
|
|
|
| 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 |
|
|
|
|
| 126 |
|
| 127 |
|
| 128 |
# =================================================================== bootstrap solution response
|
| 129 |
+
# Helpers for integration with insights finder.
|
| 130 |
+
|
| 131 |
+
class InsightFinderConstraintItem(BaseModel):
|
| 132 |
+
title: str
|
| 133 |
+
description: str
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class InsightFinderConstraintsList(BaseModel):
|
| 137 |
+
constraints: list[InsightFinderConstraintItem]
|
| 138 |
+
|
| 139 |
+
#TODO: aller voir la doc, breakage API
|
| 140 |
+
class Technology(BaseModel):
|
| 141 |
+
"""Represents a single technology entry with its details."""
|
| 142 |
+
title: str = Field(..., alias="name")
|
| 143 |
+
purpose: str
|
| 144 |
+
advantages: str
|
| 145 |
+
limitations: str
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class TechnologyData(BaseModel):
|
| 150 |
+
"""Represents the top-level object containing a list of technologies."""
|
| 151 |
+
technologies: List[Technology]
|
| 152 |
+
|
| 153 |
|
| 154 |
class _SolutionBootstrapOutput(BaseModel):
|
| 155 |
solution: SolutionModel
|
|
|
|
| 207 |
# ==========================================================================
|
| 208 |
|
| 209 |
class _RefinedSolutionModel(BaseModel):
|
| 210 |
+
"""Internal model used for bootstrapped solution refining"""
|
| 211 |
|
| 212 |
problem_description: str = Field(...,
|
| 213 |
description="New description of the problem being solved.")
|
| 214 |
solution_description: str = Field(...,
|
| 215 |
description="New detailed description of the solution.")
|
| 216 |
|
| 217 |
+
# ===========================================================================
|
| 218 |
|
| 219 |
+
class PriorArtSearchRequest(BaseModel):
|
| 220 |
+
topics: list[str] = Field(
|
| 221 |
+
..., description="The list of topics to search for to create an exhaustive prior art search for a problem draft.")
|
| 222 |
+
mode: Literal['prior_art', 'fto'] = Field(default="fto", description="")
|
| 223 |
|
|
|
|
| 224 |
|
| 225 |
+
class PriorArtSearchResponse(BaseModel):
|
| 226 |
+
content: str
|
| 227 |
+
references: list[dict]
|
| 228 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/index.html
CHANGED
|
@@ -5,9 +5,7 @@
|
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
<title>Requirements Extractor</title>
|
| 8 |
-
|
| 9 |
-
integrity="sha512-ygzQGrI/8Bfkm9ToUkBEuSMrapUZcHUys05feZh4ScVrKCYEXJsCBYNeVWZ0ghpH+n3Sl7OYlRZ/1ko01pYUCQ=="
|
| 10 |
-
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
| 11 |
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
| 12 |
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
| 13 |
</head>
|
|
@@ -75,16 +73,16 @@
|
|
| 75 |
<div role="tablist" class="tabs tabs-border tabs-xl" id="tab-container">
|
| 76 |
<a role="tab" class="tab tab-active" id="doc-table-tab">📝
|
| 77 |
Documents</a>
|
| 78 |
-
<a role="tab" class="tab tab-disabled" id="requirements-tab">
|
| 79 |
<div class="flex items-center gap-1">
|
| 80 |
<div class="badge badge-neutral badge-outline badge-xs" id="requirements-tab-badge">0</div>
|
| 81 |
<span>Requirements</span>
|
| 82 |
</div>
|
| 83 |
</a>
|
| 84 |
-
<a role="tab" class="tab tab-disabled" id="solutions-tab"
|
| 85 |
-
|
| 86 |
-
<a role="tab" class="tab tab-disabled" id="
|
| 87 |
-
|
| 88 |
</div>
|
| 89 |
|
| 90 |
<div id="doc-table-tab-contents" class="hidden">
|
|
@@ -323,20 +321,100 @@
|
|
| 323 |
</div>
|
| 324 |
</div>
|
| 325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
|
| 327 |
<!--App settings modal container-->
|
| 328 |
<dialog id="settings_modal" class="modal">
|
| 329 |
<div class="modal-box w-11/12 max-w-5xl">
|
| 330 |
-
<h3 class="text-lg font-bold">
|
| 331 |
-
<p class="py-4">Enter your LLM provider URL and key to enable using private solution assessment and
|
| 332 |
-
refining</p>
|
| 333 |
<div class="modal-action">
|
| 334 |
<form method="dialog">
|
| 335 |
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
| 336 |
</form>
|
| 337 |
</div>
|
| 338 |
<h2 class="text-lg font-bold">Private generation settings</h2>
|
| 339 |
-
<
|
|
|
|
| 340 |
<div>
|
| 341 |
<label for="provider-url" class="block mb-2 text-sm font-medium">LLM provider URL</label>
|
| 342 |
<input id="settings-provider-url" name="provider-url" class="input input-bordered w-full">
|
|
@@ -348,6 +426,13 @@
|
|
| 348 |
type="password">
|
| 349 |
</div>
|
| 350 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
<div>
|
| 352 |
<label for="assessment-rules" class="block mb-2 text-sm font-medium">Assessment rules</label>
|
| 353 |
<textarea id="settings-assessment-rules" name="assessment-rules"
|
|
@@ -361,15 +446,13 @@
|
|
| 361 |
class="textarea textarea-bordered w-full h-48"
|
| 362 |
placeholder="Enter your portfolio info here..."></textarea>
|
| 363 |
</div>
|
| 364 |
-
</
|
| 365 |
<div class="flex">
|
| 366 |
<button class="btn btn-success" id="test-btn">Save config</button>
|
| 367 |
</div>
|
| 368 |
</div>
|
| 369 |
</dialog>
|
| 370 |
|
| 371 |
-
<script type="module" src="js/sse.js"></script>
|
| 372 |
-
<script type="module" src="js/ui-utils.js"></script>
|
| 373 |
<script type="module" src="js/app.js"></script>
|
| 374 |
</body>
|
| 375 |
|
|
|
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
<title>Requirements Extractor</title>
|
| 8 |
+
<!--See JS imports for ESM modules-->
|
|
|
|
|
|
|
| 9 |
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
| 10 |
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
| 11 |
</head>
|
|
|
|
| 73 |
<div role="tablist" class="tabs tabs-border tabs-xl" id="tab-container">
|
| 74 |
<a role="tab" class="tab tab-active" id="doc-table-tab">📝
|
| 75 |
Documents</a>
|
| 76 |
+
<a role="tab" class="tab tab-disabled" id="requirements-tab" title="List all requirements">
|
| 77 |
<div class="flex items-center gap-1">
|
| 78 |
<div class="badge badge-neutral badge-outline badge-xs" id="requirements-tab-badge">0</div>
|
| 79 |
<span>Requirements</span>
|
| 80 |
</div>
|
| 81 |
</a>
|
| 82 |
+
<a role="tab" class="tab tab-disabled" id="solutions-tab"
|
| 83 |
+
title="Group requirements into categories and bootstrap a solution">🔨 Group and Bootstrap</a>
|
| 84 |
+
<a role="tab" class="tab tab-disabled hidden" id="draft-tab">🖋 Draft and Assess</a>
|
| 85 |
+
<a role="tab" class="tab tab-disabled" id="query-tab">🔎 Find relevant requirements</a>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
<div id="doc-table-tab-contents" class="hidden">
|
|
|
|
| 321 |
</div>
|
| 322 |
</div>
|
| 323 |
|
| 324 |
+
<div id="draft-tab-contents" class="mb-6 hidden">
|
| 325 |
+
<h2 class="text-2xl font-bold mt-4">Draft and assess solution</h2>
|
| 326 |
+
<div id="draft-container" class="mt-4">
|
| 327 |
+
<div class="max-w-9xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-8">
|
| 328 |
+
<!-- Actual solution draft container -->
|
| 329 |
+
<div class="lg:col-span-2 flex flex-col gap-6">
|
| 330 |
+
<div id="draft-assessment-container" class="flex-col gap-6">
|
| 331 |
+
<!-- card with the solution -->
|
| 332 |
+
<div class="card bg-base-100 shadow-xl">
|
| 333 |
+
<div class="card-body">
|
| 334 |
+
<h2 class="card-title text-2xl mb-2 font-bold">Solution Draft</h2>
|
| 335 |
+
<div id="solution-draft-display" class="space-y-2">
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
</div>
|
| 339 |
+
|
| 340 |
+
<!-- Assessment and Insights Card -->
|
| 341 |
+
<div class="card bg-base-100 shadow-xl mt-4">
|
| 342 |
+
<div class="card-body">
|
| 343 |
+
<h2 class="text-xl font-bold">Assessment Result</h2>
|
| 344 |
+
<div id="assessment-results" class="space-y-2">
|
| 345 |
+
<div>
|
| 346 |
+
<p class="font-bold text-m">Patcom recommendation: </p>
|
| 347 |
+
<p class="text-m" id="assessment-recommendation-status">NO-GO</p>
|
| 348 |
+
</div>
|
| 349 |
+
<div>
|
| 350 |
+
<p class="font-bold text-m">Quick summary:</p>
|
| 351 |
+
<p id="assessment-recommendation-summary">A short resumé of the patcom sayings should go there.</p>
|
| 352 |
+
</div>
|
| 353 |
+
<button class="btn btn-info" id="read-assessment-button">Read whole assessment</button>
|
| 354 |
+
</div>
|
| 355 |
+
|
| 356 |
+
<div class="divider my-1"></div>
|
| 357 |
+
|
| 358 |
+
<h3 class="text-lg font-bold mt-2">Actionable Insights</h3>
|
| 359 |
+
<p class="text-sm text-base-content/70">Select insights below to guide the next
|
| 360 |
+
refinement.
|
| 361 |
+
</p>
|
| 362 |
+
<div id="insights-container" class="form-control mt-4 space-y-2">
|
| 363 |
+
<!-- Checkboxes will be dynamically inserted here -->
|
| 364 |
+
</div>
|
| 365 |
+
<div class="card-actions justify-end mt-6">
|
| 366 |
+
<button id="refine-btn" class="btn btn-primary">Refine with Selected
|
| 367 |
+
Insights</button>
|
| 368 |
+
</div>
|
| 369 |
+
</div>
|
| 370 |
+
</div>
|
| 371 |
+
</div>
|
| 372 |
+
|
| 373 |
+
</div>
|
| 374 |
+
|
| 375 |
+
<!-- draft timeline -->
|
| 376 |
+
<div class="lg:col-span-1">
|
| 377 |
+
<div class="card bg-base-100 shadow-xl">
|
| 378 |
+
<div class="card-body">
|
| 379 |
+
<h2 class="card-title">Draft timeline</h2>
|
| 380 |
+
<div class="flex justify-center p-4 mt-4">
|
| 381 |
+
<ul id="timeline-container" class="steps steps-vertical">
|
| 382 |
+
<li class="step">Enter initial solution draft</li>
|
| 383 |
+
</ul>
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
</div>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
|
| 391 |
+
<!--full screen modal to read the full assessment-->
|
| 392 |
+
<dialog id="read-assessment-modal" class="modal">
|
| 393 |
+
<div class="modal-box w-11/12 max-w-5xl">
|
| 394 |
+
<div class="modal-action">
|
| 395 |
+
<form method="dialog">
|
| 396 |
+
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
| 397 |
+
</form>
|
| 398 |
+
</div>
|
| 399 |
+
<div id="read-assessment-content">
|
| 400 |
+
<p>Nothing to read here</p>
|
| 401 |
+
</div>
|
| 402 |
+
</dialog>
|
| 403 |
+
</div>
|
| 404 |
+
|
| 405 |
|
| 406 |
<!--App settings modal container-->
|
| 407 |
<dialog id="settings_modal" class="modal">
|
| 408 |
<div class="modal-box w-11/12 max-w-5xl">
|
| 409 |
+
<h3 class="text-lg font-bold">Application settings</h3>
|
|
|
|
|
|
|
| 410 |
<div class="modal-action">
|
| 411 |
<form method="dialog">
|
| 412 |
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
| 413 |
</form>
|
| 414 |
</div>
|
| 415 |
<h2 class="text-lg font-bold">Private generation settings</h2>
|
| 416 |
+
<p class="py-4">Detail your private LLM credentials under to draft and generate solution using private LLM</p>
|
| 417 |
+
<div class="space-y-4">
|
| 418 |
<div>
|
| 419 |
<label for="provider-url" class="block mb-2 text-sm font-medium">LLM provider URL</label>
|
| 420 |
<input id="settings-provider-url" name="provider-url" class="input input-bordered w-full">
|
|
|
|
| 426 |
type="password">
|
| 427 |
</div>
|
| 428 |
|
| 429 |
+
<div>
|
| 430 |
+
<label for="provider-model" class="block mb-2 text-sm font-medium">Model</label>
|
| 431 |
+
<button id="settings-fetch-models" class="btn btn-outline">Fetch models</button>
|
| 432 |
+
<select id="settings-provider-model" name="provider-model" class="select">
|
| 433 |
+
</select>
|
| 434 |
+
</div>
|
| 435 |
+
|
| 436 |
<div>
|
| 437 |
<label for="assessment-rules" class="block mb-2 text-sm font-medium">Assessment rules</label>
|
| 438 |
<textarea id="settings-assessment-rules" name="assessment-rules"
|
|
|
|
| 446 |
class="textarea textarea-bordered w-full h-48"
|
| 447 |
placeholder="Enter your portfolio info here..."></textarea>
|
| 448 |
</div>
|
| 449 |
+
</div>
|
| 450 |
<div class="flex">
|
| 451 |
<button class="btn btn-success" id="test-btn">Save config</button>
|
| 452 |
</div>
|
| 453 |
</div>
|
| 454 |
</dialog>
|
| 455 |
|
|
|
|
|
|
|
| 456 |
<script type="module" src="js/app.js"></script>
|
| 457 |
</body>
|
| 458 |
|
static/js/app.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
|
| 2 |
import {
|
| 3 |
toggleElementsEnabled, toggleContainersVisibility, showLoadingOverlay, hideLoadingOverlay, populateSelect,
|
| 4 |
-
populateCheckboxDropdown,
|
| 5 |
-
|
| 6 |
-
|
| 7 |
} from "./ui-utils.js";
|
| 8 |
import { postWithSSE } from "./sse.js";
|
| 9 |
|
|
@@ -15,15 +15,18 @@ let selectedType = ""; // "" = Tous
|
|
| 15 |
let selectedStatus = new Set(); // valeurs cochées (hors "Tous")
|
| 16 |
let selectedAgenda = new Set();
|
| 17 |
|
| 18 |
-
//
|
| 19 |
-
let accordionStates = {};
|
| 20 |
let formattedRequirements = [];
|
| 21 |
let categorizedRequirements = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
let solutionsCriticizedVersions = [];
|
| 23 |
// checksum pour vérifier si les requirements séléctionnés ont changé
|
| 24 |
let lastSelectedRequirementsChecksum = null;
|
| 25 |
-
|
| 26 |
-
let hasRequirementsExtracted = false;
|
| 27 |
|
| 28 |
// =============================================================================
|
| 29 |
// FONCTIONS MÉTIER
|
|
@@ -428,7 +431,7 @@ async function categorizeRequirements(max_categories) {
|
|
| 428 |
const data = await response.json();
|
| 429 |
categorizedRequirements = data;
|
| 430 |
displayCategorizedRequirements(categorizedRequirements.categories);
|
| 431 |
-
|
| 432 |
|
| 433 |
// Masquer le container de query et afficher les catégories et boutons solutions
|
| 434 |
// toggleContainersVisibility(['query-requirements-container'], false);
|
|
@@ -586,7 +589,6 @@ function copySelectedRequirementsAsMarkdown() {
|
|
| 586 |
const markdownText = lines.join('\n');
|
| 587 |
|
| 588 |
navigator.clipboard.writeText(markdownText).then(() => {
|
| 589 |
-
console.log("Markdown copied to clipboard.");
|
| 590 |
alert("Selected requirements copied to clipboard");
|
| 591 |
}).catch(err => {
|
| 592 |
console.error("Failed to copy markdown:", err);
|
|
@@ -718,6 +720,8 @@ function displaySearchResults(results) {
|
|
| 718 |
container.appendChild(resultsDiv);
|
| 719 |
}
|
| 720 |
|
|
|
|
|
|
|
| 721 |
function createSolutionAccordion(solutionCriticizedHistory, containerId, versionIndex = 0, categoryIndex = null) {
|
| 722 |
const container = document.getElementById(containerId);
|
| 723 |
if (!container) {
|
|
@@ -809,66 +813,30 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 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 =
|
| 814 |
console.log(isOpen);
|
| 815 |
if (!isOpen)
|
| 816 |
content.classList.add('hidden');
|
| 817 |
|
| 818 |
-
// Section Problem Description
|
| 819 |
-
const problemSection = document.createElement('div');
|
| 820 |
-
problemSection.className = 'bg-red-50 border-l-2 border-red-400 p-3 rounded-r-md';
|
| 821 |
-
problemSection.innerHTML = `
|
| 822 |
-
<h4 class="text-sm font-semibold text-red-800 mb-2 flex items-center">
|
| 823 |
-
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
| 824 |
-
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
| 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">
|
| 838 |
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
| 839 |
-
</svg>
|
| 840 |
-
Addressed requirements
|
| 841 |
-
</h4>
|
| 842 |
-
<ul class="list-disc pl-5 space-y-1 text-gray-700 text-xs">
|
| 843 |
-
${reqItemsUl}
|
| 844 |
-
</ul>
|
| 845 |
-
`
|
| 846 |
-
|
| 847 |
-
// Section Solution Description
|
| 848 |
-
const solutionSection = document.createElement('div');
|
| 849 |
-
solutionSection.className = 'bg-green-50 border-l-2 border-green-400 p-3 rounded-r-md';
|
| 850 |
-
solutionSection.innerHTML = `
|
| 851 |
-
<h4 class="text-sm font-semibold text-green-800 mb-2 flex items-center">
|
| 852 |
-
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
| 853 |
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
| 854 |
-
</svg>
|
| 855 |
-
Solution Description
|
| 856 |
-
</h4>
|
| 857 |
-
`;
|
| 858 |
-
|
| 859 |
-
// container for markdown content
|
| 860 |
-
const solContents = document.createElement('div');
|
| 861 |
-
solContents.className = "text-xs text-gray-700 leading-relaxed";
|
| 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 |
-
|
| 872 |
// Section Critique
|
| 873 |
const critiqueSection = document.createElement('div');
|
| 874 |
critiqueSection.className = 'bg-yellow-50 border-l-2 border-yellow-400 p-3 rounded-r-md';
|
|
@@ -918,65 +886,12 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 918 |
|
| 919 |
critiqueSection.innerHTML = critiqueContent;
|
| 920 |
|
| 921 |
-
// ===================================== Section sources ================================
|
| 922 |
-
|
| 923 |
-
const createEl = (tag, properties) => {
|
| 924 |
-
const element = document.createElement(tag);
|
| 925 |
-
Object.assign(element, properties);
|
| 926 |
-
return element;
|
| 927 |
-
};
|
| 928 |
-
|
| 929 |
-
// conteneur des sources
|
| 930 |
-
const sourcesSection = createEl('div', {
|
| 931 |
-
className: 'bg-gray-50 border-l-2 border-gray-400 p-3 rounded-r-md'
|
| 932 |
-
});
|
| 933 |
-
|
| 934 |
-
const heading = createEl('h4', {
|
| 935 |
-
className: 'text-sm font-semibold text-black mb-2 flex items-center',
|
| 936 |
-
innerHTML: `
|
| 937 |
-
<svg
|
| 938 |
-
xmlns="http://www.w3.org/2000/svg"
|
| 939 |
-
class="w-4 h-4 mr-1"
|
| 940 |
-
fill="none"
|
| 941 |
-
viewBox="0 0 24 24"
|
| 942 |
-
stroke="currentColor"
|
| 943 |
-
stroke-width="2">
|
| 944 |
-
<path
|
| 945 |
-
stroke-linecap="round"
|
| 946 |
-
stroke-linejoin="round"
|
| 947 |
-
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"
|
| 948 |
-
/>
|
| 949 |
-
</svg>
|
| 950 |
-
Sources
|
| 951 |
-
`
|
| 952 |
-
});
|
| 953 |
-
|
| 954 |
-
const pillContainer = createEl('div', {
|
| 955 |
-
className: 'flex flex-wrap mt-1'
|
| 956 |
-
});
|
| 957 |
-
|
| 958 |
-
// create source reference pills
|
| 959 |
-
solution['references'].forEach(source => {
|
| 960 |
-
const pillLink = createEl('a', {
|
| 961 |
-
href: source.url,
|
| 962 |
-
target: '_blank',
|
| 963 |
-
rel: 'noopener noreferrer',
|
| 964 |
-
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',
|
| 965 |
-
textContent: source.name
|
| 966 |
-
});
|
| 967 |
-
pillContainer.appendChild(pillLink);
|
| 968 |
-
});
|
| 969 |
-
|
| 970 |
-
sourcesSection.append(heading, pillContainer);
|
| 971 |
-
|
| 972 |
// ======================================================================================
|
| 973 |
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
content.appendChild(solutionSection);
|
| 978 |
content.appendChild(critiqueSection);
|
| 979 |
-
content.appendChild(sourcesSection);
|
| 980 |
|
| 981 |
// Événement de clic pour l'accordéon (exclure les boutons de navigation)
|
| 982 |
header.addEventListener('click', (e) => {
|
|
@@ -987,7 +902,7 @@ function createSingleAccordionItem(item, index, versionIndex, solutionCriticized
|
|
| 987 |
|
| 988 |
// handling open state
|
| 989 |
const isCurrentlyOpen = !content.classList.contains('hidden');
|
| 990 |
-
|
| 991 |
if (isCurrentlyOpen)
|
| 992 |
content.classList.add('hidden');
|
| 993 |
else
|
|
@@ -1036,20 +951,20 @@ function updateSingleAccordion(solutionCriticizedHistory, containerId, newVersio
|
|
| 1036 |
// Fonction d'initialisation simplifiée
|
| 1037 |
function initializeSolutionAccordion(solutionCriticizedHistory, containerId, startVersion = 0) {
|
| 1038 |
// Réinitialiser les états d'accordéon
|
| 1039 |
-
|
| 1040 |
createSolutionAccordion(solutionCriticizedHistory, containerId, startVersion);
|
| 1041 |
document.getElementById(containerId).classList.remove('hidden')
|
| 1042 |
}
|
| 1043 |
|
| 1044 |
// Supprime toutes les accordéons de solutions générées
|
| 1045 |
//FIXME: À terme, ne devrait pas exister
|
| 1046 |
-
function
|
| 1047 |
-
|
| 1048 |
solutionsCriticizedVersions = []
|
| 1049 |
document.querySelectorAll('.solution-accordion').forEach(a => a.remove());
|
| 1050 |
}
|
| 1051 |
|
| 1052 |
-
async function
|
| 1053 |
console.log(selected_categories);
|
| 1054 |
|
| 1055 |
let input_req = structuredClone(selected_categories);
|
|
@@ -1060,21 +975,21 @@ async function generateSolutions(selected_categories, user_constraints = null) {
|
|
| 1060 |
return responseObj;
|
| 1061 |
}
|
| 1062 |
|
| 1063 |
-
async function
|
| 1064 |
let response = await fetch('/solutions/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) })
|
| 1065 |
let responseObj = await response.json()
|
| 1066 |
solutionsCriticizedVersions.push(responseObj)
|
| 1067 |
}
|
| 1068 |
|
| 1069 |
-
async function
|
| 1070 |
let response = await fetch('/solutions/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) })
|
| 1071 |
let responseObj = await response.json()
|
| 1072 |
-
await
|
| 1073 |
}
|
| 1074 |
|
| 1075 |
-
async function
|
| 1076 |
let soluce;
|
| 1077 |
-
showLoadingOverlay('
|
| 1078 |
|
| 1079 |
const selected_requirements = getSelectedRequirementsByCategory();
|
| 1080 |
const user_constraints = document.getElementById('additional-gen-instr').value;
|
|
@@ -1086,17 +1001,17 @@ async function workflow(steps = 1) {
|
|
| 1086 |
|
| 1087 |
for (let step = 1; step <= steps; step++) {
|
| 1088 |
if (requirements_changed) {
|
| 1089 |
-
|
| 1090 |
console.log("Requirements checksum changed. Cleaning up");
|
| 1091 |
lastSelectedRequirementsChecksum = selected_requirements.requirements_checksum;
|
| 1092 |
}
|
| 1093 |
|
| 1094 |
if (solutionsCriticizedVersions.length == 0) {
|
| 1095 |
-
soluce = await
|
| 1096 |
-
await
|
| 1097 |
} else {
|
| 1098 |
let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
|
| 1099 |
-
await
|
| 1100 |
}
|
| 1101 |
}
|
| 1102 |
hideLoadingOverlay();
|
|
@@ -1108,12 +1023,14 @@ async function workflow(steps = 1) {
|
|
| 1108 |
// =============================================================================
|
| 1109 |
|
| 1110 |
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
| 1111 |
bindTabs();
|
|
|
|
| 1112 |
// Événements des boutons principaux
|
| 1113 |
-
// document.getElementById('get-meetings-btn').addEventListener('click', getMeetings);
|
| 1114 |
document.getElementById('working-group-select').addEventListener('change', (ev) => {
|
| 1115 |
getMeetings();
|
| 1116 |
});
|
|
|
|
| 1117 |
document.getElementById('get-tdocs-btn').addEventListener('click', getTDocs);
|
| 1118 |
document.getElementById('download-tdocs-btn').addEventListener('click', downloadTDocs);
|
| 1119 |
document.getElementById('extract-requirements-btn').addEventListener('click', extractRequirements);
|
|
@@ -1126,14 +1043,20 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
| 1126 |
// Événement pour la recherche
|
| 1127 |
document.getElementById('search-requirements-btn').addEventListener('click', searchRequirements);
|
| 1128 |
|
| 1129 |
-
// Événements pour les boutons de solutions
|
| 1130 |
document.getElementById('get-solutions-btn').addEventListener('click', () => {
|
| 1131 |
const n_steps = document.getElementById('solution-gen-nsteps').value;
|
| 1132 |
-
|
| 1133 |
});
|
| 1134 |
document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
|
| 1135 |
-
|
| 1136 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1137 |
});
|
| 1138 |
|
| 1139 |
// dseactiver le choix du nb de catégories lorsqu'en mode auto
|
|
@@ -1145,12 +1068,23 @@ document.getElementById("additional-gen-instr-btn").addEventListener('click', (e
|
|
| 1145 |
document.getElementById('additional-gen-instr').focus()
|
| 1146 |
})
|
| 1147 |
|
|
|
|
| 1148 |
document.getElementById('copy-reqs-btn').addEventListener('click', (ev) => {
|
| 1149 |
copySelectedRequirementsAsMarkdown();
|
| 1150 |
});
|
| 1151 |
|
|
|
|
| 1152 |
document.getElementById('copy-all-reqs-btn').addEventListener('click', copyAllRequirementsAsMarkdown);
|
| 1153 |
|
| 1154 |
-
|
| 1155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1156 |
});
|
|
|
|
| 1 |
|
| 2 |
import {
|
| 3 |
toggleElementsEnabled, toggleContainersVisibility, showLoadingOverlay, hideLoadingOverlay, populateSelect,
|
| 4 |
+
populateCheckboxDropdown, populateDaisyDropdown, extractTableData, switchTab, enableTabSwitching, debounceAutoCategoryCount,
|
| 5 |
+
bindTabs, checkPrivateLLMInfoAvailable, moveSolutionToDrafts, buildSolutionSubCategories, handleDraftRefine, renderDraftUI, populateLLMModelSelect,
|
| 6 |
+
displayFullAssessment
|
| 7 |
} from "./ui-utils.js";
|
| 8 |
import { postWithSSE } from "./sse.js";
|
| 9 |
|
|
|
|
| 15 |
let selectedStatus = new Set(); // valeurs cochées (hors "Tous")
|
| 16 |
let selectedAgenda = new Set();
|
| 17 |
|
| 18 |
+
// Requirements
|
|
|
|
| 19 |
let formattedRequirements = [];
|
| 20 |
let categorizedRequirements = [];
|
| 21 |
+
// les requirements ont ils été extraits au moins une fois ?
|
| 22 |
+
let hasRequirementsExtracted = false;
|
| 23 |
+
|
| 24 |
+
// Generation de solutions
|
| 25 |
+
let solutionAccordionStates = {};
|
| 26 |
let solutionsCriticizedVersions = [];
|
| 27 |
// checksum pour vérifier si les requirements séléctionnés ont changé
|
| 28 |
let lastSelectedRequirementsChecksum = null;
|
| 29 |
+
|
|
|
|
| 30 |
|
| 31 |
// =============================================================================
|
| 32 |
// FONCTIONS MÉTIER
|
|
|
|
| 431 |
const data = await response.json();
|
| 432 |
categorizedRequirements = data;
|
| 433 |
displayCategorizedRequirements(categorizedRequirements.categories);
|
| 434 |
+
clearAllBootstrapSolutions();
|
| 435 |
|
| 436 |
// Masquer le container de query et afficher les catégories et boutons solutions
|
| 437 |
// toggleContainersVisibility(['query-requirements-container'], false);
|
|
|
|
| 589 |
const markdownText = lines.join('\n');
|
| 590 |
|
| 591 |
navigator.clipboard.writeText(markdownText).then(() => {
|
|
|
|
| 592 |
alert("Selected requirements copied to clipboard");
|
| 593 |
}).catch(err => {
|
| 594 |
console.error("Failed to copy markdown:", err);
|
|
|
|
| 720 |
container.appendChild(resultsDiv);
|
| 721 |
}
|
| 722 |
|
| 723 |
+
// =========================================== Solution bootstrapping ==============================================
|
| 724 |
+
|
| 725 |
function createSolutionAccordion(solutionCriticizedHistory, containerId, versionIndex = 0, categoryIndex = null) {
|
| 726 |
const container = document.getElementById(containerId);
|
| 727 |
if (!container) {
|
|
|
|
| 813 |
content.className = `accordion-content px-4 py-3 space-y-3`;
|
| 814 |
content.id = `content-${solution['category_id']}`;
|
| 815 |
|
| 816 |
+
// enable solution drafting if private LLM info is present to enable solution private drafting
|
| 817 |
+
if (checkPrivateLLMInfoAvailable()) {
|
| 818 |
+
// Boutons pour passer la solution en draft
|
| 819 |
+
const body_btn_div = document.createElement('div');
|
| 820 |
+
body_btn_div.className = "flex justify-end";
|
| 821 |
+
|
| 822 |
+
const draft_btn = document.createElement('button');
|
| 823 |
+
draft_btn.className = "btn btn-secondary rounded-full";
|
| 824 |
+
draft_btn.innerText = "✏ Draft solution"
|
| 825 |
+
body_btn_div.appendChild(draft_btn);
|
| 826 |
+
content.appendChild(body_btn_div);
|
| 827 |
+
|
| 828 |
+
draft_btn.addEventListener('click', _ => {
|
| 829 |
+
// alert(`Drafting solution ${solution['category_id']} ${versionIndex}`)
|
| 830 |
+
moveSolutionToDrafts(solution);
|
| 831 |
+
});
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
// Vérifier l'état d'ouverture précédent
|
| 835 |
+
const isOpen = solutionAccordionStates[solution['category_id']] || false;
|
| 836 |
console.log(isOpen);
|
| 837 |
if (!isOpen)
|
| 838 |
content.classList.add('hidden');
|
| 839 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
// Section Critique
|
| 841 |
const critiqueSection = document.createElement('div');
|
| 842 |
critiqueSection.className = 'bg-yellow-50 border-l-2 border-yellow-400 p-3 rounded-r-md';
|
|
|
|
| 886 |
|
| 887 |
critiqueSection.innerHTML = critiqueContent;
|
| 888 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 889 |
// ======================================================================================
|
| 890 |
|
| 891 |
+
for (let item of buildSolutionSubCategories(solution))
|
| 892 |
+
content.appendChild(item);
|
| 893 |
+
|
|
|
|
| 894 |
content.appendChild(critiqueSection);
|
|
|
|
| 895 |
|
| 896 |
// Événement de clic pour l'accordéon (exclure les boutons de navigation)
|
| 897 |
header.addEventListener('click', (e) => {
|
|
|
|
| 902 |
|
| 903 |
// handling open state
|
| 904 |
const isCurrentlyOpen = !content.classList.contains('hidden');
|
| 905 |
+
solutionAccordionStates[solution['category_id']] = isCurrentlyOpen;
|
| 906 |
if (isCurrentlyOpen)
|
| 907 |
content.classList.add('hidden');
|
| 908 |
else
|
|
|
|
| 951 |
// Fonction d'initialisation simplifiée
|
| 952 |
function initializeSolutionAccordion(solutionCriticizedHistory, containerId, startVersion = 0) {
|
| 953 |
// Réinitialiser les états d'accordéon
|
| 954 |
+
solutionAccordionStates = {};
|
| 955 |
createSolutionAccordion(solutionCriticizedHistory, containerId, startVersion);
|
| 956 |
document.getElementById(containerId).classList.remove('hidden')
|
| 957 |
}
|
| 958 |
|
| 959 |
// Supprime toutes les accordéons de solutions générées
|
| 960 |
//FIXME: À terme, ne devrait pas exister
|
| 961 |
+
function clearAllBootstrapSolutions() {
|
| 962 |
+
solutionAccordionStates = {}
|
| 963 |
solutionsCriticizedVersions = []
|
| 964 |
document.querySelectorAll('.solution-accordion').forEach(a => a.remove());
|
| 965 |
}
|
| 966 |
|
| 967 |
+
async function generateBootstrapSolutions(selected_categories, user_constraints = null) {
|
| 968 |
console.log(selected_categories);
|
| 969 |
|
| 970 |
let input_req = structuredClone(selected_categories);
|
|
|
|
| 975 |
return responseObj;
|
| 976 |
}
|
| 977 |
|
| 978 |
+
async function generateBootstrapCriticisms(solutions) {
|
| 979 |
let response = await fetch('/solutions/criticize_solution', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(solutions) })
|
| 980 |
let responseObj = await response.json()
|
| 981 |
solutionsCriticizedVersions.push(responseObj)
|
| 982 |
}
|
| 983 |
|
| 984 |
+
async function refineBootstrapSolutions(critiques) {
|
| 985 |
let response = await fetch('/solutions/refine_solutions', { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(critiques) })
|
| 986 |
let responseObj = await response.json()
|
| 987 |
+
await generateBootstrapCriticisms(responseObj)
|
| 988 |
}
|
| 989 |
|
| 990 |
+
async function boostrapWorkflow(steps = 1) {
|
| 991 |
let soluce;
|
| 992 |
+
showLoadingOverlay('Boostrapping solutions ....');
|
| 993 |
|
| 994 |
const selected_requirements = getSelectedRequirementsByCategory();
|
| 995 |
const user_constraints = document.getElementById('additional-gen-instr').value;
|
|
|
|
| 1001 |
|
| 1002 |
for (let step = 1; step <= steps; step++) {
|
| 1003 |
if (requirements_changed) {
|
| 1004 |
+
clearAllBootstrapSolutions();
|
| 1005 |
console.log("Requirements checksum changed. Cleaning up");
|
| 1006 |
lastSelectedRequirementsChecksum = selected_requirements.requirements_checksum;
|
| 1007 |
}
|
| 1008 |
|
| 1009 |
if (solutionsCriticizedVersions.length == 0) {
|
| 1010 |
+
soluce = await generateBootstrapSolutions(selected_requirements, user_constraints ? user_constraints : null);
|
| 1011 |
+
await generateBootstrapCriticisms(soluce)
|
| 1012 |
} else {
|
| 1013 |
let prevSoluce = solutionsCriticizedVersions[solutionsCriticizedVersions.length - 1];
|
| 1014 |
+
await refineBootstrapSolutions(prevSoluce)
|
| 1015 |
}
|
| 1016 |
}
|
| 1017 |
hideLoadingOverlay();
|
|
|
|
| 1023 |
// =============================================================================
|
| 1024 |
|
| 1025 |
document.addEventListener('DOMContentLoaded', function () {
|
| 1026 |
+
// Bind tous les tabs
|
| 1027 |
bindTabs();
|
| 1028 |
+
|
| 1029 |
// Événements des boutons principaux
|
|
|
|
| 1030 |
document.getElementById('working-group-select').addEventListener('change', (ev) => {
|
| 1031 |
getMeetings();
|
| 1032 |
});
|
| 1033 |
+
|
| 1034 |
document.getElementById('get-tdocs-btn').addEventListener('click', getTDocs);
|
| 1035 |
document.getElementById('download-tdocs-btn').addEventListener('click', downloadTDocs);
|
| 1036 |
document.getElementById('extract-requirements-btn').addEventListener('click', extractRequirements);
|
|
|
|
| 1043 |
// Événement pour la recherche
|
| 1044 |
document.getElementById('search-requirements-btn').addEventListener('click', searchRequirements);
|
| 1045 |
|
| 1046 |
+
// Événements pour les boutons de solutions bootstrappées
|
| 1047 |
document.getElementById('get-solutions-btn').addEventListener('click', () => {
|
| 1048 |
const n_steps = document.getElementById('solution-gen-nsteps').value;
|
| 1049 |
+
boostrapWorkflow(n_steps);
|
| 1050 |
});
|
| 1051 |
document.getElementById('get-solutions-step-btn').addEventListener('click', () => {
|
| 1052 |
+
boostrapWorkflow(1);
|
| 1053 |
});
|
| 1054 |
+
|
| 1055 |
+
// Events des boutons pour le drafting de solutions
|
| 1056 |
+
const refineBtn = document.getElementById('refine-btn');
|
| 1057 |
+
refineBtn.addEventListener('click', handleDraftRefine);
|
| 1058 |
+
|
| 1059 |
+
renderDraftUI();
|
| 1060 |
});
|
| 1061 |
|
| 1062 |
// dseactiver le choix du nb de catégories lorsqu'en mode auto
|
|
|
|
| 1068 |
document.getElementById('additional-gen-instr').focus()
|
| 1069 |
})
|
| 1070 |
|
| 1071 |
+
// copy requirements
|
| 1072 |
document.getElementById('copy-reqs-btn').addEventListener('click', (ev) => {
|
| 1073 |
copySelectedRequirementsAsMarkdown();
|
| 1074 |
});
|
| 1075 |
|
| 1076 |
+
// copy all requirements
|
| 1077 |
document.getElementById('copy-all-reqs-btn').addEventListener('click', copyAllRequirementsAsMarkdown);
|
| 1078 |
|
| 1079 |
+
// button to fetch llm models
|
| 1080 |
+
document.getElementById('settings-fetch-models').addEventListener('click', _ => {
|
| 1081 |
+
const url = document.getElementById('settings-provider-url').value;
|
| 1082 |
+
const token = document.getElementById('settings-provider-token').value;
|
| 1083 |
+
|
| 1084 |
+
populateLLMModelSelect('settings-provider-model', url, token).catch(e => alert("Error while fetching models: " + e))
|
| 1085 |
+
});
|
| 1086 |
+
|
| 1087 |
+
// button to open full assessment modal
|
| 1088 |
+
document.getElementById('read-assessment-button').addEventListener('click', _ => {
|
| 1089 |
+
displayFullAssessment();
|
| 1090 |
});
|
static/js/gen.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import zod from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Met en forme le prompt template passé en paramètres avec les arguments
|
| 5 |
+
* @param {String} template
|
| 6 |
+
* @param {Object} args
|
| 7 |
+
*/
|
| 8 |
+
export function formatTemplate(template, args) {
|
| 9 |
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
| 10 |
+
// 'match' est la correspondance complète (ex: "{nom}")
|
| 11 |
+
// 'key' est le contenu du premier groupe capturé (ex: "nom")
|
| 12 |
+
|
| 13 |
+
if (key in args)
|
| 14 |
+
return args[key];
|
| 15 |
+
|
| 16 |
+
// Si la clé n'est pas trouvée dans args, on laisse le placeholder tel quel.
|
| 17 |
+
return "";
|
| 18 |
+
});
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Recupère le prompt pour la tâche spécifiée.
|
| 23 |
+
* @param {String} task
|
| 24 |
+
*/
|
| 25 |
+
export async function retrieveTemplate(task) {
|
| 26 |
+
const req = await fetch(`/prompt/${task}`)
|
| 27 |
+
return await req.text();
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* Lance un deep search sur le serveur pour les topics donnés.
|
| 32 |
+
* @param {Array} topics
|
| 33 |
+
*/
|
| 34 |
+
async function performDeepSearch(topics) {
|
| 35 |
+
const response = await fetch('/solutions/search_prior_art', {
|
| 36 |
+
method: 'POST',
|
| 37 |
+
headers: { 'Content-Type': 'application/json' },
|
| 38 |
+
body: JSON.stringify({ topics: topics })
|
| 39 |
+
});
|
| 40 |
+
const results = await response.json();
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Genère une completion avec le LLM specifié
|
| 45 |
+
* @param {String} providerUrl - URL du provider du LLM
|
| 46 |
+
* @param {String} modelName - Nom du modèle à appeler
|
| 47 |
+
* @param {String} apiKey - API key a utiliser
|
| 48 |
+
* @param {Array<{role: string, content: string}>} messages - Liste de messages à passer au modèle
|
| 49 |
+
* @param {Number} temperature - Température à utiliser pour la génération
|
| 50 |
+
*/
|
| 51 |
+
export async function generateCompletion(providerUrl, modelName, apiKey, messages, temperature = 0.5) {
|
| 52 |
+
const genEndpoint = providerUrl + "/chat/completions"
|
| 53 |
+
|
| 54 |
+
try {
|
| 55 |
+
const response = await fetch(genEndpoint, {
|
| 56 |
+
method: 'POST',
|
| 57 |
+
headers: {
|
| 58 |
+
'Content-Type': 'application/json',
|
| 59 |
+
'Authorization': `Bearer ${apiKey}`, // OpenAI-like authorization header
|
| 60 |
+
},
|
| 61 |
+
body: JSON.stringify({
|
| 62 |
+
model: modelName,
|
| 63 |
+
messages: messages,
|
| 64 |
+
temperature: temperature,
|
| 65 |
+
}),
|
| 66 |
+
});
|
| 67 |
+
|
| 68 |
+
if (!response.ok) {
|
| 69 |
+
const errorData = await response.json();
|
| 70 |
+
throw new Error(`API request failed with status ${response.status}: ${errorData.error?.message || 'Unknown error'}`);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
const data = await response.json();
|
| 74 |
+
|
| 75 |
+
if (data.choices && data.choices.length > 0 && data.choices[0].message && data.choices[0].message.content)
|
| 76 |
+
return data.choices[0].message.content;
|
| 77 |
+
|
| 78 |
+
} catch (error) {
|
| 79 |
+
console.error("Error calling private LLM :", error);
|
| 80 |
+
throw error;
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
/**
|
| 85 |
+
* Genère une completion avec le LLM specifié
|
| 86 |
+
* @param {String} providerUrl - URL du provider du LLM
|
| 87 |
+
* @param {String} modelName - Nom du modèle à appeler
|
| 88 |
+
* @param {String} apiKey - API key a utiliser
|
| 89 |
+
* @param {Array<{role: string, content: string}>} messages - Liste de messages à passer au modèle
|
| 90 |
+
* @param {Object} schema - Zod schema to use for structured generation
|
| 91 |
+
* @param {Number} temperature - Température à utiliser pour la génération
|
| 92 |
+
*/
|
| 93 |
+
export async function generateStructuredCompletion(providerUrl, modelName, apiKey, messages, schema, temperature = 0.5) {
|
| 94 |
+
const genEndpoint = providerUrl + "/chat/completions";
|
| 95 |
+
try {
|
| 96 |
+
const response = await fetch(genEndpoint, {
|
| 97 |
+
method: 'POST',
|
| 98 |
+
headers: {
|
| 99 |
+
'Content-Type': 'application/json',
|
| 100 |
+
'Authorization': `Bearer ${apiKey}`,
|
| 101 |
+
},
|
| 102 |
+
body: JSON.stringify({
|
| 103 |
+
model: modelName,
|
| 104 |
+
messages: messages,
|
| 105 |
+
temperature: temperature,
|
| 106 |
+
response_format: { type: "json_object" }
|
| 107 |
+
}),
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
if (!response.ok) {
|
| 111 |
+
const errorData = await response.json();
|
| 112 |
+
throw new Error(`API request failed with status ${response.status}: ${errorData.error?.message || 'Unknown error'}`);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
const data = await response.json();
|
| 116 |
+
|
| 117 |
+
console.log(data.choices[0].message.content);
|
| 118 |
+
|
| 119 |
+
// parse json output
|
| 120 |
+
const parsedJSON = JSON.parse(data.choices[0].message.content.replace('```json', '').replace("```", ""));
|
| 121 |
+
|
| 122 |
+
// validate output with zod
|
| 123 |
+
const validatedSchema = schema.parse(parsedJSON);
|
| 124 |
+
|
| 125 |
+
return validatedSchema;
|
| 126 |
+
} catch (error) {
|
| 127 |
+
console.error("Error calling private LLM :", error);
|
| 128 |
+
throw error;
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/**
|
| 133 |
+
* Retrieves a list of available models from an OpenAI-compatible API using fetch.
|
| 134 |
+
*
|
| 135 |
+
* @param {string} providerUrl The base URL of the OpenAI-compatible API endpoint (e.g., "http://localhost:8000/v1").
|
| 136 |
+
* @param {string} apiKey The API key for authentication.
|
| 137 |
+
* @returns {Promise<Array<string>>} A promise that resolves with an array of model names, or rejects with an error.
|
| 138 |
+
*/
|
| 139 |
+
export async function getModelList(providerUrl, apiKey) {
|
| 140 |
+
try {
|
| 141 |
+
// Construct the full URL for the models endpoint
|
| 142 |
+
const modelsUrl = `${providerUrl}/models`;
|
| 143 |
+
|
| 144 |
+
console.log(modelsUrl);
|
| 145 |
+
|
| 146 |
+
// Make a GET request to the models endpoint using fetch
|
| 147 |
+
const response = await fetch(modelsUrl, {
|
| 148 |
+
method: 'GET', // Explicitly state the method
|
| 149 |
+
headers: {
|
| 150 |
+
'Authorization': `Bearer ${apiKey}`, // OpenAI-compatible authorization header
|
| 151 |
+
'Content-Type': 'application/json',
|
| 152 |
+
},
|
| 153 |
+
});
|
| 154 |
+
|
| 155 |
+
// Check if the request was successful (status code 200-299)
|
| 156 |
+
if (!response.ok) {
|
| 157 |
+
// If the response is not OK, try to get more error details
|
| 158 |
+
const errorData = await response.json().catch(() => ({})); // Attempt to parse JSON error, fallback to empty object
|
| 159 |
+
throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorData.message || response.statusText}`);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
// Parse the JSON response body
|
| 163 |
+
const data = await response.json();
|
| 164 |
+
|
| 165 |
+
// The response data structure for OpenAI-compatible APIs usually contains a 'data' array
|
| 166 |
+
// where each item represents a model and has an 'id' property.
|
| 167 |
+
if (data && Array.isArray(data.data)) {
|
| 168 |
+
const allModelNames = data.data.map(model => model.id);
|
| 169 |
+
|
| 170 |
+
// Filter out models containing "embedding" (case-insensitive)
|
| 171 |
+
const filteredModelNames = allModelNames.filter(modelName =>
|
| 172 |
+
!modelName.toLowerCase().includes('embedding')
|
| 173 |
+
);
|
| 174 |
+
|
| 175 |
+
return filteredModelNames;
|
| 176 |
+
} else {
|
| 177 |
+
// Handle cases where the response format is unexpected
|
| 178 |
+
throw new Error('Unexpected response format from the API. Could not find model list.');
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
} catch (error) {
|
| 182 |
+
console.error('Error fetching model list:', error.message);
|
| 183 |
+
// Re-throw the error to allow the caller to handle it
|
| 184 |
+
throw error;
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
// # ========================================================================================== Idea assessment logic ==================================================================
|
| 189 |
+
|
| 190 |
+
const StructuredAssessmentOutput = zod.object({
|
| 191 |
+
final_verdict: zod.string(),
|
| 192 |
+
summary: zod.string(),
|
| 193 |
+
insights: zod.array(zod.string()),
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
export async function assessSolution(providerUrl, modelName, apiKey, solution, assessment_rules, portfolio_info) {
|
| 197 |
+
const template = await retrieveTemplate("assess");
|
| 198 |
+
|
| 199 |
+
const assessment_template = formatTemplate(template, {
|
| 200 |
+
notation_criterias: assessment_rules,
|
| 201 |
+
business: portfolio_info,
|
| 202 |
+
problem_description: solution.problem_description,
|
| 203 |
+
solution_description: solution.solution_description,
|
| 204 |
+
});
|
| 205 |
+
|
| 206 |
+
const assessment_full = await generateCompletion(providerUrl, modelName, apiKey, [
|
| 207 |
+
{ role: "user", content: assessment_template }
|
| 208 |
+
]);
|
| 209 |
+
|
| 210 |
+
const structured_template = await retrieveTemplate("extract");
|
| 211 |
+
const structured_filled_template = formatTemplate(structured_template, {
|
| 212 |
+
"report": assessment_full,
|
| 213 |
+
"response_schema": zod.toJSONSchema(StructuredAssessmentOutput)
|
| 214 |
+
})
|
| 215 |
+
|
| 216 |
+
const extracted_info = await generateStructuredCompletion(providerUrl, modelName, apiKey, [{ role: "user", content: structured_filled_template }], StructuredAssessmentOutput);
|
| 217 |
+
|
| 218 |
+
return { assessment_full, extracted_info };
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
export async function refineSolution(providerUrl, modelName, apiKey, solution, insights, assessment_rules, portfolio_info) {
|
| 222 |
+
const template = await retrieveTemplate("refine");
|
| 223 |
+
|
| 224 |
+
const refine_template = formatTemplate(template, {
|
| 225 |
+
"problem_description": solution.problem_description,
|
| 226 |
+
"solution_description": solution.solution_description,
|
| 227 |
+
"insights": insights.map(i => i.text).join("\n -"),
|
| 228 |
+
"business_info": portfolio_info,
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
console.log(refine_template);
|
| 232 |
+
|
| 233 |
+
const refined_idea = await generateCompletion(providerUrl, modelName, apiKey, [{ role: "user", content: refine_template }]);
|
| 234 |
+
|
| 235 |
+
const newSolution = structuredClone(solution);
|
| 236 |
+
newSolution.solution_description = refined_idea;
|
| 237 |
+
|
| 238 |
+
return newSolution;
|
| 239 |
+
}
|
static/js/ui-utils.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
// =============================================================================
|
| 2 |
// FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS
|
| 3 |
// =============================================================================
|
|
@@ -212,11 +215,74 @@ export function extractTableData(mapping) {
|
|
| 212 |
return data;
|
| 213 |
}
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
const TABS = {
|
| 216 |
'doc-table-tab': 'doc-table-tab-contents',
|
| 217 |
'requirements-tab': 'requirements-tab-contents',
|
| 218 |
'solutions-tab': 'solutions-tab-contents',
|
| 219 |
-
'query-tab': 'query-tab-contents'
|
|
|
|
| 220 |
};
|
| 221 |
|
| 222 |
/**
|
|
@@ -279,16 +345,316 @@ export function debounceAutoCategoryCount(state) {
|
|
| 279 |
document.getElementById('category-count').disabled = state;
|
| 280 |
}
|
| 281 |
|
| 282 |
-
|
| 283 |
-
/**
|
| 284 |
-
* Vérifie si les paramètres sont bien renseignés pour utiliser la génération privée.
|
| 285 |
-
*/
|
| 286 |
-
export function checkCanUsePrivateGen() {
|
| 287 |
const provider_url = document.getElementById('settings-provider-url').value;
|
| 288 |
const provider_token = document.getElementById('settings-provider-token').value;
|
|
|
|
| 289 |
const assessment_rules = document.getElementById('settings-assessment-rules').value;
|
| 290 |
const portfolio_info = document.getElementById('settings-portfolio').value;
|
| 291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
const isEmpty = (str) => (!str?.length);
|
| 293 |
-
return !isEmpty(provider_url) && !isEmpty(provider_token) && !isEmpty(assessment_rules) && !isEmpty(portfolio_info);
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { marked } from 'https://cdnjs.cloudflare.com/ajax/libs/marked/16.1.1/lib/marked.esm.js';
|
| 2 |
+
import { assessSolution, getModelList, refineSolution } from "./gen.js"
|
| 3 |
+
|
| 4 |
// =============================================================================
|
| 5 |
// FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS
|
| 6 |
// =============================================================================
|
|
|
|
| 215 |
return data;
|
| 216 |
}
|
| 217 |
|
| 218 |
+
/**
|
| 219 |
+
* Construit les sous-catégories communes dans l'affichage des solutions
|
| 220 |
+
*/
|
| 221 |
+
export function buildSolutionSubCategories(solution) {
|
| 222 |
+
// Section Problem Description
|
| 223 |
+
const problemSection = document.createElement('div');
|
| 224 |
+
problemSection.className = 'bg-red-50 border-l-2 border-red-400 p-3 rounded-r-md';
|
| 225 |
+
problemSection.innerHTML = `
|
| 226 |
+
<h4 class="text-sm font-semibold text-red-800 mb-2 flex items-center">
|
| 227 |
+
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
| 228 |
+
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
| 229 |
+
</svg>
|
| 230 |
+
Problem Description
|
| 231 |
+
</h4>
|
| 232 |
+
<p class="text-xs text-gray-700 leading-relaxed">${solution["problem_description"] || 'Aucune description du problème disponible.'}</p>
|
| 233 |
+
`;
|
| 234 |
+
|
| 235 |
+
// Section Problem requirements
|
| 236 |
+
const reqsSection = document.createElement('div');
|
| 237 |
+
reqsSection.className = "bg-gray-50 border-l-2 border-red-400 p-3 rounded-r-md";
|
| 238 |
+
const reqItemsUl = solution["requirements"].map(req => `<li>${req}</li>`).join('');
|
| 239 |
+
reqsSection.innerHTML = `
|
| 240 |
+
<h4 class="text-sm font-semibold text-gray-800 mb-2 flex items-center">
|
| 241 |
+
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
| 242 |
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
| 243 |
+
</svg>
|
| 244 |
+
Addressed 3GPP requirements
|
| 245 |
+
</h4>
|
| 246 |
+
<ul class="list-disc pl-5 space-y-1 text-gray-700 text-xs">
|
| 247 |
+
${reqItemsUl}
|
| 248 |
+
</ul>
|
| 249 |
+
`
|
| 250 |
+
|
| 251 |
+
// Section Solution Description
|
| 252 |
+
const solutionSection = document.createElement('div');
|
| 253 |
+
solutionSection.className = 'bg-green-50 border-l-2 border-green-400 p-3 rounded-r-md';
|
| 254 |
+
solutionSection.innerHTML = `
|
| 255 |
+
<h4 class="text-sm font-semibold text-green-800 mb-2 flex items-center">
|
| 256 |
+
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
| 257 |
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
| 258 |
+
</svg>
|
| 259 |
+
Solution Description
|
| 260 |
+
</h4>
|
| 261 |
+
`;
|
| 262 |
+
|
| 263 |
+
// container for markdown content
|
| 264 |
+
const solContents = document.createElement('div');
|
| 265 |
+
solContents.className = "text-xs text-gray-700 leading-relaxed";
|
| 266 |
+
solutionSection.appendChild(solContents);
|
| 267 |
+
|
| 268 |
+
try {
|
| 269 |
+
solContents.innerHTML = marked.parse(solution['solution_description']);
|
| 270 |
+
}
|
| 271 |
+
catch (e) {
|
| 272 |
+
console.error(e);
|
| 273 |
+
solContents.innerHTML = `<p class="text-xs text-gray-700 leading-relaxed">${solution['solution_description'] || 'No available solution description'}</p>`;
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
return [problemSection, reqsSection, solutionSection]
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
|
| 280 |
const TABS = {
|
| 281 |
'doc-table-tab': 'doc-table-tab-contents',
|
| 282 |
'requirements-tab': 'requirements-tab-contents',
|
| 283 |
'solutions-tab': 'solutions-tab-contents',
|
| 284 |
+
'query-tab': 'query-tab-contents',
|
| 285 |
+
'draft-tab': 'draft-tab-contents'
|
| 286 |
};
|
| 287 |
|
| 288 |
/**
|
|
|
|
| 345 |
document.getElementById('category-count').disabled = state;
|
| 346 |
}
|
| 347 |
|
| 348 |
+
export function getPrivateLLMInfo() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
const provider_url = document.getElementById('settings-provider-url').value;
|
| 350 |
const provider_token = document.getElementById('settings-provider-token').value;
|
| 351 |
+
const provider_model = document.getElementById('settings-provider-model').value;
|
| 352 |
const assessment_rules = document.getElementById('settings-assessment-rules').value;
|
| 353 |
const portfolio_info = document.getElementById('settings-portfolio').value;
|
| 354 |
|
| 355 |
+
return { provider_url, provider_token, provider_model, assessment_rules, portfolio_info };
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
/**
|
| 359 |
+
* Vérifie si les paramètres sont bien renseignés pour utiliser la génération privée.
|
| 360 |
+
*/
|
| 361 |
+
export function checkPrivateLLMInfoAvailable() {
|
| 362 |
+
const { provider_url, provider_token, provider_model, assessment_rules, portfolio_info } = getPrivateLLMInfo();
|
| 363 |
const isEmpty = (str) => (!str?.length);
|
| 364 |
+
return !isEmpty(provider_url) && !isEmpty(provider_token) && !isEmpty(assessment_rules) && !isEmpty(portfolio_info) && !isEmpty(provider_model);
|
| 365 |
+
// return true;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
/**
|
| 370 |
+
* Populates a select element with model names fetched from the API.
|
| 371 |
+
* @param {string} selectElementId The ID of the HTML select element to populate.
|
| 372 |
+
* @param {string} providerUrl The API provider URL.
|
| 373 |
+
* @param {string} apiKey The API key.
|
| 374 |
+
*/
|
| 375 |
+
export async function populateLLMModelSelect(selectElementId, providerUrl, apiKey) {
|
| 376 |
+
const selectElement = document.getElementById(selectElementId);
|
| 377 |
+
if (!selectElement) {
|
| 378 |
+
console.error(`Select element with ID "${selectElementId}" not found.`);
|
| 379 |
+
return;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
// Clear the "Loading..." option or any existing options
|
| 383 |
+
selectElement.innerHTML = '';
|
| 384 |
+
|
| 385 |
+
try {
|
| 386 |
+
const models = await getModelList(providerUrl, apiKey);
|
| 387 |
+
|
| 388 |
+
if (models.length === 0) {
|
| 389 |
+
const option = document.createElement('option');
|
| 390 |
+
option.value = "";
|
| 391 |
+
option.textContent = "No models found";
|
| 392 |
+
selectElement.appendChild(option);
|
| 393 |
+
selectElement.disabled = true; // Disable if no models
|
| 394 |
+
return;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
// Add a default "Please select" option
|
| 398 |
+
const defaultOption = document.createElement('option');
|
| 399 |
+
defaultOption.value = ""; // Or a placeholder like "select-model"
|
| 400 |
+
defaultOption.textContent = "Select a model";
|
| 401 |
+
defaultOption.disabled = true; // Make it unselectable initially
|
| 402 |
+
defaultOption.selected = true; // Make it the default selected option
|
| 403 |
+
selectElement.appendChild(defaultOption);
|
| 404 |
+
|
| 405 |
+
// Populate with the fetched models
|
| 406 |
+
models.forEach(modelName => {
|
| 407 |
+
const option = document.createElement('option');
|
| 408 |
+
option.value = modelName;
|
| 409 |
+
option.textContent = modelName;
|
| 410 |
+
selectElement.appendChild(option);
|
| 411 |
+
});
|
| 412 |
+
} catch (error) {
|
| 413 |
+
throw error;
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
// ================================================================================ Solution drafting using private LLMs ==========================================================
|
| 420 |
+
|
| 421 |
+
/** History of previously created drafts
|
| 422 |
+
* The draftHistory will look like this:
|
| 423 |
+
* {
|
| 424 |
+
* solution: {} - the solution object
|
| 425 |
+
* insights: [
|
| 426 |
+
* { id: 'i1', text: 'Some insight text', checked: false },
|
| 427 |
+
* { id: 'i2', text: 'Another insight', checked: true }
|
| 428 |
+
* ],
|
| 429 |
+
* assessment_full: The full assessment text
|
| 430 |
+
* }
|
| 431 |
+
*/
|
| 432 |
+
let draftHistory = [];
|
| 433 |
+
|
| 434 |
+
// Index of the latest draft in the draft history.
|
| 435 |
+
// -1 means theres no draft.
|
| 436 |
+
let draftCurrentIndex = -1;
|
| 437 |
+
|
| 438 |
+
/**
|
| 439 |
+
* Passe une solution bootstrappée en draft pour être itérée sur le private compute
|
| 440 |
+
* @param {Object} solution - Un objet qui représente une solution bootstrappée (SolutionModel).
|
| 441 |
+
*/
|
| 442 |
+
export function moveSolutionToDrafts(solution) {
|
| 443 |
+
const draft_tab_item = document.getElementById('draft-tab');
|
| 444 |
+
if (draft_tab_item.classList.contains("hidden")) // un-hide the draft tab the first time a solution is drafted
|
| 445 |
+
draft_tab_item.classList.remove("hidden");
|
| 446 |
+
|
| 447 |
+
switchTab('draft-tab');
|
| 448 |
+
|
| 449 |
+
const { provider_url, provider_token, provider_model, assessment_rules, portfolio_info } = getPrivateLLMInfo();
|
| 450 |
+
|
| 451 |
+
showLoadingOverlay("Assessing solution ....");
|
| 452 |
+
assessSolution(provider_url, provider_model, provider_token, solution, assessment_rules, portfolio_info).then(response => {
|
| 453 |
+
// reset the state of the draft history
|
| 454 |
+
draftHistory = [];
|
| 455 |
+
draftCurrentIndex = -1;
|
| 456 |
+
|
| 457 |
+
const insights = response.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false }));
|
| 458 |
+
|
| 459 |
+
// push the solution to the draft history
|
| 460 |
+
draftHistory.push({
|
| 461 |
+
solution: solution,
|
| 462 |
+
insights: insights,
|
| 463 |
+
assessment_full: response.assessment_full,
|
| 464 |
+
final_verdict: response.extracted_info.final_verdict,
|
| 465 |
+
assessment_summary: response.extracted_info.summary,
|
| 466 |
+
});
|
| 467 |
+
|
| 468 |
+
draftCurrentIndex++;
|
| 469 |
+
// update the UI by rendering it
|
| 470 |
+
renderDraftUI();
|
| 471 |
+
}).catch(e => {
|
| 472 |
+
alert(e);
|
| 473 |
+
}).finally(() => {
|
| 474 |
+
hideLoadingOverlay();
|
| 475 |
+
})
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
/**
|
| 479 |
+
* SIMULATED API CALL
|
| 480 |
+
* In a real app, this would be an async fetch call to a backend AI/service.
|
| 481 |
+
* @param {string} previousSolution - The text of the previous solution.
|
| 482 |
+
* @param {Array<string>} selectedInsights - An array of the selected insight texts.
|
| 483 |
+
* @returns {object} - An object with the new solution and new insights.
|
| 484 |
+
*/
|
| 485 |
+
function assessSolutionDraft(previousSolution, selectedInsights = []) {
|
| 486 |
+
const version = draftHistory.length + 1;
|
| 487 |
+
let newSolutionText;
|
| 488 |
+
|
| 489 |
+
if (selectedInsights.length > 0) {
|
| 490 |
+
newSolutionText = `V${version}: Based on "${previousSolution.substring(0, 30)}..." and the insights: [${selectedInsights.join(', ')}], we've refined the plan to focus more on digital outreach and local partnerships.`;
|
| 491 |
+
} else {
|
| 492 |
+
newSolutionText = `V${version}: This is an initial assessment of "${previousSolution.substring(0, 50)}...". The plan seems solid but could use more detail.`;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
// Generate some random new insights
|
| 496 |
+
const newInsights = [
|
| 497 |
+
{ id: `v${version}-i1`, text: `Consider social media marketing (Insight ${Math.floor(Math.random() * 100)})`, checked: false },
|
| 498 |
+
{ id: `v${version}-i2`, text: `Focus on customer retention (Insight ${Math.floor(Math.random() * 100)})`, checked: false },
|
| 499 |
+
{ id: `v${version}-i3`, text: `Expand the product line (Insight ${Math.floor(Math.random() * 100)})`, checked: false },
|
| 500 |
+
];
|
| 501 |
+
|
| 502 |
+
return { newSolutionText, newInsights };
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
/**
|
| 506 |
+
* Renders the timeline UI based on the current state
|
| 507 |
+
* @param {Number} currentIndex - Current index for latest draft
|
| 508 |
+
* @param {Array} drafts - Current history of previous drafts
|
| 509 |
+
*/
|
| 510 |
+
function renderDraftTimeline(timelineContainer, currentIndex, drafts) {
|
| 511 |
+
timelineContainer.innerHTML = '';
|
| 512 |
+
drafts.forEach((state, idx) => {
|
| 513 |
+
const li = document.createElement('li');
|
| 514 |
+
li.className = `step ${idx <= currentIndex ? 'step-primary' : ''}`;
|
| 515 |
+
li.textContent = `Draft #${idx + 1}`;
|
| 516 |
+
li.onclick = () => jumpToDraft(idx);
|
| 517 |
+
timelineContainer.appendChild(li);
|
| 518 |
+
});
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
/**
|
| 522 |
+
* Renders the entire UI based on the current state (draftHistory[currentIndex]).
|
| 523 |
+
*/
|
| 524 |
+
export function renderDraftUI() {
|
| 525 |
+
const solutionDisplay = document.getElementById('solution-draft-display');
|
| 526 |
+
const insightsContainer = document.getElementById('insights-container');
|
| 527 |
+
const timelineContainer = document.getElementById('timeline-container');
|
| 528 |
+
|
| 529 |
+
if (draftCurrentIndex < 0) {
|
| 530 |
+
solutionDisplay.innerHTML = `<p>No drafted solutions for now</p>`
|
| 531 |
+
insightsContainer.innerHTML = '';
|
| 532 |
+
timelineContainer.innerHTML = '';
|
| 533 |
+
return;
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
const currentState = draftHistory[draftCurrentIndex];
|
| 537 |
+
|
| 538 |
+
const solutionSections = buildSolutionSubCategories(currentState.solution);
|
| 539 |
+
solutionDisplay.innerHTML = '';
|
| 540 |
+
|
| 541 |
+
// 1. Render Solution
|
| 542 |
+
for (let child of solutionSections)
|
| 543 |
+
solutionDisplay.appendChild(child);
|
| 544 |
+
|
| 545 |
+
//3. but 2. actually: Print verdict and quick summary
|
| 546 |
+
document.getElementById('assessment-recommendation-status').innerText = currentState.final_verdict;
|
| 547 |
+
document.getElementById('assessment-recommendation-summary').innerText = currentState.assessment_summary;
|
| 548 |
+
|
| 549 |
+
// 2. Render Insights Checkboxes
|
| 550 |
+
insightsContainer.innerHTML = '';
|
| 551 |
+
currentState.insights.forEach(insight => {
|
| 552 |
+
const isChecked = insight.checked ? 'checked' : '';
|
| 553 |
+
const insightEl = document.createElement('label');
|
| 554 |
+
insightEl.className = 'label cursor-pointer justify-start gap-4';
|
| 555 |
+
insightEl.innerHTML = `
|
| 556 |
+
<input type="checkbox" id="${insight.id}" ${isChecked} class="checkbox checkbox-primary" />
|
| 557 |
+
<span class="label-text">${insight.text}</span>
|
| 558 |
+
`;
|
| 559 |
+
// Add event listener to update state on check/uncheck
|
| 560 |
+
insightEl.querySelector('input').addEventListener('change', (e) => {
|
| 561 |
+
insight.checked = e.target.checked;
|
| 562 |
+
});
|
| 563 |
+
insightsContainer.appendChild(insightEl);
|
| 564 |
+
});
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
// Render the timeline with the fetched timeline container
|
| 568 |
+
renderDraftTimeline(timelineContainer, draftCurrentIndex, draftHistory);
|
| 569 |
+
|
| 570 |
+
console.log(draftHistory);
|
| 571 |
+
console.log(draftCurrentIndex);
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
/**
|
| 575 |
+
* Handles the "Refine" button click.
|
| 576 |
+
*/
|
| 577 |
+
export function handleDraftRefine() {
|
| 578 |
+
// Fetch DOM elements here
|
| 579 |
+
const refineBtn = document.getElementById('refine-btn');
|
| 580 |
+
|
| 581 |
+
const currentState = draftHistory[draftCurrentIndex];
|
| 582 |
+
|
| 583 |
+
// Get selected insights from the current state
|
| 584 |
+
const selectedInsights = currentState.insights
|
| 585 |
+
.filter(i => i.checked)
|
| 586 |
+
.map(i => i.text);
|
| 587 |
+
|
| 588 |
+
if (selectedInsights.length === 0) {
|
| 589 |
+
alert('Please select at least one insight to refine the solution.');
|
| 590 |
+
return;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
// --- THIS IS THE KEY LOGIC FOR INVALIDATING THE FUTURE ---
|
| 594 |
+
// If we are not at the end of the timeline, chop off the future states.
|
| 595 |
+
if (draftCurrentIndex < draftHistory.length - 1) {
|
| 596 |
+
draftHistory = draftHistory.slice(0, draftCurrentIndex + 1);
|
| 597 |
+
}
|
| 598 |
+
// ---
|
| 599 |
+
|
| 600 |
+
const { provider_url, provider_token, provider_model, assessment_rules, portfolio_info } = getPrivateLLMInfo();
|
| 601 |
+
|
| 602 |
+
showLoadingOverlay('Refining and assessing ....')
|
| 603 |
+
|
| 604 |
+
refineSolution(provider_url, provider_model, provider_token, currentState.solution, selectedInsights, assessment_rules, portfolio_info)
|
| 605 |
+
.then(newSolution => {
|
| 606 |
+
const refinedSolution = newSolution;
|
| 607 |
+
return assessSolution(provider_url, provider_model, provider_token, newSolution, assessment_rules, portfolio_info)
|
| 608 |
+
.then(assessedResult => {
|
| 609 |
+
return { refinedSolution, assessedResult };
|
| 610 |
+
});
|
| 611 |
+
})
|
| 612 |
+
.then(result => {
|
| 613 |
+
const newInsights = result.assessedResult.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false }));
|
| 614 |
+
|
| 615 |
+
draftHistory.push({
|
| 616 |
+
solution: result.refinedSolution,
|
| 617 |
+
insights: newInsights,
|
| 618 |
+
assessment_full: result.assessedResult.assessment_full,
|
| 619 |
+
final_verdict: result.assessedResult.extracted_info.final_verdict,
|
| 620 |
+
assessment_summary: result.assessedResult.extracted_info.summary,
|
| 621 |
+
});
|
| 622 |
+
|
| 623 |
+
draftCurrentIndex++;
|
| 624 |
+
renderDraftUI();
|
| 625 |
+
})
|
| 626 |
+
.catch(error => {
|
| 627 |
+
// Handle any errors
|
| 628 |
+
alert("An error occurred:" + error);
|
| 629 |
+
}).finally(() => {
|
| 630 |
+
hideLoadingOverlay();
|
| 631 |
+
});
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
/**
|
| 635 |
+
* Jumps to a specific state in the draftHistory timeline.
|
| 636 |
+
*/
|
| 637 |
+
function jumpToDraft(index) {
|
| 638 |
+
if (index >= 0 && index < draftHistory.length) {
|
| 639 |
+
draftCurrentIndex = index;
|
| 640 |
+
renderDraftUI();
|
| 641 |
+
}
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
export function displayFullAssessment() {
|
| 645 |
+
const full_assessment_content = document.getElementById('read-assessment-content');
|
| 646 |
+
const modal = document.getElementById('read-assessment-modal');
|
| 647 |
+
|
| 648 |
+
if (draftCurrentIndex < 0)
|
| 649 |
+
return;
|
| 650 |
+
|
| 651 |
+
const lastDraft = draftHistory[draftCurrentIndex];
|
| 652 |
+
try {
|
| 653 |
+
full_assessment_content.innerHTML = marked.parse(lastDraft.assessment_full);
|
| 654 |
+
}
|
| 655 |
+
catch (e) {
|
| 656 |
+
full_assessment_content.innerHTML = lastDraft.assessment_full;
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
modal.showModal();
|
| 660 |
+
}
|