| /** | |
| * index.js | |
| * ======== | |
| * Primary Application Entrypoint for 3D Neural Architecture Visualizer. | |
| * Coordinates rendering, data pipelines, simulation loop, and user interactivity. | |
| */ | |
| import * as THREE from 'three'; | |
| import { VISUAL_CONFIG, MODEL_SPECS, TOUR_STEPS } from './constants.js'; | |
| import { ATTENTION_TOUR_STEPS, renderTourStepHUD } from './educational/attention_tour.js'; | |
| import { store } from './state/store.js'; | |
| import { createRenderer, setupResizeHandler } from './core/renderer.js'; | |
| import { createScene } from './core/scene.js'; | |
| import { createCameraAndControls } from './core/camera.js'; | |
| import { setupLighting } from './core/lighting.js'; | |
| import { | |
| materials, | |
| edgeMatMacro, | |
| edgeMatDense, | |
| tokenMaterialBaseline, | |
| tokenMaterialCompressed, | |
| trailMaterialBaseline, | |
| trailMaterialCompressed | |
| } from './core/materials.js'; | |
| import { loadSpatialData } from './data/spatial_loader.js'; | |
| import { loadModuleCatalog, loadModuleComponents } from './data/module_loader.js'; | |
| import { buildMeshNodes } from './builders/granular_builder.js'; | |
| import { buildMacroBlocks } from './builders/macro_builder.js'; | |
| import { createTokenMesh } from './builders/token_builder.js'; | |
| import { smoothFlyCamera, updateCameraFlight } from './interactions/camera_fly.js'; | |
| import { initSelectionLighting, selectPartIn3D, clearSelectionHighlight, animateSelectionPulse } from './interactions/selection.js'; | |
| import { setupRaycasting } from './interactions/raycasting.js'; | |
| import { updateTokens } from './physics/token_animation.js'; | |
| import { updateNodePulses } from './physics/node_pulses.js'; | |
| import { updateComparisonHUD } from './ui/hud.js'; | |
| import { updateInspectorWithNode } from './ui/inspector.js'; | |
| import { buildHierarchicalTree } from './ui/tree_explorer.js'; | |
| import { initModuleCatalog } from './ui/module_catalog.js'; | |
| import { initDocModal } from './educational/doc_modal.js'; | |
| import { togglePresentMode } from './educational/present_mode.js'; | |
| import { openLearnModal } from './educational/learn_modal.js'; | |
| import { openGuidedLessonsModal } from './educational/guided_lessons.js'; | |
| import { openModelMatrixDashboard } from './educational/model_matrix_dashboard.js'; | |
| import { openAttentionHeatmapExplorer } from './interactive/attention_heatmap.js'; | |
| import { openMatrixUnfoldingModal } from './educational/matrix_unfolding.js'; | |
| import { renderLayerDepthProfile, getLayerDepthProfile } from './educational/layer_depth_profile.js'; | |
| import { openTokenPredictionPlayground } from './interactive/token_prediction.js'; | |
| import { openCompressionSimulator } from './interactive/compression_simulator.js'; | |
| import { openModelRaceModal } from './interactive/model_race.js'; | |
| import { openVideoExportModal } from './export/video_exporter.js'; | |
| import { copyShareablePermalink, syncUrlHash, parseHashToState, applyHashStateToVisualizer } from './interactions/permalink.js'; | |
| import { setupControls, openKeyboardShortcutsModal } from './ui/controls.js'; | |
| import { perfMonitor } from './performance/monitor.js'; | |
| // Application Registry Collections | |
| const visualObjects = []; | |
| const interactableMeshes = []; | |
| const macroMeshes = []; | |
| const svdBottlenecks = []; | |
| const moeNodes = []; | |
| const layerWidthProfilesBaseline = []; | |
| const layerWidthProfilesCompressed = []; | |
| const tokenDataBaseline = []; | |
| const tokenDataCompressed = []; | |
| let tokenMeshBaseline = null; | |
| let tokenMeshCompressed = null; | |
| let trailMeshBaseline = null; | |
| let trailMeshCompressed = null; | |
| let maxGraphHeight = 16.0; | |
| let activeSelectedSublayerKey = null; | |
| // Tour state | |
| let tourActive = false; | |
| let tourStep = 0; | |
| let tourTimer = null; | |
| /** | |
| * Main application initialization. | |
| */ | |
| export async function bootstrapApp() { | |
| const canvas = document.getElementById('webgl-canvas'); | |
| if (!canvas) return; | |
| // 1. Initialize Core Three.js Subsystems | |
| const renderer = createRenderer(canvas); | |
| const { scene, granularGroup, macroGroup, dimensionBadgesGroup } = createScene(); | |
| const { camera, controls } = createCameraAndControls(canvas); | |
| setupLighting(scene); | |
| setupResizeHandler(renderer, camera); | |
| initSelectionLighting(scene); | |
| // 2. Load Datasets | |
| let spatialData = null; | |
| let catalogModules = []; | |
| let modulesComponents = null; | |
| try { | |
| [spatialData, catalogModules, modulesComponents] = await Promise.all([ | |
| loadSpatialData(), | |
| loadModuleCatalog(), | |
| loadModuleComponents() | |
| ]); | |
| store.set({ spatialData, modulesCatalog: catalogModules, modulesComponents }); | |
| } catch (err) { | |
| console.error('[bootstrapApp] Dataset initialization error:', err); | |
| } | |
| // 3. Clear and Rebuild 3D Scene for Active Model | |
| function rebuildSceneForModel(modelKey, moduleKey = 'svd') { | |
| clearSelectionHighlight(); | |
| // Dispose previous visual objects | |
| for (const obj of visualObjects) { | |
| if (obj.parent) obj.parent.remove(obj); | |
| if (obj.geometry) obj.geometry.dispose(); | |
| if (obj.material && !Object.values(materials).includes(obj.material)) { | |
| if (obj.material.map) obj.material.map.dispose(); | |
| obj.material.dispose(); | |
| } | |
| } | |
| visualObjects.length = 0; | |
| interactableMeshes.length = 0; | |
| macroMeshes.length = 0; | |
| svdBottlenecks.length = 0; | |
| moeNodes.length = 0; | |
| layerWidthProfilesBaseline.length = 0; | |
| layerWidthProfilesCompressed.length = 0; | |
| granularGroup.clear(); | |
| macroGroup.clear(); | |
| dimensionBadgesGroup.clear(); | |
| tokenDataBaseline.length = 0; | |
| tokenDataCompressed.length = 0; | |
| if (!spatialData || !spatialData.models[modelKey]) return; | |
| const modelData = spatialData.models[modelKey]; | |
| maxGraphHeight = modelData.max_y || 16.0; | |
| window.maxGraphHeight = maxGraphHeight; | |
| const contextBase = { | |
| granularGroup, | |
| dimensionBadgesGroup, | |
| visualObjects, | |
| interactableMeshes, | |
| svdBottlenecks, | |
| moeNodes, | |
| layerWidthProfiles: layerWidthProfilesBaseline | |
| }; | |
| const contextComp = { | |
| granularGroup, | |
| dimensionBadgesGroup, | |
| visualObjects, | |
| interactableMeshes, | |
| svdBottlenecks, | |
| moeNodes, | |
| layerWidthProfiles: layerWidthProfilesCompressed | |
| }; | |
| // Build Granular Nodes | |
| buildMeshNodes(modelData.baseline_nodes, VISUAL_CONFIG.LAYOUT.baselineX, false, contextBase); | |
| const compNodes = (modelData.modules && modelData.modules[moduleKey] && modelData.modules[moduleKey].compressed_nodes) || modelData.compressed_nodes; | |
| buildMeshNodes(compNodes, VISUAL_CONFIG.LAYOUT.compressedX, true, contextComp); | |
| // Build Macro Blocks (LOD 0) | |
| if (modelData.baseline_macro_nodes) { | |
| buildMacroBlocks(modelData.baseline_macro_nodes, VISUAL_CONFIG.LAYOUT.baselineX, false, macroGroup, visualObjects, macroMeshes, interactableMeshes); | |
| } | |
| const compMacro = (modelData.modules && modelData.modules[moduleKey] && modelData.modules[moduleKey].macro_nodes) || modelData.macro_nodes; | |
| if (compMacro) { | |
| buildMacroBlocks(compMacro, VISUAL_CONFIG.LAYOUT.compressedX, true, macroGroup, visualObjects, macroMeshes, interactableMeshes); | |
| } | |
| // Build Token Meshes | |
| const tokensBase = createTokenMesh(tokenDataBaseline, VISUAL_CONFIG.LAYOUT.baselineX, tokenMaterialBaseline, trailMaterialBaseline, maxGraphHeight, store.get('simulationSpeed')); | |
| const tokensComp = createTokenMesh(tokenDataCompressed, VISUAL_CONFIG.LAYOUT.compressedX, tokenMaterialCompressed, trailMaterialCompressed, maxGraphHeight, store.get('simulationSpeed')); | |
| tokenMeshBaseline = tokensBase.main; | |
| trailMeshBaseline = tokensBase.trail; | |
| tokenMeshCompressed = tokensComp.main; | |
| trailMeshCompressed = tokensComp.trail; | |
| granularGroup.add(tokenMeshBaseline); | |
| granularGroup.add(trailMeshBaseline); | |
| granularGroup.add(tokenMeshCompressed); | |
| granularGroup.add(trailMeshCompressed); | |
| visualObjects.push(tokenMeshBaseline, trailMeshBaseline, tokenMeshCompressed, trailMeshCompressed); | |
| // Update Tree and HUD | |
| buildHierarchicalTree(modelKey, moduleKey, modelData, (nodeId) => { | |
| window.selectPartIn3D(nodeId); | |
| }); | |
| updateComparisonHUD(modelKey, moduleKey, modelData); | |
| const numLayers = (MODEL_SPECS[modelKey] && MODEL_SPECS[modelKey].num_layers) || 6; | |
| buildLayerQuickPills(modelKey, numLayers); | |
| } | |
| // 4. Raycasting & Node Interactivity | |
| setupRaycasting(canvas, camera, interactableMeshes, (nodeData, isLeft, mesh) => { | |
| selectPartIn3D(mesh, interactableMeshes); | |
| updateInspectorWithNode(nodeData, isLeft, store.get('activeResearchModule'), store.get('currentModel')); | |
| smoothFlyCamera(camera, controls, mesh.position.y, 8.5, mesh.position.x); | |
| // Update active layer UI indicator if node belongs to a layer | |
| const lIdx = nodeData.parent_layer_index ?? nodeData.parent_layer_idx; | |
| if (lIdx !== undefined && lIdx !== null && lIdx !== 999) { | |
| updateActiveLayerIndicator(lIdx, nodeData.parent_layer_name); | |
| } | |
| }); | |
| // 5. Controls & User Events | |
| setupControls({ | |
| onPlayPause: () => { | |
| const next = !store.get('isPlaying'); | |
| store.set({ isPlaying: next }); | |
| return next; | |
| }, | |
| onSpeedChange: (speed) => { | |
| store.set({ simulationSpeed: speed }); | |
| }, | |
| onScrub: (step) => { | |
| if (window.navigateToLayer) { | |
| window.navigateToLayer(step); | |
| } else { | |
| const targetY = 1.0 + step * VISUAL_CONFIG.TOKENS.layerStride; | |
| smoothFlyCamera(camera, controls, targetY, 18.0); | |
| } | |
| }, | |
| onSwitchView: (viewMode) => { | |
| if (viewMode === '2d') { | |
| smoothFlyCamera(camera, controls, maxGraphHeight * 0.5, 32.0, 0); | |
| } else { | |
| smoothFlyCamera(camera, controls, maxGraphHeight * 0.5, 22.0, 4.0); | |
| } | |
| }, | |
| onResetCamera: () => { | |
| smoothFlyCamera(camera, controls, maxGraphHeight * 0.5, Math.max(18, maxGraphHeight * 1.35), 0); | |
| }, | |
| onSwitchModel: (modelKey) => { | |
| store.set({ currentModel: modelKey }); | |
| const curMod = store.get('activeResearchModule'); | |
| const paradigm = (curMod && curMod.paradigm) || store.get('currentCompression') || 'svd'; | |
| rebuildSceneForModel(modelKey, paradigm); | |
| }, | |
| onSwitchCompression: (compKey) => { | |
| store.set({ currentCompression: compKey }); | |
| rebuildSceneForModel(store.get('currentModel'), compKey); | |
| }, | |
| onSwitchLOD: (lod) => { | |
| if (lod === 'macro') { | |
| macroGroup.visible = true; | |
| granularGroup.visible = false; | |
| dimensionBadgesGroup.visible = false; | |
| } else if (lod === 'granular') { | |
| macroGroup.visible = false; | |
| granularGroup.visible = true; | |
| dimensionBadgesGroup.visible = true; | |
| } else { | |
| macroGroup.visible = true; | |
| granularGroup.visible = true; | |
| dimensionBadgesGroup.visible = false; | |
| } | |
| } | |
| }); | |
| // 6. Module Catalog Initialization | |
| initModuleCatalog(catalogModules, (chosenModule) => { | |
| store.set({ activeResearchModule: chosenModule }); | |
| rebuildSceneForModel(store.get('currentModel'), chosenModule.paradigm || 'svd'); | |
| const inspector = document.getElementById('inspector'); | |
| if (inspector) inspector.classList.remove('hidden'); | |
| }); | |
| // 6b. Research Paper & Markdown Document Reader Modal | |
| initDocModal((chosenModule) => { | |
| store.set({ activeResearchModule: chosenModule }); | |
| rebuildSceneForModel(store.get('currentModel'), chosenModule.paradigm || 'svd'); | |
| const inspector = document.getElementById('inspector'); | |
| if (inspector) inspector.classList.remove('hidden'); | |
| }); | |
| // 7. Mechanism Nav Bar Tab Bindings | |
| document.querySelectorAll('.mechanism-button').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const mModel = btn.dataset.model || 'distilbert'; | |
| const mModule = btn.dataset.module || 'svd'; | |
| store.set({ currentModel: mModel, currentCompression: mModule }); | |
| rebuildSceneForModel(mModel, mModule); | |
| }); | |
| }); | |
| // 8. Global Window Exports for Inline HTML Handlers | |
| window.smoothFlyCamera = (y, dist, x = 0) => smoothFlyCamera(camera, controls, y, dist, x); | |
| window.smoothZoomBy = (factor) => smoothZoomBy(camera, controls, factor); | |
| window.maxGraphHeight = maxGraphHeight; | |
| window.openKeyboardShortcutsModal = openKeyboardShortcutsModal; | |
| window.pinCurrentModule = () => { | |
| const pinBtn = document.getElementById('btn-pin-module'); | |
| if (pinBtn) pinBtn.click(); | |
| }; | |
| const zoomInBtn = document.getElementById('btn-zoom-in'); | |
| const zoomOutBtn = document.getElementById('btn-zoom-out'); | |
| if (zoomInBtn) zoomInBtn.addEventListener('click', () => smoothZoomBy(camera, controls, 0.78)); | |
| if (zoomOutBtn) zoomOutBtn.addEventListener('click', () => smoothZoomBy(camera, controls, 1.28)); | |
| const presentBtn = document.getElementById('btn-present-mode'); | |
| if (presentBtn) presentBtn.addEventListener('click', () => togglePresentMode({ camera, controls, store, rebuildSceneForModel })); | |
| const learnBtn = document.getElementById('btn-open-learn'); | |
| if (learnBtn) learnBtn.addEventListener('click', () => openLearnModal()); | |
| const lessonsBtn = document.getElementById('btn-open-lessons'); | |
| if (lessonsBtn) lessonsBtn.addEventListener('click', () => openGuidedLessonsModal({ | |
| camera, | |
| controls, | |
| smoothFlyCamera: (cam, ctrl, y, d, x) => smoothFlyCamera(cam, ctrl, y, d, x), | |
| selectPartIn3D: (t) => window.selectPartIn3D && window.selectPartIn3D(t) | |
| })); | |
| const matrixBtn = document.getElementById('btn-open-model-matrix'); | |
| if (matrixBtn) matrixBtn.addEventListener('click', () => openModelMatrixDashboard((m) => { | |
| store.set({ currentModel: m }); | |
| rebuildSceneForModel(m, (store.get('activeResearchModule') && store.get('activeResearchModule').paradigm) || 'svd'); | |
| })); | |
| const heatmapBtn = document.getElementById('btn-open-heatmap'); | |
| if (heatmapBtn) heatmapBtn.addEventListener('click', () => openAttentionHeatmapExplorer()); | |
| const unfoldingBtn = document.getElementById('btn-open-unfolding'); | |
| if (unfoldingBtn) unfoldingBtn.addEventListener('click', () => openMatrixUnfoldingModal()); | |
| const predictionBtn = document.getElementById('btn-open-prediction'); | |
| if (predictionBtn) predictionBtn.addEventListener('click', () => openTokenPredictionPlayground()); | |
| const compressionBtn = document.getElementById('btn-open-compression'); | |
| if (compressionBtn) compressionBtn.addEventListener('click', () => { | |
| const curMod = store.get('activeResearchModule'); | |
| const initRank = (curMod && curMod.rank) || 64; | |
| openCompressionSimulator({ | |
| initialRank: initRank, | |
| hiddenDim: 768, | |
| numLayers: 12, | |
| onApplyTo3D: (newRank) => { | |
| const scaleFactor = Math.max(0.25, Math.min(2.2, newRank / 64)); | |
| interactableMeshes.forEach(mesh => { | |
| if (mesh.userData && (mesh.userData.type === 'svd' || mesh.userData.sub_type === 'v_factor' || mesh.userData.sub_type === 'u_factor')) { | |
| mesh.scale.set(scaleFactor, 1.0, scaleFactor); | |
| } | |
| }); | |
| updateComparisonHUD(store.get('currentModel'), store.get('activeResearchModule') || { paradigm: 'svd', rank: newRank }); | |
| } | |
| }); | |
| }); | |
| const raceBtn = document.getElementById('btn-open-race'); | |
| if (raceBtn) raceBtn.addEventListener('click', () => openModelRaceModal()); | |
| const shareBtn = document.getElementById('btn-share'); | |
| if (shareBtn) { | |
| shareBtn.addEventListener('click', () => { | |
| const curMod = store.get('activeResearchModule'); | |
| const inspectorEl = document.getElementById('inspector'); | |
| const state = { | |
| model: store.get('currentModel'), | |
| module: curMod ? (curMod.id || curMod.name) : undefined, | |
| view: controls.enableRotate === false ? '2d' : '3d', | |
| rank: curMod && curMod.rank ? curMod.rank : undefined, | |
| inspector: inspectorEl ? !inspectorEl.classList.contains('hidden') : false | |
| }; | |
| copyShareablePermalink(state); | |
| syncUrlHash(state); | |
| }); | |
| } | |
| const videoBtn = document.getElementById('btn-record-video'); | |
| if (videoBtn) { | |
| videoBtn.addEventListener('click', () => { | |
| openVideoExportModal({ | |
| canvas: renderer.domElement, | |
| camera, | |
| controls | |
| }); | |
| }); | |
| } | |
| document.addEventListener('keydown', (e) => { | |
| if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) return; | |
| if (e.key === '+' || e.key === '=') { | |
| smoothZoomBy(camera, controls, 0.8); | |
| } else if (e.key === '-' || e.key === '_') { | |
| smoothZoomBy(camera, controls, 1.25); | |
| } else if (e.key === 'p' || e.key === 'P') { | |
| togglePresentMode({ camera, controls, store, rebuildSceneForModel }); | |
| } else if (e.key === 'l' || e.key === 'L') { | |
| openLearnModal(); | |
| } else if (e.key === 'h' || e.key === 'H') { | |
| openAttentionHeatmapExplorer(); | |
| } else if (e.key === 't' || e.key === 'T') { | |
| openTokenPredictionPlayground(); | |
| } else if (e.key === 'c' || e.key === 'C') { | |
| if (compressionBtn) compressionBtn.click(); | |
| } else if (e.key === 'v' || e.key === 'V') { | |
| if (videoBtn) videoBtn.click(); | |
| } else if (e.key === 'g' || e.key === 'G') { | |
| if (lessonsBtn) lessonsBtn.click(); | |
| } else if (e.key === 'u' || e.key === 'U') { | |
| openMatrixUnfoldingModal(); | |
| } else if (e.key >= '0' && e.key <= '9') { | |
| window.navigateToLayer(parseInt(e.key, 10)); | |
| } | |
| }); | |
| function updateActiveLayerIndicator(layerIdx, label) { | |
| // Update layer pills in bottom dock | |
| document.querySelectorAll('.layer-pill-btn').forEach(pill => { | |
| const pVal = pill.getAttribute('data-layer'); | |
| if (String(pVal) === String(layerIdx)) { | |
| pill.classList.add('active'); | |
| } else { | |
| pill.classList.remove('active'); | |
| } | |
| }); | |
| // Update timeline scrubber and labels | |
| const scrubber = document.getElementById('timeline-scrubber'); | |
| const stepLabel = document.getElementById('timeline-step'); | |
| const titleLabel = document.getElementById('timeline-label'); | |
| const numericIdx = parseInt(layerIdx, 10); | |
| if (!isNaN(numericIdx)) { | |
| if (scrubber) scrubber.value = numericIdx; | |
| if (stepLabel) stepLabel.innerText = `L${numericIdx}`; | |
| if (titleLabel) titleLabel.innerText = label || `Layer ${numericIdx}`; | |
| } else { | |
| if (stepLabel) stepLabel.innerText = '00 Global'; | |
| if (titleLabel) titleLabel.innerText = label || String(layerIdx); | |
| } | |
| // Expand and scroll to layer node in tree explorer | |
| document.querySelectorAll('.tree-layer-node').forEach(node => { | |
| const nodeIdx = parseInt(node.dataset.layerIndex, 10); | |
| if (!isNaN(numericIdx) && nodeIdx === numericIdx) { | |
| node.classList.add('open'); | |
| node.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| } | |
| }); | |
| } | |
| window.navigateToLayer = (layerIdentifier) => { | |
| if (layerIdentifier === undefined || layerIdentifier === null) return; | |
| let targetMeshes = []; | |
| const numericIdx = (typeof layerIdentifier === 'number') ? layerIdentifier : parseInt(layerIdentifier, 10); | |
| const strId = String(layerIdentifier).toLowerCase().trim(); | |
| // 1. Try matching by parent_layer_index (numeric) | |
| if (!isNaN(numericIdx) && numericIdx !== 999) { | |
| targetMeshes = interactableMeshes.filter(m => | |
| m.userData && ( | |
| m.userData.parent_layer_index === numericIdx || | |
| m.userData.parent_layer_idx === numericIdx || | |
| (m.userData.id && ( | |
| m.userData.id.includes(`.layer.${numericIdx}.`) || | |
| m.userData.id.includes(`.layers.${numericIdx}.`) || | |
| m.userData.id.includes(`.h.${numericIdx}.`) | |
| )) | |
| ) | |
| ); | |
| } | |
| // 2. Try matching by keyword (e.g. 'embeddings', 'output', 'classifier', 'pooler', 'layer 0') | |
| if (targetMeshes.length === 0) { | |
| targetMeshes = interactableMeshes.filter(m => { | |
| if (!m.userData) return false; | |
| const id = (m.userData.id || '').toLowerCase(); | |
| const label = (m.userData.label || '').toLowerCase(); | |
| const pName = (m.userData.parent_layer_name || '').toLowerCase(); | |
| return id.includes(strId) || label.includes(strId) || pName.includes(strId); | |
| }); | |
| } | |
| if (targetMeshes.length === 0) return; | |
| // Calculate average Y position of this layer | |
| let sumY = 0; | |
| targetMeshes.forEach(m => { sumY += m.position.y; }); | |
| const avgY = sumY / targetMeshes.length; | |
| // Find primary representative mesh (prefer attention q_lin/q_proj or first node) | |
| const primaryMesh = targetMeshes.find(m => { | |
| const id = (m.userData.id || '').toLowerCase(); | |
| return id.includes('q_lin') || id.includes('q_proj') || id.includes('attn') || id.includes('attention'); | |
| }) || targetMeshes[0]; | |
| if (primaryMesh) { | |
| selectPartIn3D(primaryMesh, interactableMeshes); | |
| updateInspectorWithNode(primaryMesh.userData, primaryMesh.userData.side === 'baseline', store.get('activeResearchModule'), store.get('currentModel')); | |
| } | |
| // Smoothly fly camera right in front of this layer | |
| smoothFlyCamera(camera, controls, avgY, 10.0, 0); | |
| // Update active layer UI indicators | |
| if (!isNaN(numericIdx)) { | |
| updateActiveLayerIndicator(numericIdx, (primaryMesh && primaryMesh.userData.parent_layer_name) || `Layer ${numericIdx}`); | |
| renderLayerDepthProfile(numericIdx, store.get('currentModel')); | |
| // Synchronize attention heatmap modal if open | |
| const heatSelect = document.getElementById('heatmap-layer-select'); | |
| if (heatSelect) { | |
| heatSelect.value = String(numericIdx); | |
| heatSelect.dispatchEvent(new Event('change')); | |
| } | |
| } else { | |
| updateActiveLayerIndicator(strId, strId); | |
| renderLayerDepthProfile(strId, store.get('currentModel')); | |
| } | |
| }; | |
| function buildLayerQuickPills(modelKey, numLayers = 6) { | |
| const pillsContainer = document.getElementById('layer-quick-pills'); | |
| const scrubber = document.getElementById('timeline-scrubber'); | |
| if (!pillsContainer) return; | |
| pillsContainer.innerHTML = ''; | |
| if (scrubber) { | |
| scrubber.min = '0'; | |
| scrubber.max = String(Math.max(1, numLayers - 1)); | |
| } | |
| // 1. Embeddings Pill | |
| const embBtn = document.createElement('button'); | |
| embBtn.className = 'layer-pill-btn'; | |
| embBtn.setAttribute('data-layer', 'embeddings'); | |
| embBtn.textContent = 'Emb'; | |
| embBtn.title = 'Jump to Input Embeddings & Tokenizer'; | |
| embBtn.onclick = (e) => { | |
| e.stopPropagation(); | |
| window.navigateToLayer('embeddings'); | |
| }; | |
| pillsContainer.appendChild(embBtn); | |
| // 2. Layer Pills L0 .. L(numLayers - 1) with depth-phase coloring | |
| for (let i = 0; i < numLayers; i++) { | |
| const profile = getLayerDepthProfile(i, modelKey, numLayers); | |
| const lBtn = document.createElement('button'); | |
| lBtn.className = `layer-pill-btn ${i === 0 ? 'active' : ''}`; | |
| lBtn.setAttribute('data-layer', String(i)); | |
| lBtn.textContent = `L${i}`; | |
| lBtn.title = `${profile.phaseName} (Ent: ${profile.attentionEntropy} nats, r*=${profile.optimalSvdRank})`; | |
| lBtn.style.borderLeft = `2px solid ${profile.themeColor}`; | |
| lBtn.onclick = (e) => { | |
| e.stopPropagation(); | |
| window.navigateToLayer(i); | |
| }; | |
| pillsContainer.appendChild(lBtn); | |
| } | |
| // 3. Output Pill | |
| const outBtn = document.createElement('button'); | |
| outBtn.className = 'layer-pill-btn'; | |
| outBtn.setAttribute('data-layer', 'output'); | |
| outBtn.textContent = 'Out'; | |
| outBtn.title = 'Jump to Output Head / LM Logits Unembedding'; | |
| outBtn.onclick = (e) => { | |
| e.stopPropagation(); | |
| window.navigateToLayer('output'); | |
| }; | |
| pillsContainer.appendChild(outBtn); | |
| } | |
| window.selectPartIn3D = (target) => { | |
| const mesh = selectPartIn3D(target, interactableMeshes); | |
| if (mesh) { | |
| updateInspectorWithNode(mesh.userData, mesh.userData.side === 'baseline', store.get('activeResearchModule'), store.get('currentModel')); | |
| smoothFlyCamera(camera, controls, mesh.position.y, 8.5, mesh.position.x); | |
| } | |
| }; | |
| window.focusPipelineComponent = (compId) => { | |
| window.selectPartIn3D(compId); | |
| }; | |
| window.toggleTour = () => { | |
| tourActive = !tourActive; | |
| const tourBtn = document.getElementById('btn-auto-tour'); | |
| const tourLabel = document.getElementById('tour-step-label'); | |
| if (!tourActive) { | |
| clearTimeout(tourTimer); | |
| if (tourBtn) tourBtn.innerHTML = '▶ Tour'; | |
| if (tourLabel) { | |
| tourLabel.style.display = 'none'; | |
| tourLabel.classList.remove('show'); | |
| } | |
| return; | |
| } | |
| if (tourBtn) tourBtn.innerHTML = '⏹ Stop'; | |
| tourStep = 0; | |
| let isPaused = false; | |
| function runStep() { | |
| if (!tourActive) return; | |
| const steps = (ATTENTION_TOUR_STEPS && ATTENTION_TOUR_STEPS.length) ? ATTENTION_TOUR_STEPS : TOUR_STEPS; | |
| const currentIdx = tourStep % steps.length; | |
| const s = steps[currentIdx]; | |
| if (tourLabel) { | |
| renderTourStepHUD(s, currentIdx, steps.length, { | |
| isPaused, | |
| onPrev: () => { | |
| clearTimeout(tourTimer); | |
| tourStep = (currentIdx - 1 + steps.length) % steps.length; | |
| runStep(); | |
| }, | |
| onNext: () => { | |
| clearTimeout(tourTimer); | |
| tourStep = (currentIdx + 1) % steps.length; | |
| runStep(); | |
| }, | |
| onPlayPause: () => { | |
| isPaused = !isPaused; | |
| clearTimeout(tourTimer); | |
| if (!isPaused) { | |
| scheduleNext(); | |
| } | |
| if (tourLabel) { | |
| const pauseBtn = tourLabel.querySelector('#btn-tour-hud-pause'); | |
| if (pauseBtn) pauseBtn.textContent = isPaused ? '▶' : '⏸'; | |
| } | |
| }, | |
| onClose: () => { | |
| window.toggleTour(); | |
| } | |
| }); | |
| } | |
| if (s.target) { | |
| const mesh = selectPartIn3D(s.target, interactableMeshes); | |
| if (mesh) { | |
| updateInspectorWithNode(mesh.userData, mesh.userData.side === 'baseline', store.get('activeResearchModule'), store.get('currentModel')); | |
| } | |
| } else if (s.fullFit) { | |
| clearSelectionHighlight(); | |
| } | |
| smoothFlyCamera(camera, controls, s.y, s.dist); | |
| function scheduleNext() { | |
| if (!tourActive || isPaused) return; | |
| const duration = s.durationMs || VISUAL_CONFIG.ANIM.tourStepDuration || 8000; | |
| tourTimer = setTimeout(() => { | |
| tourStep++; | |
| runStep(); | |
| }, duration); | |
| } | |
| clearTimeout(tourTimer); | |
| if (!isPaused) { | |
| scheduleNext(); | |
| } | |
| } | |
| runStep(); | |
| }; | |
| const autoTourBtn = document.getElementById('btn-auto-tour'); | |
| if (autoTourBtn) autoTourBtn.addEventListener('click', () => window.toggleTour()); | |
| // Initial Scene Build & URL Query Parameters Auto-Loader (?module=...&model=...) | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const paramModule = urlParams.get('module'); | |
| const paramModel = urlParams.get('model'); | |
| if (paramModel && ['distilbert', 'roberta', 'gpt2', 'qwen'].includes(paramModel)) { | |
| store.set({ currentModel: paramModel }); | |
| } | |
| if (paramModule && catalogModules.length > 0) { | |
| const found = catalogModules.find(m => m.id.toLowerCase() === paramModule.toLowerCase() || (m.name && m.name.toLowerCase().includes(paramModule.toLowerCase()))); | |
| if (found) { | |
| store.set({ activeResearchModule: found, currentCompression: found.paradigm || 'svd' }); | |
| const insp = document.getElementById('inspector'); | |
| if (insp) insp.classList.remove('hidden'); | |
| } | |
| } else if (!store.get('activeResearchModule') && catalogModules.length > 0) { | |
| const defaultMod = catalogModules.find(m => m.id === 'svd_linear' || m.id === 'SOAFusedAttention') || catalogModules[0]; | |
| store.set({ activeResearchModule: defaultMod, currentCompression: defaultMod.paradigm || 'svd' }); | |
| } | |
| rebuildSceneForModel(store.get('currentModel'), store.get('currentCompression')); | |
| smoothFlyCamera(camera, controls, maxGraphHeight * 0.5, Math.max(18, maxGraphHeight * 1.35)); | |
| // Prime Inspector with first node so telemetry and mathematical formulas are immediately available | |
| if (interactableMeshes.length > 0) { | |
| const firstNode = interactableMeshes[0].userData; | |
| updateInspectorWithNode(firstNode, firstNode.side === 'baseline', store.get('activeResearchModule'), store.get('currentModel')); | |
| } | |
| // Restore deep-linked state from URL hash if present | |
| if (typeof window !== 'undefined' && window.location.hash) { | |
| const parsed = parseHashToState(window.location.hash); | |
| applyHashStateToVisualizer(parsed, { | |
| store, | |
| catalogModules, | |
| rebuildSceneForModel, | |
| smoothFlyCamera: (cam, ctrl, y, d, x) => smoothFlyCamera(cam, ctrl, y, d, x), | |
| camera, | |
| controls | |
| }); | |
| } | |
| // 9. Main Animation / Simulation Frame Loop | |
| function animate(timestamp) { | |
| requestAnimationFrame(animate); | |
| perfMonitor.tick(timestamp); | |
| controls.update(); | |
| updateCameraFlight(camera, controls); | |
| animateSelectionPulse(timestamp); | |
| // Continuous Semantic LOD based on camera distance | |
| const camDist = camera.position.distanceTo(controls.target); | |
| if (camDist > VISUAL_CONFIG.ZOOM.macroThreshold) { | |
| macroGroup.visible = true; | |
| granularGroup.visible = false; | |
| dimensionBadgesGroup.visible = false; | |
| } else if (camDist > VISUAL_CONFIG.ZOOM.transitionStart) { | |
| macroGroup.visible = true; | |
| granularGroup.visible = true; | |
| dimensionBadgesGroup.visible = false; | |
| } else { | |
| macroGroup.visible = false; | |
| granularGroup.visible = true; | |
| dimensionBadgesGroup.visible = true; | |
| } | |
| // Particle Physics Simulation | |
| const simContext = { | |
| modelKey: store.get('currentModel'), | |
| paradigm: (store.get('activeResearchModule') && store.get('activeResearchModule').paradigm) || store.get('currentCompression') || 'svd', | |
| maxGraphHeight, | |
| simulationSpeed: store.get('simulationSpeed'), | |
| enablePulses: store.get('enablePulses'), | |
| isPlaying: store.get('isPlaying'), | |
| interactableMeshes, | |
| activeSelectedSublayerKey | |
| }; | |
| if (store.get('isPlaying')) { | |
| updateTokens(tokenMeshBaseline, trailMeshBaseline, tokenDataBaseline, false, simContext); | |
| updateTokens(tokenMeshCompressed, trailMeshCompressed, tokenDataCompressed, true, simContext); | |
| updateNodePulses(timestamp); | |
| } | |
| // Dual-Viewport Scissor Render | |
| const w = window.innerWidth; | |
| const h = window.innerHeight; | |
| renderer.setViewport(0, 0, w, h); | |
| renderer.setScissor(0, 0, w, h); | |
| renderer.render(scene, camera); | |
| } | |
| requestAnimationFrame(animate); | |
| } | |
Xet Storage Details
- Size:
- 33.5 kB
- Xet hash:
- 65965b70c43094c029729e65387736f8dbacc539bcce75e7fad2240f9231530e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.