feat: 实现饮食记录确认流程

- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。
- 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。
- 扩展DTO层,新增相关数据结构以支持确认流程。
- 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。
- 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
This commit is contained in:
richarjiang
2025-08-18 18:59:36 +08:00
parent 485ba1f67c
commit ede5730647
7 changed files with 903 additions and 30 deletions

View File

@@ -7,7 +7,7 @@ import { AiConversation } from './models/ai-conversation.model';
import { PostureAssessment } from './models/posture-assessment.model';
import { UserProfile } from '../users/models/user-profile.model';
import { UsersService } from '../users/users.service';
import { DietAnalysisService, DietAnalysisResult } from './services/diet-analysis.service';
import { DietAnalysisService, DietAnalysisResult, FoodRecognitionResult, FoodConfirmationOption } from './services/diet-analysis.service';
const SYSTEM_PROMPT = `作为一名资深的健康管家兼营养分析师Nutrition Analyst和健身教练我拥有丰富的专业知识包括但不限于
@@ -210,7 +210,9 @@ export class AiCoachService {
userContent: string;
systemNotice?: string;
imageUrls?: string[];
}): Promise<Readable> {
selectedChoiceId?: string;
confirmationData?: any;
}): Promise<Readable | { type: 'structured'; data: any }> {
// 解析指令(如果以 # 开头)
const commandResult = this.parseCommand(params.userContent);
@@ -235,15 +237,15 @@ export class AiCoachService {
messages.unshift({ role: 'system', content: weightContext });
}
} else if (commandResult.command === 'diet') {
// 使用饮食分析服务处理图片
if (params.imageUrls) {
const dietAnalysisResult = await this.dietAnalysisService.analyzeDietImageEnhanced(params.imageUrls);
// 如果AI确定应该记录饮食则自动添加到数据库
const createDto = await this.dietAnalysisService.processDietRecord(
// 处理饮食记录指令
if (params.selectedChoiceId && params.confirmationData) {
// 第二阶段:用户已确认选择,记录饮食
// confirmationData应该包含 { selectedOption: FoodConfirmationOption, imageUrl: string }
const { selectedOption, imageUrl } = params.confirmationData;
const createDto = await this.dietAnalysisService.createDietRecordFromConfirmation(
params.userId,
dietAnalysisResult,
params.imageUrls[0]
selectedOption,
imageUrl || ''
);
if (createDto) {
@@ -254,13 +256,70 @@ export class AiCoachService {
}
params.systemNotice = `系统提示:已成功为您记录了${createDto.foodName}的饮食信息(${createDto.portionDescription || ''},约${createDto.estimatedCalories || 0}卡路里)。`;
}
messages.push({
role: 'user',
content: `用户通过拍照记录饮食AI分析结果\n${dietAnalysisResult.analysisText}`
});
messages.unshift({ role: 'system', content: this.dietAnalysisService.buildEnhancedDietAnalysisPrompt() });
messages.push({
role: 'user',
content: `用户确认记录饮食:${selectedOption.label}`
});
messages.unshift({ role: 'system', content: this.dietAnalysisService.buildEnhancedDietAnalysisPrompt() });
}
} else if (params.imageUrls) {
// 第一阶段:图片识别,返回确认选项
const recognitionResult = await this.dietAnalysisService.recognizeFoodForConfirmation(params.imageUrls);
if (recognitionResult.recognizedItems.length > 0) {
// 返回结构化数据供用户确认
const choices = recognitionResult.recognizedItems.map(item => ({
id: item.id,
label: item.label,
value: item,
recommended: recognitionResult.recognizedItems.indexOf(item) === 0 // 第一个选项为推荐
}));
const responseContent = `我识别到了以下食物,请选择要记录的内容:\n\n${recognitionResult.analysisText}`;
// 保存AI助手的响应消息到数据库
await AiMessage.create({
conversationId: params.conversationId,
userId: params.userId,
role: RoleType.Assistant,
content: responseContent,
metadata: {
model: this.model,
interactionType: 'food_confirmation',
choices: choices.length
},
});
// 更新对话的最后消息时间
await AiConversation.update(
{ lastMessageAt: new Date(), title: this.deriveTitleIfEmpty(responseContent) },
{ where: { id: params.conversationId, userId: params.userId } }
);
return {
type: 'structured',
data: {
content: responseContent,
choices,
interactionType: 'food_confirmation',
pendingData: {
imageUrl: params.imageUrls[0],
recognitionResult
},
context: {
command: 'diet',
step: 'confirmation'
}
}
};
} else {
// 识别失败,返回普通文本响应
messages.push({
role: 'user',
content: `用户尝试记录饮食但识别失败:${recognitionResult.analysisText}`
});
}
}
}