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
相关产品推荐
相关产品推荐

