Streamlit AI 对话界面实现:CSS :has() 选择器与 Flex 布局反转
本文介绍基于 Streamlit 的 WebUI 实现方案,重点揭秘如何实现'左右气泡自动识别'这一视觉效果。
1. 极简二次元风格的对话界面
传统的 Streamlit 应用,布局往往受限于其原生组件,侧边栏、主区域泾渭分明,样式调整空间有限。而我们的目标,是创造一个沉浸式的、专注于对话本身的界面。
界面核心视觉特征:
- 极简背景:采用了浅灰蓝色系,搭配极简的圆点矩阵网格作为背景,营造出宁静、专注又不失现代感的氛围。
- 对话气泡:这是整个 UI 的灵魂。用户发送的消息以天蓝色气泡的形式,优雅地居右对齐;AI 的回复则以纯白色气泡,清晰地居左显示。每个气泡都带有圆角和微妙的阴影,质感十足。
- 智能折叠:对于具备深度思考(Chain-of-Thought)能力的模型,其推理过程(通常包裹在 `` 标签内)会被自动识别并收纳进一个可折叠的面板中。这样,主对话流保持清爽,而感兴趣的读者可以随时展开查看 AI 的'思考过程'。
- 流式输出:回答不是一次性弹出,而是模拟打字机效果,逐字实时流出。特制的 CSS 确保了在文字流出的过程中,气泡框体平滑扩展,绝不会出现令人不适的闪烁或布局跳动。
这一切效果的实现,都基于一个核心挑战:在 Streamlit 相对封闭的渲染体系内,如何根据消息的发送者(用户或 AI),动态地、准确地将气泡排列到正确的一侧?
2. 核心技术揭秘:CSS :has() 选择器与 Flex 布局反转
Streamlit 将我们写入 st.chat_message 的内容,最终渲染为 HTML。但原生的 Streamlit 并没有提供直接根据消息类型(user 或 assistant)来反转整个消息块布局的简单方法。我们无法直接告诉一个容器:'如果里面包含用户消息,就把自己整个调转方向。'
我们的解决方案,巧妙地利用了现代 CSS 中一个非常强大的选择器——:has()。
2.1 传统思路的局限
通常,我们可能会尝试在 Python 端判断消息类型,然后为不同的消息容器注入不同的 CSS 类,再分别设置样式。但这在 Streamlit 中操作起来比较繁琐,且难以实现父容器基于子元素状态的动态样式调整。
2.2 我们的'CSS 魔法'方案
思路的核心是:在子元素里埋下一个'标记',然后让父容器通过 CSS 检测到这个标记,并改变自身的布局方式。
第一步:在 Python 中注入隐形标记 我们在渲染每条消息的 HTML 内容时,偷偷加入一个看不见的 <span> 标签作为标记。例如,对于用户消息,我们注入一个带有特定类(如 user-mark)的空标签。
# 在 Streamlit 的 chat_message 容器内,通过 st.markdown 注入 HTML
with st.chat_message("user"):
st.write(user_input)
# 关键:注入一个看不见的标记元素
st.markdown('<span class="user-mark"></span>', unsafe_allow_html=True)
对于 AI 的消息,我们可以选择不注入标记,或者注入一个不同的标记(如 ai-mark)。
第二步:用 CSS :has() 选择器捕获并反转布局 接下来,在全局 CSS 中,我们编写如下规则:
/* 找到所有包含 .user-mark 子元素的聊天消息容器 */
div[data-testid=](> > ) {
: row-reverse;
: flex-end;
}

