feat: 实现饮食记录确认流程
- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。 - 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。 - 扩展DTO层,新增相关数据结构以支持确认流程。 - 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。 - 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
This commit is contained in:
151
docs/stream-response-solution-options.md
Normal file
151
docs/stream-response-solution-options.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# 流式响应与结构化数据冲突解决方案
|
||||
|
||||
## 问题描述
|
||||
|
||||
当前实现中,`#记饮食` 指令在第一阶段需要返回结构化数据(确认选项),但客户端可能设置了 `stream: true`,导致响应类型冲突。
|
||||
|
||||
## 解决方案对比
|
||||
|
||||
### 方案1:强制非流式模式 ⭐ (当前实现)
|
||||
|
||||
**优点:**
|
||||
- 实现简单,改动最小
|
||||
- 完全向后兼容
|
||||
- 客户端只需检查 Content-Type
|
||||
|
||||
**缺点:**
|
||||
- 行为不够明确(忽略stream参数)
|
||||
- 客户端需要额外处理响应类型检测
|
||||
|
||||
**实现:**
|
||||
```typescript
|
||||
// 当需要返回确认选项时,自动使用JSON响应
|
||||
if (typeof result === 'object' && 'type' in result) {
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send({ conversationId, data: result.data });
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### 方案2:分离API端点
|
||||
|
||||
**优点:**
|
||||
- API语义清晰
|
||||
- 响应类型明确
|
||||
- 易于测试和维护
|
||||
|
||||
**缺点:**
|
||||
- 需要新增API端点
|
||||
- 客户端需要适配新API
|
||||
|
||||
**建议API设计:**
|
||||
```typescript
|
||||
// 专门的食物识别API
|
||||
@Post('analyze-food')
|
||||
async analyzeFood(body: { imageUrls: string[] }): Promise<FoodRecognitionResponseDto>
|
||||
|
||||
// 确认并记录API
|
||||
@Post('confirm-food-record')
|
||||
async confirmFoodRecord(body: { selectedOption: any, imageUrl: string }): Promise<DietRecordResponseDto>
|
||||
|
||||
// 原有聊天API保持纯文本
|
||||
@Post('chat')
|
||||
async chat(): Promise<StreamableFile | { text: string }>
|
||||
```
|
||||
|
||||
### 方案3:统一JSON响应格式
|
||||
|
||||
**优点:**
|
||||
- 响应格式统一
|
||||
- 可以在JSON中指示是否需要流式处理
|
||||
|
||||
**缺点:**
|
||||
- 破坏向后兼容性
|
||||
- 所有客户端都需要修改
|
||||
|
||||
**实现示例:**
|
||||
```typescript
|
||||
// 统一响应格式
|
||||
{
|
||||
conversationId: string;
|
||||
responseType: 'text' | 'choices' | 'stream';
|
||||
data: {
|
||||
content?: string;
|
||||
choices?: any[];
|
||||
streamUrl?: string; // 流式数据的WebSocket URL
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 方案4:SSE (Server-Sent Events) 统一
|
||||
|
||||
**优点:**
|
||||
- 可以发送不同类型的事件
|
||||
- 保持连接状态
|
||||
- 支持实时交互
|
||||
|
||||
**缺点:**
|
||||
- 实现复杂度高
|
||||
- 需要客户端支持SSE
|
||||
|
||||
**实现示例:**
|
||||
```typescript
|
||||
// SSE事件类型
|
||||
event: text
|
||||
data: {"chunk": "AI回复的文本片段"}
|
||||
|
||||
event: choices
|
||||
data: {"choices": [...], "content": "请选择食物"}
|
||||
|
||||
event: complete
|
||||
data: {"conversationId": "..."}
|
||||
```
|
||||
|
||||
## 推荐方案
|
||||
|
||||
### 短期:方案1 (当前实现) ✅
|
||||
- 快速解决问题
|
||||
- 最小化影响
|
||||
- 保持兼容性
|
||||
|
||||
### 长期:方案2 (分离API端点)
|
||||
- 更清晰的API设计
|
||||
- 更好的可维护性
|
||||
- 更明确的职责分离
|
||||
|
||||
## 当前方案的客户端适配
|
||||
|
||||
```javascript
|
||||
async function sendDietRequest(imageUrls, conversationId, stream = true) {
|
||||
const response = await fetch('/ai-coach/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
conversationId,
|
||||
messages: [{ role: 'user', content: '#记饮食' }],
|
||||
imageUrls,
|
||||
stream
|
||||
})
|
||||
});
|
||||
|
||||
// 检查响应类型
|
||||
const contentType = response.headers.get('content-type');
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
// 结构化数据(确认选项)
|
||||
const data = await response.json();
|
||||
return { type: 'choices', data };
|
||||
} else {
|
||||
// 流式文本
|
||||
return { type: 'stream', stream: response.body };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
当前的方案1实现简单有效,能够解决流式响应冲突问题。虽然在语义上不够完美,但在实际使用中是可行的。建议:
|
||||
|
||||
1. **立即采用方案1**,解决当前问题
|
||||
2. **文档中明确说明**响应类型检测的必要性
|
||||
3. **后续版本考虑方案2**,提供更清晰的API设计
|
||||
Reference in New Issue
Block a user