@waterwx/dsh-novel-forge 1.8.0 → 1.9.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.
@@ -3,7 +3,7 @@
3
3
  * fetch, same origin; generation/rewrite/polish ride NDJSON streams read
4
4
  * incrementally.
5
5
  */
6
- import { type AssetsPatch, type AssetsResponse, type BibleResponse, type ChapterResponse, type ConfigPatch, type ExportResponse, type ForeshadowRequest, type ForeshadowResponse, type JobFrame, type LoadOutlineResponse, type NovelConfig, type PlanResponse, type ReviewReport, type StatusResponse, type StyleEngineRequest, type StyleAsset, type VolumesResponse } from '../protocol.ts';
6
+ import { type AssetsPatch, type AssetsResponse, type AddModelRequest, type AddModelResponse, type LlmModelsResponse, type LlmProvidersResponse, type LlmVendorsResponse, type RemoveProviderRequest, type RemoveProviderResponse, type BibleResponse, type ChapterResponse, type ConfigPatch, type ExportResponse, type ForeshadowRequest, type ForeshadowResponse, type JobFrame, type LoadOutlineResponse, type LlmTestResponse, type NovelConfig, type PlanResponse, type ReviewReport, type StatusResponse, type StyleEngineRequest, type StyleAsset, type VolumesResponse } from '../protocol.ts';
7
7
  /** Error carrying the route's JSON error message. */
8
8
  export declare class NovelApiError extends Error {
9
9
  constructor(message: string);
@@ -99,6 +99,18 @@ export declare class NovelApi {
99
99
  }>;
100
100
  /** 角色库:AI 提炼 / 采纳 / 更新 / 删除。 */
101
101
  imageTest(req: import('../protocol.ts').ImageTestRequest): Promise<import('../protocol.ts').ImageTestResponse>;
102
+ /** 对选中的提供商/模型发一次最小真实调用,验证连通性。 */
103
+ llmTest(provider: string, model: string): Promise<LlmTestResponse>;
104
+ /** 添加模型:厂商直填 key,或自定义 OpenAI 兼容路由(写 DSH 凭据 + 注册路由)。 */
105
+ addModel(req: AddModelRequest): Promise<AddModelResponse>;
106
+ /** 运行时厂商目录(DSH pi-ai 可配置提供方 + 内置适配器)。 */
107
+ llmVendors(): Promise<LlmVendorsResponse>;
108
+ /** 查询某个 provider 当前可用的模型(添加成功后可即时刷新)。 */
109
+ llmModels(provider: string): Promise<LlmModelsResponse>;
110
+ /** 已注册的提供方路由列表(提供方管理卡片)。 */
111
+ llmProviders(): Promise<LlmProvidersResponse>;
112
+ /** 移除一个提供方(unset key + 移除 llm-pi-ai 路由)。 */
113
+ removeProvider(req: RemoveProviderRequest): Promise<RemoveProviderResponse>;
102
114
  roles(req: import('../protocol.ts').RolesRequest): Promise<import('../protocol.ts').RolesResponse>;
103
115
  /** 漫剧角色库:从分镜提名 / 建卡 / 更新 / 删除 / 形象锚点 / 精修提示词。 */
104
116
  mangaRoles(req: import('../protocol.ts').MangaRolesRequest): Promise<import('../protocol.ts').MangaRolesResponse>;
@@ -142,7 +142,23 @@ export declare const zh: {
142
142
  readonly 'settings.provider': "模型提供商";
143
143
  readonly 'settings.model': "模型";
144
144
  readonly 'settings.modelCustom': "自定义…";
145
+ readonly 'settings.customProvider': "自定义提供商…";
146
+ readonly 'settings.edit': "编辑";
147
+ readonly 'settings.delete': "删除";
145
148
  readonly 'settings.modelCustomPlaceholder': "输入任意模型 id";
149
+ readonly 'settings.savedModels': "我的模型";
150
+ readonly 'settings.modelCards': "模型卡片(内置 + 已添加)";
151
+ readonly 'settings.savedModelName': "名称";
152
+ readonly 'settings.addModel': "添加模型";
153
+ readonly 'settings.catalogRefresh': "刷新厂商";
154
+ readonly 'settings.refreshModels': "刷新模型";
155
+ readonly 'settings.savedModelsHint': "手动添加常用模型(provider + 模型 id),点击即可切换当前模型;厂商模式会写入 DSH 凭据并注册路由。";
156
+ readonly 'settings.testConnection': "测试连通";
157
+ readonly 'settings.testRunning': "测试中";
158
+ readonly 'settings.testOk': "连通";
159
+ readonly 'settings.testFail': "失败";
160
+ readonly 'settings.testHint': "真实发送一次最小调用(约几十 token),验证 Key / 端点 / 模型可用。";
161
+ readonly 'settings.reasoningSection': "推理强度";
146
162
  readonly 'settings.reasoningEffort': "写作类任务思考强度 (reasoningEffort)";
147
163
  readonly 'settings.reasoningHint': "章节生成/润色/重写/审稿等写作任务使用;关掉更省更快。";
148
164
  readonly 'settings.analysisReasoning': "分析类任务思考强度(提炼/拆书/反推大纲)";
@@ -1,6 +1,7 @@
1
1
  import type { NovelApi } from '../api.ts';
2
2
  import type { BookshelfSnapshot } from '../../protocol.ts';
3
- export declare function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onImportBook, onOpenSettings, onTheme, onBackground, onOpacity, adaptEnabled }: {
3
+ import { type ProgressLine } from './ProgressConsole.tsx';
4
+ export declare function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onImportBook, onOpenSettings, onTheme, onBackground, onOpacity, adaptEnabled, progress, busy, busyLabel, liveBar, onClearProgress }: {
4
5
  api: NovelApi;
5
6
  shelf: BookshelfSnapshot;
6
7
  onOpenBook: (id: string) => void;
@@ -17,4 +18,13 @@ export declare function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBo
17
18
  onOpacity?: (n: number) => void;
18
19
  /** 是否启用改编模式(默认 false=隐藏入口)。 */
19
20
  adaptEnabled?: boolean;
21
+ /** AI 进度实时状态(与书内共享同一份)。 */
22
+ progress?: ProgressLine[];
23
+ busy?: boolean;
24
+ busyLabel?: string;
25
+ liveBar?: {
26
+ text: string;
27
+ ratio?: number;
28
+ } | null;
29
+ onClearProgress?: () => void;
20
30
  }): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import type { NovelApi } from '../api.ts';
