@waterwx/dsh-novel-forge 2.0.0-alpha.1 → 2.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.
Files changed (206) hide show
  1. package/README.md +286 -285
  2. package/lib/client.js +19561 -19439
  3. package/lib/index.js +8159 -4456
  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/toast.d.ts +2 -0
  39. package/lib/types/client/panel/views.d.ts +21 -35
  40. package/lib/types/client/run-status.d.ts +21 -0
  41. package/lib/types/client/sidebar-entry.d.ts +0 -2
  42. package/lib/types/docx-core.d.ts +16 -0
  43. package/lib/types/docx.d.ts +2 -3
  44. package/lib/types/engine.d.ts +78 -103
  45. package/lib/types/global-assets.d.ts +6 -0
  46. package/lib/types/home.d.ts +11 -0
  47. package/lib/types/index.d.ts +26 -12
  48. package/lib/types/llm-live.d.ts +70 -0
  49. package/lib/types/llm-retry.d.ts +38 -0
  50. package/lib/types/market-radar-scan.d.ts +18 -0
  51. package/lib/types/market-radar-sources.d.ts +31 -0
  52. package/lib/types/migrate-output.d.ts +25 -0
  53. package/lib/types/model-capability.d.ts +93 -0
  54. package/lib/types/output-budget.d.ts +86 -0
  55. package/lib/types/prompt-slots.d.ts +57 -0
  56. package/lib/types/prompting.d.ts +10 -0
  57. package/lib/types/protocol.d.ts +816 -601
  58. package/lib/types/revision.d.ts +76 -0
  59. package/lib/types/routes.d.ts +9 -3
  60. package/lib/types/run.d.ts +23 -1
  61. package/lib/types/settings-persist.d.ts +59 -0
  62. package/lib/types/snapshots.d.ts +46 -0
  63. package/lib/types/stage-contract.d.ts +58 -0
  64. package/lib/types/tension.d.ts +53 -0
  65. package/lib/types/timeline.d.ts +37 -0
  66. package/package.json +50 -25
  67. package/src/action-guard.ts +127 -0
  68. package/src/ai-scan.ts +0 -24
  69. package/src/assets.ts +1801 -879
  70. package/src/assistant.ts +1098 -770
  71. package/src/author-assets.ts +131 -147
  72. package/src/bookshelf.ts +193 -167
  73. package/src/client/api.ts +767 -617
  74. package/src/client/chip-prefs.ts +121 -0
  75. package/src/client/docx.ts +32 -69
  76. package/src/client/index.ts +2 -0
  77. package/src/client/llmLive.ts +111 -0
  78. package/src/client/locales.ts +15 -274
  79. package/src/client/mount.tsx +3 -2
  80. package/src/client/panel/AdaptModeView.tsx +535 -520
  81. package/src/client/panel/AiTag.tsx +7 -0
  82. package/src/client/panel/AssetsTab.tsx +448 -458
  83. package/src/client/panel/AssistantTab.tsx +355 -301
  84. package/src/client/panel/AuthorAssetsView.tsx +205 -207
  85. package/src/client/panel/AuthorHome.tsx +309 -204
  86. package/src/client/panel/BookAnalysisView.tsx +63 -0
  87. package/src/client/panel/CmdPalette.tsx +102 -0
  88. package/src/client/panel/CreateBookView.tsx +366 -352
  89. package/src/client/panel/DirectorView.tsx +200 -0
  90. package/src/client/panel/GlobalAssetLibraryView.tsx +168 -172
  91. package/src/client/panel/IdeaInspirationView.tsx +76 -0
  92. package/src/client/panel/ImportModal.tsx +302 -302
  93. package/src/client/panel/KnowledgeBaseView.tsx +125 -0
  94. package/src/client/panel/LiveFeedLog.tsx +204 -0
  95. package/src/client/panel/MarketRadarView.tsx +415 -0
  96. package/src/client/panel/ModelManager.tsx +205 -205
  97. package/src/client/panel/NovelPanel.tsx +3144 -2205
  98. package/src/client/panel/PageHeader.tsx +37 -0
  99. package/src/client/panel/ProgressConsole.tsx +63 -64
  100. package/src/client/panel/PromptSlotsView.tsx +97 -0
  101. package/src/client/panel/ReaderView.tsx +306 -306
  102. package/src/client/panel/RunPanel.tsx +56 -24
  103. package/src/client/panel/SettingsView.tsx +175 -106
  104. package/src/client/panel/ShelfView.tsx +63 -39
  105. package/src/client/panel/SubPage.tsx +42 -0
  106. package/src/client/panel/TensionView.tsx +212 -0
  107. package/src/client/panel/TimelineView.tsx +208 -0
  108. package/src/client/panel/WorldTab.tsx +202 -202
  109. package/src/client/panel/helpers.ts +7 -5
  110. package/src/client/panel/market-radar-state.ts +79 -0
  111. package/src/client/panel/panel.module.css +5767 -4360
  112. package/src/client/panel/toast.ts +51 -0
  113. package/src/client/panel/views.tsx +316 -314
  114. package/src/client/run-status.ts +171 -0
  115. package/src/client/sidebar-entry.ts +3 -3
  116. package/src/docx-core.ts +53 -0
  117. package/src/docx.ts +44 -83
  118. package/src/engine.ts +1197 -1638
  119. package/src/global-assets.ts +58 -0
  120. package/src/home.ts +33 -0
  121. package/src/index.ts +181 -101
  122. package/src/llm-live.ts +245 -0
  123. package/src/llm-retry.ts +77 -0
  124. package/src/market-radar-scan.ts +35 -0
  125. package/src/market-radar-sources.ts +173 -0
  126. package/src/migrate-output.ts +76 -0
  127. package/src/model-capability.ts +215 -0
  128. package/src/novel-context.ts +286 -287
  129. package/src/output-budget.ts +147 -0
  130. package/src/prompt-slots.ts +125 -0
  131. package/src/prompting.ts +26 -0
  132. package/src/protocol.ts +835 -614
  133. package/src/revision.ts +283 -0
  134. package/src/routes.ts +1286 -910
  135. package/src/run.ts +113 -17
  136. package/src/settings-persist.ts +114 -0
  137. package/src/snapshots.ts +174 -0
  138. package/src/stage-contract.ts +186 -0
  139. package/src/tension.ts +251 -0
  140. package/src/timeline.ts +159 -0
  141. package/assets/styles/arcane-thick-3d.webp +0 -0
  142. package/assets/styles/chinese-ink.webp +0 -0
  143. package/assets/styles/clay-stopmotion-3d.webp +0 -0
  144. package/assets/styles/dark-ghibli.webp +0 -0
  145. package/assets/styles/dark-gothic-3d.webp +0 -0
  146. package/assets/styles/film-photo.webp +0 -0
  147. package/assets/styles/guofeng-heavy.webp +0 -0
  148. package/assets/styles/guoman-2d-gufeng.webp +0 -0
  149. package/assets/styles/guoman-3d-tianyuan.webp +0 -0
  150. package/assets/styles/guoman-3d-xuanhuan.webp +0 -0
  151. package/assets/styles/horror-game-3d.webp +0 -0
  152. package/assets/styles/hyperreal-3d.webp +0 -0
  153. package/assets/styles/japan-cel-dark.webp +0 -0
  154. package/assets/styles/korean-webtoon.webp +0 -0
  155. package/assets/styles/low-cyberpunk-3d.webp +0 -0
  156. package/assets/styles/nextgen-cg.webp +0 -0
  157. package/assets/styles/noir-film.webp +0 -0
  158. package/assets/styles/openworld-survival.webp +0 -0
  159. package/assets/styles/paper-cut-3d.webp +0 -0
  160. package/assets/styles/pixar-adult-3d.webp +0 -0
  161. package/assets/styles/pop-marvel-2d.webp +0 -0
  162. package/assets/styles/sandiao-qban.webp +0 -0
  163. package/assets/styles/shinkai-anime.webp +0 -0
  164. package/assets/styles/tactical-stealth.webp +0 -0
  165. package/assets/styles/urban-lightreal-3d.webp +0 -0
  166. package/assets/styles/vaporwave-3d.webp +0 -0
  167. package/assets/styles/vhs90.webp +0 -0
  168. package/lib/types/client/panel/BookshelfBar.d.ts +0 -11
  169. package/lib/types/client/panel/FlowGuide.d.ts +0 -22
  170. package/lib/types/client/panel/MangaRoleLibrary.d.ts +0 -20
  171. package/lib/types/client/panel/MangaWorkspace.d.ts +0 -12
  172. package/lib/types/client/panel/ModelPicker.d.ts +0 -11
  173. package/lib/types/client/panel/ProgressHomeView.d.ts +0 -5
  174. package/lib/types/client/panel/PropLibrary.d.ts +0 -8
  175. package/lib/types/client/panel/SceneLibrary.d.ts +0 -16
  176. package/lib/types/client/panel/StoryboardTab.d.ts +0 -19
  177. package/lib/types/client/panel/StyleCard.d.ts +0 -8
  178. package/lib/types/client/panel/StyleGallery.d.ts +0 -1
  179. package/lib/types/comic-presets.d.ts +0 -12
  180. package/lib/types/manga-genre-rules.d.ts +0 -22
  181. package/lib/types/prompt-utils.d.ts +0 -35
  182. package/lib/types/review-policy.d.ts +0 -97
  183. package/lib/types/shot-language.d.ts +0 -46
  184. package/lib/types/story-beat-language.d.ts +0 -35
  185. package/lib/types/style-library.d.ts +0 -33
  186. package/skills/chapter-batch/SKILL.md +0 -81
  187. package/src/client/mount.tsx._new +0 -97
  188. package/src/client/mount.tsx._nf +0 -97
  189. package/src/client/mount.tsx._tmp +0 -97
  190. package/src/client/panel/BookshelfBar.tsx +0 -170
  191. package/src/client/panel/FlowGuide.tsx +0 -84
  192. package/src/client/panel/MangaRoleLibrary.tsx +0 -1002
  193. package/src/client/panel/MangaWorkspace.tsx +0 -715
  194. package/src/client/panel/ProgressHomeView.tsx +0 -87
  195. package/src/client/panel/PropLibrary.tsx +0 -175
  196. package/src/client/panel/SceneLibrary.tsx +0 -453
  197. package/src/client/panel/StoryboardTab.tsx +0 -472
  198. package/src/client/panel/StyleCard.tsx +0 -90
  199. package/src/client/panel/StyleGallery.tsx +0 -58
  200. package/src/comic-presets.ts +0 -67
  201. package/src/manga-genre-rules.ts +0 -87
  202. package/src/prompt-utils.ts +0 -73
  203. package/src/review-policy.ts +0 -161
  204. package/src/shot-language.ts +0 -181
  205. package/src/story-beat-language.ts +0 -130
  206. package/src/style-library.ts +0 -84
