feat(ai-nutritionist): Coze TTS and streaming robustness

- Add Coze TTS endpoint and service; expose binary MP3 from controller.
- Bypass ResponseFilter for /audio/speech so MP3 bodies are not UTF-8 wrapped.
- UniApp: cozeTextToSpeech, TTS UI and play flow; SSE HTTP errors and diagnostics.
- Document TTS in docs/features.md; extend test-0325-1 with curl verification.

Made-with: Cursor
This commit is contained in:
msh-agent
2026-03-31 07:07:21 +08:00
parent 35052d655f
commit 2facd355ab
8 changed files with 433 additions and 351 deletions

View File

@@ -2,41 +2,34 @@
## 页面pages/tool/ai-nutritionist
- 1. 请求后页面显示:"未能获取到有效回复。"
fetch("http://127.0.0.1:20822/api/front/coze/chat/stream", {
"headers": {
"accept": "*/*",
"authori-zation": "6f6767b2edc64949b0e4888c199ac0bb",
"content-type": "application/json",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://servicewechat.com/wx7ecf3e3699353c69/devtools/page-frame.html",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": "{\"botId\":\"7591133240535449654\",\"userId\":11,\"additionalMessages\":[{\"role\":\"user\",\"content\":\"透析患者可以喝牛奶吗?\",\"content_type\":\"text\"}],\"stream\":true,\"autoSaveHistory\":true}",
"method": "POST",
"mode": "cors",
"credentials": "omit"
});
- 1. 请求Request URL: http://127.0.0.1:20822/api/front/coze/chat/stream, 参数:{"botId":"7591133240535449654","userId":11,"additionalMessages":[{"role":"user","content":"透析患者可以喝牛奶吗?","content_type":"text"}],"stream":true,"autoSaveHistory":true},请求后页面显示:"未能获取到有效回复。"
## 修复记录
### 问题 1 修复:流式对话显示"未能获取到有效回复"
### 1. 流式对话显示"未能获取到有效回复"
**根因分析**:两个问题导致前端无法正确接收流式数据
**根因分析**(三个层次)
1. **Delta 事件过滤条件过严**`ai-nutritionist.vue``sendToAIStream()` `conversation.message.delta` 事件要求 `evt.role === 'assistant' && evt.type === 'answer'`。但 Coze SDK 在流式增量事件中可能不返回 `role``type` 字段(后端发送的精简 JSON 仅在字段非 null 时才包含),导致所有增量内容被静默丢弃
1. **HTTP 状态码未检查**`cozeChatStream()` `success` 回调不检查 `res.statusCode`。后端发生异常时(如 `botID is marked non-null but is null`Spring `GlobalExceptionHandler` 返回 HTTP 500 + JSON 错误体,前端 `parseSseResponseBody` 找不到 `data:` 行后静默忽略,直接触发 `_onComplete()`,最终展示"未能获取到有效回复"
2. **未处理非分块响应降级**`cozeChatStream()` `success` 回调未处理响应体。在微信开发者工具或某些不支持 `onChunkReceived` 的环境下,流式数据仅在 `res.data` 中一次性返回,但被完全忽略
2. **SSE 解析错误全部静默吞噬**`parseSseLines()` / `parseSseResponseBody()` 的 catch 块为空JSON 解析失败时无任何输出,排查困难
3. **错误提示固定话术,掩盖真实原因**`onError` 回调展示固定文字"抱歉,处理您的请求时出现错误",而非后端实际错误信息。
**修复内容**
- `ai-nutritionist.vue`:将 delta 过滤改为 `const role = evt.role || 'assistant'`,缺失字段时默认为预期值
- `models-api.js`:增加 `_gotChunks` 标记,当 `onChunkReceived` 未触发时,在 `success` 回调中解析 `res.data` 作为降级处理;增加 `responseType: 'text'` 确保响应体为字符串
- `models-api.js``cozeChatStream()``success` 回调添加 `res.statusCode !== 200` 检查,提取后端 `message`/`msg` 字段作为错误信息调用 `_onError`;在 chunk 接收、SSE 解析、降级解析各环节添加 `console.log` / `console.warn` 诊断日志
- `ai-nutritionist.vue``sendToAIStream()``onError` 改为展示 `err.message` 而非固定话术
**后端独立验证命令**(用于确认 SSE 是否正常):
```bash
curl -N -X POST 'http://127.0.0.1:20822/api/front/coze/chat/stream' \
-H "Content-Type: application/json" \
-d '{"botId":"7591133240535449654","userId":"11","additionalMessages":[{"role":"user","content":"透析患者可以喝牛奶吗?"}]}'
```
若后端正常,应逐行输出 `data:{"event":"conversation.message.delta","content":"..."}` 流;若返回 `{"code":500,"message":"..."}` 则说明后端有问题需优先排查。
# 参考文档
- 3. /Users/a123/msh-system/.cursor/plans/optimize_ai_nutritionist_speed_b6e9a618.plan.md
- 1. /Users/a123/msh-system/docs/测试问题分析报告_2026-03-22.md
- 2. /Users/a123/msh-system/docs/功能开发详细设计_2026-03-25.md
- 1. **coze官方api文档**https://docs.coze.cn/developer_guides/chat_v3#AJThpr1GJe
- 2. /Users/a123/msh-system/.cursor/plans/optimize_ai_nutritionist_speed_b6e9a618.plan.md