Abdelrahman Almatrooshi commited on
Commit
5627c54
·
1 Parent(s): 7b53d75

Fix FocusPageLocal.jsx syntax errors from merge

Browse files

- Add missing closing brace on onSessionEnd callback
- Remove duplicate timeline/controls/frame-rate sections

Files changed (1) hide show
  1. src/components/FocusPageLocal.jsx +21 -297
src/components/FocusPageLocal.jsx CHANGED
@@ -37,122 +37,7 @@ function CameraIcon() {
37
  );
38
  }
39
 
40
- function GazeMiniMap({ gazeData }) {
41
- const canvasRef = useRef(null);
42
- const screenAspect = typeof window !== 'undefined'
43
- ? window.screen.width / window.screen.height
44
- : 16 / 9;
45
-
46
- const MAP_H = 100;
47
- const MAP_W = Math.round(MAP_H * screenAspect);
48
-
49
- useEffect(() => {
50
- const cvs = canvasRef.current;
51
- if (!cvs) return;
52
- const ctx = cvs.getContext('2d');
53
- const w = cvs.width;
54
- const h = cvs.height;
55
-
56
- ctx.clearRect(0, 0, w, h);
57
-
58
- // Screen background
59
- ctx.fillStyle = 'rgba(20, 20, 30, 0.85)';
60
- ctx.fillRect(0, 0, w, h);
61
-
62
- // Screen border
63
- ctx.strokeStyle = 'rgba(255,255,255,0.25)';
64
- ctx.lineWidth = 1;
65
- ctx.strokeRect(0.5, 0.5, w - 1, h - 1);
66
-
67
- // Grid lines
68
- ctx.strokeStyle = 'rgba(255,255,255,0.07)';
69
- ctx.lineWidth = 0.5;
70
- for (let i = 1; i < 4; i++) {
71
- ctx.beginPath();
72
- ctx.moveTo((w * i) / 4, 0);
73
- ctx.lineTo((w * i) / 4, h);
74
- ctx.stroke();
75
- }
76
- for (let i = 1; i < 3; i++) {
77
- ctx.beginPath();
78
- ctx.moveTo(0, (h * i) / 3);
79
- ctx.lineTo(w, (h * i) / 3);
80
- ctx.stroke();
81
- }
82
-
83
- // Center crosshair
84
- const cx = w / 2;
85
- const cy = h / 2;
86
- ctx.strokeStyle = 'rgba(255,255,255,0.15)';
87
- ctx.lineWidth = 1;
88
- ctx.beginPath();
89
- ctx.moveTo(cx - 6, cy);
90
- ctx.lineTo(cx + 6, cy);
91
- ctx.moveTo(cx, cy - 6);
92
- ctx.lineTo(cx, cy + 6);
93
- ctx.stroke();
94
-
95
- if (!gazeData || gazeData.gaze_x == null || gazeData.gaze_y == null) {
96
- // No data label
97
- ctx.fillStyle = 'rgba(255,255,255,0.3)';
98
- ctx.font = '10px Arial';
99
- ctx.textAlign = 'center';
100
- ctx.fillText('No gaze data', cx, cy + 3);
101
- ctx.textAlign = 'left';
102
- return;
103
- }
104
-
105
- const gx = gazeData.gaze_x;
106
- const gy = gazeData.gaze_y;
107
- const onScreen = gazeData.on_screen;
108
-
109
- // Draw gaze dot
110
- const dotX = gx * w;
111
- const dotY = gy * h;
112
-
113
- // Trail / glow
114
- const gradient = ctx.createRadialGradient(dotX, dotY, 0, dotX, dotY, 14);
115
- gradient.addColorStop(0, onScreen ? 'rgba(74, 222, 128, 0.5)' : 'rgba(248, 113, 113, 0.5)');
116
- gradient.addColorStop(1, 'rgba(0,0,0,0)');
117
- ctx.fillStyle = gradient;
118
- ctx.fillRect(dotX - 14, dotY - 14, 28, 28);
119
-
120
- // Dot
121
- ctx.beginPath();
122
- ctx.arc(dotX, dotY, 5, 0, 2 * Math.PI);
123
- ctx.fillStyle = onScreen ? '#4ade80' : '#f87171';
124
- ctx.fill();
125
- ctx.strokeStyle = '#fff';
126
- ctx.lineWidth = 1.5;
127
- ctx.stroke();
128
-
129
- // Label
130
- ctx.fillStyle = 'rgba(255,255,255,0.5)';
131
- ctx.font = '9px Arial';
132
- ctx.textAlign = 'right';
133
- ctx.fillText(
134
- `${(gx * 100).toFixed(0)}%, ${(gy * 100).toFixed(0)}%`,
135
- w - 4,
136
- h - 4
137
- );
138
- ctx.textAlign = 'left';
139
- }, [gazeData]);
140
-
141
- return (
142
- <canvas
143
- ref={canvasRef}
144
- width={MAP_W}
145
- height={MAP_H}
146
- style={{
147
- borderRadius: '8px',
148
- border: '1px solid rgba(255,255,255,0.1)',
149
- display: 'block',
150
- }}
151
- />
152
- );
153
- }
154
-
155
- function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActive }) {
156
  const [currentFrame, setCurrentFrame] = useState(15);
157
  const [timelineEvents, setTimelineEvents] = useState([]);
158
  const [stats, setStats] = useState(null);
@@ -246,19 +131,10 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
246
  setFocusState(FOCUS_STATES.pending);
247
  setCameraReady(false);
248
  if (originalOnSessionEnd) originalOnSessionEnd(summary);
249
- videoManager.callbacks.onCalibrationUpdate = (cal) => {
250
- setCalibration(cal && cal.active ? { ...cal } : null);
251
  };
252
 
253
- videoManager.callbacks.onCalibrationUpdate = (state) => {
254
- setCalibrationState(state && state.active ? state : null);
255
- if (state && state.done && state.success) {
256
- setIsCalibrated(true);
257
- }
258
- };
259
-
260
- videoManager.callbacks.onGazeData = (data) => {
261
- setGazeData(data);
262
  };
263
 
264
  const statsInterval = setInterval(() => {
@@ -276,7 +152,7 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
276
  };
277
  }, [videoManager]);
