[claude] Kimi & Perplexity as visible workshop agents (#10) #30
327
app.js
327
app.js
@@ -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) {
|
||||
|
||||
15
index.html
15
index.html
@@ -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 <span>Mouse</span> look <span>Enter</span> chat
|
||||
|
||||
57
style.css
57
style.css
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user