Files
the-nexus/modules/panels/lora-panel.js
Perplexity Computer 675b61d65e
All checks were successful
CI / validate (pull_request) Successful in 14s
CI / auto-merge (pull_request) Successful in 0s
refactor: modularize app.js into ES module architecture
Split the monolithic 5393-line app.js into 32 focused ES modules under
modules/ with a thin ~330-line orchestrator. No bundler required — runs
in-browser via import maps.

Module structure:
  core/     — scene, ticker, state, theme, audio
  data/     — gitea, weather, bitcoin, loaders
  terrain/  — stars, clouds, island
  effects/  — matrix-rain, energy-beam, lightning, shockwave, rune-ring, gravity-zones
  panels/   — heatmap, sigil, sovereignty, dual-brain, batcave, earth, agent-board, lora-panel
  portals/  — portal-system, commit-banners
  narrative/ — bookshelves, oath, chat
  utils/    — perlin

All files pass node --check. No new dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 18:12:53 +00:00

82 lines
4.0 KiB
JavaScript

// modules/panels/lora-panel.js — LoRA adapter status panel
import * as THREE from 'three';
const LORA_ACTIVE_COLOR = '#00ff88';
const LORA_INACTIVE_COLOR = '#334466';
const LORA_PANEL_POS = new THREE.Vector3(-10.5, 4.5, 2.5);
let loraGroup, loraPanelSprite;
function createLoRAPanelTexture(data) {
const W = 420, H = 260;
const canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(0, 6, 20, 0.90)'; ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = '#cc44ff'; ctx.lineWidth = 2; ctx.strokeRect(1, 1, W - 2, H - 2);
ctx.strokeStyle = '#cc44ff'; ctx.lineWidth = 1; ctx.globalAlpha = 0.3; ctx.strokeRect(4, 4, W - 8, H - 8); ctx.globalAlpha = 1.0;
ctx.font = 'bold 14px "Courier New", monospace'; ctx.fillStyle = '#cc44ff'; ctx.textAlign = 'left'; ctx.fillText('MODEL TRAINING', 14, 24);
ctx.font = '10px "Courier New", monospace'; ctx.fillStyle = '#664488'; ctx.fillText('LoRA ADAPTERS', 14, 38);
ctx.strokeStyle = '#2a1a44'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(14, 46); ctx.lineTo(W - 14, 46); ctx.stroke();
if (!data || !data.adapters || data.adapters.length === 0) {
ctx.font = 'bold 18px "Courier New", monospace'; ctx.fillStyle = '#334466'; ctx.textAlign = 'center';
ctx.fillText('NO ADAPTERS DEPLOYED', W / 2, H / 2 + 10);
ctx.font = '11px "Courier New", monospace'; ctx.fillStyle = '#223344';
ctx.fillText('Adapters will appear here when trained', W / 2, H / 2 + 36);
ctx.textAlign = 'left';
return new THREE.CanvasTexture(canvas);
}
const activeCount = data.adapters.filter(a => a.active).length;
ctx.font = 'bold 13px "Courier New", monospace'; ctx.fillStyle = LORA_ACTIVE_COLOR; ctx.textAlign = 'right';
ctx.fillText(`${activeCount}/${data.adapters.length} ACTIVE`, W - 14, 26); ctx.textAlign = 'left';
const ROW_H = 44;
data.adapters.forEach((adapter, i) => {
const rowY = 50 + i * ROW_H;
const col = adapter.active ? LORA_ACTIVE_COLOR : LORA_INACTIVE_COLOR;
ctx.beginPath(); ctx.arc(22, rowY + 12, 6, 0, Math.PI * 2); ctx.fillStyle = col; ctx.fill();
ctx.font = 'bold 13px "Courier New", monospace'; ctx.fillStyle = adapter.active ? '#ddeeff' : '#445566'; ctx.fillText(adapter.name, 36, rowY + 16);
ctx.font = '10px "Courier New", monospace'; ctx.fillStyle = '#556688'; ctx.textAlign = 'right'; ctx.fillText(adapter.base, W - 14, rowY + 16); ctx.textAlign = 'left';
if (adapter.active) {
const BAR_X = 36, BAR_W = W - 80, BAR_Y = rowY + 22, BAR_H = 5;
ctx.fillStyle = '#0a1428'; ctx.fillRect(BAR_X, BAR_Y, BAR_W, BAR_H);
ctx.fillStyle = col; ctx.globalAlpha = 0.7; ctx.fillRect(BAR_X, BAR_Y, BAR_W * adapter.strength, BAR_H); ctx.globalAlpha = 1.0;
}
if (i < data.adapters.length - 1) {
ctx.strokeStyle = '#1a0a2a'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(14, rowY + ROW_H - 2); ctx.lineTo(W - 14, rowY + ROW_H - 2); ctx.stroke();
}
});
return new THREE.CanvasTexture(canvas);
}
function rebuildLoRAPanel(data) {
if (loraPanelSprite) {
loraGroup.remove(loraPanelSprite);
if (loraPanelSprite.material.map) loraPanelSprite.material.map.dispose();
loraPanelSprite.material.dispose();
loraPanelSprite = null;
}
const texture = createLoRAPanelTexture(data);
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, opacity: 0.93, depthWrite: false });
loraPanelSprite = new THREE.Sprite(material);
loraPanelSprite.scale.set(6.0, 3.6, 1);
loraPanelSprite.position.copy(LORA_PANEL_POS);
loraPanelSprite.userData = { baseY: LORA_PANEL_POS.y, floatPhase: 1.1, floatSpeed: 0.14, zoomLabel: 'Model Training \u2014 LoRA Adapters' };
loraGroup.add(loraPanelSprite);
}
export function init(scene) {
loraGroup = new THREE.Group();
scene.add(loraGroup);
rebuildLoRAPanel({ adapters: [] });
}
export function update(elapsed) {
if (loraPanelSprite) {
const ud = loraPanelSprite.userData;
loraPanelSprite.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.22;
}
}