Skip to content

ui.Chat: Adding Shiny inputs via chat.append_message() #1813

@kovla

Description

@kovla

I’m using Python Shiny’s built-in ui.chat_ui() and chat.append_message() to build a chat interface. I’d like to insert a Shiny input (for example, an action link or a button) directly into a new chat message so that the user can interact with it. However, when I embed an input_action_link in the text passed to chat.append_message(...), Shiny displays the link visually but does not treat it as a real input (i.e., input.my_special_link() never increments).

The rationale for this approach is linked to best practices when building AI agents with a human in the loop. A chat-based input (which could even be constructed by an LLM) is a popular approach. It can be observed in ChatGPT, when user is asked to provide feedback. It can be found in the assistant-ui library (labeled "Generative UI"), where it is used to confirm an AI action, such transaction (https://blog.langchain.dev/assistant-ui/). Of course, one could just add such an element outside the chat, but it makes the GUI more complex and can be cumbersome when you have multiple tools and/or actions. Aligning all temporary one-off inputs with their chat context seems more proper.

Below is my minimal reproducible example. When you run it and enter a message in the chat, it appends a new chat message containing a “special link,” but that link isn’t recognized by Shiny. Uncommenting the workaround (inserting an empty <script> or using ui.insert_ui) does force a re-scan, making it work. But I’d like to avoid that if possible.

from shiny import App, ui, reactive

app_ui = ui.page_fluid(
    ui.chat_ui("chat")
)

def server(input, output, session):
    # Initialize the chat
    chat = ui.Chat("chat")

    @chat.on_user_submit
    async def on_user_submit():
        user_messages = chat.messages()
        user_text = user_messages[-1]["content"]
        
        shiny_link_html = ui.input_action_link("my_special_link", "Click me!")
        # This appears as a link visually but doesn't increment input.my_special_link()
        await chat.append_message(
            f"Here is a special link in the chat: {shiny_link_html}"
        )

        # Uncommenting the code below triggers a re-scan, which "fixes" it:
        # ui.insert_ui(
        #     ui.HTML("<script></script>"),
        #     selector="body",
        #     where="beforeEnd"
        # )

    @reactive.effect
    def watch_special_click():
        clicks = input.my_special_link()
        if clicks > 0:
            print(f"[Server] Special link clicked {clicks} time(s).")

app = App(app_ui, server)
  1. Is there an official or recommended way to add Shiny inputs inside a chat message so that they’re automatically recognized by input[...] without needing a manual DOM insertion workaround?

  2. Is there a stable, built-in function to trigger a “DOM re-bind” (like Shiny.bindAll() in R Shiny) from Python, rather than using ui.insert_ui(ui.HTML("<script></script>")) as a hack?

  3. Is embedding Shiny inputs in chat messages expected to work in the future, or should I rely on separate UI insertion?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions