@waterwx/dsh-novel-forge 1.1.2 → 1.1.4

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/src/client/api.ts CHANGED
@@ -162,6 +162,26 @@ export class NovelApi {
162
162
  return postJson<{ bible: import('../protocol.ts').StoryBible }>(NOVEL_API.biblePatch, patch)
163
163
  }
164
164
 
165
+ /** 剧情线管理:增删改 + 关联章节。 */
166
+ async plotlines(req: import('../protocol.ts').PlotlinesRequest): Promise<import('../protocol.ts').PlotlinesResponse> {
167
+ return postJson<import('../protocol.ts').PlotlinesResponse>(NOVEL_API.plotlines, req)
168
+ }
169
+
170
+ /** 敏感词检查:指定章节 / 任意文本 / 全书。 */
171
+ async sensitiveCheck(req: import('../protocol.ts').SensitiveCheckRequest): Promise<import('../protocol.ts').SensitiveCheckResponse> {
172
+ return postJson<import('../protocol.ts').SensitiveCheckResponse>(NOVEL_API.sensitiveCheck, req)
173
+ }
174
+
175
+ /** 作者复盘补跑:单章(JSON)。 */
176
+ async reviewBackfillChapter(no: number): Promise<{ no: number; review: import('../protocol.ts').AuthorReview }> {
177
+ return postJson<{ no: number; review: import('../protocol.ts').AuthorReview }>(NOVEL_API.reviewBackfill, { chapterNo: no })
178
+ }
179
+
180
+ /** 作者复盘补跑:全书缺失章节(NDJSON 流)。 */
181
+ async reviewBackfillAll(onFrame: (frame: JobFrame) => void): Promise<void> {
182
+ await this.streamJob(NOVEL_API.reviewBackfill, {}, onFrame)
183
+ }
184
+
165
185
  /** 小说简介:AI 生成/补全(partial 留空 = 全量),或手动保存。 */