2
+ import type { SavedModel } from '../../protocol.ts';
3
+ /** 目录不可用时回退的内置预设(历史行为)。 */
4
+ export declare const FALLBACK_MODEL_PRESETS: readonly ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"];
5
+ export interface ModelManagerProps {
6
+ api: NovelApi;
7
+ provider: string;
8
+ model: string;
9
+ savedModels: SavedModel[];
10
+ onProvider: (provider: string) => void;
11
+ onModel: (model: string) => void;
12
+ onSavedModels: (models: SavedModel[]) => void;
13
+ }
14
+ export declare function ModelManager({ api, provider, model, onProvider, onModel }: ModelManagerProps): JSX.Element;
@@ -0,0 +1,11 @@
1
+ import type { NovelApi } from '../api.ts';
2
+ /** 目录不可用时回退的内置预设(历史行为)。 */
3
+ export declare const FALLBACK_MODEL_PRESETS: readonly ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"];
4
+ export interface ModelPickerProps {
5
+ api: NovelApi;
6
+ provider: string;
7
+ model: string;
8
+ onProvider: (provider: string) => void;
9
+ onModel: (model: string) => void;
10
+ }
11
+ export declare function ModelPicker({ api, provider, model, onProvider, onModel }: ModelPickerProps): JSX.Element;
@@ -0,0 +1,17 @@
1
+ export interface ProgressLine {
2
+ id: number;
3
+ text: string;
4
+ kind: 'info' | 'done' | 'error';
5
+ live?: boolean;
6
+ ratio?: number;
7
+ }
8
+ export declare function ProgressConsole({ progress, busy, busyLabel, liveBar, onClear }: {
9
+ progress: ProgressLine[];
10
+ busy: boolean;
11
+ busyLabel: string;
12
+ liveBar: {
13
+ text: string;
14
+ ratio?: number;
15
+ } | null;
16
+ onClear: () => void;
17
+ }): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import type { NovelConfig } from '../../protocol.ts';
2
+ export interface ReasoningSectionProps {
3
+ reasoningEffort: NovelConfig['reasoningEffort'];
4
+ analysisReasoning: NovelConfig['analysisReasoning'];
5
+ onChange: (patch: {
6
+ reasoningEffort?: NovelConfig['reasoningEffort'];
7
+ analysisReasoning?: NovelConfig['analysisReasoning'];
8
+ }) => void;
9
+ }
10
+ export declare function ReasoningSection({ reasoningEffort, analysisReasoning, onChange }: ReasoningSectionProps): JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import type { NovelApi } from '../api.ts';
2
- type SettingsTab = 'model' | 'writing' | 'image' | 'files' | 'appearance' | 'background';
2
+ type SettingsTab = 'model' | 'writing' | 'image' | 'files' | 'appearance';
3
3
  type ThemeMode = 'system' | 'light' | 'dark';
