聊天与生成式 UI

共 20 题
📑 题目列表 20 题
#
★★★

1. 如何设计 idle、streaming、awaiting-tool、awaiting-confirmation、error、success 等消息状态及转换

如何设计聊天消息的 idle、streaming、awaiting-tool、awaiting-confirmation、error、success 等状态机,以及它们之间的合法转换?

  • 消息状态机的建模与有限状态转换
  • 各状态与运行中请求、工具执行、用户确认的关联
  • 状态转换的驱动源(事件)与并发安全

用显式状态机(如用字符串字面量联合类型或 XState)建模每条消息的状态,而不是用布尔标志组合。常见状态为:idle(就绪/未开始)、streaming(正在流式输出)、awaiting-tool(正在等待工具执行)、awaiting-confirmation(等待用户确认高风险工具)、error(出错)、success(完成)。转换只能由明确事件驱动,例如:idle→streaming(收到首个 token)、streaming→awaiting-tool(遇到工具调用点)、awaiting-tool→streaming(工具返回、继续生成)、streaming→success(收到结束标记)、任意运行态→error(请求失败/超时)、streaming→idle(用户点击停止,回到可重试状态)。状态机应放在一个可穷举的 reducer 或 store 中,保证同一时刻只有一个合法状态,并让渲染层只根据状态渲染占位符、光标动画或错误卡片。

显式状态机避免了"多个布尔标志互相矛盾"的经典 bug(例如正在 error 又显示 streaming 动画)。把状态切换收敛到单一事件处理函数,便于 debug、测试和审计,也便于后续加入"已取消"等新状态。状态机还能作为"可恢复性"的依据——刷新后根据服务端持久化的状态节点恢复 UI。

type MsgState = 'idle' | 'streaming' | 'awaiting-tool' | 'awaiting-confirmation' | 'error' | 'success';
type MsgEvent =
  | { type: 'FIRST_TOKEN' }
  | { type: 'TOOL_START' }
  | { type: 'TOOL_RESULT' }
  | { type: 'STREAM_END' }
  | { type: 'FAIL' }
  | { type: 'STOP' };

function reduce(state: MsgState, ev: MsgEvent): MsgState {
  switch (state) {
    case 'idle':
      return ev.type === 'FIRST_TOKEN' ? 'streaming' : state;
    case 'streaming':
      if (ev.type === 'TOOL_START') return 'awaiting-tool';
      if (ev.type === 'STREAM_END') return 'success';
      if (ev.type === 'FAIL') return 'error';
      if (ev.type === 'STOP') return 'idle';
      return state;
    case 'awaiting-tool':
      if (ev.type === 'TOOL_RESULT') return 'streaming';
      if (ev.type === 'FAIL') return 'error';
      return state;
    case 'awaiting-confirmation':
      if (ev.type === 'TOOL_RESULT') return 'streaming';
      if (ev.type === 'STOP') return 'idle';
      return state;
    default:
      return state;
  }
}
#
★★★

2. 流式 Markdown、代码块、引用和工具进度如何安全渲染,并避免未闭合结构导致页面抖动

流式渲染中,Markdown、代码块、引用和工具进度信息应如何安全渲染,并避免未闭合结构导致的页面抖动?

  • 对半截 Markdown 的安全解析与净化(XSS 防护)
  • 未闭合结构(代码块、引用、表格)的平滑处理
  • 增量渲染与 DOM 稳定性(避免每 token 重建整棵子树)

流式渲染时,把当前累积的 Markdown 文本交给支持"部分解析"的库(如 remark 的增量解析或 streaming-markdown 类方案),对输出先做净化(DOMPurify 白名单 + CSP + Trusted Types),再渲染。对未闭合的代码块、引用、表格,应采用"占位容忍"策略:解析器能容忍未闭合围栏并在结尾补全闭合标记,或把未闭合部分渲染为普通文本并给一个临时标记,避免 DOM 重建导致光标与滚动跳动。增量更新应只更新变化的叶子节点,而不是每次 token 都整块 innerHTML 替换。遇到工具进度时,用独立的列表/卡片渲染,与正文 Markdown 渲染隔离,避免互相干扰。

