Spaces:
Sleeping
Sleeping
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 +35 -33
- requirements.txt +2 -1
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)
|
|
|
|
|
|
|
|
|
|
| 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={() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
| 262 |
-
<div>
|
| 263 |
-
<
|
| 264 |
-
<
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 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
|
| 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
|