---
description: "对话前端的约定：方案选择、自定义 HTML + SSE（推荐）、Chainlit（仅快速原型）"
---

# 对话前端

## 方案选择

根据需求选择前端方案：

| 方案 | 适合场景 | 不适合 |
|------|----------|--------|
| **自定义 HTML + SSE** | 需要定制 UI、混合业务组件、产品级交互 | — |
| **Chainlit** | 纯对话的快速原型，不需要自定义布局 | 需要改布局、加自定义组件 |

**默认选自定义 HTML。** 有 AI 辅助写前端的情况下，HTML + SSE 的开发速度不比 Chainlit 慢，但灵活性远超。
只有用户明确要求用 Chainlit 或只需要最简单的对话 demo 时才用 Chainlit。


## 自定义 HTML + SSE（推荐）

### 后端：FastAPI SSE 端点

```python
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()`：

```javascript
// 流式读取 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 集成

```python
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 应用文件

```python
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 方案
