| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- // Test parseRichOutline logic with a sample AI response
- const sampleResponse = `<think>
- 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.
- 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.
- Need to keep it short and child-friendly. Provide Chinese titles and summaries.
- The user didn't specify any constraints besides returning JSON. There's no disallowed content. So we can comply.
- 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.
- 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:
- - Chapter 1: Introduction - introduce the little star and their world
- - Chapter 2: Inciting incident - the star decides to explore
- - Chapter 3: Journey - the star meets friends and faces challenges
- - Chapter 4: Climax - the star overcomes a big challenge
- - Chapter 5: Resolution - the star returns home, learned something important
- 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.
- Now let's construct the JSON:
- {
- "mainTheme": "小星探险:一颗小星星在夜空中探险,寻找回家的路并收获友谊与勇气",
- "structureLogic": "以小星的探险旅程为线索,按事件发展顺序推进,适合儿童理解",
- "chapters": [
- {
- "number": 1,
- "title": "小星的家",
- "summary": "在遥远的夜空中,有一颗名叫小星的小星星。她和月亮是邻居,每天晚上,月亮都会给小星讲地上的故事,小星听得入迷,渴望有一天能去探险。",
- "keyPoints": ["小星的身份和住所", "小星与月亮的关系", "小星对探险的渴望"],
- "estimatedWords": 180
- },
- {
- "number": 2,
- "title": "离开家乡",
- "summary": "一天晚上,小星决定离开自己的家,开始探险。她告别月亮,向地球的方向飞去。途中,她看见了闪闪的星星伙伴们,大家鼓励她勇敢前行。",
- "keyPoints": ["小星决定探险", "告别月亮", "遇见星星伙伴"],
- "estimatedWords": 200
- },
- {
- "number": 3,
- "title": "结交新朋友",
- "summary": "小星降落到地球上的一棵大树上,结识了一只萤火虫朋友——亮亮。亮亮带领小星认识森林里的其他动物朋友,包括小兔子跳跳和小鸟飞飞。大家一起唱歌跳舞,度过了快乐的时光。",
- "keyPoints": ["遇见萤火虫亮亮", "认识森林动物朋友", "快乐的友谊时光"],
- "estimatedWords": 220
- },
- {
- "number": 4,
- "title": "遇到困难",
- "summary": "天色渐暗,小星发现自己找不到回家的路了。她有些害怕和焦急。朋友们围在她身边安慰她,告诉她不要放弃,要相信自己。",
- "keyPoints": ["小星迷路", "朋友的鼓励", "不放弃的精神"],
- "estimatedWords": 180
- },
- {
- "number": 5,
- "title": "找到回家的路",
- "summary": "在朋友们的鼓励下,小星抬头望向天空,看到了熟悉的北十字星。她顺着星光指引的方向飞去,最终回到了自己的家。小星感谢朋友们,并答应会常常来看望他们。",
- "keyPoints": ["北十字星指引方向", "小星成功回家", "友谊的延续"],
- "estimatedWords": 220
- }
- ]
- }
- `;
- function parseRichOutline(jsonStr: string): any | null {
- console.log('[Test] 原始响应长度:', jsonStr.length);
- if (jsonStr.length > 2000) {
- console.log('[Test] 响应开头:', jsonStr.substring(0, 300));
- console.log('[Test] 响应结尾:', jsonStr.substring(jsonStr.length - 300));
- }
- try {
- let cleaned = jsonStr.trim();
- // 移除思考标签内容(修复:indexOf('') → indexOf('</think>'))
- const thinkEnd = cleaned.indexOf('</think>');
- console.log('[Test] thinkEnd index:', thinkEnd);
- if (thinkEnd !== -1) {
- cleaned = cleaned.substring(thinkEnd + 8).trim();
- console.log('[Test] After removing think tag, cleaned start:', cleaned.substring(0, 200));
- } else {
- console.log('[Test] 未找到 </think> 标签');
- }
- // 策略1:尝试直接解析
- try {
- const data = JSON.parse(cleaned);
- if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
- console.log('[Test] 策略1成功:直接解析');
- return data;
- }
- } catch { console.log('[Test] 策略1失败'); }
- // 策略2:移除 markdown 代码块
- cleaned = cleaned.replace(/```json\s*/g, '').replace(/```\s*/g, '');
- try {
- const data = JSON.parse(cleaned);
- if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
- console.log('[Test] 策略2成功:移除代码块');
- return data;
- }
- } catch { console.log('[Test] 策略2失败'); }
- // 策略3:正则提取 JSON 对象
- const match = cleaned.match(/\{[\s\S]*\}/);
- if (match) {
- try {
- const data = JSON.parse(match[0]);
- if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
- console.log('[Test] 策略3成功:正则提取');
- return data;
- }
- console.log('[Test] JSON结构异常:', JSON.stringify(data).substring(0, 200));
- } catch (e: any) {
- console.log('[Test] 正则提取失败:', e.message);
- }
- } else {
- console.log('[Test] 未找到 JSON 对象');
- }
- // 策略4:尝试找最后一个合法的 JSON 对象
- const allMatches = cleaned.match(/\{[\s\S]*?\}/g) || [];
- console.log('[Test] Found', allMatches.length, 'JSON objects');
- for (let i = allMatches.length - 1; i >= 0; i--) {
- try {
- const data = JSON.parse(allMatches[i]);
- if (data.chapters && Array.isArray(data.chapters) && data.chapters.length > 0) {
- console.log('[Test] 策略4成功:倒数第', allMatches.length - i, '个对象');
- return data;
- }
- } catch { /* continue */ }
- }
- return null;
- } catch (e: any) {
- console.error('[Test] 解析异常:', e.message);
- return null;
- }
- }
- const result = parseRichOutline(sampleResponse);
- console.log('\n=== Final Result ===');
- console.log(result ? `Success! Chapters: ${result.chapters?.length}` : 'Failed to parse');
|