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); }); }