对话前端
方案选择
根据需求选择前端方案:
| 方案 | 适合场景 | 不适合 |
|---|---|---|
| 自定义 HTML + SSE | 需要定制 UI、混合业务组件、产品级交互 | — |
| Chainlit | 纯对话的快速原型,不需要自定义布局 | 需要改布局、加自定义组件 |
默认选自定义 HTML。 有 AI 辅助写前端的情况下,HTML + SSE 的开发速度不比 Chainlit 慢,但灵活性远超。 只有用户明确要求用 Chainlit 或只需要最简单的对话 demo 时才用 Chainlit。
自定义 HTML + SSE(推荐)
后端:FastAPI SSE 端点
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
app = FastAPI()
# 挂载静态文件(HTML/CSS/JS)
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/", response_class=HTMLResponse)
async def index():
with open("static/index.html") as f:
return f.read()
@app.post("/chat")
async def chat(request: Request):
body = await request.json()
message = body["message"]
async def generate():
# 替换成你的 LLM 调用(LangGraph / OpenAI / Anthropic 等)
async for chunk in your_llm_stream(message):
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
前端:最小 SSE 聊天页面
让 AI 直接写 HTML + CSS + JS 即可,核心就是一个 fetch + EventSource / getReader():
// 流式读取 SSE 响应
const response = await fetch("/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: userInput })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// 解析 SSE data: 行,追加到聊天气泡里
appendToChat(text);
}
自定义 HTML 方案的优势
- 布局完全自由(侧边栏、多面板、数据看板、嵌入图表等)
- AI 写 HTML/CSS/JS 极其成熟,几乎不出错
- 可以直接用 Tailwind CDN、Alpine.js 等轻量工具
- WebSocket / SSE 任选,通信方式不受框架限制
Chainlit(仅快速原型)
仅当用户明确要求或只需最简对话 demo 时使用。
与 FastAPI 集成
from fastapi import FastAPI
from chainlit.utils import mount_chainlit
app = FastAPI()
@app.get("/api/health")
def health():
return {"status": "ok"}
mount_chainlit(app=app, target="my_cl_app.py", path="/chainlit")
Chainlit 应用文件
import chainlit as cl
@cl.on_chat_start
async def on_chat_start():
graph = ... # 编译好的 LangGraph
cl.user_session.set("graph", graph)
@cl.on_message
async def on_message(message: cl.Message):
graph = cl.user_session.get("graph")
msg = cl.Message(content="")
async for chunk in graph.astream(
{"messages": [{"role": "user", "content": message.content}]},
stream_mode="messages"
):
await msg.stream_token(chunk.content)
await msg.send()
Chainlit 注意事项
- 有自己的配置文件
.chainlit/config.toml - 开发时用
chainlit run app.py -w启用热重载 - 高度自定义 UI 布局时会很痛苦,这时应该切换到自定义 HTML 方案