# Conflicts:
#	src/agents/productionAgent/tools.ts
#	src/router.ts
#	src/routes/script/extractAssets.ts
#	src/types/database.d.ts
This commit is contained in:
zhishi 2026-03-30 21:52:45 +08:00
commit 90cb5fc4ba
23 changed files with 1258 additions and 1280 deletions

File diff suppressed because one or more lines are too long

View File

@ -63,6 +63,7 @@
"knex": "^3.2.5", "knex": "^3.2.5",
"lodash": "^4.17.23", "lodash": "^4.17.23",
"morgan": "^1.10.1", "morgan": "^1.10.1",
"p-limit": "^7.3.0",
"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",

View File

@ -51,7 +51,7 @@ export async function decisionAI(ctx: AgentContext) {
const { textStream } = await u.Ai.Text("productionAgent").stream({ const { textStream } = await u.Ai.Text("productionAgent").stream({
messages: [ messages: [
{ role: "system", content: prompt }, { role: "system", content: prompt },
{ role: "system", content: mem }, { role: "assistant", content: mem },
{ role: "user", content: text }, { role: "user", content: text },
], ],
abortSignal, abortSignal,
@ -174,8 +174,8 @@ function createSubAgent(parentCtx: AgentContext) {
} }
async function createArtSkills(artName: string) { async function createArtSkills(artName: string) {
const path = u.getPath(["skills", "art_prompts", artName, "driector_skills"]); const workerPath = u.getPath(["skills", "art_prompts", artName, "driector_skills"]);
const skillList = await scanSkills(path + "/*.md"); const skillList = await scanSkills(workerPath + "/*.md");
const mainSkills: { path: string; name: string; description: string }[] = []; const mainSkills: { path: string; name: string; description: string }[] = [];
for (const skillPath of skillList) { for (const skillPath of skillList) {
if (!fs.existsSync(skillPath)) throw new Error(`主技能文件不存在: ${skillPath}`); if (!fs.existsSync(skillPath)) throw new Error(`主技能文件不存在: ${skillPath}`);
@ -183,8 +183,9 @@ async function createArtSkills(artName: string) {
const parsed = parseFrontmatter(content); const parsed = parseFrontmatter(content);
mainSkills.push({ path: skillPath, ...parsed }); mainSkills.push({ path: skillPath, ...parsed });
} }
return { const res = {
prompt: buildSkillPrompt(mainSkills), prompt: buildSkillPrompt(mainSkills),
tools: createSkillTools(mainSkills, { mainSkill: mainSkills, secondarySkills: [], tertiarySkills: [] }), tools: createSkillTools(mainSkills, { mainSkill: mainSkills, secondarySkills: [], tertiarySkills: [] },workerPath),
}; };
} return res
}

View File

@ -53,7 +53,7 @@ const posterItemSchema = z.object({
id: z.number().describe("海报ID"), id: z.number().describe("海报ID"),
image: z.string().describe("海报图片路径"), image: z.string().describe("海报图片路径"),
}); });
const flowDataSchema = z.object({ export const flowDataSchema = z.object({
script: z.string().describe("剧本内容"), script: z.string().describe("剧本内容"),
scriptPlan: z.string().describe("拍摄计划"), scriptPlan: z.string().describe("拍摄计划"),
assets: z.array(assetItemSchema).describe("衍生资产"), assets: z.array(assetItemSchema).describe("衍生资产"),
@ -150,7 +150,6 @@ export default (toolCpnfig: ToolConfig) => {
return res ?? "删除成功"; return res ?? "删除成功";
}, },
}), }),
generate_deriveAsset: tool({ generate_deriveAsset: tool({
description: "生成衍生资产", description: "生成衍生资产",
inputSchema: z.object({ inputSchema: z.object({

View File

@ -139,7 +139,7 @@ function createSubAgent(parentCtx: AgentContext) {
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({ return runAgent({
prompt, prompt,
system: systemPrompt + "你可以使用如下XML格式写入工作区\n<storySkeleton>故事骨架内容</storySkeleton>", system: systemPrompt + "\n你可以使用如下XML格式写入工作区\n<storySkeleton>故事骨架内容</storySkeleton>",
name: "编剧", name: "编剧",
memoryKey: "assistant:execution:storySkeleton", memoryKey: "assistant:execution:storySkeleton",
}); });
@ -154,7 +154,7 @@ function createSubAgent(parentCtx: AgentContext) {
const systemPrompt = await fs.promises.readFile(skill, "utf-8"); const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({ return runAgent({
prompt, prompt,
system: systemPrompt + "你可以使用如下XML格式写入工作区\n<adaptationStrategy>改编策略内容</adaptationStrategy>", system: systemPrompt + "\n你可以使用如下XML格式写入工作区\n<adaptationStrategy>改编策略内容</adaptationStrategy>",
name: "编剧", name: "编剧",
memoryKey: "assistant:execution:adaptationStrategy", memoryKey: "assistant:execution:adaptationStrategy",
}); });
@ -171,7 +171,7 @@ function createSubAgent(parentCtx: AgentContext) {
prompt, prompt,
system: system:
systemPrompt + systemPrompt +
`你可以使用如下XML格式写入工作区\nXML不得添加任何额外标签<script><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item></script>`, `\n你可以使用如下XML格式写入工作区\nXML不得添加任何额外标签<script><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item></script>`,
name: "编剧", name: "编剧",
memoryKey: "assistant:execution:script", memoryKey: "assistant:execution:script",
}); });

View File

@ -1,4 +1,4 @@
// @routes-hash 8d0bd75a9e06280f64490cd32d7a5b2e // @routes-hash fc02af7340ae26f567792eda4cde50a6
import { Express } from "express"; import { Express } from "express";
import route1 from "./routes/agents/clearMemory"; import route1 from "./routes/agents/clearMemory";
@ -59,36 +59,36 @@ import route55 from "./routes/production/getStoryboardData";
import route56 from "./routes/production/saveFlowData"; import route56 from "./routes/production/saveFlowData";
import route57 from "./routes/production/storyboard/batchGenerateImage"; import route57 from "./routes/production/storyboard/batchGenerateImage";
import route58 from "./routes/production/storyboard/downPreviewImage"; import route58 from "./routes/production/storyboard/downPreviewImage";
import route59 from "./routes/production/storyboard/generatestory2Image"; import route59 from "./routes/production/storyboard/getStoryboardData";
import route60 from "./routes/production/storyboard/getStoryboardData"; import route60 from "./routes/production/storyboard/pollingImage";
import route61 from "./routes/production/storyboard/pollingImage"; import route61 from "./routes/production/storyboard/previewImage";
import route62 from "./routes/production/storyboard/previewImage"; import route62 from "./routes/production/workbench/confirmSelection";
import route63 from "./routes/production/workbench/confirmSelection"; import route63 from "./routes/production/workbench/delVideo";
import route64 from "./routes/production/workbench/delVideo"; import route64 from "./routes/production/workbench/generateVideo";
import route65 from "./routes/production/workbench/generateVideo"; import route65 from "./routes/production/workbench/generateVideoPrompt";
import route66 from "./routes/production/workbench/generateVideoPrompt"; import route66 from "./routes/production/workbench/getChatLines";
import route67 from "./routes/production/workbench/getChatLines"; import route67 from "./routes/production/workbench/getVideoModelDetail";
import route68 from "./routes/production/workbench/getVideoModelDetail"; import route68 from "./routes/production/workbench/videoPolling";
import route69 from "./routes/production/workbench/videoPolling"; import route69 from "./routes/project/addProject";
import route70 from "./routes/project/addProject"; import route70 from "./routes/project/addVisual";
import route71 from "./routes/project/addVisual"; import route71 from "./routes/project/addVisualManual";
import route72 from "./routes/project/addVisualManual"; import route72 from "./routes/project/deleteVisualManual";
import route73 from "./routes/project/deleteVisualManual"; import route73 from "./routes/project/delProject";
import route74 from "./routes/project/delProject"; import route74 from "./routes/project/editProject";
import route75 from "./routes/project/editProject"; import route75 from "./routes/project/editVisualManual";
import route76 from "./routes/project/editVisualManual"; import route76 from "./routes/project/getProject";
import route77 from "./routes/project/getProject"; import route77 from "./routes/project/getVisualManual";
import route78 from "./routes/project/getVisualManual"; import route78 from "./routes/project/visualManual";
import route79 from "./routes/project/visualManual"; import route79 from "./routes/script/addScript";
import route80 from "./routes/script/addScript"; import route80 from "./routes/script/delScript";
import route81 from "./routes/script/delScript"; import route81 from "./routes/script/exportScript";
import route82 from "./routes/script/exportScript"; import route82 from "./routes/script/extractAssets";
import route83 from "./routes/script/extractAssets"; import route83 from "./routes/script/getScrptApi";
import route84 from "./routes/script/getScrptApi"; import route84 from "./routes/script/pollScriptAssets";
import route85 from "./routes/script/pollScriptAssets"; import route85 from "./routes/script/updateScript";
import route86 from "./routes/script/updateScript"; import route86 from "./routes/scriptAgent/getPlanData";
import route87 from "./routes/scriptAgent/getPlanData"; import route87 from "./routes/scriptAgent/setPlanData";
import route88 from "./routes/scriptAgent/setPlanData"; import route88 from "./routes/scriptAgent/updateData";
import route89 from "./routes/setting/about/checkUpdate"; import route89 from "./routes/setting/about/checkUpdate";
import route90 from "./routes/setting/about/downloadApp"; import route90 from "./routes/setting/about/downloadApp";
import route91 from "./routes/setting/agentDeploy/agentSetKey"; import route91 from "./routes/setting/agentDeploy/agentSetKey";
@ -180,36 +180,36 @@ export default async (app: Express) => {
app.use("/api/production/saveFlowData", route56); app.use("/api/production/saveFlowData", route56);
app.use("/api/production/storyboard/batchGenerateImage", route57); app.use("/api/production/storyboard/batchGenerateImage", route57);
app.use("/api/production/storyboard/downPreviewImage", route58); app.use("/api/production/storyboard/downPreviewImage", route58);
app.use("/api/production/storyboard/generatestory2Image", route59); app.use("/api/production/storyboard/getStoryboardData", route59);
app.use("/api/production/storyboard/getStoryboardData", route60); app.use("/api/production/storyboard/pollingImage", route60);
app.use("/api/production/storyboard/pollingImage", route61); app.use("/api/production/storyboard/previewImage", route61);
app.use("/api/production/storyboard/previewImage", route62); app.use("/api/production/workbench/confirmSelection", route62);
app.use("/api/production/workbench/confirmSelection", route63); app.use("/api/production/workbench/delVideo", route63);
app.use("/api/production/workbench/delVideo", route64); app.use("/api/production/workbench/generateVideo", route64);
app.use("/api/production/workbench/generateVideo", route65); app.use("/api/production/workbench/generateVideoPrompt", route65);
app.use("/api/production/workbench/generateVideoPrompt", route66); app.use("/api/production/workbench/getChatLines", route66);
app.use("/api/production/workbench/getChatLines", route67); app.use("/api/production/workbench/getVideoModelDetail", route67);
app.use("/api/production/workbench/getVideoModelDetail", route68); app.use("/api/production/workbench/videoPolling", route68);
app.use("/api/production/workbench/videoPolling", route69); app.use("/api/project/addProject", route69);
app.use("/api/project/addProject", route70); app.use("/api/project/addVisual", route70);
app.use("/api/project/addVisual", route71); app.use("/api/project/addVisualManual", route71);
app.use("/api/project/addVisualManual", route72); app.use("/api/project/deleteVisualManual", route72);
app.use("/api/project/deleteVisualManual", route73); app.use("/api/project/delProject", route73);
app.use("/api/project/delProject", route74); app.use("/api/project/editProject", route74);
app.use("/api/project/editProject", route75); app.use("/api/project/editVisualManual", route75);
app.use("/api/project/editVisualManual", route76); app.use("/api/project/getProject", route76);
app.use("/api/project/getProject", route77); app.use("/api/project/getVisualManual", route77);
app.use("/api/project/getVisualManual", route78); app.use("/api/project/visualManual", route78);
app.use("/api/project/visualManual", route79); app.use("/api/script/addScript", route79);
app.use("/api/script/addScript", route80); app.use("/api/script/delScript", route80);
app.use("/api/script/delScript", route81); app.use("/api/script/exportScript", route81);
app.use("/api/script/exportScript", route82); app.use("/api/script/extractAssets", route82);
app.use("/api/script/extractAssets", route83); app.use("/api/script/getScrptApi", route83);
app.use("/api/script/getScrptApi", route84); app.use("/api/script/pollScriptAssets", route84);
app.use("/api/script/pollScriptAssets", route85); app.use("/api/script/updateScript", route85);
app.use("/api/script/updateScript", route86); app.use("/api/scriptAgent/getPlanData", route86);
app.use("/api/scriptAgent/getPlanData", route87); app.use("/api/scriptAgent/setPlanData", route87);
app.use("/api/scriptAgent/setPlanData", route88); app.use("/api/scriptAgent/updateData", route88);
app.use("/api/setting/about/checkUpdate", route89); app.use("/api/setting/about/checkUpdate", route89);
app.use("/api/setting/about/downloadApp", route90); app.use("/api/setting/about/downloadApp", route90);
app.use("/api/setting/agentDeploy/agentSetKey", route91); app.use("/api/setting/agentDeploy/agentSetKey", route91);

View File

@ -111,7 +111,6 @@ export default router.post(
${config.label} ${config.label}
**** ****
- 风格: ${project?.artStyle || "未指定"}
- 小说类型: ${project?.type || "未指定"} - 小说类型: ${project?.type || "未指定"}
- 小说背景: ${project?.intro || "未指定"} - 小说背景: ${project?.intro || "未指定"}

View File

@ -60,7 +60,6 @@ export default router.post(
}, },
], ],
}); });
console.log("%c Line:35 🎂 text", "background:#3f7cff", text);
const repeloadObj = { const repeloadObj = {
prompt: text, prompt: text,

View File

@ -5,7 +5,6 @@ import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
const router = express.Router(); const router = express.Router();
import compressing from "compressing"; import compressing from "compressing";
import { flowDataSchema } from "@/agents/productionAgent/tools";
import path from "path"; import path from "path";
import getPath from "@/utils/getPath"; import getPath from "@/utils/getPath";

View File

@ -102,6 +102,7 @@ export default router.post(
3. 3.
4. 4.
5. `; 5. `;
console.log("%c Line:110 🍑 prompt", "background:#b03734", prompt);
const aiVideo = u.Ai.Video(model); const aiVideo = u.Ai.Video(model);
await aiVideo.run({ await aiVideo.run({

View File

@ -3,60 +3,55 @@ import u from "@/utils";
import { z } from "zod"; import { z } from "zod";
import { success } from "@/lib/responseFormat"; import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
import { Output } from "ai";
const router = express.Router(); const router = express.Router();
export default router.post( export default router.post(
"/", "/",
validateFields({ validateFields({
projectId: z.number(), projectId: z.number(),
storyboardId: z.array(z.number()), storyboardId: z.number(),
}), }),
async (req, res) => { async (req, res) => {
const { projectId, storyboardId } = req.body; const { projectId, storyboardId } = req.body;
// 查询分镜及其关联的资产提示词 // 查询分镜及其关联的资产提示词
const data = await u const data = await u
.db("o_storyboard") .db("o_storyboard")
.leftJoin("o_assets2Storyboard", "o_storyboard.id", "o_assets2Storyboard.storyboardId") .leftJoin("o_assets2Storyboard", "o_storyboard.id", "o_assets2Storyboard.storyboardId")
.leftJoin("o_assets", "o_assets2Storyboard.assetId", "o_assets.id") .leftJoin("o_assets", "o_assets2Storyboard.assetId", "o_assets.id")
.leftJoin("o_videoConfig", "o_storyboard.id", "o_videoConfig.storyboardId") .leftJoin("o_videoConfig", "o_storyboard.id", "o_videoConfig.storyboardId")
.whereIn("o_storyboard.id", storyboardId) .where("o_storyboard.id", storyboardId)
.select("o_storyboard.id", "o_storyboard.prompt", "o_assets.prompt as assetPrompt", "o_videoConfig.model as videoModel"); .select("o_storyboard.id", "o_storyboard.prompt", "o_assets.prompt as assetPrompt", "o_videoConfig.model as videoModel");
// 按分镜id分组聚合资产提示词 if (data.length === 0) {
const storyboardMap = new Map<number, { prompt: string; assetPrompts: string[]; videoModel: string }>(); return res.status(200).send(success({ data: null }));
for (const row of data) {
if (!storyboardMap.has(row.id)) {
storyboardMap.set(row.id, { prompt: row.prompt || "", assetPrompts: [], videoModel: row.videoModel || "" });
}
if (row.assetPrompt) {
storyboardMap.get(row.id)!.assetPrompts.push(row.assetPrompt);
}
} }
// 逐个分镜生成视频提示词 // 聚合资产提示词
const results: { storyboardId: number; videoPrompt: string }[] = []; const prompt = data[0].prompt || "";
for (const [id, { prompt, assetPrompts, videoModel }] of storyboardMap) { const videoModel = data[0].videoModel || "";
let model = ""; const assetPrompts = data.map((row) => row.assetPrompt).filter(Boolean) as string[];
if (videoModel) {
model = videoModel;
} else {
const videoModel = await u.db("o_project").where("id", projectId).select("videoModel").first();
model = videoModel?.videoModel || "";
}
if (!model) return res.status(400).json({ error: "未找到视频模型,请检查项目配置" });
const systemPrompt = `你是一个专业的${model}视频生成助手。请根据分镜提示词和关联资产提示词,生成一段完整的、可直接用于视频生成模型的中文提示词。`;
const userContent = `分镜提示词:${prompt || "无"}\n资产提示词${assetPrompts.length > 0 ? assetPrompts.join("\n") : "无"}`;
const { text } = await u.Ai.Text("universalAi").invoke({ // 确定视频模型
system: systemPrompt, let model = videoModel;
messages: [{ role: "user", content: userContent }], if (!model) {
}); const project = await u.db("o_project").where("id", projectId).select("videoModel").first();
model = project?.videoModel || "";
await u.db("o_storyboard").where("id", id).update({ videoPrompt: text }); }
results.push({ storyboardId: id, videoPrompt: text }); if (!model) {
return res.status(200).send(success({ data: null }));
} }
res.status(200).send(success({ data: results })); const systemPrompt = `你是一个专业的${model}视频生成助手。请根据分镜提示词和关联资产提示词,生成一段完整的、可直接用于视频生成模型的中文提示词。`;
const userContent = `分镜提示词:${prompt || "无"}\n资产提示词${assetPrompts.length > 0 ? assetPrompts.join("\n") : "无"}`;
const { text } = await u.Ai.Text("universalAi").invoke({
system: systemPrompt,
messages: [{ role: "user", content: userContent }],
});
await u.db("o_storyboard").where("id", storyboardId).update({ videoPrompt: text });
res.status(200).send(success({ data: { storyboardId, videoPrompt: text } }));
}, },
); );

View File

@ -63,17 +63,34 @@ export default router.post(
if (!scriptIds.length) return res.status(400).send(error("请先选择剧本")); if (!scriptIds.length) return res.status(400).send(error("请先选择剧本"));
const scripts = await u.db("o_script").whereIn("id", scriptIds); const scripts = await u.db("o_script").whereIn("id", scriptIds);
const intansce = u.Ai.Text("universalAi"); const intansce = u.Ai.Text("universalAi");
await u.db("o_script").whereIn("id", scriptIds).update({
extractState: 0, // 查询已有的剧本-资产关联,找出已经提取过资产的剧本
}); const existingScriptAssets = await u.db("o_scriptAssets").whereIn("scriptId", scriptIds).select("scriptId");
const scriptIdsWithAssets = new Set(existingScriptAssets.map((sa: any) => sa.scriptId));
// 构建 scriptId -> script 内容的映射 // 构建 scriptId -> script 内容的映射
const scriptMap = new Map(scripts.map((s: o_script) => [s.id, s])); const scriptMap = new Map(scripts.map((s: o_script) => [s.id, s]));
// 过滤掉已成功提取过资产的剧本extractState === 1 且有关联资产)
const filteredScriptIds = scriptIds.filter((id: number) => {
const script = scriptMap.get(id);
return !(script?.extractState === 1 && scriptIdsWithAssets.has(id));
});
const skippedCount = scriptIds.length - filteredScriptIds.length;
if (!filteredScriptIds.length) {
return res.send(success("所有剧本已提取过资产,无需重复提取"));
}
await u.db("o_script").whereIn("id", filteredScriptIds).update({
extractState: 0,
});
const errors: { scriptId: number; error: string }[] = []; const errors: { scriptId: number; error: string }[] = [];
let successCount = 0; let successCount = 0;
// 将 scriptIds 按 groupSize默认5分组每组一起发给 AI // 将过滤后的 scriptIds 按 groupSize默认5分组每组一起发给 AI
const scriptGroups = chunkArray(scriptIds, groupSize); const scriptGroups = chunkArray(filteredScriptIds, groupSize);
/** 一组剧本提取完成后统一入库并建立关联 */ /** 一组剧本提取完成后统一入库并建立关联 */
async function persistGroupResult(result: GroupResult) { async function persistGroupResult(result: GroupResult) {
@ -157,9 +174,7 @@ export default router.post(
// 查询当前项目已有的资产列表,提供给 AI 参考 // 查询当前项目已有的资产列表,提供给 AI 参考
const existingAssets = await u.db("o_assets").where("projectId", projectId).select("name", "type"); const existingAssets = await u.db("o_assets").where("projectId", projectId).select("name", "type");
console.log("%c Line:162 🍔 existingAssets", "background:#ea7e5c", existingAssets);
const existingAssetsList = existingAssets.map((a) => `${a.name}(${a.type})`).join("、"); const existingAssetsList = existingAssets.map((a) => `${a.name}(${a.type})`).join("、");
console.log("%c Line:164 🍫 existingAssetsList", "background:#33a5ff", existingAssetsList);
// 拼接多集剧本内容,每集用分隔标记 // 拼接多集剧本内容,每集用分隔标记
const scriptsContent = validScripts const scriptsContent = validScripts
@ -246,6 +261,6 @@ export default router.post(
}); });
} }
return res.send(success("开始提取资产")); return res.send(success(skippedCount > 0 ? `开始提取资产,跳过 ${skippedCount} 个已提取的剧本` : "开始提取资产"));
}, },
); );

View File

@ -34,6 +34,6 @@ export default router.post(
const data = JSON.parse(row.data ?? "{}"); const data = JSON.parse(row.data ?? "{}");
data.script = await u.db("o_script").where({ projectId }).select("id", "name", "content"); data.script = await u.db("o_script").where({ projectId }).select("id", "name", "content");
res.status(200).send(success(data)); res.status(200).send(success({ data, id: row.id }));
}, },
); );

View File

@ -0,0 +1,27 @@
import express from "express";
import { success } from "@/lib/responseFormat";
import u from "@/utils";
import { z } from "zod";
import { validateFields } from "@/middleware/middleware";
const router = express.Router();
export default router.post(
"/",
validateFields({
id: z.number(),
data: z.object({
storySkeleton: z.string(),
adaptationStrategy: z.string(),
}),
}),
async (req, res) => {
const { id, data } = req.body;
await u
.db("o_agentWorkData")
.where({ id: id })
.update({
data: JSON.stringify(data),
});
res.status(200).send(success("更新成功"));
},
);

View File

@ -1,5 +1,4 @@
import express from "express"; import express from "express";
import { serializeError } from "serialize-error";
import { success, error } from "@/lib/responseFormat"; import { success, error } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
import u from "@/utils"; import u from "@/utils";
@ -29,14 +28,14 @@ const vendorConfigSchema = z.object({
name: z.string(), name: z.string(),
modelName: z.string(), modelName: z.string(),
type: z.literal("text"), type: z.literal("text"),
multimodal: z.boolean(), think: z.boolean(),
tool: z.boolean(),
}), }),
z.object({ z.object({
name: z.string(), name: z.string(),
modelName: z.string(), modelName: z.string(),
type: z.literal("image"), type: z.literal("image"),
mode: z.array(z.enum(["text", "singleImage", "multiReference"])), mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
associationSkills:z.string().optional(),
}), }),
z.object({ z.object({
name: z.string(), name: z.string(),
@ -46,8 +45,6 @@ const vendorConfigSchema = z.object({
z.union([ z.union([
z.enum([ z.enum([
"singleImage", "singleImage",
"multiImage",
"gridImage",
"startEndRequired", "startEndRequired",
"endFrameOptional", "endFrameOptional",
"startFrameOptional", "startFrameOptional",
@ -55,7 +52,7 @@ const vendorConfigSchema = z.object({
"audioReference", "audioReference",
"videoReference", "videoReference",
]), ]),
z.array(z.enum(["video", "image", "audio", "text"])), z.array(z.enum(["videoReference", "imageReference", "audioReference", "textReference"])),
]), ]),
), ),
audio: z.union([z.literal("optional"), z.boolean()]), audio: z.union([z.literal("optional"), z.boolean()]),

View File

@ -78,6 +78,7 @@ export default router.post(
} }
res.status(200).send(success(fullResponse)); res.status(200).send(success(fullResponse));
} else { } else {
console.log("%c Line:83 🥔", "background:#e41a6a");
const aiTypeFn = { const aiTypeFn = {
image: "Image", image: "Image",
video: "Video", video: "Video",

View File

@ -29,14 +29,14 @@ const vendorConfigSchema = z.object({
name: z.string(), name: z.string(),
modelName: z.string(), modelName: z.string(),
type: z.literal("text"), type: z.literal("text"),
multimodal: z.boolean(), think: z.boolean(),
tool: z.boolean(),
}), }),
z.object({ z.object({
name: z.string(), name: z.string(),
modelName: z.string(), modelName: z.string(),
type: z.literal("image"), type: z.literal("image"),
mode: z.array(z.enum(["text", "singleImage", "multiReference"])), mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
associationSkills:z.string().optional(),
}), }),
z.object({ z.object({
name: z.string(), name: z.string(),
@ -46,8 +46,6 @@ const vendorConfigSchema = z.object({
z.union([ z.union([
z.enum([ z.enum([
"singleImage", "singleImage",
"multiImage",
"gridImage",
"startEndRequired", "startEndRequired",
"endFrameOptional", "endFrameOptional",
"startFrameOptional", "startFrameOptional",
@ -55,9 +53,10 @@ const vendorConfigSchema = z.object({
"audioReference", "audioReference",
"videoReference", "videoReference",
]), ]),
z.array(z.enum(["video", "image", "audio", "text"])), z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])),
]), ]),
), ),
associationSkills:z.string().optional(),
audio: z.union([z.literal("optional"), z.boolean()]), audio: z.union([z.literal("optional"), z.boolean()]),
durationResolutionMap: z.array( durationResolutionMap: z.array(
z.object({ z.object({

View File

@ -6,69 +6,6 @@ import { z } from "zod";
import { transform } from "sucrase"; import { transform } from "sucrase";
const router = express.Router(); const router = express.Router();
const vendorConfigSchema = z.object({
id: z.string(),
author: z.string(),
description: z.string().optional(),
name: z.string(),
icon: z.string().optional(),
inputs: z.array(
z.object({
key: z.string(),
label: z.string(),
type: z.enum(["text", "password", "url"]),
required: z.boolean(),
placeholder: z.string().optional(),
}),
),
inputValues: z.record(z.string(), z.string()),
models: z.array(
z.discriminatedUnion("type", [
z.object({
name: z.string(),
modelName: z.string(),
type: z.literal("text"),
multimodal: z.boolean(),
tool: z.boolean(),
}),
z.object({
name: z.string(),
modelName: z.string(),
type: z.literal("image"),
mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
}),
z.object({
name: z.string(),
modelName: z.string(),
type: z.literal("video"),
mode: z.array(
z.union([
z.enum([
"singleImage",
"multiImage",
"gridImage",
"startEndRequired",
"endFrameOptional",
"startFrameOptional",
"text",
"audioReference",
"videoReference",
]),
z.array(z.enum(["video", "image", "audio", "text"])),
]),
),
audio: z.union([z.literal("optional"), z.boolean()]),
durationResolutionMap: z.array(
z.object({
duration: z.array(z.number()),
resolution: z.array(z.string()),
}),
),
}),
]),
),
});
export default router.post( export default router.post(
"/", "/",
validateFields({ validateFields({
@ -89,14 +26,14 @@ export default router.post(
name: z.string(), name: z.string(),
modelName: z.string(), modelName: z.string(),
type: z.literal("text"), type: z.literal("text"),
multimodal: z.boolean(), think: z.boolean(),
tool: z.boolean(),
}), }),
z.object({ z.object({
name: z.string(), name: z.string(),
modelName: z.string(), modelName: z.string(),
type: z.literal("image"), type: z.literal("image"),
mode: z.array(z.enum(["text", "singleImage", "multiReference"])), mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
associationSkills: z.string().optional(),
}), }),
z.object({ z.object({
name: z.string(), name: z.string(),
@ -104,10 +41,11 @@ export default router.post(
type: z.literal("video"), type: z.literal("video"),
mode: z.array( mode: z.array(
z.union([ z.union([
z.enum(["singleImage", "multiImage", "gridImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text"]), z.enum(["singleImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text"]),
z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])), z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])),
]), ]),
), ),
associationSkills: z.string().optional(),
audio: z.union([z.literal("optional"), z.boolean()]), audio: z.union([z.literal("optional"), z.boolean()]),
durationResolutionMap: z.array( durationResolutionMap: z.array(
z.object({ z.object({

View File

@ -1,4 +1,8 @@
// @db-hash f7bc2fdb80756d5536929eb47155578b <<<<<<< HEAD
// @db-hash 93b2462070c45c2b449e9a18c4e88763
=======
// @db-hash 24748d4ef971381a79c720c846f83847
>>>>>>> 796947cef173e7fe2f96e21fa8aeae23ff0fdf4a
//该文件由脚本自动生成,请勿手动修改 //该文件由脚本自动生成,请勿手动修改
export interface _o_script_old_20260327 { export interface _o_script_old_20260327 {

View File

@ -177,9 +177,9 @@ ${skillEntries}
</available_skills>`; </available_skills>`;
} }
export function createSkillTools(skills: { name: string; description: string }[], skillPaths: SkillPaths) { export function createSkillTools(skills: { name: string; description: string }[], skillPaths: SkillPaths, rootDir: string = getPath("skills")) {
const activated = new Set<string>(); // 已激活技能集合,防止重复加载 const activated = new Set<string>(); // 已激活技能集合,防止重复加载
const skillsRootDir = path.resolve(getPath("skills")); const skillsRootDir = path.resolve(rootDir);
const skillNames = skills.map((s) => s.name); const skillNames = skills.map((s) => s.name);
const skillMap = new Map(skillPaths.mainSkill.map((s) => [s.name, s])); const skillMap = new Map(skillPaths.mainSkill.map((s) => [s.name, s]));
return { return {

View File

@ -25,10 +25,23 @@ async function getVendorTemplateFn(fnName: FnName, modelName: `${string}:${strin
const selectedModel = modelList.find((i: any) => i.modelName == name); const selectedModel = modelList.find((i: any) => i.modelName == name);
if (!selectedModel) throw new Error(`未找到模型 ${name} id=${id}`); if (!selectedModel) throw new Error(`未找到模型 ${name} id=${id}`);
const jsCode = transform(vendorConfigData.code!, { transforms: ["typescript"] }).code; const jsCode = transform(vendorConfigData.code!, { transforms: ["typescript"] }).code;
const fn = u.vm(jsCode)[fnName]; const running = u.vm(jsCode);
Object.assign(running.vendor.inputValues, JSON.parse(vendorConfigData.inputValues ?? "{}"));
running.vendor.models = modelList;
const fn = running[fnName];
if (!fn) throw new Error(`未找到供应商配置中的函数 ${fnName} id=${id}`); if (!fn) throw new Error(`未找到供应商配置中的函数 ${fnName} id=${id}`);
if (fnName == "textRequest") return fn(selectedModel); if (fnName == "textRequest") {
else return <T>(input: T) => fn(input, selectedModel); const model = fn(selectedModel);
if (!model) throw new Error(`供应商 textRequest 返回无效模型 id=${id}`);
return model;
}
return async <T>(input: T) => {
const result = await fn(input, selectedModel);
if (result === undefined || result === null) {
throw new Error(`供应商函数 ${fnName} 未返回有效结果 id=${id}`);
}
return result;
};
} }
async function withTaskRecord<T>( async function withTaskRecord<T>(

View File

@ -11,33 +11,39 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createXai } from "@ai-sdk/xai"; import { createXai } from "@ai-sdk/xai";
import { createMinimax } from "vercel-minimax-ai-provider"; import { createMinimax } from "vercel-minimax-ai-provider";
import FormData from "form-data"; import FormData from "form-data";
import jsonwebtoken from "jsonwebtoken";
export default function runCode(code: string) { export default function runCode(code: string, vendor?: Record<string, any>) {
// 创建一个沙盒 // 创建一个沙盒
const exports = {}; const exports = {};
const sandbox: Record<string, any> = {
createOpenAI,
createDeepSeek,
createZhipu,
createQwen,
createAnthropic,
createOpenAICompatible,
createXai,
createMinimax,
createGoogleGenerativeAI,
zipImage,
zipImageResolution,
urlToBase64,
mergeImages,
pollTask,
fetch,
exports,
axios,
FormData,
logger,
jsonwebtoken,
};
if (vendor !== undefined) {
sandbox.vendor = vendor;
}
const vm = new VM({ const vm = new VM({
timeout: 0, timeout: 0,
sandbox: { sandbox,
createOpenAI,
createDeepSeek,
createZhipu,
createQwen,
createAnthropic,
createOpenAICompatible,
createXai,
createMinimax,
createGoogleGenerativeAI,
zipImage,
zipImageResolution,
urlToBase64,
mergeImages,
pollTask,
fetch,
exports,
axios,
FormData,
logger,
},
compiler: "javascript", compiler: "javascript",
eval: false, eval: false,
wasm: false, wasm: false,

View File

@ -4022,6 +4022,13 @@ p-cancelable@^2.0.0:
dependencies: dependencies:
yocto-queue "^0.1.0" yocto-queue "^0.1.0"
p-limit@^7.3.0:
version "7.3.0"
resolved "https://registry.npmmirror.com/p-limit/-/p-limit-7.3.0.tgz#821398d91491c6b6a1340ecd09cdc402a9c8d0ee"
integrity sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==
dependencies:
yocto-queue "^1.2.1"
p-map@^4.0.0: p-map@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" resolved "https://registry.npmmirror.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
@ -5536,6 +5543,11 @@ yocto-queue@^0.1.0:
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yocto-queue@^1.2.1:
version "1.2.2"
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz#3e09c95d3f1aa89a58c114c99223edf639152c00"
integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==
zhipu-ai-provider@^0.2.2: zhipu-ai-provider@^0.2.2:
version "0.2.2" version "0.2.2"
resolved "https://registry.npmmirror.com/zhipu-ai-provider/-/zhipu-ai-provider-0.2.2.tgz#cbee428475b1c2fca446f273ac09006ef86f6f00" resolved "https://registry.npmmirror.com/zhipu-ai-provider/-/zhipu-ai-provider-0.2.2.tgz#cbee428475b1c2fca446f273ac09006ef86f6f00"