@waterwx/dsh-novel-forge 2.0.1-alpha → 2.1.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.
Files changed (183) hide show
  1. package/README.md +368 -362
  2. package/lib/client.js +20340 -19622
  3. package/lib/index.js +11313 -7285
  4. package/lib/types/action-guard.d.ts +61 -0
  5. package/lib/types/ai-scan.d.ts +0 -6
  6. package/lib/types/assets.d.ts +20 -9
  7. package/lib/types/assistant.d.ts +29 -0
  8. package/lib/types/author-assets.d.ts +1 -3
  9. package/lib/types/bookshelf.d.ts +13 -3
  10. package/lib/types/client/api.d.ts +136 -27
  11. package/lib/types/client/chip-prefs.d.ts +36 -0
  12. package/lib/types/client/docx.d.ts +2 -2
  13. package/lib/types/client/llmLive.d.ts +31 -0
  14. package/lib/types/client/locales.d.ts +15 -70
  15. package/lib/types/client/mount.d.ts +0 -2
  16. package/lib/types/client/panel/AiTag.d.ts +1 -0
  17. package/lib/types/client/panel/AssistantTab.d.ts +4 -1
  18. package/lib/types/client/panel/AuthorHome.d.ts +13 -11
  19. package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
  20. package/lib/types/client/panel/CmdPalette.d.ts +12 -0
  21. package/lib/types/client/panel/CreateBookView.d.ts +5 -1
  22. package/lib/types/client/panel/DirectorView.d.ts +7 -0
  23. package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
  24. package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
  25. package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
  26. package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
  27. package/lib/types/client/panel/ModelManager.d.ts +2 -2
  28. package/lib/types/client/panel/NovelPanel.d.ts +6 -1
  29. package/lib/types/client/panel/PageHeader.d.ts +18 -0
  30. package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
  31. package/lib/types/client/panel/SettingsView.d.ts +12 -9
  32. package/lib/types/client/panel/ShelfView.d.ts +10 -1
  33. package/lib/types/client/panel/SubPage.d.ts +27 -0
  34. package/lib/types/client/panel/TensionView.d.ts +18 -0
  35. package/lib/types/client/panel/TimelineView.d.ts +18 -0
  36. package/lib/types/client/panel/helpers.d.ts +5 -3
  37. package/lib/types/client/panel/market-radar-state.d.ts +56 -0
  38. package/lib/types/client/panel/outline-markdown.d.ts +112 -0
  39. package/lib/types/client/panel/status-badge.d.ts +13 -0
  40. package/lib/types/client/panel/storage-parse.d.ts +46 -0
  41. package/lib/types/client/panel/toast.d.ts +2 -0
  42. package/lib/types/client/panel/usePersistentState.d.ts +17 -0
  43. package/lib/types/client/panel/views.d.ts +21 -35
  44. package/lib/types/client/run-status.d.ts +21 -0
  45. package/lib/types/client/sidebar-entry.d.ts +0 -2
  46. package/lib/types/digest-parse.d.ts +29 -0
  47. package/lib/types/docx-core.d.ts +16 -0
  48. package/lib/types/docx.d.ts +2 -3
  49. package/lib/types/engine.d.ts +150 -103
  50. package/lib/types/global-assets.d.ts +6 -0
  51. package/lib/types/home.d.ts +11 -0
  52. package/lib/types/index.d.ts +26 -12
  53. package/lib/types/llm-live.d.ts +70 -0
  54. package/lib/types/llm-retry.d.ts +38 -0
  55. package/lib/types/market-radar-scan.d.ts +18 -0
  56. package/lib/types/market-radar-sources.d.ts +31 -0
  57. package/lib/types/migrate-output.d.ts +25 -0
  58. package/lib/types/model-capability.d.ts +119 -0
  59. package/lib/types/model-request.d.ts +36 -0
  60. package/lib/types/output-budget.d.ts +86 -0
  61. package/lib/types/plotline-align.d.ts +35 -0
  62. package/lib/types/prompt-slots.d.ts +57 -0
  63. package/lib/types/prompting.d.ts +10 -0
  64. package/lib/types/protocol.d.ts +841 -601
  65. package/lib/types/revision.d.ts +76 -0
  66. package/lib/types/routes.d.ts +9 -3
  67. package/lib/types/run-selection.d.ts +51 -0
  68. package/lib/types/run.d.ts +32 -1
  69. package/lib/types/settings-persist.d.ts +59 -0
  70. package/lib/types/snapshots.d.ts +46 -0
  71. package/lib/types/stage-contract.d.ts +58 -0
  72. package/lib/types/stream-fallback.d.ts +60 -0
  73. package/lib/types/strip-headings.d.ts +33 -0
  74. package/lib/types/tension.d.ts +53 -0
  75. package/lib/types/timeline.d.ts +37 -0
  76. package/package.json +50 -27
  77. package/assets/styles/arcane-thick-3d.webp +0 -0
  78. package/assets/styles/chinese-ink.webp +0 -0
  79. package/assets/styles/clay-stopmotion-3d.webp +0 -0
  80. package/assets/styles/dark-ghibli.webp +0 -0
  81. package/assets/styles/dark-gothic-3d.webp +0 -0
  82. package/assets/styles/film-photo.webp +0 -0
  83. package/assets/styles/guofeng-heavy.webp +0 -0
  84. package/assets/styles/guoman-2d-gufeng.webp +0 -0
  85. package/assets/styles/guoman-3d-tianyuan.webp +0 -0
  86. package/assets/styles/guoman-3d-xuanhuan.webp +0 -0
  87. package/assets/styles/horror-game-3d.webp +0 -0
  88. package/assets/styles/hyperreal-3d.webp +0 -0
  89. package/assets/styles/japan-cel-dark.webp +0 -0
  90. package/assets/styles/korean-webtoon.webp +0 -0
  91. package/assets/styles/low-cyberpunk-3d.webp +0 -0
  92. package/assets/styles/nextgen-cg.webp +0 -0
  93. package/assets/styles/noir-film.webp +0 -0
  94. package/assets/styles/openworld-survival.webp +0 -0
  95. package/assets/styles/paper-cut-3d.webp +0 -0
  96. package/assets/styles/pixar-adult-3d.webp +0 -0
  97. package/assets/styles/pop-marvel-2d.webp +0 -0
  98. package/assets/styles/sandiao-qban.webp +0 -0
  99. package/assets/styles/shinkai-anime.webp +0 -0
  100. package/assets/styles/tactical-stealth.webp +0 -0
  101. package/assets/styles/urban-lightreal-3d.webp +0 -0
  102. package/assets/styles/vaporwave-3d.webp +0 -0
  103. package/assets/styles/vhs90.webp +0 -0
  104. package/lib/types/client/panel/BookshelfBar.d.ts +0 -11
  105. package/lib/types/client/panel/FlowGuide.d.ts +0 -22
  106. package/lib/types/client/panel/MangaRoleLibrary.d.ts +0 -20
  107. package/lib/types/client/panel/MangaWorkspace.d.ts +0 -12
  108. package/lib/types/client/panel/ModelPicker.d.ts +0 -11
  109. package/lib/types/client/panel/ProgressHomeView.d.ts +0 -5
  110. package/lib/types/client/panel/PropLibrary.d.ts +0 -8
  111. package/lib/types/client/panel/SceneLibrary.d.ts +0 -16
  112. package/lib/types/client/panel/StoryboardTab.d.ts +0 -19
  113. package/lib/types/client/panel/StyleCard.d.ts +0 -8
  114. package/lib/types/client/panel/StyleGallery.d.ts +0 -1
  115. package/lib/types/comic-presets.d.ts +0 -12
  116. package/lib/types/manga-genre-rules.d.ts +0 -22
  117. package/lib/types/prompt-utils.d.ts +0 -35
  118. package/lib/types/review-policy.d.ts +0 -97
  119. package/lib/types/shot-language.d.ts +0 -46
  120. package/lib/types/story-beat-language.d.ts +0 -35
  121. package/lib/types/style-library.d.ts +0 -33
  122. package/skills/chapter-batch/SKILL.md +0 -81
  123. package/src/ai-scan.ts +0 -179
  124. package/src/assets.ts +0 -879
  125. package/src/assistant.ts +0 -770
  126. package/src/author-assets.ts +0 -147
  127. package/src/bookshelf.ts +0 -167
  128. package/src/client/api.ts +0 -617
  129. package/src/client/css-modules.d.ts +0 -8
  130. package/src/client/docx.ts +0 -69
  131. package/src/client/index.ts +0 -34
  132. package/src/client/locales.ts +0 -418
  133. package/src/client/mount.tsx +0 -97
  134. package/src/client/mount.tsx._new +0 -97
  135. package/src/client/mount.tsx._nf +0 -97
  136. package/src/client/mount.tsx._tmp +0 -97
  137. package/src/client/panel/AdaptModeView.tsx +0 -520
  138. package/src/client/panel/AssetsTab.tsx +0 -458
  139. package/src/client/panel/AssistantTab.tsx +0 -301
  140. package/src/client/panel/AuthorAssetsView.tsx +0 -208
  141. package/src/client/panel/AuthorHome.tsx +0 -204
  142. package/src/client/panel/BookshelfBar.tsx +0 -170
  143. package/src/client/panel/CreateBookView.tsx +0 -352
  144. package/src/client/panel/FlowGuide.tsx +0 -84
  145. package/src/client/panel/GlobalAssetLibraryView.tsx +0 -172
  146. package/src/client/panel/ImportModal.tsx +0 -303
  147. package/src/client/panel/MangaRoleLibrary.tsx +0 -1002
  148. package/src/client/panel/MangaWorkspace.tsx +0 -715
  149. package/src/client/panel/ModelManager.tsx +0 -205
  150. package/src/client/panel/NovelPanel.tsx +0 -4620
  151. package/src/client/panel/ProgressConsole.tsx +0 -64
  152. package/src/client/panel/ProgressHomeView.tsx +0 -87
  153. package/src/client/panel/PropLibrary.tsx +0 -175
  154. package/src/client/panel/ReaderView.tsx +0 -306
  155. package/src/client/panel/ReasoningSection.tsx +0 -31
  156. package/src/client/panel/RunPanel.tsx +0 -204
  157. package/src/client/panel/SceneLibrary.tsx +0 -453
  158. package/src/client/panel/SettingsView.tsx +0 -286
  159. package/src/client/panel/ShelfView.tsx +0 -242
  160. package/src/client/panel/StoryboardTab.tsx +0 -472
  161. package/src/client/panel/StyleCard.tsx +0 -90
  162. package/src/client/panel/StyleGallery.tsx +0 -58
  163. package/src/client/panel/WorldTab.tsx +0 -202
  164. package/src/client/panel/controller.ts +0 -45
  165. package/src/client/panel/helpers.ts +0 -67
  166. package/src/client/panel/panel.module.css +0 -4360
  167. package/src/client/panel/views.tsx +0 -314
  168. package/src/client/sidebar-entry.ts +0 -122
  169. package/src/client/text.ts +0 -28
  170. package/src/comic-presets.ts +0 -67
  171. package/src/docx.ts +0 -83
  172. package/src/engine.ts +0 -5200
  173. package/src/index.ts +0 -329
  174. package/src/manga-genre-rules.ts +0 -87
  175. package/src/novel-context.ts +0 -287
  176. package/src/prompt-utils.ts +0 -73
  177. package/src/protocol.ts +0 -2233
  178. package/src/review-policy.ts +0 -161
  179. package/src/routes.ts +0 -3419
  180. package/src/run.ts +0 -331
  181. package/src/shot-language.ts +0 -181
  182. package/src/story-beat-language.ts +0 -130
  183. package/src/style-library.ts +0 -84
