llm-semantic-visualizer / src /core /ExportController.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
5.57 kB
import { STATE, getMaxDimensions } from './State.js';
/** Builds and downloads a dimension analysis markdown export for LLM ingestion. */
export function exportDimensionAnalysis(dimIndex, customSource = null) {
const data = customSource || (STATE.reliefPinned ? STATE.reliefPinned.source : null) || STATE.reliefData || (STATE.reliefDocs && STATE.reliefDocs[0]);
if (!data || !data.matrix || data.matrix.length === 0) {
console.warn('[Relieve] No hay matriz cargada para exportar.');
return;
}
const isSAE = STATE.featureSpace === 'SAE';
const dims = getMaxDimensions();
if (dimIndex < 0 || dimIndex >= dims) {
console.warn(`[Relieve] Índice de dimensión ${dimIndex} fuera de rango (0-${dims - 1}).`);
return;
}
const rows = data.matrix.map((row, chunkIndex) => ({
chunkIndex,
value: typeof row[dimIndex] === 'number' && !Number.isNaN(row[dimIndex]) ? row[dimIndex] : 0,
text: (data.text_metadata && data.text_metadata[chunkIndex] ? data.text_metadata[chunkIndex] : '').trim(),
}));
const sorted = [...rows].sort((a, b) => b.value - a.value);
const values = rows.map(r => r.value);
const maxV = Math.max(...values);
const minV = Math.min(...values);
const meanV = values.reduce((s, v) => s + v, 0) / (values.length || 1);
const fmt = (v) => (v >= 0 ? '+' : '') + v.toFixed(4);
const docName = data.filename || 'documento';
const stamp = new Date().toISOString();
const spaceLabel = isSAE ? 'SAE (Sparse Autoencoder 8,192 features)' : `RAW Embedding (${dims}D base)`;
const lines = [];
lines.push(`# Análisis de Dimensión #${dimIndex}${docName}`);
lines.push('');
lines.push('## 🤖 Instrucciones para el modelo (Gemini / ChatGPT / LLM)');
lines.push('');
if (isSAE) {
lines.push(`Eres un experto en interpretabilidad mecanicista de redes neuronales. Abajo están los fragmentos (chunks) del documento "${docName}" ordenados por la activación de la **característica latente SAE #${dimIndex}** (Sparse Autoencoder de 8,192 características).`);
} else {
lines.push(`Eres un analista de representaciones semánticas en modelos de embeddings. Abajo están los fragmentos (chunks) del documento "${docName}" ordenados por la activación de la **dimensión #${dimIndex}** (espacio denso base).`);
}
lines.push('Los fragmentos están ordenados de mayor a menor activación: los primeros (valores positivos altos) corresponden al concepto que MÁS estimula esta dimensión; los últimos (valores negativos) corresponden al polo opuesto.');
lines.push('');
lines.push('### Tu tarea de análisis:');
lines.push(`1. **Concepto Principal**: Determina qué concepto, tema, patrón gramatical o entidad semántica específica captura la dimensión #${dimIndex}.`);
lines.push('2. **Comparación de Polos**: Contrasta los fragmentos con mayor activación positiva con los de menor o negativa activación.');
lines.push('3. **Etiqueta Sugerida**: Propón un nombre corto en MAYÚSCULAS (ej. "SEGURIDAD_VIAL", "TÉRMINOS_LEGALES") y una descripción resumida en 1-2 frases.');
lines.push('');
lines.push('## 📊 Metadatos');
lines.push('');
lines.push(`- **Documento**: ${docName}`);
lines.push(`- **Espacio de Características**: ${spaceLabel}`);
lines.push(`- **Dimensión / Feature ID**: #${dimIndex} (de ${dims})`);
lines.push(`- **Total Fragmentos**: ${rows.length}`);
lines.push(`- **Estadísticas de Activación**: Máx: ${fmt(maxV)} · Mín: ${fmt(minV)} · Media: ${fmt(meanV)}`);
lines.push(`- **Fecha de Exportación**: ${stamp}`);
lines.push('');
lines.push('## 📝 Fragmentos Ordenados por Activación');
lines.push('');
for (const r of sorted) {
lines.push(`### Chunk ${r.chunkIndex} · activación: \`${fmt(r.value)}\``);
lines.push('');
lines.push(r.text ? `> ${r.text.replace(/\n/g, '\n> ')}` : '*(fragmento sin texto)*');
lines.push('');
}
const content = lines.join('\n');
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(content).catch(() => {});
}
const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const safeDoc = docName.replace(/[^\w.-]+/g, '_').slice(0, 40);
a.href = url;
a.download = `dimension_${dimIndex}_${safeDoc}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 1000);
const exportBtn = document.getElementById('relief-hud-export');
if (exportBtn) {
const originalText = exportBtn.innerHTML;
exportBtn.innerHTML = '✅ ¡Copiado al portapapeles y descargado!';
exportBtn.style.background = 'var(--color-semantic-success)';
exportBtn.style.color = 'var(--color-neutral-950)';
setTimeout(() => {
exportBtn.innerHTML = originalText;
exportBtn.style.background = 'var(--color-brand-secondary)';
exportBtn.style.color = 'var(--color-neutral-950)';
}, 2500);
}
}
/** Wires the relief HUD export button to exportDimensionAnalysis. */
export function wireReliefExportButton() {
const exportBtn = document.getElementById('relief-hud-export');
if (!exportBtn) return;
exportBtn.addEventListener('click', (e) => {
e.stopPropagation();
const dim = STATE.reliefPinned
? STATE.reliefPinned.dimIndex
: parseInt(exportBtn.dataset.dim || '-1', 10);
const source = STATE.reliefPinned ? STATE.reliefPinned.source : null;
if (dim >= 0) exportDimensionAnalysis(dim, source);
});
}