1717
1818@dataclass (frozen = True )
1919class RoomInputOptions :
20+ text_enabled : bool = True
21+ """Whether to subscribe to text input"""
2022 audio_enabled : bool = True
2123 """Whether to subscribe to audio"""
2224 video_enabled : bool = False
@@ -48,6 +50,7 @@ class RoomOutputOptions:
4850DEFAULT_ROOM_INPUT_OPTIONS = RoomInputOptions ()
4951DEFAULT_ROOM_OUTPUT_OPTIONS = RoomOutputOptions ()
5052LK_PUBLISH_FOR_ATTR = "lk.publish_for"
53+ LK_TEXT_INPUT_TOPIC = "lk.room_text_input"
5154
5255
5356class BaseStreamHandle :
@@ -226,6 +229,7 @@ def __init__(
226229 """
227230 self ._options = options
228231 self ._room = room
232+ self ._agent : Optional ["PipelineAgent" ] = None
229233 self ._tasks : set [asyncio .Task ] = set ()
230234
231235 # target participant
@@ -263,6 +267,12 @@ def __init__(
263267 for participant in self ._room .remote_participants .values ():
264268 self ._on_participant_connected (participant )
265269
270+ # text input from datastream
271+ if options .text_enabled :
272+ self ._room .register_text_stream_handler (
273+ LK_TEXT_INPUT_TOPIC , self ._on_text_input
274+ )
275+
266276 @property
267277 def audio (self ) -> AsyncIterator [rtc .AudioFrame ] | None :
268278 if not self ._audio_handle :
@@ -287,7 +297,9 @@ async def start(self, agent: Optional["PipelineAgent"] = None) -> None:
287297 # link to the first connected participant if not set
288298 self .set_participant (participant .identity )
289299
290- if not agent :
300+ # TODO(long): should we force the agent to be set or provide a set_agent method?
301+ self ._agent = agent
302+ if not self ._agent :
291303 return
292304
293305 agent .input .audio = self .audio
@@ -399,6 +411,28 @@ async def _capture_text():
399411 self ._tasks .add (task )
400412 task .add_done_callback (self ._tasks .discard )
401413
414+ def _on_text_input (
415+ self , reader : rtc .TextStreamReader , participant_identity : str
416+ ) -> None :
417+ if participant_identity != self ._participant_identity :
418+ return
419+
420+ async def _read_text ():
421+ if not self ._agent :
422+ return
423+
424+ text = await reader .read_all ()
425+ logger .debug (
426+ "received text input" ,
427+ extra = {"text" : text , "participant" : self ._participant_identity },
428+ )
429+ self ._agent .interrupt ()
430+ self ._agent .generate_reply (user_input = text )
431+
432+ task = asyncio .create_task (_read_text ())
433+ self ._tasks .add (task )
434+ task .add_done_callback (self ._tasks .discard )
435+
402436 async def aclose (self ) -> None :
403437 self ._room .off ("participant_connected" , self ._on_participant_connected )
404438 self ._room .off ("participant_disconnected" , self ._on_participant_disconnected )
0 commit comments