Phantom buttons appearing in streamlit

Summary

I am experiencing phantom buttons appearing in my streamlit app

Steps to reproduce

Code snippet:

import streamlit as st
import time
from streamlit_chat import message


def main():
    # Set page config (must be at beginning of script)
    # Sets what is on top bar including any emoji from twitter emoji library
    st.set_page_config(
        page_title="Test Issue",
        page_icon="๐Ÿค–",
    )

    # hide hamburger menu
    hide_menu_style = """
            <style>
            #MainMenu {visibility: hidden;}
            </style>
            """
    st.markdown(hide_menu_style, unsafe_allow_html=True)
        
    # Initialization
    if 'count' not in st.session_state:
        st.session_state.count = 0
        
    def form_callback():
        st.session_state.count += 1
        
    st.session_state
                  
    help_txt = "I need help with a device."
    order_something_txt = "I want to order new supplies."
    something_else_txt = "I need help with something else"

    message("What can I help you with today?")
    device_help = st.button(f"{help_txt}", on_click=form_callback)
    order_something = st.button(f"{order_something_txt}", on_click=form_callback)
    something_else = st.button(f"{something_else_txt}", on_click=form_callback)
         
    if device_help:
        message(f"{help_txt}", is_user=True, avatar_style="big-smile")
    elif order_something:
        message(f"{order_something_txt}", is_user=True, avatar_style="big-smile")
    elif something_else:
        message(f"{something_else_txt}", is_user=True, avatar_style="big-smile")
    
    if st.session_state.count >= 1:
        if st.session_state.count == 1:
            time.sleep(0.5)
            with st.spinner('Bringing up your devices...'):
                time.sleep(1.5)
                        
        d1 = "Device 1"
        d2 = "Device 2"
        d3 = "Device 3"
        ns = "Another device/I'm not sure"
                    
        message("Which device do you need help with?")
        device1 = st.button(f"{d1}", on_click=form_callback)
        device2 = st.button(f"{d2}", on_click=form_callback)
        device3 = st.button(f"{d3}", on_click=form_callback)
        device4 = st.button(f"{ns}", on_click=form_callback)
            
        if device1:
            message(f"{d1}", is_user=True, avatar_style="big-smile")
        elif device2:
            message(f"{d2}", is_user=True, avatar_style="big-smile")
        elif device3:
            message(f"{d3}", is_user=True, avatar_style="big-smile")
        elif device4:
            message(f"{ns}", is_user=True, avatar_style="big-smile")
        
        if st.session_state.count >= 2:
            if st.session_state.count == 2:
                time.sleep(0.5)
                with st.spinner('Processing...'):
                    time.sleep(1.5)
            
            issue1 = "Issue 1"
            issue2 = "Issue 2"
            issue3 = "Issue 3"
            issue4 = "Issue 4"
            
            message("What is the problem with your device today?")
            option1 = st.button(f"{issue1}", on_click=form_callback)
            option2 = st.button(f"{issue2}", on_click=form_callback)
            option3 = st.button(f"{issue3}", on_click=form_callback)
            option4 = st.button(f"{issue4}", on_click=form_callback)
            
            if option1:
                message(f"{issue1}", is_user=True, avatar_style="big-smile")
            elif option2:
                message(f"{issue2}", is_user=True, avatar_style="big-smile")
            elif option3:
                message(f"{issue3}", is_user=True, avatar_style="big-smile")
            elif option4:
                message(f"{issue4}", is_user=True, avatar_style="big-smile")
                
            if st.session_state.count >= 3:
                if st.session_state.count == 3:
                    time.sleep(0.5)
                    with st.spinner('Processing...'):
                        time.sleep(1.5)
                        
                message("I'm sorry you're having this issue. Have you tried XYZ? Here is a link to more information: Link. Are you still experiencing the issue?")
                    
                yes = "Yes, I am still experiencing the issue"
                no = "No, the issue has resolved"
                
                continue_option = st.button(f"{yes}", on_click=form_callback)
                solved = st.button(f"{no}", on_click=form_callback)
                
                if continue_option:
                    message(f"{yes}", is_user=True, avatar_style="big-smile")
                        
                if st.session_state.count >= 4:
                    if st.session_state.count == 4:
                        time.sleep(0.5)
                        with st.spinner("Processing..."):
                            time.sleep(3)
                            
                    message("I'm sorry you're still experiencing the issue. Do I have your permission to look into your data to see what might be going on?")
                    

                    yes_view_cl = "Yes, you have my permission to view my data"
                    no_view_cl = "No, thanks"
                    
                    view_cl = st.button(f"{yes_view_cl}", on_click=form_callback)
                    no_view_cl = st.button(f"{no_view_cl}", on_click=form_callback)
                    
                    if view_cl:
                        message(f"{yes_view_cl}", is_user=True, avatar_style="big-smile")
                    elif no_view_cl:
                        message(f"{no_view_cl}", is_user=True, avatar_style="big-smile")
                        
                    if st.session_state.count >= 5:
                        if st.session_state.count == 5:
                            time.sleep(0.5)
                            with st.spinner("Accessing your data, one moment..."):
                                time.sleep(8)
                            
                        message("I see the following observation in your data that might be causing the issue: [insert observation here]. After examining the observations here, are you still experiencing the issue?")
                        
                        yes_still_experiencing = "Yes, I am still experiencing this issue"
                        no_not_experiencing = "No, the issue has now resolved"
                        
                        still_experiencing = st.button(f"{yes_still_experiencing}", on_click=form_callback)
                        it_is_solved = st.button(f"{no_not_experiencing}", on_click=form_callback)
                        
                        if still_experiencing:
                            message(f"{yes_still_experiencing}", is_user=True, avatar_style="big-smile")
                            
                        if st.session_state.count >= 6:
                            if st.session_state.count == 6:
                                time.sleep(0.5)
                                with st.spinner("Processing..."):
                                    time.sleep(3)
                                    
                            message("I am sorry you are still experiencing this issue. I have logged your complaint into our system. In the meantime, would you like me to order you a new device for overnight delivery?")
                            
                            yes_please = "Yes, please order me a new device for overnight delivery"
                            no_thanks = "No, I do not want a new device sent to me"
                            
                            deliver = st.button(f"{yes_please}", on_click=form_callback)
                            dont_deliver = st.button(f"{no_thanks}", on_click=form_callback)
                        
                            if deliver:
                                message(f"{yes_please}", is_user=True, avatar_style="big-smile")
                                
                            if st.session_state.count >= 7:
                                if st.session_state.count == 7:
                                    time.sleep(0.5)
                                    with st.spinner("Processing..."):
                                        time.sleep(3)
                                
                                message("Ok, I will order you a new device for overnight delivery now. Please check your email inbox for confirmation of your delivery. Thank you for reaching out to us today! How was your experience today?")
                                great = "Great!"
                                good = "Good!"
                                ok = "Neutral"
                                bad = "Bad"
                                terrible = "Terrible"
                                
                                great_button = st.button(f"{great}", on_click=form_callback)
                                good_button = st.button(f"{good}", on_click=form_callback)
                                ok_button = st.button(f"{ok}", on_click=form_callback)
                                bad_button = st.button(f"{bad}", on_click=form_callback)
                                terrible_button = st.button(f"{terrible}", on_click=form_callback)
                            
                                if great_button:
                                    message(f"{great}", is_user=True, avatar_style="big-smile")
                                    
                                if st.session_state.count >= 8:
                                    if st.session_state.count == 8:
                                        time.sleep(2)
                                        message ("Glad to hear it! Please come back and chat any time.")


