Merge branch 'develop' of https://github.com/HBAI-Ltd/Toonflow-app into develop
This commit is contained in:
commit
b41c788bcb
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
20
src/env.ts
20
src/env.ts
@ -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");
|
||||||
|
|||||||
@ -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] : [],
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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) })
|
||||||
|
|||||||
@ -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(),
|
||||||
}));
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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("更新配置失败"));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user