未闭合结构是流式渲染的天然难点:完整解析器的输入必须是"合法 Markdown",而流式输入永远可能是半截的。安全方面,模型输出不可信,必须净化;性能方面,必须增量 diff 而非整块重建,否则每次 token 都会造成布局抖动和滚动跳动。

#
★★★

3. 编辑重试、分支对话、停止生成、反馈和重新生成应怎样维护消息树与请求关联

编辑重试、分支对话、停止生成、反馈和重新生成等操作,应如何维护消息树与请求关联?

  • 消息树(消息节点 + 父子关系)的数据模型
  • 每个操作与请求、父消息、分支的关联
  • 请求取消、重试与计费/审计的一致性

把对话建模为消息树而非线性数组:每条消息有唯一 id、parentId、以及一个请求记录(requestId、AbortController、状态、Provider 上下文)。用户编辑或重新生成某条消息时,创建该消息的新分支(新子节点),旧分支保留可回溯。停止生成取消对应当前 requestId 的请求并更新消息状态;反馈(点赞/点踩)挂到具体消息节点上;重试则复用父消息、重新发起请求并复用配置(模型、参数、工具)。请求关联用 requestId 连接消息与底层 Provider 调用,便于取消、去重和审计。

线性数组无法表达"重新生成后的多个分支",而树模型天然支持分支与回溯。所有操作(编辑、重试、停止、反馈)都通过消息 id 定位节点,通过 requestId 关联请求,这样状态同步、取消与计费记录才能一致,避免"UI 显示已停止但服务端还在计费"。

interface MessageNode {
  id: string;
  parentId: string | null;
  role: 'user' | 'assistant' | 'tool';
  content: string;
  status: MsgState;
  requestId: string | null;
  feedback?: 'up' | 'down';
  children: string[]; // 分支子节点 id
}
#
★★★

4. 对话消息树(thread tree)应如何建模分支、回滚与重新生成,状态同步如何避免冲突

对话消息树应如何建模分支、回滚与重新生成,多路状态同步时如何避免冲突?

  • 消息树的建模与分支/回滚语义
  • 状态同步的策略(版本号、last-write-wins、CRDT)
  • 冲突检测与解决

消息树用 id + parentId + children 表示,树的深度即对话路径。分支是给同一父节点添加多个子节点;回滚是暂时把活动指针切到某个分支;重新生成是创建新分支并清空该分支后续。为避免多端(多标签页、客户端与服务端)状态冲突,每条消息带单调递增的版本号,做 optimistic concurrency:更新时校验版本号,不匹配则提示冲突;同步采用 last-write-wins 或带合并规则的版本号比较,必要时用 CRDT 保证收敛。刷新后从服务端拉取权威树并与本地缓存合并(以服务端版本为准,本地未落地的编辑保留为待提交)。

分支/回滚本质是树操作,天然幂等,便于持久化。冲突主要来自"多写"(多标签页同时编辑、服务端工具结果与本地乐观 UI 竞态),用版本号/游标做乐观并发控制比盲目覆盖更安全。CRDT 只有在多端实时协同才值得引入,多数场景版本号 + last-write-wins 已足够。

#
★★★

5. 流式回答中“工具调用”与“文本回答”应如何在 UI 上视觉区分(图标、颜色、标签)

流式回答中,"工具调用"与"文本回答"应如何在 UI 上通过图标、颜色、标签等视觉手段区分?

  • 工具调用与文本回答的分区渲染
  • 视觉区分的一致性(图标、颜色、标签、缩进)
  • 工具调用卡片的状态与参数展示

用结构化消息区分两类内容:模型输出中标记为 tool_call 的片段渲染为独立的"工具卡片",与正文文本在视觉上明确区分——工具卡用不同背景色、左侧条、工具图标和名称标签(如"搜索网页"、"计算"),并展示工具名、调用参数摘要、状态(运行中/成功/失败)和耗时;正文文本用普通对话气泡渲染。工具卡片可折叠,避免打断阅读流。采用统一的 Design Token(颜色、图标)保证不同工具的一致观感。

人类读者需要迅速区分"模型在说什么"与"模型在做什么"——工具调用是过程性信息,文本是结论性信息。结构化渲染(而非把工具调用拼接进文本)既提升可读性,也让用户对"模型为什么这么回答"有透明感,同时方便后续对工具结果做审计和回滚。

