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.