@waterwx/dsh-novel-forge 1.7.3 → 1.8.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/lib/client.js +3219 -662
- package/lib/client.js.map +1 -1
- package/lib/index.js +629 -41
- package/lib/index.js.map +1 -1
- package/lib/types/author-assets.d.ts +25 -0
- package/lib/types/client/api.d.ts +18 -0
- package/lib/types/client/panel/AdaptModeView.d.ts +4 -0
- package/lib/types/client/panel/AuthorAssetsView.d.ts +4 -0
- package/lib/types/client/panel/AuthorHome.d.ts +20 -0
- package/lib/types/client/panel/ProgressHomeView.d.ts +5 -0
- package/lib/types/client/panel/SettingsView.d.ts +14 -0
- package/lib/types/client/text.d.ts +9 -0
- package/lib/types/engine.d.ts +24 -1
- package/lib/types/index.d.ts +8 -0
- package/lib/types/protocol.d.ts +192 -0
- package/package.json +1 -1
- package/src/author-assets.ts +166 -0
- package/src/client/api.ts +41 -0
- package/src/client/mount.tsx._new +97 -0
- package/src/client/mount.tsx._nf +97 -0
- package/src/client/mount.tsx._tmp +97 -0
- package/src/client/panel/AdaptModeView.tsx +204 -0
- package/src/client/panel/AuthorAssetsView.tsx +206 -0
- package/src/client/panel/AuthorHome.tsx +93 -0
- package/src/client/panel/ImportModal.tsx +2 -1
- package/src/client/panel/NovelPanel.tsx +47 -16
- package/src/client/panel/ProgressHomeView.tsx +87 -0
- package/src/client/panel/SettingsView.tsx +285 -0
- package/src/client/panel/panel.module.css +325 -0
- package/src/client/sidebar-entry.ts +1 -1
- package/src/client/text.ts +28 -0
- package/src/engine.ts +221 -0
- package/src/index.ts +20 -0
- package/src/protocol.ts +196 -0
- package/src/routes.ts +191 -0
package/src/routes.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { spawn } from 'node:child_process'
|
|
|
13
13
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
14
14
|
import { join, basename, extname } from 'node:path'
|
|
15
15
|
import { homedir } from 'node:os'
|
|
16
|
+
import { randomBytes } from 'node:crypto'
|
|
16
17
|
import { fileURLToPath } from 'node:url'
|
|
17
18
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
|
|
18
19
|
import type { Context } from '@deepseek-ai/cordis'
|
|
@@ -105,10 +106,20 @@ import {
|
|
|
105
106
|
type SummaryRequest,
|
|
106
107
|
type VolumesRequest,
|
|
107
108
|
type VolumesResponse,
|
|
109
|
+
type AuthorAssetsResponse,
|
|
110
|
+
type AuthorAssetUpsertRequest,
|
|
111
|
+
type AuthorAssetRemoveRequest,
|
|
112
|
+
type AdaptAnalyzeRequest,
|
|
113
|
+
type AdaptAnalyzeResponse,
|
|
114
|
+
type AdaptProposeRequest,
|
|
115
|
+
type AdaptProposeResponse,
|
|
116
|
+
type AdaptExecuteRequest,
|
|
117
|
+
type AdaptExecuteResponse,
|
|
108
118
|
} from './protocol.ts'
|
|
109
119
|
import { readOutlineFromDocx } from './docx.ts'
|
|
110
120
|
import { clearAssistantHistory, loadAssistantHistory, runAssistantTurn } from './assistant.ts'
|
|
111
121
|
import { activateBook, bookshelfSnapshot, createBook, defaultOutputDirFor, importDir, loadBookshelf, removeBook, renameBook, seedBookshelfFromOutputDir } from './bookshelf.ts'
|
|
122
|
+
import { loadAuthorAssets, upsertAuthorAsset, removeAuthorAsset, importDefaultAuthorAssets } from './author-assets.ts'
|
|
112
123
|
import { BUILTIN_ANTI_AI_RULES, BUILTIN_GENRE_LIBRARY, BUILTIN_PROGRESSION_MODES, BUILTIN_STYLE_TEMPLATES, emptyProjectAssets } from './assets.ts'
|
|
113
124
|
import {
|
|
114
125
|
chapterFileName,
|
|
@@ -153,6 +164,9 @@ import {
|
|
|
153
164
|
suggestOutlines,
|
|
154
165
|
reverseOutlineFromChapters,
|
|
155
166
|
breakdownBook,
|
|
167
|
+
analyzeAdaptation,
|
|
168
|
+
proposeAdaptation,
|
|
169
|
+
applyAdaptationReplacements,
|
|
156
170
|
generateRoleReferenceImage,
|
|
157
171
|
generateMangaRoleReferenceImage,
|
|
158
172
|
testImageEndpoint,
|
|
@@ -2754,6 +2768,174 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
2754
2768
|
},
|
|
2755
2769
|
}
|
|
2756
2770
|
|
|
2771
|
+
// -------------------------------------------------- author assets (总数据)
|
|
2772
|
+
const authorAssetsRoute: WebRoute = {
|
|
2773
|
+
kind: 'exact',
|
|
2774
|
+
path: NOVEL_API.authorAssets,
|
|
2775
|
+
handler: (req, res) => {
|
|
2776
|
+
if (!guard(req, res, 'GET')) return
|
|
2777
|
+
const response: AuthorAssetsResponse = { assets: loadAuthorAssets() }
|
|
2778
|
+
writeJson(res, 200, response)
|
|
2779
|
+
},
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
const authorAssetsUpsertRoute: WebRoute = {
|
|
2783
|
+
kind: 'exact',
|
|
2784
|
+
path: NOVEL_API.authorAssetsUpsert,
|
|
2785
|
+
handler: async (req, res) => {
|
|
2786
|
+
if (!guard(req, res, 'POST')) return
|
|
2787
|
+
const body = await readJsonBody<AuthorAssetUpsertRequest>(req)
|
|
2788
|
+
const asset = body?.asset
|
|
2789
|
+
if (asset === undefined || asset.name.trim() === '' || asset.content.trim() === '') {
|
|
2790
|
+
writeJson(res, 400, { error: 'asset 须含 name / content' })
|
|
2791
|
+
return
|
|
2792
|
+
}
|
|
2793
|
+
const response: AuthorAssetsResponse = { assets: upsertAuthorAsset(asset) }
|
|
2794
|
+
writeJson(res, 200, response)
|
|
2795
|
+
},
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
const authorAssetsRemoveRoute: WebRoute = {
|
|
2799
|
+
kind: 'exact',
|
|
2800
|
+
path: NOVEL_API.authorAssetsRemove,
|
|
2801
|
+
handler: async (req, res) => {
|
|
2802
|
+
if (!guard(req, res, 'POST')) return
|
|
2803
|
+
const body = await readJsonBody<AuthorAssetRemoveRequest>(req)
|
|
2804
|
+
const id = body?.id ?? ''
|
|
2805
|
+
if (id === '') { writeJson(res, 400, { error: 'id 必填' }); return }
|
|
2806
|
+
const response: AuthorAssetsResponse = { assets: removeAuthorAsset(id) }
|
|
2807
|
+
writeJson(res, 200, response)
|
|
2808
|
+
},
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
const authorAssetsImportDefaultRoute: WebRoute = {
|
|
2812
|
+
kind: 'exact',
|
|
2813
|
+
path: NOVEL_API.authorAssetsImportDefault,
|
|
2814
|
+
handler: (req, res) => {
|
|
2815
|
+
if (!guard(req, res, 'POST')) return
|
|
2816
|
+
const response: AuthorAssetsResponse = { assets: importDefaultAuthorAssets() }
|
|
2817
|
+
writeJson(res, 200, response)
|
|
2818
|
+
},
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// --------------------------------------------------- adaptation (改编 P0 分析)
|
|
2822
|
+
const adaptAnalyzeRoute: WebRoute = {
|
|
2823
|
+
kind: 'exact',
|
|
2824
|
+
path: NOVEL_API.adaptAnalyze,
|
|
2825
|
+
handler: async (req, res) => {
|
|
2826
|
+
if (!guard(req, res, 'POST')) return
|
|
2827
|
+
if (!getConfig().enableAdaptMode) { writeJson(res, 404, { error: '改编模式未开启' }); return }
|
|
2828
|
+
const body = await readJsonBody<AdaptAnalyzeRequest>(req)
|
|
2829
|
+
const config = getConfig()
|
|
2830
|
+
let text = body?.text?.trim() ?? ''
|
|
2831
|
+
if (text === '' && body?.filePath !== undefined && body.filePath !== '') {
|
|
2832
|
+
try {
|
|
2833
|
+
text = readFileSync(body.filePath, 'utf8')
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
writeJson(res, 400, { error: '读取文件失败:' + (err as Error).message })
|
|
2836
|
+
return
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
if (text.length < 200) {
|
|
2840
|
+
writeJson(res, 400, { error: '全文内容过短(<200 字符),请上传完整小说文本' })
|
|
2841
|
+
return
|
|
2842
|
+
}
|
|
2843
|
+
try {
|
|
2844
|
+
const response: AdaptAnalyzeResponse = await analyzeAdaptation(ctx, config, text)
|
|
2845
|
+
writeJson(res, 200, response)
|
|
2846
|
+
} catch (err) {
|
|
2847
|
+
writeJson(res, 400, { error: (err as Error).message })
|
|
2848
|
+
}
|
|
2849
|
+
},
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
const adaptProposeRoute: WebRoute = {
|
|
2853
|
+
kind: 'exact',
|
|
2854
|
+
path: NOVEL_API.adaptPropose,
|
|
2855
|
+
handler: async (req, res) => {
|
|
2856
|
+
if (!guard(req, res, 'POST')) return
|
|
2857
|
+
if (!getConfig().enableAdaptMode) { writeJson(res, 404, { error: '改编模式未开启' }); return }
|
|
2858
|
+
const body = await readJsonBody<AdaptProposeRequest>(req)
|
|
2859
|
+
const config = getConfig()
|
|
2860
|
+
const text = (body?.text ?? '').trim()
|
|
2861
|
+
const selections = body?.selections ?? []
|
|
2862
|
+
if (text.length < 200 || selections.length === 0) {
|
|
2863
|
+
writeJson(res, 400, { error: '请提供全文与至少一条要改的维度' })
|
|
2864
|
+
return
|
|
2865
|
+
}
|
|
2866
|
+
try {
|
|
2867
|
+
const response: AdaptProposeResponse = await proposeAdaptation(ctx, config, text, selections, body?.dimensions)
|
|
2868
|
+
writeJson(res, 200, response)
|
|
2869
|
+
} catch (err) {
|
|
2870
|
+
writeJson(res, 400, { error: (err as Error).message })
|
|
2871
|
+
}
|
|
2872
|
+
},
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
const adaptExecuteRoute: WebRoute = {
|
|
2876
|
+
kind: 'exact',
|
|
2877
|
+
path: NOVEL_API.adaptExecute,
|
|
2878
|
+
handler: async (req, res) => {
|
|
2879
|
+
if (!guard(req, res, 'POST')) return
|
|
2880
|
+
if (!getConfig().enableAdaptMode) { writeJson(res, 404, { error: '改编模式未开启' }); return }
|
|
2881
|
+
const body = await readJsonBody<AdaptExecuteRequest>(req)
|
|
2882
|
+
const text = body?.text ?? ''
|
|
2883
|
+
const mappings = body?.mappings ?? []
|
|
2884
|
+
if (text.length < 200 || mappings.length === 0) {
|
|
2885
|
+
writeJson(res, 400, { error: '请提供全文与映射表' })
|
|
2886
|
+
return
|
|
2887
|
+
}
|
|
2888
|
+
if (body?.mode === 'rewrite') {
|
|
2889
|
+
writeJson(res, 400, { error: 'rewrite 模式尚未实现,请使用 replace(术语替换)' })
|
|
2890
|
+
return
|
|
2891
|
+
}
|
|
2892
|
+
const { adaptedText, hits } = applyAdaptationReplacements(text, mappings)
|
|
2893
|
+
const response: AdaptExecuteResponse = { adaptedText, mappings: mappings.length, hits }
|
|
2894
|
+
writeJson(res, 200, response)
|
|
2895
|
+
},
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// ------------------------------------------ theme custom background (upload + serve)
|
|
2899
|
+
const THEME_BG_DIR = join(homedir(), '.dsh', 'dsh-novel-forge-assets')
|
|
2900
|
+
const MIME_BY_EXT: Record<string, string> = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif' }
|
|
2901
|
+
|
|
2902
|
+
const themeBackgroundUploadRoute: WebRoute = {
|
|
2903
|
+
kind: 'exact',
|
|
2904
|
+
path: NOVEL_API.themeBackgroundUpload,
|
|
2905
|
+
handler: async (req, res) => {
|
|
2906
|
+
if (!guard(req, res, 'POST')) return
|
|
2907
|
+
const body = await readJsonBody<{ dataUrl?: string }>(req)
|
|
2908
|
+
const dataUrl = body?.dataUrl ?? ''
|
|
2909
|
+
const m = /^data:image\/(png|jpeg|jpg|webp|gif);base64,(.+)$/i.exec(dataUrl)
|
|
2910
|
+
if (m === null) { writeJson(res, 400, { error: '仅支持 PNG/JPG/WebP/GIF 图片' }); return }
|
|
2911
|
+
const ext = m[1]!.toLowerCase() === 'jpg' ? 'jpeg' : m[1]!.toLowerCase()
|
|
2912
|
+
const buf = Buffer.from(m[2]!, 'base64')
|
|
2913
|
+
if (buf.length === 0) { writeJson(res, 400, { error: '图片数据为空' }); return }
|
|
2914
|
+
mkdirSync(THEME_BG_DIR, { recursive: true })
|
|
2915
|
+
const name = 'theme-bg-' + Date.now().toString(36) + '-' + randomBytes(3).toString('hex') + '.' + ext
|
|
2916
|
+
writeFileSync(join(THEME_BG_DIR, name), buf)
|
|
2917
|
+
writeJson(res, 200, { url: NOVEL_API.themeBackgroundGet + '/' + name })
|
|
2918
|
+
},
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
const themeBackgroundGetRoute: WebRoute = {
|
|
2922
|
+
kind: 'prefix',
|
|
2923
|
+
path: NOVEL_API.themeBackgroundGet,
|
|
2924
|
+
handler: (req, res) => {
|
|
2925
|
+
if (!guard(req, res, 'GET')) return
|
|
2926
|
+
let pathName = ''
|
|
2927
|
+
try { pathName = new URL(req.url ?? '', 'http://localhost').pathname } catch { /* ignore */ }
|
|
2928
|
+
const name = basename(pathName)
|
|
2929
|
+
if (!name.startsWith('theme-bg-')) { writeJson(res, 404, { error: 'not found' }); return }
|
|
2930
|
+
const file = join(THEME_BG_DIR, name)
|
|
2931
|
+
if (!existsSync(file)) { writeJson(res, 404, { error: 'not found' }); return }
|
|
2932
|
+
const ext = extname(file).slice(1)
|
|
2933
|
+
const type = MIME_BY_EXT[ext] ?? 'application/octet-stream'
|
|
2934
|
+
res.writeHead(200, { 'content-type': type, 'cache-control': 'public, max-age=31536000' })
|
|
2935
|
+
res.end(readFileSync(file))
|
|
2936
|
+
},
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2757
2939
|
return [
|
|
2758
2940
|
statusRoute,
|
|
2759
2941
|
loadOutlineRoute,
|
|
@@ -2816,6 +2998,15 @@ export function makeRoutes(deps: NovelRoutesDeps): WebRoute[] {
|
|
|
2816
2998
|
runStartRoute,
|
|
2817
2999
|
runControlRoute,
|
|
2818
3000
|
runStatusRoute,
|
|
3001
|
+
authorAssetsRoute,
|
|
3002
|
+
authorAssetsUpsertRoute,
|
|
3003
|
+
authorAssetsRemoveRoute,
|
|
3004
|
+
authorAssetsImportDefaultRoute,
|
|
3005
|
+
adaptAnalyzeRoute,
|
|
3006
|
+
adaptProposeRoute,
|
|
3007
|
+
adaptExecuteRoute,
|
|
3008
|
+
themeBackgroundUploadRoute,
|
|
3009
|
+
themeBackgroundGetRoute,
|
|
2819
3010
|
]
|
|
2820
3011
|
}
|
|
2821
3012
|
|