'use client'; import { Canvas } from '@react-three/fiber'; import { CameraControls, Line, Text, } from '@react-three/drei'; import { Suspense, useEffect, useRef, useState } from 'react'; import Surreal from 'surrealdb'; // Define the shape of nodes and links from DB interface NodeData { id: string; title: string; coords_3d: [number, number, number]; } interface LinkData { in: string; // from node id out: string; // to node id } // 1. The 3D Node Component function Node({ node, onNodeClick }: { node: NodeData; onNodeClick: (node: NodeData) => void }) { const [hovered, setHovered] = useState(false); const [clicked, setClicked] = useState(false); return ( { e.stopPropagation(); onNodeClick(node); setClicked(!clicked); }} onPointerOver={(e) => { e.stopPropagation(); setHovered(true); }} onPointerOut={() => setHovered(false)} > {/* Show title on hover or click */} {(hovered || clicked) && ( {node.title} )} ); } // 2. The Main Scene Component export function ThoughtGalaxy() { const [nodes, setNodes] = useState([]); const [links, setLinks] = useState([]); const cameraControlsRef = useRef(null); // Fetch data from SurrealDB on mount useEffect(() => { async function fetchData() { // Client-side connection const db = new Surreal(); await db.connect(process.env.NEXT_PUBLIC_SURREALDB_WSS_URL!); // Get the token from the cookie const tokenCookie = document.cookie .split('; ') .find((row) => row.startsWith('ponderants-auth=')); if (!tokenCookie) { console.error('[ThoughtGalaxy] No auth token found'); return; } const token = tokenCookie.split('=')[1]; await db.authenticate(token); // Fetch nodes that have coordinates const nodeResults = await db.query<[NodeData[]]>( 'SELECT id, title, coords_3d FROM node WHERE coords_3d != NONE' ); setNodes(nodeResults[0] || []); // Fetch links const linkResults = await db.query<[LinkData[]]>('SELECT in, out FROM links_to'); setLinks(linkResults[0] || []); console.log(`[ThoughtGalaxy] Loaded ${nodeResults[0]?.length || 0} nodes and ${linkResults[0]?.length || 0} links`); } fetchData(); }, []); // Map links to node positions const linkLines = links .map((link) => { const startNode = nodes.find((n) => n.id === link.in); const endNode = nodes.find((n) => n.id === link.out); if (startNode && endNode) { return { start: startNode.coords_3d, end: endNode.coords_3d, }; } return null; }) .filter(Boolean) as { start: [number, number, number]; end: [number, number, number] }[]; // Camera animation const handleNodeClick = (node: NodeData) => { if (cameraControlsRef.current) { cameraControlsRef.current.smoothTime = 0.8; cameraControlsRef.current.setLookAt( node.coords_3d[0] + 1, node.coords_3d[1] + 1, node.coords_3d[2] + 1, node.coords_3d[0], node.coords_3d[1], node.coords_3d[2], true // Enable smooth transition ); } }; return ( {/* Render all nodes */} {nodes.map((node) => ( ))} {/* Render all links */} {linkLines.map((line, i) => ( ))} ); }