@waterwx/dsh-novel-forge 1.10.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 (194) hide show
  1. package/README.md +287 -281
  2. package/lib/client.js +19572 -18127
  3. package/lib/index.js +12171 -6932
  4. package/lib/types/action-guard.d.ts +61 -0
  5. package/lib/types/ai-scan.d.ts +30 -0
  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 +137 -21
  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/index.d.ts +1 -1
  14. package/lib/types/client/llmLive.d.ts +31 -0
  15. package/lib/types/client/locales.d.ts +15 -70
  16. package/lib/types/client/mount.d.ts +0 -2
  17. package/lib/types/client/panel/AiTag.d.ts +1 -0
  18. package/lib/types/client/panel/AssistantTab.d.ts +4 -1
  19. package/lib/types/client/panel/AuthorHome.d.ts +13 -9
  20. package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
  21. package/lib/types/client/panel/CmdPalette.d.ts +12 -0
  22. package/lib/types/client/panel/CreateBookView.d.ts +5 -1
  23. package/lib/types/client/panel/DirectorView.d.ts +7 -0
  24. package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
  25. package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
  26. package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
  27. package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
  28. package/lib/types/client/panel/ModelManager.d.ts +2 -2
  29. package/lib/types/client/panel/NovelPanel.d.ts +6 -1
  30. package/lib/types/client/panel/PageHeader.d.ts +18 -0
  31. package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
  32. package/lib/types/client/panel/SettingsView.d.ts +12 -7
  33. package/lib/types/client/panel/ShelfView.d.ts +10 -1
  34. package/lib/types/client/panel/SubPage.d.ts +27 -0
  35. package/lib/types/client/panel/TensionView.d.ts +18 -0
  36. package/lib/types/client/panel/TimelineView.d.ts +18 -0
  37. package/lib/types/client/panel/helpers.d.ts +5 -3
  38. package/lib/types/client/panel/market-radar-state.d.ts +56 -0
  39. package/lib/types/client/panel/toast.d.ts +2 -0
  40. package/lib/types/client/panel/views.d.ts +21 -35
  41. package/lib/types/client/run-status.d.ts +21 -0
  42. package/lib/types/client/sidebar-entry.d.ts +0 -2
  43. package/lib/types/docx-core.d.ts +16 -0
  44. package/lib/types/docx.d.ts +2 -3
  45. package/lib/types/engine.d.ts +83 -79
  46. package/lib/types/global-assets.d.ts +6 -0
  47. package/lib/types/home.d.ts +11 -0
  48. package/lib/types/index.d.ts +27 -13
  49. package/lib/types/llm-live.d.ts +70 -0
  50. package/lib/types/llm-retry.d.ts +38 -0
  51. package/lib/types/market-radar-scan.d.ts +18 -0
  52. package/lib/types/market-radar-sources.d.ts +31 -0
  53. package/lib/types/migrate-output.d.ts +25 -0
  54. package/lib/types/model-capability.d.ts +93 -0
  55. package/lib/types/novel-context.d.ts +80 -0
  56. package/lib/types/output-budget.d.ts +86 -0
  57. package/lib/types/prompt-slots.d.ts +57 -0
  58. package/lib/types/prompting.d.ts +10 -0
  59. package/lib/types/protocol.d.ts +829 -522
  60. package/lib/types/revision.d.ts +76 -0
  61. package/lib/types/routes.d.ts +9 -3
  62. package/lib/types/run.d.ts +23 -1
  63. package/lib/types/settings-persist.d.ts +59 -0
  64. package/lib/types/snapshots.d.ts +46 -0
  65. package/lib/types/stage-contract.d.ts +58 -0
  66. package/lib/types/tension.d.ts +53 -0
  67. package/lib/types/timeline.d.ts +37 -0
  68. package/package.json +50 -28
  69. package/src/action-guard.ts +127 -0
  70. package/src/ai-scan.ts +155 -0
  71. package/src/assets.ts +1801 -535
  72. package/src/assistant.ts +1098 -770
  73. package/src/author-assets.ts +131 -147
  74. package/src/bookshelf.ts +193 -167
  75. package/src/client/api.ts +767 -607
  76. package/src/client/chip-prefs.ts +121 -0
  77. package/src/client/docx.ts +32 -69
  78. package/src/client/index.ts +3 -1
  79. package/src/client/llmLive.ts +111 -0
  80. package/src/client/locales.ts +15 -274
  81. package/src/client/mount.tsx +4 -3
  82. package/src/client/panel/AdaptModeView.tsx +535 -520
  83. package/src/client/panel/AiTag.tsx +7 -0
  84. package/src/client/panel/AssetsTab.tsx +448 -458
  85. package/src/client/panel/AssistantTab.tsx +355 -301
  86. package/src/client/panel/AuthorAssetsView.tsx +205 -207
  87. package/src/client/panel/AuthorHome.tsx +309 -202
  88. package/src/client/panel/BookAnalysisView.tsx +63 -0
  89. package/src/client/panel/CmdPalette.tsx +102 -0
  90. package/src/client/panel/CreateBookView.tsx +366 -352
  91. package/src/client/panel/DirectorView.tsx +200 -0
  92. package/src/client/panel/GlobalAssetLibraryView.tsx +168 -172
  93. package/src/client/panel/IdeaInspirationView.tsx +76 -0
  94. package/src/client/panel/ImportModal.tsx +302 -302
  95. package/src/client/panel/KnowledgeBaseView.tsx +125 -0
  96. package/src/client/panel/LiveFeedLog.tsx +204 -0
  97. package/src/client/panel/MarketRadarView.tsx +415 -0
  98. package/src/client/panel/ModelManager.tsx +205 -205
  99. package/src/client/panel/NovelPanel.tsx +5559 -4655
  100. package/src/client/panel/PageHeader.tsx +37 -0
  101. package/src/client/panel/ProgressConsole.tsx +63 -64
  102. package/src/client/panel/PromptSlotsView.tsx +97 -0
  103. package/src/client/panel/ReaderView.tsx +306 -306
  104. package/src/client/panel/RunPanel.tsx +56 -24
  105. package/src/client/panel/SettingsView.tsx +175 -112
  106. package/src/client/panel/ShelfView.tsx +63 -39
  107. package/src/client/panel/SubPage.tsx +42 -0
  108. package/src/client/panel/TensionView.tsx +212 -0
  109. package/src/client/panel/TimelineView.tsx +208 -0
  110. package/src/client/panel/WorldTab.tsx +202 -202
  111. package/src/client/panel/helpers.ts +7 -5
  112. package/src/client/panel/market-radar-state.ts +79 -0
  113. package/src/client/panel/panel.module.css +5767 -4199
  114. package/src/client/panel/toast.ts +51 -0
  115. package/src/client/panel/views.tsx +316 -314
  116. package/src/client/run-status.ts +171 -0
  117. package/src/client/sidebar-entry.ts +3 -3
  118. package/src/docx-core.ts +53 -0
  119. package/src/docx.ts +44 -83
  120. package/src/engine.ts +1610 -1541
  121. package/src/global-assets.ts +58 -0
  122. package/src/home.ts +33 -0
  123. package/src/index.ts +184 -103
  124. package/src/llm-live.ts +245 -0
  125. package/src/llm-retry.ts +77 -0
  126. package/src/market-radar-scan.ts +35 -0
  127. package/src/market-radar-sources.ts +173 -0
  128. package/src/migrate-output.ts +76 -0
  129. package/src/model-capability.ts +215 -0
  130. package/src/novel-context.ts +286 -0
  131. package/src/output-budget.ts +147 -0
  132. package/src/prompt-slots.ts +125 -0
  133. package/src/prompting.ts +26 -0
  134. package/src/protocol.ts +2454 -2148
  135. package/src/revision.ts +283 -0
  136. package/src/routes.ts +3795 -3359
  137. package/src/run.ts +113 -17
  138. package/src/settings-persist.ts +114 -0
  139. package/src/snapshots.ts +174 -0
  140. package/src/stage-contract.ts +186 -0
  141. package/src/tension.ts +251 -0
  142. package/src/timeline.ts +159 -0
  143. package/assets/styles/arcane-thick-3d.webp +0 -0
  144. package/assets/styles/chinese-ink.webp +0 -0
  145. package/assets/styles/clay-stopmotion-3d.webp +0 -0
  146. package/assets/styles/dark-ghibli.webp +0 -0
  147. package/assets/styles/dark-gothic-3d.webp +0 -0
  148. package/assets/styles/film-photo.webp +0 -0
  149. package/assets/styles/guofeng-heavy.webp +0 -0
  150. package/assets/styles/horror-game-3d.webp +0 -0
  151. package/assets/styles/japan-cel-dark.webp +0 -0
  152. package/assets/styles/korean-webtoon.webp +0 -0
  153. package/assets/styles/low-cyberpunk-3d.webp +0 -0
  154. package/assets/styles/nextgen-cg.webp +0 -0
  155. package/assets/styles/noir-film.webp +0 -0
  156. package/assets/styles/openworld-survival.webp +0 -0
  157. package/assets/styles/paper-cut-3d.webp +0 -0
  158. package/assets/styles/pixar-adult-3d.webp +0 -0
  159. package/assets/styles/pop-marvel-2d.webp +0 -0
  160. package/assets/styles/shinkai-anime.webp +0 -0
  161. package/assets/styles/tactical-stealth.webp +0 -0
  162. package/assets/styles/vaporwave-3d.webp +0 -0
  163. package/assets/styles/vhs90.webp +0 -0
  164. package/lib/types/client/panel/BookshelfBar.d.ts +0 -11
  165. package/lib/types/client/panel/FlowGuide.d.ts +0 -21
  166. package/lib/types/client/panel/MangaRoleLibrary.d.ts +0 -18
  167. package/lib/types/client/panel/MangaWorkspace.d.ts +0 -15
  168. package/lib/types/client/panel/ModelPicker.d.ts +0 -11
  169. package/lib/types/client/panel/ProgressHomeView.d.ts +0 -5
  170. package/lib/types/client/panel/SceneLibrary.d.ts +0 -14
  171. package/lib/types/client/panel/StoryboardTab.d.ts +0 -19
  172. package/lib/types/client/panel/StyleCard.d.ts +0 -8
  173. package/lib/types/client/panel/StyleGallery.d.ts +0 -1
  174. package/lib/types/comic-presets.d.ts +0 -12
  175. package/lib/types/shot-language.d.ts +0 -46
  176. package/lib/types/story-beat-language.d.ts +0 -35
  177. package/lib/types/style-library.d.ts +0 -33
  178. package/skills/chapter-batch/SKILL.md +0 -81
  179. package/src/client/mount.tsx._new +0 -97
  180. package/src/client/mount.tsx._nf +0 -97
  181. package/src/client/mount.tsx._tmp +0 -97
  182. package/src/client/panel/BookshelfBar.tsx +0 -170
  183. package/src/client/panel/FlowGuide.tsx +0 -83
  184. package/src/client/panel/MangaRoleLibrary.tsx +0 -810
  185. package/src/client/panel/MangaWorkspace.tsx +0 -359
  186. package/src/client/panel/ProgressHomeView.tsx +0 -87
  187. package/src/client/panel/SceneLibrary.tsx +0 -396
  188. package/src/client/panel/StoryboardTab.tsx +0 -466
  189. package/src/client/panel/StyleCard.tsx +0 -90
  190. package/src/client/panel/StyleGallery.tsx +0 -58
  191. package/src/comic-presets.ts +0 -67
  192. package/src/shot-language.ts +0 -181
  193. package/src/story-beat-language.ts +0 -130
  194. package/src/style-library.ts +0 -78