166
186
  async blurb(action: 'generate' | 'save', text?: string, partial?: string): Promise<{ blurb: string }> {
167
187
  return postJson<{ blurb: string }>(NOVEL_API.blurb, { action, text, partial })
@@ -14,14 +14,14 @@ export const zh = {
14
14
  'common.success': '成功',
15
15
  'common.generating': '生成中…',
16
16
  'common.chars': '字',
17
- 'tab.workflow': '工作流',
17
+ 'tab.workflow': '工作台',
18
18
  'tab.overview': '总纲',
19
19
  'tab.plan': '章节',
20
20
  'tab.bible': '道藏',
21
21
  'tab.foreshadow': '暗线',
22
22
  'tab.assistant': 'AI 助手',
23
23
  'tab.settings': '设置',
24
- 'workflow.title': '创作工作流',
24
+ 'workflow.title': '创作工作台',
25
25
  'workflow.step1': '① 加载总纲',
26
26
  'workflow.step2': '② 提炼道藏',
27
27
  'workflow.step3': '③ 规划卷',
@@ -100,17 +100,61 @@ export const zh = {
100
100
  'foreshadow.plantedAt': '埋设于',
101
101
  'foreshadow.setPlanted': '标记已埋设',
102
102
  'foreshadow.setResolved': '标记已回收',
103
+ 'tab.facts': '编年录',
104
+ 'tab.plotlines': '剧情线',
105
+ 'plotlines.hint': '主线/支线/人物线/悬念线的推进管理:章节生成时会注入未完结的线,要求每章至少推进一条;关联章节记录推进节点。',
106
+ 'plotlines.empty': '还没有剧情线——长篇小说建议先建主线与几条支线。',
107
+ 'plotlines.new': '+ 新建剧情线',
108
+ 'plotlines.name': '线名',
109
+ 'plotlines.kind': '类型',
110
+ 'plotlines.kindMain': '主线',
111
+ 'plotlines.kindBranch': '支线',
112
+ 'plotlines.kindCharacter': '人物线',
113
+ 'plotlines.kindMystery': '悬念线',
114
+ 'plotlines.goal': '目标/终点',
115
+ 'plotlines.progress': '当前进度',
116
+ 'plotlines.status': '状态',
117
+ 'plotlines.statusActive': '推进中',
118
+ 'plotlines.statusPaused': '暂停',
119
+ 'plotlines.statusResolved': '已完结',
120
+ 'plotlines.statusAbandoned': '已废弃',
121
+ 'plotlines.save': '保存',
122
+ 'plotlines.cancel': '取消',
123
+ 'plotlines.edit': '编辑',
124
+ 'plotlines.remove': '删除',
125
+ 'plotlines.linkChapter': '关联本章',
126
+ 'plotlines.chapters': '已关联',
127
+ 'plotlines.workflowTitle': '剧情线进度',
128
+ 'plotlines.workflowEmpty': '未建剧情线(去「剧情线」页创建)',
129
+ 'sensitive.title': '敏感词检查',
130
+ 'sensitive.scanAll': '🔞 全书敏感词检查',
131
+ 'sensitive.hint': '内置违禁词库硬匹配(政治/擦边/暴力/辱骂/广告/其他),发布前建议检查;只做提示,是否修改由你判断。',
132
+ 'sensitive.clean': '✅ 未命中任何违禁词(扫描 {n} 章)',
133
+ 'sensitive.hits': '命中 {n} 处({chapters} 章受影响)',
134
+ 'sensitive.goFix': '去修订',
135
+ 'sensitive.fixPrefill': '请删除或替换正文中的违禁表达「{word}」(类别:{category}),用含蓄/间接的写法替代。',
136
+ 'facts.title': '编年录 / 时间线({n} 条)',
137
+ 'facts.hint': '每章生成后自动抽取「已确立事实」(人物状态/境界资源/关系变化/伏笔落地),最近 20 条注入后续章节生成,保证长期一致。',
138
+ 'facts.backfill': '回填历史章节事实',
139
+ 'facts.backfilled': '已从历史章节回填 {n} 条事实',
103
140
  'settings.title': '设置',
104
141
  'settings.outlinePath': '默认大纲路径',
105
142
  'settings.outputDir': '输出目录',
106
143
  'settings.provider': '模型提供商',
107
144
  'settings.model': '模型',
145
+ 'settings.theme': '主题',
146
+ 'settings.themeLiquid': 'iOS 液态玻璃(绿)',
147
+ 'settings.themeClassic': '经典毛玻璃(蓝)',
148
+ 'settings.themeNeumorph': '新拟物(双阴影)',
149
+ 'settings.themeHint': '存于浏览器本地,即时生效',
108
150
  'settings.chapterChars': '每章目标字数',
109
151
  'settings.maxTokens': '单章最大输出 tokens',
110
152
  'settings.reviewPassScore': '审稿通过分数(0-100)',
111
153
  'settings.editorFontSize': '编辑器字号(仅编辑页显示)',
112
154
  'settings.editorFontSizeHint': '存于浏览器本地,不影响其他设备',
113
155
  'settings.autoReview': '生成后自动审稿',
156
+ 'settings.autoAuthorReview': '生成后自动作者复盘',
157
+ 'settings.autoAuthorReviewHint': '复盘钩子兑现/结尾钩子/剧情线推进/连续性/节奏趋势,每章约 2000 token',
114
158
  'settings.save': '保存设置',
115
159
  'settings.saved': '设置已保存',
116
160
  'settings.openFolder': '打开输出文件夹',
@@ -149,13 +193,13 @@ export const en: Record<string, string> = {
149
193
  'common.success': 'Success',
150
194
  'common.generating': 'Generating…',
151
195
  'common.chars': ' chars',
152
- 'tab.workflow': 'Workflow',
196
+ 'tab.workflow': 'Workbench',
153
197
  'tab.overview': 'Outline',
154
198
  'tab.plan': 'Chapters',
155
199
  'tab.bible': 'Bible',
156
200
  'tab.foreshadow': 'Foreshadow',
157
201
  'tab.settings': 'Settings',
158
- 'workflow.title': 'Writing workflow',
202
+ 'workflow.title': 'Writing workbench',
159
203
  'workflow.step1': '① Load outline',
160
204
  'workflow.step2': '② Extract story bible',
161
205
  'workflow.step3': '③ Plan volumes',
@@ -239,12 +283,19 @@ export const en: Record<string, string> = {
239
283
  'settings.outputDir': 'Output directory',
240
284
  'settings.provider': 'Provider',
241
285
  'settings.model': 'Model',
286
+ 'settings.theme': 'Theme',
287
+ 'settings.themeLiquid': 'iOS Liquid Glass (green)',
288
+ 'settings.themeClassic': 'Classic frosted (blue)',
289
+ 'settings.themeNeumorph': 'Neumorphism (dual-shadow)',
290
+ 'settings.themeHint': 'Stored in browser localStorage; applies instantly',
242
291
  'settings.chapterChars': 'Chars per chapter',
243
292
  'settings.maxTokens': 'Max output tokens',
244
293
  'settings.reviewPassScore': 'Review pass score (0-100)',
245
294
  'settings.editorFontSize': 'Editor font size (workspace only)',
246
295
  'settings.editorFontSizeHint': 'Stored in browser localStorage; display-only',
247
296
  'settings.autoReview': 'Auto-review after writing',
297
+ 'settings.autoAuthorReview': 'Auto author review after writing',
298
+ 'settings.autoAuthorReviewHint': 'Reviews hook payoff / ending hook / plotline progress / continuity / pacing trend (~2000 tokens per chapter)',
248
299
  'settings.save': 'Save settings',
249
300
  'settings.saved': 'Settings saved',
250
301
  'settings.openFolder': 'Open output folder',
@@ -252,6 +303,43 @@ export const en: Record<string, string> = {
252
303
  'settings.exportTxt': 'Export TXT',
253
304
  'settings.exportMd': 'Export Markdown',
254
305
  'settings.exported': 'Exported: {file} ({chars} chars, {chapters} chapters)',
306
+ 'tab.facts': 'Chronicle',
307
+ 'tab.plotlines': 'Plotlines',
308
+ 'plotlines.hint': 'Track main/branch/character/mystery arcs: unfinished arcs are injected into chapter writing so each chapter advances at least one; linked chapters record progress nodes.',
309
+ 'plotlines.empty': 'No plotlines yet — consider creating the main arc and a few branches.',
310
+ 'plotlines.new': '+ New plotline',
311
+ 'plotlines.name': 'Name',
312
+ 'plotlines.kind': 'Type',
313
+ 'plotlines.kindMain': 'Main',
314
+ 'plotlines.kindBranch': 'Branch',
315
+ 'plotlines.kindCharacter': 'Character',
316
+ 'plotlines.kindMystery': 'Mystery',
317
+ 'plotlines.goal': 'Goal',
318
+ 'plotlines.progress': 'Current progress',
319
+ 'plotlines.status': 'Status',
320
+ 'plotlines.statusActive': 'Active',
321
+ 'plotlines.statusPaused': 'Paused',
322
+ 'plotlines.statusResolved': 'Resolved',
323
+ 'plotlines.statusAbandoned': 'Abandoned',
324
+ 'plotlines.save': 'Save',
325
+ 'plotlines.cancel': 'Cancel',
326
+ 'plotlines.edit': 'Edit',
327
+ 'plotlines.remove': 'Remove',
328
+ 'plotlines.linkChapter': 'Link chapter',
329
+ 'plotlines.chapters': 'Linked',
330
+ 'plotlines.workflowTitle': 'Plotline progress',
331
+ 'plotlines.workflowEmpty': 'No plotlines yet (create them in the Plotlines tab)',
332
+ 'sensitive.title': 'Sensitive-word check',
333
+ 'sensitive.scanAll': '🔞 Scan all chapters',
334
+ 'sensitive.hint': 'Built-in banned-word library (politics/erotica/violence/abuse/ads/other). Run before publishing; suggestions only.',
335
+ 'sensitive.clean': '✅ No banned words found ({n} chapters scanned)',
336
+ 'sensitive.hits': '{n} hits across {chapters} chapters',
337
+ 'sensitive.goFix': 'Fix',
338
+ 'sensitive.fixPrefill': 'Remove or rephrase the banned expression "{word}" (category: {category}) with an indirect alternative.',
339
+ 'facts.title': 'Chronicle / timeline ({n} facts)',
340
+ 'facts.hint': 'Auto-extracted established facts per chapter (state/resources/relations/foreshadow landings); the latest 20 are injected into later chapters for consistency.',
341
+ 'facts.backfill': 'Backfill facts from historical chapters',
342
+ 'facts.backfilled': 'Backfilled {n} facts from historical chapters',
255
343
  'progress.generating': 'Writing chapter {no} “{title}”…',
256
344
  'progress.done': 'Chapter {no} done ({chars} chars) → {file}',
257
345
  'progress.reviewed': 'Chapter {no} review: {score} — {verdict}',
@@ -205,7 +205,7 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
205
205
  const genreLibrary = data.genreLibrary
206
206
 
207
207
  return (
208
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
208
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12, flex: 1, minHeight: 0 }}>
209
209
  {error !== '' && <div className={css.card} style={{ borderColor: 'var(--nf-error)' }}><span style={{ color: 'var(--nf-error)' }}>{tt('common.error')}: {error}</span></div>}
210
210
  {notice !== '' && <div className={css.card}><span style={{ color: 'var(--nf-success)' }}>{notice}</span></div>}
211
211
 
@@ -254,7 +254,7 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
254
254
 
255
255
  {/* 推进模式库 */}
256
256
  {assetTab === 'progression' && (
257
- <div className={css.card}>
257
+ <div className={css.card} style={{ flex: 1, minHeight: 0 }}>
258
258
  <span className={css.cardTitle}>推进模式库</span>
259
259
  <span className={css.meta}>读者为什么继续看下一章?主模式 + 辅助模式注入卷规划与章节生成。</span>
260
260
  {assets.primaryProgression !== undefined && (
@@ -268,7 +268,7 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
268
268
  </div>
269
269
  ))}
270
270
  <span className={css.meta}>从内置推进模式库选择添加(第一个设为主推进):</span>
271
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 260, overflowY: 'auto' }}>
271
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: 1, minHeight: 0, overflowY: 'auto' }}>
272
272
  {data.progressionLibrary.map(mode => {
273
273
  const alreadyPrimary = assets.primaryProgression?.name === mode.name
274
274
  const alreadyAux = assets.auxiliaryProgressions.some(m => m.name === mode.name)
@@ -285,10 +285,10 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
285
285
 
286
286
  {/* 笔法帖(一键绑定) */}
287
287
  {assetTab === 'templates' && (
288
- <div className={css.card}>
288
+ <div className={css.card} style={{ flex: 1, minHeight: 0 }}>
289
289
  <span className={css.cardTitle}>笔法帖</span>
290
290
  <span className={css.meta}>从内置 8 套叙事风格模板中一键选用(来自 AI-Novel-Writing-Assistant 写法引擎),无需样本文本;绑定后生成与润色都遵循该风格。</span>
291
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, maxHeight: 420, overflowY: 'auto' }}>
291
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1, minHeight: 0, overflowY: 'auto' }}>
292
292
  {data.styleTemplates.map(template => {
293
293
  const bound = assets.styleAssets.some(s => s.name === template.name)
294
294
  return (
@@ -325,10 +325,10 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
325
325
 
326
326
  {/* 文戒 */}
327
327
  {assetTab === 'rules' && (
328
- <div className={css.card}>
328
+ <div className={css.card} style={{ flex: 1, minHeight: 0 }}>
329
329
  <span className={css.cardTitle}>文戒</span>
330
330
  <span className={css.meta}>写作时必须遵守的表达边界(内置全局 + 项目自定义),生成与审稿都会检查。内置规则可用「覆盖编辑」复制为自定义版本调整。</span>
331
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 280, overflowY: 'auto' }}>
331
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: 1, minHeight: 0, overflowY: 'auto' }}>
332
332
  {builtinRules.map(rule => {
333
333
  const overridden = customRules.some(r => r.name === rule.name)
334
334
  return (