The faded element is what we call “stale.” Streamlit removes stale elements when it knows they won’t be re-rendered. Streamilt keeps track of elements by the order they appear on the page.
Consider this very simplified example. If one script run renders:
- A title
- A header
- A chart
- A button
And the next script run renders:
- A title
- A chart
- A button
Streamlit will briefly have two charts at step 2 of the rerun (when it replaces the header with the new chart). Then, when the button renders, it replaces the chart from the previous run. Finally when the script run ends, the fourth element (the button from the first run) gets removed because Streamlit knows it’s the end and there are no more elements.
In your case, the spinner is replaced by the chart on the rerun and Streamlit won’t know to remove the stale chart until it “gets to the end.”
You can fix this by using empty. Something like this:
with st.chat_message("agent"), st.empty():
with st.spinner("Getting chart..."):
data = getChart()
st.write(data)
Here’s a toy example to compare the two. If you need the message from the assistant to have more than one Streamlit element, you can insert a container after the empty so that the container gets swapped out instead of the single chart element.
import streamlit as st
import time
import random
if "history" not in st.session_state:
st.session_state.history = []
add = st.button("Add")
def getChart():
time.sleep(5)
return random.choice(range(10))
for message in st.session_state.history:
with st.chat_message(message["role"]):
st.write(message["content"])
if add:
with st.chat_message("assistant"), st.empty():
with st.spinner("Getting chart..."):
data = getChart()
st.write(data)
st.session_state.history.append({"role":"assistant", "content":data})
# if add:
# with st.chat_message("assistant"), st.spinner("Getting chart..."):
# data = getChart()
# st.write(data)
# st.session_state.history.append({"role":"assistant", "content":data})