letxinet / assets /custom.js
C2MV's picture
Initial upload for Build Small Hackathon
68fb5e2 verified
Raw
History Blame Contribute Delete
24.6 kB
// ─── Theme Management ───
let currentTheme = 'dark';
function toggleTheme() {
currentTheme = currentTheme === 'dark' ? 'light' : 'dark';
const html = document.documentElement;
// Set theme attribute — CSS variables handle the rest
html.setAttribute('data-theme', currentTheme);
if (currentTheme === 'light') {
document.body.classList.remove('dark');
} else {
document.body.classList.add('dark');
}
// Override Gradio internal CSS variables
const root = document.documentElement;
const isLight = currentTheme === 'light';
const vars = isLight ? {
'--body-background-fill': '#f8fafc',
'--block-background-fill': '#ffffff',
'--block-border-color': '#e2e8f0',
'--block-label-text-color': '#475569',
'--block-title-text-color': '#0f172a',
'--input-background-fill': '#f1f5f9',
'--input-border-color': '#cbd5e1',
'--body-text-color': '#0f172a',
'--neutral-100': '#f1f5f9',
'--neutral-200': '#e2e8f0',
'--neutral-300': '#cbd5e1',
'--neutral-400': '#94a3b8',
'--neutral-500': '#64748b',
'--neutral-600': '#475569',
'--neutral-700': '#334155',
'--neutral-800': '#1e293b',
'--neutral-900': '#0f172a',
} : {
'--body-background-fill': '#0a0a0c',
'--block-background-fill': '#111827',
'--block-border-color': '#374151',
'--block-label-text-color': '#9ca3af',
'--block-title-text-color': '#ffffff',
'--input-background-fill': '#1f2937',
'--input-border-color': '#374151',
'--body-text-color': '#ffffff',
'--neutral-100': '#1f2937',
'--neutral-200': '#374151',
'--neutral-300': '#4b5563',
'--neutral-400': '#6b7280',
'--neutral-500': '#9ca3af',
'--neutral-600': '#d1d5db',
'--neutral-700': '#e5e7eb',
'--neutral-800': '#f3f4f6',
'--neutral-900': '#ffffff',
};
// Apply Gradio vars to all containers
document.querySelectorAll('.gradio-container').forEach(el => {
Object.entries(vars).forEach(([k, v]) => el.style.setProperty(k, v));
el.style.background = isLight ? '#f8fafc' : '#0a0a0c';
el.style.color = isLight ? '#0f172a' : '#ffffff';
});
// Force Gradio block/form/panel backgrounds
const bgColor = isLight ? '#ffffff' : '#111827';
const borderColor = isLight ? '#e2e8f0' : '#374151';
const textColor = isLight ? '#0f172a' : '#ffffff';
const subTextColor = isLight ? '#475569' : '#9ca3af';
const inputBg = isLight ? '#f1f5f9' : '#1f2937';
document.querySelectorAll('.block, .form, .panel').forEach(el => {
el.style.backgroundColor = bgColor;
el.style.borderColor = borderColor;
});
document.querySelectorAll('input, textarea, select').forEach(el => {
if (!el.closest('.glass-input-wrapper')) {
el.style.backgroundColor = inputBg;
el.style.borderColor = borderColor;
}
el.style.color = textColor;
});
document.querySelectorAll('label, .label-wrap, .block-label').forEach(el => {
if (!el.closest('.header-banner') && !el.closest('.glass-input-wrapper')) {
el.style.color = subTextColor;
}
});
// Update toggle button icon
const btn = document.getElementById('theme-toggle');
if (btn) {
btn.innerHTML = currentTheme === 'dark' ? '☀️' : '🌙';
btn.title = currentTheme === 'dark' ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro';
}
// Save preference
try { localStorage.setItem('letxipu-theme', currentTheme); } catch(e) {}
}
// Initialize theme on load (respect saved preference)
document.addEventListener('DOMContentLoaded', function() {
try {
var saved = localStorage.getItem('letxipu-theme');
if (saved === 'light') {
currentTheme = 'dark'; // will be toggled to light
toggleTheme();
return;
}
} catch(e) {}
document.body.classList.add('dark');
document.documentElement.setAttribute('data-theme', 'dark');
});
window._copyCitation = function(btn, citeText) {
navigator.clipboard.writeText(citeText).then(function() {
var originalHtml = btn.innerHTML;
btn.innerHTML = '✅ Copiado';
btn.style.color = '#10b981';
btn.style.borderColor = 'rgba(16,185,129,0.3)';
setTimeout(function() {
btn.innerHTML = originalHtml;
btn.style.color = 'var(--foreground, #fff)';
btn.style.borderColor = 'var(--border, rgba(255,255,255,0.1))';
}, 2000);
});
};
// ─── Citation Floating Card (Global) ───
window.showCiteCard = function(el) {
var b64 = el.getAttribute('data-cite-b64');
var raw = el.getAttribute('data-cite');
if (!b64 && !raw) return;
var data;
try {
if (b64) {
var decoded = decodeURIComponent(escape(atob(b64)));
data = JSON.parse(decoded);
} else {
data = JSON.parse(raw.replace(/"/g,'"').replace(/'/g,"'"));
}
} catch(e) { console.error('CiteCard parse error', e); return; }
var card = document.getElementById('cite-card-global');
var overlay = document.getElementById('cite-card-overlay-global');
if (!card) {
overlay = document.createElement('div');
overlay.id = 'cite-card-overlay-global';
overlay.style.cssText = 'display:none;position:fixed;top:0;left:0;width:100%;height:100%;z-index:999998;background:transparent;';
overlay.addEventListener('click', function(){ window.closeCiteCard(); });
document.body.appendChild(overlay);
card = document.createElement('div');
card.id = 'cite-card-global';
card.style.cssText = 'display:none;position:fixed;z-index:999999;width:340px;max-width:90vw;background:var(--popup-bg, rgba(17,24,39,0.95));backdrop-filter:blur(10px);border:1px solid var(--popup-border, rgba(139,92,246,0.3));border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,0.5),0 0 30px rgba(139,92,246,0.1);font-family:Inter,sans-serif;transition:opacity 0.2s ease,transform 0.2s ease;opacity:0;transform:translateY(8px);color:var(--foreground, #fff);display:flex;flex-direction:column;';
card.innerHTML = '<div id="cite-card-content-global" style="display:flex;flex-direction:column;height:100%;"></div>';
document.body.appendChild(card);
// Setup dragging
var isDragging = false;
var dragOffset = {x: 0, y: 0};
document.addEventListener('mousemove', function(e) {
if (isDragging && card) {
card.style.left = (e.clientX - dragOffset.x) + 'px';
card.style.top = (e.clientY - dragOffset.y) + 'px';
card.style.transform = 'none'; // Clear animation transform
}
});
document.addEventListener('mouseup', function(e) {
isDragging = false;
});
window._startCardDrag = function(e) {
isDragging = true;
var rect = card.getBoundingClientRect();
dragOffset.x = e.clientX - rect.left;
dragOffset.y = e.clientY - rect.top;
e.preventDefault();
};
}
overlay = document.getElementById('cite-card-overlay-global');
var content = document.getElementById('cite-card-content-global');
var doi = data.DOI || data.doi || '';
var pdfUrl = data.pdf_url || data.PDF || '';
var title = data['Título'] || data.title || 'Sin título';
var authors = data['Autores'] || data.authors || 'Autor desconocido';
var year = data['Año'] || data.year || '?';
var source = data['Fuente'] || data.source || 'Desconocido';
var abstract = data['Abstract'] || data.abstract || '';
// Icon SVGs
var iconMove = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 9 2 12 5 15"></polyline><polyline points="9 5 12 2 15 5"></polyline><polyline points="19 9 22 12 19 15"></polyline><polyline points="9 19 12 22 15 19"></polyline><line x1="2" y1="12" x2="22" y2="12"></line><line x1="12" y1="2" x2="12" y2="22"></line></svg>';
var iconX = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
var iconMsg = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>';
var iconDl = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>';
var iconQuote = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"></path><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"></path></svg>';
var iconLang = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 8l6 6"></path><path d="M4 14l6-6 2-3"></path><path d="M2 5h12"></path><path d="M7 2h1"></path><path d="M22 22l-5-10-5 10"></path><path d="M14 18h6"></path></svg>';
var iconBranch = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="transform: rotate(90deg);"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg>';
var iconExt = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>';
var iconSearch = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
// Header
var headerHtml =
'<div onmousedown="window._startCardDrag(event)" style="padding:12px;border-bottom:1px solid rgba(255,255,255,0.1);display:flex;justify-content:space-between;align-items:center;cursor:grab;user-select:none;background:rgba(255,255,255,0.02);border-top-left-radius:12px;border-top-right-radius:12px;">' +
'<div style="display:flex;align-items:center;gap:6px;color:var(--accent, #8b5cf6);">' +
iconMove +
'<span style="font-size:11px;font-weight:700;letter-spacing:0.5px;">CITAR</span>' +
'</div>' +
'<button class="close-cite-btn" style="background:transparent;border:none;color:#9ca3af;cursor:pointer;display:flex;">' +
iconX +
'</button>' +
'</div>';
var authorStr = Array.isArray(authors) ? authors.join(', ') : authors;
var firstAuthor = Array.isArray(authors) ? authors[0] : authors.split(",")[0];
// Compute source URL and button styling
var sourceUrl = data.url || data.URL || '';
if (!sourceUrl && doi) sourceUrl = 'https://doi.org/' + doi;
if (!sourceUrl) sourceUrl = '#';
var sourceBtnText = 'Ver fuente';
var sourceBtnColor = '#a78bfa';
var sourceBtnBg = 'rgba(139,92,246,0.1)';
var sourceBtnBorder = 'rgba(139,92,246,0.3)';
var sourceBtnIcon = iconExt;
if (source.toLowerCase().includes('pubmed')) {
sourceBtnText = 'PubMed'; sourceBtnColor = '#22c55e'; sourceBtnBg = 'rgba(34,197,94,0.1)'; sourceBtnBorder = 'rgba(34,197,94,0.3)'; sourceBtnIcon = iconSearch;
} else if (source.toLowerCase().includes('semantic')) {
sourceBtnText = 'Semantic Scholar'; sourceBtnColor = '#3b82f6'; sourceBtnBg = 'rgba(59,130,246,0.1)'; sourceBtnBorder = 'rgba(59,130,246,0.3)'; sourceBtnIcon = iconSearch;
} else if (source.toLowerCase().includes('crossref')) {
sourceBtnText = 'Crossref'; sourceBtnColor = '#f59e0b'; sourceBtnBg = 'rgba(245,158,11,0.1)'; sourceBtnBorder = 'rgba(245,158,11,0.3)'; sourceBtnIcon = iconSearch;
} else if (source.toLowerCase().includes('openalex')) {
sourceBtnText = 'OpenAlex'; sourceBtnColor = '#ec4899'; sourceBtnBg = 'rgba(236,72,153,0.1)'; sourceBtnBorder = 'rgba(236,72,153,0.3)'; sourceBtnIcon = iconSearch;
} else if (doi) {
sourceBtnText = 'DOI'; sourceBtnColor = '#06b6d4'; sourceBtnBg = 'rgba(6,182,212,0.1)'; sourceBtnBorder = 'rgba(6,182,212,0.3)'; sourceBtnIcon = iconExt;
}
var citeString = firstAuthor + " et al. (" + year + "). " + title + ". " + sourceUrl;
var escapedCiteString = citeString.replace(/'/g, "\\'").replace(/"/g, "&quot;").replace(/\n/g, " ").replace(/\r/g, "");
// Buttons
var actionBtnsHtml =
'<div style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap;">' +
'<button onclick="alert(\'Función Chat IA se implementará nativamente pronto.\')" class="paper-action-btn" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:6px;padding:6px;cursor:pointer;font-size:11px;font-weight:600;background:var(--popup-god-bg, rgba(139,92,246,0.15));border:1px solid var(--popup-god-border, rgba(139,92,246,0.3));color:var(--popup-god-color, #c084fc);">' +
iconMsg + ' Chat IA' +
'</button>' +
(pdfUrl ?
'<a href="'+pdfUrl+'" target="_blank" class="paper-action-btn" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:6px;padding:6px;cursor:pointer;font-size:11px;font-weight:600;background:var(--input-bg, rgba(255,255,255,0.05));border:1px solid var(--border, rgba(255,255,255,0.1));color:var(--foreground, #fff);text-decoration:none;">' +
iconDl + ' Descargar' +
'</a>' : '') +
'<button onclick="window._copyCitation(this, \'' + escapedCiteString + '\')" class="paper-action-btn" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:6px;padding:6px;cursor:pointer;font-size:11px;font-weight:600;background:var(--input-bg, rgba(255,255,255,0.05));border:1px solid var(--border, rgba(255,255,255,0.1));color:var(--foreground, #fff);">' +
iconQuote + ' Citar' +
'</button>' +
'<button onclick="alert(\'Traducción en la vista web disponible próximamente.\')" class="paper-action-btn" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:6px;padding:6px;cursor:pointer;font-size:11px;font-weight:600;background:var(--input-bg, rgba(255,255,255,0.05));border:1px solid var(--border, rgba(255,255,255,0.1));color:var(--foreground, #fff);">' +
iconLang + ' Traducir' +
'</button>' +
'<button onclick="alert(\'Añadido al flujo (simulación).\')" class="paper-action-btn" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:6px;padding:6px;cursor:pointer;font-size:11px;font-weight:600;background:rgba(249,115,22,0.15);border:1px solid rgba(249,115,22,0.3);color:var(--warning, #f59e0b);">' +
iconBranch + ' Flujo' +
'</button>' +
'</div>';
// Abstract box
var abstractHtml = '';
if (abstract) {
abstractHtml =
'<div class="custom-scrollbar" style="max-height:120px;overflow-y:auto;font-size:11px;line-height:1.5;color:var(--foreground, #fff);margin-bottom:16px;padding:8px;background:var(--input-bg, rgba(0,0,0,0.2));border-radius:6px;text-align:justify;">' +
abstract +
'</div>';
}
var bodyHtml =
'<div style="padding:16px;">' +
'<div style="font-size:14px;font-weight:700;margin-bottom:8px;line-height:1.4;color:white;word-break:break-word;white-space:normal;">' + title + '</div>' +
actionBtnsHtml +
'<div style="font-size:12px;color:#9ca3af;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid rgba(255,255,255,0.08);word-break:break-word;white-space:normal;">' +
authorStr + ' <span style="margin:0 6px;color:rgba(255,255,255,0.2);">|</span> ' + year +
'</div>' +
abstractHtml +
'<div style="display:flex;gap:8px;">' +
'<a href="'+sourceUrl+'" target="_blank" class="paper-action-btn" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;font-size:11px;font-weight:600;color:'+sourceBtnColor+';background:'+sourceBtnBg+';text-decoration:none;padding:8px;border-radius:6px;border:1px solid '+sourceBtnBorder+';">' +
sourceBtnIcon + sourceBtnText +
'</a>' +
'</div>' +
'</div>';
content.innerHTML = headerHtml + bodyHtml;
// Centering Logic
var rect = el.getBoundingClientRect();
var cardW = 340;
var left = rect.left + rect.width/2 - cardW/2;
if (left < 10) left = 10;
if (left + cardW > window.innerWidth - 10) left = window.innerWidth - cardW - 10;
// Position slightly offset from cursor or element
var top = rect.bottom + 10;
if (top + 400 > window.innerHeight) {
top = window.innerHeight - 410;
if (top < 10) top = 10;
}
card.style.left = left + 'px';
card.style.top = top + 'px';
overlay.style.display = 'block';
card.style.display = 'flex';
setTimeout(function(){ card.style.opacity='1'; card.style.transform='translateY(0)'; }, 10);
};
window.closeCiteCard = function() {
var card = document.getElementById('cite-card-global');
var overlay = document.getElementById('cite-card-overlay-global');
if (card) {
card.style.opacity = '0';
card.style.transform = 'translateY(8px)';
setTimeout(function(){ card.style.display='none'; if(overlay) overlay.style.display='none'; }, 200);
}
};
// Event Delegation for Cite Links (Fix for Gradio 6 removing inline onclick handlers)
document.addEventListener('click', function(e) {
const citeLink = e.target.closest('.cite-link');
if (citeLink) {
e.preventDefault();
window.showCiteCard(citeLink);
return;
}
// Check for close button
if (e.target.closest('.close-cite-btn')) {
e.preventDefault();
window.closeCiteCard();
}
});
// References Pagination Logic
window.initRefsPagination = function() {
var container = document.getElementById('refs-container');
if (!container) return;
var items = Array.from(container.querySelectorAll('.ref-item'));
var filterCb = document.getElementById('refs-filter-cited');
var citedFilter = filterCb ? filterCb.checked : false;
var visibleItems = items.filter(function(item) {
if (citedFilter && item.getAttribute('data-cited') !== 'true') return false;
return true;
});
var perPage = 10;
var totalPages = Math.ceil(visibleItems.length / perPage);
var currentPage = parseInt(container.getAttribute('data-page')) || 1;
if (currentPage > totalPages && totalPages > 0) currentPage = totalPages;
if (currentPage < 1) currentPage = 1;
items.forEach(function(item) { item.style.display = 'none'; });
var start = (currentPage - 1) * perPage;
var end = start + perPage;
for (var i = start; i < end && i < visibleItems.length; i++) {
visibleItems[i].style.display = 'flex';
}
var pagContainer = document.getElementById('refs-pagination');
if (pagContainer) {
pagContainer.innerHTML = '';
if (totalPages > 1) {
var createBtn = function(text, page, disabled, active) {
var b = document.createElement('button');
b.innerHTML = text;
b.style.cssText = 'padding: 6px 14px; margin: 0 2px; border: 1px solid var(--border, #374151); background: ' + (active ? 'var(--accent, #8b5cf6)' : 'transparent') + '; color: ' + (active ? '#fff' : 'var(--foreground, #d1d5db)') + '; border-radius: 6px; cursor: ' + (disabled ? 'default' : 'pointer') + '; opacity: ' + (disabled ? '0.5' : '1') + '; font-size: 13px; font-weight: 600; transition: all 0.2s;';
if (!disabled && !active) {
b.onmouseover = function() { b.style.background = 'rgba(139,92,246,0.15)'; };
b.onmouseout = function() { b.style.background = 'transparent'; };
b.onclick = function() {
container.setAttribute('data-page', page);
window.initRefsPagination();
// Scroll to top of container smoothly
var y = container.getBoundingClientRect().top + window.scrollY - 100;
window.scrollTo({top: y, behavior: 'smooth'});
};
}
return b;
};
pagContainer.appendChild(createBtn('Anterior', currentPage - 1, currentPage === 1, false));
var startP = Math.max(1, currentPage - 2);
var endP = Math.min(totalPages, startP + 4);
if (endP - startP < 4) startP = Math.max(1, endP - 4);
for (var p = startP; p <= endP; p++) {
pagContainer.appendChild(createBtn(p, p, false, p === currentPage));
}
pagContainer.appendChild(createBtn('Siguiente', currentPage + 1, currentPage === totalPages, false));
}
}
var stats = document.getElementById('refs-stats');
if (stats) stats.innerHTML = 'Mostrando ' + (visibleItems.length > 0 ? start + 1 : 0) + ' - ' + Math.min(end, visibleItems.length) + ' de <b>' + visibleItems.length + '</b> referencias';
};
// Make Headers Collapsible
window.makeHeadersCollapsible = function() {
var container = document.getElementById('report-content');
if (!container) return;
// Prevent double processing
if (container.getAttribute('data-collapsible-processed') === 'true') return;
container.setAttribute('data-collapsible-processed', 'true');
var headers = Array.from(container.querySelectorAll('h1, h2, h3, h4, h5, h6'));
if (headers.length === 0) return;
headers.forEach(function(h) {
if (h.parentElement && h.parentElement.tagName.toLowerCase() === 'summary') return;
var details = document.createElement('details');
details.open = true; // Open by default as requested
var summary = document.createElement('summary');
summary.innerHTML = h.innerHTML;
summary.className = h.className;
h.parentNode.insertBefore(details, h);
details.appendChild(summary);
var headerLevel = parseInt(h.tagName[1]);
var next = h.nextSibling;
while (next) {
var current = next;
next = next.nextSibling;
if (current.nodeType === 1 && current.tagName.match(/^H[1-6]$/i)) {
var currentLevel = parseInt(current.tagName[1]);
if (currentLevel <= headerLevel) {
break;
}
}
details.appendChild(current);
}
h.remove();
});
};
// MathJax Loading
window.MathJax = {
tex: { inlineMath: [['$','$'], ['\\\\(','\\\\)']], displayMath: [['$$','$$'], ['\\\\[','\\\\]']] },
options: { skipHtmlTags: ['script','noscript','style','textarea','pre','code'] }
};
const script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";
script.async = true;
document.head.appendChild(script);