1313import re
1414from dataclasses import replace
1515from jupyterlab_chat .models import Message
16- from pycrdt import ArrayEvent
16+ from pycrdt import ArrayEvent , TextEvent , MapEvent
1717from traitlets .config import LoggingConfigurable
1818from jupyter_ydoc .ybasedoc import YBaseDoc
1919
@@ -112,6 +112,9 @@ def __init__(self, *args, **kwargs):
112112 # Root observers for keeping track of incoming messages
113113 self .message_observers : Dict [str , Callable ] = {}
114114
115+ # Global awareness observer subscriber ID for cleanup
116+ self ._global_awareness_subscriber_id = None
117+
115118 self .event_loop .create_task (self ._start_observing_global_awareness ())
116119
117120 async def _room_id_from_path (self , path : str ) -> str | None :
@@ -133,7 +136,7 @@ def event_loop(self):
133136
134137 async def _start_observing_global_awareness (self ):
135138 awareness = self ._get_global_awareness ()
136- awareness .observe (self ._on_global_awareness_change )
139+ self . _global_awareness_subscriber_id = awareness .observe (self ._on_global_awareness_change )
137140
138141 async def _start_observing_room (self , room_id : str , username : str ):
139142 """Start observing a room's document and awareness changes."""
@@ -401,7 +404,8 @@ def _on_global_awareness_change(self, topic, updates):
401404 async def _handle_user_document_switch (
402405 self , username : str , current_doc : str , prev_doc : str | None
403406 ):
404- """Handle user switching documents - async version for room operations."""
407+ """Handles user switching documents, unobserves current doc room,
408+ and registers new observers for the room that becomes active."""
405409 try :
406410 # Only handle users we have observers for
407411 if username not in self .users :
@@ -436,15 +440,16 @@ async def _handle_user_document_switch(
436440 except Exception as e :
437441 self .log .error (f"Error handling document switch for user { username } : { e } " )
438442
439- def _on_notebook_change (self , room_id : str ):
443+ def _on_notebook_change (self , room_id : str , events ):
440444 """Handle notebook document changes and log event details."""
441445
446+ self .log .info (f"Change event is { events } " )
447+
442448 # Save the timestamp that a change was made indicating notebook has changed
443449 current_time = time .time ()
444450 if room_id in self .rooms :
445451 self .rooms [room_id ].last_edit_time = current_time
446- self .log .info (f"Notebook cells changed in { room_id } at { current_time } " )
447-
452+ self .log .info (f"Notebook cells changed in { room_id } at { current_time } " )
448453
449454 def _on_awareness_change (self , room_id : str , ydoc : YBaseDoc , topic , updates ):
450455 """Handle awareness changes for notebook activity tracking."""
@@ -486,17 +491,15 @@ def _on_awareness_change(self, room_id: str, ydoc: YBaseDoc, topic, updates):
486491 }
487492 continue
488493
489- if prev_active_cell != active_cell :
490- # Check if there was a notebook change since last check
491- if room .last_edit_time > prev_check :
492- # Check if enough time has passed since last trigger (debouncing)
493- if current_time - room .last_trigger_time >= self .trigger_cooldown :
494- room .last_trigger_time = current_time
495- self ._notify_notebook_activity_observers (
496- username = username ,
497- prev_active_cell = prev_active_cell ,
498- notebook_path = notebook_path
499- )
494+ if prev_active_cell != active_cell and room .last_edit_time > prev_check :
495+ # Check if enough time has passed since last trigger
496+ if current_time - room .last_trigger_time >= self .trigger_cooldown :
497+ room .last_trigger_time = current_time
498+ self ._notify_notebook_activity_observers (
499+ username = username ,
500+ prev_active_cell = prev_active_cell ,
501+ notebook_path = notebook_path
502+ )
500503
501504 # Update stored state for this user
502505 user .room_states [room_id ] = {
@@ -676,19 +679,66 @@ def _on_notebook_reset(self, room_id, ydoc: YBaseDoc) -> None:
676679 except Exception as e :
677680 self .log .error (f"Reset notebook observer error for { room_id } : { e } " )
678681
682+ def _cleanup_rooms (self ) -> None :
683+ """Clean up all room trackers and their subscriptions."""
684+ room_ids = list (self .rooms .keys ())
685+ for room_id in room_ids :
686+ room_tracker = self .rooms [room_id ]
687+ try :
688+ room_tracker .stop_observing ()
689+ self .log .debug (f"Cleaned up room tracker for { room_id } " )
690+ except Exception as e :
691+ self .log .warning (f"Failed to clean up room tracker { room_id } : { e } " )
692+ self .rooms .clear ()
693+
694+ def _cleanup_awareness_observers (self ) -> None :
695+ """Clean up global and local awareness observers."""
696+ # Clean up global awareness observer
697+ if self ._global_awareness_subscriber_id is not None :
698+ try :
699+ awareness = self ._get_global_awareness ()
700+ awareness .unobserve (self ._global_awareness_subscriber_id )
701+ self ._global_awareness_subscriber_id = None
702+ self .log .debug ("Cleaned up global awareness observer" )
703+ except Exception as e :
704+ self .log .warning (f"Failed to clean up global awareness observer: { e } " )
705+
706+ # Clean up notebook activity observers
707+ observer_ids = list (self ._observer_callbacks .keys ())
708+ for observer_id in observer_ids :
709+ try :
710+ # Observer callbacks are already disconnected via room cleanup
711+ # Just need to clear the registry
712+ del self ._observer_callbacks [observer_id ]
713+ except Exception as e :
714+ self .log .warning (f"Failed to clean up observer { observer_id } : { e } " )
715+
716+ # Reset observer counter
717+ self .observer_counter = 0
718+
679719 def cleanup (self ) -> None :
680720 """Clean up router resources."""
681721 self .log .info ("Cleaning up MessageRouter..." )
682722
723+ # Clean up room trackers and their subscriptions
724+ self ._cleanup_rooms ()
725+
726+ # Clean up user trackers
727+ self .users .clear ()
728+
729+ # Clean up awareness observers (global and local)
730+ self ._cleanup_awareness_observers ()
731+
683732 # Disconnect all chats
684733 room_ids = list (self .active_chats .keys ())
685734 for room_id in room_ids :
686735 self .disconnect_chat (room_id )
687736
688- # Clear callbacks
737+ # Clear all observer callback lists
689738 self .chat_init_observers .clear ()
690739 self .slash_cmd_observers .clear ()
691740 self .chat_msg_observers .clear ()
692741 self .chat_reset_observers .clear ()
742+ self .notebook_reset_observers .clear ()
693743
694744 self .log .info ("MessageRouter cleanup complete" )
0 commit comments