4
4
  type ThemeName = 'liquid' | 'neumorph' | 'macos' | 'clay';
5
5
  type ThemeDensity = 'comfort' | 'compact' | 'spacious';
@@ -12,7 +12,7 @@
12
12
  */
13
13
  export declare const COMPLIANCE_REDLINES: ReadonlyArray<string>;
14
14
  import type { Context } from '@deepseek-ai/cordis';
15
- import type { AdaptationDimension, AdaptAnalyzeResponse, AdaptationMapping, AdaptProposeResponse, AuditIssue, AuthorReview, BreakdownResponse, ChapterPlan, Foreshadow, NovelConfig, OutlineCandidate, Plotline, PlotlineHealthReport, PlotlinePlan, ProjectState, ReviewReport, RoleRecord, RoleStatusCard, SceneCard, StoryBible, StoryboardSkeleton, StoryboardTable, StoryboardPrompt, MangaRoleCandidate, Volume, WorldState } from './protocol.ts';
15
+ import type { AdaptationDimension, AdaptAnalyzeResponse, AdaptationMapping, AdaptProposeResponse, AuditIssue, AuthorReview, BreakdownResponse, ChapterPlan, Foreshadow, NovelConfig, OutlineCandidate, Plotline, PlotlineHealthReport, PlotlinePlan, ProjectState, ReviewReport, RoleRecord, RoleStatusCard, SceneCard, StoryBible, StoryboardSkeleton, StoryboardTable, StoryboardPrompt, AddModelRequest, AddModelResponse, LlmModelsResponse, LlmVendorsResponse, LlmProvidersResponse, RemoveProviderRequest, RemoveProviderResponse, LlmTestResponse, MangaRoleCandidate, Volume, WorldState } from './protocol.ts';
16
16
  /** Project state file name inside the output dir. */
17
17
  export declare const PROJECT_FILE = "novel-project.json";
18
18
  /** Chapter output file name, e.g. 第001章_开篇.md */
@@ -147,6 +147,21 @@ export declare function testImageEndpoint(baseURL: string, apiKey: string, model
147
147
  message?: string;
148
148
  modelFound?: boolean;
149
149
  }>;
150
+ /** 对选中的提供商/模型发一次最小真实调用(maxTokens=16),验证 Key / 端点 / 模型可用。 */
151
+ export declare function testLlmModel(ctx: Context, provider: string, model: string): Promise<LlmTestResponse>;
152
+ /** 运行时厂商目录:DSH pi-ai 可配置提供方 + 内置适配器,作为「添加模型」下拉。 */
153
+ export declare function listLlmVendors(ctx: Context): Promise<LlmVendorsResponse>;
154
+ /** 查询某个 provider 当前可用模型(添加成功后可即时刷新下拉)。 */
155
+ export declare function listLlmModels(ctx: Context, provider: string): Promise<LlmModelsResponse>;
156
+ /** 当前已注册的提供方路由列表(提供方管理卡片)。 */
157
+ export declare function listLlmProviders(ctx: Context): Promise<LlmProvidersResponse>;
158
+ /** 移除一个提供方:unset 凭据 ref + 移除 llm-pi-ai providers 路由。 */
159
+ export declare function removeLlmProvider(ctx: Context, req: RemoveProviderRequest): Promise<RemoveProviderResponse>;
160
+ /**
161
+ * 添加模型(DSH 同款体验):厂商直填 API Key,或自定义 OpenAI 兼容路由。
162
+ * 写入 DSH 凭据 refs,并(必要时)注册/更新 llm-pi-ai provider 路由。
163
+ */
164
+ export declare function registerLlmModel(ctx: Context, req: AddModelRequest): Promise<AddModelResponse>;
150
165
  /** 小说角色库:生成角色定妆图并写回角色卡(imageUrl)。 */
151
166
  export declare function generateRoleReferenceImage(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, name: string, style?: string, modelId?: string): Promise<string>;
152
167
  /** 漫剧角色卡:生成定妆图并写回漫剧卡(imageUrl)。 */