#
★★★

6. 前端如何把模型输出中的引用(citations)渲染为可点击链接并跳转到原文(带位置锚点)

前端如何把模型输出中的引用(citations)渲染为可点击链接,并跳转到原文的指定位置锚点?

  • 引用数据的结构化传递(引用 id、来源、锚点)
  • 点击跳转与锚点定位的实现
  • 引用失效/不可达的处理

模型输出中通过结构化协议标注引用(如 [1], [2] 映射到富引用块),前端把引用块单独渲染为可点击的链接或编号徽章。每条引用携带来源 URL、原文标题、以及位置锚点(如行号、段落 id、时间戳)。点击后先做 URL 校验(仅允许 http/https、禁止 javascript:),再在新标签页打开或应用内滚动到锚点。对无法定位的引用(原文已改、锚点失效)展示"失效"状态,而不是报错。链接由前端渲染,禁止把不可信 URL 直接作为协议可变的 href。

引用可点击是 AI 应用增强可信度的关键——用户要能核对来源。安全和准确性同样重要:必须先校验 URL 协议,防止 javascript: 钓鱼;锚点定位要处理失效场景,保持可用性。引用数据应作为结构化元数据传递,而非让模型自由输出 HTML。

#
★★★

7. 乐观 UI 与服务端工具执行结果冲突时,如何回滚并明确展示事实状态

乐观 UI 与服务端工具执行结果冲突时,应如何回滚并明确展示"事实状态"?

  • 乐观 UI 的更新与回滚机制
  • 事实状态与乐观状态的区分
  • 冲突后的用户提示与恢复

乐观 UI 先按预期结果立即更新界面,同时记录用于回滚的"快照"或"撤销操作"。当服务端工具结果返回且与乐观值不一致时,前端执行回滚:将相关字段恢复为服务端返回的事实值,并明确标注"已更新为服务端结果"或展示差异(如"你修改的内容与服务端结果冲突,已采用服务端版本")。回滚应精确到字段/节点,而非整块重置。对暂未落地的用户编辑,保留为待提交状态,避免丢失。

乐观 UI 提升响应感,但工具执行结果可能依赖外部真实状态,与假设不符。回滚必须可逆且有迹可循,同时把"用户假设"与"事实状态"区分清楚,让用户知道发生了什么、为什么。若用户有未提交编辑,属冲突,需合并策略而非静默覆盖。

#
★★

8. 高风险工具确认卡应固定展示哪些参数、影响和权限,为什么不能由模型自由生成按钮行为

高风险工具确认卡应固定展示哪些参数、影响和权限,为什么不能由模型自由生成按钮行为?

  • 高风险工具确认卡的内容(参数、影响、权限)
  • 确认/拒绝按钮的语义
  • 为什么按钮行为必须由代码固定而非模型生成

高风险工具(如删除、写文件、发送邮件、执行命令)确认卡应固定展示:将要执行的参数(含敏感值)、影响范围(会改动哪些资源、是否可逆)、所需权限(如写权限、网络权限)、以及耗时/成本。确认与拒绝按钮的语义由代码固定定义(确认=放行,拒绝=中止),绝不由模型自由生成按钮行为或文案。因为按钮行为是安全边界,若由模型自由生成,攻击者或被诱导的模型可能把"确认"变成"执行恶意操作",或把危险操作伪装成无害按钮。

确认卡是权限边界,属于应用可信代码,而非模型输出。模型可以建议参数,但"执行/取消"的语义必须由前端强制绑定到固定的安全回调,模型不能生成可执行逻辑或篡改按钮的目标动作。这是安全最小化原则在 UI 上的体现。

#
★★

9. 长回答中的引用如何定位原文、展示失效状态,并防止钓鱼链接和 javascript: 协议

长回答中的引用应如何定位原文、展示失效状态,并防止钓鱼链接和 javascript: 协议?

  • 引用定位到原文的方法
  • 失效状态的检测与展示
  • 链接协议安全校验(防钓鱼、防 javascript:)