@@ -1,67 +0,0 @@
1
- /** 漫画风格预设:每个预设对应一组出图提示词与排版偏好。 */
2
-
3
- export interface ComicStylePreset {
4
- id: string
5
- label: string
6
- /** 注入 imagePrompt 的风格描述词。 */
7
- prompt: string
8
- /** 可选:排版偏好说明。 */
9
- layoutHint?: string
10
- }
11
-
12
- export const COMIC_STYLE_PRESETS: ComicStylePreset[] = [
13
- {
14
- id: 'japan',
15
- label: '日漫 / 黑白',
16
- prompt: 'manga style, screentone, bold ink lines, high contrast, black and white, cel shading',
17
- layoutHint: '页漫优先',
18
- },
19
- {
20
- id: 'korea',
21
- label: '韩漫 / 条漫',
22
- prompt: 'manhwa style, soft cel shading, vibrant colors, clean lineart, webtoon vertical composition, glossy highlights',
23
- layoutHint: '条漫优先',
24
- },
25
- {
26
- id: 'china',
27
- label: '国漫 / 古风',
28
- prompt: 'chinese ink painting style, flowing brush strokes, traditional palette, ethereal atmosphere, xianxia aesthetic',
29
- layoutHint: '页漫/条漫均可',
30
- },
31
- {
32
- id: 'america',
33
- label: '美漫 / 超级英雄',
34
- prompt: 'american comic style, bold outlines, dramatic lighting, dynamic poses, vibrant colors, halftone shading',
35
- layoutHint: '页漫优先',
36
- },
37
- {
38
- id: 'chibi',
39
- label: 'Q版 / 搞笑',
40
- prompt: 'chibi style, cute proportions, big head, exaggerated expressions, simple background, bright colors',
41
- layoutHint: '条漫/四格优先',
42
- },
43
- {
44
- id: 'realistic',
45
- label: '写实 / 电影感',
46
- prompt: 'realistic cinematic style, detailed textures, natural lighting, film grain, high detail',
47
- layoutHint: '页漫/全彩',
48
- },
49
- {
50
- id: 'cyber',
51
- label: '赛博 / 暗黑',
52
- prompt: 'cyberpunk style, neon lights, dark atmosphere, high contrast, futuristic city, cool tones',
53
- layoutHint: '页漫/全彩',
54
- },
55
- ]
56
-
57
- export function getComicStylePrompt(styleId?: string): string {
58
- if (!styleId) return ''
59
- const preset = COMIC_STYLE_PRESETS.find(s => s.id === styleId)
60
- return preset?.prompt ?? ''
61
- }
62
-
63
- export function getComicStyleLabel(styleId?: string): string {
64
- if (!styleId) return ''
65
- const preset = COMIC_STYLE_PRESETS.find(s => s.id === styleId)
66
- return preset?.label ?? styleId
67
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * 题材规则库(可插拔)—— 漫剧分镜/提示词生成的「题材专用规则」。
3
- * 参考短剧工业导演规范:不同题材用不同的视觉语言 / 打斗 / 特效规则,
4
- * 按漫剧方案的 genre 注入 generateStoryboardPrompts,避免「一套模板跑所有题材」。
5
- * 每个题材给一组「生成提示词时必须遵守的题材规则」;未选题材则不注入(走通用规则)。
6
- */
7
-
8
- export interface GenreRule {
9
- /** 题材 id。 */
10
- id: string
11
- /** 题材名(展示)。 */
12
- label: string
13
- /** 该题材的生成提示词核心规则(每行一条,注入生成时)。 */
14
- rules: string[]
15
- }
16
-
17
- /** 内置题材(通用题材 default 为空规则)。 */
18
- export const GENRES: GenreRule[] = [
19
- {
20
- id: 'xuanhuan',
21
- label: '玄幻 / 仙侠',
22
- rules: [
23
- '施法必须经法宝媒介(笛/扇/印/笔等实体法宝),写清法宝本体材质+施法动作+特效出口,禁止只写"施法"。',
24
- '每个角色绑定主属性+双色粒子(雷=蓝白+紫电、冰=冰蓝+银白、金=亮金+暖白、火=赤红+暗金、木=粉白+草木青),属性决定术法形态与配色。',
25
- '术法攻防走"罡气→破罡→防御转攻":护体罡气被划开、防御术法炸裂化千万飞刃/剑气,防守转攻是高光点,必写。',
26
- '能量具象化:光柱/雷龙/法相/领域;"光柱"要写成"洪流/弧光/波纹/光丝雨"(禁裸光柱,防呆板发光管)。',
27
- '终招蓄力必写:天地灵韵向角色/法宝汇聚、能量漩涡成型,再接幂击。',
28
- '对军清场:扇形冲击波/剑气暴雨洗地/能量扇镇压大军核心,战场弹坑/碎片/云层撕开反馈。',
29
- ],
30
- },
31
- {
32
- id: 'wuxia',
33
- label: '武侠 / 国风',
34
- rules: [
35
- '打斗用 Hit-Stop 三件套:碰撞瞬间 1~2 帧微停顿 + 接触点火花 + 双刃咬死高频震颤,杜绝滑步/空气假打。',
36
- '武术动作链:起手/攻防/收势,标明具体招式(刀/剑/轻功/擒拿),剑走轻灵、刀走厚重。',
37
- '镜头强化打击感:碰撞点微环绕、贴身横移、命中定格,禁静止对峙。',
38
- '轻功/身法:衣袂翻飞、发丝飘动、残影,身形轻盈不僵硬。',
39
- ],
40
- },
41
- {
42
- id: 'anime',
43
- label: '二次元 / 动漫',
44
- rules: [
45
- '大招三段式:蓄力→释放→爆发,能量形态+爆发特效,段段画质声明复用。',
46
- '双色粒子:每角色一套主色+材质+轨迹,双方互补对冲(冷 vs 暖),防混战混淆。',
47
- '色指定:明确主色调+高光色+特效撞色,防发灰。',
48
- '禁止评价词/裸光效/空泛"粒子";能量写"光丝雨/弧光/波纹"+方向+材质。',
49
- ],
50
- },
51
- {
52
- id: 'urban',
53
- label: '都市 / 现实',
54
- rules: [
55
- '微表情生理递进:眼神/眉心/唇部/鼻翼/喉头/手指,情绪逐层推进,不夸张不脸谱。',
56
- '对白语速 3.5~5 字/秒(台词从声音层按此把控,留 0.3~0.8s 落口发酵)。',
57
- '防棚拍假冷光:用真实光线(自然光/路灯/窗光/霓虹),日景不陰间冷蓝、局部留暖光。',
58
- '场景真实还原(街头/室内/医院等),人物服装生活化,肢体动作自然不律動化。',
59
- ],
60
- },
61
- {
62
- id: 'scifi',
63
- label: '科幻 / 未来',
64
- rules: [
65
- '机甲/能量武器设定明确(颜色/材质/发光部位),能量轨迹具体。',
66
- '霓虹/屏幕光/激光防眩光,用冷色主调+重点发光处。',
67
- '宏大场景(飞船/城市/废墟)用大远景/俯拍,人物相对渺小。',
68
- ],
69
- },
70
- ]
71
-
72
- /** 通用题材(空规则,走通用生成)。 */
73
- export const DEFAULT_GENRE: GenreRule = { id: 'default', label: '通用(不限定)', rules: [] }
74
-
75
- /** 按题材 id 取规则(未找到返回通用)。 */
76
- export function getGenreRules(id?: string): string[] {
77
- if (id === undefined || id === '') return DEFAULT_GENRE.rules
78
- const g = GENRES.find(x => x.id === id)
79
- return g !== undefined ? g.rules : DEFAULT_GENRE.rules
80
- }
81
-
82
- /** 取题材 label(展示用)。 */
83
- export function getGenreLabel(id?: string): string {
84
- if (id === undefined || id === '') return DEFAULT_GENRE.label
85
- const g = GENRES.find(x => x.id === id)
86
- return g !== undefined ? g.label : id
87
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * 提示词共享常量与工具
3
- *
4
- * 所有 JSON 输出规则、通用约束集中在此,避免各阶段重复定义、互相打架。
5
- */
6
-
7
- /** JSON 输出规则(所有要求模型输出 JSON 的地方共用) */
8
- export const JSON_OUTPUT_RULES = [
9
- '输出必须是合法 JSON 对象或数组,不要输出任何其他文字、解释、Markdown 标记或代码块。',
10
- '所有字符串值内部不得包含换行符(用 \\n 转义),JSON 必须在一段内完整结束。',
11
- '直接输出 JSON 结果本身,不要把思考过程写在输出里。',
12
- '数值字段必须是数字类型,不要用字符串包裹。',
13
- '数组为空时输出 [],对象为空时输出 {}。',
14
- ]
15
-
16
- /** JSON 输出规则的紧凑版(适合放在提示词末尾) */
17
- export const JSON_OUTPUT_RULES_COMPACT = JSON_OUTPUT_RULES.join(' ')
18
-
19
- /**
20
- * 估算 prompt 的 token 数(粗略估算,用于上下文预算控制)
21
- * 中文约 1.7 字/token,英文约 4 字符/token
22
- */
23
- export function estimateTokens(text: string): number {
24
- if (text.length === 0) return 0
25
- // 统计中文字符数
26
- const chineseChars = (text.match(/[\u4e00-\u9fff]/g) ?? []).length
27
- const otherChars = text.length - chineseChars
28
- return Math.round(chineseChars / 1.7 + otherChars / 4)
29
- }
30
-
31
- /**
32
- * 按 token 预算截断文本(保留开头,末尾加省略标记)
33
- * 用于控制单块上下文不超预算。
34
- */
35
- export function truncateByTokens(text: string, maxTokens: number, marker = '…(已截断)'): string {
36
- if (estimateTokens(text) <= maxTokens) return text
37
- // 二分查找最大可保留长度
38
- let lo = 0
39
- let hi = text.length
40
- while (lo < hi) {
41
- const mid = Math.floor((lo + hi + 1) / 2)
42
- if (estimateTokens(text.slice(0, mid)) <= maxTokens - estimateTokens(marker)) {
43
- lo = mid
44
- } else {
45
- hi = mid - 1
46
- }
47
- }
48
- return text.slice(0, lo) + marker
49
- }
50
-
51
- /** 通用「只改表达不改情节」约束(润色/局部修订共用) */
52
- export const EXPRESSION_ONLY_RULE = [
53
- '只改表达,不改情节走向、人物设定、已确立事实、对话核心内容。',
54
- '保留角色口吻与性格,角色行为需符合角色卡。',
55
- '必须遵守内容合规红线与本书红线,任何一条命中(含影射、暗示)都必须避免。',
56
- '避免 AI 套话:不禁、仿佛、一时间、不由得、顿时、然而、缓缓、轻轻、微微、默默、似乎、终于等滥用。',
57
- ]
58
-
59
- /** 章节上下文各阶段的 token 预算(粗略,用于控制注入量) */
60
- export const CONTEXT_BUDGET = {
61
- /** 章节生成:道藏+角色+大纲+事实+伏笔 */
62
- writing: 12000,
63
- /** AI 审稿:道藏+角色+事实+正文 */
64
- review: 10000,
65
- /** 修订:原意见+相关事实+目标正文 */
66
- revise: 8000,
67
- /** 润色:风格资产+原正文 */
68
- polish: 6000,
69
- /** 分章规划:大纲+道藏+角色+近期事实 */
70
- plan: 15000,
71
- } as const
72
-
73
- export type ContextStage = keyof typeof CONTEXT_BUDGET
@@ -1,161 +0,0 @@
1
- /**
2
- * 审稿规则注册表
3
- *
4
- * 生成用 compact 模式(核心规则),审稿用 full 模式(全部规则),
5
- * 消除 reviewSystemPrompt 内嵌规则与 renderAllAssets() 两套打架的问题。
6
- */
7
-
8
- /** 通用写作资产规则(兼容 ProjectAssets 中的各类规则) */
9
- export interface WritingRule {
10
- name: string
11
- description: string
12
- type?: string
13
- enabled?: boolean
14
- }
15
-
16
- export interface ReviewRule {
17
- id: string
18
- /** 规则维度 */
19
- dimension: 'character' | 'setting' | 'redline' | 'writing' | 'pacing' | 'logic' | 'anti-ai' | 'presentation' | 'compliance'
20
- /** 严重度 */
21
- severity: 'high' | 'medium' | 'low'
22
- /** 规则类型:forbidden=禁止类(命中即问题),encourage=鼓励类(不命中不算错) */
23
- type: 'forbidden' | 'encourage'
24
- /** 规则描述 */
25
- description: string
26
- /** 是否启用 */
27
- enabled: boolean
28
- /** 作用域 */
29
- scope: Array<'writing' | 'review' | 'polish' | 'all'>
30
- }
31
-
32
- /** 内置审稿维度(结构化评分用) */
33
- export const REVIEW_DIMENSIONS = [
34
- { id: 'character', name: '人设一致性', weight: 0.2 },
35
- { id: 'setting', name: '设定一致性', weight: 0.15 },
36
- { id: 'redline', name: '红线检查', weight: 0.15 },
37
- { id: 'writing', name: '文笔质量', weight: 0.15 },
38
- { id: 'pacing', name: '节奏与爽点', weight: 0.1 },
39
- { id: 'logic', name: '逻辑漏洞', weight: 0.1 },
40
- { id: 'anti-ai', name: '反 AI 规则', weight: 0.05 },
41
- { id: 'presentation', name: '呈现方式', weight: 0.05 },
42
- { id: 'compliance', name: '内容合规', weight: 0.05 },
43
- ] as const
44
-
45
- export type ReviewDimensionId = typeof REVIEW_DIMENSIONS[number]['id']
46
-
47
- /** 维度结构化评分 */
48
- export interface DimensionScore {
49
- dimension: ReviewDimensionId
50
- score: number // 0-100
51
- note: string
52
- }
53
-
54
- /**
55
- * 渲染审稿规则(compact 或 full 模式)
56
- *
57
- * compact: 只渲染禁止类 + high 严重度(用于生成时的约束)
58
- * full: 渲染全部启用规则(用于审稿时的检查清单)
59
- */
60
- export function renderReviewRules(
61
- assets: WritingRule[] | undefined,
62
- mode: 'compact' | 'full' = 'full',
63
- ): string {
64
- const lines: string[] = []
65
-
66
- // 内置核心规则(所有模式都注入)
67
- const coreRules = [
68
- { dimension: 'character', desc: '角色行为符合角色卡设定(性格/目标/知情度/说话方式)' },
69
- { dimension: 'setting', desc: '金手指规则、战力体系、世界观与道藏一致' },
70
- { dimension: 'redline', desc: '不触犯本书写作红线' },
71
- { dimension: 'compliance', desc: '不触犯内容合规红线(最高优先级)' },
72
- { dimension: 'logic', desc: '无前后矛盾、时间线错误、对话失真' },
73
- { dimension: 'presentation', desc: '不纯内心推理铺陈;反派有行动力;重要配角有辨识度' },
74
- ]
75
-
76
- if (mode === 'compact') {
77
- lines.push('写作约束(核心规则):')
78
- for (const r of coreRules) {
79
- lines.push(`- ${r.desc}`)
80
- }
81
- } else {
82
- lines.push('审稿维度(逐条审查):')
83
- REVIEW_DIMENSIONS.forEach((d, i) => {
84
- const core = coreRules.find(c => c.dimension === d.id)
85
- lines.push(`${i + 1}. ${d.name}:${core?.desc ?? '按维度标准审查'}`)
86
- })
87
- lines.push('')
88
- lines.push('反 AI 规则:逐条核对下方资产规则,命中即列为问题。')
89
- lines.push('呈现方式:整章是否纯内心推理铺陈(无对话/无对抗);反派是否纯背景板;重要配角是否无名标签化——命中即列为问题。')
90
- lines.push('内容合规(最高优先级):逐条核对合规红线,任何一条命中(含影射、暗示、详细描写)必须列为 high。')
91
- }
92
-
93
- // 注入用户自定义资产规则
94
- if (assets !== undefined && assets.length > 0) {
95
- const forbidden = assets.filter(a => a.type === 'forbidden' || a.type === 'rule')
96
- const encourage = assets.filter(a => a.type === 'encourage' || a.type === 'style')
97
- if (forbidden.length > 0) {
98
- lines.push('')
99
- lines.push('禁止类规则(命中即问题):')
100
- for (const a of forbidden) {
101
- lines.push(`- ${a.name}:${a.description}`)
102
- }
103
- }
104
- if (encourage.length > 0 && mode === 'full') {
105
- lines.push('')
106
- lines.push('鼓励类规则(不命中不算错,作为低优先级建议):')
107
- for (const a of encourage) {
108
- lines.push(`- ${a.name}:${a.description}`)
109
- }
110
- }
111
- }
112
-
113
- return lines.join('\n')
114
- }
115
-
116
- /**
117
- * 从审稿 issues 中按维度统计
118
- * 用于结构化评分和 UI 展示。
119
- */
120
- export function groupIssuesByDimension(
121
- issues: Array<{ severity: string; item: string; dimension?: string }>,
122
- ): Record<string, Array<{ severity: string; item: string }>> {
123
- const result: Record<string, Array<{ severity: string; item: string }>> = {}
124
- for (const issue of issues) {
125
- const dim = issue.dimension ?? inferDimension(issue.item)
126
- if (result[dim] === undefined) result[dim] = []
127
- result[dim]!.push({ severity: issue.severity, item: issue.item })
128
- }
129
- return result
130
- }
131
-
132
- /** 根据问题描述推断维度(兼容旧报告,没有 dimension 字段时用) */
133
- function inferDimension(item: string): ReviewDimensionId {
134
- if (/人设|角色|性格|口吻|知情|OOC/.test(item)) return 'character'
135
- if (/设定|世界观|战力|金手指|规则|道藏/.test(item)) return 'setting'
136
- if (/红线|后宫|擦边|碾压|圣母/.test(item)) return 'redline'
137
- if (/语病|翻译腔|套话|文笔|流水账|AI/.test(item)) return 'writing'
138
- if (/节奏|拖沓|灌水|爽点|钩子|悬念/.test(item)) return 'pacing'
139
- if (/矛盾|时间线|逻辑|对话失真/.test(item)) return 'logic'
140
- if (/合规|敏感|色情|暴力|政治|未成年/.test(item)) return 'compliance'
141
- if (/内心推理|背景板|标签化|瘦高个|灰衣人/.test(item)) return 'presentation'
142
- return 'writing'
143
- }
144
-
145
- /**
146
- * 计算维度加权总分
147
- * 用于审稿报告的结构化评分。
148
- */
149
- export function calculateWeightedScore(
150
- dimensions: DimensionScore[],
151
- ): number {
152
- let total = 0
153
- let weightSum = 0
154
- for (const d of dimensions) {
155
- const def = REVIEW_DIMENSIONS.find(x => x.id === d.dimension)
156
- const weight = def?.weight ?? 0.1
157
- total += d.score * weight
158
- weightSum += weight
159
- }
160
- return weightSum > 0 ? Math.round(total / weightSum) : 60
161
- }
@@ -1,181 +0,0 @@
1
- /**
2
- * 镜头语言词库(分镜规范化基准)。
3
- * 分镜表的 shot/camera/composition/light 字段统一从这里取值(英文 id),
4
- * 生成提示词时用 zh 中文词块;适配英文平台时用 en。
5
- * 目的:让"景别/运镜/构图/光效"有标准词表,避免 LLM 自由发挥导致漂移。
6
- */
7
-
8
- /** 景别。 */
9
- export type ShotSizeId =
10
- | 'extreme_wide' | 'wide' | 'full' | 'medium'
11
- | 'medium_close' | 'close' | 'extreme_close' | 'big_extreme_close'
12
-
13
- /** 运镜/机位。 */
14
- export type CameraMoveId =
15
- | 'static' | 'dolly_in' | 'dolly_out' | 'pan_left' | 'pan_right'
16
- | 'track_left' | 'track_right' | 'follow' | 'pedestal_up' | 'pedestal_down'
17
- | 'orbit' | 'handheld' | 'low_angle' | 'high_angle' | 'over_shoulder'
18
-
19
- /** 构图。 */
20
- export type CompositionId =
21
- | 'rule_of_thirds' | 'center' | 'leading_line' | 'foreground'
22
- | 'low' | 'overhead' | 'symmetry'
23
-
24
- /** 光效。 */
25
- export type LightingId =
26
- | 'front' | 'side' | 'back' | 'top' | 'rembrandt'
27
- | 'neon' | 'hard' | 'soft' | 'mood' | 'contrast'
28
-
29
- export interface ShotLangEntry<T extends string> {
30
- id: T
31
- /** 中文(提示词/展示用)。 */
32
- zh: string
33
- /** 英文(英文平台适配用)。 */
34
- en: string
35
- /** 一句话说明(下拉/卡片)。 */
36
- hint: string
37
- }
38
-
39
- export const SHOT_SIZES: readonly ShotLangEntry<ShotSizeId>[] = [
40
- { id: 'extreme_wide', zh: '大远景', en: 'extreme wide shot', hint: '环境为主,人物很小' },
41
- { id: 'wide', zh: '远景', en: 'wide shot', hint: '人在景中,交代环境' },
42
- { id: 'full', zh: '全景', en: 'full shot', hint: '全身』人物整体' },
43
- { id: 'medium', zh: '中景', en: 'medium shot', hint: '膝部以上,叙事主力' },
44
- { id: 'medium_close', zh: '中近景', en: 'medium close-up', hint: '胸以上,表情+动作' },
45
- { id: 'close', zh: '近景', en: 'close-up', hint: '肩以上,突出表情' },
46
- { id: 'extreme_close', zh: '特写', en: 'extreme close-up', hint: '脸/物细节' },
47
- { id: 'big_extreme_close', zh: '大特写', en: 'big close-up', hint: '局部(眼/手/标志物)' },
48
- ]
49
-
50
- export const CAMERA_MOVES: readonly ShotLangEntry<CameraMoveId>[] = [
51
- { id: 'static', zh: '固定机位', en: 'static camera', hint: '机位不动' },
52
- { id: 'dolly_in', zh: '推近', en: 'dolly in', hint: '镜头向主体逼近' },
53
- { id: 'dolly_out', zh: '拉远', en: 'dolly out', hint: '镜头远离主体' },
54
- { id: 'pan_left', zh: '左摇', en: 'pan left', hint: '机位不动,镜头左转' },
55
- { id: 'pan_right', zh: '右摇', en: 'pan right', hint: '机位不动,镜头右转' },
56
- { id: 'track_left', zh: '左横移', en: 'track left', hint: '机位随主体左移' },
57
- { id: 'track_right', zh: '右横移', en: 'track right', hint: '机位随主体右移' },
58
- { id: 'follow', zh: '跟随', en: 'follow shot', hint: '镜头跟着主体运动' },
59
- { id: 'pedestal_up', zh: '升镜', en: 'pedestal up', hint: '机位抬高' },
60
- { id: 'pedestal_down', zh: '降镜', en: 'pedestal down', hint: '机位降低' },
61
- { id: 'orbit', zh: '环绕', en: 'orbit shot', hint: '镜头绕主体转' },
62
- { id: 'handheld', zh: '手持晃动', en: 'handheld', hint: '真实感/紧张感' },
63
- { id: 'low_angle', zh: '低机位仰拍', en: 'low angle', hint: '突出威严/压迫' },
64
- { id: 'high_angle', zh: '高机位俯拍', en: 'high angle', hint: '突出渺小/全知' },
65
- { id: 'over_shoulder', zh: '过肩镜头', en: 'over-the-shoulder', hint: '对话常用' },
66
- ]
67
-
68
- export const COMPOSITIONS: readonly ShotLangEntry<CompositionId>[] = [
69
- { id: 'rule_of_thirds', zh: '三分法', en: 'rule of thirds', hint: '主体偏三分之一处' },
70
- { id: 'center', zh: '中心对称', en: 'centered', hint: '主体居中' },
71
- { id: 'leading_line', zh: '引导线', en: 'leading lines', hint: '视线沿线条引向主体' },
72
- { id: 'foreground', zh: '前景遮挡', en: 'foreground framing', hint: '前景物框住主体' },
73
- { id: 'low', zh: '低机位', en: 'low camera', hint: '低于视线' },
74
- { id: 'overhead', zh: '俯拍', en: 'overhead', hint: '从上方俯视' },
75
- { id: 'symmetry', zh: '对称构图', en: 'symmetrical', hint: '左右镜像' },
76
- ]
77
-
78
- export const LIGHTINGS: readonly ShotLangEntry<LightingId>[] = [
79
- { id: 'front', zh: '顺光', en: 'front light', hint: '正面均匀照明' },
80
- { id: 'side', zh: '侧光', en: 'side light', hint: '明暗对比强' },
81
- { id: 'back', zh: '逆光', en: 'back light', hint: '轮廓光/剪影' },
82
- { id: 'top', zh: '顶光', en: 'top light', hint: '俯照硬朗' },
83
- { id: 'rembrandt', zh: '伦勃朗光', en: 'rembrandt lighting', hint: '侧逆光,经典人像' },
84
- { id: 'neon', zh: '霓虹光', en: 'neon lighting', hint: '赛博/夜景' },
85
- { id: 'hard', zh: '硬光', en: 'hard light', hint: '犀利阴影' },
86
- { id: 'soft', zh: '柔光', en: 'soft light', hint: '柔和过渡' },
87
- { id: 'mood', zh: '氛围光', en: 'mood lighting', hint: '情绪化色温' },
88
- { id: 'contrast', zh: '高反差', en: 'high contrast', hint: '黑白/强明暗' },
89
- ]
90
-
91
- export interface ShotLanguage {
92
- shot?: ShotSizeId
93
- camera: CameraMoveId[]
94
- composition?: CompositionId
95
- light: LightingId[]
96
- }
97
-
98
- /** 从中文文本归一化到景别(处理旧数据/LLM 口语)。未知回退 medium。 */
99
- export function normalizeShotSize(text: string | undefined): ShotSizeId {
100
- if (text === undefined || text === '') return 'medium'
101
- const t = text.trim()
102
- if (t.includes('大远景')) return 'extreme_wide'
103
- if (t.includes('远景')) return 'wide'
104
- if (t.includes('全景')) return 'full'
105
- if (t.includes('中近景') || t.includes('中景')) return 'medium'
106
- if (t.includes('近景') || t.includes('胸')) return 'close'
107
- if (t.includes('大特写')) return 'big_extreme_close'
108
- if (t.includes('特写')) return 'extreme_close'
109
- return 'medium'
110
- }
111
-
112
- /** 从中文文本归一化到运镜列表。 */
113
- export function normalizeCameras(text: string | undefined): CameraMoveId[] {
114
- if (text === undefined || text === '') return ['static']
115
- const t = text.trim()
116
- const out: CameraMoveId[] = []
117
- const push = (id: CameraMoveId) => { if (!out.includes(id)) out.push(id) }
118
- if (t.includes('推近') || t.includes('推进')) push('dolly_in')
119
- if (t.includes('拉远') || t.includes('拉出')) push('dolly_out')
120
- if (t.includes('左摇')) push('pan_left')
121
- if (t.includes('右摇')) push('pan_right')
122
- if (t.includes('横移')) push(t.includes('左') ? 'track_left' : 'track_right')
123
- if (t.includes('跟随') || t.includes('跟拍')) push('follow')
124
- if (t.includes('升降') || t.includes('升') || t.includes('降')) push('pedestal_up')
125
- if (t.includes('环绕')) push('orbit')
126
- if (t.includes('手持') || t.includes('晃动')) push('handheld')
127
- if (t.includes('低机位') || t.includes('仰拍') || t.includes('仰')) push('low_angle')
128
- if (t.includes('高机位') || t.includes('俯拍') || t.includes('俯')) push('high_angle')
129
- if (t.includes('过肩')) push('over_shoulder')
130
- if (out.length === 0) push('static')
131
- return out
132
- }
133
-
134
- /** 从中文文本归一化到构图(可选)。 */
135
- export function normalizeComposition(text: string | undefined): CompositionId | undefined {
136
- if (text === undefined || text === '') return undefined
137
- const t = text.trim()
138
- if (t.includes('三分')) return 'rule_of_thirds'
139
- if (t.includes('中心') || t.includes('居中')) return 'center'
140
- if (t.includes('引导')) return 'leading_line'
141
- if (t.includes('前景')) return 'foreground'
142
- if (t.includes('低机位')) return 'low'
143
- if (t.includes('俯拍') || t.includes('俯视')) return 'overhead'
144
- if (t.includes('对称')) return 'symmetry'
145
- return undefined
146
- }
147
-
148
- /** 从中文文本归一化到光效列表。 */
149
- export function normalizeLightings(text: string | undefined): LightingId[] {
150
- if (text === undefined || text === '') return ['soft']
151
- const t = text.trim()
152
- const out: LightingId[] = []
153
- const push = (id: LightingId) => { if (!out.includes(id)) out.push(id) }
154
- if (t.includes('顺光')) push('front')
155
- if (t.includes('侧光') || t.includes('伦勃朗')) push(t.includes('伦勃朗') ? 'rembrandt' : 'side')
156
- if (t.includes('逆光')) push('back')
157
- if (t.includes('顶光')) push('top')
158
- if (t.includes('霓虹')) push('neon')
159
- if (t.includes('硬光')) push('hard')
160
- if (t.includes('柔光')) push('soft')
161
- if (t.includes('氛围') || t.includes('情绪')) push('mood')
162
- if (t.includes('高反差')) push('contrast')
163
- if (out.length === 0) push('soft')
164
- return out
165
- }
166
-
167
- /** 取词条中文(ZHs 合并句子)。 */
168
- export function sizeZh(id: ShotSizeId | undefined): string {
169
- return SHOT_SIZES.find(e => e.id === id)?.zh ?? '中景'
170
- }
171
- export function cameraZh(ids: CameraMoveId[] | undefined): string {
172
- if (ids === undefined || ids.length === 0) return '固定机位'
173
- return CAMERA_MOVES.filter(e => ids.includes(e.id)).map(e => e.zh).join(' + ')
174
- }
175
- export function compoZh(id: CompositionId | undefined): string {
176
- return COMPOSITIONS.find(e => e.id === id)?.zh ?? ''
177
- }
178
- export function lightZh(ids: LightingId[] | undefined): string {
179
- if (ids === undefined || ids.length === 0) return '柔光'
180
- return LIGHTINGS.filter(e => ids.includes(e.id)).map(e => e.zh).join(' + ')
181
- }