@@ -4,15 +4,7 @@ import MessageBubble from "./MessageBubble";
44import { sendMessageStream , checkHealth } from "@/utils/api" ;
55import { toast } from "sonner" ;
66import type { Message } from "@/types/chat" ;
7-
8- const PREDEFINED_QUESTIONS = [
9- "What is your background?" ,
10- "Tell me about your projects" ,
11- "What are your technical skills?" ,
12- "Where did you study?" ,
13- "What's your work experience?" ,
14- "Any patents or publications?" ,
15- ] ;
7+ import { useLang } from "@/i18n/LanguageContext" ;
168
179// ── Neural network thinking indicator ─────────────────────────────────────
1810const NN_FRAMES = [
@@ -53,11 +45,14 @@ const ThinkingIndicator = () => {
5345} ;
5446
5547// ── ChatSection ────────────────────────────────────────────────────────────
48+ const INITIAL_MSG_ID = "1" ;
49+
5650const ChatSection = ( ) => {
51+ const { t, lang } = useLang ( ) ;
5752 const [ messages , setMessages ] = useState < Message [ ] > ( [
5853 {
59- id : "1" ,
60- text : "Hi! I'm representing Yifei. Ask me anything about my background, skills, projects, or experience!" ,
54+ id : INITIAL_MSG_ID ,
55+ text : t . chat . greeting ,
6156 isUser : false ,
6257 timestamp : new Date ( ) ,
6358 } ,
@@ -74,6 +69,16 @@ const ChatSection = () => {
7469 const historyIndexRef = useRef ( - 1 ) ;
7570 const pendingInputRef = useRef ( "" ) ;
7671
72+ // Update greeting when language changes (if no user messages yet)
73+ useEffect ( ( ) => {
74+ setMessages ( ( prev ) => {
75+ const hasUserMsg = prev . some ( ( m ) => m . isUser ) ;
76+ if ( hasUserMsg ) return prev ;
77+ return [ { id : INITIAL_MSG_ID , text : t . chat . greeting , isUser : false , timestamp : prev [ 0 ] . timestamp } ] ;
78+ } ) ;
79+ // eslint-disable-next-line react-hooks/exhaustive-deps
80+ } , [ lang ] ) ;
81+
7782 const isNearBottom = ( ) : boolean => {
7883 const el = scrollContainerRef . current ;
7984 if ( ! el ) return true ;
@@ -99,26 +104,27 @@ const ChatSection = () => {
99104 const isOnline = await checkHealth ( ) ;
100105 setIsServerOnline ( isOnline ) ;
101106 if ( ! isOnline ) {
102- toast . error ( "AI server is currently offline. Please try again later." ) ;
107+ toast . error ( t . chat . offlineToast ) ;
103108 }
104109 } ;
105110 checkServerStatus ( ) ;
106111 const interval = setInterval ( checkServerStatus , 30000 ) ;
107112 return ( ) => clearInterval ( interval ) ;
113+ // eslint-disable-next-line react-hooks/exhaustive-deps
108114 } , [ ] ) ;
109115
110116 const handleExport = ( ) => {
111117 if ( messages . length <= 1 ) {
112- toast . info ( "Nothing to export yet — start a conversation first!" ) ;
118+ toast . info ( t . chat . exportNothing ) ;
113119 return ;
114120 }
115121 const lines = messages . map ( ( m ) => {
116122 const time = m . timestamp . toLocaleTimeString ( [ ] , { hour : "2-digit" , minute : "2-digit" } ) ;
117123 return m . isUser
118- ? `**[${ time } ] You :** ${ m . text } `
119- : `**[${ time } ] AI :** ${ m . text } ` ;
124+ ? `**[${ time } ] ${ t . chat . youLabel } :** ${ m . text } `
125+ : `**[${ time } ] ${ t . chat . aiLabel } :** ${ m . text } ` ;
120126 } ) ;
121- const content = `# Chat Export \n\n_Exported on ${ new Date ( ) . toLocaleString ( ) } _\n\n---\n\n${ lines . join ( "\n\n---\n\n" ) } ` ;
127+ const content = `${ t . chat . exportHeader } \n\n_ ${ t . chat . exportedOn } ${ new Date ( ) . toLocaleString ( ) } _\n\n---\n\n${ lines . join ( "\n\n---\n\n" ) } ` ;
122128 const blob = new Blob ( [ content ] , { type : "text/markdown" } ) ;
123129 const url = URL . createObjectURL ( blob ) ;
124130 const a = document . createElement ( "a" ) ;
@@ -131,7 +137,7 @@ const ChatSection = () => {
131137 const handleSendMessage = async ( messageText : string ) => {
132138 if ( ! messageText . trim ( ) ) return ;
133139 if ( ! isServerOnline ) {
134- toast . error ( "AI server is currently offline. Please try again later." ) ;
140+ toast . error ( t . chat . offlineToast ) ;
135141 return ;
136142 }
137143
@@ -183,15 +189,15 @@ const ChatSection = () => {
183189 console . error ( "Stream error:" , error ) ;
184190 setIsLoading ( false ) ;
185191 setStreamingMessageId ( null ) ;
186- toast . error ( "Failed to get response. Please try again later." ) ;
192+ toast . error ( t . chat . streamError ) ;
187193 setMessages ( ( prev ) => {
188194 const hasAiMessage = prev . some ( ( m ) => m . id === aiId ) ;
189- if ( hasAiMessage ) return prev ; // partial response already shown, keep it
195+ if ( hasAiMessage ) return prev ;
190196 return [
191197 ...prev ,
192198 {
193199 id : aiId ,
194- text : "Sorry, I'm having trouble connecting right now. Please try again later." ,
200+ text : t . chat . fallbackError ,
195201 isUser : false ,
196202 timestamp : new Date ( ) ,
197203 } ,
@@ -233,7 +239,7 @@ const ChatSection = () => {
233239 < section className = "px-4 pb-6" style = { { position : "relative" , zIndex : 1 } } >
234240 < div className = "max-w-4xl mx-auto" >
235241 < div style = { { border : "1px solid var(--term-border)" , backgroundColor : "var(--term-bg)" } } >
236- { /* Terminal title bar — enhanced AI style */ }
242+ { /* Terminal title bar */ }
237243 < div
238244 style = { {
239245 borderBottom : "1px solid var(--term-border)" ,
@@ -248,16 +254,16 @@ const ChatSection = () => {
248254 >
249255 < span style = { { color : "var(--term-dim)" , fontSize : "12px" } } >
250256 🧠{ " " }
251- < span style = { { color : "var(--term-text)" } } > AI Assistant v2.0 </ span >
257+ < span style = { { color : "var(--term-text)" } } > { t . chat . titleBar } </ span >
252258 < span style = { { color : "var(--term-dim)" } } >
253- { " " } | model :{ " " }
259+ { " " } | { t . chat . modelLabel } :{ " " }
254260 </ span >
255261 < span style = { { color : "var(--term-cyan)" } } > rag-powered</ span >
256262 < span style = { { color : "var(--term-dim)" } } >
257- { " " } | status :{ " " }
263+ { " " } | { t . chat . statusLabel } :{ " " }
258264 </ span >
259265 < span style = { { color : isServerOnline ? "var(--term-green)" : "var(--term-red)" } } >
260- { isServerOnline ? "online" : "offline" }
266+ { isServerOnline ? t . chat . statusOnline : t . chat . statusOffline }
261267 </ span >
262268 </ span >
263269 < div style = { { display : "flex" , alignItems : "center" , gap : "10px" } } >
@@ -281,12 +287,12 @@ const ChatSection = () => {
281287 animation : isServerOnline ? "neuralPulse 2s ease-in-out infinite" : "none" ,
282288 } }
283289 />
284- { isServerOnline ? "server:online" : "server:offline" }
290+ { isServerOnline ? t . chat . serverOnline : t . chat . serverOffline }
285291 </ span >
286292 { /* Export button */ }
287293 < button
288294 onClick = { handleExport }
289- title = "Export chat as Markdown"
295+ title = { t . chat . exportTooltip }
290296 style = { {
291297 background : "transparent" ,
292298 border : "1px solid var(--term-border)" ,
@@ -303,7 +309,7 @@ const ChatSection = () => {
303309 className = "chat-quick-btn"
304310 >
305311 < Download size = { 11 } />
306- export
312+ { t . chat . exportBtn }
307313 </ button >
308314 </ div >
309315 </ div >
@@ -336,7 +342,7 @@ const ChatSection = () => {
336342 } }
337343 >
338344 < AlertCircle size = { 13 } />
339- error: AI server is offline. Responses unavailable.
345+ { t . chat . offlineError }
340346 </ div >
341347 ) }
342348
@@ -377,10 +383,10 @@ const ChatSection = () => {
377383 { /* Predefined questions */ }
378384 < div style = { { marginBottom : "10px" } } >
379385 < div style = { { fontSize : "11px" , color : "var(--term-dim)" , marginBottom : "6px" } } >
380- # quick commands:
386+ { t . chat . quickCommands }
381387 </ div >
382388 < div className = "grid grid-cols-2 sm:flex sm:flex-wrap gap-1.5" >
383- { PREDEFINED_QUESTIONS . map ( ( question , index ) => (
389+ { t . chat . questions . map ( ( question , index ) => (
384390 < button
385391 key = { index }
386392 onClick = { ( ) => handleSendMessage ( question ) }
@@ -422,7 +428,7 @@ const ChatSection = () => {
422428 value = { inputValue }
423429 onChange = { ( e ) => setInputValue ( e . target . value ) }
424430 onKeyDown = { handleKeyPress }
425- placeholder = "type your question..."
431+ placeholder = { t . chat . placeholder }
426432 disabled = { isLoading || ! isServerOnline }
427433 style = { {
428434 flex : 1 ,
@@ -455,7 +461,7 @@ const ChatSection = () => {
455461 flexShrink : 0 ,
456462 } }
457463 >
458- [enter]
464+ { t . chat . enterBtn }
459465 </ button >
460466 </ div >
461467 </ div >
0 commit comments