@waterwx/dsh-novel-forge 1.3.2 → 1.4.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.
@@ -26,12 +26,17 @@ export declare function renameBook(id: string, bookName: string): boolean;
26
26
  * @returns 是否发生了播种。
27
27
  */
28
28
  export declare function seedBookshelfFromOutputDir(outputDir: string): boolean;
29
+ /** 导入已有项目目录到书架:校验 novel-project.json,已存在则直接激活。 */
30
+ export declare function importDir(outputDir: string): {
31
+ book: BookEntry;
32
+ existed: boolean;
33
+ };
29
34
  /** 激活一本书。 */
30
35
  export declare function activateBook(id: string): BookEntry | undefined;
31
36
  /** 移除一本书。 */
32
37
  export declare function removeBook(id: string): boolean;
33
38
  /** 当前书输出目录(无书架则 undefined,回退 settings)。 */
34
39
  export declare function activeBookOutputDir(): string | undefined;
35
- /** 默认输出目录推断:小说目录/书名。 */
40
+ /** 默认输出目录推断:~/.dsh/novels/书名。 */
36
41
  export declare function defaultOutputDirFor(bookName: string): string;
37
42
  export {};
@@ -116,6 +116,10 @@ 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
+ /** 导入已有项目目录(Mode A):校验 novel-project.json,登记/激活书架。 */
120
+ bookImportDir(outputDir: string): Promise<import('../protocol.ts').BookImportDirResponse>;
121
+ /** 导入 txt/md 全本(Mode B):拆章建项目并登记书架。 */
122
+ bookImportText(filePath: string, outputDir?: string): Promise<import('../protocol.ts').BookImportTextResponse>;
119
123
  /** 生产单:启动批量生产(区间或新增 N 章;计划不足自动补)。 */
120
124
  runStart(req: import('../protocol.ts').RunStartRequest): Promise<import('../protocol.ts').RunState>;
121
125
  /** 生产单控制:pause / resume / stop。 */
@@ -0,0 +1,7 @@
1
+ import type { NovelApi } from '../api.ts';
2
+ export declare function ImportModal({ api, onClose, onImported, }: {
3
+ api: NovelApi;
4
+ onClose: () => void;
5
+ /** 导入成功并已激活该书后回调(刷新书架)。 */
6
+ onImported: () => void | Promise<void>;
7
+ }): import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import type { NovelApi } from '../api.ts';
2
2
  import type { BookshelfSnapshot } from '../../protocol.ts';
3
3
  /** 书架首页。 */
