@waterwx/dsh-novel-forge 1.2.0 → 1.3.0-alpha
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/README.md +236 -349
- package/lib/client.js +56064 -10565
- package/lib/index.js +13922 -5218
- package/lib/types/action-guard.d.ts +61 -0
- package/lib/types/ai-scan.d.ts +36 -0
- package/lib/types/assets.d.ts +24 -9
- package/lib/types/assistant.d.ts +29 -0
- package/lib/types/author-assets.d.ts +25 -0
- package/lib/types/bookshelf.d.ts +11 -1
- package/lib/types/client/api.d.ts +178 -10
- package/lib/types/client/docx.d.ts +2 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/llmLive.d.ts +31 -0
- package/lib/types/client/locales.d.ts +28 -11
- package/lib/types/client/panel/AdaptModeView.d.ts +5 -0
- package/lib/types/client/panel/AiTag.d.ts +1 -0
- package/lib/types/client/panel/AssistantTab.d.ts +4 -1
- package/lib/types/client/panel/AuthorAssetsView.d.ts +4 -0
- package/lib/types/client/panel/AuthorHome.d.ts +34 -0
- package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
- package/lib/types/client/panel/CmdPalette.d.ts +12 -0
- package/lib/types/client/panel/CreateBookView.d.ts +5 -1
- package/lib/types/client/panel/DirectorView.d.ts +7 -0
- package/lib/types/client/panel/GlobalAssetLibraryView.d.ts +4 -0
- package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
- package/lib/types/client/panel/ImportModal.d.ts +7 -0
- package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
- package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
- package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
- package/lib/types/client/panel/ModelManager.d.ts +14 -0
- package/lib/types/client/panel/NovelPanel.d.ts +6 -1
- package/lib/types/client/panel/PageHeader.d.ts +18 -0
- package/lib/types/client/panel/ProgressConsole.d.ts +17 -0
- package/lib/types/client/panel/ProgressHomeView.d.ts +5 -0
- package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
- package/lib/types/client/panel/ReasoningSection.d.ts +10 -0
- package/lib/types/client/panel/SettingsView.d.ts +19 -0
- package/lib/types/client/panel/ShelfView.d.ts +12 -1
- package/lib/types/client/panel/SubPage.d.ts +27 -0
- package/lib/types/client/panel/TensionView.d.ts +14 -0
- package/lib/types/client/panel/TimelineView.d.ts +14 -0
- package/lib/types/client/panel/toast.d.ts +2 -0
- package/lib/types/client/panel/views.d.ts +25 -7
- package/lib/types/client/run-status.d.ts +19 -0
- package/lib/types/client/text.d.ts +9 -0
- package/lib/types/docx-core.d.ts +16 -0
- package/lib/types/docx.d.ts +2 -3
- package/lib/types/engine.d.ts +185 -41
- package/lib/types/global-assets.d.ts +6 -0
- package/lib/types/index.d.ts +22 -2
- package/lib/types/llm-live.d.ts +75 -0
- package/lib/types/llm-retry.d.ts +38 -0
- package/lib/types/market-radar-scan.d.ts +19 -0
- package/lib/types/market-radar-sources.d.ts +31 -0
- package/lib/types/migrate-output.d.ts +25 -0
- package/lib/types/novel-context.d.ts +80 -0
- package/lib/types/prompt-slots.d.ts +57 -0
- package/lib/types/prompt-utils.d.ts +35 -0
- package/lib/types/prompting.d.ts +10 -0
- package/lib/types/protocol.d.ts +1311 -125
- package/lib/types/review-policy.d.ts +97 -0
- package/lib/types/routes.d.ts +4 -0
- package/lib/types/run.d.ts +3 -1
- package/lib/types/snapshots.d.ts +46 -0
- package/lib/types/stage-contract.d.ts +60 -0
- package/lib/types/tension.d.ts +65 -0
- package/lib/types/timeline.d.ts +37 -0
- package/package.json +52 -27
- package/scripts/patch-schemastery.mjs +47 -0
- package/src/action-guard.ts +127 -0
- package/src/ai-scan.ts +179 -0
- package/src/assets.ts +1806 -520
- package/src/assistant.ts +1098 -770
- package/src/author-assets.ts +149 -0
- package/src/bookshelf.ts +187 -151
- package/src/client/api.ts +758 -410
- package/src/client/docx.ts +37 -69
- package/src/client/index.ts +3 -1
- package/src/client/llmLive.ts +111 -0
- package/src/client/locales.ts +418 -383
- package/src/client/mount.tsx +1 -1
- package/src/client/panel/AdaptModeView.tsx +535 -0
- package/src/client/panel/AiTag.tsx +7 -0
- package/src/client/panel/AssetsTab.tsx +448 -458
- package/src/client/panel/AssistantTab.tsx +355 -301
- package/src/client/panel/AuthorAssetsView.tsx +206 -0
- package/src/client/panel/AuthorHome.tsx +309 -0
- package/src/client/panel/BookAnalysisView.tsx +63 -0
- package/src/client/panel/CmdPalette.tsx +102 -0
- package/src/client/panel/CreateBookView.tsx +366 -348
- package/src/client/panel/DirectorView.tsx +200 -0
- package/src/client/panel/GlobalAssetLibraryView.tsx +168 -0
- package/src/client/panel/IdeaInspirationView.tsx +76 -0
- package/src/client/panel/ImportModal.tsx +303 -0
- package/src/client/panel/KnowledgeBaseView.tsx +125 -0
- package/src/client/panel/LiveFeedLog.tsx +204 -0
- package/src/client/panel/MarketRadarView.tsx +377 -0
- package/src/client/panel/ModelManager.tsx +205 -0
- package/src/client/panel/NovelPanel.tsx +5517 -4597
- package/src/client/panel/PageHeader.tsx +37 -0
- package/src/client/panel/ProgressConsole.tsx +63 -0
- package/src/client/panel/PromptSlotsView.tsx +97 -0
- package/src/client/panel/ReaderView.tsx +306 -306
- package/src/client/panel/ReasoningSection.tsx +31 -0
- package/src/client/panel/RunPanel.tsx +223 -204
- package/src/client/panel/SettingsView.tsx +340 -0
- package/src/client/panel/ShelfView.tsx +266 -236
- package/src/client/panel/SubPage.tsx +42 -0
- package/src/client/panel/TensionView.tsx +196 -0
- package/src/client/panel/TimelineView.tsx +192 -0
- package/src/client/panel/WorldTab.tsx +202 -202
- package/src/client/panel/panel.module.css +5764 -2941
- package/src/client/panel/toast.ts +51 -0
- package/src/client/panel/views.tsx +401 -314
- package/src/client/run-status.ts +143 -0
- package/src/client/sidebar-entry.ts +1 -1
- package/src/client/text.ts +28 -0
- package/src/docx-core.ts +53 -0
- package/src/docx.ts +44 -83
- package/src/engine.ts +4672 -2787
- package/src/global-assets.ts +59 -0
- package/src/index.ts +323 -252
- package/src/llm-live.ts +260 -0
- package/src/llm-retry.ts +77 -0
- package/src/market-radar-scan.ts +41 -0
- package/src/market-radar-sources.ts +173 -0
- package/src/migrate-output.ts +76 -0
- package/src/novel-context.ts +286 -0
- package/src/prompt-slots.ts +125 -0
- package/src/prompting.ts +26 -0
- package/src/protocol.ts +2383 -1182
- package/src/review-policy.ts +161 -0
- package/src/routes.ts +3724 -2151
- package/src/run.ts +346 -330
- package/src/snapshots.ts +174 -0
- package/src/stage-contract.ts +189 -0
- package/src/tension.ts +240 -0
- package/src/timeline.ts +159 -0
- package/lib/client.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/src/client/panel/BookshelfBar.tsx +0 -170
package/src/client/api.ts
CHANGED
|
@@ -1,410 +1,758 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-side API client for the /api/dsh-novel-forge route family. Plain
|
|
3
|
-
* fetch, same origin; generation/rewrite/polish ride NDJSON streams read
|
|
4
|
-
* incrementally.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
NOVEL_API,
|
|
9
|
-
type AssetsPatch,
|
|
10
|
-
type AssetsResponse,
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
type
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
type
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
body
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
async
|
|
123
|
-
return postJson<
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
async
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
async
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side API client for the /api/dsh-novel-forge route family. Plain
|
|
3
|
+
* fetch, same origin; generation/rewrite/polish ride NDJSON streams read
|
|
4
|
+
* incrementally.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
NOVEL_API,
|
|
9
|
+
type AssetsPatch,
|
|
10
|
+
type AssetsResponse,
|
|
11
|
+
type AddModelRequest,
|
|
12
|
+
type AddModelResponse,
|
|
13
|
+
type LlmModelsResponse,
|
|
14
|
+
type LlmProvidersResponse,
|
|
15
|
+
type LlmVendorsResponse,
|
|
16
|
+
type RemoveProviderRequest,
|
|
17
|
+
type RemoveProviderResponse,
|
|
18
|
+
type BibleResponse,
|
|
19
|
+
type ChapterResponse,
|
|
20
|
+
type ConfigPatch,
|
|
21
|
+
type ExportResponse,
|
|
22
|
+
type ForeshadowRequest,
|
|
23
|
+
type ForeshadowResponse,
|
|
24
|
+
type JobFrame,
|
|
25
|
+
type LoadOutlineResponse,
|
|
26
|
+
type AdaptExecuteRequest,
|
|
27
|
+
type AdaptRewriteFrame,
|
|
28
|
+
type AdaptMaterializeSaveRequest,
|
|
29
|
+
type AdaptMaterializeSaveResponse,
|
|
30
|
+
type PluginUpdateResponse,
|
|
31
|
+
type LlmTestResponse,
|
|
32
|
+
type NovelConfig,
|
|
33
|
+
type PlanResponse,
|
|
34
|
+
type ReviewReport,
|
|
35
|
+
type StatusResponse,
|
|
36
|
+
type StyleEngineRequest,
|
|
37
|
+
type StyleAsset,
|
|
38
|
+
type VolumesResponse,
|
|
39
|
+
} from '../protocol.ts'
|
|
40
|
+
|
|
41
|
+
/** Error carrying the route's JSON error message. */
|
|
42
|
+
export class NovelApiError extends Error {
|
|
43
|
+
constructor(message: string) {
|
|
44
|
+
super(message)
|
|
45
|
+
this.name = 'NovelApiError'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Parse a JSON response or throw a NovelApiError. */
|
|
50
|
+
async function readJson<T>(response: Response): Promise<T> {
|
|
51
|
+
let body: unknown
|
|
52
|
+
try {
|
|
53
|
+
body = await response.json()
|
|
54
|
+
} catch {
|
|
55
|
+
throw new NovelApiError(`HTTP ${response.status}: invalid JSON response`)
|
|
56
|
+
}
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'
|
|
59
|
+
? (body as { error: string }).error
|
|
60
|
+
: `HTTP ${response.status}`
|
|
61
|
+
throw new NovelApiError(message)
|
|
62
|
+
}
|
|
63
|
+
return body as T
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 面板所绑定的当前书(打开时锁定;每次书级请求带上,避免被全局 active 串书)。 */
|
|
67
|
+
let currentBookId: string | null = null
|
|
68
|
+
export function setCurrentBook(id: string | null): void {
|
|
69
|
+
currentBookId = id
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 给 GET 路径追加当前书 bookId(读接口也按书路由)。 */
|
|
73
|
+
function withBookId(path: string): string {
|
|
74
|
+
if (currentBookId === null) return path
|
|
75
|
+
const sep = path.includes('?') ? '&' : '?'
|
|
76
|
+
return `${path}${sep}bookId=${encodeURIComponent(currentBookId)}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 给 POST 请求体注入当前书 bookId(与 withBookId 对称)。
|
|
81
|
+
*
|
|
82
|
+
* 这是本模块的不变量:**每一个书级请求都必须显式带上 bookId**,否则宿主会退回
|
|
83
|
+
* 「全局 active 书」——切书与请求并发时就会串书。
|
|
84
|
+
*/
|
|
85
|
+
function withBookIdBody(payload: unknown): unknown {
|
|
86
|
+
if (currentBookId === null) return payload
|
|
87
|
+
if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) return payload
|
|
88
|
+
return { ...(payload as Record<string, unknown>), bookId: currentBookId }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** POST JSON, return parsed JSON. 若已绑定当前书,自动把 bookId 注入请求体。 */
|
|
92
|
+
async function postJson<T>(path: string, payload: unknown): Promise<T> {
|
|
93
|
+
const response = await fetch(path, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { 'content-type': 'application/json' },
|
|
96
|
+
body: JSON.stringify(withBookIdBody(payload)),
|
|
97
|
+
})
|
|
98
|
+
return readJson<T>(response)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The browser half's only data entry point. */
|
|
102
|
+
export class NovelApi {
|
|
103
|
+
async status(): Promise<StatusResponse> {
|
|
104
|
+
const response = await fetch(withBookId(NOVEL_API.status))
|
|
105
|
+
return readJson<StatusResponse>(response)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async loadOutline(path?: string, text?: string): Promise<LoadOutlineResponse> {
|
|
109
|
+
return postJson<LoadOutlineResponse>(NOVEL_API.loadOutline, { path, text })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async saveOutline(text: string): Promise<{ ok: boolean; bookName: string }> {
|
|
113
|
+
return postJson<{ ok: boolean; bookName: string }>(NOVEL_API.saveOutline, { text })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 反推大纲:从已写章节正文反向生成全书总纲(NDJSON 流)。 */
|
|
117
|
+
async outlineReverse(onFrame: (frame: JobFrame) => void): Promise<void> {
|
|
118
|
+
await this.streamJob(NOVEL_API.outlineReverse, {}, onFrame)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 开书想法 → AI 大纲:生成 count 个方案(换批时传 exclude 避开已暂留方向)。 */
|
|
122
|
+
async outlineSuggest(idea: string, count?: number, exclude?: string[]): Promise<import('../protocol.ts').OutlineSuggestResponse> {
|
|
123
|
+
return postJson<import('../protocol.ts').OutlineSuggestResponse>(NOVEL_API.outlineSuggest, { idea, count, exclude })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 拆书分析:对已写章节做结构/人物/文风/卖点体检。 */
|
|
127
|
+
async breakdown(scope?: string, preset?: 'quick' | 'standard', budgetTokens?: number): Promise<import('../protocol.ts').BreakdownResponse> {
|
|
128
|
+
return postJson<import('../protocol.ts').BreakdownResponse>(NOVEL_API.breakdown, { scope, preset, budgetTokens })
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async plan(outline?: string, chapterCount?: number, volume?: number): Promise<PlanResponse> {
|
|
132
|
+
return postJson<PlanResponse>(NOVEL_API.plan, { outline, chapterCount, volume })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async volumes(outline?: string): Promise<VolumesResponse> {
|
|
136
|
+
return postJson<VolumesResponse>(NOVEL_API.volumes, { outline })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async bible(outline?: string): Promise<BibleResponse> {
|
|
140
|
+
return postJson<BibleResponse>(NOVEL_API.bible, { outline })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async review(chapterNo: number): Promise<{ report: ReviewReport }> {
|
|
144
|
+
return postJson<{ report: ReviewReport }>(NOVEL_API.review, { chapterNo })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async summarize(chapterNo: number): Promise<{ summary: string }> {
|
|
148
|
+
return postJson<{ summary: string }>(NOVEL_API.summary, { chapterNo })
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async foreshadow(req: ForeshadowRequest): Promise<ForeshadowResponse> {
|
|
152
|
+
return postJson<ForeshadowResponse>(NOVEL_API.foreshadow, req)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async exportBook(format: 'txt' | 'md' | 'json', scope: import('../protocol.ts').ExportScope = 'book'): Promise<ExportResponse> {
|
|
156
|
+
return postJson<ExportResponse>(NOVEL_API.exportBook, { format, scope })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async chapter(no: number): Promise<ChapterResponse> {
|
|
160
|
+
const response = await fetch(withBookId(`${NOVEL_API.chapter}?no=${no}`))
|
|
161
|
+
return readJson<ChapterResponse>(response)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 审查手动编辑的正文(不落盘)。previousReport 传入时走「验证模式」(核对原意见解决 + 只挑新增 high)。 */
|
|
165
|
+
async chapterCheck(no: number, text: string, previousReport?: ReviewReport): Promise<{ report: ReviewReport }> {
|
|
166
|
+
return postJson<{ report: ReviewReport }>(NOVEL_API.chapterCheck, { chapterNo: no, text, previousReport })
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** 保存手动编辑的正文(自动备份 .bak;带报告则沿用落盘,否则保存后自动审稿)。 */
|
|
170
|
+
async chapterSave(no: number, text: string, report?: ReviewReport): Promise<import('../protocol.ts').ChapterSaveResponse> {
|
|
171
|
+
return postJson<import('../protocol.ts').ChapterSaveResponse>(NOVEL_API.chapterSave, { chapterNo: no, text, report })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async patchConfig(patch: ConfigPatch): Promise<{ config: NovelConfig }> {
|
|
175
|
+
return postJson<{ config: NovelConfig }>(NOVEL_API.config, patch)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 输出目录迁移:dryRun=true 仅预览目录内容;确认时传 to 搬迁。 */
|
|
179
|
+
async moveOutputDir(to?: string, dryRun?: boolean): Promise<import('../protocol.ts').MoveOutputDirResponse> {
|
|
180
|
+
return postJson<import('../protocol.ts').MoveOutputDirResponse>(NOVEL_API.moveOutputDir, { to, dryRun })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 本书参数:不带 patch 读取;带 patch 合并保存(字段传 null = 回退全局)。 */
|
|
184
|
+
async bookSettings(patch?: Record<string, unknown>): Promise<import('../protocol.ts').BookSettingsResponse> {
|
|
185
|
+
return postJson<import('../protocol.ts').BookSettingsResponse>(NOVEL_API.bookSettings, patch === undefined ? {} : { patch })
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async openFolder(): Promise<void> {
|
|
189
|
+
await fetch(NOVEL_API.openFolder, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 插件自更新:在 DSH profile 目录拉取最新 npm 版(需重启 DSH 生效)。 */
|
|
193
|
+
async pluginUpdate(): Promise<PluginUpdateResponse> {
|
|
194
|
+
return postJson<PluginUpdateResponse>(NOVEL_API.pluginUpdate, {})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** 书架快照。 */
|
|
198
|
+
async bookshelf(): Promise<import('../protocol.ts').BookshelfSnapshot> {
|
|
199
|
+
const response = await fetch(NOVEL_API.bookshelf)
|
|
200
|
+
return readJson<import('../protocol.ts').BookshelfSnapshot>(response)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 新建书并激活(开书向导:可携带大纲文本,创建即建项目)。 */
|
|
204
|
+
async bookCreate(bookName: string, outputDir?: string, outline?: string): Promise<import('../protocol.ts').BookshelfSnapshot> {
|
|
205
|
+
return postJson<import('../protocol.ts').BookshelfSnapshot>(NOVEL_API.bookshelf, { bookName, outputDir, outline })
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 重置项目(清空进度;可携带新大纲)。 */
|
|
209
|
+
async reset(outline?: string): Promise<{ ok: boolean; bookName: string }> {
|
|
210
|
+
return postJson<{ ok: boolean; bookName: string }>(NOVEL_API.reset, { outline })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** 全书一致性质检。 */
|
|
214
|
+
async audit(): Promise<import('../protocol.ts').AuditResponse> {
|
|
215
|
+
return postJson<import('../protocol.ts').AuditResponse>(NOVEL_API.audit, {})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 热门题材雷达:信号 + 生产底座。 */
|
|
219
|
+
async marketRadar(req: import('../protocol.ts').MarketRadarRequest): Promise<{ result: import('../protocol.ts').MarketRadarResult }> {
|
|
220
|
+
return postJson<{ result: import('../protocol.ts').MarketRadarResult }>(NOVEL_API.marketRadar, req)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 真实榜单扫榜:抓取公开榜单,返回分组候选。 */
|
|
224
|
+
async marketRadarScan(req: { platforms?: string[] }): Promise<{ result: { scannedAt: string; groups: any[] } }> {
|
|
225
|
+
return postJson<{ result: { scannedAt: string; groups: any[] } }>(NOVEL_API.marketRadarScan, req)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 把雷达生产底座里的新资产同步进全局资源库(跨书复用;已有名字跳过)。 */
|
|
229
|
+
async marketRadarSync(req: { foundation: import('../protocol.ts').ProductionFoundation }): Promise<{ ok: boolean; synced: { genre: boolean; primaryMode: boolean; secondaryMode: boolean } }> {
|
|
230
|
+
return postJson<{ ok: boolean; synced: { genre: boolean; primaryMode: boolean; secondaryMode: boolean } }>(NOVEL_API.marketRadarSync, req)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** 书内知识库:读取。 */
|
|
234
|
+
async knowledgeList(): Promise<{ docs: import('../protocol.ts').KnowledgeDoc[] }> {
|
|
235
|
+
const response = await fetch(withBookId(NOVEL_API.knowledge))
|
|
236
|
+
return readJson<{ docs: import('../protocol.ts').KnowledgeDoc[] }>(response)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 书内知识库:新增。 */
|
|
240
|
+
async knowledgeAdd(doc: { title: string; content: string }): Promise<{ docs: import('../protocol.ts').KnowledgeDoc[] }> {
|
|
241
|
+
return postJson<{ docs: import('../protocol.ts').KnowledgeDoc[] }>(NOVEL_API.knowledge, { action: 'add', doc })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** 书内知识库:删除。 */
|
|
245
|
+
async knowledgeRemove(id: string): Promise<{ docs: import('../protocol.ts').KnowledgeDoc[] }> {
|
|
246
|
+
return postJson<{ docs: import('../protocol.ts').KnowledgeDoc[] }>(NOVEL_API.knowledge, { action: 'remove', id })
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** 书分析/拆书。 */
|
|
250
|
+
async bookAnalysis(text: string): Promise<{ result: import('../protocol.ts').BookAnalysisResult }> {
|
|
251
|
+
return postJson<{ result: import('../protocol.ts').BookAnalysisResult }>(NOVEL_API.bookAnalysis, { text })
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** 创意灵感。 */
|
|
255
|
+
async ideaInspiration(idea: string, count = 5): Promise<{ result: import('../protocol.ts').IdeaInspirationResult }> {
|
|
256
|
+
return postJson<{ result: import('../protocol.ts').IdeaInspirationResult }>(NOVEL_API.ideaInspiration, { idea, count })
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** 雷达→灵感:基于市场信号/生产底座/创意简报生成开书灵感。 */
|
|
260
|
+
async marketIdeaInspiration(req: { signals?: import('../protocol.ts').MarketRadarSignal[]; foundation?: import('../protocol.ts').ProductionFoundation; brief?: import('../protocol.ts').MarketCreativeBrief; count?: number }): Promise<{ result: import('../protocol.ts').IdeaInspirationResult }> {
|
|
261
|
+
return postJson<{ result: import('../protocol.ts').IdeaInspirationResult }>(NOVEL_API.ideaInspirationMarket, req)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** 自动编辑编排建议:基于全书上下文。 */
|
|
265
|
+
async director(focus?: string): Promise<{ result: import('../protocol.ts').DirectorAdvice }> {
|
|
266
|
+
return postJson<{ result: import('../protocol.ts').DirectorAdvice }>(NOVEL_API.director, { focus })
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 自动编辑「采纳」出的书内待办:读取。 */
|
|
270
|
+
async directorTodosList(): Promise<{ todos: import('../protocol.ts').DirectorTodo[] }> {
|
|
271
|
+
const response = await fetch(withBookId(NOVEL_API.directorTodos))
|
|
272
|
+
return readJson<{ todos: import('../protocol.ts').DirectorTodo[] }>(response)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** 待办:新增(来源 risk/fix)。 */
|
|
276
|
+
async directorTodosAdd(text: string, source: 'risk' | 'fix'): Promise<{ todos: import('../protocol.ts').DirectorTodo[] }> {
|
|
277
|
+
return postJson<{ todos: import('../protocol.ts').DirectorTodo[] }>(NOVEL_API.directorTodos, { op: 'add', text, source })
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** 待办:勾选/取消。 */
|
|
281
|
+
async directorTodosToggle(id: string): Promise<{ todos: import('../protocol.ts').DirectorTodo[] }> {
|
|
282
|
+
return postJson<{ todos: import('../protocol.ts').DirectorTodo[] }>(NOVEL_API.directorTodos, { op: 'toggle', id })
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** 待办:删除。 */
|
|
286
|
+
async directorTodosRemove(id: string): Promise<{ todos: import('../protocol.ts').DirectorTodo[] }> {
|
|
287
|
+
return postJson<{ todos: import('../protocol.ts').DirectorTodo[] }>(NOVEL_API.directorTodos, { op: 'remove', id })
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 用选中的市场信号 + 影响模式生成开书创意简报。 */
|
|
291
|
+
async marketRadarBrief(req: import('../protocol.ts').MarketRadarBriefRequest): Promise<{ creativeBrief: import('../protocol.ts').MarketCreativeBrief }> {
|
|
292
|
+
return postJson<{ creativeBrief: import('../protocol.ts').MarketCreativeBrief }>(NOVEL_API.marketRadarBrief, req)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** 把雷达生产底座一键应用到某本书(写入项目资产/开书定盘)。 */
|
|
296
|
+
async marketRadarApply(req: { bookId?: string; foundation: import('../protocol.ts').ProductionFoundation }): Promise<{ ok: boolean; bookName: string }> {
|
|
297
|
+
return postJson<{ ok: boolean; bookName: string }>(NOVEL_API.marketRadarApply, req)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** 角色卡刷新(基于事实库聚合)。 */
|
|
301
|
+
async charactersRefresh(): Promise<{ cards: import('../protocol.ts').RoleStatusCard[] }> {
|
|
302
|
+
return postJson<{ cards: import('../protocol.ts').RoleStatusCard[] }>(NOVEL_API.charactersRefresh, {})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** 事实库回填:对历史已生成章节批量抽取事实。 */
|
|
306
|
+
async factsBackfill(): Promise<{ ok: boolean; filled: number }> {
|
|
307
|
+
return postJson<{ ok: boolean; filled: number }>(NOVEL_API.factsBackfill, {})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** 道藏局部修补。 */
|
|
311
|
+
async biblePatch(patch: import('../protocol.ts').BiblePatchRequest): Promise<{ bible: import('../protocol.ts').StoryBible }> {
|
|
312
|
+
return postJson<{ bible: import('../protocol.ts').StoryBible }>(NOVEL_API.biblePatch, patch)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** 剧情线管理:增删改 + 关联章节。 */
|
|
316
|
+
async plotlines(req: import('../protocol.ts').PlotlinesRequest): Promise<import('../protocol.ts').PlotlinesResponse> {
|
|
317
|
+
return postJson<import('../protocol.ts').PlotlinesResponse>(NOVEL_API.plotlines, req)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** 敏感词检查:指定章节 / 任意文本 / 全书。 */
|
|
321
|
+
async sensitiveCheck(req: import('../protocol.ts').SensitiveCheckRequest): Promise<import('../protocol.ts').SensitiveCheckResponse> {
|
|
322
|
+
return postJson<import('../protocol.ts').SensitiveCheckResponse>(NOVEL_API.sensitiveCheck, req)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 作者复盘补跑:单章(JSON)。 */
|
|
326
|
+
async reviewBackfillChapter(no: number): Promise<{ no: number; review: import('../protocol.ts').AuthorReview }> {
|
|
327
|
+
return postJson<{ no: number; review: import('../protocol.ts').AuthorReview }>(NOVEL_API.reviewBackfill, { chapterNo: no })
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** 作者复盘补跑:全书缺失章节(NDJSON 流)。 */
|
|
331
|
+
async reviewBackfillAll(onFrame: (frame: JobFrame) => void): Promise<void> {
|
|
332
|
+
await this.streamJob(NOVEL_API.reviewBackfill, {}, onFrame)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** 章节复位:generating 卡死 → pending。 */
|
|
336
|
+
async chapterReset(no: number): Promise<{ ok: boolean; no: number }> {
|
|
337
|
+
return postJson<{ ok: boolean; no: number }>(NOVEL_API.chapterReset, { chapterNo: no })
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** 章节直接通过(作者行使最终决定权)。 */
|
|
341
|
+
async chapterApprove(no: number): Promise<{ ok: boolean; no: number }> {
|
|
342
|
+
return postJson<{ ok: boolean; no: number }>(NOVEL_API.chapterApprove, { chapterNo: no })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** 对选中的提供商/模型发一次最小真实调用,验证连通性。 */
|
|
346
|
+
async llmTest(provider: string, model: string): Promise<LlmTestResponse> {
|
|
347
|
+
return postJson<LlmTestResponse>(NOVEL_API.llmTest, { provider, model })
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** 添加模型:厂商直填 key,或自定义 OpenAI 兼容路由(写 DSH 凭据 + 注册路由)。 */
|
|
351
|
+
async addModel(req: AddModelRequest): Promise<AddModelResponse> {
|
|
352
|
+
return postJson<AddModelResponse>(NOVEL_API.addModel, req)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** 运行时厂商目录(DSH pi-ai 可配置提供方 + 内置适配器)。 */
|
|
356
|
+
async llmVendors(): Promise<LlmVendorsResponse> {
|
|
357
|
+
const response = await fetch(NOVEL_API.llmVendors)
|
|
358
|
+
return readJson<LlmVendorsResponse>(response)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** 查询某个 provider 当前可用的模型(添加成功后可即时刷新)。 */
|
|
362
|
+
async llmModels(provider: string): Promise<LlmModelsResponse> {
|
|
363
|
+
const response = await fetch(NOVEL_API.llmModels + '?provider=' + encodeURIComponent(provider))
|
|
364
|
+
return readJson<LlmModelsResponse>(response)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** 已注册的提供方路由列表(提供方管理卡片)。 */
|
|
368
|
+
async llmProviders(): Promise<LlmProvidersResponse> {
|
|
369
|
+
const response = await fetch(NOVEL_API.llmProviders)
|
|
370
|
+
return readJson<LlmProvidersResponse>(response)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** 移除一个提供方(unset key + 移除 llm-pi-ai 路由)。 */
|
|
374
|
+
async removeProvider(req: RemoveProviderRequest): Promise<RemoveProviderResponse> {
|
|
375
|
+
return postJson<RemoveProviderResponse>(NOVEL_API.llmRemove, req)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async roles(req: import('../protocol.ts').RolesRequest): Promise<import('../protocol.ts').RolesResponse> {
|
|
379
|
+
return postJson<import('../protocol.ts').RolesResponse>(NOVEL_API.roles, req)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** 小说简介:生成/补全(partial 留空 = 全量),或手动保存。 */
|
|
383
|
+
async blurb(action: 'generate' | 'save', text?: string, partial?: string): Promise<{ blurb: string }> {
|
|
384
|
+
return postJson<{ blurb: string }>(NOVEL_API.blurb, { action, text, partial })
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** 封面:读取(dataUrl;dir 指定某本书的输出目录,省略为当前书)。 */
|
|
388
|
+
async coverGet(dir?: string): Promise<import('../protocol.ts').CoverResponse> {
|
|
389
|
+
const query = dir !== undefined ? `?dir=${encodeURIComponent(dir)}` : ''
|
|
390
|
+
const response = await fetch(NOVEL_API.cover + query)
|
|
391
|
+
return readJson<import('../protocol.ts').CoverResponse>(response)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** 封面:上传(base64 data URL)或移除。 */
|
|
395
|
+
async coverPost(action: 'upload' | 'remove', dataUrl?: string): Promise<{ ok: boolean; coverPath?: string | null }> {
|
|
396
|
+
return postJson<{ ok: boolean; coverPath?: string | null }>(NOVEL_API.cover, { action, dataUrl })
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** 重命名当前书(同步项目与书架条目)。 */
|
|
400
|
+
async rename(bookName: string): Promise<{ bookName: string }> {
|
|
401
|
+
return postJson<{ bookName: string }>(NOVEL_API.rename, { bookName })
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** 大世界:提炼(generate)或手动保存(save)。 */
|
|
405
|
+
async world(action: 'generate' | 'save', world?: import('../protocol.ts').WorldState): Promise<{ world: import('../protocol.ts').WorldState }> {
|
|
406
|
+
return postJson<{ world: import('../protocol.ts').WorldState }>(NOVEL_API.world, { action, world })
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** 切换当前书。 */
|
|
410
|
+
async bookActivate(id: string): Promise<import('../protocol.ts').BookshelfSnapshot> {
|
|
411
|
+
return postJson<import('../protocol.ts').BookshelfSnapshot>('/api/dsh-novel-forge/bookshelf/activate', { id })
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** 移除书架条目。 */
|
|
415
|
+
async bookRemove(id: string): Promise<import('../protocol.ts').BookshelfSnapshot> {
|
|
416
|
+
return postJson<import('../protocol.ts').BookshelfSnapshot>('/api/dsh-novel-forge/bookshelf/remove', { id })
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** 导入已有项目目录(Mode A):校验 novel-project.json,登记/激活书架。 */
|
|
420
|
+
async bookImportDir(outputDir: string): Promise<import('../protocol.ts').BookImportDirResponse> {
|
|
421
|
+
return postJson<import('../protocol.ts').BookImportDirResponse>(NOVEL_API.bookshelfImportDir, { outputDir })
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** 导入 txt/md 全本(Mode B):服务器本地文件路径模式。 */
|
|
425
|
+
async bookImportText(filePath: string, outputDir?: string): Promise<import('../protocol.ts').BookImportTextResponse> {
|
|
426
|
+
return postJson<import('../protocol.ts').BookImportTextResponse>(NOVEL_API.bookshelfImportText, { filePath, outputDir })
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** 拆章预览(浏览器上传全文,不落盘):返回识别到的章节与跳过清单。 */
|
|
430
|
+
async bookImportTextPreview(text: string, fileName?: string): Promise<import('../protocol.ts').BookImportTextPreviewResponse> {
|
|
431
|
+
return postJson<import('../protocol.ts').BookImportTextPreviewResponse>(NOVEL_API.bookshelfImportTextPreview, { text, fileName })
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** 导入 txt/md 全本(Mode B):浏览器上传全文内容模式。 */
|
|
435
|
+
async bookImportTextContent(text: string, fileName: string, outputDir?: string): Promise<import('../protocol.ts').BookImportTextResponse> {
|
|
436
|
+
return postJson<import('../protocol.ts').BookImportTextResponse>(NOVEL_API.bookshelfImportText, { text, fileName, outputDir })
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** 生产单:启动批量生产(区间或新增 N 章;计划不足自动补)。 */
|
|
440
|
+
async runStart(req: import('../protocol.ts').RunStartRequest): Promise<import('../protocol.ts').RunState> {
|
|
441
|
+
return postJson<import('../protocol.ts').RunState>(NOVEL_API.runStart, req)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** 生产单控制:pause / resume / stop。 */
|
|
445
|
+
async runControl(action: 'pause' | 'resume' | 'stop'): Promise<import('../protocol.ts').RunState | null> {
|
|
446
|
+
const response = await fetch(NOVEL_API.runControl, {
|
|
447
|
+
method: 'POST',
|
|
448
|
+
headers: { 'Content-Type': 'application/json' },
|
|
449
|
+
body: JSON.stringify({ action }),
|
|
450
|
+
})
|
|
451
|
+
if (response.status === 400) return null
|
|
452
|
+
return readJson<import('../protocol.ts').RunState>(response)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** 生产单状态(无生产单返回 null)。 */
|
|
456
|
+
async runStatus(): Promise<import('../protocol.ts').RunState | null> {
|
|
457
|
+
const response = await fetch(NOVEL_API.runStatus)
|
|
458
|
+
if (response.status === 404) return null
|
|
459
|
+
return readJson<import('../protocol.ts').RunState | null>(response)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** Get project writing assets + built-in libraries. */
|
|
463
|
+
async assets(): Promise<AssetsResponse> {
|
|
464
|
+
const response = await fetch(withBookId(NOVEL_API.assets))
|
|
465
|
+
return readJson<AssetsResponse>(response)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** Patch project writing assets. */
|
|
469
|
+
async patchAssets(patch: AssetsPatch): Promise<AssetsResponse> {
|
|
470
|
+
return postJson<AssetsResponse>(NOVEL_API.assets, patch)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** Extract a style asset from sample text. */
|
|
474
|
+
async styleEngine(req: StyleEngineRequest): Promise<{ styleAsset: StyleAsset }> {
|
|
475
|
+
return postJson<{ styleAsset: StyleAsset }>(NOVEL_API.styleEngine, req)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Consume an NDJSON job stream (generate / rewrite / polish).
|
|
480
|
+
* @param path - the route to POST to.
|
|
481
|
+
* @param payload - the JSON body.
|
|
482
|
+
* @param onFrame - receives every frame as it lands.
|
|
483
|
+
*/
|
|
484
|
+
private async streamJob(path: string, payload: unknown, onFrame: (frame: JobFrame) => void): Promise<void> {
|
|
485
|
+
const response = await fetch(path, {
|
|
486
|
+
method: 'POST',
|
|
487
|
+
headers: { 'content-type': 'application/json' },
|
|
488
|
+
body: JSON.stringify(payload),
|
|
489
|
+
})
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
await readJson<{ error?: string }>(response)
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
if (response.body === null) throw new NovelApiError('job: no response body')
|
|
495
|
+
const reader = response.body.getReader()
|
|
496
|
+
const decoder = new TextDecoder()
|
|
497
|
+
let buffer = ''
|
|
498
|
+
for (;;) {
|
|
499
|
+
const { done, value } = await reader.read()
|
|
500
|
+
if (done) break
|
|
501
|
+
buffer += decoder.decode(value, { stream: true })
|
|
502
|
+
const lines = buffer.split('\n')
|
|
503
|
+
buffer = lines.pop() ?? ''
|
|
504
|
+
for (const line of lines) {
|
|
505
|
+
if (line.trim() === '') continue
|
|
506
|
+
let frame: JobFrame
|
|
507
|
+
try {
|
|
508
|
+
frame = JSON.parse(line) as JobFrame
|
|
509
|
+
} catch {
|
|
510
|
+
continue
|
|
511
|
+
}
|
|
512
|
+
onFrame(frame)
|
|
513
|
+
if (frame.type === 'error') {
|
|
514
|
+
throw new NovelApiError(frame.message)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** Generate one chapter. */
|
|
521
|
+
async generate(chapterNo: number, skipReview: boolean, onFrame: (frame: JobFrame) => void): Promise<void> {
|
|
522
|
+
await this.streamJob(NOVEL_API.generate, { chapterNo, skipReview }, onFrame)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Rewrite one chapter (whole-chapter, or local when `target` is given). */
|
|
526
|
+
async rewrite(chapterNo: number, instructions: string, target: string, onFrame: (frame: JobFrame) => void): Promise<void> {
|
|
527
|
+
await this.streamJob(NOVEL_API.rewrite, { chapterNo, instructions, target }, onFrame)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Polish (de-AI-ify) one chapter. */
|
|
531
|
+
async polish(chapterNo: number, onFrame: (frame: JobFrame) => void): Promise<void> {
|
|
532
|
+
await this.streamJob(NOVEL_API.polish, { chapterNo }, onFrame)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** 采纳待确认草稿(润色/重写产物),覆盖正文文件。返回采纳后的新正文(markdown)。
|
|
536
|
+
* 可携带审查报告(沿用结论定状态:通过 → approved)。 */
|
|
537
|
+
async draftApply(chapterNo: number, report?: import('../protocol.ts').ReviewReport): Promise<{ ok: boolean; chars: number; file: string; markdown: string }> {
|
|
538
|
+
return postJson<{ ok: boolean; chars: number; file: string; markdown: string }>(NOVEL_API.draftApply, { chapterNo, report })
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** 放弃待确认草稿,保留原稿。 */
|
|
542
|
+
async draftDiscard(chapterNo: number): Promise<{ ok: boolean }> {
|
|
543
|
+
return postJson<{ ok: boolean }>(NOVEL_API.draftDiscard, { chapterNo })
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Run one assistant turn (NDJSON stream). */
|
|
547
|
+
async assistant(message: string, onFrame: (frame: import('../protocol.ts').AssistantFrame) => void): Promise<void> {
|
|
548
|
+
const response = await fetch(NOVEL_API.assistant, {
|
|
549
|
+
method: 'POST',
|
|
550
|
+
headers: { 'content-type': 'application/json' },
|
|
551
|
+
// 与其它书级请求一致:显式带 bookId,避免切书后被宿主的全局 active 串书。
|
|
552
|
+
body: JSON.stringify(withBookIdBody({ message })),
|
|
553
|
+
})
|
|
554
|
+
if (!response.ok) {
|
|
555
|
+
await readJson<{ error?: string }>(response)
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
if (response.body === null) throw new NovelApiError('assistant: no response body')
|
|
559
|
+
const reader = response.body.getReader()
|
|
560
|
+
const decoder = new TextDecoder()
|
|
561
|
+
let buffer = ''
|
|
562
|
+
for (;;) {
|
|
563
|
+
const { done, value } = await reader.read()
|
|
564
|
+
if (done) break
|
|
565
|
+
buffer += decoder.decode(value, { stream: true })
|
|
566
|
+
const lines = buffer.split('\n')
|
|
567
|
+
buffer = lines.pop() ?? ''
|
|
568
|
+
for (const line of lines) {
|
|
569
|
+
if (line.trim() === '') continue
|
|
570
|
+
let frame: import('../protocol.ts').AssistantFrame
|
|
571
|
+
try {
|
|
572
|
+
frame = JSON.parse(line) as import('../protocol.ts').AssistantFrame
|
|
573
|
+
} catch {
|
|
574
|
+
continue
|
|
575
|
+
}
|
|
576
|
+
onFrame(frame)
|
|
577
|
+
if (frame.type === 'error') throw new NovelApiError(frame.message)
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** 张力曲线:数据 + 规则核对。 */
|
|
583
|
+
async tension(): Promise<import('../protocol.ts').TensionResponse> {
|
|
584
|
+
const response = await fetch(withBookId(NOVEL_API.tension))
|
|
585
|
+
return readJson<import('../protocol.ts').TensionResponse>(response)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** 设置张力曲线预设。 */
|
|
589
|
+
async tensionPreset(preset: import('../protocol.ts').TensionCurvePreset): Promise<void> {
|
|
590
|
+
await postJson(NOVEL_API.tension, { op: 'preset', preset })
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** 设置/清除某一章的目标张力。 */
|
|
594
|
+
async tensionSet(chapterNo: number, tension?: number): Promise<void> {
|
|
595
|
+
await postJson(NOVEL_API.tension, { op: 'set', chapterNo, tension })
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** 提示词槽位:列表。 */
|
|
599
|
+
async promptSlots(): Promise<import('../protocol.ts').PromptSlotsResponse> {
|
|
600
|
+
const response = await fetch(withBookId(NOVEL_API.promptSlots))
|
|
601
|
+
return readJson<import('../protocol.ts').PromptSlotsResponse>(response)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** 写入一个提示词槽位(空串 = 清空)。 */
|
|
605
|
+
async promptSlotSet(id: import('../protocol.ts').PromptSlotId, value: string): Promise<void> {
|
|
606
|
+
await postJson(NOVEL_API.promptSlots, { id, value })
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** 故事时间线:列表(含规则初筛问题)。 */
|
|
610
|
+
async timeline(): Promise<import('../protocol.ts').TimelineResponse> {
|
|
611
|
+
const response = await fetch(withBookId(NOVEL_API.timeline))
|
|
612
|
+
return readJson<import('../protocol.ts').TimelineResponse>(response)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** 抽取某一章的时间线事件。 */
|
|
616
|
+
async timelineExtract(chapterNo: number): Promise<{ events: import('../protocol.ts').TimelineEvent[] }> {
|
|
617
|
+
return postJson<{ events: import('../protocol.ts').TimelineEvent[] }>(NOVEL_API.timeline, { op: 'extract', chapterNo })
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** 检查时间线矛盾(规则初筛 + AI 复核)。 */
|
|
621
|
+
async timelineCheck(): Promise<import('../protocol.ts').TimelineResponse> {
|
|
622
|
+
return postJson<import('../protocol.ts').TimelineResponse>(NOVEL_API.timeline, { op: 'check' })
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** 手工修正一条时间线事件。 */
|
|
626
|
+
async timelineUpdate(event: import('../protocol.ts').TimelineEvent): Promise<void> {
|
|
627
|
+
await postJson(NOVEL_API.timeline, { op: 'update', event })
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** 删除一条时间线事件。 */
|
|
631
|
+
async timelineRemove(id: string): Promise<void> {
|
|
632
|
+
await postJson(NOVEL_API.timeline, { op: 'remove', id })
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** 章节历史版本列表。 */
|
|
636
|
+
async snapshots(): Promise<import('../protocol.ts').SnapshotResponse> {
|
|
637
|
+
const response = await fetch(withBookId(NOVEL_API.snapshot))
|
|
638
|
+
return readJson<import('../protocol.ts').SnapshotResponse>(response)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** 回滚到某个历史版本。 */
|
|
642
|
+
async snapshotRestore(id: string): Promise<void> {
|
|
643
|
+
await postJson(NOVEL_API.snapshot, { op: 'restore', id })
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** 给某章手工存档。 */
|
|
647
|
+
async snapshotCreate(chapterNo: number): Promise<void> {
|
|
648
|
+
await postJson(NOVEL_API.snapshot, { op: 'create', chapterNo })
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** 删除一个历史版本。 */
|
|
652
|
+
async snapshotRemove(id: string): Promise<void> {
|
|
653
|
+
await postJson(NOVEL_API.snapshot, { op: 'remove', id })
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Load the persisted assistant conversation. */
|
|
657
|
+
async assistantHistory(): Promise<import('../protocol.ts').AssistantMessage[]> {
|
|
658
|
+
const response = await fetch(withBookId(NOVEL_API.assistantHistory))
|
|
659
|
+
const body = await readJson<{ messages: import('../protocol.ts').AssistantMessage[] }>(response)
|
|
660
|
+
return body.messages
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** 清空助手对话记录。 */
|
|
664
|
+
async assistantClear(): Promise<{ ok: boolean }> {
|
|
665
|
+
return postJson<{ ok: boolean }>(NOVEL_API.assistantClear, {})
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** 作者资产库/总数据:读取跨书可复用资产。 */
|
|
669
|
+
async authorAssets(): Promise<import('../protocol.ts').AuthorAssetsResponse> {
|
|
670
|
+
const response = await fetch(NOVEL_API.authorAssets)
|
|
671
|
+
return readJson<import('../protocol.ts').AuthorAssetsResponse>(response)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** 作者资产库:新增/更新一条资产(upsert by id)。 */
|
|
675
|
+
async authorAssetsUpsert(asset: import('../protocol.ts').AuthorStyleAsset): Promise<import('../protocol.ts').AuthorAssetsResponse> {
|
|
676
|
+
return postJson<import('../protocol.ts').AuthorAssetsResponse>(NOVEL_API.authorAssetsUpsert, { asset })
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** 作者资产库:删除一条资产。 */
|
|
680
|
+
async authorAssetsRemove(id: string): Promise<import('../protocol.ts').AuthorAssetsResponse> {
|
|
681
|
+
return postJson<import('../protocol.ts').AuthorAssetsResponse>(NOVEL_API.authorAssetsRemove, { id })
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** 作者资产库:导入默认(书架书的写作资产/角色 + 内置全局库)批量沉淀。 */
|
|
685
|
+
async authorAssetsImportDefault(): Promise<import('../protocol.ts').AuthorAssetsResponse> {
|
|
686
|
+
return postJson<import('../protocol.ts').AuthorAssetsResponse>(NOVEL_API.authorAssetsImportDefault, {})
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** 改编模式 P0:上传全文 → 原文设定卡片 / 可改范围矩阵。 */
|
|
690
|
+
async adaptAnalyze(text: string, filePath?: string): Promise<import('../protocol.ts').AdaptAnalyzeResponse> {
|
|
691
|
+
return postJson<import('../protocol.ts').AdaptAnalyzeResponse>(NOVEL_API.adaptAnalyze, { text, filePath })
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** 改编模式 P1:确认要改的维度 → 生成映射表/改编规则/联动影响清单。 */
|
|
695
|
+
async adaptPropose(req: import('../protocol.ts').AdaptProposeRequest): Promise<import('../protocol.ts').AdaptProposeResponse> {
|
|
696
|
+
return postJson<import('../protocol.ts').AdaptProposeResponse>(NOVEL_API.adaptPropose, req)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** 改编模式 P2:执行术语替换(全局替换 + 命中统计 + 改编文本预览)。 */
|
|
700
|
+
async adaptExecute(req: import('../protocol.ts').AdaptExecuteRequest): Promise<import('../protocol.ts').AdaptExecuteResponse> {
|
|
701
|
+
return postJson<import('../protocol.ts').AdaptExecuteResponse>(NOVEL_API.adaptExecute, req)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/** 改编模式 P3:保存改编全文为新书(原书保留,登记书架)。 */
|
|
705
|
+
async adaptSave(req: import('../protocol.ts').AdaptSaveRequest): Promise<import('../protocol.ts').AdaptSaveResponse> {
|
|
706
|
+
return postJson<import('../protocol.ts').AdaptSaveResponse>(NOVEL_API.adaptSave, req)
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** 改编模式 P4:从源全文 + 编辑后方案提炼新书资料并保存为「待写新书」。 */
|
|
710
|
+
async adaptMaterialize(req: import('../protocol.ts').AdaptMaterializeRequest): Promise<import('../protocol.ts').AdaptMaterializeResponse> {
|
|
711
|
+
return postJson<import('../protocol.ts').AdaptMaterializeResponse>(NOVEL_API.adaptMaterialize, req)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** 改编模式:保存预览/微调后的新书资料为新书(原书保留,登记书架)。 */
|
|
715
|
+
async adaptMaterializeSave(req: AdaptMaterializeSaveRequest): Promise<AdaptMaterializeSaveResponse> {
|
|
716
|
+
return postJson<AdaptMaterializeSaveResponse>(NOVEL_API.adaptMaterializeSave, req)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** 改编模式:rewrite 逐章重写(NDJSON 流式进度,支持分段 startNo/endNo)。 */
|
|
720
|
+
async adaptRewriteStream(req: AdaptExecuteRequest, onFrame: (frame: AdaptRewriteFrame) => void): Promise<void> {
|
|
721
|
+
const response = await fetch(NOVEL_API.adaptRewriteStream, {
|
|
722
|
+
method: 'POST',
|
|
723
|
+
headers: { 'content-type': 'application/json' },
|
|
724
|
+
body: JSON.stringify(req),
|
|
725
|
+
})
|
|
726
|
+
if (!response.ok) {
|
|
727
|
+
await readJson<{ error?: string }>(response)
|
|
728
|
+
return
|
|
729
|
+
}
|
|
730
|
+
if (response.body === null) throw new NovelApiError('adapt rewrite: no response body')
|
|
731
|
+
const reader = response.body.getReader()
|
|
732
|
+
const decoder = new TextDecoder()
|
|
733
|
+
let buffer = ''
|
|
734
|
+
for (;;) {
|
|
735
|
+
const { done, value } = await reader.read()
|
|
736
|
+
if (done) break
|
|
737
|
+
buffer += decoder.decode(value, { stream: true })
|
|
738
|
+
const lines = buffer.split('\n')
|
|
739
|
+
buffer = lines.pop() ?? ''
|
|
740
|
+
for (const line of lines) {
|
|
741
|
+
if (line.trim() === '') continue
|
|
742
|
+
let frame: AdaptRewriteFrame
|
|
743
|
+
try {
|
|
744
|
+
frame = JSON.parse(line) as AdaptRewriteFrame
|
|
745
|
+
} catch {
|
|
746
|
+
continue
|
|
747
|
+
}
|
|
748
|
+
onFrame(frame)
|
|
749
|
+
if (frame.type === 'error') throw new NovelApiError(frame.message)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** 主题自定义背景:上传图片,服务端存盘并返回可访问 URL。 */
|
|
755
|
+
async themeBackgroundUpload(dataUrl: string): Promise<{ url: string }> {
|
|
756
|
+
return postJson<{ url: string }>(NOVEL_API.themeBackgroundUpload, { dataUrl })
|
|
757
|
+
}
|
|
758
|
+
}
|