"use client" import { useState, useEffect, useRef, useCallback } from "react" import { FallbackGraph } from "./fallback-graph" import { CuboidIcon as Cube, LayoutGrid } from "lucide-react" import type { Triple } from "@/utils/text-processing" interface GraphVisualizationProps { triples: Triple[] fullscreen?: boolean highlightedNodes?: string[] layoutType?: string initialMode?: '2d' | '3d' } export function GraphVisualization({ triples, fullscreen = false, highlightedNodes = [], layoutType = "force", initialMode = '2d' }: GraphVisualizationProps) { // Default to 2D view unless explicitly set to 3D const [use3D, setUse3D] = useState(initialMode === '3d') const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const iframeRef = useRef(null) const loadTimerRef = useRef(null) // Handle 3D view errors that come from the iframe const handleIframeError = useCallback((event: MessageEvent) => { if (event.data && event.data.type === '3d-graph-error') { setError(event.data.message || 'Error loading 3D graph'); setIsLoading(false); } }, []); // Handle 3D view in an iframe to completely isolate it from the main DOM useEffect(() => { if (use3D) { setIsLoading(true); setError(null); // Set a safety timeout in case the iframe never loads loadTimerRef.current = setTimeout(() => { setIsLoading(false); }, 10000); // 10 second timeout if (iframeRef.current) { // Create an event listener to know when the iframe is loaded const handleLoad = () => { if (loadTimerRef.current) { clearTimeout(loadTimerRef.current); loadTimerRef.current = null; } setTimeout(() => { setIsLoading(false); }, 2000); }; // Add the event listener iframeRef.current.addEventListener('load', handleLoad); // Add message listener for error communication window.addEventListener('message', handleIframeError); try { // Get graph ID from URL if available const params = new URLSearchParams(window.location.search); const graphId = params.get("id"); // Add highlighted nodes and layout type to the iframe parameters const highlightedNodesParam = highlightedNodes.length > 0 ? `&highlightedNodes=${encodeURIComponent(JSON.stringify(highlightedNodes))}` : ''; const timestamp = Date.now(); const baseParams = `&fullscreen=${fullscreen}&layout=${layoutType}${highlightedNodesParam}&t=${timestamp}`; let iframeSrc = ''; if (graphId) { // If we have a graph ID, we can just pass that iframeSrc = `/graph3d?id=${graphId}${baseParams}`; } else { // For large triples data, try to use stored database triples first const MAX_URL_TRIPLES = 100; // Maximum number of triples to include in URL if (triples.length > MAX_URL_TRIPLES) { console.log(`Large dataset detected (${triples.length} triples), attempting to use stored database triples`); // Try to store in database first, then use stored source fetch('/api/graph-db/triples', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ triples: triples, documentName: 'Graph Visualization Data' }) }).then(response => { if (response.ok) { console.log('Successfully stored triples in database, using stored source'); // Update iframe to use stored source if (iframeRef.current) { iframeRef.current.src = `/graph3d?source=stored${baseParams}`; } } else { console.warn('Failed to store in database, using localStorage fallback'); // Fallback to localStorage const storageId = `graph_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`; try { localStorage.setItem(storageId, JSON.stringify(triples)); console.log(`Stored ${triples.length} triples in localStorage with ID: ${storageId}`); if (iframeRef.current) { iframeRef.current.src = `/graph3d?storageId=${storageId}${baseParams}`; } } catch (storageError) { console.error("Both database and localStorage failed:", storageError); console.warn(`Using limited triples (${MAX_URL_TRIPLES} of ${triples.length}) to avoid header size issues`); const limitedTriples = triples.slice(0, MAX_URL_TRIPLES); if (iframeRef.current) { iframeRef.current.src = `/graph3d?triples=${encodeURIComponent(JSON.stringify(limitedTriples))}${baseParams}`; } } } }).catch(error => { console.error('Error storing triples in database:', error); // Fallback to localStorage const storageId = `graph_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`; try { localStorage.setItem(storageId, JSON.stringify(triples)); console.log(`Stored ${triples.length} triples in localStorage with ID: ${storageId}`); if (iframeRef.current) { iframeRef.current.src = `/graph3d?storageId=${storageId}${baseParams}`; } } catch (storageError) { console.error("Both database and localStorage failed:", storageError); console.warn(`Using limited triples (${MAX_URL_TRIPLES} of ${triples.length}) to avoid header size issues`); const limitedTriples = triples.slice(0, MAX_URL_TRIPLES); if (iframeRef.current) { iframeRef.current.src = `/graph3d?triples=${encodeURIComponent(JSON.stringify(limitedTriples))}${baseParams}`; } } }); // Set initial iframe src to stored source (will be updated by the fetch above) iframeSrc = `/graph3d?source=stored${baseParams}`; } else { // For small data sets, just use the URL parameter approach iframeSrc = `/graph3d?triples=${encodeURIComponent(JSON.stringify(triples))}${baseParams}`; } } // Set the iframe source iframeRef.current.src = iframeSrc; } catch (err) { console.error("Error setting iframe source:", err); setError("Failed to prepare graph data for visualization"); setIsLoading(false); } // Clean up return () => { if (loadTimerRef.current) { clearTimeout(loadTimerRef.current); } if (iframeRef.current) { iframeRef.current.removeEventListener('load', handleLoad); } window.removeEventListener('message', handleIframeError); }; } } }, [use3D, triples, fullscreen, handleIframeError, highlightedNodes, layoutType]); // Handle switching to 2D view const switchTo2D = () => { setUse3D(false); setError(null); }; // Handle switching to 3D view const switchTo3D = () => { setUse3D(true); setError(null); }; return (
{use3D ? (