Girish Jeswani commited on
Commit
4dda403
·
1 Parent(s): ad5a508

add chat sidebar

Browse files
phd-advisor-frontend/src/components/Sidebar.js ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ MessageSquare,
4
+ Plus,
5
+ Search,
6
+ MoreVertical,
7
+ Trash2,
8
+ Edit3,
9
+ LogOut,
10
+ User,
11
+ Settings
12
+ } from 'lucide-react';
13
+ import '../styles/Sidebar.css';
14
+
15
+ const Sidebar = ({
16
+ user,
17
+ currentSessionId,
18
+ onSelectSession,
19
+ onNewChat,
20
+ onSignOut,
21
+ authToken
22
+ }) => {
23
+ const [chatSessions, setChatSessions] = useState([]);
24
+ const [searchTerm, setSearchTerm] = useState('');
25
+ const [isLoading, setIsLoading] = useState(true);
26
+ const [showUserMenu, setShowUserMenu] = useState(false);
27
+
28
+ useEffect(() => {
29
+ if (authToken) {
30
+ fetchChatSessions();
31
+ }
32
+ }, [authToken]);
33
+
34
+ const fetchChatSessions = async () => {
35
+ try {
36
+ const response = await fetch('http://localhost:8000/api/chat-sessions', {
37
+ headers: {
38
+ 'Authorization': `Bearer ${authToken}`,
39
+ 'Content-Type': 'application/json'
40
+ }
41
+ });
42
+
43
+ if (response.ok) {
44
+ const sessions = await response.json();
45
+ setChatSessions(sessions);
46
+ } else {
47
+ console.error('Failed to fetch chat sessions');
48
+ }
49
+ } catch (error) {
50
+ console.error('Error fetching chat sessions:', error);
51
+ } finally {
52
+ setIsLoading(false);
53
+ }
54
+ };
55
+
56
+ const handleNewChat = async () => {
57
+ try {
58
+ const response = await fetch('http://localhost:8000/api/chat-sessions', {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Authorization': `Bearer ${authToken}`,
62
+ 'Content-Type': 'application/json'
63
+ },
64
+ body: JSON.stringify({
65
+ title: `Chat ${new Date().toLocaleDateString()}`
66
+ })
67
+ });
68
+
69
+ if (response.ok) {
70
+ const newSession = await response.json();
71
+ setChatSessions(prev => [newSession, ...prev]);
72
+ onNewChat(newSession.id);
73
+ }
74
+ } catch (error) {
75
+ console.error('Error creating new chat:', error);
76
+ }
77
+ };
78
+
79
+ const handleDeleteSession = async (sessionId, event) => {
80
+ event.stopPropagation();
81
+
82
+ if (window.confirm('Are you sure you want to delete this chat?')) {
83
+ try {
84
+ const response = await fetch(`http://localhost:8000/api/chat-sessions/${sessionId}`, {
85
+ method: 'DELETE',
86
+ headers: {
87
+ 'Authorization': `Bearer ${authToken}`,
88
+ 'Content-Type': 'application/json'
89
+ }
90
+ });
91
+
92
+ if (response.ok) {
93
+ setChatSessions(prev => prev.filter(session => session.id !== sessionId));
94
+ if (currentSessionId === sessionId) {
95
+ onNewChat(); // Create new session if current one was deleted
96
+ }
97
+ }
98
+ } catch (error) {
99
+ console.error('Error deleting chat session:', error);
100
+ }
101
+ }
102
+ };
103
+
104
+ const filteredSessions = chatSessions.filter(session =>
105
+ session.title.toLowerCase().includes(searchTerm.toLowerCase())
106
+ );
107
+
108
+ const formatDate = (dateString) => {
109
+ const date = new Date(dateString);
110
+ const now = new Date();
111
+ const diffTime = Math.abs(now - date);
112
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
113
+
114
+ if (diffDays === 1) return 'Today';
115
+ if (diffDays === 2) return 'Yesterday';
116
+ if (diffDays <= 7) return `${diffDays - 1} days ago`;
117
+ return date.toLocaleDateString();
118
+ };
119
+
120
+ return (
121
+ <div className="sidebar">
122
+ {/* Header */}
123
+ <div className="sidebar-header">
124
+ <div className="user-section">
125
+ <div className="user-info">
126
+ <div className="user-avatar">
127
+ <User size={20} />
128
+ </div>
129
+ <div className="user-details">
130
+ <span className="user-name">{user.firstName} {user.lastName}</span>
131
+ <span className="user-email">{user.email}</span>
132
+ </div>
133
+ </div>
134
+
135
+ <div className="user-menu-container">
136
+ <button
137
+ className="user-menu-button"
138
+ onClick={() => setShowUserMenu(!showUserMenu)}
139
+ >
140
+ <MoreVertical size={16} />
141
+ </button>
142
+
143
+ {showUserMenu && (
144
+ <div className="user-menu">
145
+ <button className="user-menu-item">
146
+ <Settings size={16} />
147
+ <span>Settings</span>
148
+ </button>
149
+ <button className="user-menu-item sign-out" onClick={onSignOut}>
150
+ <LogOut size={16} />
151
+ <span>Sign Out</span>
152
+ </button>
153
+ </div>
154
+ )}
155
+ </div>
156
+ </div>
157
+
158
+ <button className="new-chat-button" onClick={handleNewChat}>
159
+ <Plus size={16} />
160
+ <span>New Chat</span>
161
+ </button>
162
+ </div>
163
+
164
+ {/* Search */}
165
+ <div className="sidebar-search">
166
+ <div className="search-container">
167
+ <Search size={16} className="search-icon" />
168
+ <input
169
+ type="text"
170
+ placeholder="Search chats..."
171
+ value={searchTerm}
172
+ onChange={(e) => setSearchTerm(e.target.value)}
173
+ className="search-input"
174
+ />
175
+ </div>
176
+ </div>
177
+
178
+ {/* Chat Sessions */}
179
+ <div className="chat-sessions">
180
+ {isLoading ? (
181
+ <div className="loading-sessions">
182
+ <div className="loading-spinner"></div>
183
+ <span>Loading chats...</span>
184
+ </div>
185
+ ) : filteredSessions.length === 0 ? (
186
+ <div className="no-sessions">
187
+ {searchTerm ? 'No chats found' : 'No chats yet'}
188
+ </div>
189
+ ) : (
190
+ <div className="sessions-list">
191
+ {filteredSessions.map((session) => (
192
+ <div
193
+ key={session.id}
194
+ className={`session-item ${currentSessionId === session.id ? 'active' : ''}`}
195
+ onClick={() => onSelectSession(session.id)}
196
+ >
197
+ <div className="session-content">
198
+ <div className="session-icon">
199
+ <MessageSquare size={16} />
200
+ </div>
201
+ <div className="session-details">
202
+ <div className="session-title">{session.title}</div>
203
+ <div className="session-meta">
204
+ <span className="session-date">{formatDate(session.updated_at)}</span>
205
+ <span className="session-messages">{session.message_count} messages</span>
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ <button
211
+ className="session-menu-button"
212
+ onClick={(e) => handleDeleteSession(session.id, e)}
213
+ >
214
+ <Trash2 size={14} />
215
+ </button>
216
+ </div>
217
+ ))}
218
+ </div>
219
+ )}
220
+ </div>
221
+ </div>
222
+ );
223
+ };
224
+
225
+ export default Sidebar;
phd-advisor-frontend/src/styles/Sidebar.css ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Sidebar.css */
2
+ .sidebar {
3
+ width: 300px;
4
+ height: 100vh;
5
+ background: var(--bg-primary, #ffffff);
6
+ border-right: 1px solid var(--border-light, #e5e7eb);
7
+ display: flex;
8
+ flex-direction: column;
9
+ position: fixed;
10
+ left: 0;
11
+ top: 0;
12
+ z-index: 100;
13
+ overflow: hidden;
14
+ }
15
+
16
+ /* Dark theme support */
17
+ .dark .sidebar {
18
+ background: var(--bg-primary-dark, #1f2937);
19
+ border-right-color: var(--border-dark, #374151);
20
+ }
21
+
22
+ /* Header Section */
23
+ .sidebar-header {
24
+ padding: 20px;
25
+ border-bottom: 1px solid var(--border-light, #e5e7eb);
26
+ flex-shrink: 0;
27
+ }
28
+
29
+ .dark .sidebar-header {
30
+ border-bottom-color: var(--border-dark, #374151);
31
+ }
32
+
33
+ .user-section {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: space-between;
37
+ margin-bottom: 16px;
38
+ }
39
+
40
+ .user-info {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 12px;
44
+ flex: 1;
45
+ min-width: 0;
46
+ }
47
+
48
+ .user-avatar {
49
+ width: 36px;
50
+ height: 36px;
51
+ background: var(--primary-color, #3b82f6);
52
+ border-radius: 50%;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ color: white;
57
+ flex-shrink: 0;
58
+ }
59
+
60
+ .user-details {
61
+ display: flex;
62
+ flex-direction: column;
63
+ min-width: 0;
64
+ }
65
+
66
+ .user-name {
67
+ font-weight: 600;
68
+ color: var(--text-primary, #111827);
69
+ font-size: 14px;
70
+ white-space: nowrap;
71
+ overflow: hidden;
72
+ text-overflow: ellipsis;
73
+ }
74
+
75
+ .dark .user-name {
76
+ color: var(--text-primary-dark, #f9fafb);
77
+ }
78
+
79
+ .user-email {
80
+ font-size: 12px;
81
+ color: var(--text-secondary, #6b7280);
82
+ white-space: nowrap;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ }
86
+
87
+ .dark .user-email {
88
+ color: var(--text-secondary-dark, #9ca3af);
89
+ }
90
+
91
+ /* User Menu */
92
+ .user-menu-container {
93
+ position: relative;
94
+ }
95
+
96
+ .user-menu-button {
97
+ padding: 6px;
98
+ border: none;
99
+ background: none;
100
+ border-radius: 6px;
101
+ cursor: pointer;
102
+ color: var(--text-secondary, #6b7280);
103
+ transition: all 0.2s ease;
104
+ }
105
+
106
+ .user-menu-button:hover {
107
+ background: var(--bg-secondary, #f3f4f6);
108
+ color: var(--text-primary, #111827);
109
+ }
110
+
111
+ .dark .user-menu-button:hover {
112
+ background: var(--bg-secondary-dark, #374151);
113
+ color: var(--text-primary-dark, #f9fafb);
114
+ }
115
+
116
+ .user-menu {
117
+ position: absolute;
118
+ top: 100%;
119
+ right: 0;
120
+ background: var(--bg-primary, #ffffff);
121
+ border: 1px solid var(--border-light, #e5e7eb);
122
+ border-radius: 8px;
123
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
124
+ padding: 8px;
125
+ min-width: 150px;
126
+ z-index: 200;
127
+ }
128
+
129
+ .dark .user-menu {
130
+ background: var(--bg-primary-dark, #1f2937);
131
+ border-color: var(--border-dark, #374151);
132
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
133
+ }
134
+
135
+ .user-menu-item {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 10px;
139
+ padding: 10px 12px;
140
+ width: 100%;
141
+ border: none;
142
+ background: none;
143
+ border-radius: 6px;
144
+ cursor: pointer;
145
+ font-size: 14px;
146
+ color: var(--text-primary, #111827);
147
+ transition: background-color 0.2s ease;
148
+ }
149
+
150
+ .dark .user-menu-item {
151
+ color: var(--text-primary-dark, #f9fafb);
152
+ }
153
+
154
+ .user-menu-item:hover {
155
+ background: var(--bg-secondary, #f3f4f6);
156
+ }
157
+
158
+ .dark .user-menu-item:hover {
159
+ background: var(--bg-secondary-dark, #374151);
160
+ }
161
+
162
+ .user-menu-item.sign-out {
163
+ color: #ef4444;
164
+ }
165
+
166
+ .user-menu-item.sign-out:hover {
167
+ background: #fef2f2;
168
+ }
169
+
170
+ .dark .user-menu-item.sign-out:hover {
171
+ background: #7f1d1d;
172
+ }
173
+
174
+ /* New Chat Button */
175
+ .new-chat-button {
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ gap: 8px;
180
+ width: 100%;
181
+ padding: 12px 16px;
182
+ background: var(--primary-color, #3b82f6);
183
+ color: white;
184
+ border: none;
185
+ border-radius: 8px;
186
+ font-weight: 500;
187
+ cursor: pointer;
188
+ transition: all 0.2s ease;
189
+ }
190
+
191
+ .new-chat-button:hover {
192
+ background: var(--primary-color-hover, #2563eb);
193
+ transform: translateY(-1px);
194
+ }
195
+
196
+ /* Search Section */
197
+ .sidebar-search {
198
+ padding: 16px 20px;
199
+ border-bottom: 1px solid var(--border-light, #e5e7eb);
200
+ flex-shrink: 0;
201
+ }
202
+
203
+ .dark .sidebar-search {
204
+ border-bottom-color: var(--border-dark, #374151);
205
+ }
206
+
207
+ .search-container {
208
+ position: relative;
209
+ }
210
+
211
+ .search-icon {
212
+ position: absolute;
213
+ left: 12px;
214
+ top: 50%;
215
+ transform: translateY(-50%);
216
+ color: var(--text-secondary, #6b7280);
217
+ }
218
+
219
+ .search-input {
220
+ width: 100%;
221
+ padding: 10px 12px 10px 36px;
222
+ border: 1px solid var(--border-light, #e5e7eb);
223
+ border-radius: 8px;
224
+ background: var(--bg-secondary, #f9fafb);
225
+ color: var(--text-primary, #111827);
226
+ font-size: 14px;
227
+ transition: all 0.2s ease;
228
+ }
229
+
230
+ .dark .search-input {
231
+ border-color: var(--border-dark, #374151);
232
+ background: var(--bg-secondary-dark, #374151);
233
+ color: var(--text-primary-dark, #f9fafb);
234
+ }
235
+
236
+ .search-input:focus {
237
+ outline: none;
238
+ border-color: var(--primary-color, #3b82f6);
239
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
240
+ }
241
+
242
+ .search-input::placeholder {
243
+ color: var(--text-secondary, #6b7280);
244
+ }
245
+
246
+ .dark .search-input::placeholder {
247
+ color: var(--text-secondary-dark, #9ca3af);
248
+ }
249
+
250
+ /* Chat Sessions */
251
+ .chat-sessions {
252
+ flex: 1;
253
+ overflow-y: auto;
254
+ padding: 0 20px 20px;
255
+ }
256
+
257
+ .loading-sessions {
258
+ display: flex;
259
+ flex-direction: column;
260
+ align-items: center;
261
+ justify-content: center;
262
+ padding: 40px 20px;
263
+ color: var(--text-secondary, #6b7280);
264
+ gap: 12px;
265
+ }
266
+
267
+ .dark .loading-sessions {
268
+ color: var(--text-secondary-dark, #9ca3af);
269
+ }
270
+
271
+ .loading-spinner {
272
+ width: 24px;
273
+ height: 24px;
274
+ border: 2px solid var(--border-light, #e5e7eb);
275
+ border-top: 2px solid var(--primary-color, #3b82f6);
276
+ border-radius: 50%;
277
+ animation: spin 1s linear infinite;
278
+ }
279
+
280
+ .dark .loading-spinner {
281
+ border-color: var(--border-dark, #374151);
282
+ border-top-color: var(--primary-color, #3b82f6);
283
+ }
284
+
285
+ @keyframes spin {
286
+ 0% { transform: rotate(0deg); }
287
+ 100% { transform: rotate(360deg); }
288
+ }
289
+
290
+ .no-sessions {
291
+ text-align: center;
292
+ padding: 40px 20px;
293
+ color: var(--text-secondary, #6b7280);
294
+ font-size: 14px;
295
+ }
296
+
297
+ .dark .no-sessions {
298
+ color: var(--text-secondary-dark, #9ca3af);
299
+ }
300
+
301
+ .sessions-list {
302
+ display: flex;
303
+ flex-direction: column;
304
+ gap: 8px;
305
+ margin-top: 16px;
306
+ }
307
+
308
+ /* Session Item */
309
+ .session-item {
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: space-between;
313
+ padding: 12px;
314
+ border-radius: 8px;
315
+ cursor: pointer;
316
+ transition: all 0.2s ease;
317
+ border: 1px solid transparent;
318
+ group: true;
319
+ }
320
+
321
+ .session-item:hover {
322
+ background: var(--bg-secondary, #f3f4f6);
323
+ border-color: var(--border-light, #e5e7eb);
324
+ }
325
+
326
+ .dark .session-item:hover {
327
+ background: var(--bg-secondary-dark, #374151);
328
+ border-color: var(--border-dark, #4b5563);
329
+ }
330
+
331
+ .session-item.active {
332
+ background: rgba(59, 130, 246, 0.1);
333
+ border-color: var(--primary-color, #3b82f6);
334
+ }
335
+
336
+ .dark .session-item.active {
337
+ background: rgba(59, 130, 246, 0.2);
338
+ border-color: var(--primary-color, #3b82f6);
339
+ }
340
+
341
+ .session-content {
342
+ display: flex;
343
+ align-items: center;
344
+ gap: 12px;
345
+ flex: 1;
346
+ min-width: 0;
347
+ }
348
+
349
+ .session-icon {
350
+ color: var(--text-secondary, #6b7280);
351
+ flex-shrink: 0;
352
+ }
353
+
354
+ .dark .session-icon {
355
+ color: var(--text-secondary-dark, #9ca3af);
356
+ }
357
+
358
+ .session-item.active .session-icon {
359
+ color: var(--primary-color, #3b82f6);
360
+ }
361
+
362
+ .session-details {
363
+ flex: 1;
364
+ min-width: 0;
365
+ }
366
+
367
+ .session-title {
368
+ font-weight: 500;
369
+ color: var(--text-primary, #111827);
370
+ font-size: 14px;
371
+ white-space: nowrap;
372
+ overflow: hidden;
373
+ text-overflow: ellipsis;
374
+ margin-bottom: 2px;
375
+ }
376
+
377
+ .dark .session-title {
378
+ color: var(--text-primary-dark, #f9fafb);
379
+ }
380
+
381
+ .session-meta {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 8px;
385
+ font-size: 12px;
386
+ color: var(--text-secondary, #6b7280);
387
+ }
388
+
389
+ .dark .session-meta {
390
+ color: var(--text-secondary-dark, #9ca3af);
391
+ }
392
+
393
+ .session-date::after {
394
+ content: "•";
395
+ margin-left: 8px;
396
+ }
397
+
398
+ .session-menu-button {
399
+ opacity: 0;
400
+ padding: 6px;
401
+ border: none;
402
+ background: none;
403
+ border-radius: 4px;
404
+ cursor: pointer;
405
+ color: var(--text-secondary, #6b7280);
406
+ transition: all 0.2s ease;
407
+ flex-shrink: 0;
408
+ }
409
+
410
+ .session-item:hover .session-menu-button {
411
+ opacity: 1;
412
+ }
413
+
414
+ .session-menu-button:hover {
415
+ background: var(--bg-tertiary, #f3f4f6);
416
+ color: #ef4444;
417
+ }
418
+
419
+ .dark .session-menu-button:hover {
420
+ background: var(--bg-tertiary-dark, #4b5563);
421
+ }
422
+
423
+ /* Responsive Design */
424
+ @media (max-width: 768px) {
425
+ .sidebar {
426
+ width: 280px;
427
+ transform: translateX(-100%);
428
+ transition: transform 0.3s ease;
429
+ }
430
+
431
+ .sidebar.open {
432
+ transform: translateX(0);
433
+ }
434
+ }