dgx-spark-playbooks/nvidia/txt2kg/assets/frontend/components/webgpu-3d-viewer.tsx
2025-10-06 17:05:41 +00:00

483 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 { Switch } from '@/components/ui/switch'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Loader2, Cpu, Server, Monitor, Wifi, RotateCcw } from 'lucide-react'
import { useToast } from '@/hooks/use-toast'
import { EnhancedWebGPUClusteringEngine, RemoteWebGPUClusteringClient } from '@/utils/remote-webgpu-clustering'
import { WebRTCGraphViewer } from './webrtc-graph-viewer'
import { ForceGraphWrapper } from './force-graph-wrapper'
interface PerformanceMetrics {
renderingTime: number
clusteringTime?: number
totalNodes: number
totalLinks: number
memoryUsage?: number
}
interface WebGPU3DViewerProps {
graphData: {
nodes: any[]
links: any[]
} | null
remoteServiceUrl?: string
enableClustering?: boolean
onClusteringUpdate?: (metrics: PerformanceMetrics) => void
onError?: (error: string) => void
}
interface RenderingMode {
id: 'local' | 'hybrid' | 'webrtc'
name: string
description: string
available: boolean
recommended?: boolean
}
export function WebGPU3DViewer({
graphData,
remoteServiceUrl = 'http://localhost:8083',
enableClustering = true,
onClusteringUpdate,
onError
}: WebGPU3DViewerProps) {
const [activeMode, setActiveMode] = useState<string>('local')
const [isInitializing, setIsInitializing] = useState(true)
const [renderingModes, setRenderingModes] = useState<RenderingMode[]>([])
const [clusteringEngine, setClusteringEngine] = useState<EnhancedWebGPUClusteringEngine | null>(null)
const [remoteClient, setRemoteClient] = useState<RemoteWebGPUClusteringClient | null>(null)
const [capabilities, setCapabilities] = useState<any>(null)
const { toast } = useToast()
// Initialize rendering modes and capabilities
useEffect(() => {
const initializeCapabilities = async () => {
try {
setIsInitializing(true)
// Initialize enhanced clustering engine
const engine = new EnhancedWebGPUClusteringEngine([32, 18, 24], remoteServiceUrl)
await new Promise(resolve => setTimeout(resolve, 200)) // Give time to initialize
// Initialize remote client for WebRTC capabilities
const client = new RemoteWebGPUClusteringClient(remoteServiceUrl, false) // Disable proxy mode for WebSocket
const remoteAvailable = await client.checkAvailability()
const remoteCaps = client.getCapabilities()
setClusteringEngine(engine)
if (remoteAvailable) {
console.log('Remote client available, setting client state')
setRemoteClient(client)
setCapabilities(remoteCaps)
} else {
console.log('Remote client not available')
setRemoteClient(null)
setCapabilities(null)
}
// Determine available rendering modes
const modes: RenderingMode[] = [
{
id: 'local',
name: 'Local WebGPU',
description: 'Client-side WebGPU clustering and Three.js rendering',
available: Boolean(engine.isAvailable() && !engine.isUsingRemote()),
recommended: Boolean(engine.isAvailable() && !engine.isUsingRemote())
},
{
id: 'hybrid',
name: 'Hybrid GPU/CPU',
description: 'Server GPU clustering, client CPU rendering',
available: Boolean(remoteAvailable && remoteCaps?.modes?.hybrid?.available),
recommended: Boolean(!engine.isAvailable() || engine.isUsingRemote())
},
{
id: 'webrtc',
name: 'WebRTC Streaming',
description: 'Full server GPU rendering streamed to browser',
available: Boolean(remoteAvailable && remoteCaps?.modes?.webrtc_stream?.available)
}
]
setRenderingModes(modes)
// Auto-select best available mode
const recommendedMode = modes.find(m => m.recommended && m.available)
const fallbackMode = modes.find(m => m.available)
if (recommendedMode) {
setActiveMode(recommendedMode.id)
toast({
title: "Rendering Mode Selected",
description: `Using ${recommendedMode.name} for optimal performance`,
})
} else if (fallbackMode) {
setActiveMode(fallbackMode.id)
toast({
title: "Fallback Mode",
description: `Using ${fallbackMode.name} as fallback`,
variant: "destructive"
})
} else {
onError?.('No rendering modes available')
}
} catch (error) {
console.error('Failed to initialize 3D viewer capabilities:', error)
onError?.(`Initialization failed: ${error}`)
} finally {
setIsInitializing(false)
}
}
initializeCapabilities()
return () => {
if (clusteringEngine) {
clusteringEngine.dispose()
}
if (remoteClient) {
remoteClient.dispose()
}
}
}, [remoteServiceUrl])
// Handle clustering updates and performance metrics
useEffect(() => {
if (graphData && onClusteringUpdate) {
const startTime = performance.now()
// Simulate clustering time for performance metrics
// In a real implementation, this would come from the actual clustering engine
const nodeCount = graphData.nodes?.length || 0
const linkCount = graphData.links?.length || 0
if (nodeCount > 0) {
// Simulate clustering processing time based on node count
const clusteringTime = enableClustering ? Math.max(10, nodeCount * 0.01) : 0
const renderingTime = performance.now() - startTime
setTimeout(() => {
onClusteringUpdate({
renderingTime,
clusteringTime,
totalNodes: nodeCount,
totalLinks: linkCount,
})
}, clusteringTime)
}
}
}, [graphData, enableClustering, onClusteringUpdate])
// Handle mode change
const handleModeChange = useCallback((mode: string) => {
const selectedMode = renderingModes.find(m => m.id === mode)
if (selectedMode && selectedMode.available) {
setActiveMode(mode)
toast({
title: "Rendering Mode Changed",
description: `Switched to ${selectedMode.name}`,
})
}
}, [renderingModes])
if (isInitializing) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Loader2 className="h-5 w-5 animate-spin" />
Initializing 3D GPU Viewer
</CardTitle>
</CardHeader>
<CardContent>
<p>Detecting WebGPU capabilities and remote services...</p>
<Button
variant="outline"
size="sm"
className="mt-4"
onClick={() => setIsInitializing(false)}
>
Skip initialization and continue
</Button>
</CardContent>
</Card>
)
}
if (renderingModes.length === 0 || !renderingModes.some(m => m.available)) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-red-600">
<Monitor className="h-5 w-5" />
No Rendering Options Available
</CardTitle>
</CardHeader>
<CardContent>
<Alert>
<AlertDescription>
Neither local WebGPU nor remote GPU services are available.
Please ensure WebGPU is supported in your browser or that the remote service is running.
</AlertDescription>
</Alert>
</CardContent>
</Card>
)
}
return (
<div className="space-y-4">
{/* Rendering Mode Tabs */}
<Tabs value={activeMode} onValueChange={handleModeChange}>
<TabsList className="grid w-full grid-cols-3">
{renderingModes.map((mode) => (
<TabsTrigger
key={mode.id}
value={mode.id}
disabled={!mode.available}
className="flex items-center gap-2"
>
{mode.id === 'local' && <Cpu className="w-4 h-4" />}
{mode.id === 'hybrid' && <Server className="w-4 h-4" />}
{mode.id === 'webrtc' && <Monitor className="w-4 h-4" />}
{mode.name}
</TabsTrigger>
))}
</TabsList>
{/* Local WebGPU Mode */}
<TabsContent value="local" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Local WebGPU Clustering + Three.js Rendering</CardTitle>
<CardDescription>
Uses your browser's WebGPU for clustering and Three.js for 3D rendering
</CardDescription>
</CardHeader>
<CardContent>
{clusteringEngine && !clusteringEngine.isUsingRemote() ? (
<div className="space-y-4">
<Alert>
<Cpu className="h-4 w-4" />
<AlertDescription>
Local WebGPU is available. This provides the best performance with no network latency.
</AlertDescription>
</Alert>
{/* Clustering Controls */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-2">
<Switch
id="local-clustering"
checked={enableClustering}
onCheckedChange={(checked: boolean) => {
// Handle clustering toggle
if (onClusteringUpdate && graphData) {
const nodeCount = graphData.nodes?.length || 0
const linkCount = graphData.links?.length || 0
onClusteringUpdate({
renderingTime: performance.now() % 100,
clusteringTime: checked ? Math.max(10, nodeCount * 0.01) : 0,
totalNodes: nodeCount,
totalLinks: linkCount,
})
}
}}
/>
<label htmlFor="local-clustering" className="text-sm font-medium">Local WebGPU Clustering</label>
</div>
<Badge variant={enableClustering ? "default" : "secondary"}>
{enableClustering ? "Enabled" : "Disabled"}
</Badge>
</div>
{/* Standard 3D Force Graph */}
<div className="h-[500px] border rounded-lg bg-black">
{graphData ? (
<ForceGraphWrapper
jsonData={graphData}
fullscreen={false}
layoutType="3d"
enableClustering={enableClustering}
onClusteringUpdate={onClusteringUpdate}
onError={(err: Error) => onError?.(err.message)}
/>
) : (
<div className="flex items-center justify-center h-full text-white">
<div className="text-center">
<Monitor className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No graph data available</p>
<small className="text-gray-400">
Load graph data to see WebGPU clustering
</small>
</div>
</div>
)}
</div>
</div>
) : (
<Alert>
<AlertDescription>
Local WebGPU is not available in this browser or environment.
</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
</TabsContent>
{/* Hybrid Mode */}
<TabsContent value="hybrid" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Hybrid GPU/CPU Rendering</CardTitle>
<CardDescription>
Server performs GPU clustering, client handles CPU-based Three.js rendering
</CardDescription>
</CardHeader>
<CardContent>
{remoteClient ? (
<div className="space-y-4">
<Alert>
<Server className="h-4 w-4" />
<AlertDescription>
Remote GPU clustering is available. Clustering will be performed on the server GPU,
with results sent to your browser for 3D rendering.
</AlertDescription>
</Alert>
{/* Clustering Controls */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-2">
<Switch
id="hybrid-clustering"
checked={enableClustering}
onCheckedChange={(checked: boolean) => {
// Handle clustering toggle
if (onClusteringUpdate && graphData) {
const nodeCount = graphData.nodes?.length || 0
const linkCount = graphData.links?.length || 0
onClusteringUpdate({
renderingTime: performance.now() % 100,
clusteringTime: checked ? Math.max(15, nodeCount * 0.02) : 0,
totalNodes: nodeCount,
totalLinks: linkCount,
})
}
}}
/>
<label htmlFor="hybrid-clustering" className="text-sm font-medium">Remote GPU Clustering</label>
</div>
<Badge variant={enableClustering ? "default" : "secondary"}>
{enableClustering ? "Server GPU" : "Disabled"}
</Badge>
</div>
{/* Enhanced ForceGraphWrapper with remote GPU clustering */}
<div className="h-[500px] border rounded-lg bg-black">
{graphData ? (
<ForceGraphWrapper
jsonData={graphData}
fullscreen={false}
layoutType="3d"
enableClustering={enableClustering}
onClusteringUpdate={onClusteringUpdate}
onError={(err: Error) => onError?.(err.message)}
/>
) : (
<div className="flex items-center justify-center h-full text-white">
<div className="text-center">
<Monitor className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No graph data available</p>
<small className="text-gray-400">
Load graph data to see hybrid GPU clustering
</small>
</div>
</div>
)}
</div>
</div>
) : (
<Alert>
<AlertDescription>
Remote GPU clustering service is not available.
</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
</TabsContent>
{/* WebRTC Streaming Mode */}
<TabsContent value="webrtc" className="space-y-4">
<WebRTCGraphViewer
graphData={graphData}
remoteServiceUrl={remoteServiceUrl}
autoRefresh={true}
refreshInterval={1000}
onError={onError}
/>
</TabsContent>
</Tabs>
{/* Service Status */}
{capabilities && (
<Card>
<CardHeader>
<CardTitle>Service Status</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div className="space-y-1">
<p className="font-medium">Local WebGPU</p>
<Badge variant={clusteringEngine?.isAvailable() && !clusteringEngine?.isUsingRemote() ? 'default' : 'secondary'}>
{clusteringEngine?.isAvailable() && !clusteringEngine?.isUsingRemote() ? 'Available' : 'Not Available'}
</Badge>
</div>
<div className="space-y-1">
<p className="font-medium">Remote Service</p>
<Badge variant={remoteClient ? 'default' : 'secondary'}>
{remoteClient ? 'Connected' : 'Disconnected'}
</Badge>
</div>
<div className="space-y-1">
<p className="font-medium">Server GPU</p>
<Badge variant={capabilities?.gpuAcceleration?.rapidsAvailable ? 'default' : 'secondary'}>
{capabilities?.gpuAcceleration?.rapidsAvailable ? 'RAPIDS' : 'CPU Only'}
</Badge>
</div>
<div className="space-y-1">
<p className="font-medium">WebRTC</p>
<Badge variant={capabilities?.modes?.webrtc_stream?.available ? 'default' : 'secondary'}>
{capabilities?.modes?.webrtc_stream?.available ? 'Available' : 'Not Available'}
</Badge>
</div>
</div>
{capabilities && (
<div className="mt-4 pt-4 border-t text-xs text-gray-600">
<p>
Cluster dimensions: {capabilities.clusterDimensions?.join(' × ')}
({capabilities.maxClusterCount?.toLocaleString()} total clusters)
</p>
</div>
)}
</CardContent>
</Card>
)}
</div>
)
}