// // 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 { Switch } from '@/components/ui/switch' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Badge } from '@/components/ui/badge' import { Loader2, Zap, Activity, BarChart3, Eye, ExternalLink, Info } 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 PyGraphistryViewerProps { graphData: GraphData onError?: (error: Error) => void } interface VisualizationStats { node_count: number edge_count: number gpu_accelerated: boolean clustered: boolean layout_type: string avg_pagerank?: number max_pagerank?: number avg_betweenness?: number max_betweenness?: number density?: number } interface ProcessedGraphData { processed_nodes: any[] processed_edges: any[] embed_url?: string local_viz_data?: { nodes: any[] edges: any[] positions: Record clusters: Record layout_computed: boolean clusters_computed: boolean } stats: VisualizationStats & { has_embed_url?: boolean has_local_viz?: boolean } timestamp: string } export function PyGraphistryViewer({ graphData, onError }: PyGraphistryViewerProps) { const [isProcessing, setIsProcessing] = useState(false) const [processedData, setProcessedData] = useState(null) const [gpuAcceleration, setGpuAcceleration] = useState(true) const [clustering, setClustering] = useState(false) const [layoutType, setLayoutType] = useState('force') const [serviceHealth, setServiceHealth] = useState<'unknown' | 'healthy' | 'error'>('unknown') const [isServiceInitialized, setIsServiceInitialized] = useState(false) const iframeRef = useRef(null) const { toast } = useToast() // Check service health on mount useEffect(() => { checkServiceHealth() }, []) const checkServiceHealth = async () => { try { const response = await fetch('/api/pygraphistry/health') if (response.ok) { const health = await response.json() setServiceHealth('healthy') setIsServiceInitialized(health.pygraphistry_initialized) } else { setServiceHealth('error') } } catch (error) { console.error('Service health check failed:', error) setServiceHealth('error') } } const processWithPyGraphistry = async () => { if (!graphData?.nodes?.length || !graphData?.links?.length) { toast({ title: "No Graph Data", description: "Please ensure graph data is loaded before processing with PyGraphistry.", variant: "destructive" }) return } setIsProcessing(true) try { const requestData = { graph_data: { nodes: graphData.nodes, links: graphData.links }, layout_type: layoutType, gpu_acceleration: gpuAcceleration, clustering: clustering } const response = await fetch('/api/pygraphistry/visualize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }) if (!response.ok) { throw new Error(`PyGraphistry processing failed: ${response.statusText}`) } const result: ProcessedGraphData = await response.json() setProcessedData(result) toast({ title: "GPU Processing Complete", description: `Processed ${result.stats.node_count} nodes and ${result.stats.edge_count} edges with ${result.stats.gpu_accelerated ? 'GPU' : 'CPU'} acceleration.`, }) } catch (error) { console.error('PyGraphistry processing error:', error) const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' toast({ title: "Processing Failed", description: errorMessage, variant: "destructive" }) if (onError) { onError(error instanceof Error ? error : new Error(errorMessage)) } } finally { setIsProcessing(false) } } const getGraphStats = async () => { if (!graphData?.nodes?.length || !graphData?.links?.length) return try { const response = await fetch('/api/pygraphistry/stats', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nodes: graphData.nodes, links: graphData.links }) }) if (response.ok) { const stats = await response.json() toast({ title: "Graph Statistics", description: `Density: ${(stats.density * 100).toFixed(2)}%, Avg PageRank: ${stats.avg_pagerank?.toFixed(4) || 'N/A'}`, }) } } catch (error) { console.error('Failed to get graph stats:', error) } } const ServiceStatus = () => (
PyGraphistry Service: {serviceHealth === 'healthy' ? 'Connected' : serviceHealth === 'error' ? 'Disconnected' : 'Checking...'} {isServiceInitialized && ( GPU Ready )}
) const StatsDisplay = ({ stats }: { stats: VisualizationStats }) => (
{stats.node_count} Nodes
{stats.edge_count} Edges
{stats.gpu_accelerated ? 'GPU' : 'CPU'}
{stats.layout_type}
{stats.density !== undefined && (
Density: {(stats.density * 100).toFixed(2)}%
)} {stats.avg_pagerank !== undefined && (
Avg PageRank: {stats.avg_pagerank.toFixed(4)}
)}
) return (
{/* Control Panel */}
PyGraphistry GPU Visualization
{/* Configuration Controls */}
{/* Action Buttons */}
{/* Results Display */} {processedData && (
GPU-Accelerated Visualization {processedData.embed_url && ( )}
{/* Statistics */} { /* Embed Visualization */} {processedData.embed_url ? (