@@ -20,6 +20,45 @@ const PREDEFINED_QUESTIONS = [
2020 "Any patents or publications?" ,
2121] ;
2222
23+ // ── Neural network thinking indicator ─────────────────────────────────────
24+ const NN_FRAMES = [
25+ "[●○○] ──▶ [○○] ──▶ [○]" ,
26+ "[○●○] ──▶ [○○] ──▶ [○]" ,
27+ "[○○●] ──▶ [○○] ──▶ [○]" ,
28+ "[○○○] ──▶ [●○] ──▶ [○]" ,
29+ "[○○○] ──▶ [○●] ──▶ [○]" ,
30+ "[○○○] ──▶ [○○] ──▶ [●]" ,
31+ ] ;
32+
33+ const ThinkingIndicator = ( ) => {
34+ const [ frame , setFrame ] = useState ( 0 ) ;
35+
36+ useEffect ( ( ) => {
37+ const id = setInterval ( ( ) => setFrame ( ( f ) => ( f + 1 ) % NN_FRAMES . length ) , 180 ) ;
38+ return ( ) => clearInterval ( id ) ;
39+ } , [ ] ) ;
40+
41+ return (
42+ < div style = { { display : "flex" , alignItems : "center" , gap : "8px" , flexWrap : "wrap" } } >
43+ < span style = { { color : "var(--term-blue)" , fontSize : "11px" , flexShrink : 0 } } > ai</ span >
44+ < span style = { { color : "var(--term-dim)" } } > : </ span >
45+ < span
46+ style = { {
47+ color : "var(--term-dim)" ,
48+ fontSize : "11px" ,
49+ fontFamily : "inherit" ,
50+ letterSpacing : "0.5px" ,
51+ } }
52+ >
53+ { NN_FRAMES [ frame ] }
54+ </ span >
55+ < span style = { { color : "var(--term-green)" , fontSize : "12px" } } > thinking</ span >
56+ < span className = "cursor-blink" />
57+ </ div >
58+ ) ;
59+ } ;
60+
61+ // ── ChatSection ────────────────────────────────────────────────────────────
2362const ChatSection = ( ) => {
2463 const [ messages , setMessages ] = useState < Message [ ] > ( [
2564 {
@@ -32,6 +71,7 @@ const ChatSection = () => {
3271 const [ inputValue , setInputValue ] = useState ( "" ) ;
3372 const [ isLoading , setIsLoading ] = useState ( false ) ;
3473 const [ isServerOnline , setIsServerOnline ] = useState ( true ) ;
74+ const [ typingMessageId , setTypingMessageId ] = useState < string | null > ( null ) ;
3575 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
3676 const isInitialLoad = useRef ( true ) ;
3777 const scrollContainerRef = useRef < HTMLDivElement > ( null ) ;
@@ -76,6 +116,8 @@ const ChatSection = () => {
76116 toast . error ( "AI server is currently offline. Please try again later." ) ;
77117 return ;
78118 }
119+ // Finish any ongoing typewriter immediately
120+ setTypingMessageId ( null ) ;
79121
80122 const userMessage : Message = {
81123 id : Date . now ( ) . toString ( ) ,
@@ -91,8 +133,9 @@ const ChatSection = () => {
91133
92134 try {
93135 const response = await sendMessage ( messageText ) ;
136+ const aiId = ( Date . now ( ) + 1 ) . toString ( ) ;
94137 const aiMessage : Message = {
95- id : ( Date . now ( ) + 1 ) . toString ( ) ,
138+ id : aiId ,
96139 text : response . success
97140 ? response . response
98141 : response . response || "Sorry, I'm having trouble connecting right now. Please try again later." ,
@@ -101,6 +144,7 @@ const ChatSection = () => {
101144 } ;
102145 if ( ! response . success ) toast . error ( "Failed to get response. Please try again." ) ;
103146 setMessages ( ( prev ) => [ ...prev , aiMessage ] ) ;
147+ setTypingMessageId ( aiId ) ;
104148 } catch ( error ) {
105149 console . error ( "Error getting AI response:" , error ) ;
106150 toast . error ( "Failed to get response. Please try again later." ) ;
@@ -127,10 +171,10 @@ const ChatSection = () => {
127171 } ;
128172
129173 return (
130- < section className = "px-4 pb-6" >
174+ < section className = "px-4 pb-6" style = { { position : "relative" , zIndex : 1 } } >
131175 < div className = "max-w-4xl mx-auto" >
132176 < div style = { { border : "1px solid var(--term-border)" , backgroundColor : "var(--term-bg)" } } >
133- { /* Terminal title bar */ }
177+ { /* Terminal title bar — enhanced AI style */ }
134178 < div
135179 style = { {
136180 borderBottom : "1px solid var(--term-border)" ,
@@ -139,12 +183,25 @@ const ChatSection = () => {
139183 display : "flex" ,
140184 alignItems : "center" ,
141185 justifyContent : "space-between" ,
186+ flexWrap : "wrap" ,
187+ gap : "6px" ,
142188 } }
143189 >
144190 < span style = { { color : "var(--term-dim)" , fontSize : "12px" } } >
145- bash — ~/chat
191+ 🧠{ " " }
192+ < span style = { { color : "var(--term-text)" } } > AI Assistant v2.0</ span >
193+ < span style = { { color : "var(--term-dim)" } } >
194+ { " " } | model:{ " " }
195+ </ span >
196+ < span style = { { color : "var(--term-cyan)" } } > rag-powered</ span >
197+ < span style = { { color : "var(--term-dim)" } } >
198+ { " " } | status:{ " " }
199+ </ span >
200+ < span style = { { color : isServerOnline ? "var(--term-green)" : "var(--term-red)" } } >
201+ { isServerOnline ? "online" : "offline" }
202+ </ span >
146203 </ span >
147- { /* Server status */ }
204+ { /* Server status dot */ }
148205 < span
149206 style = { {
150207 fontSize : "11px" ,
@@ -161,6 +218,7 @@ const ChatSection = () => {
161218 borderRadius : "50%" ,
162219 backgroundColor : isServerOnline ? "var(--term-green)" : "var(--term-red)" ,
163220 display : "inline-block" ,
221+ animation : isServerOnline ? "neuralPulse 2s ease-in-out infinite" : "none" ,
164222 } }
165223 />
166224 { isServerOnline ? "server:online" : "server:offline" }
@@ -212,18 +270,14 @@ const ChatSection = () => {
212270 } }
213271 >
214272 { messages . map ( ( message ) => (
215- < MessageBubble key = { message . id } message = { message } />
273+ < MessageBubble
274+ key = { message . id }
275+ message = { message }
276+ isTyping = { typingMessageId === message . id }
277+ onTypingComplete = { ( ) => setTypingMessageId ( null ) }
278+ />
216279 ) ) }
217- { isLoading && (
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 >
225- </ div >
226- ) }
280+ { isLoading && < ThinkingIndicator /> }
227281 < div ref = { messagesEndRef } />
228282 </ div >
229283
@@ -261,11 +315,13 @@ const ChatSection = () => {
261315 if ( ! isLoading && isServerOnline ) {
262316 e . currentTarget . style . borderColor = "var(--term-green)" ;
263317 e . currentTarget . style . color = "var(--term-green)" ;
318+ e . currentTarget . style . backgroundColor = "rgba(63,185,80,0.06)" ;
264319 }
265320 } }
266321 onMouseLeave = { ( e ) => {
267322 e . currentTarget . style . borderColor = "var(--term-border)" ;
268323 e . currentTarget . style . color = "var(--term-dim)" ;
324+ e . currentTarget . style . backgroundColor = "transparent" ;
269325 } }
270326 >
271327 [{ index + 1 } ] { question }
@@ -311,10 +367,12 @@ const ChatSection = () => {
311367 style = { {
312368 background : "transparent" ,
313369 border : "none" ,
314- color : inputValue . trim ( ) && ! isLoading && isServerOnline
315- ? "var(--term-green)"
316- : "var(--term-border)" ,
317- cursor : inputValue . trim ( ) && ! isLoading && isServerOnline ? "pointer" : "default" ,
370+ color :
371+ inputValue . trim ( ) && ! isLoading && isServerOnline
372+ ? "var(--term-green)"
373+ : "var(--term-border)" ,
374+ cursor :
375+ inputValue . trim ( ) && ! isLoading && isServerOnline ? "pointer" : "default" ,
318376 fontFamily : "inherit" ,
319377 fontSize : "12px" ,
320378 padding : "0 4px" ,
0 commit comments