Kexin-251202 commited on
Commit
3b59312
·
verified ·
1 Parent(s): b2e6ec6

update home.jsx app.jsx main.py

Browse files
Files changed (3) hide show
  1. main.py +7 -7
  2. src/App.jsx +78 -186
  3. src/components/Home.jsx +113 -11
main.py CHANGED
@@ -579,23 +579,23 @@ async def health_check():
579
 
580
  # ================ STATIC FILES (SPA SUPPORT) ================
581
 
582
- # 1. Mount the assets folder (JS/CSS built by Vite/React)
583
- if os.path.exists("static/assets"):
584
- app.mount("/assets", StaticFiles(directory="static/assets"), name="assets")
 
 
585
 
586
- # 2. Catch-all route for SPA (React Router)
587
- # This ensures that if you refresh /customise, it serves index.html instead of 404
588
  @app.get("/{full_path:path}")
589
  async def serve_react_app(full_path: str, request: Request):
590
- # Skip API and WS routes
591
  if full_path.startswith("api") or full_path.startswith("ws"):
592
  raise HTTPException(status_code=404, detail="Not Found")
593
 
594
  file_path = os.path.join(FRONTEND_DIR, full_path)
595
  if os.path.isfile(file_path):
596
  return FileResponse(file_path)
 
597
  index_path = os.path.join(FRONTEND_DIR, "index.html")
598
  if os.path.exists(index_path):
599
  return FileResponse(index_path)
600
  else:
601
- return {"message": f"React app not found. Please run 'npm run build'."}
 
579
 
580
  # ================ STATIC FILES (SPA SUPPORT) ================
581
 
582
+ FRONTEND_DIR = "dist" if os.path.exists("dist/index.html") else "static"
583
+
584
+ assets_path = os.path.join(FRONTEND_DIR, "assets")
585
+ if os.path.exists(assets_path):
586
+ app.mount("/assets", StaticFiles(directory=assets_path), name="assets")
587
 
 
 
588
  @app.get("/{full_path:path}")
589
  async def serve_react_app(full_path: str, request: Request):
 
590
  if full_path.startswith("api") or full_path.startswith("ws"):
591
  raise HTTPException(status_code=404, detail="Not Found")
592
 
593
  file_path = os.path.join(FRONTEND_DIR, full_path)
594
  if os.path.isfile(file_path):
595
  return FileResponse(file_path)
596
+
597
  index_path = os.path.join(FRONTEND_DIR, "index.html")
598
  if os.path.exists(index_path):
599
  return FileResponse(index_path)
600
  else:
601
+ return {"message": "React app not found. Please run npm run build."}
src/App.jsx CHANGED
@@ -10,195 +10,87 @@ import Customise from './components/Customise';
10
  import Help from './components/Help';
11
 
