Skip to content

Commit f625eba

Browse files
committed
Add .get_latest_stream_result() method to Chat and MarkdownStream
1 parent 1df86c4 commit f625eba

File tree

3 files changed

+69
-14
lines changed

3 files changed

+69
-14
lines changed

shiny/ui/_chat.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ def __init__(
209209
reactive.Value(None)
210210
)
211211

212+
self._latest_stream: reactive.Value[
213+
reactive.ExtendedTask[[], str] | None
214+
] = reactive.Value(None)
215+
212216
# TODO: deprecate messages once we start promoting managing LLM message
213217
# state through other means
214218
@reactive.effect
@@ -573,6 +577,8 @@ async def _stream_task():
573577

574578
_stream_task()
575579

580+
self._latest_stream.set(_stream_task)
581+
576582
# Since the task runs in the background (outside/beyond the current context,
577583
# if any), we need to manually raise any exceptions that occur
578584
@reactive.effect
@@ -584,6 +590,33 @@ async def _handle_error():
584590

585591
return _stream_task
586592

593+
def get_latest_stream_result(self) -> str | None:
594+
"""
595+
Reactively read the latest message stream result.
596+
597+
This method reads a reactive value containing the result of the latest
598+
`.append_message_stream()`. Therefore, this method must be called in a reactive
599+
context (e.g., a render function, a :func:`~shiny.reactive.calc`, or a
600+
:func:`~shiny.reactive.effect`).
601+
602+
Returns
603+
-------
604+
:
605+
The result of the latest stream (a string).
606+
607+
Raises
608+
------
609+
:
610+
A silent exception if no stream has completed yet.
611+
"""
612+
stream = self._latest_stream()
613+
if stream is None:
614+
from .. import req
615+
616+
req(False)
617+
else:
618+
return stream.result()
619+
587620
async def _append_message_stream(self, message: AsyncIterable[Any]):
588621
id = _utils.private_random_id()
589622

shiny/ui/_markdown_stream.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .._docstring import add_example
88
from .._namespaces import resolve_id
99
from .._typing_extensions import TypedDict
10-
from ..session import require_active_session
10+
from ..session import require_active_session, session_context
1111
from ..types import NotifyException
1212
from ..ui.css import CssUnit, as_css_unit
1313
from . import Tag
@@ -86,6 +86,11 @@ def __init__(
8686

8787
self.on_error = on_error
8888

89+
with session_context(self._session):
90+
self._latest_stream: reactive.Value[
91+
reactive.ExtendedTask[[], str] | None
92+
] = reactive.Value(None)
93+
8994
async def stream(
9095
self,
9196
content: Union[Iterable[str], AsyncIterable[str]],
@@ -146,6 +151,33 @@ async def _handle_error():
146151

147152
return _task
148153

154+
def get_latest_stream_result(self) -> str | None:
155+
"""
156+
Reactively read the latest stream result.
157+
158+
This method reads a reactive value containing the result of the latest
159+
`.stream()`. Therefore, this method must be called in a reactive context (e.g.,
160+
a render function, a :func:`~shiny.reactive.calc`, or a
161+
:func:`~shiny.reactive.effect`).
162+
163+
Returns
164+
-------
165+
:
166+
The result of the latest stream (a string).
167+
168+
Raises
169+
------
170+
:
171+
A silent exception if no stream has completed yet.
172+
"""
173+
stream = self._latest_stream()
174+
if stream is None:
175+
from .. import req
176+
177+
req(False)
178+
else:
179+
return stream.result()
180+
149181
async def clear(self):
150182
"""
151183
Empty the UI element of the `MarkdownStream`.
Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import asyncio
2-
from typing import Union
32

4-
from shiny import reactive
53
from shiny.express import render, ui
6-
from shiny.reactive import ExtendedTask
74

85
chat = ui.Chat("chat")
96

@@ -19,16 +16,9 @@ async def stream_generator():
1916

2017
@chat.on_user_submit
2118
async def _(message: str):
22-
stream = await chat.append_message_stream(stream_generator())
23-
current_stream.set(stream)
24-
25-
26-
current_stream: reactive.value[Union[ExtendedTask[[], str], None]] = reactive.value(
27-
None
28-
)
19+
await chat.append_message_stream(stream_generator())
2920

3021

3122
@render.code
32-
def stream_result_ui():
33-
stream = current_stream()
34-
return stream.result() if stream else None
23+
async def stream_result_ui():
24+
return chat.get_latest_stream_result()

0 commit comments

Comments
 (0)