Files
digital-pilates/docs/food-confirmation-implementation.md
richarjiang 05a00236bc feat: 扩展饮食记录确认流程,支持选择选项和响应处理
- 在教练页面中新增AI选择选项和食物确认选项的数据结构
- 扩展消息结构以支持选择选项和交互类型
- 实现非流式JSON响应的处理逻辑,支持用户确认选择
- 添加选择选项的UI组件,提升用户交互体验
- 更新样式以适应新功能,确保视觉一致性
2025-08-18 17:29:19 +08:00

6.9 KiB
Raw Blame History

饮食记录确认流程客户端实现

概述

已完成对客户端的改动支持饮食记录确认流程的新API结构。实现了对非流式JSON响应的处理能够正确显示食物选择选项并支持用户确认选择。

主要改动

1. 数据结构扩展

app/(tabs)/coach.tsx 中添加了新的类型定义:

// AI选择选项数据结构
type AiChoiceOption = {
  id: string;
  label: string;
  value: any;
  recommended?: boolean;
};

// 餐次类型
type MealType = 'breakfast' | 'lunch' | 'dinner' | 'snack';

// 食物确认选项数据结构  
type FoodConfirmationOption = {
  id: string;
  label: string;
  foodName: string;
  portion: string;
  calories: number;
  mealType: MealType;
  nutritionData: {
    proteinGrams?: number;
    carbohydrateGrams?: number;
    fatGrams?: number;
    fiberGrams?: number;
  };
};

// AI响应数据结构
type AiResponseData = {
  content: string;
  choices?: AiChoiceOption[];
  interactionType?: 'text' | 'food_confirmation' | 'selection';
  pendingData?: any;
  context?: any;
};

2. 消息结构扩展

扩展了 ChatMessage 类型以支持新的字段:

type ChatMessage = {
  id: string;
  role: Role;
  content: string;
  attachments?: MessageAttachment[];
  choices?: AiChoiceOption[];     // 新增:选择选项
  interactionType?: string;       // 新增:交互类型
  pendingData?: any;             // 新增:待确认数据
  context?: any;                 // 新增:上下文信息
};

3. 响应处理逻辑

支持非流式JSON响应

修改了 sendRequestInternal 函数以检测和处理非流式JSON响应

// 如果是非流式请求直接调用API并处理响应
if (!body.stream) {
  try {
    const response = await api.post<{ conversationId?: string; data?: AiResponseData; text?: string }>('/api/ai-coach/chat', body);
    
    // 处理响应
    if (response.data) {
      // 结构化响应(可能包含选择选项)
      const assistantMsg: ChatMessage = {
        id: assistantId,
        role: 'assistant',
        content: response.data.content,
        choices: response.data.choices,
        interactionType: response.data.interactionType,
        pendingData: response.data.pendingData,
        context: response.data.context,
      };
      // ... 更新消息状态
    }
    // ...
  }
}

流式响应中的JSON检测

在流式响应处理中添加了JSON检测逻辑

const onChunk = (chunk: string) => {
  // 尝试解析是否为JSON结构化数据可能是确认选项
  try {
    const parsed = JSON.parse(chunk);
    if (parsed && parsed.data && parsed.data.choices) {
      // 处理结构化响应(包含选择选项)
      // ... 创建带选择选项的消息
      return;
    }
  } catch {
    // 不是JSON继续作为普通文本处理
  }
  // ... 正常的文本流处理
};

4. 选择选项UI组件

renderBubbleContent 函数中添加了选择选项的渲染逻辑:

// 检查是否有选择选项需要显示
if (item.choices && item.choices.length > 0 && item.interactionType === 'food_confirmation') {
  return (
    <View style={{ gap: 12 }}>
      <Markdown style={markdownStyles} mergeStyle>
        {item.content || ''}
      </Markdown>
      <View style={styles.choicesContainer}>
        {item.choices.map((choice) => (
          <TouchableOpacity
            key={choice.id}
            style={[
              styles.choiceButton,
              choice.recommended && styles.choiceButtonRecommended
            ]}
            onPress={() => handleChoiceSelection(choice, item)}
          >
            <View style={styles.choiceContent}>
              <Text style={[
                styles.choiceLabel,
                choice.recommended && styles.choiceLabelRecommended
              ]}>
                {choice.label}
              </Text>
              {choice.recommended && (
                <View style={styles.recommendedBadge}>
                  <Text style={styles.recommendedText}>推荐</Text>
                </View>
              )}
            </View>
          </TouchableOpacity>
        ))}
      </View>
    </View>
  );
}

5. 选择确认逻辑

实现了 handleChoiceSelection 函数处理用户选择:

async function handleChoiceSelection(choice: AiChoiceOption, message: ChatMessage) {
  try {
    // 构建确认请求
    const confirmationText = `我选择记录${choice.label}`;
    
    // 发送确认消息,包含选择的数据
    await sendStreamWithConfirmation(confirmationText, choice.id, {
      selectedOption: choice.value,
      imageUrl: message.pendingData?.imageUrl
    });
  } catch (e: any) {
    Alert.alert('选择失败', e?.message || '选择失败,请重试');
  }
}

6. 确认数据发送

添加了 sendStreamWithConfirmation 函数:

async function sendStreamWithConfirmation(text: string, selectedChoiceId: string, confirmationData: any) {
  const historyForServer = convertToServerMessages(messages);
  const cid = ensureConversationId();
  const body = {
    conversationId: cid,
    messages: [...historyForServer, { role: 'user' as const, content: text }],
    selectedChoiceId,
    confirmationData,
    stream: false, // 确认阶段使用非流式
  };
  
  await sendRequestInternal(body, text);
}

7. UI样式

添加了选择选项相关的样式:

choicesContainer: {
  gap: 8,
},
choiceButton: {
  backgroundColor: 'rgba(255,255,255,0.9)',
  borderWidth: 1,
  borderColor: 'rgba(187,242,70,0.3)',
  borderRadius: 12,
  padding: 12,
},
choiceButtonRecommended: {
  borderColor: 'rgba(187,242,70,0.6)',
  backgroundColor: 'rgba(187,242,70,0.1)',
},
// ... 其他相关样式

使用流程

  1. 用户发送饮食图片:用户点击"#记饮食"并上传食物图片
  2. AI识别返回选项服务器返回食物识别结果和确认选项非流式JSON
  3. 显示选择选项:客户端渲染食物选择选项,推荐项目带有特殊标识
  4. 用户选择确认:用户点击某个选项
  5. 发送确认数据:客户端发送确认请求,包含选择的食物数据
  6. 记录完成:服务器记录饮食数据并返回确认消息

兼容性

  • 保持对现有流式文本响应的完全兼容
  • 自动检测响应类型JSON vs 文本流)
  • 旧的饮食记录方式(文字输入)继续正常工作
  • 缓存和会话管理支持新的消息结构

技术要点

  1. 响应类型检测客户端能够自动识别JSON结构化响应和普通文本流
  2. 状态管理:新增状态正确地保存到本地缓存
  3. 用户体验:选择选项有清晰的视觉反馈,推荐选项突出显示
  4. 错误处理:完整的错误处理机制,确保用户体验流畅
  5. 性能优化:避免不必要的重渲染,保持界面响应性

这个实现完全符合API文档的要求支持饮食记录的两阶段确认流程并保持了与现有功能的兼容性。