// ─── shared.js ─── // Centralized logic for Navbar, API calls, Toast notifications, and SHAP rendering // 1. Navbar Injection const navHTML = `
`; 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.
`; container.innerHTML = html; }