Spaces:
Restarting
Restarting
import { marked } from 'https://cdnjs.cloudflare.com/ajax/libs/marked/16.1.1/lib/marked.esm.js'; | |
// import { JSZip } from 'https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm'; | |
import { assessSolution, getModelList, refineSolution, runFTOAnalysis } from "./gen.js" | |
import { clearConfig, loadConfig, saveConfig } from "./persistence.js"; | |
// ============================================================================= | |
// FONCTIONS UTILITAIRES POUR LA GESTION DES ÉLÉMENTS | |
// ============================================================================= | |
/** | |
* Active/désactive des éléments par leurs IDs | |
* @param {string[]} elementIds - Liste des IDs des éléments à activer | |
* @param {boolean} enabled - true pour activer, false pour désactiver | |
*/ | |
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'); | |
} | |
} | |
}); | |
} | |
/** | |
* Affiche/masque des conteneurs par leurs IDs | |
* @param {string[]} containerIds - Liste des IDs des conteneurs à afficher | |
* @param {boolean} visible - true pour afficher, false pour masquer | |
*/ | |
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'); | |
} | |
} | |
}); | |
} | |
/** | |
* Affiche le loading overlay avec un message personnalisé | |
* @param {string} message - Message à afficher | |
*/ | |
export function showLoadingOverlay(message = 'Chargement en cours...') { | |
document.getElementById('progress-text').textContent = message; | |
toggleContainersVisibility(['loading-overlay'], true); | |
} | |
/** | |
* Masque le loading overlay | |
*/ | |
export function hideLoadingOverlay() { | |
toggleContainersVisibility(['loading-overlay'], false); | |
} | |
/** | |
* Réinitialise un select et ajoute des options | |
* @param {string} selectId - ID du select | |
* @param {Object} options - Objet avec les options {value: text} | |
* @param {string} defaultText - Texte par défaut | |
*/ | |
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(); // reset all | |
// Ajoute chaque option | |
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); | |
} | |
// Gestion du label "Tous" | |
updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); | |
// Gestion du "Tous" global | |
const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`); | |
if (allBox && allBox.checked) allBox.checked = false; | |
// Si plus rien n'est coché, recoche "Tous" | |
if (selectionSet.size === 0 && allBox) allBox.checked = true; | |
onSelect?.(); | |
}); | |
container.appendChild(label); | |
}); | |
// Réinitialise le label | |
updateCheckboxDropdownLabel(filterType, labelId, selectionSet, options.length); | |
// Gestion de "Tous" | |
const allBox = document.querySelector(`.${filterType}-checkbox[value="all"]`); | |
if (allBox) { | |
allBox.addEventListener('change', function () { | |
if (this.checked) { | |
// Décoche tout le reste | |
selectionSet.clear(); | |
container.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false); | |
this.checked = true; // reste coché | |
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 = ''; | |
// Option "Tous" | |
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); | |
// Ajoute chaque option | |
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' : ''})`; | |
} | |
} | |
/** | |
* Extrait les données du tableau selon un mapping | |
* @param {Object} mapping - Mapping des colonnes {columnName: propertyName} | |
* @returns {Array} Données extraites | |
*/ | |
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; | |
} | |
/** | |
* Construit les sous-catégories communes dans l'affichage des solutions | |
*/ | |
export function buildSolutionSubCategories(solution) { | |
// Section Problem Description | |
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> | |
`; | |
// Section Problem requirements | |
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> | |
` | |
// Section Solution Description | |
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> | |
`; | |
// container for markdown content | |
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' | |
}; | |
/** | |
* Bascule l'affichage sur le nouveau tab | |
* @param {*} newTab | |
*/ | |
export function switchTab(newTab) { | |
// Remove active tab style from all tabs | |
Object.keys(TABS).forEach(tabId => { | |
const tabElement = document.getElementById(tabId); | |
if (tabElement) { | |
tabElement.classList.remove("tab-active"); | |
} | |
}); | |
// Hide all tab contents | |
Object.values(TABS).forEach(contentId => { | |
const contentElement = document.getElementById(contentId); | |
if (contentElement) { | |
contentElement.classList.add("hidden"); | |
} | |
}); | |
// Activate the new tab if it exists in the mapping | |
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"); | |
} | |
} | |
/** | |
* Setup les boutons pour basculer vers un autre tab | |
*/ | |
export function bindTabs() { | |
Object.keys(TABS).forEach(tabId => { | |
const tabElement = document.getElementById(tabId); | |
tabElement.addEventListener('click', _ => switchTab(tabId)); | |
}); | |
} | |
/** | |
* Bascule l'affichage vers la tab uniquement si les requirements sont | |
*/ | |
export function enableTabSwitching() { | |
Object.keys(TABS).forEach(tabId => { | |
const tab = document.getElementById(tabId); | |
if (tab) | |
tab.classList.remove("tab-disabled"); | |
}) | |
} | |
/** | |
* Change l'état d'activation du number box de choix de nb de catégories. | |
*/ | |
export function debounceAutoCategoryCount(state) { | |
document.getElementById('category-count').disabled = state; | |
} | |
// ============================================================================================ Overlay des paramètres ==================================================================== | |
/** | |
* Récupère les valeurs des champs de config des infos LLM. | |
* @returns | |
*/ | |
export function getConfigFields() { | |
const providerUrl = document.getElementById('settings-provider-url').value; | |
const providerToken = document.getElementById('settings-provider-token').value; | |
const reasoningModel = document.getElementById('settings-reasoning-model').value; | |
const fastModel = document.getElementById('settings-fast-model').value; | |
const assessmentRules = document.getElementById('settings-assessment-rules').value; | |
const businessPortfolio = document.getElementById('settings-portfolio').value; | |
const ftoTopicCount = document.getElementById('settings-fto-topic-count').value; | |
return { providerUrl, providerToken, reasoningModel, fastModel, assessmentRules, businessPortfolio, ftoTopicCount }; | |
} | |
/** | |
* Vérifie si les paramètres sont bien renseignés pour utiliser la génération privée. | |
*/ | |
export function checkPrivateLLMInfoAvailable() { | |
const { providerUrl, providerToken, reasoningModel, fastModel, assessmentRules, businessPortfolio } = getConfigFields(); | |
const isEmpty = (str) => (!str?.length); | |
return !isEmpty(providerUrl) && !isEmpty(providerToken) && !isEmpty(assessmentRules) && !isEmpty(businessPortfolio) && !isEmpty(reasoningModel) && !isEmpty(fastModel); | |
// return true; | |
} | |
/** | |
* Populates a select element with model names fetched from the API. | |
* @param {string} selectElementId The ID of the HTML select element to populate. | |
* @param {string} providerUrl The API provider URL. | |
* @param {string} apiKey The API key. | |
*/ | |
export async function populateLLMModelSelect(selectElementId, providerUrl, apiKey) { | |
const selectElement = document.getElementById(selectElementId); | |
if (!selectElement) { | |
console.error(`Select element with ID "${selectElementId}" not found.`); | |
return; | |
} | |
// Clear the "Loading..." option or any existing options | |
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; // Disable if no models | |
return; | |
} | |
// Add a default "Please select" option | |
const defaultOption = document.createElement('option'); | |
defaultOption.value = ""; // Or a placeholder like "select-model" | |
defaultOption.textContent = "Select a model"; | |
defaultOption.disabled = true; // Make it unselectable initially | |
defaultOption.selected = true; // Make it the default selected option | |
selectElement.appendChild(defaultOption); | |
// Populate with the fetched models | |
models.forEach(modelName => { | |
const option = document.createElement('option'); | |
option.value = modelName; | |
option.textContent = modelName; | |
selectElement.appendChild(option); | |
}); | |
} catch (error) { | |
throw error; | |
} | |
} | |
/** | |
* Charge le contenu de la configuration locale dans les champs HTML. | |
*/ | |
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 reasoningModel = document.getElementById('settings-reasoning-model'); | |
const fastModel = document.getElementById('settings-fast-model'); | |
const assessmentRules = document.getElementById('settings-assessment-rules'); | |
const businessPortfolio = document.getElementById('settings-portfolio'); | |
const ftoTopicCount = document.getElementById('settings-fto-topic-count'); | |
providerUrl.value = configuration.providerUrl; | |
providerToken.value = configuration.providerToken; | |
assessmentRules.value = configuration.assessmentRules; | |
businessPortfolio.value = configuration.businessPortfolio; | |
ftoTopicCount.value = configuration.ftoTopicCount; | |
fastModel.value = configuration.fastModel; | |
// on doit d'abord recup les modeles avant de set la valeur | |
populateLLMModelSelect('settings-reasoning-model', configuration.providerUrl, configuration.providerToken).then(() => { | |
reasoningModel.value = configuration.reasoningModel; | |
}).catch(e => { | |
alert("Failed to set LLM model for reasoning in the model selector. Model may not be available anymore, check LLM settings."); | |
}) | |
populateLLMModelSelect('settings-fast-model', configuration.providerUrl, configuration.providerToken).then(() => { | |
fastModel.value = configuration.fastModel; | |
}).catch(e => { | |
alert("Failed to set fast LLM model in the model selector. Model may not be available anymore, check LLM settings."); | |
}) | |
} | |
/** | |
* Sauvegarde le contenu des champs dans la configuration locale. | |
*/ | |
export function handleSaveConfigFields() { | |
saveConfig(getConfigFields()); | |
alert("Configuration saved locally."); | |
} | |
/** | |
* Clear le contenu de la configuration stockée dans localStorage. | |
* LA CONFIGURATION DANS LES CHAMPS HTML N'EST PAS AFFECTEE. | |
*/ | |
export function handleClearConfig() { | |
clearConfig(); | |
alert("Saved configuration has been cleared. Configuration set in the fields won't be saved."); | |
} | |
// ================================================================================ Solution drafting using private LLMs ========================================================== | |
/** History of previously created drafts | |
* The draftHistory will look like this: | |
* { | |
* solution: {} - the solution object | |
* insights: [ | |
* { id: 'i1', text: 'Some insight text', checked: false }, | |
* { id: 'i2', text: 'Another insight', checked: true } | |
* ], | |
* assessment_full: The full assessment text | |
* } | |
*/ | |
let draftHistory = []; | |
// Index of the latest draft in the draft history. | |
// -1 means theres no draft. | |
let draftCurrentIndex = -1; | |
/** | |
* Passe une solution bootstrappée en draft pour être itérée sur le private compute | |
* @param {Object} solution - Un objet qui représente une solution bootstrappée (SolutionModel). | |
*/ | |
export function moveSolutionToDrafts(solution) { | |
const draft_tab_item = document.getElementById('draft-tab'); | |
if (draft_tab_item.classList.contains("hidden")) // un-hide the draft tab the first time a solution is drafted | |
draft_tab_item.classList.remove("hidden"); | |
switchTab('draft-tab'); | |
const { providerUrl, providerToken, reasoningModel, fastModel, assessmentRules, businessPortfolio } = getConfigFields(); | |
showLoadingOverlay("Assessing solution ...."); | |
assessSolution(providerUrl, reasoningModel, fastModel, providerToken, solution, assessmentRules, businessPortfolio).then(response => { | |
// reset the state of the draft history | |
draftHistory = []; | |
draftCurrentIndex = -1; | |
// map from a list of insights to a selectable list of insights | |
const insights = response.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false })); | |
// push the solution to the draft history | |
draftHistory.push({ | |
type: "draft", | |
solution: solution, | |
insights: insights, | |
assessment_full: response.assessment_full, | |
final_verdict: response.extracted_info.final_verdict, | |
assessment_summary: response.extracted_info.summary, | |
}); | |
draftCurrentIndex++; | |
// update the UI by rendering it | |
renderDraftUI(); | |
}).catch(e => { | |
alert(e); | |
}).finally(() => { | |
hideLoadingOverlay(); | |
}) | |
} | |
/** | |
* Renders the timeline UI based on the current state | |
* @param {Number} currentIndex - Current index for latest draft | |
* @param {Array} drafts - Current history of previous drafts | |
*/ | |
function renderDraftTimeline(timelineContainer, currentIndex, drafts) { | |
timelineContainer.innerHTML = ''; | |
drafts.forEach((state, idx) => { | |
const li = document.createElement('li'); | |
li.className = `step ${idx <= currentIndex ? 'step-primary' : ''}`; | |
li.innerHTML = `<span class="step-icon">${state.type == "draft" ? "📝" : `🔎`}</span>${state.type == "draft" ? `Draft #${idx + 1}` : "FTO analysis "}` | |
// li.textContent = ; | |
// li.setAttribute('data-content', state.type == "draft" ? "D" : "F") | |
li.onclick = () => jumpToDraft(idx); | |
timelineContainer.appendChild(li); | |
}); | |
} | |
/** | |
* Renders the entire UI based on the current state (draftHistory[currentIndex]). | |
*/ | |
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 = ''; | |
// 1. Render the different solution sections | |
for (let child of solutionSections) | |
solutionDisplay.appendChild(child); | |
// 2. render final verdict and the quick summary | |
const finalVerdictTextEl = document.getElementById('assessment-recommendation-status'); | |
// maps final verdict to text color | |
const verdict_colors = { | |
NO_GO: "text-red-600", | |
CONDITIONAL_GO: "text-orange-600", | |
IMMEDIATE_GO: "text-green-600" | |
}; | |
// reset color of the text | |
Object.values(verdict_colors).forEach(v => { | |
finalVerdictTextEl.classList.remove(v); | |
}); | |
finalVerdictTextEl.innerText = currentState.final_verdict; | |
finalVerdictTextEl.classList.add(verdict_colors[currentState.final_verdict.replace("-", "_")]); | |
document.getElementById('assessment-recommendation-summary').innerText = currentState.assessment_summary; | |
// 2. Render Insights Checkboxes | |
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> | |
`; | |
// Add event listener to update state on check/uncheck | |
insightEl.querySelector('input').addEventListener('change', (e) => { | |
insight.checked = e.target.checked; | |
}); | |
insightsContainer.appendChild(insightEl); | |
}); | |
// Render the timeline with the fetched timeline container | |
renderDraftTimeline(timelineContainer, draftCurrentIndex, draftHistory); | |
console.log(draftHistory); | |
console.log(draftCurrentIndex); | |
} | |
/** | |
* Handles the "Refine" button click. | |
*/ | |
export function handleDraftRefine() { | |
// Fetch DOM elements here | |
const refineBtn = document.getElementById('refine-btn'); | |
const userInsightsText = document.getElementById('user-insight-text').value; | |
const currentState = draftHistory[draftCurrentIndex]; | |
// Get selected insights text from the current state | |
const selectedInsights = currentState.insights | |
.filter(i => i.checked) | |
.map(i => i.text); | |
if (selectedInsights.length === 0 && (userInsightsText === null || userInsightsText === "")) { | |
alert('Please select at least one insight to refine the solution or provide a manual user insight.'); | |
return; | |
} | |
// If we are not at the end of the timeline, chop off the future states. | |
if (draftCurrentIndex < draftHistory.length - 1) { | |
draftHistory = draftHistory.slice(0, draftCurrentIndex + 1); | |
} | |
// --- | |
const { providerUrl, providerToken, reasoningModel, fastModel, assessmentRules, businessPortfolio } = getConfigFields(); | |
showLoadingOverlay('Refining and assessing ....') | |
refineSolution(providerUrl, reasoningModel, providerToken, currentState.solution, selectedInsights, userInsightsText, assessmentRules, businessPortfolio) | |
.then(newSolution => { | |
const refinedSolution = newSolution; | |
return assessSolution(providerUrl, reasoningModel, fastModel, providerToken, newSolution, assessmentRules, businessPortfolio) | |
.then(assessedResult => { | |
return { refinedSolution, assessedResult }; | |
}); | |
}) | |
.then(result => { | |
// map from a list of insights to a selectable list of insights | |
const newInsights = result.assessedResult.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false })); | |
draftHistory.push({ | |
type: "draft", | |
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 => { | |
// Handle any errors | |
alert("An error occurred while refining a draft:" + error); | |
}).finally(() => { | |
hideLoadingOverlay(); | |
}); | |
} | |
/** | |
* Jumps to a specific state in the draftHistory timeline. | |
*/ | |
function jumpToDraft(index) { | |
if (index >= 0 && index < draftHistory.length) { | |
draftCurrentIndex = index; | |
renderDraftUI(); | |
} | |
} | |
export function handleFTOAnalysis() { | |
const { providerUrl, providerToken, reasoningModel, fastModel, businessPortfolio, ftoTopicCount } = getConfigFields(); | |
const currentState = draftHistory[draftCurrentIndex]; | |
console.log("Launching FTO analysis"); | |
showLoadingOverlay("Running FTO analysis... This may take a while"); | |
runFTOAnalysis(providerUrl, reasoningModel, fastModel, providerToken, currentState.solution, businessPortfolio, ftoTopicCount) | |
.then(result => { | |
// map from a list of insights to a selectable list of insights | |
const newInsights = result.extracted_info.insights.map((e, idx, __) => ({ id: idx, text: e, checked: false })); | |
// maps the original fto report assessment + the actual contents for displaying | |
const full_assessment_content = `${result.assessment_full}\n\n---\n---\n\n# FTO report contents\n\n${result.fto_report}`; | |
draftHistory.push({ | |
type: "fto", | |
solution: currentState.solution, | |
insights: newInsights, | |
assessment_full: full_assessment_content, | |
final_verdict: result.extracted_info.final_verdict, | |
assessment_summary: result.extracted_info.summary, | |
}); | |
draftCurrentIndex++; | |
renderDraftUI(); | |
}) | |
.catch(e => alert(e)) | |
.finally(() => hideLoadingOverlay()); | |
} | |
/** | |
* Displays the whole idea evaluation. | |
*/ | |
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(); | |
} | |
/** | |
* Exports asynchronously all drafts in the timeline | |
*/ | |
export async function handleExportDrafts() { | |
if (draftHistory.length === 0) { | |
alert("No drafts to export!"); | |
return; | |
} | |
console.log("Starting ZIP export..."); | |
const zip = new JSZip(); | |
const separator = '-----------------------------------------'; | |
// Loop through each draft in the history | |
draftHistory.forEach((draft, index) => { | |
const fileContent = `## Problem Description\n\n${draft.solution.problem_description}\n\n## Solution\n\n${draft.solution.solution_description}\n\n${separator}\n\n## Assessment \n\n${draft.assessment_full}`; | |
// Define a unique filename for each draft | |
const fileName = `${draft.type}_${index + 1}.txt`; | |
zip.file(fileName, fileContent); | |
}); | |
// 5. Generate the complete zip file as a "blob" | |
// This is an asynchronous operation, so we use .then() or await | |
try { | |
const content = await zip.generateAsync({ type: "blob" }); | |
// 6. Trigger the download in the browser | |
// Create a temporary link element | |
const link = document.createElement('a'); | |
link.href = URL.createObjectURL(content); | |
link.download = "drafts_export.zip"; // The name of the downloaded zip file | |
// Append to the document, click, and then remove | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
console.log("ZIP file generated and download triggered."); | |
} catch (error) { | |
console.error("Error exporting drafts to zip file:", error); | |
} | |
} |