Merge branch 'develop' of https://github.com/HBAI-Ltd/Toonflow-app into develop

This commit is contained in:
ACT丶流星雨 2026-02-06 16:45:07 +08:00
commit b41c788bcb
14 changed files with 117 additions and 108 deletions

View File

@ -104,9 +104,7 @@ async function generateGridPrompt(options: GridPromptOptions): Promise<GridPromp
if (!mainPrompts) return { prompt: errData, gridLayout: layout }; if (!mainPrompts) return { prompt: errData, gridLayout: layout };
const chatModel = await u.ai.text({}); const result = await u.ai.text.invoke({
const result = await chatModel!.invoke({
messages: [ messages: [
{ {
role: "system", role: "system",

View File

@ -215,8 +215,7 @@ async function filterRelevantAssets(prompts: string[], allResources: ResourceIte
return availableImages; return availableImages;
} }
const chatModel = await u.ai.text({}); const result = await u.ai.text.invoke({
const result = await chatModel!.invoke({
messages: [ messages: [
{ {
role: "user", role: "user",
@ -231,23 +230,24 @@ ${availableResources.map((r) => `- ${r.name}${r.intro}`).join("\n")}
`, `,
}, },
], ],
responseFormat: { output: {
type: "json_schema", relevantAssets: z
jsonSchema: { .array(
name: "filteredAssets", z.object({
strict: true, name: z.string().describe("资产名称"),
schema: z.toJSONSchema(filteredAssetsSchema), reason: z.string().describe("选择该资产的原因"),
}, }),
)
.describe("与分镜内容相关的资产列表"),
}, },
}); });
const data = result?.json as z.infer<typeof filteredAssetsSchema>;
if (!data?.relevantAssets || data.relevantAssets.length === 0) { if (!result?.relevantAssets || result.relevantAssets.length === 0) {
return availableImages; return availableImages;
} }
const relevantNames = new Set(data.relevantAssets.map((a) => a.name)); const relevantNames = new Set(result.relevantAssets.map((a) => a.name));
const filteredImages = availableImages.filter((img) => relevantNames.has(img.name)); const filteredImages = availableImages.filter((img) => relevantNames.has(img.name));
return filteredImages.length > 0 ? filteredImages : availableImages; return filteredImages.length > 0 ? filteredImages : availableImages;
@ -318,7 +318,7 @@ export default async (cells: { prompt: string }[], scriptId: number, projectId:
const processedImages = await processImages(filteredImages); const processedImages = await processImages(filteredImages);
const contentStr = await u.ai.generateImage({ const contentStr = await u.ai.image({
systemPrompt: resourcesMapPrompts, systemPrompt: resourcesMapPrompts,
prompt: prompts, prompt: prompts,
size: "4K", size: "4K",

View File

@ -343,7 +343,7 @@ export default async (cells: { prompt: string }[], scriptId: number, projectId:
const processedImages = await processImages(filteredImages); const processedImages = await processImages(filteredImages);
const contentStr = await u.ai.generateImage({ const contentStr = await u.ai.image({
systemPrompt: resourcesMapPrompts, systemPrompt: resourcesMapPrompts,
prompt: prompts, prompt: prompts,
size: "4K", size: "4K",

View File

@ -1,13 +1,18 @@
import { readFileSync, existsSync } from "fs"; import { readFileSync, existsSync, writeFileSync } from "fs";
function createDefaultEnvFile(path: string) {
const defaultContent = ["# 环境变量配置", "NODE_ENV=dev"].join("\n");
writeFileSync(path, defaultContent, { encoding: "utf8" });
console.log(`[环境变量]: 已创建默认的 ${path}`);
}
function loadDotenvESM(envPath = ".env.local") { function loadDotenvESM(envPath = ".env.local") {
// 尝试从 userData 目录读取环境变量,如果不存在则使用当前目录
let finalPath: string; let finalPath: string;
if (typeof process.versions?.electron !== "undefined") { if (typeof process.versions?.electron !== "undefined") {
const { app } = require("electron"); const { app } = require("electron");
finalPath = app.getPath("userData"); finalPath = app.getPath("userData") + `/${envPath}`;
// 如果 userData 目录中不存在,尝试使用当前目录 // 如果 userData 目录下的 env 文件不存在,则尝试当前目录
if (!existsSync(finalPath)) { if (!existsSync(finalPath)) {
finalPath = envPath; finalPath = envPath;
} }
@ -15,9 +20,9 @@ function loadDotenvESM(envPath = ".env.local") {
finalPath = envPath; finalPath = envPath;
} }
// 若文件不存在,自动创建一个带默认内容的环境变量文件
if (!existsSync(finalPath)) { if (!existsSync(finalPath)) {
console.log(`[环境变量]: ${envPath} 文件不存在`); createDefaultEnvFile(finalPath);
return;
} }
const text = readFileSync(finalPath, "utf8"); const text = readFileSync(finalPath, "utf8");
@ -25,7 +30,8 @@ function loadDotenvESM(envPath = ".env.local") {
const idx = line.indexOf("="); const idx = line.indexOf("=");
if (idx > 0) process.env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); if (idx > 0) process.env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
} }
console.log(`[环境变量]: ${finalPath}`); console.log(`[环境变量]: 已加载 ${finalPath}`);
} }
// 若非 Electron 环境,则加载 .env.local
if (typeof process.versions?.electron == "undefined") loadDotenvESM(".env.local"); if (typeof process.versions?.electron == "undefined") loadDotenvESM(".env.local");

View File

@ -124,7 +124,7 @@ export default router.post(
assetsId: id, assetsId: id,
}); });
const contentStr = await u.ai.generateImage({ const contentStr = await u.ai.image({
systemPrompt, systemPrompt,
prompt: userPrompt, prompt: userPrompt,
imageBase64: base64 ? [base64] : [], imageBase64: base64 ? [base64] : [],

View File

@ -22,7 +22,7 @@ async function superResolutionAndSave(
projectId: number, projectId: number,
videoRatio: string, videoRatio: string,
): Promise<{ ossPath: string; base64: string }> { ): Promise<{ ossPath: string; base64: string }> {
const contentStr = await u.ai.generateImage({ const contentStr = await u.ai.image({
aspectRatio: videoRatio, aspectRatio: videoRatio,
size: "1K", size: "1K",
resType: "b64", resType: "b64",

View File

@ -8,7 +8,6 @@ expressWs(router as unknown as Application);
router.ws("/", async (ws, req) => { router.ws("/", async (ws, req) => {
let agent: Storyboard; let agent: Storyboard;
const config = await u.getConfig("language");
const projectId = req.query.projectId; const projectId = req.query.projectId;
const scriptId = req.query.scriptId; const scriptId = req.query.scriptId;
@ -20,10 +19,6 @@ router.ws("/", async (ws, req) => {
agent = new Storyboard(Number(projectId), Number(scriptId)); agent = new Storyboard(Number(projectId), Number(scriptId));
agent.modelName = config.model ?? "";
agent.baseURL = config.baseURL ?? "";
agent.apiKey = config.apiKey ?? "";
const existing = await u const existing = await u
.db("t_chatHistory") .db("t_chatHistory")
.where({ projectId: Number(projectId) }) .where({ projectId: Number(projectId) })

View File

@ -6,11 +6,13 @@ import { z } from "zod";
const router = express.Router(); const router = express.Router();
// 图片项schema // 图片项schema
const imageItemSchema = z.object({ const imageItemSchema = z
id: z.number(), .object({
filePath: z.string(), id: z.number(),
prompt: z.string().optional(), filePath: z.string(),
}).nullable(); prompt: z.string().optional(),
})
.nullable();
// 新增视频配置 // 新增视频配置
export default router.post( export default router.post(
@ -22,31 +24,24 @@ export default router.post(
mode: z.enum(["startEnd", "multi", "single"]), mode: z.enum(["startEnd", "multi", "single"]),
startFrame: imageItemSchema.optional(), startFrame: imageItemSchema.optional(),
endFrame: imageItemSchema.optional(), endFrame: imageItemSchema.optional(),
images: z.array(z.object({ images: z
id: z.number(), .array(
filePath: z.string(), z.object({
prompt: z.string().optional(), id: z.number(),
})).optional(), filePath: z.string(),
prompt: z.string().optional(),
}),
)
.optional(),
resolution: z.string(), resolution: z.string(),
duration: z.number(), duration: z.number(),
prompt: z.string().optional(), prompt: z.string().optional(),
}), }),
async (req, res) => { async (req, res) => {
const { const { scriptId, projectId, manufacturer, mode, startFrame, endFrame, images, resolution, duration, prompt } = req.body;
scriptId,
projectId,
manufacturer,
mode,
startFrame,
endFrame,
images,
resolution,
duration,
prompt
} = req.body;
// 生成新ID // 生成新ID
const maxIdResult = await u.db("t_videoConfig").max("id as maxId").first(); const maxIdResult: any = await u.db("t_videoConfig").max("id as maxId").first();
const newId = (maxIdResult?.maxId || 0) + 1; const newId = (maxIdResult?.maxId || 0) + 1;
const now = Date.now(); const now = Date.now();
@ -68,23 +63,25 @@ export default router.post(
updateTime: now, updateTime: now,
}); });
res.status(200).send(success({ res.status(200).send(
message: "新增视频配置成功", success({
data: { message: "新增视频配置成功",
id: newId, data: {
scriptId, id: newId,
projectId, scriptId,
manufacturer, projectId,
mode, manufacturer,
startFrame, mode,
endFrame, startFrame,
images: images || [], endFrame,
resolution, images: images || [],
duration, resolution,
prompt: prompt || "", duration,
selectedResultId: null, prompt: prompt || "",
createdAt: new Date(now).toISOString(), selectedResultId: null,
} createdAt: new Date(now).toISOString(),
})); },
}),
);
}, },
); );

View File

@ -44,7 +44,7 @@ export default router.post(
// 过滤掉空值 // 过滤掉空值
let fileUrl = filePath.filter((p: string) => p && p.trim() !== ""); let fileUrl = filePath.filter((p: string) => p && p.trim() !== "");
if (fileUrl.length === 0) { if (fileUrl.length === 0) {
return res.status(400).send(error("请至少选择一张图片")); return res.status(400).send(error("请至少选择一张图片"));
} }
@ -149,17 +149,14 @@ ${prompt}
3. 3.
4. logo 4. logo
`; `;
const videoPath = await u.ai.video({
const videoPath = await u.ai.generateVideo( imageBase64,
{ savePath,
imageBase64, prompt: inputPrompt,
savePath, duration: duration as any,
prompt: inputPrompt, aspectRatio: resolution as any,
duration: duration as any, resolution: resolution as any,
aspectRatio: resolution as any, });
},
type!,
);
if (videoPath) { if (videoPath) {
// 生成成功,更新状态为 1 // 生成成功,更新状态为 1

View File

@ -47,24 +47,29 @@ export default router.post(
// 获取更新后的数据 // 获取更新后的数据
const updatedConfig = await u.db("t_videoConfig").where({ id }).first(); const updatedConfig = await u.db("t_videoConfig").where({ id }).first();
if (updatedConfig) {
res.status(200).send(success({ res.status(200).send(
message: "更新视频配置成功", success({
data: { message: "更新视频配置成功",
id: updatedConfig.id, data: {
scriptId: updatedConfig.scriptId, id: updatedConfig.id,
projectId: updatedConfig.projectId, scriptId: updatedConfig.scriptId,
manufacturer: updatedConfig.manufacturer, projectId: updatedConfig.projectId,
mode: updatedConfig.mode, manufacturer: updatedConfig.manufacturer,
startFrame: updatedConfig.startFrame ? JSON.parse(updatedConfig.startFrame) : null, mode: updatedConfig.mode,
endFrame: updatedConfig.endFrame ? JSON.parse(updatedConfig.endFrame) : null, startFrame: updatedConfig.startFrame ? JSON.parse(updatedConfig.startFrame) : null,
images: updatedConfig.images ? JSON.parse(updatedConfig.images) : [], endFrame: updatedConfig.endFrame ? JSON.parse(updatedConfig.endFrame) : null,
resolution: updatedConfig.resolution, images: updatedConfig.images ? JSON.parse(updatedConfig.images) : [],
duration: updatedConfig.duration, resolution: updatedConfig.resolution,
prompt: updatedConfig.prompt, duration: updatedConfig.duration,
selectedResultId: updatedConfig.selectedResultId, prompt: updatedConfig.prompt,
createdAt: new Date(updatedConfig.createTime).toISOString(), selectedResultId: updatedConfig.selectedResultId,
} createdAt: new Date(updatedConfig.createTime!).toISOString(),
})); },
}),
);
} else {
res.status(200).send(error("更新配置失败"));
}
}, },
); );

View File

@ -63,5 +63,5 @@ export default async (input: ImageConfig, config?: AIConfig) => {
let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL }); let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL });
if (!input.resType) input.resType = "b64"; if (!input.resType) input.resType = "b64";
if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl); if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
return input; return imageUrl;
}; };

View File

@ -45,14 +45,14 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig) => {
}, },
}; };
const output = input.output ? outputBuilders[owned.responseFormat]?.(input.output) ?? null : null; const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null;
return { return {
config: { config: {
model: model:
process.env.NODE_ENV === "dev" process.env.NODE_ENV === "dev"
? wrapLanguageModel({ ? wrapLanguageModel({
model: modelInstance(model!) as any, model: modelInstance.chat(model!) as any,
middleware: devToolsMiddleware(), middleware: devToolsMiddleware(),
}) })
: (modelInstance(model!) as LanguageModel), : (modelInstance(model!) as LanguageModel),

View File

@ -4,6 +4,7 @@ import { createZhipu } from "zhipu-ai-provider";
import { createQwen } from "qwen-ai-provider"; import { createQwen } from "qwen-ai-provider";
import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createAnthropic } from "@ai-sdk/anthropic"; import { createAnthropic } from "@ai-sdk/anthropic";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
interface Owned { interface Owned {
manufacturer: string; manufacturer: string;
@ -18,7 +19,8 @@ interface Owned {
| typeof createZhipu | typeof createZhipu
| typeof createQwen | typeof createQwen
| typeof createGoogleGenerativeAI | typeof createGoogleGenerativeAI
| typeof createAnthropic; | typeof createAnthropic
| typeof createOpenAICompatible;
} }
const modelList: Owned[] = [ const modelList: Owned[] = [
@ -409,6 +411,15 @@ const modelList: Owned[] = [
instance: createAnthropic, instance: createAnthropic,
tool: true, tool: true,
}, },
{
manufacturer: "other",
model: "gpt-4.1",
responseFormat: "schema",
image: true,
think: false,
instance: createOpenAICompatible,
tool: true,
},
]; ];
export default modelList; export default modelList;

View File

@ -79,7 +79,7 @@ async function convertDirectiveAndImages(images: Record<string, string>, directi
*/ */
export default async (images: Record<string, string>, directive: string, projectId: number) => { export default async (images: Record<string, string>, directive: string, projectId: number) => {
const { prompt, images: base64Images } = await convertDirectiveAndImages(images, directive); const { prompt, images: base64Images } = await convertDirectiveAndImages(images, directive);
const contentStr = await u.ai.generateImage({ const contentStr = await u.ai.image({
systemPrompt: "根据用户提供的具体修改指令,对上传的图片进行智能编辑。", systemPrompt: "根据用户提供的具体修改指令,对上传的图片进行智能编辑。",
prompt: prompt, prompt: prompt,
imageBase64: base64Images, imageBase64: base64Images,