4
- export declare function ShelfView({ api, shelf, onOpenBook, onReadBook, onAddBook, }: {
4
+ export declare function ShelfView({ api, shelf, onOpenBook, onReadBook, onAddBook, onImportBook, }: {
5
5
  api: NovelApi;
6
6
  shelf: BookshelfSnapshot;
7
7
  /** 点击书卡:激活该书并进入工作台。 */
@@ -10,4 +10,6 @@ export declare function ShelfView({ api, shelf, onOpenBook, onReadBook, onAddBoo
10
10
  onReadBook: (id: string) => void;
11
11
  /** 点击+:进入开书向导页。 */
12
12
  onAddBook: () => void;
13
+ /** 点击「导入」:打开导入弹窗(已有项目目录 / txt/md 全本)。 */
14
+ onImportBook: () => void;
13
15
  }): import("react").JSX.Element;
@@ -71,6 +71,12 @@ export declare function refreshPlotlineProgress(ctx: Context, config: NovelConfi
71
71
  export declare function extractRoles(ctx: Context, config: NovelConfig, project: ProjectState): Promise<RoleRecord[]>;
72
72
  /** ✨ AI 从全书提炼场景库:正文/编年录 → 高频重要场景的结构化视觉锚点。 */
73
73
  export declare function extractScenes(ctx: Context, config: NovelConfig, project: ProjectState): Promise<SceneCard[]>;
74
+ /** 从 txt/md 全本拆分章节并建立项目:正文落盘为章节文件,status=written(待审稿)。 */
75
+ export declare function importBookText(filePath: string, outputDir: string): {
76
+ bookName: string;
77
+ chapters: number;
78
+ skipped: string[];
79
+ };
74
80
  /** 动漫形象描述词(中文描述 + 英文 booru 标签 + 关键外貌标签)。 */
75
81
  export interface RoleVisualPrompt {
76
82
  zh: string;
@@ -56,7 +56,7 @@ export interface Config {
56
56
  }
57
57
  export declare const Config: z<Config>;
58
58
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
59
- 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";
59
+ 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\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\u7528\u6237\u4E3B\u76EE\u5F55 ~/.dsh/novels\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\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
60
60
  /** Resolve a config-like value into the full runtime config. */
61
61
  export declare function resolveConfig(value: Partial<Config> | undefined): NovelConfig;
62
62
  /**
@@ -35,6 +35,10 @@ export declare const NOVEL_API: {
35
35
  /** 清空助手对话记录。 */
36
36
  readonly assistantClear: "/api/dsh-novel-forge/assistant/clear";
37
37
  readonly bookshelf: "/api/dsh-novel-forge/bookshelf";
38
+ /** 导入已有项目目录(含 novel-project.json)到书架。 */
39
+ readonly bookshelfImportDir: "/api/dsh-novel-forge/bookshelf/import-dir";
40
+ /** 导入 txt/md 全本:拆章建项目并登记书架。 */
41
+ readonly bookshelfImportText: "/api/dsh-novel-forge/bookshelf/import-text";
38
42
  /** 重置项目(可选携带新大纲):清空设定/卷/章节/伏笔/资产/事实库。 */
39
43
  readonly reset: "/api/dsh-novel-forge/reset";
40
44
  /** 全书一致性质检:LLM 扫描已生成章节,输出矛盾问题清单。 */
@@ -128,6 +132,34 @@ export interface BookActivateRequest {
128
132
  export interface BookRemoveRequest {
129
133
  id: string;
130
134
  }
135
+ /** POST /bookshelf/import-dir 请求:导入已有项目目录。 */
136
+ export interface BookImportDirRequest {
137
+ /** 项目目录(须含 novel-project.json)。 */
138
+ outputDir: string;
139
+ }
140
+ /** POST /bookshelf/import-dir 响应。 */
141
+ export interface BookImportDirResponse {
142
+ book: BookEntry;
143
+ /** true = 目录已在书架中(本次为重新激活)。 */
144
+ existed: boolean;
145
+ }
146
+ /** POST /bookshelf/import-text 请求:导入 txt/md 全本。 */
147
+ export interface BookImportTextRequest {
148
+ /** 源文件绝对路径(txt 或 md)。 */
149
+ filePath: string;
150
+ /** 输出目录;缺省为 ~/.dsh/novels/书名。 */
151
+ outputDir?: string;
152
+ }
153
+ /** POST /bookshelf/import-text 响应。 */
154
+ export interface BookImportTextResponse {
155
+ bookName: string;
156
+ /** 成功拆出的章节数。 */
157
+ chapters: number;
158
+ /** 因内容过短被跳过的章节标题列表。 */
159
+ skipped: string[];
160
+ /** 登记后的书架条目。 */
161
+ book: BookEntry;
162
+ }
131
163
  /** Chapter lifecycle states (the writing pipeline's state machine). */
132
164
  export type ChapterStatus = 'pending' | 'generating' | 'written' | 'reviewing' | 'approved' | 'rejected' | 'error';
133
165
  /** One chapter in the plan. */
package/package.json CHANGED
@@ -1,97 +1,97 @@
1
- {
2
- "name": "@waterwx/dsh-novel-forge",
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.3.2",
5
- "type": "module",
6
- "engines": {
7
- "node": "^22.19.0 || >=24.0.0"
8
- },
9
- "main": "lib/index.js",
10
- "types": "lib/types/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./lib/types/index.d.ts",
14
- "default": "./lib/index.js"
15
- },
16
- "./client": {
17
- "types": "./lib/types/client/index.d.ts",
18
- "default": "./lib/client.js"
19
- },
20
- "./src/*": "./src/*",
21
- "./package.json": "./package.json"
22
- },
23
- "dsh": {
24
- "bundle": {
25
- "patch": "./cordis.patch.yml"
26
- },
27
- "client": {
28
- "inject": [
29
- "@deepseek-ai/dsh-client-runtime",
30
- "@deepseek-ai/dsh-client-connection",
31
- "@deepseek-ai/dsh-client-ui-settings"
32
- ],
33
- "platform": "web"
34
- }
35
- },
36
- "dependencies": {
37
- "fflate": "^0.8.2",
38
- "schemastery": "^3.18.0"
39
- },
40
- "peerDependencies": {
41
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
42
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
43
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
44
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
45
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
46
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
47
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
48
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
49
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
50
- "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
51
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
52
- "react": "^18.2.0",
53
- "react-dom": "^18.2.0"
54
- },
55
- "devDependencies": {
56
- "@deepseek-ai/cordis": "^4.0.1",
57
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
58
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
59
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
60
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
61
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
62
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
63
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
64
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
65
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
66
- "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
67
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
68
- "@types/node": "^22.20.0",
69
- "@types/react": "~18.3.1",
70
- "@types/react-dom": "^18.3.5",
71
- "lightningcss": "1.32.0",
72
- "react": "^18.3.1",
73
- "react-dom": "^18.3.1",
74
- "tsdown": "0.22.2",
75
- "typescript": "~5.7.2",
76
- "vitest": "^3.0.0"
77
- },
78
- "files": [
79
- "lib/**/*.js",
80
- "lib/**/*.js.map",
81
- "lib/**/*.d.ts",
82
- "lib/**/*.d.ts.map",
83
- "src",
84
- "scripts",
85
- "cordis.patch.yml",
86
- "README.md"
87
- ],
88
- "license": "Apache-2.0",
89
- "scripts": {
90
- "postinstall": "node scripts/patch-schemastery.mjs",
91
- "build": "tsc -p tsconfig.build.json && tsdown",
92
- "bundle": "tsdown",
93
- "watch": "tsdown --watch",
94
- "typecheck": "tsc --noEmit",
95
- "test": "vitest run"
96
- }
97
- }
1
+ {
2
+ "name": "@waterwx/dsh-novel-forge",
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.4.0",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": "^22.19.0 || >=24.0.0"
8
+ },
9
+ "main": "lib/index.js",
10
+ "types": "lib/types/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/types/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./client": {
17
+ "types": "./lib/types/client/index.d.ts",
18
+ "default": "./lib/client.js"
19
+ },
20
+ "./src/*": "./src/*",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "dsh": {
24
+ "bundle": {
25
+ "patch": "./cordis.patch.yml"
26
+ },
27
+ "client": {
28
+ "inject": [
29
+ "@deepseek-ai/dsh-client-runtime",
30
+ "@deepseek-ai/dsh-client-connection",
31
+ "@deepseek-ai/dsh-client-ui-settings"
32
+ ],
33
+ "platform": "web"
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "fflate": "^0.8.2",
38
+ "schemastery": "^3.18.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
42
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
43
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
44
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
45
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
46
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
47
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
48
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
49
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
50
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
51
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
52
+ "react": "^18.2.0",
53
+ "react-dom": "^18.2.0"
54
+ },
55
+ "devDependencies": {
56
+ "@deepseek-ai/cordis": "^4.0.1",
57
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.5",
58
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
59
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
60
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
61
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
62
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.5",
63
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
64
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
65
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.5",
66
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.5",
67
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.5",
68
+ "@types/node": "^22.20.0",
69
+ "@types/react": "~18.3.1",
70
+ "@types/react-dom": "^18.3.5",
71
+ "lightningcss": "1.32.0",
72
+ "react": "^18.3.1",
73
+ "react-dom": "^18.3.1",
74
+ "tsdown": "0.22.2",
75
+ "typescript": "~5.7.2",
76
+ "vitest": "^3.0.0"
77
+ },
78
+ "files": [
79
+ "lib/**/*.js",
80
+ "lib/**/*.js.map",
81
+ "lib/**/*.d.ts",
82
+ "lib/**/*.d.ts.map",
83
+ "src",
84
+ "scripts/patch-schemastery.mjs",
85
+ "cordis.patch.yml",
86
+ "README.md"
87
+ ],
88
+ "license": "Apache-2.0",
89
+ "scripts": {
90
+ "postinstall": "node scripts/patch-schemastery.mjs",
91
+ "build": "tsc -p tsconfig.build.json && tsdown",
92
+ "bundle": "tsdown",
93
+ "watch": "tsdown --watch",
94
+ "typecheck": "tsc --noEmit",
95
+ "test": "vitest run"
96
+ }
97
+ }
package/src/bookshelf.ts CHANGED
@@ -114,6 +114,22 @@ export function seedBookshelfFromOutputDir(outputDir: string): boolean {
114
114
  return true
115
115
  }
116
116
 
117
+ /** 导入已有项目目录到书架:校验 novel-project.json,已存在则直接激活。 */
118
+ export function importDir(outputDir: string): { book: BookEntry; existed: boolean } {
119
+ const project = loadProject(outputDir)
120
+ if (project === undefined) throw new Error(`目录中未找到有效的 novel-project.json:${outputDir}`)
121
+ const store = loadBookshelf()
122
+ const existed = store.books.find(b => b.outputDir === outputDir)
123
+ if (existed !== undefined) {
124
+ store.activeBookId = existed.id
125
+ saveBookshelf(store)
126
+ return { book: existed, existed: true }
127
+ }
128
+ const bookName = project.bookName !== '' ? project.bookName : (outputDir.split(/[\\/]/).pop() ?? '未命名小说')
129
+ const book = createBook(bookName, outputDir)
130
+ return { book, existed: false }
131
+ }
132
+
117
133
  /** 激活一本书。 */
118
134
  export function activateBook(id: string): BookEntry | undefined {
119
135
  const store = loadBookshelf()
@@ -144,8 +160,8 @@ export function activeBookOutputDir(): string | undefined {
144
160
  return book?.outputDir
145
161
  }
146
162
 
147
- /** 默认输出目录推断:小说目录/书名。 */
163
+ /** 默认输出目录推断:~/.dsh/novels/书名。 */
148
164
  export function defaultOutputDirFor(bookName: string): string {
149
165
  const clean = bookName.replace(/[\\/:*?"<>|]/g, '').trim().slice(0, 40) || '未命名小说'
150
- return join('D:\\ryan work\\harness\\novels', clean)
166
+ return join(homedir(), '.dsh', 'novels', clean)
151
167
  }