Spaces:
Running
Running
Komalpreet Kaur
style: reduce knowledge graph tooltip node and link label font sizes
5cf6adc unverified | import { useEffect, useState, useRef } from 'react'; | |
| import ForceGraph3D from 'react-force-graph-3d'; | |
| import * as THREE from 'three'; | |
| import { apiFetch } from '../api'; | |
| import './KnowledgeGraph.css'; | |
| // Stunning conceptual mock graph representing SOMA's cognitive architecture | |
| const MOCK_GRAPH = { | |
| nodes: [ | |
| { id: 'SOMA', label: 'SOMA (Core)', connections: 10, type: 'core' }, | |
| { id: 'Cortex', label: 'Cortex Layer', connections: 8, type: 'core' }, | |
| { id: 'Thalamus', label: 'Thalamus (Routing)', connections: 6, type: 'entity' }, | |
| { id: 'Hippocampus', label: 'Hippocampus', connections: 7, type: 'entity' }, | |
| { id: 'Neocortex', label: 'Neocortex', connections: 6, type: 'entity' }, | |
| { id: 'Sensory Cortex', label: 'Sensory Cortex', connections: 4, type: 'concept' }, | |
| { id: 'Working Memory', label: 'Working Memory', connections: 5, type: 'concept' }, | |
| { id: 'Episodic Memory', label: 'Episodic Memory', connections: 4, type: 'concept' }, | |
| { id: 'Sleep Cycle', label: 'Sleep Cycle', connections: 3, type: 'method' }, | |
| { id: 'Neural Inscription', label: 'Inscription Layer', connections: 3, type: 'method' }, | |
| { id: 'Llama 3.1', label: 'Llama 3.1', connections: 3, type: 'metric' }, | |
| { id: 'Groq API', label: 'Groq API', connections: 2, type: 'metric' } | |
| ], | |
| links: [ | |
| { source: 'SOMA', target: 'Cortex', label: 'ORCHESTRATES' }, | |
| { source: 'SOMA', target: 'Neural Inscription', label: 'ACCEPTS' }, | |
| { source: 'Cortex', target: 'Thalamus', label: 'ROUTES_BY' }, | |
| { source: 'Cortex', target: 'Hippocampus', label: 'CONSOLIDATES' }, | |
| { source: 'Cortex', target: 'Neocortex', label: 'STORES_IN' }, | |
| { source: 'Sensory Cortex', target: 'Hippocampus', label: 'WRITES_TO' }, | |
| { source: 'Working Memory', target: 'Thalamus', label: 'SYNCS_WITH' }, | |
| { source: 'Episodic Memory', target: 'Hippocampus', label: 'LOGS_IN' }, | |
| { source: 'Sleep Cycle', target: 'Hippocampus', label: 'OPTIMIZES' }, | |
| { source: 'Sleep Cycle', target: 'Neocortex', label: 'REINFORCES' }, | |
| { source: 'Cortex', target: 'Llama 3.1', label: 'COMPUTES' }, | |
| { source: 'Llama 3.1', target: 'Groq API', label: 'HOSTED_ON' } | |
| ] | |
| }; | |
| function KnowledgeGraph({ refreshTick }) { | |
| const [graphData, setGraphData] = useState({ nodes: [], links: [] }); | |
| const [loading, setLoading] = useState(true); | |
| const [dbStatus, setDbStatus] = useState('connecting'); | |
| const [dimensions, setDimensions] = useState({ width: 800, height: 500 }); | |
| const [stats, setStats] = useState({ node_count: 0, edge_count: 0, top_entities: [] }); | |
| const [physicsActive, setPhysicsActive] = useState(true); | |
| const containerRef = useRef(null); | |
| const fgRef = useRef(); | |
| // Measure container dimensions to auto-resize the canvas | |
| useEffect(() => { | |
| if (!containerRef.current) return; | |
| const resizeObserver = new ResizeObserver((entries) => { | |
| for (let entry of entries) { | |
| const { width, height } = entry.contentRect; | |
| setDimensions({ width: width || 800, height: height || 500 }); | |
| } | |
| }); | |
| resizeObserver.observe(containerRef.current); | |
| return () => resizeObserver.disconnect(); | |
| }, []); | |
| // Fetch graph database from backend | |
| const fetchGraph = async () => { | |
| setLoading(true); | |
| try { | |
| const res = await apiFetch('/api/v1/graph'); | |
| if (!res.ok) { | |
| throw new Error('Graph fetch returned unhealthy status'); | |
| } | |
| const data = await res.json(); | |
| if (data.status === 'offline' || data.status === 'error' || !data.nodes || data.nodes.length === 0) { | |
| setDbStatus(data.status || 'offline'); | |
| const nodes = MOCK_GRAPH.nodes.map(n => ({ ...n })); | |
| const links = MOCK_GRAPH.links.map(l => ({ ...l })); | |
| setGraphData({ nodes, links }); | |
| } else { | |
| setDbStatus('online'); | |
| const nodes = data.nodes.map(n => ({ | |
| id: n.id, | |
| label: n.label || n.id, | |
| connections: n.connections || 1, | |
| type: 'entity' | |
| })); | |
| const links = data.edges.map(e => ({ | |
| source: e.source, | |
| target: e.target, | |
| label: e.label || 'RELATED_TO' | |
| })); | |
| setGraphData({ nodes, links }); | |
| } | |
| } catch (error) { | |
| console.error('Graph fetch failed', error); | |
| setDbStatus('offline'); | |
| setGraphData({ | |
| nodes: MOCK_GRAPH.nodes.map(n => ({ ...n })), | |
| links: MOCK_GRAPH.links.map(l => ({ ...l })) | |
| }); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const fetchStats = async () => { | |
| try { | |
| const res = await apiFetch('/api/v1/graph/stats'); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| if (data.status === 'online') { | |
| setStats(data); | |
| } | |
| } | |
| } catch (err) { | |
| console.error('Stats fetch failed', err); | |
| } | |
| }; | |
| useEffect(() => { | |
| fetchGraph(); | |
| fetchStats(); | |
| }, [refreshTick]); | |
| // Physics force configurations to pull the neural cluster into a tight, brain-like shape | |
| useEffect(() => { | |
| if (fgRef.current) { | |
| const d3Force = fgRef.current.d3Force; | |
| if (d3Force) { | |
| d3Force('charge').strength(-50); // Gentle repulsion for an ultra-compact molecular look | |
| d3Force('link').distance(30); // Extremely tight connection paths to pull nodes together | |
| } | |
| // Auto-fit camera with very tight padding to zoom in close and eliminate empty workspace | |
| setTimeout(() => { | |
| fgRef.current.zoomToFit(800, 20); | |
| }, 600); | |
| } | |
| }, [graphData]); | |
| // Calculate max connections dynamically to scale color thresholds | |
| const maxConnections = graphData.nodes && graphData.nodes.length > 0 | |
| ? Math.max(...graphData.nodes.map(n => n.connections || 1), 1) | |
| : 1; | |
| // Color mapper based on node type & degree | |
| const getNodeColor = (node) => { | |
| if (node.id === 'SOMA') return '#ff6b35'; // Core Hub is SOMA Orange | |
| // For offline/mock mode, retain the pre-assigned structural category colors | |
| if (dbStatus !== 'online') { | |
| if (node.type === 'core') return '#ff6b35'; // Orange | |
| if (node.type === 'method') return '#ff8b54'; // Soft Orange | |
| if (node.type === 'concept') return '#ffa67c'; // Pale Orange | |
| if (node.type === 'metric') return '#a3a3a3'; // Neutral Grey | |
| return '#737373'; // Darker Grey | |
| } | |
| // For live Neo4j data, dynamically scale colors based on relative synaptic density! | |
| const connections = node.connections || 1; | |
| const ratio = connections / maxConnections; | |
| if (ratio >= 0.8) return '#ff6b35'; // High centrality (Orange) | |
| if (ratio >= 0.4) return '#ff9c7a'; // Medium centrality (Soft Orange) | |
| return '#a3a3a3'; // Low centrality (Silver/Grey) | |
| }; | |
| return ( | |
| <div className="graph-stage fade-in"> | |
| <div className="graph-toolbar"> | |
| <div className="graph-title-block"> | |
| <button className="graph-select"> | |
| <span className="material-icons">hub</span> | |
| <span>{dbStatus === 'online' ? 'Real-time 3D Neo4j Graph' : '3D SOMA Cognitive Architecture Model'}</span> | |
| </button> | |
| {dbStatus !== 'online' && ( | |
| <span className="db-status-badge warning pulse"> | |
| <span className="dot" /> | |
| Neo4j Offline - Displaying Interactive System Concept | |
| </span> | |
| )} | |
| {dbStatus === 'online' && ( | |
| <span className="db-status-badge success"> | |
| <span className="dot" /> | |
| Neo4j Synchronized (Live) | |
| </span> | |
| )} | |
| </div> | |
| <div className="graph-actions"> | |
| <button | |
| className="graph-icon-button" | |
| onClick={() => { | |
| if (fgRef.current) fgRef.current.zoomToFit(600, 20); | |
| }} | |
| title="Recenter Camera" | |
| > | |
| <span className="material-icons">zoom_out_map</span> | |
| </button> | |
| <button | |
| className="graph-icon-button" | |
| onClick={() => { | |
| if (fgRef.current) { | |
| const currentPos = fgRef.current.cameraPosition(); | |
| fgRef.current.cameraPosition( | |
| { x: currentPos.x * 0.75, y: currentPos.y * 0.75, z: currentPos.z * 0.75 }, | |
| null, | |
| 300 | |
| ); | |
| } | |
| }} | |
| title="Zoom In" | |
| > | |
| <span className="material-icons">zoom_in</span> | |
| </button> | |
| <button | |
| className="graph-icon-button" | |
| onClick={() => { | |
| if (fgRef.current) { | |
| const currentPos = fgRef.current.cameraPosition(); | |
| fgRef.current.cameraPosition( | |
| { x: currentPos.x * 1.35, y: currentPos.y * 1.35, z: currentPos.z * 1.35 }, | |
| null, | |
| 300 | |
| ); | |
| } | |
| }} | |
| title="Zoom Out" | |
| > | |
| <span className="material-icons">zoom_out</span> | |
| </button> | |
| <button | |
| className="graph-icon-button" | |
| onClick={() => { | |
| const newActive = !physicsActive; | |
| setPhysicsActive(newActive); | |
| if (fgRef.current) { | |
| if (physicsActive) { | |
| fgRef.current.d3PauseSimulation(); | |
| } else { | |
| fgRef.current.d3ResumeSimulation(); | |
| } | |
| } | |
| }} | |
| title={physicsActive ? "Pause Simulation" : "Resume Simulation"} | |
| > | |
| <span className="material-icons">{physicsActive ? "pause" : "play_arrow"}</span> | |
| </button> | |
| <button | |
| className="graph-icon-button" | |
| onClick={() => { | |
| fetchGraph(); | |
| fetchStats(); | |
| }} | |
| title="Sync Mesh" | |
| > | |
| <span className="material-icons">sync</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div className="graph-network-container" ref={containerRef}> | |
| {/* Live Telemetry HUD Overlay */} | |
| <div className="graph-hud-overlay"> | |
| <div className="hud-panel"> | |
| <div className="hud-header"> | |
| <span className="material-icons">analytics</span> | |
| <span>Semantic Telemetry</span> | |
| </div> | |
| <div className="hud-row"> | |
| <span className="hud-label">Nodes:</span> | |
| <span className="hud-value">{dbStatus === 'online' ? stats.node_count : graphData.nodes.length}</span> | |
| </div> | |
| <div className="hud-row"> | |
| <span className="hud-label">Synapses:</span> | |
| <span className="hud-value">{dbStatus === 'online' ? stats.edge_count : graphData.links.length}</span> | |
| </div> | |
| <div className="hud-row"> | |
| <span className="hud-label">Storage:</span> | |
| <span className="hud-value status-glow">{dbStatus === 'online' ? 'LTM (Neo4j)' : 'STM (Cache)'}</span> | |
| </div> | |
| </div> | |
| {dbStatus === 'online' && stats.top_entities && stats.top_entities.length > 0 && ( | |
| <div className="hud-panel top-entities"> | |
| <div className="hud-header"> | |
| <span className="material-icons">star</span> | |
| <span>Primary Hubs</span> | |
| </div> | |
| <div className="hud-entity-list"> | |
| {stats.top_entities.map((ent, idx) => ( | |
| <div key={idx} className="hud-entity-row"> | |
| <span className="entity-rank">#{idx+1}</span> | |
| <span className="entity-name">{ent.entity}</span> | |
| <span className="entity-connections">{ent.connections} rx</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| <ForceGraph3D | |
| ref={fgRef} | |
| graphData={graphData} | |
| width={dimensions.width} | |
| height={dimensions.height} | |
| backgroundColor="rgba(0, 0, 0, 0)" // Glassmorphic translucent rendering | |
| // Render gorgeous glowing 3D spheres with emissive materials (100% stable!) | |
| nodeThreeObject={node => { | |
| const size = Math.max(3.2, Math.min(node.connections * 1.6, 9.5)); // Perfectly scaled spheres | |
| const geom = new THREE.SphereGeometry(size, 24, 24); | |
| const mat = new THREE.MeshLambertMaterial({ | |
| color: getNodeColor(node), | |
| transparent: true, | |
| opacity: 0.95, | |
| emissive: getNodeColor(node), | |
| emissiveIntensity: 0.45 | |
| }); | |
| return new THREE.Mesh(geom, mat); | |
| }} | |
| // Outlined holographic label rendered natively floating beside cursor on hover (100% stable!) | |
| nodeLabel={node => ` | |
| <span style="color: ${getNodeColor(node)}; font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;"> | |
| ${node.label || node.id} | |
| </span> | |
| `} | |
| // Outlined relationship tag rendered on link hover (100% stable!) | |
| linkLabel={link => ` | |
| <span style="color: #ff6b35; font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;"> | |
| ${link.label || 'RELATED_TO'} | |
| </span> | |
| `} | |
| nodeRelSize={3} | |
| linkColor={() => 'rgba(255, 107, 53, 0.25)'} // Soft, clean axon link fibers | |
| linkWidth={1.8} // Sleek link line thickness | |
| // Glowing Thought Flows (sliding directional particles along axons) | |
| linkDirectionalParticles={3} | |
| linkDirectionalParticleSpeed={0.006} | |
| linkDirectionalParticleWidth={1.5} | |
| linkDirectionalParticleColor={() => '#ff6b35'} | |
| showNavInfo={false} | |
| enablePointerInteraction={true} | |
| enableNodeDrag={true} | |
| /> | |
| {/* Legend removed per user request */} | |
| {loading && ( | |
| <div className="graph-loading"> | |
| <span className="material-icons pulse">refresh</span> | |
| <span>Synchronizing cognitive mesh...</span> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default KnowledgeGraph; | |