Skip to content

feat(bookmarking): Add support for ui.Chat bookmarking #1951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c53934d
Commit before pivoting to using new client state class / chatlas
schloerke Apr 1, 2025
f15e6d0
Add chatlas as a dev github source
schloerke Apr 1, 2025
2cd9886
First pass at `chat.enable_bookmarking()`
schloerke Apr 1, 2025
e8346e7
Init incomplete example apps
schloerke Apr 1, 2025
b1875c4
Merge branch 'main' into bookmark-chat
schloerke Apr 1, 2025
60660a6
Require `chatlas>=0.6.0`
schloerke Apr 2, 2025
22fc7bc
Merge branch 'bookmark-chat' of https://github.com/posit-dev/py-shiny…
schloerke Apr 2, 2025
06c8e09
Add `ClientWithState` docs
schloerke Apr 2, 2025
fdae6e2
Remove unused new attr
schloerke Apr 2, 2025
57c93ae
Try to only import chatlas directly; Move checks to top; Add raises s…
schloerke Apr 2, 2025
4292d18
Use `on` and `store` for param names
schloerke Apr 2, 2025
0761374
Rename cancel callback methods
schloerke Apr 2, 2025
e3f52b0
Remove unnecessary check
schloerke Apr 2, 2025
59fc8bb
Use function to help with consistent behavior for appending init mess…
schloerke Apr 2, 2025
259bd41
Apply suggestions from code review
schloerke Apr 2, 2025
4c6c6b3
Document `on`; Document express version
schloerke Apr 2, 2025
d4bcd3e
Enable bookmarking in templates
schloerke Apr 2, 2025
56bd3c2
Fixes
schloerke Apr 2, 2025
a7ef7be
Remove unused example app
schloerke Apr 2, 2025
45de2ae
Rename variable
schloerke Apr 2, 2025
7d02692
Apply suggestions from code review
schloerke Apr 2, 2025
593425f
Use `state=` not `value=`
schloerke Apr 2, 2025
df344b1
Update template with suggested comment
schloerke Apr 2, 2025
f51c0be
Rename parameters back to `bookmark_store` and `bookmark_on`
schloerke Apr 2, 2025
895a640
Link to Bookmark class in docs; Add Returns
schloerke Apr 2, 2025
e8a3b12
Update CHANGELOG.md
schloerke Apr 2, 2025
3f6f65f
Tests
schloerke Apr 2, 2025
f86abb8
Move most of chatlas logic to `_chat_bookmark.py`
schloerke Apr 2, 2025
d319227
Rearrange sections to co-locate UI together and client together
schloerke Apr 2, 2025
63c2dfb
lint
schloerke Apr 2, 2025
97efb12
Use pydantic json objects, not str for serialization
schloerke Apr 3, 2025
cfef02b
Update CHANGELOG.md
schloerke Apr 3, 2025
51d1847
Use fake key
schloerke Apr 3, 2025
c6f66f0
Use chatlas >= 0.6.1
schloerke Apr 3, 2025
a044f39
check if this helps webkit url assertion
karangattu Apr 3, 2025
126b41e
add some timeout for webkit browser tests
karangattu Apr 3, 2025
1bca258
bump it to 8 secs to see if it is a timing issue
karangattu Apr 3, 2025
35dbc75
bump it to 30 secs
karangattu Apr 3, 2025
ece58f7
bump timeout to 30 secs :(
karangattu Apr 3, 2025
800d03c
default is 30 seconds, duh
karangattu Apr 3, 2025
b80f8b1
explicit 30 sec wait
karangattu Apr 3, 2025
4e17e85
wait up to 30 secs
karangattu Apr 3, 2025
0af0826
add missing import
karangattu Apr 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ dev = [
"langsmith<0.3",
"openai",
"ollama",
"chatlas",
"tokenizers",
"aiohttp",
"beautifulsoup4",
Expand All @@ -134,6 +135,8 @@ doc = [
"griffe>=1.3.2",
]

[tool.uv.sources]
chatlas = { git = "https://github.com/posit-dev/chatlas", branch = "main" }

[project.urls]
Homepage = "https://github.com/posit-dev/py-shiny"
Expand Down
93 changes: 93 additions & 0 deletions shiny/api-examples/chat_bookmark/app-express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ------------------------------------------------------------------------------------
# A basic Shiny Chat example powered by OpenAI.
# ------------------------------------------------------------------------------------

import requests
from chatlas import ChatOpenAI, Turn
from chatlas._content import Content
from dotenv import load_dotenv

from shiny import reactive
from shiny.bookmark import BookmarkState
from shiny.express import app_opts, session, ui
from shiny.types import MISSING

with ui.hold():
load_dotenv()

chat_client = ChatOpenAI(model="gpt-4o-mini")


# Set some Shiny page options
ui.page_opts(
fillable=True,
fillable_mobile=True,
)

# Create and display a Shiny chat component
chat = ui.Chat(
id="chat",
)
chat.ui(messages=["Hello! Would you like to know the weather today?"])


chat.enable_bookmarking(
chat_client,
bookmark_on="response",
# ONLY for ChatExpress.
# For shiny-core `ui.Chat()``, use the `App(bookmark_store=)` directly
bookmark_store="url",
)


def get_current_temperature(latitude: float, longitude: float):
"""
Get the current weather given a latitude and longitude.

Parameters
----------
latitude
The latitude of the location.
longitude
The longitude of the location.
"""
lat_lng = f"latitude={latitude}&longitude={longitude}"
print("lat_lng:", lat_lng)
url = f"https://api.open-meteo.com/v1/forecast?{lat_lng}&current=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m"
response = requests.get(url)
json = response.json()
print("json:", json)
return json["current"]


with ui.hold():
chat_client.register_tool(get_current_temperature)

# chat_client.chat("What's the weather like today in Duluth, MN?", echo="all")
# > 👤 User turn:
# >
# > What's the weather like today in Duluth, MN?
# >
# > 🤖 Assistant turn:
# >
# > # tool request (call_YRma1FOUHVPGHkfylqdHw886)
# > get_current_temperature(latitude=46.7833, longitude=-92.1062)
# >
# > << 🤖 finish reason: tool_calls >>
# >
# >
# > 👤 User turn:
# >
# > # tool result (call_YRma1FOUHVPGHkfylqdHw886)
# > {'time': '2025-02-27T17:45', 'interval': 900, 'temperature_2m': 3.2, 'wind_speed_10m': 21.6}
# >
# > 🤖 Assistant turn:
# >
# > Today in Duluth, MN, the temperature is approximately 3.2°C with a wind speed of 21.6 km/h.


# Generate a response when the user submits a message
@chat.on_user_submit
async def handle_user_input(user_input: str):
response = await chat_client.stream_async(user_input, echo="all")
await chat.append_message_stream(response)
77 changes: 77 additions & 0 deletions shiny/api-examples/chat_bookmark/app-simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from shiny.express import ui

# Set some Shiny page options
ui.page_opts(
title="Hello Shiny Chat",
fillable=True,
fillable_mobile=True,
)

# Create a welcome message
welcome = """
Hi! This is a simple Shiny `Chat` UI. Enter a message below and I will
simply repeat it back to you.

To learn more about chatbots and how to build them with Shiny, check out
[the documentation](https://shiny.posit.co/py/docs/genai-chatbots.html).
"""

# Create a chat instance
chat = ui.Chat(
id="chat",
messages=[welcome],
)

# Display it
chat.ui()


# Define a callback to run when the user submits a message
@chat.on_user_submit
async def handle_user_input(user_input: str):
# Append a response to the chat
await chat.append_message(f"You said: {user_input}")


raise RuntimeError("TODO barret make custom class here!")

chat.enable_bookmarking(
chat_client,
bookmark_on="response",
# ONLY for ChatExpress.
# For shiny-core `ui.Chat()``, use the `App(bookmark_store=)` directly
bookmark_store="url",
)


# ------------------------------------------------------------------------------------
# A basic Shiny Chat example powered by OpenAI.
# ------------------------------------------------------------------------------------

import requests
from chatlas import ChatOpenAI, Turn
from chatlas._content import Content
from dotenv import load_dotenv

from shiny import reactive
from shiny.bookmark import BookmarkState
from shiny.express import app_opts, session, ui
from shiny.types import MISSING

with ui.hold():
load_dotenv()

chat_client = ChatOpenAI(model="gpt-4o-mini")


# Set some Shiny page options
ui.page_opts(
fillable=True,
fillable_mobile=True,
)

# Create and display a Shiny chat component
chat = ui.Chat(
id="chat",
)
chat.ui(messages=["Hello! Would you like to know the weather today?"])
Loading
Loading