Skip to content

Commit fac94ba

Browse files
author
owlyyy
committed
fix(ui): apply website audit clean-up fixes (light mode, onboarding chips, SEO meta tags, API base URL config)
1 parent bdf55c1 commit fac94ba

6 files changed

Lines changed: 181 additions & 32 deletions

File tree

frontend/index.html

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,23 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/png" href="/owl_icon.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Owlbearag RAG Chat</title>
7+
<title>Owlbearag - Autonomous GPU RAG Assistant</title>
8+
<meta name="description" content="Autonomous GPU Node RAG Chat Interface & Multi-Node AI Orchestration Console." />
9+
10+
<!-- Open Graph / Facebook -->
11+
<meta property="og:type" content="website" />
12+
<meta property="og:url" content="https://areqpl.github.io/Owlbearag/" />
13+
<meta property="og:title" content="Owlbearag - Autonomous GPU RAG Assistant" />
14+
<meta property="og:description" content="Autonomous GPU Node RAG Chat Interface & Multi-Node AI Orchestration Console." />
15+
<meta property="og:image" content="https://areqpl.github.io/Owlbearag/owl_icon.png" />
16+
17+
<!-- Twitter -->
18+
<meta property="twitter:card" content="summary_large_image" />
19+
<meta property="twitter:title" content="Owlbearag - Autonomous GPU RAG Assistant" />
20+
<meta property="twitter:description" content="Autonomous GPU Node RAG Chat Interface & Multi-Node AI Orchestration Console." />
21+
<meta property="twitter:image" content="https://areqpl.github.io/Owlbearag/owl_icon.png" />
822
</head>
9-
<body class="bg-gray-900 text-gray-100 antialiased selection:bg-indigo-500 selection:text-white">
23+
<body class="bg-slate-50 text-slate-900 dark:bg-black dark:text-gray-100 antialiased selection:bg-indigo-500 selection:text-white">
1024
<div id="root"></div>
1125
<script type="module" src="/src/main.tsx"></script>
1226
</body>

frontend/src/App.tsx

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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')}

frontend/src/components/ChatMessage.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ export type Message = {
77
};
88

99
/**
10-
* Chat bubble with owl icon avatar, user avatar, and timestamp.
11-
* Styled for AMOLED dark mode & standard themes.
10+
* Chat bubble supporting Light Mode, AMOLED Dark Mode, and standard dark theme.
1211
*/
1312
export const ChatMessage: FC<Message> = ({ role, content, timestamp }) => {
1413
if (!content) return null;
@@ -17,10 +16,10 @@ export const ChatMessage: FC<Message> = ({ role, content, timestamp }) => {
1716
`flex w-full mb-3 items-start gap-2.5 ` +
1817
(isUser ? 'justify-end' : 'justify-start');
1918
const bubbleClasses =
20-
`max-w-[85%] sm:max-w-md px-4 py-2.5 rounded-2xl break-words text-sm leading-relaxed shadow-sm ` +
19+
`max-w-[85%] sm:max-w-md px-4 py-2.5 rounded-2xl break-words text-sm leading-relaxed shadow-sm transition-colors ` +
2120
(isUser
2221
? 'bg-indigo-600 text-white rounded-tr-none'
23-
: 'bg-gray-800 text-gray-100 dark:bg-[#111111] dark:border dark:border-gray-800 rounded-tl-none');
22+
: 'bg-white text-slate-800 border border-slate-200 dark:bg-[#111111] dark:text-gray-100 dark:border-gray-800 rounded-tl-none');
2423
const timeStr = new Date(timestamp || Date.now()).toLocaleTimeString([], {
2524
hour: '2-digit',
2625
minute: '2-digit',
@@ -29,8 +28,8 @@ export const ChatMessage: FC<Message> = ({ role, content, timestamp }) => {
2928
return (
3029
<div className={containerClasses}>
3130
{!isUser && (
32-
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-950 p-1 border border-indigo-700/50 shadow">
33-
<img src="/owl_icon.png" alt="Owl" className="h-full w-full object-contain" />
31+
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-100 dark:bg-indigo-950 p-1 border border-indigo-200 dark:border-indigo-700/50 shadow-sm">
32+
<img src="/owl_icon.png" alt="Owl Assistant Avatar" className="h-full w-full object-contain" />
3433
</div>
3534
)}
3635
<div
@@ -42,7 +41,7 @@ export const ChatMessage: FC<Message> = ({ role, content, timestamp }) => {
4241
<div className="mt-1 text-[10px] opacity-60 text-right">{timeStr}</div>
4342
</div>
4443
{isUser && (
45-
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-500 font-bold text-xs text-white shadow">
44+
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-600 font-bold text-xs text-white shadow-sm" aria-hidden="true">
4645
👤
4746
</div>
4847
)}

frontend/src/components/DarkModeToggle.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export const DarkModeToggle: FC = () => {
2121
return (
2222
<button
2323
onClick={() => setIsDark((prev) => !prev)}
24-
className="flex items-center gap-1 rounded-lg border border-gray-700 bg-gray-800 px-2.5 py-1.5 text-xs font-semibold text-gray-200 shadow-sm transition hover:bg-gray-700 hover:text-white dark:border-gray-800 dark:bg-black dark:hover:bg-gray-900"
25-
title="Toggle AMOLED Dark Mode"
26-
aria-label="Toggle AMOLED Dark Mode"
24+
className="flex items-center gap-1 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 focus:ring-2 focus:ring-indigo-500 focus:outline-none"
25+
title="Toggle AMOLED Dark / Light Mode"
26+
aria-label="Toggle AMOLED Dark / Light Mode"
2727
>
2828
<span>{isDark ? '🌙 AMOLED' : '☀️ Light'}</span>
2929
</button>

frontend/src/components/LanguageSwitcher.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const LanguageSwitcher: FC<Props> = ({ currentLanguage, onLanguageChange
1212
<select
1313
value={currentLanguage}
1414
onChange={(e) => onLanguageChange(e.target.value as Language)}
15-
className="rounded-lg border border-gray-700 bg-gray-800 px-2.5 py-1.5 text-xs font-semibold text-gray-200 shadow-sm focus:border-indigo-500 focus:outline-none dark:border-gray-800 dark:bg-black dark:text-gray-100"
15+
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-100 dark:hover:bg-gray-900 transition shadow-sm focus:ring-2 focus:ring-indigo-500 focus:outline-none"
1616
aria-label="Select language"
1717
>
1818
{LANGUAGES.map((lang) => (

0 commit comments

Comments
 (0)