diff --git a/skillList.json b/skillList.json deleted file mode 100644 index 99be541..0000000 --- a/skillList.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "skillId": "52c51fa8655f899a1b7aae9b6aad7251", - "attribution": "universal_agent.md" - }, - { - "skillId": "6d46cdca10b2f49e07e515885d1387a0", - "attribution": "universal_agent.md" - }, - { - "skillId": "1864df75d1d65f76e275046649ecaef8", - "attribution": "universal_agent.md" - }, - { - "skillId": "3e5efec258c8d8e6a39bcef12f8ee058", - "attribution": "universal_agent.md" - }, - { - "skillId": "7fbce6f90d7d85496ba9817e9622e640", - "attribution": "universal_agent.md" - }, - { - "skillId": "31fb5c5a1f514ec1e66b4eba9f22d4db", - "attribution": "script_agent_decision.md" - }, - { - "skillId": "27dc2dfc901de2180227d0269217583a", - "attribution": "script_agent_execution.md" - }, - { - "skillId": "d49fa09504fe784a8e6eb102756c6d56", - "attribution": "script_agent_execution.md" - }, - { - "skillId": "797906c2ddf0750f050bcdeae23eae3d", - "attribution": "script_agent_execution.md" - }, - { - "skillId": "1abd8675c0c3e62b20c0b151d2ec0fb1", - "attribution": "script_agent_execution.md" - }, - { - "skillId": "0b7828d7a6ab458a4b201122f08d6c16", - "attribution": "script_agent_supervision.md" - }, - { - "skillId": "5c1772b5f9c420d9eae9ca02914ba087", - "attribution": "production_agent_decision.md" - }, - { - "skillId": "75a45cf996015ca819582873887ec301", - "attribution": "production_agent_execution.md" - }, - { - "skillId": "fce75f69d704c19bebcb356bc1bd6e81", - "attribution": "production_agent_execution.md" - } -] \ No newline at end of file diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 3a96320..1d87253 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -425,6 +425,103 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => table.primary(["id"]); table.unique(["id"]); }, + initData: async (knex) => { + await knex("o_vendorConfig").insert([ + { + id: "toonflow", + author: "Toonflow", + description: + "## Toonflow官方中转平台\n\nToonflow官方中转平台,提供**文本、图像、视频、音频**等多模态生成能力的中转服务,支持接入多个大模型供应商,方便用户统一管理和调用不同供应商的生成能力。\n\n🔗 [前往中转平台](https://api.toonflow.net/)\n\n如果这个项目对你有帮助,可以考虑支持一下我们的开发工作 ☕", + name: "Toonflow官方中转平台", + icon: "", + inputs: '[{"key":"apiKey","label":"API密钥","type":"password","required":true}]', + inputValues: '{"apiKey":"","baseUrl":"https://api.toonflow.net/v1"}', + models: + '[{"name":"claude-sonnet-4-6","type":"text","modelName":"claude-sonnet-4-6","think":false},{"name":"claude-opus-4-6","type":"text","modelName":"claude-opus-4-6","think":false},{"name":"claude-sonnet-4-5-20250929","type":"text","modelName":"claude-sonnet-4-5-20250929","think":false},{"name":"claude-opus-4-5-20251101","type":"text","modelName":"claude-opus-4-5-20251101","think":false},{"name":"claude-haiku-4-5-20251001","type":"text","modelName":"claude-haiku-4-5-20251001","think":false},{"name":"gpt-5.4","type":"text","modelName":"gpt-5.4","think":false},{"name":"gpt-5.2","type":"text","modelName":"gpt-5.2","think":false},{"name":"MiniMax-M2.7","type":"text","modelName":"MiniMax-M2.7","think":true},{"name":"MiniMax-M2.5","type":"text","modelName":"MiniMax-M2.5","think":true},{"name":"Wan2.6 I2V 1080P (支持真人)","type":"video","modelName":"Wan2.6-I2V-1080P","mode":["text","startEndRequired"],"durationResolutionMap":[{"duration":[2,3,4,5,6,7,8,9,10,11,12,13,14,15],"resolution":["1080p"]}],"audio":true},{"name":"Wan2.6 I2V 720P (支持真人)","type":"video","modelName":"Wan2.6-I2V-720P","mode":["text","startEndRequired"],"durationResolutionMap":[{"duration":[2,3,4,5,6,7,8,9,10,11,12,13,14,15],"resolution":["720p"]}],"audio":true},{"name":"Seedance 1.5 Pro","type":"video","modelName":"doubao-seedance-1-5-pro-251215","durationResolutionMap":[{"duration":[4,5,6,7,8,9,10,11,12],"resolution":["480p","720p","1080p"]}],"mode":["text","endFrameOptional"],"audio":true},{"name":"vidu2 turbo","type":"video","modelName":"ViduQ2-turbo","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":false},{"name":"ViduQ3 pro","type":"video","modelName":"ViduQ3-pro","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":false},{"name":"ViduQ2 pro","type":"video","modelName":"ViduQ2-pro","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":false},{"name":"Doubao Seedream 5.0 Lite","type":"image","modelName":"Doubao-Seedream-5.0-Lite","mode":["text","singleImage","multiReference"]},{"name":"Doubao Seedream 4.5","type":"image","modelName":"doubao-seedream-4-5-251128","mode":["text","singleImage","multiReference"]}]', + code: '//如需遥测AI请使用在toonflow安装目录运行npx @ai-sdk/devtools (要求在其他设置中打开遥测功能,且toonflow有权限在安装目录创建.devtools文件夹)\r\n// ==================== 类型定义 ====================\r\n// 文本模型\r\ninterface TextModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "text";\r\n think: boolean; // 前端显示用\r\n}\r\n\r\n// 图像模型\r\ninterface ImageModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "image";\r\n mode: ("text" | "singleImage" | "multiReference")[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n}\r\n// 视频模型\r\ninterface VideoModel {\r\n name: string; // 显示名称\r\n modelName: string; //全局唯一\r\n type: "video";\r\n mode: (\r\n | "singleImage" // 单图\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[] // 混合参考\r\n )[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n audio: "optional" | false | true; // 音频配置\r\n durationResolutionMap: { duration: number[]; resolution: string[] }[];\r\n}\r\n\r\ninterface TTSModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "tts";\r\n voices: {\r\n title: string; //显示名称\r\n voice: string; //说话人\r\n }[];\r\n}\r\n// 供应商配置\r\ninterface VendorConfig {\r\n id: string; //供应商唯一标识,必须全局唯一\r\n author: string;\r\n description?: string; //md5格式\r\n name: string;\r\n icon?: string; //仅支持base64格式\r\n inputs: {\r\n key: string;\r\n label: string;\r\n type: "text" | "password" | "url";\r\n required: boolean;\r\n placeholder?: string;\r\n }[];\r\n inputValues: Record;\r\n models: (TextModel | ImageModel | VideoModel)[];\r\n}\r\n// ==================== 全局工具函数 ====================\r\n//Axios实例\r\n//压缩图片大小(1MB = 1 * 1024 * 1024)\r\ndeclare const zipImage: (completeBase64: string, size: number) => Promise;\r\n//压缩图片分辨率\r\ndeclare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise;\r\n//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb\r\ndeclare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise;\r\n//Url转Base64\r\ndeclare const urlToBase64: (url: string) => Promise;\r\n//轮询函数\r\ndeclare const pollTask: (\r\n fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,\r\n interval?: number,\r\n timeout?: number,\r\n) => Promise<{ completed: boolean; data?: string; error?: string }>;\r\ndeclare const axios: any;\r\ndeclare const createOpenAI: any;\r\ndeclare const createDeepSeek: any;\r\ndeclare const createZhipu: any;\r\ndeclare const createQwen: any;\r\ndeclare const createAnthropic: any;\r\ndeclare const createOpenAICompatible: any;\r\ndeclare const createXai: any;\r\ndeclare const createMinimax: any;\r\ndeclare const createGoogleGenerativeAI: any;\r\ndeclare const logger: (logstring: string) => void;\r\ndeclare const jsonwebtoken: any;\r\n\r\n// ==================== 供应商数据 ====================\r\nconst vendor: VendorConfig = {\r\n id: "toonflow",\r\n author: "Toonflow",\r\n description:\r\n "## Toonflow官方中转平台\\n\\nToonflow官方中转平台,提供**文本、图像、视频、音频**等多模态生成能力的中转服务,支持接入多个大模型供应商,方便用户统一管理和调用不同供应商的生成能力。\\n\\n🔗 [前往中转平台](https://api.toonflow.net/)\\n\\n如果这个项目对你有帮助,可以考虑支持一下我们的开发工作 ☕",\r\n name: "Toonflow官方中转平台",\r\n icon: "",\r\n inputs: [\r\n { key: "apiKey", label: "API密钥", type: "password", required: true },\r\n ],\r\n inputValues: {\r\n apiKey: "",\r\n baseUrl: "https://api.toonflow.net/v1"\r\n },\r\n models: [\r\n {\r\n name: "claude-sonnet-4-6",\r\n type: "text",\r\n modelName: "claude-sonnet-4-6",\r\n think: false,\r\n },\r\n {\r\n name: "claude-opus-4-6",\r\n type: "text",\r\n modelName: "claude-opus-4-6",\r\n think: false,\r\n },\r\n {\r\n name: "claude-sonnet-4-5-20250929",\r\n type: "text",\r\n modelName: "claude-sonnet-4-5-20250929",\r\n think: false,\r\n },\r\n {\r\n name: "claude-opus-4-5-20251101",\r\n type: "text",\r\n modelName: "claude-opus-4-5-20251101",\r\n think: false,\r\n },\r\n {\r\n name: "claude-haiku-4-5-20251001",\r\n type: "text",\r\n modelName: "claude-haiku-4-5-20251001",\r\n think: false,\r\n },\r\n {\r\n name: "gpt-5.4",\r\n type: "text",\r\n modelName: "gpt-5.4",\r\n think: false,\r\n },\r\n {\r\n name: "gpt-5.2",\r\n type: "text",\r\n modelName: "gpt-5.2",\r\n think: false,\r\n },\r\n {\r\n name: "MiniMax-M2.7",\r\n type: "text",\r\n modelName: "MiniMax-M2.7",\r\n think: true,\r\n },\r\n {\r\n name: "MiniMax-M2.5",\r\n type: "text",\r\n modelName: "MiniMax-M2.5",\r\n think: true,\r\n },\r\n {\r\n name: "Wan2.6 I2V 1080P (支持真人)",\r\n type: "video",\r\n modelName: "Wan2.6-I2V-1080P",\r\n mode: ["text", "startEndRequired"],\r\n durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["1080p"] }],\r\n audio: true,\r\n },\r\n {\r\n name: "Wan2.6 I2V 720P (支持真人)",\r\n type: "video",\r\n modelName: "Wan2.6-I2V-720P",\r\n mode: ["text", "startEndRequired"],\r\n durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p"] }],\r\n audio: true,\r\n },\r\n {\r\n name: "Seedance 1.5 Pro",\r\n type: "video",\r\n modelName: "doubao-seedance-1-5-pro-251215",\r\n durationResolutionMap: [{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }],\r\n mode: ["text", "endFrameOptional"],\r\n audio: true,\r\n },\r\n {\r\n name: "vidu2 turbo",\r\n type: "video",\r\n modelName: "ViduQ2-turbo",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: false,\r\n },\r\n {\r\n name: "ViduQ3 pro",\r\n type: "video",\r\n modelName: "ViduQ3-pro",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: false,\r\n },\r\n {\r\n name: "ViduQ2 pro",\r\n type: "video",\r\n modelName: "ViduQ2-pro",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: false,\r\n },\r\n\r\n {\r\n name: "Doubao Seedream 5.0 Lite",\r\n type: "image",\r\n modelName: "Doubao-Seedream-5.0-Lite",\r\n mode: ["text", "singleImage", "multiReference"],\r\n },\r\n {\r\n name: "Doubao Seedream 4.5",\r\n type: "image",\r\n modelName: "doubao-seedream-4-5-251128",\r\n mode: ["text", "singleImage", "multiReference"],\r\n },\r\n ],\r\n};\r\nexports.vendor = vendor;\r\n\r\n// ==================== 适配器函数 ====================\r\n\r\n// 文本请求函数\r\nconst textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");\r\n\r\n return createOpenAI({\r\n baseURL: vendor.inputValues.baseUrl,\r\n apiKey: apiKey,\r\n }).chat(textModel.modelName);\r\n};\r\nexports.textRequest = textRequest;\r\n\r\n//图片请求函数\r\ninterface ImageConfig {\r\n prompt: string; //图片提示词\r\n imageBase64: string[]; //输入的图片提示词\r\n size: "1K" | "2K" | "4K"; // 图片尺寸\r\n aspectRatio: `${number}:${number}`; // 长宽比\r\n}\r\n//豆包格式适配\r\nfunction doubaoAdaptor(imageConfig: ImageConfig, imageModel: ImageModel) {\r\n const size = imageConfig.size === "1K" ? "2K" : imageConfig.size;\r\n const sizeMap: Record> = {\r\n "16:9": {\r\n "2k": "2848x1600",\r\n "2K": "2848x1600",\r\n "4K": "4096x2304",\r\n "4k": "4096x2304",\r\n },\r\n "9:16": {\r\n "4k": "2304x4096",\r\n "2k": "1600x2848",\r\n "2K": "1600x2848",\r\n "4K": "2304x4096",\r\n },\r\n };\r\n const body = {\r\n model: imageModel.modelName,\r\n prompt: imageConfig.prompt,\r\n size: sizeMap[imageConfig.aspectRatio][size],\r\n response_format: "url",\r\n sequential_image_generation: "disabled",\r\n stream: false,\r\n watermark: false,\r\n ...(imageConfig.imageBase64 && { image: imageConfig.imageBase64 }),\r\n };\r\n return {\r\n body,\r\n processFn: (data) => {\r\n return data.data[0].url;\r\n },\r\n };\r\n}\r\n\r\n// 提取图片内容\r\nfunction extractFirstImageFromMd(content) {\r\n const regex = /!\\[([^\\]]*)\\]\\((data:image\\/[^;]+;base64,[A-Za-z0-9+/=]+|https?:\\/\\/[^\\s)]+|\\/\\/[^\\s)]+|[^\\s)]+)\\)/;\r\n const match = content.match(regex);\r\n if (!match) return null;\r\n const raw = match[2].trim();\r\n const url = raw.startsWith("data:") ? raw : raw.split(/\\s+/)[0];\r\n return {\r\n alt: match[1],\r\n url,\r\n type: url.startsWith("data:image") ? "base64" : "url",\r\n };\r\n}\r\n// gemini 图片请求适配\r\nfunction geminiImageAdaptor(imageConfig: ImageConfig, imageModel: ImageModel) {\r\n const images = [];\r\n if (imageConfig.imageBase64 && imageConfig.imageBase64.length) {\r\n images.push({\r\n role: "user",\r\n content: imageConfig.imageBase64.map((i) => ({\r\n type: "image_url",\r\n image_url: {\r\n url: i,\r\n },\r\n })),\r\n });\r\n }\r\n const imageConfigGoogle = {\r\n aspect_ratio: imageConfig.aspectRatio,\r\n };\r\n // if(imageModel.ModelName == \'gemini-3-pro-image-preview-vt\'){\r\n imageConfigGoogle.image_size = imageConfig.size;\r\n // }\r\n const body = {\r\n model: imageModel.modelName,\r\n messages: [{ role: "user", content: imageConfig.prompt + `请直接输出图片` }, ...images],\r\n extra_body: {\r\n google: {\r\n image_config: {\r\n ...imageConfigGoogle,\r\n },\r\n },\r\n },\r\n };\r\n return {\r\n body,\r\n url: `${vendor.inputValues.baseUrl}/chat/completions`,\r\n processFn: (data: any) => {\r\n return extractFirstImageFromMd(data.choices[0].message.content).url;\r\n },\r\n };\r\n}\r\nfunction commonAdaptor(imageConfig: ImageConfig, imageModel: ImageModel) {\r\n const defaultImageFn = [\r\n ["doubao", doubaoAdaptor],\r\n ["nano", geminiImageAdaptor],\r\n ["gemini", geminiImageAdaptor],\r\n ["seedream", doubaoAdaptor],\r\n ];\r\n const modelName = imageModel.modelName;\r\n const lowerName = modelName.toLowerCase();\r\n const match = defaultImageFn.find(([key]) => lowerName.includes(key));\r\n return match ? match[1](imageConfig, imageModel) : {};\r\n}\r\nconst imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");\r\n const adaptor = commonAdaptor(imageConfig, imageModel);\r\n\r\n const requestUrl = adaptor?.url ? `${vendor.inputValues.baseUrl}/chat/completions` : vendor.inputValues.baseUrl + "/images/generations";\r\n const response = await fetch(requestUrl, {\r\n method: "POST",\r\n headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },\r\n body: JSON.stringify(adaptor.body),\r\n });\r\n if (!response.ok) {\r\n const errorText = await response.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n const data = await response.json();\r\n return adaptor.processFn(data);\r\n};\r\nexports.imageRequest = imageRequest;\r\n\r\ninterface VideoConfig {\r\n duration: number; //视频时长,单位秒\r\n resolution: string; //视频分辨率,如"720p"、"1080p"\r\n aspectRatio: "16:9" | "9:16"; //视频长宽比\r\n prompt: string; //视频提示词\r\n fileBase64?: string[]; // 文件base64 包含图片base64、视频base64、音频base64\r\n audio?: boolean;\r\n mode:\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[]; // 混合参考\r\n}\r\n// 豆包视频\r\nconst buildDoubaoMetadata = (videoConfig: VideoConfig) => {\r\n const metaData = {\r\n ...(typeof videoConfig.audio == "boolean" && { generate_audio: videoConfig.audio ?? false }),\r\n ratio: videoConfig.aspectRatio,\r\n image_roles: [],\r\n references: [],\r\n };\r\n if (videoConfig.imageBase64 && videoConfig.imageBase64.length) {\r\n videoConfig.imageBase64.forEach((i, index) => {\r\n if (Array.isArray(videoConfig.mode)) {\r\n metaData.references.push(i);\r\n } else {\r\n if (videoConfig.mode == "startEndRequired" || videoConfig.mode == "endFrameOptional" || videoConfig.mode == "startFrameOptional") {\r\n (metaData.image_roles as string[]).push(index == 0 ? "first_frame" : "last_frame");\r\n }\r\n if (videoConfig.mode == "singleImage") {\r\n (metaData.image_roles as string[]).push("reference_image");\r\n }\r\n }\r\n });\r\n }\r\n\r\n return metaData;\r\n};\r\n\r\n// 万象\r\nconst buildWanMetadata = (videoConfig: VideoConfig) => {\r\n const images = videoConfig.imageBase64 ?? [];\r\n const metaData: Record = {};\r\n if (\r\n (videoConfig.mode === "startEndRequired" || videoConfig.mode == "endFrameOptional" || videoConfig.mode == "startFrameOptional") &&\r\n images.length == 2\r\n ) {\r\n if (images[0]) metaData.first_frame_url = images[0];\r\n if (images[1]) metaData.last_frame_url = images[1];\r\n } else if (images.length) {\r\n metaData.img_url = images[0]!;\r\n }\r\n if (typeof videoConfig.audio == "boolean") {\r\n metaData.audio = videoConfig.audio;\r\n }\r\n return metaData;\r\n};\r\n// 千问视频\r\nconst buildViduMetadata = (videoConfig: VideoConfig) => ({\r\n aspect_ratio: videoConfig.aspectRatio,\r\n audio: videoConfig.audio ?? false,\r\n off_peak: false,\r\n});\r\n// 可灵\r\nconst buildKlingAdaptor = (videoConfig: VideoConfig) => {\r\n const metaData: any = {\r\n aspect_ratio: videoConfig.aspectRatio,\r\n };\r\n\r\n if (videoConfig.imageBase64 && videoConfig.imageBase64.length) {\r\n if (Array.isArray(videoConfig.mode)) {\r\n metaData.reference = videoConfig.imageBase64;\r\n }\r\n if (videoConfig.mode == "endFrameOptional") {\r\n metaData.image_tail = videoConfig.imageBase64[0];\r\n }\r\n if (videoConfig.mode == "startEndRequired") {\r\n metaData.image_list = [\r\n {\r\n image_url: videoConfig.imageBase64[0],\r\n type: "first_frame",\r\n },\r\n {\r\n image_url: videoConfig.imageBase64[1],\r\n type: "last_frame",\r\n },\r\n ];\r\n }\r\n if (videoConfig.mode == "singleImage") {\r\n metaData.image = videoConfig.imageBase64[0];\r\n }\r\n }\r\n\r\n return metaData;\r\n};\r\ntype MetadataBuilder = (config: VideoConfig) => Record;\r\nconst METADATA_BUILDERS: Array<[string, MetadataBuilder]> = [\r\n ["doubao", buildDoubaoMetadata],\r\n ["wan", buildWanMetadata],\r\n ["vidu", buildViduMetadata],\r\n ["seedance", buildDoubaoMetadata],\r\n ["kling", buildKlingAdaptor],\r\n];\r\nconst buildModelMetadata = (modelName: string, videoConfig: VideoConfig) => {\r\n const lowerName = modelName.toLowerCase();\r\n const match = METADATA_BUILDERS.find(([key]) => lowerName.includes(key));\r\n return match ? match[1](videoConfig) : {};\r\n};\r\nconst videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");\r\n try {\r\n videoConfig.mode = JSON.parse(videoConfig.mode);\r\n } catch (e) {\r\n videoConfig.mode = videoConfig.mode as any;\r\n }\r\n // 构建每个模型对应的附加参数\r\n const metadata = buildModelMetadata(videoModel.modelName, videoConfig);\r\n\r\n //公共请求参数\r\n const publicBody = {\r\n model: videoModel.modelName,\r\n ...(videoConfig.imageBase64 && videoConfig.imageBase64.length && !Array.isArray(videoConfig.mode) ? { images: videoConfig.imageBase64 } : {}),\r\n prompt: videoConfig.prompt,\r\n duration: videoConfig.duration,\r\n metadata: metadata,\r\n };\r\n\r\n if (videoModel.modelName.toLocaleLowerCase().includes("wan")) {\r\n const sizeMap: Record> = {\r\n "480p": {\r\n "16:9": "832*480",\r\n "9:16": "480*832",\r\n },\r\n "720p": {\r\n "16:9": "1280*720",\r\n "9:16": "720*1280",\r\n },\r\n "1080p": {\r\n "16:9": "1920*1080",\r\n "9:16": "1080*1920",\r\n },\r\n };\r\n const size = sizeMap[videoConfig.resolution]?.[videoConfig.aspectRatio];\r\n publicBody.size = size;\r\n }\r\n const requestUrl = vendor.inputValues.baseUrl + "/video/generations";\r\n const queryUrl = vendor.inputValues.baseUrl + "/video/generations/{id}";\r\n const response = await fetch(requestUrl, {\r\n method: "POST",\r\n headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },\r\n body: JSON.stringify(publicBody),\r\n });\r\n if (!response.ok) {\r\n const errorText = await response.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n const data = await response.json();\r\n const taskId = data.id;\r\n const res = await pollTask(async () => {\r\n const queryResponse = await fetch(queryUrl.replace("{id}", taskId), {\r\n method: "GET",\r\n headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },\r\n });\r\n if (!queryResponse.ok) {\r\n const errorText = await queryResponse.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", queryResponse.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${queryResponse.status}, 错误信息: ${errorText}`);\r\n }\r\n const queryData = await queryResponse.json();\r\n const status = queryData?.status ?? queryData?.data?.status;\r\n const fail_reason = queryData?.data?.fail_reason ?? queryData?.data;\r\n switch (status) {\r\n case "completed":\r\n case "SUCCESS":\r\n case "success":\r\n return { completed: true, data: queryData.data.result_url };\r\n case "FAILURE":\r\n return { completed: false, error: fail_reason || "视频生成失败" };\r\n default:\r\n return { completed: false };\r\n }\r\n });\r\n if (res.error) throw new Error(res.error);\r\n return res.data;\r\n};\r\nexports.videoRequest = videoRequest;\r\n\r\ninterface TTSConfig {\r\n text: string;\r\n voice: string;\r\n speechRate: number;\r\n pitchRate: number;\r\n volume: number;\r\n}\r\nconst ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {\r\n return null;\r\n};\r\nexports.ttsRequest = ttsRequest;\r\n', + enable: 1, + createTime: 1775164020756, + }, + { + id: "volcengine", + author: "leeqi", + description: "火山引擎方舟官方直连模板,接入 Ark 的文本、图片、视频生成 API,支持 Doubao、DeepSeek、GLM 等模型。", + name: "火山引擎", + icon: "", + inputs: + '[{"key":"apiKey","label":"ARK API Key","type":"password","required":true},{"key":"text","label":"文本生成接口","type":"url","required":false,"placeholder":"如非必要请勿更改"},{"key":"baseUrl","label":"Ark Base URL","type":"url","required":false,"placeholder":"如非必要请勿更改"},{"key":"image","label":"图片生成接口","type":"url","required":false,"placeholder":"如非必要请勿更改"},{"key":"videoCreate","label":"视频任务创建接口","type":"url","required":false,"placeholder":"如非必要请勿更改"},{"key":"videoQuery","label":"视频任务查询接口","type":"url","required":false,"placeholder":"如非必要请勿更改"}]', + inputValues: + '{"apiKey":"","text":"https://ark.cn-beijing.volces.com/api/v3","baseUrl":"https://ark.cn-beijing.volces.com/api/v3","image":"https://ark.cn-beijing.volces.com/api/v3/images/generations","videoCreate":"https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks","videoQuery":"https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/{id}"}', + models: + '[{"name":"Doubao-Seed-2.0-pro","type":"text","modelName":"doubao-seed-2-0-pro-260215","think":false},{"name":"Doubao-Seed-2.0-lite","type":"text","modelName":"doubao-seed-2-0-lite-260215","think":false},{"name":"Doubao-Seed-2.0-mini","type":"text","modelName":"doubao-seed-2-0-mini-260215","think":false},{"name":"Doubao-Seed-2.0-Code","type":"text","modelName":"doubao-seed-2-0-code-preview-260215","think":false},{"name":"Doubao-1.5-pro-32k","type":"text","modelName":"doubao-1-5-pro-32k-250115","think":false},{"name":"deepseek-v3-250324","type":"text","modelName":"deepseek-v3-250324","think":false},{"name":"glm-4-7-251222","type":"text","modelName":"glm-4-7-251222","think":false},{"name":"Doubao-Seedream-5.0-lite","type":"image","modelName":"doubao-seedream-5-0-260128","mode":["text","singleImage","multiReference"]},{"name":"Doubao-Seedream-4.5","type":"image","modelName":"doubao-seedream-4-5-251128","mode":["text","singleImage","multiReference"]},{"name":"Doubao-Seedream-4.0","type":"image","modelName":"doubao-seedream-4-0-250828","mode":["text","singleImage","multiReference"]},{"name":"Doubao-Seedance-1.5-pro","type":"video","modelName":"doubao-seedance-1-5-pro-251215","mode":["text","singleImage","endFrameOptional"],"audio":true,"durationResolutionMap":[{"duration":[5],"resolution":["480p","720p","1080p"]}]},{"name":"Doubao-Seedance-1.0-pro-fast","type":"video","modelName":"doubao-seedance-1-0-pro-fast-251015","mode":["text","singleImage"],"audio":false,"durationResolutionMap":[{"duration":[2,3,4,5,6,7,8,9,10,11,12],"resolution":["480p","720p","1080p"]}]}]', + code: '//如需遥测AI请使用在toonflow安装目录运行npx @ai-sdk/devtools (要求在其他设置中打开遥测功能,且toonflow有权限在安装目录创建.devtools文件夹)\r\n// ==================== 类型定义 ====================\r\n// 文本模型\r\ninterface TextModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "text";\r\n think: boolean; // 前端显示用\r\n}\r\n\r\n// 图像模型\r\ninterface ImageModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "image";\r\n mode: ("text" | "singleImage" | "multiReference")[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n}\r\n// 视频模型\r\ninterface VideoModel {\r\n name: string; // 显示名称\r\n modelName: string; //全局唯一\r\n type: "video";\r\n mode: (\r\n | "singleImage" // 单图\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[] // 混合参考\r\n )[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n audio: "optional" | false | true; // 音频配置\r\n durationResolutionMap: { duration: number[]; resolution: string[] }[];\r\n}\r\n\r\ninterface TTSModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "tts";\r\n voices: {\r\n title: string; //显示名称\r\n voice: string; //说话人\r\n }[];\r\n}\r\n// 供应商配置\r\ninterface VendorConfig {\r\n id: string; //供应商唯一标识,必须全局唯一\r\n author: string;\r\n description?: string; //md5格式\r\n name: string;\r\n icon?: string; //仅支持base64格式\r\n inputs: {\r\n key: string;\r\n label: string;\r\n type: "text" | "password" | "url";\r\n required: boolean;\r\n placeholder?: string;\r\n }[];\r\n inputValues: Record;\r\n models: (TextModel | ImageModel | VideoModel)[];\r\n}\r\n// ==================== 全局工具函数 ====================\r\n//Axios实例\r\n//压缩图片大小(1MB = 1 * 1024 * 1024)\r\ndeclare const zipImage: (completeBase64: string, size: number) => Promise;\r\n//压缩图片分辨率\r\ndeclare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise;\r\n//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb\r\ndeclare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise;\r\n//Url转Base64\r\ndeclare const urlToBase64: (url: string) => Promise;\r\n//轮询函数\r\ndeclare const pollTask: (\r\n fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,\r\n interval?: number,\r\n timeout?: number,\r\n) => Promise<{ completed: boolean; data?: string; error?: string }>;\r\ndeclare const axios: any;\r\ndeclare const createOpenAI: any;\r\ndeclare const createDeepSeek: any;\r\ndeclare const createZhipu: any;\r\ndeclare const createQwen: any;\r\ndeclare const createAnthropic: any;\r\ndeclare const createOpenAICompatible: any;\r\ndeclare const createXai: any;\r\ndeclare const createMinimax: any;\r\ndeclare const createGoogleGenerativeAI: any;\r\ndeclare const logger: (logstring: string) => void;\r\ndeclare const jsonwebtoken: any;\r\n// ==================== 供应商数据 ====================\r\nconst ARK_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";\r\nconst SUCCESS_TASK_STATUS = ["succeeded", "completed", "success"];\r\nconst FAILED_TASK_STATUS = ["failed", "failure", "error", "canceled", "cancelled"];\r\n\r\nconst vendor: VendorConfig = {\r\n id: "volcengine",\r\n version: 1,\r\n author: "leeqi",\r\n description: "火山引擎方舟官方直连模板,接入 Ark 的文本、图片、视频生成 API,支持 Doubao、DeepSeek、GLM 等模型。",\r\n name: "火山引擎",\r\n inputs: [\r\n { key: "apiKey", label: "ARK API Key", type: "password", required: true },\r\n { key: "text", label: "文本生成接口", type: "url", required: false, placeholder: "如非必要请勿更改" },\r\n { key: "baseUrl", label: "Ark Base URL", type: "url", required: false, placeholder: "如非必要请勿更改" },\r\n { key: "image", label: "图片生成接口", type: "url", required: false, placeholder: "如非必要请勿更改" },\r\n { key: "videoCreate", label: "视频任务创建接口", type: "url", required: false, placeholder: "如非必要请勿更改" },\r\n { key: "videoQuery", label: "视频任务查询接口", type: "url", required: false, placeholder: "如非必要请勿更改" },\r\n ],\r\n inputValues: {\r\n apiKey: "",\r\n text: ARK_BASE_URL,\r\n baseUrl: ARK_BASE_URL,\r\n image: `${ARK_BASE_URL}/images/generations`,\r\n videoCreate: `${ARK_BASE_URL}/contents/generations/tasks`,\r\n videoQuery: `${ARK_BASE_URL}/contents/generations/tasks/{id}`,\r\n },\r\n models: [\r\n {\r\n name: "Doubao-Seed-2.0-pro",\r\n type: "text",\r\n modelName: "doubao-seed-2-0-pro-260215",\r\n think: false,\r\n },\r\n {\r\n name: "Doubao-Seed-2.0-lite",\r\n type: "text",\r\n modelName: "doubao-seed-2-0-lite-260215",\r\n think: false,\r\n },\r\n {\r\n name: "Doubao-Seed-2.0-mini",\r\n type: "text",\r\n modelName: "doubao-seed-2-0-mini-260215",\r\n think: false,\r\n },\r\n {\r\n name: "Doubao-Seed-2.0-Code",\r\n type: "text",\r\n modelName: "doubao-seed-2-0-code-preview-260215",\r\n think: false,\r\n },\r\n {\r\n name: "Doubao-1.5-pro-32k",\r\n type: "text",\r\n modelName: "doubao-1-5-pro-32k-250115",\r\n think: false,\r\n },\r\n {\r\n name: "deepseek-v3-250324",\r\n type: "text",\r\n modelName: "deepseek-v3-250324",\r\n think: false,\r\n },\r\n {\r\n name: "glm-4-7-251222",\r\n type: "text",\r\n modelName: "glm-4-7-251222",\r\n think: false,\r\n },\r\n {\r\n name: "Doubao-Seedream-5.0-lite",\r\n type: "image",\r\n modelName: "doubao-seedream-5-0-260128",\r\n mode: ["text", "singleImage", "multiReference"],\r\n },\r\n {\r\n name: "Doubao-Seedream-4.5",\r\n type: "image",\r\n modelName: "doubao-seedream-4-5-251128",\r\n mode: ["text", "singleImage", "multiReference"],\r\n },\r\n {\r\n name: "Doubao-Seedream-4.0",\r\n type: "image",\r\n modelName: "doubao-seedream-4-0-250828",\r\n mode: ["text", "singleImage", "multiReference"],\r\n },\r\n {\r\n name: "Doubao-Seedance-1.5-pro",\r\n type: "video",\r\n modelName: "doubao-seedance-1-5-pro-251215",\r\n mode: ["text", "singleImage", "endFrameOptional"],\r\n audio: true,\r\n durationResolutionMap: [{ duration: [5], resolution: ["480p", "720p", "1080p"] }],\r\n },\r\n {\r\n name: "Doubao-Seedance-1.0-pro-fast",\r\n type: "video",\r\n modelName: "doubao-seedance-1-0-pro-fast-251015",\r\n mode: ["text", "singleImage"],\r\n audio: false,\r\n durationResolutionMap: [{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }],\r\n },\r\n ],\r\n};\r\nexports.vendor = vendor;\r\n\r\n// ==================== 适配器函数 ====================\r\nconst getApiKey = () => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n return vendor.inputValues.apiKey.replace(/^Bearer\\s+/i, "");\r\n};\r\n\r\nconst getBaseUrl = () => vendor.inputValues.baseUrl || ARK_BASE_URL;\r\nconst buildUrl = (overrideUrl: string | undefined, fallbackPath: string) => overrideUrl || `${getBaseUrl().replace(/\\/$/, "")}${fallbackPath}`;\r\n\r\nconst getHeaders = () => ({\r\n Authorization: `Bearer ${getApiKey()}`,\r\n "Content-Type": "application/json",\r\n});\r\n\r\nconst getOpenAIBaseUrl = () =>\r\n (vendor.inputValues.text || getBaseUrl())\r\n .trim()\r\n .replace(/\\/$/, "")\r\n .replace(/\\/chat\\/completions$/i, "");\r\n\r\nconst readJson = async (response: Response, action: string) => {\r\n if (!response.ok) {\r\n const errorText = await response.text();\r\n throw new Error(`${action}失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n\r\n return response.json();\r\n};\r\n\r\n//补齐图片 data url 前缀\r\nconst normalizeImageInput = (value: string) => {\r\n if (!value) return value;\r\n if (/^(https?:\\/\\/|data:|volc:)/i.test(value)) return value;\r\n return `data:image/png;base64,${value}`;\r\n};\r\n\r\nconst normalizeImageList = (imageBase64?: string[]) => (imageBase64 || []).filter(Boolean).map(normalizeImageInput);\r\n\r\nconst extractImageResult = (data: any) => {\r\n const first = data?.data?.[0] ?? data?.images?.[0] ?? data?.output?.[0];\r\n return first?.url ?? first?.image_url ?? first?.b64_json ?? first?.base64;\r\n};\r\n\r\nconst extractTaskId = (data: any) => data?.id ?? data?.task_id ?? data?.taskId ?? data?.data?.id ?? data?.data?.task_id ?? data?.data;\r\n\r\nconst extractVideoUrl = (data: any) =>\r\n data?.content?.video_url ??\r\n data?.content?.video_urls?.[0] ??\r\n data?.data?.content?.video_url ??\r\n data?.data?.content?.video_urls?.[0] ??\r\n data?.data?.video_url ??\r\n data?.result?.video_url ??\r\n data?.result_url ??\r\n data?.data?.result_url;\r\n\r\nconst getTaskStatus = (data: any) => (data?.status ?? data?.data?.status ?? "").toString().toLowerCase();\r\n\r\nconst getTaskError = (data: any) =>\r\n data?.error?.message ?? data?.message ?? data?.data?.message ?? data?.data?.fail_reason ?? data?.fail_reason ?? "任务执行失败";\r\n\r\n// 文本请求函数\r\nconst textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {\r\n return createOpenAI({\r\n baseURL: getOpenAIBaseUrl(),\r\n apiKey: getApiKey(),\r\n }).chat(textModel.modelName);\r\n};\r\nexports.textRequest = textRequest;\r\n\r\n//图片请求函数\r\ninterface ImageConfig {\r\n prompt: string; //图片提示词\r\n imageBase64: string[]; //输入的图片提示词\r\n size: "1K" | "2K" | "4K"; // 图片尺寸\r\n aspectRatio: `${number}:${number}`; // 长宽比\r\n}\r\n\r\nconst normalizeImageSize = (imageConfig: ImageConfig) => {\r\n const normalizedSize = imageConfig.size.toUpperCase();\r\n const normalizedAspectRatio = imageConfig.aspectRatio;\r\n\r\n if (normalizedSize === "1K") {\r\n return "2k";\r\n }\r\n\r\n const sizeMap: Record<"16:9" | "9:16", Record<"2K" | "4K", string>> = {\r\n "16:9": {\r\n "2K": "2848x1600",\r\n "4K": "4096x2304",\r\n },\r\n "9:16": {\r\n "2K": "1600x2848",\r\n "4K": "2304x4096",\r\n },\r\n };\r\n\r\n if (normalizedAspectRatio === "16:9" || normalizedAspectRatio === "9:16") {\r\n return sizeMap[normalizedAspectRatio][normalizedSize as "2K" | "4K"];\r\n }\r\n\r\n return normalizedSize === "4K" ? "3k" : "2k";\r\n};\r\n\r\nconst imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {\r\n const images = normalizeImageList(imageConfig.imageBase64);\r\n const body = {\r\n model: imageModel.modelName,\r\n prompt: imageConfig.prompt,\r\n size: normalizeImageSize(imageConfig),\r\n response_format: "url",\r\n sequential_image_generation: "disabled",\r\n stream: false,\r\n watermark: false,\r\n ...(images.length ? { image: images.length === 1 ? images[0] : images } : {}),\r\n };\r\n\r\n const data = await readJson(\r\n await fetch(buildUrl(vendor.inputValues.image, "/images/generations"), {\r\n method: "POST",\r\n headers: getHeaders(),\r\n body: JSON.stringify(body),\r\n }),\r\n "图片生成",\r\n );\r\n\r\n const result = extractImageResult(data);\r\n if (!result) throw new Error(`图片生成返回格式异常: ${JSON.stringify(data)}`);\r\n return result;\r\n};\r\nexports.imageRequest = imageRequest;\r\n\r\ninterface VideoConfig {\r\n duration: number;\r\n resolution: string;\r\n aspectRatio: "16:9" | "9:16";\r\n prompt: string;\r\n imageBase64?: string[];\r\n audio?: boolean;\r\n mode:\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("video" | "image" | "audio" | "text")[]; // 混合参考\r\n}\r\n\r\nconst isSeedanceModel = (modelName: string) => /^doubao-seedance-/i.test(modelName);\r\nconst isSeedance15ProModel = (modelName: string) => /^doubao-seedance-1-5-pro/i.test(modelName);\r\n\r\nconst appendPromptFlag = (prompt: string, flag: string, value: string | number | boolean | undefined) => {\r\n if (value === undefined || value === null || value === "") return prompt;\r\n const normalizedPrompt = prompt.trim();\r\n const flagPattern = new RegExp(`(^|\\\\s)--${flag}(?:\\\\s+\\\\S+)?(?=\\\\s|$)`, "i");\r\n if (flagPattern.test(normalizedPrompt)) return normalizedPrompt;\r\n return `${normalizedPrompt} --${flag} ${value}`.trim();\r\n};\r\n\r\nconst buildVideoPrompt = (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n if (!isSeedanceModel(videoModel.modelName)) return videoConfig.prompt || "";\r\n\r\n let prompt = videoConfig.prompt || "";\r\n prompt = appendPromptFlag(prompt, "resolution", videoConfig.resolution);\r\n prompt = appendPromptFlag(prompt, "duration", videoConfig.duration);\r\n prompt = appendPromptFlag(prompt, "camerafixed", false);\r\n prompt = appendPromptFlag(prompt, "watermark", false);\r\n return prompt;\r\n};\r\n\r\nconst buildVideoContent = (videoConfig: VideoConfig, videoModel: VideoModel) => [\r\n { type: "text", text: buildVideoPrompt(videoConfig, videoModel) },\r\n ...normalizeImageList(videoConfig.imageBase64).map((image) => ({\r\n type: "image_url",\r\n image_url: { url: image },\r\n })),\r\n];\r\n\r\nconst buildVideoBody = (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n const isSeedance = isSeedanceModel(videoModel.modelName);\r\n const isSeedance15Pro = isSeedance15ProModel(videoModel.modelName);\r\n\r\n return {\r\n model: videoModel.modelName,\r\n content: buildVideoContent(videoConfig, videoModel),\r\n ...(!isSeedance15Pro && !isSeedance\r\n ? {\r\n duration: videoConfig.duration,\r\n resolution: videoConfig.resolution,\r\n ratio: videoConfig.aspectRatio,\r\n }\r\n : {}),\r\n ...(videoModel.audio === true && typeof videoConfig.audio === "boolean" ? { generate_audio: videoConfig.audio } : {}),\r\n };\r\n};\r\n\r\nconst queryVideoResult = async (taskId: string) => {\r\n const queryData = await readJson(\r\n await fetch(buildUrl(vendor.inputValues.videoQuery, "/contents/generations/tasks/{id}").replace("{id}", taskId), {\r\n method: "GET",\r\n headers: getHeaders(),\r\n }),\r\n "视频任务查询",\r\n );\r\n\r\n const status = getTaskStatus(queryData);\r\n if (SUCCESS_TASK_STATUS.includes(status)) {\r\n const videoUrl = extractVideoUrl(queryData);\r\n if (!videoUrl) return { completed: true, error: `视频任务成功但未返回结果: ${JSON.stringify(queryData)}` };\r\n return { completed: true, data: videoUrl };\r\n }\r\n\r\n if (FAILED_TASK_STATUS.includes(status)) {\r\n return { completed: false, error: getTaskError(queryData) };\r\n }\r\n\r\n return { completed: false };\r\n};\r\n\r\nconst videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n const createData = await readJson(\r\n await fetch(buildUrl(vendor.inputValues.videoCreate, "/contents/generations/tasks"), {\r\n method: "POST",\r\n headers: getHeaders(),\r\n body: JSON.stringify(buildVideoBody(videoConfig, videoModel)),\r\n }),\r\n "视频任务创建",\r\n );\r\n\r\n const taskId = extractTaskId(createData);\r\n if (!taskId) throw new Error(`视频任务创建返回格式异常: ${JSON.stringify(createData)}`);\r\n\r\n const result = await pollTask(() => queryVideoResult(taskId));\r\n if (result.error) throw new Error(result.error);\r\n return result.data;\r\n};\r\nexports.videoRequest = videoRequest;\r\n\r\ninterface TTSConfig {\r\n text: string;\r\n voice: string;\r\n speechRate: number;\r\n pitchRate: number;\r\n volume: number;\r\n}\r\n\r\nconst ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {\r\n return null;\r\n};\r\nexports.ttsRequest = ttsRequest;\r\n', + enable: 0, + createTime: 1775155204210, + }, + { + id: "minimax", + author: "Toonflow", + description: "MiniMax标准格式接口,如果没有你想要的模型请手动添加。", + name: "MiniMax标准接口", + icon: "", + inputs: + '[{"key":"apiKey","label":"API密钥","type":"password","required":true},{"key":"baseUrl","label":"请求地址","type":"url","required":true,"placeholder":"已默认填入官方地址,非特殊情况不需要更改"}]', + inputValues: '{"apiKey":"","baseUrl":"https://api.minimaxi.com/v1"}', + models: + '[{"name":"MiniMax-M2.7","modelName":"MiniMax-M2.7","type":"text","think":true},{"name":"MiniMax-M2.7-highspeed","modelName":"MiniMax-M2.7-highspeed","type":"text","think":true},{"name":"MiniMax-M2.5","modelName":"MiniMax-M2.5","type":"text","think":true},{"name":"MiniMax-M2.5-highspeed","modelName":"MiniMax-M2.5-highspeed","type":"text","think":true}]', + code: '//如需遥测AI请使用在toonflow安装目录运行npx @ai-sdk/devtools (要求在其他设置中打开遥测功能,且toonflow有权限在安装目录创建.devtools文件夹)\r\n// ==================== 类型定义 ====================\r\n// 文本模型\r\ninterface TextModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "text";\r\n think: boolean; // 前端显示用\r\n}\r\n\r\n// 图像模型\r\ninterface ImageModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "image";\r\n mode: ("text" | "singleImage" | "multiReference")[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n}\r\n// 视频模型\r\ninterface VideoModel {\r\n name: string; // 显示名称\r\n modelName: string; //全局唯一\r\n type: "video";\r\n mode: (\r\n | "singleImage" // 单图\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[]\r\n )[]; // 混合参考\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n audio: "optional" | false | true; // 音频配置\r\n durationResolutionMap: { duration: number[]; resolution: string[] }[];\r\n}\r\n\r\ninterface TTSModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "tts";\r\n voices: {\r\n title: string; //显示名称\r\n voice: string; //说话人\r\n }[];\r\n}\r\n// 供应商配置\r\ninterface VendorConfig {\r\n id: string; //供应商唯一标识,必须全局唯一\r\n author: string;\r\n description?: string; //md5格式\r\n name: string;\r\n icon?: string; //仅支持base64格式\r\n inputs: {\r\n key: string;\r\n label: string;\r\n type: "text" | "password" | "url";\r\n required: boolean;\r\n placeholder?: string;\r\n }[];\r\n inputValues: Record;\r\n models: (TextModel | ImageModel | VideoModel)[];\r\n}\r\n// ==================== 全局工具函数 ====================\r\n//Axios实例\r\n//压缩图片大小(1MB = 1 * 1024 * 1024)\r\ndeclare const zipImage: (completeBase64: string, size: number) => Promise;\r\n//压缩图片分辨率\r\ndeclare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise;\r\n//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb\r\ndeclare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise;\r\n//Url转Base64\r\ndeclare const urlToBase64: (url: string) => Promise;\r\n//轮询函数\r\ndeclare const pollTask: (\r\n fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,\r\n interval?: number,\r\n timeout?: number,\r\n) => Promise<{ completed: boolean; data?: string; error?: string }>;\r\ndeclare const axios: any;\r\ndeclare const createOpenAI: any;\r\ndeclare const createDeepSeek: any;\r\ndeclare const createZhipu: any;\r\ndeclare const createQwen: any;\r\ndeclare const createAnthropic: any;\r\ndeclare const createOpenAICompatible: any;\r\ndeclare const createXai: any;\r\ndeclare const createMinimax: any;\r\ndeclare const createGoogleGenerativeAI: any;\r\ndeclare const logger: (logstring: string) => void;\r\ndeclare const jsonwebtoken: any;\r\n\r\n// ==================== 供应商数据 ====================\r\nconst vendor: VendorConfig = {\r\n id: "minimax",\r\n author: "Toonflow",\r\n description: "MiniMax标准格式接口,如果没有你想要的模型请手动添加。",\r\n name: "MiniMax标准接口",\r\n icon: "",\r\n inputs: [\r\n { key: "apiKey", label: "API密钥", type: "password", required: true },\r\n { key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "已默认填入官方地址,非特殊情况不需要更改" },\r\n ],\r\n inputValues: {\r\n apiKey: "",\r\n baseUrl: "https://api.minimaxi.com/v1",\r\n },\r\n models: [\r\n {\r\n name: "MiniMax-M2.7",\r\n modelName: "MiniMax-M2.7",\r\n type: "text",\r\n think: true,\r\n },\r\n {\r\n name: "MiniMax-M2.7-highspeed",\r\n modelName: "MiniMax-M2.7-highspeed",\r\n type: "text",\r\n think: true,\r\n },\r\n {\r\n name: "MiniMax-M2.5",\r\n modelName: "MiniMax-M2.5",\r\n type: "text",\r\n think: true,\r\n },\r\n {\r\n name: "MiniMax-M2.5-highspeed",\r\n modelName: "MiniMax-M2.5-highspeed",\r\n type: "text",\r\n think: true,\r\n },\r\n ],\r\n};\r\nexports.vendor = vendor;\r\n\r\n// ==================== 适配器函数 ====================\r\n\r\n// 文本请求函数\r\nconst textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");\r\n\r\n return createOpenAI({\r\n baseURL: vendor.inputValues.baseUrl,\r\n apiKey: apiKey,\r\n }).chat(textModel.modelName);\r\n};\r\nexports.textRequest = textRequest;\r\n\r\n//图片请求函数\r\ninterface ImageConfig {\r\n prompt: string; //图片提示词\r\n imageBase64: string[]; //输入的图片提示词\r\n size: "1K" | "2K" | "4K"; // 图片尺寸\r\n aspectRatio: `${number}:${number}`; // 长宽比\r\n}\r\nconst imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {\r\n return null;\r\n};\r\nexports.imageRequest = imageRequest;\r\n\r\ninterface VideoConfig {\r\n duration: number;\r\n resolution: string;\r\n aspectRatio: "16:9" | "9:16";\r\n prompt: string;\r\n imageBase64?: string[];\r\n audio?: boolean;\r\n mode:\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("video" | "image" | "audio" | "text")[]; // 混合参考\r\n}\r\n\r\nconst videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n return null;\r\n};\r\nexports.videoRequest = videoRequest;\r\n\r\ninterface TTSConfig {\r\n text: string;\r\n voice: string;\r\n speechRate: number;\r\n pitchRate: number;\r\n volume: number;\r\n}\r\nconst ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {\r\n return null;\r\n};\r\nexports.ttsRequest = ttsRequest;\r\n', + enable: 0, + createTime: 1775154441614, + }, + { + id: "openai", + author: "Toonflow", + description: "OpenAI标准格式接口,如果没有你想要的模型请手动添加。", + name: "OpenAI标准接口", + icon: "", + inputs: + '[{"key":"apiKey","label":"API密钥","type":"password","required":true},{"key":"baseUrl","label":"请求地址","type":"url","required":true,"placeholder":"以v1结束,示例:https://api.openai.com/v1"}]', + inputValues: '{"apiKey":"","baseUrl":"http://192.168.0.116:33332/v1"}', + models: + '[{"name":"GPT-4o","modelName":"gpt-4o","type":"text","think":false},{"name":"GPT-4.1","modelName":"gpt-4.1","type":"text","think":false},{"name":"GPT-5.1","modelName":"gpt-5.1","type":"text","think":false},{"name":"GPT-5.2","modelName":"gpt-5.2","type":"text","think":false},{"name":"GPT-5.4","modelName":"gpt-5.4","type":"text","think":false}]', + code: '//如需遥测AI请使用在toonflow安装目录运行npx @ai-sdk/devtools (要求在其他设置中打开遥测功能,且toonflow有权限在安装目录创建.devtools文件夹)\r\n// ==================== 类型定义 ====================\r\n// 文本模型\r\ninterface TextModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "text";\r\n think: boolean; // 前端显示用\r\n}\r\n\r\n// 图像模型\r\ninterface ImageModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "image";\r\n mode: ("text" | "singleImage" | "multiReference")[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n}\r\n// 视频模型\r\ninterface VideoModel {\r\n name: string; // 显示名称\r\n modelName: string; //全局唯一\r\n type: "video";\r\n mode: (\r\n | "singleImage" // 单图\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[]\r\n )[]; // 混合参考\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n audio: "optional" | false | true; // 音频配置\r\n durationResolutionMap: { duration: number[]; resolution: string[] }[];\r\n}\r\n\r\ninterface TTSModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "tts";\r\n voices: {\r\n title: string; //显示名称\r\n voice: string; //说话人\r\n }[];\r\n}\r\n// 供应商配置\r\ninterface VendorConfig {\r\n id: string; //供应商唯一标识,必须全局唯一\r\n author: string;\r\n description?: string; //md5格式\r\n name: string;\r\n icon?: string; //仅支持base64格式\r\n inputs: {\r\n key: string;\r\n label: string;\r\n type: "text" | "password" | "url";\r\n required: boolean;\r\n placeholder?: string;\r\n }[];\r\n inputValues: Record;\r\n models: (TextModel | ImageModel | VideoModel)[];\r\n}\r\n// ==================== 全局工具函数 ====================\r\n//Axios实例\r\n//压缩图片大小(1MB = 1 * 1024 * 1024)\r\ndeclare const zipImage: (completeBase64: string, size: number) => Promise;\r\n//压缩图片分辨率\r\ndeclare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise;\r\n//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb\r\ndeclare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise;\r\n//Url转Base64\r\ndeclare const urlToBase64: (url: string) => Promise;\r\n//轮询函数\r\ndeclare const pollTask: (\r\n fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,\r\n interval?: number,\r\n timeout?: number,\r\n) => Promise<{ completed: boolean; data?: string; error?: string }>;\r\ndeclare const axios: any;\r\ndeclare const createOpenAI: any;\r\ndeclare const createDeepSeek: any;\r\ndeclare const createZhipu: any;\r\ndeclare const createQwen: any;\r\ndeclare const createAnthropic: any;\r\ndeclare const createOpenAICompatible: any;\r\ndeclare const createXai: any;\r\ndeclare const createMinimax: any;\r\ndeclare const createGoogleGenerativeAI: any;\r\ndeclare const logger: (logstring: string) => void;\r\ndeclare const jsonwebtoken: any;\r\n\r\n// ==================== 供应商数据 ====================\r\nconst vendor: VendorConfig = {\r\n id: "openai",\r\n author: "Toonflow",\r\n description: "OpenAI标准格式接口,如果没有你想要的模型请手动添加。",\r\n name: "OpenAI标准接口",\r\n icon: "",\r\n inputs: [\r\n { key: "apiKey", label: "API密钥", type: "password", required: true },\r\n { key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "以v1结束,示例:https://api.openai.com/v1" },\r\n ],\r\n inputValues: {\r\n apiKey: "",\r\n baseUrl: "https://api.openai.com/v1",\r\n },\r\n models: [\r\n {\r\n name: "GPT-4o",\r\n modelName: "gpt-4o",\r\n type: "text",\r\n think: false,\r\n },\r\n {\r\n name: "GPT-4.1",\r\n modelName: "gpt-4.1",\r\n type: "text",\r\n think: false,\r\n },\r\n {\r\n name: "GPT-5.1",\r\n modelName: "gpt-5.1",\r\n type: "text",\r\n think: false,\r\n },\r\n {\r\n name: "GPT-5.2",\r\n modelName: "gpt-5.2",\r\n type: "text",\r\n think: false,\r\n },\r\n {\r\n name: "GPT-5.4",\r\n modelName: "gpt-5.4",\r\n type: "text",\r\n think: false,\r\n },\r\n ],\r\n};\r\nexports.vendor = vendor;\r\n\r\n// ==================== 适配器函数 ====================\r\n\r\n// 文本请求函数\r\nconst textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");\r\n\r\n return createOpenAI({\r\n baseURL: vendor.inputValues.baseUrl,\r\n apiKey: apiKey,\r\n }).chat(textModel.modelName);\r\n};\r\nexports.textRequest = textRequest;\r\n\r\n//图片请求函数\r\ninterface ImageConfig {\r\n prompt: string; //图片提示词\r\n imageBase64: string[]; //输入的图片提示词\r\n size: "1K" | "2K" | "4K"; // 图片尺寸\r\n aspectRatio: `${number}:${number}`; // 长宽比\r\n}\r\nconst imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {\r\n return null;\r\n};\r\nexports.imageRequest = imageRequest;\r\n\r\ninterface VideoConfig {\r\n duration: number;\r\n resolution: string;\r\n aspectRatio: "16:9" | "9:16";\r\n prompt: string;\r\n imageBase64?: string[];\r\n audio?: boolean;\r\n mode:\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("video" | "image" | "audio" | "text")[]; // 混合参考\r\n}\r\n\r\nconst videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n return null;\r\n};\r\nexports.videoRequest = videoRequest;\r\n\r\ninterface TTSConfig {\r\n text: string;\r\n voice: string;\r\n speechRate: number;\r\n pitchRate: number;\r\n volume: number;\r\n}\r\nconst ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {\r\n return null;\r\n};\r\nexports.ttsRequest = ttsRequest;\r\n', + enable: 0, + createTime: 1775154125094, + }, + { + id: "klingai", + author: "klingai", + description: + "可灵AI新一代AI创意生产力工具,基于快手大模型团队自研的 图像生成@可图大模型 和 视频生成@可灵大模型 技术,提供丰富的AI图片、AI视频及相关可控编辑能力。https://app.klingai.com/cn/", + name: "可灵AI", + icon: "", + inputs: + '[{"key":"apiKey","label":"accsseKey","type":"password","required":true,"placeholder":"请到可灵官方申请"},{"key":"sk","label":"SecretKey","type":"password","required":true,"placeholder":"请到可灵官方申请"}]', + inputValues: '{"apiKey":"","sk":""}', + models: + '[{"name":"kling-video-o1 标准版","type":"video","modelName":"kling-video-o1-std","durationResolutionMap":[{"duration":[5,10],"resolution":["540p","720p","1080p"]}],"mode":["text","startEndRequired"],"audio":false},{"name":"kling-video-o1 pro","type":"video","modelName":"kling-video-o1-pro","durationResolutionMap":[{"duration":[5,10],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":true},{"name":"kling-v3-omni std","type":"video","modelName":"kling-v3-omni-std","durationResolutionMap":[{"duration":[3,4,5,6,7,8,9,10,11,12,13,14,15],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":false},{"name":"kling-v3-omni pro","type":"video","modelName":"kling-v3-omni-pro","durationResolutionMap":[{"duration":[3,4,5,6,7,8,9,10,11,12,13,14,15],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":true},{"name":"kling-v1 std 5s","type":"video","modelName":"kling-v1-std-5s","durationResolutionMap":[{"duration":[5],"resolution":["720p"]}],"mode":["singleImage","text","startEndRequired"],"audio":true},{"name":"kling-v1 std 10s","type":"video","modelName":"kling-v1-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["720p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v1 pro 5s","type":"video","modelName":"kling-v1-pro-5s","durationResolutionMap":[{"duration":[5],"resolution":["720p"]}],"mode":["singleImage","text","startEndRequired"],"audio":true},{"name":"kling-v1 pro 10s","type":"video","modelName":"kling-v1-pro-10s","durationResolutionMap":[{"duration":[10],"resolution":["720p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v1-5 std 5s","type":"video","modelName":"kling-v1-5-std-5s","durationResolutionMap":[{"duration":[5],"resolution":["720p"]}],"mode":["singleImage"],"audio":true},{"name":"kling-v1-5 std 10s","type":"video","modelName":"kling-v1-5-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["720p"]}],"mode":["singleImage"],"audio":true},{"name":"kling-v1-5 pro 5s","type":"video","modelName":"kling-v1-5-pro-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["singleImage","startEndRequired","startFrameOptional"],"audio":true},{"name":"kling-v1-5 pro 10s","type":"video","modelName":"kling-v1-5-pro-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["singleImage","startEndRequired","startFrameOptional"],"audio":true},{"name":"kling-v1-6 std 5s","type":"video","modelName":"kling-v1-5-std-5s","durationResolutionMap":[{"duration":[5],"resolution":["720p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v1-6 std 10s","type":"video","modelName":"kling-v1-5-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["720p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v1-6 pro 5s","type":"video","modelName":"kling-v1-5-pro-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["singleImage","text","startEndRequired","startFrameOptional"],"audio":true},{"name":"kling-v1-6 pro 10s","type":"video","modelName":"kling-v1-5-pro-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["singleImage","text","startEndRequired","startFrameOptional"],"audio":true},{"name":"kling-v2-master 5s","type":"video","modelName":"kling-v2-master-5s","durationResolutionMap":[{"duration":[5],"resolution":["720p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v2-master 10s","type":"video","modelName":"kling-v1-5-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["720p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v2-1 std 5s","type":"video","modelName":"kling-v1-5-std-5s","durationResolutionMap":[{"duration":[5],"resolution":["720p"]}],"mode":["singleImage"],"audio":true},{"name":"kling-v2-1 std 10s","type":"video","modelName":"kling-v1-5-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["720p"]}],"mode":["singleImage"],"audio":true},{"name":"kling-v2-1 pro 5s","type":"video","modelName":"kling-v2-1-pro-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"kling-v2-1 pro 10s","type":"video","modelName":"kling-v2-1-pro-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"kling-v2-1-master 5s","type":"video","modelName":"kling-v2-1-master-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v2-1-master 10s","type":"video","modelName":"kling-v2-1-master-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["singleImage","text"],"audio":true},{"name":"kling-v2-5-turbo std 5s","type":"video","modelName":"kling-v2-5-turbo-std-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["text","singleImage"],"audio":true},{"name":"kling-v2-5-turbo std 10s","type":"video","modelName":"kling-v2-5-turbo-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["text","singleImage"],"audio":true},{"name":"kling-v2-5-turbo pro 5s","type":"video","modelName":"kling-v2-5-turbo-pro-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["text","singleImage","startEndRequired"],"audio":true},{"name":"kling-v2-5-turbo pro 10s","type":"video","modelName":"kling-v2-5-turbo-pro-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["text","singleImage","startEndRequired"],"audio":true},{"name":"kling-v2-6 std 5s","type":"video","modelName":"kling-v2-6-std-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["text","singleImage"],"audio":false},{"name":"kling-v2-6 std 10s","type":"video","modelName":"kling-v2-6-std-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["text","singleImage"],"audio":false},{"name":"kling-v2-6 pro 5s","type":"video","modelName":"kling-v2-6-pro-5s","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["text","singleImage","startEndRequired"],"audio":false},{"name":"kling-v2-6 pro 10s","type":"video","modelName":"kling-v2-6-pro-10s","durationResolutionMap":[{"duration":[10],"resolution":["1080p"]}],"mode":["text","singleImage","startEndRequired"],"audio":false},{"name":"kling-v3-omni std","type":"video","modelName":"kling-v3-omni-std","durationResolutionMap":[{"duration":[3,4,5,6,7,8,9,10,11,12,13,14,15],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":false},{"name":"kling-v3-omni pro","type":"video","modelName":"kling-v3-omni-pro","durationResolutionMap":[{"duration":[3,4,5,6,7,8,9,10,11,12,13,14,15],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":false}]', + code: '// ==================== 类型定义 ====================\r\n\r\n// 文本模型\r\ninterface TextModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "text";\r\n think: boolean; // 前端显示用\r\n}\r\n\r\n// 图像模型\r\ninterface ImageModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "image";\r\n mode: ("text" | "singleImage" | "multiReference")[];\r\n}\r\n// 视频模型\r\ninterface VideoModel {\r\n name: string; // 显示名称\r\n modelName: string; //全局唯一\r\n type: "video";\r\n mode: (\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[]\r\n )[]; // 混合参考\r\n audio: "optional" | false | true; // 音频配置\r\n durationResolutionMap: { duration: number[]; resolution: string[] }[];\r\n}\r\n\r\ninterface TTSModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "tts";\r\n voices: {\r\n title: string; //显示名称\r\n voice: string; //说话人\r\n }[];\r\n}\r\n// 供应商配置\r\ninterface VendorConfig {\r\n id: string; //供应商唯一标识,必须全局唯一\r\n author: string;\r\n description?: string; //md5格式\r\n name: string;\r\n icon?: string; //仅支持base64格式\r\n inputs: {\r\n key: string;\r\n label: string;\r\n type: "text" | "password" | "url";\r\n required: boolean;\r\n placeholder?: string;\r\n }[];\r\n inputValues: Record;\r\n models: (TextModel | ImageModel | VideoModel)[];\r\n}\r\n// ==================== 供应商数据 ====================\r\nconst KLINGAI_API_URL = "https://api-beijing.klingai.com";\r\nconst vendor: VendorConfig = {\r\n id: "klingai",\r\n author: "klingai",\r\n description:\r\n "可灵AI新一代AI创意生产力工具,基于快手大模型团队自研的 图像生成@可图大模型 和 视频生成@可灵大模型 技术,提供丰富的AI图片、AI视频及相关可控编辑能力。https://app.klingai.com/cn/",\r\n name: "可灵AI",\r\n inputs: [\r\n { key: "apiKey", label: "accsseKey", type: "password", required: true, placeholder: "请到可灵官方申请" },\r\n { key: "sk", label: "SecretKey", type: "password", required: true, placeholder: "请到可灵官方申请" },\r\n ],\r\n inputValues: {\r\n apiKey: "",\r\n sk: "",\r\n },\r\n models: [\r\n {\r\n name: "kling-video-o1 标准版",\r\n type: "video",\r\n modelName: "kling-video-o1-std",\r\n durationResolutionMap: [{ duration: [5, 10], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["text", "startEndRequired"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-video-o1 pro",\r\n type: "video",\r\n modelName: "kling-video-o1-pro",\r\n durationResolutionMap: [{ duration: [5, 10], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v3-omni std",\r\n type: "video",\r\n modelName: "kling-v3-omni-std",\r\n durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-v3-omni pro",\r\n type: "video",\r\n modelName: "kling-v3-omni-pro",\r\n durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1 std 5s",\r\n type: "video",\r\n modelName: "kling-v1-std-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["720p"] }],\r\n mode: ["singleImage", "text", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1 std 10s",\r\n type: "video",\r\n modelName: "kling-v1-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["720p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1 pro 5s",\r\n type: "video",\r\n modelName: "kling-v1-pro-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["720p"] }],\r\n mode: ["singleImage", "text", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1 pro 10s",\r\n type: "video",\r\n modelName: "kling-v1-pro-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["720p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-5 std 5s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["720p"] }],\r\n mode: ["singleImage"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-5 std 10s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["720p"] }],\r\n mode: ["singleImage"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-5 pro 5s",\r\n type: "video",\r\n modelName: "kling-v1-5-pro-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "startFrameOptional"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-5 pro 10s",\r\n type: "video",\r\n modelName: "kling-v1-5-pro-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "startFrameOptional"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-6 std 5s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["720p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-6 std 10s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["720p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-6 pro 5s",\r\n type: "video",\r\n modelName: "kling-v1-5-pro-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["singleImage", "text", "startEndRequired", "startFrameOptional"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v1-6 pro 10s",\r\n type: "video",\r\n modelName: "kling-v1-5-pro-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["singleImage", "text", "startEndRequired", "startFrameOptional"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-master 5s",\r\n type: "video",\r\n modelName: "kling-v2-master-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["720p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-master 10s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["720p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-1 std 5s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["720p"] }],\r\n mode: ["singleImage"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-1 std 10s",\r\n type: "video",\r\n modelName: "kling-v1-5-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["720p"] }],\r\n mode: ["singleImage"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-1 pro 5s",\r\n type: "video",\r\n modelName: "kling-v2-1-pro-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-1 pro 10s",\r\n type: "video",\r\n modelName: "kling-v2-1-pro-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-1-master 5s",\r\n type: "video",\r\n modelName: "kling-v2-1-master-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-1-master 10s",\r\n type: "video",\r\n modelName: "kling-v2-1-master-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["singleImage", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-5-turbo std 5s",\r\n type: "video",\r\n modelName: "kling-v2-5-turbo-std-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-5-turbo std 10s",\r\n type: "video",\r\n modelName: "kling-v2-5-turbo-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-5-turbo pro 5s",\r\n type: "video",\r\n modelName: "kling-v2-5-turbo-pro-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-5-turbo pro 10s",\r\n type: "video",\r\n modelName: "kling-v2-5-turbo-pro-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "kling-v2-6 std 5s",\r\n type: "video",\r\n modelName: "kling-v2-6-std-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-v2-6 std 10s",\r\n type: "video",\r\n modelName: "kling-v2-6-std-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-v2-6 pro 5s",\r\n type: "video",\r\n modelName: "kling-v2-6-pro-5s",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage", "startEndRequired"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-v2-6 pro 10s",\r\n type: "video",\r\n modelName: "kling-v2-6-pro-10s",\r\n durationResolutionMap: [{ duration: [10], resolution: ["1080p"] }],\r\n mode: ["text", "singleImage", "startEndRequired"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-v3-omni std",\r\n type: "video",\r\n modelName: "kling-v3-omni-std",\r\n durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: false,\r\n },\r\n {\r\n name: "kling-v3-omni pro",\r\n type: "video",\r\n modelName: "kling-v3-omni-pro",\r\n durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: false,\r\n },\r\n ],\r\n};\r\nexports.vendor = vendor;\r\n\r\n// ==================== 全局工具函数 ====================\r\n//Axios实例\r\n//压缩图片大小(1MB = 1 * 1024 * 1024)\r\ndeclare const zipImage: (completeBase64: string, size: number) => Promise;\r\n//压缩图片分辨率\r\ndeclare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise;\r\n//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb\r\ndeclare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise;\r\n//Url转Base64\r\ndeclare const urlToBase64: (url: string) => Promise;\r\n//轮询函数\r\ndeclare const pollTask: (\r\n fn: () => Promise<{ completed: boolean; data?: string | []; error?: string }>,\r\n interval?: number,\r\n timeout?: number,\r\n) => Promise<{ completed: boolean; data?: string; error?: string }>;\r\n\r\ndeclare const JWT: any;\r\n// ==================== 适配器函数 ====================\r\n\r\nfunction getToken() {\r\n const headers = {\r\n alg: "HS256",\r\n typ: "JWT",\r\n };\r\n const payload = {\r\n iss: vendor.inputValues.ak,\r\n exp: Date.now() + 1800000,\r\n nbf: Date.now() - 5000,\r\n };\r\n let token = jwt.sign(payload, vendor.inputValues.sk, headers);\r\n return token;\r\n}\r\n\r\n// 文本请求函数\r\nconst textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {\r\n throw new Error("可灵暂未提供文本大模型");\r\n};\r\nexports.textRequest = textRequest;\r\n\r\n//图片请求函数\r\ninterface ImageConfig {\r\n prompt: string; //图片提示词\r\n imageBase64: string[]; //输入的图片提示词\r\n size: "1K" | "2K" | "4K"; // 图片尺寸\r\n aspectRatio: `${number}:${number}`; // 长宽比\r\n}\r\nconst imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const token = getToken();\r\n\r\n const size = imageConfig.size === "1K" ? "2K" : imageConfig.size;\r\n const sizeMap: Record> = {\r\n "16:9": {\r\n "1k": "1920x1080",\r\n "2K": "2848x1600",\r\n "4K": "4096x2304",\r\n },\r\n "9:16": {\r\n "1k": "1920x1080",\r\n "2K": "1600x2848",\r\n "4K": "2304x4096",\r\n },\r\n };\r\n\r\n const body: Record = {\r\n model_name: imageModel.modelName,\r\n prompt: imageConfig.prompt,\r\n aspect_ratio: sizeMap[imageConfig.aspectRatio][size],\r\n seed: 0,\r\n resolution: size,\r\n ...(imageConfig.imageBase64 && { image: imageConfig.imageBase64 }),\r\n };\r\n\r\n const createImageUrl = KLINGAI_API_URL + "/v1/images/omni-image";\r\n const response = await fetch(createImageUrl, {\r\n method: "POST",\r\n headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },\r\n body: JSON.stringify(body),\r\n });\r\n if (!response.ok) {\r\n const errorText = await response.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n const data = await response.json();\r\n const checkUrl = KLINGAI_API_URL + "/v1/images/omni-image/{id}";\r\n const res = await checkKlingTaskResult(data.data.task_id, checkUrl);\r\n const resData = JSON.parse(JSON.stringify(res.data));\r\n return resData?.task_result.images[0].url;\r\n};\r\nexports.imageRequest = imageRequest;\r\n\r\ninterface VideoConfig {\r\n duration: number;\r\n resolution: string;\r\n aspectRatio: "16:9" | "9:16";\r\n prompt: string;\r\n imageBase64?: string[];\r\n audio?: boolean;\r\n mode:\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("video" | "image" | "audio" | "text")[]; // 混合参考\r\n}\r\n\r\n// 检查生成物结果\r\nconst checkKlingTaskResult = async (taskId: string, checkUrl: string) => {\r\n const token = getToken();\r\n const res = await pollTask(async () => {\r\n const queryResponse = await fetch(checkUrl.replace("{id}", taskId), {\r\n method: "GET",\r\n headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },\r\n });\r\n if (!queryResponse.ok) {\r\n const errorText = await queryResponse.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", queryResponse.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${queryResponse.status}, 错误信息: ${errorText}`);\r\n }\r\n const queryData = await queryResponse.json();\r\n const status = queryData?.state ?? queryData?.data?.state;\r\n const fail_reason = queryData?.data?.err_code ?? queryData?.data;\r\n switch (status) {\r\n case "completed":\r\n case "SUCCESS":\r\n case "success":\r\n return { completed: true, data: queryData.data };\r\n case "FAILURE":\r\n case "failed":\r\n return { completed: false, error: fail_reason || "生成失败" };\r\n default:\r\n return { completed: false };\r\n }\r\n });\r\n if (res.error) throw new Error(res.error);\r\n return res;\r\n};\r\n\r\nconst videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const token = getToken();\r\n\r\n //公共请求参数\r\n const publicBody = {\r\n model_name: videoModel.modelName,\r\n ...(videoConfig.imageBase64 && videoConfig.imageBase64.length ? { images: videoConfig.imageBase64 } : {}),\r\n prompt: videoConfig.prompt,\r\n size: videoConfig.resolution,\r\n duration: videoConfig.duration,\r\n };\r\n\r\n const requestUrl = KLINGAI_API_URL + "/v1/videos/omni-video";\r\n const response = await fetch(requestUrl, {\r\n method: "POST",\r\n headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },\r\n body: JSON.stringify(publicBody),\r\n });\r\n if (!response.ok) {\r\n const errorText = await response.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n const data = await response.json();\r\n\r\n const checkUrl = KLINGAI_API_URL + "/v1/videos/omni-video/{id}";\r\n const result = await checkKlingTaskResult(data.data.task_id, checkUrl);\r\n const resData = JSON.parse(JSON.stringify(result.data));\r\n return resData?.task_result.videos;\r\n};\r\nexports.videoRequest = videoRequest;\r\n\r\ninterface TTSConfig {\r\n text: string;\r\n voice: string;\r\n speechRate: number;\r\n pitchRate: number;\r\n volume: number;\r\n}\r\nconst ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {\r\n throw new Error("可灵 暂不支持语音合成(TTS)");\r\n};\r\n', + enable: 0, + createTime: 1775155145953, + }, + { + id: "vidu", + author: "搬砖的Coder", + description: + "Vidu 是由生数科技联合清华大学正式发布的中国首个长时长、高一致性、高动态性视频大模型。Vidu 在语义理解、推理速度、动态幅度等方面具备领先优势,并上线了全球首个“多主体参考”功能,突破视频模型一致性生成难题,开启了视觉上下文时代", + name: "Vidu 开放平台", + icon: "", + inputs: + '[{"key":"apiKey","label":"API密钥","type":"password","required":true,"placeholder":"请到Vidu官方申请"},{"key":"baseUrl","label":"接口路径","type":"url","required":false,"placeholder":"https://api.vidu.cn/ent/v2"}]', + inputValues: '{"apiKey":"","baseUrl":"https://api.vidu.cn/ent/v2"}', + models: + '[{"name":"ViduQ3 turbo","type":"video","modelName":"ViduQ3-turbo","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":true},{"name":"ViduQ3 pro","type":"video","modelName":"ViduQ3-pro","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":true},{"name":"ViduQ2 pro fast","type":"video","modelName":"ViduQ2-pro-fast","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10],"resolution":["720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"viduQ2 turbo","type":"video","modelName":"ViduQ2-turbo","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"ViduQ2 pro","type":"video","modelName":"ViduQ2-pro","durationResolutionMap":[{"duration":[1,2,3,4,5,6,7,8,9,10],"resolution":["540p","720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"ViduQ2","type":"video","modelName":"ViduQ2","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["text"],"audio":true},{"name":"ViduQ1","type":"video","modelName":"ViduQ1","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["singleImage","startEndRequired","text"],"audio":true},{"name":"ViduQ1 classic","type":"video","modelName":"viduQ1-classic","durationResolutionMap":[{"duration":[5],"resolution":["1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"Vidu2.0","type":"video","modelName":"vidu2.0","durationResolutionMap":[{"duration":[4,8],"resolution":["360p","720p","1080p"]}],"mode":["singleImage","startEndRequired"],"audio":true},{"name":"viduq1 for image","type":"image","modelName":"viduq1","mode":["text"]},{"name":"viduq2 for image","type":"image","modelName":"viduq2","mode":["text","singleImage","multiReference"]}]', + code: '//如需遥测AI请使用在toonflow安装目录运行npx @ai-sdk/devtools (要求在其他设置中打开遥测功能,且toonflow有权限在安装目录创建.devtools文件夹)\r\n// ==================== 类型定义 ====================\r\n// 文本模型\r\ninterface TextModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "text";\r\n think: boolean; // 前端显示用\r\n}\r\n\r\n// 图像模型\r\ninterface ImageModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "image";\r\n mode: ("text" | "singleImage" | "multiReference")[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n}\r\n// 视频模型\r\ninterface VideoModel {\r\n name: string; // 显示名称\r\n modelName: string; //全局唯一\r\n type: "video";\r\n mode: (\r\n | "singleImage" // 单图\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("videoReference" | "imageReference" | "audioReference" | "textReference")[] // 混合参考\r\n )[];\r\n associationSkills?: string; // 关联技能,多个技能用逗号分隔\r\n audio: "optional" | false | true; // 音频配置\r\n durationResolutionMap: { duration: number[]; resolution: string[] }[];\r\n}\r\n\r\ninterface TTSModel {\r\n name: string; // 显示名称\r\n modelName: string;\r\n type: "tts";\r\n voices: {\r\n title: string; //显示名称\r\n voice: string; //说话人\r\n }[];\r\n}\r\n// 供应商配置\r\ninterface VendorConfig {\r\n id: string; //供应商唯一标识,必须全局唯一\r\n author: string;\r\n description?: string; //md5格式\r\n name: string;\r\n icon?: string; //仅支持base64格式\r\n inputs: {\r\n key: string;\r\n label: string;\r\n type: "text" | "password" | "url";\r\n required: boolean;\r\n placeholder?: string;\r\n }[];\r\n inputValues: Record;\r\n models: (TextModel | ImageModel | VideoModel)[];\r\n}\r\n// ==================== 全局工具函数 ====================\r\n//Axios实例\r\n//压缩图片大小(1MB = 1 * 1024 * 1024)\r\ndeclare const zipImage: (completeBase64: string, size: number) => Promise;\r\n//压缩图片分辨率\r\ndeclare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise;\r\n//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb\r\ndeclare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise;\r\n//Url转Base64\r\ndeclare const urlToBase64: (url: string) => Promise;\r\n//轮询函数\r\ndeclare const pollTask: (\r\n fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,\r\n interval?: number,\r\n timeout?: number,\r\n) => Promise<{ completed: boolean; data?: string; error?: string }>;\r\ndeclare const axios: any;\r\ndeclare const createOpenAI: any;\r\ndeclare const createDeepSeek: any;\r\ndeclare const createZhipu: any;\r\ndeclare const createQwen: any;\r\ndeclare const createAnthropic: any;\r\ndeclare const createOpenAICompatible: any;\r\ndeclare const createXai: any;\r\ndeclare const createMinimax: any;\r\ndeclare const createGoogleGenerativeAI: any;\r\ndeclare const logger: (logstring: string) => void;\r\ndeclare const jsonwebtoken: any;\r\n// ==================== 供应商数据 ====================\r\nconst vendor: VendorConfig = {\r\n id: "vidu",\r\n author: "搬砖的Coder",\r\n description:\r\n "Vidu 是由生数科技联合清华大学正式发布的中国首个长时长、高一致性、高动态性视频大模型。Vidu 在语义理解、推理速度、动态幅度等方面具备领先优势,并上线了全球首个“多主体参考”功能,突破视频模型一致性生成难题,开启了视觉上下文时代",\r\n name: "Vidu 开放平台",\r\n inputs: [\r\n { key: "apiKey", label: "API密钥", type: "password", required: true, placeholder: "请到Vidu官方申请" },\r\n { key: "baseUrl", label: "接口路径", type: "url", required: false, placeholder: "https://api.vidu.cn/ent/v2" },\r\n ],\r\n inputValues: {\r\n apiKey: "",\r\n baseUrl: "https://api.vidu.cn/ent/v2",\r\n },\r\n models: [\r\n {\r\n name: "ViduQ3 turbo",\r\n type: "video",\r\n modelName: "ViduQ3-turbo",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "ViduQ3 pro",\r\n type: "video",\r\n modelName: "ViduQ3-pro",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "ViduQ2 pro fast",\r\n type: "video",\r\n modelName: "ViduQ2-pro-fast",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "viduQ2 turbo",\r\n type: "video",\r\n modelName: "ViduQ2-turbo",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "ViduQ2 pro",\r\n type: "video",\r\n modelName: "ViduQ2-pro",\r\n durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"], //参考生视频无有效设置值\r\n audio: true,\r\n },\r\n {\r\n name: "ViduQ2",\r\n type: "video",\r\n modelName: "ViduQ2",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["text"],\r\n audio: true,\r\n },\r\n {\r\n name: "ViduQ1",\r\n type: "video",\r\n modelName: "ViduQ1",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["singleImage", "startEndRequired", "text"],\r\n audio: true,\r\n },\r\n {\r\n name: "ViduQ1 classic",\r\n type: "video",\r\n modelName: "viduQ1-classic",\r\n durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "Vidu2.0",\r\n type: "video",\r\n modelName: "vidu2.0",\r\n durationResolutionMap: [{ duration: [4, 8], resolution: ["360p", "720p", "1080p"] }],\r\n mode: ["singleImage", "startEndRequired"],\r\n audio: true,\r\n },\r\n {\r\n name: "viduq1 for image",\r\n type: "image",\r\n modelName: "viduq1",\r\n mode: ["text"],\r\n },\r\n {\r\n name: "viduq2 for image",\r\n type: "image",\r\n modelName: "viduq2",\r\n mode: ["text", "singleImage", "multiReference"],\r\n },\r\n ],\r\n};\r\nexports.vendor = vendor;\r\n\r\n// ==================== 适配器函数 ====================\r\n\r\n// 文本请求函数\r\nconst textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {\r\n throw new Error("当前供应商仅支持视频大模型,谢谢!");\r\n};\r\nexports.textRequest = textRequest;\r\n\r\n//图片请求函数\r\ninterface ImageConfig {\r\n prompt: string; //图片提示词\r\n imageBase64: string[]; //输入的图片提示词\r\n size: "1K" | "2K" | "4K"; // 图片尺寸\r\n aspectRatio: `${number}:${number}`; // 长宽比\r\n}\r\nconst imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Token ", "");\r\n\r\n const size = imageConfig.size === "1K" ? "2K" : imageConfig.size;\r\n const sizeMap: Record> = {\r\n "16:9": {\r\n "1k": "1920x1080",\r\n "2K": "2848x1600",\r\n "4K": "4096x2304",\r\n },\r\n "9:16": {\r\n "1k": "1920x1080",\r\n "2K": "1600x2848",\r\n "4K": "2304x4096",\r\n },\r\n };\r\n\r\n const body: Record = {\r\n model: imageModel.modelName,\r\n prompt: imageConfig.prompt,\r\n aspect_ratio: sizeMap[imageConfig.aspectRatio][size],\r\n seed: 0,\r\n resolution: size,\r\n ...(imageConfig.imageBase64 && { image: imageConfig.imageBase64 }),\r\n };\r\n\r\n const createImageUrl = vendor.inputValues.baseUrl + "/reference2image";\r\n const response = await fetch(createImageUrl, {\r\n method: "POST",\r\n headers: { Authorization: `Token ${apiKey}`, "Content-Type": "application/json" },\r\n body: JSON.stringify(body),\r\n });\r\n if (!response.ok) {\r\n const errorText = await response.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n const data = await response.json();\r\n const res = await checkTaskResult(data.task_id);\r\n if (!res.data) {\r\n throw new Error("图片未能生成");\r\n }\r\n const list = JSON.parse(JSON.stringify(res.data));\r\n return list[0].url;\r\n};\r\nexports.imageRequest = imageRequest;\r\n\r\ninterface VideoConfig {\r\n duration: number;\r\n resolution: string;\r\n aspectRatio: "16:9" | "9:16";\r\n prompt: string;\r\n imageBase64?: string[];\r\n audio?: boolean;\r\n mode:\r\n | "singleImage" // 单图\r\n | "multiImage" // 多图模式\r\n | "gridImage" // 网格单图(传入一张图片,但该图片是网格图)\r\n | "startEndRequired" // 首尾帧(两张都得有)\r\n | "endFrameOptional" // 首尾帧(尾帧可选)\r\n | "startFrameOptional" // 首尾帧(首帧可选)\r\n | "text" // 文本生视频\r\n | ("video" | "image" | "audio" | "text")[]; // 混合参考\r\n}\r\n\r\n// 构建 各个平台的metadata参数\r\n\r\nconst buildViduMetadata = (videoConfig: VideoConfig) => ({\r\n aspect_ratio: videoConfig.aspectRatio,\r\n audio: videoConfig.audio ?? false,\r\n off_peak: false,\r\n});\r\n\r\ntype MetadataBuilder = (config: VideoConfig) => Record;\r\nconst METADATA_BUILDERS: Array<[string, MetadataBuilder]> = [["vidu", buildViduMetadata]];\r\nconst buildModelMetadata = (modelName: string, videoConfig: VideoConfig) => {\r\n const lowerName = modelName.toLowerCase();\r\n const match = METADATA_BUILDERS.find(([key]) => lowerName.includes(key));\r\n return match ? match[1](videoConfig) : {};\r\n};\r\n// 检查生成物结果\r\nconst checkTaskResult = async (taskId: string) => {\r\n const queryUrl = vendor.inputValues.baseUrl + "/tasks/{id}/creations";\r\n const apiKey = vendor.inputValues.apiKey;\r\n const res = await pollTask(async () => {\r\n const queryResponse = await fetch(queryUrl.replace("{id}", taskId), {\r\n method: "GET",\r\n headers: { Authorization: `Token ${apiKey}`, "Content-Type": "application/json" },\r\n });\r\n if (!queryResponse.ok) {\r\n const errorText = await queryResponse.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", queryResponse.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${queryResponse.status}, 错误信息: ${errorText}`);\r\n }\r\n const queryData = await queryResponse.json();\r\n const status = queryData?.state ?? queryData?.data?.state;\r\n const fail_reason = queryData?.data?.err_code ?? queryData?.data;\r\n switch (status) {\r\n case "completed":\r\n case "SUCCESS":\r\n case "success":\r\n return { completed: true, data: queryData.creations };\r\n case "FAILURE":\r\n case "failed":\r\n return { completed: false, error: fail_reason || "生成失败" };\r\n default:\r\n return { completed: false };\r\n }\r\n });\r\n if (res.error) throw new Error(res.error);\r\n return res;\r\n};\r\n\r\nconst videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {\r\n if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");\r\n const apiKey = vendor.inputValues.apiKey.replace("Token ", "");\r\n\r\n // 构建每个模型对应的附加参数\r\n const metadata = buildModelMetadata(videoModel.modelName, videoConfig);\r\n\r\n //公共请求参数\r\n const publicBody = {\r\n model: videoModel.modelName,\r\n ...(videoConfig.imageBase64 && videoConfig.imageBase64.length ? { images: videoConfig.imageBase64 } : {}),\r\n prompt: videoConfig.prompt,\r\n size: videoConfig.resolution,\r\n duration: videoConfig.duration,\r\n metadata: metadata,\r\n };\r\n\r\n const requestUrl = vendor.inputValues.baseUrl + "/start-end2video";\r\n const response = await fetch(requestUrl, {\r\n method: "POST",\r\n headers: { Authorization: `Token ${apiKey}`, "Content-Type": "application/json" },\r\n body: JSON.stringify(publicBody),\r\n });\r\n if (!response.ok) {\r\n const errorText = await response.text(); // 获取错误信息\r\n console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);\r\n throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);\r\n }\r\n const data = await response.json();\r\n const taskId = data.id;\r\n const result = await checkTaskResult(taskId);\r\n return result.data;\r\n};\r\nexports.videoRequest = videoRequest;\r\n\r\ninterface TTSConfig {\r\n text: string;\r\n voice: string;\r\n speechRate: number;\r\n pitchRate: number;\r\n volume: number;\r\n}\r\nconst ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {\r\n throw new Error("Vidu 暂不支持语音合成(TTS)");\r\n};\r\n', + enable: 0, + createTime: 1775155162784, + }, + ]); + }, }, //图片工作流表 { diff --git a/src/routes/assetsGenerate/batchPolishAssetsPrompt.ts b/src/routes/assetsGenerate/batchPolishAssetsPrompt.ts index 898f3b5..04db8ff 100644 --- a/src/routes/assetsGenerate/batchPolishAssetsPrompt.ts +++ b/src/routes/assetsGenerate/batchPolishAssetsPrompt.ts @@ -83,13 +83,13 @@ export default router.post( }); }); const result: ResultItem[] = Object.values(itemMap); - // 批量更新所有 item 状态为生成中 const assetsIds = items.map((item: { assetsId: number }) => item.assetsId); - await u.db("o_assets").whereIn("id", assetsIds).update({ promptState: "生成中" }); //查询所有资产,用于判断每个资产是否是衍生资产 const assetsDataList = await u.db("o_assets").whereIn("id", assetsIds).select("id", "assetsId"); if (!assetsDataList || assetsDataList.length === 0) return res.status(500).send(error("资产不存在")); const assetsDataMap = new Map(assetsDataList.map((a: any) => [a.id, a])); + // 所有前置检测通过后,再批量更新状态为生成中 + await u.db("o_assets").whereIn("id", assetsIds).update({ promptState: "生成中" }); const getTypeConfig = ( isDerivative: boolean, @@ -128,7 +128,10 @@ export default router.post( if (!config) return; //获取到视觉手册 const visualManual = await u.getArtPrompt(project.artStyle as string, "art_skills", config.visualManual); - if (!visualManual) return res.status(500).send(error("视觉手册未定义")); + if (!visualManual) { + await u.db("o_assets").where("id", item.assetsId).update({ promptState: "生成失败", promptErrorReason: "视觉手册未定义" }); + return; + } findItemByName(result, item.name, config.itemType); const systemPrompt = visualManual; try { diff --git a/src/routes/assetsGenerate/polishAssetsPrompt.ts b/src/routes/assetsGenerate/polishAssetsPrompt.ts index d03ee2b..e1914bb 100644 --- a/src/routes/assetsGenerate/polishAssetsPrompt.ts +++ b/src/routes/assetsGenerate/polishAssetsPrompt.ts @@ -108,8 +108,6 @@ export default router.post( if (!config.visualManual) return res.status(500).send(error("视觉手册未定义")); //获取到视觉手册 const visualManual = await u.getArtPrompt(project.artStyle as string, "art_skills", config.visualManual); - console.log("%c Line:111 🍬 visualManual", "background:#6ec1c2", visualManual); - return if (!visualManual) return res.status(500).send(error("视觉手册未定义")); findItemByName(result, name, config.itemType); const systemPrompt = visualManual; diff --git a/src/routes/production/assets/batchGenerateAssetsImage.ts b/src/routes/production/assets/batchGenerateAssetsImage.ts index 40b13da..d7ec5d8 100644 --- a/src/routes/production/assets/batchGenerateAssetsImage.ts +++ b/src/routes/production/assets/batchGenerateAssetsImage.ts @@ -27,7 +27,7 @@ export default router.post( .db("o_assets") .leftJoin("o_image", "o_assets.imageId", "o_image.id") .whereIn("o_assets.id", parentIds as number[]) - .select("o_assets.id", "o_image.filePath"); + .select("o_assets.id", "o_image.filePath", "o_assets.describe"); const assetsSrcArr = await Promise.all( parentAssetsData.map(async (item) => { return { @@ -36,6 +36,12 @@ export default router.post( }; }), ); + assetsDataArr.forEach((i: any) => { + const parent = parentAssetsData.find((item) => item.id === i.assetsId); + if (parent) { + i.parentDescribe = parent.describe; + } + }); const imageUrlRecord: Record = {}; assetsSrcArr.forEach((item) => { imageUrlRecord[item.id] = item.src; @@ -70,7 +76,7 @@ export default router.post( const imageData: { id: number; state: string; src: string }[] = []; res.status(200).send(success("开始生成资产图片")); - const generateSingleAsset = async (item: (typeof assetsDataArr)[number]) => { + const generateSingleAsset = async (item: any) => { const imageId = imageIdMap[item.id!]; const typeConfig = promptRecord[item.type!] || promptRecord["role"]; @@ -79,7 +85,9 @@ export default router.post( messages: [ { role: "user", - content: `资产描述: ${item.describe || "无详细描述"}`, + content: ` + 父级资产描述: ${item.parentDescribe || "无详细描述"} + 当前资产描述: ${item.describe || "无详细描述"}`, }, ], }); diff --git a/src/routes/setting/agentDeploy/agentSetKey.ts b/src/routes/setting/agentDeploy/agentSetKey.ts index 8514c0c..efb62e9 100644 --- a/src/routes/setting/agentDeploy/agentSetKey.ts +++ b/src/routes/setting/agentDeploy/agentSetKey.ts @@ -24,7 +24,7 @@ export default router.post( inputValues: JSON.stringify(inputValue), }); try { - const resText = await u.Ai.Text(`toonflow:gpt-4.1`).invoke({ + const resText = await u.Ai.Text(`toonflow:claude-haiku-4-5-20251001`).invoke({ prompt: "1+1等于几?", }); if (resText.text) { @@ -46,6 +46,7 @@ export default router.post( res.status(200).send(success("一键填入成功")); } } catch (err) { + console.error(err); inputValue.apiKey = ""; await u .db("o_vendorConfig") diff --git a/src/routes/setting/vendorConfig/modelTest.ts b/src/routes/setting/vendorConfig/modelTest.ts index f5e6f0b..34ab8d8 100644 --- a/src/routes/setting/vendorConfig/modelTest.ts +++ b/src/routes/setting/vendorConfig/modelTest.ts @@ -76,7 +76,7 @@ export default router.post( for await (const chunk of textStream) { fullResponse += chunk; } - if(!fullResponse) return res.status(500).send(error("模型未返回结果")); + if (!fullResponse) return res.status(500).send(error("模型未返回结果")); res.status(200).send(success(fullResponse)); } else { const aiTypeFn = { @@ -87,11 +87,11 @@ export default router.post( ...reqConfig.modelData, }); await reqFn.save(type == "video" ? "test.mp4" : "testImage.jpg"); - const resultUrl = await u.oss.getFileUrl(type == "video" ? "test.mp4" : "testImage.jpg"); res.status(200).send(success(resultUrl)); } } catch (err) { + console.error(err); const msg = u.error(err).message; console.error(msg); res.status(500).send(error(msg)); diff --git a/src/routes/test/test.ts b/src/routes/test/test.ts index 58de179..a551c40 100644 --- a/src/routes/test/test.ts +++ b/src/routes/test/test.ts @@ -1,36 +1,12 @@ import express from "express"; const router = express.Router(); import u from "@/utils"; -import fs from "fs"; -import Memory from "@/utils/agent/memory"; - - -function buildMemPrompt(mem: Awaited>): string { - let memoryContext = ""; - if (mem.rag.length) { - memoryContext += `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`; - } - if (mem.summaries.length) { - if (memoryContext) memoryContext += "\n\n"; - memoryContext += `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`; - } - if (mem.shortTerm.length) { - if (memoryContext) memoryContext += "\n\n"; - memoryContext += `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`; - } - return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; -} +import fs from 'fs'; export default router.get("/", async (req, res) => { + return res.send("ok"); + const test = await u.db("o_vendorConfig").select("*"); + fs.writeFileSync("test.json", JSON.stringify(test, null, 2)); - const isolationKey = "1:productionAgent:1"; - const input = "你好" - - const memory = new Memory("productionAgent", isolationKey); - await memory.add("user", input); - - - const mem = buildMemPrompt(await memory.get(input)); - - res.send(mem); + res.send(test); });