278
 
279
- // Fetch available models and settings (including L2CS boost) on mount
280
  useEffect(() => {
281
  fetch('/api/models')
282
  .then((res) => res.json())
@@ -287,14 +163,6 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
287
  if (data.l2cs_boost_available !== undefined) setL2csBoostAvailable(data.l2cs_boost_available);
288
  })
289
  .catch((err) => console.error('Failed to fetch models:', err));
290
-
291
- fetch('/api/settings')
292
- .then((res) => res.json())
293
- .then((data) => {
294
- if (data && data.l2cs_boost !== undefined) setL2csBoost(data.l2cs_boost);
295
- if (data && data.l2cs_boost_available !== undefined) setL2csBoostAvailable(data.l2cs_boost_available);
296
- })
297
- .catch((err) => console.error('Failed to fetch settings:', err));
298
  }, []);
299
 
300
  useEffect(() => {
@@ -356,53 +224,6 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
356
  }
357
  };
358
 
359
- const handleL2csBoostToggle = async () => {
360
- if (!l2csBoostAvailable) return;
361
- const next = !l2csBoost;
362
- try {
363
- const res = await fetch('/api/settings', {
364
- method: 'PUT',
365
- headers: { 'Content-Type': 'application/json' },
366
- body: JSON.stringify({ l2cs_boost: next })
367
- });
368
- if (res.ok) {
369
- setL2csBoost(next);
370
- } else {
371
- const err = await res.json().catch(() => ({}));
372
- alert(err.detail || 'Could not enable L2CS boost');
373
- }
374
- } catch (err) {
375
- console.error('Failed to toggle L2CS boost:', err);
376
- }
377
- };
378
-
379
- const handleEyeGazeToggle = async () => {
380
- const next = !eyeGazeEnabled;
381
- if (next) {
382
- // Turning ON: save current model, switch to l2cs
383
- setPrevModel(currentModel);
384
- await handleModelChange('l2cs');
385
- setEyeGazeEnabled(true);
386
- } else {
387
- // Turning OFF: switch back to previous model
388
- const restoreTo = prevModel === 'l2cs' ? 'mlp' : prevModel;
389
- await handleModelChange(restoreTo);
390
- setEyeGazeEnabled(false);
391
- setIsCalibrated(false);
392
- setGazeData(null);
393
- }
394
- };
395
-
396
- const [calibrationSetupOpen, setCalibrationSetupOpen] = useState(false);
397
-
398
- const handleCalibrate = () => {
399
- setCalibrationSetupOpen(true);
400
- };
401
-
402
- const handleCalibrationServerStart = () => {
403
- if (videoManager) videoManager.startCalibration();
404
- };
405
-
406
  const handleEnableCamera = async () => {
407
  if (!videoManager) return;
408
 
@@ -788,14 +609,6 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
788
 
789
  return (
790
  <main id="page-b" className="page" style={pageStyle}>
791
- <CalibrationOverlay
792
- calibration={calibrationState}
793
- videoManager={videoManager}
794
- localVideoRef={localVideoRef}
795
- onRequestStart={handleCalibrationServerStart}
796
- setupOpen={calibrationSetupOpen}
797
- setSetupOpen={setCalibrationSetupOpen}
798
- />
799
  {renderIntroCard()}
800
 
801
  <section id="display-area" className="focus-display-shell">
@@ -878,6 +691,22 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
878
  </div>
879
  )}
