“vinit5112” commited on
Commit
6f1f94e
·
1 Parent(s): 45f1618

Upgrade UI

Browse files
frontend/src/App.js CHANGED
@@ -3,7 +3,7 @@ import { Toaster } from 'react-hot-toast';
3
  import ChatInterface from './components/ChatInterface';
4
  import Sidebar from './components/Sidebar';
5
  import WelcomeScreen from './components/WelcomeScreen';
6
- import { SunIcon, MoonIcon, HomeIcon } from '@heroicons/react/24/outline';
7
  import ConversationStorage from './utils/conversationStorage';
8
 
9
  function App() {
@@ -104,66 +104,104 @@ function App() {
104
  ? 'bg-gray-900 text-white'
105
  : 'bg-gray-50 text-gray-900'
106
  }`}>
107
- {/* Header */}
108
  <header className={`fixed top-0 left-0 right-0 z-50 ${
109
  darkMode
110
  ? 'bg-gray-800/95 border-gray-700'
111
  : 'bg-white/95 border-gray-200'
112
- } backdrop-blur-sm border-b`}>
113
- <div className="flex items-center justify-between px-4 py-3">
114
- <div className="flex items-center space-x-4">
 
 
115
  <button
116
  onClick={() => setSidebarOpen(!sidebarOpen)}
117
- className={`p-2 rounded-lg transition-colors ${
118
  darkMode
119
- ? 'hover:bg-gray-700 text-gray-300'
120
- : 'hover:bg-gray-100 text-gray-600'
121
- }`}
 
122
  >
123
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
124
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
125
- </svg>
126
  </button>
127
 
 
128
  <button
129
  onClick={goBackToHome}
130
- className="text-lg font-semibold gradient-text hover:opacity-80 transition-opacity"
131
  >
132
- CA Study Assistant
 
 
 
 
 
 
 
 
133
  </button>
134
  </div>
135
 
136
- <div className="flex items-center space-x-2">
 
 
137
  {chatStarted && (
138
  <button
139
  onClick={goBackToHome}
140
- className={`p-2 rounded-lg transition-colors ${
141
  darkMode
142
- ? 'hover:bg-gray-700 text-gray-300'
143
- : 'hover:bg-gray-100 text-gray-600'
144
- }`}
145
  title="Back to Home"
146
  >
147
- <HomeIcon className="w-5 h-5" />
148
  </button>
149
  )}
150
 
 
151
  <button
152
  onClick={toggleDarkMode}
153
- className={`p-2 rounded-lg transition-colors ${
154
  darkMode
155
- ? 'hover:bg-gray-700 text-gray-300'
156
- : 'hover:bg-gray-100 text-gray-600'
157
- }`}
 
158
  >
159
  {darkMode ? (
160
- <SunIcon className="w-5 h-5" />
161
  ) : (
162
- <MoonIcon className="w-5 h-5" />
163
  )}
164
  </button>
 
 
 
 
 
 
 
 
 
 
 
165
  </div>
166
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  </header>
168
 
169
  {/* Sidebar */}
@@ -179,10 +217,10 @@ function App() {
179
  darkMode={darkMode}
180
  />
181
 
182
- {/* Main Content */}
183
  <main className={`transition-all duration-200 ${
184
- sidebarOpen ? 'md:ml-64' : 'ml-0'
185
- } pt-16`}>
186
  {chatStarted ? (
187
  <ChatInterface
188
  conversationId={activeConversationId}
@@ -199,18 +237,86 @@ function App() {
199
  )}
200
  </main>
201
 
202
- {/* Toast notifications */}
203
  <Toaster
204
- position="top-right"
 
 
 
205
  toastOptions={{
206
  duration: 4000,
207
  style: {
208
  background: darkMode ? '#374151' : '#ffffff',
209
  color: darkMode ? '#f9fafb' : '#111827',
210
  border: darkMode ? '1px solid #4b5563' : '1px solid #e5e7eb',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  },
212
  }}
213
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  </div>
215
  );
216
  }
 
3
  import ChatInterface from './components/ChatInterface';
4
  import Sidebar from './components/Sidebar';
5
  import WelcomeScreen from './components/WelcomeScreen';
6
+ import { SunIcon, MoonIcon, HomeIcon, Bars3Icon } from '@heroicons/react/24/outline';
7
  import ConversationStorage from './utils/conversationStorage';
8
 
9
  function App() {
 
104
  ? 'bg-gray-900 text-white'
105
  : 'bg-gray-50 text-gray-900'
106
  }`}>
107
+ {/* Header - Mobile optimized */}
108
  <header className={`fixed top-0 left-0 right-0 z-50 ${
109
  darkMode
110
  ? 'bg-gray-800/95 border-gray-700'
111
  : 'bg-white/95 border-gray-200'
112
+ } backdrop-blur-sm border-b shadow-sm`}>
113
+ <div className="flex items-center justify-between px-3 md:px-4 py-3 md:py-3">
114
+ {/* Left side - Menu and Title */}
115
+ <div className="flex items-center space-x-2 md:space-x-4 flex-1 min-w-0">
116
+ {/* Hamburger Menu Button - Larger touch target */}
117
  <button
118
  onClick={() => setSidebarOpen(!sidebarOpen)}
119
+ className={`p-2 md:p-2 rounded-lg md:rounded-xl transition-all duration-200 touch-manipulation ${
120
  darkMode
121
+ ? 'hover:bg-gray-700 active:bg-gray-600 text-gray-300 hover:text-white'
122
+ : 'hover:bg-gray-100 active:bg-gray-200 text-gray-600 hover:text-gray-900'
123
+ } min-w-[44px] min-h-[44px] md:min-w-[40px] md:min-h-[40px] flex items-center justify-center`}
124
+ title="Toggle menu"
125
  >
126
+ <Bars3Icon className="w-6 h-6 md:w-5 md:h-5" />
 
 
127
  </button>
128
 
129
+ {/* App Title - Mobile optimized */}
130
  <button
131
  onClick={goBackToHome}
132
+ className="flex-1 min-w-0 text-left group"
133
  >
134
+ <h1 className="text-lg md:text-xl font-bold gradient-text hover:opacity-80 transition-opacity truncate">
135
+ CA Study Assistant
136
+ </h1>
137
+ {/* Subtitle for larger screens */}
138
+ <p className={`text-xs hidden md:block ${
139
+ darkMode ? 'text-gray-400' : 'text-gray-500'
140
+ }`}>
141
+ AI-powered study companion
142
+ </p>
143
  </button>
144
  </div>
145
 
146
+ {/* Right side - Action buttons */}
147
+ <div className="flex items-center space-x-1 md:space-x-2 flex-shrink-0">
148
+ {/* Home Button - Only show in chat mode */}
149
  {chatStarted && (
150
  <button
151
  onClick={goBackToHome}
152
+ className={`p-2 md:p-2 rounded-lg md:rounded-xl transition-all duration-200 touch-manipulation ${
153
  darkMode
154
+ ? 'hover:bg-gray-700 active:bg-gray-600 text-gray-300 hover:text-white'
155
+ : 'hover:bg-gray-100 active:bg-gray-200 text-gray-600 hover:text-gray-900'
156
+ } min-w-[44px] min-h-[44px] md:min-w-[40px] md:min-h-[40px] flex items-center justify-center`}
157
  title="Back to Home"
158
  >
159
+ <HomeIcon className="w-6 h-6 md:w-5 md:h-5" />
160
  </button>
161
  )}
162
 
163
+ {/* Dark Mode Toggle - Larger touch target */}
164
  <button
165
  onClick={toggleDarkMode}
166
+ className={`p-2 md:p-2 rounded-lg md:rounded-xl transition-all duration-200 touch-manipulation ${
167
  darkMode
168
+ ? 'hover:bg-gray-700 active:bg-gray-600 text-gray-300 hover:text-yellow-400'
169
+ : 'hover:bg-gray-100 active:bg-gray-200 text-gray-600 hover:text-yellow-600'
170
+ } min-w-[44px] min-h-[44px] md:min-w-[40px] md:min-h-[40px] flex items-center justify-center`}
171
+ title={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}
172
  >
173
  {darkMode ? (
174
+ <SunIcon className="w-6 h-6 md:w-5 md:h-5" />
175
  ) : (
176
+ <MoonIcon className="w-6 h-6 md:w-5 md:h-5" />
177
  )}
178
  </button>
179
+
180
+ {/* Conversation count badge - Mobile optimized */}
181
+ {conversations.length > 0 && (
182
+ <div className={`hidden sm:flex items-center px-2 md:px-3 py-1 md:py-1.5 rounded-full text-xs md:text-sm font-medium ${
183
+ darkMode
184
+ ? 'bg-primary-900/30 text-primary-300 border border-primary-700/50'
185
+ : 'bg-primary-50 text-primary-700 border border-primary-200'
186
+ }`}>
187
+ {conversations.length} chat{conversations.length !== 1 ? 's' : ''}
188
+ </div>
189
+ )}
190
  </div>
191
  </div>
192
+
193
+ {/* Mobile conversation indicator - Shows only on small screens */}
194
+ {conversations.length > 0 && (
195
+ <div className="sm:hidden px-3 pb-2">
196
+ <div className={`flex items-center justify-center px-3 py-1 rounded-full text-xs font-medium ${
197
+ darkMode
198
+ ? 'bg-primary-900/30 text-primary-300 border border-primary-700/50'
199
+ : 'bg-primary-50 text-primary-700 border border-primary-200'
200
+ }`}>
201
+ 📚 {conversations.length} conversation{conversations.length !== 1 ? 's' : ''} saved
202
+ </div>
203
+ </div>
204
+ )}
205
  </header>
206
 
207
  {/* Sidebar */}
 
217
  darkMode={darkMode}
218
  />
219
 
220
+ {/* Main Content - Mobile optimized */}
221
  <main className={`transition-all duration-200 ${
222
+ sidebarOpen ? 'md:ml-80' : 'ml-0'
223
+ } pt-16 md:pt-20 min-h-screen`}>
224
  {chatStarted ? (
225
  <ChatInterface
226
  conversationId={activeConversationId}
 
237
  )}
238
  </main>
239
 
240
+ {/* Toast notifications - Mobile optimized */}
241
  <Toaster
242
+ position="top-center"
243
+ containerStyle={{
244
+ top: '80px', // Account for header height
245
+ }}
246
  toastOptions={{
247
  duration: 4000,
248
  style: {
249
  background: darkMode ? '#374151' : '#ffffff',
250
  color: darkMode ? '#f9fafb' : '#111827',
251
  border: darkMode ? '1px solid #4b5563' : '1px solid #e5e7eb',
252
+ borderRadius: '12px',
253
+ padding: '12px 16px',
254
+ fontSize: '14px',
255
+ maxWidth: '90vw',
256
+ wordBreak: 'break-word',
257
+ },
258
+ success: {
259
+ iconTheme: {
260
+ primary: '#10b981',
261
+ secondary: '#ffffff',
262
+ },
263
+ },
264
+ error: {
265
+ iconTheme: {
266
+ primary: '#ef4444',
267
+ secondary: '#ffffff',
268
+ },
269
  },
270
  }}
271
  />
