11import { 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" ;
53import MessageBubble from "./MessageBubble" ;
64import { sendMessage , checkHealth } from "@/utils/api" ;
75import { 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