// ─── shared.js ───
// Centralized logic for Navbar, API calls, Toast notifications, and SHAP rendering
// 1. Navbar Injection
const navHTML = `
MedBarcenas Hemodiálisis Beta 4
`;
function injectNavbar(activeId) {
const container = document.getElementById('navbar-container');
if (container) {
container.innerHTML = navHTML;
if (activeId) {
const activeLink = document.getElementById(activeId);
if (activeLink) activeLink.classList.add('active');
}
}
}
// 2. Toast Notifications
function showToast(message, type = 'error') {
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
document.body.appendChild(toastContainer);
}
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
const icon = type === 'error' ?
'' :
'';
toast.innerHTML = `
${icon}
${message}
`;
toastContainer.appendChild(toast);
// Trigger animation
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 4000);
}
// 3. API Client
async function apiClient(endpoint, payload, loaderBtnId) {
const btn = loaderBtnId ? document.getElementById(loaderBtnId) : null;
let originalHtml = '';
if (btn) {
originalHtml = btn.innerHTML;
btn.classList.add('loading');
btn.innerHTML = `
Calculando...
`;
}
try {
// En Gradio nativo, la API vive en /gradio_api/run/nombre_api
const apiName = endpoint.replace('/api/', 'api_');
// USAMOS LA URL ABSOLUTA DEL ESPACIO GRADIO PROXY PARA PERMITIR QUE EL FRONTEND VIVA EN UN STATIC SPACE
const proxyBaseUrl = 'https://danielbrdz-medbarcenas-beta-4.hf.space';
const gradioEndpoint = `${proxyBaseUrl}/gradio_api/run/${apiName}`;
const response = await fetch(gradioEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
// Gradio exige envolver el payload en {"data": [ ... ]}
body: JSON.stringify({ data: [payload] })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const rawData = await response.json();
// Gradio devuelve la respuesta en {"data": [ ... ]}
let finalData = rawData;
if (rawData.data && Array.isArray(rawData.data) && rawData.data.length > 0) {
finalData = rawData.data[0];
}
if (finalData.error) {
throw new Error(finalData.error);
}
return finalData;
} catch (err) {
showToast('Error de conexión: ' + err.message, 'error');
throw err;
} finally {
if (btn) {
btn.classList.remove('loading');
btn.innerHTML = originalHtml;
}
}
}
// 4. SHAP Explanations Renderer
function renderSharedExplanations(explanations, containerId, customTitle, customSubtitle, customLabels, customColorUp = '#f0435a', customColorDown = '#16D391', reverseArrowDirection = false) {
const container = document.getElementById(containerId);
if (!explanations || explanations.length === 0) {
if(container) container.innerHTML = '';
return;
}
const maxShap = Math.max(...explanations.map(e => Math.abs(e.shap)));
const title = customTitle || '¿Por qué este resultado?';
const subtitle = customSubtitle || 'Factores que más influyeron en la predicción (ordenados por importancia)';
let html = `
${title}
${subtitle}
`;
explanations.forEach((exp, i) => {
let isPositive = exp.direction === 'up';
let color = isPositive ? customColorUp : customColorDown;
let arrow = isPositive ? '▲' : '▽';
if (reverseArrowDirection) {
arrow = isPositive ? '▽' : '▲';
}
let labelText = isPositive ? 'Aumenta riesgo' : 'Reduce riesgo';
if (customLabels) {
labelText = isPositive ? customLabels.up : customLabels.down;
} else {
if (exp.label) labelText = exp.label;
}
const barPct = Math.min((Math.abs(exp.shap) / maxShap) * 100, 100);
html += `
${i + 1}
${exp.feature}${arrow} ${labelText}
▾
${exp.explanation}
`;
});
html += `
Aviso Importante
Esta herramienta es únicamente un recurso de apoyo clínico. Los algoritmos presentados NO constituyen un diagnóstico médico ni reemplazan el juicio del médico tratante.