You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Streamlit聊天应用Linux下LLM流式响应滚动条卡顿跳顶求助

Streamlit流式LLM响应导致Linux下聊天界面滚动卡顿/跳顶问题解决建议

我开发了一款类似ChatGPT的Streamlit聊天应用,实现LLM响应的流式输出时,在Linux系统中会出现聊天界面滚动条卡顿、偶尔跳转到顶部的问题,Windows系统则无此现象。以下是核心代码及已尝试的无效方案,求解决建议。

核心代码

获取LLM响应的函数

def general_chat(user_input):
    response_placeholder = st.empty()
    streamed_response = ""
    try:
        blocked_topics = "\n".join([f"Do not answer any questions about: {keyword}" for keyword in key_data])
        history_context = "\n".join(f"{chat['role']}: {chat['message']}" for chat in st.session_state.chat_history)
        prompt_input = (
            f"{history_context}\n"
            f"Instructions:\n"
            f"{blocked_topics}\n"  # Explicitly define what is off-limits
            f"User: {user_input}\n"
            f"Assistant:"
        )
        for chunk in llm.stream(prompt_input):  # Adjust to your LLM's streaming API
            streamed_response += chunk
            response_placeholder.write(streamed_response)
            time.sleep(0.1)
        st.session_state.chat_history.append({"role": "assistant", "message": streamed_response.strip()})
    except Exception as e:
        st.session_state.chat_history.append({"role": "assistant", "message": "Server busy. Please try again later."})
        logger.info(str(e))

渲染聊天页面的函数

def chat_page():
    """Render the chat page."""
    st.markdown(
        """
<style>
            .st-emotion-cache-1c7y2kd {
                flex-direction: row-reverse;
                text-align: right;
            }
</style>
        """,
        unsafe_allow_html=True,
    )
    # hide_streamlit_toolbar()
    col1, col2 = st.columns([6, 2])
    with col1:
        st.subheader('ThinkBot')
        st.write('Welcome to ThinkBot! I am an AI Chatbot. Choose "General" for general inquiries or '
                 'select a specific project for project-related questions.')

    # Initialize chat history in session state if not present
    if 'chat_history' not in st.session_state:
        st.session_state.chat_history = []
        st.session_state.chat_history.append({"role": "assistant", "message": "How can I help you?"})
    # Display chat history with alignment
    bot_image = image_to_base64("images/chat_profile-bot.png")
    me_image = image_to_base64("images/chat_profile-me.png")
    for idx, chat in enumerate(st.session_state.chat_history):
        message_key = f"{chat['role']}_{idx}"  # Unique key based on role and index
        if chat["role"] == "user":
            with st.chat_message("user", avatar="images/chat_profile-me.png"):
                st.write(chat['message'])
        else:
            with st.chat_message("assistant", avatar="images/chat_profile-bot.png"):
                st.write(chat['message'])
    # Collect user input
    if user_input := st.chat_input(placeholder="Your Message"):
        st.session_state.chat_history.append({"role": "user", "message": user_input})
        st.session_state.process_input = True
        st.rerun()
    # Check if we need to process the input
    if st.session_state.get("process_input"):
        with st.spinner('Processing...'):
            general_chat(st.session_state.chat_history[-1]["message"])
            st.session_state.process_input = False
            st.rerun()

已尝试的无效方法

  • 使用streamlit_chat组件
  • 添加CSS禁用自动滚动:
.stAppScrollToBottomContainer {
overflow-anchor: none !important;
}

解决方案建议

1. 优化流式更新逻辑,减少重渲染开销

当前每次调用response_placeholder.write(streamed_response)都会完全替换内容,在Linux下可能触发浏览器异常重排重绘。可以改用st.markdown并移除不必要的延迟:

# 替换general_chat中的循环部分
for chunk in llm.stream(prompt_input):
    streamed_response += chunk
    # 使用markdown替代write,降低渲染开销
    response_placeholder.markdown(streamed_response)
    # 移除time.sleep或调小间隔,减少不必要的等待
    # time.sleep(0.1)

2. 用JS强制控制滚动行为,锁定到底部

在chat_page函数渲染完所有聊天消息后,添加JS代码监听DOM变化,确保滚动条始终停在最新消息位置:

# 放在chat_page函数末尾,渲染完聊天历史之后
st.markdown(
    """
<script>
const observer = new MutationObserver(() => {
    const chatContainer = document.querySelector('.stChatFloatingInputContainer').previousElementSibling;
    if (chatContainer) {
        chatContainer.scrollTop = chatContainer.scrollHeight;
    }
});
observer.observe(document.body, { childList: true, subtree: true });
</script>
""",
    unsafe_allow_html=True
)

3. 改用SessionState驱动流式更新,避免独立占位符冲突

调整逻辑,直接在聊天历史中动态更新流式消息,利用Streamlit原生状态更新机制减少DOM干扰:

  • 修改输入处理逻辑,提前添加空助手消息占位符:
if user_input := st.chat_input(placeholder="Your Message"):
    st.session_state.chat_history.append({"role": "user", "message": user_input})
    # 添加空的助手消息占位符用于流式输出
    st.session_state.chat_history.append({"role": "assistant", "message": ""})
    st.session_state.process_input = True
    st.rerun()
  • 更新general_chat函数,直接修改SessionState中的消息并触发重渲染:
def general_chat(user_input):
    streamed_response = ""
    try:
        blocked_topics = "\n".join([f"Do not answer any questions about: {keyword}" for keyword in key_data])
        # 排除最后一个空占位符生成上下文
        history_context = "\n".join(f"{chat['role']}: {chat['message']}" for chat in st.session_state.chat_history[:-1])
        prompt_input = (
            f"{history_context}\n"
            f"Instructions:\n"
            f"{blocked_topics}\n"
            f"User: {user_input}\n"
            f"Assistant:"
        )
        for chunk in llm.stream(prompt_input):
            streamed_response += chunk
            # 直接更新SessionState中的消息
            st.session_state.chat_history[-1]["message"] = streamed_response.strip()
            st.rerun()  # 触发页面局部更新
            # time.sleep(0.05)  # 可选:调整间隔降低重绘频率
    except Exception as e:
        st.session_state.chat_history[-1]["message"] = "Server busy. Please try again later."
        logger.info(str(e))

4. 排查Linux浏览器环境差异

  • 更新Chrome/Firefox到最新版本,避免旧渲染引擎的兼容性问题
  • 启动Streamlit时添加参数:streamlit run app.py --server.enableCORS false --server.enableXsrfProtection false,排除跨域相关的渲染干扰
  • 尝试不同浏览器测试,确认是否为特定浏览器的问题

内容的提问来源于stack exchange,提问作者themadhatter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:24:53