Skip to content

Commit 37e83fd

Browse files
Spectualclaude
andcommitted
UI 全面重设计:终端/命令行风格
- 统一配色方案切换为终端风格(#0d1117 深色背景、绿色/蓝色强调色) - 全站使用 JetBrains Mono 等宽字体 - 导航栏改为终端标签栏风格,显示 ~/chat ~/resume ~/projects 路径 - ProfileSection:neofetch 风格个人信息展示,键值对布局 - ChatSection:终端窗口样式,❯ 提示符输入,[n] 快捷命令按钮 - MessageBubble:user/ai 前缀的命令行风格消息气泡 - GetInTouchSection:$ contact --send-message 风格的终端表单 - Resume/Projects 页面:统一终端窗口风格,ls -la 风格项目列表 - 光标闪烁动画 cursor-blink、终端滚动条样式 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f5e0da6 commit 37e83fd

File tree

8 files changed

+1080
-495
lines changed

8 files changed

+1080
-495
lines changed

src/components/ChatSection.tsx

Lines changed: 175 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { useState, useEffect, useRef } from "react";
2-
import { Button } from "@/components/ui/button";
3-
import { Input } from "@/components/ui/input";
4-
import { Send, Bot, AlertCircle, Wifi, WifiOff } from "lucide-react";
2+
import { AlertCircle } from "lucide-react";
53
import MessageBubble from "./MessageBubble";
64
import { sendMessage, checkHealth } from "@/utils/api";
75
import { toast } from "sonner";
@@ -37,6 +35,7 @@ const ChatSection = () => {
3735
const messagesEndRef = useRef<HTMLDivElement>(null);
3836
const isInitialLoad = useRef(true);
3937
const scrollContainerRef = useRef<HTMLDivElement>(null);
38+
const inputRef = useRef<HTMLInputElement>(null);
4039

4140
const isNearBottom = (): boolean => {
4241
const el = scrollContainerRef.current;
@@ -116,6 +115,7 @@ const ChatSection = () => {
116115
]);
117116
} finally {
118117
setIsLoading(false);
118+
inputRef.current?.focus();
119119
}
120120
};
121121

@@ -127,97 +127,204 @@ const ChatSection = () => {
127127
};
128128