引用定位到原文通过携带来源 URL + 锚点(段落/行号/时间戳)实现,前端把引用渲染为可点击链接。失效检测:点击或渲染时校验锚点是否可达,或由服务端预校验来源可用性,失效时展示"来源已失效"的占位而非报错。安全方面,所有引用 URL 必须走白名单校验:仅允许 http/https,禁止 javascript:、data: 等危险协议,并对域名做钓鱼校验(如与已知域名黑名单比对、提示未知域名)。URL 由前端解析与渲染,不把不可信字符串直接作为 href。

引用是模型输出的"外部输入",既可能失效也可能被恶意构造。安全上必须协议白名单 + 域名校验;可用性上必须处理失效锚点。二者结合才能既可信又安全。

#
★★

10. 停止生成按钮的响应延迟应控制在多少毫秒以内,前端如何立即取消 Provider 请求

停止生成按钮的响应延迟应控制在多少毫秒以内?前端如何立即取消 Provider 请求?

  • 停止按钮的响应延迟目标
  • 前端取消请求的机制(AbortController、WebSocket 关闭)
  • 取消后 UI 与服务端的一致

停止生成按钮的响应延迟应控制在 100ms 以内(理想 <50ms),保证用户感觉"立即停止"。前端取消请求:对 fetch/SSE 用 AbortController.abort() 立即中止连接;对 WebSocket 发送停止消息或主动 close;对通过 BFF 的请求,前端取消后 BFF 才能向上游 Provider 传播取消。取消后立即更新消息状态为"已停止",并清理未完成的 DOM 节点与占位符。若服务端模型推理无法立即中断,还需 BFF 层的独立取消协议。

停止是否"立即"是用户感知 AI 可控性的关键指标,100ms 是常见交互预算。前端只能取消浏览器侧的请求,真正停止计费需要服务端配合传播取消,因此前端取消 + BFF 取消协议要配合。

#
★★

11. 为何不直接展示 CoT/thinking tokens,怎样用步骤状态和证据摘要替代

为什么不直接展示模型的 CoT/thinking tokens?怎样用步骤状态和证据摘要替代?

  • 不展示 CoT 的原因(安全、泄露、成本、保真)
  • 步骤状态与证据摘要替代方案
  • 透明度与保密性的平衡

不直接展示完整 CoT/thinking tokens 的原因:首先降本增效(CoT 可能很长且含内部推理),其次是安全与竞品保密(完整思维链可能泄露训练数据、内部推理策略或被用于提示注入攻击),再次是原始 CoT 噪声大、不保真。替代方案:展示高层"步骤状态"(如"正在检索资料""正在分析代码")和"证据摘要"(引用了哪些来源、得出的关键结论),这些由模型按结构化协议输出,而非将原始 thinking 直接透传。步骤状态让用户理解过程,证据摘要让用户能核对结论,兼顾透明度与安全性。

完整 CoT 既是安全负担也是成本负担。用户真正需要的是"过程可控、结论可查",而非逐字推理。做法是让模型输出结构化步骤与证据,前端渲染为步骤列表,既保留透明度又不泄露内部思维链。

#
★★

12. 聊天 UI 中“重新生成”应保持哪些配置(模型、参数、工具)

聊天 UI 中点击"重新生成"应保持哪些配置(模型、参数、工具)?

  • 重新生成要复用的配置项
  • 与"新对话"的差别
  • 上下文与分支的处理

重新生成应保持当前消息的上下文(之前的对话历史)、模型、采样参数(温度、top_p、max_tokens 等)、已启用的工具集、以及系统提示词,只重新生成该条 assistant 消息。重新生成不是"新对话",因此上下文不能清空;参数应与原文一致,除非用户显式修改。重新生成通常创建新分支(保留旧回复)。若用户更改了模型或参数,则新生成应使用新配置并明确标注差异。

用户期望"重新生成"是"同条件下再试一次",因此上下文与配置必须稳定,否则结果不可对比。同时保留分支让用户能回看旧结果。这与"换模型再问"或"新对话"是不同语义,需区分。

#
★★

13. 长回答(>5k 字)滚动与阅读体验应如何设计(TOC、章节折叠、进度条)

长回答(>5k 字)的滚动与阅读体验应如何设计?考虑 TOC、章节折叠、进度条?

  • 长答案的阅读辅助(TOC、锚点跳转)
  • 章节折叠与进度条
  • 与虚拟列表、滚动锚点的配合

