返回 oh-my-ppt
source-plan.ts
根目录 / src / main / thinking / source-plan.ts
1 import path from 'path'
2 import type { SourceDocumentPlan } from '@shared/generation'
3
4 const MAX_THINKING_PAGE_OUTLINE_CHARS = 520
5 const MAX_THINKING_PAGE_KEY_POINTS = 10
6
7 const compactThinkingOutlineText = (value: string): string => value.replace(/\s+/g, ' ').trim()
8
9 const readThinkingPageField = (blockLines: string[], field: string): string => {
10 const pattern = new RegExp(`^-\\s*${field}\\s*:`, 'i')
11 return (
12 blockLines
13 .find((line) => pattern.test(line.trim()))
14 ?.replace(pattern, '')
15 .trim() || ''
16 )
17 }
18
19 export const buildThinkingPageOutline = (blockLines: string[]): string => {
20 const objective = readThinkingPageField(blockLines, 'Objective')
21 const summaryLines: string[] = []
22 const keyPointLines: string[] = []
23 let collectingSummary = false
24
25 for (const rawLine of blockLines) {
26 const line = rawLine.trim()
27 if (!line) {
28 collectingSummary = false
29 continue
30 }
31 if (/^-\s*(Role|Objective)\s*:/i.test(line)) continue
32 if (/^-\s+/.test(line)) {
33 keyPointLines.push(line.replace(/^-\s+/, '').trim())
34 collectingSummary = false
35 continue
36 }
37 if (!collectingSummary && summaryLines.length > 0) continue
38 summaryLines.push(line)
39 collectingSummary = true
40 }
41
42 const parts = [
43 objective,
44 compactThinkingOutlineText(summaryLines.join(' ')),
45 ...keyPointLines.slice(0, MAX_THINKING_PAGE_KEY_POINTS).map(compactThinkingOutlineText)
46 ].filter(Boolean)
47
48 return compactThinkingOutlineText(parts.join(';')).slice(0, MAX_THINKING_PAGE_OUTLINE_CHARS)
49 }
50
51 export function buildThinkingSourcePlan(
52 thinkingMd: string,
53 thinkingDocumentPath: string
54 ): SourceDocumentPlan | null {
55 const lines = thinkingMd.split(/\r?\n/)
56 const headings: Array<{ pageNumber: number; title: string; lineNumber: number }> = []
57
58 lines.forEach((line, index) => {
59 const match = line.match(/^##\s*Page\s+(\d+)\s*:\s*(.+)$/)
60 if (!match) return
61 const pageNumber = Number.parseInt(match[1], 10)
62 const title = match[2].trim()
63 if (!Number.isFinite(pageNumber) || !title) return
64 headings.push({ pageNumber, title, lineNumber: index + 1 })
65 })
66
67 if (headings.length === 0) return null
68
69 return {
70 version: 1,
71 confidence: 'high',
72 sourceDocumentPath: thinkingDocumentPath,
73 sourceDocumentName: path.basename(thinkingDocumentPath),
74 pageSkeleton: headings.map((heading, index) => {
75 const next = headings[index + 1]
76 const lineStart = heading.lineNumber
77 const lineEnd = Math.max(lineStart, next ? next.lineNumber - 1 : lines.length)
78 const blockLines = lines.slice(heading.lineNumber, lineEnd)
79 const roleText = readThinkingPageField(blockLines, 'Role')
80 const pageOutline = buildThinkingPageOutline(blockLines)
81 const roleBasis = `${heading.title}\n${roleText}`
82 const role = /chapter|section|divider|cover|title|agenda|章节|过渡|封面|目录|标题/i.test(
83 roleBasis
84 )
85 ? 'chapter-divider'
86 : 'content'
87
88 return {
89 pageNumber: heading.pageNumber,
90 title: heading.title,
91 role,
92 sourceHeading: `Page ${heading.pageNumber}: ${heading.title}`,
93 headingLevel: 2,
94 lineStart,
95 lineEnd,
96 reason: pageOutline || roleText || 'Thinking page section'
97 }
98 })
99 }
100 }
101
101 lines TYPESCRIPT