feat(mission-control): restore MC tabs - temple, office, memory, claude, pdf-viewer, resume, resume-upload, temple-3d, demos

Also added:
- Memory API endpoints
- Briefs API endpoints
- AnveVoice stats API
- Claude spawn API
- TTS proxy
- Cleopatra voice widget
- api-auth middleware
This commit is contained in:
2026-03-23 16:30:44 +01:00
parent d5575b58e3
commit 45af56d9cf
30 changed files with 5092 additions and 715 deletions
+518
View File
@@ -0,0 +1,518 @@
"use client";
import { useEffect, useRef, useState } from "react";
export default function Temple3DPage() {
const containerRef = useRef<HTMLDivElement>(null);
const [selectedAgent, setSelectedAgent] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!containerRef.current) return;
let cancelled = false;
import("three").then((THREE) => {
if (cancelled || !containerRef.current) return;
const container = containerRef.current;
// Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a);
scene.fog = new THREE.FogExp2(0x0a0a1a, 0.015);
// Camera
const camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(0, 15, 35);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
// Lighting
const ambient = new THREE.AmbientLight(0xffd700, 0.3);
scene.add(ambient);
const sun = new THREE.DirectionalLight(0xfff5e0, 1.2);
sun.position.set(50, 100, 50);
sun.castShadow = true;
sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;
scene.add(sun);
const blueLight = new THREE.PointLight(0x40e0d0, 0.8, 100);
blueLight.position.set(-30, 20, -20);
scene.add(blueLight);
const purpleLight = new THREE.PointLight(0x9b59b6, 0.5, 80);
purpleLight.position.set(30, 15, 10);
scene.add(purpleLight);
// Materials
const sandstone = new THREE.MeshStandardMaterial({
color: 0xc2a366,
roughness: 0.9,
metalness: 0.1
});
const darkSandstone = new THREE.MeshStandardMaterial({
color: 0x8b7355,
roughness: 0.85,
metalness: 0.1
});
const gold = new THREE.MeshStandardMaterial({
color: 0xffd700,
metalness: 0.9,
roughness: 0.2,
emissive: 0xffd700,
emissiveIntensity: 0.3
});
const lapis = new THREE.MeshStandardMaterial({
color: 0x1e3a5f,
metalness: 0.3,
roughness: 0.6
});
// Ground - sand
const groundGeo = new THREE.PlaneGeometry(200, 200);
const groundMat = new THREE.MeshStandardMaterial({ color: 0xd4b896, roughness: 1 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.1;
ground.receiveShadow = true;
scene.add(ground);
// ============== KARNAK TEMPLE STRUCTURE ==============
// First Pylon (massive gateway) - like Karnak
const pylon1Geo = new THREE.BoxGeometry(40, 25, 5);
const pylon1 = new THREE.Mesh(pylon1Geo, sandstone);
pylon1.position.set(0, 12.5, 25);
pylon1.castShadow = true;
pylon1.receiveShadow = true;
scene.add(pylon1);
// Pylon decorations (pillars on pylon)
for (let i = 0; i < 6; i++) {
const pillarGeo = new THREE.BoxGeometry(2, 22, 2);
const pillar = new THREE.Mesh(pillarGeo, darkSandstone);
pillar.position.set(-18 + i * 7, 11, 28);
pillar.castShadow = true;
scene.add(pillar);
}
// Courtyard columns - Lotus columns like Karnak
const columnPositions = [
[-15, 0], [-10, 0], [-5, 0], [0, 0], [5, 0], [10, 0], [15, 0],
[-15, -10], [-10, -10], [-5, -10], [0, -10], [5, -10], [10, -10], [15, -10],
];
columnPositions.forEach(([x, z]) => {
// Column shaft (bundled papyrus style)
const shaftGeo = new THREE.CylinderGeometry(1.2, 1.3, 18, 16);
const shaft = new THREE.Mesh(shaftGeo, sandstone);
shaft.position.set(x, 9, z);
shaft.castShadow = true;
scene.add(shaft);
// Column capital (open lotus)
const capitalGeo = new THREE.ConeGeometry(2, 4, 16);
const capital = new THREE.Mesh(capitalGeo, sandstone);
capital.position.set(x, 20, z);
capital.castShadow = true;
scene.add(capital);
});
// Hypostyle Hall (second section with smaller columns)
const hypostyleCols = [
[-12, -18], [-6, -18], [0, -18], [6, -18], [12, -18],
[-12, -22], [-6, -22], [0, -22], [6, -22], [12, -22],
];
hypostyleCols.forEach(([x, z]) => {
const shaftGeo = new THREE.CylinderGeometry(0.8, 0.9, 14, 12);
const shaft = new THREE.Mesh(shaftGeo, darkSandstone);
shaft.position.set(x, 7, z);
shaft.castShadow = true;
scene.add(shaft);
});
// Second Pylon
const pylon2Geo = new THREE.BoxGeometry(30, 20, 4);
const pylon2 = new THREE.Mesh(pylon2Geo, sandstone);
pylon2.position.set(0, 10, -25);
pylon2.castShadow = true;
scene.add(pylon2);
// Third Pylon
const pylon3Geo = new THREE.BoxGeometry(25, 18, 3);
const pylon3 = new THREE.Mesh(pylon3Geo, darkSandstone);
pylon3.position.set(0, 9, -35);
pylon3.castShadow = true;
scene.add(pylon3);
// Sacred Lake (water feature)
const lakeGeo = new THREE.CircleGeometry(8, 32);
const lakeMat = new THREE.MeshStandardMaterial({
color: 0x1e5f8f,
metalness: 0.3,
roughness: 0.2,
transparent: true,
opacity: 0.8
});
const lake = new THREE.Mesh(lakeGeo, lakeMat);
lake.rotation.x = -Math.PI / 2;
lake.position.set(-25, 0.1, 5);
scene.add(lake);
// Obelisks
const obeliskGeo = new THREE.CylinderGeometry(0.5, 0.8, 20, 4);
const obelisk1 = new THREE.Mesh(obeliskGeo, darkSandstone);
obelisk1.position.set(-8, 10, -25);
obelisk1.castShadow = true;
scene.add(obelisk1);
const obelisk2 = new THREE.Mesh(obeliskGeo, darkSandstone);
obelisk2.position.set(8, 10, -25);
obelisk2.castShadow = true;
scene.add(obelisk2);
// Golden pyramid cap on obelisks
const capGeo = new THREE.ConeGeometry(1.2, 3, 4);
const cap1 = new THREE.Mesh(capGeo, gold);
cap1.position.set(-8, 21.5, -25);
scene.add(cap1);
const cap2 = new THREE.Mesh(capGeo, gold);
cap2.position.set(8, 21.5, -25);
scene.add(cap2);
// Sphinx Avenue (ram-headed sphinxes leading to entrance)
for (let i = 0; i < 6; i++) {
// Body
const sphinxGeo = new THREE.BoxGeometry(3, 2, 6);
const sphinx = new THREE.Mesh(sphinxGeo, sandstone);
sphinx.position.set(-12 + i * 5, 1, 40 + (i % 2) * 3);
sphinx.castShadow = true;
scene.add(sphinx);
// Head
const headGeo = new THREE.SphereGeometry(1.2, 16, 16);
const head = new THREE.Mesh(headGeo, sandstone);
head.position.set(-12 + i * 5, 2.5, 42 + (i % 2) * 3);
head.castShadow = true;
scene.add(head);
}
// Holy of Holies - Sanctuary (dark room at center)
const sanctuaryGeo = new THREE.BoxGeometry(15, 12, 10);
const sanctuary = new THREE.Mesh(sanctuaryGeo, darkSandstone);
sanctuary.position.set(0, 6, -42);
sanctuary.castShadow = true;
scene.add(sanctuary);
// Solar barque (boat) in sanctuary area
const boatGeo = new THREE.BoxGeometry(12, 0.5, 3);
const boat = new THREE.Mesh(boatGeo, lapis);
boat.position.set(0, 0.5, -42);
scene.add(boat);
// Golden Eye of Horus at sanctuary entrance
const eyeGeo = new THREE.TorusGeometry(3, 0.5, 8, 32);
const eye = new THREE.Mesh(eyeGeo, gold);
eye.position.set(0, 5, -36.5);
scene.add(eye);
// Eye center
const eyeCenterGeo = new THREE.SphereGeometry(1.5, 16, 16);
const eyeCenter = new THREE.Mesh(eyeCenterGeo, new THREE.MeshStandardMaterial({
color: 0x000000,
emissive: 0xffd700,
emissiveIntensity: 0.2
}));
eyeCenter.position.set(0, 5, -36);
scene.add(eyeCenter);
// ============== EGYPTIAN AGENTS (Animated) ==============
const agents = [
{ name: "ANUBIS", symbol: "🐕", color: 0x1a1a1a, glow: 0xffd700, pos: [0, 1.5, 42], room: "AVENUE OF SPHINXES", role: "Guardian of Leads" },
{ name: "PTAH", symbol: "🔨", color: 0xf5deb3, glow: 0xffd700, pos: [-12, 1.5, 0], room: "COURTYARD", role: "Builder" },
{ name: "SESHAT", symbol: "📝", color: 0x228b22, glow: 0x228b22, pos: [-6, 1.5, 0], room: "COURTYARD", role: "Scribe" },
{ name: "SEKHMET", symbol: "🦁", color: 0xb22222, glow: 0xb22222, pos: [6, 1.5, 0], room: "COURTYARD", role: "Warrior" },
{ name: "THOTH", symbol: "🐦", color: 0xffffff, glow: 0x40e0d0, pos: [-6, 1.5, -18], room: "HYPOSTYLE HALL", role: "Knowledge Keeper" },
{ name: "MAAT", symbol: "⚖️", color: 0x40e0d0, glow: 0x40e0d0, pos: [6, 1.5, -18], room: "HYPOSTYLE HALL", role: "Truth" },
{ name: "CLEOPATRA", symbol: "👑", color: 0x9b59b6, glow: 0x9b59b6, pos: [0, 1.5, -22], room: "SECOND PYLON", role: "Voice of the Queen" },
{ name: "HORUS", symbol: "👁️", color: 0xffd700, glow: 0xffd700, pos: [0, 4, -42], room: "HOLY OF HOLIES", role: "Lord of the Sky" },
];
const agentMeshes: any[] = [];
agents.forEach((agent, i) => {
// Create Egyptian-styled agent (hieroglyphic figure shape)
const group = new THREE.Group();
// Body (wrapped mummy style for some, human for others)
const bodyGeo = new THREE.CylinderGeometry(0.3, 0.5, 1.5, 8);
const bodyMat = new THREE.MeshStandardMaterial({
color: agent.color,
emissive: agent.color,
emissiveIntensity: 0.4,
metalness: 0.2,
roughness: 0.6
});
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.y = 0.75;
group.add(body);
// Head (sphere for all, representing hieroglyphic style)
const headGeo = new THREE.SphereGeometry(0.5, 16, 16);
const head = new THREE.Mesh(headGeo, bodyMat);
head.position.y = 1.8;
group.add(head);
// Symbol on chest (floating element)
const symbolGeo = new THREE.CircleGeometry(0.3, 16);
const symbolMat = new THREE.MeshBasicMaterial({ color: agent.glow });
const symbol = new THREE.Mesh(symbolGeo, symbolMat);
symbol.position.set(0, 1, 0.35);
group.add(symbol);
// Glow aura
const glowGeo = new THREE.SphereGeometry(1.2, 16, 16);
const glowMat = new THREE.MeshBasicMaterial({
color: agent.glow,
transparent: true,
opacity: 0.15
});
const glow = new THREE.Mesh(glowGeo, glowMat);
glow.position.y = 1.2;
group.add(glow);
group.position.set(agent.pos[0], agent.pos[1], agent.pos[2]);
group.userData = { name: agent.name, symbol: agent.symbol, role: agent.role, room: agent.room };
group.castShadow = true;
scene.add(group);
agentMeshes.push(group);
});
// ============== ANIMATION ==============
let time = 0;
const clock = new THREE.Clock();
const animate = () => {
if (cancelled) return;
requestAnimationFrame(animate);
const delta = clock.getDelta();
time += delta;
// Animate agents - floating and bobbing motion
agentMeshes.forEach((agent, i) => {
const originalY = agents[i].pos[1];
const bobSpeed = 1 + i * 0.2;
const bobAmount = 0.3;
// Floating bob
agent.position.y = originalY + Math.sin(time * bobSpeed) * bobAmount;
// Gentle rotation
agent.rotation.y = Math.sin(time * 0.5 + i) * 0.2;
// Symbol pulsing glow
const child = agent.children[2]; // symbol
if (child && child.material) {
child.material.opacity = 0.3 + Math.sin(time * 2 + i) * 0.2;
}
});
// Animate Eye of Horus
eye.rotation.z = Math.sin(time * 0.3) * 0.1;
eyeCenter.material.emissiveIntensity = 0.2 + Math.sin(time * 2) * 0.1;
// Animate lake water
lake.material.opacity = 0.7 + Math.sin(time * 0.5) * 0.1;
renderer.render(scene, camera);
};
animate();
// ============== CONTROLS ==============
let isDragging = false;
let theta = 0;
let phi = Math.PI / 6;
let target = new THREE.Vector3(0, 5, -20);
let radius = 50;
const updateCamera = () => {
camera.position.x = target.x + radius * Math.sin(phi) * Math.cos(theta);
camera.position.y = target.y + radius * Math.cos(phi);
camera.position.z = target.z + radius * Math.sin(phi) * Math.sin(theta);
camera.lookAt(target);
};
const onPointerDown = (e: PointerEvent) => {
isDragging = true;
};
const onPointerUp = () => {
isDragging = false;
};
const onPointerMove = (e: PointerEvent) => {
if (!isDragging) return;
theta -= e.movementX * 0.005;
phi = Math.max(0.1, Math.min(Math.PI / 2, phi + e.movementY * 0.005));
updateCamera();
};
const onWheel = (e: WheelEvent) => {
radius = Math.max(20, Math.min(100, radius + e.deltaY * 0.05));
updateCamera();
};
container.addEventListener("pointerdown", onPointerDown);
container.addEventListener("pointerup", onPointerUp);
container.addEventListener("pointermove", onPointerMove);
container.addEventListener("wheel", onWheel);
// Click detection
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const onClick = (e: MouseEvent) => {
const rect = container.getBoundingClientRect();
mouse.x = ((e.clientX - rect.left) / container.clientWidth) * 2 - 1;
mouse.y = -((e.clientY - rect.top) / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(agentMeshes, true);
// Find parent group
for (const hit of intersects) {
let obj: any = hit.object;
while (obj.parent && !obj.userData.name) {
obj = obj.parent;
}
if (obj.userData.name) {
setSelectedAgent(obj.userData);
break;
}
}
};
container.addEventListener("click", onClick);
updateCamera();
setLoading(false);
// Handle resize
const handleResize = () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
};
window.addEventListener("resize", handleResize);
// Cleanup
return () => {
cancelled = true;
window.removeEventListener("resize", handleResize);
container.removeEventListener("pointerdown", onPointerDown);
container.removeEventListener("pointerup", onPointerUp);
container.removeEventListener("pointermove", onPointerMove);
container.removeEventListener("wheel", onWheel);
container.removeEventListener("click", onClick);
if (container.contains(renderer.domElement)) {
container.removeChild(renderer.domElement);
}
renderer.dispose();
};
});
}, []);
return (
<div className="min-h-screen bg-gradient-to-b from-stone-950 to-stone-900">
{/* Header */}
<div className="text-center py-3 px-4 bg-black/30">
<h1 className="text-2xl md:text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-amber-200 via-yellow-400 to-amber-200">
🏛 TEMPLE OF AI - KARNAK
</h1>
<p className="text-amber-300/60 text-xs mt-1">
Drag to orbit Scroll to zoom Click agents for details
</p>
</div>
{/* Loading */}
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-stone-950 z-50">
<div className="text-center">
<div className="text-6xl mb-4 animate-pulse">🏛</div>
<p className="text-amber-300">Building Karnak Temple...</p>
</div>
</div>
)}
{/* 3D Canvas */}
<div
ref={containerRef}
className="w-full h-[calc(100vh-100px)]"
style={{ cursor: "grab", touchAction: "none" }}
/>
{/* Agent Info Panel */}
{selectedAgent && (
<div className="fixed bottom-4 left-4 right-4 md:left-auto md:right-4 md:w-80 bg-black/90 backdrop-blur-xl rounded-2xl p-5 border border-amber-500/40 shadow-2xl z-50">
<div className="flex items-start justify-between mb-3">
<div>
<span className="text-4xl">{selectedAgent.symbol}</span>
</div>
<button
onClick={() => setSelectedAgent(null)}
className="text-amber-400 hover:text-amber-300 text-2xl leading-none"
>
×
</button>
</div>
<h3 className="text-xl font-bold text-amber-200 mb-1">{selectedAgent.name}</h3>
<p className="text-amber-400/60 text-sm mb-3">{selectedAgent.role}</p>
<div className="bg-stone-800/50 rounded-lg p-3">
<p className="text-xs text-amber-300/60 mb-1">Location</p>
<p className="text-amber-200 font-medium">{selectedAgent.room}</p>
</div>
</div>
)}
{/* Bottom Legend */}
<div className="fixed bottom-4 left-4 bg-black/70 backdrop-blur rounded-xl p-3 border border-amber-500/20">
<h4 className="text-amber-200 font-bold text-sm mb-2">🏛 Temple Map</h4>
<div className="text-xs text-amber-100/70 space-y-1">
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-500"></span>
Avenue of Sphinxes
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-600"></span>
First Pylon
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-700"></span>
Courtyard
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-800"></span>
Hypostyle Hall
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-yellow-600"></span>
Sanctuary
</div>
</div>
</div>
</div>
);
}