@cloud411716/fancy-webnovel 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.js +201 -15
- package/cordis.patch.yml +5 -0
- package/docs/douyin-plugin.md +118 -0
- package/docs/material-library/README.md +404 -0
- package/docs/material-library/import.js +437 -0
- package/docs/material-library/prompt.md +370 -0
- package/docs/material-library/queries.surql +94 -0
- package/docs/material-library/sample.jsonl +14 -0
- package/docs/material-library/schema.surql +46 -0
- package/docs/mcp-profile.example.yml +24 -0
- package/docs/story-graph-design.md +602 -0
- package/docs/story-graph-validation.md +81 -0
- package/docs/workspace-mcp.md +284 -0
- package/index.js +11 -7
- package/lib/atomic-write.js +109 -0
- package/lib/chapter-order.js +100 -0
- package/lib/confirmation.js +80 -0
- package/lib/db-init.js +123 -0
- package/lib/deployed-state.js +11 -11
- package/lib/direction-labels.js +265 -0
- package/lib/error-codes.js +31 -31
- package/lib/infra.js +29 -4
- package/lib/material-db.js +149 -0
- package/lib/path-guard.js +150 -0
- package/lib/projects.js +400 -0
- package/lib/scraper-utils.js +140 -0
- package/lib/surreal-server.js +338 -0
- package/lib/topic-decision.js +860 -0
- package/lib/topic-fields.js +251 -0
- package/lib/topic-schema.js +225 -0
- package/lib/workspace.js +501 -0
- package/package.json +29 -4
- package/plugins/fancy-analyze/analyze-definitions.md +519 -0
- package/plugins/fancy-analyze/index.js +1128 -77
- package/plugins/fancy-analyze/ingest-coverage.js +107 -0
- package/plugins/fancy-analyze/schema-analyze.md +355 -107
- package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
- package/plugins/fancy-analyze/summary-progress.js +71 -7
- package/plugins/fancy-analyze/templates.js +437 -139
- package/plugins/fancy-bootstrap/index.js +276 -131
- package/plugins/fancy-bootstrap/scripts/init.js +59 -25
- package/plugins/fancy-bootstrap/templates.js +35 -18
- package/plugins/fancy-db/index.js +172 -0
- package/plugins/fancy-douyin/deploy.js +134 -0
- package/plugins/fancy-douyin/index.js +551 -0
- package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
- package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
- package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
- package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
- package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
- package/plugins/fancy-douyin/state.js +197 -0
- package/plugins/fancy-douyin/templates.js +127 -0
- package/plugins/fancy-export/exporter.js +805 -0
- package/plugins/fancy-export/index.js +63 -0
- package/plugins/fancy-import/import-driver.js +29 -6
- package/plugins/fancy-import/index.js +13 -5
- package/plugins/fancy-import/prompts/01-import-prompt.md +39 -14
- package/plugins/fancy-import/templates.js +17 -8
- package/plugins/fancy-material/exporter.js +153 -0
- package/plugins/fancy-material/genres.js +256 -0
- package/plugins/fancy-material/index.js +835 -0
- package/plugins/fancy-material/inventory.js +143 -0
- package/plugins/fancy-material/kinds.js +313 -0
- package/plugins/fancy-material/queue.js +55 -0
- package/plugins/fancy-material/scripts/query.js +254 -0
- package/plugins/fancy-material/templates.js +258 -0
- package/plugins/fancy-material/tool-deploy.js +138 -0
- package/plugins/fancy-material/validate.js +149 -0
- package/plugins/fancy-project/index.js +133 -0
- package/plugins/fancy-scan/existing-block.js +100 -0
- package/plugins/fancy-scan/index.js +410 -21
- package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
- package/plugins/fancy-scan/scripts/append-block.js +725 -0
- package/plugins/fancy-scan/scripts/report-validate.js +824 -0
- package/plugins/fancy-scan/scripts/run-scan.js +169 -22
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +145 -16
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +179 -49
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +38 -20
- package/plugins/fancy-scan/templates/blocks.js +155 -0
- package/plugins/fancy-scan/templates/platforms.js +372 -0
- package/plugins/fancy-scan/templates/report-schema.js +90 -0
- package/plugins/fancy-scan/templates.js +545 -146
- package/plugins/fancy-scan/tool-deploy.js +335 -0
- package/plugins/fancy-scan/wait-turn.js +46 -0
- package/plugins/fancy-story-builder/db/README.md +106 -0
- package/plugins/fancy-story-builder/db/client.js +228 -0
- package/plugins/fancy-story-builder/db/commands.js +145 -0
- package/plugins/fancy-story-builder/db/ids.js +39 -0
- package/plugins/fancy-story-builder/db/import.js +798 -0
- package/plugins/fancy-story-builder/db/project.js +299 -0
- package/plugins/fancy-story-builder/db/query.js +361 -0
- package/plugins/fancy-story-builder/db/schema.js +417 -0
- package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
- package/plugins/fancy-story-builder/flesh-output.js +19 -6
- package/plugins/fancy-story-builder/index.js +4105 -396
- package/plugins/fancy-story-builder/patch-driver.js +390 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +29 -19
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +72 -10
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +40 -5
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +37 -0
- package/plugins/fancy-story-builder/purpose-template.md +42 -66
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -56
- package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
- package/plugins/fancy-story-builder/references/glossary.md +15 -6
- package/plugins/fancy-story-builder/references/index.md +97 -6
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +1 -1
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +25 -7
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +5 -1
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +6 -2
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +15 -5
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +1 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +2 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +1 -1
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +19 -1
- package/plugins/fancy-story-builder/schema-setting.json +1100 -0
- package/plugins/fancy-story-builder/schema-setting.md +201 -293
- package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +4 -1
- package/plugins/fancy-story-builder/scripts/flesh-validate.js +135 -12
- package/plugins/fancy-story-builder/scripts/graph-utils.js +146 -47
- package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +100 -4
- package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +5 -1
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +158 -17
- package/plugins/fancy-story-builder/scripts/stage-batches.js +36 -2
- package/plugins/fancy-story-builder/scripts/stage-contract.js +12 -0
- package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +1 -1
- package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
- package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
- package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
- package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
- package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
- package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
- package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
- package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
- package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
- package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
- package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
- package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
- package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
- package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
- package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
- package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
- package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
- package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
- package/plugins/fancy-story-builder/storydb/README.md +271 -0
- package/plugins/fancy-story-builder/storydb/cli.js +458 -0
- package/plugins/fancy-story-builder/storydb/client.js +351 -0
- package/plugins/fancy-story-builder/storydb/compile.js +452 -0
- package/plugins/fancy-story-builder/storydb/context.js +288 -0
- package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
- package/plugins/fancy-story-builder/storydb/driver.js +106 -0
- package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
- package/plugins/fancy-story-builder/storydb/export.js +175 -0
- package/plugins/fancy-story-builder/storydb/ids.js +31 -0
- package/plugins/fancy-story-builder/storydb/impact.js +170 -0
- package/plugins/fancy-story-builder/storydb/import.js +559 -0
- package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
- package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
- package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
- package/plugins/fancy-story-builder/storydb/stage.js +426 -0
- package/plugins/fancy-story-builder/storydb/store.js +891 -0
- package/plugins/fancy-story-builder/storydb/tools.js +592 -0
- package/plugins/fancy-story-builder/storydb/validation.js +183 -0
- package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
- package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
- package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
- package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
- package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
- package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
- package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +267 -44
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +121 -19
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +129 -21
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/266/344/273/226/345/256/236/344/275/223/346/250/241/346/235/277.json +62 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +76 -17
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +56 -8
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +63 -13
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +34 -6
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +81 -18
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +202 -42
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +160 -27
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +217 -41
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +215 -41
- package/plugins/fancy-story-builder/templates.js +272 -74
- package/plugins/fancy-story-builder/wiki-references.js +429 -93
- package/plugins/fancy-story-builder/writing-driver.js +336 -9
- package/plugins/fancy-topic/index.js +414 -61
- package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
- package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
- package/plugins/fancy-topic/scripts/select-topic.js +106 -63
- package/plugins/fancy-topic/templates.js +225 -122
- package/plugins/fancy-topic/tool-deploy.js +200 -0
- package/lib/safe-rm.js +0 -94
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genre discovery for the material library.
|
|
3
|
+
*
|
|
4
|
+
* Two sources:
|
|
5
|
+
* 1. A topic_decision file: use the genres listed under its "选题方向" section.
|
|
6
|
+
* 2. Fanqie scan data: the `## <genre> — N 本` headings contain the most
|
|
7
|
+
* complete genre list available in the workspace.
|
|
8
|
+
*
|
|
9
|
+
* If neither exists (for example when printing the prompt outside a workspace),
|
|
10
|
+
* fall back to the known Fanqie genre list. That keeps `prompt` usable as a
|
|
11
|
+
* standalone all-genres prompt.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { workspacePaths } from '#workspace';
|
|
17
|
+
|
|
18
|
+
export const FALLBACK_GENRES = [
|
|
19
|
+
'西方奇幻',
|
|
20
|
+
'东方仙侠',
|
|
21
|
+
'科幻末世',
|
|
22
|
+
'都市日常',
|
|
23
|
+
'都市修真',
|
|
24
|
+
'都市高武',
|
|
25
|
+
'历史古代',
|
|
26
|
+
'战神赘婿',
|
|
27
|
+
'都市种田',
|
|
28
|
+
'传统玄幻',
|
|
29
|
+
'历史脑洞',
|
|
30
|
+
'悬疑脑洞',
|
|
31
|
+
'都市脑洞',
|
|
32
|
+
'玄幻脑洞',
|
|
33
|
+
'悬疑灵异',
|
|
34
|
+
'抗战谍战',
|
|
35
|
+
'游戏体育',
|
|
36
|
+
'动漫衍生',
|
|
37
|
+
'男频衍生',
|
|
38
|
+
'古风世情',
|
|
39
|
+
'女频衍生',
|
|
40
|
+
'玄幻言情',
|
|
41
|
+
'种田',
|
|
42
|
+
'年代',
|
|
43
|
+
'现言脑洞',
|
|
44
|
+
'宫斗宅斗',
|
|
45
|
+
'古言脑洞',
|
|
46
|
+
'快穿',
|
|
47
|
+
'青春甜宠',
|
|
48
|
+
'星光璀璨',
|
|
49
|
+
'女频悬疑',
|
|
50
|
+
'职场婚恋',
|
|
51
|
+
'豪门总裁',
|
|
52
|
+
'民国言情',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function unique(values) {
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const value of values) {
|
|
59
|
+
const text = String(value || '').trim();
|
|
60
|
+
if (!text || seen.has(text)) continue;
|
|
61
|
+
seen.add(text);
|
|
62
|
+
out.push(text);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function cleanGenre(value) {
|
|
68
|
+
return String(value || '')
|
|
69
|
+
.replace(/\*\*/g, '')
|
|
70
|
+
.replace(/^[\s\-*|]+/, '')
|
|
71
|
+
.replace(/[\s\-*|]+$/, '')
|
|
72
|
+
.replace(/[((].*?[))]/g, '')
|
|
73
|
+
.trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function splitGenres(value) {
|
|
77
|
+
return unique(String(value || '')
|
|
78
|
+
.split(/[、,,/;;]/)
|
|
79
|
+
.map(cleanGenre)
|
|
80
|
+
.filter((item) => item && !['全部', '通用', '其他', '未知'].includes(item)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* List all topic_decision_*.md files in <workspace>/扫榜结果, newest first.
|
|
85
|
+
*
|
|
86
|
+
* The material command lets the user pick one when several reports exist;
|
|
87
|
+
* it never merges the genre lists of multiple reports.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} root workspace directory
|
|
90
|
+
* @returns {string[]} absolute file paths
|
|
91
|
+
*/
|
|
92
|
+
export function listTopicDecisionFiles(root) {
|
|
93
|
+
const dir = workspacePaths(root).scan;
|
|
94
|
+
let names;
|
|
95
|
+
try {
|
|
96
|
+
names = fs.readdirSync(dir);
|
|
97
|
+
} catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
return names
|
|
101
|
+
.filter((name) => /^topic_decision_.*\.md$/i.test(name))
|
|
102
|
+
.sort()
|
|
103
|
+
.reverse()
|
|
104
|
+
.map((name) => path.join(dir, name))
|
|
105
|
+
.filter((file) => {
|
|
106
|
+
try {
|
|
107
|
+
const stat = fs.lstatSync(file);
|
|
108
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find the latest topic_decision_*.md in <workspace>/扫榜结果.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} root workspace directory
|
|
119
|
+
* @returns {string|null}
|
|
120
|
+
*/
|
|
121
|
+
export function latestTopicDecision(root) {
|
|
122
|
+
return listTopicDecisionFiles(root)[0] || null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract genres from the "选题方向" section of a topic_decision report.
|
|
127
|
+
*
|
|
128
|
+
* Supports both the current markdown table (`方向 | 题材 | ...`) and simple
|
|
129
|
+
* bullet lines such as `- **题材**:仙侠、都市高武`.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} text
|
|
132
|
+
* @returns {string[]}
|
|
133
|
+
*/
|
|
134
|
+
export function parseTopicDecisionGenres(text) {
|
|
135
|
+
const source = String(text || '');
|
|
136
|
+
const lines = source.split(/\r?\n/);
|
|
137
|
+
let start = -1;
|
|
138
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
139
|
+
if (/^##\s*(?:[一二三四五六七八九十]+、)?选题方向(?:建议)?\s*$/.test(lines[i].trim())) {
|
|
140
|
+
start = i;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (start === -1) return [];
|
|
145
|
+
|
|
146
|
+
let end = lines.length;
|
|
147
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
148
|
+
if (/^##\s+/.test(lines[i])) {
|
|
149
|
+
end = i;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const section = lines.slice(start + 1, end);
|
|
154
|
+
const genres = [];
|
|
155
|
+
|
|
156
|
+
// Markdown table: find the header that contains 题材.
|
|
157
|
+
let header = null;
|
|
158
|
+
let genreIndex = -1;
|
|
159
|
+
for (const line of section) {
|
|
160
|
+
const trimmed = line.trim();
|
|
161
|
+
if (!trimmed.startsWith('|')) continue;
|
|
162
|
+
const cells = trimmed.split('|').slice(1, -1).map((cell) => cell.trim());
|
|
163
|
+
if (!cells.length) continue;
|
|
164
|
+
const idx = cells.findIndex((cell) => cell.replace(/\*\*/g, '').trim() === '题材');
|
|
165
|
+
if (idx !== -1) {
|
|
166
|
+
header = cells;
|
|
167
|
+
genreIndex = idx;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (header && genreIndex !== -1) {
|
|
172
|
+
let inBody = false;
|
|
173
|
+
for (const line of section) {
|
|
174
|
+
const trimmed = line.trim();
|
|
175
|
+
if (!trimmed.startsWith('|')) continue;
|
|
176
|
+
const cells = trimmed.split('|').slice(1, -1).map((cell) => cell.trim());
|
|
177
|
+
if (!inBody) {
|
|
178
|
+
if (cells.every((cell) => /^:?-{2,}:?$/.test(cell.replace(/\s/g, '')))) {
|
|
179
|
+
inBody = true;
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (cells.length <= genreIndex) continue;
|
|
184
|
+
genres.push(...splitGenres(cells[genreIndex]));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Bullet fallback, including lines like `- **题材**:仙侠`.
|
|
189
|
+
for (const line of section) {
|
|
190
|
+
const match = line.match(/^\s*[-*]\s*(?:\*\*)?题材(?:\*\*)?\s*[::]\s*(.+)$/);
|
|
191
|
+
if (match) genres.push(...splitGenres(match[1]));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return unique(genres);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Read the genre headings from Fanqie scan files.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} root workspace directory
|
|
201
|
+
* @returns {string[]}
|
|
202
|
+
*/
|
|
203
|
+
export function parseFanqieGenres(root) {
|
|
204
|
+
const dir = workspacePaths(root).scan;
|
|
205
|
+
let names;
|
|
206
|
+
try {
|
|
207
|
+
names = fs.readdirSync(dir);
|
|
208
|
+
} catch {
|
|
209
|
+
return [...FALLBACK_GENRES];
|
|
210
|
+
}
|
|
211
|
+
const files = names
|
|
212
|
+
.filter((name) => /^番茄.*\.md$/i.test(name))
|
|
213
|
+
.sort();
|
|
214
|
+
const genres = [];
|
|
215
|
+
for (const name of files) {
|
|
216
|
+
let text;
|
|
217
|
+
try {
|
|
218
|
+
text = fs.readFileSync(path.join(dir, name), 'utf8');
|
|
219
|
+
} catch {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
for (const line of text.split(/\r?\n/)) {
|
|
223
|
+
const match = line.match(/^##\s+(.+?)\s+—\s+\d+\s+本\s*$/);
|
|
224
|
+
if (match) genres.push(...splitGenres(match[1]));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return genres.length ? unique(genres) : [...FALLBACK_GENRES];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Resolve the genres for a generation run.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} root workspace directory
|
|
234
|
+
* @param {{topicFile?: string|null}} [options] explicit report selected by the user
|
|
235
|
+
* @returns {{mode: 'topic_decision'|'all_genres', genres: string[], topicFile: string|null}}
|
|
236
|
+
*/
|
|
237
|
+
export function resolveMaterialGenres(root, options = {}) {
|
|
238
|
+
const topicFile = options.topicFile || latestTopicDecision(root);
|
|
239
|
+
if (topicFile) {
|
|
240
|
+
try {
|
|
241
|
+
const genres = parseTopicDecisionGenres(fs.readFileSync(topicFile, 'utf8'));
|
|
242
|
+
if (genres.length) return { mode: 'topic_decision', genres, topicFile };
|
|
243
|
+
} catch { /* fall through to all genres */ }
|
|
244
|
+
}
|
|
245
|
+
return { mode: 'all_genres', genres: parseFanqieGenres(root), topicFile: null };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* All known genres, used by the `prompt` subcommand.
|
|
250
|
+
*
|
|
251
|
+
* @param {string|null} root
|
|
252
|
+
* @returns {string[]}
|
|
253
|
+
*/
|
|
254
|
+
export function allKnownGenres(root) {
|
|
255
|
+
return root ? parseFanqieGenres(root) : [...FALLBACK_GENRES];
|
|
256
|
+
}
|