@waterwx/dsh-novel-forge 1.1.8 → 1.2.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.
@@ -32,6 +32,6 @@ export declare function activateBook(id: string): BookEntry | undefined;
32
32
  export declare function removeBook(id: string): boolean;
33
33
  /** 当前书输出目录(无书架则 undefined,回退 settings)。 */
34
34
  export declare function activeBookOutputDir(): string | undefined;
35
- /** 默认输出目录推断:桌面/书名。 */
35
+ /** 默认输出目录推断:小说目录/书名。 */
36
36
  export declare function defaultOutputDirFor(bookName: string): string;
37
37
  export {};
@@ -116,6 +116,12 @@ export declare class NovelApi {
116
116
  bookActivate(id: string): Promise<import('../protocol.ts').BookshelfSnapshot>;
117
117
  /** 移除书架条目。 */
118
118
  bookRemove(id: string): Promise<import('../protocol.ts').BookshelfSnapshot>;
119
+ /** 生产单:启动批量生产(区间或新增 N 章;计划不足自动补)。 */
120
+ runStart(req: import('../protocol.ts').RunStartRequest): Promise<import('../protocol.ts').RunState>;
121
+ /** 生产单控制:pause / resume / stop。 */
122
+ runControl(action: 'pause' | 'resume' | 'stop'): Promise<import('../protocol.ts').RunState | null>;
123
+ /** 生产单状态(无生产单返回 null)。 */
124
+ runStatus(): Promise<import('../protocol.ts').RunState | null>;
119
125
  /** Get project writing assets + built-in libraries. */
120
126
  assets(): Promise<AssetsResponse>;
121
127
  /** Patch project writing assets. */
@@ -141,6 +141,14 @@ export declare const zh: {
141
141
  readonly 'settings.outputDir': "输出目录";
142
142
  readonly 'settings.provider': "模型提供商";
143
143
  readonly 'settings.model': "模型";
144
+ readonly 'settings.modelCustom': "自定义…";
145
+ readonly 'settings.modelCustomPlaceholder': "输入任意模型 id";
146
+ readonly 'settings.reasoningEffort': "思考强度 (reasoningEffort)";
147
+ readonly 'settings.reasoning.off': "off · 关闭思考(推荐日常写文)";
148
+ readonly 'settings.reasoning.low': "low · 低思考";
149
+ readonly 'settings.reasoning.high': "high · 高思考";
150
+ readonly 'settings.reasoning.max': "max · 最高思考(质检/推演)";
151
+ readonly 'settings.reasoningHint': "off 最快最省;high/max 更慢更贵但推理更强";
144
152
  readonly 'settings.theme': "主题";
145
153
  readonly 'settings.themeLiquid': "iOS 液态玻璃(绿)";
146
154
  readonly 'settings.themeClassic': "经典毛玻璃(蓝)";
@@ -1,7 +1,7 @@
1
1
  import type { NovelApi } from '../api.ts';
2
2
  import type { PanelController } from './controller.ts';
3
3
  /** The panel's tab identifiers. */
4
- export type NovelTab = 'workflow' | 'overview' | 'blurb' | 'plan' | 'bible' | 'world' | 'foreshadow' | 'assistant' | 'settings' | 'characters' | 'roles' | 'facts' | 'plotlines' | 'reviews' | 'progress' | 'breakdown' | 'storyboard' | 'assetsGenre' | 'assetsProgression' | 'assetsTemplates' | 'assetsRules' | 'assetsStyle';
4
+ export type NovelTab = 'workflow' | 'overview' | 'blurb' | 'plan' | 'bible' | 'world' | 'foreshadow' | 'assistant' | 'settings' | 'characters' | 'roles' | 'facts' | 'plotlines' | 'reviews' | 'progress' | 'breakdown' | 'storyboard' | 'assetsGenre' | 'assetsProgression' | 'assetsTemplates' | 'assetsRules' | 'assetsStyle' | 'run' | 'book' | 'assets';
5
5
  /** Panel shell props. */
6
6
  export interface NovelPanelProps {
7
7
  /** The panel state owner (open/close/toggle). */
@@ -0,0 +1,5 @@
1
+ import type { NovelApi } from '../api.ts';
2
+ export declare function RunPanel({ api, totalChapters }: {
3
+ api: NovelApi;
4
+ totalChapters: number;
5
+ }): import("react").JSX.Element;
@@ -5,6 +5,12 @@
5
5
  * tracking, project persistence, and whole-book export. Pure Node (no
6
6
  * web-server dependencies), so routes stay thin and logic is testable.
7
7
  */
8
+ /**
9
+ * 内容合规红线(平台硬性要求):所有书籍、所有章节无条件生效,
10
+ * 优先级高于单书大纲/圣经中的任何设定与作者自定义红线。
11
+ * 注入点:章节生成系统提示 + 审稿系统提示(命中即 high)。
12
+ */
13
+ export declare const COMPLIANCE_REDLINES: ReadonlyArray<string>;
8
14
  import type { Context } from '@deepseek-ai/cordis';
9
15
  import type { AuditIssue, AuthorReview, BreakdownResponse, ChapterPlan, Foreshadow, NovelConfig, OutlineCandidate, Plotline, PlotlineHealthReport, PlotlinePlan, ProjectState, ReviewReport, RoleRecord, RoleStatusCard, StoryBible, StoryboardPlanResponse, StoryboardResponse, Volume, WorldState } from './protocol.ts';
10
16
  /** Project state file name inside the output dir. */
@@ -168,7 +174,7 @@ export declare function markForeshadowPlanted(project: ProjectState, outputDir:
168
174
  */
169
175
  export declare function extractFacts(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number): Promise<number>;
170
176
  /** 全书一致性质检:LLM 分批扫描已生成章节 + 设定 + 事实库,聚合矛盾清单。 */
171
- export declare function auditBook(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string): Promise<AuditIssue[]>;
177
+ export declare function auditBook(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, onProgress?: (completedBatches: number, totalBatches: number) => void): Promise<AuditIssue[]>;
172
178
  /** 小说简介:AI 生成或按已写开头补全(面向读者的作品门面)。 */
173
179
  export declare function generateBlurb(ctx: Context, config: NovelConfig, project: ProjectState, partial?: string): Promise<string>;
174
180
  /**
@@ -33,6 +33,8 @@ export interface Config {
33
33
  provider?: string;
34
34
  /** LLM model id. */
35
35
  model?: string;
36
+ /** LLM reasoning effort (off/low/high/max). */
37
+ reasoningEffort?: 'off' | 'low' | 'high' | 'max';
36
38
  /** Target characters per chapter. */
37
39
  chapterChars?: number;
38
40
  /** Max output tokens per chapter call. */
@@ -48,7 +50,7 @@ export interface Config {
48
50
  }
49
51
  export declare const Config: z<Config>;
50
52
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
51
- export declare const NOVEL_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-novel-forge \u63D2\u4EF6\uFF08AI \u7F16\u8BD1\u5C0F\u8BF4\u5DE5\u4F5C\u53F0\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u5C0F\u8BF4\u5DE5\u574A\u300D\u5165\u53E3\u3002\u80FD\u529B\uFF1A\u8BFB\u53D6 docx \u5927\u7EB2\uFF08\u9ED8\u8BA4\u684C\u9762\u300A\u5F52\u589F\u7389\u4E3B\u300B\u5927\u7EB2\uFF09\u6216\u7C98\u8D34\u5927\u7EB2\u6587\u672C\uFF1B\u7528 LLM \u63D0\u70BC\u9053\u85CF\uFF08\u4EBA\u8BBE/\u4E16\u754C\u89C2/\u91D1\u624B\u6307\u89C4\u5219/\u5199\u4F5C\u7EA2\u7EBF\uFF0C\u5373\u8BBE\u5B9A\u5723\u7ECF\uFF09\uFF1B\u751F\u6210\u5377\u8BA1\u5212\u4E0E\u7AE0\u8282\u8BA1\u5212\uFF1B\u9010\u7AE0\u8C03\u7528 LLM \u751F\u6210 3000-4000 \u5B57\u6B63\u6587\u5E76\u4FDD\u5B58\u4E3A Markdown\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230 \u684C\u9762\\\u5F52\u589F\u7389\u4E3B\uFF09\uFF1B\u6BCF\u7AE0\u81EA\u52A8\u751F\u6210\u6458\u8981\uFF08\u53D9\u4E8B\u8BB0\u5FC6\uFF09\u3001\u81EA\u52A8 AI \u5BA1\u7A3F\uFF08\u4EBA\u8BBE/\u8BBE\u5B9A/\u7EA2\u7EBF/\u6587\u7B14/\u723D\u70B9/\u903B\u8F91\uFF09\uFF0C\u652F\u6301\u6309\u5BA1\u7A3F\u610F\u89C1\u91CD\u5199\u3001\u53BB AI \u5473\u6DA6\u8272\u3001\u6697\u7EBF\uFF08\u4F0F\u7B14\uFF09\u7BA1\u7406\u3001\u6279\u91CF\u8FDE\u5199\u4E0E\u5168\u672C\u5BFC\u51FA\uFF08txt/md\uFF09\u3002\u9650\u5236\uFF1A\u751F\u6210\u6D88\u8017 LLM API \u989D\u5EA6\uFF1B\u8F93\u51FA\u76EE\u5F55\u4E0E\u6A21\u578B\u53EF\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u4FEE\u6539\uFF1B\u7AE0\u8282\u6B63\u6587\u8D28\u91CF\u53D6\u51B3\u4E8E\u5927\u7EB2\u5B8C\u6574\u5EA6\u3002\u7528\u6237\u63D0\u5230\u300C\u5C0F\u8BF4 / \u5927\u7EB2 / \u5199\u5C0F\u8BF4 / \u7AE0\u8282 / \u5BA1\u7A3F / \u6DA6\u8272 / \u5F52\u589F\u7389\u4E3B\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
53
+ export declare const NOVEL_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-novel-forge \u63D2\u4EF6\uFF08AI \u7F16\u8BD1\u5C0F\u8BF4\u5DE5\u4F5C\u53F0\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u5C0F\u8BF4\u5DE5\u574A\u300D\u5165\u53E3\u3002\u80FD\u529B\uFF1A\u8BFB\u53D6 docx \u5927\u7EB2\uFF08\u9ED8\u8BA4 D:\\ryan work\\harness\\novels\\\u5F52\u589F\u7389\u4E3B \u5927\u7EB2\uFF09\u6216\u7C98\u8D34\u5927\u7EB2\u6587\u672C\uFF1B\u7528 LLM \u63D0\u70BC\u9053\u85CF\uFF08\u4EBA\u8BBE/\u4E16\u754C\u89C2/\u91D1\u624B\u6307\u89C4\u5219/\u5199\u4F5C\u7EA2\u7EBF\uFF0C\u5373\u8BBE\u5B9A\u5723\u7ECF\uFF09\uFF1B\u751F\u6210\u5377\u8BA1\u5212\u4E0E\u7AE0\u8282\u8BA1\u5212\uFF1B\u9010\u7AE0\u8C03\u7528 LLM \u751F\u6210 3000-4000 \u5B57\u6B63\u6587\u5E76\u4FDD\u5B58\u4E3A Markdown\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230 D:\\ryan work\\harness\\novels\\\u5F52\u589F\u7389\u4E3B\uFF09\uFF1B\u6BCF\u7AE0\u81EA\u52A8\u751F\u6210\u6458\u8981\uFF08\u53D9\u4E8B\u8BB0\u5FC6\uFF09\u3001\u81EA\u52A8 AI \u5BA1\u7A3F\uFF08\u4EBA\u8BBE/\u8BBE\u5B9A/\u7EA2\u7EBF/\u6587\u7B14/\u723D\u70B9/\u903B\u8F91\uFF09\uFF0C\u652F\u6301\u6309\u5BA1\u7A3F\u610F\u89C1\u91CD\u5199\u3001\u53BB AI \u5473\u6DA6\u8272\u3001\u6697\u7EBF\uFF08\u4F0F\u7B14\uFF09\u7BA1\u7406\u3001\u6279\u91CF\u8FDE\u5199\u4E0E\u5168\u672C\u5BFC\u51FA\uFF08txt/md\uFF09\u3002\u9650\u5236\uFF1A\u751F\u6210\u6D88\u8017 LLM API \u989D\u5EA6\uFF1B\u8F93\u51FA\u76EE\u5F55\u4E0E\u6A21\u578B\u53EF\u5728\u63D2\u4EF6\u8BBE\u7F6E\u4E2D\u4FEE\u6539\uFF1B\u7AE0\u8282\u6B63\u6587\u8D28\u91CF\u53D6\u51B3\u4E8E\u5927\u7EB2\u5B8C\u6574\u5EA6\u3002\u7528\u6237\u63D0\u5230\u300C\u5C0F\u8BF4 / \u5927\u7EB2 / \u5199\u5C0F\u8BF4 / \u7AE0\u8282 / \u5BA1\u7A3F / \u6DA6\u8272 / \u5F52\u589F\u7389\u4E3B\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
52
54
  /** Resolve a config-like value into the full runtime config. */
53
55
  export declare function resolveConfig(value: Partial<Config> | undefined): NovelConfig;
54
56
  /**
@@ -73,6 +73,12 @@ export declare const NOVEL_API: {
73
73
  readonly storyboard: "/api/dsh-novel-forge/storyboard";
74
74
  /** 漫剧分集计划:读一卷 → 按故事弧线分集(高潮拆集/过渡并章)。 */
75
75
  readonly storyboardPlan: "/api/dsh-novel-forge/storyboard/plan";
76
+ /** 生产单:启动批量生产(计划补足 + 逐章生成 + 被拒分级处理)。 */
77
+ readonly runStart: "/api/dsh-novel-forge/run/start";
78
+ /** 生产单控制:pause / resume / stop。 */
79
+ readonly runControl: "/api/dsh-novel-forge/run/control";
80
+ /** 生产单状态(含进度统计与日志)。 */
81
+ readonly runStatus: "/api/dsh-novel-forge/run/status";
76
82
  readonly config: "/api/dsh-novel-forge/config";
77
83
  readonly openFolder: "/api/dsh-novel-forge/open-folder";
78
84
  };
@@ -279,6 +285,26 @@ export interface AuditResponse {
279
285
  /** 质检时间。 */
280
286
  auditedAt: string;
281
287
  }
288
+ /** 全书质检的实时状态(通过 /status 暴露给面板/外部读取)。 */
289
+ export interface AuditStatus {
290
+ status: 'idle' | 'running' | 'done' | 'error';
291
+ /** 开始时间(ISO)。 */
292
+ startedAt?: string;
293
+ /** 结束时间(ISO)。 */
294
+ finishedAt?: string;
295
+ /** 总批次数(0 = 尚未开始/无章节)。 */
296
+ totalBatches: number;
297
+ /** 已完成批次数。 */
298
+ completedBatches: number;
299
+ /** 参与质检的章节数。 */
300
+ auditedChapters?: number;
301
+ /** 发现的问题数(done 后有效)。 */
302
+ issuesCount?: number;
303
+ /** 最近一次质检的问题清单(done 后有效;error 时为空)。 */
304
+ issues?: AuditIssue[];
305
+ /** 失败信息(error 时有效)。 */
306
+ error?: string;
307
+ }
282
308
  /** 一条剧情线(主线/支线/人物线/悬念线)。 */
283
309
  export interface Plotline {
284
310
  /** 稳定 id。 */
@@ -706,6 +732,8 @@ export interface NovelConfig {
706
732
  provider: string;
707
733
  /** LLM model id (e.g. deepseek-v4-flash). */
708
734
  model: string;
735
+ /** LLM reasoning effort: off = no thinking; low/high/max = thinking intensity. */
736
+ reasoningEffort: 'off' | 'low' | 'high' | 'max';
709
737
  /** Target characters per chapter. */
710
738
  chapterChars: number;
711
739
  /** Max output tokens per chapter call. */
@@ -726,6 +754,54 @@ export interface StatusResponse {
726
754
  project?: ProjectState;
727
755
  /** Chapter files already on disk (basenames, sorted). */
728
756
  generatedFiles: string[];
757
+ /** 全书质检实时状态(用于面板/外部读取进度)。 */
758
+ audit?: AuditStatus;
759
+ }
760
+ /** 生产单状态(生产单 = 区间批量生产执行器:计划补足 + 逐章生成 + 被拒分级处理)。 */
761
+ export interface RunState {
762
+ runId: string;
763
+ /** 区间起点(含)。 */
764
+ startNo: number;
765
+ /** 区间终点(含)。 */
766
+ endNo: number;
767
+ status: 'running' | 'paused' | 'done' | 'stopped' | 'error';
768
+ /** 当前处理到的章号(已 approved 的章会快进)。 */
769
+ currentNo: number;
770
+ stats: {
771
+ /** 新生成(含审稿)的章数。 */
772
+ generated: number;
773
+ /** 被拒后按意见修订并通过的章数。 */
774
+ revised: number;
775
+ /** 被拒后豁免通过(无 high)的章数。 */
776
+ exempted: number;
777
+ /** error 后重新生成的章数。 */
778
+ regenerated: number;
779
+ /** 生成失败的章数。 */
780
+ error: number;
781
+ };
782
+ /** 两轮修订仍不过、保留草稿待人工的章号。 */
783
+ pendingManual: number[];
784
+ /** 运行日志(保留最近 300 条)。 */
785
+ log: Array<{
786
+ at: string;
787
+ text: string;
788
+ }>;
789
+ startedAt: string;
790
+ updatedAt: string;
791
+ error?: string;
792
+ }
793
+ /** POST /run/start request. */
794
+ export interface RunStartRequest {
795
+ /** 起始章号(默认 1)。 */
796
+ startNo?: number;
797
+ /** 结束章号(含;超出计划时自动补计划)。 */
798
+ endNo?: number;
799
+ /** 或指定新增章数(从当前最后一章 +1 起)。 */
800
+ count?: number;
801
+ }
802
+ /** POST /run/control request. */
803
+ export interface RunControlRequest {
804
+ action: 'pause' | 'resume' | 'stop';
729
805
  }
730
806
  /** POST /load-outline request: either a docx path or raw text. */
731
807
  export interface LoadOutlineRequest {
@@ -887,6 +963,7 @@ export interface ConfigPatch {
887
963
  outputDir?: string;
888
964
  provider?: string;
889
965
  model?: string;
966
+ reasoningEffort?: 'off' | 'low' | 'high' | 'max';
890
967
  chapterChars?: number;
891
968
  maxTokens?: number;
892
969
  reviewPassScore?: number;
@@ -0,0 +1,32 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { NovelConfig, RunState } from './protocol.ts';
3
+ export declare class ProductionRunner {
4
+ private deps;
5
+ private state;
6
+ private working;
7
+ private pauseRequested;
8
+ private stopRequested;
9
+ /** 生产单绑定目录(start 时快照;所有读写固定用它,防止运行中切书导致写错目录)。 */
10
+ private bookDir;
11
+ constructor(deps: {
12
+ ctx: Context;
13
+ getConfig: () => NovelConfig;
14
+ });
15
+ /** 当前生产单状态(内存优先;web 重启后从磁盘恢复)。 */
16
+ status(): RunState | null;
17
+ private persist;
18
+ private log;
19
+ /** 启动/续跑生产单:startNo..endNo 区间,endNo 超出计划时先自动补计划。 */
20
+ start(startNo: number, endNo: number): Promise<RunState>;
21
+ pause(): void;
22
+ resume(): void;
23
+ stop(): void;
24
+ /** 主循环:从 currentNo 扫描到 endNo,逐章处理;支持暂停/停止。 */
25
+ private loop;
26
+ private processChapter;
27
+ /** 完整质量门:生成 → 摘要+事实 → 伏笔标记 → 审稿 → 作者复盘。 */
28
+ private produce;
29
+ /** 被拒分级处理:无 high 豁免;有 high 按意见修订(最多 2 轮)+ 验证模式;仍不过 → 待人工。 */
30
+ private handleRejected;
31
+ private applyDraft;
32
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@waterwx/dsh-novel-forge",
3
3
  "description": "AI 编译小说工作台 for the dsh web GUI: import a novel outline (docx/text), plan chapters with LLM, generate chapter by chapter (3000-4000 chars each), and save every chapter as Markdown into your chosen output folder.",
4
- "version": "1.1.8",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -86,6 +86,7 @@
86
86
  ],
87
87
  "license": "Apache-2.0",
88
88
  "scripts": {
89
+ "postinstall": "node scripts/patch-schemastery.mjs",
89
90
  "build": "tsc -p tsconfig.build.json && tsdown",
90
91
  "bundle": "tsdown",
91
92
  "watch": "tsdown --watch",
package/src/bookshelf.ts CHANGED
@@ -144,8 +144,8 @@ export function activeBookOutputDir(): string | undefined {
144
144
  return book?.outputDir
145
145
  }
146
146
 
147
- /** 默认输出目录推断:桌面/书名。 */
147
+ /** 默认输出目录推断:小说目录/书名。 */
148
148
  export function defaultOutputDirFor(bookName: string): string {
149
149
  const clean = bookName.replace(/[\\/:*?"<>|]/g, '').trim().slice(0, 40) || '未命名小说'
150
- return join(homedir(), 'Desktop', clean)
150
+ return join('D:\\ryan work\\harness\\novels', clean)
151
151
  }
package/src/client/api.ts CHANGED
@@ -254,6 +254,29 @@ export class NovelApi {
254
254
  return postJson<import('../protocol.ts').BookshelfSnapshot>('/api/dsh-novel-forge/bookshelf/remove', { id })
255
255
  }
256
256
 
257
+ /** 生产单:启动批量生产(区间或新增 N 章;计划不足自动补)。 */
258
+ async runStart(req: import('../protocol.ts').RunStartRequest): Promise<import('../protocol.ts').RunState> {
259
+ return postJson<import('../protocol.ts').RunState>(NOVEL_API.runStart, req)
260
+ }
261
+
262
+ /** 生产单控制:pause / resume / stop。 */
263
+ async runControl(action: 'pause' | 'resume' | 'stop'): Promise<import('../protocol.ts').RunState | null> {
264
+ const response = await fetch(NOVEL_API.runControl, {
265
+ method: 'POST',
266
+ headers: { 'Content-Type': 'application/json' },
267
+ body: JSON.stringify({ action }),
268
+ })
269
+ if (response.status === 400) return null
270
+ return readJson<import('../protocol.ts').RunState>(response)
271
+ }
272
+
273
+ /** 生产单状态(无生产单返回 null)。 */
274
+ async runStatus(): Promise<import('../protocol.ts').RunState | null> {
275
+ const response = await fetch(NOVEL_API.runStatus)
276
+ if (response.status === 404) return null
277
+ return readJson<import('../protocol.ts').RunState | null>(response)
278
+ }
279
+
257
280
  /** Get project writing assets + built-in libraries. */
258
281
  async assets(): Promise<AssetsResponse> {
259
282
  const response = await fetch(NOVEL_API.assets)
@@ -142,6 +142,14 @@ export const zh = {
142
142
  'settings.outputDir': '输出目录',
143
143
  'settings.provider': '模型提供商',
144
144
  'settings.model': '模型',
145
+ 'settings.modelCustom': '自定义…',
146
+ 'settings.modelCustomPlaceholder': '输入任意模型 id',
147
+ 'settings.reasoningEffort': '思考强度 (reasoningEffort)',
148
+ 'settings.reasoning.off': 'off · 关闭思考(推荐日常写文)',
149
+ 'settings.reasoning.low': 'low · 低思考',
150
+ 'settings.reasoning.high': 'high · 高思考',
151
+ 'settings.reasoning.max': 'max · 最高思考(质检/推演)',
152
+ 'settings.reasoningHint': 'off 最快最省;high/max 更慢更贵但推理更强',
145
153
  'settings.theme': '主题',
146
154
  'settings.themeLiquid': 'iOS 液态玻璃(绿)',
147
155
  'settings.themeClassic': '经典毛玻璃(蓝)',
@@ -285,6 +293,14 @@ export const en: Record<string, string> = {
285
293
  'settings.outputDir': 'Output directory',
286
294
  'settings.provider': 'Provider',
287
295
  'settings.model': 'Model',
296
+ 'settings.modelCustom': 'Custom…',
297
+ 'settings.modelCustomPlaceholder': 'Type any model id',
298
+ 'settings.reasoningEffort': 'Reasoning effort',
299
+ 'settings.reasoning.off': 'off · no thinking (recommended)',
300
+ 'settings.reasoning.low': 'low',
301
+ 'settings.reasoning.high': 'high',
302
+ 'settings.reasoning.max': 'max (audit/reasoning)',
303
+ 'settings.reasoningHint': 'off is fastest/cheapest; high/max are slower and stronger',
288
304
  'settings.theme': 'Theme',
289
305
  'settings.themeLiquid': 'iOS Liquid Glass (green)',
290
306
  'settings.themeClassic': 'Classic frosted (blue)',
@@ -206,6 +206,10 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
206
206
 
207
207
  return (
208
208
  <div style={{ display: 'flex', flexDirection: 'column', gap: 12, flex: 1, minHeight: 0 }}>
209
+ <div className={css.row} style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}>
210
+ <span className={css.cardTitle} style={{ fontSize: 17, fontWeight: 700 }}>🧰 创作资产</span>
211
+ <span className={css.meta}>题材基底 / 推进模式 / 笔法帖 / 文戒 / 心法</span>
212
+ </div>
209
213
  {error !== '' && <div className={css.card} style={{ borderColor: 'var(--nf-error)' }}><span style={{ color: 'var(--nf-error)' }}>{tt('common.error')}: {error}</span></div>}
210
214
  {notice !== '' && <div className={css.card}><span style={{ color: 'var(--nf-success)' }}>{notice}</span></div>}
211
215
 
@@ -235,6 +239,21 @@ export function AssetsTab({ api, initialTab = 'genre' }: AssetsTabProps) {
235
239
  </div>
236
240
  </div>
237
241
 
242
+ {/* 子页签:题材基底 / 推进模式 / 笔法帖 / 文戒 / 心法 */}
243
+ <div className={css.row} style={{ flexWrap: 'wrap', gap: 6 }}>
244
+ {SUB_TABS.map(t => (
245
+ <button
246
+ key={t.id}
247
+ type="button"
248
+ className={`${css.button} ${assetTab === t.id ? css.buttonPrimary : ''}`}
249
+ style={{ fontSize: 14, flex: 1 }}
250
+ onClick={() => { setAssetTab(t.id) }}
251
+ >
252
+ {t.label}
253
+ </button>
254
+ ))}
255
+ </div>
256
+
238
257
  {/* 题材基底库 */}
239
258
  {assetTab === 'genre' && (
240
259
  <div className={css.card}>
@@ -86,8 +86,8 @@ export function CreateBookView({
86
86
 
87
87
  /** 生成/换批:只补未暂留的空槽;exclude 传已暂留方案的卖点方向。 */
88
88
  const handleSuggest = async (): Promise<void> => {
89
- if (idea.trim().length < 50) {
90
- setError('想法太短(<50 字):多写一两句——主角是谁、什么世界、想要什么爽点')
89
+ if (idea.trim().length < 10) {
90
+ setError('想法太短(<10 字):至少写一句完整想法,如「男主穿越修仙界靠做菜无敌」')
91
91
  return
92
92
  }
93
93
  setSuggesting(true)
@@ -253,7 +253,7 @@ export function CreateBookView({
253
253
  <button
254
254
  type="button"
255
255
  className={`${css.button} ${css.buttonSmall} ${css.buttonPrimary}`}
256
- disabled={suggesting || idea.trim().length < 50}
256
+ disabled={suggesting || idea.trim().length < 10}
257
257
  onClick={() => { void handleSuggest() }}
258
258
  title="生成 3 个方向不同的大纲方案供选择(约消耗 6-8k token)"
259
259
  >