@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.
- package/LICENSE +202 -0
- package/README.md +180 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.js +3924 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +3120 -0
- package/lib/index.js.map +1 -0
- package/lib/types/assets.d.ts +35 -0
- package/lib/types/assistant.d.ts +43 -0
- package/lib/types/bookshelf.d.ts +35 -0
- package/lib/types/client/api.d.ts +68 -0
- package/lib/types/client/docx.d.ts +15 -0
- package/lib/types/client/index.d.ts +14 -0
- package/lib/types/client/locales.d.ts +139 -0
- package/lib/types/client/mount.d.ts +9 -0
- package/lib/types/client/panel/AssetsTab.d.ts +7 -0
- package/lib/types/client/panel/AssistantTab.d.ts +7 -0
- package/lib/types/client/panel/BookshelfBar.d.ts +11 -0
- package/lib/types/client/panel/NovelPanel.d.ts +13 -0
- package/lib/types/client/panel/controller.d.ts +19 -0
- package/lib/types/client/panel/helpers.d.ts +8 -0
- package/lib/types/client/sidebar-entry.d.ts +13 -0
- package/lib/types/docx.d.ts +19 -0
- package/lib/types/engine.d.ts +95 -0
- package/lib/types/index.d.ts +55 -0
- package/lib/types/protocol.d.ts +521 -0
- package/lib/types/routes.d.ts +29 -0
- package/package.json +105 -0
- package/src/assets.ts +518 -0
- package/src/assistant.ts +547 -0
- package/src/bookshelf.ts +137 -0
- package/src/client/api.ts +254 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/docx.ts +69 -0
- package/src/client/index.ts +34 -0
- package/src/client/locales.ts +271 -0
- package/src/client/mount.tsx +97 -0
- package/src/client/panel/AssetsTab.tsx +341 -0
- package/src/client/panel/AssistantTab.tsx +188 -0
- package/src/client/panel/BookshelfBar.tsx +116 -0
- package/src/client/panel/NovelPanel.tsx +990 -0
- package/src/client/panel/controller.ts +45 -0
- package/src/client/panel/helpers.ts +17 -0
- package/src/client/panel/panel.module.css +894 -0
- package/src/client/sidebar-entry.ts +122 -0
- package/src/docx.ts +83 -0
- package/src/engine.ts +1019 -0
- package/src/index.ts +184 -0
- package/src/protocol.ts +539 -0
- 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
|
+
}
|