feat: 实现饮食记录确认流程
- 新增饮食记录确认流程,将自动记录模式升级为用户确认模式,提升用户交互体验。 - 实现两阶段饮食记录流程,支持AI识别食物并生成确认选项,用户选择后记录到数据库并提供营养分析。 - 扩展DTO层,新增相关数据结构以支持确认流程。 - 更新服务层,新增处理确认逻辑的方法,优化饮食记录的创建流程。 - 增强API文档,详细说明新流程及使用建议,确保开发者理解和使用新功能。
This commit is contained in:
@@ -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}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user