Girish Jeswani commited on
Commit
ff0136e
·
1 Parent(s): da0affa

update canvas

Browse files
multi_llm_chatbot_backend/app/api/routes/chat_sessions.py CHANGED
@@ -222,6 +222,30 @@ async def save_message_to_session(
222
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
223
  detail="Could not save message"
224
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  @router.delete("/chat-sessions/{session_id}")
227
  async def delete_chat_session(
 
222
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
223
  detail="Could not save message"
224
  )
225
+
226
+ @router.get("/chat-sessions/count")
227
+ async def get_chat_sessions_count(
228
+ current_user: User = Depends(get_current_active_user)
229
+ ):
230
+ """Get count of user's chat sessions"""
231
+ try:
232
+ db = get_database()
233
+ user_object_id = ObjectId(str(current_user.id))
234
+
235
+ count = await db.chat_sessions.count_documents({
236
+ "user_id": user_object_id,
237
+ "is_active": {"$ne": False},
238
+ "deleted_at": {"$exists": False}
239
+ })
240
+
241
+ return {"count": count}
242
+
243
+ except Exception as e:
244
+ logger.error(f"Error counting chat sessions for user {current_user.id}: {e}")
245
+ raise HTTPException(
246
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
247
+ detail="Failed to count chat sessions"
248
+ )
249
 
250
  @router.delete("/chat-sessions/{session_id}")
251
  async def delete_chat_session(
multi_llm_chatbot_backend/app/api/routes/phd_canvas.py CHANGED
@@ -175,26 +175,55 @@ async def trigger_auto_update(
175
  if not canvas.auto_update:
176
  return {"message": "Auto-update is disabled for this canvas"}
177
 
 
 
 
 
 
 
178
  # Check if we need to update (has been more than 1 hour since last update)
179
  from datetime import timedelta
180
- needs_update = (
181
  canvas.last_updated is None or
182
  (datetime.utcnow() - canvas.last_updated) > timedelta(hours=1)
183
  )
184
 
185
- if needs_update:
186
- logger.info(f"Auto-updating canvas for user {current_user.id}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- # Queue incremental update in background
189
  background_tasks.add_task(
190
  _background_canvas_update,
191
  str(current_user.id),
192
  UpdateCanvasRequest(force_full_update=False)
193
  )
194
 
195
- return {"message": "Canvas update queued", "status": "updating"}
 
 
 
 
196
  else:
197
- return {"message": "Canvas is up to date", "status": "current"}
 
 
 
198
 
199
  except Exception as e:
200
  logger.error(f"Error triggering auto-update for user {current_user.id}: {e}")
 
175
  if not canvas.auto_update:
176
  return {"message": "Auto-update is disabled for this canvas"}
177
 
178
+ # Check if this is a first-time canvas (never processed any chats)
179
+ is_first_time_canvas = (
180
+ canvas.last_chat_processed is None and
181
+ canvas.total_insights == 0
182
+ )
183
+
184
  # Check if we need to update (has been more than 1 hour since last update)
185
  from datetime import timedelta
186
+ needs_regular_update = (
187
  canvas.last_updated is None or
188
  (datetime.utcnow() - canvas.last_updated) > timedelta(hours=1)
189
  )
190
 
191
+ if is_first_time_canvas:
192
+ logger.info(f"First-time canvas detected for user {current_user.id}, triggering full update")
193
+
194
+ # For first-time canvas, always do full update to process all historical chats
195
+ background_tasks.add_task(
196
+ _background_canvas_update,
197
+ str(current_user.id),
198
+ UpdateCanvasRequest(force_full_update=True)
199
+ )
200
+
201
+ return {
202
+ "message": "First-time canvas update initiated",
203
+ "status": "processing",
204
+ "type": "full_update"
205
+ }
206
+
207
+ elif needs_regular_update:
208
+ logger.info(f"Regular auto-updating canvas for user {current_user.id}")
209
 
210
+ # For existing canvas, do incremental update
211
  background_tasks.add_task(
212
  _background_canvas_update,
213
  str(current_user.id),
214
  UpdateCanvasRequest(force_full_update=False)
215
  )
216
 
217
+ return {
218
+ "message": "Canvas update queued",
219
+ "status": "updating",
220
+ "type": "incremental_update"
221
+ }
222
  else:
223
+ return {
224
+ "message": "Canvas is up to date",
225
+ "status": "current"
226
+ }
227
 
228
  except Exception as e:
229
  logger.error(f"Error triggering auto-update for user {current_user.id}: {e}")
multi_llm_chatbot_backend/app/core/canvas_manager.py CHANGED
@@ -5,7 +5,6 @@ from bson import ObjectId
5
 
6
  from app.models.phd_canvas import PhdCanvas, CanvasInsight, UpdateCanvasRequest
7
  from app.core.canvas_analysis import CanvasAnalysisService
8
- # Remove this import: from app.core.database import get_database
9
  from app.core.bootstrap import llm
10
 
11
  logger = logging.getLogger(__name__)
@@ -63,6 +62,17 @@ class CanvasManager:
63
 
64
  logger.info(f"Updating canvas for user {user_id}, force_full={request.force_full_update}")
65
 
 
 
 
 
 
 
 
 
 
 
 
66
  # Determine which chats to process
67
  if request.force_full_update:
68
  # Process all chats
@@ -193,12 +203,22 @@ class CanvasManager:
193
  {"updated_at": {"$gt": since}}
194
  ]
195
  else:
196
- # If no since time, get chats from last 7 days as default
197
- one_week_ago = datetime.utcnow() - timedelta(days=7)
198
- query_filter["created_at"] = {"$gte": one_week_ago}
 
 
 
 
 
 
 
 
199
 
 
 
200
  cursor = db.chat_sessions.find(query_filter).sort("created_at", -1)
201
- chat_sessions = await cursor.to_list(length=50) # Limit for incremental updates
202
 
203
  return chat_sessions
204
 
 
5
 
6
  from app.models.phd_canvas import PhdCanvas, CanvasInsight, UpdateCanvasRequest
7
  from app.core.canvas_analysis import CanvasAnalysisService
 
8
  from app.core.bootstrap import llm
9
 
10
  logger = logging.getLogger(__name__)
 
62
 
63
  logger.info(f"Updating canvas for user {user_id}, force_full={request.force_full_update}")
64
 
65
+ # IMPORTANT: Auto-detect if this should be a full update for first-time canvas
66
+ is_first_time_update = (
67
+ canvas.last_chat_processed is None and
68
+ canvas.total_insights == 0 and
69
+ not request.force_full_update
70
+ )
71
+
72
+ if is_first_time_update:
73
+ logger.info(f"Auto-detecting first-time canvas update for user {user_id}. Converting to full update.")
74
+ request.force_full_update = True
75
+
76
  # Determine which chats to process
77
  if request.force_full_update:
78
  # Process all chats
 
203
  {"updated_at": {"$gt": since}}
204
  ]
205
  else:
206
+ # IMPORTANT FIX: If no since time and this is being called for incremental update,
207
+ # we should still get more chats for proper first-time processing
208
+ # The caller should handle the force_full_update logic properly
209
+
210
+ # For now, get chats from last 30 days instead of 7 as a safer fallback
211
+ # But ideally, first-time canvas should always use force_full_update=True
212
+ one_month_ago = datetime.utcnow() - timedelta(days=30)
213
+ query_filter["created_at"] = {"$gte": one_month_ago}
214
+
215
+ logger.warning(f"Getting new chat sessions for user {user_id} with no 'since' timestamp. "
216
+ f"This should only happen for incremental updates. Consider using force_full_update=True for first-time canvas.")
217
 
218
+ # Increase limit for incremental updates to catch more historical chats
219
+ limit = 100 # Increased from 50
220
  cursor = db.chat_sessions.find(query_filter).sort("created_at", -1)
221
+ chat_sessions = await cursor.to_list(length=limit)
222
 
223
  return chat_sessions
224
 
phd-advisor-frontend/src/pages/CanvasPage.js CHANGED
@@ -92,11 +92,21 @@ const CanvasPage = ({ user, authToken, onNavigateToChat, onSignOut }) => {
92
  const [expandedSections, setExpandedSections] = useState({});
93
  const [stats, setStats] = useState({});
94
  const [isPrintView, setIsPrintView] = useState(false);
 
95
 
96
  useEffect(() => {
97
- fetchCanvas();
98
- fetchStats();
99
- triggerAutoUpdate();
 
 
 
 
 
 
 
 
 
100
  }, []);
101
 
102
  const fetchCanvas = async () => {
@@ -148,15 +158,87 @@ const CanvasPage = ({ user, authToken, onNavigateToChat, onSignOut }) => {
148
  }
149
  };
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  const triggerAutoUpdate = async () => {
152
  try {
153
- await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/auto-update`, {
154
  method: 'POST',
155
  headers: {
156
  'Authorization': `Bearer ${authToken}`,
157
  'Content-Type': 'application/json'
158
  }
159
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  } catch (error) {
161
  console.error('Error triggering auto-update:', error);
162
  }
@@ -174,15 +256,52 @@ const CanvasPage = ({ user, authToken, onNavigateToChat, onSignOut }) => {
174
  });
175
 
176
  if (response.ok) {
177
- // Wait a moment then refresh the canvas
 
 
 
178
  setTimeout(() => {
179
  fetchCanvas();
180
  fetchStats();
181
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  }
183
  } catch (error) {
184
  console.error('Error refreshing canvas:', error);
185
- } finally {
186
  setIsUpdating(false);
187
  }
188
  };
@@ -296,13 +415,41 @@ const CanvasPage = ({ user, authToken, onNavigateToChat, onSignOut }) => {
296
  <div className="empty-canvas">
297
  <FileText className="empty-canvas-icon" />
298
  <h2>Your Canvas is Empty</h2>
299
- <p>Start chatting with your AI advisors to populate your PhD Canvas with insights and guidance!</p>
300
- <button
301
- className="start-chatting-button"
302
- onClick={onNavigateToChat}
303
- >
304
- Start Chatting
305
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  </div>
307
  ) : (
308
  <div className="sections-container">
 
92
  const [expandedSections, setExpandedSections] = useState({});
93
  const [stats, setStats] = useState({});
94
  const [isPrintView, setIsPrintView] = useState(false);
95
+ const [isProcessingFirstTime, setIsProcessingFirstTime] = useState(false);
96
 
97
  useEffect(() => {
98
+ const initializeCanvas = async () => {
99
+ await fetchCanvas();
100
+ await fetchStats();
101
+ await triggerAutoUpdate();
102
+
103
+ // After initial load, check if we need to populate empty canvas
104
+ setTimeout(() => {
105
+ checkForEmptyCanvasWithChats();
106
+ }, 2000); // Wait 2 seconds after initial load
107
+ };
108
+
109
+ initializeCanvas();
110
  }, []);
111
 
112
  const fetchCanvas = async () => {
 
158
  }
159
  };
160
 
161
+ // Check if user has chats but empty canvas
162
+ const checkForEmptyCanvasWithChats = async () => {
163
+ try {
164
+ // Check if canvas is empty (no insights)
165
+ const isEmpty = !canvasData || canvasData.total_insights === 0;
166
+
167
+ if (isEmpty) {
168
+ // Check if user has any chat sessions
169
+ const response = await fetch(`${process.env.REACT_APP_API_URL}/api/chat-sessions/count`, {
170
+ headers: {
171
+ 'Authorization': `Bearer ${authToken}`,
172
+ 'Content-Type': 'application/json'
173
+ }
174
+ });
175
+
176
+ if (response.ok) {
177
+ const { count } = await response.json();
178
+
179
+ if (count > 0) {
180
+ // User has chats but empty canvas - trigger full refresh
181
+ console.log(`User has ${count} chats but empty canvas. Triggering full refresh.`);
182
+ await handleFullRefresh();
183
+ }
184
+ }
185
+ }
186
+ } catch (error) {
187
+ console.error('Error checking for empty canvas with chats:', error);
188
+ }
189
+ };
190
+
191
  const triggerAutoUpdate = async () => {
192
  try {
193
+ const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/auto-update`, {
194
  method: 'POST',
195
  headers: {
196
  'Authorization': `Bearer ${authToken}`,
197
  'Content-Type': 'application/json'
198
  }
199
  });
200
+
201
+ if (response.ok) {
202
+ const result = await response.json();
203
+
204
+ // If this is a first-time canvas update, show appropriate message
205
+ if (result.type === 'full_update') {
206
+ console.log('First-time canvas detected. Processing all your chats...');
207
+
208
+ // Show loading state
209
+ setIsProcessingFirstTime(true);
210
+ setIsUpdating(true);
211
+
212
+ // Poll for updates every 10 seconds for up to 3 minutes
213
+ let attempts = 0;
214
+ const maxAttempts = 18; // 3 minutes / 10 seconds
215
+
216
+ const pollForUpdates = setInterval(async () => {
217
+ attempts++;
218
+
219
+ try {
220
+ await fetchCanvas();
221
+
222
+ // If canvas now has insights, stop polling
223
+ if (canvasData && canvasData.total_insights > 0) {
224
+ clearInterval(pollForUpdates);
225
+ setIsUpdating(false);
226
+ setIsProcessingFirstTime(false);
227
+ console.log('Canvas successfully populated with insights!');
228
+ }
229
+
230
+ // Stop polling after max attempts
231
+ if (attempts >= maxAttempts) {
232
+ clearInterval(pollForUpdates);
233
+ setIsUpdating(false);
234
+ setIsProcessingFirstTime(false);
235
+ }
236
+ } catch (error) {
237
+ console.error('Error polling for updates:', error);
238
+ }
239
+ }, 10000);
240
+ }
241
+ }
242
  } catch (error) {
243
  console.error('Error triggering auto-update:', error);
244
  }
 
256
  });
257
 
258
  if (response.ok) {
259
+ const result = await response.json();
260
+ console.log('Full refresh initiated:', result);
261
+
262
+ // Poll for updates
263
  setTimeout(() => {
264
  fetchCanvas();
265
  fetchStats();
266
+ }, 5000);
267
+
268
+ setTimeout(() => {
269
+ setIsUpdating(false);
270
+ }, 10000);
271
+ }
272
+ } catch (error) {
273
+ console.error('Error refreshing canvas:', error);
274
+ setIsUpdating(false);
275
+ }
276
+ };
277
+
278
+ const handleFullRefresh = async () => {
279
+ setIsUpdating(true);
280
+ try {
281
+ const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/refresh`, {
282
+ method: 'GET',
283
+ headers: {
284
+ 'Authorization': `Bearer ${authToken}`,
285
+ 'Content-Type': 'application/json'
286
+ }
287
+ });
288
+
289
+ if (response.ok) {
290
+ const result = await response.json();
291
+ console.log('Full refresh initiated:', result);
292
+
293
+ // Poll for updates
294
+ setTimeout(() => {
295
+ fetchCanvas();
296
+ fetchStats();
297
+ }, 5000);
298
+
299
+ setTimeout(() => {
300
+ setIsUpdating(false);
301
+ }, 10000);
302
  }
303
  } catch (error) {
304
  console.error('Error refreshing canvas:', error);
 
305
  setIsUpdating(false);
306
  }
307
  };
 
415
  <div className="empty-canvas">
416
  <FileText className="empty-canvas-icon" />
417
  <h2>Your Canvas is Empty</h2>
418
+ {isUpdating || isProcessingFirstTime ? (
419
+ <div>
420
+ <p>
421
+ {isProcessingFirstTime
422
+ ? 'Processing your chat history to populate insights...'
423
+ : 'Updating canvas with latest insights...'
424
+ }
425
+ </p>
426
+ <div className="loading-spinner">
427
+ <RefreshCw className="spinning" />
428
+ </div>
429
+ <p className="processing-note">
430
+ This may take a few minutes for extensive chat history.
431
+ </p>
432
+ </div>
433
+ ) : (
434
+ <div>
435
+ <p>Start chatting with your AI advisors to populate your PhD Canvas with insights!</p>
436
+ <div className="empty-canvas-actions">
437
+ <button
438
+ className="start-chatting-button"
439
+ onClick={onNavigateToChat}
440
+ >
441
+ Start Chatting
442
+ </button>
443
+ <button
444
+ className="refresh-button secondary"
445
+ onClick={handleFullRefresh}
446
+ >
447
+ <RefreshCw className="action-icon" />
448
+ Process Existing Chats
449
+ </button>
450
+ </div>
451
+ </div>
452
+ )}
453
  </div>
454
  ) : (
455
  <div className="sections-container">
phd-advisor-frontend/src/styles/CanvasPage.css CHANGED
@@ -191,10 +191,9 @@
191
  .empty-canvas {
192
  text-align: center;
193
  padding: 4rem 2rem;
194
- background: var(--bg-primary);
195
- border-radius: 16px;
196
- border: 1px solid var(--border-primary);
197
- box-shadow: var(--shadow-md);
198
  }
199
 
200
  .empty-canvas-icon {
@@ -205,15 +204,13 @@
205
  }
206
 
207
  .empty-canvas h2 {
208
- margin: 1rem 0;
209
- font-size: 1.8rem;
210
  color: var(--text-primary);
211
  }
212
 
213
  .empty-canvas p {
214
- margin-bottom: 2rem;
215
- color: var(--text-secondary);
216
- font-size: 1.1rem;
217
  }
218
 
219
  .start-chatting-button {
@@ -401,6 +398,44 @@
401
  color: var(--text-primary);
402
  }
403
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  .insight-footer {
405
  display: flex;
406
  justify-content: space-between;
 
191
  .empty-canvas {
192
  text-align: center;
193
  padding: 4rem 2rem;
194
+ color: var(--text-secondary);
195
+ max-width: 600px;
196
+ margin: 0 auto;
 
197
  }
198
 
199
  .empty-canvas-icon {
 
204
  }
205
 
206
  .empty-canvas h2 {
207
+ margin-bottom: 1rem;
 
208
  color: var(--text-primary);
209
  }
210
 
211
  .empty-canvas p {
212
+ margin-bottom: 1.5rem;
213
+ line-height: 1.6;
 
214
  }
215
 
216
  .start-chatting-button {
 
398
  color: var(--text-primary);
399
  }
400
 
401
+ .empty-canvas-actions {
402
+ display: flex;
403
+ gap: 1rem;
404
+ justify-content: center;
405
+ align-items: center;
406
+ margin-top: 1.5rem;
407
+ }
408
+
409
+ .refresh-button.secondary {
410
+ background: var(--bg-secondary);
411
+ color: var(--text-secondary);
412
+ border: 1px solid var(--border-primary);
413
+ }
414
+
415
+ .refresh-button.secondary:hover {
416
+ background: var(--bg-tertiary);
417
+ color: var(--text-primary);
418
+ }
419
+
420
+ .loading-spinner {
421
+ display: flex;
422
+ justify-content: center;
423
+ align-items: center;
424
+ margin: 1rem 0;
425
+ }
426
+
427
+ .loading-spinner .spinning {
428
+ animation: spin 2s linear infinite;
429
+ width: 2rem;
430
+ height: 2rem;
431
+ color: var(--accent-primary);
432
+ }
433
+
434
+ @keyframes spin {
435
+ from { transform: rotate(0deg); }
436
+ to { transform: rotate(360deg); }
437
+ }
438
+
439
  .insight-footer {
440
  display: flex;
441
  justify-content: space-between;