nwo-agent-graph / static /index.html
CPater's picture
Upload index.html
099d7f9 verified
Raw
History Blame Contribute Delete
33 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>NWO Agent Graph</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@supabase/supabase-js@2/dist/umd/supabase.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-force-graph-3d"></script>
<script src="https://cdn.jsdelivr.net/npm/react-force-graph-2d"></script>
<style>
:root{
--bg:#0a130a;
--bg-2:#0d1a0d;
--bg-3:#112011;
--panel:#0e180e;
--border:rgba(151,196,89,0.18);
--border-strong:rgba(151,196,89,0.35);
--text:#ffffff;
--text-2:#cfe0cf;
--text-3:#9db09d;
--text-4:#5a6b5a;
--brand:#97c459;
--brand-deep:#1D9E75;
--brand-soft:rgba(151,196,89,0.12);
--brand-mid:rgba(151,196,89,0.22);
--alert:#c4a459;
--critical:#c95959;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html,body,#root{height:100%;width:100%;overflow:hidden}
body{background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,sans-serif;font-size:14px}
::-webkit-scrollbar{width:5px;height:5px}
::-webkit-scrollbar-track{background:var(--bg-2)}
::-webkit-scrollbar-thumb{background:rgba(151,196,89,0.25);border-radius:2px}
::-webkit-scrollbar-thumb:hover{background:rgba(151,196,89,0.45)}
input,select,textarea{background:var(--bg-2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;padding:8px 10px;outline:none;font-family:inherit;width:100%}
input:focus,select:focus,textarea:focus{border-color:var(--brand)}
button{cursor:pointer;font-family:inherit}
button:disabled{opacity:.4;cursor:not-allowed}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
</style>
</head>
<body>
<div id="root"></div>
<script>
const CFG = {
supabaseUrl: 'https://kbweprgbawghpzfpxiav.supabase.co',
supabaseAnon: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imtid2VwcmdiYXdnaHB6ZnB4aWF2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzYwMjM4MjcsImV4cCI6MjA5MTU5OTgyN30.fxtsT5JZ0bV_GgTuMi_0RqEAAdnGctmPdxLy-fSQ4u4',
};
const sb = supabase.createClient(CFG.supabaseUrl, CFG.supabaseAnon, {
auth:{persistSession:true,autoRefreshToken:true}
});
(async function(){
const hash = window.location.hash;
if(!hash.includes('access_token')) return;
const p = new URLSearchParams(hash.slice(1));
const at = p.get('access_token'), rt = p.get('refresh_token');
if(at && rt){
try{ await sb.auth.setSession({access_token:at, refresh_token:rt}); }catch(e){}
history.replaceState(null,'',window.location.pathname);
}
})();
const ACTOR_COLOR = {human:'#97c459', agent:'#1D9E75', robot:'#4a8f5c', cron:'#2d5a3f'};
const actorColor = t => ACTOR_COLOR[t] || '#688068';
const ACTOR_ICON = {human:'👤', agent:'✦', robot:'⬡', cron:'⏱'};
const {createElement:h,useState,useEffect,useRef,useCallback,useMemo} = React;
const ForceGraph3D = window.ForceGraph3D;
const ForceGraph2D = window.ForceGraph2D;
function LoginPage(){
const [email,setEmail]=useState('');
const [sent,setSent]=useState(false);
const [loading,setLoading]=useState(false);
const [err,setErr]=useState('');
async function send(){
if(!email.trim())return;
setLoading(true);setErr('');
const{error}=await sb.auth.signInWithOtp({email:email.trim(),options:{emailRedirectTo:"https://huggingface.co/spaces/CPater/nwo-agent-graph"}});
if(error)setErr(error.message);else setSent(true);
setLoading(false);
}
return h('div',{style:{position:'fixed',inset:0,background:'rgba(10,19,10,0.92)',backdropFilter:'blur(4px)',display:'flex',alignItems:'center',justifyContent:'center',padding:16,zIndex:200}},
h('div',{style:{background:'var(--panel)',border:'1px solid var(--border-strong)',borderRadius:16,padding:'36px 32px',width:380,maxWidth:'100%',boxShadow:'0 20px 60px rgba(0,0,0,0.5)'}},
h('div',{style:{fontSize:28,textAlign:'center',marginBottom:4,color:'var(--brand)'}},'⬡'),
h('div',{style:{fontSize:20,fontWeight:500,textAlign:'center',marginBottom:6,color:'var(--text)'}},'NWO Agent Graph'),
h('div',{style:{fontSize:13,color:'var(--text-3)',textAlign:'center',marginBottom:28}},'Sign in to access your private graph and manage robots'),
err&&h('div',{style:{padding:'8px 12px',borderRadius:6,background:'rgba(201,89,89,0.12)',border:'1px solid rgba(201,89,89,0.35)',color:'var(--critical)',fontSize:12,marginBottom:12}},err),
sent
?h('div',{style:{padding:'10px 12px',borderRadius:8,background:'var(--brand-soft)',border:'1px solid var(--border-strong)',color:'var(--brand)',fontSize:13}},'✉ Magic link sent — check your email')
:h('div',null,
h('label',{style:{fontSize:11,color:'var(--text-3)',display:'block',marginBottom:4,letterSpacing:1}},'EMAIL'),
h('input',{type:'email',placeholder:'you@example.com',value:email,onChange:e=>setEmail(e.target.value),onKeyDown:e=>e.key==='Enter'&&send(),autoFocus:true,style:{marginBottom:12}}),
h('button',{onClick:send,disabled:loading||!email,style:{width:'100%',padding:'11px 0',borderRadius:8,border:'1px solid var(--border-strong)',background:'var(--brand-soft)',color:'var(--brand)',fontSize:14,fontWeight:500}},loading?'Sending…':'Send magic link')),
h('div',{style:{marginTop:20,fontSize:11,color:'var(--text-4)',textAlign:'center',lineHeight:1.6}},'Public graph is visible without signing in')
)
);
}
function ActorBadge({type,name,compact}){
const icon = ACTOR_ICON[type] || '?';
const color = actorColor(type);
return h('span',{style:{display:'inline-flex',alignItems:'center',gap:3,padding:compact?'1px 6px':'2px 8px',borderRadius:4,fontSize:compact?10:11,fontWeight:500,background:'rgba(151,196,89,0.08)',color:'var(--text-2)',border:'1px solid var(--border)'}},
h('span',{style:{color}}, icon),' ',name||type);
}
function FeedPanel({userId,onNodeSelect}){
const [posts,setPosts]=useState([]);
const [filter,setFilter]=useState('all');
const [draft,setDraft]=useState('');
const [sending,setSending]=useState(false);
useEffect(()=>{
sb.from('graph_posts')
.select('id,content,actor_type,nwo_agent_id,created_at,metadata,node_id,visibility,graph_nodes(name,category,color)')
.order('created_at',{ascending:false}).limit(60)
.then(({data})=>setPosts(data||[]));
const ch=sb.channel('feed_rt')
.on('postgres_changes',{event:'INSERT',schema:'public',table:'graph_posts'},p=>{
setPosts(prev=>[p.new,...prev].slice(0,200));
}).subscribe();
return()=>sb.removeChannel(ch);
},[]);
async function post(){
if(!draft.trim())return;
setSending(true);
await sb.from('graph_posts').insert({content:draft.trim(),actor_type:'human',actor_id:userId,visibility:'public'});
setDraft('');setSending(false);
}
const FILTERS=['all','human','agent','robot'];
const visible=filter==='all'?posts:posts.filter(p=>p.actor_type===filter);
return h('div',{style:{display:'flex',flexDirection:'column',height:'100%',background:'var(--panel)',borderLeft:'1px solid var(--border)'}},
h('div',{style:{padding:'10px 14px',borderBottom:'1px solid var(--border)',display:'flex',alignItems:'center',justifyContent:'space-between'}},
h('span',{style:{fontSize:11,fontWeight:600,color:'var(--text-3)',letterSpacing:1}},'FEED'),
h('span',{style:{fontSize:10,color:'var(--text-4)'}},posts.length,' posts')
),
h('div',{style:{display:'flex',gap:4,padding:'6px 10px',borderBottom:'1px solid var(--border)',flexWrap:'wrap'}},
FILTERS.map(f=>h('button',{key:f,onClick:()=>setFilter(f),style:{padding:'2px 8px',borderRadius:4,fontSize:10,border:`1px solid ${filter===f?'var(--brand)':'var(--border)'}`,background:filter===f?'var(--brand-soft)':'transparent',color:filter===f?'var(--brand)':'var(--text-3)'}},f,' (',f==='all'?posts.length:posts.filter(p=>p.actor_type===f).length,')'))
),
h('div',{style:{flex:1,overflowY:'auto'}},
visible.length===0&&h('div',{style:{padding:20,color:'var(--text-4)',fontSize:12}},'No posts yet.'),
visible.map(p=>h('div',{key:p.id,style:{padding:'10px 14px',borderBottom:'1px solid var(--border)'}},
h('div',{style:{display:'flex',alignItems:'center',gap:6,marginBottom:5,flexWrap:'wrap'}},
h(ActorBadge,{type:p.actor_type,name:p.nwo_agent_id?.slice(0,10),compact:true}),
h('span',{style:{fontSize:10,color:'var(--text-4)',marginLeft:'auto'}},new Date(p.created_at).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}))
),
h('div',{style:{fontSize:13,color:'var(--text-2)',lineHeight:1.5}},p.content),
p.graph_nodes?.name&&h('div',{onClick:()=>onNodeSelect&&onNodeSelect(p.node_id),style:{fontSize:11,color:'var(--brand)',marginTop:4,cursor:'pointer'}},'→ ',p.graph_nodes.name),
p.visibility==='private'&&h('span',{style:{fontSize:9,color:'var(--text-4)'}},' 🔒')
))
),
h('div',{style:{padding:'10px 14px',borderTop:'1px solid var(--border)'}},
h('textarea',{rows:2,placeholder:'Post to the graph feed…',value:draft,onChange:e=>setDraft(e.target.value),onKeyDown:e=>e.key==='Enter'&&e.ctrlKey&&post(),style:{marginBottom:6,resize:'none'}}),
h('button',{onClick:post,disabled:sending||!draft.trim(),style:{padding:'5px 14px',borderRadius:5,border:'1px solid var(--border-strong)',background:'var(--brand-soft)',color:'var(--brand)',fontSize:12}},sending?'Posting…':'Post ↵')
)
);
}
function GraphCanvas({nodes,links,onNodeClick,width,height,mode}){
const fgRef=useRef(null);
const data=useMemo(()=>({
nodes:nodes.map(n=>({...n,_color:n.color||actorColor(n.actor_type)})),
links:links.map(l=>({...l}))
}),[nodes,links]);
if(mode==='3d' && ForceGraph3D){
return h(ForceGraph3D,{
ref:fgRef,
graphData:data,
width:width,
height:height,
backgroundColor:'#0a130a',
nodeId:'id',
nodeLabel:n=>`${n.name}${n.description?'\n'+n.description.slice(0,120):''}`,
nodeRelSize:6,
nodeVal:n=>(n.val||1)*1.5,
nodeColor:n=>n._color,
nodeOpacity:0.92,
nodeResolution:16,
linkColor:()=>'rgba(151,196,89,0.35)',
linkWidth:l=>Math.max(0.5,(l.value||0.5)*1.5),
linkOpacity:0.45,
linkDirectionalParticles:2,
linkDirectionalParticleSpeed:0.004,
linkDirectionalParticleWidth:1.2,
linkDirectionalParticleColor:()=>'#97c459',
onNodeClick:onNodeClick,
onNodeDragEnd:node=>{
node.fx=node.x; node.fy=node.y; node.fz=node.z;
sb.from('graph_nodes').update({x_position:node.x,y_position:node.y}).eq('id',node.id);
},
enableNodeDrag:true,
enableNavigationControls:true,
showNavInfo:false,
warmupTicks:50,
cooldownTicks:100,
});
}
if(ForceGraph2D){
return h(ForceGraph2D,{
ref:fgRef,
graphData:data,
width:width,
height:height,
backgroundColor:'#0a130a',
nodeId:'id',
nodeLabel:n=>n.name,
nodeRelSize:6,
nodeVal:n=>(n.val||1)*1.5,
nodeColor:n=>n._color,
linkColor:()=>'rgba(151,196,89,0.3)',
linkWidth:l=>Math.max(0.5,(l.value||0.5)*1.5),
onNodeClick:onNodeClick,
nodeCanvasObjectMode:()=>'after',
nodeCanvasObject:(node,ctx,scale)=>{
const label = node.name && node.name.length > 22 ? node.name.slice(0,20)+'…' : (node.name||'');
const r = Math.sqrt((node.val||1)*1.5) * 6;
ctx.font = `${11/scale}px system-ui,sans-serif`;
ctx.fillStyle = 'rgba(255,255,255,0.8)';
ctx.textAlign = 'center';
ctx.fillText(label, node.x, node.y + r + 10/scale);
}
});
}
return h('div',{style:{padding:20,color:'var(--text-3)'}},'Loading graph library…');
}
function NodeDetail({node,onClose,userId,onTogglePrivacy,onNodeSelect,allNodes,allLinks}){
const [msg,setMsg]=useState('');
const [children,setChildren]=useState([]);
const [grandchildren,setGrandchildren]=useState({});
const [loadingChildren,setLoadingChildren]=useState(true);
const isOwner=userId&&node.owner_user_id===userId;
const isPrivate=node.visibility==='private';
useEffect(()=>{
setLoadingChildren(true);
const directLinks=(allLinks||[]).filter(l=>{
const src=l.source?.id||l.source;
return src===node.id;
});
const childIds=new Set(directLinks.map(l=>l.target?.id||l.target));
const childNodes=(allNodes||[]).filter(n=>childIds.has(n.id));
setChildren(childNodes);
const gc={};
childNodes.forEach(child=>{
const gcLinks=(allLinks||[]).filter(l=>(l.source?.id||l.source)===child.id);
const gcIds=new Set(gcLinks.map(l=>l.target?.id||l.target));
gc[child.id]=(allNodes||[]).filter(n=>gcIds.has(n.id));
});
setGrandchildren(gc);
setLoadingChildren(false);
},[node.id,allNodes,allLinks]);
async function requestExpand(){
setMsg('Queuing…');
let queued=false;
try{
const r=await fetch('https://redciprianpater-nwo-agent-graph.hf.space/graph/expand',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({node_id:node.id})});
if(r.ok){queued=true;}
}catch(e){}
if(!queued){
const{error}=await sb.from('graph_nodes').update({expand_requested:true,expand_done:false}).eq('id',node.id);
if(!error)queued=true;
}
setMsg(queued?'✦ Queued — new nodes appear within 60 seconds':'Could not queue — try signing in first');
setTimeout(()=>setMsg(''),6000);
}
async function togglePrivacy(){
const v=isPrivate?'public':'private';
await sb.from('graph_nodes').update({visibility:v}).eq('id',node.id);
onTogglePrivacy?.(node.id,v);
setMsg('Visibility updated');setTimeout(()=>setMsg(''),3000);
}
return h('div',{style:{position:'absolute',right:0,top:0,bottom:0,width:320,background:'var(--panel)',borderLeft:'1px solid var(--border-strong)',overflowY:'auto',zIndex:10,boxShadow:'-12px 0 28px rgba(0,0,0,0.35)'}},
h('div',{style:{padding:'14px 16px',borderBottom:'1px solid var(--border)',position:'sticky',top:0,background:'var(--panel)',zIndex:1}},
h('button',{onClick:onClose,style:{position:'absolute',top:12,right:12,background:'none',border:'none',color:'var(--text-3)',fontSize:22,cursor:'pointer',lineHeight:1}},'×'),
h('div',{style:{marginBottom:8}},h(ActorBadge,{type:node.actor_type})),
h('div',{style:{fontSize:16,fontWeight:500,color:'var(--text)',marginBottom:4,paddingRight:24}},node.name),
h('div',{style:{display:'flex',gap:8,alignItems:'center',flexWrap:'wrap'}},
node.category&&h('span',{style:{fontSize:10,color:'var(--text-3)',background:'var(--brand-soft)',padding:'2px 6px',borderRadius:3,border:'1px solid var(--border)'}},node.category),
h('span',{style:{fontSize:10,color:isPrivate?'var(--brand)':'var(--text-4)'}},isPrivate?'🔒 Private':'🌐 Public')
)
),
h('div',{style:{padding:'12px 16px'}},
node.description&&h('div',{style:{fontSize:12,color:'var(--text-2)',lineHeight:1.6,marginBottom:12,background:'var(--bg-2)',padding:'8px 10px',borderRadius:6,border:'1px solid var(--border)'}},node.description),
node.battery_level!=null&&h('div',{style:{marginBottom:12}},
h('div',{style:{fontSize:10,color:'var(--text-3)',marginBottom:4,letterSpacing:1}},'BATTERY'),
h('div',{style:{height:5,borderRadius:3,background:'var(--bg-3)',overflow:'hidden',marginBottom:3}},
h('div',{style:{height:'100%',width:`${node.battery_level}%`,background:node.battery_level<20?'var(--critical)':node.battery_level<50?'var(--alert)':'var(--brand)',borderRadius:3}})
),
h('div',{style:{fontSize:11,color:'var(--text-2)'}},node.battery_level.toFixed(0),'%')
),
node.nwo_agent_id&&h('div',{style:{marginBottom:12}},
h('div',{style:{fontSize:10,color:'var(--text-3)',marginBottom:3,letterSpacing:1}},'NWO AGENT'),
h('div',{style:{fontSize:10,color:'var(--text-2)',fontFamily:'monospace',wordBreak:'break-all',background:'var(--bg-2)',padding:'4px 8px',borderRadius:4,border:'1px solid var(--border)'}},node.nwo_agent_id)
),
h('div',{style:{marginBottom:12}},
h('div',{style:{fontSize:10,color:'var(--text-3)',letterSpacing:1,marginBottom:8,display:'flex',alignItems:'center',justifyContent:'space-between'}},
'EXPANSION TREE',
h('span',{style:{fontSize:10,color:children.length?'var(--brand)':'var(--text-4)'}},children.length,' nodes')
),
loadingChildren&&h('div',{style:{fontSize:11,color:'var(--text-4)',padding:'6px 0'}},'Loading…'),
children.length===0&&!loadingChildren&&h('div',{style:{fontSize:11,color:'var(--text-4)',padding:'8px 10px',border:'1px dashed var(--border)',borderRadius:6,textAlign:'center',lineHeight:1.5}},
'No child nodes yet.',h('br'),
h('span',{style:{fontSize:10}},'Click "Queue BitNet expansion" below')
),
children.map(child=>{
const gc=grandchildren[child.id]||[];
const cColor=child.color||actorColor(child.actor_type);
return h('div',{key:child.id,style:{marginBottom:6}},
h('div',{onClick:()=>onNodeSelect&&onNodeSelect(child),
style:{display:'flex',alignItems:'flex-start',gap:8,padding:'7px 10px',borderRadius:6,background:'var(--bg-2)',border:'1px solid var(--border)',cursor:'pointer',transition:'background 0.15s'},
onMouseEnter:e=>e.currentTarget.style.background='var(--bg-3)',
onMouseLeave:e=>e.currentTarget.style.background='var(--bg-2)'},
h('div',{style:{width:8,height:8,borderRadius:'50%',background:cColor,flexShrink:0,marginTop:3}}),
h('div',{style:{flex:1,minWidth:0}},
h('div',{style:{fontSize:12,color:'var(--text)',fontWeight:500,marginBottom:2,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}},child.name),
child.description&&h('div',{style:{fontSize:10,color:'var(--text-3)',lineHeight:1.4,overflow:'hidden',textOverflow:'ellipsis',display:'-webkit-box',WebkitLineClamp:2,WebkitBoxOrient:'vertical'}},child.description)
),
child.category&&h('span',{style:{fontSize:9,color:'var(--text-3)',background:'var(--brand-soft)',padding:'1px 4px',borderRadius:2,flexShrink:0}},child.category)
),
gc.length>0&&h('div',{style:{marginLeft:16,marginTop:3}},
gc.map(gc_node=>h('div',{key:gc_node.id,onClick:()=>onNodeSelect&&onNodeSelect(gc_node),
style:{display:'flex',alignItems:'center',gap:6,padding:'5px 8px',borderRadius:4,background:'var(--bg-2)',border:'1px solid var(--border)',cursor:'pointer',marginBottom:3},
onMouseEnter:e=>e.currentTarget.style.background='var(--bg-3)',
onMouseLeave:e=>e.currentTarget.style.background='var(--bg-2)'},
h('div',{style:{width:6,height:6,borderRadius:'50%',background:gc_node.color||actorColor(gc_node.actor_type),flexShrink:0}}),
h('div',{style:{fontSize:11,color:'var(--text-2)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',flex:1}},gc_node.name)
))
)
);
})
),
h('div',{style:{borderTop:'1px solid var(--border)',paddingTop:12}},
h('div',{style:{fontSize:10,color:'var(--text-3)',letterSpacing:1,marginBottom:8}},'ACTIONS'),
h('button',{onClick:requestExpand,style:{display:'block',width:'100%',padding:'8px 10px',marginBottom:6,borderRadius:6,border:'1px solid var(--border-strong)',background:'var(--brand-soft)',color:'var(--brand)',fontSize:12,textAlign:'left'}},'✦ Queue BitNet expansion'),
(isOwner||!userId)&&h('button',{onClick:togglePrivacy,style:{display:'block',width:'100%',padding:'8px 10px',marginBottom:6,borderRadius:6,border:`1px solid ${isPrivate?'var(--border-strong)':'var(--border)'}`,background:isPrivate?'var(--brand-soft)':'transparent',color:isPrivate?'var(--brand)':'var(--text-3)',fontSize:12,textAlign:'left'}},isPrivate?'🌐 Make public':'🔒 Make private'),
msg&&h('div',{style:{marginTop:6,padding:'6px 10px',borderRadius:5,background:'var(--bg-2)',border:'1px solid var(--border)',fontSize:11,color:'var(--text-2)'}},msg)
)
)
);
}
function AddNodeModal({onAdd,onClose}){
const [name,setName]=useState('');
const [desc,setDesc]=useState('');
const [cat,setCat]=useState('topic');
const [priv,setPriv]=useState(false);
const [saving,setSaving]=useState(false);
async function add(){
if(!name.trim())return;
setSaving(true);
await onAdd(name.trim(),desc.trim(),cat,priv);
setSaving(false);onClose();
}
return h('div',{onClick:e=>e.target===e.currentTarget&&onClose(),
style:{position:'fixed',inset:0,background:'rgba(10,19,10,0.8)',backdropFilter:'blur(4px)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:100}},
h('div',{style:{background:'var(--panel)',border:'1px solid var(--border-strong)',borderRadius:14,padding:24,width:380,maxWidth:'90vw',boxShadow:'0 20px 60px rgba(0,0,0,0.5)'}},
h('div',{style:{fontSize:15,fontWeight:500,color:'var(--text)',marginBottom:16}},'Add graph node'),
h('label',{style:{fontSize:11,color:'var(--text-3)',display:'block',marginBottom:4,letterSpacing:1}},'NAME'),
h('input',{value:name,onChange:e=>setName(e.target.value),placeholder:'Topic or concept',autoFocus:true,style:{marginBottom:10}}),
h('label',{style:{fontSize:11,color:'var(--text-3)',display:'block',marginBottom:4,letterSpacing:1}},'DESCRIPTION'),
h('textarea',{rows:2,value:desc,onChange:e=>setDesc(e.target.value),placeholder:'Optional',style:{marginBottom:10,resize:'none'}}),
h('label',{style:{fontSize:11,color:'var(--text-3)',display:'block',marginBottom:4,letterSpacing:1}},'CATEGORY'),
h('select',{value:cat,onChange:e=>setCat(e.target.value),style:{marginBottom:10}},['topic','event','observation','task','inference'].map(c=>h('option',{key:c,value:c},c))),
h('label',{style:{display:'flex',alignItems:'center',gap:8,marginBottom:16,cursor:'pointer',fontSize:12,color:'var(--text-2)'}},
h('input',{type:'checkbox',checked:priv,onChange:e=>setPriv(e.target.checked),style:{width:'auto'}}),
'🔒 Private node (only visible to you)'
),
h('div',{style:{display:'flex',gap:8,justifyContent:'flex-end'}},
h('button',{onClick:onClose,style:{padding:'7px 16px',borderRadius:6,border:'1px solid var(--border)',background:'transparent',color:'var(--text-3)',fontSize:13}},'Cancel'),
h('button',{onClick:add,disabled:!name.trim()||saving,style:{padding:'7px 16px',borderRadius:6,border:'1px solid var(--border-strong)',background:'var(--brand-soft)',color:'var(--brand)',fontSize:13}},saving?'Adding…':'Add node')
)
)
);
}
function App(){
const [user,setUser]=useState(undefined);
const [graphData,setGraphData]=useState({nodes:[],links:[]});
const [filter,setFilter]=useState('all');
const [selected,setSelected]=useState(null);
const [showAdd,setShowAdd]=useState(false);
const [showLogin,setShowLogin]=useState(false);
const [showFeed,setShowFeed]=useState(true);
const [liveMode,setLiveMode]=useState(false);
const [viewMode,setViewMode]=useState('3d');
const [dims,setDims]=useState({w:800,h:600});
const canvasWrap=useRef(null);
useEffect(()=>{
sb.auth.getSession().then(({data:{session}})=>setUser(session?.user||null));
const{data:{subscription}}=sb.auth.onAuthStateChange((_,s)=>setUser(s?.user||null));
return()=>subscription.unsubscribe();
},[]);
useEffect(()=>{loadGraph();},[user]);
async function loadGraph(){
let nodes=null,links=null;
try{
const r=await sb.from('graph_nodes').select('id,name,val,color,depth_level,description,category,actor_type,visibility,owner_user_id,x_position,y_position,nwo_agent_id,battery_level,created_at').order('created_at',{ascending:false}).limit(500);
nodes=r.data;
}catch(e){
const r=await sb.from('graph_nodes').select('id,name,val,color,depth_level,description,category,actor_type,x_position,y_position,nwo_agent_id,battery_level,created_at').order('created_at',{ascending:false}).limit(500);
nodes=r.data;
}
try{
const r=await sb.from('graph_links').select('source_id,target_id,similarity_score,link_type').limit(1000);
links=r.data;
}catch(e){links=[];}
setGraphData({
nodes:(nodes||[]).map(n=>({...n,x:n.x_position,y:n.y_position,val:parseFloat(n.val)||1})),
links:(links||[]).map(l=>({source:l.source_id,target:l.target_id,value:parseFloat(l.similarity_score)||0.5}))
});
}
useEffect(()=>{
const nodeCh=sb.channel('nodes_rt')
.on('postgres_changes',{event:'INSERT',schema:'public',table:'graph_nodes'},p=>{
setLiveMode(true);
const newNode={...p.new,x:p.new.x_position||null,y:p.new.y_position||null,val:parseFloat(p.new.val)||1};
setGraphData(prev=>({
nodes:prev.nodes.find(n=>n.id===newNode.id)?prev.nodes:[newNode,...prev.nodes].slice(0,500),
links:prev.links
}));
}).subscribe();
const linkCh=sb.channel('links_rt')
.on('postgres_changes',{event:'INSERT',schema:'public',table:'graph_links'},p=>{
const newLink={source:p.new.source_id,target:p.new.target_id,value:parseFloat(p.new.similarity_score)||0.5,link_type:p.new.link_type};
setGraphData(prev=>({
nodes:prev.nodes,
links:prev.links.find(l=>(l.source?.id||l.source)===newLink.source&&(l.target?.id||l.target)===newLink.target)?prev.links:[...prev.links,newLink]
}));
}).subscribe();
const poll=setInterval(()=>{
sb.from('graph_nodes').select('id,name,val,color,depth_level,description,category,actor_type,visibility,owner_user_id,x_position,y_position,nwo_agent_id,battery_level,created_at').order('created_at',{ascending:false}).limit(500)
.then(({data})=>{
if(!data)return;
setGraphData(prev=>{
const existingIds=new Set(prev.nodes.map(n=>n.id));
const newNodes=data.filter(n=>!existingIds.has(n.id)).map(n=>({...n,x:n.x_position||null,y:n.y_position||null,val:parseFloat(n.val)||1}));
if(newNodes.length===0)return prev;
setLiveMode(true);
return{nodes:[...newNodes,...prev.nodes].slice(0,500),links:prev.links};
});
sb.from('graph_links').select('source_id,target_id,similarity_score,link_type').limit(1000)
.then(({data:ldata})=>{
if(!ldata)return;
setGraphData(prev=>({nodes:prev.nodes,links:ldata.map(l=>({source:l.source_id,target:l.target_id,value:parseFloat(l.similarity_score)||0.5,link_type:l.link_type}))}));
});
});
},30000);
return()=>{sb.removeChannel(nodeCh);sb.removeChannel(linkCh);clearInterval(poll);};
},[]);
useEffect(()=>{
const obs=new ResizeObserver(e=>{
const{width,height}=e[0].contentRect;
setDims({w:width,h:height});
});
if(canvasWrap.current)obs.observe(canvasWrap.current);
return()=>obs.disconnect();
},[]);
const visible=filter==='all'?graphData.nodes:graphData.nodes.filter(n=>n.actor_type===filter);
const vids=new Set(visible.map(n=>n.id));
const vlinks=graphData.links.filter(l=>vids.has(l.source?.id||l.source)&&vids.has(l.target?.id||l.target));
async function addNode(name,desc,cat,isPrivate){
const{data}=await sb.from('graph_nodes').insert({
name,description:desc,category:cat,val:2,color:'#97c459',
depth_level:0,actor_type:'human',user_id:user?.id,
owner_user_id:user?.id,visibility:isPrivate?'private':'public',
expand_requested:true,expand_done:false
}).select().single();
if(data)setGraphData(prev=>({nodes:[{...data,x:null,y:null,val:2},...prev.nodes],links:prev.links}));
}
function handleTogglePrivacy(id,vis){
setGraphData(prev=>({...prev,nodes:prev.nodes.map(n=>n.id===id?{...n,visibility:vis}:n)}));
if(selected?.id===id)setSelected(s=>({...s,visibility:vis}));
}
if(user===undefined)return h('div',{style:{height:'100vh',display:'flex',alignItems:'center',justifyContent:'center',color:'var(--text-3)'}},'Loading…');
return h('div',{style:{display:'flex',flexDirection:'column',height:'100vh',background:'var(--bg)'}},
h('div',{style:{display:'flex',alignItems:'center',gap:10,padding:'6px 14px',background:'var(--bg-2)',borderBottom:'1px solid var(--border)',flexWrap:'wrap'}},
h('span',{style:{fontSize:16,color:'var(--brand)'}},'⬡'),
h('span',{style:{fontSize:13,fontWeight:500,color:'var(--text)'}},'NWO Agent Graph'),
h('div',{style:{width:1,height:16,background:'var(--border)'}}),
['all','human','agent','robot'].map(f=>h('button',{key:f,onClick:()=>setFilter(f),style:{padding:'3px 10px',borderRadius:4,border:`1px solid ${filter===f?'var(--brand)':'var(--border)'}`,background:filter===f?'var(--brand-soft)':'transparent',color:filter===f?'var(--brand)':'var(--text-3)',fontSize:11}},f)),
h('button',{onClick:()=>user?setShowAdd(true):setShowLogin(true),style:{padding:'4px 12px',borderRadius:5,border:'1px solid var(--border-strong)',background:'var(--brand-soft)',color:'var(--brand)',fontSize:12}},'+ Node'),
h('button',{onClick:loadGraph,style:{padding:'4px 8px',borderRadius:4,border:'1px solid var(--border)',background:'transparent',color:'var(--text-3)',fontSize:12}},'↻'),
h('div',{style:{display:'flex',border:'1px solid var(--border)',borderRadius:4,overflow:'hidden'}},
['3d','2d'].map(m=>h('button',{key:m,onClick:()=>setViewMode(m),style:{padding:'3px 10px',background:viewMode===m?'var(--brand-soft)':'transparent',color:viewMode===m?'var(--brand)':'var(--text-3)',border:'none',fontSize:11,textTransform:'uppercase',letterSpacing:1}},m))
),
liveMode&&h('span',{style:{fontSize:10,color:'var(--brand)',display:'flex',alignItems:'center',gap:4}},
h('span',{style:{width:6,height:6,borderRadius:'50%',background:'var(--brand)',display:'inline-block',animation:'pulse 2s ease-in-out infinite'}}),'LIVE'),
h('span',{style:{fontSize:11,color:'var(--text-4)'}},visible.length,' nodes'),
h('div',{style:{marginLeft:'auto',display:'flex',alignItems:'center',gap:8}},
user
?h('div',{style:{display:'flex',alignItems:'center',gap:8}},
h('span',{style:{fontSize:11,color:'var(--text-3)'}},user.email?.slice(0,24)),
h('button',{onClick:()=>sb.auth.signOut(),style:{fontSize:11,padding:'3px 8px',border:'1px solid var(--border)',borderRadius:4,background:'transparent',color:'var(--text-3)'}},'Sign out'))
:h('button',{onClick:()=>setShowLogin(true),style:{fontSize:12,padding:'4px 12px',border:'1px solid var(--border-strong)',borderRadius:5,background:'var(--brand-soft)',color:'var(--brand)'}},'Sign in')
)
),
h('div',{style:{display:'flex',flex:1,overflow:'hidden',position:'relative'}},
h('div',{ref:canvasWrap,style:{flex:1,position:'relative',overflow:'hidden',background:'var(--bg)'}},
dims.w>0 && h(GraphCanvas,{nodes:visible,links:vlinks,onNodeClick:setSelected,width:dims.w,height:dims.h,mode:viewMode}),
h('div',{style:{position:'absolute',bottom:12,left:12,display:'flex',gap:8,flexWrap:'wrap',pointerEvents:'none'}},
[['human','👤'],['agent','✦'],['robot','⬡'],['cron','⏱']].map(([t,i])=>{
const c=actorColor(t);
return h('span',{key:t,style:{fontSize:10,color:'var(--text-2)',background:'rgba(10,19,10,0.72)',padding:'3px 8px',borderRadius:4,border:'1px solid var(--border)',display:'inline-flex',alignItems:'center',gap:4}},
h('span',{style:{width:8,height:8,borderRadius:'50%',background:c,display:'inline-block'}}),
h('span',{style:{color:'var(--text-2)'}}, i,' ',t)
);
})
),
selected&&h(NodeDetail,{node:selected,onClose:()=>setSelected(null),userId:user?.id,onTogglePrivacy:handleTogglePrivacy,onNodeSelect:setSelected,allNodes:graphData.nodes,allLinks:graphData.links})
),
h('button',{onClick:()=>setShowFeed(v=>!v),style:{position:'absolute',right:showFeed?320:0,top:'50%',transform:'translateY(-50%)',zIndex:20,background:'var(--panel)',border:'1px solid var(--border)',borderRadius:showFeed?'6px 0 0 6px':'0 6px 6px 0',color:'var(--text-3)',padding:'8px 3px',fontSize:10,width:16}},showFeed?'▶':'◀'),
showFeed&&h('div',{style:{width:320,minWidth:320}},
h(FeedPanel,{userId:user?.id,onNodeSelect:id=>{const n=graphData.nodes.find(n=>n.id===id);if(n)setSelected(n);}})
)
),
showLogin&&h(LoginPage,null),
showAdd&&h(AddNodeModal,{onAdd:addNode,onClose:()=>setShowAdd(false)})
);
}
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(App));
</script>
</body>
</html>