对话式 AI 界面设计

Chat UI 的工程美学:从流式响应到多轮上下文的全链路设计


对话式 AI 界面的特殊性

对话式 AI 界面看起来只是"聊天窗口",但它承载的交互复杂度远超传统即时通讯。核心差异在于:AI 的响应是生成式的——长度不可预测、耗时不确定、质量有波动、且需要用户在对话过程中不断校准意图。

本文从 8 个维度拆解对话式 AI 界面的设计与工程实现。


一、Chat UI 基础架构

1.1 消息类型系统

消息类型 来源 特征 渲染方式
用户文本 用户输入 短文本为主 右对齐气泡
AI 文本 模型生成 长文本 + Markdown 左对齐,支持 Markdown
AI 结构化 模型 + 后处理 表格/图表/卡片 自定义组件
系统消息 系统 状态/提示 居中,弱化样式
工具调用 Agent 框架 函数调用 + 结果 折叠面板
文件附件 用户上传 文档/图片/音频 预览卡片
引用块 AI 引用来源 带来源标注 引用样式

1.2 消息数据模型

interface ChatMessage {
  id: string;
  role: 'user' | 'assistant' | 'system' | 'tool';
  content: string;
  timestamp: number;

  // AI-specific fields
  model?: string;
  tokens?: { prompt: number; completion: number };
  latency_ms?: number;
  confidence?: number;

  // Rich content
  attachments?: Attachment[];
  citations?: Citation[];
  toolCalls?: ToolCall[];

  // Interaction state
  feedback?: 'positive' | 'negative' | null;
  isStreaming?: boolean;
  isEdited?: boolean;
  regenerationCount?: number;
}

interface Citation {
  index: number;
  title: string;
  url?: string;
  snippet: string;
  relevanceScore: number;
}

二、流式响应(Streaming Response)

2.1 为什么必须流式

方案 首字节延迟 用户感知 实现复杂度
一次性返回 5-30s "卡死了"
流式输出 200-500ms "在思考"
流式 + 骨架 < 200ms "即时响应" 中高

2.2 SSE 流式实现

