fix: preserve complete consultation replies and recover retained history
This commit is contained in:
@@ -122,11 +122,61 @@ describe('teacher text and suggestions transport', () => {
|
||||
|
||||
it('marks multiple envelopes or duplicate fields as ambiguous without showing legacy JSON', () => {
|
||||
const parsed = parseTeacherReply(JSON.stringify(envelope) + '\n{"reply":"第二份","tool":{"kind":"ideas"}}');
|
||||
expect(parsed).toMatchObject({ ...envelope, parseError: expect.any(String) });
|
||||
expect(parsed).toMatchObject({ incomplete: true, quickReplies: [], parseError: expect.any(String) });
|
||||
expect(parsed.reply).not.toContain('tool');
|
||||
expect(parseTeacherReply('{"reply":"第一份","reply":"第二份"}').parseError).toBeTruthy();
|
||||
});
|
||||
|
||||
it('recovers the complete screenshot reply around internal unescaped quotes', () => {
|
||||
const reply = '它早就不是"刚搭好架子"那种阶段了——骨架、玩法、成长线都在跑。\n现在的阶段是「能玩,正在调手感」。';
|
||||
const raw = '{"reply":"' + reply + '","quickReplies":[],"tool":{"kind":"structure","nodes":[{"id":"core","label":"旧结构"}]}}';
|
||||
expect(parseTeacherReply(raw)).toEqual({ reply, quickReplies: [], recovered: true });
|
||||
});
|
||||
|
||||
it.each([
|
||||
'他说"再看一下",然后按 "继续"。',
|
||||
'第一行\n第二行\r\n制表符\t与 "引号" 一起保留。',
|
||||
'示例代码:\n```js\nconst text = "hello";\nif (ready) { show(text); }\n```\n然后继续说明。',
|
||||
])('repairs only prose escaping without rewriting its content: %s', reply => {
|
||||
expect(parseTeacherReply('{"reply":"' + reply + '","quickReplies":["继续"]}'))
|
||||
.toEqual({ reply, quickReplies: ['继续'], recovered: true });
|
||||
});
|
||||
|
||||
it('leaves correctly encoded quotes, backslashes, code and Unicode untouched', () => {
|
||||
const reply = '它早就不是"刚搭好架子"了。\\路径\\\n```json\n{"reply":"代码例子"}\n```\n🍎 继续。';
|
||||
expect(parseTeacherReply(JSON.stringify({ reply, quickReplies: [] })))
|
||||
.toEqual({ reply, quickReplies: [] });
|
||||
});
|
||||
|
||||
it('does not label a quoted prefix or a genuinely interrupted string as complete', () => {
|
||||
for (const raw of [
|
||||
'{"reply":"它早就不是"刚搭好架子',
|
||||
'{"reply":"它早就不是"刚搭好架子"那种阶段了',
|
||||
'{"reply":"它早就不是"',
|
||||
'{"reply":"半句", ordinary words are still part of broken prose',
|
||||
'{"reply":"引号 \\q 无效","quickReplies":[]}',
|
||||
]) {
|
||||
expect(parseTeacherReply(raw)).toMatchObject({ incomplete: true, quickReplies: [], parseError: expect.any(String) });
|
||||
expect(parseTeacherReply(raw).reply).not.toMatch(/它早就不是|半句|ordinary words|无效/);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps complete prose when suggestions fail without swallowing their bytes', () => {
|
||||
for (const tail of ['["断在按钮里', 'null}', '[13]}', '["坏" }']) {
|
||||
const parsed = parseTeacherReply('{"reply":"完整回答,包含 \\"正确引号\\"。","quickReplies":' + tail);
|
||||
expect(parsed).toMatchObject({ reply: '完整回答,包含 "正确引号"。', quickReplies: [], parseError: expect.any(String) });
|
||||
expect(parsed.incomplete).toBeUndefined();
|
||||
expect(parsed.reply).not.toContain('quickReplies');
|
||||
}
|
||||
});
|
||||
|
||||
it('refuses to guess across ambiguous prose/auxiliary boundaries or duplicate bodies', () => {
|
||||
const raw = '{"reply":"代码里有 "text", "tool": {"reply":"不能冒充正文"} 后面还有正文。","quickReplies":[]}';
|
||||
expect(parseTeacherReply(raw)).toMatchObject({ incomplete: true });
|
||||
expect(parseTeacherReply('{"reply":"第一句","reply":"第二句"}')).toMatchObject({ incomplete: true });
|
||||
expect(parseTeacherReply(raw).reply).not.toContain('不能冒充正文');
|
||||
});
|
||||
|
||||
it.each([
|
||||
'{"reply":"正文保留。"},"tool":{"kind":"ideas","items":[]}',
|
||||
'{"reply":"正文保留。"}}',
|
||||
|
||||
Reference in New Issue
Block a user