@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.
- package/lib/client.js +1439 -816
- package/lib/client.js.map +1 -1
- package/lib/index.js +519 -121
- package/lib/index.js.map +1 -1
- package/lib/types/client/api.d.ts +13 -1
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/client/panel/AuthorHome.d.ts +11 -1
- package/lib/types/client/panel/ModelManager.d.ts +14 -0
- package/lib/types/client/panel/ModelPicker.d.ts +11 -0
- package/lib/types/client/panel/ProgressConsole.d.ts +17 -0
- package/lib/types/client/panel/ReasoningSection.d.ts +10 -0
- package/lib/types/client/panel/SettingsView.d.ts +1 -1
- package/lib/types/engine.d.ts +16 -1
- package/lib/types/protocol.d.ts +147 -0
- package/package.json +1 -1
- package/src/client/api.ts +41 -0
- package/src/client/locales.ts +32 -0
- package/src/client/panel/AdaptModeView.tsx +3 -5
- package/src/client/panel/AuthorHome.tsx +19 -5
- package/src/client/panel/ModelManager.tsx +205 -0
- package/src/client/panel/NovelPanel.tsx +26 -63
- package/src/client/panel/ProgressConsole.tsx +64 -0
- package/src/client/panel/ProgressHomeView.tsx +1 -1
- package/src/client/panel/ReasoningSection.tsx +31 -0
- package/src/client/panel/SettingsView.tsx +56 -49
- package/src/engine.ts +221 -0
- package/src/index.ts +2 -2
- package/src/protocol.ts +160 -0
- package/src/routes.ts +132 -0
package/lib/index.js
CHANGED
|
@@ -1186,6 +1186,205 @@ function normalizeEmotions(text) {
|
|
|
1186
1186
|
return out;
|
|
1187
1187
|
}
|
|
1188
1188
|
//#endregion
|
|
1189
|
+
//#region src/protocol.ts
|
|
1190
|
+
/** The /api/dsh-novel-forge route family (same-origin, loopback-fenced). */
|
|
1191
|
+
const NOVEL_API = {
|
|
1192
|
+
status: "/api/dsh-novel-forge/status",
|
|
1193
|
+
loadOutline: "/api/dsh-novel-forge/load-outline",
|
|
1194
|
+
saveOutline: "/api/dsh-novel-forge/save-outline",
|
|
1195
|
+
plan: "/api/dsh-novel-forge/plan",
|
|
1196
|
+
volumes: "/api/dsh-novel-forge/volumes",
|
|
1197
|
+
bible: "/api/dsh-novel-forge/bible",
|
|
1198
|
+
assets: "/api/dsh-novel-forge/assets",
|
|
1199
|
+
styleEngine: "/api/dsh-novel-forge/style-engine",
|
|
1200
|
+
generate: "/api/dsh-novel-forge/generate",
|
|
1201
|
+
review: "/api/dsh-novel-forge/review",
|
|
1202
|
+
rewrite: "/api/dsh-novel-forge/rewrite",
|
|
1203
|
+
polish: "/api/dsh-novel-forge/polish",
|
|
1204
|
+
/** 采纳待确认草稿(润色/重写产物)覆盖正文文件。 */
|
|
1205
|
+
draftApply: "/api/dsh-novel-forge/draft/apply",
|
|
1206
|
+
/** 放弃待确认草稿,保留原稿。 */
|
|
1207
|
+
draftDiscard: "/api/dsh-novel-forge/draft/discard",
|
|
1208
|
+
summary: "/api/dsh-novel-forge/summary",
|
|
1209
|
+
foreshadow: "/api/dsh-novel-forge/foreshadow",
|
|
1210
|
+
exportBook: "/api/dsh-novel-forge/export",
|
|
1211
|
+
chapter: "/api/dsh-novel-forge/chapter",
|
|
1212
|
+
/** 审查任意正文文本(作者手动编辑后,不落盘)。 */
|
|
1213
|
+
chapterCheck: "/api/dsh-novel-forge/chapter/check",
|
|
1214
|
+
/** 保存手动编辑的正文(自动备份 .bak)。 */
|
|
1215
|
+
chapterSave: "/api/dsh-novel-forge/chapter/save",
|
|
1216
|
+
assistant: "/api/dsh-novel-forge/assistant",
|
|
1217
|
+
assistantHistory: "/api/dsh-novel-forge/assistant-history",
|
|
1218
|
+
/** 清空助手对话记录。 */
|
|
1219
|
+
assistantClear: "/api/dsh-novel-forge/assistant/clear",
|
|
1220
|
+
bookshelf: "/api/dsh-novel-forge/bookshelf",
|
|
1221
|
+
/** 导入已有项目目录(含 novel-project.json)到书架。 */
|
|
1222
|
+
bookshelfImportDir: "/api/dsh-novel-forge/bookshelf/import-dir",
|
|
1223
|
+
/** 导入 txt/md 全本:拆章建项目并登记书架。 */
|
|
1224
|
+
bookshelfImportText: "/api/dsh-novel-forge/bookshelf/import-text",
|
|
1225
|
+
/** 导入 txt/md 全本:拆章预览(不落盘)。 */
|
|
1226
|
+
bookshelfImportTextPreview: "/api/dsh-novel-forge/bookshelf/import-text/preview",
|
|
1227
|
+
/** 漫剧方案管理:create/remove/activate。 */
|
|
1228
|
+
manhuaPlans: "/api/dsh-novel-forge/manhua/plans",
|
|
1229
|
+
/** 漫剧角色库:从分镜提名(规则+LLM两段式)/ 建卡 / 更新 / 删除 / 形象锚点 / 精修提示词。 */
|
|
1230
|
+
mangaRoles: "/api/dsh-novel-forge/manga/roles",
|
|
1231
|
+
/** 分镜·编剧级:单章 → 剧情骨架(节拍链)。 */
|
|
1232
|
+
storyboardSkeleton: "/api/dsh-novel-forge/storyboard/skeleton",
|
|
1233
|
+
/** 分镜·导演级:骨架 → 分镜表(镜头级)。 */
|
|
1234
|
+
storyboardTable: "/api/dsh-novel-forge/storyboard/table",
|
|
1235
|
+
/** 分镜·提示词级:分镜表 → 即梦可粘贴视频提示词。 */
|
|
1236
|
+
storyboardPrompts: "/api/dsh-novel-forge/storyboard/prompts",
|
|
1237
|
+
/** 生图接口连通性测试(设置页每个模型条目用)。 */
|
|
1238
|
+
imageTest: "/api/dsh-novel-forge/image-test",
|
|
1239
|
+
/** LLM 模型连通性测试:真实最小调用,验证 Key / 端点 / 模型可用。 */
|
|
1240
|
+
llmTest: "/api/dsh-novel-forge/llm-test",
|
|
1241
|
+
/** 添加模型:厂商直填 key 或自定义路由,写进 DSH 凭据与 llm-pi-ai 路由。 */
|
|
1242
|
+
addModel: "/api/dsh-novel-forge/llm-add",
|
|
1243
|
+
/** 运行时厂商目录(DSH pi-ai 可配置提供方 + 内置适配器)。 */
|
|
1244
|
+
llmVendors: "/api/dsh-novel-forge/llm-vendors",
|
|
1245
|
+
/** 查询某个 provider 当前可用的模型(添加成功后可即时刷新)。 */
|
|
1246
|
+
llmModels: "/api/dsh-novel-forge/llm-models",
|
|
1247
|
+
/** 已注册的提供方路由列表(提供方管理)。 */
|
|
1248
|
+
llmProviders: "/api/dsh-novel-forge/llm-providers",
|
|
1249
|
+
/** 移除一个提供方。 */
|
|
1250
|
+
llmRemove: "/api/dsh-novel-forge/llm-remove",
|
|
1251
|
+
/** 重置项目(可选携带新大纲):清空设定/卷/章节/伏笔/资产/事实库。 */
|
|
1252
|
+
reset: "/api/dsh-novel-forge/reset",
|
|
1253
|
+
/** 全书一致性质检:LLM 扫描已生成章节,输出矛盾问题清单。 */
|
|
1254
|
+
audit: "/api/dsh-novel-forge/audit",
|
|
1255
|
+
/** 角色卡刷新:基于事实库与各章摘要聚合角色当前状态。 */
|
|
1256
|
+
charactersRefresh: "/api/dsh-novel-forge/characters/refresh",
|
|
1257
|
+
/** 事实库回填:对历史已生成章节批量抽取事实(旧章节无事实记录时用)。 */
|
|
1258
|
+
factsBackfill: "/api/dsh-novel-forge/facts/backfill",
|
|
1259
|
+
/** 设定圣经局部修补(如世界观规则编辑)。 */
|
|
1260
|
+
biblePatch: "/api/dsh-novel-forge/bible/patch",
|
|
1261
|
+
/** 小说简介:生成(AI)/补全(AI)/保存。 */
|
|
1262
|
+
blurb: "/api/dsh-novel-forge/blurb",
|
|
1263
|
+
/** 重命名当前书(同步项目与书架条目)。 */
|
|
1264
|
+
rename: "/api/dsh-novel-forge/rename",
|
|
1265
|
+
/** 大世界:AI 提炼 / 保存结构化数据(境界/区域/势力)。 */
|
|
1266
|
+
world: "/api/dsh-novel-forge/world",
|
|
1267
|
+
/** 封面:GET 读取(dataUrl)/ POST 上传或移除。 */
|
|
1268
|
+
cover: "/api/dsh-novel-forge/blurb/cover",
|
|
1269
|
+
/** 剧情线管理:增删改 + 关联章节。 */
|
|
1270
|
+
plotlines: "/api/dsh-novel-forge/plotlines",
|
|
1271
|
+
/** 角色库:AI 提炼 / 采纳 / 更新 / 删除。 */
|
|
1272
|
+
roles: "/api/dsh-novel-forge/roles",
|
|
1273
|
+
scenes: "/api/dsh-novel-forge/scenes",
|
|
1274
|
+
visualRules: "/api/dsh-novel-forge/visual-rules",
|
|
1275
|
+
/** 作者复盘补跑:对已写章节补齐 authorReview(全书流式 / 单章 JSON)。 */
|
|
1276
|
+
reviewBackfill: "/api/dsh-novel-forge/review/backfill",
|
|
1277
|
+
/** 章节复位:generating 卡死 → pending(可重新生成)。 */
|
|
1278
|
+
chapterReset: "/api/dsh-novel-forge/chapter/reset",
|
|
1279
|
+
/** 章节直接通过:作者对 rejected/written 章节行使最终决定权。 */
|
|
1280
|
+
chapterApprove: "/api/dsh-novel-forge/chapter/approve",
|
|
1281
|
+
/** 敏感词检查:全书已写章节或指定文本。 */
|
|
1282
|
+
sensitiveCheck: "/api/dsh-novel-forge/sensitive-check",
|
|
1283
|
+
/** 开书想法 → AI 补全大纲:输入一句话想法,生成 2-3 个可选大纲方案。 */
|
|
1284
|
+
outlineSuggest: "/api/dsh-novel-forge/outline/suggest",
|
|
1285
|
+
/** 反推大纲:从已写章节正文反向生成全书总纲(NDJSON 流)。 */
|
|
1286
|
+
outlineReverse: "/api/dsh-novel-forge/outline/reverse",
|
|
1287
|
+
/** 拆书分析:对已写章节做结构/人物/文风/卖点四维体检(两阶段:源笔记→分节分析)。 */
|
|
1288
|
+
breakdown: "/api/dsh-novel-forge/breakdown",
|
|
1289
|
+
/** 漫剧分镜生成:章节 → 角色锚点 + 分镜表(可适配豆包/Seedance/SD)。 */
|
|
1290
|
+
/** 漫剧分集计划:读一卷 → 按故事弧线分集(高潮拆集/过渡并章)。 */
|
|
1291
|
+
/** 漫画脚本:章节 → 分页分格漫画脚本(含角色视觉锚点)。 */
|
|
1292
|
+
/** 生产单:启动批量生产(计划补足 + 逐章生成 + 被拒分级处理)。 */
|
|
1293
|
+
runStart: "/api/dsh-novel-forge/run/start",
|
|
1294
|
+
/** 生产单控制:pause / resume / stop。 */
|
|
1295
|
+
runControl: "/api/dsh-novel-forge/run/control",
|
|
1296
|
+
/** 生产单状态(含进度统计与日志)。 */
|
|
1297
|
+
runStatus: "/api/dsh-novel-forge/run/status",
|
|
1298
|
+
config: "/api/dsh-novel-forge/config",
|
|
1299
|
+
openFolder: "/api/dsh-novel-forge/open-folder",
|
|
1300
|
+
/** 作者资产库/总数据:读取个人跨书资产(笔法/红线/套路/角色模板/世界观模板)。 */
|
|
1301
|
+
authorAssets: "/api/dsh-novel-forge/author-assets",
|
|
1302
|
+
/** 作者资产库:新增/更新一条资产(upsert by id)。 */
|
|
1303
|
+
authorAssetsUpsert: "/api/dsh-novel-forge/author-assets/upsert",
|
|
1304
|
+
/** 作者资产库:删除一条资产。 */
|
|
1305
|
+
authorAssetsRemove: "/api/dsh-novel-forge/author-assets/remove",
|
|
1306
|
+
/** 作者资产库:导入默认(书架书的写作资产/角色 + 内置全局库)批量沉淀。 */
|
|
1307
|
+
authorAssetsImportDefault: "/api/dsh-novel-forge/author-assets/import-default",
|
|
1308
|
+
/** 改编模式:上传全文 → 分析 → 原文设定卡片/可改范围矩阵。 */
|
|
1309
|
+
adaptAnalyze: "/api/dsh-novel-forge/adapt/analyze",
|
|
1310
|
+
/** 改编模式:确认要改的维度 → 生成映射表/改编规则/联动影响清单。 */
|
|
1311
|
+
adaptPropose: "/api/dsh-novel-forge/adapt/propose",
|
|
1312
|
+
/** 改编模式:执行术语替换(全局替换 + 命中统计 + 改编文本预览)。 */
|
|
1313
|
+
adaptExecute: "/api/dsh-novel-forge/adapt/execute",
|
|
1314
|
+
/** 主题自定义背景:上传图片(POST,存盘并返回服务端 URL)。 */
|
|
1315
|
+
themeBackgroundUpload: "/api/dsh-novel-forge/theme/background",
|
|
1316
|
+
/** 主题自定义背景:读取已上传文件(GET prefix,/theme/background/<name>)。 */
|
|
1317
|
+
themeBackgroundGet: "/api/dsh-novel-forge/theme/background"
|
|
1318
|
+
};
|
|
1319
|
+
/** 预置的常见厂商(id=provider 路由;添加模型下拉兜底用,其余厂商由运行时目录动态补充)。 */
|
|
1320
|
+
const LLM_VENDORS = [
|
|
1321
|
+
{
|
|
1322
|
+
id: "deepseek-official",
|
|
1323
|
+
name: "DeepSeek",
|
|
1324
|
+
route: "deepseek-official",
|
|
1325
|
+
apiKeyEnv: "DEEPSEEK_API_KEY",
|
|
1326
|
+
defaultModel: "deepseek-v4-flash",
|
|
1327
|
+
models: [
|
|
1328
|
+
"deepseek-v4-flash",
|
|
1329
|
+
"deepseek-v4-pro",
|
|
1330
|
+
"deepseek-chat",
|
|
1331
|
+
"deepseek-reasoner"
|
|
1332
|
+
],
|
|
1333
|
+
builtin: true
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
id: "zai-coding-cn",
|
|
1337
|
+
name: "智谱 GLM",
|
|
1338
|
+
route: "zai-coding-cn",
|
|
1339
|
+
apiKeyEnv: "ZAI_CODING_CN_API_KEY",
|
|
1340
|
+
defaultModel: "glm-5.3-flash",
|
|
1341
|
+
models: [
|
|
1342
|
+
"glm-4.5-air",
|
|
1343
|
+
"glm-4.7",
|
|
1344
|
+
"glm-5-turbo",
|
|
1345
|
+
"glm-5.1",
|
|
1346
|
+
"glm-5.2",
|
|
1347
|
+
"glm-5.3",
|
|
1348
|
+
"glm-5.3-flash",
|
|
1349
|
+
"glm-5v-turbo"
|
|
1350
|
+
]
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
id: "qwen-token-plan-cn",
|
|
1354
|
+
name: "千问百炼",
|
|
1355
|
+
route: "qwen-token-plan-cn",
|
|
1356
|
+
apiKeyEnv: "QWEN_TOKEN_PLAN_CN_API_KEY",
|
|
1357
|
+
defaultModel: "qwen3.7-max",
|
|
1358
|
+
models: [
|
|
1359
|
+
"qwen3.6-flash",
|
|
1360
|
+
"qwen3.6-plus",
|
|
1361
|
+
"qwen3.7-max",
|
|
1362
|
+
"qwen3.7-plus",
|
|
1363
|
+
"qwen3.8-max",
|
|
1364
|
+
"qwen3.8-max-preview"
|
|
1365
|
+
]
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
id: "openrouter",
|
|
1369
|
+
name: "OpenRouter",
|
|
1370
|
+
route: "openrouter",
|
|
1371
|
+
apiKeyEnv: "OPENROUTER_API_KEY",
|
|
1372
|
+
defaultModel: "z-ai/glm-5.3-flash",
|
|
1373
|
+
models: [
|
|
1374
|
+
"z-ai/glm-4.6",
|
|
1375
|
+
"z-ai/glm-4.7",
|
|
1376
|
+
"z-ai/glm-5",
|
|
1377
|
+
"z-ai/glm-5-turbo",
|
|
1378
|
+
"z-ai/glm-5.1",
|
|
1379
|
+
"z-ai/glm-5.2",
|
|
1380
|
+
"z-ai/glm-5.3-flash",
|
|
1381
|
+
"auto",
|
|
1382
|
+
"deepseek/deepseek-v4-flash",
|
|
1383
|
+
"anthropic/claude-sonnet-4.6"
|
|
1384
|
+
]
|
|
1385
|
+
}
|
|
1386
|
+
];
|
|
1387
|
+
//#endregion
|
|
1189
1388
|
//#region src/engine.ts
|
|
1190
1389
|
/**
|
|
1191
1390
|
* Novel engine — the host half's core: LLM-driven story-bible extraction,
|
|
@@ -3630,6 +3829,225 @@ async function testImageEndpoint(baseURL, apiKey, model) {
|
|
|
3630
3829
|
};
|
|
3631
3830
|
}
|
|
3632
3831
|
}
|
|
3832
|
+
/** LLM 连通性失败的错误码 → 人话(供设置页“测试连通”回显)。 */
|
|
3833
|
+
const LLM_TEST_ERROR_HINT = {
|
|
3834
|
+
NO_ADAPTER: "提供商路由不存在或未启用",
|
|
3835
|
+
UNKNOWN_MODEL: "模型不在该提供商的目录里",
|
|
3836
|
+
MISSING_CREDENTIAL: "API Key 未配置(检查 DSH 凭据里的引用)",
|
|
3837
|
+
INVALID_CREDENTIAL: "API Key 格式无效",
|
|
3838
|
+
AUTH: "认证失败:API Key 无效或无权限",
|
|
3839
|
+
RATE_LIMIT: "触发提供商限流,请稍后再试",
|
|
3840
|
+
QUOTA: "配额/余额不足",
|
|
3841
|
+
CONTEXT_WINDOW_EXCEEDED: "上下文超限(测试调用不应触发,请核实模型配置)",
|
|
3842
|
+
EMPTY_RESPONSE: "端点连通但返回空响应(模型可能暂不可用)",
|
|
3843
|
+
TIMEOUT: "连接超时",
|
|
3844
|
+
ABORTED: "测试超时(30 秒无响应)",
|
|
3845
|
+
UNSUPPORTED_REASONING_EFFORT: "推理档位不受此模型支持"
|
|
3846
|
+
};
|
|
3847
|
+
function describeLlmTestError(err) {
|
|
3848
|
+
const code = typeof err.code === "string" ? err.code : "";
|
|
3849
|
+
const hint = code !== "" ? LLM_TEST_ERROR_HINT[code] : void 0;
|
|
3850
|
+
const detail = err.message !== "" ? err.message : "未知错误";
|
|
3851
|
+
return hint !== void 0 ? `${hint}(${detail})` : detail;
|
|
3852
|
+
}
|
|
3853
|
+
/** 对选中的提供商/模型发一次最小真实调用(maxTokens=16),验证 Key / 端点 / 模型可用。 */
|
|
3854
|
+
async function testLlmModel(ctx, provider, model) {
|
|
3855
|
+
const start = Date.now();
|
|
3856
|
+
const request = {
|
|
3857
|
+
provider,
|
|
3858
|
+
model,
|
|
3859
|
+
messages: [createUserMessage({
|
|
3860
|
+
content: [{
|
|
3861
|
+
type: "text",
|
|
3862
|
+
text: "只回复两个字:OK"
|
|
3863
|
+
}],
|
|
3864
|
+
source: {
|
|
3865
|
+
kind: "plugin",
|
|
3866
|
+
plugin: "dsh-novel-forge"
|
|
3867
|
+
}
|
|
3868
|
+
})],
|
|
3869
|
+
maxTokens: 16,
|
|
3870
|
+
temperature: 0
|
|
3871
|
+
};
|
|
3872
|
+
const controller = new AbortController();
|
|
3873
|
+
const timer = setTimeout(() => controller.abort(), 3e4);
|
|
3874
|
+
try {
|
|
3875
|
+
const assembler = new BlockAssembler();
|
|
3876
|
+
let sawBlock = false;
|
|
3877
|
+
for await (const chunk of ctx.llm.stream({
|
|
3878
|
+
...request,
|
|
3879
|
+
signal: controller.signal
|
|
3880
|
+
})) {
|
|
3881
|
+
assembler.push(chunk);
|
|
3882
|
+
if (chunk.type === "block-end") {
|
|
3883
|
+
sawBlock = true;
|
|
3884
|
+
break;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
if (sawBlock) return {
|
|
3888
|
+
ok: true,
|
|
3889
|
+
ms: Date.now() - start
|
|
3890
|
+
};
|
|
3891
|
+
const finish = assembler.finish;
|
|
3892
|
+
if (finish !== void 0 && (finish.kind === "error" || finish.kind === "aborted")) {
|
|
3893
|
+
const failure = finish.failure;
|
|
3894
|
+
throw Object.assign(new Error(failure.message), { code: failure.code });
|
|
3895
|
+
}
|
|
3896
|
+
return {
|
|
3897
|
+
ok: true,
|
|
3898
|
+
ms: Date.now() - start
|
|
3899
|
+
};
|
|
3900
|
+
} catch (error) {
|
|
3901
|
+
const err = error;
|
|
3902
|
+
return {
|
|
3903
|
+
ok: false,
|
|
3904
|
+
ms: Date.now() - start,
|
|
3905
|
+
code: err.code,
|
|
3906
|
+
message: describeLlmTestError(err)
|
|
3907
|
+
};
|
|
3908
|
+
} finally {
|
|
3909
|
+
clearTimeout(timer);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
/** 确保 pi-ai 的一条 provider 路由存在(settings seam 深度合并,保留已有字段)。 */
|
|
3913
|
+
async function ensurePiAiProvider(ctx, route, cfg) {
|
|
3914
|
+
const settings = ctx.get("settings");
|
|
3915
|
+
if (settings === void 0) throw new Error("DSH settings 服务不可用,无法注册路由");
|
|
3916
|
+
await settings.update("llm-pi-ai", { providers: { [route]: cfg } });
|
|
3917
|
+
}
|
|
3918
|
+
/** 运行时厂商目录:DSH pi-ai 可配置提供方 + 内置适配器,作为「添加模型」下拉。 */
|
|
3919
|
+
async function listLlmVendors(ctx) {
|
|
3920
|
+
const map = /* @__PURE__ */ new Map();
|
|
3921
|
+
for (const v of LLM_VENDORS) map.set(v.route, {
|
|
3922
|
+
id: v.route,
|
|
3923
|
+
name: v.name,
|
|
3924
|
+
models: v.models,
|
|
3925
|
+
apiKeyEnv: v.apiKeyEnv,
|
|
3926
|
+
builtin: v.builtin
|
|
3927
|
+
});
|
|
3928
|
+
try {
|
|
3929
|
+
for (const p of ctx.llm.listConfigurableProviders()) if (!map.has(p.provider)) map.set(p.provider, {
|
|
3930
|
+
id: p.provider,
|
|
3931
|
+
name: p.displayName !== "" ? p.displayName : p.provider,
|
|
3932
|
+
models: [],
|
|
3933
|
+
apiKeyEnv: "PI_AI_" + p.provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_") + "_API_KEY"
|
|
3934
|
+
});
|
|
3935
|
+
} catch {}
|
|
3936
|
+
try {
|
|
3937
|
+
for (const p of ctx.llm.listProviders()) if (!map.has(p.id)) map.set(p.id, {
|
|
3938
|
+
id: p.id,
|
|
3939
|
+
name: p.name !== "" && p.name !== p.id ? p.name : p.id,
|
|
3940
|
+
models: [],
|
|
3941
|
+
builtin: true
|
|
3942
|
+
});
|
|
3943
|
+
} catch {}
|
|
3944
|
+
return { vendors: [...map.values()] };
|
|
3945
|
+
}
|
|
3946
|
+
/** 查询某个 provider 当前可用模型(添加成功后可即时刷新下拉)。 */
|
|
3947
|
+
async function listLlmModels(ctx, provider) {
|
|
3948
|
+
if (provider.trim() === "") return { models: [] };
|
|
3949
|
+
try {
|
|
3950
|
+
return { models: (await ctx.llm.listModels(provider.trim())).map((m) => ({
|
|
3951
|
+
id: m.id,
|
|
3952
|
+
name: m.name
|
|
3953
|
+
})) };
|
|
3954
|
+
} catch {
|
|
3955
|
+
return { models: [] };
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
/** 当前已注册的提供方路由列表(提供方管理卡片)。 */
|
|
3959
|
+
async function listLlmProviders(ctx) {
|
|
3960
|
+
try {
|
|
3961
|
+
return { providers: ctx.llm.listProviders().map((p) => ({
|
|
3962
|
+
id: p.id,
|
|
3963
|
+
name: p.name !== "" && p.name !== p.id ? p.name : p.id
|
|
3964
|
+
})) };
|
|
3965
|
+
} catch {
|
|
3966
|
+
return { providers: [{
|
|
3967
|
+
id: "deepseek-official",
|
|
3968
|
+
name: "DeepSeek"
|
|
3969
|
+
}] };
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
/** 移除一个提供方:unset 凭据 ref + 移除 llm-pi-ai providers 路由。 */
|
|
3973
|
+
async function removeLlmProvider(ctx, req) {
|
|
3974
|
+
const provider = (req.provider ?? "").trim();
|
|
3975
|
+
if (provider === "") throw new Error("缺少 provider");
|
|
3976
|
+
if (provider === "deepseek-official") throw new Error("内置 DeepSeek 提供方不可删除");
|
|
3977
|
+
const creds = ctx.get("credentials");
|
|
3978
|
+
if (creds?.unset !== void 0 && req.apiKeyEnv !== void 0 && req.apiKeyEnv.trim() !== "") await creds.unset(req.apiKeyEnv.trim());
|
|
3979
|
+
const settings = ctx.get("settings");
|
|
3980
|
+
if (settings?.mutate !== void 0) await settings.mutate("llm-pi-ai", [{
|
|
3981
|
+
op: "unset",
|
|
3982
|
+
path: ["providers", provider]
|
|
3983
|
+
}]);
|
|
3984
|
+
return {
|
|
3985
|
+
ok: true,
|
|
3986
|
+
message: "已移除提供方 " + provider
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* 添加模型(DSH 同款体验):厂商直填 API Key,或自定义 OpenAI 兼容路由。
|
|
3991
|
+
* 写入 DSH 凭据 refs,并(必要时)注册/更新 llm-pi-ai provider 路由。
|
|
3992
|
+
*/
|
|
3993
|
+
async function registerLlmModel(ctx, req) {
|
|
3994
|
+
const apiKey = req.apiKey?.trim() ?? "";
|
|
3995
|
+
const model = req.model?.trim() ?? "";
|
|
3996
|
+
if (apiKey === "") throw new Error("API Key 不能为空");
|
|
3997
|
+
if (model === "") throw new Error("模型 id 不能为空");
|
|
3998
|
+
const creds = ctx.get("credentials");
|
|
3999
|
+
if (creds === void 0) throw new Error("DSH credentials 服务不可用,无法写入 API Key");
|
|
4000
|
+
let route;
|
|
4001
|
+
let env;
|
|
4002
|
+
let displayName;
|
|
4003
|
+
let message;
|
|
4004
|
+
if (req.mode === "vendor") {
|
|
4005
|
+
const vendorId = (req.vendor ?? "").trim();
|
|
4006
|
+
if (vendorId === "") throw new Error("请选择厂商");
|
|
4007
|
+
const v = LLM_VENDORS.find((x) => x.route === vendorId);
|
|
4008
|
+
route = vendorId;
|
|
4009
|
+
env = req.apiKeyEnv?.trim() || v?.apiKeyEnv || "PI_AI_" + vendorId.toUpperCase().replace(/[^A-Z0-9]+/g, "_") + "_API_KEY";
|
|
4010
|
+
displayName = v?.name ?? vendorId;
|
|
4011
|
+
const isBuiltin = v?.builtin === true || vendorId === "deepseek-official";
|
|
4012
|
+
await creds.set(env, apiKey);
|
|
4013
|
+
if (isBuiltin) message = "已写入 DSH 凭据(" + env + ")";
|
|
4014
|
+
else {
|
|
4015
|
+
await ensurePiAiProvider(ctx, route, { apiKeyEnv: env });
|
|
4016
|
+
message = "已写入 DSH 凭据并注册路由 " + route;
|
|
4017
|
+
}
|
|
4018
|
+
} else {
|
|
4019
|
+
route = (req.provider ?? "").trim();
|
|
4020
|
+
const baseURL = (req.baseURL ?? "").trim();
|
|
4021
|
+
if (route === "") throw new Error("自定义模式需填提供商路由 id");
|
|
4022
|
+
if (baseURL === "") throw new Error("自定义模式需填接口地址 (baseURL)");
|
|
4023
|
+
env = "NOVEL_CUSTOM_" + route.toUpperCase().replace(/[^A-Z0-9]+/g, "_") + "_API_KEY";
|
|
4024
|
+
displayName = req.name?.trim() || "Custom " + route;
|
|
4025
|
+
await creds.set(env, apiKey);
|
|
4026
|
+
await ensurePiAiProvider(ctx, route, {
|
|
4027
|
+
displayName,
|
|
4028
|
+
apiKeyEnv: env,
|
|
4029
|
+
api: "openai-completions",
|
|
4030
|
+
baseURL,
|
|
4031
|
+
models: [{ id: model }],
|
|
4032
|
+
compat: {
|
|
4033
|
+
supportsDeveloperRole: false,
|
|
4034
|
+
maxTokensField: "max_tokens"
|
|
4035
|
+
}
|
|
4036
|
+
});
|
|
4037
|
+
message = "已写入 DSH 凭据并注册路由 " + route;
|
|
4038
|
+
}
|
|
4039
|
+
return {
|
|
4040
|
+
ok: true,
|
|
4041
|
+
saved: {
|
|
4042
|
+
id: "saved-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 6),
|
|
4043
|
+
name: req.name?.trim() || displayName + " · " + model,
|
|
4044
|
+
provider: route,
|
|
4045
|
+
model
|
|
4046
|
+
},
|
|
4047
|
+
provider: route,
|
|
4048
|
+
message
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
3633
4051
|
/** 底层:按形象锚点调用生图接口生成定妆图(不写库;写库由上层调用方负责)。 */
|
|
3634
4052
|
async function generateReferenceImageFrom(config, anchor, style = "", modelId) {
|
|
3635
4053
|
if (config.imageApiEnabled !== true) throw new Error("生图未启用,请在设置中启用一个生图模型");
|
|
@@ -5660,125 +6078,6 @@ var ProductionRunner = class {
|
|
|
5660
6078
|
}
|
|
5661
6079
|
};
|
|
5662
6080
|
//#endregion
|
|
5663
|
-
//#region src/protocol.ts
|
|
5664
|
-
/** The /api/dsh-novel-forge route family (same-origin, loopback-fenced). */
|
|
5665
|
-
const NOVEL_API = {
|
|
5666
|
-
status: "/api/dsh-novel-forge/status",
|
|
5667
|
-
loadOutline: "/api/dsh-novel-forge/load-outline",
|
|
5668
|
-
saveOutline: "/api/dsh-novel-forge/save-outline",
|
|
5669
|
-
plan: "/api/dsh-novel-forge/plan",
|
|
5670
|
-
volumes: "/api/dsh-novel-forge/volumes",
|
|
5671
|
-
bible: "/api/dsh-novel-forge/bible",
|
|
5672
|
-
assets: "/api/dsh-novel-forge/assets",
|
|
5673
|
-
styleEngine: "/api/dsh-novel-forge/style-engine",
|
|
5674
|
-
generate: "/api/dsh-novel-forge/generate",
|
|
5675
|
-
review: "/api/dsh-novel-forge/review",
|
|
5676
|
-
rewrite: "/api/dsh-novel-forge/rewrite",
|
|
5677
|
-
polish: "/api/dsh-novel-forge/polish",
|
|
5678
|
-
/** 采纳待确认草稿(润色/重写产物)覆盖正文文件。 */
|
|
5679
|
-
draftApply: "/api/dsh-novel-forge/draft/apply",
|
|
5680
|
-
/** 放弃待确认草稿,保留原稿。 */
|
|
5681
|
-
draftDiscard: "/api/dsh-novel-forge/draft/discard",
|
|
5682
|
-
summary: "/api/dsh-novel-forge/summary",
|
|
5683
|
-
foreshadow: "/api/dsh-novel-forge/foreshadow",
|
|
5684
|
-
exportBook: "/api/dsh-novel-forge/export",
|
|
5685
|
-
chapter: "/api/dsh-novel-forge/chapter",
|
|
5686
|
-
/** 审查任意正文文本(作者手动编辑后,不落盘)。 */
|
|
5687
|
-
chapterCheck: "/api/dsh-novel-forge/chapter/check",
|
|
5688
|
-
/** 保存手动编辑的正文(自动备份 .bak)。 */
|
|
5689
|
-
chapterSave: "/api/dsh-novel-forge/chapter/save",
|
|
5690
|
-
assistant: "/api/dsh-novel-forge/assistant",
|
|
5691
|
-
assistantHistory: "/api/dsh-novel-forge/assistant-history",
|
|
5692
|
-
/** 清空助手对话记录。 */
|
|
5693
|
-
assistantClear: "/api/dsh-novel-forge/assistant/clear",
|
|
5694
|
-
bookshelf: "/api/dsh-novel-forge/bookshelf",
|
|
5695
|
-
/** 导入已有项目目录(含 novel-project.json)到书架。 */
|
|
5696
|
-
bookshelfImportDir: "/api/dsh-novel-forge/bookshelf/import-dir",
|
|
5697
|
-
/** 导入 txt/md 全本:拆章建项目并登记书架。 */
|
|
5698
|
-
bookshelfImportText: "/api/dsh-novel-forge/bookshelf/import-text",
|
|
5699
|
-
/** 导入 txt/md 全本:拆章预览(不落盘)。 */
|
|
5700
|
-
bookshelfImportTextPreview: "/api/dsh-novel-forge/bookshelf/import-text/preview",
|
|
5701
|
-
/** 漫剧方案管理:create/remove/activate。 */
|
|
5702
|
-
manhuaPlans: "/api/dsh-novel-forge/manhua/plans",
|
|
5703
|
-
/** 漫剧角色库:从分镜提名(规则+LLM两段式)/ 建卡 / 更新 / 删除 / 形象锚点 / 精修提示词。 */
|
|
5704
|
-
mangaRoles: "/api/dsh-novel-forge/manga/roles",
|
|
5705
|
-
/** 分镜·编剧级:单章 → 剧情骨架(节拍链)。 */
|
|
5706
|
-
storyboardSkeleton: "/api/dsh-novel-forge/storyboard/skeleton",
|
|
5707
|
-
/** 分镜·导演级:骨架 → 分镜表(镜头级)。 */
|
|
5708
|
-
storyboardTable: "/api/dsh-novel-forge/storyboard/table",
|
|
5709
|
-
/** 分镜·提示词级:分镜表 → 即梦可粘贴视频提示词。 */
|
|
5710
|
-
storyboardPrompts: "/api/dsh-novel-forge/storyboard/prompts",
|
|
5711
|
-
/** 生图接口连通性测试(设置页每个模型条目用)。 */
|
|
5712
|
-
imageTest: "/api/dsh-novel-forge/image-test",
|
|
5713
|
-
/** 重置项目(可选携带新大纲):清空设定/卷/章节/伏笔/资产/事实库。 */
|
|
5714
|
-
reset: "/api/dsh-novel-forge/reset",
|
|
5715
|
-
/** 全书一致性质检:LLM 扫描已生成章节,输出矛盾问题清单。 */
|
|
5716
|
-
audit: "/api/dsh-novel-forge/audit",
|
|
5717
|
-
/** 角色卡刷新:基于事实库与各章摘要聚合角色当前状态。 */
|
|
5718
|
-
charactersRefresh: "/api/dsh-novel-forge/characters/refresh",
|
|
5719
|
-
/** 事实库回填:对历史已生成章节批量抽取事实(旧章节无事实记录时用)。 */
|
|
5720
|
-
factsBackfill: "/api/dsh-novel-forge/facts/backfill",
|
|
5721
|
-
/** 设定圣经局部修补(如世界观规则编辑)。 */
|
|
5722
|
-
biblePatch: "/api/dsh-novel-forge/bible/patch",
|
|
5723
|
-
/** 小说简介:生成(AI)/补全(AI)/保存。 */
|
|
5724
|
-
blurb: "/api/dsh-novel-forge/blurb",
|
|
5725
|
-
/** 重命名当前书(同步项目与书架条目)。 */
|
|
5726
|
-
rename: "/api/dsh-novel-forge/rename",
|
|
5727
|
-
/** 大世界:AI 提炼 / 保存结构化数据(境界/区域/势力)。 */
|
|
5728
|
-
world: "/api/dsh-novel-forge/world",
|
|
5729
|
-
/** 封面:GET 读取(dataUrl)/ POST 上传或移除。 */
|
|
5730
|
-
cover: "/api/dsh-novel-forge/blurb/cover",
|
|
5731
|
-
/** 剧情线管理:增删改 + 关联章节。 */
|
|
5732
|
-
plotlines: "/api/dsh-novel-forge/plotlines",
|
|
5733
|
-
/** 角色库:AI 提炼 / 采纳 / 更新 / 删除。 */
|
|
5734
|
-
roles: "/api/dsh-novel-forge/roles",
|
|
5735
|
-
scenes: "/api/dsh-novel-forge/scenes",
|
|
5736
|
-
visualRules: "/api/dsh-novel-forge/visual-rules",
|
|
5737
|
-
/** 作者复盘补跑:对已写章节补齐 authorReview(全书流式 / 单章 JSON)。 */
|
|
5738
|
-
reviewBackfill: "/api/dsh-novel-forge/review/backfill",
|
|
5739
|
-
/** 章节复位:generating 卡死 → pending(可重新生成)。 */
|
|
5740
|
-
chapterReset: "/api/dsh-novel-forge/chapter/reset",
|
|
5741
|
-
/** 章节直接通过:作者对 rejected/written 章节行使最终决定权。 */
|
|
5742
|
-
chapterApprove: "/api/dsh-novel-forge/chapter/approve",
|
|
5743
|
-
/** 敏感词检查:全书已写章节或指定文本。 */
|
|
5744
|
-
sensitiveCheck: "/api/dsh-novel-forge/sensitive-check",
|
|
5745
|
-
/** 开书想法 → AI 补全大纲:输入一句话想法,生成 2-3 个可选大纲方案。 */
|
|
5746
|
-
outlineSuggest: "/api/dsh-novel-forge/outline/suggest",
|
|
5747
|
-
/** 反推大纲:从已写章节正文反向生成全书总纲(NDJSON 流)。 */
|
|
5748
|
-
outlineReverse: "/api/dsh-novel-forge/outline/reverse",
|
|
5749
|
-
/** 拆书分析:对已写章节做结构/人物/文风/卖点四维体检(两阶段:源笔记→分节分析)。 */
|
|
5750
|
-
breakdown: "/api/dsh-novel-forge/breakdown",
|
|
5751
|
-
/** 漫剧分镜生成:章节 → 角色锚点 + 分镜表(可适配豆包/Seedance/SD)。 */
|
|
5752
|
-
/** 漫剧分集计划:读一卷 → 按故事弧线分集(高潮拆集/过渡并章)。 */
|
|
5753
|
-
/** 漫画脚本:章节 → 分页分格漫画脚本(含角色视觉锚点)。 */
|
|
5754
|
-
/** 生产单:启动批量生产(计划补足 + 逐章生成 + 被拒分级处理)。 */
|
|
5755
|
-
runStart: "/api/dsh-novel-forge/run/start",
|
|
5756
|
-
/** 生产单控制:pause / resume / stop。 */
|
|
5757
|
-
runControl: "/api/dsh-novel-forge/run/control",
|
|
5758
|
-
/** 生产单状态(含进度统计与日志)。 */
|
|
5759
|
-
runStatus: "/api/dsh-novel-forge/run/status",
|
|
5760
|
-
config: "/api/dsh-novel-forge/config",
|
|
5761
|
-
openFolder: "/api/dsh-novel-forge/open-folder",
|
|
5762
|
-
/** 作者资产库/总数据:读取个人跨书资产(笔法/红线/套路/角色模板/世界观模板)。 */
|
|
5763
|
-
authorAssets: "/api/dsh-novel-forge/author-assets",
|
|
5764
|
-
/** 作者资产库:新增/更新一条资产(upsert by id)。 */
|
|
5765
|
-
authorAssetsUpsert: "/api/dsh-novel-forge/author-assets/upsert",
|
|
5766
|
-
/** 作者资产库:删除一条资产。 */
|
|
5767
|
-
authorAssetsRemove: "/api/dsh-novel-forge/author-assets/remove",
|
|
5768
|
-
/** 作者资产库:导入默认(书架书的写作资产/角色 + 内置全局库)批量沉淀。 */
|
|
5769
|
-
authorAssetsImportDefault: "/api/dsh-novel-forge/author-assets/import-default",
|
|
5770
|
-
/** 改编模式:上传全文 → 分析 → 原文设定卡片/可改范围矩阵。 */
|
|
5771
|
-
adaptAnalyze: "/api/dsh-novel-forge/adapt/analyze",
|
|
5772
|
-
/** 改编模式:确认要改的维度 → 生成映射表/改编规则/联动影响清单。 */
|
|
5773
|
-
adaptPropose: "/api/dsh-novel-forge/adapt/propose",
|
|
5774
|
-
/** 改编模式:执行术语替换(全局替换 + 命中统计 + 改编文本预览)。 */
|
|
5775
|
-
adaptExecute: "/api/dsh-novel-forge/adapt/execute",
|
|
5776
|
-
/** 主题自定义背景:上传图片(POST,存盘并返回服务端 URL)。 */
|
|
5777
|
-
themeBackgroundUpload: "/api/dsh-novel-forge/theme/background",
|
|
5778
|
-
/** 主题自定义背景:读取已上传文件(GET prefix,/theme/background/<name>)。 */
|
|
5779
|
-
themeBackgroundGet: "/api/dsh-novel-forge/theme/background"
|
|
5780
|
-
};
|
|
5781
|
-
//#endregion
|
|
5782
6081
|
//#region src/docx.ts
|
|
5783
6082
|
/**
|
|
5784
6083
|
* docx outline extraction: a .docx is a zip whose word/document.xml holds the
|
|
@@ -8917,6 +9216,99 @@ function makeRoutes(deps) {
|
|
|
8917
9216
|
}
|
|
8918
9217
|
}
|
|
8919
9218
|
};
|
|
9219
|
+
/** LLM 连通性测试:对选中的提供商/模型发一次最小真实调用。 */
|
|
9220
|
+
const llmTestRoute = {
|
|
9221
|
+
kind: "exact",
|
|
9222
|
+
path: NOVEL_API.llmTest,
|
|
9223
|
+
handler: async (req, res) => {
|
|
9224
|
+
if (!guard(req, res, "POST")) return;
|
|
9225
|
+
const body = await readJsonBody(req);
|
|
9226
|
+
const provider = (body?.provider ?? "").trim();
|
|
9227
|
+
const model = (body?.model ?? "").trim();
|
|
9228
|
+
if (provider === "" || model === "") {
|
|
9229
|
+
writeJson(res, 400, { error: "provider 与 model 必填" });
|
|
9230
|
+
return;
|
|
9231
|
+
}
|
|
9232
|
+
writeJson(res, 200, await testLlmModel(ctx, provider, model));
|
|
9233
|
+
}
|
|
9234
|
+
};
|
|
9235
|
+
/** 添加模型:厂商直填 API Key,或自定义 OpenAI 兼容路由(写 DSH 凭据 + router)。 */
|
|
9236
|
+
const addModelRoute = {
|
|
9237
|
+
kind: "exact",
|
|
9238
|
+
path: NOVEL_API.addModel,
|
|
9239
|
+
handler: async (req, res) => {
|
|
9240
|
+
if (!guard(req, res, "POST")) return;
|
|
9241
|
+
const body = await readJsonBody(req);
|
|
9242
|
+
if (body === void 0) {
|
|
9243
|
+
writeJson(res, 400, { error: "缺少请求体" });
|
|
9244
|
+
return;
|
|
9245
|
+
}
|
|
9246
|
+
try {
|
|
9247
|
+
writeJson(res, 200, await registerLlmModel(ctx, body));
|
|
9248
|
+
} catch (error) {
|
|
9249
|
+
writeJson(res, 500, { error: `添加模型失败:${error.message}` });
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
};
|
|
9253
|
+
/** 运行时厂商目录(DSH pi-ai 可配置提供方 + 内置适配器)。 */
|
|
9254
|
+
const llmVendorsRoute = {
|
|
9255
|
+
kind: "exact",
|
|
9256
|
+
path: NOVEL_API.llmVendors,
|
|
9257
|
+
handler: async (req, res) => {
|
|
9258
|
+
if (!guard(req, res, "GET")) return;
|
|
9259
|
+
try {
|
|
9260
|
+
writeJson(res, 200, await listLlmVendors(ctx));
|
|
9261
|
+
} catch (error) {
|
|
9262
|
+
writeJson(res, 500, { error: `读取厂商目录失败:${error.message}` });
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
};
|
|
9266
|
+
/** 查询某个 provider 当前可用的模型(添加成功后可即时刷新下拉)。 */
|
|
9267
|
+
const llmModelsRoute = {
|
|
9268
|
+
kind: "exact",
|
|
9269
|
+
path: NOVEL_API.llmModels,
|
|
9270
|
+
handler: async (req, res) => {
|
|
9271
|
+
if (!guard(req, res, "GET")) return;
|
|
9272
|
+
const url = new URL(req.url ?? "", "http://localhost");
|
|
9273
|
+
const provider = decodeURIComponent((url.searchParams.get("provider") ?? "").trim());
|
|
9274
|
+
try {
|
|
9275
|
+
writeJson(res, 200, await listLlmModels(ctx, provider));
|
|
9276
|
+
} catch (error) {
|
|
9277
|
+
writeJson(res, 500, { error: `读取模型列表失败:${error.message}` });
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
};
|
|
9281
|
+
/** 已注册的提供方路由列表(提供方管理卡片)。 */
|
|
9282
|
+
const llmProvidersRoute = {
|
|
9283
|
+
kind: "exact",
|
|
9284
|
+
path: NOVEL_API.llmProviders,
|
|
9285
|
+
handler: async (req, res) => {
|
|
9286
|
+
if (!guard(req, res, "GET")) return;
|
|
9287
|
+
try {
|
|
9288
|
+
writeJson(res, 200, await listLlmProviders(ctx));
|
|
9289
|
+
} catch (error) {
|
|
9290
|
+
writeJson(res, 500, { error: `读取提供方列表失败:${error.message}` });
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
};
|
|
9294
|
+
/** 移除一个提供方(unset key + 移除 llm-pi-ai 路由)。 */
|
|
9295
|
+
const llmRemoveRoute = {
|
|
9296
|
+
kind: "exact",
|
|
9297
|
+
path: NOVEL_API.llmRemove,
|
|
9298
|
+
handler: async (req, res) => {
|
|
9299
|
+
if (!guard(req, res, "POST")) return;
|
|
9300
|
+
const body = await readJsonBody(req);
|
|
9301
|
+
if (body === void 0) {
|
|
9302
|
+
writeJson(res, 400, { error: "缺少请求体" });
|
|
9303
|
+
return;
|
|
9304
|
+
}
|
|
9305
|
+
try {
|
|
9306
|
+
writeJson(res, 200, await removeLlmProvider(ctx, body));
|
|
9307
|
+
} catch (error) {
|
|
9308
|
+
writeJson(res, 500, { error: `移除提供方失败:${error.message}` });
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
};
|
|
8920
9312
|
/** 分镜·导演级:骨架 → 分镜表(镜头级)。 */
|
|
8921
9313
|
const storyboardTableRoute = {
|
|
8922
9314
|
kind: "exact",
|
|
@@ -9408,6 +9800,12 @@ function makeRoutes(deps) {
|
|
|
9408
9800
|
styleImageRoute,
|
|
9409
9801
|
storyboardSkeletonRoute,
|
|
9410
9802
|
imageTestRoute,
|
|
9803
|
+
llmTestRoute,
|
|
9804
|
+
addModelRoute,
|
|
9805
|
+
llmVendorsRoute,
|
|
9806
|
+
llmModelsRoute,
|
|
9807
|
+
llmProvidersRoute,
|
|
9808
|
+
llmRemoveRoute,
|
|
9411
9809
|
storyboardTableRoute,
|
|
9412
9810
|
storyboardPromptsRoute,
|
|
9413
9811
|
breakdownRoute,
|
|
@@ -9537,7 +9935,7 @@ const Config = z.object({
|
|
|
9537
9935
|
themeBackground: z.string().default(""),
|
|
9538
9936
|
themeBackgroundBlur: z.number().default(0),
|
|
9539
9937
|
themeOpacity: z.number().default(100),
|
|
9540
|
-
enableAdaptMode: z.boolean().default(
|
|
9938
|
+
enableAdaptMode: z.boolean().default(true)
|
|
9541
9939
|
});
|
|
9542
9940
|
/** Schema defaults, re-read for hand-built test contexts. */
|
|
9543
9941
|
const DEFAULT_ANNOUNCE = true;
|
|
@@ -9594,7 +9992,7 @@ function resolveConfig(value) {
|
|
|
9594
9992
|
themeBackground: value?.themeBackground ?? "",
|
|
9595
9993
|
themeBackgroundBlur: value?.themeBackgroundBlur ?? 0,
|
|
9596
9994
|
themeOpacity: value?.themeOpacity ?? 100,
|
|
9597
|
-
enableAdaptMode: value?.enableAdaptMode ??
|
|
9995
|
+
enableAdaptMode: value?.enableAdaptMode ?? true
|
|
9598
9996
|
};
|
|
9599
9997
|
}
|
|
9600
9998
|
/** 生图默认接口地址(豆包 ark,OpenAI 兼容)。 */
|