Subhadip007 commited on
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 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: '24px' }}>
394
- <div className="brand" style={{ fontSize: '1.1rem', gap: '8px' }}>
395
- <Brain size={20} color="var(--accent)" /> ResearchPilot
396
  </div>
397
  {sidebarOpen && (
398
  <PanelLeftClose size={20} color="#fff" onClick={() => setSidebarOpen(false)} style={{ cursor: 'pointer' }} />
399
  )}
400
  </div>
401
 
402
- <button className="new-chat-btn" onClick={handleNewChat}>
 
 
 
 
 
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
- {sidebarOpen && <div className="sidebar-overlay" onClick={() => setSidebarOpen(false)} />}
 
 
 
 
 
 
 
 
 
 
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.5, maxWidth: '400px' }}>
428
- <Brain size={48} style={{ margin: '0 auto 16px auto', display: 'block' }} />
429
- <h2>How can I help you with ML research?</h2>
430
- </div>
 
 
 
431
  ) : (
432
  currentMessages.map((msg, i) => (
433
- <div key={msg.id} className={msg.role === 'user' ? 'message-user' : 'message-ai'}>
 
 
 
 
 
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: '8px', fontSize: '0.85rem', color: 'var(--accent)', fontWeight: 600 }}>
440
- <Brain size={16} /> ResearchPilot {msg.model_used && <span style={{fontSize: '0.7rem', color: '#666', background: '#222', padding: '2px 6px', borderRadius: '4px'}}>{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: '16px', paddingTop: '16px', borderTop: '1px solid rgba(255,255,255,0.1)' }}>
455
- <div style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--text-muted)', marginBottom: '8px' }}>SOURCES</div>
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 onClick={handleSend} disabled={!query.trim() || isStreaming} className="send-btn">
 
 
 
 
 
 
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.7.0",
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.7.0",
5261
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz",
5262
- "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==",
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.7.0",
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",