# Conflicts:
#	src/router.ts
#	src/types/database.d.ts
This commit is contained in:
zhishi 2026-03-21 10:01:48 +08:00
commit 7ef0ddc3a2
21 changed files with 757 additions and 398 deletions

View File

@ -1,24 +1,25 @@
--- ---
name: execution name: execution
description: 短剧漫剧制作助手。协助用户进行制作视频。 description: 用户需要拆分剧本的时候可以看此skill的参考资料了解拆分原则和示例
--- ---
# Production Agent # execution Agent
短剧漫剧制作专用技能,提供剧本创作、分镜设计、角色设定等全流程指导 执行层,负责整体决策和协调。接收用户需求后,完成对应的任务
## 何时使用 ## 何时使用
当用户需要以下帮助时激活此技能: 当用户需要以下帮助时激活此技能:
- 开始制作视频 - 拆分剧本
## 工作指引 ## 工作指引
1. 理解用户的创作意图,根据项目类型(短剧/漫剧)调整输出风格 ### 拆分剧本流程
2. 遵循标准的剧本格式,包含场景描述、角色动作、对白等要素
3. 保持角色一致性,关注剧情连贯性 - 当执行拆分剧本任务的时候,你需要先调用 `get_flowData` 获取原始剧本
4. 输出使用中文 - 根据[剧本拆分]文档中的拆分原则和示例,将剧本拆分成视频模型能够处理的片段
- 将拆分后的剧本片段必须使用`set_flowData_script`工具保存,然后仅需告知用户拆分完成
## 参考资料 ## 参考资料

View File

@ -16,83 +16,24 @@
- `string[]` - `string[]`
- 数组每个元素是“原始剧本的一段子串” - 数组每个元素是“原始剧本的一段子串”
- 拼接后应可还原原文语义顺序(允许空白规范化差异) - 拼接后应可还原原文语义顺序(允许空白规范化差异)
- 开头必须是**喵,**
示例: 示例:
```ts ```ts
[ [
"第一段原文...", "喵,第一段原文...",
"第二段原文...", "喵,第二段原文...",
"第三段原文..." "喵,第三段原文..."
] ]
``` ```
## 2. 切割原则
1. 只切原文,不改写剧情。
2. 尽量在自然边界切分:
- 场景切换处
- 段落边界
- 完整对白后
3. 禁止在以下位置切分:
- 一句对白中间
- 一个动作描述中间
- 专有名词中间
4. 每段尽量长度均衡,避免极短碎片。
## 3. 长度约束(建议)
按模型容量设置目标长度,建议采用“软上限 + 硬上限”:
- `targetLen`:目标字符数(例如 800 到 1500
- `maxLen`:硬上限字符数(例如 1800
规则:
1. 优先接近 `targetLen`
2. 若继续追加会超过 `maxLen`,必须切分。
3. 超长单段(天然超过 `maxLen`)可在最近标点处强制切分。
## 4. 切割流程
1. 预处理:统一换行符、去除明显重复空行。
2. 粗切按双换行或场景标记如“场景X”“INT/EXT”分段。
3. 合并:将粗切段按顺序累加到当前块,直到接近 `targetLen`
4. 截断:若超过 `maxLen`,在最近的句末标点处切开。
5. 收尾:去掉首尾多余空白,输出 `string[]`
## 5. 最低质量检查
输出前检查:
1. 数组不为空。
2. 每个元素非空字符串。
3. 顺序与原文一致。
4. 不存在明显断句错误(例如对白断半句)。
## 6. 给智能体的固定执行指令
当用户要求“拆分长剧本给多个视频模型并行生成”时:
1. 仅基于原始剧本文本切割。
2. 不增加任何结构化字段。
3. 最终仅输出 `string[]`
4. 不输出额外解释性对象。
## 7. 简例
原文(节选):
- 场景1雨夜街道。林夏快步穿过巷口。
- 场景2天台。她停下回头看向门口。
- 场景3脚步声逼近。她握紧手机。
可能输出: 可能输出:
```ts ```ts
[ [
"场景1雨夜街道。林夏快步穿过巷口。", "喵场景1雨夜街道。林夏快步穿过巷口。",
"场景2天台。她停下回头看向门口。", "喵场景2天台。她停下回头看向门口。",
"场景3脚步声逼近。她握紧手机。" "喵场景3脚步声逼近。她握紧手机。"
] ]
``` ```

View File

@ -64,6 +64,7 @@
"qwen-ai-provider-v5": "^2.1.0", "qwen-ai-provider-v5": "^2.1.0",
"serialize-error": "^13.0.1", "serialize-error": "^13.0.1",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"socket.io": "^4.8.3",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"sucrase": "^3.35.1", "sucrase": "^3.35.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",

View File

@ -0,0 +1,144 @@
import { Socket } from "socket.io";
import { tool } from "ai";
import { z } from "zod";
import u from "@/utils";
import Memory from "@/utils/agent/memory";
import { useSkill } from "@/utils/agent/skillsTools";
import useTools from "@/agents/productionAgent/tools";
import ResTool from "@/socket/resTool";
export interface AgentContext {
socket: Socket;
isolationKey: string;
text: string;
abortSignal?: AbortSignal;
resTool: ResTool;
}
function buildSystemPrompt(skillPrompt: string, mem: Awaited<ReturnType<Memory["get"]>>): string {
let memoryContext = "";
if (mem.rag.length) {
memoryContext += `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`;
}
if (mem.summaries.length) {
if (memoryContext) memoryContext += "\n\n";
memoryContext += `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`;
}
if (mem.shortTerm.length) {
if (memoryContext) memoryContext += "\n\n";
memoryContext += `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`;
}
if (!memoryContext) return skillPrompt;
return `${skillPrompt}\n\n## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`;
}
const subAgentList = ["executionAI", "supervisionAI"] as const;
export async function decisionAI(ctx: AgentContext) {
const { socket, isolationKey, text, abortSignal } = ctx;
const memory = new Memory("productionAgent", isolationKey);
await memory.add("user", text);
const [skill, mem] = await Promise.all([useSkill("production-agent", "decision"), memory.get(text)]);
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
const prefixSystem = `不论用户说什么要做什么都请直接调用run_sub_agent运行**executionAI**执行剧本切分任务`;
const { textStream } = await u.Ai.Text("productionAgent").stream({
system: prefixSystem + systemPrompt,
messages: [{ role: "user", content: text }],
abortSignal,
tools: {
...skill.tools,
...memory.getTools(),
run_sub_agent: runSubAgent(ctx),
...useTools(ctx.socket),
},
onFinish: async (completion) => {
await memory.add("decisionAI", completion.text);
},
});
return textStream;
}
//====================== 执行层 ======================
export async function executionAI(ctx: AgentContext) {
const { isolationKey, text, abortSignal, resTool } = ctx;
resTool.systemMessage("执行层AI 接管聊天");
const memory = new Memory("productionAgent", isolationKey);
const [skill, mem] = await Promise.all([useSkill("production-agent", "execution"), memory.get(text)]);
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
const { textStream } = await u.Ai.Text("productionAgent").stream({
system: systemPrompt,
messages: [{ role: "user", content: text }],
abortSignal,
tools: {
...skill.tools,
...memory.getTools(),
...useTools(ctx.socket),
},
onFinish: async (completion) => {
await memory.add("executionAI", completion.text);
},
});
return textStream;
}
export async function supervisionAI(ctx: AgentContext) {
const { isolationKey, text, abortSignal } = ctx;
const memory = new Memory("productionAgent", isolationKey);
await memory.add("user", text);
const [skill, mem] = await Promise.all([useSkill("production-agent", "supervision"), memory.get(text)]);
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
const { textStream } = await u.Ai.Text("productionAgent").stream({
system: systemPrompt,
messages: [{ role: "user", content: text }],
abortSignal,
tools: {
...skill.tools,
...memory.getTools(),
},
onFinish: async (completion) => {
await memory.add("supervisionAI", completion.text);
},
});
return textStream;
}
//工具函数
function runSubAgent(parentCtx: AgentContext) {
return tool({
description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI",
inputSchema: z.object({
agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"),
prompt: z.string().describe("交给子Agent的任务描述"),
}),
execute: async ({ agent, prompt }) => {
const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)];
//运行子Agent
const subTextStream = await fn({ ...parentCtx, text: prompt });
let msg: ReturnType<typeof parentCtx.resTool.textMessage>;
let fullResponse = "";
for await (const chunk of subTextStream) {
if (!msg!) msg = parentCtx.resTool.textMessage();
msg.send(chunk);
fullResponse += chunk;
}
msg!.end();
return fullResponse;
},
});
}

