@@ -23,14 +23,19 @@ export const App: FC = () => {
2323 return saved || 'en' ;
2424 } ) ;
2525
26+ const [ apiBaseUrl , setApiBaseUrl ] = useState < string > ( ( ) => {
27+ return localStorage . getItem ( 'owlbearag_api_base' ) || import . meta. env . VITE_API_BASE_URL || '' ;
28+ } ) ;
29+
30+ const [ showSettings , setShowSettings ] = useState ( false ) ;
2631 const [ loading , setLoading ] = useState ( false ) ;
2732 const [ error , setError ] = useState < string | null > ( null ) ;
2833
2934 useEffect ( ( ) => {
3035 try {
3136 localStorage . setItem ( 'owlbearag_chat_messages' , JSON . stringify ( messages ) ) ;
3237 } catch {
33- // Ignore quota error
38+ // Ignore storage quota error
3439 }
3540 } , [ messages ] ) ;
3641
@@ -39,11 +44,20 @@ export const App: FC = () => {
3944 localStorage . setItem ( 'owlbearag_lang' , newLang ) ;
4045 } ;
4146
47+ const handleSaveApiBase = ( url : string ) => {
48+ setApiBaseUrl ( url ) ;
49+ localStorage . setItem ( 'owlbearag_api_base' , url ) ;
50+ } ;
51+
4252 const handleClearChat = ( ) => {
4353 setMessages ( [ ] ) ;
4454 localStorage . removeItem ( 'owlbearag_chat_messages' ) ;
4555 } ;
4656
57+ const handleSelectChip = ( promptText : string ) => {
58+ setInput ( promptText ) ;
59+ } ;
60+
4761 const handleSubmit = async ( e : React . FormEvent ) => {
4862 e . preventDefault ( ) ;
4963 const trimmed = input . trim ( ) ;
@@ -66,8 +80,10 @@ export const App: FC = () => {
6680 setLoading ( true ) ;
6781 setError ( null ) ;
6882
83+ const targetUrl = apiBaseUrl ? `${ apiBaseUrl . replace ( / \/ $ / , '' ) } /query` : '/query' ;
84+
6985 try {
70- const resp = await fetch ( '/query' , {
86+ const resp = await fetch ( targetUrl , {
7187 method : 'POST' ,
7288 headers : { 'Content-Type' : 'application/json' } ,
7389 body : JSON . stringify ( { query : trimmed , stream : true } ) ,
@@ -112,7 +128,7 @@ export const App: FC = () => {
112128 } catch ( err ) {
113129 const msg = err instanceof Error ? err . message : getTranslation ( lang , 'serverError' ) ;
114130 setError ( msg ) ;
115- // Remove empty assistant placeholder on error
131+ // Remove empty placeholder on error
116132 setMessages ( ( prev ) => prev . filter ( ( m ) => m . content . length > 0 ) ) ;
117133 } finally {
118134 setLoading ( false ) ;
@@ -122,26 +138,45 @@ export const App: FC = () => {
122138 const t = ( key : string ) => getTranslation ( lang , key ) ;
123139
124140 return (
125- < section className = "flex h-screen flex-col bg-gray-900 text-gray-100 dark:bg-black transition-colors duration-200" >
141+ < section className = "flex h-screen flex-col bg-slate-50 text-slate-900 dark:bg-black dark:text-gray-100 transition-colors duration-200" >
126142 { /* Header Bar */ }
127- < header className = "flex flex-wrap items-center justify-between border-b border-gray-800 bg-gray-900 /90 px-4 py-3 backdrop-blur dark:bg-black/90 dark:border-gray-800" >
143+ < header className = "flex flex-wrap items-center justify-between border-b border-slate-200 bg-white /90 px-4 py-3 backdrop-blur dark:bg-black/90 dark:border-gray-800 shadow-sm " >
128144 < div className = "flex items-center gap-2.5" >
129- < img src = "/owl_icon.png" alt = "Owlbearag Logo" className = "h-7 w-7 object-contain" />
145+ < img src = "/owl_icon.png" alt = "Owlbearag Logo" className = "h-8 w-8 object-contain" />
130146 < div >
131- < h1 className = "text-lg font-bold tracking-tight text-indigo-400 dark:text-indigo-300 " >
147+ < h1 className = "text-lg font-bold tracking-tight text-indigo-600 dark:text-indigo-400 " >
132148 { t ( 'title' ) }
133149 </ h1 >
134- < p className = "text-[10px] text-gray-400 font-mono" > { t ( 'subtitle' ) } </ p >
150+ < p className = "text-[10px] text-slate-500 dark:text- gray-400 font-mono" > { t ( 'subtitle' ) } </ p >
135151 </ div >
136152 </ div >
137153
138- < div className = "flex items-center gap-2 mt-2 sm:mt-0" >
154+ < div className = "flex items-center gap-2 mt-2 sm:mt-0 flex-wrap" >
155+ < a
156+ href = "https://github.com/areqpl/Owlbearag"
157+ target = "_blank"
158+ rel = "noreferrer"
159+ className = "rounded-lg border border-slate-300 bg-slate-100 px-2.5 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-200 dark:border-gray-800 dark:bg-black dark:text-gray-200 dark:hover:bg-gray-900 transition flex items-center gap-1 shadow-sm"
160+ title = "GitHub Repository"
161+ >
162+ ⭐ { t ( 'github' ) }
163+ </ a >
164+
165+ < button
166+ onClick = { ( ) => setShowSettings ( ! showSettings ) }
167+ className = "rounded-lg border border-slate-300 bg-slate-100 px-2.5 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-200 dark:border-gray-800 dark:bg-black dark:text-gray-200 dark:hover:bg-gray-900 transition shadow-sm"
168+ title = "Configure API Endpoint"
169+ >
170+ ⚙️ API
171+ </ button >
172+
139173 < LanguageSwitcher currentLanguage = { lang } onLanguageChange = { handleLanguageChange } />
140174 < DarkModeToggle />
175+
141176 { messages . length > 0 && (
142177 < button
143178 onClick = { handleClearChat }
144- className = "rounded-lg border border-red-900/50 bg-red-950/30 px-2.5 py-1.5 text-xs font-semibold text-red-300 hover:bg-red-900/60 hover: text-white transition"
179+ className = "rounded-lg border border-red-200 bg-red-50 px-2.5 py-1.5 text-xs font-semibold text-red-600 hover:bg-red-100 dark:border-red- 900/50 dark:bg-red-950/30 dark: text-red-300 dark:hover:bg-red-900/60 transition shadow-sm "
145180 title = { t ( 'clear' ) }
146181 >
147182 🗑️ { t ( 'clear' ) }
@@ -150,26 +185,85 @@ export const App: FC = () => {
150185 </ div >
151186 </ header >
152187
153- { /* Main Chat Display */ }
154- < ChatWindow messages = { messages } />
188+ { /* Settings Modal Bar (if toggled) */ }
189+ { showSettings && (
190+ < div className = "border-b border-indigo-200 bg-indigo-50/80 px-4 py-2.5 text-xs dark:border-indigo-900/40 dark:bg-indigo-950/20 transition-all flex items-center gap-2" >
191+ < label htmlFor = "apiBaseInput" className = "font-medium text-slate-700 dark:text-gray-300" >
192+ { t ( 'apiUrlLabel' ) }
193+ </ label >
194+ < input
195+ id = "apiBaseInput"
196+ type = "text"
197+ value = { apiBaseUrl }
198+ onChange = { ( e ) => handleSaveApiBase ( e . target . value ) }
199+ placeholder = "http://127.0.0.1:8000"
200+ className = "flex-1 rounded border border-slate-300 bg-white px-2.5 py-1 text-slate-800 placeholder-gray-400 focus:border-indigo-500 focus:outline-none dark:border-gray-800 dark:bg-black dark:text-gray-100"
201+ />
202+ </ div >
203+ ) }
204+
205+ { /* Empty State / Welcome Container */ }
206+ { messages . length === 0 ? (
207+ < div className = "flex-1 flex flex-col items-center justify-center p-6 text-center" >
208+ < div className = "max-w-md space-y-4" >
209+ < div className = "inline-flex h-16 w-16 items-center justify-center rounded-full bg-indigo-100 p-2.5 dark:bg-indigo-950/80 border border-indigo-200 dark:border-indigo-800 shadow-md" >
210+ < img src = "/owl_icon.png" alt = "Owlbearag Icon" className = "h-full w-full object-contain" />
211+ </ div >
212+ < h2 className = "text-xl font-bold text-slate-900 dark:text-white" >
213+ { t ( 'welcomeTitle' ) }
214+ </ h2 >
215+ < p className = "text-xs text-slate-600 dark:text-gray-400 leading-relaxed" >
216+ { t ( 'welcomeSub' ) }
217+ </ p >
218+
219+ { /* Starter Prompt Chips */ }
220+ < div className = "pt-2 flex flex-col gap-2" >
221+ < button
222+ onClick = { ( ) => handleSelectChip ( t ( 'chip1' ) ) }
223+ className = "rounded-xl border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-700 hover:border-indigo-400 hover:bg-indigo-50/50 dark:border-gray-800 dark:bg-[#111111] dark:text-gray-200 dark:hover:border-indigo-600 dark:hover:bg-indigo-950/30 transition text-left shadow-sm flex items-center justify-between"
224+ >
225+ < span > 💡 { t ( 'chip1' ) } </ span >
226+ < span className = "text-indigo-500 font-bold" > →</ span >
227+ </ button >
228+ < button
229+ onClick = { ( ) => handleSelectChip ( t ( 'chip2' ) ) }
230+ className = "rounded-xl border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-700 hover:border-indigo-400 hover:bg-indigo-50/50 dark:border-gray-800 dark:bg-[#111111] dark:text-gray-200 dark:hover:border-indigo-600 dark:hover:bg-indigo-950/30 transition text-left shadow-sm flex items-center justify-between"
231+ >
232+ < span > ⚡ { t ( 'chip2' ) } </ span >
233+ < span className = "text-indigo-500 font-bold" > →</ span >
234+ </ button >
235+ < button
236+ onClick = { ( ) => handleSelectChip ( t ( 'chip3' ) ) }
237+ className = "rounded-xl border border-slate-200 bg-white px-4 py-2 text-xs font-medium text-slate-700 hover:border-indigo-400 hover:bg-indigo-50/50 dark:border-gray-800 dark:bg-[#111111] dark:text-gray-200 dark:hover:border-indigo-600 dark:hover:bg-indigo-950/30 transition text-left shadow-sm flex items-center justify-between"
238+ >
239+ < span > 🧠 { t ( 'chip3' ) } </ span >
240+ < span className = "text-indigo-500 font-bold" > →</ span >
241+ </ button >
242+ </ div >
243+ </ div >
244+ </ div >
245+ ) : (
246+ /* Main Chat Window */
247+ < ChatWindow messages = { messages } />
248+ ) }
155249
156250 { /* Input Form */ }
157251 < form
158252 onSubmit = { handleSubmit }
159- className = "flex items-center gap-2 border-t border-gray-800 bg-gray-900 px-4 py-3 dark:bg-black dark:border-gray-800"
253+ className = "flex items-center gap-2 border-t border-slate-200 bg-white px-4 py-3 dark:bg-black dark:border-gray-800 shadow-lg "
160254 >
161255 < input
162256 type = "text"
163257 value = { input }
164258 onChange = { ( e ) => setInput ( e . target . value ) }
165259 placeholder = { t ( 'placeholder' ) }
166- className = "flex-1 rounded-xl border border-gray-700 bg-gray-800 px-4 py-2.5 text-sm text-white placeholder-gray -400 focus:border-indigo-500 focus:outline-none dark:border-gray-800 dark:bg-[#111111]"
260+ className = "flex-1 rounded-xl border border-slate-300 bg-slate-50 px-4 py-2.5 text-sm text-slate-900 placeholder-slate -400 focus:border-indigo-500 focus:bg-white focus: outline-none dark:border-gray-800 dark:bg-[#111111] dark:text-white dark:placeholder-gray-500 shadow-inner "
167261 aria-label = { t ( 'placeholder' ) }
168262 disabled = { loading }
169263 />
170264 < button
171265 type = "submit"
172- className = "rounded-xl bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-500 disabled:opacity-50 shadow-md"
266+ className = "rounded-xl bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-500 disabled:opacity-50 shadow-md focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 "
173267 disabled = { loading || ! input . trim ( ) }
174268 >
175269 { t ( 'send' ) }
0 commit comments