feat(nutrition): 优化营养成分表分析功能并移除流式显示
- 移除流式分析文本显示,简化用户界面 - 修复ImagePicker媒体类型配置,使用数组格式 - 简化API响应处理逻辑,直接使用服务端返回数据 - 移除旧格式转换函数,统一使用新的API响应格式 - 清理冗余状态变量和UI组件,提升代码可维护性
This commit is contained in:
@@ -5,10 +5,7 @@ import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import {
|
||||
analyzeNutritionImage,
|
||||
analyzeNutritionLabelStream,
|
||||
convertNewApiResultToOldFormat,
|
||||
type NutritionAnalysisResponse,
|
||||
type NutritionItem,
|
||||
type NutritionLabelAnalysisResult
|
||||
type NutritionAnalysisResponse
|
||||
} from '@/services/nutritionLabelAnalysis';
|
||||
import { triggerLightHaptic } from '@/utils/haptics';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
@@ -40,8 +37,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
const [imageUri, setImageUri] = useState<string | null>(null);
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||
const [showImagePreview, setShowImagePreview] = useState(false);
|
||||
const [analysisText, setAnalysisText] = useState<string>('');
|
||||
const [analysisResult, setAnalysisResult] = useState<NutritionLabelAnalysisResult | null>(null);
|
||||
const [newAnalysisResult, setNewAnalysisResult] = useState<NutritionAnalysisResponse | null>(null);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
@@ -94,7 +89,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
triggerLightHaptic();
|
||||
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
mediaTypes: ['images'],
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 0.8,
|
||||
@@ -102,9 +97,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
|
||||
if (!result.canceled && result.assets[0]) {
|
||||
setImageUri(result.assets[0].uri);
|
||||
setAnalysisText(''); // 清除之前的分析文本
|
||||
setAnalysisResult(null); // 清除之前的分析结果
|
||||
setNewAnalysisResult(null); // 清除新的分析结果
|
||||
setNewAnalysisResult(null); // 清除之前的分析结果
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,7 +106,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
triggerLightHaptic();
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
mediaTypes: ['images'],
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
quality: 0.8,
|
||||
@@ -121,9 +114,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
|
||||
if (!result.canceled && result.assets[0]) {
|
||||
setImageUri(result.assets[0].uri);
|
||||
setAnalysisText(''); // 清除之前的分析文本
|
||||
setAnalysisResult(null); // 清除之前的分析结果
|
||||
setNewAnalysisResult(null); // 清除新的分析结果
|
||||
setNewAnalysisResult(null); // 清除之前的分析结果
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,7 +131,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
|
||||
// 清理状态
|
||||
setIsAnalyzing(false);
|
||||
setAnalysisText('');
|
||||
|
||||
// 触觉反馈
|
||||
triggerLightHaptic();
|
||||
@@ -154,8 +144,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
if (isAnalyzing) return;
|
||||
|
||||
setIsAnalyzing(true);
|
||||
setAnalysisText('');
|
||||
setAnalysisResult(null);
|
||||
|
||||
// 延迟滚动到分析结果区域,给UI一些时间更新
|
||||
setTimeout(() => {
|
||||
@@ -169,17 +157,8 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
{ imageUri: uri },
|
||||
{
|
||||
onChunk: (chunk: string) => {
|
||||
setAnalysisText(prev => {
|
||||
const newText = prev + chunk;
|
||||
// 在接收到第一个文本块时滚动到分析结果区域
|
||||
if (isFirstChunk && newText.length > 0) {
|
||||
isFirstChunk = false;
|
||||
setTimeout(() => {
|
||||
scrollViewRef.current?.scrollToEnd({ animated: true });
|
||||
}, 100);
|
||||
}
|
||||
return newText;
|
||||
});
|
||||
// 流式分析暂时保留,但不再显示文本
|
||||
console.log('[NUTRITION_ANALYSIS] Stream chunk:', chunk);
|
||||
},
|
||||
onEnd: () => {
|
||||
setIsAnalyzing(false);
|
||||
@@ -189,7 +168,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
onError: (error: any) => {
|
||||
console.error('[NUTRITION_ANALYSIS] Analysis failed:', error);
|
||||
setIsAnalyzing(false);
|
||||
setAnalysisText('');
|
||||
streamAbortRef.current = null;
|
||||
|
||||
// 如果是用户主动取消,不显示错误提示
|
||||
@@ -202,7 +180,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
} catch (error) {
|
||||
console.error('[NUTRITION_ANALYSIS] Analysis error:', error);
|
||||
setIsAnalyzing(false);
|
||||
setAnalysisText('');
|
||||
Alert.alert('分析失败', '无法识别成分表,请尝试拍摄更清晰的照片');
|
||||
}
|
||||
}, [isAnalyzing]);
|
||||
@@ -212,8 +189,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
if (isAnalyzing || isUploading) return;
|
||||
|
||||
setIsUploading(true);
|
||||
setAnalysisText('');
|
||||
setAnalysisResult(null);
|
||||
setNewAnalysisResult(null);
|
||||
|
||||
// 延迟滚动到分析结果区域,给UI一些时间更新
|
||||
@@ -239,21 +214,8 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
console.log('[NUTRITION_ANALYSIS] API响应:', analysisResponse);
|
||||
|
||||
if (analysisResponse.success && analysisResponse.data) {
|
||||
// 转换为旧格式以便与现有UI兼容
|
||||
const oldFormatResult = convertNewApiResultToOldFormat(analysisResponse, uploadResult.url);
|
||||
|
||||
if (oldFormatResult) {
|
||||
setAnalysisResult(oldFormatResult);
|
||||
setNewAnalysisResult(analysisResponse);
|
||||
|
||||
// 生成分析文本
|
||||
const analysisText = analysisResponse.data
|
||||
.map((item: NutritionItem) => `**${item.name}**: ${item.value}\n${item.analysis}`)
|
||||
.join('\n\n');
|
||||
setAnalysisText(analysisText);
|
||||
} else {
|
||||
throw new Error('无法解析API返回结果');
|
||||
}
|
||||
// 直接使用服务端返回的数据,不做任何转换
|
||||
setNewAnalysisResult(analysisResponse);
|
||||
} else {
|
||||
throw new Error(analysisResponse.message || '分析失败');
|
||||
}
|
||||
@@ -261,7 +223,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
console.error('[NUTRITION_ANALYSIS] 新API分析失败:', error);
|
||||
setIsUploading(false);
|
||||
setIsAnalyzing(false);
|
||||
setAnalysisText('');
|
||||
|
||||
// 显示错误提示
|
||||
Alert.alert(
|
||||
@@ -318,7 +279,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* 开始分析按钮 */}
|
||||
{!isAnalyzing && !isUploading && !analysisText && (
|
||||
{!isAnalyzing && !isUploading && !newAnalysisResult && (
|
||||
<TouchableOpacity
|
||||
style={styles.analyzeButton}
|
||||
onPress={() => startNewAnalysis(imageUri)}
|
||||
@@ -334,8 +295,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
style={styles.deleteImageButton}
|
||||
onPress={() => {
|
||||
setImageUri(null);
|
||||
setAnalysisText('');
|
||||
setAnalysisResult(null);
|
||||
setNewAnalysisResult(null);
|
||||
triggerLightHaptic();
|
||||
}}
|
||||
@@ -373,31 +332,6 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 流式分析文本区域 */}
|
||||
{analysisText && (
|
||||
<View style={styles.analysisTextCard}>
|
||||
<View style={styles.analysisTextHeader}>
|
||||
<Text style={styles.analysisTextTitle}>分析结果</Text>
|
||||
{isAnalyzing && (
|
||||
<TouchableOpacity
|
||||
style={styles.cancelAnalysisButton}
|
||||
onPress={cancelAnalysis}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Ionicons name="close-outline" size={16} color="#FF4444" />
|
||||
<Text style={styles.cancelAnalysisText}>停止</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.analysisTextContent}>{analysisText}</Text>
|
||||
{isAnalyzing && (
|
||||
<View style={styles.streamingIndicator}>
|
||||
<ActivityIndicator size="small" color={Colors.light.primary} />
|
||||
<Text style={styles.streamingText}>正在分析中...</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 新API营养成分详细分析结果 */}
|
||||
{newAnalysisResult && newAnalysisResult.success && newAnalysisResult.data && (
|
||||
@@ -428,7 +362,7 @@ export default function NutritionLabelAnalysisScreen() {
|
||||
)}
|
||||
|
||||
{/* 加载状态 */}
|
||||
{isAnalyzing && !analysisText && !isUploading && (
|
||||
{isAnalyzing && !newAnalysisResult && !isUploading && (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.light.primary} />
|
||||
<Text style={styles.loadingText}>正在分析成分表...</Text>
|
||||
@@ -725,66 +659,6 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '600',
|
||||
marginLeft: 6,
|
||||
},
|
||||
// 分析文本卡片样式
|
||||
analysisTextCard: {
|
||||
backgroundColor: Colors.light.background,
|
||||
margin: 16,
|
||||
borderRadius: 16,
|
||||
padding: 20,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
elevation: 3,
|
||||
},
|
||||
analysisTextHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
analysisTextTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: Colors.light.text,
|
||||
},
|
||||
cancelAnalysisButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 12,
|
||||
backgroundColor: 'rgba(255,68,68,0.1)',
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255,68,68,0.3)',
|
||||
},
|
||||
cancelAnalysisText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#FF4444',
|
||||
},
|
||||
analysisTextContent: {
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
color: Colors.light.text,
|
||||
marginBottom: 12,
|
||||
},
|
||||
streamingIndicator: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 8,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
streamingText: {
|
||||
fontSize: 14,
|
||||
color: Colors.light.textSecondary,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
// 营养成分详细分析卡片样式
|
||||
nutritionDetailsCard: {
|
||||
backgroundColor: Colors.light.background,
|
||||
|
||||
Reference in New Issue
Block a user