5、对话接口与前端交互文档
1. 接口概览
| 项目 | 说明 |
|---|---|
| 路径 | POST /chat/send |
| Content-Type | application/x-www-form-urlencoded |
| 响应类型 | text/event-stream(SSE 事件流) |
| 入口 | [ChatController.java:L141](file:///Users/yangguojun01/IdeaProjects/InfoHelper/src/main/java/com/yang/llm/infohelper/chat/controller/ChatController.java#L141) |
| 核心逻辑 | [ChatApplicationService.chat()](file:///Users/yangguojun01/IdeaProjects/InfoHelper/src/main/java/com/yang/llm/infohelper/chat/service/ChatApplicationService.java#L316) |
| 前端页面 | [chat.html](file:///Users/yangguojun01/IdeaProjects/InfoHelper/src/main/resources/static/chat.html) |
2. 请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId | String | 是 | 用户唯一标识,如"test_user_001" |
content | String | 是 | 用户问题文本,如"ods库的user_info表有哪些字段?" |
conversationId | String | 否 | 会话ID,不传则自动创建新会话;传入则继续保持多轮对话 |
请求示例
# 新会话 POST /chat/send?userId=test_user_001&content=ods库的user_info表有哪些字段? # 继续对话 POST /chat/send?userId=test_user_001&content=那这个表有哪些字段类型?&conversationId=abc123-def4563. SSE 响应事件类型
后端返回的是一个Flux<String>,被 Spring WebFlux 自动序列化为 SSE 格式(每行data:xxx,以空行分隔)。前端解析出 5 类事件:
3.1 进度通知[PROGRESS]:xxx
| 消息内容 | 触发时机 | 说明 |
|---|---|---|
正在识别您的意图... | 意图识别开始 | 调用 LLM 判断是否数据治理领域 |
正在优化您的问题... | QueryTransformer 开始 | 将用户问题改写为更精确的检索查询 |
正在路由您的问题... | QueryRouter 开始 | 决定走哪些检索器 |
正在检索知识库内容... | 每个 ContentRetriever 开始 | 4路检索器(ES KNN / ES BM25 / SQL / Neo4j)可多次触发 |
正在排序筛选结果... | ContentAggregator 开始 | 融合排序多路检索结果 |
正在生成回答... | LLM 流式生成开始 | 进入最终回答生成阶段 |
3.2 引用资料[REFERENCE]:json
[{"documentTitle":"数据标准规范_v2.0","url":"https://...","similarityScore":0.95,"chunkContent":"..."}]前端渲染为"参考来源"卡片,每条引用包含:
- 文档标题(可点击跳转)
- 相似度分数
- 去重处理(按
documentTitle + url去重)
3.3 警告消息[WARN]:xxx
[WARN]:未找到相关参考信息,以下回答基于通用知识生成。前端渲染为带 ⚠️ 图标的黄色警告条。
3.4 流式内容 Token
data:您好,ods库的 data:user_info表包含以下字段: data: data:1. **user_id** (VARCHAR) - 用户ID data:2. **user_name** (VARCHAR) - 用户姓名LLM 逐 token 输出的文本内容,前端累积拼接后实时按 Markdown 渲染。
3.5 结束标记[DONE]:conversationId
data:[DONE]:abc123-def456test_user_001- 前端收到后更新
currentConversationId,下次请求自动带上实现多轮对话 - 刷新左侧会话列表
- 停止 loading 状态
4. 前端交互流程
4.1 整体流程
用户输入问题 │ ├── 1. 前端渲染用户消息气泡(👤 头像) ├── 2. 创建空的 AI 消息气泡(🤖 头像 + 打字动画) ├── 3. 发送 fetch POST 请求到 /chat/send ├── 4. 读取 response.body (ReadableStream) ├── 5. 按 SSE 双空行分隔事件 ├── 6. 逐事件解析并渲染 │ ├── [PROGRESS]:xxx → 渲染进度步骤列表(✓ / spinner) │ ├── [REFERENCE]:json → 渲染参考来源卡片 │ ├── [WARN]:xxx → 渲染警告条 │ ├── 普通文本 → 累积拼接 + Markdown 实时渲染 │ └── [DONE]:xxx → 更新会话ID,刷新列表 └── 7. finally:移除打字动画,恢复按钮状态4.2 关键代码片段
// 发送消息asyncfunctionsendMessage(){constparams=newURLSearchParams();params.append('userId',currentUserId);params.append('content',content);if(currentConversationId){params.append('conversationId',currentConversationId);}constresponse=awaitfetch(`/chat/send?${params.toString()}`,{method:'POST'});constreader=response.body.getReader();constdecoder=newTextDecoder();letbuffer='';while(true){const{done,value}=awaitreader.read();if(done)break;buffer+=decoder.decode(value,{stream:true});// SSE 事件以空行分隔consteventSep=/\r?\n\r?\n/g;letm,lastIndex=0;while((m=eventSep.exec(buffer))!==null){processEvent(buffer.substring(lastIndex,m.index));lastIndex=m.index+m[0].length;}buffer=buffer.substring(lastIndex);}}4.3 事件解析
constprocessEvent=(eventBlock)=>{// 提取 data: 行,多条用 \n 拼接constdata=dataLines.join('\n');if(data.startsWith('[DONE]:')){/* 更新会话ID */}if(data.startsWith('[PROGRESS]:')){/* 更新进度列表 */}if(data.startsWith('[REFERENCE]:')){/* 解析JSON + 渲染参考来源 */}if(data.startsWith('[WARN]:')){/* 渲染警告消息 */}// 否则 → 普通文本,累积 + Markdown 渲染};5. 进度步骤渲染
5.1 状态管理
前端维护一个progressSteps数组,每个步骤有两种状态:
| 状态 | 图标 | 说明 |
|---|---|---|
active | 🔵 spinner 动画 | 当前正在执行的步骤 |
done | ✓ 绿色勾 | 已完成的步骤 |
5.2 渲染逻辑
收到 [PROGRESS]:正在优化您的问题... → 上一步标记为 done ✓ → 新步骤添加为 active(spinner) 收到首个内容 token → 最后一步标记为 done ✓ → 进度列表保持在消息正文上方5.3 渲染位置
进度列表始终插入在.message-text(正文)之前,保证:
┌─ AI 消息气泡 ─────────────────┐ │ ✓ 正在识别您的意图... │ │ ✓ 正在优化您的问题... │ │ ✓ 正在路由您的问题... │ │ ✓ 正在检索知识库内容... │ │ ✓ 正在排序筛选结果... │ │ ✓ 正在生成回答... │ │ ────────────────────────────── │ │ 您好,ods库的user_info表包含... │ ← 正文在进度下方 │ 1. user_id (VARCHAR) ... │ │ 2. user_name (VARCHAR) ... │ │ ────────────────────────────── │ │ 📎 参考来源: │ │ [1] 数据标准规范_v2.0 │ │ [2] 数据资产目录 │ └────────────────────────────────┘6. 参考来源渲染
functionbuildReferencesHtml(references){// 1. 按 documentTitle + url 去重// 2. 渲染为链接列表return`<div class="message-references"> <div class="references-label">参考来源:</div>${references.map((ref,idx)=>`<a href="${ref.url}" target="_blank" class="reference-item"> <span class="reference-index">[${idx+1}]</span> <span class="reference-title">${ref.documentTitle}</span> </a>`)}</div>`;}字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
documentTitle | String | 文档标题 |
url | String | 文档链接 |
similarityScore | Number | 向量相似度(0-1) |
chunkContent | String | 匹配到的文档片段文本 |
7. Markdown 渲染与安全
前端使用三层库处理 AI 回答的 Markdown 内容:
| 库 | 作用 |
|---|---|
marked | 将 Markdown 解析为 HTML |
DOMPurify | 过滤 XSS 攻击,白名单式净化 HTML |
highlight.js | 代码块语法高亮 |
functionrenderMarkdown(text){constrawHtml=marked.parse(text||'');returnDOMPurify.sanitize(rawHtml,{ALLOWED_TAGS:['p','br','strong','em','code','pre','h1','h2','h3','h4','h5','h6','ul','ol','li','a','img','table','thead','tbody','tr','th','td','blockquote','hr','span','div'],ALLOWED_ATTR:['href','src','alt','class','target','rel']});}8. 多轮对话支持
第一轮: POST /chat/send?userId=u1&content=ods库有哪些表? → SSE: ... [DONE]:abc123-u1 第二轮(前端自动带上): POST /chat/send?userId=u1&content=user_info表有哪些字段?&conversationId=abc123-u1 → SSE: ... [DONE]:abc123-u1conversationId由后端首次创建,通过[DONE]:xxx返回给前端- 前端存储在
currentConversationId变量中,后续请求自动附带 - 后端通过
conversationId关联 Redis 中的对话记忆,实现上下文感知
9. 错误处理
9.1 后端异常
// 全局异常兜底:SSE 响应中无法返回 JSON 错误体,转为文本消息.onErrorResume(e->{log.error("流式对话异常: conversationId={}",finalConversationId,e);returnMono.just("系统处理您的问题时遇到异常,请稍后重试。");})9.2 前端异常
}catch(error){console.error('发送消息失败:',error);updateMessageContent(aiMessageElement,'发送失败: '+error.message);}finally{isStreaming=false;sendBtn.disabled=false;removeTypingIndicator(aiMessageElement);}9.3 防护措施
- 按钮防抖:
isStreaming = true时禁用发送按钮 - 空内容保护:未收到流式内容时显示"对话已创建,请继续输入您的问题。"
- 非 SSE 兜底:兼容直接返回的裸文本(如
[DONE]:xxx不带data:前缀)
10. 完整 SSE 数据流示例
输入:userId=test_user_001, content="ods库的user_info表有哪些字段?" data:[PROGRESS]:正在识别您的意图... data:[PROGRESS]:正在优化您的问题... data:[PROGRESS]:正在路由您的问题... data:[PROGRESS]:正在检索知识库内容... data:[PROGRESS]:正在排序筛选结果... data:[PROGRESS]:正在生成回答... data:您好,ods库的 data:user_info表包含以下字段: data: data:1. **user_id** (VARCHAR) - 用户ID,主键 data:2. **user_name** (VARCHAR) - 用户姓名 data:3. **email** (VARCHAR) - 邮箱地址 data:4. **create_time** (DATETIME) - 创建时间 data: data:如需了解更多字段详情,请参考相关文档。 data:[REFERENCE]:[{"documentTitle":"数据标准规范_v2.0","url":"https://...","similarityScore":0.95}] data:[DONE]:abc123-def456test_user_001