Disable chat_input while chat bot is responding

x = st.empty()
        messages = st.container(height=300)
        if prompt := x.chat_input("Say something"):
            x.empty()
            x.chat_input("Say something", disabled=True)
            time.sleep(2)
            messages.chat_message("user").write(prompt)
            messages.chat_message("assistant").write(f"Echo: {prompt}")

I’m trying to disable the chat_input when the bot is thinking. I’m trying to use x = st.empty() and x.empty(), but it gives me DuplicateWidgetID error.

Can someone help? Thanks.

Create a variable to set the chat_input()'s disabled parameter to true/false.

# Define a variable to enable/disable chat_input()
if 'is_chat_input_disabled' not in ss:
    ss.is_chat_input_disabled = False

In the prompt, we will use that variable, to reenter the condition.

# If there is user message or chat_input() is disabled, we can enter the following block.
if prompt := st.chat_input("your message", disabled=ss.is_chat_input_disabled) or ss.is_chat_input_disabled:

    # Print and save the user message if and only if the is_chat_input_disabled is false.
    if not ss.is_chat_input_disabled:
        ss.msg.append({"role": "user", "content": prompt})
        with st.chat_message("user"):
            ss.is_chat_input_disabled = True  # disable the chat_input()
            st.markdown(prompt)

            # Rerun so that the chat_input() will be rendered disabled. This is our main goal. But we
            # can still re-enter without user prompt because is_chat_input_disabled is now True.
            st.rerun()   

Complete code

import random
import string
import time

import streamlit as st
from streamlit import session_state as ss


# Define a variable to enable/disable chat_input()
if 'is_chat_input_disabled' not in ss:
    ss.is_chat_input_disabled = False

# Define a variable to save message history.
if 'msg' not in ss:
    ss.msg = []

# Define a variable to store feedback object.
if 'fbdata' not in ss:
    ss.fbdata = {'feedback_thumb': '👉', 'feedback_star': '☆', 'feedback_comment': ''}


def feedback_cb():
    """Processes feedback."""
    ss.fbdata = {'feedback_thumb': ss.fbthuk, 'feedback_star': ss.fbstark, 'feedback_comment': ss.fbcomk}

    # Save to message history
    ss.msg.append({"role": "user", "content": ss.fbdata})


def generate_random_response(msg):
    time.sleep(10)
    letters = string.ascii_lowercase
    return f'my response to {msg} is ' + ''.join(random.choice(letters) for _ in range(6))


# Show message history.
for message in ss.msg:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# If there is user message or chat_input() is disabled, we can enter thie following block.
if prompt := st.chat_input("your message", disabled=ss.is_chat_input_disabled) or ss.is_chat_input_disabled:

    # Print and save the user message if and only if the is_chat_input_disabled is false.
    if not ss.is_chat_input_disabled:
        ss.msg.append({"role": "user", "content": prompt})
        with st.chat_message("user"):
            ss.is_chat_input_disabled = True  # disable the chat_input()
            st.markdown(prompt)

            # Rerun so that the chat_input() will be rendered disabled. This is our main goal. But we
            # can still re-enter without user prompt because is_chat_input_disabled is now True.
            st.rerun()

    # Statements below will be executed when chat_input() is disabled.
    # 1. Get response.
    with st.chat_message("assistant"):
        response = generate_random_response(ss.msg[-1]['content'])
        st.markdown(response)
        ss.is_chat_input_disabled = False  # reset
        ss.msg.append({"role": "assistant", "content": response})

    # 2. Get feedback.
    with st.form('fb_form'):
        st.radio('feedback thumb', options=['👉', '👍', '👎'], key='fbthuk', horizontal=True)
        st.radio('feedback star', options=['☆', '⭐', '⭐⭐', '⭐⭐⭐', '⭐⭐⭐⭐', '⭐⭐⭐⭐⭐'], key='fbstark', horizontal=True)
        st.text_input('feedback comment', key='fbcomk')
        st.form_submit_button('Send', on_click=feedback_cb)

Thanks @ferduy for your response. I’ve used your code as reference to get it to reload. However, I’ve noticed that after several prompts and responses, each rerun call forces the page to go back and forth from top to bottom, which can be a little jarring. Do you have any thoughts on how to handle this problem?

No idea at the moment.

Try to visit Issues · streamlit/streamlit, check if such issue is raised there if not create an issue or feature request.

When the elements change between reruns, Streamlit can lose its place. I suspect using the form outside of the chat message (and having it appear and disappear between reruns), may be causing some jumping. When I have a lot of changing elements on a page, I use containers to make sure the top-level view of the elements remains the same. (i.e. If the appearing and disappearing elements are in a persistent container, they will generally cause fewer issues.)

In this case, you could try to put the form inside the chat message container to possibly create more stability.

Here’s another example just using a feedback widget instead of a full form:

import streamlit as st
import time

if "messages" not in st.session_state:
    st.session_state.messages = []
if "disabled" not in st.session_state:
    st.session_state.disabled = False

def disable():
    st.session_state.disabled = True

def save_rating(i):
    st.session_state.messages[i]["feedback"] = st.session_state[f"feedback_{i}"]

for i, message in enumerate(st.session_state.messages):
    with st.chat_message(message["role"]):
        st.write(message["content"])
        if message["role"] == "ai":
            if "feedback" in message:
                st.markdown(":star:"*(message["feedback"]+1))
            else:
                st.feedback("stars", key=f"feedback_{i}", on_change=save_rating, args=[i])

if prompt := st.chat_input("Type one word for demo", disabled=st.session_state.disabled, on_submit=disable):
    with st.chat_message("user"):
        st.write(prompt)
    st.session_state.messages.append(
        {
            "role": "user",
            "content": prompt
        }
    )
    with st.chat_message("ai"):
        time.sleep(2)
        st.write(prompt)
    st.session_state.messages.append(
        {
            "role": "ai",
            "content": prompt
        }
    )
    st.session_state.disabled = False
    st.rerun()
1 Like

Thanks for sharing! I gave it a try and with the disable variable and rerun(), it is working fine!