Files
agent-framework/visualize-3d.html
T

285 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Framework • 3D Sphere</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body { margin: 0; overflow: hidden; }
#canvas-container { width: 100%; height: 100vh; }
.label {
position: absolute;
color: #a1a1aa;
font-size: 11px;
pointer-events: none;
text-shadow: 0 1px 3px rgba(0,0,0,0.9);
font-family: ui-monospace, monospace;
}
</style>
</head>
<body class="bg-zinc-950 text-zinc-200">
<div class="absolute top-6 left-6 z-10">
<div>
<div class="text-2xl font-semibold tracking-tight">Agent Framework</div>
<div class="text-xs text-zinc-500">3D Phase Visualization</div>
</div>
</div>
<div id="canvas-container"></div>
<!-- Details Panel -->
<div class="absolute top-6 right-6 w-80 bg-zinc-900/90 backdrop-blur border border-zinc-800 rounded-3xl p-6 z-20 hidden" id="details-panel">
<div id="details-content"></div>
</div>
<div class="absolute bottom-6 left-6 text-[10px] text-zinc-500 z-10">
Drag to rotate • Scroll to zoom • Click nodes
</div>
<script>
const phaseData = {
"Research": { prompt: "prompts/research.md", outputs: "SPEC.md", description: "Explores requirements and produces a clear specification." },
"Design": { prompt: "prompts/design.md", outputs: "DESIGN.md", description: "Produces data models, flows, architecture, and risks." },
"Implement": { prompt: "prompts/implement.md", outputs: "Code + tests", description: "Builds the solution using SPEC.md and DESIGN.md." },
"Bug Find": { prompt: "prompts/bug_finder.md", outputs: "BUG_REPORT.md", description: "Adversarially discovers bugs and spec deviations." },
"Disprove": { prompt: "prompts/disprover.md", outputs: "DISPROVALS.md", description: "Attempts to disprove findings from the bug report." },
"Referee": { prompt: "prompts/referee.md", outputs: "VERDICT.md", description: "Judges implementation and compares Bug Finder vs Disprover." },
"Onboard": { prompt: "prompts/onboarding.md", outputs: "Framework files", description: "Initializes the agent framework in a project." },
"Orchestrate": { prompt: "prompts/orchestrate.md", outputs: "Recommendation", description: "Suggests the next logical phase based on state." },
"Compaction": { prompt: "prompts/compaction.md", outputs: "Clean rules", description: "Consolidates and deduplicates rules." }
};
const phases = Object.keys(phaseData);
let scene, camera, renderer;
let nodes = [];
let lines = [];
let raycaster, mouse;
function initThreeJS() {
const container = document.getElementById('canvas-container');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// Sphere
const sphereGeometry = new THREE.SphereGeometry(2.4, 64, 64);
const sphereMaterial = new THREE.MeshPhongMaterial({
color: 0x18181b,
wireframe: true,
transparent: true,
opacity: 0.12
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 0.9);
pointLight.position.set(10, 10, 10);
scene.add(pointLight);
camera.position.z = 6.5;
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// Create nodes + connections
createNodesOnSphere();
createConnections();
// Events
window.addEventListener('resize', onWindowResize);
container.addEventListener('click', onClick);
// Orbit controls (simple)
let isDragging = false;
let previousMouseX = 0, previousMouseY = 0;
container.addEventListener('mousedown', (e) => {
isDragging = true;
previousMouseX = e.clientX;
previousMouseY = e.clientY;
});
window.addEventListener('mouseup', () => isDragging = false);
container.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaMove = { x: e.clientX - previousMouseX, y: e.clientY - previousMouseY };
const deltaRotation = new THREE.Quaternion().setFromEuler(
new THREE.Euler(deltaMove.y * 0.004, deltaMove.x * 0.004, 0, 'XYZ')
);
sphere.quaternion.multiplyQuaternions(deltaRotation, sphere.quaternion);
nodes.forEach(n => n.position.applyQuaternion(deltaRotation));
lines.forEach(line => {
line.geometry.dispose();
const newGeo = new THREE.BufferGeometry().setFromPoints([
line.userData.from.position,
line.userData.to.position
]);
line.geometry = newGeo;
});
previousMouseX = e.clientX;
previousMouseY = e.clientY;
});
container.addEventListener('wheel', (e) => {
camera.position.z += e.deltaY * 0.008;
camera.position.z = Math.max(3.5, Math.min(11, camera.position.z));
});
animate();
}
function createNodesOnSphere() {
const radius = 2.4;
const nodeCount = phases.length;
phases.forEach((phase, i) => {
const phi = Math.acos(-1 + (2 * i) / nodeCount);
const theta = Math.sqrt(nodeCount * Math.PI) * phi;
const x = radius * Math.sin(phi) * Math.cos(theta);
const y = radius * Math.sin(phi) * Math.sin(theta);
const z = radius * Math.cos(phi);
const geometry = new THREE.SphereGeometry(0.11, 16, 16);
const material = new THREE.MeshPhongMaterial({
color: 0x3b82f6,
emissive: 0x1e40af
});
const nodeMesh = new THREE.Mesh(geometry, material);
nodeMesh.position.set(x, y, z);
nodeMesh.userData = { phase: phase };
scene.add(nodeMesh);
nodes.push(nodeMesh);
// Label
const label = document.createElement('div');
label.className = 'label';
label.textContent = phase;
document.getElementById('canvas-container').appendChild(label);
nodeMesh.userData.label = label;
});
}
function createConnections() {
const connections = [
["Research", "Design"],
["Design", "Implement"],
["Implement", "Bug Find"],
["Bug Find", "Disprove"],
["Disprove", "Referee"],
["Referee", "Implement"],
["Onboard", "Research"],
["Orchestrate", "Research"],
["Orchestrate", "Design"],
["Orchestrate", "Implement"],
["Orchestrate", "Bug Find"],
["Orchestrate", "Referee"],
["Compaction", "Orchestrate"]
];
const lineMaterial = new THREE.LineBasicMaterial({
color: 0x3f3f46,
transparent: true,
opacity: 0.6
});
connections.forEach(([fromName, toName]) => {
const fromNode = nodes.find(n => n.userData.phase === fromName);
const toNode = nodes.find(n => n.userData.phase === toName);
if (!fromNode || !toNode) return;
const geometry = new THREE.BufferGeometry().setFromPoints([
fromNode.position,
toNode.position
]);
const line = new THREE.Line(geometry, lineMaterial);
line.userData = { from: fromNode, to: toNode };
scene.add(line);
lines.push(line);
});
}
function onClick(event) {
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(nodes);
if (intersects.length > 0) {
const phase = intersects[0].object.userData.phase;
showDetails(phase);
}
}
function showDetails(phase) {
const panel = document.getElementById('details-panel');
const data = phaseData[phase];
panel.classList.remove('hidden');
panel.innerHTML = `
<div class="flex justify-between items-start mb-4">
<div>
<div class="text-xl font-semibold">${phase}</div>
<div class="text-xs text-blue-400 font-mono mt-1">${data.prompt}</div>
</div>
<button onclick="hideDetails()" class="text-zinc-500 hover:text-white text-xl">×</button>
</div>
<div class="text-sm leading-relaxed text-zinc-300">${data.description}</div>
<div class="mt-6">
<div class="text-xs uppercase tracking-wider text-zinc-500 mb-2">Outputs</div>
<div class="text-sm font-mono bg-zinc-950 px-4 py-2 rounded-2xl border border-zinc-800">${data.outputs}</div>
</div>
`;
}
function hideDetails() {
document.getElementById('details-panel').classList.add('hidden');
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
// Update labels
nodes.forEach(node => {
if (node.userData.label) {
const vector = node.position.clone();
vector.project(camera);
const x = (vector.x * 0.5 + 0.5) * window.innerWidth;
const y = (-vector.y * 0.5 + 0.5) * window.innerHeight;
node.userData.label.style.left = `${x}px`;
node.userData.label.style.top = `${y}px`;
}
});
}
function init() {
initThreeJS();
}
window.onload = init;
</script>
</body>
</html>