View File

@ -1,75 +1,49 @@
import { tool } from "ai"; import { tool, Tool } from "ai";
import { z } from "zod"; import { z } from "zod";
import u from "@/utils"; import u from "@/utils";
import { useSkill } from "@/utils/agent/skillsTools"; import { Socket } from "socket.io";
import { createAGUIStream } from "@/utils/agent/aguiTools";
interface FlowData { interface FlowData {
rawScript: string;
script: { script: {
blocks: string[]; blocks: string[];
}; };
} }
export default (isolationKey: string, agui: ReturnType<typeof createAGUIStream>) => { export default (socket: Socket, toolsNames?: string[]) => {
const flowData: FlowData = { let flowData: FlowData = {
rawScript: "",
script: { script: {
blocks: [], blocks: [],
}, },
}; };
return {
get_project_info: tool({ const tools: Record<string, Tool> = {
description: "获取项目信息", get_flowData: tool({
inputSchema: z.object({}), description: "获取当前工作区的状态/数据",
execute: async () => { inputSchema: z.object({
return ` key: z.enum(["script"]).describe("state的key,rawScript代表原始剧本文字,script代表分块后的剧本"),
}),
3D动漫 execute: async ({ key }) => {
flowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res)));
console.log("[tool] get_flowData:", key);
242 return flowData[key];
3
`;
}, },
}), }),
get_state: tool({ set_flowData_script: tool({
description: "获取工作流指定板块数据", description: "保存数据到工作区",
inputSchema: z.object({ inputSchema: z.object({
block: z.enum(["script"]).describe("板块名称,如 script"), value: z.array(z.string()).describe("剧本分块后的文本数组"),
}), }),
execute: async ({ block }) => { execute: async ({ value }) => {
return flowData[block]; flowData.script.blocks = value;
}, socket.emit("setFlowData", { key: "script", value: { blocks: value } });
}),
execution: tool({
description: "执行层,负责具体执行具体的任务",
inputSchema: z.object({
taskDescription: z.string().describe("具体的任务描述详细信息"),
}),
execute: async ({ taskDescription }) => {
agui.custom("systemMessage", "已由 执行层AI 接管对话");
const skill = await useSkill("production-agent", "execution"); return true;
const { textStream } = await u.Ai.Text("productionAgent").stream({
system: skill.prompt,
messages: [{ role: "user", content: `请完成任务:${taskDescription}` }],
tools: {
...skill.tools,
},
});
let msg: ReturnType<typeof agui.textMessage> | null = null;
let fullResponse = "";
for await (const chunk of textStream) {
if (!msg) msg = agui.textMessage();
msg.send(chunk);
fullResponse += chunk;
}
msg?.end();
return { found: true, memories: ["第一条记忆内容", "第二条记忆内容"] };
}, },
}), }),
}; };
if (!toolsNames) return tools;
else return Object.fromEntries(Object.entries(tools).filter(([name]) => toolsNames.includes(name)));
}; };

View File

@ -2,6 +2,8 @@ import "./logger";
import "./err"; import "./err";
import "./env"; import "./env";
import express, { Request, Response, NextFunction } from "express"; import express, { Request, Response, NextFunction } from "express";
import { Server } from "socket.io";
import http from "node:http";
import expressWs from "express-ws"; import expressWs from "express-ws";
import logger from "morgan"; import logger from "morgan";
import cors from "cors"; import cors from "cors";
@ -9,11 +11,15 @@ import buildRoute from "@/core";
import fs from "fs"; import fs from "fs";
import u from "@/utils"; import u from "@/utils";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import socketInit from "@/socket/index";
const app = express(); const app = express();
let server: ReturnType<typeof app.listen> | null = null; const server = http.createServer(app);
export default async function startServe(randomPort: Boolean = false) { export default async function startServe(randomPort: Boolean = false) {
const io = new Server(server, { cors: { origin: "*" } });
socketInit(io);
if (process.env.NODE_ENV == "dev") await buildRoute(); if (process.env.NODE_ENV == "dev") await buildRoute();
expressWs(app); expressWs(app);
@ -71,8 +77,8 @@ export default async function startServe(randomPort: Boolean = false) {
const port = randomPort ? 0 : parseInt(process.env.PORT || "60000"); const port = randomPort ? 0 : parseInt(process.env.PORT || "60000");
return await new Promise((resolve) => { return await new Promise((resolve) => {
server = app.listen(port, async (v) => { server.listen(port, async () => {
const address = server?.address(); const address = server.address();
const realPort = typeof address === "string" ? address : address?.port; const realPort = typeof address === "string" ? address : address?.port;
console.log(`[服务启动成功]: http://localhost:${realPort}`); console.log(`[服务启动成功]: http://localhost:${realPort}`);
resolve(realPort); resolve(realPort);

View File

@ -1,4 +1,4 @@
// @routes-hash 84d4d9cf99aad5cde060895dad6ed2b1 // @routes-hash ee45dd26b9149e6eabf834c1fb5cfbb6
import { Express } from "express"; import { Express } from "express";
import route1 from "./routes/agents/clearMemory"; import route1 from "./routes/agents/clearMemory";
@ -35,38 +35,35 @@ import route31 from "./routes/novel/getNovel";
import route32 from "./routes/novel/updateNovel"; import route32 from "./routes/novel/updateNovel";
import route33 from "./routes/other/deleteAllData"; import route33 from "./routes/other/deleteAllData";
import route34 from "./routes/other/getCaptcha"; import route34 from "./routes/other/getCaptcha";
import route35 from "./routes/production/editStoryboard/generateStoryboardImage"; import route35 from "./routes/production/getProductionData";
import route36 from "./routes/production/editStoryboard/getStoryboardFlow"; import route36 from "./routes/production/workbench/generateVideo";
import route37 from "./routes/production/editStoryboard/saveStoryboardFlow"; import route37 from "./routes/production/workbench/getVideoModelDetail";
import route38 from "./routes/production/editStoryboard/updateStoryboardFlow"; import route38 from "./routes/project/addProject";
import route39 from "./routes/production/getProductionData"; import route39 from "./routes/project/delProject";
import route40 from "./routes/production/workbench/getVideoModelDetail"; import route40 from "./routes/project/editProject";
import route41 from "./routes/project/addProject"; import route41 from "./routes/project/getProject";
import route42 from "./routes/project/delProject"; import route42 from "./routes/script/addScript";
import route43 from "./routes/project/editProject"; import route43 from "./routes/script/delScript";
import route44 from "./routes/project/getProject"; import route44 from "./routes/script/getScrptApi";
import route45 from "./routes/script/addScript"; import route45 from "./routes/script/updateScript";
import route46 from "./routes/script/delScript"; import route46 from "./routes/setting/agentDeploy/deployAgentModel";
import route47 from "./routes/script/getScrptApi"; import route47 from "./routes/setting/agentDeploy/getAgentDeploy";
import route48 from "./routes/script/updateScript"; import route48 from "./routes/setting/agentDeploy/updateKey";
import route49 from "./routes/setting/agentDeploy/deployAgentModel"; import route49 from "./routes/setting/dbConfig/clearData";
import route50 from "./routes/setting/agentDeploy/getAgentDeploy"; import route50 from "./routes/setting/getTextModel";
import route51 from "./routes/setting/agentDeploy/updateKey"; import route51 from "./routes/setting/loginConfig/getUser";
import route52 from "./routes/setting/dbConfig/clearData"; import route52 from "./routes/setting/loginConfig/updateUserPwd";
import route53 from "./routes/setting/getTextModel"; import route53 from "./routes/setting/memoryConfig/getMemory";
import route54 from "./routes/setting/loginConfig/getUser"; import route54 from "./routes/setting/memoryConfig/sureMemory";
import route55 from "./routes/setting/loginConfig/updateUserPwd"; import route55 from "./routes/setting/vendorConfig/addVendor";
import route56 from "./routes/setting/memoryConfig/getMemory"; import route56 from "./routes/setting/vendorConfig/deleteVendor";
import route57 from "./routes/setting/memoryConfig/sureMemory"; import route57 from "./routes/setting/vendorConfig/getVendorList";
import route58 from "./routes/setting/vendorConfig/addVendor"; import route58 from "./routes/setting/vendorConfig/modelTest";
import route59 from "./routes/setting/vendorConfig/deleteVendor"; import route59 from "./routes/setting/vendorConfig/updateVendor";
import route60 from "./routes/setting/vendorConfig/getVendorList"; import route60 from "./routes/task/getMyTaskApi";
import route61 from "./routes/setting/vendorConfig/modelTest"; import route61 from "./routes/task/getTaskCategories";
import route62 from "./routes/setting/vendorConfig/updateVendor"; import route62 from "./routes/task/taskDetails";
import route63 from "./routes/task/getMyTaskApi"; import route63 from "./routes/test/test";
import route64 from "./routes/task/getTaskCategories";
import route65 from "./routes/task/taskDetails";
import route66 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);
@ -103,36 +100,33 @@ export default async (app: Express) => {
app.use("/api/novel/updateNovel", route32); app.use("/api/novel/updateNovel", route32);
app.use("/api/other/deleteAllData", route33); app.use("/api/other/deleteAllData", route33);
app.use("/api/other/getCaptcha", route34); app.use("/api/other/getCaptcha", route34);
app.use("/api/production/editStoryboard/generateStoryboardImage", route35); app.use("/api/production/getProductionData", route35);
app.use("/api/production/editStoryboard/getStoryboardFlow", route36); app.use("/api/production/workbench/generateVideo", route36);
app.use("/api/production/editStoryboard/saveStoryboardFlow", route37); app.use("/api/production/workbench/getVideoModelDetail", route37);
app.use("/api/production/editStoryboard/updateStoryboardFlow", route38); app.use("/api/project/addProject", route38);
app.use("/api/production/getProductionData", route39); app.use("/api/project/delProject", route39);
app.use("/api/production/workbench/getVideoModelDetail", route40); app.use("/api/project/editProject", route40);
app.use("/api/project/addProject", route41); app.use("/api/project/getProject", route41);
app.use("/api/project/delProject", route42); app.use("/api/script/addScript", route42);
app.use("/api/project/editProject", route43); app.use("/api/script/delScript", route43);
app.use("/api/project/getProject", route44); app.use("/api/script/getScrptApi", route44);
app.use("/api/script/addScript", route45); app.use("/api/script/updateScript", route45);
app.use("/api/script/delScript", route46); app.use("/api/setting/agentDeploy/deployAgentModel", route46);
app.use("/api/script/getScrptApi", route47); app.use("/api/setting/agentDeploy/getAgentDeploy", route47);
app.use("/api/script/updateScript", route48); app.use("/api/setting/agentDeploy/updateKey", route48);
app.use("/api/setting/agentDeploy/deployAgentModel", route49); app.use("/api/setting/dbConfig/clearData", route49);
app.use("/api/setting/agentDeploy/getAgentDeploy", route50); app.use("/api/setting/getTextModel", route50);
app.use("/api/setting/agentDeploy/updateKey", route51); app.use("/api/setting/loginConfig/getUser", route51);
app.use("/api/setting/dbConfig/clearData", route52); app.use("/api/setting/loginConfig/updateUserPwd", route52);
app.use("/api/setting/getTextModel", route53); app.use("/api/setting/memoryConfig/getMemory", route53);
app.use("/api/setting/loginConfig/getUser", route54); app.use("/api/setting/memoryConfig/sureMemory", route54);
app.use("/api/setting/loginConfig/updateUserPwd", route55); app.use("/api/setting/vendorConfig/addVendor", route55);
app.use("/api/setting/memoryConfig/getMemory", route56); app.use("/api/setting/vendorConfig/deleteVendor", route56);
app.use("/api/setting/memoryConfig/sureMemory", route57); app.use("/api/setting/vendorConfig/getVendorList", route57);
app.use("/api/setting/vendorConfig/addVendor", route58); app.use("/api/setting/vendorConfig/modelTest", route58);
app.use("/api/setting/vendorConfig/deleteVendor", route59); app.use("/api/setting/vendorConfig/updateVendor", route59);
app.use("/api/setting/vendorConfig/getVendorList", route60); app.use("/api/task/getMyTaskApi", route60);
app.use("/api/setting/vendorConfig/modelTest", route61); app.use("/api/task/getTaskCategories", route61);
app.use("/api/setting/vendorConfig/updateVendor", route62); app.use("/api/task/taskDetails", route62);
app.use("/api/task/getMyTaskApi", route63); app.use("/api/test/test", route63);
app.use("/api/task/getTaskCategories", route64);
app.use("/api/task/taskDetails", route65);
app.use("/api/test/test", route66);
} }

View File

@ -1,71 +0,0 @@
import express from "express";
import { createAGUIStream } from "@/utils/agent/aguiTools";
import u from "@/utils";
import Memory from "@/utils/agent/memory";
import { useSkill } from "@/utils/agent/skillsTools";
import tools from "@/agents/productionAgent/tools";
const router = express.Router();
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export default router.post("/", async (req, res) => {
const { prompt: text, projectId, episodesId } = req.body;
const isolationKey = `${projectId}:${episodesId}`;
//记忆
const memory = new Memory("productionAgent", isolationKey);
//skill
const skill = await useSkill("production-agent", "decision");
const agui = createAGUIStream(res);
agui.runStarted();
agui.custom("systemMessage", "已由 决策层AI 接管对话");
// 存入用户消息
await memory.add("user", text);
// 获取记忆上下文
const mem = await memory.get(text);
const memoryContext = [
mem.rag.length > 0 && `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`,
mem.summaries.length > 0 && `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`,
mem.shortTerm.length > 0 && `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`,
]
.filter(Boolean)
.join("\n\n");
const systemPrompt = [skill.prompt, memoryContext && `## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`]
.filter(Boolean)
.join("\n\n");
const { textStream } = await u.Ai.Text("productionAgent").stream({
system: systemPrompt,
messages: [{ role: "user", content: text }],
tools: {
...skill.tools,
...memory.getTools(),
...tools(isolationKey, agui),
},
onFinish: async (completion) => {
// 存入助手回复
await memory.add("decisionAI", completion.text);
},
});
let msg: ReturnType<typeof agui.textMessage> | null = null;
let fullResponse = "";
for await (const chunk of textStream) {
if (!msg) msg = agui.textMessage();
msg.send(chunk);
fullResponse += chunk;
}
msg?.end();
agui.runFinished();
agui.end();
});

View File

@ -6,34 +6,38 @@ import { validateFields } from "@/middleware/middleware";
const router = express.Router(); const router = express.Router();
export default router.post( export default router.post(
"/", "/",
validateFields({ validateFields({
type: z.enum(["text", "image", "video", "all"]), type: z.enum(["text", "image", "video", "all"]),
}), }),
async (req, res) => { async (req, res) => {
const { type } = req.body; const { type } = req.body;
const data = await u.db("o_vendorConfig").select("id", "models", "name").first(); const data = await u.db("o_vendorConfig").select("id", "models", "name").first();
if (!data) { if (!data) {
return res.status(404).send({ error: "模型未找到" }); return res.status(404).send({ error: "模型未找到" });
}
const models = JSON.parse(data.models!);
if (type === "all") {
const allData = models.filter((item: { type: string }) => item.type !== "video").map((item: { name: string; modelName: string; type: string }) => ({
id: data.id,
label: item.name,
value: item.modelName,
type: item.type,
name: data.name,
}));
return res.status(200).send(success(allData));
}
const filteredData = models.filter((item: { type: string }) => item.type === type).map((item: { name: string; modelName: string; type: string }) => ({
id: data.id,
label: item.name,
value: item.modelName,
type: item.type,
name: data.name,
}));
res.status(200).send(success(filteredData));
} }
const models = JSON.parse(data.models!);
if (type === "all") {
const allData = models
.filter((item: { type: string }) => item.type !== "video")
.map((item: { name: string; modelName: string; type: string }) => ({
id: data.id,
label: item.name,
value: item.modelName,
type: item.type,
name: data.name,
}));
return res.status(200).send(success(allData));
}
const filteredData = models
.filter((item: { type: string }) => item.type === type)
.map((item: { name: string; modelName: string; type: string }) => ({
id: data.id,
label: item.name,
value: item.modelName,
type: item.type,
name: data.name,
}));
res.status(200).send(success(filteredData));
},
); );

View File

@ -0,0 +1,70 @@
import express from "express";
import u from "@/utils";
import { z } from "zod";
import { v4 as uuidv4 } from "uuid";
import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
scriptId: z.number(),
projectId: z.number(),
storyboardId: z.number(),
prompt: z.string(),
imageData: z.array(z.string()).optional(),
model: z.string(),
duration: z.number(),
resolution: z.string(),
audio: z.boolean().optional(),
modeData: z.string(),
}),
async (req, res) => {
const { scriptId, projectId, storyboardId, prompt, imageData, model, duration, resolution, audio, modeData } = req.body;
console.log("%c Line:24 🍡 req.body", "background:#b03734", req.body);
try {
const relatedObjects = {
id: storyboardId,
projectId,
type: "视频",
};
const systemPrompt = `你是一个专业的视频生成引擎,能够根据用户提供的提示词、图片和参数生成高质量的视频内容。请严格按照用户的需求进行视频创作,确保输出的视频符合以下要求:
1.
2.
3.
4.
5. `;
const videoPath = `/${projectId}/video/${uuidv4()}.mp4`;
const aiVideo = u.Ai.Video(model);
await aiVideo.run({
systemPrompt, // 系统提示词
projectId: projectId,
storyboardId: storyboardId,
prompt: prompt,
imageData: imageData,
modeData: modeData,
duration: duration,
resolution: resolution,
audio: audio,
taskClass: "视频生成",
describe: "根据提示词生成视频",
relatedObjects: JSON.stringify(relatedObjects),
});
await aiVideo.save(videoPath); // 保存视频
//保存视频信息到数据库
// await u.db("o_video").insert({
// resolution,
// prompt,
// filePath: videoPath,
// model,
// time: new Date(),
// state: "生成成功",
// scriptId: scriptId,
// });
res.status(200).send(success("视频生成成功"));
} catch (error) {
res.status(500).send({ error: "视频生成失败" });
}
},
);

View File

@ -40,16 +40,9 @@ const vendorConfigSchema = z.object({
modelName: z.string(), modelName: z.string(),
type: z.literal("video"), type: z.literal("video"),
mode: z.array( mode: z.array(
z.enum([ z.union([
"singleImage", z.enum(["singleImage", "multiImage", "gridImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text"]),
"multiImage", z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])),
"gridImage",
"startEndRequired",
"endFrameOptional",
"startFrameOptional",
"text",
"audioReference",
"videoReference",
]), ]),
), ),
audio: z.union([z.literal("optional"), z.boolean()]), audio: z.union([z.literal("optional"), z.boolean()]),
@ -100,16 +93,9 @@ export default router.post(
modelName: z.string(), modelName: z.string(),
type: z.literal("video"), type: z.literal("video"),
mode: z.array( mode: z.array(
z.enum([ z.union([
"singleImage", z.enum(["singleImage", "multiImage", "gridImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text"]),
"multiImage", z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])),
"gridImage",
"startEndRequired",
"endFrameOptional",
"startFrameOptional",
"text",
"audioReference",
"videoReference",
]), ]),
), ),
audio: z.union([z.literal("optional"), z.boolean()]), audio: z.union([z.literal("optional"), z.boolean()]),

16
src/socket/index.ts Normal file
View File

@ -0,0 +1,16 @@
import { Server } from "socket.io";
import productionAgent from "./routes/productionAgent";
import chat from "./routes/chat";
export default (io: Server) => {
const routes: Record<string, (nsp: ReturnType<Server["of"]>) => void> = {
productionAgent,
chat,
};
for (const [name, handler] of Object.entries(routes)) {
const nsp = io.of(`/api/socket/${name}`);
handler(nsp);
console.log(`[Socket] 注册命名空间: /api/socket/${name}`);
}
};

76
src/socket/resTool.ts Normal file
View File

@ -0,0 +1,76 @@
import u from "@/utils";
import { Socket } from "socket.io";
type Role = "developer" | "system" | "assistant" | "user";
class ResTool {
constructor(private socket: Socket) {}
textMessage(name: string = "AI") {
const messageId = u.uuid();
this.socket.emit("textMessage", {
type: "start",
messageId,
delta: null,
role: "assistant",
name,
});
const handle = {
send: (delta: string) => {
this.socket.emit("textMessage", {
type: "content",
messageId,
delta,
role: "assistant",
name,
});
return handle;
},
end: () => {
this.socket.emit("textMessage", {
type: "end",
messageId,
delta: null,
role: "assistant",
name,
});
},
};
return handle;
}
thinkMessage() {
const messageId = u.uuid();
this.socket.emit("thinkMessage", {
type: "start",
messageId,
delta: null,
role: "assistant",
});
const handle = {
send: (delta: string) => {
this.socket.emit("thinkMessage", {
type: "content",
messageId,
delta,
role: "assistant",
});
return handle;
},
end: () => {
this.socket.emit("thinkMessage", {
type: "end",
messageId,
delta: null,
role: "assistant",
});
},
};
return handle;
}
systemMessage(content: string) {
const messageId = u.uuid();
this.socket.emit("systemMessage", { messageId, content });
}
}
export default ResTool;

43
src/socket/routes/chat.ts Normal file
View File

@ -0,0 +1,43 @@
import { Namespace, Socket } from "socket.io";
const users = new Map<string, string>(); // socketId -> username
export default (nsp: Namespace) => {
nsp.on("connection", (socket: Socket) => {
console.log("[chat] 用户已连接:", socket.id);
socket.on("userLogin", (username: string) => {
users.set(socket.id, username);
socket.broadcast.emit("notification", `${username} 加入了聊天室`);
});
socket.on("sendMessage", (data: { message: string }) => {
const username = users.get(socket.id) || "匿名";
const msg = {
type: "user" as const,
username,
message: data.message,
time: new Date().toLocaleTimeString(),
};
nsp.emit("newMessage", msg);
});
socket.on("typing", () => {
const username = users.get(socket.id);
if (username) socket.broadcast.emit("userTyping", username);
});
socket.on("stopTyping", () => {
socket.broadcast.emit("userStopTyping");
});
socket.on("disconnect", () => {
const username = users.get(socket.id);
if (username) {
users.delete(socket.id);
socket.broadcast.emit("notification", `${username} 离开了聊天室`);
}
console.log("[chat] 用户已断开:", socket.id);
});
});
};

View File

@ -0,0 +1,69 @@
import jwt from "jsonwebtoken";
import u from "@/utils";
import { Namespace, Socket } from "socket.io";
import * as agent from "@/agents/productionAgent/index";
import ResTool from "@/socket/resTool";
async function verifyToken(rawToken: string): Promise<Boolean> {
const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first();
if (!setting) return false;
const { value: tokenKey } = setting;
if (!rawToken) return false;
const token = rawToken.replace("Bearer ", "");
try {
jwt.verify(token, tokenKey as string);
return true;
} catch (err) {
return false;
}
}
export default (nsp: Namespace) => {
nsp.on("connection", async (socket: Socket) => {
const token = socket.handshake.auth.token;
if (!token || !(await verifyToken(token))) {
console.log("[productionAgent] 连接失败token无效");
socket.disconnect();
return;
}
const isolationKey = socket.handshake.auth.isolationKey;
if (!isolationKey) {
console.log("[productionAgent] 连接失败,缺少 isolationKey");
socket.disconnect();
return;
}
console.log("[productionAgent] 已连接:", socket.id);
const resTool = new ResTool(socket);
let abortController: AbortController | null = null;
socket.on("message", async (text: string) => {
abortController?.abort();
abortController = new AbortController();
const currentController = abortController;
const textStream = await agent.decisionAI({ socket, isolationKey, text, abortSignal: currentController.signal, resTool });
let msg = resTool.textMessage();
try {
for await (const chunk of textStream) {
msg.send(chunk);
}
} catch (err: any) {
if (err.name !== "AbortError") throw err;
} finally {
msg.end();
if (abortController === currentController) {
abortController = null;
}
}
});
socket.on("stop", () => {
abortController?.abort();
abortController = null;
});
});
};

View File

@ -1,9 +1,9 @@
// @db-hash 85366ab7662bd484810dcec778054957 // @db-hash feca77a2c2ec5b6a2989347f982558d5
//该文件由脚本自动生成,请勿手动修改 //该文件由脚本自动生成,请勿手动修改
export interface memories { export interface memories {
'content': string; 'content': string;
'createdAt': number; 'createTime': number;
'embedding'?: string | null; 'embedding'?: string | null;
'id'?: string; 'id'?: string;
'isolationKey': string; 'isolationKey': string;
@ -163,17 +163,8 @@ export interface o_skills {
} }
export interface o_storyboard { export interface o_storyboard {
'createTime'?: number | null; 'createTime'?: number | null;
'detail'?: string | null;
'id'?: number; 'id'?: number;
'imageId'?: number | null;
'name'?: string | null; 'name'?: string | null;
'prompt'?: string | null;
'seconds'?: string | null;
}
export interface o_storyboardFlow {
'flowData': string;
'id'?: string;
'stroryboardId': string;
} }
export interface o_storyboardScript { export interface o_storyboardScript {
'id'?: number; 'id'?: number;
@ -263,7 +254,6 @@ export interface DB {
"o_setting": o_setting; "o_setting": o_setting;
"o_skills": o_skills; "o_skills": o_skills;
"o_storyboard": o_storyboard; "o_storyboard": o_storyboard;
"o_storyboardFlow": o_storyboardFlow;
"o_storyboardScript": o_storyboardScript; "o_storyboardScript": o_storyboardScript;
"o_tasks": o_tasks; "o_tasks": o_tasks;
"o_user": o_user; "o_user": o_user;

View File

@ -97,11 +97,11 @@ class Memory {
embedding: JSON.stringify(embedding), embedding: JSON.stringify(embedding),
relatedMessageIds: null, relatedMessageIds: null,
summarized: 0, summarized: 0,
createdAt: Date.now(), createTime: Date.now(),
} as any); } as any);
// 检查未总结消息数量 // 检查未总结消息数量
const unsummarized = await u.db("memories").where({ isolationKey, type: "message", summarized: 0 }).orderBy("createdAt", "asc"); const unsummarized = await u.db("memories").where({ isolationKey, type: "message", summarized: 0 }).orderBy("createTime", "asc");
if (unsummarized.length >= Number(messagesPerSummary)) { if (unsummarized.length >= Number(messagesPerSummary)) {
const batch = unsummarized.slice(0, Number(messagesPerSummary)); const batch = unsummarized.slice(0, Number(messagesPerSummary));
@ -120,7 +120,7 @@ class Memory {
embedding: JSON.stringify(summaryEmbedding), embedding: JSON.stringify(summaryEmbedding),
relatedMessageIds: JSON.stringify(batchIds), relatedMessageIds: JSON.stringify(batchIds),
summarized: 0, summarized: 0,
createdAt: Date.now(), createTime: Date.now(),
} as any); } as any);
// 标记已总结 // 标记已总结
@ -140,12 +140,12 @@ class Memory {
const shortTerm = await u const shortTerm = await u
.db("memories") .db("memories")
.where({ isolationKey, type: "message", summarized: 0 }) .where({ isolationKey, type: "message", summarized: 0 })
.orderBy("createdAt", "desc") .orderBy("createTime", "desc")
.limit(Number(shortTermLimit)); .limit(Number(shortTermLimit));
shortTerm.reverse(); // 最旧在前 shortTerm.reverse(); // 最旧在前
// summaries: 最近的 summary // summaries: 最近的 summary
const summaries = await u.db("memories").where({ isolationKey, type: "summary" }).orderBy("createdAt", "desc").limit(Number(summaryLimit)); const summaries = await u.db("memories").where({ isolationKey, type: "summary" }).orderBy("createTime", "desc").limit(Number(summaryLimit));
summaries.reverse(); summaries.reverse();
// rag: 向量搜索所有 messages // rag: 向量搜索所有 messages
@ -154,12 +154,12 @@ class Memory {
const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit)); const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit));
return { return {
shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createdAt: m.createdAt })), shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createTime: m.createTime })),
summaries: summaries.map((s) => ({ summaries: summaries.map((s) => ({
id: s.id, id: s.id,
content: s.content, content: s.content,
relatedMessageIds: JSON.parse(s.relatedMessageIds || "[]"), relatedMessageIds: JSON.parse(s.relatedMessageIds || "[]"),
createdAt: (s as any).createdAt, createTime: (s as any).createTime,
})), })),
rag: ragResults.map((r) => ({ id: r.id, content: r.content, similarity: r.similarity })), rag: ragResults.map((r) => ({ id: r.id, content: r.content, similarity: r.similarity })),
}; };
@ -190,9 +190,9 @@ class Memory {
if (messageIds.length === 0) return []; if (messageIds.length === 0) return [];
const messages = await u.db("memories").whereIn("id", messageIds).orderBy("createdAt", "asc"); const messages = await u.db("memories").whereIn("id", messageIds).orderBy("createTime", "asc");
return messages.map((m) => ({ id: m.id, content: m.content, createdAt: m.createdAt })); return messages.map((m) => ({ id: m.id, content: m.content, createTime: m.createTime }));
} }
getTools() { getTools() {

View File

@ -23,10 +23,16 @@ function parseFrontmatter(content: string): { name: string; description: string
for (let i = 0; i < lines.length; ) { for (let i = 0; i < lines.length; ) {
const colonIndex = lines[i].indexOf(":"); const colonIndex = lines[i].indexOf(":");
if (colonIndex === -1) { i++; continue; } if (colonIndex === -1) {
i++;
continue;
}
const key = lines[i].slice(0, colonIndex).trim(); const key = lines[i].slice(0, colonIndex).trim();
if (!key) { i++; continue; } if (!key) {
i++;
continue;
}
let value = lines[i].slice(colonIndex + 1).trim(); let value = lines[i].slice(colonIndex + 1).trim();
i++; i++;
@ -44,8 +50,7 @@ function parseFrontmatter(content: string): { name: string; description: string
result[key] = value; result[key] = value;
} }
if (!result.name) throw new Error("Frontmatter missing required field: name"); if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description");
if (!result.description) throw new Error("Frontmatter missing required field: description");
return { name: result.name, description: result.description }; return { name: result.name, description: result.description };
} }
@ -57,13 +62,17 @@ function stripFrontmatter(content: string): string {
async function listResources(dir: string, base = ""): Promise<string[]> { async function listResources(dir: string, base = ""): Promise<string[]> {
let entries; let entries;
try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return []; } try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
return [];
}
const files: string[] = []; const files: string[] = [];
for (const entry of entries) { for (const entry of entries) {
const rel = base ? `${base}/${entry.name}` : entry.name; const rel = base ? `${base}/${entry.name}` : entry.name;
if (entry.isDirectory()) { if (entry.isDirectory()) {
files.push(...await listResources(path.join(dir, entry.name), rel)); files.push(...(await listResources(path.join(dir, entry.name), rel)));
} else if (entry.name !== "SKILL.md") { } else if (entry.name !== "SKILL.md") {
files.push(rel); files.push(rel);
} }
@ -71,22 +80,39 @@ async function listResources(dir: string, base = ""): Promise<string[]> {
return files; return files;
} }
// ==================== 读取单个技能 ====================
async function readSkillFromDir(skillDir: string): Promise<SkillRecord | null> {
const location = path.join(skillDir, "SKILL.md");
let content: string;
try {
content = await fs.readFile(location, "utf-8");
} catch {
return null;
}
try {
const meta = parseFrontmatter(content);
console.log(`[Skill] ✅ 发现技能:${meta.name}${meta.description}`);
return { ...meta, location, baseDir: skillDir };
} catch (e) {
console.log(`[Skill] ⚠️ 解析失败 "${skillDir}"${(e as Error).message}`);
return null;
}
}
// ==================== 构建技能目录 ==================== // ==================== 构建技能目录 ====================
function buildCatalog(skill: SkillRecord): string { function buildCatalog(skills: SkillRecord[]): string {
return [ const entries = skills.map((s) => ` <skill>\n <name>${s.name}</name>\n <description>${s.description}</description>\n </skill>`).join("\n");
"## Skills",
"以下技能提供了专业任务的专用指令。", return `## Skills
"当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。",
"加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。", activate_skill
"", read_skill_file
"<available_skills>",
` <skill>`, <available_skills>
` <name>${skill.name}</name>`, ${entries}
` <description>${skill.description}</description>`, </available_skills>`;
` </skill>`,
"</available_skills>",
].join("\n");
} }
// ==================== 激活 + 执行工具 ==================== // ==================== 激活 + 执行工具 ====================
@ -107,14 +133,15 @@ function createSkillTools(skills: SkillRecord[]) {
console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`); console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`);
return { error: `Skill '${name}' not found` }; return { error: `Skill '${name}' not found` };
} }
if (activated.has(name)) { if (activated.has(name)) {
console.log(`[Skill] 技能 "${name}" 已在当前会话中激活,跳过重复注入`); console.log(`[Skill] 技能 "${name}" 已在当前会话中激活,跳过重复注入`);
return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` }; return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` };
} }
let content: string; let content: string;
try { content = await fs.readFile(skill.location, "utf-8"); } catch { try {
content = await fs.readFile(skill.location, "utf-8");
} catch {
console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`); console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`);
return { error: `Failed to read SKILL.md for '${name}'` }; return { error: `Failed to read SKILL.md for '${name}'` };
} }
@ -122,23 +149,19 @@ function createSkillTools(skills: SkillRecord[]) {
const body = stripFrontmatter(content); const body = stripFrontmatter(content);
const resources = await listResources(skill.baseDir); const resources = await listResources(skill.baseDir);
activated.add(name); activated.add(name);
console.log(`[Skill] 📖 已激活技能:${skill.name}${body.length} 字符,${resources.length} 个资源文件)`); console.log(`[Skill] 📖 已激活技能:${skill.name}${body.length} 字符,${resources.length} 个资源文件)`);
const resourcesXml = resources.length > 0 const resourcesXml =
? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>` resources.length > 0 ? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>` : "";
: "";
return { return {
content: [ content: `<skill_content name="${skill.name}">
`<skill_content name="${skill.name}">`, ${body}
body,
"", Skill directory: ${skill.baseDir}
`Skill directory: ${skill.baseDir}`, 使 read_skill_file
`相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。`, ${resourcesXml}
resourcesXml, </skill_content>`,
`</skill_content>`,
].join("\n"),
}; };
}, },
}), }),
@ -180,26 +203,25 @@ function createSkillTools(skills: SkillRecord[]) {
export async function useSkill(...segments: string[]) { export async function useSkill(...segments: string[]) {
if (segments.length === 0) return { prompt: "", tools: {} }; if (segments.length === 0) return { prompt: "", tools: {} };
const baseDir = path.join(getPath("skills"), ...segments); const skills = new Map<string, SkillRecord>();
const location = path.join(baseDir, "SKILL.md");
let content: string; const primary = await readSkillFromDir(path.join(getPath("skills"), ...segments));
try { content = await fs.readFile(location, "utf-8"); } catch { if (primary) skills.set(primary.name, primary);
console.log(`[Skill] ⚠️ 未发现技能:${segments.join("/")}`);
return { prompt: "", tools: {} }; const publicDir = path.join(getPath("skills"), "public");
try {
const entries = await fs.readdir(publicDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const skill = await readSkillFromDir(path.join(publicDir, entry.name));
if (skill && !skills.has(skill.name)) skills.set(skill.name, skill);
}
} catch {
/* public dir not found */
} }
let metadata: { name: string; description: string }; if (skills.size === 0) return { prompt: "", tools: {} };
try { metadata = parseFrontmatter(content); } catch (e) {
console.log(`[Skill] ⚠️ 解析失败 "${segments.join("/")}"${(e as Error).message}`);
return { prompt: "", tools: {} };
}
const skill: SkillRecord = { ...metadata, location, baseDir }; const allSkills = [...skills.values()];
console.log(`[Skill] ✅ 发现技能:${skill.name}${skill.description}`); return { prompt: buildCatalog(allSkills), tools: createSkillTools(allSkills) };
return {
prompt: buildCatalog(skill),
tools: createSkillTools([skill]),
};
} }

View File

@ -112,13 +112,28 @@ class AiImage {
return this; return this;
} }
} }
interface VideoConfig {
projectId: number; // 项目ID
storyboardId: number; // 关联的分镜ID
systemPrompt?: string; // 系统提示词
prompt: string; //视频提示词
imageData: string[]; //输入的图片提示词
modeData: string; //模式
duration: number; // 视频时长,单位秒
resolution: string; // 视频分辨率
audio: boolean; // 是否需要配音
taskClass: string; // 任务分类
describe: string; // 任务描述
relatedObjects: string; // 相关对象信息,便于后续分析和追踪
}
class AiVideo { class AiVideo {
private key: `${number}:${string}`; private key: `${number}:${string}`;
private result: string = ""; private result: string = "";
constructor(key: `${number}:${string}`) { constructor(key: `${number}:${string}`) {
this.key = key; this.key = key;
} }
async run(input: ImageConfig) { async run(input: VideoConfig) {
return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => { return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => {
const fn = await getVendorTemplateFn("videoRequest", modelName); const fn = await getVendorTemplateFn("videoRequest", modelName);
this.result = await fn(input); this.result = await fn(input);
@ -137,7 +152,7 @@ class AiAudio {
constructor(key: `${number}:${string}`) { constructor(key: `${number}:${string}`) {
this.key = key; this.key = key;
} }
async run(input: ImageConfig) { async run(input: VideoConfig) {
return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => { return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => {
const fn = await getVendorTemplateFn("ttsRequest", modelName); const fn = await getVendorTemplateFn("ttsRequest", modelName);
this.result = await fn(input); this.result = await fn(input);

View File

@ -742,6 +742,11 @@
resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
"@socket.io/component-emitter@~3.1.0":
version "3.1.2"
resolved "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
"@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0": "@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
@ -784,7 +789,7 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/cors@^2.8.19": "@types/cors@^2.8.12", "@types/cors@^2.8.19":
version "2.8.19" version "2.8.19"
resolved "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" resolved "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342"
integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==
@ -882,7 +887,7 @@
dependencies: dependencies:
undici-types "~7.18.0" undici-types "~7.18.0"
"@types/node@>=13.7.0": "@types/node@>=10.0.0", "@types/node@>=13.7.0":
version "25.5.0" version "25.5.0"
resolved "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31" resolved "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31"
integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw== integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==
@ -946,7 +951,7 @@
resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb" resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb"
integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg== integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==
"@types/ws@*": "@types/ws@*", "@types/ws@^8.5.12":
version "8.18.1" version "8.18.1"
resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
@ -988,6 +993,14 @@ accepts@^2.0.0:
mime-types "^3.0.0" mime-types "^3.0.0"
negotiator "^1.0.0" negotiator "^1.0.0"
accepts@~1.3.4:
version "1.3.8"
resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
acorn-walk@^8.3.4: acorn-walk@^8.3.4:
version "8.3.5" version "8.3.5"
resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496"
@ -1230,6 +1243,11 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64id@2.0.0, base64id@~2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
basic-auth@~2.0.1: basic-auth@~2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.npmmirror.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" resolved "https://registry.npmmirror.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
@ -1661,7 +1679,7 @@ cookie-signature@^1.2.1:
resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
cookie@^0.7.1: cookie@^0.7.1, cookie@~0.7.2:
version "0.7.2" version "0.7.2"
resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
@ -1671,7 +1689,7 @@ core-util-is@1.0.2:
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
cors@^2.8.5: cors@^2.8.5, cors@~2.8.5:
version "2.8.6" version "2.8.6"
resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96"
integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==
@ -1710,7 +1728,7 @@ debug@2.6.9:
dependencies: dependencies:
ms "2.0.0" ms "2.0.0"
debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3: debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3, debug@~4.4.1:
version "4.4.3" version "4.4.3"
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
@ -1974,6 +1992,27 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies: dependencies:
once "^1.4.0" once "^1.4.0"
engine.io-parser@~5.2.1:
version "5.2.3"
resolved "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
engine.io@~6.6.0:
version "6.6.6"
resolved "https://registry.npmmirror.com/engine.io/-/engine.io-6.6.6.tgz#9942111e7a4dc31f057e73470d7b7fcc7f74c390"
integrity sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==
dependencies:
"@types/cors" "^2.8.12"
"@types/node" ">=10.0.0"
"@types/ws" "^8.5.12"
accepts "~1.3.4"
base64id "2.0.0"
cookie "~0.7.2"
cors "~2.8.5"
debug "~4.4.1"
engine.io-parser "~5.2.1"
ws "~8.18.3"
env-paths@^2.2.0: env-paths@^2.2.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@ -3131,7 +3170,7 @@ mime-db@^1.54.0:
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-types@^2.1.12: mime-types@^2.1.12, mime-types@~2.1.34:
version "2.1.35" version "2.1.35"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -3344,6 +3383,11 @@ napi-build-utils@^2.0.0:
resolved "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" resolved "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
negotiator@^0.6.2: negotiator@^0.6.2:
version "0.6.4" version "0.6.4"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
@ -4303,6 +4347,35 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0:
resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
socket.io-adapter@~2.5.2:
version "2.5.6"
resolved "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz#c697f609d36a676a46749782274607d8df52c1d8"
integrity sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==
dependencies:
debug "~4.4.1"
ws "~8.18.3"
socket.io-parser@~4.2.4:
version "4.2.6"
resolved "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.6.tgz#19156bf179af3931abd05260cfb1491822578a6f"
integrity sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.4.1"
socket.io@^4.8.3:
version "4.8.3"
resolved "https://registry.npmmirror.com/socket.io/-/socket.io-4.8.3.tgz#ca6ba1431c69532e1e0a6f496deebeb601dbc4df"
integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==
dependencies:
accepts "~1.3.4"
base64id "~2.0.0"
cors "~2.8.5"
debug "~4.4.1"
engine.io "~6.6.0"
socket.io-adapter "~2.5.2"
socket.io-parser "~4.2.4"
socks-proxy-agent@^6.0.0: socks-proxy-agent@^6.0.0:
version "6.2.1" version "6.2.1"
resolved "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" resolved "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce"
@ -4939,6 +5012,11 @@ ws@^7.4.6:
resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
ws@~8.18.3:
version "8.18.3"
resolved "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1:
version "15.1.1" version "15.1.1"
resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"