Merge branch 'master' of https://github.com/HBAI-Ltd/Toonflow-app
This commit is contained in:
commit
c80abaa694
1411
output.json
Normal file
1411
output.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,7 @@
|
||||
"@ai-sdk/anthropic": "^3.0.35",
|
||||
"@ai-sdk/deepseek": "^2.0.17",
|
||||
"@ai-sdk/devtools": "^0.0.11",
|
||||
"@ai-sdk/google": "^3.0.20",
|
||||
"@ai-sdk/google": "^3.0.43",
|
||||
"@ai-sdk/openai": "^3.0.25",
|
||||
"@ai-sdk/openai-compatible": "^2.0.27",
|
||||
"@ai-sdk/xai": "^3.0.47",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -176,13 +176,19 @@ async function processImages(images: ImageInfo[]): Promise<Buffer[]> {
|
||||
|
||||
if (images.length <= maxImages) {
|
||||
const buffers = await Promise.all(images.map((img) => u.oss.getFile(img.filePath)));
|
||||
|
||||
processedBuffers = await Promise.all(buffers.map((buffer) => compressImage(buffer)));
|
||||
} else {
|
||||
const mergeStartIndex = maxImages - 1;
|
||||
|
||||
const firstBuffers = await Promise.all(images.slice(0, mergeStartIndex).map((img) => u.oss.getFile(img.filePath)));
|
||||
|
||||
const compressedFirstImages = await Promise.all(firstBuffers.map((buffer) => compressImage(buffer)));
|
||||
|
||||
const imagesToMergeList = images.slice(mergeStartIndex).map((img) => img.filePath);
|
||||
|
||||
const mergedImage = await mergeImages(imagesToMergeList);
|
||||
|
||||
processedBuffers = [...compressedFirstImages, mergedImage];
|
||||
}
|
||||
|
||||
@ -290,7 +296,7 @@ export default async (cells: { prompt: string }[], scriptId: number, projectId:
|
||||
const filteredImages = await filterRelevantAssets(cellPrompts, resources, allImages);
|
||||
|
||||
const resourcesMapPrompts = buildResourcesMapPrompts(filteredImages);
|
||||
console.log("====润色前:", cellPrompts);
|
||||
|
||||
const promptsData = await generateImagePromptsTool({
|
||||
prompts: cellPrompts,
|
||||
style: `类型:${projectInfo?.type!},风格:${projectInfo?.artStyle!}`,
|
||||
@ -305,7 +311,6 @@ export default async (cells: { prompt: string }[], scriptId: number, projectId:
|
||||
// 注意:请严格按照提示词内容生成图片,确保人物样貌、艺术风格、色调光影一致。
|
||||
// `;
|
||||
const prompts = promptsData.prompt;
|
||||
console.log("====润色后:", prompts);
|
||||
|
||||
const processedImages = await processImages(filteredImages);
|
||||
const apiConfig = await u.getPromptAi("storyboardImage");
|
||||
@ -317,10 +322,13 @@ export default async (cells: { prompt: string }[], scriptId: number, projectId:
|
||||
size: "4K",
|
||||
aspectRatio: projectInfo?.videoRatio ? (projectInfo.videoRatio as any) : "16:9",
|
||||
imageBase64: processedImages.map((buf) => buf.toString("base64")),
|
||||
taskClass: "分镜图生成",
|
||||
name: `分镜图-${outline?.title || "未知剧集"}`,
|
||||
describe: prompts,
|
||||
projectId,
|
||||
},
|
||||
apiConfig,
|
||||
);
|
||||
|
||||
const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/);
|
||||
const base64Str = match?.[1] ?? contentStr;
|
||||
const buffer = Buffer.from(base64Str, "base64");
|
||||
|
||||
@ -98,14 +98,13 @@ export default class Storyboard {
|
||||
|
||||
private log(action: string, detail?: string) {
|
||||
const msg = detail ? `${action}: ${detail}` : action;
|
||||
console.log(`\n[${new Date().toLocaleTimeString()}] ${msg}\n`);
|
||||
}
|
||||
|
||||
// ==================== 剧本相关操作 ====================
|
||||
|
||||
getScript = tool({
|
||||
title: "getScript",
|
||||
description: "获取剧本内容",
|
||||
description: "用于获取剧本内容",
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
this.log("获取剧本", `scriptId: ${this.scriptId}`);
|
||||
@ -242,7 +241,6 @@ ${sections.join("\n\n")}
|
||||
const skipped: number[] = [];
|
||||
|
||||
for (const item of shots) {
|
||||
|
||||
const exists = this.shots.some((f) => f.segmentId === item.segmentIndex);
|
||||
if (exists) {
|
||||
skipped.push(item.segmentIndex);
|
||||
@ -445,6 +443,7 @@ ${sections.join("\n\n")}
|
||||
this.scriptId,
|
||||
this.projectId,
|
||||
);
|
||||
|
||||
// 通知前端正在分割图片
|
||||
this.emit("shotImageGenerateProgress", { shotId, status: "splitting", message: "正在分割宫格图片为单张镜头图" });
|
||||
|
||||
@ -460,8 +459,10 @@ ${sections.join("\n\n")}
|
||||
|
||||
for (let i = 0; i < imageBuffers.length; i++) {
|
||||
const fileName = `${this.projectId}/chat/${this.scriptId}/storyboard/shot_${shotId}_take_${i}_${timestamp}.png`;
|
||||
|
||||
await u.oss.writeFile(fileName, imageBuffers[i]);
|
||||
const imageUrl = await u.oss.getFileUrl(fileName);
|
||||
|
||||
imagePaths.push(imageUrl);
|
||||
|
||||
// 每保存一张镜头图片通知进度
|
||||
@ -661,7 +662,9 @@ ${task}
|
||||
inputSchema: z.object({
|
||||
taskDescription: z.string().describe("具体的任务描述,包含章节范围、修改要求等详细信息"),
|
||||
}),
|
||||
execute: async ({ taskDescription }) => this.invokeSubAgent(agentType, taskDescription),
|
||||
execute: async ({ taskDescription }) => {
|
||||
return this.invokeSubAgent(agentType, taskDescription);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -671,7 +674,7 @@ ${task}
|
||||
return {
|
||||
segmentAgent: this.createSubAgentTool(
|
||||
"segmentAgent",
|
||||
"调用片段师。负责根据剧本生成片段,会自行调用 getScript 获取剧本内容,并调用 updateSegments 保存片段结果。",
|
||||
"调用片段师。负责根据剧本生成片段,必须调用 getScript工具 获取剧本内容,并调用 updateSegments 保存片段结果。",
|
||||
),
|
||||
shotAgent: this.createSubAgentTool(
|
||||
"shotAgent",
|
||||
|
||||
1411
src/lib/artStyle.ts
Normal file
1411
src/lib/artStyle.ts
Normal file
File diff suppressed because it is too large
Load Diff
118
src/lib/fixDB.ts
118
src/lib/fixDB.ts
File diff suppressed because one or more lines are too long
@ -1,5 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { artStyle } from "./artStyle";
|
||||
interface TableSchema {
|
||||
name: string;
|
||||
builder: (table: Knex.CreateTableBuilder) => void;
|
||||
@ -96,6 +97,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
name: "t_project",
|
||||
builder: (table) => {
|
||||
table.integer("id");
|
||||
// table.string("projectType");
|
||||
table.text("name");
|
||||
table.text("intro");
|
||||
table.text("type");
|
||||
@ -160,20 +162,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
table.unique(["id"]);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "t_taskList",
|
||||
builder: (table) => {
|
||||
table.integer("id").notNullable();
|
||||
table.integer("projectName");
|
||||
table.text("name");
|
||||
table.text("prompt");
|
||||
table.text("state");
|
||||
table.text("startTime");
|
||||
table.text("endTime");
|
||||
table.primary(["id"]);
|
||||
table.unique(["id"]);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "t_image",
|
||||
builder: (table) => {
|
||||
@ -207,6 +195,36 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
},
|
||||
initData: async (knex) => {},
|
||||
},
|
||||
// {
|
||||
// name: "t_myTasks",
|
||||
// builder: (table) => {
|
||||
// table.integer("id").notNullable();
|
||||
// table.integer("projectId");
|
||||
// table.string("taskClass");
|
||||
// table.string("relatedObjects");
|
||||
// table.string("model");
|
||||
// table.text("describe");
|
||||
// table.string("state");
|
||||
// table.integer("startTime");
|
||||
// table.text("reason");
|
||||
// table.primary(["id"]);
|
||||
// table.unique(["id"]);
|
||||
// },
|
||||
// initData: async (knex) => {},
|
||||
// },
|
||||
{
|
||||
name: "t_artStyle",
|
||||
builder: (table) => {
|
||||
table.integer("id").notNullable();
|
||||
table.string("name");
|
||||
table.text("styles");
|
||||
table.primary(["id"]);
|
||||
table.unique(["id"]);
|
||||
},
|
||||
initData: async (knex) => {
|
||||
await knex("t_artStyle").insert(artStyle.map((item, index) => ({ id: index + 1, name: item.name, styles: JSON.stringify(item.styles) })));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "t_videoConfig",
|
||||
builder: (table) => {
|
||||
@ -573,54 +591,38 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
{ manufacturer: "deepSeek", model: "deepseek-chat", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "deepSeek", model: "deepseek-reasoner", responseFormat: "schema", image: 0, think: 1, tool: 1 },
|
||||
{ manufacturer: "volcengine", model: "doubao-seed-2-0-pro-260215", responseFormat: "object", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "volcengine", model: "doubao-seed-2-0-lite-260215", responseFormat: "object", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "volcengine", model: "doubao-seed-2-0-mini-260215", responseFormat: "object", image: 1, think: 1, tool: 1 },
|
||||
{ manufacturer: "volcengine", model: "doubao-seed-1-8-251228", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "volcengine", model: "doubao-seed-1-6-251015", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "volcengine", model: "doubao-seed-1-6-lite-251015", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "volcengine", model: "doubao-seed-1-6-flash-250828", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
{ manufacturer: "zhipu", model: "glm-4.7", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4.7-flashx", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "zhipu", model: "glm-4.6", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4.5-air", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4.5-airx", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4-long", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4-flashx-250414", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "zhipu", model: "glm-4.7-flash", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4.5-flash", responseFormat: "object", image: 0, think: 1, tool: 1 },
|
||||
// { manufacturer: "zhipu", model: "glm-4-flash-250414", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "zhipu", model: "glm-4.6v", responseFormat: "object", image: 1, think: 1, tool: 1 },
|
||||
{ manufacturer: "qwen", model: "qwen-vl-max", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "qwen", model: "qwen-plus-latest", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "qwen", model: "qwen-max", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "qwen", model: "qwen2.5-72b-instruct", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "qwen", model: "qwen2.5-14b-instruct-1m", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "qwen", model: "qwen2.5-vl-72b-instruct", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "openai", model: "gpt-4o", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "openai", model: "gpt-4o-mini", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "openai", model: "gpt-4.1", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "openai", model: "gpt-5.1", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "openai", model: "gpt-5.2", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "gemini", model: "gemini-3-pro-preview", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
{ manufacturer: "gemini", model: "gemini-2.5-pro", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "gemini", model: "gemini-2.5-flash", responseFormat: "schema", image: 1, think: 1, tool: 1 },
|
||||
// { manufacturer: "gemini", model: "gemini-2.0-flash", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "gemini", model: "gemini-2.0-flash-lite", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "gemini", model: "gemini-1.5-pro", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "gemini", model: "gemini-1.5-flash", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "anthropic", model: "claude-opus-4-5", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "anthropic", model: "claude-haiku-4-5", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "anthropic", model: "claude-sonnet-4-5", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "anthropic", model: "claude-opus-4-1", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "anthropic", model: "claude-opus-4-0", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "anthropic", model: "claude-sonnet-4-0", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "anthropic", model: "claude-3-7-sonnet-latest", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "anthropic", model: "claude-3-5-haiku-latest", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "xai", model: "grok-3", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "xai", model: "grok-4", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
{ manufacturer: "xai", model: "grok-4.1", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "other", model: "", responseFormat: "object", image: 1, think: 0, tool: 1 },
|
||||
{ manufacturer: "modelScope", model: "deepseek-ai/DeepSeek-V3.2", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
|
||||
// { manufacturer: "formal", model: "gpt-4.1", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "doubao-seed-2-0-pro-260215", responseFormat: "object", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "doubao-seed-1-8-251215", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "gpt-5.2", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "gpt-5.1", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "claude-sonnet-4-6", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "claude-sonnet-4-5-20250929", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "claude-opus-4-5-20251101", responseFormat: "schema", image: 0, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "qwen3.5-plus", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
// { manufacturer: "formal", model: "qwen3-max-2026-01-23", responseFormat: "schema", image: 1, think: 0, tool: 1 },
|
||||
]);
|
||||
},
|
||||
},
|
||||
@ -638,7 +640,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
await knex("t_imageModel").insert([
|
||||
{ manufacturer: "volcengine", model: "doubao-seedream-5-0-260128", grid: 1, type: "ti2i" },
|
||||
{ manufacturer: "volcengine", model: "doubao-seedream-4-5-251128", grid: 0, type: "ti2i" },
|
||||
// { manufacturer: "volcengine", model: "doubao-seedream-4-0-250828", grid: 0, type: "ti2i" },
|
||||
{ manufacturer: "kling", model: "kling-image-o1", grid: 0, type: "ti2i" },
|
||||
{ manufacturer: "gemini", model: "gemini-2.5-flash-image", grid: 1, type: "ti2i" },
|
||||
{ manufacturer: "gemini", model: "gemini-3-pro-image-preview", grid: 1, type: "ti2i" },
|
||||
@ -650,6 +651,10 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
{ manufacturer: "grsai", model: "nano-banana-pro", grid: 1, type: "ti2i" },
|
||||
{ manufacturer: "grsai", model: "nano-banana", grid: 1, type: "ti2i" },
|
||||
{ manufacturer: "grsai", model: "nano-banana-2", grid: 1, type: "ti2i" },
|
||||
|
||||
// { manufacturer: "formal", model: "Doubao-Seedream-5.0-Lite", grid: 1, type: "ti2i" },
|
||||
// { manufacturer: "formal", model: "doubao-seedream-4-5-251128", grid: 1, type: "ti2i" },
|
||||
// { manufacturer: "formal", model: "doubao-seedream-4-0-250828", grid: 1, type: "ti2i" },
|
||||
]);
|
||||
},
|
||||
},
|
||||
@ -668,7 +673,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
initData: async (knex) => {
|
||||
await knex("t_videoModel").insert([
|
||||
{
|
||||
id: 1,
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seedance-1-5-pro-251215",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -677,7 +681,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "endFrameOptional"]),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seedance-1-0-pro-250528",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -686,7 +689,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "endFrameOptional"]),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seedance-1-0-pro-fast-251015",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -695,7 +697,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seedance-1-0-lite-i2v-250428",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -704,106 +705,54 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["endFrameOptional", "reference"]),
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seedance-1-0-lite-t2v-250428",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
model: "doubao-seedance-2-0-260128",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "480p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
audio: 1,
|
||||
type: JSON.stringify(["endFrameOptional", "multiImage"]),
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v1(STD)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["720p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "1:1", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
type: JSON.stringify(["text", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v1(STD)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["720p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v1(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "1:1", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
type: JSON.stringify(["text", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v1(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v1-6(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "1:1", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
type: JSON.stringify(["text", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v1-6(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v2-5-turbo(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "1:1", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
type: JSON.stringify(["text", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v2-5-turbo(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v2-6(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "1:1", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
type: JSON.stringify(["text", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
manufacturer: "kling",
|
||||
model: "kling-v2-6(PRO)",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq3-pro",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
@ -811,21 +760,9 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16", "3:4", "4:3", "1:1"]),
|
||||
audio: 1,
|
||||
type: JSON.stringify(["text"]),
|
||||
type: JSON.stringify(["text", "singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq3-pro",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] },
|
||||
]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 1,
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-pro-fast",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["720p", "1080p"] }]),
|
||||
@ -834,70 +771,22 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-pro",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16", "3:4", "4:3", "1:1"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-pro",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "reference", "startEndRequired"]),
|
||||
type: JSON.stringify(["singleImage", "reference", "startEndRequired", "text"]),
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-turbo",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16", "3:4", "4:3", "1:1"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-turbo",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "reference", "startEndRequired"]),
|
||||
type: JSON.stringify(["singleImage", "reference", "startEndRequired", "text"]),
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq1",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16", "1:1"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq1",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "reference", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 25,
|
||||
manufacturer: "vidu",
|
||||
model: "viduq1-classic",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["1080p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 26,
|
||||
manufacturer: "vidu",
|
||||
model: "vidu2.0",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
@ -909,7 +798,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage", "reference", "startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 27,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.6-t2v",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "1080p"] }]),
|
||||
@ -918,7 +806,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 28,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.5-t2v-preview",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -927,7 +814,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 29,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.2-t2v-plus",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "1080p"] }]),
|
||||
@ -936,7 +822,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
manufacturer: "wan",
|
||||
model: "wanx2.1-t2v-turbo",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "720p"] }]),
|
||||
@ -945,7 +830,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
manufacturer: "wan",
|
||||
model: "wanx2.1-t2v-plus",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["720p"] }]),
|
||||
@ -954,7 +838,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text"]),
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.6-i2v-flash",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "1080p"] }]),
|
||||
@ -963,7 +846,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.6-i2v",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p", "1080p"] }]),
|
||||
@ -972,7 +854,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 34,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.5-i2v-preview",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -981,7 +862,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 35,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.2-i2v-flash",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -990,7 +870,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 36,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.2-i2v-plus",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "1080p"] }]),
|
||||
@ -999,25 +878,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 37,
|
||||
manufacturer: "wan",
|
||||
model: "wanx2.1-i2v-plus",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["720p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 38,
|
||||
manufacturer: "wan",
|
||||
model: "wanx2.1-i2v-turbo",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [3, 4, 5], resolution: ["480p", "720p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 39,
|
||||
manufacturer: "wan",
|
||||
model: "wan2.2-kf2v-flash",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "720p", "1080p"] }]),
|
||||
@ -1026,16 +886,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 40,
|
||||
manufacturer: "wan",
|
||||
model: "wanx2.1-kf2v-plus",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["720p"] }]),
|
||||
aspectRatio: JSON.stringify([]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired"]),
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
manufacturer: "gemini",
|
||||
model: "veo-3.1-generate-preview",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
@ -1047,7 +897,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "singleImage", "startEndRequired", "endFrameOptional", "reference"]),
|
||||
},
|
||||
{
|
||||
id: 42,
|
||||
manufacturer: "gemini",
|
||||
model: "veo-3.1-fast-generate-preview",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
@ -1059,7 +908,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "singleImage", "startEndRequired", "endFrameOptional", "reference"]),
|
||||
},
|
||||
{
|
||||
id: 43,
|
||||
manufacturer: "gemini",
|
||||
model: "veo-3.0-generate-preview",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
@ -1071,7 +919,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
manufacturer: "gemini",
|
||||
model: "veo-3.0-fast-generate-preview",
|
||||
durationResolutionMap: JSON.stringify([
|
||||
@ -1083,7 +930,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
manufacturer: "gemini",
|
||||
model: "veo-2.0-generate-001",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [5, 6, 7, 8], resolution: ["720p"] }]),
|
||||
@ -1092,34 +938,30 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["text", "singleImage"]),
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
manufacturer: "runninghub",
|
||||
model: "sora-2",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [10, 15], resolution: [] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "text"]),
|
||||
type: JSON.stringify(["singleImage", "text", "multiImage"]),
|
||||
},
|
||||
{
|
||||
id: 47,
|
||||
manufacturer: "runninghub",
|
||||
model: "sora-2-pro",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [15, 25], resolution: [] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "text"]),
|
||||
type: JSON.stringify(["singleImage", "text", "multiImage"]),
|
||||
},
|
||||
{
|
||||
id: 48,
|
||||
manufacturer: "grsai",
|
||||
model: "sora-2",
|
||||
durationResolutionMap: JSON.stringify([{ duration: [10, 15], resolution: [] }]),
|
||||
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||
audio: 0,
|
||||
type: JSON.stringify(["singleImage", "text"]),
|
||||
type: JSON.stringify(["singleImage", "text", "multiImage"]),
|
||||
},
|
||||
{
|
||||
id: 49,
|
||||
manufacturer: "grsai",
|
||||
model: "veo3.1-pro",
|
||||
durationResolutionMap: JSON.stringify([]),
|
||||
@ -1129,7 +971,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
},
|
||||
|
||||
{
|
||||
id: 50,
|
||||
manufacturer: "grsai",
|
||||
model: "veo3.1-pro-1080p",
|
||||
durationResolutionMap: JSON.stringify([]),
|
||||
@ -1138,7 +979,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["startEndRequired", "text"]),
|
||||
},
|
||||
{
|
||||
id: 51,
|
||||
manufacturer: "grsai",
|
||||
model: "veo3.1-pro-4k",
|
||||
durationResolutionMap: JSON.stringify([]),
|
||||
@ -1147,7 +987,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["startEndRequired", "text"]),
|
||||
},
|
||||
{
|
||||
id: 52,
|
||||
manufacturer: "grsai",
|
||||
model: "veo3.1-fast",
|
||||
durationResolutionMap: JSON.stringify([]),
|
||||
@ -1156,7 +995,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["startEndRequired", "text"]),
|
||||
},
|
||||
{
|
||||
id: 53,
|
||||
manufacturer: "grsai",
|
||||
model: "veo3.1-fast-1080p",
|
||||
durationResolutionMap: JSON.stringify([]),
|
||||
@ -1165,7 +1003,6 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
type: JSON.stringify(["startEndRequired", "text"]),
|
||||
},
|
||||
{
|
||||
id: 54,
|
||||
manufacturer: "grsai",
|
||||
model: "veo3.1-fast-4k",
|
||||
durationResolutionMap: JSON.stringify([]),
|
||||
@ -1173,6 +1010,108 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
||||
audio: 0,
|
||||
type: JSON.stringify(["startEndRequired", "text"]),
|
||||
},
|
||||
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "Seedance-1.5-Pro-audio",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["endFrameOptional"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "Seedance-1.5-Pro-NotAudio",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["endFrameOptional"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "Seedance-1.0-Pro",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["endFrameOptional"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "ViduQ3-turbo",
|
||||
// durationResolutionMap: JSON.stringify([
|
||||
// { duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] },
|
||||
// ]),
|
||||
// aspectRatio: JSON.stringify(["16:9", "9:16", "3:4", "4:3", "1:1"]),
|
||||
// audio: 1,
|
||||
// type: JSON.stringify(["singleImage"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "ViduQ3-pro",
|
||||
// durationResolutionMap: JSON.stringify([
|
||||
// { duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] },
|
||||
// ]),
|
||||
// aspectRatio: JSON.stringify(["16:9", "9:16", "3:4", "4:3", "1:1"]),
|
||||
// audio: 1,
|
||||
// type: JSON.stringify(["singleImage"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "ViduQ2-pro-fast",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["singleImage", "startEndRequired"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "ViduQ2-pro",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["singleImage", "reference", "startEndRequired"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "ViduQ2-turbo",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["singleImage", "reference", "startEndRequired"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "ViduQ2",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["singleImage", "reference"]),
|
||||
// },
|
||||
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "Sora-2-I2V",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [4, 8, 12], resolution: [] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 0,
|
||||
// type: JSON.stringify(["singleImage", "reference"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "Wan2.6-I2V-720P",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p"] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 1,
|
||||
// type: JSON.stringify(["singleImage"]),
|
||||
// },
|
||||
// {
|
||||
// manufacturer: "formal",
|
||||
// model: "Wan2.6-I2V-720P-1080P",
|
||||
// durationResolutionMap: JSON.stringify([{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["1080p"] }]),
|
||||
// aspectRatio: JSON.stringify([]),
|
||||
// audio: 1,
|
||||
// type: JSON.stringify(["singleImage"]),
|
||||
// },
|
||||
]);
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
// @routes-hash c97cf72361299980ea4b0c43549a0de8
|
||||
// @routes-hash d8b95db972bd0ab01243d87d89a004f0
|
||||
import { Express } from "express";
|
||||
|
||||
import route1 from "./routes/assets/addAssets";
|
||||
import route2 from "./routes/assets/delAssets";
|
||||
import route3 from "./routes/assets/delAssetsImage";
|
||||
import route4 from "./routes/assets/generateAssets";
|
||||
import route5 from "./routes/assets/getAssets";
|
||||
import route6 from "./routes/assets/getImage";
|
||||
import route7 from "./routes/assets/getStoryboard";
|
||||
import route8 from "./routes/assets/polishPrompt";
|
||||
import route9 from "./routes/assets/saveAssets";
|
||||
import route10 from "./routes/assets/updateAssets";
|
||||
import route11 from "./routes/index/index";
|
||||
import route1 from "./routes/artStyle/getArtStyle";
|
||||
import route2 from "./routes/assets/addAssets";
|
||||
import route3 from "./routes/assets/delAssets";
|
||||
import route4 from "./routes/assets/delAssetsImage";
|
||||
import route5 from "./routes/assets/generateAssets";
|
||||
import route6 from "./routes/assets/getAssets";
|
||||
import route7 from "./routes/assets/getImage";
|
||||
import route8 from "./routes/assets/getScriptList";
|
||||
import route9 from "./routes/assets/polishAssetsPrompt";
|
||||
import route10 from "./routes/assets/saveAssets";
|
||||
import route11 from "./routes/assets/updateAssets";
|
||||
import route12 from "./routes/novel/addNovel";
|
||||
import route13 from "./routes/novel/delNovel";
|
||||
import route14 from "./routes/novel/getNovel";
|
||||
@ -55,18 +55,18 @@ import route51 from "./routes/setting/getSetting";
|
||||
import route52 from "./routes/setting/getVideoModelDetail";
|
||||
import route53 from "./routes/setting/getVideoModelList";
|
||||
import route54 from "./routes/setting/updateModel";
|
||||
import route55 from "./routes/setting/updeteModel";
|
||||
import route56 from "./routes/storyboard/batchSuperScoreImage";
|
||||
import route57 from "./routes/storyboard/chatStoryboard";
|
||||
import route58 from "./routes/storyboard/delStoryboard";
|
||||
import route59 from "./routes/storyboard/generateShotImage";
|
||||
import route60 from "./routes/storyboard/generateStoryboardApi";
|
||||
import route61 from "./routes/storyboard/generateVideoPrompt";
|
||||
import route62 from "./routes/storyboard/getStoryboard";
|
||||
import route63 from "./routes/storyboard/keepStoryboard";
|
||||
import route64 from "./routes/storyboard/saveStoryboard";
|
||||
import route65 from "./routes/storyboard/uploadImage";
|
||||
import route66 from "./routes/task/getTaskApi";
|
||||
import route55 from "./routes/storyboard/batchSuperScoreImage";
|
||||
import route56 from "./routes/storyboard/chatStoryboard";
|
||||
import route57 from "./routes/storyboard/delStoryboard";
|
||||
import route58 from "./routes/storyboard/generateShotImage";
|
||||
import route59 from "./routes/storyboard/generateVideoPrompt";
|
||||
import route60 from "./routes/storyboard/getStoryboard";
|
||||
import route61 from "./routes/storyboard/keepStoryboard";
|
||||
import route62 from "./routes/storyboard/saveStoryboard";
|
||||
import route63 from "./routes/storyboard/storyboardImageEdit";
|
||||
import route64 from "./routes/storyboard/uploadImage";
|
||||
import route65 from "./routes/task/getMyTaskApi";
|
||||
import route66 from "./routes/task/getTaskCategories";
|
||||
import route67 from "./routes/task/taskDetails";
|
||||
import route68 from "./routes/user/getUser";
|
||||
import route69 from "./routes/user/saveUser";
|
||||
@ -85,17 +85,17 @@ import route81 from "./routes/video/saveVideo";
|
||||
import route82 from "./routes/video/upDateVideoConfig";
|
||||
|
||||
export default async (app: Express) => {
|
||||
app.use("/assets/addAssets", route1);
|
||||
app.use("/assets/delAssets", route2);
|
||||
app.use("/assets/delAssetsImage", route3);
|
||||
app.use("/assets/generateAssets", route4);
|
||||
app.use("/assets/getAssets", route5);
|
||||
app.use("/assets/getImage", route6);
|
||||
app.use("/assets/getStoryboard", route7);
|
||||
app.use("/assets/polishPrompt", route8);
|
||||
app.use("/assets/saveAssets", route9);
|
||||
app.use("/assets/updateAssets", route10);
|
||||
app.use("/index", route11);
|
||||
app.use("/artStyle/getArtStyle", route1);
|
||||
app.use("/assets/addAssets", route2);
|
||||
app.use("/assets/delAssets", route3);
|
||||
app.use("/assets/delAssetsImage", route4);
|
||||
app.use("/assets/generateAssets", route5);
|
||||
app.use("/assets/getAssets", route6);
|
||||
app.use("/assets/getImage", route7);
|
||||
app.use("/assets/getScriptList", route8);
|
||||
app.use("/assets/polishAssetsPrompt", route9);
|
||||
app.use("/assets/saveAssets", route10);
|
||||
app.use("/assets/updateAssets", route11);
|
||||
app.use("/novel/addNovel", route12);
|
||||
app.use("/novel/delNovel", route13);
|
||||
app.use("/novel/getNovel", route14);
|
||||
@ -139,18 +139,18 @@ export default async (app: Express) => {
|
||||
app.use("/setting/getVideoModelDetail", route52);
|
||||
app.use("/setting/getVideoModelList", route53);
|
||||
app.use("/setting/updateModel", route54);
|
||||
app.use("/setting/updeteModel", route55);
|
||||
app.use("/storyboard/batchSuperScoreImage", route56);
|
||||
app.use("/storyboard/chatStoryboard", route57);
|
||||
app.use("/storyboard/delStoryboard", route58);
|
||||
app.use("/storyboard/generateShotImage", route59);
|
||||
app.use("/storyboard/generateStoryboardApi", route60);
|
||||
app.use("/storyboard/generateVideoPrompt", route61);
|
||||
app.use("/storyboard/getStoryboard", route62);
|
||||
app.use("/storyboard/keepStoryboard", route63);
|
||||
app.use("/storyboard/saveStoryboard", route64);
|
||||
app.use("/storyboard/uploadImage", route65);
|
||||
app.use("/task/getTaskApi", route66);
|
||||
app.use("/storyboard/batchSuperScoreImage", route55);
|
||||
app.use("/storyboard/chatStoryboard", route56);
|
||||
app.use("/storyboard/delStoryboard", route57);
|
||||
app.use("/storyboard/generateShotImage", route58);
|
||||
app.use("/storyboard/generateVideoPrompt", route59);
|
||||
app.use("/storyboard/getStoryboard", route60);
|
||||
app.use("/storyboard/keepStoryboard", route61);
|
||||
app.use("/storyboard/saveStoryboard", route62);
|
||||
app.use("/storyboard/storyboardImageEdit", route63);
|
||||
app.use("/storyboard/uploadImage", route64);
|
||||
app.use("/task/getMyTaskApi", route65);
|
||||
app.use("/task/getTaskCategories", route66);
|
||||
app.use("/task/taskDetails", route67);
|
||||
app.use("/user/getUser", route68);
|
||||
app.use("/user/saveUser", route69);
|
||||
|
||||
19
src/routes/artStyle/getArtStyle.ts
Normal file
19
src/routes/artStyle/getArtStyle.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
name: z.string(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { name } = req.body;
|
||||
const data = await u.db("t_artStyle").where("name", name).select("styles").first();
|
||||
const styles = data?.styles ? JSON.parse(data.styles) : [];
|
||||
res.status(200).send(success(styles));
|
||||
},
|
||||
);
|
||||
@ -40,7 +40,7 @@ export default router.post(
|
||||
const { id, type, projectId, base64, prompt, name } = req.body;
|
||||
|
||||
//获取风格
|
||||
const project = await u.db("t_project").where("id", projectId).select("artStyle", "type", "intro").first();
|
||||
const project = await u.db("t_project").where("id", projectId).select("artStyle", "type", "intro", "videoRatio").first();
|
||||
if (!project) return res.status(500).send(success({ message: "项目为空" }));
|
||||
|
||||
const promptsList = await u
|
||||
@ -123,8 +123,13 @@ export default router.post(
|
||||
state: "生成中",
|
||||
assetsId: id,
|
||||
});
|
||||
const apiConfig = await u.getPromptAi("assetsImage");
|
||||
let taskClass = "";
|
||||
if (type == "role") taskClass = "角色图生成";
|
||||
if (type == "scene") taskClass = "场景图生成";
|
||||
if (type == "props") taskClass = "道具图生成";
|
||||
if (type == "storyboard") taskClass = "分镜图生成";
|
||||
|
||||
const apiConfig = await u.getPromptAi("assetsImage");
|
||||
try {
|
||||
const contentStr = await u.ai.image(
|
||||
{
|
||||
@ -132,7 +137,11 @@ export default router.post(
|
||||
prompt: userPrompt,
|
||||
imageBase64: base64 ? [base64] : [],
|
||||
size: "2K",
|
||||
aspectRatio: "16:9",
|
||||
aspectRatio: project.videoRatio ?? "16:9",
|
||||
taskClass: taskClass,
|
||||
name: name,
|
||||
describe: prompt,
|
||||
projectId: projectId,
|
||||
},
|
||||
apiConfig,
|
||||
);
|
||||
@ -171,7 +180,6 @@ export default router.post(
|
||||
filePath: imagePath,
|
||||
type: insertType,
|
||||
});
|
||||
|
||||
const path = await u.oss.getFileUrl(imagePath!);
|
||||
|
||||
// const state = await u.db("t_assets").where("id", id).select("state").first();
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
const router = express.Router();
|
||||
import { z } from "zod";
|
||||
import { error } from "@/lib/responseFormat";
|
||||
|
||||
export default router.get("/", async (req, res, next) => {
|
||||
const id = 14;
|
||||
const targetOutlineData = await u.db("t_outline").where("id", id).select("data").first();
|
||||
if (!targetOutlineData) return res.status(400).send(error("大纲不存在"));
|
||||
//筛选出改大纲特有的资产
|
||||
const allOutlineDataList = await u.db("t_outline").where("projectId", 8).andWhere("id", "!=", id).select("data");
|
||||
//找出目标ID大纲特有的资产名称
|
||||
const allOutlineData = allOutlineDataList
|
||||
.map((item) => {
|
||||
const data = JSON.parse(item?.data || "[]");
|
||||
return [...data.characters, ...data.props, ...data.scenes].map((item: any) => item.name);
|
||||
})
|
||||
.flat();
|
||||
|
||||
const targetOutLineNames = JSON.parse(targetOutlineData?.data || "[]");
|
||||
const targetNames = [...targetOutLineNames.characters, ...targetOutLineNames.props, ...targetOutLineNames.scenes].map((item: any) => item.name);
|
||||
|
||||
const diffAssetsNames = targetNames.filter((item) => !allOutlineData.includes(item));
|
||||
|
||||
res.status(200).send(123);
|
||||
});
|
||||
@ -48,6 +48,7 @@ export default router.post(
|
||||
);
|
||||
res.status(200).send(success(reply));
|
||||
} catch (err) {
|
||||
console.log("%c Line:51 🍬 err", "background:#465975", err);
|
||||
const msg = u.error(err).message;
|
||||
console.error(msg);
|
||||
res.status(500).send(error(msg));
|
||||
|
||||
@ -22,8 +22,12 @@ export default router.post(
|
||||
prompt:
|
||||
"一张16:9比例的图片,完美等分为2x2四宫格布局,各区域无缝衔接:\n左上宫格:一只可爱的猫,毛发蓬松,眼睛明亮,姿态俏皮\n右上宫格:一只友善的狗,金毛犬,表情愉悦,摇着尾巴\n左下宫格:一头健壮的牛,田园背景,目光温和,皮毛光泽\n右下宫格:一匹骏马,姿态优雅,鬃毛飘逸,肌肉健美\n风格要求:四个宫格风格统一,色彩鲜艳饱和,高清画质,细节清晰锐利,专业插画风格,线条干净,统一的左上方光源,柔和阴影,和谐配色,卡通/半写实风格,宫格间用白色或浅灰细线分隔",
|
||||
imageBase64: [],
|
||||
aspectRatio: "16:9",
|
||||
size: "1K",
|
||||
aspectRatio: "9:16",
|
||||
size: "4K",
|
||||
taskClass: "测试任务",
|
||||
name: "测试图片生成",
|
||||
describe: "测试语言模型生成图片",
|
||||
projectId: 0,
|
||||
},
|
||||
{
|
||||
model: modelName,
|
||||
@ -34,6 +38,7 @@ export default router.post(
|
||||
);
|
||||
res.status(200).send(success(image));
|
||||
} catch (err) {
|
||||
console.log("%c Line:41 🍖 err", "background:#fca650", err);
|
||||
const msg = u.error(err).message;
|
||||
console.error(msg);
|
||||
res.status(500).send(error(msg));
|
||||
|
||||
@ -28,6 +28,10 @@ export default router.post(
|
||||
aspectRatio: "16:9",
|
||||
audio: false,
|
||||
mode: "single",
|
||||
taskClass: "测试视频生成",
|
||||
name: "测试视频生成",
|
||||
describe: "测试视频生成",
|
||||
projectId: 0,
|
||||
},
|
||||
{
|
||||
model: modelName,
|
||||
|
||||
@ -5,7 +5,7 @@ import { error, success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 删除大纲
|
||||
// 获取历史消息记录
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
|
||||
@ -5,7 +5,7 @@ import { error, success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 删除大纲
|
||||
// 保存历史消息记录
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
|
||||
@ -9,6 +9,7 @@ const router = express.Router();
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
projectType: z.string().optional(),
|
||||
name: z.string(),
|
||||
intro: z.string(),
|
||||
type: z.string(),
|
||||
@ -16,7 +17,7 @@ export default router.post(
|
||||
videoRatio: z.string(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { name, intro, type, artStyle, videoRatio } = req.body;
|
||||
const { projectType, name, intro, type, artStyle, videoRatio } = req.body;
|
||||
|
||||
await u.db("t_project").insert({
|
||||
name,
|
||||
@ -29,5 +30,5 @@ export default router.post(
|
||||
});
|
||||
|
||||
res.status(200).send(success({ message: "新增项目成功" }));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -27,6 +27,7 @@ export default router.post(
|
||||
await u.db("t_novel").where("projectId", id).delete();
|
||||
await u.db("t_storyline").where("projectId", id).delete();
|
||||
await u.db("t_outline").where("projectId", id).delete();
|
||||
// await u.db("t_myTasks").where("projectId", id).delete();
|
||||
|
||||
await u.db("t_script").where("projectId", id).delete();
|
||||
await u.db("t_assets").where("projectId", id).delete();
|
||||
@ -55,5 +56,5 @@ export default router.post(
|
||||
}
|
||||
|
||||
res.status(200).send(success({ message: "删除项目成功" }));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -14,9 +14,10 @@ export default router.post(
|
||||
type: z.string().optional().nullable(),
|
||||
artStyle: z.string().optional().nullable(),
|
||||
videoRatio: z.string().optional().nullable(),
|
||||
projectType: z.string().optional().nullable(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, intro, type, artStyle, videoRatio } = req.body;
|
||||
const { id, intro, type, artStyle, videoRatio, projectType } = req.body;
|
||||
|
||||
await u.db("t_project").where("id", id).update({
|
||||
intro,
|
||||
@ -26,5 +27,5 @@ export default router.post(
|
||||
});
|
||||
|
||||
res.status(200).send(success({ message: "修改成功" }));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -21,13 +21,22 @@ export default router.post(
|
||||
.db(sqlTableMap[type as "image" | "text" | "video"])
|
||||
.whereNot("manufacturer", "other")
|
||||
.select("id", "manufacturer", "model");
|
||||
|
||||
const result: Record<string, any[]> = {};
|
||||
const modelCache: Record<string, Set<string>> = {};
|
||||
|
||||
for (const row of modelLists) {
|
||||
if (!result[row.manufacturer]) {
|
||||
result[row.manufacturer] = [];
|
||||
modelCache[row.manufacturer] = new Set();
|
||||
}
|
||||
if (!modelCache[row.manufacturer].has(row.model)) {
|
||||
result[row.manufacturer].push({ label: row.model, value: row.model });
|
||||
modelCache[row.manufacturer].add(row.model);
|
||||
}
|
||||
result[row.manufacturer].push({ label: row.model, value: row.model });
|
||||
}
|
||||
|
||||
res.status(200).send(success(result));
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.number(),
|
||||
type: z.enum(["text", "video", "image"]),
|
||||
model: z.string(),
|
||||
baseUrl: z.string(),
|
||||
modelType: z.string(),
|
||||
apiKey: z.string(),
|
||||
manufacturer: z.string(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, type, model, baseUrl, apiKey, manufacturer, modelType } = req.body;
|
||||
|
||||
await u.db("t_config").where("id", id).update({
|
||||
type,
|
||||
model,
|
||||
baseUrl,
|
||||
apiKey,
|
||||
manufacturer,
|
||||
modelType,
|
||||
});
|
||||
res.status(200).send(success("编辑成功"));
|
||||
},
|
||||
);
|
||||
@ -27,6 +27,10 @@ async function superResolutionAndSave(src: string, projectId: number, videoRatio
|
||||
systemPrompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率",
|
||||
prompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率",
|
||||
imageBase64: [await urlToBase64(src)],
|
||||
taskClass: "分镜图超分",
|
||||
name: `分镜图超分-${v4()}`,
|
||||
describe: `原始图片链接: ${src}`,
|
||||
projectId,
|
||||
},
|
||||
apiConfig,
|
||||
);
|
||||
@ -37,7 +41,7 @@ async function superResolutionAndSave(src: string, projectId: number, videoRatio
|
||||
await u.oss.writeFile(ossPath, buffer);
|
||||
return { ossPath, base64: `data:image/jpg;base64,${base64Str}` };
|
||||
}
|
||||
|
||||
// 图片超分
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
|
||||
@ -5,6 +5,7 @@ import Storyboard from "@/agents/storyboard";
|
||||
const router = express.Router();
|
||||
expressWs(router as unknown as Application);
|
||||
|
||||
// 分镜对话Agent
|
||||
router.ws("/", async (ws, req) => {
|
||||
let agent: Storyboard;
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
//删除分镜
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
|
||||
@ -99,9 +99,9 @@ const prompt = `
|
||||
`;
|
||||
async function urlToBase64(imageUrl: string): Promise<string> {
|
||||
const response = await axios.get(imageUrl, { responseType: "arraybuffer" });
|
||||
const contentType = response.headers["content-type"] || "image/png";
|
||||
const contentType = response.headers["content-type"] || "image/jpg";
|
||||
const base64 = Buffer.from(response.data, "binary").toString("base64");
|
||||
return `data:${contentType};base64,${base64}`;
|
||||
return `${base64}`;
|
||||
}
|
||||
// 生成单个分镜提示
|
||||
async function generateSingleVideoPrompt({
|
||||
@ -113,6 +113,8 @@ async function generateSingleVideoPrompt({
|
||||
storyboardPrompt: string;
|
||||
ossPath: string;
|
||||
}): Promise<{ content: string; time: number; name: string }> {
|
||||
console.log("%c Line:116 🍭 ossPath", "background:#6ec1c2", ossPath);
|
||||
|
||||
const messages: any[] = [
|
||||
{
|
||||
role: "system",
|
||||
@ -134,8 +136,10 @@ async function generateSingleVideoPrompt({
|
||||
];
|
||||
|
||||
try {
|
||||
console.log("%c Line:140 🍩", "background:#4fff4B");
|
||||
const apiConfig = await u.getPromptAi("videoPrompt");
|
||||
|
||||
console.log("%c Line:143 🍑", "background:#e41a6a");
|
||||
const result = await u.ai.text.invoke(
|
||||
{
|
||||
messages,
|
||||
@ -159,6 +163,7 @@ async function generateSingleVideoPrompt({
|
||||
|
||||
return result;
|
||||
} catch (err: any) {
|
||||
console.log("%c Line:167 🥤 err", "background:#465975", err);
|
||||
console.error("generateSingleVideoPrompt 调用失败:", err?.message || err);
|
||||
throw new Error(`生成视频提示词失败: ${err?.message || "未知错误"}`);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { validateFields } from "@/middleware/middleware";
|
||||
import { z } from "zod";
|
||||
const router = express.Router();
|
||||
|
||||
// 生成分镜图
|
||||
// 图片编辑
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
import { v4 as uuid } from "uuid";
|
||||
const router = express.Router();
|
||||
|
||||
// 上传对话图片
|
||||
// 图片上传
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
|
||||
@ -4,50 +4,45 @@ import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { number, z } from "zod";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.get(
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
projectName: z.string(),
|
||||
taskName: z.string(),
|
||||
state: z.string(),
|
||||
state: z.string().optional().nullable(),
|
||||
taskClass: z.string().optional().nullable(),
|
||||
page: z.number(),
|
||||
limit: z.number(),
|
||||
projectId: z.number(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { projectName, taskName, state, page = 1, limit = 10 }: any = req.query;
|
||||
const { taskClass, state, page = 1, limit = 10, projectId }: any = req.body;
|
||||
const offset = (page - 1) * limit;
|
||||
const data = await u
|
||||
.db("t_taskList")
|
||||
.db("t_myTasks")
|
||||
.where("projectId", projectId)
|
||||
.andWhere((qb) => {
|
||||
if (projectName) {
|
||||
qb.andWhere("t_taskList.projectName", projectName);
|
||||
}
|
||||
if (taskName) {
|
||||
qb.andWhere("t_taskList.name", taskName);
|
||||
if (taskClass) {
|
||||
qb.andWhere("t_myTasks.taskClass", taskClass);
|
||||
}
|
||||
if (state) {
|
||||
qb.andWhere("t_taskList.state", state);
|
||||
qb.andWhere("t_myTasks.state", state);
|
||||
}
|
||||
})
|
||||
.select("*")
|
||||
.offset(offset)
|
||||
.limit(limit);
|
||||
const totalQuery = (await u
|
||||
.db("t_taskList")
|
||||
.db("t_myTasks")
|
||||
.where("projectId", projectId)
|
||||
.andWhere((qb) => {
|
||||
if (projectName) {
|
||||
qb.andWhere("t_taskList.projectName", projectName);
|
||||
}
|
||||
if (taskName) {
|
||||
qb.andWhere("t_taskList.name", taskName);
|
||||
if (taskClass) {
|
||||
qb.andWhere("t_myTasks.taskClass", taskClass);
|
||||
}
|
||||
if (state) {
|
||||
qb.andWhere("t_taskList.state", state);
|
||||
qb.andWhere("t_myTasks.state", state);
|
||||
}
|
||||
})
|
||||
.count("* as total")
|
||||
.first()) as any;
|
||||
res.status(200).send(success({ data, total: totalQuery?.total }));
|
||||
}
|
||||
},
|
||||
);
|
||||
17
src/routes/task/getTaskCategories.ts
Normal file
17
src/routes/task/getTaskCategories.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { number, z } from "zod";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
projectId: z.number(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const data = await u.db("t_myTasks").where("projectId", req.body.projectId).select("taskClass").groupBy("taskClass");
|
||||
res.status(200).send(success(data));
|
||||
},
|
||||
);
|
||||
@ -35,11 +35,18 @@ export default router.post(
|
||||
return res.status(500).send(error("请先选择图片"));
|
||||
}
|
||||
const configData = await u.db("t_videoConfig").where("id", configId).first();
|
||||
|
||||
if (!configData) {
|
||||
return res.status(500).send(error("视频配置不存在"));
|
||||
}
|
||||
if (configData.manufacturer == "runninghub") {
|
||||
// 优先使用视频配置中的AI配置ID查询,查不到再使用传入的aiConfigId
|
||||
let aiConfigData = null;
|
||||
if (configData.aiConfigId) {
|
||||
aiConfigData = await u.db("t_config").where("id", configData.aiConfigId).first();
|
||||
}
|
||||
if (!aiConfigData || !aiConfigData?.model) {
|
||||
return res.status(500).send(error("模型不存在"));
|
||||
}
|
||||
if (aiConfigData.model?.includes("sora")) {
|
||||
if (filePath.length > 1) {
|
||||
const gridUrl = await sharpProcessingImage(filePath, projectId);
|
||||
if (gridUrl) {
|
||||
@ -49,18 +56,6 @@ export default router.post(
|
||||
}
|
||||
}
|
||||
|
||||
// 优先使用视频配置中的AI配置ID查询,查不到再使用传入的aiConfigId
|
||||
let aiConfigData = null;
|
||||
if (configData.aiConfigId) {
|
||||
aiConfigData = await u.db("t_config").where("id", configData.aiConfigId).first();
|
||||
}
|
||||
if (!aiConfigData) {
|
||||
aiConfigData = await u.db("t_config").where("id", aiConfigId).first();
|
||||
}
|
||||
|
||||
if (!aiConfigData) {
|
||||
return res.status(500).send(error("模型配置不存在"));
|
||||
}
|
||||
// 过滤掉空值
|
||||
let fileUrl = filePath.filter((p: string) => p && p.trim() !== "");
|
||||
|
||||
@ -177,6 +172,10 @@ ${prompt}
|
||||
resolution: resolution as any,
|
||||
audio: audioEnabled,
|
||||
mode: mode as any,
|
||||
taskClass: "视频生成",
|
||||
name: `视频生成-${videoId}`,
|
||||
describe: `视频生成,时长${duration}秒,分辨率${resolution}`,
|
||||
projectId,
|
||||
},
|
||||
{
|
||||
baseURL: aiConfigData?.baseUrl!,
|
||||
@ -197,7 +196,7 @@ ${prompt}
|
||||
await u.db("t_video").where("id", videoId).update({ state: -1 });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`视频生成失败 videoId=${videoId}:`, err);
|
||||
// console.error(`视频生成失败 videoId=${videoId}:`, (err as any).response);
|
||||
await u
|
||||
.db("t_video")
|
||||
.where("id", videoId)
|
||||
|
||||
230
src/types/database.d.ts
vendored
230
src/types/database.d.ts
vendored
@ -1,12 +1,203 @@
|
||||
// @db-hash 0f9789bd5ad2eebd79bd502988efcb4e
|
||||
// @db-hash f991a54893850ab9ff4a67665391191d
|
||||
//该文件由脚本自动生成,请勿手动修改
|
||||
|
||||
export interface memories {
|
||||
'access_count'?: number;
|
||||
'content': string;
|
||||
'created_at': number;
|
||||
'embedding': string;
|
||||
'id'?: string;
|
||||
'type': string;
|
||||
}
|
||||
export interface o_agentDeploy {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'startTime'?: number | null;
|
||||
}
|
||||
export interface o_artStyle {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'styles'?: string | null;
|
||||
}
|
||||
export interface o_assets {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'startTime'?: number | null;
|
||||
}
|
||||
export interface o_chatHistory {
|
||||
'data'?: string | null;
|
||||
'id'?: number;
|
||||
'novel'?: string | null;
|
||||
'projectId'?: number | null;
|
||||
'type'?: string | null;
|
||||
}
|
||||
export interface o_event {
|
||||
'createTime'?: number | null;
|
||||
'detail'?: string | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_eventChapter {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_image {
|
||||
'assetsId'?: number | null;
|
||||
'filePath'?: string | null;
|
||||
'id'?: number;
|
||||
'projectId'?: number | null;
|
||||
'scriptId'?: number | null;
|
||||
'state'?: string | null;
|
||||
'type'?: string | null;
|
||||
'videoId'?: number | null;
|
||||
}
|
||||
export interface o_model {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'startTime'?: number | null;
|
||||
}
|
||||
export interface o_myTasks {
|
||||
'describe'?: string | null;
|
||||
'id'?: number;
|
||||
'model'?: string | null;
|
||||
'projectId'?: number | null;
|
||||
'reason'?: string | null;
|
||||
'relatedObjects'?: string | null;
|
||||
'startTime'?: number | null;
|
||||
'state'?: string | null;
|
||||
'taskClass'?: string | null;
|
||||
}
|
||||
export interface o_novel {
|
||||
'chapter'?: string | null;
|
||||
'chapterData'?: string | null;
|
||||
'chapterIndex'?: number | null;
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'projectId'?: number | null;
|
||||
'reel'?: string | null;
|
||||
}
|
||||
export interface o_outline {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_outlineNovel {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_project {
|
||||
'artStyle'?: string | null;
|
||||
'createTime'?: number | null;
|
||||
'id'?: number | null;
|
||||
'intro'?: string | null;
|
||||
'name'?: string | null;
|
||||
'projectType'?: string | null;
|
||||
'type'?: string | null;
|
||||
'userId'?: number | null;
|
||||
'videoRatio'?: string | null;
|
||||
}
|
||||
export interface o_prompts {
|
||||
'code'?: string | null;
|
||||
'customValue'?: string | null;
|
||||
'defaultValue'?: string | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'parentCode'?: string | null;
|
||||
'type'?: string | null;
|
||||
}
|
||||
export interface o_script {
|
||||
'content'?: string | null;
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'projectId'?: number | null;
|
||||
}
|
||||
export interface o_scriptAssets {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_scriptOutline {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_setting {
|
||||
'id'?: number;
|
||||
'imageModel'?: string | null;
|
||||
'languageModel'?: string | null;
|
||||
'projectId'?: number | null;
|
||||
'tokenKey'?: string | null;
|
||||
'userId'?: number | null;
|
||||
}
|
||||
export interface o_skills {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'startTime'?: number | null;
|
||||
}
|
||||
export interface o_storyboard {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_storyboardScript {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_user {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'password'?: string | null;
|
||||
'tokenKey'?: string | null;
|
||||
}
|
||||
export interface o_vendorConfig {
|
||||
'code'?: string | null;
|
||||
'createTime'?: number | null;
|
||||
'icon'?: string | null;
|
||||
'id'?: number;
|
||||
'inputs'?: string | null;
|
||||
'inputValues'?: string | null;
|
||||
'models'?: string | null;
|
||||
'name'?: string | null;
|
||||
'version'?: string | null;
|
||||
}
|
||||
export interface o_video {
|
||||
'createTime'?: number | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface o_videoConfig {
|
||||
'aiConfigId'?: number | null;
|
||||
'audioEnabled'?: number | null;
|
||||
'createTime'?: number | null;
|
||||
'duration'?: number | null;
|
||||
'endFrame'?: string | null;
|
||||
'id'?: number;
|
||||
'images'?: string | null;
|
||||
'manufacturer'?: string | null;
|
||||
'mode'?: string | null;
|
||||
'projectId'?: number | null;
|
||||
'prompt'?: string | null;
|
||||
'resolution'?: string | null;
|
||||
'scriptId'?: number | null;
|
||||
'selectedResultId'?: number | null;
|
||||
'startFrame'?: string | null;
|
||||
'updateTime'?: number | null;
|
||||
}
|
||||
export interface t_aiModelMap {
|
||||
'configId'?: number | null;
|
||||
'id'?: number;
|
||||
'key'?: string | null;
|
||||
'name'?: string | null;
|
||||
}
|
||||
export interface t_artStyle {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'styles'?: string | null;
|
||||
}
|
||||
export interface t_assets {
|
||||
'duration'?: string | null;
|
||||
'episode'?: string | null;
|
||||
@ -115,15 +306,6 @@ export interface t_storyline {
|
||||
'novelIds'?: string | null;
|
||||
'projectId'?: number | null;
|
||||
}
|
||||
export interface t_taskList {
|
||||
'endTime'?: string | null;
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
'projectName'?: number | null;
|
||||
'prompt'?: string | null;
|
||||
'startTime'?: string | null;
|
||||
'state'?: string | null;
|
||||
}
|
||||
export interface t_textModel {
|
||||
'id'?: number;
|
||||
'image'?: number | null;
|
||||
@ -182,7 +364,34 @@ export interface t_videoModel {
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
"memories": memories;
|
||||
"o_agentDeploy": o_agentDeploy;
|
||||
"o_artStyle": o_artStyle;
|
||||
"o_assets": o_assets;
|
||||
"o_chatHistory": o_chatHistory;
|
||||
"o_event": o_event;
|
||||
"o_eventChapter": o_eventChapter;
|
||||
"o_image": o_image;
|
||||
"o_model": o_model;
|
||||
"o_myTasks": o_myTasks;
|
||||
"o_novel": o_novel;
|
||||
"o_outline": o_outline;
|
||||
"o_outlineNovel": o_outlineNovel;
|
||||
"o_project": o_project;
|
||||
"o_prompts": o_prompts;
|
||||
"o_script": o_script;
|
||||
"o_scriptAssets": o_scriptAssets;
|
||||
"o_scriptOutline": o_scriptOutline;
|
||||
"o_setting": o_setting;
|
||||
"o_skills": o_skills;
|
||||
"o_storyboard": o_storyboard;
|
||||
"o_storyboardScript": o_storyboardScript;
|
||||
"o_user": o_user;
|
||||
"o_vendorConfig": o_vendorConfig;
|
||||
"o_video": o_video;
|
||||
"o_videoConfig": o_videoConfig;
|
||||
"t_aiModelMap": t_aiModelMap;
|
||||
"t_artStyle": t_artStyle;
|
||||
"t_assets": t_assets;
|
||||
"t_chatHistory": t_chatHistory;
|
||||
"t_config": t_config;
|
||||
@ -195,7 +404,6 @@ export interface DB {
|
||||
"t_script": t_script;
|
||||
"t_setting": t_setting;
|
||||
"t_storyline": t_storyline;
|
||||
"t_taskList": t_taskList;
|
||||
"t_textModel": t_textModel;
|
||||
"t_user": t_user;
|
||||
"t_video": t_video;
|
||||
|
||||
35
src/utils/ai/image/adapter/volcengine.ts
Normal file
35
src/utils/ai/image/adapter/volcengine.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import "../type";
|
||||
|
||||
export function buildReqBody(input: ImageConfig, config: AIConfig) {
|
||||
const size = input.size === "1K" ? "2K" : input.size;
|
||||
const sizeMap: Record<string, Record<string, string>> = {
|
||||
"16:9": {
|
||||
"2K": "2848x1600",
|
||||
"4K": "4096x2304",
|
||||
},
|
||||
"9:16": {
|
||||
"2K": "1600x2848",
|
||||
"4K": "2304x4096",
|
||||
},
|
||||
};
|
||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||
|
||||
const requestBody: Record<string, any> = {
|
||||
model: config.model,
|
||||
prompt: fullPrompt,
|
||||
size: sizeMap[input.aspectRatio][size],
|
||||
response_format: "url",
|
||||
sequential_image_generation: "disabled",
|
||||
stream: false,
|
||||
watermark: false,
|
||||
...(input.imageBase64 && { image: input.imageBase64 }),
|
||||
};
|
||||
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
export function buildReqUrl(baseUrl: string) {
|
||||
return {
|
||||
requestUrl: `${baseUrl}/v1/images/generations`,
|
||||
};
|
||||
}
|
||||
@ -12,7 +12,8 @@ import other from "./owned/other";
|
||||
import gemini from "./owned/gemini";
|
||||
import modelScope from "./owned/modelScope";
|
||||
import grsai from "./owned/grsai";
|
||||
|
||||
import { tr } from "zod/locales";
|
||||
import formal from "./owned/formal";
|
||||
const urlToBase64 = async (url: string): Promise<string> => {
|
||||
const res = await axios.get(url, { responseType: "arraybuffer" });
|
||||
const base64 = Buffer.from(res.data).toString("base64");
|
||||
@ -29,20 +30,32 @@ const modelInstance = {
|
||||
// apimart: apimart,
|
||||
modelScope,
|
||||
other,
|
||||
grsai
|
||||
grsai,
|
||||
formal,
|
||||
} as const;
|
||||
|
||||
export default async (input: ImageConfig, config: AIConfig) => {
|
||||
const { model, apiKey, baseURL, manufacturer } = { ...config };
|
||||
|
||||
if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确");
|
||||
|
||||
const manufacturerFn = modelInstance[manufacturer as keyof typeof modelInstance];
|
||||
if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的图片厂商");
|
||||
|
||||
// if (manufacturer !== "other") {
|
||||
// const owned = modelList.find((m) => m.model === model);
|
||||
// if (!owned) throw new Error("不支持的模型");
|
||||
// }
|
||||
|
||||
//添加到任务中心
|
||||
// const [taskId] = await u.db("t_myTasks").insert({
|
||||
// taskClass: input.taskClass,
|
||||
// relatedObjects: input.name,
|
||||
// model: config?.model ? config.model : "未知模型",
|
||||
// describe: input.describe ? input.describe : "无",
|
||||
// state: "进行中",
|
||||
// startTime: Date.now(),
|
||||
// projectId: input.projectId,
|
||||
// });
|
||||
// 补充图片的 base64 内容类型字符串
|
||||
if (input.imageBase64 && input.imageBase64.length > 0) {
|
||||
input.imageBase64 = input.imageBase64.map((img) => {
|
||||
@ -66,9 +79,20 @@ export default async (input: ImageConfig, config: AIConfig) => {
|
||||
return `data:image/png;base64,${img}`;
|
||||
});
|
||||
}
|
||||
try {
|
||||
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 === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
|
||||
return imageUrl;
|
||||
if (!input.resType) input.resType = "b64";
|
||||
if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
|
||||
// await u.db("t_myTasks").where("id", taskId).update({
|
||||
// state: "已完成",
|
||||
// });
|
||||
return imageUrl;
|
||||
} catch (error: any) {
|
||||
// await u.db("t_myTasks").where("id", taskId).update({
|
||||
// state: "生成失败",
|
||||
// reason: error.message,
|
||||
// });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
62
src/utils/ai/image/owned/formal.ts
Normal file
62
src/utils/ai/image/owned/formal.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import "../type";
|
||||
import { generateImage, generateText, ModelMessage } from "ai";
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||
import { pollTask } from "@/utils/ai/utils";
|
||||
import u from "@/utils";
|
||||
import axios from "axios";
|
||||
import * as volcengine from "../adapter/volcengine";
|
||||
const modelFn = {
|
||||
volcengine,
|
||||
} as const;
|
||||
function template(replaceObj: Record<string, any>, url: string) {
|
||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||
});
|
||||
}
|
||||
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||
if (!config.model) throw new Error("缺少Model名称");
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
const { requestUrl, queryUrl = null } = modelFn["volcengine"].buildReqUrl(config?.baseURL);
|
||||
const taskBody = modelFn["volcengine"].buildReqBody(input, config);
|
||||
|
||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||
try {
|
||||
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
||||
|
||||
if (queryUrl) {
|
||||
if (data.code != "success") throw new Error(`任务提交失败: ${data || "未知错误"}`);
|
||||
const taskId = data.data;
|
||||
|
||||
return await pollTask(async () => {
|
||||
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
|
||||
const { status, result_url, fail_reason } = queryData.data || {};
|
||||
|
||||
if (status === "FAILURE") {
|
||||
return { completed: false, error: fail_reason ?? "图片生成失败" };
|
||||
}
|
||||
|
||||
if (status === "SUCCESS") {
|
||||
return { completed: true, url: result_url };
|
||||
}
|
||||
|
||||
return { completed: false };
|
||||
});
|
||||
} else {
|
||||
return data.data[0]?.url;
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = u.error(error).message || "图片生成失败";
|
||||
throw new Error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
async function urlToBase64(url: string): Promise<string> {
|
||||
const res = await axios.get(url, { responseType: "arraybuffer" });
|
||||
const base64 = Buffer.from(res.data).toString("base64");
|
||||
const mimeType = res.headers["content-type"] || "image/png";
|
||||
return `data:${mimeType};base64,${base64}`;
|
||||
}
|
||||
@ -6,7 +6,7 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
if (!config.model) throw new Error("缺少Model名称");
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
if (!input.prompt) throw new Error("缺少提示词");
|
||||
|
||||
|
||||
const options: any = {};
|
||||
if (config.apiKey) options.apiKey = config.apiKey;
|
||||
if (config?.baseURL) options.baseURL = config.baseURL;
|
||||
@ -42,7 +42,6 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
},
|
||||
},
|
||||
},
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
if (!result.files.length) {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import "../type";
|
||||
import { generateImage, generateText, ModelMessage } from "ai";
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||
import { createOpenAI, OpenAIProviderSettings } from "@ai-sdk/openai";
|
||||
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||
@ -11,7 +14,7 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||
|
||||
const otherProvider = createOpenAICompatible({
|
||||
name: "xixixi",
|
||||
name: "other",
|
||||
baseURL: config.baseURL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
@ -28,6 +31,12 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||
const model = config.model;
|
||||
if (model.includes("gemini") || model.includes("nano")) {
|
||||
// 对于 Gemini 模型,使用 Google provider 以支持 imageConfig 参数
|
||||
const googleProvider = createGoogleGenerativeAI({
|
||||
apiKey: apiKey,
|
||||
baseURL: config.baseURL,
|
||||
});
|
||||
|
||||
let promptData;
|
||||
if (input.imageBase64 && input.imageBase64.length) {
|
||||
promptData = [{ role: "system", content: fullPrompt + `请直接输出图片` }];
|
||||
@ -35,15 +44,14 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
role: "user",
|
||||
content: input.imageBase64.map((i) => ({
|
||||
type: "image",
|
||||
image: i,
|
||||
image: i.replace(/^data:image\/[^;]+;base64,/, ""),
|
||||
})),
|
||||
});
|
||||
} else {
|
||||
promptData = fullPrompt + `请直接输出图片`;
|
||||
}
|
||||
|
||||
const result = await generateText({
|
||||
model: otherProvider.languageModel(model),
|
||||
model: googleProvider.languageModel(model),
|
||||
prompt: promptData as string | ModelMessage[],
|
||||
providerOptions: {
|
||||
google: {
|
||||
@ -52,7 +60,6 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
? { aspectRatio: input.aspectRatio }
|
||||
: { aspectRatio: input.aspectRatio, imageSize: input.size }),
|
||||
},
|
||||
responseModalities: ["IMAGE"],
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -105,7 +112,11 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
size: sizeMap[input.size] ?? "1024x1024",
|
||||
});
|
||||
|
||||
return image.base64;
|
||||
if (image.base64.startsWith("data:image/")) {
|
||||
return image.base64;
|
||||
} else {
|
||||
return `data:image/png;base64,${image.base64}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -5,6 +5,10 @@ interface ImageConfig {
|
||||
size: "1K" | "2K" | "4K";
|
||||
aspectRatio: string;
|
||||
resType?: "url" | "b64";
|
||||
taskClass: string;
|
||||
name: string;
|
||||
describe: string;
|
||||
projectId: number;
|
||||
}
|
||||
|
||||
interface AIConfig {
|
||||
|
||||
@ -35,11 +35,18 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig = {}) => {
|
||||
}
|
||||
if (!owned) throw new Error("不支持的厂商");
|
||||
|
||||
const modelInstance = owned.instance({ apiKey, baseURL: baseURL!, name: "xixixi" });
|
||||
const modelInstance = owned.instance({ apiKey: apiKey!, baseURL: baseURL! });
|
||||
|
||||
const maxStep = input.maxStep ?? (input.tools ? Object.keys(input.tools).length * 5 : undefined);
|
||||
const outputBuilders: Record<string, (schema: any) => any> = {
|
||||
schema: (s) => {
|
||||
const schemaPrompt = `\n请按照以下 schema 格式返回结果:\n${JSON.stringify(
|
||||
z.toJSONSchema(z.object(s)),
|
||||
null,
|
||||
2,
|
||||
)}\n请输出JSON格式,只返回结果,不要将Schema返回。`;
|
||||
input.system = (input.system ?? "") + schemaPrompt;
|
||||
// 返回验证模式
|
||||
return Output.object({ schema: z.object(s) });
|
||||
},
|
||||
object: () => {
|
||||
@ -47,14 +54,14 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig = {}) => {
|
||||
z.toJSONSchema(z.object(input.output)),
|
||||
null,
|
||||
2,
|
||||
)}\n只返回结果,不要将Schema返回。`;
|
||||
)}\n请输出JSON格式,只返回结果,不要将Schema返回。`;
|
||||
input.system = (input.system ?? "") + jsonSchemaPrompt;
|
||||
// return Output.json();
|
||||
},
|
||||
};
|
||||
|
||||
const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null;
|
||||
const chatModelManufacturer = ["volcengine", "other", "openai", "modelScope","grsai"];
|
||||
const chatModelManufacturer = ["volcengine", "other", "openai", "modelScope", "grsai", "formal"];
|
||||
const modelFn = chatModelManufacturer.includes(owned.manufacturer) ? (modelInstance as OpenAIProvider).chat(model!) : modelInstance(model!);
|
||||
|
||||
return {
|
||||
|
||||
@ -21,8 +21,7 @@ interface Owned {
|
||||
| typeof createZhipu
|
||||
| typeof createQwen
|
||||
| typeof createGoogleGenerativeAI
|
||||
| typeof createAnthropic
|
||||
| typeof createOpenAICompatible;
|
||||
| typeof createAnthropic;
|
||||
}
|
||||
const instanceMap = {
|
||||
deepSeek: createDeepSeek,
|
||||
@ -35,7 +34,8 @@ const instanceMap = {
|
||||
modelScope: (options: OpenAIProviderSettings) => createOpenAI({ ...options, headers: { ...options?.headers, "X-ModelScope-Async-Mode": "true" } }),
|
||||
xai: createXai,
|
||||
other: createOpenAI,
|
||||
grsai:createOpenAI
|
||||
grsai: createOpenAI,
|
||||
formal: createOpenAI,
|
||||
};
|
||||
const modelList: Owned[] = [
|
||||
// DeepSeek
|
||||
|
||||
39
src/utils/ai/video/adapter/openai.ts
Normal file
39
src/utils/ai/video/adapter/openai.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import sharp from "sharp";
|
||||
import "../type";
|
||||
import FormData from "form-data";
|
||||
|
||||
export async function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||
const sizeMap: Record<string, string> = {
|
||||
"16:9": "1280x720",
|
||||
"9:16": "720x1280",
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append("model", config.model!);
|
||||
formData.append("prompt", input.prompt);
|
||||
formData.append("seconds", String(input.duration));
|
||||
|
||||
const size = sizeMap[input.aspectRatio] || "1280x720";
|
||||
formData.append("size", size);
|
||||
if (input.imageBase64 && input.imageBase64.length) {
|
||||
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
|
||||
// 解析尺寸
|
||||
const [width, height] = size.split("x").map(Number);
|
||||
|
||||
// 使用 sharp 调整图片尺寸
|
||||
const resizedBuffer = await sharp(buffer).resize(width, height, { fit: "cover" }).jpeg({ quality: 100 }).toBuffer();
|
||||
|
||||
formData.append("input_reference", resizedBuffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string; downLoadUrl: string } {
|
||||
return {
|
||||
requestUrl: `${baseUrl}/v1/videos`,
|
||||
queryUrl: `${baseUrl}/v1/videos/{id}`,
|
||||
downLoadUrl: `${baseUrl}/v1/videos/{id}/content`,
|
||||
};
|
||||
}
|
||||
26
src/utils/ai/video/adapter/vidu.ts
Normal file
26
src/utils/ai/video/adapter/vidu.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import "../type";
|
||||
|
||||
export function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||
const requestBody: any = {
|
||||
model: config.model,
|
||||
...(input.imageBase64 && input.imageBase64.length ? { images: input.imageBase64 } : {}),
|
||||
prompt: input.prompt,
|
||||
duration: input.duration,
|
||||
size: input.resolution,
|
||||
metadata: {
|
||||
aspect_ratio: input.aspectRatio,
|
||||
audio: input?.audio ?? false,
|
||||
off_peak: false,
|
||||
},
|
||||
};
|
||||
|
||||
console.log("%c Line:5 🍔 requestBody", "background:#465975", requestBody);
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string } {
|
||||
return {
|
||||
requestUrl: `${baseUrl}/v1/video/generations`,
|
||||
queryUrl: `${baseUrl}/v1/video/generations/{id}`,
|
||||
};
|
||||
}
|
||||
54
src/utils/ai/video/adapter/volcengine.ts
Normal file
54
src/utils/ai/video/adapter/volcengine.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import "../type";
|
||||
|
||||
export function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||
const hasStartEndType = input.mode === "startEnd";
|
||||
const images = input.imageBase64 || [];
|
||||
// 判断是否为首尾帧模式(需要两张图且类型支持首尾帧)
|
||||
const isStartEndMode = images.length === 2 && hasStartEndType;
|
||||
|
||||
// 构建图片内容
|
||||
const imageContent = images.map((base64, index) => {
|
||||
const item: Record<string, any> = {
|
||||
type: "image_url",
|
||||
image_url: { url: base64 },
|
||||
};
|
||||
if (isStartEndMode) {
|
||||
item.role = index === 0 ? "first_frame" : "last_frame";
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
// // 构建请求体
|
||||
// const requestBody: Record<string, any> = {
|
||||
// model: config.model,
|
||||
// content: [{ type: "text", text: input.prompt }, ...imageContent],
|
||||
// duration: input.duration,
|
||||
// resolution: input.resolution,
|
||||
// watermark: false,
|
||||
// };
|
||||
const requestBody: any = {
|
||||
model: config.model,
|
||||
...(input.imageBase64 && input.imageBase64.length ? { images: input.imageBase64 } : {}),
|
||||
prompt: input.prompt,
|
||||
duration: input.duration,
|
||||
size: input.resolution,
|
||||
metadata: {
|
||||
generate_audio: input?.audio ?? false,
|
||||
ratio: input.aspectRatio,
|
||||
image_roles: ["first_frame", "last_frame"],
|
||||
},
|
||||
};
|
||||
|
||||
// // 仅当模型支持音频时才添加 generate_audio 字段
|
||||
// if (typeof input.audio == "boolean") {
|
||||
// requestBody.generate_audio = input.audio ?? false;
|
||||
// }
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string } {
|
||||
return {
|
||||
requestUrl: `${baseUrl}/v1/video/generations`,
|
||||
queryUrl: `${baseUrl}/v1/video/generations/{id}`,
|
||||
};
|
||||
}
|
||||
61
src/utils/ai/video/adapter/wan.ts
Normal file
61
src/utils/ai/video/adapter/wan.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import "../type";
|
||||
|
||||
export function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||
const images = input.imageBase64 || [];
|
||||
|
||||
// 构建图片内容
|
||||
const imageContent = images.map((base64, index) => {
|
||||
const item: Record<string, any> = {
|
||||
type: "image_url",
|
||||
image: { url: base64 },
|
||||
};
|
||||
return item;
|
||||
});
|
||||
const sizeMap: Record<string, Record<string, string>> = {
|
||||
"480p": {
|
||||
"16:9": "832*480",
|
||||
"9:16": "480*832",
|
||||
},
|
||||
"720p": {
|
||||
"16:9": "1280*720",
|
||||
"9:16": "720*1280",
|
||||
},
|
||||
"1080p": {
|
||||
"16:9": "1920*1080",
|
||||
"9:16": "1080*1920",
|
||||
},
|
||||
};
|
||||
const hasStartEnd = input.mode == "startEnd";
|
||||
|
||||
const imageReq: Record<string, string> = {};
|
||||
if (hasStartEnd && Array.isArray(images) && images.length) {
|
||||
if (images[0]) imageReq.first_frame_url = images[0];
|
||||
if (images[1]) imageReq.last_frame_url = images[1];
|
||||
} else if (!hasStartEnd && Array.isArray(images) && images[0]) {
|
||||
imageReq.img_url = images[0];
|
||||
}
|
||||
|
||||
const resolutionKey = input.resolution;
|
||||
|
||||
const size = sizeMap[resolutionKey]?.[input.aspectRatio];
|
||||
|
||||
const requestBody: any = {
|
||||
model: config.model,
|
||||
...(imageReq?.img_url ? { input_reference: imageReq.img_url } : {}),
|
||||
prompt: input.prompt,
|
||||
size,
|
||||
duration: input.duration,
|
||||
metadata: {
|
||||
...imageReq,
|
||||
audio: input?.audio ?? false,
|
||||
},
|
||||
};
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string } {
|
||||
return {
|
||||
requestUrl: `${baseUrl}/v1/video/generations`,
|
||||
queryUrl: `${baseUrl}/v1/video/generations/{id}`,
|
||||
};
|
||||
}
|
||||
@ -12,6 +12,8 @@ import gemini from "./owned/gemini";
|
||||
import apimart from "./owned/apimart";
|
||||
import other from "./owned/other";
|
||||
import grsai from "./owned/grsai";
|
||||
import formal from "./owned/formal";
|
||||
|
||||
const modelInstance = {
|
||||
volcengine: volcengine,
|
||||
kling: kling,
|
||||
@ -21,7 +23,8 @@ const modelInstance = {
|
||||
runninghub: runninghub,
|
||||
apimart: apimart,
|
||||
other: other,
|
||||
grsai:grsai
|
||||
grsai: grsai,
|
||||
formal: formal,
|
||||
} as const;
|
||||
|
||||
export default async (input: VideoConfig, config?: AIConfig) => {
|
||||
@ -32,7 +35,16 @@ export default async (input: VideoConfig, config?: AIConfig) => {
|
||||
if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的视频厂商");
|
||||
// const owned = modelList.find((m) => m.model === model);
|
||||
// if (!owned) throw new Error("不支持的模型");
|
||||
|
||||
//添加到任务中心
|
||||
// const [taskId] = await u.db("t_myTasks").insert({
|
||||
// taskClass: input.taskClass,
|
||||
// relatedObjects: input.name,
|
||||
// model: config?.model ? config.model : "未知模型",
|
||||
// describe: input.describe ? input.describe : "无",
|
||||
// state: "进行中",
|
||||
// startTime: Date.now(),
|
||||
// projectId: input.projectId,
|
||||
// });
|
||||
// 补充图片的 base64 内容类型字符串
|
||||
if (input.imageBase64 && input.imageBase64.length > 0) {
|
||||
input.imageBase64 = input.imageBase64.map((img) => {
|
||||
@ -59,9 +71,20 @@ export default async (input: VideoConfig, config?: AIConfig) => {
|
||||
|
||||
let videoUrl = await manufacturerFn(input, { model, apiKey, baseURL });
|
||||
if (videoUrl) {
|
||||
const response = await axios.get(videoUrl, { responseType: "stream" });
|
||||
await u.oss.writeFile(input.savePath, response.data);
|
||||
return input.savePath;
|
||||
try {
|
||||
const response = await axios.get(videoUrl, { responseType: "stream" });
|
||||
await u.oss.writeFile(input.savePath, response.data);
|
||||
// await u.db("t_myTasks").where("id", taskId).update({
|
||||
// state: "已完成",
|
||||
// });
|
||||
return input.savePath;
|
||||
} catch (err: any) {
|
||||
// await u.db("t_myTasks").where("id", taskId).update({
|
||||
// state: "生成失败",
|
||||
// reason: err.message,
|
||||
// });
|
||||
return videoUrl;
|
||||
}
|
||||
}
|
||||
return videoUrl;
|
||||
};
|
||||
|
||||
@ -159,15 +159,6 @@ const modelList: Owned[] = [
|
||||
audio: false,
|
||||
},
|
||||
// ================== ViduQ3系列 ==================
|
||||
// viduq3-pro 文生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
model: "viduq3-pro",
|
||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }],
|
||||
aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"],
|
||||
type: ["text"],
|
||||
audio: true,
|
||||
},
|
||||
// viduq3-pro 图生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
@ -187,14 +178,6 @@ const modelList: Owned[] = [
|
||||
audio: false,
|
||||
},
|
||||
// viduq2-pro 文生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-pro",
|
||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],
|
||||
aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"],
|
||||
type: ["text"],
|
||||
audio: false,
|
||||
},
|
||||
// viduq2-pro 图生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
@ -205,14 +188,6 @@ const modelList: Owned[] = [
|
||||
audio: false,
|
||||
},
|
||||
// viduq2-turbo 文生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
model: "viduq2-turbo",
|
||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],
|
||||
aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"],
|
||||
type: ["text"],
|
||||
audio: false,
|
||||
},
|
||||
// viduq2-turbo 图生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
@ -223,14 +198,6 @@ const modelList: Owned[] = [
|
||||
audio: false,
|
||||
},
|
||||
// viduq1 文生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
model: "viduq1",
|
||||
durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],
|
||||
aspectRatio: ["16:9", "9:16", "1:1"],
|
||||
type: ["text"],
|
||||
audio: false,
|
||||
},
|
||||
// viduq1 图生视频
|
||||
{
|
||||
manufacturer: "vidu",
|
||||
@ -465,7 +432,7 @@ const modelList: Owned[] = [
|
||||
type: ["singleImage", "text"],
|
||||
audio: false,
|
||||
},
|
||||
// ================== Apimart 系列 ==================
|
||||
// ================== Apimart 系列 ==================
|
||||
// sora
|
||||
{
|
||||
manufacturer: "apimart",
|
||||
|
||||
169
src/utils/ai/video/owned/formal.ts
Normal file
169
src/utils/ai/video/owned/formal.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import "../type";
|
||||
import { pollTask } from "@/utils/ai/utils";
|
||||
import u from "@/utils";
|
||||
import axios from "axios";
|
||||
import path from "path";
|
||||
|
||||
import * as volcengine from "../adapter/volcengine";
|
||||
import * as openai from "../adapter/openai";
|
||||
import * as vidu from "../adapter/vidu";
|
||||
import * as wan from "../adapter/wan";
|
||||
|
||||
// 适配器映射
|
||||
const modelFn = {
|
||||
volcengine,
|
||||
vidu,
|
||||
openai,
|
||||
wan,
|
||||
} as const;
|
||||
|
||||
// 模型名称到适配器的映射(精确匹配)
|
||||
const modelMapping: Record<string, keyof typeof modelFn> = {
|
||||
// Volcengine 火山引擎模型
|
||||
"doubao-seedance-1-5-pro-251215": "volcengine",
|
||||
"doubao-seedance-1-0-pro-250528": "volcengine",
|
||||
"Seedance-2.0": "volcengine",
|
||||
// Vidu 模型
|
||||
ViduQ2: "vidu",
|
||||
"ViduQ2-turbo": "vidu",
|
||||
"ViduQ2-pro": "vidu",
|
||||
"ViduQ3-pro": "vidu",
|
||||
// OpenAI 模型
|
||||
sora2: "openai",
|
||||
"sora2-pro": "openai",
|
||||
"gpt-video": "openai",
|
||||
// 万象/Wan 模型
|
||||
"Wan2.6-T2V": "wan",
|
||||
"Wan2.6-I2V": "wan",
|
||||
};
|
||||
|
||||
// 模型名称关键字到适配器的映射(模糊匹配)
|
||||
const modelKeywords: Array<{ keywords: string[]; adapter: keyof typeof modelFn }> = [
|
||||
{ keywords: ["doubao", "volcengine", "seedance"], adapter: "volcengine" },
|
||||
{ keywords: ["vidu"], adapter: "vidu" },
|
||||
{ keywords: ["sora", "openai", "gpt"], adapter: "openai" },
|
||||
{ keywords: ["wan", "wanx"], adapter: "wan" },
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据模型名称获取对应的适配器
|
||||
*/
|
||||
function getModelAdapter(modelName: string) {
|
||||
// 1. 先尝试精确匹配
|
||||
const exactMatch = modelMapping[modelName.toLowerCase()];
|
||||
if (exactMatch) {
|
||||
return modelFn[exactMatch];
|
||||
}
|
||||
|
||||
// 2. 尝试关键字模糊匹配
|
||||
const lowerModelName = modelName.toLowerCase();
|
||||
for (const { keywords, adapter } of modelKeywords) {
|
||||
if (keywords.some((kw) => lowerModelName.includes(kw.toLowerCase()))) {
|
||||
return modelFn[adapter];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 如果模型名称本身就是适配器名称
|
||||
if (modelName in modelFn) {
|
||||
return modelFn[modelName as keyof typeof modelFn];
|
||||
}
|
||||
|
||||
return modelFn["wan"];
|
||||
}
|
||||
function template(replaceObj: Record<string, any>, url: string) {
|
||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||
});
|
||||
}
|
||||
export default async (input: VideoConfig, config: AIConfig): Promise<string> => {
|
||||
if (!config.model) throw new Error("缺少Model名称");
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
// 根据模型名称获取对应的适配器
|
||||
const modelAdapter = getModelAdapter(config.model);
|
||||
|
||||
const { requestUrl, queryUrl, downLoadUrl = null } = modelAdapter.buildReqUrl(config.baseURL);
|
||||
const taskBody = await modelAdapter.buildReqBody(input, config);
|
||||
|
||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||
try {
|
||||
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
||||
console.log("%c Line:91 🌽 data", "background:#3f7cff", data);
|
||||
|
||||
const taskId = data.id ?? data.taskId ?? data.task_id ?? data.data;
|
||||
|
||||
if (!taskId) throw new Error(`任务提交失败: ${data ? JSON.stringify(data) : "未知错误"}`);
|
||||
|
||||
return await pollTask(async () => {
|
||||
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
console.log("%c Line:99 🥝 queryData", "background:#e41a6a", queryData);
|
||||
|
||||
// const { status, result_url, fail_reason } = queryData.data || {};
|
||||
|
||||
const status = queryData?.status ?? queryData?.data?.status;
|
||||
const result_url = queryData?.metadata?.url ?? queryData?.data?.result_url;
|
||||
const fail_reason = queryData?.data?.fail_reason ?? queryData?.data;
|
||||
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "SUCCESS":
|
||||
case "success":
|
||||
if (downLoadUrl) {
|
||||
// 下载视频,带重试机制
|
||||
let videoRes;
|
||||
let retries = 3;
|
||||
let lastError;
|
||||
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
// 构建下载URL
|
||||
const finalDownloadUrl = downLoadUrl
|
||||
? template({ id: taskId }, downLoadUrl)
|
||||
: queryData.video_url || queryData.url || queryData.metadata.url; // 从响应中获取视频URL
|
||||
|
||||
videoRes = await axios.get(finalDownloadUrl, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
responseType: "arraybuffer",
|
||||
timeout: 60 * 1000 * 10, // 60秒超时
|
||||
});
|
||||
break; // 成功则跳出循环
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`视频下载失败,第 ${i + 1}/${retries} 次尝试:`, error);
|
||||
if (i < retries - 1) {
|
||||
// 等待后重试,使用指数退避
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!videoRes) {
|
||||
throw new Error(`视频下载失败,已重试 ${retries} 次: ${lastError}`);
|
||||
}
|
||||
|
||||
// 将视频buffer转换为base64或直接返回buffer
|
||||
const savePath = input.savePath.endsWith(".mp4") ? input.savePath : path.join(input.savePath, `other_${Date.now()}.mp4`);
|
||||
await u.oss.writeFile(input.savePath, videoRes.data);
|
||||
|
||||
return { completed: true, url: savePath };
|
||||
} else {
|
||||
return { completed: true, url: result_url };
|
||||
}
|
||||
}
|
||||
if (status === "FAILURE") {
|
||||
return { completed: false, error: fail_reason ? fail_reason : "视频生成失败" };
|
||||
}
|
||||
|
||||
if (status === "SUCCESS") {
|
||||
return { completed: true, url: result_url };
|
||||
}
|
||||
|
||||
return { completed: false };
|
||||
});
|
||||
} catch (error: any) {
|
||||
const msg = u.error(error).message || "图片生成失败";
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
};
|
||||
@ -2,58 +2,145 @@ import "../type";
|
||||
import axios from "axios";
|
||||
import sharp from "sharp";
|
||||
import FormData from "form-data";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import u from "@/utils";
|
||||
|
||||
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import { experimental_generateVideo as generateVideo } from "ai";
|
||||
function template(replaceObj: Record<string, any>, url: string) {
|
||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||
});
|
||||
}
|
||||
export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
if (!config.baseURL) throw new Error("缺少baseURL");
|
||||
// const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
||||
const [requestUrl, queryUrl] = config.baseURL.split("|");
|
||||
|
||||
const authorization = `Bearer ${config.apiKey}`;
|
||||
const urls = config.baseURL.split("|");
|
||||
const isThreeUrlMode = urls.length === 3;
|
||||
console.log("%c Line:24 🌭 isThreeUrlMode", "background:#ed9ec7", isThreeUrlMode);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("model", config.model);
|
||||
formData.append("prompt", input.prompt);
|
||||
formData.append("seconds", String(input.duration));
|
||||
let requestUrl: string, queryUrl: string, downLoadUrl: string | undefined;
|
||||
|
||||
if (isThreeUrlMode) {
|
||||
[requestUrl, queryUrl, downLoadUrl] = urls;
|
||||
} else {
|
||||
[requestUrl, queryUrl] = urls;
|
||||
}
|
||||
|
||||
// 根据 aspectRatio 设置 size
|
||||
const sizeMap: Record<string, string> = {
|
||||
"16:9": "1280x720",
|
||||
"9:16": "720x1280",
|
||||
};
|
||||
formData.append("size", sizeMap[input.aspectRatio] || "1920x1080");
|
||||
let resData;
|
||||
let taskId = "";
|
||||
if (isThreeUrlMode) {
|
||||
// 三个地址:使用 FormData 方式
|
||||
const formData = new FormData();
|
||||
formData.append("model", config.model);
|
||||
formData.append("prompt", input.prompt);
|
||||
formData.append("seconds", String(input.duration));
|
||||
|
||||
if (input.imageBase64 && input.imageBase64.length) {
|
||||
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
formData.append("input_reference", buffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
||||
}
|
||||
const size = sizeMap[input.aspectRatio] || "1280x720";
|
||||
formData.append("size", size);
|
||||
|
||||
const body = {
|
||||
model: config.model,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: input.prompt,
|
||||
},
|
||||
],
|
||||
if (input.imageBase64 && input.imageBase64.length) {
|
||||
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
|
||||
// 解析尺寸
|
||||
const [width, height] = size.split("x").map(Number);
|
||||
|
||||
// 使用 sharp 调整图片尺寸
|
||||
const resizedBuffer = await sharp(buffer).resize(width, height, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
|
||||
|
||||
formData.append("input_reference", resizedBuffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
||||
}
|
||||
|
||||
const response = await axios.post(requestUrl, formData, {
|
||||
headers: { Authorization: authorization, ...formData.getHeaders() },
|
||||
});
|
||||
|
||||
taskId = response.data?.task_id || response.data?.id;
|
||||
resData = response.data;
|
||||
} else {
|
||||
// 两个地址:使用 JSON 方式
|
||||
|
||||
const requestBody: any = {
|
||||
model: config.model,
|
||||
prompt: input.prompt,
|
||||
aspect_ratio: input.aspectRatio || "16:9",
|
||||
size: "720p",
|
||||
};
|
||||
|
||||
if (input.imageBase64 && input.imageBase64.length) {
|
||||
requestBody.images = input.imageBase64;
|
||||
}
|
||||
|
||||
const response = await axios.post(requestUrl, JSON.stringify(requestBody), {
|
||||
headers: {
|
||||
Authorization: authorization,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
],
|
||||
};
|
||||
const { data } = await axios.post(
|
||||
config.baseURL,
|
||||
{ ...body },
|
||||
{
|
||||
headers: { "Content-Type": "application/json", Authorization: authorization },
|
||||
},
|
||||
);
|
||||
});
|
||||
taskId = response.data.id;
|
||||
resData = response.data;
|
||||
}
|
||||
console.log("%c Line:87 🥒 taskId", "background:#f5ce50", taskId);
|
||||
|
||||
console.log("%c Line:49 🥓 data", "background:#ffdd4d", data);
|
||||
if (!taskId) throw new Error(`任务提交失败: ${resData ? JSON.stringify(resData) : "未知错误"}`);
|
||||
|
||||
if (data.status === "FAILED") throw new Error(`任务提交失败: ${data.errorMessage || "未知错误"}`);
|
||||
return await pollTask(async () => {
|
||||
// 构建查询URL,两个地址模式时使用URL参数
|
||||
const finalQueryUrl = isThreeUrlMode ? template({ id: taskId }, queryUrl) : `${queryUrl}?id=${taskId}`;
|
||||
|
||||
const { data: queryData } = await axios.get(finalQueryUrl, {
|
||||
headers: { Authorization: authorization },
|
||||
});
|
||||
console.log("%c Line:100 🥑 queryData", "background:#42b983", queryData);
|
||||
|
||||
if (queryData.status === "completed") {
|
||||
// 下载视频,带重试机制
|
||||
let videoRes;
|
||||
let retries = 3;
|
||||
let lastError;
|
||||
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
// 构建下载URL
|
||||
const finalDownloadUrl = isThreeUrlMode && downLoadUrl ? template({ id: taskId }, downLoadUrl) : queryData.video_url || queryData.url; // 从响应中获取视频URL
|
||||
|
||||
videoRes = await axios.get(finalDownloadUrl, {
|
||||
headers: isThreeUrlMode ? { Authorization: authorization } : {},
|
||||
responseType: "arraybuffer",
|
||||
timeout: 60 * 1000 * 10, // 60秒超时
|
||||
});
|
||||
break; // 成功则跳出循环
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`视频下载失败,第 ${i + 1}/${retries} 次尝试:`, error);
|
||||
if (i < retries - 1) {
|
||||
// 等待后重试,使用指数退避
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!videoRes) {
|
||||
throw new Error(`视频下载失败,已重试 ${retries} 次: ${lastError}`);
|
||||
}
|
||||
|
||||
// 将视频buffer转换为base64或直接返回buffer
|
||||
const savePath = input.savePath.endsWith(".mp4") ? input.savePath : path.join(input.savePath, `other_${Date.now()}.mp4`);
|
||||
await u.oss.writeFile(input.savePath, videoRes.data);
|
||||
|
||||
return { completed: true, url: savePath };
|
||||
}
|
||||
if (queryData.status === "failed") return { completed: false, error: `任务失败: ${queryData.error || "未知错误"}` };
|
||||
// if (queryData.status === "QUEUED" || queryData.status === "RUNNING") return { completed: false };
|
||||
return { completed: false };
|
||||
});
|
||||
};
|
||||
|
||||
@ -67,6 +67,9 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
console.log("%c Line:65 🍢 response", "background:#3f7cff", response);
|
||||
|
||||
|
||||
taskId = response.data.task_id;
|
||||
} else {
|
||||
// 图生视频
|
||||
@ -89,6 +92,8 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
console.log("%c Line:90 🍷 response", "background:#2eafb0", response.data);
|
||||
|
||||
taskId = response.data.task_id;
|
||||
}
|
||||
|
||||
@ -103,6 +108,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
task_ids: [taskId],
|
||||
},
|
||||
});
|
||||
console.log("%c Line:113 🧀 response.data", "background:#33a5ff", response.data);
|
||||
|
||||
const tasks = response.data.tasks;
|
||||
if (!tasks || tasks.length === 0) {
|
||||
|
||||
@ -21,6 +21,8 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
};
|
||||
if (isStartEndMode) {
|
||||
item.role = index === 0 ? "first_frame" : "last_frame";
|
||||
} else {
|
||||
item.role = "reference_image";
|
||||
}
|
||||
return item;
|
||||
});
|
||||
@ -35,9 +37,10 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
};
|
||||
|
||||
// 仅当模型支持音频时才添加 generate_audio 字段
|
||||
if (input?.audio) {
|
||||
if (typeof input?.audio == "boolean") {
|
||||
requestBody.generate_audio = input.audio ?? false;
|
||||
}
|
||||
|
||||
// 创建视频生成任务
|
||||
const createResponse = await axios.post(baseUrl, requestBody, {
|
||||
headers: {
|
||||
@ -45,6 +48,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
console.log("%c Line:44 🍡 createResponse", "background:#2eafb0", createResponse.data);
|
||||
|
||||
const taskId = createResponse.data.id;
|
||||
|
||||
@ -55,18 +59,29 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
const data = await axios.get(`${baseUrl}/${taskId}`, {
|
||||
headers: { Authorization: authorization },
|
||||
});
|
||||
console.log("%c Line:62 🥕 data.data", "background:#e41a6a", data.data);
|
||||
|
||||
const { status, content } = data.data;
|
||||
const { status, content, error } = data.data;
|
||||
|
||||
switch (status) {
|
||||
case "succeeded":
|
||||
case "completed":
|
||||
return { completed: true, url: content?.video_url };
|
||||
case "failed":
|
||||
case "cancelled":
|
||||
case "expired":
|
||||
return { completed: false, error: `任务${status}` };
|
||||
let errorMsg = "";
|
||||
try {
|
||||
errorMsg = typeof error === "string" ? error : JSON.stringify(error);
|
||||
} catch (e) {
|
||||
errorMsg = error || "";
|
||||
}
|
||||
return { completed: false, error: `任务${status}: ${errorMsg}` };
|
||||
case "queued":
|
||||
case "running":
|
||||
case "unknown":
|
||||
case "submit":
|
||||
case "in_progress":
|
||||
return { completed: false };
|
||||
default:
|
||||
return { completed: false, error: `未知状态: ${status}` };
|
||||
|
||||
@ -7,6 +7,10 @@ interface VideoConfig {
|
||||
imageBase64?: string[];
|
||||
audio?: boolean;
|
||||
mode: "startEnd" | "multi" | "single" | "text";
|
||||
taskClass: string;
|
||||
name: string;
|
||||
projectId: number;
|
||||
describe?: string;
|
||||
}
|
||||
|
||||
interface AIConfig {
|
||||
|
||||
@ -88,6 +88,10 @@ export default async (images: Record<string, string>, directive: string, project
|
||||
imageBase64: base64Images,
|
||||
aspectRatio: aspectRatio ? aspectRatio : "16:9",
|
||||
size: "1K",
|
||||
taskClass: "图片编辑",
|
||||
name: `图片编辑-${uuid()}`,
|
||||
describe: `编辑指令: ${directive}`,
|
||||
projectId,
|
||||
},
|
||||
apiConfig,
|
||||
);
|
||||
|
||||
2454
tempCodeRunnerFile.javascript
Normal file
2454
tempCodeRunnerFile.javascript
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user