@@ -1,147 +1,131 @@
1
- /**
2
- * 作者资产库/总数据(跨书)— 按作者维度聚合的可复用资产。
3
- * 与书架同惯例持久化到 ~/.dsh/dsh-novel-forge-author-assets.json。
4
- * 提供读取/持久化/新增或更新/删除,以及「导入默认」:把书架书的写作资产/角色 +
5
- * 内置全局库(题材/反AI规则/风格模板/推进模式)批量沉淀成资产条目(默认库)。
6
- */
7
-
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
9
- import { homedir } from 'node:os'
10
- import { join } from 'node:path'
11
- import { randomBytes } from 'node:crypto'
12
- import type { AuthorAssetLibrary, AuthorStyleAsset, GenreNode, AntiAiRule, StyleTemplate, ProgressionMode } from './protocol.ts'
13
-
14
- import { loadBookshelf } from './bookshelf.ts'
15
- import { loadProject } from './engine.ts'
16
-
17
- /** 作者资产库配置文件路径。 */
18
- export function authorAssetsFile(): string {
19
- return join(homedir(), '.dsh', 'dsh-novel-forge-author-assets.json')
20
- }
21
-
22
- /** 默认空资产库。 */
23
- function defaultLibrary(): AuthorAssetLibrary {
24
- return { version: 1, items: [] }
25
- }
26
-
27
- /** 读取作者资产库(不存在/损坏则返回空库,不抛错)。 */
28
- export function loadAuthorAssets(): AuthorAssetLibrary {
29
- const file = authorAssetsFile()
30
- if (!existsSync(file)) return defaultLibrary()
31
- try {
32
- let raw = readFileSync(file, 'utf8')
33
- if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
34
- const parsed = JSON.parse(raw) as Partial<AuthorAssetLibrary>
35
- if (!Array.isArray(parsed.items)) return defaultLibrary()
36
- return { version: 1, items: parsed.items }
37
- } catch {
38
- return defaultLibrary()
39
- }
40
- }
41
-
42
- /** 持久化作者资产库。 */
43
- export function saveAuthorAssets(library: AuthorAssetLibrary): void {
44
- const file = authorAssetsFile()
45
- mkdirSync(join(homedir(), '.dsh'), { recursive: true })
46
- writeFileSync(file, JSON.stringify(library, null, 2), 'utf8')
47
- }
48
-
49
- /** 新建一条资产(自动生成 id 与时间戳)。 */
50
- export function createAuthorAsset(input: Omit<AuthorStyleAsset, 'id' | 'createdAt' | 'updatedAt'>): AuthorStyleAsset {
51
- const now = new Date().toISOString()
52
- const asset: AuthorStyleAsset = {
53
- ...input,
54
- id: 'aa-' + Date.now().toString(36) + '-' + randomBytes(3).toString('hex'),
55
- sourceBooks: input.sourceBooks ?? [],
56
- tags: input.tags ?? [],
57
- createdAt: now,
58
- updatedAt: now,
59
- }
60
- const library = loadAuthorAssets()
61
- library.items.push(asset)
62
- saveAuthorAssets(library)
63
- return asset
64
- }
65
-
66
- /** 按 id 更新一条资产;不存在则追加。 */
67
- export function upsertAuthorAsset(input: AuthorStyleAsset): AuthorAssetLibrary {
68
- const library = loadAuthorAssets()
69
- const idx = library.items.findIndex(a => a.id === input.id)
70
- const now = new Date().toISOString()
71
- if (idx >= 0) {
72
- const prev = library.items[idx]
73
- if (prev !== undefined) {
74
- library.items[idx] = {
75
- ...prev,
76
- ...input,
77
- id: prev.id,
78
- createdAt: prev.createdAt,
79
- updatedAt: now,
80
- }
81
- }
82
- } else {
83
- library.items.push({ ...input, createdAt: input.createdAt !== '' ? input.createdAt : now, updatedAt: now })
84
- }
85
- saveAuthorAssets(library)
86
- return library
87
- }
88
-
89
- /** 删除一条资产;返回更新后的资产库。 */
90
- export function removeAuthorAsset(id: string): AuthorAssetLibrary {
91
- const library = loadAuthorAssets()
92
- library.items = library.items.filter(a => a.id !== id)
93
- saveAuthorAssets(library)
94
- return library
95
- }
96
-
97
- /**
98
- * 导入默认:把书架所有书的写作资产/角色 + 内置全局库批量沉淀到作者资产库。
99
- * 按 `kind:name` 去重(已存在则跳过),不覆盖用户已有条目。
100
- * @returns 更新后的资产库。
101
- */
102
- export function importDefaultAuthorAssets(): AuthorAssetLibrary {
103
- const library = loadAuthorAssets()
104
- const seen = new Set(library.items.map(a => a.kind + ':' + a.name))
105
- const now = new Date().toISOString()
106
- const push = (kind: AuthorStyleAsset['kind'], name: string, summary: string, content: string, tags: string[], sourceBooks: string[], structured?: Record<string, unknown>): void => {
107
- const key = kind + ':' + name.trim()
108
- if (name.trim() === '' || seen.has(key)) return
109
- seen.add(key)
110
- library.items.push({
111
- id: 'aa-' + Date.now().toString(36) + '-' + randomBytes(3).toString('hex'),
112
- kind, name: name.trim(), summary: summary.trim(), content: content.trim(),
113
- sourceBooks, tags, structured, createdAt: now, updatedAt: now,
114
- });
115
- };
116
-
117
- // 1) 清理内置公共库重复沉淀:作者库只保留个人沉淀,不再收录内置公共资产。
118
- const builtinKinds = new Set<AuthorStyleAsset['kind']>(['genre', 'antiAi', 'progression', 'style', 'plotBeat'])
119
- library.items = library.items.filter(a => !(a.tags.includes('内置') && builtinKinds.has(a.kind) && (a.sourceBooks ?? []).length === 0))
120
-
121
- // 2) 书架里的书:写作资产 + 角色模板
122
- const shelf = loadBookshelf()
123
- for (const book of shelf.books) {
124
- const project = loadProject(book.outputDir)
125
- if (project === undefined) continue
126
- const src = [project.bookName]
127
- const assets = project.assets
128
- if (assets !== undefined) {
129
- if (assets.genre !== undefined) push('genre', assets.genre.name, assets.genre.description, assets.genre.description, ['书', '题材'], src, { description: assets.genre.description });
130
- if (assets.primaryProgression !== undefined) push('progression', assets.primaryProgression.name, assets.primaryProgression.driver, '驱动:' + assets.primaryProgression.driver + '\n期待:' + assets.primaryProgression.readerExpectation, src, [], { driver: assets.primaryProgression.driver, primary: true });
131
- for (const p of assets.auxiliaryProgressions ?? []) push('progression', p.name, p.driver, '驱动:' + p.driver + '\n期待:' + p.readerExpectation, src, [], { driver: p.driver, primary: false });
132
- for (const r of assets.antiAiRules ?? []) push('antiAi', r.name, r.avoid, '避免:' + r.avoid + '\n修正:' + r.fix, src, [], { avoid: r.avoid, fix: r.fix, detectPatterns: r.detectPatterns });
133
- for (const s of assets.styleAssets ?? []) push('style', s.name, s.proseRules.join(';'),
134
- '叙述:' + s.proseRules.join(';') + '\n台词:' + s.dialogueRules.join(';') + '\n描写:' + s.descriptionRules.join(';') + '\n边界:' + s.boundaries.join(';'), src, [], { sourceText: s.sourceText });
135
- }
136
- // 作者库角色模板只收男主/女主(可复用原型),其余角色/路人不入库,避免刷屏。
137
- for (const role of project.roles ?? []) {
138
- if (role.roleLabel !== 'protagonist' && role.roleLabel !== 'female_lead') continue
139
- const persona = ('定位:' + role.roleLabel + '\n身份:' + role.identity + '\n性格:' + role.traits.join('、') + '\n目标:' + role.goals + '\n关系:' + role.relations.join('、') + '\n成长线:' + (role.arc.length > 0 ? role.arc.join(' > ') : '—') + '\n知情度:' + role.knowledge.join('、')).trim();
140
- push('roleTemplate', role.name, role.identity, persona, src, [], { roleLabel: role.roleLabel, traits: role.traits, relations: role.relations, arc: role.arc, knowledge: role.knowledge });
141
- }
142
- }
143
-
144
- saveAuthorAssets(library)
145
- return library
146
- }
147
-
1
+ /**
2
+ * 作者资产库/总数据(跨书)— 按作者维度聚合的可复用资产。
3
+ * 与书架同惯例持久化到 <DSH_HOME>/dsh-novel-forge-author-assets.json。
4
+ * 提供读取/持久化/新增或更新/删除,以及「导入默认」:把书架书的写作资产/角色 +
5
+ * 内置全局库(题材/反AI规则/风格模板/推进模式)批量沉淀成资产条目(默认库)。
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
9
+ import { randomBytes } from 'node:crypto'
10
+ import type { AuthorAssetLibrary, AuthorStyleAsset } from './protocol.ts'
11
+
12
+ import { loadBookshelf } from './bookshelf.ts'
13
+ import { loadProject } from './engine.ts'
14
+ import { dshHome, dshHomePath } from './home.ts'
15
+
16
+ /** 作者资产库配置文件路径。 */
17
+ export function authorAssetsFile(): string {
18
+ return dshHomePath('dsh-novel-forge-author-assets.json')
19
+ }
20
+
21
+ /** 默认空资产库。 */
22
+ function defaultLibrary(): AuthorAssetLibrary {
23
+ return { version: 1, items: [] }
24
+ }
25
+
26
+ /** 读取作者资产库(不存在/损坏则返回空库,不抛错)。 */
27
+ export function loadAuthorAssets(): AuthorAssetLibrary {
28
+ const file = authorAssetsFile()
29
+ if (!existsSync(file)) return defaultLibrary()
30
+ try {
31
+ let raw = readFileSync(file, 'utf8')
32
+ if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
33
+ const parsed = JSON.parse(raw) as Partial<AuthorAssetLibrary>
34
+ if (!Array.isArray(parsed.items)) return defaultLibrary()
35
+ return { version: 1, items: parsed.items }
36
+ } catch (error) {
37
+ // 资产库损坏回退默认值会遮蔽数据丢失,留痕到服务端日志。
38
+ console.error(`[novel-forge] author assets file unreadable/corrupt: ${file} — ${(error as Error).message}`)
39
+ return defaultLibrary()
40
+ }
41
+ }
42
+
43
+ /** 持久化作者资产库。 */
44
+ export function saveAuthorAssets(library: AuthorAssetLibrary): void {
45
+ const file = authorAssetsFile()
46
+ mkdirSync(dshHome(), { recursive: true })
47
+ writeFileSync(file, JSON.stringify(library, null, 2), 'utf8')
48
+ }
49
+
50
+ /** 按 id 更新一条资产;不存在则追加。 */
51
+ export function upsertAuthorAsset(input: AuthorStyleAsset): AuthorAssetLibrary {
52
+ const library = loadAuthorAssets()
53
+ const idx = library.items.findIndex(a => a.id === input.id)
54
+ const now = new Date().toISOString()
55
+ if (idx >= 0) {
56
+ const prev = library.items[idx]
57
+ if (prev !== undefined) {
58
+ library.items[idx] = {
59
+ ...prev,
60
+ ...input,
61
+ id: prev.id,
62
+ createdAt: prev.createdAt,
63
+ updatedAt: now,
64
+ }
65
+ }
66
+ } else {
67
+ library.items.push({ ...input, createdAt: input.createdAt !== '' ? input.createdAt : now, updatedAt: now })
68
+ }
69
+ saveAuthorAssets(library)
70
+ return library
71
+ }
72
+
73
+ /** 删除一条资产;返回更新后的资产库。 */
74
+ export function removeAuthorAsset(id: string): AuthorAssetLibrary {
75
+ const library = loadAuthorAssets()
76
+ library.items = library.items.filter(a => a.id !== id)
77
+ saveAuthorAssets(library)
78
+ return library
79
+ }
80
+
81
+ /**
82
+ * 导入默认:把书架所有书的写作资产/角色 + 内置全局库批量沉淀到作者资产库。
83
+ * 按 `kind:name` 去重(已存在则跳过),不覆盖用户已有条目。
84
+ * @returns 更新后的资产库。
85
+ */
86
+ export function importDefaultAuthorAssets(): AuthorAssetLibrary {
87
+ const library = loadAuthorAssets()
88
+ const seen = new Set(library.items.map(a => a.kind + ':' + a.name))
89
+ const now = new Date().toISOString()
90
+ const push = (kind: AuthorStyleAsset['kind'], name: string, summary: string, content: string, tags: string[], sourceBooks: string[], structured?: Record<string, unknown>): void => {
91
+ const key = kind + ':' + name.trim()
92
+ if (name.trim() === '' || seen.has(key)) return
93
+ seen.add(key)
94
+ library.items.push({
95
+ id: 'aa-' + Date.now().toString(36) + '-' + randomBytes(3).toString('hex'),
96
+ kind, name: name.trim(), summary: summary.trim(), content: content.trim(),
97
+ sourceBooks, tags, structured, createdAt: now, updatedAt: now,
98
+ });
99
+ };
100
+
101
+ // 1) 清理内置公共库重复沉淀:作者库只保留个人沉淀,不再收录内置公共资产。
102
+ const builtinKinds = new Set<AuthorStyleAsset['kind']>(['genre', 'antiAi', 'progression', 'style', 'plotBeat'])
103
+ library.items = library.items.filter(a => !(a.tags.includes('内置') && builtinKinds.has(a.kind) && (a.sourceBooks ?? []).length === 0))
104
+
105
+ // 2) 书架里的书:写作资产 + 角色模板
106
+ const shelf = loadBookshelf()
107
+ for (const book of shelf.books) {
108
+ const project = loadProject(book.outputDir)
109
+ if (project === undefined) continue
110
+ const src = [project.bookName]
111
+ const assets = project.assets
112
+ if (assets !== undefined) {
113
+ if (assets.genre !== undefined) push('genre', assets.genre.name, assets.genre.description, assets.genre.description, ['书', '题材'], src, { description: assets.genre.description });
114
+ if (assets.primaryProgression !== undefined) push('progression', assets.primaryProgression.name, assets.primaryProgression.driver, '驱动:' + assets.primaryProgression.driver + '\n期待:' + assets.primaryProgression.readerExpectation, src, [], { driver: assets.primaryProgression.driver, primary: true });
115
+ for (const p of assets.auxiliaryProgressions ?? []) push('progression', p.name, p.driver, '驱动:' + p.driver + '\n期待:' + p.readerExpectation, src, [], { driver: p.driver, primary: false });
116
+ for (const r of assets.antiAiRules ?? []) push('antiAi', r.name, r.avoid, '避免:' + r.avoid + '\n修正:' + r.fix, src, [], { avoid: r.avoid, fix: r.fix, detectPatterns: r.detectPatterns });
117
+ for (const s of assets.styleAssets ?? []) push('style', s.name, s.proseRules.join(';'),
118
+ '叙述:' + s.proseRules.join(';') + '\n台词:' + s.dialogueRules.join(';') + '\n描写:' + s.descriptionRules.join(';') + '\n边界:' + s.boundaries.join(';'), src, [], { sourceText: s.sourceText });
119
+ }
120
+ // 作者库角色模板只收男主/女主(可复用原型),其余角色/路人不入库,避免刷屏。
121
+ for (const role of project.roles ?? []) {
122
+ if (role.roleLabel !== 'protagonist' && role.roleLabel !== 'female_lead') continue
123
+ const persona = ('定位:' + role.roleLabel + '\n身份:' + role.identity + '\n性格:' + role.traits.join('、') + '\n目标:' + role.goals + '\n关系:' + role.relations.join('、') + '\n成长线:' + (role.arc.length > 0 ? role.arc.join(' > ') : '—') + '\n知情度:' + role.knowledge.join('、')).trim();
124
+ push('roleTemplate', role.name, role.identity, persona, src, [], { roleLabel: role.roleLabel, traits: role.traits, relations: role.relations, arc: role.arc, knowledge: role.knowledge });
125
+ }
126
+ }
127
+
128
+ saveAuthorAssets(library)
129
+ return library
130
+ }
131
+