Skip to content

🐛 JSONL 格式解析错误:JSON 选项内容意外显示在聊天主文本中 #23

@cubxxw

Description

@cubxxw

问题描述

在聊天界面中,AI 返回的响应内容出现了格式化错误。原本应该被解析为选项的 JSONL 格式内容意外显示在了主聊天文本中,导致用户看到原始的 JSON 字符串而不是格式化的内容。

重现步骤

  1. 在聊天界面中发送一个需要 AI 返回推荐选项的消息
  2. AI 回复包含 JSONL 格式的推荐选项
  3. 观察到原始 JSON 内容显示在聊天文本中,而不是被解析为选项按钮

期望行为

  • JSONL 格式的推荐选项应该被正确解析
  • 主聊天文本中不应显示原始 JSON 字符串
  • 解析后的选项应该显示在右侧选项面板中

实际行为

  • 原始 JSON 字符串显示在主聊天文本中
  • 选项解析失败或不完整
  • 用户界面混乱,影响阅读体验

技术分析

根据代码分析,问题出现在 NextStepChat.tsxsplitContentAndOptions 函数中:

问题根因

  1. 反向扫描逻辑缺陷:函数从文本末尾向前扫描 JSON 行,但在遇到第一个空行或非 JSON 行时就停止了
  2. 混合内容处理不当:当 JSONL 选项与主文本内容混合在一起时,解析逻辑无法正确分离
  3. 空行判断过于严格:函数在遇到空行时立即停止扫描,导致前面的有效 JSON 行被遗漏

具体问题位置

文件src/components/NextStepChat.tsx
函数splitContentAndOptions (第63-96行)
关键逻辑:第72行 if (!line) break; 导致扫描过早停止

解决方案

1. 改进 JSONL 解析逻辑

function splitContentAndOptions(raw: string): { main: string; options: NextStepOption[] } {
  if (!raw) return { main: '', options: [] };
  
  const lines = raw.split('\n');
  const collected: NextStepOption[] = [];
  const jsonLineIndices: number[] = [];
  
  // 扫描所有行,识别有效的 JSONL 行
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    if (!line) continue;
    
    try {
      const obj = JSON.parse(line);
      if (
        obj && typeof obj === 'object' &&
        (obj.type === 'deepen' || obj.type === 'next') &&
        typeof obj.content === 'string' &&
        typeof obj.describe === 'string'
      ) {
        collected.push({
          type: obj.type,
          content: obj.content,
          describe: obj.describe
        });
        jsonLineIndices.push(i);
      }
    } catch {
      // 不是 JSON,继续扫描
    }
  }
  
  // 移除识别出的 JSON 行,保留主内容
  const mainLines = lines.filter((_, index) => !jsonLineIndices.includes(index));
  const main = mainLines.join('\n').trim();
  
  return { main, options: collected.slice(0, 6) };
}

2. 增强错误处理

// 添加调试日志
console.log('Raw response:', raw);
const result = splitContentAndOptions(raw);
console.log('Parsed main:', result.main);
console.log('Parsed options:', result.options);

3. 改进流式响应处理

sendMessageInternal 函数中,确保流式响应完成后正确解析:

() => {
  // finalize parse options with improved error handling
  try {
    const { main, options: incoming } = splitContentAndOptions(assembled);
    
    // 更新消息内容,移除 JSON 部分
    setMessages((prev: ChatMessage[]) => 
      prev.map((m: ChatMessage) => 
        m.id === assistantId ? { ...m, content: main || assembled } : m
      )
    );
    
    if (incoming.length) {
      mergeOptions(incoming, assistantId);
    }
  } catch (error) {
    console.error('Failed to parse options:', error);
    // 降级处理:至少显示完整响应
  }
  
  setIsLoading(false);
}

优先级

🔴 高优先级 - 影响核心用户体验,需要立即修复

附加信息

  • 浏览器:所有主流浏览器
  • 组件:NextStepChat
  • 影响范围:所有聊天会话中的选项解析
  • 修复难度:中等(需要重构解析逻辑)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions