这篇博客主要记录一下我在项目第一阶段做的工作:如何用 Streamlit 搭建一个标准的工程骨架,以及如何解决聊天应用中常见的“交互延迟”问题。

1.选择 Streamlit的原因

Streamlit 允许用纯 Python 快速构建界面,且内置了丰富的组件,非常适合大数据租房推荐系统这种数据驱动的系统。

2.项目的结构

刚开始写代码时,我习惯把所有逻辑都堆在 app.py 里。但随着功能增加,文件很快变得难以阅读。为了解决这个问题,我参考了一些工程化的实践,把项目拆分成了几个模块:frontend/app.py:这是应用的入口,只负责页面的布局和组件的调用,不写复杂的业务逻辑。


//AI 选房助手 — Streamlit 入口

from __future__ import annotations

import time

import streamlit as st

from app_lib.config import (
    LAYOUT,
    LOADING_HINT,
    MAX_CHAT_HISTORY,
    PAGE_ICON,
    PAGE_TITLE,
)
from app_lib.state import append_message, init_session_state

st.set_page_config(
    page_title=PAGE_TITLE,
    page_icon=PAGE_ICON,
    layout=LAYOUT,
    initial_sidebar_state="expanded",
)

init_session_state()


def _render_sidebar() -> None:
    st.sidebar.header("会话")
    st.sidebar.caption(f"历史保留上限:{MAX_CHAT_HISTORY} 条(当前 {len(st.session_state.messages)} 条)")
    if st.sidebar.button("清空对话", use_container_width=True):
        st.session_state.messages = []
        st.session_state.pop("pending_reply", None)
        st.rerun()
    st.sidebar.divider()
    st.sidebar.markdown(
       
    )


def _render_chat() -> None:
    st.title(f"{PAGE_ICON} {PAGE_TITLE}")

    if prompt := st.chat_input("描述区域、预算、户型等找房需求…"):
        append_message("user", prompt)
        st.session_state.pending_reply = True
        st.rerun()

    for msg in st.session_state.messages:
        with st.chat_message(msg["role"]):
            st.markdown(msg["content"])

    if st.session_state.get("pending_reply"):
        with st.spinner(LOADING_HINT):
            time.sleep(0.15)
            reply = (
                "已记录您的需求(前端骨架占位回复)。\n\n"
                "后续部分将接入:流式输出、房源卡片、图表与地图。"
            )
        append_message("assistant", reply)
        st.session_state.pending_reply = False
        st.rerun()


_render_sidebar()
_render_chat()

app_lib/config.py:用来存放所有的配置项,比如页面标题、最大聊天历史记录数



PAGE_TITLE = "AI 选房助手"
PAGE_ICON = "🏠"
LAYOUT = "wide"

//对话历史上限
MAX_CHAT_HISTORY = 50
//单屏房源卡片数量
MAX_LISTING_CARDS_VISIBLE = 10
// 加载提示
LOADING_HINT = "正在响应,请稍候…"

(MAX_CHAT_HISTORY)等。这样做的好处是,如果以后要修改配置,只需要改这一个文件。app_lib/state.py:专门用来管理 st.session_state。聊天应用的核心就是状态管理,把这部分逻辑独立出来,可以让主程序更清晰。


通过这种分层,代码的可维护性提高了不少,也为后续接入真实的 AI 接口留好了接口。

3.核心难点:Streamlit 的执行模型与“双阶段刷新”

在实现聊天功能时,我遇到了一个很典型的问题:当用户点击“发送”后,页面需要重新运行。如果此时直接调用后端接口(假设耗时 2 秒),用户界面就会卡住,直到 AI 回复完成,体验非常差。

理想的情况是:用户点击发送 -> 消息立即显示 -> 出现“AI 正在思考”的 loading 动画 -> AI 回复显示。

为了实现这个效果,我采用了“双阶段 Rerun”的策略:

第一阶段:用户输入内容。程序捕获输入,将用户消息写入 st.session_state,并设置一个 pending_reply=True 的标记,然后立即调用 st.rerun()

第二阶段:页面重跑。程序检测到 pending_reply 为真,先渲染出用户刚才发的消息,然后进入 with st.spinner() 代码块,显示加载动画。在这个代码块里模拟调用后端(目前是延时),拿到结果后写入会话状态,清除标记,再次重跑。

    if prompt := st.chat_input("描述区域、预算、户型等找房需求…"):
        append_message("user", prompt)
        st.session_state.pending_reply = True
        st.rerun()

    for msg in st.session_state.messages:
        with st.chat_message(msg["role"]):
            st.markdown(msg["content"])

    if st.session_state.get("pending_reply"):
        with st.spinner(LOADING_HINT):
            time.sleep(0.15)
            reply = (
                "已记录您的需求(前端骨架占位回复)。\n\n"
                "后续部分将接入:流式输出、房源卡片、图表与地图。"
            )
        append_message("assistant", reply)
        st.session_state.pending_reply = False
        st.rerun()

4.总结与展望

目前,项目的第一阶段已经完成。我搭建了一个结构清晰、交互流畅的聊天骨架。虽然现在的 AI 还是个“假”的(只是简单的回显,给出的一般式回复),但地基已经打好了。我接下来将进行优化对话体验,避免长时间卡断

更多推荐