272
+
273
+ {/* Global loading overlay for mobile (if needed) */}
274
+ {/* This can be used for app-wide loading states */}
275
+
276
+ {/* Safe area spacing for mobile devices */}
277
+ <style jsx global>{`
278
+ @supports (padding: max(0px)) {
279
+ .pb-safe {
280
+ padding-bottom: max(env(safe-area-inset-bottom), 1rem);
281
+ }
282
+ }
283
+
284
+ /* Prevent zoom on double-tap for iOS */
285
+ button, input, select, textarea {
286
+ touch-action: manipulation;
287
+ }
288
+
289
+ /* Improve scrolling on mobile */
290
+ * {
291
+ -webkit-overflow-scrolling: touch;
292
+ }
293
+
294
+ /* Custom gradient text */
295
+ .gradient-text {
296
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
297
+ -webkit-background-clip: text;
298
+ -webkit-text-fill-color: transparent;
299
+ background-clip: text;
300
+ }
301
+
302
+ /* Mobile-specific touch improvements */
303
+ @media (max-width: 768px) {
304
+ /* Increase minimum touch target size */
305
+ button, [role="button"] {
306
+ min-height: 44px;
307
+ min-width: 44px;
308
+ }
309
+
310
+ /* Reduce motion for users who prefer it */
311
+ @media (prefers-reduced-motion: reduce) {
312
+ * {
313
+ animation-duration: 0.01ms !important;
314
+ animation-iteration-count: 1 !important;
315
+ transition-duration: 0.01ms !important;
316
+ }
317
+ }
318
+ }
319
+ `}</style>
320
  </div>
321
  );
322
  }
