@waterwx/dsh-novel-forge 0.1.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 (50) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +180 -0
  3. package/cordis.patch.yml +13 -0
  4. package/lib/client.js +3924 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +3120 -0
  7. package/lib/index.js.map +1 -0
  8. package/lib/types/assets.d.ts +35 -0
  9. package/lib/types/assistant.d.ts +43 -0
  10. package/lib/types/bookshelf.d.ts +35 -0
  11. package/lib/types/client/api.d.ts +68 -0
  12. package/lib/types/client/docx.d.ts +15 -0
  13. package/lib/types/client/index.d.ts +14 -0
  14. package/lib/types/client/locales.d.ts +139 -0
  15. package/lib/types/client/mount.d.ts +9 -0
  16. package/lib/types/client/panel/AssetsTab.d.ts +7 -0
  17. package/lib/types/client/panel/AssistantTab.d.ts +7 -0
  18. package/lib/types/client/panel/BookshelfBar.d.ts +11 -0
  19. package/lib/types/client/panel/NovelPanel.d.ts +13 -0
  20. package/lib/types/client/panel/controller.d.ts +19 -0
  21. package/lib/types/client/panel/helpers.d.ts +8 -0
  22. package/lib/types/client/sidebar-entry.d.ts +13 -0
  23. package/lib/types/docx.d.ts +19 -0
  24. package/lib/types/engine.d.ts +95 -0
  25. package/lib/types/index.d.ts +55 -0
  26. package/lib/types/protocol.d.ts +521 -0
  27. package/lib/types/routes.d.ts +29 -0
  28. package/package.json +105 -0
  29. package/src/assets.ts +518 -0
  30. package/src/assistant.ts +547 -0
  31. package/src/bookshelf.ts +137 -0
  32. package/src/client/api.ts +254 -0
  33. package/src/client/css-modules.d.ts +8 -0
  34. package/src/client/docx.ts +69 -0
  35. package/src/client/index.ts +34 -0
  36. package/src/client/locales.ts +271 -0
  37. package/src/client/mount.tsx +97 -0
  38. package/src/client/panel/AssetsTab.tsx +341 -0
  39. package/src/client/panel/AssistantTab.tsx +188 -0
  40. package/src/client/panel/BookshelfBar.tsx +116 -0
  41. package/src/client/panel/NovelPanel.tsx +990 -0
  42. package/src/client/panel/controller.ts +45 -0
  43. package/src/client/panel/helpers.ts +17 -0
  44. package/src/client/panel/panel.module.css +894 -0
  45. package/src/client/sidebar-entry.ts +122 -0
  46. package/src/docx.ts +83 -0
  47. package/src/engine.ts +1019 -0
  48. package/src/index.ts +184 -0
  49. package/src/protocol.ts +539 -0
  50. package/src/routes.ts +955 -0
