@waterwx/dsh-novel-forge 1.6.0 → 1.7.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 +35487 -745
- package/lib/client.js.map +1 -1
- package/lib/index.js +111 -12
- package/lib/index.js.map +1 -1
- package/lib/types/client/api.d.ts +1 -0
- package/lib/types/client/panel/MangaWorkspace.d.ts +6 -4
- package/lib/types/client/panel/RoleVisualPanel.d.ts +5 -3
- package/lib/types/client/panel/SceneLibrary.d.ts +1 -1
- package/lib/types/client/panel/StoryboardTab.d.ts +1 -1
- package/lib/types/engine.d.ts +8 -1
- package/lib/types/index.d.ts +6 -4
- package/lib/types/protocol.d.ts +47 -3
- package/package.json +99 -98
- package/src/client/api.ts +4 -0
- package/src/client/panel/MangaWorkspace.tsx +7 -3
- package/src/client/panel/NovelPanel.tsx +389 -231
- package/src/client/panel/RoleVisualPanel.tsx +23 -6
- package/src/client/panel/RunPanel.tsx +1 -1
- package/src/client/panel/SceneLibrary.tsx +1 -1
- package/src/client/panel/ShelfView.tsx +28 -35
- package/src/client/panel/StoryboardTab.tsx +1 -1
- package/src/client/panel/panel.module.css +407 -23
- package/src/engine.ts +48 -8
- package/src/index.ts +37 -6
- package/src/protocol.ts +50 -3
- package/src/routes.ts +26 -1
package/lib/index.js
CHANGED
|
@@ -2948,14 +2948,66 @@ async function breakdownBook(ctx, config, project, outputDir, scope = "recent",
|
|
|
2948
2948
|
usedTokens
|
|
2949
2949
|
};
|
|
2950
2950
|
}
|
|
2951
|
-
|
|
2951
|
+
/** 生图接口连通性测试(设置页模型条目用):GET {baseURL}/models,计时返回延迟。 */
|
|
2952
|
+
async function testImageEndpoint(baseURL, apiKey, model) {
|
|
2953
|
+
const trimmed = baseURL.trim();
|
|
2954
|
+
if (trimmed === "") return {
|
|
2955
|
+
ok: false,
|
|
2956
|
+
ms: 0,
|
|
2957
|
+
message: "接口地址不能为空"
|
|
2958
|
+
};
|
|
2959
|
+
const url = trimmed.replace(/\/+$/, "") + "/models";
|
|
2960
|
+
const start = Date.now();
|
|
2961
|
+
try {
|
|
2962
|
+
const res = await fetch(url, {
|
|
2963
|
+
method: "GET",
|
|
2964
|
+
headers: {
|
|
2965
|
+
Authorization: "Bearer " + apiKey,
|
|
2966
|
+
"Content-Type": "application/json"
|
|
2967
|
+
}
|
|
2968
|
+
});
|
|
2969
|
+
const ms = Date.now() - start;
|
|
2970
|
+
if (!res.ok) {
|
|
2971
|
+
const text = (await res.text().catch(() => "")).slice(0, 200);
|
|
2972
|
+
return {
|
|
2973
|
+
ok: false,
|
|
2974
|
+
ms,
|
|
2975
|
+
message: "HTTP " + res.status + (text !== "" ? ":" + text : "")
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
let modelFound;
|
|
2979
|
+
if (model !== void 0 && model !== "") {
|
|
2980
|
+
const list = (await res.json().catch(() => null))?.data;
|
|
2981
|
+
if (Array.isArray(list)) modelFound = list.some((m) => {
|
|
2982
|
+
if (typeof m !== "object" || m === null) return false;
|
|
2983
|
+
const o = m;
|
|
2984
|
+
return o.id === model || o.model === model;
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
return {
|
|
2988
|
+
ok: true,
|
|
2989
|
+
ms,
|
|
2990
|
+
modelFound
|
|
2991
|
+
};
|
|
2992
|
+
} catch (err) {
|
|
2993
|
+
return {
|
|
2994
|
+
ok: false,
|
|
2995
|
+
ms: Date.now() - start,
|
|
2996
|
+
message: err.message
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
async function generateRoleReferenceImage(ctx, config, project, outputDir, name, style = "", modelId) {
|
|
2952
3001
|
const role = project.roles?.find((r) => r.name === name);
|
|
2953
3002
|
if (role === void 0) throw new Error(`角色 ${name} 不存在`);
|
|
2954
3003
|
if (role.imagePrompt === void 0) throw new Error(`角色 ${name} 还没有形象锚点,请先生成锚点`);
|
|
2955
|
-
if (config.imageApiEnabled !== true) throw new Error("
|
|
2956
|
-
const
|
|
2957
|
-
const
|
|
2958
|
-
|
|
3004
|
+
if (config.imageApiEnabled !== true) throw new Error("生图未启用,请在设置中启用一个生图模型");
|
|
3005
|
+
const activeImage = (modelId !== void 0 && modelId !== "" ? (config.imageModels ?? []).find((m) => m.id === modelId) : void 0) ?? (config.imageModels ?? []).find((m) => m.enabled) ?? (config.imageModels ?? [])[0];
|
|
3006
|
+
const apiKey = activeImage?.apiKey ?? config.imageApiKey;
|
|
3007
|
+
const model = activeImage?.model ?? config.imageApiModel;
|
|
3008
|
+
const baseURL = (activeImage?.baseURL ?? config.imageBaseUrl ?? "").replace(/\/+$/, "");
|
|
3009
|
+
if (baseURL === "") throw new Error("请先在设置中填写生图接口地址");
|
|
3010
|
+
if (apiKey === void 0 || apiKey === "" || model === void 0 || model === "") throw new Error("请先在设置中配置生图模型的 API Key 和模型 id");
|
|
2959
3011
|
const stylePrompt = getComicStylePrompt(style);
|
|
2960
3012
|
const prompt = [
|
|
2961
3013
|
role.imagePrompt.zh,
|
|
@@ -2964,7 +3016,7 @@ async function generateRoleReferenceImage(ctx, config, project, outputDir, name,
|
|
|
2964
3016
|
stylePrompt,
|
|
2965
3017
|
"character reference sheet, front view, clean background, consistent design"
|
|
2966
3018
|
].filter(Boolean).join(", ");
|
|
2967
|
-
const res = await fetch("
|
|
3019
|
+
const res = await fetch(baseURL + "/images/generations", {
|
|
2968
3020
|
method: "POST",
|
|
2969
3021
|
headers: {
|
|
2970
3022
|
"Content-Type": "application/json",
|
|
@@ -2981,12 +3033,12 @@ async function generateRoleReferenceImage(ctx, config, project, outputDir, name,
|
|
|
2981
3033
|
});
|
|
2982
3034
|
if (!res.ok) {
|
|
2983
3035
|
const text = await res.text();
|
|
2984
|
-
throw new Error(
|
|
3036
|
+
throw new Error(`生图失败(${res.status}):${text}`);
|
|
2985
3037
|
}
|
|
2986
3038
|
const url = (await res.json()).data?.[0]?.url;
|
|
2987
|
-
if (url === void 0 || url === "") throw new Error("
|
|
3039
|
+
if (url === void 0 || url === "") throw new Error("生图接口未返回图片 URL");
|
|
2988
3040
|
const imgRes = await fetch(url);
|
|
2989
|
-
if (!imgRes.ok) throw new Error("
|
|
3041
|
+
if (!imgRes.ok) throw new Error("下载生图结果失败");
|
|
2990
3042
|
const buf = Buffer.from(await imgRes.arrayBuffer());
|
|
2991
3043
|
const dataUrl = `data:${imgRes.headers.get("content-type") || "image/jpeg"};base64,${buf.toString("base64")}`;
|
|
2992
3044
|
role.imageUrl = dataUrl;
|
|
@@ -4729,6 +4781,8 @@ const NOVEL_API = {
|
|
|
4729
4781
|
storyboardTable: "/api/dsh-novel-forge/storyboard/table",
|
|
4730
4782
|
/** 分镜·提示词级:分镜表 → 即梦可粘贴视频提示词。 */
|
|
4731
4783
|
storyboardPrompts: "/api/dsh-novel-forge/storyboard/prompts",
|
|
4784
|
+
/** 生图接口连通性测试(设置页每个模型条目用)。 */
|
|
4785
|
+
imageTest: "/api/dsh-novel-forge/image-test",
|
|
4732
4786
|
/** 重置项目(可选携带新大纲):清空设定/卷/章节/伏笔/资产/事实库。 */
|
|
4733
4787
|
reset: "/api/dsh-novel-forge/reset",
|
|
4734
4788
|
/** 全书一致性质检:LLM 扫描已生成章节,输出矛盾问题清单。 */
|
|
@@ -7186,7 +7240,7 @@ function makeRoutes(deps) {
|
|
|
7186
7240
|
return;
|
|
7187
7241
|
}
|
|
7188
7242
|
try {
|
|
7189
|
-
const imageUrl = await generateRoleReferenceImage(ctx, config, project, config.outputDir, name, body?.style ?? "");
|
|
7243
|
+
const imageUrl = await generateRoleReferenceImage(ctx, config, project, config.outputDir, name, body?.style ?? "", body?.modelId);
|
|
7190
7244
|
project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7191
7245
|
saveProject(config.outputDir, project);
|
|
7192
7246
|
writeJson(res, 200, {
|
|
@@ -7521,6 +7575,24 @@ function makeRoutes(deps) {
|
|
|
7521
7575
|
}
|
|
7522
7576
|
}
|
|
7523
7577
|
};
|
|
7578
|
+
/** 生图接口连通性测试:GET {baseURL}/models + 计时(设置页模型条目用)。 */
|
|
7579
|
+
const imageTestRoute = {
|
|
7580
|
+
kind: "exact",
|
|
7581
|
+
path: NOVEL_API.imageTest,
|
|
7582
|
+
handler: async (req, res) => {
|
|
7583
|
+
if (!guard(req, res, "POST")) return;
|
|
7584
|
+
const body = await readJsonBody(req);
|
|
7585
|
+
if (body?.baseURL === void 0 || body?.apiKey === void 0) {
|
|
7586
|
+
writeJson(res, 400, { error: "baseURL 与 apiKey 必填" });
|
|
7587
|
+
return;
|
|
7588
|
+
}
|
|
7589
|
+
try {
|
|
7590
|
+
writeJson(res, 200, await testImageEndpoint(body.baseURL, body.apiKey, body.model));
|
|
7591
|
+
} catch (error) {
|
|
7592
|
+
writeJson(res, 500, { error: `测试失败:${error.message}` });
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
};
|
|
7524
7596
|
/** 分镜·导演级:骨架 → 分镜表(镜头级)。 */
|
|
7525
7597
|
const storyboardTableRoute = {
|
|
7526
7598
|
kind: "exact",
|
|
@@ -7825,6 +7897,7 @@ function makeRoutes(deps) {
|
|
|
7825
7897
|
manhuaPlansRoute,
|
|
7826
7898
|
styleImageRoute,
|
|
7827
7899
|
storyboardSkeletonRoute,
|
|
7900
|
+
imageTestRoute,
|
|
7828
7901
|
storyboardTableRoute,
|
|
7829
7902
|
storyboardPromptsRoute,
|
|
7830
7903
|
breakdownRoute,
|
|
@@ -7933,6 +8006,14 @@ const Config = z.object({
|
|
|
7933
8006
|
autoReview: z.boolean().default(true),
|
|
7934
8007
|
autoAuthorReview: z.boolean().default(true),
|
|
7935
8008
|
autoReviewAfterRevise: z.boolean().default(true),
|
|
8009
|
+
imageModels: z.array(z.object({
|
|
8010
|
+
id: z.string().default(""),
|
|
8011
|
+
name: z.string().default(""),
|
|
8012
|
+
baseURL: z.string().default(""),
|
|
8013
|
+
apiKey: z.string().default(""),
|
|
8014
|
+
model: z.string().default(""),
|
|
8015
|
+
enabled: z.boolean().default(false)
|
|
8016
|
+
})).default([]),
|
|
7936
8017
|
imageApiKey: z.string().default(""),
|
|
7937
8018
|
imageApiModel: z.string().default(""),
|
|
7938
8019
|
imageApiEnabled: z.boolean().default(false)
|
|
@@ -7970,11 +8051,29 @@ function resolveConfig(value) {
|
|
|
7970
8051
|
autoReview: value?.autoReview ?? DEFAULT_AUTO_REVIEW,
|
|
7971
8052
|
autoAuthorReview: value?.autoAuthorReview ?? DEFAULT_AUTO_AUTHOR_REVIEW,
|
|
7972
8053
|
autoReviewAfterRevise: value?.autoReviewAfterRevise ?? DEFAULT_AUTO_REVIEW_AFTER_REVISE,
|
|
7973
|
-
|
|
7974
|
-
|
|
8054
|
+
imageModels: value?.imageModels !== void 0 ? value.imageModels : value?.imageApiKey !== void 0 && value.imageApiKey !== "" ? [{
|
|
8055
|
+
id: "img-legacy",
|
|
8056
|
+
name: "豆包(旧配置)",
|
|
8057
|
+
baseURL: DEFAULT_IMAGE_BASE,
|
|
8058
|
+
apiKey: value.imageApiKey,
|
|
8059
|
+
model: value.imageApiModel ?? "",
|
|
8060
|
+
enabled: true
|
|
8061
|
+
}] : [],
|
|
8062
|
+
imageBaseUrl: (() => {
|
|
8063
|
+
const active = (value?.imageModels ?? []).find((m) => m.enabled) ?? (value?.imageModels ?? [])[0];
|
|
8064
|
+
return active?.baseURL !== void 0 && active.baseURL !== "" ? active.baseURL : value?.imageApiKey !== void 0 && value.imageApiKey !== "" ? DEFAULT_IMAGE_BASE : void 0;
|
|
8065
|
+
})(),
|
|
8066
|
+
imageApiKey: (() => {
|
|
8067
|
+
return ((value?.imageModels ?? []).find((m) => m.enabled) ?? (value?.imageModels ?? [])[0])?.apiKey ?? value?.imageApiKey;
|
|
8068
|
+
})(),
|
|
8069
|
+
imageApiModel: (() => {
|
|
8070
|
+
return ((value?.imageModels ?? []).find((m) => m.enabled) ?? (value?.imageModels ?? [])[0])?.model ?? value?.imageApiModel;
|
|
8071
|
+
})(),
|
|
7975
8072
|
imageApiEnabled: value?.imageApiEnabled ?? false
|
|
7976
8073
|
};
|
|
7977
8074
|
}
|
|
8075
|
+
/** 生图默认接口地址(豆包 ark,OpenAI 兼容)。 */
|
|
8076
|
+
const DEFAULT_IMAGE_BASE = "https://ark.cn-beijing.volces.com/api/v3";
|
|
7978
8077
|
/**
|
|
7979
8078
|
* Mount the routes and announcement.
|
|
7980
8079
|
* @param ctx - host plugin context carrying webServer/llm/systemPrompt.
|