From 869bb5e998cc560999571bc3dc52c0b8be16b698 Mon Sep 17 00:00:00 2001 From: zhishi <1951671751@qq.com> Date: Fri, 27 Feb 2026 18:02:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=9E=8B=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=EF=BC=8C=E8=A7=A3=E9=99=A4=E6=A8=A1=E5=9E=8B=E5=86=99?= =?UTF-8?q?=E6=AD=BB=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/initDB.ts | 551 ++++++++++++++++++++++ src/router.ts | 130 ++--- src/routes/assets/generateAssets.ts | 105 +++-- src/routes/other/testVideo.ts | 1 + src/routes/setting/getAiModelList.ts | 33 ++ src/routes/setting/getVideoModelDetail.ts | 31 ++ src/routes/video/generateVideo.ts | 4 +- src/types/database.d.ts | 65 ++- src/utils/ai/image/index.ts | 12 +- src/utils/ai/image/modelList.ts | 7 - src/utils/ai/image/owned/modelScope.ts | 133 ++++++ src/utils/ai/text/index.ts | 11 +- src/utils/ai/text/modelList.ts | 61 ++- src/utils/ai/utils.ts | 4 +- src/utils/ai/video/index.ts | 4 +- src/utils/ai/video/owned/gemini.ts | 26 +- src/utils/ai/video/owned/kling.ts | 10 +- src/utils/ai/video/owned/runninghub.ts | 11 +- src/utils/ai/video/owned/vidu.ts | 46 +- src/utils/ai/video/owned/volcengine.ts | 8 +- src/utils/ai/video/owned/wan.ts | 18 +- src/utils/ai/video/type.ts | 1 + 22 files changed, 1064 insertions(+), 208 deletions(-) create mode 100644 src/routes/setting/getAiModelList.ts create mode 100644 src/routes/setting/getVideoModelDetail.ts create mode 100644 src/utils/ai/image/owned/modelScope.ts diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index ec89e5e..62c0713 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -554,6 +554,557 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => ]); }, }, + + { + name: "t_textModel", + builder: (table) => { + table.integer("id").notNullable(); + table.text("manufacturer"); + table.text("model"); + table.text("responseFormat"); + table.integer("image"); + table.integer("think"); + table.integer("tool"); + table.primary(["id"]); + }, + initData: async (knex) => { + await knex("t_textModel").insert([ + { 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-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: "gpt-4.1", responseFormat: "schema", image: 1, think: 0, tool: 1 }, + { manufacturer: "modelScope", model: "deepseek-ai/DeepSeek-V3.2", responseFormat: "object", image: 0, think: 0, tool: 1 }, + ]); + }, + }, + { + name: "t_imageModel", + builder: (table) => { + table.integer("id").notNullable(); + table.text("manufacturer"); + table.text("model"); + table.integer("grid"); + table.text("type"); + table.primary(["id"]); + }, + initData: async (knex) => { + await knex("t_imageModel").insert([ + { id: 1, manufacturer: "volcengine", model: "doubao-seedream-4-5-251128", grid: 0, type: "ti2i" }, + { id: 2, manufacturer: "volcengine", model: "doubao-seedream-4-0-250828", grid: 0, type: "ti2i" }, + { id: 3, manufacturer: "kling", model: "kling-image-o1", grid: 0, type: "ti2i" }, + { id: 4, manufacturer: "gemini", model: "gemini-2.5-flash-image", grid: 1, type: "ti2i" }, + { id: 5, manufacturer: "gemini", model: "gemini-3-pro-image-preview", grid: 1, type: "ti2i" }, + { id: 6, manufacturer: "vidu", model: "viduq1", grid: 0, type: "i2i" }, + { id: 7, manufacturer: "vidu", model: "viduq2", grid: 0, type: "ti2i" }, + { id: 8, manufacturer: "runninghub", model: "nanobanana", grid: 1, type: "ti2i" }, + { id: 9, manufacturer: "modelScope", model: "Qwen/Qwen-Image", grid: 1, type: "ti2i" }, + ]); + }, + }, + { + name: "t_videoModel", + builder: (table) => { + table.integer("id").notNullable(); + table.text("manufacturer"); + table.text("model"); + table.text("durationResolutionMap"); + table.text("aspectRatio"); + table.integer("audio"); + table.text("type"); + table.primary(["id"]); + }, + 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"] }]), + aspectRatio: JSON.stringify(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]), + audio: 1, + 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"] }]), + aspectRatio: JSON.stringify(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]), + audio: 0, + 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"] }]), + aspectRatio: JSON.stringify(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]), + audio: 0, + 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"] }]), + aspectRatio: JSON.stringify([]), + audio: 0, + 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"] }]), + aspectRatio: JSON.stringify(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"]), + audio: 0, + type: JSON.stringify(["text"]), + }, + { + 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"]), + }, + { + 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"]), + }, + { + 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"]), + }, + { + 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"]), + }, + { + 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"]), + }, + { + 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([ + { 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(["text"]), + }, + { + 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"] }]), + aspectRatio: JSON.stringify([]), + audio: 0, + 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"]), + }, + { + 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"]), + }, + { + 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([ + { duration: [4], resolution: ["360p", "720p", "1080p"] }, + { duration: [8], resolution: ["720p"] }, + ]), + aspectRatio: JSON.stringify([]), + audio: 0, + 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"] }]), + aspectRatio: JSON.stringify(["16:9", "9:16", "1:1", "4:3", "3:4"]), + audio: 1, + type: JSON.stringify(["text"]), + }, + { + id: 28, + manufacturer: "wan", + model: "wan2.5-t2v-preview", + durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["480p", "720p", "1080p"] }]), + aspectRatio: JSON.stringify(["16:9", "9:16", "1:1", "4:3", "3:4"]), + audio: 1, + type: JSON.stringify(["text"]), + }, + { + id: 29, + manufacturer: "wan", + model: "wan2.2-t2v-plus", + durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "1080p"] }]), + aspectRatio: JSON.stringify(["16:9", "9:16", "1:1", "4:3", "3:4"]), + audio: 0, + type: JSON.stringify(["text"]), + }, + { + id: 30, + manufacturer: "wan", + model: "wanx2.1-t2v-turbo", + durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "720p"] }]), + aspectRatio: JSON.stringify(["16:9", "9:16", "1:1", "4:3", "3:4"]), + audio: 0, + type: JSON.stringify(["text"]), + }, + { + id: 31, + manufacturer: "wan", + model: "wanx2.1-t2v-plus", + durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["720p"] }]), + aspectRatio: JSON.stringify(["16:9", "9:16", "1:1", "4:3", "3:4"]), + audio: 0, + 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"] }]), + aspectRatio: JSON.stringify([]), + audio: 1, + 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"] }]), + aspectRatio: JSON.stringify([]), + audio: 1, + type: JSON.stringify(["singleImage"]), + }, + { + id: 34, + manufacturer: "wan", + model: "wan2.5-i2v-preview", + durationResolutionMap: JSON.stringify([{ duration: [5, 10], resolution: ["480p", "720p", "1080p"] }]), + aspectRatio: JSON.stringify([]), + audio: 1, + type: JSON.stringify(["singleImage"]), + }, + { + id: 35, + manufacturer: "wan", + model: "wan2.2-i2v-flash", + durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "720p", "1080p"] }]), + aspectRatio: JSON.stringify([]), + audio: 0, + type: JSON.stringify(["singleImage"]), + }, + { + id: 36, + manufacturer: "wan", + model: "wan2.2-i2v-plus", + durationResolutionMap: JSON.stringify([{ duration: [5], resolution: ["480p", "1080p"] }]), + aspectRatio: JSON.stringify([]), + audio: 0, + 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"] }]), + aspectRatio: JSON.stringify([]), + audio: 0, + 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([ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ]), + aspectRatio: JSON.stringify(["16:9", "9:16"]), + audio: 1, + type: JSON.stringify(["text", "singleImage", "startEndRequired", "endFrameOptional", "reference"]), + }, + { + id: 42, + manufacturer: "gemini", + model: "veo-3.1-fast-generate-preview", + durationResolutionMap: JSON.stringify([ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ]), + aspectRatio: JSON.stringify(["16:9", "9:16"]), + audio: 1, + type: JSON.stringify(["text", "singleImage", "startEndRequired", "endFrameOptional", "reference"]), + }, + { + id: 43, + manufacturer: "gemini", + model: "veo-3.0-generate-preview", + durationResolutionMap: JSON.stringify([ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ]), + aspectRatio: JSON.stringify(["16:9", "9:16"]), + audio: 1, + type: JSON.stringify(["text", "singleImage"]), + }, + { + id: 44, + manufacturer: "gemini", + model: "veo-3.0-fast-generate-preview", + durationResolutionMap: JSON.stringify([ + { duration: [4, 6], resolution: ["720p"] }, + { duration: [8], resolution: ["720p", "1080p"] }, + ]), + aspectRatio: JSON.stringify(["16:9", "9:16"]), + audio: 1, + 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"] }]), + aspectRatio: JSON.stringify(["16:9", "9:16"]), + audio: 0, + 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"]), + }, + { + 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"]), + }, + ]); + }, + }, ]; for (const t of tables) { diff --git a/src/router.ts b/src/router.ts index abef8f6..9e089fd 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash a5e432459af85c08bbc13a86444f292c +// @routes-hash 88e9c15b913a8fd111d3adf162583c91 import { Express } from "express"; import route1 from "./routes/assets/addAssets"; @@ -48,37 +48,39 @@ import route44 from "./routes/script/geScriptApi"; import route45 from "./routes/setting/addModel"; import route46 from "./routes/setting/configurationModel"; import route47 from "./routes/setting/delModel"; -import route48 from "./routes/setting/getAiModelMap"; -import route49 from "./routes/setting/getLog"; -import route50 from "./routes/setting/getSetting"; -import route51 from "./routes/setting/getVideoModelList"; -import route52 from "./routes/setting/updateModel"; -import route53 from "./routes/setting/updeteModel"; -import route54 from "./routes/storyboard/batchSuperScoreImage"; -import route55 from "./routes/storyboard/chatStoryboard"; -import route56 from "./routes/storyboard/generateShotImage"; -import route57 from "./routes/storyboard/generateStoryboardApi"; -import route58 from "./routes/storyboard/generateVideoPrompt"; -import route59 from "./routes/storyboard/getStoryboard"; -import route60 from "./routes/storyboard/keepStoryboard"; -import route61 from "./routes/storyboard/saveStoryboard"; -import route62 from "./routes/storyboard/uploadImage"; -import route63 from "./routes/task/getTaskApi"; -import route64 from "./routes/task/taskDetails"; -import route65 from "./routes/user/getUser"; -import route66 from "./routes/video/addVideo"; -import route67 from "./routes/video/addVideoConfig"; -import route68 from "./routes/video/deleteVideoConfig"; -import route69 from "./routes/video/generatePrompt"; -import route70 from "./routes/video/generateVideo"; -import route71 from "./routes/video/getManufacturer"; -import route72 from "./routes/video/getVideo"; -import route73 from "./routes/video/getVideoConfigs"; -import route74 from "./routes/video/getVideoModel"; -import route75 from "./routes/video/getVideoStoryboards"; -import route76 from "./routes/video/reviseVideoStoryboards"; -import route77 from "./routes/video/saveVideo"; -import route78 from "./routes/video/upDateVideoConfig"; +import route48 from "./routes/setting/getAiModelList"; +import route49 from "./routes/setting/getAiModelMap"; +import route50 from "./routes/setting/getLog"; +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/generateShotImage"; +import route59 from "./routes/storyboard/generateStoryboardApi"; +import route60 from "./routes/storyboard/generateVideoPrompt"; +import route61 from "./routes/storyboard/getStoryboard"; +import route62 from "./routes/storyboard/keepStoryboard"; +import route63 from "./routes/storyboard/saveStoryboard"; +import route64 from "./routes/storyboard/uploadImage"; +import route65 from "./routes/task/getTaskApi"; +import route66 from "./routes/task/taskDetails"; +import route67 from "./routes/user/getUser"; +import route68 from "./routes/video/addVideo"; +import route69 from "./routes/video/addVideoConfig"; +import route70 from "./routes/video/deleteVideoConfig"; +import route71 from "./routes/video/generatePrompt"; +import route72 from "./routes/video/generateVideo"; +import route73 from "./routes/video/getManufacturer"; +import route74 from "./routes/video/getVideo"; +import route75 from "./routes/video/getVideoConfigs"; +import route76 from "./routes/video/getVideoModel"; +import route77 from "./routes/video/getVideoStoryboards"; +import route78 from "./routes/video/reviseVideoStoryboards"; +import route79 from "./routes/video/saveVideo"; +import route80 from "./routes/video/upDateVideoConfig"; export default async (app: Express) => { app.use("/assets/addAssets", route1); @@ -128,35 +130,37 @@ export default async (app: Express) => { app.use("/setting/addModel", route45); app.use("/setting/configurationModel", route46); app.use("/setting/delModel", route47); - app.use("/setting/getAiModelMap", route48); - app.use("/setting/getLog", route49); - app.use("/setting/getSetting", route50); - app.use("/setting/getVideoModelList", route51); - app.use("/setting/updateModel", route52); - app.use("/setting/updeteModel", route53); - app.use("/storyboard/batchSuperScoreImage", route54); - app.use("/storyboard/chatStoryboard", route55); - app.use("/storyboard/generateShotImage", route56); - app.use("/storyboard/generateStoryboardApi", route57); - app.use("/storyboard/generateVideoPrompt", route58); - app.use("/storyboard/getStoryboard", route59); - app.use("/storyboard/keepStoryboard", route60); - app.use("/storyboard/saveStoryboard", route61); - app.use("/storyboard/uploadImage", route62); - app.use("/task/getTaskApi", route63); - app.use("/task/taskDetails", route64); - app.use("/user/getUser", route65); - app.use("/video/addVideo", route66); - app.use("/video/addVideoConfig", route67); - app.use("/video/deleteVideoConfig", route68); - app.use("/video/generatePrompt", route69); - app.use("/video/generateVideo", route70); - app.use("/video/getManufacturer", route71); - app.use("/video/getVideo", route72); - app.use("/video/getVideoConfigs", route73); - app.use("/video/getVideoModel", route74); - app.use("/video/getVideoStoryboards", route75); - app.use("/video/reviseVideoStoryboards", route76); - app.use("/video/saveVideo", route77); - app.use("/video/upDateVideoConfig", route78); + app.use("/setting/getAiModelList", route48); + app.use("/setting/getAiModelMap", route49); + app.use("/setting/getLog", route50); + app.use("/setting/getSetting", route51); + 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/generateShotImage", route58); + app.use("/storyboard/generateStoryboardApi", route59); + app.use("/storyboard/generateVideoPrompt", route60); + app.use("/storyboard/getStoryboard", route61); + app.use("/storyboard/keepStoryboard", route62); + app.use("/storyboard/saveStoryboard", route63); + app.use("/storyboard/uploadImage", route64); + app.use("/task/getTaskApi", route65); + app.use("/task/taskDetails", route66); + app.use("/user/getUser", route67); + app.use("/video/addVideo", route68); + app.use("/video/addVideoConfig", route69); + app.use("/video/deleteVideoConfig", route70); + app.use("/video/generatePrompt", route71); + app.use("/video/generateVideo", route72); + app.use("/video/getManufacturer", route73); + app.use("/video/getVideo", route74); + app.use("/video/getVideoConfigs", route75); + app.use("/video/getVideoModel", route76); + app.use("/video/getVideoStoryboards", route77); + app.use("/video/reviseVideoStoryboards", route78); + app.use("/video/saveVideo", route79); + app.use("/video/upDateVideoConfig", route80); } diff --git a/src/routes/assets/generateAssets.ts b/src/routes/assets/generateAssets.ts index e510a2c..3cdc1c3 100644 --- a/src/routes/assets/generateAssets.ts +++ b/src/routes/assets/generateAssets.ts @@ -2,7 +2,7 @@ import express from "express"; import u from "@/utils"; import { z } from "zod"; import { v4 as uuidv4 } from "uuid"; -import { success } from "@/lib/responseFormat"; +import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import sharp from "sharp"; const router = express.Router(); @@ -124,60 +124,67 @@ export default router.post( assetsId: id, }); const apiConfig = await u.getPromptAi("assetsImage"); + try { + const contentStr = await u.ai.image( + { + systemPrompt, + prompt: userPrompt, + imageBase64: base64 ? [base64] : [], + size: "2K", + aspectRatio: "16:9", + }, + apiConfig, + ); - const contentStr = await u.ai.image( - { - systemPrompt, - prompt: userPrompt, - imageBase64: base64 ? [base64] : [], - size: "2K", - aspectRatio: "16:9", - }, - apiConfig, - ); + let insertType; + const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); + let buffer = Buffer.from(match && match.length >= 2 ? match[1]! : contentStr!, "base64"); - let insertType; - const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); - let buffer = Buffer.from(match && match.length >= 2 ? match[1]! : contentStr!, "base64"); + if (type != "storyboard") { + //添加文本 + // buffer = await imageAddText(name, buffer); + } + let imagePath; + if (type == "role") { + insertType = "角色"; + imagePath = `/${projectId}/role/${uuidv4()}.jpg`; + } + if (type == "scene") { + insertType = "场景"; + imagePath = `/${projectId}/scene/${uuidv4()}.jpg`; + } + if (type == "props") { + insertType = "道具"; + imagePath = `/${projectId}/props/${uuidv4()}.jpg`; + } + if (type == "storyboard") { + insertType = "分镜"; + imagePath = `/${projectId}/storyboard/${uuidv4()}.jpg`; + } - if (type != "storyboard") { - //添加文本 - // buffer = await imageAddText(name, buffer); - } - let imagePath; - if (type == "role") { - insertType = "角色"; - imagePath = `/${projectId}/role/${uuidv4()}.jpg`; - } - if (type == "scene") { - insertType = "场景"; - imagePath = `/${projectId}/scene/${uuidv4()}.jpg`; - } - if (type == "props") { - insertType = "道具"; - imagePath = `/${projectId}/props/${uuidv4()}.jpg`; - } - if (type == "storyboard") { - insertType = "分镜"; - imagePath = `/${projectId}/storyboard/${uuidv4()}.jpg`; - } + await u.oss.writeFile(imagePath!, buffer); + const imageData = await u.db("t_image").where("id", imageId).select("*").first(); + if (imageData) { + await u.db("t_image").where("id", imageId).update({ + state: "生成成功", + filePath: imagePath, + type: insertType, + }); - await u.oss.writeFile(imagePath!, buffer); - const imageData = await u.db("t_image").where("id", imageId).select("*").first(); - if (imageData) { + const path = await u.oss.getFileUrl(imagePath!); + + // const state = await u.db("t_assets").where("id", id).select("state").first(); + + return res.status(200).send(success({ path, assetsId: id })); + } else { + return res.status(500).send("资产已被删除"); + } + } catch (e) { await u.db("t_image").where("id", imageId).update({ - state: "生成成功", - filePath: imagePath, - type: insertType, + state: "生成失败", }); - - const path = await u.oss.getFileUrl(imagePath!); - - // const state = await u.db("t_assets").where("id", id).select("state").first(); - - return res.status(200).send(success({ path, assetsId: id })); - } else { - return res.status(500).send("资产已被删除"); + const msg = u.error(e).message || "图片生成失败"; + return res.status(400).send(error(msg)); } }, ); diff --git a/src/routes/other/testVideo.ts b/src/routes/other/testVideo.ts index 745ee97..b51dbd3 100644 --- a/src/routes/other/testVideo.ts +++ b/src/routes/other/testVideo.ts @@ -27,6 +27,7 @@ export default router.post( resolution: "720p", aspectRatio: "16:9", audio: false, + mode: "single", }, { model: modelName, diff --git a/src/routes/setting/getAiModelList.ts b/src/routes/setting/getAiModelList.ts new file mode 100644 index 0000000..313d9dd --- /dev/null +++ b/src/routes/setting/getAiModelList.ts @@ -0,0 +1,33 @@ +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({ + type: z.enum(["text", "image", "video"]), + }), + async (req, res) => { + const { type } = req.body; + const sqlTableMap = { + text: "t_textModel", + image: "t_imageModel", + video: "t_videoModel", + }; + const modelLists = await u + .db(sqlTableMap[type as "image" | "text" | "video"]) + .whereNot("manufacturer", "other") + .select("id", "manufacturer", "model"); + const result: Record = {}; + for (const row of modelLists) { + if (!result[row.manufacturer]) { + result[row.manufacturer] = []; + } + result[row.manufacturer].push({ label: row.model, value: row.model }); + } + res.status(200).send(success(result)); + }, +); diff --git a/src/routes/setting/getVideoModelDetail.ts b/src/routes/setting/getVideoModelDetail.ts new file mode 100644 index 0000000..3905fba --- /dev/null +++ b/src/routes/setting/getVideoModelDetail.ts @@ -0,0 +1,31 @@ +import express from "express"; +import u from "@/utils"; +import { success } from "@/lib/responseFormat"; +const router = express.Router(); + +export default router.post("/", async (req, res) => { + const videoData = await u.db("t_videoModel").select("*"); + const allData = videoData.map((i) => { + const durationResolutionMap = JSON.parse(i.durationResolutionMap ?? "[]"); + const aspectRatio = JSON.parse(i.aspectRatio ?? "[]"); + const type = JSON.parse(i.type ?? "[]"); + return { + ...i, + durationResolutionMap, + aspectRatio, + type, + audio: i.audio === 1, + }; + }); + + const otherConfig = { + manufacturer: "other", + model: "", + durationResolutionMap: [{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["480p", "720p", "1080p"] }], + aspectRatio: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"], + type: ["text", "endFrameOptional", "singleImage", "multiImage"], + audio: true, + }; + const returnData = [otherConfig, ...allData]; + res.status(200).send(success(returnData)); +}); diff --git a/src/routes/video/generateVideo.ts b/src/routes/video/generateVideo.ts index e0b1b5b..1564441 100644 --- a/src/routes/video/generateVideo.ts +++ b/src/routes/video/generateVideo.ts @@ -119,7 +119,7 @@ export default router.post( res.status(200).send(success({ id: videoId, configId: configId || null })); // 异步生成视频 - generateVideoAsync(videoId, projectId, fileUrl, savePath, prompt, duration, resolution, audioEnabled, aiConfigData); + generateVideoAsync(videoId, projectId, fileUrl, savePath, prompt, duration, resolution, audioEnabled, aiConfigData, mode); }, ); @@ -134,6 +134,7 @@ async function generateVideoAsync( resolution: string, audioEnabled: boolean, aiConfigData: t_config, + mode: string, ) { try { const projectData = await u.db("t_project").where("id", projectId).select("artStyle", "videoRatio").first(); @@ -175,6 +176,7 @@ ${prompt} aspectRatio: projectData?.videoRatio as any, resolution: resolution as any, audio: audioEnabled, + mode: mode as any, }, { baseURL: aiConfigData?.baseUrl!, diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 78e03b1..25cd1ff 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,6 +1,20 @@ -// @db-hash 8ef9e37c14c453b2d95832b971baca8a +// @db-hash ab4e3e93bfba304164daa7d28c804eaf //该文件由脚本自动生成,请勿手动修改 +export interface _t_video_old_20260210 { + 'aiConfigId'?: number | null; + 'configId'?: number | null; + 'filePath'?: string | null; + 'firstFrame'?: string | null; + 'id'?: number; + 'model'?: string | null; + 'prompt'?: string | null; + 'resolution'?: string | null; + 'scriptId'?: number | null; + 'state'?: number | null; + 'storyboardImgs'?: string | null; + 'time'?: number | null; +} export interface t_aiModelMap { 'configId'?: number | null; 'id'?: number; @@ -39,7 +53,6 @@ export interface t_config { 'manufacturer'?: string | null; 'model'?: string | null; 'modelType'?: string | null; - 'name'?: string | null; 'type'?: string | null; 'userId'?: number | null; } @@ -53,6 +66,20 @@ export interface t_image { 'type'?: string | null; 'videoId'?: number | null; } +export interface t_imageConfig { + 'grid'?: number | null; + 'id'?: number; + 'manufacturer'?: string | null; + 'model'?: string | null; + 'type'?: string | null; +} +export interface t_imageModel { + 'grid'?: number | null; + 'id'?: number; + 'manufacturer'?: string | null; + 'model'?: string | null; + 'type'?: string | null; +} export interface t_novel { 'chapter'?: string | null; 'chapterData'?: string | null; @@ -118,6 +145,24 @@ export interface t_taskList { 'startTime'?: string | null; 'state'?: string | null; } +export interface t_textConfig { + 'id'?: number; + 'image'?: number | null; + 'manufacturer'?: string | null; + 'model'?: string | null; + 'responseFormat'?: string | null; + 'think'?: number | null; + 'tool'?: number | null; +} +export interface t_textModel { + 'id'?: number; + 'image'?: number | null; + 'manufacturer'?: string | null; + 'model'?: string | null; + 'responseFormat'?: string | null; + 'think'?: number | null; + 'tool'?: number | null; +} export interface t_user { 'id'?: number; 'name'?: string | null; @@ -139,6 +184,7 @@ export interface t_video { 'time'?: number | null; } export interface t_videoConfig { + 'aiConfigId'?: number | null; 'audioEnabled'?: number | null; 'createTime'?: number | null; 'duration'?: number | null; @@ -155,13 +201,25 @@ export interface t_videoConfig { 'startFrame'?: string | null; 'updateTime'?: number | null; } +export interface t_videoModel { + 'aspectRatio'?: string | null; + 'audio'?: number | null; + 'durationResolutionMap'?: string | null; + 'id'?: number; + 'manufacturer'?: string | null; + 'model'?: string | null; + 'type'?: string | null; +} export interface DB { + "_t_video_old_20260210": _t_video_old_20260210; "t_aiModelMap": t_aiModelMap; "t_assets": t_assets; "t_chatHistory": t_chatHistory; "t_config": t_config; "t_image": t_image; + "t_imageConfig": t_imageConfig; + "t_imageModel": t_imageModel; "t_novel": t_novel; "t_outline": t_outline; "t_project": t_project; @@ -170,7 +228,10 @@ export interface DB { "t_setting": t_setting; "t_storyline": t_storyline; "t_taskList": t_taskList; + "t_textConfig": t_textConfig; + "t_textModel": t_textModel; "t_user": t_user; "t_video": t_video; "t_videoConfig": t_videoConfig; + "t_videoModel": t_videoModel; } diff --git a/src/utils/ai/image/index.ts b/src/utils/ai/image/index.ts index 3054ebd..3826c08 100644 --- a/src/utils/ai/image/index.ts +++ b/src/utils/ai/image/index.ts @@ -10,6 +10,7 @@ import runninghub from "./owned/runninghub"; import apimart from "./owned/apimart"; import other from "./owned/other"; import gemini from "./owned/gemini"; +import modelScope from "./owned/modelScope"; const urlToBase64 = async (url: string): Promise => { const res = await axios.get(url, { responseType: "arraybuffer" }); @@ -25,20 +26,20 @@ const modelInstance = { vidu: vidu, runninghub: runninghub, // apimart: apimart, + modelScope, other, } as const; export default async (input: ImageConfig, config: AIConfig) => { - console.log("%c Line:32 🥪 config", "background:#33a5ff", config); 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("不支持的模型"); - } + // if (manufacturer !== "other") { + // const owned = modelList.find((m) => m.model === model); + // if (!owned) throw new Error("不支持的模型"); + // } // 补充图片的 base64 内容类型字符串 if (input.imageBase64 && input.imageBase64.length > 0) { @@ -65,7 +66,6 @@ export default async (input: ImageConfig, config: AIConfig) => { } let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL }); - console.log("%c Line:68 🍷 imageUrl", "background:#4fff4B", imageUrl); if (!input.resType) input.resType = "b64"; if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl); return imageUrl; diff --git a/src/utils/ai/image/modelList.ts b/src/utils/ai/image/modelList.ts index 1cce6d9..a324586 100644 --- a/src/utils/ai/image/modelList.ts +++ b/src/utils/ai/image/modelList.ts @@ -59,13 +59,6 @@ const modelList: Owned[] = [ grid: true, type: "ti2i", }, - //ApiMart - { - manufacturer: "apimart", - model: "nanobanana", - grid: true, - type: "ti2i", - }, ]; export default modelList; diff --git a/src/utils/ai/image/owned/modelScope.ts b/src/utils/ai/image/owned/modelScope.ts new file mode 100644 index 0000000..4a13dee --- /dev/null +++ b/src/utils/ai/image/owned/modelScope.ts @@ -0,0 +1,133 @@ +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"; +function getApiUrl(apiUrl: string) { + if (apiUrl.includes("|")) { + const parts = apiUrl.split("|"); + if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) { + throw new Error("url 格式错误,请使用 url1|url2 格式"); + } + return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() }; + } + throw new Error("请填写正确的url"); +} +function template(replaceObj: Record, url: string) { + return url.replace(/\{(\w+)\}/g, (match, varName) => { + return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match; + }); +} +async function compressionPrompt(prompt: string) { + const apiConfigData = await u.getPromptAi("assetsPrompt"); + + const result = await u.ai.text.invoke( + { + messages: [ + { + role: "system", + content: ` +你是一名资深Prompt工程师和文本摘要专家。你的任务是将用户输入的提示词文本内容压缩至2000字以内。请按照如下要求操作: +1. 准确梳理并提炼输入文本的主要内容、核心要点和关键信息。 +2. 剔除冗余、重复、无关或细枝末节的描述,压缩内容至2000字以内。 +3. 在压缩过程中,严格保持中立,避免被用户输入的风格、情绪或暗示性表述影响,始终按照“精准摘要到2000字”的目标执行,不被内容带偏。 +4. 输出为浓缩摘要文本,语言精炼、结构清晰。 +5. 请适配各类文本场景,无论是叙述性、说明性、议论性还是其他类型的文本,都要确保压缩后的内容完整传达原文的核心信息和主要观点。 + +直接输出压缩后的文本,不要任何额外的说明或引导语。请立即开始压缩,并确保输出内容不超过2000字。 + `, + }, + { + role: "user", + content: prompt, + }, + ], + }, + apiConfigData, + ); + return result.text; +} +export default async (input: ImageConfig, config: AIConfig): Promise => { + if (!config.model) throw new Error("缺少Model名称"); + if (!config.apiKey) throw new Error("缺少API Key"); + + const defaultBaseURL = "https://api-inference.modelscope.cn/v1/images/generations|https://api-inference.modelscope.cn/v1/tasks/{id}"; + const { requestUrl, queryUrl } = getApiUrl(config.baseURL! ?? defaultBaseURL); + // 根据 size 配置映射到具体尺寸 + const sizeMap: Record> = { + "1K": { + "16:9": "1664x928", + "9:16": "928x1664", + }, + "2K": { + "16:9": "2048x1152", + "9:16": "1152x2048", + }, + "4K": { + "16:9": "2048x1152", + "9:16": "1152x2048", + }, + }; + // 构建完整的提示词 + const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt; + + let newPrompt = fullPrompt; + if (fullPrompt.length > 2000) { + let compressed = await compressionPrompt(fullPrompt); + + newPrompt = compressed; + } + let mergedImage = input.imageBase64; + if (mergedImage && mergedImage.length) { + const smallImage = await u.imageTools.mergeImages(mergedImage, "5mb"); + mergedImage = [smallImage]; + } + + const size = sizeMap[input.size]?.[input.aspectRatio] ?? "1024x1024"; + + const taskBody: Record = { + model: config.model, + prompt: newPrompt, + negative_prompt: "", + size, + ...(mergedImage && mergedImage.length ? { image_url: mergedImage } : {}), + }; + + const apiKey = config.apiKey.replace("Bearer ", ""); + try { + const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}`, "X-ModelScope-Async-Mode": "true" } }); + + if (data.task_status != "SUCCEED") throw new Error(`任务提交失败: ${data || "未知错误"}`); + const taskId = data.task_id; + + return await pollTask(async () => { + const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), { + headers: { Authorization: `Bearer ${apiKey}`, "X-ModelScope-Task-Type": "image_generation" }, + }); + + const { task_status, output_images } = queryData || {}; + + if (task_status === "FAILED") { + return { completed: false, error: "图片生成失败" }; + } + + if (task_status === "SUCCEED") { + return { completed: true, url: output_images?.[0] }; + } + + return { completed: false }; + }); + } catch (error: any) { + console.error("%c Line:90 🥪 error", "background:#93c0a4", error.response?.data?.errors?.message); + const msg = u.error(error).message || "图片生成失败"; + throw new Error(msg); + } +}; + +async function urlToBase64(url: string): Promise { + 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}`; +} diff --git a/src/utils/ai/text/index.ts b/src/utils/ai/text/index.ts index bfc45d7..a2dfdf5 100644 --- a/src/utils/ai/text/index.ts +++ b/src/utils/ai/text/index.ts @@ -3,7 +3,7 @@ import { generateText, streamText, Output, stepCountIs, ModelMessage, LanguageMo import { wrapLanguageModel } from "ai"; import { devToolsMiddleware } from "@ai-sdk/devtools"; import { parse } from "best-effort-json-parser"; -import modelList from "./modelList"; +import { getModelList } from "./modelList"; import { z } from "zod"; import { OpenAIProvider } from "@ai-sdk/openai"; interface AIInput | undefined = undefined> { @@ -26,12 +26,15 @@ const buildOptions = async (input: AIInput, config: AIConfig = {}) => { if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确"); const { model, apiKey, baseURL, manufacturer } = { ...config }; let owned; + const modelList = await getModelList(); if (manufacturer == "other") { owned = modelList.find((m) => m.manufacturer === manufacturer); } else { owned = modelList.find((m) => m.model === model); + if (!owned) owned = modelList.find((m) => m.manufacturer === manufacturer); } - if (!owned) throw new Error("不支持的模型或厂商"); + if (!owned) throw new Error("不支持的厂商"); + console.log("%c Line:36 🥛 owned", "background:#6ec1c2", owned); const modelInstance = owned.instance({ apiKey, baseURL: baseURL!, name: "xixixi" }); @@ -52,7 +55,7 @@ const buildOptions = async (input: AIInput, config: AIConfig = {}) => { }; const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null; - const chatModelManufacturer = ["doubao", "other", "openai"]; + const chatModelManufacturer = ["volcengine", "other", "openai"]; const modelFn = chatModelManufacturer.includes(owned.manufacturer) ? (modelInstance as OpenAIProvider).chat(model!) : modelInstance(model!); return { config: { @@ -76,6 +79,7 @@ const ai = Object.create({}) as { ai.invoke = async (input: AIInput, config: AIConfig) => { const options = await buildOptions(input, config); + console.log("%c Line:81 🍧 options", "background:#93c0a4", options); const result = await generateText(options.config); if (options.responseFormat === "object" && input.output) { const pattern = /{[^{}]*}|{(?:[^{}]*|{[^{}]*})*}/g; @@ -92,6 +96,7 @@ ai.invoke = async (input: AIInput, config: AIConfig) => { ai.stream = async (input: AIInput, config: AIConfig) => { const options = await buildOptions(input, config); + console.log("%c Line:98 🍬 options", "background:#fca650", options); return streamText(options.config); }; diff --git a/src/utils/ai/text/modelList.ts b/src/utils/ai/text/modelList.ts index 421885a..ea1789c 100644 --- a/src/utils/ai/text/modelList.ts +++ b/src/utils/ai/text/modelList.ts @@ -1,11 +1,12 @@ -import { createOpenAI } from "@ai-sdk/openai"; +import { createOpenAI, OpenAIProviderSettings } from "@ai-sdk/openai"; import { createDeepSeek } from "@ai-sdk/deepseek"; import { createZhipu } from "zhipu-ai-provider"; import { createQwen } from "qwen-ai-provider"; import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createAnthropic } from "@ai-sdk/anthropic"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; -import { createXai } from '@ai-sdk/xai'; +import { createXai } from "@ai-sdk/xai"; +import db from "@/utils/db"; interface Owned { manufacturer: string; @@ -23,11 +24,24 @@ interface Owned { | typeof createAnthropic | typeof createOpenAICompatible; } +const instanceMap = { + deepSeek: createDeepSeek, + volcengine: createOpenAI, + openai: createOpenAI, + zhipu: createZhipu, + qwen: createQwen, + gemini: createGoogleGenerativeAI, + + anthropic: createAnthropic, + modelScope: (options: OpenAIProviderSettings) => createOpenAI({ ...options, headers: { ...options?.headers, "X-ModelScope-Async-Mode": "true" } }), + xai: createXai, + other: createOpenAI, +}; const modelList: Owned[] = [ // DeepSeek { - manufacturer: "deepseek", + manufacturer: "deepSeek", model: "deepseek-chat", responseFormat: "schema", image: false, @@ -36,7 +50,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "deepseek", + manufacturer: "deepSeek", model: "deepseek-reasoner", responseFormat: "schema", image: false, @@ -47,7 +61,16 @@ const modelList: Owned[] = [ // 豆包 { - manufacturer: "doubao", + manufacturer: "volcengine", + model: "doubao-seed-2-0-mini-260215", + responseFormat: "object", + image: true, + think: false, + instance: createOpenAI, + tool: true, + }, + { + manufacturer: "volcengine", model: "doubao-seed-1-8-251228", responseFormat: "schema", image: true, @@ -56,7 +79,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "doubao", + manufacturer: "volcengine", model: "doubao-seed-1-6-251015", responseFormat: "schema", image: true, @@ -65,7 +88,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "doubao", + manufacturer: "volcengine", model: "doubao-seed-1-6-lite-251015", responseFormat: "schema", image: true, @@ -74,7 +97,7 @@ const modelList: Owned[] = [ tool: true, }, { - manufacturer: "doubao", + manufacturer: "volcengine", model: "doubao-seed-1-6-flash-250828", responseFormat: "schema", image: true, @@ -413,7 +436,7 @@ const modelList: Owned[] = [ tool: true, }, //xai - { + { manufacturer: "xai", model: "grok-3", responseFormat: "schema", @@ -422,7 +445,7 @@ const modelList: Owned[] = [ instance: createXai, tool: true, }, - { + { manufacturer: "xai", model: "grok-4", responseFormat: "schema", @@ -431,7 +454,7 @@ const modelList: Owned[] = [ instance: createXai, tool: true, }, - { + { manufacturer: "xai", model: "grok-4.1", responseFormat: "schema", @@ -444,12 +467,24 @@ const modelList: Owned[] = [ { manufacturer: "other", model: "gpt-4.1", - responseFormat: "schema", + responseFormat: "object", image: true, think: false, instance: createOpenAI, tool: true, }, ]; - +export const getModelList = async () => { + const modelLists = await db("t_textModel").select("*"); + const resultInstaceList = modelLists.map((model) => { + return { + ...model, + tool: model.tool == 1 ? true : false, + think: model.think == 1 ? true : false, + image: model.image == 1 ? true : false, + instance: instanceMap[model.manufacturer as keyof typeof instanceMap], + }; + }); + return resultInstaceList as Owned[]; +}; export default modelList; diff --git a/src/utils/ai/utils.ts b/src/utils/ai/utils.ts index d03aa2e..e38da30 100644 --- a/src/utils/ai/utils.ts +++ b/src/utils/ai/utils.ts @@ -1,5 +1,5 @@ import modelList from "./video/modelList"; - +import { db } from "../db"; interface ValidateResult { owned: (typeof modelList)[number]; images: string[]; @@ -80,3 +80,5 @@ export const pollTask = async ( } throw new Error(`任务轮询超时,已尝试 ${maxAttempts} 次`); }; + + diff --git a/src/utils/ai/video/index.ts b/src/utils/ai/video/index.ts index ad39c9a..1524b07 100644 --- a/src/utils/ai/video/index.ts +++ b/src/utils/ai/video/index.ts @@ -29,8 +29,8 @@ export default async (input: VideoConfig, config?: AIConfig) => { const manufacturerFn = modelInstance[manufacturer as keyof typeof modelInstance]; if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的视频厂商"); - const owned = modelList.find((m) => m.model === model); - if (!owned) throw new Error("不支持的模型"); + // const owned = modelList.find((m) => m.model === model); + // if (!owned) throw new Error("不支持的模型"); // 补充图片的 base64 内容类型字符串 if (input.imageBase64 && input.imageBase64.length > 0) { diff --git a/src/utils/ai/video/owned/gemini.ts b/src/utils/ai/video/owned/gemini.ts index ab01e03..7c5beea 100644 --- a/src/utils/ai/video/owned/gemini.ts +++ b/src/utils/ai/video/owned/gemini.ts @@ -10,7 +10,7 @@ export default async (input: VideoConfig, config: AIConfig) => { if (!config.model) throw new Error("缺少Model名称"); if (!config.apiKey) throw new Error("缺少API Key"); - const { owned, images, hasStartEndType } = validateVideoConfig(input, config); + // const { owned, images, hasStartEndType } = validateVideoConfig(input, config); const defaultBaseUrl = [ "https://generativelanguage.googleapis.com/v1beta/models/{model}:predictLongRunning", @@ -18,8 +18,7 @@ export default async (input: VideoConfig, config: AIConfig) => { ].join("|"); const [submitUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); - - + const headers = { "x-goog-api-key": config.apiKey }; const instance: Record = { prompt: input.prompt }; @@ -30,17 +29,17 @@ export default async (input: VideoConfig, config: AIConfig) => { }; // 根据图片数量和模型能力决定图片用法 - const len = images.length; - const hasRef = owned.type.includes("reference"); - const hasSingle = owned.type.includes("singleImage"); - + const len = input.imageBase64 ? input.imageBase64.length : 0; + const hasRef = input.mode == "multi"; + const hasSingle = input.mode == "single"; + const hasStartEndType = input.mode === "startEnd"; if (len === 2 && hasStartEndType) { - instance.image = buildInlineImage(images[0]); - parameters.lastFrame = buildInlineImage(images[1]); + instance.image = buildInlineImage(input.imageBase64![0]); + parameters.lastFrame = buildInlineImage(input.imageBase64![1]); } else if (len === 1 && (hasSingle || hasStartEndType)) { - instance.image = buildInlineImage(images[0]); + instance.image = buildInlineImage(input.imageBase64![0]); } else if (len >= 1 && len <= 3 && hasRef) { - parameters.referenceImages = images.map((img) => ({ image: buildInlineImage(img), referenceType: "asset" })); + parameters.referenceImages = input.imageBase64!.map((img) => ({ image: buildInlineImage(img), referenceType: "asset" })); } const { data } = await axios.post( @@ -53,15 +52,14 @@ export default async (input: VideoConfig, config: AIConfig) => { return pollTask(async () => { const { data: status } = await axios.get(queryUrl.replace("{name}", data.name), { headers }); - + const { done, response, error } = status; - if (!done) return { completed: false }; if (error) return { completed: false, error: `任务失败: ${error.message || JSON.stringify(error)}` }; const videoUri = response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri; - + if (!videoUri) return { completed: false, error: "未获取到视频下载地址" }; const videoRes = await axios.get(videoUri, { headers, responseType: "arraybuffer", maxRedirects: 5 }); diff --git a/src/utils/ai/video/owned/kling.ts b/src/utils/ai/video/owned/kling.ts index bb3c539..ce766a7 100644 --- a/src/utils/ai/video/owned/kling.ts +++ b/src/utils/ai/video/owned/kling.ts @@ -6,7 +6,7 @@ export default async (input: VideoConfig, config: AIConfig) => { if (!config.apiKey) throw new Error("缺少API Key"); if (!config.baseURL) throw new Error("缺少baseURL配置"); - const { images } = validateVideoConfig(input, config); + // const { images } = validateVideoConfig(input, config); // 解析URL配置:图生视频|文生视频|查询地址 const defaultBaseUrl = @@ -24,7 +24,7 @@ export default async (input: VideoConfig, config: AIConfig) => { const mode = modelMatch ? (modelMatch[2].toLowerCase() as "std" | "pro") : "std"; // 判断是图生视频还是文生视频 - const hasImage = images.length > 0; + const hasImage = input.imageBase64 ? input.imageBase64.length > 0 : false; const createUrl = hasImage ? image2videoUrl : text2videoUrl; // 去除图片的内容类型前缀(kling要求纯base64) @@ -41,9 +41,9 @@ export default async (input: VideoConfig, config: AIConfig) => { if (hasImage) { // 图生视频:首帧和尾帧 - body.image = stripDataUrl(images[0]); - if (images.length > 1) { - body.image_tail = stripDataUrl(images[1]); + body.image = stripDataUrl(input.imageBase64![0]); + if (input.imageBase64!.length > 1) { + body.image_tail = stripDataUrl(input.imageBase64![1]); } } diff --git a/src/utils/ai/video/owned/runninghub.ts b/src/utils/ai/video/owned/runninghub.ts index 1a4d6c2..cbec950 100644 --- a/src/utils/ai/video/owned/runninghub.ts +++ b/src/utils/ai/video/owned/runninghub.ts @@ -7,7 +7,7 @@ import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; export default async (input: VideoConfig, config: AIConfig) => { if (!config.apiKey) throw new Error("缺少API Key"); - const { owned, images, hasTextType } = validateVideoConfig(input, config); + // const { owned, images, hasTextType } = validateVideoConfig(input, config); const defaultBaseUrl = [ "https://www.runninghub.cn/openapi/v2/rhart-video-s/image-to-video", @@ -19,8 +19,8 @@ export default async (input: VideoConfig, config: AIConfig) => { ].join("|"); const [image2videoUrl, image2videoProUrl, text2videoUrl, text2videoProUrl, queryUrl, uploadUrl] = (config.baseURL || defaultBaseUrl).split("|"); - - const isPro = owned.model === "sora-2-pro"; + const hasTextType = input.mode == "text"; + const isPro = config.model === "sora-2-pro"; const authorization = `Bearer ${config.apiKey}`; // 上传 base64 图片 @@ -65,20 +65,19 @@ export default async (input: VideoConfig, config: AIConfig) => { return { taskId: data.taskId, status: data.status, url: data.results?.[0]?.url }; }; - const isTextToVideo = images.length === 0 && hasTextType; + const isTextToVideo = (!input.imageBase64 || input.imageBase64.length === 0) && hasTextType; const submitUrl = isTextToVideo ? (isPro ? text2videoProUrl : text2videoUrl) : isPro ? image2videoProUrl : image2videoUrl; const requestBody: Record = { prompt: input.prompt, duration: String(input.duration), aspectRatio: input.aspectRatio, - ...(isTextToVideo ? {} : { imageUrl: await uploadImage(images[0]) }), + ...(isTextToVideo ? {} : { imageUrl: await uploadImage(input.imageBase64![0]) }), }; const { taskId } = await submitTask(submitUrl, requestBody); return await pollTask(async () => { - const { data } = await axios.post( queryUrl, { diff --git a/src/utils/ai/video/owned/vidu.ts b/src/utils/ai/video/owned/vidu.ts index 86cac52..368a7a1 100644 --- a/src/utils/ai/video/owned/vidu.ts +++ b/src/utils/ai/video/owned/vidu.ts @@ -20,30 +20,30 @@ export default async (input: VideoConfig, config: AIConfig) => { const hasImages = input.imageBase64 && input.imageBase64.length > 0; // 根据是否有图片,查找匹配的模型配置 - const customOwned = modelList.find((m) => { - if (m.manufacturer !== "vidu") return false; - if (m.model !== config.model) return false; - if (hasImages) { - return m.type.some((t) => t !== "text"); - } else { - return m.type.includes("text"); - } - }); + // const customOwned = modelList.find((m) => { + // if (m.manufacturer !== "vidu") return false; + // if (m.model !== config.model) return false; + // if (hasImages) { + // return m.type.some((t) => t !== "text"); + // } else { + // return m.type.includes("text"); + // } + // }); - if (!customOwned) { - throw new Error(`未找到匹配的模型配置: ${config.model}`); - } + // if (!customOwned) { + // throw new Error(`未找到匹配的模型配置: ${config.model}`); + // } // 使用统一校验函数 - const { owned, images } = validateVideoConfig(input, config, customOwned); + // const { owned, images } = validateVideoConfig(input, config, customOwned); // 判断生成类型 - const genType: "text" | "image" = images.length === 0 ? "text" : "image"; + const genType: "text" | "image" = input.imageBase64 && input.imageBase64.length === 0 ? "text" : "image"; - // 校验宽高比(仅文生视频需要) - if (genType === "text" && owned.aspectRatio.length > 0 && !owned.aspectRatio.includes(input.aspectRatio as `${number}:${number}`)) { - throw new Error(`模型 ${owned.model} 不支持宽高比 ${input.aspectRatio},支持的宽高比:${owned.aspectRatio.join("、")}`); - } + // // 校验宽高比(仅文生视频需要) + // if (genType === "text" && owned.aspectRatio.length > 0 && !owned.aspectRatio.includes(input.aspectRatio as `${number}:${number}`)) { + // throw new Error(`模型 ${owned.model} 不支持宽高比 ${input.aspectRatio},支持的宽高比:${owned.aspectRatio.join("、")}`); + // } // 创建任务 let taskId: string; @@ -51,13 +51,13 @@ export default async (input: VideoConfig, config: AIConfig) => { if (genType === "text") { // 文生视频 const requestBody: Record = { - model: owned.model, + model: config.model, prompt: input.prompt, duration: input.duration, resolution: input.resolution, aspect_ratio: input.aspectRatio, }; - if (owned.audio && input.audio !== undefined) { + if (input.audio && input.audio !== undefined) { requestBody.audio = input.audio; } @@ -71,15 +71,15 @@ export default async (input: VideoConfig, config: AIConfig) => { } else { // 图生视频 const requestBody: Record = { - model: owned.model, - images: images, + model: config.model, + images: input.imageBase64, duration: input.duration, resolution: input.resolution, }; if (input.prompt) { requestBody.prompt = input.prompt; } - if (owned.audio && input.audio !== undefined) { + if (input.audio && input.audio !== undefined) { requestBody.audio = input.audio; } diff --git a/src/utils/ai/video/owned/volcengine.ts b/src/utils/ai/video/owned/volcengine.ts index 328e031..b84fd44 100644 --- a/src/utils/ai/video/owned/volcengine.ts +++ b/src/utils/ai/video/owned/volcengine.ts @@ -5,11 +5,11 @@ import { pollTask, validateVideoConfig } from "@/utils/ai/utils"; export default async (input: VideoConfig, config: AIConfig) => { if (!config.apiKey) throw new Error("缺少API Key"); - const { owned, images, hasStartEndType } = validateVideoConfig(input, config); - + // const { owned, images, hasStartEndType } = validateVideoConfig(input, config); + const hasStartEndType = input.mode === "startEnd"; const authorization = "Bearer " + config.apiKey.replace(/^Bearer\s*/i, "").trim(); const baseUrl = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks"; - + const images = input.imageBase64 || []; // 判断是否为首尾帧模式(需要两张图且类型支持首尾帧) const isStartEndMode = images.length === 2 && hasStartEndType; @@ -35,7 +35,7 @@ export default async (input: VideoConfig, config: AIConfig) => { }; // 仅当模型支持音频时才添加 generate_audio 字段 - if (owned.audio) { + if (input?.audio) { requestBody.generate_audio = input.audio ?? false; } // 创建视频生成任务 diff --git a/src/utils/ai/video/owned/wan.ts b/src/utils/ai/video/owned/wan.ts index e319ad6..a64d00d 100644 --- a/src/utils/ai/video/owned/wan.ts +++ b/src/utils/ai/video/owned/wan.ts @@ -39,8 +39,10 @@ const getSizeFromConfig = (resolution: string, aspectRatio: string): string => { export default async (input: VideoConfig, config: AIConfig) => { if (!config.apiKey) throw new Error("缺少API Key"); - const { owned, images, hasStartEndType, hasTextType } = validateVideoConfig(input, config); - + // const { owned, images, hasStartEndType, hasTextType } = validateVideoConfig(input, config); + const hasStartEndType = input.mode === "startEnd"; + const hasTextType = input.mode === "text"; + const images = input.imageBase64 || []; const defaultBaseUrl = [ "https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis", @@ -49,7 +51,7 @@ export default async (input: VideoConfig, config: AIConfig) => { const [i2vUrl, kf2vUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|"); - const types = owned.type; + // const types = owned.type; const authorization = `Bearer ${config.apiKey}`; // 确定端点和构建请求体 @@ -69,7 +71,7 @@ export default async (input: VideoConfig, config: AIConfig) => { duration: input.duration, }, }; - } else if (types.includes("singleImage")) { + } else if (input.mode == 'single' && images.length === 1) { // 图生视频 submitUrl = i2vUrl; body = { @@ -84,7 +86,7 @@ export default async (input: VideoConfig, config: AIConfig) => { }, }; // audio参数仅部分模型支持 - if (owned.audio && input.audio !== undefined) { + if (input.audio && input.audio !== undefined) { body.parameters.audio = input.audio; } } else if (hasStartEndType) { @@ -95,9 +97,7 @@ export default async (input: VideoConfig, config: AIConfig) => { first_frame_url: images[0], }; // 尾帧处理 - if (types.includes("startEndRequired")) { - inputObj.last_frame_url = images[1]; - } else if ((types.includes("endFrameOptional") || types.includes("startFrameOptional")) && images.length >= 2) { + if (hasStartEndType && images.length >= 2) { inputObj.last_frame_url = images[1]; } body = { @@ -109,7 +109,7 @@ export default async (input: VideoConfig, config: AIConfig) => { }, }; } else { - throw new Error(`不支持的视频生成类型: ${types.join(", ")}`); + throw new Error(`不支持的视频生成类型: ${hasStartEndType ? "startEnd" : hasTextType ? "text" : "single"}`); } // 提交任务 diff --git a/src/utils/ai/video/type.ts b/src/utils/ai/video/type.ts index 4c38699..499681a 100644 --- a/src/utils/ai/video/type.ts +++ b/src/utils/ai/video/type.ts @@ -6,6 +6,7 @@ interface VideoConfig { savePath: string; imageBase64?: string[]; audio?: boolean; + mode: "startEnd" | "multi" | "single" | "text"; } interface AIConfig {