5、对话接口与前端交互文档

发布时间:2026/7/23 13:14:28
5、对话接口与前端交互文档 1. 接口概览项目说明路径POST /chat/sendContent-Typeapplication/x-www-form-urlencoded响应类型text/event-streamSSE 事件流入口[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. 请求参数参数类型必填说明userIdString是用户唯一标识如test_user_001contentString是用户问题文本如ods库的user_info表有哪些字段conversationIdString否会话ID不传则自动创建新会话传入则继续保持多轮对话请求示例# 新会话 POST /chat/send?userIdtest_user_001contentods库的user_info表有哪些字段 # 继续对话 POST /chat/send?userIdtest_user_001content那这个表有哪些字段类型conversationIdabc123-def4563. SSE 响应事件类型后端返回的是一个FluxString被 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 流式内容 Tokendata:您好ods库的 data:user_info表包含以下字段 data: data:1. **user_id** (VARCHAR) - 用户ID data:2. **user_name** (VARCHAR) - 用户姓名LLM 逐 token 输出的文本内容前端累积拼接后实时按 Markdown 渲染。3.5 结束标记[DONE]:conversationIddata:[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(){constparamsnewURLSearchParams();params.append(userId,currentUserId);params.append(content,content);if(currentConversationId){params.append(conversationId,currentConversationId);}constresponseawaitfetch(/chat/send?${params.toString()},{method:POST});constreaderresponse.body.getReader();constdecodernewTextDecoder();letbuffer;while(true){const{done,value}awaitreader.read();if(done)break;bufferdecoder.decode(value,{stream:true});// SSE 事件以空行分隔consteventSep/\r?\n\r?\n/g;letm,lastIndex0;while((meventSep.exec(buffer))!null){processEvent(buffer.substring(lastIndex,m.index));lastIndexm.indexm[0].length;}bufferbuffer.substring(lastIndex);}}4.3 事件解析constprocessEvent(eventBlock){// 提取 data: 行多条用 \n 拼接constdatadataLines.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 ✓ → 新步骤添加为 activespinner 收到首个内容 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. 渲染为链接列表returndiv classmessage-references div classreferences-label参考来源/div${references.map((ref,idx)a href${ref.url} target_blank classreference-item span classreference-index[${idx1}]/span span classreference-title${ref.documentTitle}/span /a)}/div;}字段说明字段类型说明documentTitleString文档标题urlString文档链接similarityScoreNumber向量相似度0-1chunkContentString匹配到的文档片段文本7. Markdown 渲染与安全前端使用三层库处理 AI 回答的 Markdown 内容库作用marked将 Markdown 解析为 HTMLDOMPurify过滤 XSS 攻击白名单式净化 HTMLhighlight.js代码块语法高亮functionrenderMarkdown(text){constrawHtmlmarked.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?userIdu1contentods库有哪些表 → SSE: ... [DONE]:abc123-u1 第二轮前端自动带上 POST /chat/send?userIdu1contentuser_info表有哪些字段conversationIdabc123-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{isStreamingfalse;sendBtn.disabledfalse;removeTypingIndicator(aiMessageElement);}9.3 防护措施按钮防抖isStreaming true时禁用发送按钮空内容保护未收到流式内容时显示对话已创建请继续输入您的问题。非 SSE 兜底兼容直接返回的裸文本如[DONE]:xxx不带data:前缀10. 完整 SSE 数据流示例输入userIdtest_user_001, contentods库的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