test-parse.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // Test parseRichOutline logic with a sample AI response
  2. const sampleResponse = `<think>
  3. The user wants a JSON output for a children's story book titled "小星探险". They want a chapter-level outline (genLevel=1), with chapters each having number, title, summary, keyPoints, estimatedWords. The story should be about 1000 words total, suitable for children.
  4. We need to produce a valid JSON with a "chapters" array and each object must have those fields. The total estimated words sum should be close to 1000 words, distributed among chapters. Possibly 5 chapters, each around 200 words? Let's decide on maybe 5 chapters. Or maybe 4 chapters. Let's aim for 5 chapters with each approx 200 words, sum 1000.
  5. Need to keep it short and child-friendly. Provide Chinese titles and summaries.
  6. The user didn't specify any constraints besides returning JSON. There's no disallowed content. So we can comply.
  7. We should ensure JSON is valid and includes necessary fields: number (int), title (string), summary (string), keyPoints (array of strings), estimatedWords (int). Provide values that make sense for a children's story about a little star's adventure. Each chapter should have a title that appeals to children and a brief summary of what happens in that chapter. keyPoints could highlight the main themes or lessons of each chapter.
  8. We need to think about what makes a good children's story structure. Typically, stories have a beginning, middle, and end. For a short story of about 1000 words with say 5 chapters, we could do:
  9. - Chapter 1: Introduction - introduce the little star and their world
  10. - Chapter 2: Inciting incident - the star decides to explore
  11. - Chapter 3: Journey - the star meets friends and faces challenges
  12. - Chapter 4: Climax - the star overcomes a big challenge
  13. - Chapter 5: Resolution - the star returns home, learned something important
  14. We also need to think about estimatedWords distribution. If we have about 1000 words total and 5 chapters, that's about 200 words per chapter. But some chapters might need more words to develop the story properly. Perhaps 150-250 words per chapter.
  15. Now let's construct the JSON:
  16. {
  17. "mainTheme": "小星探险:一颗小星星在夜空中探险,寻找回家的路并收获友谊与勇气",
  18. "structureLogic": "以小星的探险旅程为线索,按事件发展顺序推进,适合儿童理解",
  19. "chapters": [
  20. {
  21. "number": 1,
  22. "title": "小星的家",
  23. "summary": "在遥远的夜空中,有一颗名叫小星的小星星。她和月亮是邻居,每天晚上,月亮都会给小星讲地上的故事,小星听得入迷,渴望有一天能去探险。",
  24. "keyPoints": ["小星的身份和住所", "小星与月亮的关系", "小星对探险的渴望"],
  25. "estimatedWords": 180
  26. },
  27. {
  28. "number": 2,
  29. "title": "离开家乡",
  30. "summary": "一天晚上,小星决定离开自己的家,开始探险。她告别月亮,向地球的方向飞去。途中,她看见了闪闪的星星伙伴们,大家鼓励她勇敢前行。",
  31. "keyPoints": ["小星决定探险", "告别月亮", "遇见星星伙伴"],
  32. "estimatedWords": 200
  33. },
  34. {
  35. "number": 3,
  36. "title": "结交新朋友",
  37. "summary": "小星降落到地球上的一棵大树上,结识了一只萤火虫朋友——亮亮。亮亮带领小星认识森林里的其他动物朋友,包括小兔子跳跳和小鸟飞飞。大家一起唱歌跳舞,度过了快乐的时光。",
  38. "keyPoints": ["遇见萤火虫亮亮", "认识森林动物朋友", "快乐的友谊时光"],
  39. "estimatedWords": 220
  40. },
  41. {
  42. "number": 4,
  43. "title": "遇到困难",
  44. "summary": "天色渐暗,小星发现自己找不到回家的路了。她有些害怕和焦急。朋友们围在她身边安慰她,告诉她不要放弃,要相信自己。",
  45. "keyPoints": ["小星迷路", "朋友的鼓励", "不放弃的精神"],
  46. "estimatedWords": 180
  47. },
  48. {
  49. "number": 5,
  50. "title": "找到回家的路",
  51. "summary": "在朋友们的鼓励下,小星抬头望向天空,看到了熟悉的北十字星。她顺着星光指引的方向飞去,最终回到了自己的家。小星感谢朋友们,并答应会常常来看望他们。",
  52. "keyPoints": ["北十字星指引方向", "小星成功回家", "友谊的延续"],
  53. "estimatedWords": 220
  54. }
  55. ]
  56. }
  57. `;
  58. function parseRichOutline(jsonStr: string): any | null {
  59. console.log('[Test] 原始响应长度:', jsonStr.length);
  60. if (jsonStr.length > 2000) {
  61. console.log('[Test] 响应开头:', jsonStr.substring(0, 300));
  62. console.log('[Test] 响应结尾:', jsonStr.substring(jsonStr.length - 300));
  63. }
  64. try {
  65. let cleaned = jsonStr.trim();
  66. // 移除思考标签内容(修复:indexOf('') → indexOf('</think>'))
  67. const thinkEnd = cleaned.indexOf('</think>');
  68. console.log('[Test] thinkEnd index:', thinkEnd);
  69. if (thinkEnd !== -1) {
  70. cleaned = cleaned.substring(thinkEnd + 8).trim();
  71. console.log('[Test] After removing think tag, cleaned start:', cleaned.substring(0, 200));
  72. } else {
  73. console.log('[Test] 未找到 </think> 标签');
  74. }
  75. // 策略1:尝试直接解析
  76. try {
  77. const data = JSON.parse(cleaned);
  78. if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
  79. console.log('[Test] 策略1成功:直接解析');
  80. return data;
  81. }
  82. } catch { console.log('[Test] 策略1失败'); }
  83. // 策略2:移除 markdown 代码块
  84. cleaned = cleaned.replace(/```json\s*/g, '').replace(/```\s*/g, '');
  85. try {
  86. const data = JSON.parse(cleaned);
  87. if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
  88. console.log('[Test] 策略2成功:移除代码块');
  89. return data;
  90. }
  91. } catch { console.log('[Test] 策略2失败'); }
  92. // 策略3:正则提取 JSON 对象
  93. const match = cleaned.match(/\{[\s\S]*\}/);
  94. if (match) {
  95. try {
  96. const data = JSON.parse(match[0]);
  97. if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
  98. console.log('[Test] 策略3成功:正则提取');
  99. return data;
  100. }
  101. console.log('[Test] JSON结构异常:', JSON.stringify(data).substring(0, 200));
  102. } catch (e: any) {
  103. console.log('[Test] 正则提取失败:', e.message);
  104. }
  105. } else {
  106. console.log('[Test] 未找到 JSON 对象');
  107. }
  108. // 策略4:尝试找最后一个合法的 JSON 对象
  109. const allMatches = cleaned.match(/\{[\s\S]*?\}/g) || [];
  110. console.log('[Test] Found', allMatches.length, 'JSON objects');
  111. for (let i = allMatches.length - 1; i >= 0; i--) {
  112. try {
  113. const data = JSON.parse(allMatches[i]);
  114. if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
  115. console.log('[Test] 策略4成功:倒数第', allMatches.length - i, '个对象');
  116. return data;
  117. }
  118. } catch { /* continue */ }
  119. }
  120. return null;
  121. } catch (e: any) {
  122. console.error('[Test] 解析异常:', e.message);
  123. return null;
  124. }
  125. }
  126. const result = parseRichOutline(sampleResponse);
  127. console.log('\n=== Final Result ===');
  128. console.log(result ? `Success! Chapters: ${result.chapters?.length}` : 'Failed to parse');