@waterwx/dsh-novel-forge 2.1.2-alpha → 2.1.2

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 (253) hide show
  1. package/README.md +278 -368
  2. package/lib/client.js +19568 -21381
  3. package/lib/index.js +4850 -4002
  4. package/lib/types/action-guard.d.ts +61 -0
  5. package/lib/types/ai-scan.d.ts +66 -12
  6. package/lib/types/assets.d.ts +7 -9
  7. package/lib/types/assistant.d.ts +25 -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 +49 -29
  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 +12 -14
  14. package/lib/types/client/locales.d.ts +12 -67
  15. package/lib/types/client/mount.d.ts +0 -2
  16. package/lib/types/client/panel/AdaptModeView.d.ts +1 -1
  17. package/lib/types/client/panel/AiTag.d.ts +1 -0
  18. package/lib/types/client/panel/AssetsTab.d.ts +1 -1
  19. package/lib/types/client/panel/AssistantTab.d.ts +4 -1
  20. package/lib/types/client/panel/AuthorAssetsView.d.ts +1 -1
  21. package/lib/types/client/panel/AuthorHome.d.ts +12 -12
  22. package/lib/types/client/panel/CmdPalette.d.ts +12 -0
  23. package/lib/types/client/panel/CreateBookView.d.ts +1 -1
  24. package/lib/types/client/panel/GlobalAssetLibraryView.d.ts +1 -1
  25. package/lib/types/client/panel/ImportModal.d.ts +1 -1
  26. package/lib/types/client/panel/LiveFeedLog.d.ts +8 -0
  27. package/lib/types/client/panel/ModelManager.d.ts +2 -2
  28. package/lib/types/client/panel/NovelPanel.d.ts +7 -2
  29. package/lib/types/client/panel/PageHeader.d.ts +18 -0
  30. package/lib/types/client/panel/ProgressConsole.d.ts +1 -1
  31. package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
  32. package/lib/types/client/panel/ReaderView.d.ts +1 -1
  33. package/lib/types/client/panel/RunPanel.d.ts +1 -1
  34. package/lib/types/client/panel/SettingsView.d.ts +13 -10
  35. package/lib/types/client/panel/ShelfView.d.ts +11 -2
  36. package/lib/types/client/panel/SubPage.d.ts +27 -0
  37. package/lib/types/client/panel/TensionView.d.ts +18 -0
  38. package/lib/types/client/panel/TimelineView.d.ts +18 -0
  39. package/lib/types/client/panel/WorldTab.d.ts +1 -1
  40. package/lib/types/client/panel/helpers.d.ts +5 -3
  41. package/lib/types/client/panel/market-radar-state.d.ts +56 -0
  42. package/lib/types/client/panel/outline-markdown.d.ts +112 -0
  43. package/lib/types/client/panel/status-badge.d.ts +13 -0
  44. package/lib/types/client/panel/storage-parse.d.ts +46 -0
  45. package/lib/types/client/panel/toast.d.ts +2 -0
  46. package/lib/types/client/panel/usePersistentState.d.ts +17 -0
  47. package/lib/types/client/panel/views.d.ts +21 -35
  48. package/lib/types/client/run-status.d.ts +21 -0
  49. package/lib/types/client/sidebar-entry.d.ts +0 -2
  50. package/lib/types/digest-parse.d.ts +29 -0
  51. package/lib/types/docx-core.d.ts +16 -0
  52. package/lib/types/docx.d.ts +2 -3
  53. package/lib/types/engine.d.ts +122 -103
  54. package/lib/types/home.d.ts +11 -0
  55. package/lib/types/index.d.ts +21 -13
  56. package/lib/types/llm-live.d.ts +62 -36
  57. package/lib/types/llm-retry.d.ts +38 -0
  58. package/lib/types/market-radar-scan.d.ts +0 -1
  59. package/lib/types/market-radar-sources.d.ts +1 -1
  60. package/lib/types/migrate-output.d.ts +25 -0
  61. package/lib/types/model-capability.d.ts +119 -0
  62. package/lib/types/model-request.d.ts +36 -0
  63. package/lib/types/output-budget.d.ts +86 -0
  64. package/lib/types/plotline-align.d.ts +35 -0
  65. package/lib/types/prompt-slots.d.ts +57 -0
  66. package/lib/types/prompting.d.ts +2 -27
  67. package/lib/types/protocol.d.ts +479 -595
  68. package/lib/types/revision.d.ts +76 -0
  69. package/lib/types/routes.d.ts +9 -3
  70. package/lib/types/run-selection.d.ts +51 -0
  71. package/lib/types/run.d.ts +31 -0
  72. package/lib/types/settings-persist.d.ts +59 -0
  73. package/lib/types/snapshots.d.ts +46 -0
  74. package/lib/types/stage-contract.d.ts +58 -0
  75. package/lib/types/stream-fallback.d.ts +60 -0
  76. package/lib/types/strip-headings.d.ts +33 -0
  77. package/lib/types/tension.d.ts +53 -0
  78. package/lib/types/timeline.d.ts +37 -0
  79. package/package.json +41 -28
  80. package/assets/styles/arcane-thick-3d.webp +0 -0
  81. package/assets/styles/chinese-ink.webp +0 -0
  82. package/assets/styles/clay-stopmotion-3d.webp +0 -0
  83. package/assets/styles/dark-ghibli.webp +0 -0
  84. package/assets/styles/dark-gothic-3d.webp +0 -0
  85. package/assets/styles/film-photo.webp +0 -0
  86. package/assets/styles/guofeng-heavy.webp +0 -0
  87. package/assets/styles/guoman-2d-gufeng.webp +0 -0
  88. package/assets/styles/guoman-3d-tianyuan.webp +0 -0
  89. package/assets/styles/guoman-3d-xuanhuan.webp +0 -0
  90. package/assets/styles/horror-game-3d.webp +0 -0
  91. package/assets/styles/hyperreal-3d.webp +0 -0
  92. package/assets/styles/japan-cel-dark.webp +0 -0
  93. package/assets/styles/korean-webtoon.webp +0 -0
  94. package/assets/styles/low-cyberpunk-3d.webp +0 -0
  95. package/assets/styles/nextgen-cg.webp +0 -0
  96. package/assets/styles/noir-film.webp +0 -0
  97. package/assets/styles/openworld-survival.webp +0 -0
  98. package/assets/styles/paper-cut-3d.webp +0 -0
  99. package/assets/styles/pixar-adult-3d.webp +0 -0
  100. package/assets/styles/pop-marvel-2d.webp +0 -0
  101. package/assets/styles/sandiao-qban.webp +0 -0
  102. package/assets/styles/shinkai-anime.webp +0 -0
  103. package/assets/styles/tactical-stealth.webp +0 -0
  104. package/assets/styles/urban-lightreal-3d.webp +0 -0
  105. package/assets/styles/vaporwave-3d.webp +0 -0
  106. package/assets/styles/vhs90.webp +0 -0
  107. package/lib/types/client/panel/BookshelfBar.d.ts +0 -11
  108. package/lib/types/client/panel/FlowGuide.d.ts +0 -22
  109. package/lib/types/client/panel/MangaRoleLibrary.d.ts +0 -20
  110. package/lib/types/client/panel/MangaWorkspace.d.ts +0 -12
  111. package/lib/types/client/panel/ModelPicker.d.ts +0 -11
  112. package/lib/types/client/panel/ProgressHomeView.d.ts +0 -5
  113. package/lib/types/client/panel/PropLibrary.d.ts +0 -8
  114. package/lib/types/client/panel/SceneLibrary.d.ts +0 -16
  115. package/lib/types/client/panel/StoryboardTab.d.ts +0 -19
  116. package/lib/types/client/panel/StyleCard.d.ts +0 -8
  117. package/lib/types/client/panel/StyleGallery.d.ts +0 -1
  118. package/lib/types/comic-presets.d.ts +0 -12
  119. package/lib/types/manga-genre-rules.d.ts +0 -22
  120. package/lib/types/prompt-utils.d.ts +0 -35
  121. package/lib/types/review-policy.d.ts +0 -97
  122. package/lib/types/shot-language.d.ts +0 -46
  123. package/lib/types/src/ai-scan.d.ts +0 -36
  124. package/lib/types/src/assets.d.ts +0 -37
  125. package/lib/types/src/assistant.d.ts +0 -45
  126. package/lib/types/src/author-assets.d.ts +0 -25
  127. package/lib/types/src/bookshelf.d.ts +0 -42
  128. package/lib/types/src/client/api.d.ts +0 -234
  129. package/lib/types/src/client/docx.d.ts +0 -15
  130. package/lib/types/src/client/index.d.ts +0 -14
  131. package/lib/types/src/client/locales.d.ts +0 -212
  132. package/lib/types/src/client/mount.d.ts +0 -9
  133. package/lib/types/src/client/panel/AdaptModeView.d.ts +0 -5
  134. package/lib/types/src/client/panel/AssetsTab.d.ts +0 -12
  135. package/lib/types/src/client/panel/AssistantTab.d.ts +0 -7
  136. package/lib/types/src/client/panel/AuthorAssetsView.d.ts +0 -4
  137. package/lib/types/src/client/panel/AuthorHome.d.ts +0 -32
  138. package/lib/types/src/client/panel/BookshelfBar.d.ts +0 -11
  139. package/lib/types/src/client/panel/CreateBookView.d.ts +0 -9
  140. package/lib/types/src/client/panel/FlowGuide.d.ts +0 -22
  141. package/lib/types/src/client/panel/GlobalAssetLibraryView.d.ts +0 -4
  142. package/lib/types/src/client/panel/ImportModal.d.ts +0 -7
  143. package/lib/types/src/client/panel/MangaRoleLibrary.d.ts +0 -20
  144. package/lib/types/src/client/panel/MangaWorkspace.d.ts +0 -12
  145. package/lib/types/src/client/panel/ModelManager.d.ts +0 -14
  146. package/lib/types/src/client/panel/NovelPanel.d.ts +0 -13
  147. package/lib/types/src/client/panel/ProgressConsole.d.ts +0 -17
  148. package/lib/types/src/client/panel/ProgressHomeView.d.ts +0 -5
  149. package/lib/types/src/client/panel/PropLibrary.d.ts +0 -8
  150. package/lib/types/src/client/panel/ReaderView.d.ts +0 -8
  151. package/lib/types/src/client/panel/ReasoningSection.d.ts +0 -10
  152. package/lib/types/src/client/panel/RunPanel.d.ts +0 -5
  153. package/lib/types/src/client/panel/SceneLibrary.d.ts +0 -16
  154. package/lib/types/src/client/panel/SettingsView.d.ts +0 -16
  155. package/lib/types/src/client/panel/ShelfView.d.ts +0 -15
  156. package/lib/types/src/client/panel/StoryboardTab.d.ts +0 -19
  157. package/lib/types/src/client/panel/StyleCard.d.ts +0 -8
  158. package/lib/types/src/client/panel/StyleGallery.d.ts +0 -1
  159. package/lib/types/src/client/panel/WorldTab.d.ts +0 -10
  160. package/lib/types/src/client/panel/controller.d.ts +0 -19
  161. package/lib/types/src/client/panel/helpers.d.ts +0 -18
  162. package/lib/types/src/client/panel/views.d.ts +0 -77
  163. package/lib/types/src/client/sidebar-entry.d.ts +0 -13
  164. package/lib/types/src/client/text.d.ts +0 -9
  165. package/lib/types/src/comic-presets.d.ts +0 -12
  166. package/lib/types/src/docx.d.ts +0 -19
  167. package/lib/types/src/engine.d.ts +0 -402
  168. package/lib/types/src/index.d.ts +0 -79
  169. package/lib/types/src/manga-genre-rules.d.ts +0 -22
  170. package/lib/types/src/novel-context.d.ts +0 -80
  171. package/lib/types/src/prompt-utils.d.ts +0 -35
  172. package/lib/types/src/protocol.d.ts +0 -2176
  173. package/lib/types/src/review-policy.d.ts +0 -97
  174. package/lib/types/src/routes.d.ts +0 -29
  175. package/lib/types/src/run.d.ts +0 -32
  176. package/lib/types/src/shot-language.d.ts +0 -46
  177. package/lib/types/src/story-beat-language.d.ts +0 -35
  178. package/lib/types/src/style-library.d.ts +0 -33
  179. package/lib/types/story-beat-language.d.ts +0 -35
  180. package/lib/types/style-library.d.ts +0 -33
  181. package/src/ai-scan.ts +0 -179
  182. package/src/assets.ts +0 -1774
  183. package/src/assistant.ts +0 -915
  184. package/src/author-assets.ts +0 -147
  185. package/src/bookshelf.ts +0 -167
  186. package/src/client/api.ts +0 -716
  187. package/src/client/css-modules.d.ts +0 -8
  188. package/src/client/docx.ts +0 -69
  189. package/src/client/index.ts +0 -34
  190. package/src/client/llmLive.ts +0 -101
  191. package/src/client/locales.ts +0 -418
  192. package/src/client/mount.tsx +0 -97
  193. package/src/client/mount.tsx._new +0 -97
  194. package/src/client/mount.tsx._nf +0 -97
  195. package/src/client/mount.tsx._tmp +0 -97
  196. package/src/client/panel/AdaptModeView.tsx +0 -535
  197. package/src/client/panel/AssetsTab.tsx +0 -458
  198. package/src/client/panel/AssistantTab.tsx +0 -339
  199. package/src/client/panel/AuthorAssetsView.tsx +0 -208
  200. package/src/client/panel/AuthorHome.tsx +0 -234
  201. package/src/client/panel/BookAnalysisView.tsx +0 -60
  202. package/src/client/panel/BookshelfBar.tsx +0 -170
  203. package/src/client/panel/CreateBookView.tsx +0 -371
  204. package/src/client/panel/DirectorView.tsx +0 -200
  205. package/src/client/panel/FlowGuide.tsx +0 -84
  206. package/src/client/panel/GlobalAssetLibraryView.tsx +0 -172
  207. package/src/client/panel/IdeaInspirationView.tsx +0 -71
  208. package/src/client/panel/ImportModal.tsx +0 -303
  209. package/src/client/panel/KnowledgeBaseView.tsx +0 -73
  210. package/src/client/panel/LiveFeedLog.tsx +0 -76
  211. package/src/client/panel/MangaRoleLibrary.tsx +0 -1002
  212. package/src/client/panel/MangaWorkspace.tsx +0 -715
  213. package/src/client/panel/MarketRadarView.tsx +0 -374
  214. package/src/client/panel/ModelManager.tsx +0 -205
  215. package/src/client/panel/NovelPanel.tsx +0 -4721
  216. package/src/client/panel/ProgressConsole.tsx +0 -63
  217. package/src/client/panel/ProgressHomeView.tsx +0 -87
  218. package/src/client/panel/PropLibrary.tsx +0 -175
  219. package/src/client/panel/ReaderView.tsx +0 -306
  220. package/src/client/panel/ReasoningSection.tsx +0 -31
  221. package/src/client/panel/RunPanel.tsx +0 -204
  222. package/src/client/panel/SceneLibrary.tsx +0 -453
  223. package/src/client/panel/SettingsView.tsx +0 -289
  224. package/src/client/panel/ShelfView.tsx +0 -242
  225. package/src/client/panel/StoryboardTab.tsx +0 -472
  226. package/src/client/panel/StyleCard.tsx +0 -90
  227. package/src/client/panel/StyleGallery.tsx +0 -58
  228. package/src/client/panel/WorldTab.tsx +0 -202
  229. package/src/client/panel/controller.ts +0 -45
  230. package/src/client/panel/helpers.ts +0 -67
  231. package/src/client/panel/panel.module.css +0 -4359
  232. package/src/client/panel/views.tsx +0 -314
  233. package/src/client/sidebar-entry.ts +0 -122
  234. package/src/client/text.ts +0 -28
  235. package/src/comic-presets.ts +0 -67
  236. package/src/docx.ts +0 -83
  237. package/src/engine.ts +0 -5636
  238. package/src/global-assets.ts +0 -59
  239. package/src/index.ts +0 -346
  240. package/src/llm-live.ts +0 -68
  241. package/src/manga-genre-rules.ts +0 -87
  242. package/src/market-radar-scan.ts +0 -41
  243. package/src/market-radar-sources.ts +0 -173
  244. package/src/novel-context.ts +0 -287
  245. package/src/prompt-utils.ts +0 -73
  246. package/src/prompting.ts +0 -159
  247. package/src/protocol.ts +0 -2565
  248. package/src/review-policy.ts +0 -161
  249. package/src/routes.ts +0 -3939
  250. package/src/run.ts +0 -338
  251. package/src/shot-language.ts +0 -181
  252. package/src/story-beat-language.ts +0 -130
  253. package/src/style-library.ts +0 -84