对长回答,前端解析出章节结构(h1/h2/h3)生成目录(TOC)侧边栏,点击可锚点跳转;长章节支持折叠/展开以控制信息密度;提供阅读进度条显示当前阅读位置;在回答底部提供"回到顶部"和"复制全文"。这些辅助应与虚拟列表、滚动锚点配合,避免折叠展开导致滚动跳动。TOC 通过观察滚动位置高亮当前章节(IntersectionObserver)。

长回答的信息可寻址性很重要——用户要能快速定位、检索、跳转。TOC、折叠、进度条都是"信息导航"手段,配合稳定锚点避免滚动漂移,提升长文本的可读性。

#
★★

14. 输入框应支持哪些快捷键(Cmd+Enter 发送、Shift+Enter 换行、Cmd+K 清空)

输入框应支持哪些快捷键(如 Cmd+Enter 发送、Shift+Enter 换行、Cmd+K 清空)?

  • 发送与换行的快捷键约定
  • 命令面板与清空的快捷键
  • 快捷键冲突与无障碍

常见约定:Cmd/Ctrl+Enter 发送消息,Shift+Enter 换行(多行输入时 Enter 发送、Shift+Enter 换行,或相反);Cmd/Ctrl+K 打开命令面板或清空输入;Cmd/Ctrl+Shift+C 或组合键清空会话;Esc 取消当前生成或关闭面板。快捷键需在 textarea 上处理且避免与浏览器内置冲突(如 Cmd+K 可能被浏览器占用时需拦截),并支持键盘可达性(无障碍标注)。所有快捷键应可配置并可显示在帮助面板。

快捷键提升高频操作效率,是"心流"体验的一部分。约定需一致(区分发送/换行),且要处理浏览器冲突与无障碍。Cmd+K 在聊天中常被用作命令面板而非清空,团队需一致约定。

#
★★

15. 消息反馈(点赞、点踩)应如何既便于用户反馈又不打扰正常对话流程

消息反馈(点赞、点踩)应如何既便于用户反馈又不打扰正常对话流程?

  • 反馈入口的放置与交互
  • 不打扰对话的交互设计
  • 点击踩后的可选跟进(如"为什么"、"重新生成")

反馈按钮(点赞/点踩)放在消息角落,hover 时显示、默认淡显,避免视觉噪音;点击后给出轻量反馈(图标高亮 + 简短提示),不打断阅读。点踩后可展开可选跟进(如"请说明原因"、"重新生成"),但默认不强制,保持轻量。反馈数据与消息 id 关联上报,用于改进模型。反馈交互应支持键盘与无障碍。

反馈是改进模型的重要信号,但高频打扰会伤害体验。放在角落、hover 显现、轻量确认,兼顾采集与体验。点踩跟进是可选增强,避免强制打断。

#
★★

16. 对话历史应保留多久、按用户/项目隔离,前端 IndexedDB 缓存策略如何设计

对话历史应保留多久、按用户/项目如何隔离?前端 IndexedDB 缓存策略如何设计?

  • 历史保留时长与隔离策略
  • IndexedDB 缓存的数据结构与容量
  • 缓存与远程同步、过期清理

历史保留时长由产品与合规定义(如 30/90 天),按用户 + 项目(workspace)隔离,避免跨用户跨项目泄漏。前端 IndexedDB 缓存:按 userId+projectId 分库或分 key 前缀,存消息、附件元数据、引用、会话版本;设置容量上限(如 50MB),用 LRU 或按时间清理旧会话;缓存仅为离线/提速,权威数据在服务端,刷新后以服务端版本合并并回写缓存。缓存内容需加密(IndexedDB 本身不加密,敏感数据需 App 层加密)。

隔离是安全底线,保留时长是合规要求。IndexedDB 是本地缓存而非权威源,需容量管理、过期清理,并与服务端版本同步。敏感会话数据加密存储。

#

17. AI 回复中包含 Markdown 表格、列表、代码块时,复制到剪贴板应保留原始格式吗

AI 回复中包含 Markdown 表格、列表、代码块时,复制到剪贴板应保留原始格式吗?

  • 复制对话框时格式保留策略
  • 富文本 vs 纯文本复制
  • 结构化内容的复制(表格、代码块)