129129
return (
130-
<section className="px-6 pb-8">
130+
<section className="px-4 pb-6">
131131
<div className="max-w-4xl mx-auto">
132-
<div className="bg-stone-900 rounded-2xl border border-stone-800 overflow-hidden">
133-
{/* Header */}
134-
<div className="bg-stone-900 p-5 border-b border-stone-800">
135-
<div className="flex items-center gap-3">
136-
<div className="w-9 h-9 bg-stone-800 border border-stone-700 rounded-xl flex items-center justify-center shrink-0">
137-
<Bot className="text-[#cf6b47]" size={18} />
138-
</div>
139-
<div className="flex-1 min-w-0">
140-
<h2 className="text-base font-semibold text-stone-100">Ask Anything About Me</h2>
141-
<p className="text-stone-500 text-xs">Powered by RAG · instant answers about background & projects</p>
142-
</div>
143-
{/* Server status pill */}
144-
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border ${
145-
isServerOnline
146-
? "bg-emerald-950/60 border-emerald-800/50 text-emerald-400"
147-
: "bg-red-950/60 border-red-800/50 text-red-400"
148-
}`}>
149-
{isServerOnline ? <Wifi size={11} /> : <WifiOff size={11} />}
150-
{isServerOnline ? "Online" : "Offline"}
151-
</div>
152-
</div>
132+
<div style={{ border: "1px solid var(--term-border)", backgroundColor: "var(--term-bg)" }}>
133+
{/* Terminal title bar */}
134+
<div
135+
style={{
136+
borderBottom: "1px solid var(--term-border)",
137+
backgroundColor: "var(--term-bg2)",
138+
padding: "6px 16px",
139+
display: "flex",
140+
alignItems: "center",
141+
justifyContent: "space-between",
142+
}}
143+
>
144+
<span style={{ color: "var(--term-dim)", fontSize: "12px" }}>
145+
bash — ~/chat
146+
</span>
147+
{/* Server status */}
148+
<span
149+
style={{
150+
fontSize: "11px",
151+
color: isServerOnline ? "var(--term-green)" : "var(--term-red)",
152+
display: "flex",
153+
alignItems: "center",
154+
gap: "4px",
155+
}}
156+
>
157+
<span
158+
style={{
159+
width: "6px",
160+
height: "6px",
161+
borderRadius: "50%",
162+
backgroundColor: isServerOnline ? "var(--term-green)" : "var(--term-red)",
163+
display: "inline-block",
164+
}}
165+
/>
166+
{isServerOnline ? "server:online" : "server:offline"}
167+
</span>
168+
</div>
153169

154-
{!isServerOnline && (
155-
<div className="mt-3 bg-red-950/40 rounded-xl p-3 border border-red-900/50 flex items-center gap-2.5">
156-
<AlertCircle className="text-red-400 shrink-0" size={15} />
157-
<p className="text-sm text-red-400">AI server is offline. Responses may be unavailable.</p>
158-
</div>
159-
)}
170+
{/* Header command */}
171+
<div style={{ padding: "12px 16px 0", fontSize: "12px", borderBottom: "1px solid var(--term-border)" }}>
172+
<div style={{ display: "flex", alignItems: "center", gap: "6px", paddingBottom: "10px" }}>
173+
<span style={{ color: "var(--term-green)" }}>spectual</span>
174+
<span style={{ color: "var(--term-dim)" }}>@</span>
175+
<span style={{ color: "var(--term-blue)" }}>github.io</span>
176+
<span style={{ color: "var(--term-text)" }}>:~$</span>
177+
<span style={{ color: "var(--term-text)", marginLeft: "4px" }}>
178+
chat --model rag-powered
179+
</span>
180+
</div>
160181
</div>
161182

162-
{/* Messages */}
183+
{!isServerOnline && (
184+
<div
185+
style={{
186+
margin: "8px 16px",
187+
padding: "8px 12px",
188+
border: "1px solid var(--term-red)",
189+
backgroundColor: "rgba(248, 81, 73, 0.08)",
190+
display: "flex",
191+
alignItems: "center",
192+
gap: "8px",
193+
fontSize: "12px",
194+
color: "var(--term-red)",
195+
}}
196+
>
197+
<AlertCircle size={13} />
198+
error: AI server is offline. Responses unavailable.
199+
</div>
200+
)}
201+
202+
{/* Messages area */}
163203
<div
164204
ref={scrollContainerRef}
165-
className="h-96 overflow-y-auto p-5 space-y-4 scrollbar-thin scrollbar-thumb-stone-700 scrollbar-track-transparent"
205+
className="terminal-scroll"
206+
style={{
207+
height: "384px",
208+
overflowY: "auto",
209+
padding: "16px",
210+
fontFamily: "inherit",
211+
fontSize: "13px",
212+
}}
166213
>
167214
{messages.map((message) => (
168215
<MessageBubble key={message.id} message={message} />
169216
))}
170217
{isLoading && (
171-
<div className="flex justify-start">
172-
<div className="bg-stone-800 rounded-2xl px-4 py-3 border border-stone-700/60">
173-
<div className="flex space-x-1.5 items-center">
174-
<div className="w-1.5 h-1.5 bg-stone-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
175-
<div className="w-1.5 h-1.5 bg-stone-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
176-
<div className="w-1.5 h-1.5 bg-stone-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
177-
</div>
178-
</div>
218+
<div style={{ display: "flex", alignItems: "center", gap: "6px", color: "var(--term-dim)" }}>
219+
<span style={{ color: "var(--term-blue)" }}>ai</span>
220+
<span style={{ color: "var(--term-dim)" }}>: </span>
221+
<span style={{ color: "var(--term-green)" }}>
222+
thinking
223+
<span className="cursor-blink" />
224+
</span>
179225
</div>
180226
)}
181227
<div ref={messagesEndRef} />
182228
</div>
183229

184-
{/* Input + Chips */}
185-
<div className="p-5 bg-stone-950/50 border-t border-stone-800 space-y-3">
186-
{/* Input row */}
187-
<div className="flex gap-2">
188-
<Input
189-
value={inputValue}
190-
onChange={(e) => setInputValue(e.target.value)}
191-
onKeyDown={handleKeyPress}
192-
placeholder="Ask me anything about my background..."
193-
className="bg-stone-800 border-stone-700 text-stone-100 placeholder:text-stone-500 rounded-xl focus-visible:ring-[#cf6b47]/40 focus-visible:border-stone-600"
194-
disabled={isLoading || !isServerOnline}
195-
/>
196-
<Button
197-
onClick={() => handleSendMessage(inputValue)}
198-
disabled={isLoading || !inputValue.trim() || !isServerOnline}
199-
className="rounded-xl px-3.5 bg-[#cf6b47]/20 hover:bg-[#cf6b47]/30 text-[#cf6b47] border border-[#cf6b47]/30 hover:border-[#cf6b47]/50 transition-all duration-150 disabled:opacity-40"
200-
>
201-
<Send size={15} />
202-
</Button>
203-
</div>
204-
205-
{/* Predefined question chips */}
206-
<div>
207-
<p className="text-xs text-stone-600 mb-2 font-medium uppercase tracking-wider">Try asking</p>
208-
<div className="flex flex-wrap gap-1.5">
230+
{/* Input area */}
231+
<div
232+
style={{
233+
borderTop: "1px solid var(--term-border)",
234+
padding: "12px 16px",
235+
backgroundColor: "var(--term-bg)",
236+
}}
237+
>
238+
{/* Predefined questions */}
239+
<div style={{ marginBottom: "10px" }}>
240+
<div style={{ fontSize: "11px", color: "var(--term-dim)", marginBottom: "6px" }}>
241+
# quick commands:
242+
</div>
243+
<div style={{ display: "flex", flexWrap: "wrap", gap: "6px" }}>
209244
{PREDEFINED_QUESTIONS.map((question, index) => (
210245
<button
211246
key={index}
212247
onClick={() => handleSendMessage(question)}
213248
disabled={isLoading || !isServerOnline}
214-
className="px-3 py-1.5 text-xs border border-stone-700 text-stone-400 hover:border-stone-600 hover:text-stone-300 rounded-full transition-all duration-150 disabled:opacity-40 disabled:cursor-not-allowed"
249+
style={{
250+
padding: "2px 8px",
251+
border: "1px solid var(--term-border)",
252+
backgroundColor: "transparent",
253+
color: "var(--term-dim)",
254+
fontSize: "11px",
255+
cursor: "pointer",
256+
fontFamily: "inherit",
257+
transition: "all 0.15s",
258+
opacity: isLoading || !isServerOnline ? 0.4 : 1,
259+
}}
260+
onMouseEnter={(e) => {
261+
if (!isLoading && isServerOnline) {
262+
e.currentTarget.style.borderColor = "var(--term-green)";
263+
e.currentTarget.style.color = "var(--term-green)";
264+
}
265+
}}
266+
onMouseLeave={(e) => {
267+
e.currentTarget.style.borderColor = "var(--term-border)";
268+
e.currentTarget.style.color = "var(--term-dim)";
269+
}}
215270
>
216-
{question}
271+
[{index + 1}] {question}
217272
</button>
218273
))}
219274
</div>
220275
</div>
276+
277+
{/* Command prompt input */}
278+
<div
279+
style={{
280+
display: "flex",
281+
alignItems: "center",
282+
gap: "6px",
283+
border: "1px solid var(--term-border)",
284+
padding: "8px 12px",
285+
backgroundColor: "var(--term-bg2)",
286+
}}
287+
onClick={() => inputRef.current?.focus()}
288+
>
289+
<span style={{ color: "var(--term-green)", fontSize: "13px", flexShrink: 0 }}></span>
290+
<input
291+
ref={inputRef}
292+
value={inputValue}
293+
onChange={(e) => setInputValue(e.target.value)}
294+
onKeyDown={handleKeyPress}
295+
placeholder="type your question..."
296+
disabled={isLoading || !isServerOnline}
297+
style={{
298+
flex: 1,
299+
background: "transparent",
300+
border: "none",
301+
outline: "none",
302+
color: "var(--term-text)",
303+
fontFamily: "inherit",
304+
fontSize: "13px",
305+
caretColor: "var(--term-green)",
306+
}}
307+
/>
308+
<button
309+
onClick={() => handleSendMessage(inputValue)}
310+
disabled={isLoading || !inputValue.trim() || !isServerOnline}
311+
style={{
312+
background: "transparent",
313+
border: "none",
314+
color: inputValue.trim() && !isLoading && isServerOnline
315+
? "var(--term-green)"
316+
: "var(--term-border)",
317+
cursor: inputValue.trim() && !isLoading && isServerOnline ? "pointer" : "default",
318+
fontFamily: "inherit",
319+
fontSize: "12px",
320+
padding: "0 4px",
321+
transition: "color 0.15s",
322+
flexShrink: 0,
323+
}}
324+
>
325+
[enter]
326+
</button>
327+
</div>
221328
</div>
222329
</div>
223330
</div>

0 commit comments

Comments
 (0)