Skip to content

Commit ad01b33

Browse files
committed
Working tracker for single user, collaborative tracking doesn't work.
1 parent 7d0ec47 commit ad01b33

File tree

2 files changed

+541
-21
lines changed

2 files changed

+541
-21
lines changed

jupyter_ai_router/extension.py

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import annotations
2+
import asyncio
3+
import json
24
from typing import TYPE_CHECKING
35
import time
46
from jupyter_events import EventLogger
57
from jupyter_server.extension.application import ExtensionApp
8+
from jupyter_ydoc.ybasedoc import YBaseDoc
69

710
from jupyter_ai_router.handlers import RouteHandler
811

@@ -41,6 +44,18 @@ class RouterExtension(ExtensionApp):
4144

4245
router: MessageRouter
4346

47+
@property
48+
def event_loop(self) -> asyncio.AbstractEventLoop:
49+
"""
50+
Returns a reference to the asyncio event loop.
51+
"""
52+
return asyncio.get_event_loop_policy().get_event_loop()
53+
54+
@property
55+
def fileid_manager(self):
56+
return self.serverapp.web_app.settings["file_id_manager"]
57+
58+
4459
def initialize_settings(self):
4560
"""Initialize router settings and event listeners."""
4661
start = time.time()
@@ -59,10 +74,53 @@ def initialize_settings(self):
5974
self.event_logger.add_listener(
6075
schema_id=JUPYTER_COLLABORATION_EVENTS_URI, listener=self._on_chat_event
6176
)
62-
77+
self.event_loop.create_task(self._check_notebook_observer())
78+
6379
elapsed = time.time() - start
6480
self.log.info(f"Initialized RouterExtension in {elapsed:.2f}s")
6581

82+
83+
async def _check_notebook_observer(self):
84+
await asyncio.sleep(20)
85+
def callback(username, prev_active_cell, notebook_path):
86+
self.log.info(
87+
f"notebook observer callback : {username=}, {prev_active_cell=}, {notebook_path=}"
88+
)
89+
90+
jcollab_api = self.serverapp.web_app.settings["jupyter_server_ydoc"]
91+
yroom_manager = jcollab_api.yroom_manager
92+
yroom = yroom_manager.get_room("JupyterLab:globalAwareness")
93+
awareness = yroom.get_awareness()
94+
for _, state in awareness.states.items():
95+
if username := state.get("user", {}).get("username", None):
96+
self.router.observe_notebook_activity(
97+
username=username, callback=callback
98+
)
99+
break
100+
101+
102+
def _get_global_awareness(self):
103+
# TODO: make this compatible with jcollab
104+
jcollab_api = self.serverapp.web_app.settings["jupyter_server_ydoc"]
105+
yroom_manager = jcollab_api.yroom_manager
106+
yroom = yroom_manager.get_room("JupyterLab:globalAwareness")
107+
return yroom.get_awareness()
108+
109+
async def _room_id_from_path(self, path: str) -> str | None:
110+
"""Returns room_id from document path"""
111+
# TODO: Make this compatible with jcollab
112+
yroom_manager = self.serverapp.web_app.settings["yroom_manager"]
113+
for room_id in yroom_manager._rooms_by_id:
114+
if room_id == "JupyterLab:globalAwareness":
115+
continue
116+
ydoc = await self._get_doc(room_id)
117+
state = ydoc.awareness.get_local_state()
118+
file_id = state["file_id"]
119+
ydoc_path = self.fileid_manager.get_path(file_id)
120+
if ydoc_path == path:
121+
print(f"Found match in path {path}")
122+
return room_id
123+
66124
async def _on_chat_event(
67125
self, logger: EventLogger, schema_id: str, data: dict
68126
) -> None:
@@ -87,6 +145,84 @@ async def _on_chat_event(
87145
# Connect chat to router
88146
self.router.connect_chat(room_id, ychat)
89147

148+
async def _on_notebook_event(
149+
self, logger: EventLogger, schema_id: str, data: dict
150+
) -> None:
151+
"""Handle notebook room events and connect new chats to router."""
152+
# Only handle notebook room initialization events
153+
if not (
154+
data["room"].startswith("json:notebook:")
155+
and data["action"] == "initialize"
156+
and data["msg"] == "Room initialized"
157+
):
158+
return
159+
160+
room_id = data["room"]
161+
self.log.info(f"New notebook room detected: {room_id}")
162+
163+
# Get YDoc document for the room
164+
ydoc = await self._get_doc(room_id)
165+
if ydoc is None:
166+
self.log.error(f"Failed to get YDoc for room {room_id}")
167+
return
168+
169+
# Connect notebook to router
170+
self.router.connect_notebook(room_id, ydoc)
171+
172+
async def _get_doc(self, room_id: str) -> YBaseDoc | None:
173+
"""
174+
Get YDoc instance for a room ID.
175+
176+
Dispatches to either `_get_doc_jcollab()` or `_get_doc_jsd()` based on
177+
whether `jupyter_server_documents` is installed.
178+
"""
179+
180+
if JSD_PRESENT:
181+
return await self._get_doc_jsd(room_id)
182+
else:
183+
return await self._get_doc_jcollab(room_id)
184+
185+
async def _get_doc_jcollab(self, room_id: str) -> YBaseDoc | None:
186+
"""
187+
Method used to retrieve the `YDoc` instance for a given room when
188+
`jupyter_server_documents` **is not** installed.
189+
"""
190+
if not self.serverapp:
191+
return None
192+
193+
try:
194+
collaboration = self.serverapp.web_app.settings["jupyter_server_ydoc"]
195+
document = await collaboration.get_document(room_id=room_id, copy=False)
196+
return document
197+
except Exception as e:
198+
self.log.error(f"Error getting ydoc for {room_id}: {e}")
199+
return None
200+
201+
async def _get_doc_jsd(self, room_id: str) -> YBaseDoc | None:
202+
"""
203+
Method used to retrieve the `YDoc` instance for a given room when
204+
`jupyter_server_documents` **is** installed.
205+
206+
This method uniquely attaches a callback which is fired whenever the
207+
`YDoc` is reset.
208+
"""
209+
if not self.serverapp:
210+
return None
211+
212+
try:
213+
jcollab_api = self.serverapp.web_app.settings["jupyter_server_ydoc"]
214+
yroom_manager = jcollab_api.yroom_manager
215+
yroom = yroom_manager.get_room(room_id)
216+
217+
def _on_ydoc_reset(new_ydoc: YBaseDoc):
218+
self.router._on_notebook_reset(room_id, new_ydoc)
219+
220+
ydoc = await yroom.get_jupyter_ydoc(on_reset=_on_ydoc_reset)
221+
return ydoc
222+
except Exception as e:
223+
self.log.error(f"Error getting ydoc for {room_id}: {e}")
224+
return None
225+
90226
async def _get_chat(self, room_id: str) -> YChat | None:
91227
"""
92228
Get YChat instance for a room ID.

0 commit comments

Comments
 (0)