llm-semantic-visualizer / src /visualizer /ThreadManager.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
6.61 kB
import * as THREE from 'three';
import { CONFIG, STATE } from '../core/State.js';
import { clearRelief, isReliefActive } from './ReliefRenderer.js';
export function clearThreads(scene) {
if (!STATE.threads) return;
if (CONFIG.debug) console.log("[VISUALIZER] Clearing all threads");
STATE.threads.forEach(t => {
if (t.lineMesh) {
scene.remove(t.lineMesh);
if (t.lineMesh.geometry) t.lineMesh.geometry.dispose();
if (t.lineMesh.material) t.lineMesh.material.dispose();
}
if (t.pointsMesh) {
scene.remove(t.pointsMesh);
if (t.pointsMesh.material) t.pointsMesh.material.dispose();
}
if (t.labelSprite) {
scene.remove(t.labelSprite);
if (t.labelSprite.material) {
if (t.labelSprite.material.map) t.labelSprite.material.map.dispose();
t.labelSprite.material.dispose();
}
}
});
STATE.threads = [];
STATE.focusedThreads.clear();
STATE.hiddenThreads.clear();
// Clear Instanced Mesh if exists
if (STATE.instancedMesh) {
scene.remove(STATE.instancedMesh);
if (STATE.instancedMesh.geometry) STATE.instancedMesh.geometry.dispose();
if (STATE.instancedMesh.material) STATE.instancedMesh.material.dispose();
STATE.instancedMesh = null;
}
// Clear Macro Meshes (New GPU Mode)
if (STATE.macroMeshes) {
if (STATE.macroMeshes.lines) {
scene.remove(STATE.macroMeshes.lines);
if (STATE.macroMeshes.lines.geometry) STATE.macroMeshes.lines.geometry.dispose();
if (STATE.macroMeshes.lines.material) STATE.macroMeshes.lines.material.dispose();
}
if (STATE.macroMeshes.points) {
scene.remove(STATE.macroMeshes.points);
if (STATE.macroMeshes.points.geometry) STATE.macroMeshes.points.geometry.dispose();
if (STATE.macroMeshes.points.material) STATE.macroMeshes.points.material.dispose();
}
STATE.macroMeshes = null;
}
}
export function updateThreadVisibility() {
if (!STATE.threads) return;
const isRelief = (STATE.blanketSubMode === 'RELIEF');
// Automatically clean up active relief if we switch away from RELIEF submode
if (!isRelief && isReliefActive() && STATE.scene) {
clearRelief(STATE.scene);
}
// Update GPU Macro Meshes visibility
if (STATE.macroMeshes) {
if (STATE.macroMeshes.lines) STATE.macroMeshes.lines.visible = !isRelief;
if (STATE.macroMeshes.points) STATE.macroMeshes.points.visible = !isRelief;
}
const hasFocus = STATE.focusedThreads.size > 0;
STATE.threads.forEach((t, index) => {
const isFocused = STATE.focusedThreads.has(index);
const isHidden = STATE.hiddenThreads.has(index);
// Visibility (Hidden) Logic
if (isRelief || isHidden) {
if (t.lineMesh) t.lineMesh.visible = false;
if (t.pointsMesh) t.pointsMesh.visible = false;
if (t.labelSprite) t.labelSprite.visible = false;
return; // Skip opacity logic if hidden or in relief submode
} else {
if (t.lineMesh) t.lineMesh.visible = true;
if (t.pointsMesh) t.pointsMesh.visible = true;
if (t.labelSprite) t.labelSprite.visible = true;
}
// Polarity Filter Logic (Points Only)
// We filter points by moving them out of view if they don't match the polarity
const polarity = STATE.polarityMode || 'ALL';
if (t.pointsMesh && t.pointsMesh.geometry) {
const positions = t.pointsMesh.geometry.attributes.position;
const count = positions.count;
let needsUpdate = false;
for (let i = 0; i < count; i++) {
const val = t.embedding[i];
let visible = true;
if (polarity === 'POS' && val <= 0) visible = false;
if (polarity === 'NEG' && val >= 0) visible = false;
const currentY = positions.getY(i);
// FIX: Flatten to 0 instead of moving to infinity
const targetY = visible ? (val * CONFIG.amplitudeScale) : 0;
// Optimization: Only update if changed
if (Math.abs(currentY - targetY) > 0.001) {
positions.setY(i, targetY);
needsUpdate = true;
}
}
if (needsUpdate) {
positions.needsUpdate = true;
}
}
// Focus (Opacity) Logic
let targetOpacity = 1.0;
let targetLineWidth = 1;
if (hasFocus) {
if (isFocused) {
targetOpacity = 1.0;
targetLineWidth = 2;
} else {
targetOpacity = 0.05;
targetLineWidth = 1;
}
} else {
targetOpacity = 1.0;
targetLineWidth = 1;
}
// Apply to Line
if (t.lineMesh && t.lineMesh.material) {
t.lineMesh.material.opacity = targetOpacity;
t.lineMesh.material.linewidth = targetLineWidth;
t.lineMesh.material.needsUpdate = true;
}
// Apply to Points (Shader)
if (t.pointsMesh && t.pointsMesh.material && t.pointsMesh.material.uniforms) {
t.pointsMesh.material.uniforms.opacity.value = targetOpacity;
}
// Apply to Sprite
if (t.labelSprite) {
t.labelSprite.material.opacity = targetOpacity;
}
});
}
export function updateThreadColor(thread, colorHex) {
if (!thread) return;
const newColor = new THREE.Color(colorHex);
thread.color = newColor;
// 1. Update Line
if (thread.lineMesh && thread.lineMesh.material) {
thread.lineMesh.material.color.set(newColor);
thread.lineMesh.material.needsUpdate = true;
}
// 2. Update Points
if (thread.pointsMesh && thread.pointsMesh.material) {
if (thread.pointsMesh.material.uniforms && thread.pointsMesh.material.uniforms.color) {
thread.pointsMesh.material.uniforms.color.value.set(newColor);
} else if (thread.pointsMesh.material.color) {
thread.pointsMesh.material.color.set(newColor);
}
thread.pointsMesh.material.needsUpdate = true;
}
// 3. Update Label (Fast Tinting)
if (thread.labelSprite && thread.labelSprite.material) {
thread.labelSprite.material.color.set(newColor);
thread.labelSprite.material.needsUpdate = true;
}
}