@@ -1,81 +0,0 @@
1
- ---
2
- name: novel-forge-chapter-batch
3
- description: 批量生成小说章节并值守处理审稿未过的章节。当用户说「生成 N 章」「连写」「批量生成」「继续写章节」「值守处理未过章节」时使用。依赖 dsh-novel-forge 插件(http://127.0.0.1:3080/api/dsh-novel-forge/*)。
4
- ---
5
-
6
- # 小说章节批量生产与值守处理
7
-
8
- 把「批量生成章节 + 审稿未过处理」固化为可复现流程。所有操作通过 dsh-novel-forge 的 HTTP API 执行。
9
-
10
- ## 0. 值守纪律(最高优先级,违反即空转)
11
-
12
- 1. **启动后台任务后立即停下,不轮询、不连续 wait、不做无意义动作**——后台任务(批量生成/处理)由运行时在完成时自动通知。
13
- 2. 只有收到「任务完成」通知或用户新消息时,才继续下一步(读结果 → 处理未过 → 汇报)。
14
- 3. 等待期间可以做「与当前任务无关」的其他有用工作(如用户提出的其他问题、代码改进),但不得为等任务而空转。
15
- 4. 任务因 web 重启中断时:等用户确认或检测到 3080 恢复后,复位卡死章节(generating/error → reset)再续跑,并向用户说明中断原因。
16
-
17
- ## 1. 前置检查(每次必做)
18
-
19
- 1. 确认服务在跑:`GET /api/dsh-novel-forge/status` 返回 200,读 `project.chapters` 数量与书名。
20
- 2. 确认目标章节状态:列出 `pending`(待生成)章节号区间,避免重复生成已 approved/rejected 的章。
21
- 3. **串行执行,禁止并行写 project.json**——多个脚本同时调 API 会造成状态互相覆盖(实测教训:第 166 章曾被并发回滚成 rejected)。
22
-
23
- ## 2. 批量生成
24
-
25
- 对目标章节区间(如 181-205)逐章串行调用:
26
-
27
- ```
28
- POST /api/dsh-novel-forge/generate
29
- { "chapterNo": N, "skipReview": false }
30
- ```
31
-
32
- - `skipReview: false` 走完整质量门(生成 → 摘要+编年录 → AI 审稿 → 作者复盘)。
33
- - 响应为 NDJSON 流;找 `{"type":"review","report":{...}}` 帧取 `score`/`passed`。
34
- - 每章 2-5 分钟;失败自动重试 1 次(间隔 3 秒)。
35
- - 预估总时长并告知用户(25 章 ≈ 45-75 分钟),放后台跑,期间可并行做别的(只读操作不受影响)。
36
- - 推荐用 Node 脚本(fetch + 逐章循环),避免 PowerShell 的引号/UTF-8 转义坑;脚本用完删除。
37
-
38
- ## 2. 未过章节处理(分级策略)
39
-
40
- 生成完成后汇总:通过 / 未过(rejected)/ 失败(error)。对未过章按此分级:
41
-
42
- ### A. 无 high(全是 medium/low 主观项)→ 豁免通过
43
- ```
44
- POST /api/dsh-novel-forge/chapter/approve { "chapterNo": N }
45
- ```
46
- 主观项(套话/节奏/比喻/心理描写)不值得逐章磨,直接通过。
47
-
48
- ### B. 有 high 且问题明确(逻辑/设定/时间线/用词)→ 按意见修订 + 验证模式
49
- 1. 取 review 的 high(无 high 取前 3 条 medium),拼指令:
50
- `按审稿意见修订(优先处理):\n[severity] item → suggestion`
51
- 2. `POST /rewrite` `{ chapterNo, instructions }` → 产出 pendingDraft
52
- 3. **验证模式**:`POST /chapter/check` `{ chapterNo, text: draft, previousReport: review }`——携带上一轮报告,LLM 只核对原意见是否解决 + 只挑新增 high,不全新找茬
53
- 4. 判定:`passed || issues 全非 high` → 可接受
54
- 5. 可接受 → `POST /draft/apply` `{ chapterNo, report: { score: max(score,70), passed: true, issues: [], ... } }` 落盘为 approved
55
- 6. 不可接受 → 看剩余 high 是否明确可修 → 第二轮修订(指令更精确,如「把第 141 章的养伤时间线统一为七天」)→ 再验证
56
- 7. **每章最多修订 2 轮**,仍不过 → 保留草稿,标记「待人工」,不硬来
57
-
58
- ### C. 有 high 但结构性(修订改不好,如逻辑矛盾被坐实)→ 重新生成
59
- - `POST /draft/discard` 清草稿 → `POST /generate` 整章重写
60
- - 重新生成后若只剩主观项 → 走 A 豁免
61
-
62
- ### D. error 状态(无审稿、chars 丢失)→ 重置重生成
63
- - `POST /chapter/reset` `{ chapterNo }` → `POST /generate`
64
-
65
- ### 特殊注意
66
- - **approved 但 review 有 high**:已通过状态保留,不折腾(high 可能已被正文处理或审稿误标)。
67
- - **修订指令越精确越有效**:具体数字/行为(如「茶梗尺寸统一为半截指节」)一次就过;泛泛的「修正矛盾」容易改不到位。
68
-
69
- ## 3. 收尾
70
-
71
- 1. 最终验证:`GET /status` 读目标区间全部 approved。
72
- 2. 可选:对涉及剧情的章节刷新剧情线进度(`POST /plotlines` `{ op:'refresh', id }`)——注意刷新函数只读最近 8 章摘要,早期章节的线可能刷不准,必要时手动 update progress + link 章节。
73
- 3. 向用户汇报:通过/未过/失败数量 + 每章处理方式 + 遗留待人工项。
74
-
75
- ## 4. 已知陷阱(血泪教训)
76
-
77
- - **并发写 project.json 会互相覆盖**——多个生成/处理任务必须串行。
78
- - **rewrite 的草稿可能不落盘**(偶发 500 或时序)——应用前检查 `pendingDraft` 是否存在,不存在则重跑 rewrite。
79
- - **PowerShell 调 API 的引号/中文转义是坑**——统一用 Node 脚本。
80
- - **status 接口会做数据快照**——以磁盘 novel-project.json 为准复核关键状态。
81
- - **验证模式 500 偶发**——重试一次即可(LLM 瞬时错误)。
package/src/ai-scan.ts DELETED
@@ -1,179 +0,0 @@
1
- /**
2
- * 本地 AI 味扫描器(不调 LLM,确定性检测)
3
- *
4
- * 用途:生成/润色后自动扫描,结果作为「事实锚点」注入审稿提示词,
5
- * 让 LLM 做判断而非机械统计。也可用于 UI 展示 AI 味指标。
6
- */
7
-
8
- export interface AiScanResult {
9
- /** 总评分 0-100,越高越像 AI 写的 */
10
- aiScore: number
11
- /** 命中的套话及次数 */
12
- clicheHits: Array<{ word: string; count: number }>
13
- /** 段落长度方差(过高=段落过于整齐) */
14
- paragraphLengthVariance: number
15
- /** 连续解释性叙事段数(≥3 视为问题) */
16
- consecutiveExpositoryParagraphs: number
17
- /** 句式重复率(相同开头句式占比) */
18
- sentenceRepetitionRate: number
19
- /** 过长段落数(>300字) */
20
- longParagraphCount: number
21
- /** 过短段落数(<20字) */
22
- shortParagraphCount: number
23
- /** 对话占比(0-1) */
24
- dialogueRatio: number
25
- /** 问题摘要,可直接注入审稿提示词 */
26
- summary: string
27
- }
28
-
29
- /** 重灾区套话(高频出现即问题) */
30
- const HEAVY_CLICHES = [
31
- '不禁', '仿佛', '一时间', '不由得', '顿时', '然而',
32
- '缓缓', '轻轻', '微微', '默默', '似乎', '终于',
33
- '显然', '其实', '无法形容', '难以言喻', '不由自主',
34
- ]
35
-
36
- /** 轻微套话(偶尔出现可接受,高频才问题) */
37
- const LIGHT_CLICHES = [
38
- '心中', '脑海', '眼神', '嘴角', '眉头', '身影',
39
- '气息', '光芒', '力量', '感觉', '知道', '明白',
40
- ]
41
-
42
- /** 解释性叙事开头模式 */
43
- const EXPOSITORY_STARTS = [
44
- '原来', '因为', '由于', '所以', '因此', '于是',
45
- '这就是', '也就是说', '换句话说', '事实上', '实际上',
46
- ]
47
-
48
- export function scanAiFlavor(text: string): AiScanResult {
49
- const paragraphs = text.split(/\n{2,}/).map(p => p.trim()).filter(p => p.length > 0)
50
- const totalChars = text.length
51
-
52
- // 1. 套话统计
53
- const clicheHits: Array<{ word: string; count: number }> = []
54
- let clicheTotal = 0
55
- for (const word of [...HEAVY_CLICHES, ...LIGHT_CLICHES]) {
56
- const count = countOccurrences(text, word)
57
- if (count > 0) {
58
- clicheHits.push({ word, count })
59
- clicheTotal += count
60
- }
61
- }
62
- clicheHits.sort((a, b) => b.count - a.count)
63
-
64
- // 2. 段落长度方差
65
- const paraLengths = paragraphs.map(p => p.length)
66
- const avgLen = paraLengths.length > 0 ? paraLengths.reduce((a, b) => a + b, 0) / paraLengths.length : 0
67
- const variance = paraLengths.length > 0
68
- ? paraLengths.reduce((sum, len) => sum + Math.pow(len - avgLen, 2), 0) / paraLengths.length
69
- : 0
70
-
71
- // 3. 连续解释性叙事
72
- let maxConsecutive = 0
73
- let currentConsecutive = 0
74
- for (const p of paragraphs) {
75
- const isExpository = EXPOSITORY_STARTS.some(s => p.startsWith(s)) ||
76
- (p.length > 150 && !p.includes('"') && !p.includes('「') && !p.includes('『'))
77
- if (isExpository) {
78
- currentConsecutive++
79
- maxConsecutive = Math.max(maxConsecutive, currentConsecutive)
80
- } else {
81
- currentConsecutive = 0
82
- }
83
- }
84
-
85
- // 4. 句式重复率(相同句首词)
86
- const sentences = text.split(/[。!?!?]/).map(s => s.trim()).filter(s => s.length > 0)
87
- const starterCounts = new Map<string, number>()
88
- for (const s of sentences) {
89
- const starter = s.slice(0, 2)
90
- starterCounts.set(starter, (starterCounts.get(starter) ?? 0) + 1)
91
- }
92
- const repeatedStarters = Array.from(starterCounts.values()).filter(c => c >= 3).reduce((a, b) => a + b, 0)
93
- const sentenceRepetitionRate = sentences.length > 0 ? repeatedStarters / sentences.length : 0
94
-
95
- // 5. 段落长度分布
96
- const longParagraphCount = paraLengths.filter(l => l > 300).length
97
- const shortParagraphCount = paraLengths.filter(l => l < 20).length
98
-
99
- // 6. 对话占比
100
- const dialogueChars = (text.match(/["「『][^"」』]*["」』]/g) ?? []).join('').length
101
- const dialogueRatio = totalChars > 0 ? dialogueChars / totalChars : 0
102
-
103
- // 7. 综合 AI 味评分(0-100,越高越像 AI)
104
- let aiScore = 0
105
- // 套话密度(每千字套话数)
106
- const clicheDensity = totalChars > 0 ? (clicheTotal / totalChars) * 1000 : 0
107
- aiScore += Math.min(40, clicheDensity * 8)
108
- // 段落过于整齐(方差小=AI特征)
109
- if (variance < 2000 && paraLengths.length >= 5) aiScore += 15
110
- // 连续解释性叙事
111
- aiScore += Math.min(20, maxConsecutive * 5)
112
- // 句式重复
113
- aiScore += Math.min(15, sentenceRepetitionRate * 30)
114
- // 对话过少
115
- if (dialogueRatio < 0.05 && totalChars > 1000) aiScore += 10
116
- aiScore = Math.min(100, Math.round(aiScore))
117
-
118
- // 8. 问题摘要
119
- const issues: string[] = []
120
- if (clicheDensity > 3) {
121
- const topCliches = clicheHits.slice(0, 5).map(h => `${h.word}×${h.count}`).join('、')
122
- issues.push(`套话密度偏高(每千字 ${clicheDensity.toFixed(1)} 次):${topCliches}`)
123
- }
124
- if (maxConsecutive >= 3) issues.push(`连续 ${maxConsecutive} 段解释性叙事,缺少对话/动作`)
125
- if (sentenceRepetitionRate > 0.15) issues.push(`句式重复率 ${(sentenceRepetitionRate * 100).toFixed(0)}%,开头句式单一`)
126
- if (longParagraphCount > 3) issues.push(`${longParagraphCount} 段超过 300 字,段落过长`)
127
- if (dialogueRatio < 0.05 && totalChars > 1000) issues.push('对话占比过低,整章偏叙述')
128
-
129
- const summary = issues.length > 0
130
- ? `本地 AI 味扫描(AI 味指数 ${aiScore}/100):\n` + issues.map(i => `- ${i}`).join('\n')
131
- : `本地 AI 味扫描(AI 味指数 ${aiScore}/100):未发现明显问题。`
132
-
133
- return {
134
- aiScore,
135
- clicheHits,
136
- paragraphLengthVariance: Math.round(variance),
137
- consecutiveExpositoryParagraphs: maxConsecutive,
138
- sentenceRepetitionRate: Math.round(sentenceRepetitionRate * 100) / 100,
139
- longParagraphCount,
140
- shortParagraphCount,
141
- dialogueRatio: Math.round(dialogueRatio * 100) / 100,
142
- summary,
143
- }
144
- }
145
-
146
- function countOccurrences(text: string, word: string): number {
147
- if (word.length === 0) return 0
148
- let count = 0
149
- let idx = text.indexOf(word)
150
- while (idx !== -1) {
151
- count++
152
- idx = text.indexOf(word, idx + word.length)
153
- }
154
- return count
155
- }
156
-
157
- /** 扫描前后对比(润色时用) */
158
- export function compareAiScan(before: AiScanResult, after: AiScanResult): {
159
- improved: boolean
160
- deltaScore: number
161
- details: string[]
162
- } {
163
- const deltaScore = before.aiScore - after.aiScore
164
- const details: string[] = []
165
- if (deltaScore > 0) details.push(`AI 味指数下降 ${deltaScore} 分(${before.aiScore} → ${after.aiScore})`)
166
- else if (deltaScore < 0) details.push(`AI 味指数上升 ${-deltaScore} 分(${before.aiScore} → ${after.aiScore}),需检查`)
167
- else details.push(`AI 味指数持平(${before.aiScore})`)
168
-
169
- const beforeCliches = before.clicheHits.reduce((a, b) => a + b.count, 0)
170
- const afterCliches = after.clicheHits.reduce((a, b) => a + b.count, 0)
171
- if (afterCliches < beforeCliches) details.push(`套话减少 ${beforeCliches - afterCliches} 处`)
172
- else if (afterCliches > beforeCliches) details.push(`套话增加 ${afterCliches - beforeCliches} 处`)
173
-
174
- return {
175
- improved: deltaScore > 0,
176
- deltaScore,
177
- details,
178
- }
179
- }