@@ -1,172 +0,0 @@
1
- /**
2
- * 全局写作资产库:只读浏览插件内置的公共资产(题材/反AI规则/风格模板/推进模式)。
3
- * 内置库为固定内容;实际绑定到某本书在「创作资产」页。
4
- */
5
- import { useEffect, useState } from 'react'
6
- import { Info, BookOpen } from 'lucide-react'
7
- import type { NovelApi } from '../api.ts'
8
- import type { AssetsResponse, GenreNode, AntiAiRule, StyleTemplate, ProgressionMode, PlotBeatTemplate } from '../../protocol.ts'
9
- import css from './panel.module.css'
10
-
11
- type Tab = 'genre' | 'antiAi' | 'style' | 'progression' | 'plotBeat'
12
-
13
- export function GlobalAssetLibraryView({ api }: { api: NovelApi }) {
14
- const [data, setData] = useState<AssetsResponse | null>(null);
15
- const [error, setError] = useState('');
16
- const [tab, setTab] = useState<Tab>('genre');
17
-
18
- useEffect(() => {
19
- let cancelled = false
20
- void (async () => {
21
- try {
22
- const res = await api.assets()
23
- if (!cancelled) setData(res)
24
- } catch (err) {
25
- if (!cancelled) setError((err as Error).message)
26
- }
27
- })()
28
- return () => { cancelled = true }
29
- }, [api]);
30
-
31
- const renderGenreDescendants = (children: GenreNode[], depth: number): JSX.Element[] =>
32
- children.map(c => (
33
- <div key={c.name} style={{ paddingLeft: depth * 12, marginTop: 2 }}>
34
- <span className={css.meta}><b>{c.name}</b>:{c.description}</span>
35
- {(c.children ?? []).length > 0 && renderGenreDescendants(c.children ?? [], depth + 1)}
36
- </div>
37
- ));
38
-
39
- const renderGenre = (nodes: GenreNode[]): JSX.Element[] =>
40
- nodes.map(n => (
41
- <div key={n.name} className={css.assetCard}>
42
- <div className={css.assetCardName}>{n.name}</div>
43
- <div className={css.meta}>{n.description}</div>
44
- {(n.children ?? []).length > 0 && (
45
- <div style={{ marginTop: 6 }}>{renderGenreDescendants(n.children ?? [], 0)}</div>
46
- )}
47
- </div>
48
- ));
49
-
50
- const renderAntiAi = (rules: AntiAiRule[]): JSX.Element[] =>
51
- rules.map(r => (
52
- <div key={r.name} className={css.assetCard}>
53
- <div className={css.assetCardTop}><span className={css.assetKind} style={{ color: 'var(--nf-warn)' }}>反AI</span></div>
54
- <div className={css.assetCardName}>{r.name}</div>
55
- <div className={css.assetCardContent}>避免:{r.avoid}{r.fix !== '' ? '\n修正:' + r.fix : ''}</div>
56
- {(r.detectPatterns ?? []).length > 0 && <div className={css.meta}>命中模式:{(r.detectPatterns ?? []).join('、')}</div>}
57
- </div>
58
- ));
59
-
60
- const renderStyle = (templates: StyleTemplate[]): JSX.Element[] =>
61
- templates.map(t => (
62
- <div key={t.key} className={css.assetCard}>
63
- <div className={css.assetCardTop}><span className={css.assetKind} style={{ color: 'var(--nf-info)' }}>{t.category}</span></div>
64
- <div className={css.assetCardName}>{t.name}</div>
65
- <div className={css.meta}>{t.description}</div>
66
- <div className={css.assetCardContent}>
67
- 适用:{(t.applicableGenres ?? []).join('、')}
68
- {t.proseRules.length > 0 ? '\n叙述:' + t.proseRules.join(';') : ''}
69
- {t.dialogueRules.length > 0 ? '\n台词:' + t.dialogueRules.join(';') : ''}
70
- {t.languageRules.length > 0 ? '\n语言:' + t.languageRules.join(';') : ''}
71
- {t.rhythmRules.length > 0 ? '\n节奏:' + t.rhythmRules.join(';') : ''}
72
- </div>
73
- </div>
74
- ));
75
-
76
- const renderProgression = (modes: ProgressionMode[]): JSX.Element[] =>
77
- modes.map(m => (
78
- <div key={m.name} className={css.assetCard}>
79
- <div className={css.assetCardTop}><span className={css.assetKind} style={{ color: 'var(--nf-accent)' }}>{m.primary ? '主模式' : '辅助'}</span></div>
80
- <div className={css.assetCardName}>{m.name}</div>
81
- <div className={css.meta}>驱动:{m.driver}</div>
82
- <div className={css.assetCardContent}>
83
- 期待:{m.readerExpectation}
84
- {m.payoffs.length > 0 ? '\n兑现:' + m.payoffs.join(';') : ''}
85
- {m.risks.length > 0 ? '\n风险:' + m.risks.join(';') : ''}
86
- </div>
87
- </div>
88
- ));
89
-
90
- const renderPlotBeat = (beats: PlotBeatTemplate[]): JSX.Element[] =>
91
- beats.map(b => (
92
- <div key={b.key} className={css.assetCard}>
93
- <div className={css.assetCardTop}><span className={css.assetKind} style={{ color: 'var(--nf-accent)' }}>{b.category}</span></div>
94
- <div className={css.assetCardName}>{b.name}</div>
95
- <div className={css.meta}>{b.summary}</div>
96
- <div className={css.assetCardContent}>
97
- 位置:{b.position}
98
- {(b.preconditions ?? []).length > 0 ? '\n前置:' + b.preconditions.join(';') : ''}
99
- {(b.payoffSource ?? []).length > 0 ? '\n爽点:' + b.payoffSource.join(';') : ''}
100
- {(b.combos ?? []).length > 0 ? '\n组合:' + b.combos.join(';') : ''}
101
- {(b.taboos ?? []).length > 0 ? '\n禁忌:' + b.taboos.join(';') : ''}
102
- </div>
103
- </div>
104
- ));
105
-
106
- return (
107
- <div className={css.authorPageBody}>
108
- <div className={css.authorPageHero}>
109
- <div className={css.authorPageHeader}>
110
- <div>
111
- <h2 className={css.panelTitle} style={{ margin: 0 }}>🎨 全局写作资产库</h2>
112
- <span className={css.meta}>内置题材基底库 / 反AI规则库 / 风格模板 / 推进模式 / 剧情桥段库(跨书)——插件自带、只读。</span>
113
- </div>
114
- </div>
115
- <div className={css.assetFilterBar}>
116
- <button type="button" className={css.assetFilterChip + (tab === 'genre' ? ' ' + css.assetFilterChipActive : '')} onClick={() => setTab('genre')}>题材 {(data?.genreLibrary ?? []).length}</button>
117
- <button type="button" className={css.assetFilterChip + (tab === 'antiAi' ? ' ' + css.assetFilterChipActive : '')} onClick={() => setTab('antiAi')}>反AI规则 {(data?.antiAiLibrary ?? []).length}</button>
118
- <button type="button" className={css.assetFilterChip + (tab === 'style' ? ' ' + css.assetFilterChipActive : '')} onClick={() => setTab('style')}>风格模板 {(data?.styleTemplates ?? []).length}</button>
119
- <button type="button" className={css.assetFilterChip + (tab === 'progression' ? ' ' + css.assetFilterChipActive : '')} onClick={() => setTab('progression')}>推进模式 {(data?.progressionLibrary ?? []).length}</button>
120
- <button type="button" className={css.assetFilterChip + (tab === 'plotBeat' ? ' ' + css.assetFilterChipActive : '')} onClick={() => setTab('plotBeat')}>剧情桥段 {(data?.plotBeatLibrary ?? []).length}</button>
121
- </div>
122
- </div>
123
-
124
- {error !== '' && <div className={css.noticeError}>{error}</div>}
125
-
126
- {data === null ? (
127
- <div className={css.shelfEmpty} style={{ minHeight: 160 }}>
128
- <span className={css.shelfEmptyIcon}><Info size={30} /></span>
129
- <span className={css.shelfEmptyTitle}>加载中…</span>
130
- <span className={css.meta}>正在读取全局内置资产库。</span>
131
- </div>
132
- ) : (
133
- <div className={css.assetGroups}>
134
- {tab === 'genre' && (
135
- <div className={css.assetGroup}>
136
- <div className={css.assetGroupHeader}>题材基底库 <span className={css.meta}>树形 / 只读</span></div>
137
- <div className={css.assetGrid}>{renderGenre(data.genreLibrary)}</div>
138
- </div>
139
- )}
140
- {tab === 'antiAi' && (
141
- <div className={css.assetGroup}>
142
- <div className={css.assetGroupHeader}>反AI规则库 <span className={css.meta}>只读 / 可在书内复制为自定义</span></div>
143
- <div className={css.assetGrid}>{renderAntiAi(data.antiAiLibrary)}</div>
144
- </div>
145
- )}
146
- {tab === 'style' && (
147
- <div className={css.assetGroup}>
148
- <div className={css.assetGroupHeader}>风格模板 <span className={css.meta}>只读 / 可在书内一键绑定</span></div>
149
- <div className={css.assetGrid}>{renderStyle(data.styleTemplates)}</div>
150
- </div>
151
- )}
152
- {tab === 'progression' && (
153
- <div className={css.assetGroup}>
154
- <div className={css.assetGroupHeader}>推进模式 <span className={css.meta}>只读</span></div>
155
- <div className={css.assetGrid}>{renderProgression(data.progressionLibrary)}</div>
156
- </div>
157
- )}
158
- {tab === 'plotBeat' && (
159
- <div className={css.assetGroup}>
160
- <div className={css.assetGroupHeader}>剧情桥段库 <span className={css.meta}>只读 / 可收藏或导入作者库</span></div>
161
- <div className={css.assetGrid}>{renderPlotBeat(data.plotBeatLibrary)}</div>
162
- </div>
163
- )}
164
- </div>
165
- )}
166
-
167
- <div className={css.meta} style={{ marginTop: 10 }}>
168
- 提示:内置库为插件自带、只读。想要在书里使用,请进入某本书的「创作资产」页绑定为项目资产,或在「作者资产库」里「导入默认资产」沉淀成可复用的个人条目。
169
- </div>
170
- </div>
171
- );
172
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * 创意灵感:一句话/题材 → 多方向开书灵感。
3
- */
4
- import { useState } from 'react'
5
- import type { NovelApi } from '../api.ts'
6
- import type { IdeaInspirationResult } from '../../protocol.ts'
7
- import css from './panel.module.css'
8
-
9
- export default function IdeaInspirationView({ api, onUseIdea }: { api: NovelApi; onUseIdea?: (idea: IdeaInspirationResult['ideas'][number]) => void }): JSX.Element {
10
- const [idea, setIdea] = useState('')
11
- const [count, setCount] = useState(5)
12
- const [busy, setBusy] = useState(false)
13
- const [error, setError] = useState('')
14
- const [result, setResult] = useState<IdeaInspirationResult | null>(null)
15
-
16
- const copyText = (it: IdeaInspirationResult['ideas'][number]): string =>
17
- `《${it.title}》\n题材:${it.genre}\n视角:${it.pov}\n钩子:${it.hook}\n长期兑现:${it.payoff}`
18
-
19
- const copy = async (it: IdeaInspirationResult['ideas'][number]): Promise<void> => {
20
- try { await navigator.clipboard.writeText(copyText(it)); setError('') } catch (e) { setError('复制失败:' + (e as Error).message) }
21
- }
22
-
23
- const run = async (): Promise<void> => {
24
- if (idea.trim().length < 2) { setError('请填写你的方向/一句话想法'); return }
25
- setBusy(true); setError('')
26
- try {
27
- const r = await api.ideaInspiration(idea.trim(), count)
28
- setResult(r.result)
29
- } catch (e) { setError((e as Error).message) } finally { setBusy(false) }
30
- }
31
-
32
- return (
33
- <div className={css.authorPageBody}>
34
- <div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-10)' }}>
35
- <span className={css.cardTitle}>💡 创意灵感</span>
36
- <span className={css.meta}>输入一句话/题材方向 → 给出多个可开书的差异化创意(不照搬具体作品)。</span>
37
- <textarea className={css.input} style={{ minHeight: 120, resize: 'vertical' }} value={idea} onChange={e => setIdea(e.target.value)} placeholder="例:重生后我在城隍庙摆摊;反派每天都被打脸却越来越强…" />
38
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
39
- <label style={{ fontSize: 12 }}>数量</label>
40
- <select className={css.input} style={{ width: 'auto', padding: '4px 8px' }} value={count} onChange={e => setCount(Number(e.target.value))}>
41
- {[3, 5, 8].map(n => <option key={n} value={n}>{n}</option>)}
42
- </select>
43
- <button type="button" className={`${css.button} ${css.buttonPrimary}`} disabled={busy} onClick={() => { void run() }}>
44
- {busy ? '生成中…' : '生成灵感'}
45
- </button>
46
- </div>
47
- </div>
48
-
49
- {error !== '' && <div style={{ color: 'var(--nf-error)', fontSize: 13 }}>⚠ {error}</div>}
50
-
51
- {result !== null && (
52
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 'var(--nf-space-10)' }}>
53
- {(result.ideas ?? []).map((it, i) => (
54
- <div key={i} className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-6)' }}>
55
- <div style={{ fontSize: 15, fontWeight: 600 }}>{it.title}</div>
56
- <div style={{ fontSize: 12, color: 'var(--nf-success)' }}>{it.genre} · {it.pov}</div>
57
- <div style={{ fontSize: 13 }}>钩子:{it.hook}</div>
58
- <div style={{ fontSize: 12, color: 'var(--nf-text-2)' }}>兑现:{it.payoff}</div>
59
- <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
60
- <button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { void copy(it) }} title="复制给开书向导/其他用到">复制</button>
61
- {onUseIdea !== undefined && (
62
- <button type="button" className={`${css.button} ${css.buttonSmall} ${css.buttonPrimary}`} onClick={() => onUseIdea(it)} title="带着这个灵感去开书(预填书名与想法)">以此方向开书</button>
63
- )}
64
- </div>
65
- </div>
66
- ))}
67
- </div>
68
- )}
69
- </div>
70
- )
71
- }
@@ -1,303 +0,0 @@
1
- /**
2
- * 导入小说弹窗:两种模式 ——
3
- * A) 已有项目目录(含 novel-project.json)→ 登记/激活书架(粘贴服务器本地路径);
4
- * B) txt/md 全本 → 点击选择文件,浏览器读取内容 → 服务器拆章预览 → 确认后导入。
5
- * Mode B 也保留「服务器本地路径」高级选项(大文件或服务器端已有文件时用)。
6
- */
7
- import { useEffect, useRef, useState } from 'react'
8
- import type { NovelApi } from '../api.ts'
9
- import type { BookImportTextPreviewResponse } from '../../protocol.ts'
10
- import { readFileTextSmart } from '../text.ts'
11
- import css from './panel.module.css'
12
-
13
- type ImportMode = 'dir' | 'text'
14
-
15
- /** 导入结果(成功态展示)。 */
16
- interface ImportResult {
17
- kind: 'dir' | 'text'
18
- bookName: string
19
- existed?: boolean
20
- chapters?: number
21
- skipped?: string[]
22
- }
23
-
24
- export function ImportModal({
25
- api,
26
- onClose,
27
- onImported,
28
- }: {
29
- api: NovelApi
30
- onClose: () => void
31
- /** 导入成功并已激活该书后回调(刷新书架)。 */
32
- onImported: () => void | Promise<void>
33
- }) {
34
- const [mode, setMode] = useState<ImportMode>('dir')
35
- // Mode A
36
- const [dir, setDir] = useState('')
37
- // Mode B:文件选择 + 预览
38
- const fileInputRef = useRef<HTMLInputElement | null>(null)
39
- const [fileName, setFileName] = useState('')
40
- const [fileText, setFileText] = useState<string | null>(null)
41
- const [previewing, setPreviewing] = useState(false)
42
- const [preview, setPreview] = useState<BookImportTextPreviewResponse | null>(null)
43
- // Mode B:服务器本地路径(高级)
44
- const [showPath, setShowPath] = useState(false)
45
- const [filePath, setFilePath] = useState('')
46
- // 通用
47
- const [outDir, setOutDir] = useState('')
48
- const [busy, setBusy] = useState(false)
49
- const [error, setError] = useState('')
50
- const [result, setResult] = useState<ImportResult | null>(null)
51
-
52
- /** 选择文件 → 读取内容 → 调服务器预览拆章。 */
53
- const onPickFile = async (file: File | undefined) => {
54
- if (file === undefined) return
55
- setError('')
56
- setResult(null)
57
- setPreview(null)
58
- setFileName(file.name)
59
- setPreviewing(true)
60
- try {
61
- const text = await readFileTextSmart(file)
62
- setFileText(text)
63
- const p = await api.bookImportTextPreview(text, file.name)
64
- setPreview(p)
65
- } catch (err) {
66
- setError((err as Error).message)
67
- } finally {
68
- setPreviewing(false)
69
- }
70
- }
71
-
72
- const runImport = async () => {
73
- setError('')
74
- setResult(null)
75
- if (mode === 'dir') {
76
- const d = dir.trim()
77
- if (d === '') { setError('请输入项目目录(含 novel-project.json 的文件夹)'); return }
78
- setBusy(true)
79
- try {
80
- const r = await api.bookImportDir(d)
81
- setResult({ kind: 'dir', bookName: r.book.bookName, existed: r.existed })
82
- await onImported()
83
- } catch (err) {
84
- setError((err as Error).message)
85
- } finally {
86
- setBusy(false)
87
- }
88
- return
89
- }
90
- // Mode B:优先上传内容;未选文件时退回服务器路径
91
- if (fileText !== null && fileText.length > 0) {
92
- setBusy(true)
93
- try {
94
- const o = outDir.trim()
95
- const r = await api.bookImportTextContent(fileText, fileName, o !== '' ? o : undefined)
96
- setResult({ kind: 'text', bookName: r.bookName, chapters: r.chapters, skipped: r.skipped })
97
- await onImported()
98
- } catch (err) {
99
- setError((err as Error).message)
100
- } finally {
101
- setBusy(false)
102
- }
103
- return
104
- }
105
- const f = filePath.trim()
106
- if (f === '') { setError('请先选择文件,或输入服务器本地文件路径'); return }
107
- setBusy(true)
108
- try {
109
- const o = outDir.trim()
110
- const r = await api.bookImportText(f, o !== '' ? o : undefined)
111
- setResult({ kind: 'text', bookName: r.bookName, chapters: r.chapters, skipped: r.skipped })
112
- await onImported()
113
- } catch (err) {
114
- setError((err as Error).message)
115
- } finally {
116
- setBusy(false)
117
- }
118
- }
119
-
120
- const switchMode = (m: ImportMode) => { setMode(m); setError(''); setResult(null); setPreview(null) }
121
-
122
- // Esc 关闭弹窗。
123
- useEffect(() => {
124
- const onKey = (e: KeyboardEvent): void => {
125
- if (e.key === 'Escape') onClose()
126
- }
127
- window.addEventListener('keydown', onKey)
128
- return () => { window.removeEventListener('keydown', onKey) }
129
- }, [onClose])
130
-
131
- return (
132
- <div className={css.importModalOverlay} role="dialog" aria-modal="true" aria-label="导入小说" onClick={e => { if (e.target === e.currentTarget) onClose() }}>
133
- <div className={css.importModal}>
134
- <div className={css.importModalHead}>
135
- <span className={css.panelTitle} style={{ margin: 0 }}>📥 导入小说</span>
136
- <button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={onClose} title="关闭">✕</button>
137
- </div>
138
-
139
- <div className={css.importModalTabs} role="tablist">
140
- <button
141
- type="button"
142
- role="tab"
143
- aria-selected={mode === 'dir'}
144
- className={`${css.button} ${mode === 'dir' ? css.buttonPrimary : ''}`}
145
- onClick={() => { switchMode('dir') }}
146
- >
147
- 📂 A · 已有项目目录
148
- </button>
149
- <button
150
- type="button"
151
- role="tab"
152
- aria-selected={mode === 'text'}
153
- className={`${css.button} ${mode === 'text' ? css.buttonPrimary : ''}`}
154
- onClick={() => { switchMode('text') }}
155
- >
156
- 📄 B · txt/md 全本
157
- </button>
158
- </div>
159
-
160
- <div className={css.importModalBody}>
161
- {mode === 'dir' ? (
162
- <>
163
- <label className={css.importField}>
164
- <span>项目目录(绝对路径)</span>
165
- <input
166
- className={css.input}
167
- type="text"
168
- placeholder="例如 D:\novels\我的小说"
169
- value={dir}
170
- onChange={e => { setDir(e.target.value) }}
171
- />
172
- </label>
173
- <div className={css.importHint}>
174
- 目录需含 novel-project.json。已在书架中的目录会直接切换激活,不会重复登记。
175
- </div>
176
- </>
177
- ) : (
178
- <>
179
- {/* 文件选择 + 预览 */}
180
- <input
181
- ref={fileInputRef}
182
- type="file"
183
- accept=".txt,.md,text/plain,text/markdown"
184
- style={{ display: 'none' }}
185
- onChange={e => { void onPickFile(e.target.files?.[0]); e.target.value = '' }}
186
- />
187
- <div className={css.importField}>
188
- <span>全本文本文件(txt / md)</span>
189
- <button
190
- type="button"
191
- className={`${css.button} ${css.buttonPrimary}`}
192
- onClick={() => { fileInputRef.current?.click() }}
193
- >
194
- 📄 选择文件
195
- </button>
196
- </div>
197
- {fileName !== '' && (
198
- <div className={css.importFileInfo}>
199
- <span>已选择:{fileName}</span>
200
- {previewing && <span className={css.meta}>正在拆章预览…</span>}
201
- </div>
202
- )}
203
- {preview !== null && (
204
- <div className={css.importPreview}>
205
- <span>📖 《{preview.bookName}》 · 识别到 {preview.chapters.length} 章</span>
206
- {preview.chapters.length > 0 && (
207
- <ul className={css.importPreviewList}>
208
- {preview.chapters.slice(0, 5).map(c => (
209
- <li key={c.no}>第{c.no}章 {c.title}({c.chars} 字)</li>
210
- ))}
211
- {preview.chapters.length > 5 && <li className={css.meta}>… 共 {preview.chapters.length} 章</li>}
212
- </ul>
213
- )}
214
- {preview.skipped.length > 0 && (
215
- <span className={css.meta}>
216
- 跳过 {preview.skipped.length} 个过短章节:{preview.skipped.slice(0, 5).join('、')}
217
- {preview.skipped.length > 5 ? ' 等' : ''}
218
- </span>
219
- )}
220
- </div>
221
- )}
222
-
223
- {/* 输出目录 + 高级路径选项 */}
224
- <label className={css.importField}>
225
- <span>输出目录(可选,默认 ~/.dsh/novels/书名)</span>
226
- <input
227
- className={css.input}
228
- type="text"
229
- placeholder="留空使用默认目录"
230
- value={outDir}
231
- onChange={e => { setOutDir(e.target.value) }}
232
- />
233
- </label>
234
- <button
235
- type="button"
236
- className={`${css.button} ${css.buttonSmall}`}
237
- onClick={() => { setShowPath(!showPath) }}
238
- >
239
- {showPath ? '▾ 收起高级选项' : '▸ 高级:使用服务器本地文件路径'}
240
- </button>
241
- {showPath && (
242
- <label className={css.importField}>
243
- <span>服务器本地文件绝对路径(大文件或服务器端已有文件时用)</span>
244
- <input
245
- className={css.input}
246
- type="text"
247
- placeholder="例如 D:\novels\全本.txt"
248
- value={filePath}
249
- onChange={e => { setFilePath(e.target.value) }}
250
- />
251
- </label>
252
- )}
253
- <div className={css.importHint}>
254
- 服务器按「第X章/第X回/第X节/第X卷」、中文数字章节(一、二、三…)、序章/楔子/尾声、英文 Chapter N 拆章;正文过短(&lt;50 字)的章节会跳过并列出。
255
- </div>
256
- </>
257
- )}
258
-
259
- {error !== '' && <div className={css.importError}>{error}</div>}
260
-
261
- {result !== null && (
262
- <div className={css.importResult}>
263
- {result.kind === 'dir' ? (
264
- <>
265
- <span>✅ 已{result.existed === true ? '重新激活' : '登记'}《{result.bookName}》</span>
266
- <span className={css.meta}>
267
- {result.existed === true
268
- ? '该目录已在书架中,现已切换为当前书;工作台数据按该书目录加载。'
269
- : '已加入书架并设为当前书,进入工作台即可看到全部模块。'
270
- }
271
- </span>
272
- </>
273
- ) : (
274
- <>
275
- <span>✅ 《{result.bookName}》导入完成:{result.chapters} 章</span>
276
- {(result.skipped ?? []).length > 0 && (
277
- <span className={css.meta}>
278
- 跳过 {result.skipped!.length} 个过短章节:{result.skipped!.slice(0, 8).join('、')}
279
- {result.skipped!.length > 8 ? ` 等 ${result.skipped!.length} 个` : ''}
280
- </span>
281
- )}
282
- <span className={css.meta}>章节已按「written」登记,进入工作台后逐章审稿/补设定即可。</span>
283
- </>
284
- )}
285
- </div>
286
- )}
287
- </div>
288
-
289
- <div className={css.importModalActions}>
290
- <button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={onClose}>关闭</button>
291
- <button
292
- type="button"
293
- className={`${css.button} ${css.buttonPrimary}`}
294
- disabled={busy}
295
- onClick={runImport}
296
- >
297
- {busy ? '导入中…' : '开始导入'}
298
- </button>
299
- </div>
300
- </div>
301
- </div>
302
- )
303
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * 书内知识库:添加/删除参考资料,生成/规划时自动检索注入。
3
- */
4
- import { useEffect, useState } from 'react'
5
- import type { NovelApi } from '../api.ts'
6
- import type { KnowledgeDoc } from '../../protocol.ts'
7
- import css from './panel.module.css'
8
-
9
- export default function KnowledgeBaseView({ api }: { api: NovelApi }): JSX.Element {
10
- const [docs, setDocs] = useState<KnowledgeDoc[]>([])
11
- const [title, setTitle] = useState('')
12
- const [content, setContent] = useState('')
13
- const [busy, setBusy] = useState(false)
14
- const [notice, setNotice] = useState('')
15
- const [error, setError] = useState('')
16
-
17
- const load = async (): Promise<void> => {
18
- try {
19
- const r = await api.knowledgeList()
20
- setDocs(r.docs)
21
- } catch (e) { setError((e as Error).message) }
22
- }
23
- useEffect(() => { void load() }, [])
24
-
25
- const add = async (): Promise<void> => {
26
- if (title.trim() === '') { setError('标题不能为空'); return }
27
- setBusy(true); setError(''); setNotice('')
28
- try {
29
- const r = await api.knowledgeAdd({ title: title.trim(), content: content.trim() })
30
- setDocs(r.docs); setTitle(''); setContent(''); setNotice('✅ 已添加')
31
- } catch (e) { setError((e as Error).message) } finally { setBusy(false) }
32
- }
33
-
34
- const remove = async (id: string): Promise<void> => {
35
- setBusy(true); setError('')
36
- try {
37
- const r = await api.knowledgeRemove(id)
38
- setDocs(r.docs)
39
- } catch (e) { setError((e as Error).message) } finally { setBusy(false) }
40
- }
41
-
42
- return (
43
- <div className={css.authorPageBody}>
44
- <div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-10)' }}>
45
- <span className={css.cardTitle}>📚 书内知识库</span>
46
- <span className={css.meta}>自由参考文档:把零散设定/资料/参考放这里,正文生成会按当前章节检索注入。结构化设定(世界观/角色/境界/红线)请放「本书设定」,这里是补充/碎片资料。</span>
47
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)' }}>
48
- <input className={css.input} value={title} onChange={e => setTitle(e.target.value)} placeholder="标题(如:天机锁设定 / 止水驿关系表)" />
49
- <textarea className={css.input} style={{ minHeight: 100, resize: 'vertical' }} value={content} onChange={e => setContent(e.target.value)} placeholder="内容(将被检索注入生成提示)" />
50
- </div>
51
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
52
- <button type="button" className={`${css.button} ${css.buttonPrimary}`} disabled={busy} onClick={() => { void add() }}>添加</button>
53
- {notice !== '' && <span style={{ fontSize: 12, color: 'var(--nf-success)' }}>{notice}</span>}
54
- </div>
55
- </div>
56
-
57
- {error !== '' && <div style={{ color: 'var(--nf-error)', fontSize: 13 }}>⚠ {error}</div>}
58
-
59
- <div className={`${css.card} ${css.settingsCard}`} style={{ gap: 'var(--nf-space-8)' }}>
60
- <span className={css.cardTitle}>已有文档({docs.length})</span>
61
- {docs.length === 0 ? <div style={{ color: 'var(--nf-text-2)', fontSize: 13 }}>暂无知识库文档。</div> : docs.map(d => (
62
- <div key={d.id} style={{ border: '1px solid var(--nf-border)', borderRadius: 8, padding: 'var(--nf-space-8)' }}>
63
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
64
- <b style={{ flex: 1 }}>{d.title}</b>
65
- <button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { void remove(d.id) }}>删除</button>
66
- </div>
67
- <div style={{ fontSize: 12, color: 'var(--nf-text-2)', marginTop: 4, whiteSpace: 'pre-wrap' }}>{d.content}</div>
68
- </div>
69
- ))}
70
- </div>
71
- </div>
72
- )
73
- }