优化模型接入,解除模型写死限制

This commit is contained in:
zhishi 2026-02-27 18:02:22 +08:00
parent 3dba471500
commit 869bb5e998
22 changed files with 1064 additions and 208 deletions

View File

@ -554,6 +554,557 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
]);
},
},
{
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) {

View File

@ -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);
}

View File

@ -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));
}
},
);

View File

@ -27,6 +27,7 @@ export default router.post(
resolution: "720p",
aspectRatio: "16:9",
audio: false,
mode: "single",
},
{
model: modelName,

View File

@ -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<string, any[]> = {};
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));
},
);

View File

@ -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));
});

View File

@ -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!,

View File

@ -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;
}

View File

@ -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<string> => {
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;

View File

@ -59,13 +59,6 @@ const modelList: Owned[] = [
grid: true,
type: "ti2i",
},
//ApiMart
{
manufacturer: "apimart",
model: "nanobanana",
grid: true,
type: "ti2i",
},
];
export default modelList;

View File

@ -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<string, any>, 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<string> => {
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<string, Record<string, string>> = {
"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<string, any> = {
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<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}`;
}

View File

@ -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<T extends Record<string, z.ZodTypeAny> | undefined = undefined> {
@ -26,12 +26,15 @@ const buildOptions = async (input: AIInput<any>, 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<any>, 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<any>, 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<any>, 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);
};

View File

@ -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;

View File

@ -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}`);
};

View File

@ -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) {

View File

@ -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<string, any> = { 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 });

View File

@ -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]);
}
}

View File

@ -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<string, unknown> = {
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,
{

View File

@ -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<string, unknown> = {
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<string, unknown> = {
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;
}

View File

@ -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;
}
// 创建视频生成任务

View File

@ -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"}`);
}
// 提交任务

View File

@ -6,6 +6,7 @@ interface VideoConfig {
savePath: string;
imageBase64?: string[];
audio?: boolean;
mode: "startEnd" | "multi" | "single" | "text";
}
interface AIConfig {