|
|
@@ -299,20 +299,10 @@ export class PlannerAgent extends BaseAgent {
|
|
|
|
|
|
private findOutlineNode(volumeOutline: string, chapterNumber: number): string | undefined {
|
|
|
const lines = volumeOutline.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
|
- const exactHeadingPatterns = [
|
|
|
- new RegExp(`^#+\\s*Chapter\\s*${chapterNumber}\\b`, "i"),
|
|
|
- new RegExp(`^#+\\s*第\\s*${chapterNumber}\\s*章`),
|
|
|
- ];
|
|
|
- const exactInlinePatterns = [
|
|
|
- new RegExp(`^(?:[-*]\\s+)?(?:\\*\\*)?Chapter\\s*${chapterNumber}(?:[::-])?(?:\\*\\*)?\\s*(.+)$`, "i"),
|
|
|
- new RegExp(`^(?:[-*]\\s+)?(?:\\*\\*)?第\\s*${chapterNumber}\\s*章(?:[::-])?(?:\\*\\*)?\\s*(.+)$`),
|
|
|
- ];
|
|
|
|
|
|
for (let index = 0; index < lines.length; index += 1) {
|
|
|
const line = lines[index]!;
|
|
|
- const match = exactInlinePatterns
|
|
|
- .map((pattern) => line.match(pattern))
|
|
|
- .find((result): result is RegExpMatchArray => Boolean(result));
|
|
|
+ const match = this.matchExactOutlineLine(line, chapterNumber);
|
|
|
if (!match) continue;
|
|
|
|
|
|
const inlineContent = this.cleanOutlineContent(match[1]);
|
|
|
@@ -326,13 +316,6 @@ export class PlannerAgent extends BaseAgent {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const exactHeading = lines.find((line) => exactHeadingPatterns.some((pattern) => pattern.test(line)));
|
|
|
- if (exactHeading) {
|
|
|
- const headingIndex = lines.indexOf(exactHeading);
|
|
|
- const nextLine = lines[headingIndex + 1];
|
|
|
- return nextLine && !nextLine.startsWith("#") ? nextLine : exactHeading.replace(/^#+\s*/, "");
|
|
|
- }
|
|
|
-
|
|
|
for (let index = 0; index < lines.length; index += 1) {
|
|
|
const line = lines[index]!;
|
|
|
const match = this.matchRangeOutlineLine(line, chapterNumber);
|
|
|
@@ -349,11 +332,32 @@ export class PlannerAgent extends BaseAgent {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const rangeHeading = lines.find((line) => this.matchRangeOutlineHeading(line, chapterNumber));
|
|
|
- if (rangeHeading) {
|
|
|
- const headingIndex = lines.indexOf(rangeHeading);
|
|
|
- const nextLine = lines[headingIndex + 1];
|
|
|
- return nextLine && !nextLine.startsWith("#") ? nextLine : rangeHeading.replace(/^#+\s*/, "");
|
|
|
+ for (let index = 0; index < lines.length; index += 1) {
|
|
|
+ const line = lines[index]!;
|
|
|
+ if (!this.isOutlineAnchorLine(line)) continue;
|
|
|
+
|
|
|
+ const exactMatch = this.matchAnyExactOutlineLine(line);
|
|
|
+ if (exactMatch) {
|
|
|
+ const inlineContent = this.cleanOutlineContent(exactMatch[1]);
|
|
|
+ if (inlineContent) {
|
|
|
+ return inlineContent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const rangeMatch = this.matchAnyRangeOutlineLine(line);
|
|
|
+ if (rangeMatch) {
|
|
|
+ const inlineContent = this.cleanOutlineContent(rangeMatch[3]);
|
|
|
+ if (inlineContent) {
|
|
|
+ return inlineContent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const nextContent = this.findNextOutlineContent(lines, index + 1);
|
|
|
+ if (nextContent) {
|
|
|
+ return nextContent;
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
return this.extractFirstDirective(volumeOutline);
|
|
|
@@ -369,17 +373,18 @@ export class PlannerAgent extends BaseAgent {
|
|
|
private findNextOutlineContent(lines: ReadonlyArray<string>, startIndex: number): string | undefined {
|
|
|
for (let index = startIndex; index < lines.length; index += 1) {
|
|
|
const line = lines[index]!;
|
|
|
- if (!line || line.startsWith("#")) {
|
|
|
+ if (!line) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- if (
|
|
|
- /^(?:[-*]\s+)?(?:\*\*)?Chapter\s*\d+(?:[::-])?(?:\*\*)?\s*$/i.test(line)
|
|
|
- || /^(?:[-*]\s+)?(?:\*\*)?第\s*\d+\s*章(?:[::-])?(?:\*\*)?\s*$/.test(line)
|
|
|
- ) {
|
|
|
+ if (this.isOutlineAnchorLine(line)) {
|
|
|
return undefined;
|
|
|
}
|
|
|
|
|
|
+ if (line.startsWith("#")) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
const cleaned = this.cleanOutlineContent(line);
|
|
|
if (cleaned) {
|
|
|
return cleaned;
|
|
|
@@ -389,32 +394,52 @@ export class PlannerAgent extends BaseAgent {
|
|
|
return undefined;
|
|
|
}
|
|
|
|
|
|
- private matchRangeOutlineLine(line: string, chapterNumber: number): RegExpMatchArray | undefined {
|
|
|
+ private matchExactOutlineLine(line: string, chapterNumber: number): RegExpMatchArray | undefined {
|
|
|
const patterns = [
|
|
|
- /^(?:[-*]\s+)?(?:\*\*)?Chapter\s*(\d+)\s*[-~–—]\s*(\d+)(?:[::-])?(?:\*\*)?\s*(.+)$/i,
|
|
|
- /^(?:[-*]\s+)?(?:\*\*)?第\s*(\d+)\s*[-~–—]\s*(\d+)\s*章(?:[::-])?(?:\*\*)?\s*(.+)$/i,
|
|
|
+ new RegExp(`^(?:#+\\s*)?(?:[-*]\\s+)?(?:\\*\\*)?Chapter\\s*${chapterNumber}(?!\\d|\\s*[-~–—]\\s*\\d)(?:[::-])?(?:\\*\\*)?\\s*(.*)$`, "i"),
|
|
|
+ new RegExp(`^(?:#+\\s*)?(?:[-*]\\s+)?(?:\\*\\*)?第\\s*${chapterNumber}\\s*章(?!\\d|\\s*[-~–—]\\s*\\d)(?:[::-])?(?:\\*\\*)?\\s*(.*)$`),
|
|
|
];
|
|
|
|
|
|
- for (const pattern of patterns) {
|
|
|
- const match = line.match(pattern);
|
|
|
- if (!match) continue;
|
|
|
- if (this.isChapterWithinRange(match[1], match[2], chapterNumber)) {
|
|
|
- return match;
|
|
|
- }
|
|
|
+ return patterns
|
|
|
+ .map((pattern) => line.match(pattern))
|
|
|
+ .find((result): result is RegExpMatchArray => Boolean(result));
|
|
|
+ }
|
|
|
+
|
|
|
+ private matchAnyExactOutlineLine(line: string): RegExpMatchArray | undefined {
|
|
|
+ const patterns = [
|
|
|
+ /^(?:#+\s*)?(?:[-*]\s+)?(?:\*\*)?Chapter\s*\d+(?!\s*[-~–—]\s*\d)(?:[::-])?(?:\*\*)?\s*(.*)$/i,
|
|
|
+ /^(?:#+\s*)?(?:[-*]\s+)?(?:\*\*)?第\s*\d+\s*章(?!\s*[-~–—]\s*\d)(?:[::-])?(?:\*\*)?\s*(.*)$/i,
|
|
|
+ ];
|
|
|
+
|
|
|
+ return patterns
|
|
|
+ .map((pattern) => line.match(pattern))
|
|
|
+ .find((result): result is RegExpMatchArray => Boolean(result));
|
|
|
+ }
|
|
|
+
|
|
|
+ private matchRangeOutlineLine(line: string, chapterNumber: number): RegExpMatchArray | undefined {
|
|
|
+ const match = this.matchAnyRangeOutlineLine(line);
|
|
|
+ if (!match) return undefined;
|
|
|
+ if (this.isChapterWithinRange(match[1], match[2], chapterNumber)) {
|
|
|
+ return match;
|
|
|
}
|
|
|
|
|
|
return undefined;
|
|
|
}
|
|
|
|
|
|
- private matchRangeOutlineHeading(line: string, chapterNumber: number): boolean {
|
|
|
- return this.matchRangeOutlineLine(line, chapterNumber) !== undefined
|
|
|
- || [
|
|
|
- /^#+\s*Chapter\s*(\d+)\s*[-~–—]\s*(\d+)\b/i,
|
|
|
- /^#+\s*第\s*(\d+)\s*[-~–—]\s*(\d+)\s*章/i,
|
|
|
- ].some((pattern) => {
|
|
|
- const match = line.match(pattern);
|
|
|
- return match ? this.isChapterWithinRange(match[1], match[2], chapterNumber) : false;
|
|
|
- });
|
|
|
+ private matchAnyRangeOutlineLine(line: string): RegExpMatchArray | undefined {
|
|
|
+ const patterns = [
|
|
|
+ /^(?:#+\s*)?(?:[-*]\s+)?(?:\*\*)?Chapter\s*(\d+)\s*[-~–—]\s*(\d+)\b(?:[::-])?(?:\*\*)?\s*(.*)$/i,
|
|
|
+ /^(?:#+\s*)?(?:[-*]\s+)?(?:\*\*)?第\s*(\d+)\s*[-~–—]\s*(\d+)\s*章(?:[::-])?(?:\*\*)?\s*(.*)$/i,
|
|
|
+ ];
|
|
|
+
|
|
|
+ return patterns
|
|
|
+ .map((pattern) => line.match(pattern))
|
|
|
+ .find((result): result is RegExpMatchArray => Boolean(result));
|
|
|
+ }
|
|
|
+
|
|
|
+ private isOutlineAnchorLine(line: string): boolean {
|
|
|
+ return this.matchAnyExactOutlineLine(line) !== undefined
|
|
|
+ || this.matchAnyRangeOutlineLine(line) !== undefined;
|
|
|
}
|
|
|
|
|
|
private isChapterWithinRange(startText: string | undefined, endText: string | undefined, chapterNumber: number): boolean {
|