880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  </section>
882
 
883
  {/* Server CPU / Memory (always visible) */}
@@ -1003,42 +832,6 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
1003
  {isStarting ? 'Starting...' : 'Start'}
1004
  </button>
1005
 
1006
- <button
1007
- type="button"
1008
- className="action-btn"
1009
- style={{
1010
- backgroundColor: eyeGazeEnabled ? '#8b5cf6' : '#475569',
1011
- position: 'relative',
1012
- }}
1013
- onClick={handleEyeGazeToggle}
1014
- title={eyeGazeEnabled
1015
- ? (isCalibrated ? 'Eye Gaze ON (Calibrated)' : 'Eye Gaze ON (Uncalibrated)')
1016
- : 'Enable L2CS eye gaze tracking'}
1017
- >
1018
- Eye Gaze {eyeGazeEnabled ? 'ON' : 'OFF'}
1019
- {eyeGazeEnabled && (
1020
- <span style={{
1021
- position: 'absolute', top: '-4px', right: '-4px',
1022
- width: '10px', height: '10px', borderRadius: '50%',
1023
- backgroundColor: isCalibrated ? '#4ade80' : '#fbbf24',
1024
- border: '2px solid #1e1e2e',
1025
- }} title={isCalibrated ? 'Calibrated' : 'Not calibrated'} />
1026
- )}
1027
- </button>
1028
-
1029
- {eyeGazeEnabled ? (
1030
- <button
1031
- type="button"
1032
- className="action-btn"
1033
- style={{ backgroundColor: isCalibrated ? '#22c55e' : '#8b5cf6' }}
1034
- onClick={handleCalibrate}
1035
- disabled={!videoManager?.isStreaming}
1036
- title="9-point gaze calibration for accurate tracking"
1037
- >
1038
- {isCalibrated ? 'Re-Calibrate' : 'Calibrate'}
1039
- </button>
1040
- ) : null}
1041
-
1042
  <button id="btn-floating" className="action-btn yellow" onClick={handlePiP}>
1043
  Floating Window
1044
  </button>
@@ -1051,32 +844,12 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
1051
  Preview Result
1052
  </button>
1053
 
 
1054
  <button id="btn-cam-stop" className="action-btn red" onClick={handleStop}>
1055
  Stop
1056
  </button>
1057
  </section>
1058
 
1059
- {eyeGazeEnabled && videoManager?.isStreaming ? (
1060
- <section style={{
1061
- display: 'flex',
1062
- alignItems: 'center',
1063
- justifyContent: 'center',
1064
- gap: '14px',
1065
- padding: '8px 14px',
1066
- background: 'rgba(0,0,0,0.3)',
1067
- borderRadius: '10px',
1068
- margin: '6px auto',
1069
- maxWidth: '400px',
1070
- }}>
1071
- <div style={{ textAlign: 'center' }}>
1072
- <div style={{ fontSize: '11px', color: '#888', marginBottom: '4px', letterSpacing: '0.5px' }}>
1073
- GAZE MAP {isCalibrated ? '(calibrated)' : '(raw)'}
1074
- </div>
1075
- <GazeMiniMap gazeData={gazeData} />
1076
- </div>
1077
- </section>
1078
- ) : null}
1079
-
1080
  {cameraError ? (
1081
  <div className="focus-inline-error focus-inline-error-standalone">{cameraError}</div>
1082
  ) : null}
@@ -1102,55 +875,6 @@ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActiv
1102
  </section>
1103
  </>
1104
  ) : null}
1105
- ))}
1106
- </div>
1107
- <div id="timeline-line"></div>
1108
- </section>
1109
-
1110
- {/* 4. Control Buttons */}
1111
- <section id="control-panel">
1112
- <button id="btn-cam-start" className="action-btn green" onClick={handleStart}>
1113
- Start
1114
- </button>
1115
-
1116
- <button id="btn-floating" className="action-btn yellow" onClick={handleFloatingWindow}>
1117
- Floating Window
1118
- </button>
1119
-
1120
- <button
1121
- id="btn-preview"
1122
- className="action-btn"
1123
- style={{ backgroundColor: '#6c5ce7' }}
1124
- onClick={handlePreview}
1125
- >
1126
- Preview Result
1127
- </button>
1128
-
1129
- <button id="btn-cam-stop" className="action-btn red" onClick={handleStop}>
1130
- Stop
1131
- </button>
1132
- </section>
1133
-
1134
- {/* 5. Frame Control */}
1135
- <section id="frame-control">
1136
- <label htmlFor="frame-slider">Frame Rate (FPS)</label>
1137
- <input
1138
- type="range"
1139
- id="frame-slider"
1140
- min="10"
1141
- max="30"
1142
- value={currentFrame}
1143
- onChange={(e) => handleFrameChange(e.target.value)}
1144
- />
1145
- <input
1146
- type="number"
1147
- id="frame-input"
1148
- min="10"
1149
- max="30"
1150
- value={currentFrame}
1151
- onChange={(e) => handleFrameChange(e.target.value)}
1152
- />
1153
- </section>
1154
 
