-
Notifications
You must be signed in to change notification settings - Fork 114
Open
Description
Hi @schloerke: @cpsievert suggested connecting with you about the new state save and restore features. Hopefully this path is appropriate.
I cobbled together a working solution to save and restore data, incl dataframes, but it seems a bit clunky (i.e., state is being updated many times to avoid losing information on browser refresh). See example below. Would love to hear your suggestions.
A few additional questions:
- The shiny bookmarks folder can fill up quickly with this approach. My ideal would be to store state for a specific user in one file (or folder with a data and a json file) only on (1) browser refresh and (2) a user button click. I see a setting for ".dir". Is there a setting to control the file name for state?
- If you "Add data" in the app and then refresh, the "Select data" dropdown visibly flips from C to A to C. Not an issue in this app but if an analysis would run based on the value of "Select data" ...
- Any suggestions on how to best approach save state to a specific file/folder and then restoring state from a specific file / folder in shiny-for-python would be very interesting.
import os
from chatlas import ChatOpenAI
from shiny import App, ui, reactive, render
from shiny.bookmark import BookmarkState, RestoreState
from starlette.requests import Request
from dotenv import load_dotenv
import pandas as pd
import pickle
from pathlib import Path
load_dotenv()
chat_client = ChatOpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o",
system_prompt="You are a helpful assistant.",
)
DATASETS_PATH = Path("datasets.pkl")
def app_ui(request: Request):
return ui.page_sidebar(
ui.sidebar(
ui.h4("Test"),
ui.input_select("select_letter", "Select letter", choices=["X", "Y", "Z"]),
ui.input_select("select_data", "Select data", choices=["A", "B"]),
# ui.output_ui("ui_select_data"), # Use different ID for output
ui.input_action_button("add_data", "Add Data"),
ui.input_action_button("remove_data", "Remove Data"),
ui.output_ui("data"),
width=400,
),
ui.chat_ui(
id="chat",
messages=["Hello! How can I help you today?"],
),
width=400,
)
def server(input, output, session):
if DATASETS_PATH.exists() and DATASETS_PATH.stat().st_size > 0:
with open(DATASETS_PATH, "rb") as f:
_datasets = pickle.load(f)
else:
_datasets = {
"A": pd.DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]}),
"B": pd.DataFrame({"b": [3, 4], "c": [7, 8], "d": [9, 10]}),
}
datasets = reactive.value(_datasets)
available_datasets = reactive.value(list(_datasets.keys()))
chat = ui.Chat(id="chat")
@reactive.Effect
@reactive.event(input.add_data)
def _():
tmp = datasets.get()
tmp["C"] = pd.DataFrame({"c": [5, 6], "d": [7, 8], "e": [9, 10]})
datasets.set(tmp)
tmp = list(tmp.keys())
available_datasets.set(tmp)
ui.update_select("select_data", choices=tmp, selected="C")
@reactive.Effect
@reactive.event(input.remove_data)
def _():
tmp = datasets.get()
del tmp[input.select_data()]
datasets.set(tmp)
tmp = list(tmp.keys())
available_datasets.set(tmp)
ui.update_select("select_data", choices=tmp, selected=tmp[0])
@render.ui
def ui_select_data(): # Match the output ID
choices = available_datasets.get()
return ui.input_select("select_data", "Select data", choices=choices)
@render.ui
@reactive.event(input.select_data)
def data():
selected = input.select_data()
data_dict = datasets.get()
if selected in data_dict:
return ui.output_data_frame("selected_data")
return ui.p("No data selected")
@render.data_frame
def selected_data():
selected = input.select_data()
data_dict = datasets.get()
return data_dict.get(selected)
chat.enable_bookmarking(chat_client)
@chat.on_user_submit
async def handle_user_input(user_input: str):
response = await chat_client.stream_async(user_input)
await chat.append_message_stream(response)
@session.bookmark.on_restore
def _(state: RestoreState) -> None:
if "available_datasets" in state.values and "select_data" in state.values:
tmp = available_datasets.get()
ui.update_select(
"select_data", choices=tmp, selected=state.values["select_data"]
)
@session.bookmark.on_bookmark
def _(state: BookmarkState) -> None:
state.values["available_datasets"] = available_datasets.get()
state.values["select_data"] = input.select_data()
state.values["select_letter"] = input.select_letter()
@reactive.Effect
@reactive.event(
input.select_data, input.select_letter, input.add_data, input.remove_data
)
async def _():
await session.bookmark()
@reactive.Effect
@reactive.event(input.add_data, input.remove_data)
def _():
with open(DATASETS_PATH, "wb") as f:
pickle.dump(datasets.get(), f)
app = App(app_ui, server, bookmark_store="server")Metadata
Metadata
Assignees
Labels
No labels