frontend/src/components/ChatInterface.js CHANGED
@@ -112,40 +112,40 @@ const ChatInterface = ({ conversationId, conversations, setConversations, darkMo
112
 
113
  return (
114
  <div className="flex flex-col h-screen">
115
- {/* Messages Container */}
116
- <div className="flex-1 overflow-y-auto px-4 py-6">
117
- <div className="max-w-3xl mx-auto">
118
- {/* Empty State */}
119
  {messages.length === 0 && !isLoading && (
120
  <motion.div
121
  initial={{ opacity: 0, y: 20 }}
122
  animate={{ opacity: 1, y: 0 }}
123
  transition={{ duration: 0.6 }}
124
- className="flex flex-col items-center justify-center min-h-[60vh] text-center"
125
  >
126
- {/* CA Assistant Avatar */}
127
  <motion.div
128
  initial={{ scale: 0.8 }}
129
  animate={{ scale: 1 }}
130
  transition={{ duration: 0.5, delay: 0.2 }}
131
- className={`w-20 h-20 rounded-full flex items-center justify-center mb-6 ${
132
  darkMode
133
  ? 'bg-gradient-to-br from-primary-600 to-purple-600'
134
  : 'bg-gradient-to-br from-primary-500 to-purple-500'
135
  } shadow-lg`}
136
  >
137
- <svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
139
  d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
140
  </svg>
141
  </motion.div>
142
 
143
- {/* Welcome Message */}
144
  <motion.h2
145
  initial={{ opacity: 0 }}
146
  animate={{ opacity: 1 }}
147
  transition={{ delay: 0.4 }}
148
- className="text-2xl md:text-3xl font-bold mb-3 gradient-text"
149
  >
150
  Hello! I'm your CA Study Assistant
151
  </motion.h2>
@@ -154,26 +154,27 @@ const ChatInterface = ({ conversationId, conversations, setConversations, darkMo
154
  initial={{ opacity: 0 }}
155
  animate={{ opacity: 1 }}
156
  transition={{ delay: 0.5 }}
157
- className={`text-lg mb-8 ${darkMode ? 'text-gray-300' : 'text-gray-600'}`}
158
  >
159
  I'm here to help you with accounting, finance, taxation, and auditing concepts.
160
  Ask me anything or upload your study materials!
161
  </motion.p>
162
 
163
- {/* Quick Start Suggestions */}
164
  <motion.div
165
  initial={{ opacity: 0, y: 20 }}
166
  animate={{ opacity: 1, y: 0 }}
167
  transition={{ delay: 0.6 }}
168
- className="w-full max-w-2xl"
169
  >
170
- <h3 className={`text-sm font-semibold mb-4 ${
171
  darkMode ? 'text-gray-400' : 'text-gray-500'
172
- }`}>
173
  Try asking me about:
174
  </h3>
175
 
176
- <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
 
177
  {[
178
  { icon: "📊", text: "Financial statement analysis", query: "Explain financial statement analysis" },
179
  { icon: "💰", text: "Depreciation methods", query: "What are different depreciation methods?" },
@@ -190,41 +191,41 @@ const ChatInterface = ({ conversationId, conversations, setConversations, darkMo
190
  whileHover={{ scale: 1.02, y: -2 }}
191
  whileTap={{ scale: 0.98 }}
192
  onClick={() => setMessage(suggestion.query)}
193
- className={`flex items-center p-4 rounded-xl text-left transition-all ${
194
  darkMode
195
- ? 'bg-gray-800 hover:bg-gray-700 border-gray-700 text-gray-300'
196
- : 'bg-gray-50 hover:bg-gray-100 border-gray-200 text-gray-700'
197
- } border hover:border-primary-300 hover:shadow-md`}
198
  >
199
- <span className="text-2xl mr-3">{suggestion.icon}</span>
200
- <span className="font-medium">{suggestion.text}</span>
201
  </motion.button>
202
  ))}
203
  </div>
204
  </motion.div>
205
 
206
- {/* Upload Reminder */}
207
  <motion.div
208
  initial={{ opacity: 0 }}
209
  animate={{ opacity: 1 }}
210
  transition={{ delay: 1.2 }}
211
- className={`mt-8 p-4 rounded-xl ${
212
  darkMode
213
  ? 'bg-primary-900/20 border-primary-700/30'
214
  : 'bg-primary-50 border-primary-200'
215
  } border`}
216
  >
217
  <div className="flex items-center justify-center">
218
- <svg className={`w-5 h-5 mr-2 ${
219
  darkMode ? 'text-primary-400' : 'text-primary-600'
220
  }`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
221
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
222
  d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
223
  </svg>
224
- <span className={`text-sm ${
225
  darkMode ? 'text-primary-300' : 'text-primary-700'
226
  }`}>
227
- 💡 Upload your study materials for more specific and detailed answers
228
  </span>
229
  </div>
230
  </motion.div>
@@ -249,118 +250,96 @@ const ChatInterface = ({ conversationId, conversations, setConversations, darkMo
249
  </div>
250
  </div>
251
 
252
- {/* File Uploader Modal */}
253
  <AnimatePresence>
254
  {showFileUploader && (
255
  <motion.div
256
  initial={{ opacity: 0 }}
257
  animate={{ opacity: 1 }}
258
  exit={{ opacity: 0 }}
259
- className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
260
  onClick={() => setShowFileUploader(false)}
261
  >
262
  <motion.div
263
- initial={{ scale: 0.9, opacity: 0 }}
264
- animate={{ scale: 1, opacity: 1 }}
265
- exit={{ scale: 0.9, opacity: 0 }}
266
  onClick={(e) => e.stopPropagation()}
267
- className={`max-w-md w-full p-6 rounded-2xl ${
268
  darkMode ? 'bg-gray-800' : 'bg-white'
269
- } shadow-2xl`}
270
  >
271
- <h3 className="text-lg font-semibold mb-4">Upload Document</h3>
272
  <FileUploader darkMode={darkMode} onClose={() => setShowFileUploader(false)} />
273
  </motion.div>
274
  </motion.div>
275
  )}
276
  </AnimatePresence>
277
 
278
- {/* Input Area */}
279
  <div className={`border-t ${
280
  darkMode ? 'border-gray-700/50 bg-gray-900/95' : 'border-gray-200/50 bg-white/95'
281
- } backdrop-blur-sm p-6`}>
282
- <div className="max-w-3xl mx-auto">
283
  <form onSubmit={handleSubmit} className="relative">
284
- {/* Enhanced Input Container */}
285
- <div className={`relative overflow-hidden rounded-2xl border-2 transition-all duration-300 ${
286
  darkMode
287
  ? 'bg-gradient-to-br from-gray-800 to-gray-900 border-gray-600 focus-within:border-primary-500 focus-within:from-gray-700 focus-within:to-gray-800'
288
  : 'bg-gradient-to-br from-white to-gray-50 border-gray-300 focus-within:border-primary-500 focus-within:from-blue-50 focus-within:to-white'
289
- } focus-within:ring-4 focus-within:ring-primary-500/20 shadow-xl hover:shadow-2xl focus-within:shadow-2xl`}>
290
 
291
- {/* Subtle Inner Glow */}
292
- <div className={`absolute inset-0 opacity-0 focus-within:opacity-100 transition-opacity duration-300 ${
293
- darkMode
294
- ? 'bg-gradient-to-br from-primary-900/20 to-purple-900/20'
295
- : 'bg-gradient-to-br from-primary-50/50 to-purple-50/50'
296
- }`} />
297
-
298
- {/* Input Content */}
299
- <div className="relative flex items-end space-x-4 p-4">
300
- {/* File Upload Button */}
301
  <motion.button
302
  type="button"
303
  whileHover={{ scale: 1.05 }}
304
  whileTap={{ scale: 0.95 }}
305
  onClick={() => setShowFileUploader(true)}
306
- className={`flex-shrink-0 p-3 rounded-xl transition-all duration-200 ${
307
  darkMode
308
- ? 'hover:bg-gray-700/70 text-gray-400 hover:text-primary-400 hover:shadow-lg'
309
- : 'hover:bg-gray-100/70 text-gray-500 hover:text-primary-600 hover:shadow-md'
310
- } relative group backdrop-blur-sm`}
311
  title="Upload document"
312
  >
313
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
314
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
315
  d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
316
  </svg>
317
-
318
- {/* Enhanced Tooltip */}
319
- <div className={`absolute -top-14 left-1/2 transform -translate-x-1/2 px-3 py-2 rounded-lg text-xs whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-200 ${
320
- darkMode ? 'bg-gray-800 text-white shadow-xl border border-gray-700' : 'bg-gray-900 text-white shadow-xl'
321
- }`}>
322
- Upload documents
323
- <div className={`absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent ${
324
- darkMode ? 'border-t-gray-800' : 'border-t-gray-900'
325
- }`} />
326
- </div>
327
  </motion.button>
328
 
329
- {/* Enhanced Text Input */}
330
  <div className="flex-1 relative">
331
  <textarea
332
  ref={textareaRef}
333
  value={message}
334
  onChange={(e) => setMessage(e.target.value)}
335
  onKeyDown={handleKeyDown}
336
- placeholder={messages.length === 0 ? "Hi! Ask me about accounting, finance, taxation, or upload your study materials..." : "Ask a follow-up question..."}
337
- className={`w-full resize-none border-none outline-none bg-transparent py-3 px-2 text-base leading-relaxed ${
338
  darkMode ? 'text-white placeholder-gray-400' : 'text-gray-900 placeholder-gray-500'
339
- } placeholder:text-sm placeholder:leading-relaxed`}
340
  rows={1}
341
  disabled={isLoading}
342
  style={{
343
  minHeight: '24px',
344
- maxHeight: '120px',
345
  lineHeight: '1.5'
346
  }}
347
  />
348
-
349
- {/* Input Focus Indicator */}
350
- <div className={`absolute left-0 bottom-0 h-0.5 w-0 bg-gradient-to-r from-primary-500 to-purple-500 transition-all duration-300 ${
351
- message.trim() ? 'w-full' : 'group-focus-within:w-full'
352
- }`} />
353
  </div>
354
 
355
- {/* Enhanced Send Button */}
356
  <motion.button
357
  type="submit"
358
  disabled={!message.trim() || isLoading}
359
  whileHover={message.trim() && !isLoading ? { scale: 1.05 } : {}}
360
  whileTap={message.trim() && !isLoading ? { scale: 0.95 } : {}}
361
- className={`flex-shrink-0 p-3 rounded-xl transition-all duration-200 relative group ${
362
  message.trim() && !isLoading
363
- ? 'bg-gradient-to-r from-primary-600 to-primary-700 hover:from-primary-700 hover:to-primary-800 text-white shadow-lg hover:shadow-xl'
364
  : darkMode
365
  ? 'bg-gray-600/50 text-gray-400 hover:bg-gray-600/70'
366
  : 'bg-gray-300/50 text-gray-500 hover:bg-gray-300/70'
@@ -369,31 +348,23 @@ const ChatInterface = ({ conversationId, conversations, setConversations, darkMo
369
  >
370
  {isLoading ? (
371
  <div className="relative">
372
- <StopIcon className="w-5 h-5" />
373
  <div className="absolute inset-0 border-2 border-white border-t-transparent rounded-full animate-spin opacity-50"></div>
374
  </div>
375
  ) : (
376
- <PaperAirplaneIcon className="w-5 h-5" />
377
- )}
378
-
379
- {/* Enhanced Send Button Glow Effect */}
380
- {message.trim() && !isLoading && (
381
- <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-primary-600 to-primary-700 opacity-0 group-hover:opacity-30 transition-opacity duration-200 blur-lg -z-10"></div>
382
  )}
383
  </motion.button>
384
  </div>
385
-
386
- {/* Bottom Border Accent */}
387
- <div className={`absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-primary-500 to-transparent opacity-0 focus-within:opacity-100 transition-opacity duration-300`} />
388
  </div>
389
  </form>
390
 
391
- {/* Footer Text */}
392
  <motion.p
393
  initial={{ opacity: 0 }}
394
  animate={{ opacity: 1 }}
395
  transition={{ delay: 0.3 }}
396
- className={`text-xs text-center mt-3 ${
397
  darkMode ? 'text-gray-500' : 'text-gray-400'
398
  }`}
399
  >
 
112
 
113
  return (
114
  <div className="flex flex-col h-screen">
115
+ {/* Messages Container - Mobile optimized */}
116
+ <div className="flex-1 overflow-y-auto px-3 md:px-4 py-4 md:py-6">
117
+ <div className="max-w-4xl mx-auto">
118
+ {/* Empty State - Mobile optimized */}
119
  {messages.length === 0 && !isLoading && (
120
  <motion.div
121
  initial={{ opacity: 0, y: 20 }}
122
  animate={{ opacity: 1, y: 0 }}
123
  transition={{ duration: 0.6 }}
124
+ className="flex flex-col items-center justify-center min-h-[50vh] md:min-h-[60vh] text-center px-4"
125
  >
126
+ {/* CA Assistant Avatar - Larger on mobile */}
127
  <motion.div
128
  initial={{ scale: 0.8 }}
129
  animate={{ scale: 1 }}
130
  transition={{ duration: 0.5, delay: 0.2 }}
131
+ className={`w-16 h-16 md:w-20 md:h-20 rounded-full flex items-center justify-center mb-4 md:mb-6 ${
132
  darkMode
133
  ? 'bg-gradient-to-br from-primary-600 to-purple-600'
134
  : 'bg-gradient-to-br from-primary-500 to-purple-500'
135
  } shadow-lg`}
136
  >
137
+ <svg className="w-8 h-8 md:w-10 md:h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
139
  d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
140
  </svg>
141
  </motion.div>
142
 
143
+ {/* Welcome Message - Mobile optimized */}
144
  <motion.h2
145
  initial={{ opacity: 0 }}
146
  animate={{ opacity: 1 }}
147
  transition={{ delay: 0.4 }}
148
+ className="text-xl md:text-2xl lg:text-3xl font-bold mb-2 md:mb-3 gradient-text text-center"
149
  >
150
  Hello! I'm your CA Study Assistant
151
  </motion.h2>
 
154
  initial={{ opacity: 0 }}
155
  animate={{ opacity: 1 }}
156
  transition={{ delay: 0.5 }}
157
+ className={`text-base md:text-lg mb-6 md:mb-8 px-2 ${darkMode ? 'text-gray-300' : 'text-gray-600'} text-center max-w-md`}
158
  >
159
  I'm here to help you with accounting, finance, taxation, and auditing concepts.
160
  Ask me anything or upload your study materials!
161
  </motion.p>
162
 
163
+ {/* Quick Start Suggestions - Mobile optimized */}
164
  <motion.div
165
  initial={{ opacity: 0, y: 20 }}
166
  animate={{ opacity: 1, y: 0 }}
167
  transition={{ delay: 0.6 }}
168
+ className="w-full max-w-lg md:max-w-2xl"
169
  >
170
+ <h3 className={`text-xs md:text-sm font-semibold mb-3 md:mb-4 ${
171
  darkMode ? 'text-gray-400' : 'text-gray-500'
172
+ } text-center`}>
173
  Try asking me about:
174
  </h3>
175
 
176
+ {/* Mobile: Single column, Desktop: Two columns */}
177
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-3">
178
  {[
179
  { icon: "📊", text: "Financial statement analysis", query: "Explain financial statement analysis" },
180
  { icon: "💰", text: "Depreciation methods", query: "What are different depreciation methods?" },
 
191
  whileHover={{ scale: 1.02, y: -2 }}
192
  whileTap={{ scale: 0.98 }}
193
  onClick={() => setMessage(suggestion.query)}
194
+ className={`flex items-center p-3 md:p-4 rounded-lg md:rounded-xl text-left transition-all touch-manipulation ${
195
  darkMode
196
+ ? 'bg-gray-800 hover:bg-gray-700 active:bg-gray-600 border-gray-700 text-gray-300'
197
+ : 'bg-gray-50 hover:bg-gray-100 active:bg-gray-200 border-gray-200 text-gray-700'
198
+ } border hover:border-primary-300 hover:shadow-md active:shadow-lg`}
199
  >
200
+ <span className="text-xl md:text-2xl mr-2 md:mr-3 flex-shrink-0">{suggestion.icon}</span>
201
+ <span className="font-medium text-sm md:text-base">{suggestion.text}</span>
202
  </motion.button>
203
  ))}
204
  </div>
205
  </motion.div>
206
 
207
+ {/* Upload Reminder - Mobile optimized */}
208
  <motion.div
209
  initial={{ opacity: 0 }}
210
  animate={{ opacity: 1 }}
211
  transition={{ delay: 1.2 }}
212
+ className={`mt-6 md:mt-8 p-3 md:p-4 rounded-lg md:rounded-xl max-w-md ${
213
  darkMode
214
  ? 'bg-primary-900/20 border-primary-700/30'
215
  : 'bg-primary-50 border-primary-200'
216
  } border`}
217
  >
218
  <div className="flex items-center justify-center">
219
+ <svg className={`w-4 h-4 md:w-5 md:h-5 mr-2 flex-shrink-0 ${
220
  darkMode ? 'text-primary-400' : 'text-primary-600'
221
  }`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
222
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
223
  d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
224
  </svg>
225
+ <span className={`text-xs md:text-sm text-center ${
226
  darkMode ? 'text-primary-300' : 'text-primary-700'
227
  }`}>
228
+ 💡 Upload your study materials for more specific answers
229
  </span>
230
  </div>
231
  </motion.div>
 
250
  </div>
251
  </div>
252
 
253
+ {/* File Uploader Modal - Mobile optimized */}
254
  <AnimatePresence>
255
  {showFileUploader && (
256
  <motion.div
257
  initial={{ opacity: 0 }}
258
  animate={{ opacity: 1 }}
259
  exit={{ opacity: 0 }}
260
+ className="fixed inset-0 bg-black bg-opacity-50 flex items-end md:items-center justify-center z-50 p-0 md:p-4"
261
  onClick={() => setShowFileUploader(false)}
262
  >
263
  <motion.div
264
+ initial={{ y: '100%', opacity: 0 }}
265
+ animate={{ y: 0, opacity: 1 }}
266
+ exit={{ y: '100%', opacity: 0 }}
267
  onClick={(e) => e.stopPropagation()}
268
+ className={`w-full max-w-md md:max-w-lg p-4 md:p-6 rounded-t-3xl md:rounded-2xl ${
269
  darkMode ? 'bg-gray-800' : 'bg-white'
270
+ } shadow-2xl max-h-[80vh] overflow-y-auto`}
271
  >
272
+ <h3 className="text-lg md:text-xl font-semibold mb-4 text-center md:text-left">Upload Document</h3>
273
  <FileUploader darkMode={darkMode} onClose={() => setShowFileUploader(false)} />
274
  </motion.div>
275
  </motion.div>
276
  )}
277
  </AnimatePresence>
278
 
279
+ {/* Input Area - Mobile-first optimized */}
280
  <div className={`border-t ${
281
  darkMode ? 'border-gray-700/50 bg-gray-900/95' : 'border-gray-200/50 bg-white/95'
282
+ } backdrop-blur-sm p-3 md:p-6 pb-safe`}>
283
+ <div className="max-w-4xl mx-auto">
284
  <form onSubmit={handleSubmit} className="relative">
285
+ {/* Mobile-optimized Input Container */}
286
+ <div className={`relative overflow-hidden rounded-2xl md:rounded-3xl border-2 transition-all duration-300 ${
287
  darkMode
288
  ? 'bg-gradient-to-br from-gray-800 to-gray-900 border-gray-600 focus-within:border-primary-500 focus-within:from-gray-700 focus-within:to-gray-800'
289
  : 'bg-gradient-to-br from-white to-gray-50 border-gray-300 focus-within:border-primary-500 focus-within:from-blue-50 focus-within:to-white'
290
+ } focus-within:ring-4 focus-within:ring-primary-500/20 shadow-lg md:shadow-xl hover:shadow-xl md:hover:shadow-2xl focus-within:shadow-xl md:focus-within:shadow-2xl`}>
291
 
292
+ {/* Input Content - Mobile optimized */}
293
+ <div className="relative flex items-end space-x-2 md:space-x-4 p-3 md:p-4">
294
+ {/* File Upload Button - Larger touch target */}
 
 
 
 
 
 
 
295
  <motion.button
296
  type="button"
297
  whileHover={{ scale: 1.05 }}
298
  whileTap={{ scale: 0.95 }}
299
  onClick={() => setShowFileUploader(true)}
300
+ className={`flex-shrink-0 p-3 md:p-3 rounded-xl md:rounded-xl transition-all duration-200 touch-manipulation ${
301
  darkMode
302
+ ? 'hover:bg-gray-700/70 active:bg-gray-600/70 text-gray-400 hover:text-primary-400'
303
+ : 'hover:bg-gray-100/70 active:bg-gray-200/70 text-gray-500 hover:text-primary-600'
304
+ } relative group backdrop-blur-sm min-h-[44px] min-w-[44px] flex items-center justify-center`}
305
  title="Upload document"
306
  >
307
+ <svg className="w-6 h-6 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
308
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
309
  d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
310
  </svg>
 
 
 
 
 
 
 
 
 
 
311
  </motion.button>
312
 
313
+ {/* Text Input - Mobile optimized */}
314
  <div className="flex-1 relative">
315
  <textarea
316
  ref={textareaRef}
317
  value={message}
318
  onChange={(e) => setMessage(e.target.value)}
319
  onKeyDown={handleKeyDown}
320
+ placeholder={messages.length === 0 ? "Ask me about accounting, finance, taxation..." : "Ask a follow-up question..."}
321
+ className={`w-full resize-none border-none outline-none bg-transparent py-3 md:py-3 px-2 text-base md:text-base leading-relaxed ${
322
  darkMode ? 'text-white placeholder-gray-400' : 'text-gray-900 placeholder-gray-500'
323
+ } placeholder:text-sm md:placeholder:text-sm placeholder:leading-relaxed touch-manipulation`}
324
  rows={1}
325
  disabled={isLoading}
326
  style={{
327
  minHeight: '24px',
328
+ maxHeight: '100px',
329
  lineHeight: '1.5'
330
  }}
331
  />
 
 
 
 
 
332
  </div>
333
 
334
+ {/* Send Button - Larger touch target */}
335
  <motion.button
336
  type="submit"
337
  disabled={!message.trim() || isLoading}
338
  whileHover={message.trim() && !isLoading ? { scale: 1.05 } : {}}
339
  whileTap={message.trim() && !isLoading ? { scale: 0.95 } : {}}
340
+ className={`flex-shrink-0 p-3 md:p-3 rounded-xl md:rounded-xl transition-all duration-200 relative group touch-manipulation min-h-[44px] min-w-[44px] flex items-center justify-center ${
341
  message.trim() && !isLoading
342
+ ? 'bg-gradient-to-r from-primary-600 to-primary-700 hover:from-primary-700 hover:to-primary-800 active:from-primary-800 active:to-primary-900 text-white shadow-lg hover:shadow-xl active:shadow-2xl'
343
  : darkMode
344
  ? 'bg-gray-600/50 text-gray-400 hover:bg-gray-600/70'
345
  : 'bg-gray-300/50 text-gray-500 hover:bg-gray-300/70'
 
348
  >
349
  {isLoading ? (
350
  <div className="relative">
351
+ <StopIcon className="w-6 h-6 md:w-5 md:h-5" />
352
  <div className="absolute inset-0 border-2 border-white border-t-transparent rounded-full animate-spin opacity-50"></div>
353
  </div>
354
  ) : (
355
+ <PaperAirplaneIcon className="w-6 h-6 md:w-5 md:h-5" />
 
 
 
 
 
356
  )}
357
  </motion.button>
358
  </div>
 
 
 
359
  </div>
360
  </form>
361
 
362
+ {/* Footer Text - Mobile optimized */}
363
  <motion.p
364
  initial={{ opacity: 0 }}
365
  animate={{ opacity: 1 }}
366
  transition={{ delay: 0.3 }}
367
+ className={`text-xs text-center mt-2 md:mt-3 px-2 ${
368
  darkMode ? 'text-gray-500' : 'text-gray-400'
369
  }`}
370
  >
frontend/src/components/FileUploader.js CHANGED
@@ -110,173 +110,284 @@ const FileUploader = ({ darkMode, onClose }) => {
110
  };
111
 
112
  return (
113
- <div className="space-y-4">
114
- {/* Dropzone */}
115
  <motion.div
116
  {...getRootProps()}
117
- whileHover={{ scale: 1.02 }}
118
- whileTap={{ scale: 0.98 }}
119
- className={`file-drop-zone border-2 border-dashed rounded-2xl p-8 text-center cursor-pointer transition-all ${
120
  isDragActive
121
  ? darkMode
122
- ? 'border-primary-400 bg-primary-900/20'
123
- : 'border-primary-500 bg-primary-50'
124
  : darkMode
125
- ? 'border-gray-600 hover:border-gray-500 bg-gray-800'
126
- : 'border-gray-300 hover:border-gray-400 bg-gray-50'
127
- }`}
128
  >
129
  <input {...getInputProps()} />
130
 
131
- <CloudArrowUpIcon className={`w-12 h-12 mx-auto mb-4 ${
132
- isDragActive
133
- ? darkMode ? 'text-primary-400' : 'text-primary-500'
134
- : darkMode ? 'text-gray-400' : 'text-gray-500'
135
- }`} />
 
 
 
 
 
 
136
 
137
- <h3 className={`text-lg font-semibold mb-2 ${
 
138
  darkMode ? 'text-white' : 'text-gray-900'
139
  }`}>
140
- {isDragActive ? 'Drop files here' : 'Upload study materials'}
141
  </h3>
142
 
143
- <p className={`mb-4 ${
144
  darkMode ? 'text-gray-400' : 'text-gray-600'
145
  }`}>
146
- Drag & drop files here, or click to browse
 
 
 
147
  </p>
148
 
149
- <p className={`text-xs mb-4 ${
 
150
  darkMode ? 'text-gray-500' : 'text-gray-500'
151
  }`}>
152
- Maximum file size: 100MB
153
  </p>
154
 
155
- <div className="flex justify-center space-x-2">
156
- <span className={`px-3 py-1 rounded-full text-xs font-medium ${
157
- darkMode
158
- ? 'bg-blue-900/30 text-blue-400'
159
- : 'bg-blue-100 text-blue-700'
160
- }`}>
161
- PDF
162
- </span>
163
- <span className={`px-3 py-1 rounded-full text-xs font-medium ${
164
- darkMode
165
- ? 'bg-green-900/30 text-green-400'
166
- : 'bg-green-100 text-green-700'
167
- }`}>
168
- DOCX
169
- </span>
170
- <span className={`px-3 py-1 rounded-full text-xs font-medium ${
171
- darkMode
172
- ? 'bg-purple-900/30 text-purple-400'
173
- : 'bg-purple-100 text-purple-700'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  }`}>
175
- TXT
176
- </span>
177
  </div>
178
  </motion.div>
179
 
180
- {/* Upload Progress */}
181
- {uploading && (
182
- <motion.div
183
- initial={{ opacity: 0, y: 20 }}
184
- animate={{ opacity: 1, y: 0 }}
185
- className={`p-4 rounded-lg ${
186
- darkMode ? 'bg-gray-800' : 'bg-gray-100'
187
- }`}
188
- >
189
- <div className="flex items-center space-x-3">
190
- <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-primary-500"></div>
191
- <span className={`${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
192
- Uploading files...
193
- </span>
194
- </div>
195
- </motion.div>
196
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
- {/* Uploaded Files List */}
199
  <AnimatePresence>
200
  {uploadedFiles.length > 0 && (
201
  <motion.div
202
  initial={{ opacity: 0, height: 0 }}
203
  animate={{ opacity: 1, height: 'auto' }}
204
  exit={{ opacity: 0, height: 0 }}
205
- className="space-y-2"
206
  >
207
- <h4 className={`font-medium ${
208
- darkMode ? 'text-gray-300' : 'text-gray-700'
209
  }`}>
210
- Uploaded Files
211
  </h4>
212
 
213
- {uploadedFiles.map((file, index) => (
214
- <motion.div
215
- key={index}
216
- initial={{ opacity: 0, x: -20 }}
217
- animate={{ opacity: 1, x: 0 }}
218
- className={`flex items-center justify-between p-3 rounded-lg ${
219
- darkMode ? 'bg-gray-800' : 'bg-gray-100'
220
- }`}
221
- >
222
- <div className="flex items-center space-x-3">
223
- <DocumentIcon className={`w-5 h-5 ${
224
- darkMode ? 'text-gray-400' : 'text-gray-500'
225
- }`} />
226
-
227
- <div>
228
- <p className={`text-sm font-medium ${
229
- darkMode ? 'text-white' : 'text-gray-900'
 
 
 
 
 
 
230
  }`}>
231
- {file.name}
232
- </p>
233
- <p className={`text-xs ${
234
- darkMode ? 'text-gray-500' : 'text-gray-400'
235
- }`}>
236
- {formatFileSize(file.size)}
237
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  </div>
239
- </div>
240
-
241
- <div className="flex items-center space-x-2">
242
- {file.status === 'success' ? (
243
- <CheckCircleIcon className="w-5 h-5 text-green-500" />
244
- ) : (
245
- <XCircleIcon className="w-5 h-5 text-red-500" />
246
- )}
247
 
248
- <button
249
- onClick={() => removeFile(index)}
250
- className={`p-1 rounded transition-colors ${
251
- darkMode
252
- ? 'hover:bg-gray-700 text-gray-400'
253
- : 'hover:bg-gray-200 text-gray-500'
254
- }`}
255
- >
256
- <XMarkIcon className="w-4 h-4" />
257
- </button>
258
- </div>
259
- </motion.div>
260
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  </motion.div>
262
  )}
263
  </AnimatePresence>
264
 
265
- {/* Close Button */}
266
- {onClose && (
267
- <div className="flex justify-end">
268
- <button
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  onClick={onClose}
270
- className={`px-4 py-2 rounded-lg font-medium transition-colors ${
271
  darkMode
272
- ? 'bg-gray-700 hover:bg-gray-600 text-white'
273
- : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
274
- }`}
275
  >
276
- Close
277
- </button>
278
- </div>
279
- )}
280
  </div>
281
  );
282
  };
 
110
  };
111
 
112
  return (
113
+ <div className="space-y-4 md:space-y-6">
114
+ {/* Dropzone - Mobile optimized */}
115
  <motion.div
116
  {...getRootProps()}
117
+ whileHover={{ scale: 1.01 }}
118
+ whileTap={{ scale: 0.99 }}
119
+ className={`file-drop-zone border-2 border-dashed rounded-2xl md:rounded-3xl p-6 md:p-8 text-center cursor-pointer transition-all touch-manipulation ${
120
  isDragActive
121
  ? darkMode
122
+ ? 'border-primary-400 bg-primary-900/20 scale-[1.02]'
123
+ : 'border-primary-500 bg-primary-50 scale-[1.02]'
124
  : darkMode
125
+ ? 'border-gray-600 hover:border-gray-500 active:border-primary-400 bg-gray-800/50 hover:bg-gray-800'
126
+ : 'border-gray-300 hover:border-gray-400 active:border-primary-300 bg-gray-50 hover:bg-gray-100'
127
+ } min-h-[200px] md:min-h-[240px] flex flex-col justify-center`}
128
  >
129
  <input {...getInputProps()} />
130
 
131
+ {/* Upload Icon - Mobile optimized */}
132
+ <motion.div
133
+ animate={isDragActive ? { y: -5, scale: 1.1 } : { y: 0, scale: 1 }}
134
+ transition={{ duration: 0.2 }}
135
+ >
136
+ <CloudArrowUpIcon className={`w-16 h-16 md:w-20 md:h-20 mx-auto mb-4 md:mb-6 ${
137
+ isDragActive
138
+ ? darkMode ? 'text-primary-400' : 'text-primary-500'
139
+ : darkMode ? 'text-gray-400' : 'text-gray-500'
140
+ }`} />
141
+ </motion.div>
142
 
143
+ {/* Upload Text - Mobile optimized */}
144
+ <h3 className={`text-xl md:text-2xl font-bold mb-3 md:mb-4 ${
145
  darkMode ? 'text-white' : 'text-gray-900'
146
  }`}>
147
+ {isDragActive ? 'Drop files here!' : 'Upload study materials'}
148
  </h3>
149
 
150
+ <p className={`mb-4 md:mb-6 text-base md:text-lg px-2 ${
151
  darkMode ? 'text-gray-400' : 'text-gray-600'
152
  }`}>
153
+ {isDragActive
154
+ ? 'Release to upload your files'
155
+ : 'Drag & drop files here, or tap to browse'
156
+ }
157
  </p>
158
 
159
+ {/* File size info - Mobile optimized */}
160
+ <p className={`text-sm md:text-base mb-4 md:mb-6 ${
161
  darkMode ? 'text-gray-500' : 'text-gray-500'
162
  }`}>
163
+ Maximum file size: <span className="font-semibold">100MB</span>
164
  </p>
165
 
166
+ {/* File type badges - Mobile optimized */}
167
+ <div className="flex flex-wrap justify-center gap-2 md:gap-3">
168
+ <motion.span
169
+ whileHover={{ scale: 1.05 }}
170
+ className={`px-4 py-2 md:px-5 md:py-2.5 rounded-full text-sm md:text-base font-semibold shadow-md ${
171
+ darkMode
172
+ ? 'bg-blue-900/40 text-blue-300 border border-blue-700/50'
173
+ : 'bg-blue-100 text-blue-800 border border-blue-200'
174
+ }`}
175
+ >
176
+ 📄 PDF
177
+ </motion.span>
178
+ <motion.span
179
+ whileHover={{ scale: 1.05 }}
180
+ className={`px-4 py-2 md:px-5 md:py-2.5 rounded-full text-sm md:text-base font-semibold shadow-md ${
181
+ darkMode
182
+ ? 'bg-green-900/40 text-green-300 border border-green-700/50'
183
+ : 'bg-green-100 text-green-800 border border-green-200'
184
+ }`}
185
+ >
186
+ 📝 DOCX
187
+ </motion.span>
188
+ <motion.span
189
+ whileHover={{ scale: 1.05 }}
190
+ className={`px-4 py-2 md:px-5 md:py-2.5 rounded-full text-sm md:text-base font-semibold shadow-md ${
191
+ darkMode
192
+ ? 'bg-purple-900/40 text-purple-300 border border-purple-700/50'
193
+ : 'bg-purple-100 text-purple-800 border border-purple-200'
194
+ }`}
195
+ >
196
+ 📋 TXT
197
+ </motion.span>
198
+ </div>
199
+
200
+ {/* Mobile-specific help text */}
201
+ <div className="md:hidden mt-4">
202
+ <p className={`text-xs ${
203
+ darkMode ? 'text-gray-500' : 'text-gray-400'
204
  }`}>
205
+ 💡 Tip: You can select multiple files at once
206
+ </p>
207
  </div>
208
  </motion.div>
209
 
210
+ {/* Upload Progress - Mobile optimized */}
211
+ <AnimatePresence>
212
+ {uploading && (
213
+ <motion.div
214
+ initial={{ opacity: 0, y: 20, scale: 0.95 }}
215
+ animate={{ opacity: 1, y: 0, scale: 1 }}
216
+ exit={{ opacity: 0, y: -20, scale: 0.95 }}
217
+ className={`p-4 md:p-6 rounded-2xl ${
218
+ darkMode ? 'bg-gray-800/50 border border-gray-700/50' : 'bg-gray-100/50 border border-gray-200/50'
219
+ } backdrop-blur-sm`}
220
+ >
221
+ <div className="flex items-center space-x-3 md:space-x-4">
222
+ <div className="relative">
223
+ <div className="animate-spin rounded-full h-6 w-6 md:h-8 md:w-8 border-b-2 border-primary-500"></div>
224
+ <div className="absolute inset-0 rounded-full border-2 border-primary-500/20"></div>
225
+ </div>
226
+ <div>
227
+ <span className={`font-medium text-base md:text-lg ${
228
+ darkMode ? 'text-gray-200' : 'text-gray-800'
229
+ }`}>
230
+ Uploading files...
231
+ </span>
232
+ <p className={`text-sm ${
233
+ darkMode ? 'text-gray-400' : 'text-gray-600'
234
+ }`}>
235
+ Please wait while we process your documents
236
+ </p>
237
+ </div>
238
+ </div>
239
+ </motion.div>
240
+ )}
241
+ </AnimatePresence>
242
 
243
+ {/* Uploaded Files List - Mobile optimized */}
244
  <AnimatePresence>
245
  {uploadedFiles.length > 0 && (
246
  <motion.div
247
  initial={{ opacity: 0, height: 0 }}
248
  animate={{ opacity: 1, height: 'auto' }}
249
  exit={{ opacity: 0, height: 0 }}
250
+ className="space-y-3 md:space-y-4"
251
  >
252
+ <h4 className={`font-bold text-lg md:text-xl ${
253
+ darkMode ? 'text-gray-200' : 'text-gray-800'
254
  }`}>
255
+ Uploaded Files ({uploadedFiles.length})
256
  </h4>
257
 
258
+ <div className="space-y-2 md:space-y-3">
259
+ {uploadedFiles.map((file, index) => (
260
+ <motion.div
261
+ key={index}
262
+ initial={{ opacity: 0, x: -20 }}
263
+ animate={{ opacity: 1, x: 0 }}
264
+ transition={{ delay: index * 0.1 }}
265
+ className={`flex items-center justify-between p-4 md:p-5 rounded-xl md:rounded-2xl transition-all ${
266
+ darkMode
267
+ ? 'bg-gray-800/70 border border-gray-700/50 hover:bg-gray-800'
268
+ : 'bg-gray-50 border border-gray-200/50 hover:bg-gray-100'
269
+ } shadow-sm hover:shadow-md`}
270
+ >
271
+ <div className="flex items-center space-x-3 md:space-x-4 flex-1 min-w-0">
272
+ {/* File Icon */}
273
+ <div className={`p-2 md:p-3 rounded-lg flex-shrink-0 ${
274
+ file.status === 'success'
275
+ ? darkMode
276
+ ? 'bg-green-900/30 text-green-400'
277
+ : 'bg-green-100 text-green-700'
278
+ : darkMode
279
+ ? 'bg-red-900/30 text-red-400'
280
+ : 'bg-red-100 text-red-700'
281
  }`}>
282
+ <DocumentIcon className="w-5 h-5 md:w-6 md:h-6" />
283
+ </div>
284
+
285
+ {/* File Info */}
286
+ <div className="flex-1 min-w-0">
287
+ <p className={`font-medium text-sm md:text-base truncate ${
288
+ darkMode ? 'text-gray-100' : 'text-gray-900'
289
+ }`}>
290
+ {file.name}
291
+ </p>
292
+ <div className="flex items-center space-x-2 md:space-x-3 mt-1">
293
+ <p className={`text-xs md:text-sm ${
294
+ darkMode ? 'text-gray-400' : 'text-gray-500'
295
+ }`}>
296
+ {formatFileSize(file.size)}
297
+ </p>
298
+ {file.status === 'error' && file.error && (
299
+ <>
300
+ <span className={`text-xs ${
301
+ darkMode ? 'text-gray-600' : 'text-gray-400'
302
+ }`}>•</span>
303
+ <p className={`text-xs truncate ${
304
+ darkMode ? 'text-red-400' : 'text-red-600'
305
+ }`}>
306
+ {file.error}
307
+ </p>
308
+ </>
309
+ )}
310
+ </div>
311
+ </div>
312
  </div>
 
 
 
 
 
 
 
 
313
 
314
+ {/* Status and Actions */}
315
+ <div className="flex items-center space-x-2 md:space-x-3 flex-shrink-0">
316
+ {/* Status Icon */}
317
+ {file.status === 'success' ? (
318
+ <motion.div
319
+ initial={{ scale: 0 }}
320
+ animate={{ scale: 1 }}
321
+ transition={{ type: "spring", delay: 0.2 }}
322
+ >
323
+ <CheckCircleIcon className="w-6 h-6 md:w-7 md:h-7 text-green-500" />
324
+ </motion.div>
325
+ ) : (
326
+ <motion.div
327
+ initial={{ scale: 0 }}
328
+ animate={{ scale: 1 }}
329
+ transition={{ type: "spring", delay: 0.2 }}
330
+ >
331
+ <XCircleIcon className="w-6 h-6 md:w-7 md:h-7 text-red-500" />
332
+ </motion.div>
333
+ )}
334
+
335
+ {/* Remove Button - Larger touch target */}
336
+ <motion.button
337
+ whileHover={{ scale: 1.1 }}
338
+ whileTap={{ scale: 0.9 }}
339
+ onClick={() => removeFile(index)}
340
+ className={`p-2 md:p-2.5 rounded-lg transition-colors touch-manipulation ${
341
+ darkMode
342
+ ? 'hover:bg-gray-700 active:bg-gray-600 text-gray-400 hover:text-gray-200'
343
+ : 'hover:bg-gray-200 active:bg-gray-300 text-gray-500 hover:text-gray-700'
344
+ }`}
345
+ title="Remove file"
346
+ >
347
+ <XMarkIcon className="w-4 h-4 md:w-5 md:h-5" />
348
+ </motion.button>
349
+ </div>
350
+ </motion.div>
351
+ ))}
352
+ </div>
353
  </motion.div>
354
  )}
355
  </AnimatePresence>
356
 
357
+ {/* Action Buttons - Mobile optimized */}
358
+ <div className="flex flex-col sm:flex-row gap-3 md:gap-4 pt-2">
359
+ {/* Upload More Button */}
360
+ <motion.button
361
+ whileHover={{ scale: 1.02 }}
362
+ whileTap={{ scale: 0.98 }}
363
+ {...getRootProps()}
364
+ className={`flex-1 sm:flex-none px-6 py-3 md:py-3.5 rounded-xl md:rounded-2xl font-semibold transition-all touch-manipulation ${
365
+ darkMode
366
+ ? 'bg-primary-600 hover:bg-primary-700 active:bg-primary-800 text-white shadow-lg'
367
+ : 'bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white shadow-lg'
368
+ } hover:shadow-xl active:shadow-2xl disabled:opacity-50 disabled:cursor-not-allowed`}
369
+ disabled={uploading}
370
+ >
371
+ <input {...getInputProps()} />
372
+ {uploading ? 'Uploading...' : 'Upload More Files'}
373
+ </motion.button>
374
+
375
+ {/* Close Button */}
376
+ {onClose && (
377
+ <motion.button
378
+ whileHover={{ scale: 1.02 }}
379
+ whileTap={{ scale: 0.98 }}
380
  onClick={onClose}
381
+ className={`flex-1 sm:flex-none px-6 py-3 md:py-3.5 rounded-xl md:rounded-2xl font-semibold transition-all touch-manipulation ${
382
  darkMode
383
+ ? 'bg-gray-700 hover:bg-gray-600 active:bg-gray-500 text-gray-200 shadow-lg'
384
+ : 'bg-gray-200 hover:bg-gray-300 active:bg-gray-400 text-gray-700 shadow-lg'
385
+ } hover:shadow-xl active:shadow-2xl`}
386
  >
387
+ Done
388
+ </motion.button>
389
+ )}
390
+ </div>
391
  </div>
392
  );
393
  };
frontend/src/components/MessageBubble.js CHANGED
@@ -1,136 +1,285 @@
1
- import React from 'react';
2
  import { motion } from 'framer-motion';
3
  import ReactMarkdown from 'react-markdown';
4
- import remarkGfm from 'remark-gfm';
5
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
6
- import { tomorrow, prism } from 'react-syntax-highlighter/dist/esm/styles/prism';
7
- import { UserIcon, AcademicCapIcon } from '@heroicons/react/24/solid';
 
8
 
9
- const MessageBubble = ({ message, darkMode, isLast }) => {
10
- const isUser = message.role === 'user';
11
-
12
- const messageVariants = {
13
- hidden: { opacity: 0, y: 20 },
14
- visible: {
15
- opacity: 1,
16
- y: 0,
17
- transition: {
18
- duration: 0.3,
19
- ease: "easeOut"
20
- }
21
  }
22
  };
23
 
24
- const formatTime = (timestamp) => {
25
- return new Date(timestamp).toLocaleTimeString([], {
 
26
  hour: '2-digit',
27
- minute: '2-digit'
 
28
  });
29
  };
30
 
31
  return (
32
  <motion.div
33
- variants={messageVariants}
34
- initial="hidden"
35
- animate="visible"
36
- className={`flex gap-4 mb-6 ${isUser ? 'justify-end' : 'justify-start'}`}
37
  >
38
- {!isUser && (
39
- <div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
40
- darkMode ? 'bg-primary-600' : 'bg-primary-500'
41
- }`}>
42
- <AcademicCapIcon className="w-5 h-5 text-white" />
43
- </div>
44
- )}
45
-
46
- <div className={`max-w-[80%] ${isUser ? 'order-first' : ''}`}>
47
- <div className={`rounded-2xl px-4 py-3 ${
48
- isUser
49
- ? darkMode
50
- ? 'bg-primary-600 text-white'
51
- : 'bg-primary-500 text-white'
52
- : darkMode
53
- ? 'bg-gray-800 border border-gray-700'
54
- : 'bg-white border border-gray-200 shadow-sm'
55
  }`}>
56
- {isUser ? (
57
- <p className="whitespace-pre-wrap">{message.content}</p>
58
- ) : (
59
- <div className="message-content">
60
- <ReactMarkdown
61
- remarkPlugins={[remarkGfm]}
62
- components={{
63
- code({ node, inline, className, children, ...props }) {
64
- const match = /language-(\w+)/.exec(className || '');
65
- return !inline && match ? (
66
- <SyntaxHighlighter
67
- style={darkMode ? tomorrow : prism}
68
- language={match[1]}
69
- PreTag="div"
70
- {...props}
71
- >
72
- {String(children).replace(/\n$/, '')}
73
- </SyntaxHighlighter>
74
- ) : (
75
- <code className={className} {...props}>
76
- {children}
77
- </code>
78
- );
79
- },
80
- p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
81
- ul: ({ children }) => <ul className="list-disc list-inside mb-2">{children}</ul>,
82
- ol: ({ children }) => <ol className="list-decimal list-inside mb-2">{children}</ol>,
83
- li: ({ children }) => <li className="mb-1">{children}</li>,
84
- h1: ({ children }) => <h1 className="text-xl font-bold mb-2">{children}</h1>,
85
- h2: ({ children }) => <h2 className="text-lg font-semibold mb-2">{children}</h2>,
86
- h3: ({ children }) => <h3 className="text-md font-medium mb-2">{children}</h3>,
87
- blockquote: ({ children }) => (
88
- <blockquote className={`border-l-4 pl-4 italic my-2 ${
89
- darkMode ? 'border-gray-600 text-gray-300' : 'border-gray-300 text-gray-600'
90
- }`}>
91
- {children}
92
- </blockquote>
93
- ),
94
- }}
95
- >
96
- {message.content}
97
- </ReactMarkdown>
98
  </div>
99
  )}
100
- </div>
101
-
102
- {/* Timestamp and Sources */}
103
- <div className={`text-xs mt-2 ${
104
- darkMode ? 'text-gray-500' : 'text-gray-400'
105
- } ${isUser ? 'text-right' : 'text-left'}`}>
106
- <span>{formatTime(message.timestamp)}</span>
107
 
108
- {!isUser && message.sources && message.sources.length > 0 && (
109
- <div className="mt-2">
110
- <span className="font-medium">Sources: </span>
111
- {message.sources.map((source, index) => (
112
- <span key={index} className={`inline-block mr-2 px-2 py-1 rounded text-xs ${
113
- darkMode
114
- ? 'bg-gray-700 text-gray-300'
115
- : 'bg-gray-100 text-gray-600'
116
- }`}>
117
- {source}
118
- </span>
119
- ))}
 
 
 
 
 
 
 
 
 
 
120
  </div>
121
  )}
122
  </div>
123
- </div>
124
 
125
- {isUser && (
126
- <div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
127
- darkMode ? 'bg-gray-700' : 'bg-gray-300'
128
  }`}>
129
- <UserIcon className={`w-5 h-5 ${
130
- darkMode ? 'text-gray-300' : 'text-gray-600'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }`} />
132
  </div>
133
- )}
134
  </motion.div>
135
  );
136
  };
 
1
+ import React, { useState } from 'react';
2
  import { motion } from 'framer-motion';
3
  import ReactMarkdown from 'react-markdown';
 
4
  import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
5
+ import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
6
+ import remarkGfm from 'remark-gfm';
7
+ import { ClipboardIcon, CheckIcon } from '@heroicons/react/24/outline';
8
 
9
+ const MessageBubble = ({ message, darkMode, isLast = false }) => {
10
+ const [copied, setCopied] = useState(false);
11
+
12
+ const copyToClipboard = async (text) => {
13
+ try {
14
+ await navigator.clipboard.writeText(text);
15
+ setCopied(true);
16
+ setTimeout(() => setCopied(false), 2000);
17
+ } catch (err) {
18
+ console.error('Failed to copy text: ', err);
 
 
19
  }
20
  };
21
 
22
+ const formatTimestamp = (timestamp) => {
23
+ const date = new Date(timestamp);
24
+ return date.toLocaleTimeString('en-US', {
25
  hour: '2-digit',
26
+ minute: '2-digit',
27
+ hour12: false
28
  });
29
  };
30
 
31
  return (
32
  <motion.div
33
+ initial={{ opacity: 0, y: 20 }}
34
+ animate={{ opacity: 1, y: 0 }}
35
+ transition={{ duration: 0.3 }}
36
+ className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} mb-4 md:mb-6 px-2 md:px-4`}
37
  >
38
+ <div className={`max-w-[85%] md:max-w-[80%] lg:max-w-[70%] ${
39
+ message.role === 'user' ? 'order-2' : 'order-1'
40
+ }`}>
41
+ {/* Avatar and Timestamp - Mobile optimized */}
42
+ <div className={`flex items-center mb-2 ${
43
+ message.role === 'user' ? 'justify-end' : 'justify-start'
 
 
 
 
 
 
 
 
 
 
 
44
  }`}>
45
+ {message.role === 'assistant' && (
46
+ <div className={`w-6 h-6 md:w-8 md:h-8 rounded-full flex items-center justify-center mr-2 md:mr-3 flex-shrink-0 ${
47
+ darkMode
48
+ ? 'bg-gradient-to-br from-primary-600 to-purple-600'
49
+ : 'bg-gradient-to-br from-primary-500 to-purple-500'
50
+ }`}>
51
+ <svg className="w-3 h-3 md:w-4 md:h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
53
+ d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
54
+ </svg>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  </div>
56
  )}
 
 
 
 
 
 
 
57
 
58
+ <span className={`text-xs md:text-sm font-medium ${
59
+ darkMode ? 'text-gray-400' : 'text-gray-500'
60
+ } ${message.role === 'user' ? 'order-2' : 'order-1'}`}>
61
+ {message.role === 'user' ? 'You' : 'CA Assistant'}
62
+ </span>
63
+
64
+ <span className={`text-xs ${
65
+ darkMode ? 'text-gray-500' : 'text-gray-400'
66
+ } ml-2 ${message.role === 'user' ? 'order-1 mr-2 ml-0' : 'order-2'}`}>
67
+ {formatTimestamp(message.timestamp)}
68
+ </span>
69
+
70
+ {message.role === 'user' && (
71
+ <div className={`w-6 h-6 md:w-8 md:h-8 rounded-full flex items-center justify-center ml-2 md:ml-3 flex-shrink-0 ${
72
+ darkMode
73
+ ? 'bg-gradient-to-br from-blue-600 to-blue-700'
74
+ : 'bg-gradient-to-br from-blue-500 to-blue-600'
75
+ }`}>
76
+ <svg className="w-3 h-3 md:w-4 md:h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
78
+ d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
79
+ </svg>
80
  </div>
81
  )}
82
  </div>
 
83
 
84
+ {/* Message Content - Mobile optimized */}
85
+ <div className={`relative group ${
86
+ message.role === 'user' ? 'text-right' : 'text-left'
87
  }`}>
88
+ <div className={`inline-block p-3 md:p-4 rounded-2xl md:rounded-2xl relative shadow-md hover:shadow-lg transition-all duration-200 ${
89
+ message.role === 'user'
90
+ ? darkMode
91
+ ? 'bg-gradient-to-br from-blue-600 to-blue-700 text-white'
92
+ : 'bg-gradient-to-br from-blue-500 to-blue-600 text-white'
93
+ : darkMode
94
+ ? 'bg-gradient-to-br from-gray-800 to-gray-850 border border-gray-700/50 text-gray-100'
95
+ : 'bg-gradient-to-br from-white to-gray-50 border border-gray-200/50 text-gray-900'
96
+ } max-w-full`}>
97
+
98
+ {/* Message Text - Mobile optimized */}
99
+ <div className={`prose prose-sm md:prose-base max-w-none ${
100
+ message.role === 'user'
101
+ ? 'prose-invert'
102
+ : darkMode
103
+ ? 'prose-invert prose-gray'
104
+ : 'prose-gray'
105
+ } ${message.role === 'user' ? 'text-left' : ''}`}>
106
+ {message.role === 'user' ? (
107
+ <p className="mb-0 text-sm md:text-base leading-relaxed break-words">{message.content}</p>
108
+ ) : (
109
+ <ReactMarkdown
110
+ remarkPlugins={[remarkGfm]}
111
+ components={{
112
+ // Mobile-optimized code blocks
113
+ code({ node, inline, className, children, ...props }) {
114
+ const match = /language-(\w+)/.exec(className || '');
115
+ return !inline && match ? (
116
+ <div className="relative my-3 md:my-4 rounded-lg md:rounded-xl overflow-hidden">
117
+ <div className={`flex items-center justify-between px-3 md:px-4 py-2 md:py-3 text-xs md:text-sm ${
118
+ darkMode ? 'bg-gray-900 text-gray-300' : 'bg-gray-800 text-gray-200'
119
+ }`}>
120
+ <span className="font-medium">{match[1]}</span>
121
+ <button
122
+ onClick={() => copyToClipboard(String(children).replace(/\n$/, ''))}
123
+ className={`flex items-center space-x-1 md:space-x-2 px-2 md:px-3 py-1 md:py-1.5 rounded-md transition-colors touch-manipulation ${
124
+ darkMode
125
+ ? 'hover:bg-gray-800 active:bg-gray-700'
126
+ : 'hover:bg-gray-700 active:bg-gray-600'
127
+ }`}
128
+ title="Copy code"
129
+ >
130
+ {copied ? (
131
+ <CheckIcon className="w-3 h-3 md:w-4 md:h-4" />
132
+ ) : (
133
+ <ClipboardIcon className="w-3 h-3 md:w-4 md:h-4" />
134
+ )}
135
+ <span className="text-xs hidden md:inline">
136
+ {copied ? 'Copied!' : 'Copy'}
137
+ </span>
138
+ </button>
139
+ </div>
140
+ <SyntaxHighlighter
141
+ style={darkMode ? oneDark : oneLight}
142
+ language={match[1]}
143
+ PreTag="div"
144
+ className="!m-0 text-xs md:text-sm"
145
+ customStyle={{
146
+ fontSize: '12px',
147
+ lineHeight: '1.4',
148
+ padding: '12px 16px',
149
+ }}
150
+ {...props}
151
+ >
152
+ {String(children).replace(/\n$/, '')}
153
+ </SyntaxHighlighter>
154
+ </div>
155
+ ) : (
156
+ <code
157
+ className={`px-1.5 md:px-2 py-0.5 md:py-1 rounded text-xs md:text-sm font-mono ${
158
+ darkMode
159
+ ? 'bg-gray-700 text-gray-200'
160
+ : 'bg-gray-200 text-gray-800'
161
+ }`}
162
+ {...props}
163
+ >
164
+ {children}
165
+ </code>
166
+ );
167
+ },
168
+ // Mobile-optimized paragraphs
169
+ p: ({ children }) => (
170
+ <p className="mb-3 md:mb-4 last:mb-0 text-sm md:text-base leading-relaxed break-words">
171
+ {children}
172
+ </p>
173
+ ),
174
+ // Mobile-optimized lists
175
+ ul: ({ children }) => (
176
+ <ul className="mb-3 md:mb-4 ml-4 md:ml-6 space-y-1 md:space-y-2 text-sm md:text-base">
177
+ {children}
178
+ </ul>
179
+ ),
180
+ ol: ({ children }) => (
181
+ <ol className="mb-3 md:mb-4 ml-4 md:ml-6 space-y-1 md:space-y-2 text-sm md:text-base">
182
+ {children}
183
+ </ol>
184
+ ),
185
+ li: ({ children }) => (
186
+ <li className="leading-relaxed break-words">
187
+ {children}
188
+ </li>
189
+ ),
190
+ // Mobile-optimized headings
191
+ h1: ({ children }) => (
192
+ <h1 className="text-lg md:text-xl font-bold mb-2 md:mb-3 mt-4 md:mt-6 first:mt-0 break-words">
193
+ {children}
194
+ </h1>
195
+ ),
196
+ h2: ({ children }) => (
197
+ <h2 className="text-base md:text-lg font-bold mb-2 md:mb-3 mt-3 md:mt-4 first:mt-0 break-words">
198
+ {children}
199
+ </h2>
200
+ ),
201
+ h3: ({ children }) => (
202
+ <h3 className="text-sm md:text-base font-bold mb-1 md:mb-2 mt-2 md:mt-3 first:mt-0 break-words">
203
+ {children}
204
+ </h3>
205
+ ),
206
+ // Mobile-optimized blockquotes
207
+ blockquote: ({ children }) => (
208
+ <blockquote className={`border-l-3 md:border-l-4 pl-3 md:pl-4 my-3 md:my-4 italic text-sm md:text-base ${
209
+ darkMode ? 'border-gray-600 text-gray-300' : 'border-gray-400 text-gray-600'
210
+ }`}>
211
+ {children}
212
+ </blockquote>
213
+ ),
214
+ // Mobile-optimized tables
215
+ table: ({ children }) => (
216
+ <div className="overflow-x-auto my-3 md:my-4 -mx-1">
217
+ <table className={`min-w-full text-xs md:text-sm border-collapse ${
218
+ darkMode ? 'border-gray-600' : 'border-gray-300'
219
+ }`}>
220
+ {children}
221
+ </table>
222
+ </div>
223
+ ),
224
+ th: ({ children }) => (
225
+ <th className={`border px-2 md:px-3 py-1 md:py-2 font-medium text-left ${
226
+ darkMode
227
+ ? 'border-gray-600 bg-gray-700/50'
228
+ : 'border-gray-300 bg-gray-100'
229
+ }`}>
230
+ {children}
231
+ </th>
232
+ ),
233
+ td: ({ children }) => (
234
+ <td className={`border px-2 md:px-3 py-1 md:py-2 ${
235
+ darkMode ? 'border-gray-600' : 'border-gray-300'
236
+ }`}>
237
+ {children}
238
+ </td>
239
+ ),
240
+ }}
241
+ >
242
+ {message.content || '*Thinking...*'}
243
+ </ReactMarkdown>
244
+ )}
245
+ </div>
246
+
247
+ {/* Copy Button for Assistant Messages - Mobile optimized */}
248
+ {message.role === 'assistant' && message.content && (
249
+ <button
250
+ onClick={() => copyToClipboard(message.content)}
251
+ className={`absolute top-2 md:top-3 right-2 md:right-3 opacity-0 group-hover:opacity-100 transition-all duration-200 p-1.5 md:p-2 rounded-lg touch-manipulation ${
252
+ darkMode
253
+ ? 'hover:bg-gray-700/70 active:bg-gray-600/70 text-gray-400 hover:text-gray-200'
254
+ : 'hover:bg-gray-200/70 active:bg-gray-300/70 text-gray-500 hover:text-gray-700'
255
+ } backdrop-blur-sm`}
256
+ title="Copy message"
257
+ >
258
+ {copied ? (
259
+ <CheckIcon className="w-3 h-3 md:w-4 md:h-4" />
260
+ ) : (
261
+ <ClipboardIcon className="w-3 h-3 md:w-4 md:h-4" />
262
+ )}
263
+ </button>
264
+ )}
265
+ </div>
266
+
267
+ {/* Message tail/pointer - Mobile optimized */}
268
+ <div className={`absolute top-3 md:top-4 w-0 h-0 ${
269
+ message.role === 'user'
270
+ ? 'right-0 border-l-8 md:border-l-10 border-t-8 md:border-t-10 border-transparent'
271
+ : 'left-0 border-r-8 md:border-r-10 border-t-8 md:border-t-10 border-transparent'
272
+ } ${
273
+ message.role === 'user'
274
+ ? darkMode
275
+ ? 'border-t-blue-600'
276
+ : 'border-t-blue-500'
277
+ : darkMode
278
+ ? 'border-t-gray-800'
279
+ : 'border-t-white'
280
  }`} />
281
  </div>
282
+ </div>
283
  </motion.div>
284
  );
285
  };
frontend/src/components/Sidebar.js CHANGED
@@ -2,10 +2,11 @@ import React from 'react';
2
  import { motion, AnimatePresence } from 'framer-motion';
3
  import {
4
  PlusIcon,
5
- XMarkIcon,
 
6
  ChatBubbleLeftIcon,
7
- TrashIcon,
8
- HomeIcon
9
  } from '@heroicons/react/24/outline';
10
 
11
  const Sidebar = ({
@@ -19,197 +20,271 @@ const Sidebar = ({
19
  onBackToHome,
20
  darkMode
21
  }) => {
 
22
  const formatDate = (date) => {
23
  const now = new Date();
24
  const messageDate = new Date(date);
25
  const diffTime = Math.abs(now - messageDate);
26
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
27
-
28
  if (diffDays === 1) return 'Today';
29
  if (diffDays === 2) return 'Yesterday';
30
- if (diffDays <= 7) return `${diffDays} days ago`;
31
  return messageDate.toLocaleDateString();
32
  };
33
 
34
- const sidebarVariants = {
35
- open: {
36
- x: 0,
37
- transition: {
38
- type: "spring",
39
- stiffness: 300,
40
- damping: 30
41
- }
42
- },
43
- closed: {
44
- x: -280,
45
- transition: {
46
- type: "spring",
47
- stiffness: 300,
48
- damping: 30
49
- }
50
- }
51
- };
52
-
53
- const overlayVariants = {
54
- open: { opacity: 1 },
55
- closed: { opacity: 0 }
56
  };
57
 
58
  return (
59
- <>
60
- {/* Mobile Overlay */}
61
- <AnimatePresence>
62
- {open && (
63
  <motion.div
64
- variants={overlayVariants}
65
- initial="closed"
66
- animate="open"
67
- exit="closed"
68
  className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden"
69
  onClick={onClose}
70
  />
71
- )}
72
- </AnimatePresence>
73
 
74
- {/* Sidebar */}
75
- <motion.aside
76
- variants={sidebarVariants}
77
- initial="closed"
78
- animate={open ? "open" : "closed"}
79
- className={`fixed left-0 top-16 h-[calc(100vh-4rem)] w-64 z-50 ${
80
- darkMode
81
- ? 'bg-gray-900 border-gray-700'
82
- : 'bg-white border-gray-200'
83
- } border-r shadow-lg flex flex-col`}
84
- >
85
- {/* Header */}
86
- <div className="p-4 border-b border-gray-200 dark:border-gray-700">
87
- <div className="flex items-center justify-between mb-4">
88
- <h2 className="font-semibold text-lg">Conversations</h2>
89
- <button
90
- onClick={onClose}
91
- className={`p-1 rounded-lg transition-colors md:hidden ${
92
- darkMode
93
- ? 'hover:bg-gray-800 text-gray-400'
94
- : 'hover:bg-gray-100 text-gray-500'
95
- }`}
96
- >
97
- <XMarkIcon className="w-5 h-5" />
98
- </button>
99
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- <div className="space-y-2">
102
- <motion.button
103
- whileHover={{ scale: 1.02 }}
104
- whileTap={{ scale: 0.98 }}
105
- onClick={onNewChat}
106
- className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
107
- darkMode
108
- ? 'bg-gray-800 hover:bg-gray-700 text-white border-gray-600'
109
- : 'bg-gray-50 hover:bg-gray-100 text-gray-900 border-gray-300'
110
- } border`}
111
- >
112
- <PlusIcon className="w-4 h-4" />
113
- <span className="font-medium">New Chat</span>
114
- </motion.button>
115
-
116
- <motion.button
117
- whileHover={{ scale: 1.02 }}
118
- whileTap={{ scale: 0.98 }}
119
- onClick={onBackToHome}
120
- className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
121
- darkMode
122
- ? 'bg-primary-600 hover:bg-primary-700 text-white'
123
- : 'bg-primary-50 hover:bg-primary-100 text-primary-900 border-primary-300'
124
- } border`}
125
- >
126
- <HomeIcon className="w-4 h-4" />
127
- <span className="font-medium">Back to Home</span>
128
- </motion.button>
129
- </div>
130
- </div>
131
 
132
- {/* Conversations List */}
133
- <div className="flex-1 overflow-y-auto p-2">
134
- {conversations.length === 0 ? (
135
- <div className={`text-center py-8 ${
136
- darkMode ? 'text-gray-500' : 'text-gray-400'
137
- }`}>
138
- <ChatBubbleLeftIcon className="w-12 h-12 mx-auto mb-3 opacity-50" />
139
- <p className="text-sm">No conversations yet</p>
140
- <p className="text-xs mt-1">Start a new chat to begin</p>
141
- </div>
142
- ) : (
143
- <div className="space-y-1">
144
- {conversations.map((conversation) => (
145
  <motion.button
146
- key={conversation.id}
147
- whileHover={{ x: 4 }}
148
  onClick={() => {
149
- onConversationSelect(conversation.id);
150
  onClose();
151
  }}
152
- className={`w-full text-left p-3 rounded-lg transition-all group ${
153
- activeConversationId === conversation.id
154
- ? darkMode
155
- ? 'bg-primary-600 text-white'
156
- : 'bg-primary-50 text-primary-900 border-primary-200'
157
- : darkMode
158
- ? 'hover:bg-gray-800 text-gray-300'
159
- : 'hover:bg-gray-50 text-gray-700'
160
- }`}
161
  >
162
- <div className="flex items-start justify-between">
163
- <div className="flex-1 min-w-0">
164
- <p className="font-medium truncate text-sm">
165
- {conversation.title}
166
- </p>
167
- <p className={`text-xs mt-1 ${
168
- activeConversationId === conversation.id
169
- ? 'text-primary-200'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  : darkMode
171
- ? 'text-gray-500'
172
- : 'text-gray-500'
173
- }`}>
174
- {formatDate(conversation.createdAt)}
175
- </p>
176
- </div>
177
-
178
- <button
179
- onClick={(e) => {
180
- e.stopPropagation();
181
- if (window.confirm('Are you sure you want to delete this conversation?')) {
182
- onDeleteConversation(conversation.id);
183
- }
184
- }}
185
- className={`opacity-0 group-hover:opacity-100 p-1 rounded transition-opacity ${
186
- darkMode
187
- ? 'hover:bg-gray-700 text-gray-400'
188
- : 'hover:bg-gray-200 text-gray-500'
189
  }`}
 
 
 
 
190
  >
191
- <TrashIcon className="w-4 h-4" />
192
- </button>
193
- </div>
194
- </motion.button>
195
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  </div>
197
- )}
198
- </div>
199
 
200
- {/* Footer */}
201
- <div className={`p-4 border-t ${
202
- darkMode ? 'border-gray-700' : 'border-gray-200'
203
- }`}>
204
- <div className={`text-xs ${
205
- darkMode ? 'text-gray-500' : 'text-gray-400'
206
- }`}>
207
- <p>CA Study Assistant v2.0</p>
208
- <p className="mt-1">Powered by AI</p>
209
- </div>
210
- </div>
211
- </motion.aside>
212
- </>
 
 
213
  );
214
  };
215
 
 
2
  import { motion, AnimatePresence } from 'framer-motion';
3
  import {
4
  PlusIcon,
5
+ TrashIcon,
6
+ XMarkIcon,
7
  ChatBubbleLeftIcon,
8
+ HomeIcon,
9
+ Bars3Icon
10
  } from '@heroicons/react/24/outline';
11
 
12
  const Sidebar = ({
 
20
  onBackToHome,
21
  darkMode
22
  }) => {
23
+
24
  const formatDate = (date) => {
25
  const now = new Date();
26
  const messageDate = new Date(date);
27
  const diffTime = Math.abs(now - messageDate);
28
  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
29
+
30
  if (diffDays === 1) return 'Today';
31
  if (diffDays === 2) return 'Yesterday';
32
+ if (diffDays <= 7) return `${diffDays - 1} days ago`;
33
  return messageDate.toLocaleDateString();
34
  };
35
 
36
+ const truncateTitle = (title, maxLength = 25) => {
37
+ if (title.length <= maxLength) return title;
38
+ return title.substring(0, maxLength) + '...';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  };
40
 
41
  return (
42
+ <AnimatePresence>
43
+ {open && (
44
+ <>
45
+ {/* Mobile: Full-screen overlay background */}
46
  <motion.div
47
+ initial={{ opacity: 0 }}
48
+ animate={{ opacity: 1 }}
49
+ exit={{ opacity: 0 }}
 
50
  className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden"
51
  onClick={onClose}
52
  />
 
 
53
 
54
+ {/* Sidebar Content */}
55
+ <motion.div
56
+ initial={{
57
+ x: '-100%',
58
+ opacity: 0
59
+ }}
60
+ animate={{
61
+ x: 0,
62
+ opacity: 1
63
+ }}
64
+ exit={{
65
+ x: '-100%',
66
+ opacity: 0
67
+ }}
68
+ transition={{
69
+ type: "spring",
70
+ damping: 25,
71
+ stiffness: 200
72
+ }}
73
+ className={`fixed top-0 left-0 h-full w-full md:w-80 z-50 md:z-30 ${
74
+ darkMode
75
+ ? 'bg-gray-900 border-gray-700'
76
+ : 'bg-white border-gray-200'
77
+ } border-r shadow-2xl md:shadow-xl flex flex-col`}
78
+ >
79
+ {/* Header - Mobile optimized */}
80
+ <div className={`p-4 md:p-6 border-b ${
81
+ darkMode ? 'border-gray-700/50' : 'border-gray-200/50'
82
+ } flex-shrink-0`}>
83
+ {/* Top row: Close button and title */}
84
+ <div className="flex items-center justify-between mb-4 md:mb-3">
85
+ <h2 className={`text-lg md:text-xl font-bold ${
86
+ darkMode ? 'text-white' : 'text-gray-900'
87
+ }`}>
88
+ Conversations
89
+ </h2>
90
+
91
+ <button
92
+ onClick={onClose}
93
+ className={`p-2 md:p-1.5 rounded-lg transition-colors touch-manipulation ${
94
+ darkMode
95
+ ? 'hover:bg-gray-800 active:bg-gray-700 text-gray-400'
96
+ : 'hover:bg-gray-100 active:bg-gray-200 text-gray-500'
97
+ }`}
98
+ title="Close sidebar"
99
+ >
100
+ <XMarkIcon className="w-6 h-6 md:w-5 md:h-5" />
101
+ </button>
102
+ </div>
103
 
104
+ {/* Action buttons - Mobile optimized */}
105
+ <div className="flex flex-col sm:flex-row gap-2 md:gap-3">
106
+ <motion.button
107
+ whileHover={{ scale: 1.02 }}
108
+ whileTap={{ scale: 0.98 }}
109
+ onClick={() => {
110
+ onNewChat();
111
+ onClose();
112
+ }}
113
+ className={`flex items-center justify-center gap-2 md:gap-3 px-4 py-3 md:py-2.5 rounded-xl md:rounded-lg font-medium transition-all touch-manipulation ${
114
+ darkMode
115
+ ? 'bg-primary-600 hover:bg-primary-700 active:bg-primary-800 text-white shadow-lg'
116
+ : 'bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white shadow-lg'
117
+ } hover:shadow-xl active:shadow-2xl flex-1 sm:flex-none`}
118
+ >
119
+ <PlusIcon className="w-5 h-5 md:w-4 md:h-4" />
120
+ <span className="text-base md:text-sm">New Chat</span>
121
+ </motion.button>
 
 
 
 
 
 
 
 
 
 
 
 
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  <motion.button
124
+ whileHover={{ scale: 1.02 }}
125
+ whileTap={{ scale: 0.98 }}
126
  onClick={() => {
127
+ onBackToHome();
128
  onClose();
129
  }}
130
+ className={`flex items-center justify-center gap-2 md:gap-3 px-4 py-3 md:py-2.5 rounded-xl md:rounded-lg font-medium transition-all touch-manipulation ${
131
+ darkMode
132
+ ? 'bg-gray-700 hover:bg-gray-600 active:bg-gray-500 text-gray-200 shadow-lg'
133
+ : 'bg-gray-200 hover:bg-gray-300 active:bg-gray-400 text-gray-700 shadow-lg'
134
+ } hover:shadow-xl active:shadow-2xl flex-1 sm:flex-none`}
 
 
 
 
135
  >
136
+ <HomeIcon className="w-5 h-5 md:w-4 md:h-4" />
137
+ <span className="text-base md:text-sm">Home</span>
138
+ </motion.button>
139
+ </div>
140
+ </div>
141
+
142
+ {/* Conversations List - Mobile optimized */}
143
+ <div className="flex-1 overflow-y-auto p-3 md:p-4">
144
+ {conversations.length === 0 ? (
145
+ <div className="flex flex-col items-center justify-center h-full text-center px-4">
146
+ <ChatBubbleLeftIcon className={`w-12 h-12 md:w-16 md:h-16 mb-4 ${
147
+ darkMode ? 'text-gray-600' : 'text-gray-400'
148
+ }`} />
149
+ <p className={`text-base md:text-lg font-medium mb-2 ${
150
+ darkMode ? 'text-gray-400' : 'text-gray-500'
151
+ }`}>
152
+ No conversations yet
153
+ </p>
154
+ <p className={`text-sm md:text-base ${
155
+ darkMode ? 'text-gray-500' : 'text-gray-400'
156
+ }`}>
157
+ Start a new chat to begin your CA study session
158
+ </p>
159
+ </div>
160
+ ) : (
161
+ <div className="space-y-2 md:space-y-1">
162
+ {conversations.map((conv) => (
163
+ <motion.div
164
+ key={conv.id}
165
+ initial={{ opacity: 0, x: -20 }}
166
+ animate={{ opacity: 1, x: 0 }}
167
+ whileHover={{ x: 4 }}
168
+ className={`group relative p-3 md:p-3 rounded-xl md:rounded-lg cursor-pointer transition-all touch-manipulation ${
169
+ activeConversationId === conv.id
170
+ ? darkMode
171
+ ? 'bg-primary-600/20 border-primary-500/30 shadow-lg'
172
+ : 'bg-primary-50 border-primary-200 shadow-lg'
173
  : darkMode
174
+ ? 'hover:bg-gray-800 active:bg-gray-700'
175
+ : 'hover:bg-gray-50 active:bg-gray-100'
176
+ } border ${
177
+ activeConversationId === conv.id
178
+ ? ''
179
+ : darkMode
180
+ ? 'border-transparent'
181
+ : 'border-transparent'
 
 
 
 
 
 
 
 
 
 
182
  }`}
183
+ onClick={() => {
184
+ onConversationSelect(conv.id);
185
+ onClose();
186
+ }}
187
  >
188
+ {/* Conversation Content */}
189
+ <div className="flex items-start justify-between">
190
+ <div className="flex-1 min-w-0 mr-2">
191
+ {/* Title */}
192
+ <h3 className={`font-medium text-sm md:text-sm mb-1 truncate ${
193
+ activeConversationId === conv.id
194
+ ? darkMode
195
+ ? 'text-primary-300'
196
+ : 'text-primary-700'
197
+ : darkMode
198
+ ? 'text-gray-200'
199
+ : 'text-gray-900'
200
+ }`}>
201
+ {truncateTitle(conv.title || 'New Conversation')}
202
+ </h3>
203
+
204
+ {/* Message count and date */}
205
+ <div className="flex items-center space-x-2">
206
+ <span className={`text-xs ${
207
+ activeConversationId === conv.id
208
+ ? darkMode
209
+ ? 'text-primary-400/80'
210
+ : 'text-primary-600/80'
211
+ : darkMode
212
+ ? 'text-gray-500'
213
+ : 'text-gray-500'
214
+ }`}>
215
+ {conv.messages?.length || 0} messages
216
+ </span>
217
+ <span className={`text-xs ${
218
+ darkMode ? 'text-gray-600' : 'text-gray-400'
219
+ }`}>
220
+
221
+ </span>
222
+ <span className={`text-xs ${
223
+ darkMode ? 'text-gray-500' : 'text-gray-400'
224
+ }`}>
225
+ {formatDate(conv.createdAt)}
226
+ </span>
227
+ </div>
228
+
229
+ {/* Last message preview - Mobile optimized */}
230
+ {conv.messages && conv.messages.length > 0 && (
231
+ <p className={`text-xs mt-1 truncate ${
232
+ darkMode ? 'text-gray-500' : 'text-gray-500'
233
+ }`}>
234
+ {conv.messages[conv.messages.length - 1].content.substring(0, 40)}...
235
+ </p>
236
+ )}
237
+ </div>
238
+
239
+ {/* Delete Button - Larger touch target for mobile */}
240
+ <motion.button
241
+ whileHover={{ scale: 1.1 }}
242
+ whileTap={{ scale: 0.9 }}
243
+ onClick={(e) => {
244
+ e.stopPropagation();
245
+ onDeleteConversation(conv.id);
246
+ }}
247
+ className={`p-2 md:p-1.5 rounded-lg opacity-0 md:group-hover:opacity-100 transition-all touch-manipulation ${
248
+ darkMode
249
+ ? 'hover:bg-red-600/20 active:bg-red-600/30 text-red-400'
250
+ : 'hover:bg-red-50 active:bg-red-100 text-red-500'
251
+ } flex-shrink-0 md:opacity-100 sm:opacity-100`}
252
+ title="Delete conversation"
253
+ >
254
+ <TrashIcon className="w-4 h-4 md:w-4 md:h-4" />
255
+ </motion.button>
256
+ </div>
257
+
258
+ {/* Active indicator */}
259
+ {activeConversationId === conv.id && (
260
+ <motion.div
261
+ layoutId="activeConversation"
262
+ className={`absolute left-0 top-0 bottom-0 w-1 rounded-r ${
263
+ darkMode ? 'bg-primary-500' : 'bg-primary-500'
264
+ }`}
265
+ />
266
+ )}
267
+ </motion.div>
268
+ ))}
269
+ </div>
270
+ )}
271
  </div>
 
 
272
 
273
+ {/* Footer - Mobile optimized */}
274
+ <div className={`p-4 md:p-6 border-t ${
275
+ darkMode ? 'border-gray-700/50' : 'border-gray-200/50'
276
+ } flex-shrink-0`}>
277
+ <div className={`text-center text-xs ${
278
+ darkMode ? 'text-gray-500' : 'text-gray-400'
279
+ }`}>
280
+ <p className="mb-1">📚 CA Study Assistant</p>
281
+ <p>{conversations.length} conversation{conversations.length !== 1 ? 's' : ''}</p>
282
+ </div>
283
+ </div>
284
+ </motion.div>
285
+ </>
286
+ )}
287
+ </AnimatePresence>
288
  );
289
  };
290