popboat1 commited on
Commit
6281c1f
·
1 Parent(s): 12cd8e3

applied some fixes to page.js

Browse files
frontend/3d-visualizer/src/app/page.js CHANGED
@@ -26,11 +26,16 @@ export default function NetworkVisualizer() {
26
  const lastFrameTime = useRef(0);
27
  const isVideoStateRef = useRef(false);
28
 
29
- useEffect(() => {
30
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws';
 
 
 
 
31
  const wsUrl = `${protocol}//${window.location.host}/ws/predict-video`;
32
 
33
  wsRef.current = new WebSocket(wsUrl);
 
34
  wsRef.current.onmessage = (event) => {
35
  if (!isVideoStateRef.current) return;
36
  const data = JSON.parse(event.data);
@@ -38,6 +43,15 @@ export default function NetworkVisualizer() {
38
  setPrediction(data.prediction);
39
  isAwaitingResponse.current = false;
40
  };
 
 
 
 
 
 
 
 
 
41
  return () => {
42
  if (wsRef.current) wsRef.current.close();
43
  cancelAnimationFrame(animationFrameId.current);
@@ -47,6 +61,10 @@ export default function NetworkVisualizer() {
47
  const processVideoFrame = (timestamp) => {
48
  if (!videoRef.current || videoRef.current.paused || videoRef.current.ended) return;
49
 
 
 
 
 
50
  if (timestamp - lastFrameTime.current >= 80) {
51
  if (!isAwaitingResponse.current && wsRef.current?.readyState === WebSocket.OPEN) {
52
  const canvas = canvasRef.current;
@@ -130,7 +148,7 @@ export default function NetworkVisualizer() {
130
  setPrediction(data.prediction);
131
  } catch (error) {
132
  console.error("Error:", error);
133
- alert("Make sure your FastAPI server is running on localhost:8000!");
134
  } finally {
135
  setIsProcessing(false);
136
  }
@@ -143,7 +161,7 @@ export default function NetworkVisualizer() {
143
  ref={videoRef}
144
  src={isVideo && previewImage ? previewImage : undefined}
145
  className="hidden absolute top-0 left-0 w-0 h-0"
146
- autoPlay muted loop
147
  onPlay={() => requestAnimationFrame(processVideoFrame)}
148
  />
149
  <canvas ref={canvasRef} width="227" height="227" className="hidden absolute top-0 left-0 w-0 h-0" />
@@ -204,7 +222,7 @@ export default function NetworkVisualizer() {
204
  {previewImage && (
205
  <div className="h-16 w-16 md:hidden border border-white/20 rounded-lg overflow-hidden relative shrink-0">
206
  {isVideo && <div className="absolute top-1 right-1 bg-red-500 w-2 h-2 rounded-full animate-pulse z-10"></div>}
207
- {isVideo ? <video src={previewImage} className="w-full h-full object-cover" autoPlay muted loop /> : <img src={previewImage} alt="Input" className="w-full h-full object-cover" />}
208
  </div>
209
  )}
210
  </div>
@@ -223,7 +241,7 @@ export default function NetworkVisualizer() {
223
  {previewImage && (
224
  <div className="hidden md:block h-16 w-16 border border-white/20 rounded-lg overflow-hidden relative shrink-0 shadow-lg">
225
  {isVideo && <div className="absolute top-1 right-1 bg-red-500 w-2 h-2 rounded-full animate-pulse z-10"></div>}
226
- {isVideo ? <video src={previewImage} className="w-full h-full object-cover" autoPlay muted loop /> : <img src={previewImage} alt="Input" className="w-full h-full object-cover" />}
227
  </div>
228
  )}
229
  </div>
@@ -237,7 +255,6 @@ export default function NetworkVisualizer() {
237
  </button>
238
  )}
239
 
240
- {/* FIX: Set md:w-[400px] instead of md-w-100 */}
241
  <div
242
  className={`absolute top-0 right-0 bottom-0 md:top-6 md:right-6 md:bottom-6 w-full md:w-[400px] z-40 bg-[#0f0f0f]/95 md:bg-[#0f0f0f]/80 backdrop-blur-2xl md:border border-white/10 md:rounded-2xl flex flex-col shadow-2xl overflow-hidden transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] ${isPanelOpen ? 'translate-x-0' : 'translate-x-full md:translate-x-[120%]'}`}
243
  >
@@ -315,7 +332,7 @@ export default function NetworkVisualizer() {
315
  return (
316
  <div
317
  key={i}
318
- onClick={() => hasData && setZoomedFeature({ layer: activeLayerPanelData.layer_index, index: i, bgPosX, bgPosY, url: activeLayerPanelData.texture_b64, gridSize })}
319
  className="aspect-square bg-black/80 rounded-sm cursor-pointer hover:ring-2 hover:ring-[#00ffcc] hover:z-10 transition-all relative"
320
  style={{
321
  backgroundImage: hasData ? `url(${activeLayerPanelData.texture_b64})` : 'none',
@@ -337,7 +354,6 @@ export default function NetworkVisualizer() {
337
  <div className="flex flex-col items-center justify-center h-full text-center pb-10 animate-fadeIn">
338
  <p className="text-xs text-gray-500 uppercase tracking-wider mb-4 font-semibold">Final Classification</p>
339
  <div className="bg-black/50 border border-[#f97316]/30 rounded-2xl p-6 md:p-8 w-full shadow-[0_0_30px_rgba(249,115,22,0.1)]">
340
- {/* FIX: Corrected typo 'warp-break-word' to 'break-words' */}
341
  <h1 className="text-2xl md:text-3xl font-black text-[#f97316] uppercase tracking-widest break-words drop-shadow-md">
342
  {prediction || "No Data"}
343
  </h1>
@@ -347,36 +363,42 @@ export default function NetworkVisualizer() {
347
  </div>
348
  </div>
349
 
350
- {zoomedFeature && (
351
- <div
352
- className="absolute inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
353
- onClick={() => setZoomedFeature(null)}
354
- >
 
355
  <div
356
- className="bg-[#0f0f0f] border border-white/10 p-6 md:p-8 rounded-2xl shadow-2xl flex flex-col items-center max-w-full"
357
- onClick={(e) => e.stopPropagation()}
358
  >
359
- <h3 className="text-[#00ffcc] font-bold font-mono mb-6 text-lg md:text-xl tracking-widest text-center">
360
- LAYER {zoomedFeature.layer} <span className="text-gray-600">{"//"}</span> FEATURE {zoomedFeature.index + 1}
361
- </h3>
362
  <div
363
- className="w-64 h-64 md:w-96 md:h-96 border-2 border-white/10 rounded-xl bg-black shadow-inner"
364
- style={{
365
- backgroundImage: `url(${zoomedFeature.url})`,
366
- backgroundSize: `${zoomedFeature.gridSize * 100}% ${zoomedFeature.gridSize * 100}%`,
367
- backgroundPosition: `${zoomedFeature.bgPosX}% ${zoomedFeature.bgPosY}%`,
368
- imageRendering: 'pixelated'
369
- }}
370
- />
371
- <button
372
- className="mt-8 w-full bg-white hover:bg-gray-200 text-black font-bold py-3 rounded-lg transition-colors text-sm"
373
- onClick={() => setZoomedFeature(null)}
374
  >
375
- Close View
376
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  </div>
378
- </div>
379
- )}
380
  </div>
381
  );
382
  }
 
26
  const lastFrameTime = useRef(0);
27
  const isVideoStateRef = useRef(false);
28
 
29
+ const connectWebSocket = () => {
30
+ if (wsRef.current && (wsRef.current.readyState === WebSocket.OPEN || wsRef.current.readyState === WebSocket.CONNECTING)) {
31
+ return;
32
+ }
33
+
34
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
35
  const wsUrl = `${protocol}//${window.location.host}/ws/predict-video`;
36
 
37
  wsRef.current = new WebSocket(wsUrl);
38
+
39
  wsRef.current.onmessage = (event) => {
40
  if (!isVideoStateRef.current) return;
41
  const data = JSON.parse(event.data);
 
43
  setPrediction(data.prediction);
44
  isAwaitingResponse.current = false;
45
  };
46
+
47
+ wsRef.current.onclose = () => {
48
+ console.log("WebSocket closed by server.");
49
+ isAwaitingResponse.current = false;
50
+ };
51
+ };
52
+
53
+ useEffect(() => {
54
+ connectWebSocket();
55
  return () => {
56
  if (wsRef.current) wsRef.current.close();
57
  cancelAnimationFrame(animationFrameId.current);
 
61
  const processVideoFrame = (timestamp) => {
62
  if (!videoRef.current || videoRef.current.paused || videoRef.current.ended) return;
63
 
64
+ if (!wsRef.current || wsRef.current.readyState === WebSocket.CLOSED) {
65
+ connectWebSocket();
66
+ }
67
+
68
  if (timestamp - lastFrameTime.current >= 80) {
69
  if (!isAwaitingResponse.current && wsRef.current?.readyState === WebSocket.OPEN) {
70
  const canvas = canvasRef.current;
 
148
  setPrediction(data.prediction);
149
  } catch (error) {
150
  console.error("Error:", error);
151
+ alert("Make sure your FastAPI server is running!");
152
  } finally {
153
  setIsProcessing(false);
154
  }
 
161
  ref={videoRef}
162
  src={isVideo && previewImage ? previewImage : undefined}
163
  className="hidden absolute top-0 left-0 w-0 h-0"
164
+ autoPlay muted loop playsInline
165
  onPlay={() => requestAnimationFrame(processVideoFrame)}
166
  />
167
  <canvas ref={canvasRef} width="227" height="227" className="hidden absolute top-0 left-0 w-0 h-0" />
 
222
  {previewImage && (
223
  <div className="h-16 w-16 md:hidden border border-white/20 rounded-lg overflow-hidden relative shrink-0">
224
  {isVideo && <div className="absolute top-1 right-1 bg-red-500 w-2 h-2 rounded-full animate-pulse z-10"></div>}
225
+ {isVideo ? <video src={previewImage} className="w-full h-full object-cover" autoPlay muted loop playsInline /> : <img src={previewImage} alt="Input" className="w-full h-full object-cover" />}
226
  </div>
227
  )}
228
  </div>
 
241
  {previewImage && (
242
  <div className="hidden md:block h-16 w-16 border border-white/20 rounded-lg overflow-hidden relative shrink-0 shadow-lg">
243
  {isVideo && <div className="absolute top-1 right-1 bg-red-500 w-2 h-2 rounded-full animate-pulse z-10"></div>}
244
+ {isVideo ? <video src={previewImage} className="w-full h-full object-cover" autoPlay muted loop playsInline /> : <img src={previewImage} alt="Input" className="w-full h-full object-cover" />}
245
  </div>
246
  )}
247
  </div>
 
255
  </button>
256
  )}
257
 
 
258
  <div
259
  className={`absolute top-0 right-0 bottom-0 md:top-6 md:right-6 md:bottom-6 w-full md:w-[400px] z-40 bg-[#0f0f0f]/95 md:bg-[#0f0f0f]/80 backdrop-blur-2xl md:border border-white/10 md:rounded-2xl flex flex-col shadow-2xl overflow-hidden transition-transform duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] ${isPanelOpen ? 'translate-x-0' : 'translate-x-full md:translate-x-[120%]'}`}
260
  >
 
332
  return (
333
  <div
334
  key={i}
335
+ onClick={() => hasData && setZoomedFeature({ layer: activeLayerPanelData.layer_index, index: i, bgPosX, bgPosY, gridSize })}
336
  className="aspect-square bg-black/80 rounded-sm cursor-pointer hover:ring-2 hover:ring-[#00ffcc] hover:z-10 transition-all relative"
337
  style={{
338
  backgroundImage: hasData ? `url(${activeLayerPanelData.texture_b64})` : 'none',
 
354
  <div className="flex flex-col items-center justify-center h-full text-center pb-10 animate-fadeIn">
355
  <p className="text-xs text-gray-500 uppercase tracking-wider mb-4 font-semibold">Final Classification</p>
356
  <div className="bg-black/50 border border-[#f97316]/30 rounded-2xl p-6 md:p-8 w-full shadow-[0_0_30px_rgba(249,115,22,0.1)]">
 
357
  <h1 className="text-2xl md:text-3xl font-black text-[#f97316] uppercase tracking-widest break-words drop-shadow-md">
358
  {prediction || "No Data"}
359
  </h1>
 
363
  </div>
364
  </div>
365
 
366
+ {zoomedFeature && (() => {
367
+ // FIX 3: Instead of using a static URL, look up the fresh frame dynamically
368
+ const liveLayer = layerData.find(l => l.layer_index === zoomedFeature.layer);
369
+ const liveUrl = liveLayer ? liveLayer.texture_b64 : "";
370
+
371
+ return (
372
  <div
373
+ className="absolute inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4"
374
+ onClick={() => setZoomedFeature(null)}
375
  >
 
 
 
376
  <div
377
+ className="bg-[#0f0f0f] border border-white/10 p-6 md:p-8 rounded-2xl shadow-2xl flex flex-col items-center max-w-full"
378
+ onClick={(e) => e.stopPropagation()}
 
 
 
 
 
 
 
 
 
379
  >
380
+ <h3 className="text-[#00ffcc] font-bold font-mono mb-6 text-lg md:text-xl tracking-widest text-center">
381
+ LAYER {zoomedFeature.layer} <span className="text-gray-600">{"//"}</span> FEATURE {zoomedFeature.index + 1}
382
+ </h3>
383
+ <div
384
+ className="w-64 h-64 md:w-96 md:h-96 border-2 border-white/10 rounded-xl bg-black shadow-inner"
385
+ style={{
386
+ backgroundImage: `url(${liveUrl})`,
387
+ backgroundSize: `${zoomedFeature.gridSize * 100}% ${zoomedFeature.gridSize * 100}%`,
388
+ backgroundPosition: `${zoomedFeature.bgPosX}% ${zoomedFeature.bgPosY}%`,
389
+ imageRendering: 'pixelated'
390
+ }}
391
+ />
392
+ <button
393
+ className="mt-8 w-full bg-white hover:bg-gray-200 text-black font-bold py-3 rounded-lg transition-colors text-sm"
394
+ onClick={() => setZoomedFeature(null)}
395
+ >
396
+ Close View
397
+ </button>
398
+ </div>
399
  </div>
400
+ );
401
+ })()}
402
  </div>
403
  );
404
  }