Skip to content

Commit fb2393a

Browse files
committed
Add mention detection to update_message
This patch extends the mention detection functionality from add_message (added in PR #235) to update_message. This enables automatic @mention detection when messages are updated, which is particularly useful for streaming scenarios where message content is built incrementally. Changes: - Refactored mention detection logic into reusable _extract_mentions() helper method - Updated add_message to use the new helper method - Added mention detection to update_message that scans the complete message body - Added three test cases covering mention updates, append with mentions, and deduplication - Fixed regex pattern to use raw string to avoid SyntaxWarning The implementation ensures no duplicate mentions are added when the same message is updated multiple times, making it safe for streaming use cases.
1 parent 7a046de commit fb2393a

File tree

2 files changed

+83
-8
lines changed

2 files changed

+83
-8
lines changed

python/jupyterlab-chat/jupyterlab_chat/tests/test_ychat.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,70 @@ def test_update_message_should_append_content():
167167
assert message_dict["sender"] == msg.sender
168168

169169

170+
def test_update_message_includes_mentions():
171+
chat = YChat()
172+
chat.set_user(USER)
173+
chat.set_user(USER2)
174+
chat.set_user(USER3)
175+
176+
# Add a message with one mention
177+
new_msg = create_new_message(f"@{USER2.mention_name} Hello!")
178+
msg_id = chat.add_message(new_msg)
179+
msg = chat.get_message(msg_id)
180+
assert msg
181+
assert set(msg.mentions) == set([USER2.username])
182+
183+
# Update the message to mention a different user
184+
msg.body = f"@{USER3.mention_name} Goodbye!"
185+
chat.update_message(msg)
186+
updated_msg = chat.get_message(msg_id)
187+
assert updated_msg
188+
assert set(updated_msg.mentions) == set([USER3.username])
189+
190+
191+
def test_update_message_append_includes_mentions():
192+
chat = YChat()
193+
chat.set_user(USER)
194+
chat.set_user(USER2)
195+
chat.set_user(USER3)
196+
197+
# Add a message with one mention
198+
new_msg = create_new_message(f"@{USER2.mention_name} Hello!")
199+
msg_id = chat.add_message(new_msg)
200+
msg = chat.get_message(msg_id)
201+
assert msg
202+
assert set(msg.mentions) == set([USER2.username])
203+
204+
# Append content with another mention
205+
msg.body = f" and @{USER3.mention_name}!"
206+
chat.update_message(msg, append=True)
207+
updated_msg = chat.get_message(msg_id)
208+
assert updated_msg
209+
# Should now mention both users
210+
assert set(updated_msg.mentions) == set([USER2.username, USER3.username])
211+
212+
213+
def test_update_message_append_no_duplicate_mentions():
214+
chat = YChat()
215+
chat.set_user(USER)
216+
chat.set_user(USER2)
217+
218+
# Add a message with a mention
219+
new_msg = create_new_message(f"@{USER2.mention_name} Hello!")
220+
msg_id = chat.add_message(new_msg)
221+
msg = chat.get_message(msg_id)
222+
assert msg
223+
assert set(msg.mentions) == set([USER2.username])
224+
225+
# Append content that mentions the same user again
226+
msg.body = f" @{USER2.mention_name} again!"
227+
chat.update_message(msg, append=True)
228+
updated_msg = chat.get_message(msg_id)
229+
assert updated_msg
230+
# Should only have one mention despite appearing twice
231+
assert set(updated_msg.mentions) == set([USER2.username])
232+
233+
170234
def test_indexes_by_id():
171235
chat = YChat()
172236
msg = create_new_message()

python/jupyterlab-chat/jupyterlab_chat/ychat.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ def get_messages(self) -> list[Message]:
118118
message_dicts = self._get_messages()
119119
return [Message(**message_dict) for message_dict in message_dicts]
120120

121+
def _extract_mentions(self, body: str) -> list[str]:
122+
"""
123+
Extract mentioned usernames from a message body.
124+
Finds all @mentions in the body and returns the corresponding usernames.
125+
"""
126+
mention_pattern = re.compile(r"@([\w-]+):?")
127+
mentioned_names: Set[str] = set(re.findall(mention_pattern, body))
128+
users = self.get_users()
129+
mentioned_usernames = []
130+
for username, user in users.items():
131+
if user.mention_name in mentioned_names and user.username not in mentioned_usernames:
132+
mentioned_usernames.append(username)
133+
return mentioned_usernames
134+
121135
def _get_messages(self) -> list[dict]:
122136
"""
123137
Returns the messages of the document as dict.
@@ -137,14 +151,7 @@ def add_message(self, new_message: NewMessage) -> str:
137151
)
138152

139153
# find all mentioned users and add them as message mentions
140-
mention_pattern = re.compile("@([\w-]+):?")
141-
mentioned_names: Set[str] = set(re.findall(mention_pattern, message.body))
142-
users = self.get_users()
143-
mentioned_usernames = []
144-
for username, user in users.items():
145-
if user.mention_name in mentioned_names and user.username not in mentioned_usernames:
146-
mentioned_usernames.append(username)
147-
message.mentions = mentioned_usernames
154+
message.mentions = self._extract_mentions(message.body)
148155

149156
with self._ydoc.transaction():
150157
index = len(self._ymessages) - next((i for i, v in enumerate(self._get_messages()[::-1]) if v["time"] < timestamp), len(self._ymessages))
@@ -166,6 +173,10 @@ def update_message(self, message: Message, append: bool = False):
166173
message.time = initial_message["time"] # type:ignore[index]
167174
if append:
168175
message.body = initial_message["body"] + message.body # type:ignore[index]
176+
177+
# Extract and update mentions from the message body
178+
message.mentions = self._extract_mentions(message.body)
179+
169180
self._ymessages[index] = asdict(message, dict_factory=message_asdict_factory)
170181

171182
def get_attachments(self) -> dict[str, Union[FileAttachment, NotebookAttachment]]:

0 commit comments

Comments
 (0)