@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,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
+