if __name__ == "__main__":
    main()

If you run the code snippet above, you should be able to reproduce. The issue occurs in the latter half of the flow. I have also added screenshots.

Expected Behavior:

The buttons should show after the message, and once clicked can remain there. However, there shouldnโ€™t be a greyed out button while the page is refreshing. Quite often the greyed out button is not what the user clicked, causing confusion.

Actual behavior:

A phantom reproduction of buttons sometimes randomly appears.

Debug info

  • Streamlit version: 1.14.0
  • Python version: 3.9.13
  • Conda
  • OS version: Windows 10 Enterprise
  • Browser version: Google Chrome

Requirements file

streamlit
time
streamlit_chat

Additional information

Screenshots of issue:



Iโ€™m looking at the phantom buttons, but I have a little suggestion if you want a quick answer to make it work more than getting into the details of why it doesnโ€™t:

You could create a chat container and user-prompt area separately. If you use st.empty() for the user prompts/buttons then they always clear out.

chat_box = st.container()
user_prompt = st.empty()

# Your main code

if # some condition here to determine the next conversation point
    with chat_box:
        # Write out message posted by bot
    with user_prompt:
        with st.container(): # Because empty only wants one thing inside it
            # Button 1
            # Button 2
if # Some condition explaining the response
    with user_prompt:
        user_prompt.empty()
    with chat_box:
        # Write out the message from user indicating response

This would always remove buttons after used, replacing them with just the user message response recorded in the chat_box.

Thank you - I tried this and it worked! Good option if you donโ€™t care about the buttons remaining (which I donโ€™t). Thanks

1 Like

Just to add my 2 cents: I encountered the same problem in my apps. It can happen when often switchting between showing/hiding buttons. I never understood though why it happened. And yes, in the end I found a solution by working around it with st.empty or st.container.
Not sure if this is a bug and should be reported. :thinking:

Do you have a code example of the phantom button in unadorned Streamlit? I looked a bit at @drwampaโ€™s example and thought it would take a bit of digging into the streamlit-chat library to figure out. If there are cases in vanilla Streamlit, that would be easier to look at.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.