@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,97 +0,0 @@
1
- /**
2
- * Panel view mounting — mirrors the family plugins: a container appended
3
- * inside the conversation grid item, hidden while inactive; toggling is a
4
- * data attribute on <html>, with cross-plugin activation events.
5
- */
6
- import { createRoot, type Root } from 'react-dom/client'
7
- import type { NovelApi } from './api.ts'
8
- import type { PanelController } from './panel/controller.ts'
9
- import { NovelPanel } from './panel/NovelPanel.tsx'
10
- import css from './panel/panel.module.css'
11
-
12
- /** The injected panel container. */
13
- export const PANEL_VIEW_SELECTOR = '[data-dsh-novelforge-view]'
14
-
15
- const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
16
- const ACTIVE_ATTR = 'data-dsh-novelforge-active'
17
- /** Sibling panels' activation attributes (evicted when this panel opens). */
18
- const OTHER_ACTIVE_ATTRS = ['data-dsh-taskboard-active', 'data-dsh-ssh-active']
19
- /** Cross-plugin activation event; detail is the activating panel name. */
20
- const ACTIVATE_EVENT = 'dsh-panel-activate'
21
- const PANEL_NAME = 'novelforge'
22
-
23
- /** Find the center column, or undefined while the frame is not mounted. */
24
- function conversationColumn(): HTMLElement | undefined {
25
- return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
26
- }
27
-
28
- /**
29
- * Mount the panel React tree into the center column and bind visibility to
30
- * the controller.
31
- */
32
- export function mountPanel(controller: PanelController, api: NovelApi): () => void {
33
- let root: Root | undefined
34
- let container: HTMLDivElement | undefined
35
-
36
- const ensure = (): void => {
37
- if (container !== undefined) {
38
- if (container.isConnected) return
39
- root?.unmount()
40
- root = undefined
41
- container.remove()
42
- container = undefined
43
- }
44
- const column = conversationColumn()
45
- if (column === undefined) return
46
- container = document.createElement('div')
47
- container.dataset.dshNovelforgeView = 'true'
48
- container.className = css.view
49
- column.appendChild(container)
50
- root = createRoot(container)
51
- root.render(<NovelPanel controller={controller} api={api} />)
52
- }
53
-
54
- const waitObserver = new MutationObserver(() => { ensure() })
55
- waitObserver.observe(document.body, { childList: true, subtree: true })
56
-
57
- const applyActive = (): void => {
58
- if (controller.getSnapshot().panelOpen) {
59
- for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
60
- document.documentElement.setAttribute(ACTIVE_ATTR, '')
61
- document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
62
- } else {
63
- document.documentElement.removeAttribute(ACTIVE_ATTR)
64
- }
65
- }
66
- const onOtherActivate = (event: Event): void => {
67
- const detail = (event as CustomEvent).detail
68
- if ((detail === 'taskboard' || detail === 'ssh') && controller.getSnapshot().panelOpen) {
69
- controller.close()
70
- }
71
- }
72
- // Jump out on sidebar context clicks.
73
- const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
74
- const onClickSidebarRow = (event: MouseEvent): void => {
75
- if (!controller.getSnapshot().panelOpen) return
76
- const target = event.target as HTMLElement | null
77
- if (target === null) return
78
- if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close()
79
- }
80
- document.addEventListener('click', onClickSidebarRow, true)
81
- document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
82
- const unsubscribe = controller.subscribe(applyActive)
83
- applyActive()
84
- ensure()
85
-
86
- return () => {
87
- document.removeEventListener('click', onClickSidebarRow, true)
88
- document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
89
- waitObserver.disconnect()
90
- unsubscribe()
91
- document.documentElement.removeAttribute(ACTIVE_ATTR)
92
- root?.unmount()
93
- root = undefined
94
- container?.remove()
95
- container = undefined
96
- }
97
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * Panel view mounting — mirrors the family plugins: a container appended
3
- * inside the conversation grid item, hidden while inactive; toggling is a
4
- * data attribute on <html>, with cross-plugin activation events.
5
- */
6
- import { createRoot, type Root } from 'react-dom/client'
7
- import type { NovelApi } from './api.ts'
8
- import type { PanelController } from './panel/controller.ts'
9
- import { NovelPanel } from './panel/NovelPanel.tsx'
10
- import css from './panel/panel.module.css'
11
-
12
- /** The injected panel container. */
13
- export const PANEL_VIEW_SELECTOR = '[data-dsh-novelforge-view]'
14
-
15
- const CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"], [class*="centerCol"]'
16
- const ACTIVE_ATTR = 'data-dsh-novelforge-active'
17
- /** Sibling panels' activation attributes (evicted when this panel opens). */
18
- const OTHER_ACTIVE_ATTRS = ['data-dsh-taskboard-active', 'data-dsh-ssh-active']
19
- /** Cross-plugin activation event; detail is the activating panel name. */
20
- const ACTIVATE_EVENT = 'dsh-panel-activate'
21
- const PANEL_NAME = 'novelforge'
22
-
23
- /** Find the center column, or undefined while the frame is not mounted. */
24
- function conversationColumn(): HTMLElement | undefined {
25
- return document.querySelector<HTMLElement>(CONVERSATION_COLUMN_SELECTOR) ?? undefined
26
- }
27
-
28
- /**
29
- * Mount the panel React tree into the center column and bind visibility to
30
- * the controller.
31
- */
32
- export function mountPanel(controller: PanelController, api: NovelApi): () => void {
33
- let root: Root | undefined
34
- let container: HTMLDivElement | undefined
35
-
36
- const ensure = (): void => {
37
- if (container !== undefined) {
38
- if (container.isConnected) return
39
- root?.unmount()
40
- root = undefined
41
- container.remove()
42
- container = undefined
43
- }
44
- const column = conversationColumn()
45
- if (column === undefined) return
46
- container = document.createElement('div')
47
- container.dataset.dshNovelforgeView = 'true'
48
- container.className = css.view
49
- column.appendChild(container)
50
- root = createRoot(container)
51
- root.render(<NovelPanel controller={controller} api={api} />)
52
- }
53
-
54
- const waitObserver = new MutationObserver(() => { ensure() })
55
- waitObserver.observe(document.body, { childList: true, subtree: true })
56
-
57
- const applyActive = (): void => {
58
- if (controller.getSnapshot().panelOpen) {
59
- for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr)
60
- document.documentElement.setAttribute(ACTIVE_ATTR, '')
61
- document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }))
62
- } else {
63
- document.documentElement.removeAttribute(ACTIVE_ATTR)
64
- }
65
- }
66
- const onOtherActivate = (event: Event): void => {
67
- const detail = (event as CustomEvent).detail
68
- if ((detail === 'taskboard' || detail === 'ssh') && controller.getSnapshot().panelOpen) {
69
- controller.close()
70
- }
71
- }
72
- // Jump out on sidebar context clicks.
73
- const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
74
- const onClickSidebarRow = (event: MouseEvent): void => {
75
- if (!controller.getSnapshot().panelOpen) return
76
- const target = event.target as HTMLElement | null
77
- if (target === null) return
78
- if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close()
79
- }
80
- document.addEventListener('click', onClickSidebarRow, true)
81
- document.addEventListener(ACTIVATE_EVENT, onOtherActivate)
82
- const unsubscribe = controller.subscribe(applyActive)
83
- applyActive()
84
- ensure()
85
-
86
- return () => {
87
- document.removeEventListener('click', onClickSidebarRow, true)
88
- document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)
89
- waitObserver.disconnect()
90
- unsubscribe()
91
- document.documentElement.removeAttribute(ACTIVE_ATTR)
92
- root?.unmount()
93
- root = undefined
94
- container?.remove()
95
- container = undefined
96
- }
97
- }
@@ -1,170 +0,0 @@
1
- /**
2
- * 书架条:显示所有书,点击切换当前书(继续编译),+ 新建。
3
- * 新建 = 开书向导:书名 + 大纲(拖拽/选择 docx 或粘贴文本),创建即建项目。
4
- */
5
- import { useRef, useState } from 'react'
6
- import type { NovelApi } from '../api.ts'
7
- import { extractDocxTextFromBuffer } from '../docx.ts'
8
- import type { BookshelfSnapshot } from '../../protocol.ts'
9
- import css from './panel.module.css'
10
-
11
- /** Props. */
12
- export interface BookshelfBarProps {
13
- api: NovelApi
14
- shelf: BookshelfSnapshot
15
- /** 刷新整个面板(切换书后重新拉状态)。 */
16
- onSwitch: () => void
17
- }
18
-
19
- /** 书架条。 */
20
- export function BookshelfBar({ api, shelf, onSwitch }: BookshelfBarProps) {
21
- const [creating, setCreating] = useState(false)
22
- const [name, setName] = useState('')
23
- const [outlineText, setOutlineText] = useState('')
24
- const [outlineName, setOutlineName] = useState('')
25
- const [busy, setBusy] = useState(false)
26
- const [error, setError] = useState('')
27
- const outlineFileRef = useRef<HTMLInputElement | null>(null)
28
-
29
- const handlePickOutlineFile = async (file: File | undefined): Promise<void> => {
30
- if (file === undefined) return
31
- try {
32
- const buffer = await file.arrayBuffer()
33
- const text = extractDocxTextFromBuffer(buffer)
34
- if (text.length < 50) {
35
- setError('大纲内容过短(<50 字符),请检查文件')
36
- return
37
- }
38
- setOutlineText(text)
39
- setOutlineName(file.name)
40
- setError('')
41
- } catch (err) {
42
- setError(`读取大纲失败:${(err as Error).message}`)
43
- }
44
- }
45
-
46
- const handleCreate = async (): Promise<void> => {
47
- const bookName = name.trim()
48
- if (bookName === '') return
49
- setBusy(true)
50
- setError('')
51
- try {
52
- await api.bookCreate(bookName, undefined, outlineText.trim() !== '' ? outlineText.trim() : undefined)
53
- setCreating(false)
54
- setName('')
55
- setOutlineText('')
56
- setOutlineName('')
57
- onSwitch()
58
- } catch (err) {
59
- setError((err as Error).message)
60
- } finally {
61
- setBusy(false)
62
- }
63
- }
64
-
65
- const handleActivate = async (id: string): Promise<void> => {
66
- if (id === shelf.activeBookId) return
67
- setBusy(true)
68
- try {
69
- await api.bookActivate(id)
70
- onSwitch()
71
- } catch (err) {
72
- setError((err as Error).message)
73
- } finally {
74
- setBusy(false)
75
- }
76
- }
77
-
78
- const handleRemove = async (id: string): Promise<void> => {
79
- setBusy(true)
80
- try {
81
- await api.bookRemove(id)
82
- onSwitch()
83
- } catch (err) {
84
- setError((err as Error).message)
85
- } finally {
86
- setBusy(false)
87
- }
88
- }
89
-
90
- return (
91
- <div className={css.bookshelf}>
92
- <span className={css.bookshelfLabel}>书架</span>
93
- <div className={css.bookshelfList}>
94
- {shelf.books.map(book => (
95
- <div
96
- key={book.id}
97
- className={`${css.bookChip} ${book.id === shelf.activeBookId ? css.bookChipActive : ''}`}
98
- onClick={() => { void handleActivate(book.id) }}
99
- title={book.outputDir}
100
- >
101
- <span className={css.bookChipName}>{book.bookName}</span>
102
- <span className={css.bookChipMeta}>{book.hasProject ? `${book.done}/${book.total} 章` : '未开书'}</span>
103
- <button
104
- type="button"
105
- className={css.bookChipRemove}
106
- title="从书架移除"
107
- onClick={(e) => { e.stopPropagation(); void handleRemove(book.id) }}
108
- >
109
- ×
110
- </button>
111
- </div>
112
- ))}
113
- {!creating ? (
114
- <button type="button" className={css.bookAdd} onClick={() => { setCreating(true) }}>
115
- + 新书
116
- </button>
117
- ) : (
118
- <div className={css.bookCreateForm}>
119
- <input
120
- className={css.input}
121
- style={{ width: 140 }}
122
- placeholder="书名"
123
- value={name}
124
- onChange={e => { setName(e.target.value) }}
125
- onKeyDown={e => { if (e.key === 'Enter') void handleCreate() }}
126
- autoFocus
127
- />
128
- {/* 开书向导:大纲(可选,推荐) */}
129
- <div className={css.bookCreateOutline}>
130
- <button
131
- type="button"
132
- className={`${css.button} ${css.buttonSmall}`}
133
- onClick={() => { outlineFileRef.current?.click() }}
134
- >
135
- {outlineName !== '' ? `✓ ${outlineName}` : '📄 选择大纲 docx'}
136
- </button>
137
- <input
138
- ref={outlineFileRef}
139
- type="file"
140
- accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
141
- style={{ display: 'none' }}
142
- onChange={e => {
143
- void handlePickOutlineFile(e.target.files?.[0])
144
- e.target.value = ''
145
- }}
146
- />
147
- <textarea
148
- className={css.input}
149
- style={{ width: 280, minHeight: 64, resize: 'vertical' }}
150
- placeholder="或直接粘贴大纲文本(提供大纲后开书即建立项目,书名自动从大纲首行识别)"
151
- value={outlineText}
152
- onChange={e => { setOutlineText(e.target.value) }}
153
- />
154
- </div>
155
- <div className={css.row}>
156
- <button type="button" className={`${css.button} ${css.buttonSmall} ${css.buttonPrimary}`} disabled={busy || name.trim() === ''} onClick={() => { void handleCreate() }}>
157
- 开书
158
- </button>
159
- <button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { setCreating(false) }}>
160
- 取消
161
- </button>
162
- <span className={css.meta}>{outlineText.length > 0 ? `大纲 ${outlineText.length} 字` : '未提供大纲(稍后在大纲页导入)'}</span>
163
- </div>
164
- </div>
165
- )}
166
- </div>
167
- {error !== '' && <span style={{ color: 'var(--nf-error)', fontSize: 'var(--nf-fs-12)' }}>{error}</span>}
168
- </div>
169
- )
170
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * 漫剧工作台·全流程步骤条(唯一导航,方案X):
3
- * ①创建方案 → ②一键生成 → ③分镜 → ④角色定妆 → ⑤场景底图 → ⑥导出使用。
4
- * 每个步骤对应一个独立页面主体;完成度自动判定(读 project 现有字段)。
5
- */
6
- import type { ProjectState } from '../../protocol.ts'
7
- import css from './panel.module.css'
8
-
9
- /** 步骤 → 页面主体的一一对应目标。 */
10
- export type FlowTarget =
11
- | 'plan' // ① 创建方案页
12
- | 'rules' // ② 视觉规则页
13
- | 'skeleton' // ③ 剧情骨架页(分镜·骨架)
14
- | 'table' // ④ 分镜表页(分镜·分镜表)
15
- | 'import' // ⑤ 导入角色页(角色·导入)
16
- | 'makeup' // ⑥ 角色定妆页(角色·卡片)
17
- | 'prompts' // ⑦ 视频提示词页(分镜·提示词)
18
- | 'scenes' // ⑧ 场景库页
19
- | 'props' // ⑨ 道具库页
20
- | 'export' // ⑩ 导出使用页
21
-
22
- export interface FlowStep {
23
- no: number
24
- label: string
25
- hint: string
26
- done: boolean
27
- target: FlowTarget
28
- }
29
-
30
- /** 依据 project 数据计算 6 步完成度(与存储顺序无关,按生产顺序排列)。 */
31
- export function computeFlowSteps(project: ProjectState | null, exported?: boolean): FlowStep[] {
32
- const plans = project?.mangaPlans ?? []
33
- const rules = project?.visualRules ?? []
34
- const sbs = project?.storyboards ?? []
35
- const manga = project?.mangaRoles ?? []
36
- const scenes = project?.scenes ?? []
37
- const props = project?.props ?? []
38
- const hasSkeleton = sbs.some(e => e.skeleton !== undefined)
39
- const hasTable = sbs.some(e => e.table !== undefined)
40
- const hasPrompts = sbs.some(e => (e.prompts ?? []).length > 0)
41
- return [
42
- { no: 1, label: '创建方案', hint: '选基底风格+题材并命名;此后所有提示词按此风格生成', done: plans.length > 0, target: 'plan' },
43
- { no: 2, label: '一键生成', hint: '选章节后自动做 剧情骨架→分镜表→角色导入;视频提示词留到⑤', done: hasTable, target: 'import' },
44
- { no: 3, label: '角色库', hint: '主要角色定妆图提示词→去即梦出图→建智能角色', done: manga.some(c => c.status === 'anchored'), target: 'makeup' },
45
- { no: 4, label: '场景库', hint: '(选做)从正文提炼场景卡并出场景参考图,提示词自动引用', done: scenes.length > 0, target: 'scenes' },
46
- { no: 5, label: '道具库', hint: '(选做)从正文提炼常驻道具,提示词里道具外观跨镜头统一', done: props.length > 0, target: 'props' },
47
- { no: 6, label: '分镜·提示词', hint: '生成逐镜即梦提示词(可粘贴)→复制去即梦;含导出存档', done: hasPrompts, target: 'prompts' },
48
- ]
49
- }
50
-
51
- export function FlowGuide({
52
- project,
53
- onNavigate,
54
- exported,
55
- }: {
56
- project: ProjectState | null
57
- onNavigate: (target: FlowTarget) => void
58
- exported?: boolean
59
- }) {
60
- const steps = computeFlowSteps(project, exported)
61
- const doneCount = steps.filter(s => s.done).length
62
- return (
63
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--nf-space-6)', marginTop: 'var(--nf-space-8)' }}>
64
- <div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
65
- <span className={css.meta}>🧭 全流程({doneCount}/{steps.length}):每步一个页面,完成自动打勾</span>
66
- <span className={css.meta}>点击步骤打开对应页面</span>
67
- </div>
68
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--nf-space-6)' }}>
69
- {steps.map(s => (
70
- <button
71
- key={s.no}
72
- type="button"
73
- title={s.hint}
74
- className={css.button + ' ' + css.buttonSmall + (s.done ? ' ' + css.buttonPrimary : '')}
75
- style={{ minWidth: 0 }}
76
- onClick={() => { onNavigate(s.target) }}
77
- >
78
- {s.done ? '✓ ' : s.no + '. '}{s.label}
79
- </button>
80
- ))}
81
- </div>
82
- </div>
83
- )
84
- }