/** * 大纲解析器 * 容错解析LLM返回的大纲JSON */ export interface OutlineData { mainTheme: string; structureLogic: string; chapters: Array<{ number: number; title: string; summary: string; keyPoints: string[]; estimatedWords: number; }>; } export function parseOutline(jsonStr: string): OutlineData | null { if (!jsonStr || typeof jsonStr !== 'string') { console.error('[OutlineParser] 输入为空或非字符串'); return null; } try { let data: any; // 策略1:直接解析(最理想情况,LLM 直接输出纯 JSON) let cleanedStr = jsonStr.trim(); // 清理思考标签 cleanedStr = cleanedStr.replace(/^[\s\S]*?<\/?blockquote>[\s\S]*/gi, ''); const thinkEndIdx = cleanedStr.indexOf(''); if (thinkEndIdx !== -1) { cleanedStr = cleanedStr.substring(thinkEndIdx + 8).trim(); } try { data = JSON.parse(cleanedStr); } catch { // 策略2:提取 JSON 对象(处理 LLM 加了 markdown 标记或多余文字) const match = cleanedStr.match(/\{[\s\S]*\}/); if (!match) { console.error('[OutlineParser] 未找到 JSON 对象'); return null; } try { data = JSON.parse(match[0]); } catch (parseErr) { console.error('[OutlineParser] JSON 解析失败:', parseErr); return null; } } // 验证必要字段 if (!data || typeof data !== 'object') { console.error('[OutlineParser] 解析结果非对象'); return null; } if (!Array.isArray(data.chapters)) { console.error('[OutlineParser] chapters 字段缺失或非数组'); // 尝试兼容:若顶层就是章节数组 if (Array.isArray(data)) { data = { chapters: data }; } else { return null; } } return { mainTheme: data.mainTheme || '主题待定', structureLogic: data.structureLogic || '由浅入深', chapters: data.chapters.map((c: any, i: number) => ({ number: c.number || i + 1, title: c.title || `第${i + 1}章`, summary: typeof c.summary === 'string' ? c.summary : '', keyPoints: Array.isArray(c.keyPoints) ? c.keyPoints : [], estimatedWords: typeof c.estimatedWords === 'number' ? c.estimatedWords : 1000, })), }; } catch (err) { console.error('[OutlineParser] 未知错误:', err); return null; } }