Skip to content

Commit 72f5de5

Browse files
committed
合并改进分支:UI 现代化、安全加固、SEO 优化
Merge branch 'claude/hungry-wescoff' into main
2 parents 0eb0872 + 3d3a310 commit 72f5de5

File tree

18 files changed

+473
-392
lines changed

18 files changed

+473
-392
lines changed

backend/app.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ def get_user_id():
7676
session_id = str(uuid.uuid4())
7777
return session_id
7878

79+
# Admin API key for protected endpoints
80+
admin_api_key = os.getenv('ADMIN_API_KEY')
81+
82+
def require_admin_key():
83+
"""Check ADMIN_API_KEY header. Returns error response or None if ok."""
84+
if not admin_api_key:
85+
return jsonify({'error': 'ADMIN_API_KEY not configured on server', 'success': False}), 500
86+
provided = request.headers.get('X-Admin-Key') or request.args.get('admin_key')
87+
if provided != admin_api_key:
88+
return jsonify({'error': 'Unauthorized', 'success': False}), 401
89+
return None
90+
7991
# Get OpenAI API key from environment variables and create client
8092
api_key = os.getenv('OPENAI_API_KEY')
8193
client = OpenAI(api_key=api_key) if api_key else None
@@ -246,6 +258,9 @@ def health_check():
246258
@app.route('/api/rebuild-vectorstore', methods=['POST'])
247259
def rebuild_vectorstore():
248260
"""Rebuild vector database"""
261+
auth_error = require_admin_key()
262+
if auth_error:
263+
return auth_error
249264
try:
250265
if not api_key:
251266
return jsonify({
@@ -272,6 +287,9 @@ def rebuild_vectorstore():
272287
@app.route('/api/logs', methods=['GET'])
273288
def get_logs():
274289
"""Get chat logs with optional filtering"""
290+
auth_error = require_admin_key()
291+
if auth_error:
292+
return auth_error
275293
try:
276294
# Get query parameters
277295
date = request.args.get('date') # Format: YYYY-MM-DD
@@ -320,6 +338,9 @@ def get_logs():
320338
@app.route('/api/logs/stats', methods=['GET'])
321339
def get_log_stats():
322340
"""Get statistics about chat usage"""
341+
auth_error = require_admin_key()
342+
if auth_error:
343+
return auth_error
323344
try:
324345
# Get all log files
325346
log_files = list(logs_dir.glob('chat_logs_*.json'))

backend/env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# OpenAI API Key
22
# Get from https://platform.openai.com/api-keys
3-
OPENAI_API_KEY=sk-your-api-key-here
3+
OPENAI_API_KEY=sk-your-api-key-here
4+
5+
# Admin API Key — required to access /api/logs and /api/rebuild-vectorstore
6+
# Set to any strong random string, e.g.: openssl rand -hex 32
7+
ADMIN_API_KEY=your-admin-secret-here

index.html

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,52 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/png" href="/my-icon.png" />
5+
<link rel="icon" type="image/png" href="/favicon-32.png" sizes="32x32" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<meta name="description" content="Yifei's Profile" />
7+
<meta name="description" content="Yifei Bao — AI/ML Engineer at Boston University. Chat with an AI assistant to learn about my background, projects, and experience." />
88
<meta name="author" content="Yifei Bao" />
9-
<title>Yifei's Profile</title>
10-
11-
<!-- Open Graph / Facebook -->
12-
<meta property="og:type" content="website" />
13-
<meta property="og:description" content="Yifei's Profile - Chat with me!" />
14-
<meta property="og:title" content="Yifei's Profile" />
15-
<meta property="og:image" content="/my-icon.png" />
16-
9+
<title>Yifei Bao · AI/ML Engineer</title>
10+
11+
<!-- Open Graph / Social sharing -->
12+
<meta property="og:type" content="profile" />
13+
<meta property="og:title" content="Yifei Bao · AI/ML Engineer" />
14+
<meta property="og:description" content="Chat with an AI to learn about Yifei's background, projects, and experience." />
15+
<meta property="og:image" content="/og-image.webp" />
16+
<meta property="og:url" content="https://spectual.github.io" />
17+
1718
<!-- Twitter -->
18-
<meta property="twitter:card" content="summary_large_image" />
19-
<meta property="twitter:title" content="Yifei's Profile" />
20-
<meta property="twitter:description" content="Yifei's Profile - Chat with me!" />
21-
<meta property="twitter:image" content="/my-icon.png" />
19+
<meta name="twitter:card" content="summary_large_image" />
20+
<meta name="twitter:title" content="Yifei Bao · AI/ML Engineer" />
21+
<meta name="twitter:description" content="Chat with an AI to learn about Yifei's background, projects, and experience." />
22+
<meta name="twitter:image" content="/og-image.webp" />
23+
24+
<!-- Schema.org Person structured data -->
25+
<script type="application/ld+json">
26+
{
27+
"@context": "https://schema.org",
28+
"@type": "Person",
29+
"name": "Yifei Bao",
30+
"jobTitle": "AI / ML Engineer",
31+
"email": "baoyifei@bu.edu",
32+
"url": "https://spectual.github.io",
33+
"image": "https://spectual.github.io/avatar.webp",
34+
"alumniOf": [
35+
{
36+
"@type": "CollegeOrUniversity",
37+
"name": "Boston University"
38+
},
39+
{
40+
"@type": "CollegeOrUniversity",
41+
"name": "Nanjing Normal University"
42+
}
43+
],
44+
"sameAs": [
45+
"https://linkedin.com/in/yifei-bao-mscs",
46+
"https://github.com/spectual"
47+
],
48+
"knowsAbout": ["Machine Learning", "Computer Vision", "LLM", "RAG", "PyTorch", "Python"]
49+
}
50+
</script>
2251
</head>
2352
<body>
2453
<div id="root"></div>

public/avatar.webp

15.2 KB
Loading

public/favicon-32.png

2.43 KB
Loading

public/favicon.webp

8.44 KB
Loading

public/og-image.webp

13.5 KB
Loading

src/App.css

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1 @@
1-
#root {
2-
max-width: 1280px;
3-
margin: 0 auto;
4-
padding: 2rem;
5-
text-align: center;
6-
}
7-
8-
.logo {
9-
height: 6em;
10-
padding: 1.5em;
11-
will-change: filter;
12-
transition: filter 300ms;
13-
}
14-
.logo:hover {
15-
filter: drop-shadow(0 0 2em #646cffaa);
16-
}
17-
.logo.react:hover {
18-
filter: drop-shadow(0 0 2em #61dafbaa);
19-
}
20-
21-
@keyframes logo-spin {
22-
from {
23-
transform: rotate(0deg);
24-
}
25-
to {
26-
transform: rotate(360deg);
27-
}
28-
}
29-
30-
@media (prefers-reduced-motion: no-preference) {
31-
a:nth-of-type(2) .logo {
32-
animation: logo-spin infinite 20s linear;
33-
}
34-
}
35-
36-
.card {
37-
padding: 2em;
38-
}
39-
40-
.read-the-docs {
41-
color: #888;
42-
}
1+
/* App.css — intentionally empty. Global styles live in src/index.css */

src/App.tsx

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Component, type ReactNode } from "react";
12
import { Toaster } from "@/components/ui/toaster";
23
import { Toaster as Sonner } from "@/components/ui/sonner";
34
import { TooltipProvider } from "@/components/ui/tooltip";
@@ -8,24 +9,66 @@ import Resume from "./pages/Resume";
89
import Projects from "./pages/Projects";
910
import NotFound from "./pages/NotFound";
1011

12+
class ErrorBoundary extends Component<
13+
{ children: ReactNode },
14+
{ hasError: boolean; error: Error | null }
15+
> {
16+
constructor(props: { children: ReactNode }) {
17+
super(props);
18+
this.state = { hasError: false, error: null };
19+
}
20+
21+
static getDerivedStateFromError(error: Error) {
22+
return { hasError: true, error };
23+
}
24+
25+
componentDidCatch(error: Error, info: { componentStack: string }) {
26+
console.error("Uncaught error:", error, info.componentStack);
27+
}
28+
29+
render() {
30+
if (this.state.hasError) {
31+
return (
32+
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 flex items-center justify-center px-6">
33+
<div className="backdrop-blur-xl bg-white/10 rounded-3xl border border-white/20 p-10 max-w-md w-full text-center shadow-2xl">
34+
<p className="text-5xl mb-4">⚠️</p>
35+
<h1 className="text-xl font-bold text-white mb-2">Something went wrong</h1>
36+
<p className="text-slate-400 text-sm mb-6">
37+
{this.state.error?.message ?? "An unexpected error occurred."}
38+
</p>
39+
<button
40+
onClick={() => window.location.reload()}
41+
className="px-5 py-2.5 bg-cyan-600/20 hover:bg-cyan-600/30 text-cyan-300 border border-cyan-500/30 rounded-full text-sm font-medium transition-all"
42+
>
43+
Reload page
44+
</button>
45+
</div>
46+
</div>
47+
);
48+
}
49+
return this.props.children;
50+
}
51+
}
52+
1153
const queryClient = new QueryClient();
1254

1355
const App = () => (
14-
<QueryClientProvider client={queryClient}>
15-
<TooltipProvider>
16-
<Toaster />
17-
<Sonner />
18-
<HashRouter>
19-
<Routes>
20-
<Route path="/" element={<Index />} />
21-
<Route path="/resume" element={<Resume />} />
22-
<Route path="/projects" element={<Projects />} />
23-
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
24-
<Route path="*" element={<NotFound />} />
25-
</Routes>
26-
</HashRouter>
27-
</TooltipProvider>
28-
</QueryClientProvider>
56+
<ErrorBoundary>
57+
<QueryClientProvider client={queryClient}>
58+
<TooltipProvider>
59+
<Toaster />
60+
<Sonner />
61+
<HashRouter>
62+
<Routes>
63+
<Route path="/" element={<Index />} />
64+
<Route path="/resume" element={<Resume />} />
65+
<Route path="/projects" element={<Projects />} />
66+
<Route path="*" element={<NotFound />} />
67+
</Routes>
68+
</HashRouter>
69+
</TooltipProvider>
70+
</QueryClientProvider>
71+
</ErrorBoundary>
2972
);
3073

3174
export default App;

0 commit comments

Comments
 (0)