Files
digital-pilates/services/nutritionLabelAnalysis.ts
richarjiang 5013464a2c feat(nutrition): 添加营养成分表拍照分析功能
新增营养成分表拍照识别功能,用户可通过拍摄食物包装上的成分表自动解析营养信息:
- 创建成分表分析页面,支持拍照/选择图片和结果展示
- 集成新的营养成分分析API,支持图片上传和流式分析
- 在营养雷达卡片中添加成分表分析入口
- 更新应用版本至1.0.19
2025-10-16 12:16:08 +08:00

174 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<NutritionLabelAnalysisResult> {
return api.post<NutritionLabelAnalysisResult>('/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<any> {
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<NutritionAnalysisResponse> {
return api.post<NutritionAnalysisResponse>('/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: '未知',
};
}