[claude] Kimi & Perplexity as visible workshop agents (#10) #30

Closed
claude wants to merge 1 commits from claude/the-nexus:claude/issue-10 into main
3 changed files with 397 additions and 2 deletions

327
app.js
View File

@@ -35,6 +35,12 @@ let frameCount = 0, lastFPSTime = 0, fps = 0;
let chatOpen = true;
let loadProgress = 0;
// Agent colors
const AGENTS = {
kimi: { color: 0xff8844, hex: '#ff8844', label: 'KIMI', task: 'coding #10' },
perplexity: { color: 0x20d9d2, hex: '#20d9d2', label: 'PERPLEXITY', task: 'research' },
};
// ═══ INIT ═══
function init() {
clock = new THREE.Clock();
@@ -77,6 +83,9 @@ function init() {
createDustParticles();
updateLoad(85);
createAmbientStructures();
updateLoad(88);
createKimiWorkbench();
createPerplexityLibrary();
updateLoad(90);
// Post-processing
@@ -387,10 +396,10 @@ function createBatcaveTerminal() {
title: '🤖 AGENTS',
lines: [
'Claude Code ● ACTIVE',
'Kimi ACTIVE',
'Kimi [WB]CODING',
'Gemini ○ STANDBY',
'Hermes ● ACTIVE',
'Perplexity ● ACTIVE',
'Perplexity[LB] ● RESEARCH',
],
color: 0xff8844,
});
@@ -787,6 +796,271 @@ function createAmbientStructures() {
scene.add(pedestal);
}
// ═══ AGENT: KIMI (Workbench — left side) ═══
function createKimiWorkbench() {
const group = new THREE.Group();
group.position.set(-13, 0, -4);
group.rotation.y = 0.45;
group.name = 'kimi-workbench';
const col = AGENTS.kimi.color;
const hex = AGENTS.kimi.hex;
// Desk surface
const deskMat = new THREE.MeshStandardMaterial({
color: 0x1a1a2e, roughness: 0.3, metalness: 0.7,
emissive: 0x1a0800, emissiveIntensity: 0.12,
});
const desk = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.12, 1.5), deskMat);
desk.position.set(0, 1.38, 0);
desk.castShadow = true;
group.add(desk);
// Desk legs
[[-1.5, -0.6], [-1.5, 0.6], [1.5, -0.6], [1.5, 0.6]].forEach(([lx, lz]) => {
const leg = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.4, 0.1), deskMat.clone());
leg.position.set(lx, 0.7, lz);
group.add(leg);
});
// Monitor body
const monitor = new THREE.Mesh(
new THREE.BoxGeometry(1.85, 1.15, 0.08),
new THREE.MeshStandardMaterial({ color: 0x050510, roughness: 0.5, metalness: 0.8 })
);
monitor.position.set(0, 2.22, -0.51);
group.add(monitor);
// Monitor screen (canvas texture)
const sc = document.createElement('canvas');
sc.width = 256; sc.height = 160;
const sx = sc.getContext('2d');
sx.fillStyle = '#000a14';
sx.fillRect(0, 0, 256, 160);
sx.font = 'bold 13px "JetBrains Mono", monospace';
sx.fillStyle = hex;
sx.fillText('> kimi@nexus:~$', 8, 20);
sx.fillStyle = '#4af0c0';
sx.fillText('ISSUE #10: Agent Presence', 8, 40);
sx.fillStyle = '#a0b8d0';
sx.fillText('Building workshop agents...', 8, 60);
sx.fillText('PROGRESS: ████████░░ 80%', 8, 80);
sx.fillStyle = hex;
sx.fillText('● CODING', 8, 110);
sx.font = '10px "JetBrains Mono", monospace';
sx.fillStyle = '#3a4a5a';
sx.fillText('branch: claude/issue-10', 8, 140);
const screenMesh = new THREE.Mesh(
new THREE.PlaneGeometry(1.7, 1.05),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(sc), side: THREE.FrontSide })
);
screenMesh.position.set(0, 2.22, -0.465);
group.add(screenMesh);
// Monitor screen glow
const screenGlow = new THREE.Mesh(
new THREE.PlaneGeometry(1.85, 1.15),
new THREE.MeshBasicMaterial({ color: col, transparent: true, opacity: 0.06, side: THREE.DoubleSide })
);
screenGlow.position.set(0, 2.22, -0.52);
group.add(screenGlow);
// Agent body
const figMat = new THREE.MeshPhysicalMaterial({
color: col, emissive: col, emissiveIntensity: 0.45,
roughness: 0.2, metalness: 0.6, transparent: true, opacity: 0.85,
});
const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.22, 0.72, 8, 16), figMat);
body.name = 'kimi-body';
body.position.set(-1.5, 2.0, 0.4);
group.add(body);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), figMat.clone());
head.name = 'kimi-head';
head.position.set(-1.5, 2.92, 0.4);
group.add(head);
// Activity rings
const rMat = new THREE.MeshBasicMaterial({ color: col, transparent: true, opacity: 0.6 });
const ring1 = new THREE.Mesh(new THREE.TorusGeometry(0.44, 0.025, 8, 32), rMat);
ring1.name = 'kimi-ring1';
ring1.position.set(-1.5, 2.42, 0.4);
group.add(ring1);
const ring2 = new THREE.Mesh(new THREE.TorusGeometry(0.6, 0.018, 8, 32), rMat.clone());
ring2.name = 'kimi-ring2';
ring2.position.set(-1.5, 2.42, 0.4);
ring2.rotation.x = Math.PI / 3;
group.add(ring2);
// Point light
const glow = new THREE.PointLight(col, 1.8, 7, 1.5);
glow.position.set(-1.5, 2.5, 0.4);
group.add(glow);
// Name label
const lc = document.createElement('canvas');
lc.width = 256; lc.height = 48;
const lx = lc.getContext('2d');
lx.font = 'bold 26px "Orbitron", sans-serif';
lx.fillStyle = hex;
lx.textAlign = 'center';
lx.fillText('◈ KIMI', 128, 34);
const label = new THREE.Mesh(
new THREE.PlaneGeometry(1.8, 0.34),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide })
);
label.position.set(-1.5, 3.55, 0.4);
group.add(label);
// Floor zone marker
const zc = document.createElement('canvas');
zc.width = 320; zc.height = 40;
const zx = zc.getContext('2d');
zx.font = '15px "JetBrains Mono", monospace';
zx.fillStyle = '#5a3a1a';
zx.textAlign = 'center';
zx.fillText('[ WORKBENCH ]', 160, 26);
const zone = new THREE.Mesh(
new THREE.PlaneGeometry(3, 0.3),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(zc), transparent: true, side: THREE.DoubleSide })
);
zone.position.set(0, 0.06, 0);
zone.rotation.x = -Math.PI / 2;
group.add(zone);
scene.add(group);
}
// ═══ AGENT: PERPLEXITY (Library — right-front) ═══
function createPerplexityLibrary() {
const group = new THREE.Group();
group.position.set(5, 0, 9);
group.rotation.y = Math.PI + 0.2;
group.name = 'perplexity-library';
const col = AGENTS.perplexity.color;
const hex = AGENTS.perplexity.hex;
const shelfMat = new THREE.MeshStandardMaterial({
color: 0x0a0f1a, roughness: 0.4, metalness: 0.6,
emissive: 0x001a18, emissiveIntensity: 0.12,
});
// Shelf back panel
const back = new THREE.Mesh(new THREE.BoxGeometry(3.6, 4.6, 0.14), shelfMat);
back.position.set(0, 2.3, -0.45);
group.add(back);
// Shelf sides
for (const sx of [-1.8, 1.8]) {
const side = new THREE.Mesh(new THREE.BoxGeometry(0.12, 4.6, 0.65), shelfMat.clone());
side.position.set(sx, 2.3, -0.2);
group.add(side);
}
// Shelf boards + data crystals
for (let i = 0; i < 4; i++) {
const board = new THREE.Mesh(new THREE.BoxGeometry(3.35, 0.08, 0.55), shelfMat.clone());
board.position.set(0, 0.55 + i * 1.1, -0.2);
group.add(board);
for (let j = 0; j < 5; j++) {
const crystalGeo = new THREE.OctahedronGeometry(0.09 + (i + j) % 3 * 0.03, 0);
const crystalMat = new THREE.MeshPhysicalMaterial({
color: col, emissive: col,
emissiveIntensity: 0.5 + ((i * 3 + j) % 5) * 0.12,
roughness: 0.1, metalness: 0.3, transmission: 0.4, thickness: 0.5,
});
const crystal = new THREE.Mesh(crystalGeo, crystalMat);
crystal.name = `px-crystal-${i}-${j}`;
crystal.position.set(-1.4 + j * 0.7, 0.67 + i * 1.1, -0.05);
crystal.rotation.set((j % 3) * 0.4, (i + j) * 0.8, (i % 2) * 0.3);
group.add(crystal);
}
}
// Agent body
const figMat = new THREE.MeshPhysicalMaterial({
color: col, emissive: col, emissiveIntensity: 0.45,
roughness: 0.2, metalness: 0.6, transparent: true, opacity: 0.85,
});
const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.22, 0.72, 8, 16), figMat);
body.name = 'perplexity-body';
body.position.set(1.8, 2.0, 0.55);
group.add(body);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), figMat.clone());
head.name = 'perplexity-head';
head.position.set(1.8, 2.92, 0.55);
group.add(head);
// Orbiting research orbs
const orbMat = new THREE.MeshBasicMaterial({ color: col });
for (let i = 0; i < 3; i++) {
const orb = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 8), orbMat.clone());
orb.name = `perplexity-orb-${i}`;
group.add(orb);
}
// Point light
const glow = new THREE.PointLight(col, 1.8, 7, 1.5);
glow.position.set(1.8, 2.5, 0.55);
group.add(glow);
// Research query panel
const qc = document.createElement('canvas');
qc.width = 320; qc.height = 160;
const qx = qc.getContext('2d');
qx.fillStyle = 'rgba(0,10,10,0.85)';
qx.fillRect(0, 0, 320, 160);
qx.font = 'bold 14px "JetBrains Mono", monospace';
qx.fillStyle = hex;
qx.fillText('PERPLEXITY RESEARCH', 10, 22);
qx.strokeStyle = hex;
qx.globalAlpha = 0.25;
qx.beginPath(); qx.moveTo(10, 30); qx.lineTo(310, 30); qx.stroke();
qx.globalAlpha = 1;
qx.font = '12px "JetBrains Mono", monospace';
qx.fillStyle = '#88c8c8';
qx.fillText('QUERY: agent spatial presence', 10, 52);
qx.fillText('SOURCES: 12 indexed', 10, 72);
qx.fillText('CONFIDENCE: ████████ 94%', 10, 92);
qx.fillStyle = hex;
qx.fillText('● RESEARCHING', 10, 126);
const queryPanel = new THREE.Mesh(
new THREE.PlaneGeometry(2.2, 1.1),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(qc), transparent: true, side: THREE.DoubleSide })
);
queryPanel.name = 'perplexity-query';
queryPanel.position.set(1.8, 3.65, 0.85);
group.add(queryPanel);
// Name label
const lc = document.createElement('canvas');
lc.width = 320; lc.height = 48;
const lx = lc.getContext('2d');
lx.font = 'bold 22px "Orbitron", sans-serif';
lx.fillStyle = hex;
lx.textAlign = 'center';
lx.fillText('◈ PERPLEXITY', 160, 34);
const label = new THREE.Mesh(
new THREE.PlaneGeometry(2.2, 0.34),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide })
);
label.position.set(1.8, 5.1, 0.55);
group.add(label);
// Floor zone marker
const zc = document.createElement('canvas');
zc.width = 320; zc.height = 40;
const zx = zc.getContext('2d');
zx.font = '15px "JetBrains Mono", monospace';
zx.fillStyle = '#1a3a38';
zx.textAlign = 'center';
zx.fillText('[ LIBRARY ]', 160, 26);
const zone = new THREE.Mesh(
new THREE.PlaneGeometry(3, 0.3),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(zc), transparent: true, side: THREE.DoubleSide })
);
zone.position.set(0, 0.06, 0);
zone.rotation.x = -Math.PI / 2;
group.add(zone);
scene.add(group);
}
// ═══ CONTROLS ═══
function setupControls() {
document.addEventListener('keydown', (e) => {
@@ -941,6 +1215,55 @@ function gameLoop() {
}
}
// Animate Kimi
const kimiBody = scene.getObjectByName('kimi-body');
const kimiHead = scene.getObjectByName('kimi-head');
const kimiRing1 = scene.getObjectByName('kimi-ring1');
const kimiRing2 = scene.getObjectByName('kimi-ring2');
if (kimiBody) {
const bob = Math.sin(elapsed * 2.8) * 0.05;
kimiBody.position.y = 2.0 + bob;
kimiHead.position.y = 2.92 + bob;
}
if (kimiRing1) {
kimiRing1.rotation.z = elapsed * 2.8;
kimiRing1.rotation.y = elapsed * 1.0;
}
if (kimiRing2) {
kimiRing2.rotation.z = -elapsed * 2.0;
kimiRing2.rotation.x = Math.PI / 3 + elapsed * 0.9;
}
// Animate Perplexity
const pxBody = scene.getObjectByName('perplexity-body');
const pxHead = scene.getObjectByName('perplexity-head');
if (pxBody) {
const bob = Math.sin(elapsed * 1.6 + 1.5) * 0.04;
pxBody.position.y = 2.0 + bob;
pxHead.position.y = 2.92 + bob;
}
for (let i = 0; i < 3; i++) {
const orb = scene.getObjectByName(`perplexity-orb-${i}`);
if (orb) {
const angle = elapsed * 1.8 + (i / 3) * Math.PI * 2;
orb.position.set(
1.8 + Math.cos(angle) * 0.5,
2.5 + Math.sin(angle * 0.7) * 0.18,
0.55 + Math.sin(angle) * 0.5
);
}
}
const pxQuery = scene.getObjectByName('perplexity-query');
if (pxQuery) {
pxQuery.position.y = 3.65 + Math.sin(elapsed * 0.9) * 0.1;
}
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 5; j++) {
const cx = scene.getObjectByName(`px-crystal-${i}-${j}`);
if (cx) cx.material.emissiveIntensity = 0.4 + Math.sin(elapsed * 1.5 + i + j * 0.8) * 0.28;
}
}
// Animate nexus core
const core = scene.getObjectByName('nexus-core');
if (core) {

View File

@@ -95,6 +95,21 @@
</div>
</div>
<!-- Agent Status (Top Right) -->
<div id="agent-status" class="agent-status-panel">
<div class="agent-status-header">AGENTS ONLINE</div>
<div class="agent-row">
<span class="agent-dot agent-dot-kimi"></span>
<span class="agent-name">KIMI</span>
<span class="agent-task">coding #10 · workbench</span>
</div>
<div class="agent-row">
<span class="agent-dot agent-dot-perplexity"></span>
<span class="agent-name">PERPLEXITY</span>
<span class="agent-task">research · library</span>
</div>
</div>
<!-- Minimap / Controls hint -->
<div class="hud-controls">
<span>WASD</span> move &nbsp; <span>Mouse</span> look &nbsp; <span>Enter</span> chat

View File

@@ -330,6 +330,63 @@ canvas#nexus-canvas {
background: rgba(74, 240, 192, 0.1);
}
/* === AGENT STATUS PANEL === */
.agent-status-panel {
position: absolute;
top: var(--space-3);
right: var(--space-3);
background: var(--color-surface);
backdrop-filter: blur(var(--panel-blur));
border: 1px solid var(--color-border);
border-radius: var(--panel-radius);
padding: var(--space-2) var(--space-3) var(--space-3);
pointer-events: none;
min-width: 200px;
}
.agent-status-header {
font-family: var(--font-display);
font-size: 10px;
letter-spacing: 0.14em;
color: var(--color-text-muted);
margin-bottom: var(--space-2);
padding-bottom: var(--space-1);
border-bottom: 1px solid var(--color-border);
}
.agent-row {
display: flex;
align-items: center;
gap: var(--space-2);
padding: 3px 0;
font-size: var(--text-xs);
}
.agent-dot {
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
animation: dot-pulse 2s ease-in-out infinite;
}
.agent-dot-kimi {
background: #ff8844;
box-shadow: 0 0 6px #ff8844;
}
.agent-dot-perplexity {
background: #20d9d2;
box-shadow: 0 0 6px #20d9d2;
animation-delay: 0.7s;
}
.agent-name {
font-weight: 700;
color: var(--color-text-bright);
letter-spacing: 0.05em;
min-width: 76px;
font-size: 11px;
}
.agent-task {
color: var(--color-text-muted);
font-size: 10px;
}
/* === FOOTER === */
.nexus-footer {
position: fixed;