popboat1 commited on
Commit
c7da975
·
1 Parent(s): 31222e8

Add websockets dependency and implement arrow UI navigation

Browse files
frontend/3d-visualizer/src/app/page.js CHANGED
@@ -59,7 +59,10 @@ export default function NetworkVisualizer() {
59
  }, []);
60
 
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();
@@ -110,11 +113,27 @@ export default function NetworkVisualizer() {
110
  ? layerData.find(l => l.layer_index === activeTab.layerIndex) || layout.mappedLayers.find(l => l.layer_index === activeTab.layerIndex)
111
  : null;
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  const handleFileUpload = async (event) => {
114
  const file = event.target.files[0];
115
  if (!file) return;
116
 
117
- cancelAnimationFrame(animationFrameId.current);
118
  isAwaitingResponse.current = false;
119
  if (videoRef.current) {
120
  videoRef.current.pause();
@@ -162,7 +181,12 @@ export default function NetworkVisualizer() {
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" />
168
 
@@ -258,38 +282,17 @@ export default function NetworkVisualizer() {
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
  >
261
- <div className="p-4 md:p-5 border-b border-white/10 bg-black/40 flex justify-between items-start">
262
- <div>
263
- <h3 className="text-[10px] md:text-xs text-gray-400 uppercase tracking-widest mb-3 font-semibold">Navigate Architecture</h3>
264
- <div className="flex flex-wrap gap-2">
265
- <button
266
- onClick={() => setActiveTab({ type: 'prediction', layerIndex: null })}
267
- className={`px-3 py-1.5 text-xs font-bold font-mono rounded border transition-colors ${
268
- activeTab.type === 'prediction'
269
- ? 'bg-[#f97316] text-black border-[#f97316]'
270
- : 'bg-[#111] text-gray-400 border-gray-700 hover:bg-[#222]'
271
- }`}
272
- >
273
- Output
274
- </button>
275
- {layout.mappedLayers.map((layer) => (
276
- <button
277
- key={layer.layer_index}
278
- onClick={() => setActiveTab({ type: 'layer', layerIndex: layer.layer_index })}
279
- className={`px-3 py-1.5 text-xs font-bold font-mono rounded border transition-colors ${
280
- activeTab.type === 'layer' && activeTab.layerIndex === layer.layer_index
281
- ? 'bg-[#00ffcc] text-black border-[#00ffcc]'
282
- : 'bg-[#111] text-gray-400 border-gray-700 hover:bg-[#222]'
283
- }`}
284
- >
285
- Layer {layer.layer_index}
286
- </button>
287
- ))}
288
- </div>
289
  </div>
290
  <button
291
  onClick={() => setIsPanelOpen(false)}
292
- className="text-gray-500 hover:text-white font-bold ml-2 p-2 rounded-full hover:bg-white/10 transition-colors"
293
  title="Close Panel"
294
  >
295
  ✕
@@ -361,7 +364,6 @@ export default function NetworkVisualizer() {
361
  </div>
362
 
363
  {zoomedFeature && (() => {
364
- // FIX 3: Instead of using a static URL, look up the fresh frame dynamically
365
  const liveLayer = layerData.find(l => l.layer_index === zoomedFeature.layer);
366
  const liveUrl = liveLayer ? liveLayer.texture_b64 : "";
367
 
 
59
  }, []);
60
 
61
  const processVideoFrame = (timestamp) => {
62
+ if (!videoRef.current || videoRef.current.paused || videoRef.current.ended) {
63
+ animationFrameId.current = requestAnimationFrame(processVideoFrame);
64
+ return;
65
+ }
66
 
67
  if (!wsRef.current || wsRef.current.readyState === WebSocket.CLOSED) {
68
  connectWebSocket();
 
113
  ? layerData.find(l => l.layer_index === activeTab.layerIndex) || layout.mappedLayers.find(l => l.layer_index === activeTab.layerIndex)
114
  : null;
115
 
116
+ const tabs = ['Output', ...layout.mappedLayers.map(l => l.layer_index)];
117
+ const currentTabIndex = activeTab.type === 'prediction' ? 0 : tabs.indexOf(activeTab.layerIndex);
118
+
119
+ const handlePrevTab = () => {
120
+ const newIdx = currentTabIndex === 0 ? tabs.length - 1 : currentTabIndex - 1;
121
+ const val = tabs[newIdx];
122
+ if (val === 'Output') setActiveTab({ type: 'prediction', layerIndex: null });
123
+ else setActiveTab({ type: 'layer', layerIndex: val });
124
+ };
125
+
126
+ const handleNextTab = () => {
127
+ const newIdx = currentTabIndex === tabs.length - 1 ? 0 : currentTabIndex + 1;
128
+ const val = tabs[newIdx];
129
+ if (val === 'Output') setActiveTab({ type: 'prediction', layerIndex: null });
130
+ else setActiveTab({ type: 'layer', layerIndex: val });
131
+ };
132
+
133
  const handleFileUpload = async (event) => {
134
  const file = event.target.files[0];
135
  if (!file) return;
136
 
 
137
  isAwaitingResponse.current = false;
138
  if (videoRef.current) {
139
  videoRef.current.pause();
 
181
  src={isVideo && previewImage ? previewImage : undefined}
182
  className="hidden absolute top-0 left-0 w-0 h-0"
183
  autoPlay muted loop playsInline
184
+ onPlay={() => {
185
+ // Start the loop if it isn't already running
186
+ if (!animationFrameId.current) {
187
+ animationFrameId.current = requestAnimationFrame(processVideoFrame);
188
+ }
189
+ }}
190
  />
191
  <canvas ref={canvasRef} width="227" height="227" className="hidden absolute top-0 left-0 w-0 h-0" />
192
 
 
282
  <div
283
  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%]'}`}
284
  >
285
+ <div className="p-4 md:p-5 border-b border-white/10 bg-black/40 flex justify-between items-center">
286
+ <div className="flex items-center justify-between bg-[#111] border border-gray-700 rounded-lg p-1 w-48 shadow-inner">
287
+ <button onClick={handlePrevTab} className="px-3 py-1 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors text-sm">◀</button>
288
+ <span className="font-mono font-bold text-sm text-[#00ffcc] tracking-widest">
289
+ {activeTab.type === 'prediction' ? 'OUTPUT' : `LAYER ${activeTab.layerIndex}`}
290
+ </span>
291
+ <button onClick={handleNextTab} className="px-3 py-1 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors text-sm">▶</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  </div>
293
  <button
294
  onClick={() => setIsPanelOpen(false)}
295
+ className="text-gray-500 hover:text-white font-bold p-2 rounded-full hover:bg-white/10 transition-colors"
296
  title="Close Panel"
297
  >
298
  ✕
 
364
  </div>
365
 
366
  {zoomedFeature && (() => {
 
367
  const liveLayer = layerData.find(l => l.layer_index === zoomedFeature.layer);
368
  const liveUrl = liveLayer ? liveLayer.texture_b64 : "";
369
 
requirements.txt CHANGED
@@ -4,4 +4,5 @@ python-multipart
4
  opencv-python
5
  numpy
6
  matplotlib
7
- tensorflow-cpu
 
 
4
  opencv-python
5
  numpy
6
  matplotlib
7
+ tensorflow-cpu
8
+ websockets