import { api, postTextStream, type TextStreamCallbacks } from '@/services/api'; import type { CreateDietRecordDto, MealType } from './dietRecords'; export interface NutritionLabelData { energy: number; // 能量 (kJ/千卡) protein: number; // 蛋白质 (g) fat: number; // 脂肪 (g) carbohydrate: number; // 碳水化合物 (g) sodium: number; // 钠 (mg) fiber?: number; // 膳食纤维 (g) sugar?: number; // 糖 (g) servingSize?: string; // 每份量 } export interface NutritionLabelAnalysisResult { id: string; imageUri: string; nutritionData: NutritionLabelData; confidence: number; analyzedAt: string; foodName?: string; // 识别出的食物名称 brand?: string; // 品牌 } export interface NutritionLabelAnalysisRequest { imageUri: string; } // 新API返回的营养成分项 export interface NutritionItem { key: string; name: string; value: string; analysis: string; } // 新API返回的响应格式 export interface NutritionAnalysisResponse { success: boolean; data: NutritionItem[]; message?: string; // 仅在失败时返回 } // 新API请求参数 export interface NutritionAnalysisRequest { imageUrl: string; } /** * 分析营养成分表(非流式) */ export async function analyzeNutritionLabel(request: NutritionLabelAnalysisRequest): Promise { return api.post('/ai-coach/nutrition-label-analysis', request); } /** * 流式分析营养成分表 */ export async function analyzeNutritionLabelStream( request: NutritionLabelAnalysisRequest, callbacks: TextStreamCallbacks ) { const body = { imageUri: request.imageUri, stream: true }; return postTextStream('/ai-coach/nutrition-label-analysis', body, callbacks, { timeoutMs: 120000 }); } /** * 保存成分表分析结果到饮食记录 */ export async function saveNutritionLabelToDietRecord( analysisResult: NutritionLabelAnalysisResult, mealType: MealType ): Promise { const dietRecordData: CreateDietRecordDto = { mealType, foodName: analysisResult.foodName || '成分表分析食物', foodDescription: `品牌: ${analysisResult.brand || '未知'}`, portionDescription: analysisResult.nutritionData.servingSize || '100g', estimatedCalories: analysisResult.nutritionData.energy, proteinGrams: analysisResult.nutritionData.protein, carbohydrateGrams: analysisResult.nutritionData.carbohydrate, fatGrams: analysisResult.nutritionData.fat, fiberGrams: analysisResult.nutritionData.fiber, sugarGrams: analysisResult.nutritionData.sugar, sodiumMg: analysisResult.nutritionData.sodium, source: 'vision', mealTime: new Date().toISOString(), imageUrl: analysisResult.imageUri, aiAnalysisResult: analysisResult, }; return api.post('/diet-records', dietRecordData); } /** * 分析营养成分表图片(新API) * 需要先上传图片到COS获取URL,然后调用此接口 */ export async function analyzeNutritionImage(request: NutritionAnalysisRequest): Promise { return api.post('/diet-records/analyze-nutrition-image', request); } /** * 将新API的分析结果转换为旧格式,以便与现有UI兼容 */ export function convertNewApiResultToOldFormat( newResult: NutritionAnalysisResponse, imageUri: string ): NutritionLabelAnalysisResult | null { if (!newResult.success || !newResult.data || newResult.data.length === 0) { return null; } // 从新API结果中提取营养数据 const nutritionData: NutritionLabelData = { energy: 0, protein: 0, fat: 0, carbohydrate: 0, sodium: 0, fiber: 0, sugar: 0, }; // 查找各个营养素的值并转换为数字 newResult.data.forEach(item => { const valueStr = item.value; // 提取数字部分 const numericValue = parseFloat(valueStr.replace(/[^\d.]/g, '')); switch (item.key) { case 'energy_kcal': // 如果是千焦,转换为千卡 (1千焦 ≈ 0.239千卡) if (valueStr.includes('千焦')) { nutritionData.energy = Math.round(numericValue * 0.239); } else { nutritionData.energy = numericValue; } break; case 'protein': nutritionData.protein = numericValue; break; case 'fat': nutritionData.fat = numericValue; break; case 'carbohydrate': nutritionData.carbohydrate = numericValue; break; case 'sodium': nutritionData.sodium = numericValue; break; case 'fiber': nutritionData.fiber = numericValue; break; case 'sugar': nutritionData.sugar = numericValue; break; } }); return { id: Date.now().toString(), imageUri, nutritionData, confidence: 0.9, // 新API没有提供置信度,使用默认值 analyzedAt: new Date().toISOString(), foodName: '营养成分表分析', brand: '未知', }; }