Merge branch '108' of https://github.com/HBAI-Ltd/Toonflow-app into 108
# Conflicts: # src/router.ts # src/types/database.d.ts
This commit is contained in:
commit
2367a7bc5e
@ -32,6 +32,16 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
|
|||||||
return planData[key];
|
return planData[key];
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
get_novel_text: tool({
|
||||||
|
description: "获取小说章节原始文本内容",
|
||||||
|
inputSchema: z.object({
|
||||||
|
id: z.string().describe("章节id"),
|
||||||
|
}),
|
||||||
|
execute: async ({ id }) => {
|
||||||
|
console.log(id);
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
}),
|
||||||
set_planData_event: tool({
|
set_planData_event: tool({
|
||||||
description: "保存章节事件到工作区",
|
description: "保存章节事件到工作区",
|
||||||
inputSchema: z.object({ value: planData.shape.event }),
|
inputSchema: z.object({ value: planData.shape.event }),
|
||||||
|
|||||||
@ -202,6 +202,8 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
|||||||
table.text("chapter");
|
table.text("chapter");
|
||||||
table.text("chapterData");
|
table.text("chapterData");
|
||||||
table.integer("projectId");
|
table.integer("projectId");
|
||||||
|
table.integer("eventState");
|
||||||
|
table.text("event");
|
||||||
table.integer("createTime");
|
table.integer("createTime");
|
||||||
table.primary(["id"]);
|
table.primary(["id"]);
|
||||||
table.unique(["id"]);
|
table.unique(["id"]);
|
||||||
|
|||||||
182
src/router.ts
182
src/router.ts
@ -1,4 +1,4 @@
|
|||||||
// @routes-hash 0a85dfe35e0882ce29f9d4fc68c3fd73
|
// @routes-hash 30de02ae85f1e841f356d128583a5867
|
||||||
import { Express } from "express";
|
import { Express } from "express";
|
||||||
|
|
||||||
import route1 from "./routes/agents/clearMemory";
|
import route1 from "./routes/agents/clearMemory";
|
||||||
@ -31,50 +31,52 @@ import route27 from "./routes/novel/event/deletEvent";
|
|||||||
import route28 from "./routes/novel/event/generateEvents";
|
import route28 from "./routes/novel/event/generateEvents";
|
||||||
import route29 from "./routes/novel/event/getEvent";
|
import route29 from "./routes/novel/event/getEvent";
|
||||||
import route30 from "./routes/novel/getNovel";
|
import route30 from "./routes/novel/getNovel";
|
||||||
import route31 from "./routes/novel/updateNovel";
|
import route31 from "./routes/novel/getNovelEventState";
|
||||||
import route32 from "./routes/other/deleteAllData";
|
import route32 from "./routes/novel/getNovelIndex";
|
||||||
import route33 from "./routes/other/getCaptcha";
|
import route33 from "./routes/novel/updateNovel";
|
||||||
import route34 from "./routes/production/editStoryboard/generateStoryboardImage";
|
import route34 from "./routes/other/deleteAllData";
|
||||||
import route35 from "./routes/production/editStoryboard/getStoryboardFlow";
|
import route35 from "./routes/other/getCaptcha";
|
||||||
import route36 from "./routes/production/editStoryboard/saveStoryboardFlow";
|
import route36 from "./routes/production/assets/getAssetsData";
|
||||||
import route37 from "./routes/production/editStoryboard/updateStoryboardFlow";
|
import route37 from "./routes/production/editStoryboard/generateStoryboardImage";
|
||||||
import route38 from "./routes/production/exportImage";
|
import route38 from "./routes/production/editStoryboard/getStoryboardFlow";
|
||||||
import route39 from "./routes/production/getFlowData";
|
import route39 from "./routes/production/editStoryboard/saveStoryboardFlow";
|
||||||
import route40 from "./routes/production/getProductionData";
|
import route40 from "./routes/production/editStoryboard/updateStoryboardFlow";
|
||||||
import route41 from "./routes/production/getStoryboardData";
|
import route41 from "./routes/production/getFlowData";
|
||||||
import route42 from "./routes/production/saveFlowData";
|
import route42 from "./routes/production/getProductionData";
|
||||||
import route43 from "./routes/production/workbench/confirmSelection";
|
import route43 from "./routes/production/getStoryboardData";
|
||||||
import route44 from "./routes/production/workbench/delVideo";
|
import route44 from "./routes/production/saveFlowData";
|
||||||
import route45 from "./routes/production/workbench/generateVideo";
|
import route45 from "./routes/production/workbench/confirmSelection";
|
||||||
import route46 from "./routes/production/workbench/getVideoModelDetail";
|
import route46 from "./routes/production/workbench/delVideo";
|
||||||
import route47 from "./routes/production/workbench/videoPolling";
|
import route47 from "./routes/production/workbench/generateVideo";
|
||||||
import route48 from "./routes/project/addProject";
|
import route48 from "./routes/production/workbench/getVideoModelDetail";
|
||||||
import route49 from "./routes/project/delProject";
|
import route49 from "./routes/production/workbench/videoPolling";
|
||||||
import route50 from "./routes/project/editProject";
|
import route50 from "./routes/project/addProject";
|
||||||
import route51 from "./routes/project/getProject";
|
import route51 from "./routes/project/delProject";
|
||||||
import route52 from "./routes/script/addScript";
|
import route52 from "./routes/project/editProject";
|
||||||
import route53 from "./routes/script/delScript";
|
import route53 from "./routes/project/getProject";
|
||||||
import route54 from "./routes/script/exportScript";
|
import route54 from "./routes/script/addScript";
|
||||||
import route55 from "./routes/script/getScrptApi";
|
import route55 from "./routes/script/delScript";
|
||||||
import route56 from "./routes/script/updateScript";
|
import route56 from "./routes/script/exportScript";
|
||||||
import route57 from "./routes/setting/agentDeploy/deployAgentModel";
|
import route57 from "./routes/script/getScrptApi";
|
||||||
import route58 from "./routes/setting/agentDeploy/getAgentDeploy";
|
import route58 from "./routes/script/updateScript";
|
||||||
import route59 from "./routes/setting/agentDeploy/updateKey";
|
import route59 from "./routes/setting/agentDeploy/deployAgentModel";
|
||||||
import route60 from "./routes/setting/dbConfig/clearData";
|
import route60 from "./routes/setting/agentDeploy/getAgentDeploy";
|
||||||
import route61 from "./routes/setting/getTextModel";
|
import route61 from "./routes/setting/agentDeploy/updateKey";
|
||||||
import route62 from "./routes/setting/loginConfig/getUser";
|
import route62 from "./routes/setting/dbConfig/clearData";
|
||||||
import route63 from "./routes/setting/loginConfig/updateUserPwd";
|
import route63 from "./routes/setting/getTextModel";
|
||||||
import route64 from "./routes/setting/memoryConfig/getMemory";
|
import route64 from "./routes/setting/loginConfig/getUser";
|
||||||
import route65 from "./routes/setting/memoryConfig/sureMemory";
|
import route65 from "./routes/setting/loginConfig/updateUserPwd";
|
||||||
import route66 from "./routes/setting/vendorConfig/addVendor";
|
import route66 from "./routes/setting/memoryConfig/getMemory";
|
||||||
import route67 from "./routes/setting/vendorConfig/deleteVendor";
|
import route67 from "./routes/setting/memoryConfig/sureMemory";
|
||||||
import route68 from "./routes/setting/vendorConfig/getVendorList";
|
import route68 from "./routes/setting/vendorConfig/addVendor";
|
||||||
import route69 from "./routes/setting/vendorConfig/modelTest";
|
import route69 from "./routes/setting/vendorConfig/deleteVendor";
|
||||||
import route70 from "./routes/setting/vendorConfig/updateVendor";
|
import route70 from "./routes/setting/vendorConfig/getVendorList";
|
||||||
import route71 from "./routes/task/getTaskApi";
|
import route71 from "./routes/setting/vendorConfig/modelTest";
|
||||||
import route72 from "./routes/task/getTaskCategories";
|
import route72 from "./routes/setting/vendorConfig/updateVendor";
|
||||||
import route73 from "./routes/task/taskDetails";
|
import route73 from "./routes/task/getTaskApi";
|
||||||
import route74 from "./routes/test/test";
|
import route74 from "./routes/task/getTaskCategories";
|
||||||
|
import route75 from "./routes/task/taskDetails";
|
||||||
|
import route76 from "./routes/test/test";
|
||||||
|
|
||||||
export default async (app: Express) => {
|
export default async (app: Express) => {
|
||||||
app.use("/api/agents/clearMemory", route1);
|
app.use("/api/agents/clearMemory", route1);
|
||||||
@ -107,48 +109,50 @@ export default async (app: Express) => {
|
|||||||
app.use("/api/novel/event/generateEvents", route28);
|
app.use("/api/novel/event/generateEvents", route28);
|
||||||
app.use("/api/novel/event/getEvent", route29);
|
app.use("/api/novel/event/getEvent", route29);
|
||||||
app.use("/api/novel/getNovel", route30);
|
app.use("/api/novel/getNovel", route30);
|
||||||
app.use("/api/novel/updateNovel", route31);
|
app.use("/api/novel/getNovelEventState", route31);
|
||||||
app.use("/api/other/deleteAllData", route32);
|
app.use("/api/novel/getNovelIndex", route32);
|
||||||
app.use("/api/other/getCaptcha", route33);
|
app.use("/api/novel/updateNovel", route33);
|
||||||
app.use("/api/production/editStoryboard/generateStoryboardImage", route34);
|
app.use("/api/other/deleteAllData", route34);
|
||||||
app.use("/api/production/editStoryboard/getStoryboardFlow", route35);
|
app.use("/api/other/getCaptcha", route35);
|
||||||
app.use("/api/production/editStoryboard/saveStoryboardFlow", route36);
|
app.use("/api/production/assets/getAssetsData", route36);
|
||||||
app.use("/api/production/editStoryboard/updateStoryboardFlow", route37);
|
app.use("/api/production/editStoryboard/generateStoryboardImage", route37);
|
||||||
app.use("/api/production/exportImage", route38);
|
app.use("/api/production/editStoryboard/getStoryboardFlow", route38);
|
||||||
app.use("/api/production/getFlowData", route39);
|
app.use("/api/production/editStoryboard/saveStoryboardFlow", route39);
|
||||||
app.use("/api/production/getProductionData", route40);
|
app.use("/api/production/editStoryboard/updateStoryboardFlow", route40);
|
||||||
app.use("/api/production/getStoryboardData", route41);
|
app.use("/api/production/getFlowData", route41);
|
||||||
app.use("/api/production/saveFlowData", route42);
|
app.use("/api/production/getProductionData", route42);
|
||||||
app.use("/api/production/workbench/confirmSelection", route43);
|
app.use("/api/production/getStoryboardData", route43);
|
||||||
app.use("/api/production/workbench/delVideo", route44);
|
app.use("/api/production/saveFlowData", route44);
|
||||||
app.use("/api/production/workbench/generateVideo", route45);
|
app.use("/api/production/workbench/confirmSelection", route45);
|
||||||
app.use("/api/production/workbench/getVideoModelDetail", route46);
|
app.use("/api/production/workbench/delVideo", route46);
|
||||||
app.use("/api/production/workbench/videoPolling", route47);
|
app.use("/api/production/workbench/generateVideo", route47);
|
||||||
app.use("/api/project/addProject", route48);
|
app.use("/api/production/workbench/getVideoModelDetail", route48);
|
||||||
app.use("/api/project/delProject", route49);
|
app.use("/api/production/workbench/videoPolling", route49);
|
||||||
app.use("/api/project/editProject", route50);
|
app.use("/api/project/addProject", route50);
|
||||||
app.use("/api/project/getProject", route51);
|
app.use("/api/project/delProject", route51);
|
||||||
app.use("/api/script/addScript", route52);
|
app.use("/api/project/editProject", route52);
|
||||||
app.use("/api/script/delScript", route53);
|
app.use("/api/project/getProject", route53);
|
||||||
app.use("/api/script/exportScript", route54);
|
app.use("/api/script/addScript", route54);
|
||||||
app.use("/api/script/getScrptApi", route55);
|
app.use("/api/script/delScript", route55);
|
||||||
app.use("/api/script/updateScript", route56);
|
app.use("/api/script/exportScript", route56);
|
||||||
app.use("/api/setting/agentDeploy/deployAgentModel", route57);
|
app.use("/api/script/getScrptApi", route57);
|
||||||
app.use("/api/setting/agentDeploy/getAgentDeploy", route58);
|
app.use("/api/script/updateScript", route58);
|
||||||
app.use("/api/setting/agentDeploy/updateKey", route59);
|
app.use("/api/setting/agentDeploy/deployAgentModel", route59);
|
||||||
app.use("/api/setting/dbConfig/clearData", route60);
|
app.use("/api/setting/agentDeploy/getAgentDeploy", route60);
|
||||||
app.use("/api/setting/getTextModel", route61);
|
app.use("/api/setting/agentDeploy/updateKey", route61);
|
||||||
app.use("/api/setting/loginConfig/getUser", route62);
|
app.use("/api/setting/dbConfig/clearData", route62);
|
||||||
app.use("/api/setting/loginConfig/updateUserPwd", route63);
|
app.use("/api/setting/getTextModel", route63);
|
||||||
app.use("/api/setting/memoryConfig/getMemory", route64);
|
app.use("/api/setting/loginConfig/getUser", route64);
|
||||||
app.use("/api/setting/memoryConfig/sureMemory", route65);
|
app.use("/api/setting/loginConfig/updateUserPwd", route65);
|
||||||
app.use("/api/setting/vendorConfig/addVendor", route66);
|
app.use("/api/setting/memoryConfig/getMemory", route66);
|
||||||
app.use("/api/setting/vendorConfig/deleteVendor", route67);
|
app.use("/api/setting/memoryConfig/sureMemory", route67);
|
||||||
app.use("/api/setting/vendorConfig/getVendorList", route68);
|
app.use("/api/setting/vendorConfig/addVendor", route68);
|
||||||
app.use("/api/setting/vendorConfig/modelTest", route69);
|
app.use("/api/setting/vendorConfig/deleteVendor", route69);
|
||||||
app.use("/api/setting/vendorConfig/updateVendor", route70);
|
app.use("/api/setting/vendorConfig/getVendorList", route70);
|
||||||
app.use("/api/task/getTaskApi", route71);
|
app.use("/api/setting/vendorConfig/modelTest", route71);
|
||||||
app.use("/api/task/getTaskCategories", route72);
|
app.use("/api/setting/vendorConfig/updateVendor", route72);
|
||||||
app.use("/api/task/taskDetails", route73);
|
app.use("/api/task/getTaskApi", route73);
|
||||||
app.use("/api/test/test", route74);
|
app.use("/api/task/getTaskCategories", route74);
|
||||||
|
app.use("/api/task/taskDetails", route75);
|
||||||
|
app.use("/api/test/test", route76);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,23 +16,35 @@ export default router.post(
|
|||||||
reel: z.string(),
|
reel: z.string(),
|
||||||
chapter: z.string(),
|
chapter: z.string(),
|
||||||
chapterData: z.string(),
|
chapterData: z.string(),
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { projectId, data } = req.body;
|
const { projectId, data } = req.body;
|
||||||
|
const totalNovelId = [];
|
||||||
for (const item of data) {
|
for (const item of data) {
|
||||||
await u.db("o_novel").insert({
|
const [id] = await u.db("o_novel").insert({
|
||||||
projectId,
|
projectId,
|
||||||
chapterIndex: item.index,
|
chapterIndex: item.index,
|
||||||
reel: item.reel,
|
reel: item.reel,
|
||||||
chapter: item.chapter,
|
chapter: item.chapter,
|
||||||
chapterData: item.chapterData,
|
chapterData: item.chapterData,
|
||||||
createTime: Date.now(),
|
createTime: Date.now(),
|
||||||
|
eventState: 0,
|
||||||
});
|
});
|
||||||
|
totalNovelId.push(id);
|
||||||
}
|
}
|
||||||
|
const chapterAllList = await u.db("o_novel").where("projectId", projectId).whereIn("id", totalNovelId);
|
||||||
|
const novelClass = new u.cleanNovel();
|
||||||
|
novelClass.emitter.on("item", async (item) => {
|
||||||
|
if (item.event)
|
||||||
|
await u
|
||||||
|
.db("o_novel")
|
||||||
|
.where("id", item.id)
|
||||||
|
.update({ event: item.event, eventState: item.event ? 1 : -1 });
|
||||||
|
});
|
||||||
|
novelClass.start(chapterAllList, projectId);
|
||||||
|
|
||||||
res.status(200).send(success({ message: "新增原文成功" }));
|
res.status(200).send(success({ message: "新增原文成功" }));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,84 +6,31 @@ import { validateFields } from "@/middleware/middleware";
|
|||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// 解析章节字符串,支持逗号分隔的多段(如 "1-3,5,7-9")
|
|
||||||
function parseChapters(str: string): number[] {
|
|
||||||
const result: number[] = [];
|
|
||||||
// 逗号和空格之间加以划分
|
|
||||||
const segments = str
|
|
||||||
.split(",")
|
|
||||||
.map((s) => s.replace(/[^\d\-]/g, "").trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
for (const seg of segments) {
|
|
||||||
// 匹配区间
|
|
||||||
if (/^\d+\-\d+$/.test(seg)) {
|
|
||||||
const [start, end] = seg.split("-").map(Number);
|
|
||||||
if (start <= end) {
|
|
||||||
for (let i = start; i <= end; i++) result.push(i);
|
|
||||||
}
|
|
||||||
} else if (/^\d+$/.test(seg)) {
|
|
||||||
result.push(Number(seg));
|
|
||||||
}
|
|
||||||
// 其它格式自动忽略
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
parseChapters("7-8章");
|
|
||||||
// 清洗小说原文,生成事件列表
|
// 清洗小说原文,生成事件列表
|
||||||
export default router.post(
|
export default router.post(
|
||||||
"/",
|
"/",
|
||||||
validateFields({
|
validateFields({
|
||||||
projectId: z.number(),
|
projectId: z.number(),
|
||||||
windowSize: z.number().optional().default(5), // 每组数量,默认 5
|
novelIds: z.array(z.number()),
|
||||||
overlap: z.number().optional().default(1), // 交叠数量,默认 1
|
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { projectId, windowSize, overlap } = req.body;
|
const { projectId, novelIds } = req.body;
|
||||||
//删除之前的事件
|
|
||||||
const [allChapters, novel] = await Promise.all([
|
const [allChapters, novel] = await Promise.all([
|
||||||
u.db("o_novel").where("projectId", projectId),
|
u.db("o_novel").where("projectId", projectId).whereIn("id", novelIds),
|
||||||
Promise.resolve(new u.cleanNovel(windowSize, overlap)),
|
Promise.resolve(new u.cleanNovel()),
|
||||||
]);
|
]);
|
||||||
const novelIds = allChapters.map((i) => i.id);
|
|
||||||
await u
|
|
||||||
.db("o_eventChapter")
|
|
||||||
.whereIn("novelId", novelIds as number[])
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
const eventIds = await u.db("o_eventChapter").whereIn("novelId", novelIds).select("eventId").pluck("eventId");
|
await u.db("o_novel").where("projectId", projectId).update({ eventState: 0, event: null });
|
||||||
|
novel.emitter.on("item", async (item) => {
|
||||||
|
if (item.event)
|
||||||
|
await u
|
||||||
|
.db("o_novel")
|
||||||
|
.where("id", item.id)
|
||||||
|
.update({ event: item.event, eventState: item.event ? 1 : -1 });
|
||||||
|
});
|
||||||
|
novel.start(allChapters, projectId);
|
||||||
|
|
||||||
await u
|
return res.status(200).send(success("生成事件成功"));
|
||||||
.db("o_event")
|
|
||||||
.whereIn("id", eventIds as number[])
|
|
||||||
.delete();
|
|
||||||
|
|
||||||
const data = await novel.start(allChapters, projectId);
|
|
||||||
|
|
||||||
const chapterMap = new Map(allChapters.map((c) => [c.chapterIndex, c]));
|
|
||||||
|
|
||||||
const novelEvent: { eventId: number; novelId: number }[] = [];
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
for (const item of data) {
|
|
||||||
const [id] = await u.db("o_event").insert({
|
|
||||||
name: item.name,
|
|
||||||
detail: item.detail,
|
|
||||||
createTime: now,
|
|
||||||
});
|
|
||||||
|
|
||||||
parseChapters(item.chapter).forEach((chapterIndex) => {
|
|
||||||
const chapter = chapterMap.get(chapterIndex);
|
|
||||||
if (chapter) {
|
|
||||||
novelEvent.push({ eventId: id, novelId: chapter.id! });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (novelEvent.length > 0) {
|
|
||||||
await u.db("o_eventChapter").insert(novelEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send(success(data));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,10 +6,6 @@ import { success } from "@/lib/responseFormat";
|
|||||||
import { validateFields } from "@/middleware/middleware";
|
import { validateFields } from "@/middleware/middleware";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// CREATE TABLE `o_event` (`id` integer not null, `name` varchar(255), `detail` varchar(255), `createTime` integer, primary key (`id`));
|
|
||||||
// CREATE TABLE `o_eventChapter` (`id` integer not null, `eventId` integer, `novelId` integer, foreign key(`eventId`) references `o_event`(`id`), foreign key(`novelId`) references `o_novel`(`id`), primary key (`id`));
|
|
||||||
// CREATE TABLE `o_novel` (`id` integer not null, `chapterIndex` integer, `reel` text, `chapter` text, `chapterData` text, `projectId` integer, `createTime` integer, primary key (`id`));
|
|
||||||
|
|
||||||
export default router.post(
|
export default router.post(
|
||||||
"/",
|
"/",
|
||||||
validateFields({
|
validateFields({
|
||||||
@ -43,13 +39,7 @@ export default router.post(
|
|||||||
// 分页查询:每个事件对应多个 chapterIndex,用 GROUP_CONCAT 聚合
|
// 分页查询:每个事件对应多个 chapterIndex,用 GROUP_CONCAT 聚合
|
||||||
const rows = await baseQuery
|
const rows = await baseQuery
|
||||||
.clone()
|
.clone()
|
||||||
.select(
|
.select("e.id", "e.name as eventName", "e.detail", "e.createTime", db.raw("GROUP_CONCAT(n.chapterIndex) as chapterIndexes"))
|
||||||
"e.id",
|
|
||||||
"e.name as eventName",
|
|
||||||
"e.detail",
|
|
||||||
"e.createTime",
|
|
||||||
db.raw("GROUP_CONCAT(n.chapterIndex) as chapterIndexes"),
|
|
||||||
)
|
|
||||||
.groupBy("e.id")
|
.groupBy("e.id")
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
|||||||
@ -13,7 +13,6 @@ export default router.post(
|
|||||||
page: z.number(),
|
page: z.number(),
|
||||||
limit: z.number(),
|
limit: z.number(),
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
|
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { projectId, page, limit, search } = req.body;
|
const { projectId, page, limit, search } = req.body;
|
||||||
@ -21,7 +20,7 @@ export default router.post(
|
|||||||
const data = await u
|
const data = await u
|
||||||
.db("o_novel")
|
.db("o_novel")
|
||||||
.where("projectId", projectId)
|
.where("projectId", projectId)
|
||||||
.select("id", "chapterIndex as index", "reel", "chapter", "chapterData")
|
.select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState")
|
||||||
.andWhere((qb) => {
|
.andWhere((qb) => {
|
||||||
if (search) {
|
if (search) {
|
||||||
qb.where("chapter", "like", `%${search}%`);
|
qb.where("chapter", "like", `%${search}%`);
|
||||||
@ -30,6 +29,7 @@ export default router.post(
|
|||||||
.orderBy("chapterIndex", "asc")
|
.orderBy("chapterIndex", "asc")
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
|
||||||
// 统计总数
|
// 统计总数
|
||||||
const totalQuery = (await u
|
const totalQuery = (await u
|
||||||
.db("o_novel")
|
.db("o_novel")
|
||||||
@ -43,5 +43,5 @@ export default router.post(
|
|||||||
.first()) as any;
|
.first()) as any;
|
||||||
|
|
||||||
res.status(200).send(success({ data, total: totalQuery.total }));
|
res.status(200).send(success({ data, total: totalQuery.total }));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
19
src/routes/novel/getNovelEventState.ts
Normal file
19
src/routes/novel/getNovelEventState.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import express from "express";
|
||||||
|
import u from "@/utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { success } from "@/lib/responseFormat";
|
||||||
|
import { validateFields } from "@/middleware/middleware";
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// 获取原文数据
|
||||||
|
export default router.post(
|
||||||
|
"/",
|
||||||
|
validateFields({
|
||||||
|
ids: z.array(z.number()),
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
const { ids } = req.body;
|
||||||
|
const data = await u.db("o_novel").whereIn("id", ids).whereNot("eventState", 0).select("id", "event", "eventState");
|
||||||
|
res.status(200).send(success(data));
|
||||||
|
},
|
||||||
|
);
|
||||||
19
src/routes/novel/getNovelIndex.ts
Normal file
19
src/routes/novel/getNovelIndex.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import express from "express";
|
||||||
|
import u from "@/utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { success } from "@/lib/responseFormat";
|
||||||
|
import { validateFields } from "@/middleware/middleware";
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router.post(
|
||||||
|
"/",
|
||||||
|
validateFields({
|
||||||
|
projectId: z.number(),
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
const { projectId } = req.body;
|
||||||
|
const data = await u.db("o_novel").where("projectId", projectId).select("id", "chapterIndex as index", "chapter").orderBy("chapterIndex", "asc");
|
||||||
|
|
||||||
|
res.status(200).send(success(data));
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -14,15 +14,17 @@ export default router.post(
|
|||||||
reel: z.string(),
|
reel: z.string(),
|
||||||
chapter: z.string(),
|
chapter: z.string(),
|
||||||
chapterData: z.string(),
|
chapterData: z.string(),
|
||||||
|
event: z.string(),
|
||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { id, index, reel, chapter, chapterData } = req.body;
|
const { id, index, reel, chapter, chapterData, event } = req.body;
|
||||||
|
|
||||||
await u.db("o_novel").where("id", id).update({
|
await u.db("o_novel").where("id", id).update({
|
||||||
chapterIndex: index,
|
chapterIndex: index,
|
||||||
reel,
|
reel,
|
||||||
chapter,
|
chapter,
|
||||||
chapterData,
|
chapterData,
|
||||||
|
event: event,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).send(success({ message: "更新原文成功" }));
|
res.status(200).send(success({ message: "更新原文成功" }));
|
||||||
|
|||||||
60
src/routes/production/assets/getAssetsData.ts
Normal file
60
src/routes/production/assets/getAssetsData.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import express from "express";
|
||||||
|
import u from "@/utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { success } from "@/lib/responseFormat";
|
||||||
|
import { validateFields } from "@/middleware/middleware";
|
||||||
|
import { o_assets } from "@/types/database";
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router.post(
|
||||||
|
"/",
|
||||||
|
validateFields({
|
||||||
|
projectId: z.number(),
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
const { projectId } = req.body;
|
||||||
|
const parentAssetsData = await u.db("o_assets").where("projectId", projectId).whereNotNull("sonId");
|
||||||
|
const parentIds = parentAssetsData.map((i) => i.id);
|
||||||
|
const parnetIdsMap: Record<number, number> = {};
|
||||||
|
const sonAssetsData = await u.db("o_assets").whereIn("sonId", parentIds);
|
||||||
|
const sonAssetsMap: Record<number, o_assets[]> = {};
|
||||||
|
|
||||||
|
const imageIds = [...parentAssetsData.map((i) => i.imageId).concat(sonAssetsData.map((i) => i.imageId))].filter(Boolean);
|
||||||
|
const imagePaths = await u
|
||||||
|
.db("o_image")
|
||||||
|
.whereIn("id", imageIds as unknown as string[])
|
||||||
|
.select("id", "filePath");
|
||||||
|
const imageSignUrls = await Promise.all(
|
||||||
|
imagePaths.map(async (i) => {
|
||||||
|
return { id: i.id, src: i.filePath ? await u.oss.getFileUrl(i.filePath) : null };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const imageUrlMap: Record<number, string | null> = {};
|
||||||
|
imageSignUrls.forEach((i, index) => {
|
||||||
|
imageUrlMap[i.id!] = i.src;
|
||||||
|
});
|
||||||
|
sonAssetsData.forEach((i) => {
|
||||||
|
if (!sonAssetsMap[i.sonId!]) {
|
||||||
|
sonAssetsMap[i.sonId!] = [];
|
||||||
|
}
|
||||||
|
const obj = {
|
||||||
|
assetsId: i.id,
|
||||||
|
name: i.name,
|
||||||
|
desc: i.describe,
|
||||||
|
src: imageUrlMap[i.imageId!] ?? null,
|
||||||
|
derive: sonAssetsMap[i.id!] ?? [],
|
||||||
|
};
|
||||||
|
sonAssetsMap[i.sonId!].push(obj);
|
||||||
|
});
|
||||||
|
const returnData = parentAssetsData.map((i) => {
|
||||||
|
return {
|
||||||
|
assetsId: i.id,
|
||||||
|
name: i.name,
|
||||||
|
desc: i.describe,
|
||||||
|
src: imageUrlMap[i.imageId!] ?? null,
|
||||||
|
derive: sonAssetsMap[i.id!] ?? [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
res.status(200).send(success(returnData));
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -1,40 +1,13 @@
|
|||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { ModelMessage, Output } from "ai";
|
import { ModelMessage, Output } from "ai";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
import { o_novel } from "@/types/database";
|
import { o_novel } from "@/types/database";
|
||||||
import ai from "@/utils/ai";
|
import ai from "@/utils/ai";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
export interface EventType {
|
export interface EventType {
|
||||||
name: string;
|
id: number;
|
||||||
detail: string;
|
event: string;
|
||||||
chapter: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Novel {
|
|
||||||
event: EventType[];
|
|
||||||
}
|
|
||||||
// 章节拆分
|
|
||||||
function getChapterGroups<T>(chapters: T[], windowSize: number = 5, overlap: number = 1): T[][] {
|
|
||||||
const res: T[][] = [];
|
|
||||||
if (windowSize < 1 || overlap < 0) return res;
|
|
||||||
let i = 0;
|
|
||||||
const length = chapters.length;
|
|
||||||
while (i < length) {
|
|
||||||
if (res.length === 0) {
|
|
||||||
// 第一组,直接取 windowSize 个
|
|
||||||
res.push(chapters.slice(i, i + windowSize));
|
|
||||||
i += windowSize;
|
|
||||||
} else {
|
|
||||||
// 取上一组最后 overlap 个,加上新的 windowSize 个
|
|
||||||
const prevGroup = res[res.length - 1];
|
|
||||||
const overlapItems = prevGroup.slice(-overlap);
|
|
||||||
const newItems = chapters.slice(i, i + windowSize);
|
|
||||||
if (newItems.length === 0) break; // 已经取完,跳出
|
|
||||||
res.push([...overlapItems, ...newItems]);
|
|
||||||
i += windowSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 文本数据清洗
|
/* 文本数据清洗
|
||||||
@ -45,42 +18,18 @@ function getChapterGroups<T>(chapters: T[], windowSize: number = 5, overlap: num
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class CleanNovel {
|
class CleanNovel {
|
||||||
windowSize: number;
|
emitter: EventEmitter;
|
||||||
overlap: number;
|
constructor() {
|
||||||
constructor(windowSize: number = 5, overlap: number = 1) {
|
this.emitter = new EventEmitter();
|
||||||
this.windowSize = windowSize;
|
|
||||||
this.overlap = overlap;
|
|
||||||
}
|
}
|
||||||
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
|
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
|
||||||
const groups = getChapterGroups(allChapters!, this.windowSize, this.overlap);
|
|
||||||
|
|
||||||
let preData: Novel | null = null;
|
|
||||||
//所有事件
|
//所有事件
|
||||||
let totalEvent: EventType[] = [];
|
let totalEvent: EventType[] = [];
|
||||||
const intansce = u.Ai.Text("eventExtractAi");
|
const intansce = u.Ai.Text("eventExtractAi");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let gi = 0; gi < groups.length; gi++) {
|
for (let gi = 0; gi < allChapters.length; gi++) {
|
||||||
const group = groups[gi];
|
const novel = allChapters[gi];
|
||||||
// 第一批没有交叠章节,后续批次前 overlap 个是交叠章节(仅作上下文,不输出事件)
|
|
||||||
const overlapCount = gi === 0 ? 0 : this.overlap;
|
|
||||||
const overlapChapterIndexes = group.slice(0, overlapCount).map((i) => i.chapterIndex);
|
|
||||||
|
|
||||||
const cleanText = group
|
|
||||||
.map((i, index: number) => {
|
|
||||||
const isOverlap = overlapChapterIndexes.includes(i.chapterIndex);
|
|
||||||
return {
|
|
||||||
role: "user",
|
|
||||||
content: isOverlap
|
|
||||||
? `【上文衔接章节,仅供上下文参考,禁止为本章生成情节单元】\n第${i.chapterIndex}章:\n\n${i.chapterData}`
|
|
||||||
: `第${i.chapterIndex}章:\n\n${i.chapterData}`,
|
|
||||||
} as ModelMessage;
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
const taskRecord = await u.task(projectId, "事件提取", "gpt-4.1", {
|
|
||||||
describe: "根据小说原文,提取情节单元",
|
|
||||||
content: cleanText,
|
|
||||||
});
|
|
||||||
let resData;
|
let resData;
|
||||||
try {
|
try {
|
||||||
resData = await intansce.invoke({
|
resData = await intansce.invoke({
|
||||||
@ -88,67 +37,31 @@ class CleanNovel {
|
|||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
content: `
|
content: `
|
||||||
你是专业剧本结构分析师,负责将用户提供的章节文本拆分为标准情节单元。请严格遵循以下规则执行。
|
你是一位专业的叙事结构分析师。
|
||||||
|
请阅读以下小说章节,用**一段话**提炼本章的情节单元摘要。
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
## 要求
|
||||||
【情节单元拆分规则】
|
- 按事件发生顺序,串联本章核心情节节点
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
- 突出人物行为、关键转折、因果关系
|
||||||
|
- 语言简洁紧凑,100-150字以内
|
||||||
▍拆分粒度
|
- 不加主观评价,只陈述"发生了什么"
|
||||||
- 连续多个章节若属同一戏剧动作,可合并为 1 个情节单元,禁止过细拆分;
|
---
|
||||||
- 每个情节单元的 detail 字数控制在 100~200 字。
|
【章节内容】:
|
||||||
|
|
||||||
▍每个情节单元包含以下三个字段
|
|
||||||
- chapter:事件覆盖的章节范围(如"1-3章"),每个章节只能归属一个事件;
|
|
||||||
- name:事件名称,须具体描述实际戏剧动作,禁止使用"XXX踏上征程""命运转折"等笼统标题;
|
|
||||||
- detail:事件过程详情,包含时间、地点、涉及人物、起因、经过、结果。
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
【执行规则】
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
✅ 必须执行
|
|
||||||
1. 所有章节按剧情顺序逐一覆盖,不得遗漏;
|
|
||||||
2. 标注为【上文衔接章节】的内容仅作上下文理解使用,禁止为其生成任何情节单元。
|
|
||||||
|
|
||||||
🚫 禁止出现
|
|
||||||
- 笼统事件名称;
|
|
||||||
- 单个章节拆分为多个情节单元;
|
|
||||||
- 遗漏任何章节。
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
...cleanText,
|
{
|
||||||
|
role: "user",
|
||||||
|
content: novel.chapterData!,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
output: Output.object({
|
|
||||||
schema: z.object({
|
|
||||||
event: z.array(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
chapter: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"事件覆盖的章节(如1-3章、4-6章),章节划分必须连续,每个章节范围只能属于一个事件。事件分割不可过细——避免只描述琐碎、日常细节的微小事件。",
|
|
||||||
),
|
|
||||||
name: z.string().describe("事件名称"),
|
|
||||||
detail: z.string().describe("事件过程详情(包括起因、经过、结果、场景、人物等)"),
|
|
||||||
})
|
|
||||||
.describe("事件必须在100-200字说明起因经过结果,不可将单一章节或细小场景独立成事件,"),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const preData = resData.text;
|
||||||
|
|
||||||
|
this.emitter.emit("item", { id: novel.id, event: preData });
|
||||||
|
totalEvent.push({ id: novel.id!, event: preData });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
taskRecord(-1, u.error(e).message);
|
this.emitter.emit("item", { id: novel.id, event: null });
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
taskRecord(1);
|
|
||||||
|
|
||||||
preData = JSON.parse(resData.text);
|
|
||||||
|
|
||||||
const newEvents = preData?.event || [];
|
|
||||||
newEvents.forEach((newItem) => {
|
|
||||||
totalEvent.push({ ...newItem });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user