[claude] Model training status — show LoRA adapters (#277) (#324)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled

This commit was merged in pull request #324.
This commit is contained in:
2026-03-24 04:59:20 +00:00
parent 5794c7ed71
commit 6213b36d66
2 changed files with 197 additions and 0 deletions

188
app.js
View File

@@ -996,6 +996,12 @@ function animate() {
sprite.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.22;
}
// Animate LoRA status panel — gentle float
if (loraPanelSprite) {
const ud = loraPanelSprite.userData;
loraPanelSprite.position.y = ud.baseY + Math.sin(elapsed * ud.floatSpeed + ud.floatPhase) * 0.22;
}
// Animate Timmy speech bubble — fade in, hold, fade out
if (timmySpeechState) {
const age = elapsed - timmySpeechState.startTime;
@@ -2112,6 +2118,188 @@ async function refreshAgentBoard() {
refreshAgentBoard();
setInterval(refreshAgentBoard, 30000);
// === LORA ADAPTER STATUS PANEL ===
// Holographic panel showing which LoRA fine-tuning adapters are currently active.
// Reads from lora-status.json, falls back to stub data when unavailable.
const LORA_STATUS_STUB = {
adapters: [
{ name: 'timmy-voice-v3', base: 'mistral-7b', active: true, strength: 0.85 },
{ name: 'nexus-style-v2', base: 'llama-3-8b', active: true, strength: 0.70 },
{ name: 'sovereign-tone-v1', base: 'phi-3-mini', active: false, strength: 0.50 },
{ name: 'btc-domain-v1', base: 'mistral-7b', active: true, strength: 0.60 },
],
updated: '',
};
const LORA_ACTIVE_COLOR = '#00ff88'; // green — adapter is loaded
const LORA_INACTIVE_COLOR = '#334466'; // dim blue — adapter is off
/**
* Builds a canvas texture for the LoRA status panel.
* @param {typeof LORA_STATUS_STUB} data
* @returns {THREE.CanvasTexture}
*/
function createLoRAPanelTexture(data) {
const W = 420, H = 260;
const canvas = document.createElement('canvas');
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext('2d');
// Background
ctx.fillStyle = 'rgba(0, 6, 20, 0.90)';
ctx.fillRect(0, 0, W, H);
// Outer border — magenta/purple for "training" theme
ctx.strokeStyle = '#cc44ff';
ctx.lineWidth = 2;
ctx.strokeRect(1, 1, W - 2, H - 2);
// Inner border
ctx.strokeStyle = '#cc44ff';
ctx.lineWidth = 1;
ctx.globalAlpha = 0.3;
ctx.strokeRect(4, 4, W - 8, H - 8);
ctx.globalAlpha = 1.0;
// Header label
ctx.font = 'bold 14px "Courier New", monospace';
ctx.fillStyle = '#cc44ff';
ctx.textAlign = 'left';
ctx.fillText('MODEL TRAINING', 14, 24);
// "LoRA ADAPTERS" sub-label
ctx.font = '10px "Courier New", monospace';
ctx.fillStyle = '#664488';
ctx.fillText('LoRA ADAPTERS', 14, 38);
// Active count badge (top-right)
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';
// Separator
ctx.strokeStyle = '#2a1a44';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(14, 46);
ctx.lineTo(W - 14, 46);
ctx.stroke();
// Adapter rows
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;
// Status dot
ctx.beginPath();
ctx.arc(22, rowY + 12, 6, 0, Math.PI * 2);
ctx.fillStyle = col;
ctx.fill();
// Adapter name
ctx.font = 'bold 13px "Courier New", monospace';
ctx.fillStyle = adapter.active ? '#ddeeff' : '#445566';
ctx.fillText(adapter.name, 36, rowY + 16);
// Base model (right-aligned)
ctx.font = '10px "Courier New", monospace';
ctx.fillStyle = '#556688';
ctx.textAlign = 'right';
ctx.fillText(adapter.base, W - 14, rowY + 16);
ctx.textAlign = 'left';
// Strength bar
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;
ctx.font = '9px "Courier New", monospace';
ctx.fillStyle = col;
ctx.textAlign = 'right';
ctx.fillText(`${Math.round(adapter.strength * 100)}%`, W - 14, rowY + 28);
ctx.textAlign = 'left';
}
// Row divider (except after last)
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);
}
const loraGroup = new THREE.Group();
scene.add(loraGroup);
const LORA_PANEL_POS = new THREE.Vector3(-10.5, 4.5, 2.5);
let loraPanelSprite = null;
/**
* (Re)builds the LoRA panel sprite from fresh data.
* @param {typeof LORA_STATUS_STUB} data
*/
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 — LoRA Adapters',
};
loraGroup.add(loraPanelSprite);
}
/**
* Fetches live LoRA adapter status, falling back to stub when unavailable.
*/
async function loadLoRAStatus() {
try {
const res = await fetch('./lora-status.json');
if (!res.ok) throw new Error('not found');
const data = await res.json();
if (!Array.isArray(data.adapters)) throw new Error('invalid');
rebuildLoRAPanel(data);
} catch {
rebuildLoRAPanel(LORA_STATUS_STUB);
}
}
loadLoRAStatus();
// Refresh every 60 s so live updates propagate
setInterval(loadLoRAStatus, 60000);
// === TIMMY SPEECH BUBBLE ===
// When Timmy sends a chat message, a glowing floating text sprite appears near
// his avatar position above the platform. Fades in quickly, holds for 5 s total,

9
lora-status.json Normal file
View File

@@ -0,0 +1,9 @@
{
"adapters": [
{ "name": "timmy-voice-v3", "base": "mistral-7b", "active": true, "strength": 0.85 },
{ "name": "nexus-style-v2", "base": "llama-3-8b", "active": true, "strength": 0.70 },
{ "name": "sovereign-tone-v1", "base": "phi-3-mini", "active": false, "strength": 0.50 },
{ "name": "btc-domain-v1", "base": "mistral-7b", "active": true, "strength": 0.60 }
],
"updated": "2026-03-24T00:00:00Z"
}