Spaces:
Running
Running
Commit ·
88b90e7
1
Parent(s): 5900d1e
feat(ui): add polished glassy design, framer motion mechanics, and api status header
Browse files- frontend-next/app/globals.css +19 -0
- frontend-next/app/page.tsx +68 -19
- frontend-next/package-lock.json +4 -4
- frontend-next/package.json +1 -1
frontend-next/app/globals.css
CHANGED
|
@@ -969,3 +969,22 @@ select {
|
|
| 969 |
.blinking-cursor { display: inline-block; width: 8px; height: 16px; background: #fff; animation: blink 1s step-end infinite; }
|
| 970 |
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
| 971 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 969 |
.blinking-cursor { display: inline-block; width: 8px; height: 16px; background: #fff; animation: blink 1s step-end infinite; }
|
| 970 |
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
| 971 |
|
| 972 |
+
|
| 973 |
+
/* -- Additional UI Enhancements -- */
|
| 974 |
+
.top-api-status { position: absolute; right: 24px; top: 16px; z-index: 50; }
|
| 975 |
+
.hero-icon-container { width: 90px; height: 90px; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; background: rgba(0,240,255,0.05); border: 1px solid rgba(0,240,255,0.2); border-radius: 20px; box-shadow: 0 0 40px rgba(0,240,255,0.1); }
|
| 976 |
+
.ai-avatar { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; background: rgba(0,240,255,0.1); border-radius: 6px; box-shadow: 0 0 15px rgba(0,240,255,0.2); }
|
| 977 |
+
.model-badge { font-family: "JetBrains Mono", monospace; font-size: 0.65rem; color: #fff; background: rgba(138,43,226,0.5); padding: 3px 8px; border-radius: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; border: 1px solid rgba(138,43,226,0.8); }
|
| 978 |
+
|
| 979 |
+
/* Improved Chat Bubbles */
|
| 980 |
+
.message-user {
|
| 981 |
+
background: linear-gradient(135deg, rgba(30,58,138,0.6), rgba(88,28,135,0.6)) !important;
|
| 982 |
+
border: 1px solid rgba(255,255,255,0.1) !important;
|
| 983 |
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
| 984 |
+
}
|
| 985 |
+
|
| 986 |
+
.chat-input-wrapper { background: rgba(20,25,35,0.8) !important; backdrop-filter: blur(20px); border: 1px solid rgba(0,240,255,0.2) !important; }
|
| 987 |
+
.chat-input-wrapper:focus-within { background: rgba(25,30,45,0.95); border-color: var(--accent); box-shadow: 0 0 30px rgba(0, 240, 255, 0.2), 0 8px 30px rgba(0, 0, 0, 0.6); }
|
| 988 |
+
|
| 989 |
+
.send-btn { background: #fff !important; border: none; outline: none; }
|
| 990 |
+
.send-btn:hover:not(:disabled) { background: var(--accent) !important; color: #000; box-shadow: 0 0 20px rgba(0,240,255,0.5); }
|
frontend-next/app/page.tsx
CHANGED
|
@@ -203,6 +203,18 @@ export default function App() {
|
|
| 203 |
const [settingsOpen, setSettingsOpen] = useState(false);
|
| 204 |
const [topK, setTopK] = useState(5);
|
| 205 |
const [category, setCategory] = useState("All");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
const chatEndRef = useRef<HTMLDivElement>(null);
|
| 208 |
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
@@ -390,18 +402,23 @@ export default function App() {
|
|
| 390 |
|
| 391 |
{/* Sidebar */}
|
| 392 |
<div className={`sidebar ${sidebarOpen ? 'open' : ''}`}>
|
| 393 |
-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '
|
| 394 |
-
<div className="brand" style={{ fontSize: '1.
|
| 395 |
-
<Brain size={
|
| 396 |
</div>
|
| 397 |
{sidebarOpen && (
|
| 398 |
<PanelLeftClose size={20} color="#fff" onClick={() => setSidebarOpen(false)} style={{ cursor: 'pointer' }} />
|
| 399 |
)}
|
| 400 |
</div>
|
| 401 |
|
| 402 |
-
<button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
<Plus size={16} /> New Chat
|
| 404 |
-
</button>
|
| 405 |
|
| 406 |
<div style={{ flex: 1, overflowY: 'auto' }}>
|
| 407 |
<div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-muted)', marginBottom: '12px', textTransform: 'uppercase' }}>Recent</div>
|
|
@@ -418,26 +435,52 @@ export default function App() {
|
|
| 418 |
</div>
|
| 419 |
|
| 420 |
{/* Overlay for mobile sidebar */}
|
| 421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
|
| 423 |
{/* Main Area */}
|
| 424 |
<div className="main-chat-area">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
<div className="chat-container">
|
| 426 |
{currentMessages.length === 0 ? (
|
| 427 |
-
<div style={{ margin: 'auto', textAlign: 'center', opacity: 0.
|
| 428 |
-
<
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
| 431 |
) : (
|
| 432 |
currentMessages.map((msg, i) => (
|
| 433 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
{msg.role === 'user' ? (
|
| 435 |
<div style={{ whiteSpace: 'pre-wrap' }}>{msg.content}</div>
|
| 436 |
) : (
|
| 437 |
<div style={{ width: '100%' }}>
|
| 438 |
{/* Name header for AI */}
|
| 439 |
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '
|
| 440 |
-
<Brain size={16} /> ResearchPilot {msg.model_used && <span
|
| 441 |
</div>
|
| 442 |
|
| 443 |
{ /* Stream logic vs Final logic */
|
|
@@ -451,8 +494,8 @@ export default function App() {
|
|
| 451 |
<MessageRenderer content={msg.content} />
|
| 452 |
{/* Citations section if present */}
|
| 453 |
{msg.citations && msg.citations.length > 0 && (
|
| 454 |
-
<div style={{ marginTop: '
|
| 455 |
-
<div style={{ fontSize: '0.8rem', fontWeight:
|
| 456 |
<div className="citations-grid">
|
| 457 |
{msg.citations.map(c => (
|
| 458 |
<div key={c.paper_id} className="citation-card">
|
|
@@ -466,7 +509,7 @@ export default function App() {
|
|
| 466 |
</div>
|
| 467 |
))}
|
| 468 |
</div>
|
| 469 |
-
</div>
|
| 470 |
)}
|
| 471 |
{/* Feedback Row */}
|
| 472 |
{!isStreaming && msg.role === 'assistant' && msg.content && (
|
|
@@ -482,7 +525,7 @@ export default function App() {
|
|
| 482 |
}
|
| 483 |
</div>
|
| 484 |
)}
|
| 485 |
-
</div>
|
| 486 |
))
|
| 487 |
)}
|
| 488 |
<div ref={chatEndRef} style={{ height: "1px" }} />
|
|
@@ -532,9 +575,15 @@ export default function App() {
|
|
| 532 |
className="chat-input"
|
| 533 |
rows={1}
|
| 534 |
/>
|
| 535 |
-
<button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
<Send size={18} />
|
| 537 |
-
</button>
|
| 538 |
</div>
|
| 539 |
</div>
|
| 540 |
</div>
|
|
|
|
| 203 |
const [settingsOpen, setSettingsOpen] = useState(false);
|
| 204 |
const [topK, setTopK] = useState(5);
|
| 205 |
const [category, setCategory] = useState("All");
|
| 206 |
+
const [apiStatus, setApiStatus] = useState<"connecting" | "online" | "offline">("connecting");
|
| 207 |
+
|
| 208 |
+
useEffect(() => {
|
| 209 |
+
const checkStatus = () => {
|
| 210 |
+
fetch(`${API_URL}/health`)
|
| 211 |
+
.then(res => setApiStatus(res.ok ? "online" : "offline"))
|
| 212 |
+
.catch(() => setApiStatus("offline"));
|
| 213 |
+
};
|
| 214 |
+
checkStatus();
|
| 215 |
+
const interval = setInterval(checkStatus, 30000);
|
| 216 |
+
return () => clearInterval(interval);
|
| 217 |
+
}, []);
|
| 218 |
|
| 219 |
const chatEndRef = useRef<HTMLDivElement>(null);
|
| 220 |
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
| 402 |
|
| 403 |
{/* Sidebar */}
|
| 404 |
<div className={`sidebar ${sidebarOpen ? 'open' : ''}`}>
|
| 405 |
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '32px' }}>
|
| 406 |
+
<div className="brand" style={{ fontSize: '1.2rem', gap: '10px' }}>
|
| 407 |
+
<div className="brand-icon"><Brain size={22} color="var(--accent)" /></div> ResearchPilot
|
| 408 |
</div>
|
| 409 |
{sidebarOpen && (
|
| 410 |
<PanelLeftClose size={20} color="#fff" onClick={() => setSidebarOpen(false)} style={{ cursor: 'pointer' }} />
|
| 411 |
)}
|
| 412 |
</div>
|
| 413 |
|
| 414 |
+
<motion.button
|
| 415 |
+
whileHover={{ scale: 1.02, backgroundColor: "rgba(0, 240, 255, 0.1)", borderColor: "rgba(0, 240, 255, 0.3)" }}
|
| 416 |
+
whileTap={{ scale: 0.98 }}
|
| 417 |
+
className="new-chat-btn"
|
| 418 |
+
onClick={handleNewChat}
|
| 419 |
+
>
|
| 420 |
<Plus size={16} /> New Chat
|
| 421 |
+
</motion.button>
|
| 422 |
|
| 423 |
<div style={{ flex: 1, overflowY: 'auto' }}>
|
| 424 |
<div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-muted)', marginBottom: '12px', textTransform: 'uppercase' }}>Recent</div>
|
|
|
|
| 435 |
</div>
|
| 436 |
|
| 437 |
{/* Overlay for mobile sidebar */}
|
| 438 |
+
<AnimatePresence>
|
| 439 |
+
{sidebarOpen && (
|
| 440 |
+
<motion.div
|
| 441 |
+
initial={{ opacity: 0 }}
|
| 442 |
+
animate={{ opacity: 1 }}
|
| 443 |
+
exit={{ opacity: 0 }}
|
| 444 |
+
className="sidebar-overlay"
|
| 445 |
+
onClick={() => setSidebarOpen(false)}
|
| 446 |
+
/>
|
| 447 |
+
)}
|
| 448 |
+
</AnimatePresence>
|
| 449 |
|
| 450 |
{/* Main Area */}
|
| 451 |
<div className="main-chat-area">
|
| 452 |
+
{/* Header API Status */}
|
| 453 |
+
<div className="top-api-status">
|
| 454 |
+
<div className="nav-status">
|
| 455 |
+
<div className={`status-dot ${apiStatus === 'online' ? 'status-online' : 'status-offline'}`} />
|
| 456 |
+
{apiStatus === 'online' ? 'API Online' : apiStatus === 'connecting' ? 'Connecting...' : 'API Offline'}
|
| 457 |
+
</div>
|
| 458 |
+
</div>
|
| 459 |
+
|
| 460 |
<div className="chat-container">
|
| 461 |
{currentMessages.length === 0 ? (
|
| 462 |
+
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} style={{ margin: 'auto', textAlign: 'center', opacity: 0.8, maxWidth: '400px' }}>
|
| 463 |
+
<div className="hero-icon-container">
|
| 464 |
+
<Brain size={56} color="var(--accent)" />
|
| 465 |
+
</div>
|
| 466 |
+
<h2 style={{ fontSize: '1.6rem', fontWeight: 700, marginBottom: '12px' }}>Welcome to ResearchPilot</h2>
|
| 467 |
+
<p style={{ color: 'var(--text-muted)', fontSize: '0.95rem' }}>Ask questions about Machine Learning research, get answers backed directly by ArXiv papers.</p>
|
| 468 |
+
</motion.div>
|
| 469 |
) : (
|
| 470 |
currentMessages.map((msg, i) => (
|
| 471 |
+
<motion.div
|
| 472 |
+
initial={{ opacity: 0, y: 15 }}
|
| 473 |
+
animate={{ opacity: 1, y: 0 }}
|
| 474 |
+
key={msg.id}
|
| 475 |
+
className={msg.role === 'user' ? 'message-user' : 'message-ai'}
|
| 476 |
+
>
|
| 477 |
{msg.role === 'user' ? (
|
| 478 |
<div style={{ whiteSpace: 'pre-wrap' }}>{msg.content}</div>
|
| 479 |
) : (
|
| 480 |
<div style={{ width: '100%' }}>
|
| 481 |
{/* Name header for AI */}
|
| 482 |
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px', fontSize: '0.9rem', color: 'var(--accent)', fontWeight: 600 }}>
|
| 483 |
+
<div className="ai-avatar"><Brain size={16} /></div> ResearchPilot {msg.model_used && <span className="model-badge">{msg.model_used}</span>}
|
| 484 |
</div>
|
| 485 |
|
| 486 |
{ /* Stream logic vs Final logic */
|
|
|
|
| 494 |
<MessageRenderer content={msg.content} />
|
| 495 |
{/* Citations section if present */}
|
| 496 |
{msg.citations && msg.citations.length > 0 && (
|
| 497 |
+
<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} style={{ marginTop: '24px', paddingTop: '20px', borderTop: '1px solid var(--border)' }}>
|
| 498 |
+
<div style={{ fontSize: '0.8rem', fontWeight: 700, color: 'var(--text-muted)', marginBottom: '12px', letterSpacing: '0.05em' }}>SOURCES</div>
|
| 499 |
<div className="citations-grid">
|
| 500 |
{msg.citations.map(c => (
|
| 501 |
<div key={c.paper_id} className="citation-card">
|
|
|
|
| 509 |
</div>
|
| 510 |
))}
|
| 511 |
</div>
|
| 512 |
+
</motion.div>
|
| 513 |
)}
|
| 514 |
{/* Feedback Row */}
|
| 515 |
{!isStreaming && msg.role === 'assistant' && msg.content && (
|
|
|
|
| 525 |
}
|
| 526 |
</div>
|
| 527 |
)}
|
| 528 |
+
</motion.div>
|
| 529 |
))
|
| 530 |
)}
|
| 531 |
<div ref={chatEndRef} style={{ height: "1px" }} />
|
|
|
|
| 575 |
className="chat-input"
|
| 576 |
rows={1}
|
| 577 |
/>
|
| 578 |
+
<motion.button
|
| 579 |
+
whileHover={{ scale: 1.1, boxShadow: "0 0 20px rgba(0,240,255,0.6)", backgroundColor: "var(--accent)" }}
|
| 580 |
+
whileTap={{ scale: 0.9 }}
|
| 581 |
+
onClick={handleSend}
|
| 582 |
+
disabled={!query.trim() || isStreaming}
|
| 583 |
+
className="send-btn"
|
| 584 |
+
>
|
| 585 |
<Send size={18} />
|
| 586 |
+
</motion.button>
|
| 587 |
</div>
|
| 588 |
</div>
|
| 589 |
</div>
|
frontend-next/package-lock.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
| 11 |
"clsx": "^2.1.1",
|
| 12 |
"framer-motion": "^12.38.0",
|
| 13 |
"katex": "^0.16.45",
|
| 14 |
-
"lucide-react": "^1.
|
| 15 |
"next": "16.2.2",
|
| 16 |
"react": "19.2.4",
|
| 17 |
"react-dom": "19.2.4",
|
|
@@ -5257,9 +5257,9 @@
|
|
| 5257 |
}
|
| 5258 |
},
|
| 5259 |
"node_modules/lucide-react": {
|
| 5260 |
-
"version": "1.
|
| 5261 |
-
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.
|
| 5262 |
-
"integrity": "sha512-
|
| 5263 |
"license": "ISC",
|
| 5264 |
"peerDependencies": {
|
| 5265 |
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
|
|
| 11 |
"clsx": "^2.1.1",
|
| 12 |
"framer-motion": "^12.38.0",
|
| 13 |
"katex": "^0.16.45",
|
| 14 |
+
"lucide-react": "^1.8.0",
|
| 15 |
"next": "16.2.2",
|
| 16 |
"react": "19.2.4",
|
| 17 |
"react-dom": "19.2.4",
|
|
|
|
| 5257 |
}
|
| 5258 |
},
|
| 5259 |
"node_modules/lucide-react": {
|
| 5260 |
+
"version": "1.8.0",
|
| 5261 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz",
|
| 5262 |
+
"integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==",
|
| 5263 |
"license": "ISC",
|
| 5264 |
"peerDependencies": {
|
| 5265 |
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
frontend-next/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
| 12 |
"clsx": "^2.1.1",
|
| 13 |
"framer-motion": "^12.38.0",
|
| 14 |
"katex": "^0.16.45",
|
| 15 |
-
"lucide-react": "^1.
|
| 16 |
"next": "16.2.2",
|
| 17 |
"react": "19.2.4",
|
| 18 |
"react-dom": "19.2.4",
|
|
|
|
| 12 |
"clsx": "^2.1.1",
|
| 13 |
"framer-motion": "^12.38.0",
|
| 14 |
"katex": "^0.16.45",
|
| 15 |
+
"lucide-react": "^1.8.0",
|
| 16 |
"next": "16.2.2",
|
| 17 |
"react": "19.2.4",
|
| 18 |
"react-dom": "19.2.4",
|