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>
82 lines
4.0 KiB
JavaScript
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;
|
|
}
|
|
}
|