// // SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // "use client" import React, { useState, useEffect, useRef } from 'react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Loader2, Zap, Activity, Cpu, Cloud, ExternalLink, Settings } from 'lucide-react' import { useToast } from '@/hooks/use-toast' interface GraphData { nodes: Array<{ id: string name: string group?: string [key: string]: any }> links: Array<{ source: string target: string name: string [key: string]: any }> } interface UnifiedGPUViewerProps { graphData: GraphData onError?: (error: Error) => void } interface ProcessingCapabilities { processing_modes: { pygraphistry_cloud: { available: boolean, description: string } local_gpu: { available: boolean, description: string } local_cpu: { available: boolean, description: string } } has_rapids: boolean has_torch_geometric: boolean gpu_available: boolean } interface ProcessedData { processed_nodes: any[] processed_edges: any[] processing_mode: string embed_url?: string gpu_processed?: boolean layout_positions?: Record clusters?: Record centrality?: Record> stats: { node_count: number edge_count: number gpu_accelerated: boolean has_embed_url?: boolean layout_computed?: boolean clusters_computed?: boolean centrality_computed?: boolean } timestamp: string } export function UnifiedGPUViewer({ graphData, onError }: UnifiedGPUViewerProps) { const [isProcessing, setIsProcessing] = useState(false) const [processedData, setProcessedData] = useState(null) const [capabilities, setCapabilities] = useState(null) const [serviceHealth, setServiceHealth] = useState<'unknown' | 'healthy' | 'error'>('unknown') // Processing mode and options const [processingMode, setProcessingMode] = useState<'pygraphistry_cloud' | 'local_gpu' | 'local_cpu'>('pygraphistry_cloud') // PyGraphistry options const [layoutType, setLayoutType] = useState('force') const [gpuAcceleration, setGpuAcceleration] = useState(true) const [clustering, setClustering] = useState(false) // Local GPU options const [layoutAlgorithm, setLayoutAlgorithm] = useState('force_atlas2') const [clusteringAlgorithm, setClusteringAlgorithm] = useState('leiden') const [computeCentrality, setComputeCentrality] = useState(true) const { toast } = useToast() const wsRef = useRef(null) // Check service health and capabilities on mount useEffect(() => { checkServiceHealth() getCapabilities() setupWebSocket() return () => { if (wsRef.current) { wsRef.current.close() } } }, []) const checkServiceHealth = async () => { try { const response = await fetch('/api/unified-gpu/health') if (response.ok) { setServiceHealth('healthy') } else { setServiceHealth('error') } } catch (error) { console.error('Unified service health check failed:', error) setServiceHealth('error') } } const getCapabilities = async () => { try { const response = await fetch('/api/unified-gpu/capabilities') if (response.ok) { const caps = await response.json() setCapabilities(caps) // Set default mode based on capabilities if (caps.processing_modes.pygraphistry_cloud.available) { setProcessingMode('pygraphistry_cloud') } else if (caps.processing_modes.local_gpu.available) { setProcessingMode('local_gpu') } else { setProcessingMode('local_cpu') } } } catch (error) { console.error('Failed to get capabilities:', error) } } const setupWebSocket = () => { try { const ws = new WebSocket('ws://localhost:8080/ws') ws.onopen = () => { console.log('WebSocket connected to unified service') } ws.onmessage = (event) => { try { const message = JSON.parse(event.data) if (message.type === 'graph_processed') { setProcessedData(message.data) setIsProcessing(false) toast({ title: "Processing Complete", description: `Processed ${message.data.stats.node_count} nodes with ${message.data.processing_mode}`, }) } } catch (error) { console.error('WebSocket message error:', error) } } wsRef.current = ws } catch (error) { console.error('WebSocket setup failed:', error) } } const processGraph = async () => { if (!graphData?.nodes?.length || !graphData?.links?.length) { toast({ title: "No Graph Data", description: "Please ensure graph data is loaded before processing.", variant: "destructive" }) return } setIsProcessing(true) try { const requestData = { graph_data: { nodes: graphData.nodes, links: graphData.links }, processing_mode: processingMode, // PyGraphistry options layout_type: layoutType, gpu_acceleration: gpuAcceleration, clustering: clustering, // Local GPU options layout_algorithm: layoutAlgorithm, clustering_algorithm: clusteringAlgorithm, compute_centrality: computeCentrality } const response = await fetch('/api/unified-gpu/visualize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }) if (!response.ok) { throw new Error(`Processing failed: ${response.statusText}`) } // Result will come via WebSocket or direct response const result = await response.json() if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { // Handle direct response if WebSocket not available setProcessedData(result) setIsProcessing(false) toast({ title: "Processing Complete", description: `Processed ${result.stats.node_count} nodes with ${result.processing_mode}`, }) } } catch (error) { console.error('Processing error:', error) const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' toast({ title: "Processing Failed", description: errorMessage, variant: "destructive" }) setIsProcessing(false) if (onError) { onError(error instanceof Error ? error : new Error(errorMessage)) } } } const ServiceStatus = () => (
Unified Service: {serviceHealth === 'healthy' ? 'Connected' : serviceHealth === 'error' ? 'Disconnected' : 'Checking...'}
) const ProcessingModeSelector = () => (

{capabilities?.processing_modes[processingMode]?.description}

) const StatsDisplay = ({ stats }: { stats: ProcessedData['stats'] }) => (
{stats.node_count.toLocaleString()} nodes
{stats.edge_count.toLocaleString()} edges
{stats.gpu_accelerated ? ( ) : ( )} {stats.gpu_accelerated ? 'GPU' : 'CPU'} accelerated
) return (
{/* Service Status */} Unified GPU Visualization {/* Processing Mode Selection */} {/* Processing Options */} Cloud Local GPU Local CPU
{capabilities && (
RAPIDS cuGraph: {capabilities.has_rapids ? '✓ Available' : '✗ Not Available'}
)}

CPU fallback mode - basic processing without GPU acceleration

{/* Action Button */}
{/* Results */} {processedData && (
Processing Results {processedData.processing_mode.replace('_', ' ')}
{/* Statistics */} {/* Visualization */}
{processedData.embed_url ? ( // PyGraphistry Cloud embed <>
☁️ PyGraphistry Cloud Visualization
Interactive GPU-accelerated visualization