// Backend: SSE streaming endpoint
async function handleChat(req: Request, res: Response) {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const stream = await model.chat.completions.create({
    model: 'gpt-4',
    messages: req.body.messages,
    stream: true,
  });

  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content;
    if (content) {
      res.write(`data: ${JSON.stringify({ type: 'token', content })}\n\n`);
    }
  }

  res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`);
  res.end();
}

// Frontend: Consuming SSE stream
function useStreamingChat() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [isStreaming, setIsStreaming] = useState(false);

  const sendMessage = async (content: string) => {
    setIsStreaming(true);
    const assistantMsg: ChatMessage = {
      id: crypto.randomUUID(),
      role: 'assistant',
      content: '',
      timestamp: Date.now(),
      isStreaming: true,
    };
    setMessages(prev => [...prev, assistantMsg]);

    const response = await fetch('/api/chat', {
      method: 'POST',
      body: JSON.stringify({ messages: [...messages, { role: 'user', content }] }),
    });

    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);
      const lines = text.split('\n').filter(l => l.startsWith('data: '));

      for (const line of lines) {
        const data = JSON.parse(line.slice(6));
        if (data.type === 'token') {
          setMessages(prev => {
            const updated = [...prev];
            const last = updated[updated.length - 1];
            last.content += data.content;
            return updated;
          });
        }
      }
    }

    setIsStreaming(false);
  };

  return { messages, sendMessage, isStreaming };
}

2.3 流式渲染优化

性能关键路径:
  Token 到达 -> 状态更新 -> DOM 重渲染 -> 滚动跟随

优化策略:
  1. 批量更新: 累积 50ms 内的 tokens 一次性渲染(不是每个 token 都触发 re-render)
  2. 虚拟滚动: 消息列表超过 100 条时启用虚拟化
  3. Markdown 延迟解析: 流式期间用纯文本,结束后再解析 Markdown
  4. 代码高亮延迟: 代码块在流式结束后才做语法高亮

三、提示词建议(Suggested Prompts)

3.1 三层提示词体系

Layer 1: 静态预设
  产品预定义的通用提示词
  例: "帮我分析这份报表" / "解释这段代码"

Layer 2: 上下文相关
  基于当前对话/页面/用户状态动态生成
  例: (用户刚上传发票) "检查这张发票的合规性"

Layer 3: 个性化推荐
  基于用户历史行为推荐
  例: (用户经常问税率) "查询最新增值税率表"

3.2 提示词卡片设计

┌──────────────────────────────────────────────────┐
│                                                    │
│  试试这些问题:                                     │
│                                                    │
│  ┌─────────────────┐  ┌─────────────────┐          │
│  │ 分析发票合规性   │  │ 计算增值税额     │          │
│  │ 上传发票即可开始 │  │ 输入金额和税率   │          │
│  └─────────────────┘  └─────────────────┘          │
│                                                    │
│  ┌─────────────────┐  ┌─────────────────┐          │
│  │ 生成月度报表     │  │ 查询税收政策     │          │
│  │ 选择时间范围     │  │ 按行业/地区搜索  │          │
│  └─────────────────┘  └─────────────────┘          │
│                                                    │
└──────────────────────────────────────────────────┘

3.3 跟随式建议

在 AI 回答结束后,自动生成 2-3 个"追问建议":

AI: 这张发票的税率应为 13%,属于增值税一般纳税人开具...

────────────────────────────
你可能还想了解:
  [为什么不是 9%?]  [查看完整税目表]  [导出合规报告]

四、对话分支(Conversation Branching)

4.1 分支场景

用户在对话中间编辑了一条消息,或者点击"重新生成",就会产生对话分支。

          Message 1 (User)
              │
          Message 2 (AI)
              │
          Message 3 (User)
         ┌────┴────┐
    Message 4a   Message 4b (edited)
    (AI, v1)     (AI, v2)
         │            │
    Message 5a   Message 5b
    (User)       (User)

4.2 分支策略对比

策略 描述 优点 缺点
覆盖(Overwrite) 编辑后丢弃旧分支 简单,节省存储 无法回溯
分叉(Fork) 保留所有分支,可切换 完整历史 UI 复杂
版本(Version) 同一位置显示多个版本 易于对比 存储成本

推荐策略:版本模式 —— 在同一位置显示版本切换器。

┌──────────────────────────────────────┐
│  AI Response                  [v2/3] │
│                           [< >]      │
│  根据最新税法规定,该交易应...         │
│                                      │
│  Version 1 | Version 2 | Version 3   │
└──────────────────────────────────────┘

五、多轮上下文管理

5.1 上下文窗口策略

策略 描述 Token 成本 质量
全量发送 发送完整对话历史 极高 最好
滑动窗口 只发最近 N 轮 可控 良好
摘要压缩 旧消息压缩为摘要 中等 良好
混合策略 摘要 + 最近 N 轮 + 关键消息 最优 最优

5.2 混合上下文构建

def build_context(messages: list[dict], max_tokens: int = 8000) -> list[dict]:
    """Build optimized context for multi-turn conversation."""
    system_msg = messages[0]  # System prompt (always included)
    recent = messages[-6:]     # Last 3 turns (always included)

    remaining_budget = max_tokens - count_tokens(system_msg) - count_tokens(recent)

    # Summarize older messages
    older = messages[1:-6]
    if older and remaining_budget > 500:
        summary = summarize_conversation(older, max_tokens=remaining_budget)
        return [system_msg, {"role": "system", "content": f"Previous context summary: {summary}"}, *recent]

    return [system_msg, *recent]

def summarize_conversation(messages: list[dict], max_tokens: int) -> str:
    """Compress conversation history into a summary."""
    # Use a fast model for summarization
    response = fast_model.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Summarize the key points, decisions, and context from this conversation. Be concise."},
            {"role": "user", "content": format_messages(messages)}
        ],
        max_tokens=max_tokens
    )
    return response.choices[0].message.content

5.3 上下文指示器

让用户知道 AI "记住了"多少:

┌──────────────────────────────────────────┐
│  Context: 12 messages | 3,240 tokens     │
│  Memory: [Full ████████░░ 78%]           │
│  Older messages summarized               │
│  [View full history] [Clear context]     │
└──────────────────────────────────────────┘

六、移动端优化

6.1 移动端核心挑战

挑战 桌面端 移动端 解决方案
输入区域 宽敞 键盘占半屏 自适应高度 + 快捷输入
长文本阅读 适中 困难 折叠 + 摘要优先
多模态操作 鼠标精确 手指粗略 大按钮 + 手势
网络状况 稳定 不稳定 离线缓存 + 断点续传

6.2 移动端布局规范

Mobile Chat Layout (375px width):

┌─────────────────────────┐
│  Header (48px)           │
│  [Back] Title [Menu]     │
├─────────────────────────┤
│                          │
│  Message Area            │
│  (flex-grow: 1)          │
│                          │
│  - Max bubble width: 85% │
│  - Font: 15px            │
│  - Line height: 1.6      │
│  - Padding: 12px 16px    │
│                          │
├─────────────────────────┤
│  Suggestion Chips (opt.) │
│  [Chip 1] [Chip 2]      │
├─────────────────────────┤
│  Input Bar (min 48px)    │
│  [+] [Input...] [Send]  │
│  Attachment | Voice      │
└─────────────────────────┘

6.3 输入优化

function MobileChatInput() {
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const [value, setValue] = useState('');

  // Auto-resize textarea
  useEffect(() => {
    const el = textareaRef.current;
    if (!el) return;
    el.style.height = 'auto';
    el.style.height = Math.min(el.scrollHeight, 120) + 'px';
  }, [value]);

  return (
    <div className="fixed bottom-0 left-0 right-0 bg-white border-t safe-area-bottom">
      {/* Quick actions */}
      <div className="flex gap-2 px-3 py-1 overflow-x-auto">
        <button className="shrink-0 px-3 py-1 text-sm bg-gray-100 rounded-full">
          Upload
        </button>
        <button className="shrink-0 px-3 py-1 text-sm bg-gray-100 rounded-full">
          Voice
        </button>
      </div>

      {/* Input row */}
      <div className="flex items-end gap-2 px-3 py-2">
        <textarea
          ref={textareaRef}
          value={value}
          onChange={e => setValue(e.target.value)}
          placeholder="Ask anything..."
          rows={1}
          className="flex-1 resize-none rounded-2xl border px-4 py-2 text-[15px]"
          style={{ maxHeight: 120 }}
        />
        <button
          disabled={!value.trim()}
          className="shrink-0 w-10 h-10 rounded-full bg-blue-500 text-white"
        >
          Send
        </button>
      </div>
    </div>
  );
}

七、高级交互模式

7.1 内联编辑

用户可以直接在 AI 回复中编辑特定段落,然后让 AI 基于编辑重新生成后续内容。

7.2 多 Agent 对话

┌──────────────────────────────────────────┐
│  Research Agent:                          │
│  "我找到了 3 篇相关法规..."               │
│                                          │
│  Analysis Agent:                          │
│  "基于以上法规,该交易的合规风险是..."      │
│                                          │
│  Summary Agent:                           │
│  "综合分析结论:..."                       │
│                                          │
│  [展开各 Agent 详情]                      │
└──────────────────────────────────────────┘

7.3 交互模式对比

模式 适用场景 用户控制度 实现复杂度
自由对话 开放式探索
引导式对话 结构化任务
命令式 专家用户 最高
混合模式 通用 灵活

八、性能与可访问性

8.1 性能基线

指标 目标值 测量方式
首条消息渲染 < 200ms Performance API
流式首字节 < 500ms SSE timestamp
消息列表滚动 60fps Chrome DevTools
100 条消息内存 < 50MB Memory profiler
输入响应 < 16ms Input latency

8.2 可访问性清单

Accessibility Checklist:
  [ ] 所有消息有 aria-label(包含角色 + 时间)
  [ ] 流式输出有 aria-live="polite" 区域
  [ ] 键盘导航: Tab 切换消息,Enter 展开详情
  [ ] 屏幕阅读器: 新消息到达时播报
  [ ] 高对比度模式: 用户/AI 消息可区分
  [ ] 字体缩放: 支持到 200% 不破版
  [ ] 减少动画: prefers-reduced-motion 适配

设计检查清单

Chat UI Launch Checklist:
  [ ] 流式输出已实现且首字节 < 500ms
  [ ] 消息支持 Markdown 渲染(表格/代码/链接)
  [ ] 提示词建议已覆盖空状态 + 跟随式
  [ ] 重新生成 + 版本切换可用
  [ ] 多轮上下文有压缩策略
  [ ] 移动端适配已完成(键盘/滚动/输入)
  [ ] 反馈机制已接入(点赞/点踩/报告)
  [ ] 代码块有复制按钮
  [ ] 长回复有"跳到底部"按钮
  [ ] 离线/断网有友好提示

总结

对话式 AI 界面设计的 8 个维度形成一个完整的体验闭环:

  1. 消息架构 —— 定义数据模型和消息类型
  2. 流式响应 —— 消除等待焦虑
  3. 提示词建议 —— 降低使用门槛
  4. 对话分支 —— 支持探索和回溯
  5. 上下文管理 —— 平衡质量和成本
  6. 移动优化 —— 覆盖主流使用场景
  7. 高级交互 —— 满足进阶需求
  8. 性能与可访问性 —— 确保基础体验

做好这 8 点,Chat UI 就不只是一个聊天窗口,而是一个真正的人机协作界面。


Maurice | maurice_wen@proton.me