// // 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, { useEffect, useRef, useState, useCallback } from 'react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Alert, AlertDescription } from '@/components/ui/alert' import { Loader2, Play, Square, RotateCcw, Monitor, Wifi } from 'lucide-react' import { useToast } from '@/hooks/use-toast' import { RemoteWebGPUClusteringClient, type ClusteringResult } from '@/utils/remote-webgpu-clustering' interface WebRTCGraphViewerProps { graphData: { nodes: any[] links: any[] } | null remoteServiceUrl?: string autoRefresh?: boolean refreshInterval?: number onError?: (error: string) => void } interface StreamingStats { sessionId: string | null isStreaming: boolean lastFrameTime: Date | null frameCount: number connectionStatus: 'disconnected' | 'connecting' | 'connected' | 'error' processingTime: number | null } export function WebRTCGraphViewer({ graphData, remoteServiceUrl = 'http://localhost:8083', autoRefresh = true, refreshInterval = 1000, onError }: WebRTCGraphViewerProps) { const [client, setClient] = useState(null) const [isInitializing, setIsInitializing] = useState(true) const [serviceAvailable, setServiceAvailable] = useState(false) const [capabilities, setCapabilities] = useState(null) const [streamingStats, setStreamingStats] = useState({ sessionId: null, isStreaming: false, lastFrameTime: null, frameCount: 0, connectionStatus: 'disconnected', processingTime: null }) const imgRef = useRef(null) const refreshIntervalRef = useRef(null) const { toast } = useToast() // Initialize remote client useEffect(() => { const initializeClient = async () => { try { const remoteClient = new RemoteWebGPUClusteringClient(remoteServiceUrl, false) // Disable proxy mode for WebSocket const available = await remoteClient.checkAvailability() if (available) { const caps = remoteClient.getCapabilities() setCapabilities(caps) setServiceAvailable(true) setClient(remoteClient) // Set up event listeners remoteClient.on('connected', () => { setStreamingStats(prev => ({ ...prev, connectionStatus: 'connected' })) toast({ title: "Connected", description: "Connected to remote GPU service", }) }) remoteClient.on('disconnected', () => { setStreamingStats(prev => ({ ...prev, connectionStatus: 'disconnected' })) }) remoteClient.on('error', (error: any) => { setStreamingStats(prev => ({ ...prev, connectionStatus: 'error' })) onError?.(`WebSocket error: ${error}`) }) remoteClient.on('clusteringComplete', (result: ClusteringResult) => { setStreamingStats(prev => ({ ...prev, processingTime: result.processingTime })) }) // Connect WebSocket remoteClient.connectWebSocket() setStreamingStats(prev => ({ ...prev, connectionStatus: 'connecting' })) } else { setServiceAvailable(false) onError?.('Remote WebGPU service not available') } } catch (error) { console.error('Failed to initialize WebRTC client:', error) setServiceAvailable(false) onError?.(`Failed to connect: ${error}`) } finally { setIsInitializing(false) } } initializeClient() return () => { if (client) { client.dispose() } if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current) } } }, [remoteServiceUrl]) // Start streaming const startStreaming = useCallback(async () => { if (!client || !graphData || !serviceAvailable) { toast({ title: "Cannot Start Streaming", description: "Service not available or no graph data", variant: "destructive" }) return } try { setStreamingStats(prev => ({ ...prev, isStreaming: true })) const sessionId = await client.startWebRTCStreaming(graphData.nodes, graphData.links) if (sessionId) { setStreamingStats(prev => ({ ...prev, sessionId, frameCount: 0, lastFrameTime: new Date() })) // Start frame refreshing if (autoRefresh) { startFrameRefresh(sessionId) } else { // Load initial frame loadFrame(sessionId) } toast({ title: "Streaming Started", description: `WebRTC session ${sessionId.substring(0, 8)}... created`, }) } else { throw new Error('Failed to create streaming session') } } catch (error) { console.error('Failed to start streaming:', error) setStreamingStats(prev => ({ ...prev, isStreaming: false })) onError?.(`Failed to start streaming: ${error}`) } }, [client, graphData, serviceAvailable, autoRefresh]) // Stop streaming const stopStreaming = useCallback(async () => { if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current) refreshIntervalRef.current = null } if (client && streamingStats.sessionId) { try { await client.cleanupWebRTCSession(streamingStats.sessionId) } catch (error) { console.warn('Failed to cleanup session:', error) } } setStreamingStats(prev => ({ ...prev, sessionId: null, isStreaming: false, lastFrameTime: null, frameCount: 0 })) toast({ title: "Streaming Stopped", description: "WebRTC session ended", }) }, [client, streamingStats.sessionId]) // Start frame refresh interval const startFrameRefresh = useCallback((sessionId: string) => { if (refreshIntervalRef.current) { clearInterval(refreshIntervalRef.current) } refreshIntervalRef.current = setInterval(() => { loadFrame(sessionId) }, refreshInterval) }, [refreshInterval]) // Load a single frame const loadFrame = useCallback((sessionId: string) => { if (!client || !imgRef.current) return const frameUrl = client.getStreamFrameUrl(sessionId) const img = imgRef.current // Add timestamp to prevent caching const urlWithTimestamp = `${frameUrl}?t=${Date.now()}` img.onload = () => { setStreamingStats(prev => ({ ...prev, lastFrameTime: new Date(), frameCount: prev.frameCount + 1 })) } img.onerror = () => { console.warn('Failed to load frame') } img.src = urlWithTimestamp }, [client]) // Refresh current frame const refreshFrame = useCallback(() => { if (streamingStats.sessionId) { loadFrame(streamingStats.sessionId) } }, [streamingStats.sessionId, loadFrame]) if (isInitializing) { return ( Initializing WebRTC Viewer

Connecting to remote GPU service...

) } if (!serviceAvailable) { return ( Service Unavailable Remote WebGPU service is not available at {remoteServiceUrl}. Please ensure the service is running and accessible. ) } return (
{/* Service Status */} WebRTC GPU Streaming Stream GPU-rendered visualizations from remote server

Connection

{streamingStats.connectionStatus}

GPU Available

{capabilities?.gpuAcceleration?.rapidsAvailable ? 'Yes' : 'CPU Only'}

Streaming

{streamingStats.isStreaming ? 'Active' : 'Inactive'}

Frame Count

{streamingStats.frameCount}
{/* Controls */}
{!streamingStats.isStreaming ? ( ) : ( )} {streamingStats.isStreaming && !autoRefresh && ( )}
{streamingStats.lastFrameTime && (

Last frame: {streamingStats.lastFrameTime.toLocaleTimeString()} {streamingStats.processingTime && ( (processed in {streamingStats.processingTime.toFixed(2)}s) )}

)}
{/* Streamed Visualization */} {streamingStats.sessionId && ( GPU-Rendered Visualization Session: {streamingStats.sessionId.substring(0, 8)}...
GPU-rendered graph visualization {streamingStats.isStreaming && autoRefresh && (
Live
)}
)} {/* Capabilities Info */} {capabilities && ( Service Capabilities

Available Modes:

    {Object.entries(capabilities.modes).map(([mode, info]: [string, any]) => (
  • {mode} {info.description}
  • ))}

GPU Acceleration:

  • RAPIDS cuGraph/cuDF
  • OpenCV Image processing
  • Plotting Visualization

Cluster dimensions: {capabilities?.clusterDimensions?.join(' × ') || 'N/A'} ({capabilities?.maxClusterCount?.toLocaleString() || 'N/A'} total clusters)

)}
) }