| | import { marked } from 'https://cdnjs.cloudflare.com/ajax/libs/marked/16.1.1/lib/marked.esm.js'; |
| | import { assessSolution, getModelList, refineSolution } from "./gen.js" |
| | import { clearConfig, loadConfig, saveConfig } from "./persistence.js"; |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export function toggleElementsEnabled(elementIds, enabled = true) { |
| | elementIds.forEach(id => { |
| | const element = document.getElementById(id); |
| | if (element) { |
| | if (enabled) { |
| | element.removeAttribute('disabled'); |
| | } else { |
| | element.setAttribute('disabled', 'true'); |
| | } |
| | } |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export function toggleContainersVisibility(containerIds, visible = true) { |
| | containerIds.forEach(id => { |
| | const container = document.getElementById(id); |
| | if (container) { |
| | if (visible) { |
| | container.classList.remove('hidden'); |
| | } else { |
| | container.classList.add('hidden'); |
| | } |
| | } |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function showLoadingOverlay(message = 'Chargement en cours...') { |
| | document.getElementById('progress-text').textContent = message; |
| | toggleContainersVisibility(['loading-overlay'], true); |
| | } |
| |
|
| | |
| | |
| | |
| | export function hideLoadingOverlay() { |
| | toggleContainersVisibility(['loading-overlay'], false); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export function populateSelect(selectId, options, defaultText = 'Sélectionner...') { |
| | const select = document.getElementById(selectId); |
| | if (select) { |
| | select.innerHTML = `<option value="">${defaultText}</option>`; |
| | Object.entries(options).forEach(([text, value]) => { |
| | const option = document.createElement('option'); |
| | option.value = value; |
| | option.textContent = text; |
| | select.appendChild(option); |
| | }); |
| | } |
| | } |
| |
|
| | export function populateCheckboxDropdown(optionsContainerId, options, filterType, labelId, selectionSet, onSelect) { |
| | const container = document.getElementById(optionsContainerId); |
| | container.innerHTML = ''; |
| | selectionSet.clear(); |
| |
|
| | |
| | options.forEach(option => { |
| | const safeId = `${filterType}-${encodeURIComponent(option).replace(/[%\s]/g, '_')}`; |
| | const label = document.createElement('label'); |
| | label.className = "flex items-center gap-2 cursor-pointer py-1"; |
| | label.innerHTML = ` |
| | <input type="checkbox" class="${filterType}-checkbox option-checkbox" id="${safeId}" value="${option}"> |
| | <span>${option}</span> |
| | `; |
| | label.querySelector('input').addEventListener('change', function () { |
| | if (this.checked) { |
| | selectionSet.add(this.value); |
| | } else { |
| | selectionSet.delete(this.value); |
| | } |
| |
|
| | |
| | updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); |
| | |
| | const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`); |
| | if (allBox && allBox.checked) allBox.checked = false; |
| | |
| | if (selectionSet.size === 0 && allBox) allBox.checked = true; |
| | onSelect?.(); |
| | }); |
| | container.appendChild(label); |
| | }); |
| |
|
| | |
| | updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); |
| |
|
| | |
| | const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`); |
| | if (allBox) { |
| | allBox.addEventListener('change', function () { |
| | if (this.checked) { |
| | |
| | selectionSet.clear(); |
| | container.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); |
| | this.checked = true; |
| | updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); |
| | applyFilters(); |
| | } |
| | }); |
| | } |
| | } |
| |
|
| | export function updateCheckboxDropdownLabel(type, labelId, set, totalCount) { |
| | const label = document.getElementById(labelId); |
| | if (!set.size) { |
| | label.textContent = type.charAt(0).toUpperCase() + type.slice(1) + " (Tous)"; |
| | } else if (set.size === 1) { |
| | label.textContent = [...set][0]; |
| | } else { |
| | label.textContent = `${type.charAt(0).toUpperCase() + type.slice(1)} (${set.size}/${totalCount})`; |
| | } |
| | } |
| |
|
| | export function updateSelectedFilters(filterType, value, isChecked) { |
| | if (isChecked) { |
| | selectedFilters[filterType].add(value); |
| | } else { |
| | selectedFilters[filterType].delete(value); |
| | } |
| | } |
| |
|
| | export function populateDaisyDropdown(menuId, options, labelId, onSelect) { |
| | const menu = document.getElementById(menuId); |
| | menu.innerHTML = ''; |
| | |
| | const liAll = document.createElement('li'); |
| | liAll.innerHTML = `<a data-value="">Tous</a>`; |
| | liAll.querySelector('a').onclick = e => { |
| | e.preventDefault(); |
| | document.getElementById(labelId).textContent = "Type"; |
| | onSelect(""); |
| | }; |
| | menu.appendChild(liAll); |
| |
|
| | |
| | options.forEach(opt => { |
| | const li = document.createElement('li'); |
| | li.innerHTML = `<a data-value="${opt}">${opt}</a>`; |
| | li.querySelector('a').onclick = e => { |
| | e.preventDefault(); |
| | document.getElementById(labelId).textContent = opt; |
| | onSelect(opt); |
| | }; |
| | menu.appendChild(li); |
| | }); |
| | } |
| |
|
| | export function updateFilterLabel(filterType) { |
| | const selectedCount = selectedFilters[filterType].size; |
| | const labelElement = document.getElementById(`${filterType}-filter-label`); |
| |
|
| | if (selectedCount === 0) { |
| | labelElement.textContent = `${filterType} (Tous)`; |
| | } else { |
| | labelElement.textContent = `${filterType} (${selectedCount} sélectionné${selectedCount > 1 ? 's' : ''})`; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export function extractTableData(mapping) { |
| | const tbody = document.querySelector('#data-table tbody'); |
| | const rows = tbody.querySelectorAll('tr'); |
| | const data = []; |
| |
|
| | rows.forEach(row => { |
| | const checkboxes = row.querySelectorAll('input[type="checkbox"]:checked'); |
| | if (checkboxes.length > 0) { |
| | const rowData = {}; |
| | Object.entries(mapping).forEach(([columnName, propertyName]) => { |
| | const cell = row.querySelector(`td[data-column="${columnName}"]`); |
| | if (cell) { |
| | if (columnName == "URL") { |
| | rowData[propertyName] = cell.querySelector('a').getAttribute('href'); |
| | } else { |
| | rowData[propertyName] = cell.textContent.trim(); |
| | } |
| | } |
| | }); |
| | data.push(rowData); |
| | } |
| | }); |
| |
|
| | return data; |
| | } |
| |
|
| | |
| | |
| | |
| | export function buildSolutionSubCategories(solution) { |
| | |
| | const problemSection = document.createElement('div'); |
| | problemSection.className = 'bg-red-50 border-l-2 border-red-400 p-3 rounded-r-md'; |
| | problemSection.innerHTML = ` |
| | <h4 class="text-sm font-semibold text-red-800 mb-2 flex items-center"> |
| | <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> |
| | <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> |
| | </svg> |
| | Problem Description |
| | </h4> |
| | <p class="text-xs text-gray-700 leading-relaxed">${solution["problem_description"] || 'Aucune description du problème disponible.'}</p> |
| | `; |
| |
|
| | |
| | const reqsSection = document.createElement('div'); |
| | reqsSection.className = "bg-gray-50 border-l-2 border-red-400 p-3 rounded-r-md"; |
| | const reqItemsUl = solution["requirements"].map(req => `<li>${req}</li>`).join(''); |
| | reqsSection.innerHTML = ` |
| | <h4 class="text-sm font-semibold text-gray-800 mb-2 flex items-center"> |
| | <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> |
| | <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> |
| | </svg> |
| | Addressed 3GPP requirements |
| | </h4> |
| | <ul class="list-disc pl-5 space-y-1 text-gray-700 text-xs"> |
| | ${reqItemsUl} |
| | </ul> |
| | ` |
| |
|
| | |
| | const solutionSection = document.createElement('div'); |
| | solutionSection.className = 'bg-green-50 border-l-2 border-green-400 p-3 rounded-r-md'; |
| | solutionSection.innerHTML = ` |
| | <h4 class="text-sm font-semibold text-green-800 mb-2 flex items-center"> |
| | <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"> |
| | <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> |
| | </svg> |
| | Solution Description |
| | </h4> |
| | `; |
| |
|
| | |
| | const solContents = document.createElement('div'); |
| | solContents.className = "text-xs text-gray-700 leading-relaxed"; |
| | solutionSection.appendChild(solContents); |
| |
|
| | try { |
| | solContents.innerHTML = marked.parse(solution['solution_description']); |
| | } |
| | catch (e) { |
| | console.error(e); |
| | solContents.innerHTML = `<p class="text-xs text-gray-700 leading-relaxed">${solution['solution_description'] || 'No available solution description'}</p>`; |
| | } |
| |
|
| | return [problemSection, reqsSection, solutionSection] |
| | } |
| |
|
| |
|
| | const TABS = { |
| | 'doc-table-tab': 'doc-table-tab-contents', |
| | 'requirements-tab': 'requirements-tab-contents', |
| | 'solutions-tab': 'solutions-tab-contents', |
| | 'query-tab': 'query-tab-contents', |
| | 'draft-tab': 'draft-tab-contents' |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | export function switchTab(newTab) { |
| | |
| | Object.keys(TABS).forEach(tabId => { |
| | const tabElement = document.getElementById(tabId); |
| | if (tabElement) { |
| | tabElement.classList.remove("tab-active"); |
| | } |
| | }); |
| |
|
| | |
| | Object.values(TABS).forEach(contentId => { |
| | const contentElement = document.getElementById(contentId); |
| | if (contentElement) { |
| | contentElement.classList.add("hidden"); |
| | } |
| | }); |
| |
|
| | |
| | if (newTab in TABS) { |
| | const newTabElement = document.getElementById(newTab); |
| | const newContentElement = document.getElementById(TABS[newTab]); |
| |
|
| | if (newTabElement) newTabElement.classList.add("tab-active"); |
| | if (newContentElement) newContentElement.classList.remove("hidden"); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export function bindTabs() { |
| | Object.keys(TABS).forEach(tabId => { |
| | const tabElement = document.getElementById(tabId); |
| | tabElement.addEventListener('click', _ => switchTab(tabId)); |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | export function enableTabSwitching() { |
| | Object.keys(TABS).forEach(tabId => { |
| | const tab = document.getElementById(tabId); |
| | if (tab) |
| | tab.classList.remove("tab-disabled"); |
| | }) |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | export function debounceAutoCategoryCount(state) { |
| | document.getElementById('category-count').disabled = state; |
| | } |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | export function getConfigFields() { |
| | const providerUrl = document.getElementById('settings-provider-url').value; |
| | const providerToken = document.getElementById('settings-provider-token').value; |
| | const providerModel = document.getElementById('settings-provider-model').value; |
| | const assessmentRules = document.getElementById('settings-assessment-rules').value; |
| | const businessPortfolio = document.getElementById('settings-portfolio').value; |
| |
|
| | return { providerUrl, providerToken, providerModel, assessmentRules, businessPortfolio }; |
| | } |
| |
|
| | |
| | |
| | |
| | export function checkPrivateLLMInfoAvailable() { |
| | const { providerUrl, providerToken, providerModel, assessmentRules, businessPortfolio } = getConfigFields(); |
| | const isEmpty = (str) => (!str?.length); |
| | return !isEmpty(providerUrl) && !isEmpty(providerToken) && !isEmpty(assessmentRules) && !isEmpty(businessPortfolio) && !isEmpty(providerModel); |
| | |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export async function populateLLMModelSelect(selectElementId, providerUrl, apiKey) { |
| | const selectElement = document.getElementById(selectElementId); |
| | if (!selectElement) { |
| | console.error(`Select element with ID "${selectElementId}" not found.`); |
| | return; |
| | } |
| |
|
| | |
| | selectElement.innerHTML = ''; |
| |
|
| | try { |
| | const models = await getModelList(providerUrl, apiKey); |
| |
|
| | if (models.length === 0) { |
| | const option = document.createElement('option'); |
| | option.value = ""; |
| | option.textContent = "No models found"; |
| | selectElement.appendChild(option); |
| | selectElement.disabled = true; |
| | return; |
| | } |
| |
|
| | |
| | const defaultOption = document.createElement('option'); |
| | defaultOption.value = ""; |
| | defaultOption.textContent = "Select a model"; |
| | defaultOption.disabled = true; |
| | defaultOption.selected = true; |
| | selectElement.appendChild(defaultOption); |
| |
|
| | |
| | models.forEach(modelName => { |
| | const option = document.createElement('option'); |
| | option.value = modelName; |
| | option.textContent = modelName; |
| | selectElement.appendChild(option); |
| | }); |
| | } catch (error) { |
| | throw error; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export function handleLoadConfigFields() { |
| | const configuration = loadConfig(); |
| | if (configuration === null) |
| | return; |
| |
|
| | const providerUrl = document.getElementById('settings-provider-url'); |
| | const providerToken = document.getElementById('settings-provider-token'); |
| | const providerModel = document.getElementById('settings-provider-model'); |
| | const assessmentRules = document.getElementById('settings-assessment-rules'); |
| | const businessPortfolio = document.getElementById('settings-portfolio'); |
| |
|
| | providerUrl.value = configuration.providerUrl; |
| | providerToken.value = configuration.providerToken; |
| | assessmentRules.value = configuration.assessmentRules; |
| | businessPortfolio.value = configuration.businessPortfolio; |
| |
|
| | |
| | populateLLMModelSelect('settings-provider-model', configuration.providerUrl, configuration.providerToken).then(() => { |
| | providerModel.value = configuration.providerModel; |
| | }).catch(e => { |
| | alert("Failed to set LLM model in model selector. Model may not be available anymore, check model in LLM settings."); |
| | }) |
| | } |
| |
|
| | |
| | |
| | |
| | export function handleSaveConfigFields() { |
| | saveConfig(getConfigFields()); |
| | alert("Configuration saved locally."); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function handleClearConfig() { |
| | clearConfig(); |
| | alert("Saved configuration has been cleared. Configuration set in the fields won't be saved."); |
| | } |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | let draftHistory = []; |
| |
|
| | |
| | |
| | let draftCurrentIndex = -1; |
| |
|
| | |
| | |
| | |
| | |
| | export function moveSolutionToDrafts(solution) { |
| | const draft_tab_item = document.getElementById('draft-tab'); |
| | if (draft_tab_item.classList.contains("hidden")) |
| | draft_tab_item.classList.remove("hidden"); |
| |
|
| | switchTab('draft-tab'); |
| |
|
| | const { providerUrl, providerToken, providerModel, assessmentRules, businessPortfolio } = getConfigFields(); |
| |
|
| | showLoadingOverlay("Assessing solution ...."); |
| | assessSolution(providerUrl, providerModel, providerToken, solution, assessmentRules, businessPortfolio).then(response => { |
| | |
| | draftHistory = []; |
| | draftCurrentIndex = -1; |
| |
|
| | |
| | const insights = response.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false })); |
| |
|
| | |
| | draftHistory.push({ |
| | solution: solution, |
| | insights: insights, |
| | assessment_full: response.assessment_full, |
| | final_verdict: response.extracted_info.final_verdict, |
| | assessment_summary: response.extracted_info.summary, |
| | }); |
| |
|
| | draftCurrentIndex++; |
| | |
| | renderDraftUI(); |
| | }).catch(e => { |
| | alert(e); |
| | }).finally(() => { |
| | hideLoadingOverlay(); |
| | }) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | function renderDraftTimeline(timelineContainer, currentIndex, drafts) { |
| | timelineContainer.innerHTML = ''; |
| | drafts.forEach((state, idx) => { |
| | const li = document.createElement('li'); |
| | li.className = `step ${idx <= currentIndex ? 'step-primary' : ''}`; |
| | li.textContent = `Draft #${idx + 1}`; |
| | li.onclick = () => jumpToDraft(idx); |
| | timelineContainer.appendChild(li); |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | export function renderDraftUI() { |
| | const solutionDisplay = document.getElementById('solution-draft-display'); |
| | const insightsContainer = document.getElementById('insights-container'); |
| | const timelineContainer = document.getElementById('timeline-container'); |
| |
|
| | if (draftCurrentIndex < 0) { |
| | solutionDisplay.innerHTML = `<p>No drafted solutions for now</p>` |
| | insightsContainer.innerHTML = ''; |
| | timelineContainer.innerHTML = ''; |
| | return; |
| | } |
| |
|
| | const currentState = draftHistory[draftCurrentIndex]; |
| |
|
| | const solutionSections = buildSolutionSubCategories(currentState.solution); |
| | solutionDisplay.innerHTML = ''; |
| |
|
| | |
| | for (let child of solutionSections) |
| | solutionDisplay.appendChild(child); |
| |
|
| | |
| | document.getElementById('assessment-recommendation-status').innerText = currentState.final_verdict; |
| | document.getElementById('assessment-recommendation-summary').innerText = currentState.assessment_summary; |
| |
|
| | |
| | insightsContainer.innerHTML = ''; |
| | currentState.insights.forEach(insight => { |
| | const isChecked = insight.checked ? 'checked' : ''; |
| | const insightEl = document.createElement('label'); |
| | insightEl.className = 'label cursor-pointer justify-start gap-4'; |
| | insightEl.innerHTML = ` |
| | <input type="checkbox" id="${insight.id}" ${isChecked} class="checkbox checkbox-primary" /> |
| | <span class="label-text">${insight.text}</span> |
| | `; |
| | |
| | insightEl.querySelector('input').addEventListener('change', (e) => { |
| | insight.checked = e.target.checked; |
| | }); |
| | insightsContainer.appendChild(insightEl); |
| | }); |
| |
|
| |
|
| | |
| | renderDraftTimeline(timelineContainer, draftCurrentIndex, draftHistory); |
| |
|
| | console.log(draftHistory); |
| | console.log(draftCurrentIndex); |
| | } |
| |
|
| | |
| | |
| | |
| | export function handleDraftRefine() { |
| | |
| | const refineBtn = document.getElementById('refine-btn'); |
| |
|
| | const currentState = draftHistory[draftCurrentIndex]; |
| |
|
| | |
| | const selectedInsights = currentState.insights |
| | .filter(i => i.checked) |
| | .map(i => i.text); |
| |
|
| | if (selectedInsights.length === 0) { |
| | alert('Please select at least one insight to refine the solution.'); |
| | return; |
| | } |
| |
|
| | |
| | |
| | if (draftCurrentIndex < draftHistory.length - 1) { |
| | draftHistory = draftHistory.slice(0, draftCurrentIndex + 1); |
| | } |
| | |
| |
|
| | const { providerUrl, providerToken, providerModel, assessmentRules, businessPortfolio } = getConfigFields(); |
| |
|
| | showLoadingOverlay('Refining and assessing ....') |
| |
|
| | refineSolution(providerUrl, providerModel, providerToken, currentState.solution, selectedInsights, assessmentRules, businessPortfolio) |
| | .then(newSolution => { |
| | const refinedSolution = newSolution; |
| | return assessSolution(providerUrl, providerModel, providerToken, newSolution, assessmentRules, businessPortfolio) |
| | .then(assessedResult => { |
| | return { refinedSolution, assessedResult }; |
| | }); |
| | }) |
| | .then(result => { |
| | |
| | const newInsights = result.assessedResult.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false })); |
| |
|
| | draftHistory.push({ |
| | solution: result.refinedSolution, |
| | insights: newInsights, |
| | assessment_full: result.assessedResult.assessment_full, |
| | final_verdict: result.assessedResult.extracted_info.final_verdict, |
| | assessment_summary: result.assessedResult.extracted_info.summary, |
| | }); |
| |
|
| | draftCurrentIndex++; |
| | renderDraftUI(); |
| | }) |
| | .catch(error => { |
| | |
| | alert("An error occurred:" + error); |
| | }).finally(() => { |
| | hideLoadingOverlay(); |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | function jumpToDraft(index) { |
| | if (index >= 0 && index < draftHistory.length) { |
| | draftCurrentIndex = index; |
| | renderDraftUI(); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export function displayFullAssessment() { |
| | const full_assessment_content = document.getElementById('read-assessment-content'); |
| | const modal = document.getElementById('read-assessment-modal'); |
| |
|
| | if (draftCurrentIndex < 0) |
| | return; |
| |
|
| | const lastDraft = draftHistory[draftCurrentIndex]; |
| | try { |
| | full_assessment_content.innerHTML = marked.parse(lastDraft.assessment_full); |
| | } |
| | catch (e) { |
| | full_assessment_content.innerHTML = lastDraft.assessment_full; |
| | } |
| |
|
| | modal.showModal(); |
| | } |