12
  function App() {
13
- const [activeTab, setActiveTab] = useState('home');
14
- const videoManagerRef = useRef(null);
15
- const [isSessionActive, setIsSessionActive] = useState(false);
16
- const [sessionResult, setSessionResult] = useState(null);
17
-
18
- // 新增状态:控制身份和弹窗
19
- const [role, setRole] = useState('user');
20
- const [showWelcomePopup, setShowWelcomePopup] = useState(false);
21
- const handleAutoImport = async () => {
22
- const backup = localStorage.getItem('focus_magic_backup');
23
- if (backup) {
24
- try {
25
- const sessions = JSON.parse(backup);
26
- const response = await fetch('/api/import', {
27
- method: 'POST',
28
- headers: { 'Content-Type': 'application/json' },
29
- body: JSON.stringify(sessions)
30
- });
31
- if (response.ok) {
32
- alert("Auto-recovery successful! Data restored from local cache.");
33
- setShowWelcomePopup(false);
34
- } else {
35
- alert("Auto-recovery failed on server.");
36
- }
37
- } catch (err) {
38
- alert("Error reading cache: " + err.message);
39
- }
40
- } else {
41
- alert("No previous automatic backup found. Please use 'Manual Import' to select your downloaded JSON file.");
42
- }
43
- };
44
- const fileInputRef = useRef(null);
45
-
46
- // 刚进网页时的初始化逻辑:先清空,后弹窗
47
- useEffect(() => {
48
- fetch('/api/history', { method: 'DELETE' })
49
- .then(() => {
50
- console.log("History cleared for new user.");
51
- setShowWelcomePopup(true);
52
- })
53
- .catch(err => console.error("Failed to clear history on load", err));
54
-
55
- const callbacks = {
56
- onSessionStart: () => {
57
- setIsSessionActive(true);
58
- setSessionResult(null);
59
- },
60
- onSessionEnd: (summary) => {
61
- setIsSessionActive(false);
62
- if (summary) setSessionResult(summary);
63
- }
64
- };
65
- videoManagerRef.current = new VideoManagerLocal(callbacks);
66
-
67
- return () => {
68
- if (videoManagerRef.current) videoManagerRef.current.stopStreaming();
69
- };
70
- }, []);
71
-
72
- // 手动导入文件的处理逻辑 (借用 Customise 的逻辑)
73
- const handleFileChange = async (event) => {
74
- const file = event.target.files[0];
75
- if (!file) return;
76
- const reader = new FileReader();
77
- reader.onload = async (e) => {
78
- try {
79
- const sessions = JSON.parse(e.target.result);
80
- const response = await fetch('/api/import', {
81
- method: 'POST',
82
- headers: { 'Content-Type': 'application/json' },
83
- body: JSON.stringify(sessions)
84
- });
85
- if (response.ok) {
86
- alert("Import successful! You can check your records now.");
87
- setShowWelcomePopup(false);
88
- } else {
89
- alert("Import failed on server side.");
90
- }
91
- } catch (err) {
92
- alert("Error parsing file: " + err.message);
93
- }
94
- event.target.value = '';
95
- };
96
- reader.readAsText(file);
97
- };
98
-
99
- // 点击左上角头像切换 Admin/User
100
- const handleAvatarClick = async () => {
101
- if (role === 'admin') {
102
- if (window.confirm("Switch back to User mode? This will clear current test data.")) {
103
- await fetch('/api/history', { method: 'DELETE' });
104
- setRole('user');
105
- setShowWelcomePopup(true);
106
- }
107
- } else {
108
- const pwd = window.prompt("Enter Admin Password:");
109
- if (pwd === "123") { // 这里是你设定的测试密码
110
- try {
111
- // 1. 先清空当前数据
112
- await fetch('/api/history', { method: 'DELETE' });
113
- // 2. 读取 public 目录下的测试文件
114
- const res = await fetch('/test_data.json');
115
- if (!res.ok) throw new Error("test_data.json not found in public folder");
116
- const testData = await res.json();
117
- // 3. 导入测试数据
118
- const importRes = await fetch('/api/import', {
119
- method: 'POST',
120
- headers: { 'Content-Type': 'application/json' },
121
- body: JSON.stringify(testData)
122
- });
123
- if (importRes.ok) {
124
- setRole('admin');
125
- alert("Admin mode activated! Test data loaded successfully.");
126
- }
127
- } catch (error) {
128
- alert("Admin login failed: " + error.message);
129
- }
130
- } else if (pwd !== null) {
131
- alert("Incorrect password!");
132
- }
133
- }
134
- };
135
-
136
- return (
137
- <div className="app-container">
138
-
139
- {/* 迎宾弹窗 */}
140
- {showWelcomePopup && (
141
- <div className="welcome-modal-overlay">
142
- <div className="welcome-modal">
143
- <h2>Welcome to FocusGuard!</h2>
144
- <p>Would you like to start fresh or import your previous local records?</p>
145
- <div className="welcome-buttons">
146
- <button className="btn-main" onClick={() => setShowWelcomePopup(false)}>New Start</button>
147
- <button className="btn-main" style={{backgroundColor: '#4CAF50'}} onClick={handleAutoImport}>Auto Import</button>
148
- <button className="btn-main yellow" onClick={() => fileInputRef.current.click()}>Manual Import</button>
149
  </div>
150
- {/* 隐藏的上传框 */}
151
- <input type="file" ref={fileInputRef} style={{ display: 'none' }} accept=".json" onChange={handleFileChange} />
152
- </div>
153
- </div>
154
- )}
155
 
156
- <nav id="top-menu">
157
- {/* 左上角头像区域 */}
158
- <div className="avatar-container" onClick={handleAvatarClick} title="Click to switch User/Admin">
159
- <div className={`avatar-circle ${role}`}>
160
- {role === 'admin' ? 'A' : 'U'}
161
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  </div>
163
-
164
- <button className={`menu-btn ${activeTab === 'focus' ? 'active' : ''}`} onClick={() => setActiveTab('focus')}>
165
- Start Focus {isSessionActive && <span style={{marginLeft: '8px', color: '#00FF00'}}>●</span>}
166
- </button>
167
- <div className="separator"></div>
168
-
169
- <button className={`menu-btn ${activeTab === 'achievement' ? 'active' : ''}`} onClick={() => setActiveTab('achievement')}>
170
- My Achievement
171
- </button>
172
- <div className="separator"></div>
173
-
174
- <button className={`menu-btn ${activeTab === 'records' ? 'active' : ''}`} onClick={() => setActiveTab('records')}>
175
- My Records
176
- </button>
177
- <div className="separator"></div>
178
-
179
- <button className={`menu-btn ${activeTab === 'customise' ? 'active' : ''}`} onClick={() => setActiveTab('customise')}>
180
- Customise
181
- </button>
182
- <div className="separator"></div>
183
-
184
- <button className={`menu-btn ${activeTab === 'help' ? 'active' : ''}`} onClick={() => setActiveTab('help')}>
185
- Help
186
- </button>
187
- </nav>
188
-
189
- {activeTab === 'home' && <Home setActiveTab={setActiveTab} />}
190
- <FocusPageLocal
191
- videoManager={videoManagerRef.current}
192
- sessionResult={sessionResult}
193
- setSessionResult={setSessionResult}
194
- isActive={activeTab === 'focus'}
195
- />
196
- {activeTab === 'achievement' && <Achievement />}
197
- {activeTab === 'records' && <Records />}
198
- {activeTab === 'customise' && <Customise />}
199
- {activeTab === 'help' && <Help />}
200
- </div>
201
- );
202
  }
