@waterwx/dsh-novel-forge 1.4.0 → 1.4.1
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/lib/client.js +825 -564
- package/lib/client.js.map +1 -1
- package/lib/index.js +330 -58
- package/lib/index.js.map +1 -1
- package/lib/types/client/api.d.ts +7 -1
- package/lib/types/client/locales.d.ts +4 -2
- package/lib/types/engine.d.ts +27 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/protocol.d.ts +45 -3
- package/package.json +1 -1
- package/src/client/api.ts +16 -1
- package/src/client/locales.ts +7 -3
- package/src/client/panel/ImportModal.tsx +123 -21
- package/src/client/panel/NovelPanel.tsx +221 -147
- package/src/client/panel/panel.module.css +77 -0
- package/src/engine.ts +210 -39
- package/src/index.ts +6 -0
- package/src/protocol.ts +36 -3
- package/src/routes.ts +88 -11
package/src/routes.ts
CHANGED
|
@@ -39,6 +39,8 @@ import {
|
|
|
39
39
|
type BookImportDirResponse,
|
|
40
40
|
type BookImportTextRequest,
|
|
41
41
|
type BookImportTextResponse,
|
|
42
|
+
type BookImportTextPreviewRequest,
|
|
43
|
+
type BookImportTextPreviewResponse,
|
|
42
44
|
type BookRemoveRequest,
|
|
43
45
|
type BookshelfSnapshot,
|
|
44
46
|
type ChapterResponse,
|
|
@@ -101,6 +103,8 @@ import {
|
|
|
101
103
|
createProject,
|
|
102
104
|
exportBook,
|
|
103
105
|
importBookText,
|
|
106
|
+
importBookTextFromText,
|
|
107
|
+
previewBookText,
|
|
104
108
|
extractBible,
|
|
105
109
|
extractStyleAsset,
|
|
106
110
|
extractWorld,
|
|
@@ -128,6 +132,7 @@ import {
|
|
|
128
132
|
extractVisualRules,
|
|
129
133
|
extractRoleVisual,
|
|
130
134
|
suggestOutlines,
|
|
135
|
+
reverseOutlineFromChapters,
|
|
131
136
|
breakdownBook,
|
|
132
137
|
generateRoleReferenceImage,
|
|
133
138
|
checkSensitiveText,
|
|
@@ -139,7 +144,7 @@ import {
|
|
|
139
144
|
} from './engine.ts'
|
|
140
145
|
|
|
141
146
|
/** Cap on JSON request bodies (generous: cover images travel as base64). */
|
|
142
|
-
const MAX_JSON_BODY_BYTES =
|
|
147
|
+
const MAX_JSON_BODY_BYTES = 64 * 1024 * 1024
|
|
143
148
|
|
|
144
149
|
/** Loopback-only fence (mirrors the family plugins' pairing routes). */
|
|
145
150
|
function isLoopbackRequest(request: IncomingMessage): boolean {
|
|
@@ -1236,23 +1241,35 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
1236
1241
|
}
|
|
1237
1242
|
|
|
1238
1243
|
// ------------------------------------------- bookshelf import-text
|
|
1239
|
-
/** 导入 txt/md 全本(Mode B
|
|
1244
|
+
/** 导入 txt/md 全本(Mode B):拆章落盘建项目,登记书架。支持浏览器上传(text+fileName)与服务器本地文件(filePath)两种模式。 */
|
|
1240
1245
|
const bookshelfImportTextRoute: WebRoute = {
|
|
1241
1246
|
kind: 'exact',
|
|
1242
1247
|
path: NOVEL_API.bookshelfImportText,
|
|
1243
1248
|
handler: async (req, res) => {
|
|
1244
1249
|
if (!guard(req, res, 'POST')) return
|
|
1245
1250
|
const body = await readJsonBody<BookImportTextRequest>(req)
|
|
1246
|
-
const filePath = body?.filePath?.trim()
|
|
1247
|
-
if (filePath === undefined || filePath === '') {
|
|
1248
|
-
writeJson(res, 400, { error: 'filePath 不能为空' })
|
|
1249
|
-
return
|
|
1250
|
-
}
|
|
1251
|
-
if (!existsSync(filePath)) {
|
|
1252
|
-
writeJson(res, 400, { error: `文件不存在:${filePath}` })
|
|
1253
|
-
return
|
|
1254
|
-
}
|
|
1255
1251
|
try {
|
|
1252
|
+
// 模式二:浏览器上传全文内容
|
|
1253
|
+
if (body?.text !== undefined && body.text.length > 0) {
|
|
1254
|
+
const bookName = (body.fileName ?? '').replace(/\.[^.]+$/, '').trim().slice(0, 40) || '导入小说'
|
|
1255
|
+
const outDir = body?.outputDir?.trim() !== undefined && body.outputDir.trim() !== ''
|
|
1256
|
+
? body.outputDir.trim()
|
|
1257
|
+
: defaultOutputDirFor(bookName)
|
|
1258
|
+
const result = importBookTextFromText(body.text, outDir, bookName)
|
|
1259
|
+
const { book } = importDir(outDir)
|
|
1260
|
+
writeJson(res, 200, { ...result, book })
|
|
1261
|
+
return
|
|
1262
|
+
}
|
|
1263
|
+
// 模式一:服务器本地文件
|
|
1264
|
+
const filePath = body?.filePath?.trim()
|
|
1265
|
+
if (filePath === undefined || filePath === '') {
|
|
1266
|
+
writeJson(res, 400, { error: 'filePath 不能为空(或请上传文件内容)' })
|
|
1267
|
+
return
|
|
1268
|
+
}
|
|
1269
|
+
if (!existsSync(filePath)) {
|
|
1270
|
+
writeJson(res, 400, { error: `文件不存在:${filePath}` })
|
|
1271
|
+
return
|
|
1272
|
+
}
|
|
1256
1273
|
const bookName = basename(filePath, extname(filePath)).slice(0, 40) || '导入小说'
|
|
1257
1274
|
const outDir = body?.outputDir?.trim() !== undefined && body.outputDir.trim() !== ''
|
|
1258
1275
|
? body.outputDir.trim()
|
|
@@ -1266,6 +1283,28 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
1266
1283
|
},
|
|
1267
1284
|
}
|
|
1268
1285
|
|
|
1286
|
+
// --------------------------------------- bookshelf import-text preview
|
|
1287
|
+
/** 上传全文做拆章预览(不落盘、不登记书架)。 */
|
|
1288
|
+
const bookshelfImportTextPreviewRoute: WebRoute = {
|
|
1289
|
+
kind: 'exact',
|
|
1290
|
+
path: NOVEL_API.bookshelfImportTextPreview,
|
|
1291
|
+
handler: async (req, res) => {
|
|
1292
|
+
if (!guard(req, res, 'POST')) return
|
|
1293
|
+
const body = await readJsonBody<BookImportTextPreviewRequest>(req)
|
|
1294
|
+
if (body?.text === undefined || body.text.length === 0) {
|
|
1295
|
+
writeJson(res, 400, { error: 'text 不能为空' })
|
|
1296
|
+
return
|
|
1297
|
+
}
|
|
1298
|
+
try {
|
|
1299
|
+
const bookName = (body.fileName ?? '').replace(/\.[^.]+$/, '').trim().slice(0, 40) || '导入小说'
|
|
1300
|
+
const preview = previewBookText(body.text)
|
|
1301
|
+
writeJson(res, 200, { bookName, ...preview })
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
writeJson(res, 400, { error: err instanceof Error ? err.message : String(err) })
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1269
1308
|
// ---------------------------------------------------------------- audit
|
|
1270
1309
|
/** 全书一致性质检。 */
|
|
1271
1310
|
const auditRoute: WebRoute = {
|
|
@@ -2071,6 +2110,42 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
2071
2110
|
},
|
|
2072
2111
|
}
|
|
2073
2112
|
|
|
2113
|
+
// ------------------------------------------------- outline reverse
|
|
2114
|
+
/** 反推大纲:从已写章节正文反向生成全书总纲(NDJSON 流式进度)。 */
|
|
2115
|
+
const outlineReverseRoute: WebRoute = {
|
|
2116
|
+
kind: 'exact',
|
|
2117
|
+
path: NOVEL_API.outlineReverse,
|
|
2118
|
+
handler: async (req, res) => {
|
|
2119
|
+
if (!guard(req, res, 'POST')) return
|
|
2120
|
+
const config = getConfig()
|
|
2121
|
+
const project = requireProject(res)
|
|
2122
|
+
if (project === undefined) return
|
|
2123
|
+
res.writeHead(200, {
|
|
2124
|
+
'content-type': 'application/x-ndjson; charset=utf-8',
|
|
2125
|
+
'cache-control': 'no-cache',
|
|
2126
|
+
'x-accel-buffering': 'no',
|
|
2127
|
+
'referrer-policy': 'no-referrer',
|
|
2128
|
+
})
|
|
2129
|
+
const send = (frame: JobFrame): void => {
|
|
2130
|
+
res.write(JSON.stringify(frame) + '\n')
|
|
2131
|
+
}
|
|
2132
|
+
try {
|
|
2133
|
+
const outline = await reverseOutlineFromChapters(ctx, config, project, config.outputDir, (done, total, phase) => {
|
|
2134
|
+
send({ type: 'outline-progress', done, total, phase })
|
|
2135
|
+
})
|
|
2136
|
+
// 保存总纲(仅更新文本,不动书名/进度)。
|
|
2137
|
+
project.outline = outline
|
|
2138
|
+
project.updatedAt = new Date().toISOString()
|
|
2139
|
+
saveProject(config.outputDir, project)
|
|
2140
|
+
send({ type: 'outline-done', outline, chars: outline.length })
|
|
2141
|
+
} catch (error) {
|
|
2142
|
+
send({ type: 'error', no: 0, message: (error as Error).message })
|
|
2143
|
+
} finally {
|
|
2144
|
+
res.end()
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2074
2149
|
// ----------------------------------------------------------- breakdown
|
|
2075
2150
|
/** 拆书分析:对已写章节做结构/人物/文风/卖点体检(两阶段:源笔记→分节分析)。 */
|
|
2076
2151
|
const breakdownRoute: WebRoute = {
|
|
@@ -2291,6 +2366,7 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
2291
2366
|
bookshelfRemoveRoute,
|
|
2292
2367
|
bookshelfImportDirRoute,
|
|
2293
2368
|
bookshelfImportTextRoute,
|
|
2369
|
+
bookshelfImportTextPreviewRoute,
|
|
2294
2370
|
resetRoute,
|
|
2295
2371
|
auditRoute,
|
|
2296
2372
|
charactersRefreshRoute,
|
|
@@ -2311,6 +2387,7 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
2311
2387
|
configRoute,
|
|
2312
2388
|
openFolderRoute,
|
|
2313
2389
|
outlineSuggestRoute,
|
|
2390
|
+
outlineReverseRoute,
|
|
2314
2391
|
breakdownRoute,
|
|
2315
2392
|
runStartRoute,
|
|
2316
2393
|
runControlRoute,
|