1155
  {/* Calibration overlay (fixed fullscreen, must be outside overflow:hidden containers) */}
1156
  <CalibrationOverlay calibration={calibration} videoManager={videoManager} />
 
37
  );
38
  }
39
 
40
+ function FocusPageLocal({ videoManager, sessionResult, setSessionResult, isActive, role }) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  const [currentFrame, setCurrentFrame] = useState(15);
42
  const [timelineEvents, setTimelineEvents] = useState([]);
43
  const [stats, setStats] = useState(null);
 
131
  setFocusState(FOCUS_STATES.pending);
132
  setCameraReady(false);
133
  if (originalOnSessionEnd) originalOnSessionEnd(summary);
 
 
134
  };
135
 
136
+ videoManager.callbacks.onCalibrationUpdate = (cal) => {
137
+ setCalibration(cal && cal.active ? { ...cal } : null);
 
 
 
 
 
 
 
138
  };
139
 
140
  const statsInterval = setInterval(() => {
 
152
  };
153
  }, [videoManager]);
154
 
155
+ // Fetch available models on mount
156
  useEffect(() => {
157
  fetch('/api/models')
158
  .then((res) => res.json())
 
163
  if (data.l2cs_boost_available !== undefined) setL2csBoostAvailable(data.l2cs_boost_available);
164
  })
165
  .catch((err) => console.error('Failed to fetch models:', err));
 
 
 
 
 
 
 
 
166
  }, []);
167
 
168
  useEffect(() => {
 
224
  }
225
  };
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  const handleEnableCamera = async () => {
228
  if (!videoManager) return;
229
 
 
609
 
610
  return (
611
  <main id="page-b" className="page" style={pageStyle}>
 
 
 
 
 
 
 
 
612
  {renderIntroCard()}
613
 
614
  <section id="display-area" className="focus-display-shell">
 
691
  </div>
692
  )}
693
 
694
+ {role === 'admin' && stats && stats.isStreaming ? (
695
+ <div className="focus-debug-panel">
696
+ <div>Session: {stats.sessionId}</div>
697
+ <div>Sent: {stats.framesSent}</div>
698
+ <div>Processed: {stats.framesProcessed}</div>
699
+ <div>Latency: {stats.avgLatency.toFixed(0)}ms</div>
700
+ <div>Status: {stats.currentStatus ? 'Focused' : 'Not Focused'}</div>
701
+ <div>Confidence: {(stats.lastConfidence * 100).toFixed(1)}%</div>
702
+ {systemStats && systemStats.cpu_percent != null && (
703
+ <div style={{ marginTop: '6px', borderTop: '1px solid #444', paddingTop: '4px' }}>
704
+ <div>CPU: {systemStats.cpu_percent}%</div>
705
+ <div>RAM: {systemStats.memory_percent}% ({systemStats.memory_used_mb}/{systemStats.memory_total_mb} MB)</div>
706
+ </div>
707
+ )}
708
+ </div>
709
+ ) : null}
710
  </section>
711
 
712
  {/* Server CPU / Memory (always visible) */}
 
832
  {isStarting ? 'Starting...' : 'Start'}
833
  </button>
834
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
  <button id="btn-floating" className="action-btn yellow" onClick={handlePiP}>
836
  Floating Window
837
  </button>
 
844
  Preview Result
845
  </button>
846
 
847
+
848
  <button id="btn-cam-stop" className="action-btn red" onClick={handleStop}>
849
  Stop
850
  </button>
851
  </section>
852
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
853
  {cameraError ? (
854
  <div className="focus-inline-error focus-inline-error-standalone">{cameraError}</div>
855
  ) : null}
 
875
  </section>
876
  </>
877
  ) : null}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
 
879
  {/* Calibration overlay (fixed fullscreen, must be outside overflow:hidden containers) */}
880
  <CalibrationOverlay calibration={calibration} videoManager={videoManager} />