feat: 实现饮食记录确认流程
- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。 - 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。 - 扩展DTO层,新增相关数据结构以支持确认流程。 - 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。 - 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
This commit is contained in:
239
docs/diet-confirmation-flow-api.md
Normal file
239
docs/diet-confirmation-flow-api.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# 饮食记录确认流程 API 文档
|
||||
|
||||
## 概述
|
||||
|
||||
新的饮食记录流程分为两个阶段:
|
||||
1. **图片识别阶段**:AI识别食物并返回确认选项
|
||||
2. **用户确认阶段**:用户选择确认选项后记录到数据库
|
||||
|
||||
## 重要说明
|
||||
|
||||
⚠️ **流式响应兼容性**:当系统需要返回确认选项时,会自动使用非流式模式返回JSON结构,即使客户端请求了 `stream: true`。这确保了确认选项的正确显示。
|
||||
|
||||
## API 流程
|
||||
|
||||
### 第一阶段:图片识别(返回确认选项)
|
||||
|
||||
**请求示例:**
|
||||
```json
|
||||
POST /ai-coach/chat
|
||||
{
|
||||
"conversationId": "user123-1234567890",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "#记饮食"
|
||||
}
|
||||
],
|
||||
"imageUrls": ["https://example.com/food-image.jpg"],
|
||||
"stream": false
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"conversationId": "user123-1234567890",
|
||||
"data": {
|
||||
"content": "我识别到了以下食物,请选择要记录的内容:\n\n图片中识别到烤鱼和米饭,看起来是一份营养均衡的晚餐。",
|
||||
"choices": [
|
||||
{
|
||||
"id": "food_0",
|
||||
"label": "一条烤鱼 220卡",
|
||||
"value": {
|
||||
"id": "food_0",
|
||||
"foodName": "烤鱼",
|
||||
"portion": "1条",
|
||||
"calories": 220,
|
||||
"mealType": "dinner",
|
||||
"nutritionData": {
|
||||
"proteinGrams": 35,
|
||||
"carbohydrateGrams": 2,
|
||||
"fatGrams": 8,
|
||||
"fiberGrams": 0
|
||||
}
|
||||
},
|
||||
"recommended": true
|
||||
},
|
||||
{
|
||||
"id": "food_1",
|
||||
"label": "一碗米饭 150卡",
|
||||
"value": {
|
||||
"id": "food_1",
|
||||
"foodName": "米饭",
|
||||
"portion": "1碗",
|
||||
"calories": 150,
|
||||
"mealType": "dinner",
|
||||
"nutritionData": {
|
||||
"proteinGrams": 3,
|
||||
"carbohydrateGrams": 32,
|
||||
"fatGrams": 0.5,
|
||||
"fiberGrams": 1
|
||||
}
|
||||
},
|
||||
"recommended": false
|
||||
}
|
||||
],
|
||||
"interactionType": "food_confirmation",
|
||||
"pendingData": {
|
||||
"imageUrl": "https://example.com/food-image.jpg",
|
||||
"recognitionResult": {
|
||||
"recognizedItems": [...],
|
||||
"analysisText": "图片中识别到烤鱼和米饭...",
|
||||
"confidence": 85
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"command": "diet",
|
||||
"step": "confirmation"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 第二阶段:用户确认选择
|
||||
|
||||
**请求示例:**
|
||||
```json
|
||||
POST /ai-coach/chat
|
||||
{
|
||||
"conversationId": "user123-1234567890",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "我选择记录烤鱼"
|
||||
}
|
||||
],
|
||||
"selectedChoiceId": "food_0",
|
||||
"confirmationData": {
|
||||
"selectedOption": {
|
||||
"id": "food_0",
|
||||
"foodName": "烤鱼",
|
||||
"portion": "1条",
|
||||
"calories": 220,
|
||||
"mealType": "dinner",
|
||||
"nutritionData": {
|
||||
"proteinGrams": 35,
|
||||
"carbohydrateGrams": 2,
|
||||
"fatGrams": 8,
|
||||
"fiberGrams": 0
|
||||
}
|
||||
},
|
||||
"imageUrl": "https://example.com/food-image.jpg"
|
||||
},
|
||||
"stream": false
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"conversationId": "user123-1234567890",
|
||||
"text": "很好!我已经为您记录了这份烤鱼(1条,约220卡路里)。\n\n根据您的饮食记录,这是一份优质的蛋白质来源,包含35克蛋白质,脂肪含量适中。建议搭配一些蔬菜来增加膳食纤维的摄入。\n\n您今天的饮食营养搭配看起来不错,记得保持均衡的饮食习惯!"
|
||||
}
|
||||
```
|
||||
|
||||
## 数据结构说明
|
||||
|
||||
### AiChoiceOptionDto
|
||||
```typescript
|
||||
{
|
||||
id: string; // 选项唯一标识符
|
||||
label: string; // 显示给用户的文本(如"一条鱼 200卡")
|
||||
value: any; // 选项对应的数据
|
||||
recommended?: boolean; // 是否为推荐选项
|
||||
}
|
||||
```
|
||||
|
||||
### AiResponseDataDto
|
||||
```typescript
|
||||
{
|
||||
content: string; // AI回复的文本内容
|
||||
choices?: AiChoiceOptionDto[]; // 选择选项(可选)
|
||||
interactionType?: string; // 交互类型:'text' | 'food_confirmation' | 'selection'
|
||||
pendingData?: any; // 需要用户确认的数据(可选)
|
||||
context?: any; // 上下文信息(可选)
|
||||
}
|
||||
```
|
||||
|
||||
### FoodConfirmationOption
|
||||
```typescript
|
||||
{
|
||||
id: string; // 唯一标识符
|
||||
label: string; // 显示文本
|
||||
foodName: string; // 食物名称
|
||||
portion: string; // 份量描述
|
||||
calories: number; // 估算热量
|
||||
mealType: MealType; // 餐次类型
|
||||
nutritionData: { // 营养数据
|
||||
proteinGrams?: number; // 蛋白质(克)
|
||||
carbohydrateGrams?: number; // 碳水化合物(克)
|
||||
fatGrams?: number; // 脂肪(克)
|
||||
fiberGrams?: number; // 膳食纤维(克)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 图片识别失败
|
||||
如果图片模糊或无法识别食物,API会返回正常的文本响应:
|
||||
```json
|
||||
{
|
||||
"conversationId": "user123-1234567890",
|
||||
"text": "抱歉,我无法清晰地识别图片中的食物。请确保图片清晰,光线充足,食物在画面中清晰可见,然后重新上传。"
|
||||
}
|
||||
```
|
||||
|
||||
### 无效的确认数据
|
||||
如果第二阶段的确认数据无效,系统会返回错误提示:
|
||||
```json
|
||||
{
|
||||
"conversationId": "user123-1234567890",
|
||||
"text": "确认数据无效,请重新选择要记录的食物。"
|
||||
}
|
||||
```
|
||||
|
||||
## 使用建议
|
||||
|
||||
1. **图片质量**:确保上传的图片清晰,光线充足,食物在画面中清晰可见
|
||||
2. **选择确认**:用户可以选择多个食物选项,每次确认记录一种食物
|
||||
3. **营养分析**:系统会基于用户的历史饮食记录提供个性化的营养分析和建议
|
||||
4. **流式响应处理**:
|
||||
- 客户端应该检查响应的 `Content-Type`
|
||||
- `application/json`:结构化数据(确认选项)
|
||||
- `text/plain`:流式文本
|
||||
- 当返回确认选项时,系统会忽略 `stream` 参数并返回JSON
|
||||
|
||||
## 客户端适配指南
|
||||
|
||||
### 响应类型检测
|
||||
```javascript
|
||||
// 检查响应类型
|
||||
if (response.headers['content-type'].includes('application/json')) {
|
||||
// 处理结构化数据(确认选项)
|
||||
const data = await response.json();
|
||||
if (data.data && data.data.choices) {
|
||||
// 显示选择选项
|
||||
showFoodConfirmationOptions(data.data.choices);
|
||||
}
|
||||
} else {
|
||||
// 处理流式文本
|
||||
handleStreamResponse(response);
|
||||
}
|
||||
```
|
||||
|
||||
### 确认选择发送
|
||||
```javascript
|
||||
// 用户选择后发送确认
|
||||
const confirmationRequest = {
|
||||
conversationId: "user123-1234567890",
|
||||
messages: [{ role: "user", content: "我选择记录烤鱼" }],
|
||||
selectedChoiceId: "food_0",
|
||||
confirmationData: {
|
||||
selectedOption: selectedFoodOption,
|
||||
imageUrl: originalImageUrl
|
||||
},
|
||||
stream: true // 第二阶段可以使用流式
|
||||
};
|
||||
```
|
||||
Reference in New Issue
Block a user