203
 
204
  export default App;
 
10
  import Help from './components/Help';
11
 
12
  function App() {
13
+ const [activeTab, setActiveTab] = useState('home');
14
+ const videoManagerRef = useRef(null);
15
+ const [isSessionActive, setIsSessionActive] = useState(false);
16
+ const [sessionResult, setSessionResult] = useState(null);
17
+ const [role, setRole] = useState('user');
18
+
19
+ // 刚进网页时,静默清空数据库,不弹窗!
20
+ useEffect(() => {
21
+ fetch('/api/history', { method: 'DELETE' }).catch(err => console.error(err));
22
+
23
+ const callbacks = {
24
+ onSessionStart: () => {
25
+ setIsSessionActive(true);
26
+ setSessionResult(null);
27
+ },
28
+ onSessionEnd: (summary) => {
29
+ setIsSessionActive(false);
30
+ if (summary) setSessionResult(summary);
31
+ }
32
+ };
33
+ videoManagerRef.current = new VideoManagerLocal(callbacks);
34
+
35
+ return () => {
36
+ if (videoManagerRef.current) videoManagerRef.current.stopStreaming();
37
+ };
38
+ }, []);
39
+
40
+ // 点击头像,直接跳转到首页(Home)
41
+ const handleAvatarClick = () => {
42
+ setActiveTab('home');
43
+ };
44
+
45
+ return (
46
+ <div className="app-container">
47
+ <nav id="top-menu">
48
+ <div className="avatar-container" onClick={handleAvatarClick} title="Back to Home">
49
+ <div className={`avatar-circle ${role}`}>
50
+ {role === 'admin' ? 'A' : 'U'}
51
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  </div>
 
 
 
 
 
53
 
54
+ <button className={`menu-btn ${activeTab === 'focus' ? 'active' : ''}`} onClick={() => setActiveTab('focus')}>
55
+ Start Focus {isSessionActive && <span style={{marginLeft: '8px', color: '#00FF00'}}>●</span>}
56
+ </button>
57
+ <div className="separator"></div>
58
+
59
+ <button className={`menu-btn ${activeTab === 'achievement' ? 'active' : ''}`} onClick={() => setActiveTab('achievement')}>
60
+ My Achievement
61
+ </button>
62
+ <div className="separator"></div>
63
+
64
+ <button className={`menu-btn ${activeTab === 'records' ? 'active' : ''}`} onClick={() => setActiveTab('records')}>
65
+ My Records
66
+ </button>
67
+ <div className="separator"></div>
68
+
69
+ <button className={`menu-btn ${activeTab === 'customise' ? 'active' : ''}`} onClick={() => setActiveTab('customise')}>
70
+ Customise
71
+ </button>
72
+ <div className="separator"></div>
73
+
74
+ <button className={`menu-btn ${activeTab === 'help' ? 'active' : ''}`} onClick={() => setActiveTab('help')}>
75
+ Help
76
+ </button>
77
+ </nav>
78
+
79
+ {/* 把核心状态传给 Home 组件 */}
80
+ {activeTab === 'home' && <Home setActiveTab={setActiveTab} role={role} setRole={setRole} />}
81
+
82
+ <FocusPageLocal
83
+ videoManager={videoManagerRef.current}
84
+ sessionResult={sessionResult}
85
+ setSessionResult={setSessionResult}
86
+ isActive={activeTab === 'focus'}
87
+ />
88
+ {activeTab === 'achievement' && <Achievement />}
89
+ {activeTab === 'records' && <Records />}
90
+ {activeTab === 'customise' && <Customise />}
91
+ {activeTab === 'help' && <Help />}
92
  </div>
93
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
 
96
  export default App;
src/components/Home.jsx CHANGED
@@ -1,20 +1,122 @@
1
- import React from 'react';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- // receive setActiveTab to change page
4
- function Home({ setActiveTab }) {
5
  return (
6
  <main id="page-a" className="page">
7
  <h1>FocusGuard</h1>
8
  <p>Your productivity monitor assistant.</p>
9
 
10
- {/* click button change to 'focus' page */}
11
- <button
12
- id="start-button"
13
- className="btn-main"
14
- onClick={() => setActiveTab('focus')}
15
- >
16
- Start
17
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </main>
19
  );
20
  }
 
1
+ import React, { useRef } from 'react';
2
+
3
+ function Home({ setActiveTab, role, setRole }) {
4
+ const fileInputRef = useRef(null);
5
+
6
+ // 1. 开始新生活
7
+ const handleNewStart = async () => {
8
+ await fetch('/api/history', { method: 'DELETE' });
9
+ setActiveTab('focus');
10
+ };
11
+
12
+ // 2. 自动导入 (使用缓存魔术)
13
+ const handleAutoImport = async () => {
14
+ const backup = localStorage.getItem('focus_magic_backup');
15
+ if (backup) {
16
+ try {
17
+ const sessions = JSON.parse(backup);
18
+ const response = await fetch('/api/import', {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify(sessions)
22
+ });
23
+ if (response.ok) {
24
+ alert("Auto-recovery successful!");
25
+ } else {
26
+ alert("Auto-recovery failed.");
27
+ }
28
+ } catch (err) {
29
+ alert("Error: " + err.message);
30
+ }
31
+ } else {
32
+ alert("No previous backup found. Please use Manual Import.");
33
+ }
34
+ };
35
+
36
+ // 3. 手动导入
37
+ const handleFileChange = async (event) => {
38
+ const file = event.target.files[0];
39
+ if (!file) return;
40
+ const reader = new FileReader();
41
+ reader.onload = async (e) => {
42
+ try {
43
+ const sessions = JSON.parse(e.target.result);
44
+ const response = await fetch('/api/import', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify(sessions)
48
+ });
49
+ if (response.ok) {
50
+ alert("Import successful!");
51
+ }
52
+ } catch (err) {
53
+ alert("Error: " + err.message);
54
+ }
55
+ event.target.value = '';
56
+ };
57
+ reader.readAsText(file);
58
+ };
59
+
60
+ // 4. 切换 Admin/User 模式
61
+ const handleAdminToggle = async () => {
62
+ if (role === 'admin') {
63
+ if (window.confirm("Switch back to User mode? Current data will be cleared.")) {
64
+ await fetch('/api/history', { method: 'DELETE' });
65
+ setRole('user');
66
+ alert("Switched to User mode.");
67
+ }
68
+ } else {
69
+ const pwd = window.prompt("Enter Admin Password:");
70
+ if (pwd === "admin123") {
71
+ try {
72
+ await fetch('/api/history', { method: 'DELETE' });
73
+ const res = await fetch('/test_data.json');
74
+ if (!res.ok) throw new Error("test_data.json not found");
75
+ const testData = await res.json();
76
+ const importRes = await fetch('/api/import', {
77
+ method: 'POST',
78
+ headers: { 'Content-Type': 'application/json' },
79
+ body: JSON.stringify(testData)
80
+ });
81
+ if (importRes.ok) {
82
+ setRole('admin');
83
+ alert("Admin mode activated!");
84
+ }
85
+ } catch (error) {
86
+ alert("Admin login failed: " + error.message);
87
+ }
88
+ } else if (pwd !== null) {
89
+ alert("Incorrect password!");
90
+ }
91
+ }
92
+ };
93
 
 
 
94
  return (
95
  <main id="page-a" className="page">
96
  <h1>FocusGuard</h1>
97
  <p>Your productivity monitor assistant.</p>
98
 
99
+ {/* 选项按钮组,全部采用和谐的蓝色按钮 (btn-main) */}
100
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '15px', alignItems: 'center', marginTop: '30px' }}>
101
+
102
+ <button className="btn-main" onClick={handleNewStart} style={{ width: '250px' }}>
103
+ Start Focus
104
+ </button>
105
+
106
+ <button className="btn-main" onClick={handleAutoImport} style={{ width: '250px' }}>
107
+ Auto Import History
108
+ </button>
109
+
110
+ <button className="btn-main" onClick={() => fileInputRef.current.click()} style={{ width: '250px' }}>
111
+ Manual Import History
112
+ </button>
113
+ <input type="file" ref={fileInputRef} style={{ display: 'none' }} accept=".json" onChange={handleFileChange} />
114
+
115
+ <button className="btn-main" onClick={handleAdminToggle} style={{ width: '250px' }}>
116
+ {role === 'admin' ? 'Switch to User Mode' : 'Admin Login'}
117
+ </button>
118
+
119
+ </div>
120
  </main>
121
  );
122
  }