466 lines
15 KiB
PHP
Executable File
466 lines
15 KiB
PHP
Executable File
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="csrf-token" content="<?php echo e(csrf_token()); ?>">
|
|
<title>Fox Brain</title>
|
|
<?php echo app('Illuminate\Foundation\Vite')(['resources/css/app.css']); ?>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
background: #04060a;
|
|
color: #e2e8f0;
|
|
font-family: 'JetBrains Mono', 'Share Tech Mono', monospace;
|
|
min-height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.brain-root {
|
|
display: grid;
|
|
grid-template-columns: 260px 1fr;
|
|
grid-template-rows: 48px 1fr;
|
|
height: 100vh;
|
|
}
|
|
|
|
/* ── Topbar ── */
|
|
.brain-topbar {
|
|
grid-column: 1 / -1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 0 20px;
|
|
background: rgba(79,70,229,0.06);
|
|
border-bottom: 0.5px solid rgba(79,70,229,0.25);
|
|
}
|
|
.brain-title {
|
|
font-size: 11px;
|
|
letter-spacing: 3px;
|
|
color: rgba(165,180,252,0.9);
|
|
text-transform: uppercase;
|
|
}
|
|
.brain-topbar-nav {
|
|
margin-left: auto;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
.brain-nav-link {
|
|
font-size: 9px;
|
|
letter-spacing: 1.5px;
|
|
color: rgba(255,255,255,0.3);
|
|
text-decoration: none;
|
|
padding: 4px 10px;
|
|
border: 0.5px solid rgba(255,255,255,0.1);
|
|
border-radius: 3px;
|
|
transition: all 0.15s;
|
|
}
|
|
.brain-nav-link:hover {
|
|
color: rgba(165,180,252,0.9);
|
|
border-color: rgba(79,70,229,0.4);
|
|
}
|
|
|
|
/* ── Sidebar ── */
|
|
.brain-sidebar {
|
|
padding: 20px 16px;
|
|
border-right: 0.5px solid rgba(79,70,229,0.2);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.stat-card {
|
|
background: rgba(79,70,229,0.06);
|
|
border: 0.5px solid rgba(79,70,229,0.2);
|
|
border-radius: 8px;
|
|
padding: 14px 16px;
|
|
}
|
|
.stat-label {
|
|
font-size: 8px;
|
|
letter-spacing: 2px;
|
|
color: rgba(255,255,255,0.3);
|
|
text-transform: uppercase;
|
|
margin-bottom: 6px;
|
|
}
|
|
.stat-value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
color: rgba(165,180,252,0.95);
|
|
line-height: 1;
|
|
}
|
|
.stat-card.pending .stat-value {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
.sidebar-section {
|
|
font-size: 8px;
|
|
letter-spacing: 2px;
|
|
color: rgba(255,255,255,0.2);
|
|
text-transform: uppercase;
|
|
padding: 8px 0 4px;
|
|
border-top: 0.5px solid rgba(255,255,255,0.05);
|
|
}
|
|
|
|
.legend {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 9px;
|
|
color: rgba(255,255,255,0.5);
|
|
}
|
|
.legend-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ── Graph Canvas ── */
|
|
.brain-graph-wrap {
|
|
position: relative;
|
|
overflow: hidden;
|
|
min-height: 0; /* grid fix: allow shrinking */
|
|
}
|
|
|
|
#fox-brain-graph {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
|
|
#fox-brain-graph canvas {
|
|
display: block;
|
|
width: 100% !important;
|
|
height: 100% !important;
|
|
}
|
|
|
|
/* Node tooltip */
|
|
.brain-tooltip {
|
|
position: fixed;
|
|
background: #0d0d1a;
|
|
border: 0.5px solid rgba(79,70,229,0.4);
|
|
border-radius: 6px;
|
|
padding: 8px 12px;
|
|
font-size: 10px;
|
|
pointer-events: none;
|
|
z-index: 999;
|
|
max-width: 220px;
|
|
display: none;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.6);
|
|
}
|
|
.brain-tooltip.visible { display: block; }
|
|
.tooltip-type {
|
|
font-size: 8px;
|
|
letter-spacing: 1.5px;
|
|
color: rgba(165,180,252,0.6);
|
|
text-transform: uppercase;
|
|
margin-bottom: 2px;
|
|
}
|
|
.tooltip-title {
|
|
color: #e2e8f0;
|
|
font-weight: 600;
|
|
margin-bottom: 4px;
|
|
}
|
|
.tooltip-content {
|
|
font-size: 9px;
|
|
color: rgba(255,255,255,0.4);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Empty state */
|
|
.brain-empty {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
color: rgba(255,255,255,0.15);
|
|
font-size: 11px;
|
|
letter-spacing: 2px;
|
|
pointer-events: none;
|
|
}
|
|
.brain-empty-icon {
|
|
font-size: 40px;
|
|
opacity: 0.2;
|
|
}
|
|
|
|
/* Scrollbar */
|
|
::-webkit-scrollbar { width: 4px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: rgba(79,70,229,0.3); border-radius: 2px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="brain-root">
|
|
|
|
<header class="brain-topbar">
|
|
<div class="brain-title">F · O · X / BRAIN</div>
|
|
<nav class="brain-topbar-nav">
|
|
<a href="<?php echo e(route('hud')); ?>" class="brain-nav-link">HUD</a>
|
|
<a href="<?php echo e(route('dashboard')); ?>" class="brain-nav-link">DASHBOARD</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<aside class="brain-sidebar">
|
|
<div class="stat-card">
|
|
<div class="stat-label">Memory Nodes</div>
|
|
<div class="stat-value"><?php echo e($nodes_count); ?></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">Connections</div>
|
|
<div class="stat-value"><?php echo e($edges_count); ?></div>
|
|
</div>
|
|
<div class="stat-card pending">
|
|
<div class="stat-label">Pending Review</div>
|
|
<div class="stat-value"><?php echo e($pending_count); ?></div>
|
|
</div>
|
|
|
|
<div class="sidebar-section">Node Types</div>
|
|
<div class="legend" id="brain-legend">
|
|
|
|
</div>
|
|
|
|
<div class="sidebar-section">Controls</div>
|
|
<div class="legend">
|
|
<div class="legend-item">Scroll — zoom</div>
|
|
<div class="legend-item">Drag node — move</div>
|
|
<div class="legend-item">Hover — details</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="brain-graph-wrap">
|
|
<div id="fox-brain-graph"></div>
|
|
|
|
<div class="brain-empty" id="brain-empty" style="display:none">
|
|
<div class="brain-empty-icon">◎</div>
|
|
<div>NO MEMORY NODES YET</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="brain-tooltip" id="brain-tooltip">
|
|
<div class="tooltip-type" id="tt-type"></div>
|
|
<div class="tooltip-title" id="tt-title"></div>
|
|
<div class="tooltip-content" id="tt-content"></div>
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
'use strict';
|
|
|
|
const TYPE_COLORS={user:'#818cf8',device:'#22d3ee',place:'#34d399',project:'#f59e0b',rule:'#f87171',skill:'#a78bfa',fact:'#94a3b8',preference:'#fb7185',person:'#60a5fa'};
|
|
function typeColor(t){return TYPE_COLORS[String(t||'').toLowerCase()]||'#94a3b8';}
|
|
|
|
function fibSphere(n,r){
|
|
const g=Math.PI*(3-Math.sqrt(5)),pts=[];
|
|
for(let i=0;i<n;i++){
|
|
const y=1-(i/(n-1))*2,rad=Math.sqrt(1-y*y),phi=g*i;
|
|
pts.push({x:Math.cos(phi)*rad*r,y:y*r,z:Math.sin(phi)*rad*r});
|
|
}
|
|
return pts;
|
|
}
|
|
|
|
function project(p,cx,cy,fov,ry,rx){
|
|
const cy2=Math.cos(ry),sy2=Math.sin(ry);
|
|
let x=p.x*cy2-p.z*sy2,z=p.x*sy2+p.z*cy2,y=p.y;
|
|
const cx2=Math.cos(rx),sx2=Math.sin(rx);
|
|
const y2=y*cx2-z*sx2,z2=y*sx2+z*cx2;
|
|
const d=fov/(fov+z2+300);
|
|
return{sx:cx+x*d,sy:cy+y2*d,depth:d,z:z2};
|
|
}
|
|
|
|
function buildLegend(nodes){
|
|
const el=document.getElementById('brain-legend');
|
|
if(!el)return;
|
|
const types=[...new Set(nodes.map(n=>n.type))];
|
|
el.innerHTML=types.map(t=>`<div class="legend-item"><div class="legend-dot" style="background:${typeColor(t)}"></div><span>${t}</span></div>`).join('');
|
|
}
|
|
|
|
function start(rawNodes,rawEdges){
|
|
const container=document.getElementById('fox-brain-graph');
|
|
if(!container)return;
|
|
|
|
const canvas=document.createElement('canvas');
|
|
canvas.style.cssText='position:absolute;inset:0;display:block;';
|
|
container.appendChild(canvas);
|
|
const ctx=canvas.getContext('2d');
|
|
let W=0,H=0,RADIUS=0,dpr=1;
|
|
|
|
function resize(){
|
|
const r=container.getBoundingClientRect();
|
|
W=r.width||window.innerWidth;
|
|
H=r.height||window.innerHeight;
|
|
dpr=window.devicePixelRatio||1;
|
|
canvas.width=Math.round(W*dpr);
|
|
canvas.height=Math.round(H*dpr);
|
|
canvas.style.width=W+'px';
|
|
canvas.style.height=H+'px';
|
|
ctx.setTransform(dpr,0,0,dpr,0,0);
|
|
RADIUS=Math.min(W,H)*0.32;
|
|
}
|
|
resize();
|
|
new ResizeObserver(resize).observe(container);
|
|
|
|
// Mesh points + proximity pairs
|
|
const MESH_N=420;
|
|
let meshPts=[],pairs=[];
|
|
|
|
function buildMesh(){
|
|
meshPts=fibSphere(MESH_N,RADIUS);
|
|
const LDIST=RADIUS*0.28;
|
|
pairs=[];
|
|
for(let i=0;i<MESH_N;i++){
|
|
let c=0;
|
|
for(let j=i+1;j<MESH_N&&c<5;j++){
|
|
const dx=meshPts[i].x-meshPts[j].x,dy=meshPts[i].y-meshPts[j].y,dz=meshPts[i].z-meshPts[j].z;
|
|
if(Math.sqrt(dx*dx+dy*dy+dz*dz)<LDIST){pairs.push([i,j]);c++;}
|
|
}
|
|
}
|
|
}
|
|
buildMesh();
|
|
|
|
// Memory nodes
|
|
const maxW=Math.max(...rawNodes.map(n=>n.weight),1);
|
|
const nodePts=rawNodes.length?fibSphere(rawNodes.length,RADIUS):[];
|
|
const nodeData=rawNodes.map((n,i)=>({...n,pt:nodePts[i],color:typeColor(n.type),r:7+(n.weight/maxW)*11}));
|
|
const edgeList=rawEdges.map(e=>{
|
|
const si=rawNodes.findIndex(n=>n.id===e.source),ti=rawNodes.findIndex(n=>n.id===e.target);
|
|
return(si>=0&&ti>=0)?{si,ti,strength:e.strength??1}:null;
|
|
}).filter(Boolean);
|
|
|
|
let rotY=0.4,rotX=0.12,velY=0,velX=0;
|
|
let isDrag=false,lx=0,ly=0,scale=1.0;
|
|
let mouseX=-9999,mouseY=-9999,hovered=-1;
|
|
const FOV=520;
|
|
|
|
const tooltip=document.getElementById('brain-tooltip');
|
|
const ttType=document.getElementById('tt-type');
|
|
const ttTitle=document.getElementById('tt-title');
|
|
const ttContent=document.getElementById('tt-content');
|
|
|
|
canvas.addEventListener('mousedown',e=>{isDrag=true;lx=e.clientX;ly=e.clientY;});
|
|
window.addEventListener('mouseup',()=>{isDrag=false;});
|
|
canvas.addEventListener('mousemove',e=>{
|
|
const rect=canvas.getBoundingClientRect();
|
|
mouseX=e.clientX-rect.left;mouseY=e.clientY-rect.top;
|
|
if(isDrag){velY+=(e.clientX-lx)*0.006;velX+=(e.clientY-ly)*0.006;lx=e.clientX;ly=e.clientY;}
|
|
if(tooltip){tooltip.style.left=Math.min(e.clientX+14,window.innerWidth-240)+'px';tooltip.style.top=(e.clientY-10)+'px';}
|
|
});
|
|
canvas.addEventListener('mouseleave',()=>{mouseX=-9999;mouseY=-9999;});
|
|
canvas.addEventListener('wheel',e=>{scale=Math.max(0.45,Math.min(1.8,scale-e.deltaY*0.001));},{passive:true});
|
|
|
|
let prevRadius=RADIUS;
|
|
|
|
function draw(){
|
|
requestAnimationFrame(draw);
|
|
|
|
// Rebuild mesh if radius changed after resize
|
|
if(Math.abs(RADIUS-prevRadius)>1){buildMesh();prevRadius=RADIUS;}
|
|
|
|
rotY+=isDrag?velY:0.0018+velY;
|
|
rotX+=velX;
|
|
velY*=0.91;velX*=0.91;
|
|
|
|
const s=scale,cx=W/2,cy=H/2;
|
|
|
|
ctx.fillStyle='#030508';
|
|
ctx.fillRect(0,0,W,H);
|
|
|
|
const proj=meshPts.map(p=>project({x:p.x*s,y:p.y*s,z:p.z*s},cx,cy,FOV,rotY,rotX));
|
|
|
|
// Lines
|
|
for(const[a,b]of pairs){
|
|
const pa=proj[a],pb=proj[b];
|
|
const avgZ=(pa.z+pb.z)/2;
|
|
const alpha=Math.max(0.03,0.03+((avgZ+RADIUS)/(RADIUS*2))*0.20);
|
|
ctx.beginPath();
|
|
ctx.moveTo(pa.sx,pa.sy);
|
|
ctx.lineTo(pb.sx,pb.sy);
|
|
ctx.strokeStyle=`rgba(255,255,255,${alpha})`;
|
|
ctx.lineWidth=0.6;
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Dots
|
|
for(const p of proj){
|
|
const front=p.z>-RADIUS*0.05;
|
|
ctx.beginPath();
|
|
ctx.arc(p.sx,p.sy,front?1.4:0.7,0,Math.PI*2);
|
|
ctx.fillStyle=front?'rgba(255,255,255,0.65)':'rgba(255,255,255,0.15)';
|
|
ctx.fill();
|
|
}
|
|
|
|
// Node projections
|
|
const np=nodeData.map(n=>project({x:n.pt.x*s,y:n.pt.y*s,z:n.pt.z*s},cx,cy,FOV,rotY,rotX));
|
|
|
|
// Data edges
|
|
for(const e of edgeList){
|
|
const pa=np[e.si],pb=np[e.ti];
|
|
ctx.beginPath();ctx.moveTo(pa.sx,pa.sy);ctx.lineTo(pb.sx,pb.sy);
|
|
ctx.strokeStyle=`rgba(255,255,255,${0.30+e.strength*0.22})`;
|
|
ctx.lineWidth=1.2+e.strength*0.4;ctx.stroke();
|
|
}
|
|
|
|
// Memory nodes
|
|
let newHov=-1;
|
|
for(let i=0;i<nodeData.length;i++){
|
|
const n=nodeData[i],p=np[i];
|
|
const nr=n.r*s;
|
|
const dx=mouseX-p.sx,dy=mouseY-p.sy;
|
|
if(Math.sqrt(dx*dx+dy*dy)<nr+8)newHov=i;
|
|
const isHov=hovered===i,fr=nr*(isHov?1.6:1);
|
|
// Glow
|
|
const g=ctx.createRadialGradient(p.sx,p.sy,0,p.sx,p.sy,fr*3);
|
|
g.addColorStop(0,n.color+(isHov?'bb':'44'));
|
|
g.addColorStop(1,n.color+'00');
|
|
ctx.beginPath();ctx.arc(p.sx,p.sy,fr*3,0,Math.PI*2);ctx.fillStyle=g;ctx.fill();
|
|
// Circle
|
|
ctx.beginPath();ctx.arc(p.sx,p.sy,fr,0,Math.PI*2);
|
|
ctx.fillStyle=n.color+'cc';ctx.fill();
|
|
ctx.strokeStyle=n.color;ctx.lineWidth=1.8;ctx.stroke();
|
|
// Label
|
|
const fs=Math.max(9,Math.round(11*s));
|
|
ctx.font=(isHov?`bold ${fs}px`:`${fs}px`)+' "JetBrains Mono",monospace';
|
|
ctx.fillStyle=isHov?'#fff':'rgba(226,232,240,0.8)';
|
|
ctx.textAlign='center';ctx.textBaseline='top';
|
|
ctx.fillText(n.label,p.sx,p.sy+fr+6);
|
|
ctx.textBaseline='alphabetic';
|
|
}
|
|
|
|
if(newHov!==hovered){
|
|
hovered=newHov;
|
|
if(tooltip){
|
|
if(hovered>=0){
|
|
const d=nodeData[hovered];
|
|
ttType.textContent=String(d.type||'').toUpperCase();
|
|
ttTitle.textContent=d.label;
|
|
ttContent.textContent=d.content?d.content.slice(0,160)+(d.content.length>160?'…':''):`weight: ${d.weight}`;
|
|
tooltip.classList.add('visible');
|
|
}else{tooltip.classList.remove('visible');}
|
|
}
|
|
}
|
|
canvas.style.cursor=hovered>=0?'pointer':'default';
|
|
}
|
|
draw();
|
|
buildLegend(rawNodes);
|
|
}
|
|
|
|
// Load data and start
|
|
fetch('/fox/brain/graph')
|
|
.then(r=>r.json())
|
|
.then(d=>start(d.nodes||[],d.edges||[]))
|
|
.catch(()=>{const e=document.getElementById('brain-empty');if(e)e.style.display='flex';});
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
<?php /**PATH /var/www/resources/views/fox/brain/index.blade.php ENDPATH**/ ?>
|