应保留原始格式:复制整个回答时,表格、列表、代码块应复制为结构化的 Markdown(或适配目标应用的富文本/HTML),而不是平铺成纯文本。代码块应复制为代码(不带 Markdown 围栏符号),表格可复制为 Markdown 表格或 CSV。提供"复制 Markdown"与"复制纯文本"两种选项,满足不同需求。Clipboard API 可以写 text/plain 与 text/html 双份,自定义格式则用 text/plain 写入 Markdown。

用户复制 AI 回答常是为了粘贴到文档、笔记、代码库,保留格式能大幅提升复用价值。纯文本会丢失表格结构、代码块高亮。提供自定义格式(Markdown)与富文本双写,兼顾目标场景。

#

18. 聊天 UI 中“加载历史消息”应如何避免请求风暴(分页、虚拟列表、IndexedDB 缓存)

聊天 UI 中"加载历史消息"应如何避免请求风暴?考虑分页、虚拟列表、IndexedDB 缓存?

  • 分页/游标加载
  • 虚拟列表限制 DOM 数量
  • IndexedDB 缓存避免重复请求

加载历史用游标分页(按时间倒序,每次加载一批),避免一次性拉取全部;渲染用虚拟列表只渲染可视区 + 缓冲区的消息,避免大量 DOM 节点;首次加载后把历史写入 IndexedDB 缓存,再次打开或用"加载更早"时优先读缓存,命中则不重复请求。同时用请求去重/防抖,避免快速滚动触发多次重复请求。滚动到顶部时再请求更早的分页。

请求风暴来自"一次性拉全量"和"快速滚动多次触发"。分页 + 虚拟列表 + 本地缓存三者结合,既减少请求量又保持渲染流畅。缓存减少重复网络请求,是防风暴的关键。

#

19. 为什么聊天应用不应仅依赖客户端渲染,对 SEO 友好的初始 HTML 应如何服务端预渲染

为什么聊天应用不应仅依赖客户端渲染?对 SEO 友好的初始 HTML 应如何服务端预渲染?

  • CSR 的局限(SEO、首屏、可访问性)
  • 服务端预渲染(SSR/prerender)的实现
  • 流式渲染与 hydration

纯客户端渲染(CSR)的首屏是空壳,搜索引擎爬虫与无 JS 用户看不到内容,首屏延迟也高。对 SEO 友好的场景(如公开分享的对话、文档、FAQ),应服务端预渲染:在服务端把初始消息渲染成 HTML 返回,包含标题、meta、正文内容,客户端再 hydration 接管交互。对流式回答,可先返回静态骨架 + 首轮内容,再通过 SSR 流式或静态快照补充。Meta 标签(title、description、og)由 SSR 填充。

聊天应用常被误认为"不需要 SEO",但公开内容(分享页、帮助文档)需要可爬取。SSR/prerender 让首屏有内容、可索引、可访问,同时保留客户端交互。是否 SSR 取决于内容是否公开——私有对话不需要。

#

20. 多模态输入(图像、文件、语音)的 UI 应如何与文本输入统一到同一消息流

多模态输入(图像、文件、语音)的 UI 应如何与文本输入统一到同一消息流?

  • 多模态附件与文本的统一消息模型
  • 附件预览与状态管理
  • 发送与上传的整合

把图像、文件、语音都建模为消息的附件(attachments),与文本内容统一在一条消息里,渲染为"文本 + 附件卡片"的同一气泡。发消息时附件与文本一起发送;附件先做本地预览(缩略图、文件名、大小),上传走分片 + 进度条,失败可重试。语音输入转成文本或音频附件,与文本消息流一致。消息模型用 content: [{type:'text', text}, {type:'image', url}, {type:'file', meta}] 的结构化数组。

统一消息模型让多模态成为消息的一部分,而非独立入口,UI 与后处理都一致。结构化 content 数组既表达多模态又便于增量渲染和权限校验。

type MessageContentPart =
  | { type: 'text'; text: string }
  | { type: 'image'; url: string; thumb?: string; status: 'uploading'|'done'|'error' }
  | { type: 'file'; name: string; size: number; mime: string; status: 'uploading'|'done'|'error' };