@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,59 +0,0 @@
1
- /**
2
- * 全局资源库(跨书可复用的自定义题材/推进模式)。
3
- * 雷达「同步底座到全局资源库」写入此处;/assets 返回时与内置库合并展示。
4
- */
5
- import { homedir } from 'node:os'
6
- import { join } from 'node:path'
7
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
8
- import type { GenreNode, ProgressionMode } from './protocol.ts'
9
-
10
- interface GlobalAssets {
11
- genres: GenreNode[]
12
- modes: ProgressionMode[]
13
- }
14
-
15
- const FILE = join(homedir(), '.dsh', 'novel-forge-global-assets.json')
16
- let cache: GlobalAssets | null = null
17
-
18
- function load(): GlobalAssets {
19
- if (cache !== null) return cache
20
- try {
21
- const raw = readFileSync(FILE, 'utf8')
22
- const parsed = JSON.parse(raw) as Partial<GlobalAssets>
23
- cache = { genres: Array.isArray(parsed.genres) ? parsed.genres : [], modes: Array.isArray(parsed.modes) ? parsed.modes : [] }
24
- } catch {
25
- cache = { genres: [], modes: [] }
26
- }
27
- return cache
28
- }
29
-
30
- function persist(): void {
31
- if (cache === null) return
32
- mkdirSync(join(homedir(), '.dsh'), { recursive: true })
33
- writeFileSync(FILE, JSON.stringify(cache, null, 2), 'utf8')
34
- }
35
-
36
- export function globalGenreLibrary(): GenreNode[] {
37
- return load().genres
38
- }
39
-
40
- export function globalProgressionLibrary(): ProgressionMode[] {
41
- return load().modes
42
- }
43
-
44
- /** 新增/已有则跳过;返回 true=新增。 */
45
- export function addGlobalGenre(g: GenreNode): boolean {
46
- const lib = load()
47
- if (lib.genres.some(x => x.name === g.name)) return false
48
- lib.genres.push({ ...g, children: g.children ?? [] })
49
- persist()
50
- return true
51
- }
52
-
53
- export function addGlobalMode(m: ProgressionMode): boolean {
54
- const lib = load()
55
- if (lib.modes.some(x => x.name === m.name)) return false
56
- lib.modes.push({ ...m, payoffs: m.payoffs ?? [], risks: m.risks ?? [], primary: m.primary ?? false })
57
- persist()
58
- return true
59
- }
package/src/index.ts DELETED
@@ -1,346 +0,0 @@
1
- /**
2
- * dsh-novel-forge — host half. Mounts the AI novel-forge workbench: docx
3
- * outline import, LLM chapter planning, chapter-by-chapter generation
4
- * (3000-4000 chars each), Markdown output into your chosen folder, and the
5
- * /api/dsh-novel-forge route family. The browser half (./client) renders the
6
- * workbench panel. Everything rides official NPM SDK packages — no dsh source
7
- * changes.
8
- */
9
-
10
- import { homedir } from 'node:os'
11
- import { join } from 'node:path'
12
- import type { Context } from '@deepseek-ai/cordis'
13
- import type {} from '@deepseek-ai/dsh-settings'
14
- import z from 'schemastery'
15
- import type {} from '@deepseek-ai/dsh-host-webserver'
16
- import type {} from '@deepseek-ai/dsh-llm'
17
- import type {} from '@deepseek-ai/dsh-system-prompt'
18
- import type { ConfigPatch, ImageModelConfig, NovelConfig } from './protocol.ts'
19
- import { makeRoutes } from './routes.ts'
20
- import { activeBookOutputDir } from './bookshelf.ts'
21
-
22
- /** Stable cordis plugin name. */
23
- export const name = 'novel-forge'
24
-
25
- /** Services required before the novel-forge surfaces can mount. */
26
- export const inject = ['webServer', 'llm', 'systemPrompt', 'settings']
27
-
28
- /**
29
- * Settings namespace of the novel-forge capability — the section the web
30
- * settings surface edits. Spelled here rather than imported: the browser half
31
- * spells the same value and must not depend on a Host package.
32
- */
33
- export const NOVEL_SETTINGS_NAMESPACE = 'dsh-novel-forge' as const
34
-
35
- /** Plugin config, validated by the same-named schemastery schema. */
36
- export interface Config {
37
- /** When true (default), a system-prompt section announces the plugin to every agent. */
38
- announceToAgent?: boolean
39
- /** Master switch for the plugin (routes, prompt section). */
40
- enabled?: boolean
41
- /** Absolute path of the default docx outline to load. */
42
- outlinePath?: string
43
- /** Absolute output directory for chapters + project state. */
44
- outputDir?: string
45
- /** LLM provider route. */
46
- provider?: string
47
- /** LLM model id. */
48
- model?: string
49
- /** 任务级模型路由:正文生成模型(留空则跟随 model)。 */
50
- generateModel?: string
51
- /** 任务级模型路由:审稿模型(留空则跟随 model)。 */
52
- reviewModel?: string
53
- /** 任务级模型路由:AI 复核/质检模型(留空则跟随 model)。 */
54
- auditModel?: string
55
- /** LLM reasoning effort (off/low/high/max). */
56
- reasoningEffort?: 'off' | 'low' | 'high' | 'max'
57
- /** 分析类任务(提炼/拆书/反推大纲等)的推理档位;默认 low。 */
58
- analysisReasoning?: 'off' | 'low' | 'high' | 'max'
59
- /** Target characters per chapter. */
60
- chapterChars?: number
61
- /** Max output tokens per chapter call. */
62
- maxTokens?: number
63
- /** Review pass threshold (0-100). */
64
- reviewPassScore?: number
65
- /** Whether generation auto-runs review after writing. */
66
- autoReview?: boolean
67
- /** Whether generation auto-runs the author review (hook/continuity/trend). */
68
- autoAuthorReview?: boolean
69
- /** 修订/润色产出草稿后自动附带一次 AI 审查(默认开,可在设置页关闭省 token)。 */
70
- autoReviewAfterRevise?: boolean
71
- /** 生图模型库(多套并存,启用一条生效)。 */
72
- imageModels?: ImageModelConfig[]
73
- /** 豆包/Seedream 生图 API Key(旧字段,兼容迁移用)。 */
74
- imageApiKey?: string
75
- /** 豆包/Seedream 生图模型 ID(旧字段,兼容迁移用)。 */
76
- imageApiModel?: string
77
- /** 是否启用豆包生图(旧字段,兼容迁移用)。 */
78
- imageApiEnabled?: boolean
79
- /** 自定义背景图(URL / dataURL / 服务端路径引用)。 */
80
- themeBackground?: string
81
- /** 自定义背景遮罩/模糊强度 0-80。 */
82
- themeBackgroundBlur?: number
83
- /** 玻璃透明度 0-100(100=当前原样)。 */
84
- themeOpacity?: number
85
- /** 是否启用改编模式(默认关闭)。 */
86
- enableAdaptMode?: boolean
87
- }
88
-
89
- export const Config: z<Config> = z.object({
90
- announceToAgent: z.boolean().default(true),
91
- enabled: z.boolean().default(true),
92
- outlinePath: z.string().default(''),
93
- outputDir: z.string().default(join(homedir(), '.dsh', 'novels')),
94
- provider: z.string().default('deepseek-official'),
95
- model: z.string().default('deepseek-v4-flash'),
96
- generateModel: z.string().default(''),
97
- reviewModel: z.string().default(''),
98
- auditModel: z.string().default(''),
99
- reasoningEffort: z.union(['off', 'low', 'high', 'max']).default('off'),
100
- analysisReasoning: z.union(['off', 'low', 'high', 'max']).default('low'),
101
- chapterChars: z.number().default(3500),
102
- maxTokens: z.number().default(12000),
103
- reviewPassScore: z.number().default(70),
104
- autoReview: z.boolean().default(true),
105
- autoAuthorReview: z.boolean().default(true),
106
- autoReviewAfterRevise: z.boolean().default(true),
107
- imageModels: z.array(z.object({
108
- id: z.string().default(''),
109
- name: z.string().default(''),
110
- baseURL: z.string().default(''),
111
- apiKey: z.string().default(''),
112
- model: z.string().default(''),
113
- enabled: z.boolean().default(false),
114
- })).default([]),
115
- imageApiKey: z.string().default(''),
116
- imageApiModel: z.string().default(''),
117
- imageApiEnabled: z.boolean().default(false),
118
- themeBackground: z.string().default(''),
119
- themeBackgroundBlur: z.number().default(0),
120
- themeOpacity: z.number().default(100),
121
- enableAdaptMode: z.boolean().default(true),
122
- })
123
-
124
- /** Schema defaults, re-read for hand-built test contexts. */
125
- const DEFAULT_ANNOUNCE = true
126
- const DEFAULT_OUTLINE_PATH = ''
127
- const DEFAULT_OUTPUT_DIR = join(homedir(), '.dsh', 'novels')
128
- const DEFAULT_PROVIDER = 'deepseek-official'
129
- const DEFAULT_MODEL = 'deepseek-v4-flash'
130
- const DEFAULT_REASONING_EFFORT = 'off' as const
131
- const DEFAULT_ANALYSIS_REASONING = 'low' as const
132
- const DEFAULT_CHAPTER_CHARS = 3500
133
- const DEFAULT_MAX_TOKENS = 12000
134
- const DEFAULT_REVIEW_PASS_SCORE = 70
135
- const DEFAULT_AUTO_REVIEW = true
136
- const DEFAULT_AUTO_AUTHOR_REVIEW = true
137
- const DEFAULT_AUTO_REVIEW_AFTER_REVISE = true
138
-
139
- /** Order of the announcement section within the tool-guidance band. */
140
- const SECTION_ORDER = 160
141
-
142
- /** Model-facing announcement: plugin presence, capabilities, and limits. */
143
- export const NOVEL_GUIDANCE = '本机已安装 dsh-novel-forge 插件(AI 编译小说工作台):侧边栏「小说工坊」入口。能力:读取 docx 大纲或粘贴大纲文本;用 LLM 提炼道藏(人设/世界观/金手指规则/写作红线);生成卷计划与章节计划;逐章调用 LLM 生成 3000-4000 字正文并保存为 Markdown(默认输出到用户主目录 ~/.dsh/novels);每章自动生成摘要(叙事记忆)、自动 AI 审稿(人设/设定/红线/文笔/爽点/逻辑),支持按审稿意见重写、去 AI 味润色、暗线(伏笔)管理、批量连写与全本导出(txt/md)。限制:生成消耗 LLM API 额度;输出目录与模型可在插件设置中修改;章节正文质量取决于大纲完整度。用户提到「小说 / 大纲 / 写小说 / 章节 / 审稿 / 润色」时即指本插件,请据此协作。'
144
-
145
- /** Resolve a config-like value into the full runtime config. */
146
- export function resolveConfig(value: Partial<Config> | undefined): NovelConfig {
147
- return {
148
- outlinePath: value?.outlinePath ?? DEFAULT_OUTLINE_PATH,
149
- outputDir: value?.outputDir ?? DEFAULT_OUTPUT_DIR,
150
- provider: value?.provider ?? DEFAULT_PROVIDER,
151
- model: value?.model ?? DEFAULT_MODEL,
152
- generateModel: value?.generateModel,
153
- reviewModel: value?.reviewModel,
154
- auditModel: value?.auditModel,
155
- reasoningEffort: value?.reasoningEffort ?? DEFAULT_REASONING_EFFORT,
156
- analysisReasoning: value?.analysisReasoning ?? DEFAULT_ANALYSIS_REASONING,
157
- chapterChars: value?.chapterChars ?? DEFAULT_CHAPTER_CHARS,
158
- maxTokens: value?.maxTokens ?? DEFAULT_MAX_TOKENS,
159
- reviewPassScore: value?.reviewPassScore ?? DEFAULT_REVIEW_PASS_SCORE,
160
- autoReview: value?.autoReview ?? DEFAULT_AUTO_REVIEW,
161
- autoAuthorReview: value?.autoAuthorReview ?? DEFAULT_AUTO_AUTHOR_REVIEW,
162
- autoReviewAfterRevise: value?.autoReviewAfterRevise ?? DEFAULT_AUTO_REVIEW_AFTER_REVISE,
163
- // 生图模型库:只有「从未保存过 imageModels」时才用旧配置迁移一条(豆包默认地址);
164
- // 已保存为空数组 = 用户主动删光,保持空,不再复活。
165
- imageModels: value?.imageModels !== undefined
166
- ? value.imageModels
167
- : (value?.imageApiKey !== undefined && value.imageApiKey !== ''
168
- ? [{ id: 'img-legacy', name: '豆包(旧配置)', baseURL: DEFAULT_IMAGE_BASE, apiKey: value.imageApiKey, model: value.imageApiModel ?? '', enabled: true }]
169
- : []),
170
- // 运行时生效值 = 启用条目(兼容旧代码读取 imageApiKey/imageApiModel/imageApiEnabled)。
171
- imageBaseUrl: (() => {
172
- const active = (value?.imageModels ?? []).find(m => m.enabled) ?? (value?.imageModels ?? [])[0]
173
- return active?.baseURL !== undefined && active.baseURL !== '' ? active.baseURL : (value?.imageApiKey !== undefined && value.imageApiKey !== '' ? DEFAULT_IMAGE_BASE : undefined)
174
- })(),
175
- imageApiKey: (() => {
176
- const active = (value?.imageModels ?? []).find(m => m.enabled) ?? (value?.imageModels ?? [])[0]
177
- return active?.apiKey ?? value?.imageApiKey
178
- })(),
179
- imageApiModel: (() => {
180
- const active = (value?.imageModels ?? []).find(m => m.enabled) ?? (value?.imageModels ?? [])[0]
181
- return active?.model ?? value?.imageApiModel
182
- })(),
183
- // 生图启用:总开关打开,或模型库里存在启用条目,均视为已启用(避免用户只配模型忘了开总开关)。
184
- imageApiEnabled: value?.imageApiEnabled === true || (value?.imageModels ?? []).some(m => m.enabled === true),
185
- themeBackground: value?.themeBackground ?? '',
186
- themeBackgroundBlur: value?.themeBackgroundBlur ?? 0,
187
- themeOpacity: value?.themeOpacity ?? 100,
188
- enableAdaptMode: value?.enableAdaptMode ?? true,
189
- }
190
- }
191
-
192
- /** 生图默认接口地址(豆包 ark,OpenAI 兼容)。 */
193
- const DEFAULT_IMAGE_BASE = 'https://ark.cn-beijing.volces.com/api/v3'
194
-
195
- /**
196
- * Mount the routes and announcement.
197
- * @param ctx - host plugin context carrying webServer/llm/systemPrompt.
198
- * @param config - resolved plugin config (schema defaults applied by the loader).
199
- */
200
- export function apply(ctx: Context, config?: Config): void {
201
- // The live source the routes read: the settings section once the web
202
- // settings surface is served, the composition entry otherwise.
203
- let current: () => Config = () => config ?? {}
204
- const resolve = (): NovelConfig => {
205
- const resolved = resolveConfig(current())
206
- // 书架激活的书优先决定输出目录(settings 仍可改默认值)。
207
- const shelfDir = activeBookOutputDir()
208
- if (shelfDir !== undefined) {
209
- return { ...resolved, outputDir: shelfDir }
210
- }
211
- return resolved
212
- }
213
-
214
- const patchConfig = async (patch: ConfigPatch): Promise<NovelConfig> => {
215
- const next: ConfigPatch = {}
216
- if (patch.outlinePath !== undefined) next.outlinePath = patch.outlinePath
217
- if (patch.outputDir !== undefined) next.outputDir = patch.outputDir
218
- if (patch.provider !== undefined) next.provider = patch.provider
219
- if (patch.model !== undefined) next.model = patch.model
220
- if (patch.generateModel !== undefined) next.generateModel = patch.generateModel
221
- if (patch.reviewModel !== undefined) next.reviewModel = patch.reviewModel
222
- if (patch.auditModel !== undefined) next.auditModel = patch.auditModel
223
- if (patch.reasoningEffort !== undefined) next.reasoningEffort = patch.reasoningEffort
224
- if (patch.analysisReasoning !== undefined) next.analysisReasoning = patch.analysisReasoning
225
- if (patch.chapterChars !== undefined) next.chapterChars = patch.chapterChars
226
- if (patch.maxTokens !== undefined) next.maxTokens = patch.maxTokens
227
- if (patch.reviewPassScore !== undefined) next.reviewPassScore = patch.reviewPassScore
228
- if (patch.autoReview !== undefined) next.autoReview = patch.autoReview
229
- if (patch.autoAuthorReview !== undefined) next.autoAuthorReview = patch.autoAuthorReview
230
- if (patch.autoReviewAfterRevise !== undefined) next.autoReviewAfterRevise = patch.autoReviewAfterRevise
231
- if (patch.imageApiKey !== undefined) next.imageApiKey = patch.imageApiKey
232
- if (patch.imageApiModel !== undefined) next.imageApiModel = patch.imageApiModel
233
- if (patch.imageApiEnabled !== undefined) next.imageApiEnabled = patch.imageApiEnabled
234
- if (patch.themeBackground !== undefined) next.themeBackground = patch.themeBackground
235
- if (patch.themeBackgroundBlur !== undefined) next.themeBackgroundBlur = patch.themeBackgroundBlur
236
- if (patch.themeOpacity !== undefined) next.themeOpacity = patch.themeOpacity
237
- if (patch.enableAdaptMode !== undefined) next.enableAdaptMode = patch.enableAdaptMode
238
- if (patch.imageModels !== undefined) next.imageModels = patch.imageModels
239
- // Persist through the settings seam when available; otherwise keep in memory.
240
- // (ctx.get is the non-strict service access — no inject requirement, same
241
- // pattern installSettingsSection itself uses.)
242
- const settings = ctx.get('settings')
243
- if (settings !== undefined) {
244
- await settings.update(NOVEL_SETTINGS_NAMESPACE, next as Record<string, unknown>)
245
- } else {
246
- current = () => ({ ...current(), ...next })
247
- }
248
- return resolve()
249
- }
250
-
251
- let disposeSection: (() => void) | undefined
252
- let disposeRoutes: (() => void) | undefined
253
-
254
- const sync = (): void => {
255
- if (disposeSection !== undefined) {
256
- disposeSection()
257
- disposeSection = undefined
258
- }
259
- if (disposeRoutes !== undefined) {
260
- disposeRoutes()
261
- disposeRoutes = undefined
262
- }
263
- const value = resolve()
264
- if (!(current().enabled ?? true)) return
265
- if (current().announceToAgent ?? DEFAULT_ANNOUNCE) {
266
- disposeSection = ctx.systemPrompt.section({
267
- name: 'plugin:dsh-novel-forge',
268
- order: SECTION_ORDER,
269
- text: NOVEL_GUIDANCE,
270
- })
271
- }
272
- const routes = makeRoutes({ ctx, getConfig: resolve, patchConfig })
273
- disposeRoutes = ctx.effect(
274
- () => {
275
- const disposers = routes.map(route => ctx.webServer.register(route))
276
- return () => { for (const dispose of disposers) dispose() }
277
- },
278
- 'dsh-novel-forge: routes',
279
- )
280
- void value
281
- }
282
-
283
- ctx.settings.installSection(ctx, NOVEL_SETTINGS_NAMESPACE, Config, config ?? {}, {
284
- setSource: (source) => {
285
- current = source
286
- sync()
287
- },
288
- onChange: sync,
289
- })
290
-
291
- // 运行时 skill:章节批量生产与值守处理(只在本插件环境可用)。
292
- // 通过 ctx.get('skills') 获取注册表(服务存在时注册,缺失则跳过,不影响其他能力)。
293
- const skillsService = ctx.get('skills') as { register?: (skill: { name: string; provider: string; description: string; content: string }) => () => void } | undefined
294
- // [disabled 2026-09-05] 运行时 skill 注册按用户要求暂时关闭(排查 .length 报错)。
295
- const enableRuntimeSkill: boolean = false
296
- if (enableRuntimeSkill && skillsService !== undefined && skillsService.register !== undefined) {
297
- const disposeSkill = skillsService.register({
298
- name: 'novel-forge-chapter-batch',
299
- provider: 'dsh-novel-forge',
300
- description: '批量生成小说章节并值守处理审稿未过的章节(豁免/按意见修订/验证模式/重新生成),依赖 dsh-novel-forge 的 /api/dsh-novel-forge/* 路由。',
301
- content: [
302
- '# 小说章节批量生产与值守处理',
303
- '',
304
- '## 0. 值守纪律(最高优先级,违反即空转)',
305
- '1. 启动后台任务后立即停下,不轮询、不连续 wait、不做无意义动作——后台任务完成时运行时自动通知。',
306
- '2. 只有收到「任务完成」通知或用户新消息时,才继续下一步(读结果→处理未过→汇报)。',
307
- '3. 等待期间可做与当前任务无关的其他有用工作,但不得为等任务而空转。',
308
- '4. 任务因 web 重启中断时:待 3080 恢复后,复位卡死章节(generating/error → reset)再续跑,并向用户说明中断原因。',
309
- '',
310
- '## 1. 前置检查',
311
- '1. `GET /api/dsh-novel-forge/status` 确认服务在跑,读项目章节数。',
312
- '2. 列出目标区间 pending 章节号,避免重复生成已处理章节。',
313
- '3. **串行执行,禁止并行写 project.json**(并发会互相覆盖状态,实测教训)。',
314
- '',
315
- '## 2. 批量生成',
316
- '逐章串行调用 `POST /generate` `{ chapterNo, skipReview: false }`(走完整质量门:生成→摘要+编年录→审稿→复盘)。',
317
- '响应为 NDJSON,找 review 帧取 score/passed。每章 2-5 分钟;失败重试 1 次;后台跑并告知预计时长。',
318
- '用 Node 脚本(fetch 逐章循环),避免 PowerShell 转义坑,用完删除。',
319
- '',
320
- '## 3. 未过章节分级处理',
321
- '### A. 无 high → 豁免通过:`POST /chapter/approve`(主观项不磨)',
322
- '### B. 有 high 且明确 → 按意见修订 + 验证模式:',
323
- '1. 拼指令 `按审稿意见修订(优先处理):\n[severity] item → suggestion`(high 优先,无 high 取前 3 medium)',
324
- '2. `POST /rewrite` 产草稿;3. `POST /chapter/check` 带 `previousReport` 走验证模式(只核对原意见解决+只挑新增 high)',
325
- '4. 判定 `passed || 无 high` 可接受;5. `POST /draft/apply` 带 passed 报告落盘 approved',
326
- '6. 不可接受 → 第二轮修订(指令更精确)→ 再验证;每章最多修 2 轮,仍不过保留草稿待人工',
327
- '### C. 结构性 high(修订改不好)→ `POST /draft/discard` 后 `POST /generate` 重写,剩主观项再走 A',
328
- '### D. error 状态 → `POST /chapter/reset` 后 `POST /generate`',
329
- '注意:approved 但 review 有 high 的保留不动;修订指令越精确越有效(具体数字/行为一次就过)。',
330
- '',
331
- '## 4. 收尾',
332
- '`GET /status` 验证目标区间全 approved;可选刷新剧情线(`POST /plotlines` op=refresh,注意只读最近 8 章摘要);汇报通过/未过/失败与遗留项。',
333
- '',
334
- '## 5. 已知陷阱',
335
- '- 并发写 project.json 互相覆盖 → 必须串行',
336
- '- rewrite 草稿可能偶发不落盘 → 应用前检查 pendingDraft,不存在则重跑',
337
- '- PowerShell 调 API 引号/中文转义是坑 → 用 Node 脚本',
338
- '- 验证模式 500 偶发 → 重试一次',
339
- ].join('\n'),
340
- })
341
- ctx.effect(() => disposeSkill, 'dsh-novel-forge: skill')
342
- }
343
-
344
- // Initial registration from the composition entry.
345
- sync()
346
- }
package/src/llm-live.ts DELETED
@@ -1,68 +0,0 @@
1
- /**
2
- * LLM 实况 feed(对齐上游「AI 创作实况 / LIVE LOG」的轻量实现)。
3
- *
4
- * 以「每次 LLM 调用」为粒度,把生命周期事件(开始/流式正文/阶段变化/完成)推给订阅者,
5
- * 并提供环形缓冲供新连上的客户端重放。不持久化(与上游缓存不同,如需成 cache 再扩展)。
6
- */
7
-
8
- export type LlmLivePhase = 'requesting' | 'streaming' | 'completed' | 'failed'
9
-
10
- export interface LlmLiveFrame {
11
- type: 'session_started' | 'output_delta' | 'reasoning_delta' | 'phase_changed' | 'session_completed'
12
- /** 由服务端生成,形如 ll-<n>。 */
13
- sessionId: string
14
- /** 本次调用用途(如 正文生成/审稿/章节规划)。 */
15
- label?: string
16
- /** 使用模型(用于展示)。 */
17
- model?: string
18
- /** 事件时间(ISO)。 */
19
- at: string
20
- /** session_started / 部分帧:交互上下文。 */
21
- context?: { taskId?: string; interactionId: string }
22
- /** output_delta / reasoning_delta:本次增量文本。 */
23
- content?: string
24
- /** session 累计字符数。 */
25
- totalChars?: number
26
- /** reasoning 累计字符数。 */
27
- totalReasoningChars?: number
28
- /** phase_changed:新阶段。 */
29
- phase?: LlmLivePhase
30
- /** phase_changed:阶段描述。 */
31
- phaseMessage?: string
32
- /** session_completed:最终预览(截断)。 */
33
- preview?: string
34
- /** session_completed:失败信息。 */
35
- error?: string
36
- }
37
-
38
- const MAX_BUFFER = 400
39
- let buffer: LlmLiveFrame[] = []
40
- type Listener = (frame: LlmLiveFrame) => void
41
- let listeners = new Set<Listener>()
42
- let seq = 0
43
-
44
- export function nextSessionId(): string {
45
- seq++
46
- return `ll-${Date.now().toString(36)}-${seq}`
47
- }
48
-
49
- export function subscribeLiveFeed(listener: Listener): () => void {
50
- // 先重放现有缓冲(新连接能立即看到最近调用),再进入实时。
51
- for (const frame of buffer) listener(frame)
52
- listeners.add(listener)
53
- return () => { listeners.delete(listener) }
54
- }
55
-
56
- export function emitLive(frame: LlmLiveFrame): void {
57
- buffer.push(frame)
58
- if (buffer.length > MAX_BUFFER) buffer = buffer.slice(-MAX_BUFFER)
59
- for (const l of listeners) l(frame)
60
- }
61
-
62
- export function liveLatest(): LlmLiveFrame[] {
63
- return [...buffer]
64
- }
65
-
66
- export function clearLiveFeed(): void {
67
- buffer = []
68
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * 题材规则库(可插拔)—— 漫剧分镜/提示词生成的「题材专用规则」。
3
- * 参考短剧工业导演规范:不同题材用不同的视觉语言 / 打斗 / 特效规则,
4
- * 按漫剧方案的 genre 注入 generateStoryboardPrompts,避免「一套模板跑所有题材」。
5
- * 每个题材给一组「生成提示词时必须遵守的题材规则」;未选题材则不注入(走通用规则)。
6
- */
7
-
8
- export interface GenreRule {
9
- /** 题材 id。 */
10
- id: string
11
- /** 题材名(展示)。 */
12
- label: string
13
- /** 该题材的生成提示词核心规则(每行一条,注入生成时)。 */
14
- rules: string[]
15
- }
16
-
17
- /** 内置题材(通用题材 default 为空规则)。 */
18
- export const GENRES: GenreRule[] = [
19
- {
20
- id: 'xuanhuan',
21
- label: '玄幻 / 仙侠',
22
- rules: [
23
- '施法必须经法宝媒介(笛/扇/印/笔等实体法宝),写清法宝本体材质+施法动作+特效出口,禁止只写"施法"。',
24
- '每个角色绑定主属性+双色粒子(雷=蓝白+紫电、冰=冰蓝+银白、金=亮金+暖白、火=赤红+暗金、木=粉白+草木青),属性决定术法形态与配色。',
25
- '术法攻防走"罡气→破罡→防御转攻":护体罡气被划开、防御术法炸裂化千万飞刃/剑气,防守转攻是高光点,必写。',
26
- '能量具象化:光柱/雷龙/法相/领域;"光柱"要写成"洪流/弧光/波纹/光丝雨"(禁裸光柱,防呆板发光管)。',
27
- '终招蓄力必写:天地灵韵向角色/法宝汇聚、能量漩涡成型,再接幂击。',
28
- '对军清场:扇形冲击波/剑气暴雨洗地/能量扇镇压大军核心,战场弹坑/碎片/云层撕开反馈。',
29
- ],
30
- },
31
- {
32
- id: 'wuxia',
33
- label: '武侠 / 国风',
34
- rules: [
35
- '打斗用 Hit-Stop 三件套:碰撞瞬间 1~2 帧微停顿 + 接触点火花 + 双刃咬死高频震颤,杜绝滑步/空气假打。',
36
- '武术动作链:起手/攻防/收势,标明具体招式(刀/剑/轻功/擒拿),剑走轻灵、刀走厚重。',
37
- '镜头强化打击感:碰撞点微环绕、贴身横移、命中定格,禁静止对峙。',
38
- '轻功/身法:衣袂翻飞、发丝飘动、残影,身形轻盈不僵硬。',
39
- ],
40
- },
41
- {
42
- id: 'anime',
43
- label: '二次元 / 动漫',
44
- rules: [
45
- '大招三段式:蓄力→释放→爆发,能量形态+爆发特效,段段画质声明复用。',
46
- '双色粒子:每角色一套主色+材质+轨迹,双方互补对冲(冷 vs 暖),防混战混淆。',
47
- '色指定:明确主色调+高光色+特效撞色,防发灰。',
48
- '禁止评价词/裸光效/空泛"粒子";能量写"光丝雨/弧光/波纹"+方向+材质。',
49
- ],
50
- },
51
- {
52
- id: 'urban',
53
- label: '都市 / 现实',
54
- rules: [
55
- '微表情生理递进:眼神/眉心/唇部/鼻翼/喉头/手指,情绪逐层推进,不夸张不脸谱。',
56
- '对白语速 3.5~5 字/秒(台词从声音层按此把控,留 0.3~0.8s 落口发酵)。',
57
- '防棚拍假冷光:用真实光线(自然光/路灯/窗光/霓虹),日景不陰间冷蓝、局部留暖光。',
58
- '场景真实还原(街头/室内/医院等),人物服装生活化,肢体动作自然不律動化。',
59
- ],
60
- },
61
- {
62
- id: 'scifi',
63
- label: '科幻 / 未来',
64
- rules: [
65
- '机甲/能量武器设定明确(颜色/材质/发光部位),能量轨迹具体。',
66
- '霓虹/屏幕光/激光防眩光,用冷色主调+重点发光处。',
67
- '宏大场景(飞船/城市/废墟)用大远景/俯拍,人物相对渺小。',
68
- ],
69
- },
70
- ]
71
-
72
- /** 通用题材(空规则,走通用生成)。 */
73
- export const DEFAULT_GENRE: GenreRule = { id: 'default', label: '通用(不限定)', rules: [] }
74
-
75
- /** 按题材 id 取规则(未找到返回通用)。 */
76
- export function getGenreRules(id?: string): string[] {
77
- if (id === undefined || id === '') return DEFAULT_GENRE.rules
78
- const g = GENRES.find(x => x.id === id)
79
- return g !== undefined ? g.rules : DEFAULT_GENRE.rules
80
- }
81
-
82
- /** 取题材 label(展示用)。 */
83
- export function getGenreLabel(id?: string): string {
84
- if (id === undefined || id === '') return DEFAULT_GENRE.label
85
- const g = GENRES.find(x => x.id === id)
86
- return g !== undefined ? g.label : id
87
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * 热门题材雷达:真实榜单扫榜(聚合各平台榜单源,容错)。
3
- */
4
- import { collectMarketSource, MARKET_RADAR_SOURCES, type CollectedRankingItem, type MarketRadarPlatform, type MarketRadarListSource } from './market-radar-sources.ts'
5
-
6
- export interface MarketRadarGroup {
7
- platform: MarketRadarPlatform
8
- platformLabel: string
9
- listKey: string
10
- listLabel: string
11
- status: 'ok' | 'error'
12
- error?: string
13
- items: CollectedRankingItem[]
14
- }
15
-
16
- export interface MarketScanResult {
17
- scannedAt: string
18
- groups: MarketRadarGroup[]
19
- }
20
-
21
- let lastScan: MarketScanResult | null = null
22
-
23
- export function getLastMarketScan(): MarketScanResult | null {
24
- return lastScan
25
- }
26
-
27
- export async function scanMarketRanking(platforms?: string[]): Promise<MarketScanResult> {
28
- const wanted = new Set<string>(platforms?.length ? platforms : ['fanqie', 'qidian', 'jinjiang'])
29
- const sources = MARKET_RADAR_SOURCES.filter(s => wanted.has(s.platform))
30
- const groups: MarketRadarGroup[] = []
31
- await Promise.all(sources.map(async (source: MarketRadarListSource) => {
32
- try {
33
- const items = await collectMarketSource(source)
34
- groups.push({ platform: source.platform, platformLabel: source.platformLabel, listKey: source.listKey, listLabel: source.listLabel, status: 'ok', items })
35
- } catch (error) {
36
- groups.push({ platform: source.platform, platformLabel: source.platformLabel, listKey: source.listKey, listLabel: source.listLabel, status: 'error', error: (error as Error).message, items: [] })
37
- }
38
- }))
39
- lastScan = { scannedAt: new Date().toISOString(), groups }
40
- return lastScan
41
- }