| 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(); |
| } |