package/src/index.ts ADDED
@@ -0,0 +1,184 @@
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 type { Context } from '@deepseek-ai/cordis'
11
+ import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
12
+ import z from 'schemastery'
13
+ import type {} from '@deepseek-ai/dsh-host-webserver'
14
+ import type {} from '@deepseek-ai/dsh-llm'
15
+ import type {} from '@deepseek-ai/dsh-system-prompt'
16
+ import type { ConfigPatch, NovelConfig } from './protocol.ts'
17
+ import { makeRoutes } from './routes.ts'
18
+ import { activeBookOutputDir } from './bookshelf.ts'
19
+
20
+ /** Stable cordis plugin name. */
21
+ export const name = 'novel-forge'
22
+
23
+ /** Services required before the novel-forge surfaces can mount. */
24
+ export const inject = ['webServer', 'llm', 'systemPrompt']
25
+
26
+ /**
27
+ * Settings namespace of the novel-forge capability — the section the web
28
+ * settings surface edits. Spelled here rather than imported: the browser half
29
+ * spells the same value and must not depend on a Host package.
30
+ */
31
+ export const NOVEL_SETTINGS_NAMESPACE = settingsNamespace('dsh-novel-forge')
32
+
33
+ /** Plugin config, validated by the same-named schemastery schema. */
34
+ export interface Config {
35
+ /** When true (default), a system-prompt section announces the plugin to every agent. */
36
+ announceToAgent?: boolean
37
+ /** Master switch for the plugin (routes, prompt section). */
38
+ enabled?: boolean
39
+ /** Absolute path of the default docx outline to load. */
40
+ outlinePath?: string
41
+ /** Absolute output directory for chapters + project state. */
42
+ outputDir?: string
43
+ /** LLM provider route. */
44
+ provider?: string
45
+ /** LLM model id. */
46
+ model?: string
47
+ /** Target characters per chapter. */
48
+ chapterChars?: number
49
+ /** Max output tokens per chapter call. */
50
+ maxTokens?: number
51
+ /** Review pass threshold (0-100). */
52
+ reviewPassScore?: number
53
+ /** Whether generation auto-runs review after writing. */
54
+ autoReview?: boolean
55
+ }
56
+
57
+ export const Config: z<Config> = z.object({
58
+ announceToAgent: z.boolean().default(true),
59
+ enabled: z.boolean().default(true),
60
+ outlinePath: z.string().default('C:\\Users\\Ryan\\Desktop\\《归墟玉主》全书大纲_重新排版版.docx'),
61
+ outputDir: z.string().default('C:\\Users\\Ryan\\Desktop\\归墟玉主'),
62
+ provider: z.string().default('deepseek-official'),
63
+ model: z.string().default('deepseek-v4-flash'),
64
+ chapterChars: z.number().default(3500),
65
+ maxTokens: z.number().default(12000),
66
+ reviewPassScore: z.number().default(70),
67
+ autoReview: z.boolean().default(true),
68
+ })
69
+
70
+ /** Schema defaults, re-read for hand-built test contexts. */
71
+ const DEFAULT_ANNOUNCE = true
72
+ const DEFAULT_OUTLINE_PATH = 'C:\\Users\\Ryan\\Desktop\\《归墟玉主》全书大纲_重新排版版.docx'
73
+ const DEFAULT_OUTPUT_DIR = 'C:\\Users\\Ryan\\Desktop\\归墟玉主'
74
+ const DEFAULT_PROVIDER = 'deepseek-official'
75
+ const DEFAULT_MODEL = 'deepseek-v4-flash'
76
+ const DEFAULT_CHAPTER_CHARS = 3500
77
+ const DEFAULT_MAX_TOKENS = 12000
78
+ const DEFAULT_REVIEW_PASS_SCORE = 70
79
+ const DEFAULT_AUTO_REVIEW = true
80
+
81
+ /** Order of the announcement section within the tool-guidance band. */
82
+ const SECTION_ORDER = 160
83
+
84
+ /** Model-facing announcement: plugin presence, capabilities, and limits. */
85
+ export const NOVEL_GUIDANCE = '本机已安装 dsh-novel-forge 插件(AI 编译小说工作台):侧边栏「小说工坊」入口。能力:读取 docx 大纲(默认桌面《归墟玉主》大纲)或粘贴大纲文本;用 LLM 提炼设定圣经(人设/世界观/金手指规则/写作红线);生成卷计划与章节计划;逐章调用 LLM 生成 3000-4000 字正文并保存为 Markdown(默认输出到 桌面\\归墟玉主);每章自动生成摘要(叙事记忆)、自动 AI 审稿(人设/设定/红线/文笔/爽点/逻辑),支持按审稿意见重写、去 AI 味润色、伏笔管理、批量连写与全本导出(txt/md)。限制:生成消耗 LLM API 额度;输出目录与模型可在插件设置中修改;章节正文质量取决于大纲完整度。用户提到「小说 / 大纲 / 写小说 / 章节 / 审稿 / 伏笔 / 润色 / 归墟玉主」时即指本插件,请据此协作。'
86
+
87
+ /** Resolve a config-like value into the full runtime config. */
88
+ export function resolveConfig(value: Partial<Config> | undefined): NovelConfig {
89
+ return {
90
+ outlinePath: value?.outlinePath ?? DEFAULT_OUTLINE_PATH,
91
+ outputDir: value?.outputDir ?? DEFAULT_OUTPUT_DIR,
92
+ provider: value?.provider ?? DEFAULT_PROVIDER,
93
+ model: value?.model ?? DEFAULT_MODEL,
94
+ chapterChars: value?.chapterChars ?? DEFAULT_CHAPTER_CHARS,
95
+ maxTokens: value?.maxTokens ?? DEFAULT_MAX_TOKENS,
96
+ reviewPassScore: value?.reviewPassScore ?? DEFAULT_REVIEW_PASS_SCORE,
97
+ autoReview: value?.autoReview ?? DEFAULT_AUTO_REVIEW,
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Mount the routes and announcement.
103
+ * @param ctx - host plugin context carrying webServer/llm/systemPrompt.
104
+ * @param config - resolved plugin config (schema defaults applied by the loader).
105
+ */
106
+ export function apply(ctx: Context, config?: Config): void {
107
+ // The live source the routes read: the settings section once the web
108
+ // settings surface is served, the composition entry otherwise.
109
+ let current: () => Config = () => config ?? {}
110
+ const resolve = (): NovelConfig => {
111
+ const resolved = resolveConfig(current())
112
+ // 书架激活的书优先决定输出目录(settings 仍可改默认值)。
113
+ const shelfDir = activeBookOutputDir()
114
+ if (shelfDir !== undefined) {
115
+ return { ...resolved, outputDir: shelfDir }
116
+ }
117
+ return resolved
118
+ }
119
+
120
+ const patchConfig = async (patch: ConfigPatch): Promise<NovelConfig> => {
121
+ const next: ConfigPatch = {}
122
+ if (patch.outlinePath !== undefined) next.outlinePath = patch.outlinePath
123
+ if (patch.outputDir !== undefined) next.outputDir = patch.outputDir
124
+ if (patch.provider !== undefined) next.provider = patch.provider
125
+ if (patch.model !== undefined) next.model = patch.model
126
+ if (patch.chapterChars !== undefined) next.chapterChars = patch.chapterChars
127
+ if (patch.maxTokens !== undefined) next.maxTokens = patch.maxTokens
128
+ if (patch.reviewPassScore !== undefined) next.reviewPassScore = patch.reviewPassScore
129
+ if (patch.autoReview !== undefined) next.autoReview = patch.autoReview
130
+ // Persist through the settings seam when available; otherwise keep in memory.
131
+ // (ctx.get is the non-strict service access — no inject requirement, same
132
+ // pattern installSettingsSection itself uses.)
133
+ const settings = ctx.get('settings')
134
+ if (settings !== undefined) {
135
+ await settings.update(NOVEL_SETTINGS_NAMESPACE, next as Record<string, unknown>)
136
+ } else {
137
+ current = () => ({ ...current(), ...next })
138
+ }
139
+ return resolve()
140
+ }
141
+
142
+ let disposeSection: (() => void) | undefined
143
+ let disposeRoutes: (() => void) | undefined
144
+
145
+ const sync = (): void => {
146
+ if (disposeSection !== undefined) {
147
+ disposeSection()
148
+ disposeSection = undefined
149
+ }
150
+ if (disposeRoutes !== undefined) {
151
+ disposeRoutes()
152
+ disposeRoutes = undefined
153
+ }
154
+ const value = resolve()
155
+ if (!(current().enabled ?? true)) return
156
+ if (current().announceToAgent ?? DEFAULT_ANNOUNCE) {
157
+ disposeSection = ctx.systemPrompt.section({
158
+ name: 'plugin:dsh-novel-forge',
159
+ order: SECTION_ORDER,
160
+ text: NOVEL_GUIDANCE,
161
+ })
162
+ }
163
+ const routes = makeRoutes({ ctx, getConfig: resolve, patchConfig })
164
+ disposeRoutes = ctx.effect(
165
+ () => {
166
+ const disposers = routes.map(route => ctx.webServer.register(route))
167
+ return () => { for (const dispose of disposers) dispose() }
168
+ },
169
+ 'dsh-novel-forge: routes',
170
+ )
171
+ void value
172
+ }
173
+
174
+ installSettingsSection(ctx, NOVEL_SETTINGS_NAMESPACE, Config, config ?? {}, {
175
+ setSource: (source) => {
176
+ current = source
177
+ sync()
178
+ },
179
+ onChange: sync,
180
+ })
181
+
182
+ // Initial registration from the composition entry.
183
+ sync()
184
+ }