Impossible to disable st.chat_input while writing the model's response
Checklist
- [X] I have searched the existing issues for similar issues.
- [X] I added a very descriptive title to this issue.
- [X] I have provided sufficient information below to help reproduce this issue.
Summary
When a user enters a prompt in st.chat_input, it is not possible to disable the input while an LLM is generating the response to that input.
This leads to the possibility of the user 'interrupting' the model output and messing up the conversation structure.
I've tried binding the disable argument to a variable inside the st.session_state, but it does not work.
This happens in the official 'chat-GPT like' implementation on streamlit forums.
Reproducible Code Example
import streamlit as st
import time
st.title("ChatGPT-like clone")
def stream_example_response():
response = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
# yield 5 characters every 0.1 seconds
for i in range(0, len(response), 5):
time.sleep(0.1)
yield response[i:i+5]
if "messages" not in st.session_state:
st.session_state.messages = []
if "disable_chat_input" not in st.session_state:
st.session_state.disable_chat_input = False
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("What is up?", disabled=st.session_state.disable_chat_input):
st.session_state['disable_chat_input'] = True
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
response = st.write_stream(stream_example_response())
st.session_state.messages.append({"role": "assistant", "content": response})
st.session_state['disable_chat_input'] = False
Steps To Reproduce
- Run the minimal example provided (
streamlit run minimal.py) - Send a prompt
- While the response is streaming, send another prompt
- The response gets interrupted, doesn't get registered on the history and the messages become messed up.
Expected Behavior
With the disabled=st.session_state.disable_chat_input I was expecting that the user input would be disabled until the response stops streaming.
Perhaps would be useful to have an argument in st.chat_input or something so that it automatically disables until all the code inside the if prompt:= ... statement ends, whatever works best.
Current Behavior
No response
Is this a regression?
- [ ] Yes, this used to work in a previous version.
Debug info
- Streamlit version: 1.31.1
- Python version: 3.10.13
- Operating System: Windows
- Browser: Firefox
Additional Information
Video of the current (wrong) behavior:
To help Streamlit prioritize this feature, react with a 👍 (thumbs up emoji) to the initial post.
Your vote helps us identify which enhancements matter most to our users.
@Napuh Thanks for reporting this issue. This is currently expected, but we definitely could improve this behaviour.
So in the current state of streamlit and st.chat_input, there is no way to disable the input while the response is generating?
How could this be implemented? In the case of an hipothetical PR
@Napuh There might be a workaround by using st.rerun, but that could get a bit tricky. You would need to store the current text that's returned with chat_input in the session state, triggered a rerun, and check if this is in session state, set the chat_input to disabled and react to the text.
We could solve it in Streamlit by adding some functionality that auto-disables chat input after the users submits a text for the remaining session. But that would require some discussions with the product team.
This would be a desired functionality to be able to to disable chat input during model's processing! :)
This would also be a desirable functionality to disable chat while prompting for streamlit-feedback, making feedback mandatory after each request.
Right now saving chat state and performing st.rerun() makes the feedback box disappear.
Following is my workaround; it works well for my simple chatbot:
- use session state to control the input state, and get the input value
- move everything into the on_submit callback, DO NOT use st.chat_message("user").write(...) in it, or will write dup items.
if "disabled" not in st.session_state:
st.session_state.disabled = False
if "messages" not in st.session_state or 0 == len(st.session_state.messages):
st.session_state.messages = [{"role": "assistant", "content": "Hi, what can I do for you?"}]
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
def on_submit():
st.session_state.disabled = True
st.session_state.messages.append({"role": "user", "content": st.session_state.prompt})
# st.chat_message("user").write(st.session_state.prompt)
response = llm.chat(st.session_state.prompt)
st.session_state.messages.append({"role": "assistant", "content": response})
# st.chat_message("assistant").write(response)
st.session_state.disabled = False
if prompt := st.chat_input("You question here", on_submit=on_submit, disabled=st.session_state.disabled, key="prompt"):
pass
@Napuh There might be a workaround by using
st.rerun, but that could get a bit tricky. You would need to store the current text that's returned withchat_inputin the session state, triggered a rerun, and check if this is in session state, set the chat_input to disabled and react to the text.We could solve it in Streamlit by adding some functionality that auto-disables chat input after the users submits a text for the remaining session. But that would require some discussions with the product team.
Hi @LukasMasuch , any update on this ?
EDIT: added css selector for streamlit 1.39.0
This was was a 'must be fixed'. Callback was not working for me. I ended up using following css hack:
import time
import streamlit as st
# .stApp[data-teststate=running] is present when bot answer is streaming
st.markdown("""
<style>
.stApp[data-teststate=running] .stChatInput textarea,
.stApp[data-test-script-state=running] .stChatInput textarea {
display: none;
}
</style>
""", unsafe_allow_html=True)
def stream_text(text):
for char in text:
time.sleep(0.1)
yield char
if prompt := st.chat_input("Say something. (This is disabled while assistant is streaming)"):
with st.chat_message("user"):
st.write(prompt)
with st.chat_message("assistant"):
st.write_stream(stream_text(f"user typed: {prompt}"))
Unfortunately, focus is lost from chat input field because of this hack. Works at least with Streamlit 1.37.1 and 1.39.0, but may break after any update...
Any updates on this? I think this would be a much-needed feature for almost all chatbots
@chaosddp Thanks this look like an elegant way. Did you use this more? Any issues that might arise down the line? Also could you get this working with text streaming? An important part is to not do any rendering in the callback but then you can't stream the response
@chaosddp Thanks this look like an elegant way. Did you use this more? Any issues that might arise down the line? Also could you get this working with text streaming? An important part is to not do any rendering in the callback but then you can't stream the response
No, I changed to another framework (chainlit) for my frontend, it is much easy to use for me. :)
For future readers, following this worked for me - https://discuss.streamlit.io/t/disable-st-input-chat-during-conversation/50258/2?u=aflah1
For future readers, here is my approach (it works for me on streamlit version 1.39.0):
import time
import streamlit as st
# beginning of your code here
def _handle_user_interaction():
st.session_state["waiting_for_answer"] = True
if "waiting_for_answer" not in st.session_state:
st.session_state["waiting_for_answer"] = False
# some more code here
if user_prompt := st.chat_input(
"Ask a question",
on_submit=_handle_user_interaction,
disabled=st.session_state["waiting_for_answer"]
):
with st.chat_message("user"):
st.write(user_prompt)
with st.chat_message("assistant"):
with st.spinner():
time.sleep(5)
# stream the answer here
if st.session_state["waiting_for_answer"]:
st.session_state["waiting_for_answer"] = False
st.rerun()
The callback is needed for disabling the st.chat_input component and any other component you'd like to disable when the user submits a prompt, through setting the waiting_for_answer flag. Then, after printing the response, the flag is set to False again. Unfortunately, streamlit needs another user interaction to re-render the app with the flag disabled, so a st.rerun is needed
The problem with this last solution is that apparently , the chat text box loses focus every time...