@@ -55,6 +55,18 @@ export declare const NOVEL_API: {
55
55
  readonly storyboardPrompts: "/api/dsh-novel-forge/storyboard/prompts";
56
56
  /** 生图接口连通性测试(设置页每个模型条目用)。 */
57
57
  readonly imageTest: "/api/dsh-novel-forge/image-test";
58
+ /** LLM 模型连通性测试:真实最小调用,验证 Key / 端点 / 模型可用。 */
59
+ readonly llmTest: "/api/dsh-novel-forge/llm-test";
60
+ /** 添加模型:厂商直填 key 或自定义路由,写进 DSH 凭据与 llm-pi-ai 路由。 */
61
+ readonly addModel: "/api/dsh-novel-forge/llm-add";
62
+ /** 运行时厂商目录(DSH pi-ai 可配置提供方 + 内置适配器)。 */
63
+ readonly llmVendors: "/api/dsh-novel-forge/llm-vendors";
64
+ /** 查询某个 provider 当前可用的模型(添加成功后可即时刷新)。 */
65
+ readonly llmModels: "/api/dsh-novel-forge/llm-models";
66
+ /** 已注册的提供方路由列表(提供方管理)。 */
67
+ readonly llmProviders: "/api/dsh-novel-forge/llm-providers";
68
+ /** 移除一个提供方。 */
69
+ readonly llmRemove: "/api/dsh-novel-forge/llm-remove";
58
70
  /** 重置项目(可选携带新大纲):清空设定/卷/章节/伏笔/资产/事实库。 */
59
71
  readonly reset: "/api/dsh-novel-forge/reset";
60
72
  /** 全书一致性质检:LLM 扫描已生成章节,输出矛盾问题清单。 */
@@ -995,6 +1007,21 @@ export interface ImageTestResponse {
995
1007
  /** 模型 id 是否出现在接口的模型列表(探测成功时才有)。 */
996
1008
  modelFound?: boolean;
997
1009
  }
1010
+ /** POST /llm-test 请求:对选中的提供商/模型发一次最小真实调用。 */
1011
+ export interface LlmTestRequest {
1012
+ provider: string;
1013
+ model: string;
1014
+ }
1015
+ /** POST /llm-test 响应。 */
1016
+ export interface LlmTestResponse {
1017
+ ok: boolean;
1018
+ /** 连通延迟(毫秒)。 */
1019
+ ms?: number;
1020
+ /** 失败原因(已映射为人话)。 */
1021
+ message?: string;
1022
+ /** 稳定错误码(LlmError code),便于排查。 */
1023
+ code?: string;
1024
+ }
998
1025
  /** POST /roles 响应。 */
999
1026
  export interface RolesResponse {
1000
1027
  roles: RoleRecord[];
@@ -1158,6 +1185,122 @@ export interface ImageModelConfig {
1158
1185
  enabled: boolean;
1159
1186
  }
1160
1187
  /** Runtime config surface exposed to the panel (subset of plugin Config). */
1188
+ /** 手动添加的模型库条目(只存插件内,不改 DSH 全局)。 */
1189
+ export interface SavedModel {
1190
+ /** 唯一 id(前端生成的短 id)。 */
1191
+ id: string;
1192
+ /** 展示名(可为空,回退到 model)。 */
1193
+ name: string;
1194
+ /** DSH 提供商路由(如 zai-coding-cn)。 */
1195
+ provider: string;
1196
+ /** 模型 id(如 glm-5.3-flash)。 */
1197
+ model: string;
1198
+ }
1199
+ /** DSH 模型添加:厂商预设(选择厂商,直接填 API Key)。 */
1200
+ export interface LlmVendor {
1201
+ id: string;
1202
+ name: string;
1203
+ /** 该厂商对应的 provider 路由。 */
1204
+ route: string;
1205
+ /** DSH 凭据引用名(写入 .credentials.yaml 的 refs)。 */
1206
+ apiKeyEnv: string;
1207
+ /** 默认模型 id。 */
1208
+ defaultModel: string;
1209
+ /** 建议可选模型 id 列表(下拉 data-list 用)。 */
1210
+ models: string[];
1211
+ /** 内置适配器(如 deepseek-official),无需注册 pi-ai 路由。 */
1212
+ builtin?: boolean;
1213
+ }
1214
+ /** 预置的常见厂商(id=provider 路由;添加模型下拉兜底用,其余厂商由运行时目录动态补充)。 */
1215
+ export declare const LLM_VENDORS: LlmVendor[];
1216
+ /** 运行时厂商目录的一项(添加到模型下拉;由 DSH 的 pi-ai 可配置提供方动态生成)。 */
1217
+ export interface LlmVendorOption {
1218
+ /** provider 路由 id(也是厂商下拉的值)。 */
1219
+ id: string;
1220
+ /** 展示名。 */
1221
+ name: string;
1222
+ /** 建议模型 id(可为空,用户手填)。 */
1223
+ models: string[];
1224
+ /** 已知 DSH 凭据引用名;为空时 host 生成 `PI_AI_<ID>_API_KEY`。 */
1225
+ apiKeyEnv?: string;
1226
+ /** 内置适配器(只写凭据,不注册 pi-ai 路由)。 */
1227
+ builtin?: boolean;
1228
+ }
1229
+ /** GET /llm-vendors 响应。 */
1230
+ export interface LlmVendorsResponse {
1231
+ vendors: LlmVendorOption[];
1232
+ }
1233
+ /** /llm-models 里的一条模型。 */
1234
+ export interface LlmModelOption {
1235
+ id: string;
1236
+ name: string;
1237
+ }
1238
+ /** GET /llm-models?provider=x 响应。 */
1239
+ export interface LlmModelsResponse {
1240
+ models: LlmModelOption[];
1241
+ }
1242
+ /** GET /llm-providers 响应:当前已注册的提供方路由。 */
1243
+ export interface LlmProvidersResponse {
1244
+ providers: {
1245
+ id: string;
1246
+ name: string;
1247
+ }[];
1248
+ }
1249
+ /** POST /llm-remove 请求:移除一个提供方(unset key + 移除 llm-pi-ai 路由)。 */
1250
+ export interface RemoveProviderRequest {
1251
+ provider: string;
1252
+ /** 该提供方对应的 DSH 凭据引用名(用于 unset)。 */
1253
+ apiKeyEnv?: string;
1254
+ }
1255
+ /** POST /llm-remove 响应。 */
1256
+ export interface RemoveProviderResponse {
1257
+ ok: boolean;
1258
+ message?: string;
1259
+ }
1260
+ /** GET /llm-providers 响应:当前已注册的提供方路由。 */
1261
+ export interface LlmProvidersResponse {
1262
+ providers: {
1263
+ id: string;
1264
+ name: string;
1265
+ }[];
1266
+ }
1267
+ /** POST /llm-remove 请求:移除一个提供方(unset key + 移除 llm-pi-ai 路由)。 */
1268
+ export interface RemoveProviderRequest {
1269
+ provider: string;
1270
+ /** 该提供方对应的 DSH 凭据引用名(用于 unset)。 */
1271
+ apiKeyEnv?: string;
1272
+ }
1273
+ /** POST /llm-remove 响应。 */
1274
+ export interface RemoveProviderResponse {
1275
+ ok: boolean;
1276
+ message?: string;
1277
+ }
1278
+ /** POST /llm-add 请求:添加一个模型(厂商直填 key,或自定义路由)。 */
1279
+ export interface AddModelRequest {
1280
+ mode: 'vendor' | 'custom';
1281
+ /** 厂商模式用:provider 路由 id(来自 /llm-vendors)。 */
1282
+ vendor?: string;
1283
+ /** 厂商已知的 DSH 凭据引用名(可选;为空时 host 生成)。 */
1284
+ apiKeyEnv?: string;
1285
+ /** 自定义模式用:provider 路由 id。 */
1286
+ provider?: string;
1287
+ /** 模型 id。 */
1288
+ model: string;
1289
+ /** API Key。 */
1290
+ apiKey: string;
1291
+ /** 展示名(可选)。 */
1292
+ name?: string;
1293
+ /** 自定义模式用:OpenAI 兼容 base URL。 */
1294
+ baseURL?: string;
1295
+ }
1296
+ /** POST /llm-add 响应。 */
1297
+ export interface AddModelResponse {
1298
+ ok: boolean;
1299
+ saved: SavedModel;
1300
+ provider: string;
1301
+ message?: string;
1302
+ }
1303
+ /** 手动添加的模型库条目(只存插件内,不改 DSH 全局)。 */
1161
1304
  export interface NovelConfig {
1162
1305
  /** Absolute path of the default docx outline to load. */
1163
1306
  outlinePath: string;
@@ -1201,6 +1344,8 @@ export interface NovelConfig {
1201
1344
  themeOpacity?: number;
1202
1345
  /** 是否启用改编模式(默认关闭;发布后开启)。 */
1203
1346
  enableAdaptMode?: boolean;
1347
+ /** 手动添加的模型库(「我的模型」条目;只存插件内)。 */
1348
+ savedModels?: SavedModel[];
1204
1349
  }
1205
1350
  /** GET /status response. */
1206
1351
  export interface StatusResponse {
@@ -1447,6 +1592,8 @@ export interface ConfigPatch {
1447
1592
  enableAdaptMode?: boolean;
1448
1593
  /** 生图模型库(完整替换保存)。 */
1449
1594
  imageModels?: ImageModelConfig[];
1595
+ /** 手动添加的模型库(完整替换保存)。 */
1596
+ savedModels?: SavedModel[];
1450
1597
  }
1451
1598
  /** One assistant conversation message (persisted per project). */
1452
1599
  export interface AssistantMessage {
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.8.0",
4
+ "version": "1.9.0",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
package/src/client/api.ts CHANGED
@@ -8,6 +8,13 @@ import {
8
8
  NOVEL_API,
9
9
  type AssetsPatch,
10
10
  type AssetsResponse,
11
+ type AddModelRequest,
12
+ type AddModelResponse,
13
+ type LlmModelsResponse,
14
+ type LlmProvidersResponse,
15
+ type LlmVendorsResponse,
16
+ type RemoveProviderRequest,
17
+ type RemoveProviderResponse,
11
18
  type BibleResponse,
12
19
  type ChapterResponse,
13
20
  type ConfigPatch,
@@ -16,6 +23,7 @@ import {
16
23
  type ForeshadowResponse,
17
24
  type JobFrame,
18
25
  type LoadOutlineResponse,
26
+ type LlmTestResponse,
19
27
  type NovelConfig,
20
28
  type PlanResponse,
21
29
  type ReviewReport,
@@ -232,6 +240,39 @@ export class NovelApi {
232
240
  return postJson<import('../protocol.ts').ImageTestResponse>(NOVEL_API.imageTest, req)
233
241
  }
234
242
 
243
+ /** 对选中的提供商/模型发一次最小真实调用,验证连通性。 */
244
+ async llmTest(provider: string, model: string): Promise<LlmTestResponse> {
245
+ return postJson<LlmTestResponse>(NOVEL_API.llmTest, { provider, model })
246
+ }
247
+
248
+ /** 添加模型:厂商直填 key,或自定义 OpenAI 兼容路由(写 DSH 凭据 + 注册路由)。 */
249
+ async addModel(req: AddModelRequest): Promise<AddModelResponse> {
250
+ return postJson<AddModelResponse>(NOVEL_API.addModel, req)
251
+ }
252
+
253
+ /** 运行时厂商目录(DSH pi-ai 可配置提供方 + 内置适配器)。 */
254
+ async llmVendors(): Promise<LlmVendorsResponse> {
255
+ const response = await fetch(NOVEL_API.llmVendors)
256
+ return readJson<LlmVendorsResponse>(response)
257
+ }
258
+
259
+ /** 查询某个 provider 当前可用的模型(添加成功后可即时刷新)。 */
260
+ async llmModels(provider: string): Promise<LlmModelsResponse> {
261
+ const response = await fetch(NOVEL_API.llmModels + '?provider=' + encodeURIComponent(provider))
262
+ return readJson<LlmModelsResponse>(response)
263
+ }
264
+
265
+ /** 已注册的提供方路由列表(提供方管理卡片)。 */
266
+ async llmProviders(): Promise<LlmProvidersResponse> {
267
+ const response = await fetch(NOVEL_API.llmProviders)
268
+ return readJson<LlmProvidersResponse>(response)
269
+ }
270
+
271
+ /** 移除一个提供方(unset key + 移除 llm-pi-ai 路由)。 */
272
+ async removeProvider(req: RemoveProviderRequest): Promise<RemoveProviderResponse> {
273
+ return postJson<RemoveProviderResponse>(NOVEL_API.llmRemove, req)
274
+ }
275
+
235
276
  async roles(req: import('../protocol.ts').RolesRequest): Promise<import('../protocol.ts').RolesResponse> {
236
277
  return postJson<import('../protocol.ts').RolesResponse>(NOVEL_API.roles, req)
237
278
  }
@@ -143,7 +143,23 @@ export const zh = {
143
143
  'settings.provider': '模型提供商',
144
144
  'settings.model': '模型',
145
145
  'settings.modelCustom': '自定义…',
146
+ 'settings.customProvider': '自定义提供商…',
147
+ 'settings.edit': '编辑',
148
+ 'settings.delete': '删除',
146
149
  'settings.modelCustomPlaceholder': '输入任意模型 id',
150
+ 'settings.savedModels': '我的模型',
151
+ 'settings.modelCards': '模型卡片(内置 + 已添加)',
152
+ 'settings.savedModelName': '名称',
153
+ 'settings.addModel': '添加模型',
154
+ 'settings.catalogRefresh': '刷新厂商',
155
+ 'settings.refreshModels': '刷新模型',
156
+ 'settings.savedModelsHint': '手动添加常用模型(provider + 模型 id),点击即可切换当前模型;厂商模式会写入 DSH 凭据并注册路由。',
157
+ 'settings.testConnection': '测试连通',
158
+ 'settings.testRunning': '测试中',
159
+ 'settings.testOk': '连通',
160
+ 'settings.testFail': '失败',
161
+ 'settings.testHint': '真实发送一次最小调用(约几十 token),验证 Key / 端点 / 模型可用。',
162
+ 'settings.reasoningSection': '推理强度',
147
163
  'settings.reasoningEffort': '写作类任务思考强度 (reasoningEffort)',
148
164
  'settings.reasoningHint': '章节生成/润色/重写/审稿等写作任务使用;关掉更省更快。',
149
165
  'settings.analysisReasoning': '分析类任务思考强度(提炼/拆书/反推大纲)',
@@ -295,7 +311,23 @@ export const en: Record<string, string> = {
295
311
  'settings.provider': 'Provider',
296
312
  'settings.model': 'Model',
297
313
  'settings.modelCustom': 'Custom…',
314
+ 'settings.customProvider': 'Custom provider…',
315
+ 'settings.edit': 'Edit',
316
+ 'settings.delete': 'Delete',
298
317
  'settings.modelCustomPlaceholder': 'Type any model id',
318
+ 'settings.savedModels': 'My Models',
319
+ 'settings.modelCards': 'Model cards (built-in + added)',
320
+ 'settings.savedModelName': 'Name',
321
+ 'settings.addModel': 'Add model',
322
+ 'settings.catalogRefresh': 'Refresh vendors',
323
+ 'settings.refreshModels': 'Refresh models',
324
+ 'settings.savedModelsHint': 'Manually add a model (provider + model id); click to switch. Vendor mode writes the key into DSH credentials and registers the route.',
325
+ 'settings.testConnection': 'Test',
326
+ 'settings.testRunning': 'Testing',
327
+ 'settings.testOk': 'OK',
328
+ 'settings.testFail': 'Failed',
329
+ 'settings.testHint': 'Sends one minimal real call (~dozens of tokens) to verify key / endpoint / model.',
330
+ 'settings.reasoningSection': 'Reasoning',
299
331
  'settings.reasoningEffort': 'Writing-task reasoning effort',
300
332
  'settings.analysisReasoning': 'Analysis-task reasoning (extract / breakdown / reverse outline)',
301
333
  'settings.analysisReasoningHint': 'One-off analysis tasks (roles/scenes/bible extract, breakdown, reverse outline) default to low; not affected by the writing-level switch.',
@@ -95,11 +95,9 @@ export function AdaptModeView({ api }: { api: NovelApi }) {
95
95
 
96
96
  return (
97
97
  <div className={css.authorPageBody}>
98
- <div className={css.authorPageHeader}>
99
- <div>
100
- <h2 className={css.panelTitle} style={{ margin: 0 }}>🎬 改编模式</h2>
101
- <span className={css.meta}>上传全文 → 分析可改范围 → 勾选/填新值 → 生成映射表 → 术语替换执行。</span>
102
- </div>
98
+ <div className={css.card + ' ' + css.settingsCard} style={{ gap: 'var(--nf-space-6)' }}>
99
+ <h2 className={css.panelTitle} style={{ margin: 0 }}>🎬 改编模式</h2>
100
+ <span className={css.meta}>上传全文 → 分析可改范围 → 勾选/填新值 → 生成映射表 → 术语替换执行。</span>
103
101
  </div>
104
102
 
105
103
  <div className={css.adaptInputCard}>
@@ -9,10 +9,14 @@ import type { BookshelfSnapshot } from '../../protocol.ts'
9
9
  import { ShelfView } from './ShelfView.tsx'
10
10
  import { AdaptModeView } from './AdaptModeView.tsx'
11
11
  import { AuthorAssetsView } from './AuthorAssetsView.tsx'
12
- import { ProgressHomeView } from './ProgressHomeView.tsx'
12
+ import { ProgressConsole, type ProgressLine } from './ProgressConsole.tsx'
13
13
  import { SettingsView } from './SettingsView.tsx'
14
14
  import css from './panel.module.css'
15
15
 
16
+ /** 构建时注入的插件版本(tsdown define 替换为字符串字面量)。 */
17
+ declare const __NOVEL_FORGE_VERSION__: string | undefined
18
+ const PLUGIN_VERSION: string = typeof __NOVEL_FORGE_VERSION__ !== 'undefined' ? __NOVEL_FORGE_VERSION__ : '0.0.0'
19
+
16
20
  type AuthorNav = 'shelf' | 'adapt' | 'assets' | 'library' | 'progress' | 'settings';
17
21
 
18
22
  const NAV_ITEMS: Array<{ id: Exclude<AuthorNav, 'settings'>; label: string; icon: JSX.Element; hint: string }> = [
@@ -26,7 +30,7 @@ const NAV_ITEMS: Array<{ id: Exclude<AuthorNav, 'settings'>; label: string; icon
26
30
  function Placeholder({ title, hint }: { title: string; hint: string }) {
27
31
  return (
28
32
  <div className={css.authorPageBody}>
29
- <div className={css.authorPageHeader}><h2 className={css.panelTitle} style={{ margin: 0 }}>{title}</h2><span className={css.meta}>{hint}</span></div>
33
+ <div className={css.card + ' ' + css.settingsCard} style={{ gap: 'var(--nf-space-6)' }}><h2 className={css.panelTitle} style={{ margin: 0 }}>{title}</h2><span className={css.meta}>{hint}</span></div>
30
34
  <div className={css.shelfEmpty} style={{ minHeight: 160 }}>
31
35
  <span className={css.shelfEmptyIcon}><Info size={30} /></span>
32
36
  <span className={css.shelfEmptyTitle}>此入口为占位</span>
@@ -36,7 +40,7 @@ function Placeholder({ title, hint }: { title: string; hint: string }) {
36
40
  );
37
41
  }
38
42
 
39
- export function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onImportBook, onOpenSettings, onTheme, onBackground, onOpacity, adaptEnabled }: {
43
+ export function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onImportBook, onOpenSettings, onTheme, onBackground, onOpacity, adaptEnabled, progress, busy, busyLabel, liveBar, onClearProgress }: {
40
44
  api: NovelApi; shelf: BookshelfSnapshot;
41
45
  onOpenBook: (id: string) => void; onReadBook: (id: string) => void; onAddBook: () => void; onImportBook: () => void;
42
46
  /** 兼容旧入口:首页设置现为独立设置页,此回调保留但不再使用。 */
@@ -49,6 +53,12 @@ export function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onIm
49
53
  onOpacity?: (n: number) => void;
50
54
  /** 是否启用改编模式(默认 false=隐藏入口)。 */
51
55
  adaptEnabled?: boolean;
56
+ /** AI 进度实时状态(与书内共享同一份)。 */
57
+ progress?: ProgressLine[];
58
+ busy?: boolean;
59
+ busyLabel?: string;
60
+ liveBar?: { text: string; ratio?: number } | null;
61
+ onClearProgress?: () => void;
52
62
  }) {
53
63
  const [nav, setNav] = useState<AuthorNav>('shelf');
54
64
 
@@ -77,7 +87,7 @@ export function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onIm
77
87
  <span className={css.navTabLabel}>设置</span>
78
88
  </button>
79
89
  <div className={css.navAbout}>
80
- <div className={css.navAboutRow}><span>ℹ️ v1.7.3 · 改编/资产库</span></div>
90
+ <div className={css.navAboutRow}><span>ℹ️ v{PLUGIN_VERSION} · 改编/资产库</span></div>
81
91
  </div>
82
92
  </nav>
83
93
  <div className={css.authorContent}>
@@ -85,7 +95,11 @@ export function AuthorHome({ api, shelf, onOpenBook, onReadBook, onAddBook, onIm
85
95
  {nav === 'adapt' && <AdaptModeView api={api} />}
86
96
  {nav === 'assets' && <AuthorAssetsView api={api} />}
87
97
  {nav === 'library' && <Placeholder title="🎨 全局写作资产库" hint="内置题材基底库 / 反AI规则库 / 风格模板 / 推进模式(跨书)" />}
88
- {nav === 'progress' && <ProgressHomeView shelf={shelf} onOpenBook={onOpenBook} />}
98
+ {nav === 'progress' && (
99
+ <div className={css.authorPageBody}>
100
+ <ProgressConsole progress={progress ?? []} busy={busy ?? false} busyLabel={busyLabel ?? ''} liveBar={liveBar ?? null} onClear={() => onClearProgress?.()} />
101
+ </div>
102
+ )}
89
103
  {nav === 'settings' && <SettingsView api={api} onTheme={onTheme} onBackground={onBackground} onOpacity={onOpacity} />}
90
104
  </div>
91
105
  </div>