更新供应商函数

This commit is contained in:
ACT丶流星雨 2026-04-10 11:53:54 +08:00
parent cacadd94c4
commit a58e5a3ab8
10 changed files with 2685 additions and 93 deletions

655
data/vendor/klingai.ts vendored Normal file
View File

@ -0,0 +1,655 @@
/**
* Toonflow AI供应商模板 - AI
* @version 2.0
*/
// ============================================================
// 类型定义
// ============================================================
type VideoMode =
| "singleImage"
| "startEndRequired"
| "endFrameOptional"
| "startFrameOptional"
| "text"
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[];
interface TextModel {
name: string;
modelName: string;
type: "text";
think: boolean;
}
interface ImageModel {
name: string;
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string;
}
interface VideoModel {
name: string;
modelName: string;
type: "video";
mode: VideoMode[];
associationSkills?: string;
audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string;
modelName: string;
type: "tts";
voices: { title: string; voice: string }[];
}
interface VendorConfig {
id: string;
version: string;
name: string;
author: string;
description?: string;
icon?: string;
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[];
}
type ReferenceList =
| ({ type: "image" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }))
| ({ type: "audio" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }))
| ({ type: "video" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }));
interface ImageConfig {
prompt: string;
referenceList?: Extract<ReferenceList, { type: "image" }>[];
size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`;
}
interface VideoConfig {
duration: number;
resolution: string;
aspectRatio: "16:9" | "9:16";
prompt: string;
referenceList?: ReferenceList[];
audio?: boolean;
mode: VideoMode[];
}
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
referenceList?: Extract<ReferenceList, { type: "audio" }>[];
}
interface PollResult {
completed: boolean;
data?: string;
error?: string;
}
// ============================================================
// 全局声明
// ============================================================
declare const axios: any;
declare const logger: (msg: string) => void;
declare const jsonwebtoken: any;
declare const zipImage: (base64: string, size: number) => Promise<string>;
declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>;
declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>;
declare const urlToBase64: (url: string) => Promise<string>;
declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>;
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const exports: {
vendor: VendorConfig;
textRequest: (m: TextModel) => any;
uploadReference: (base64: string, fileType: "image" | "audio" | "video") => Promise<ReferenceList>;
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>;
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>;
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>;
checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>;
updateVendor?: () => Promise<string>;
};
// ============================================================
// 供应商配置
// ============================================================
const vendor: VendorConfig = {
id: "klingai",
version: "2.0",
author: "Toonflow",
name: "可灵AI",
description:
"## 可灵AI视频生成\n\n支持可灵全系列视频模型包括 kling-video-o1、kling-v3-omni、kling-v3、kling-v2-6、kling-v2-5-turbo、kling-v2-1、kling-v2-master、kling-v1-6、kling-v1-5、kling-v1 等。\n\n需要在[可灵AI开放平台](https://klingai.com)获取 Access Key 和 Secret Key。",
inputs: [
{ key: "accessKey", label: "Access Key", type: "password", required: true, placeholder: "请输入可灵AI的Access Key" },
{ key: "secretKey", label: "Secret Key", type: "password", required: true, placeholder: "请输入可灵AI的Secret Key" },
{ key: "baseUrl", label: "请求地址", type: "url", required: false, placeholder: "默认https://api-beijing.klingai.com" },
],
inputValues: { accessKey: "", secretKey: "", baseUrl: "https://api-beijing.klingai.com" },
models: [
// kling-video-o1 (Omni)
{
name: "kling-video-o1 标准",
modelName: "kling-video-o1:std",
type: "video",
mode: ["text", "singleImage", "startEndRequired", ["imageReference:7", "videoReference:1"]],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
{
name: "kling-video-o1 专家",
modelName: "kling-video-o1:pro",
type: "video",
mode: ["text", "singleImage", "startEndRequired", ["imageReference:7", "videoReference:1"]],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
// kling-v3-omni (Omni)
{
name: "kling-v3-omni 标准",
modelName: "kling-v3-omni:std",
type: "video",
mode: ["text", "singleImage", "startEndRequired", ["imageReference:7", "videoReference:1"]],
audio: false,
durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p"] }],
},
{
name: "kling-v3-omni 专家",
modelName: "kling-v3-omni:pro",
type: "video",
mode: ["text", "singleImage", "startEndRequired", ["imageReference:7", "videoReference:1"]],
audio: false,
durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p"] }],
},
// kling-v3
{
name: "kling-v3 标准",
modelName: "kling-v3:std",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p"] }],
},
{
name: "kling-v3 专家",
modelName: "kling-v3:pro",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [{ duration: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["720p"] }],
},
// kling-v2-6
{
name: "kling-v2-6 标准",
modelName: "kling-v2-6:std",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
{
name: "kling-v2-6 专家",
modelName: "kling-v2-6:pro",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: "optional",
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
// kling-v2-5-turbo
{
name: "kling-v2-5-turbo 标准",
modelName: "kling-v2-5-turbo:std",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
{
name: "kling-v2-5-turbo 专家",
modelName: "kling-v2-5-turbo:pro",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
// kling-v2-1
{
name: "kling-v2-1 标准",
modelName: "kling-v2-1:std",
type: "video",
mode: ["singleImage"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
{
name: "kling-v2-1 专家",
modelName: "kling-v2-1:pro",
type: "video",
mode: ["singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
// kling-v2-1-master
{
name: "kling-v2-1 Master",
modelName: "kling-v2-1-master:pro",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
// kling-v2-master
{
name: "kling-v2 Master",
modelName: "kling-v2-master:pro",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
// kling-v1-6
{
name: "kling-v1-6 标准",
modelName: "kling-v1-6:std",
type: "video",
mode: ["text", "singleImage", ["imageReference:4"]],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
{
name: "kling-v1-6 专家",
modelName: "kling-v1-6:pro",
type: "video",
mode: ["text", "singleImage", "endFrameOptional", ["imageReference:4"]],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
// kling-v1-5
{
name: "kling-v1-5 标准",
modelName: "kling-v1-5:std",
type: "video",
mode: ["singleImage"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
{
name: "kling-v1-5 专家",
modelName: "kling-v1-5:pro",
type: "video",
mode: ["singleImage", "endFrameOptional"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["1080p"] }],
},
// kling-v1
{
name: "kling-v1 标准",
modelName: "kling-v1:std",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
{
name: "kling-v1 专家",
modelName: "kling-v1:pro",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [{ duration: [5, 10], resolution: ["720p"] }],
},
],
};
// ============================================================
// 辅助工具
// ============================================================
/**
* AI的JWT鉴权Token
*/
const generateAuthToken = (): string => {
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: vendor.inputValues.accessKey,
exp: now + 1800,
nbf: now - 5,
};
return jsonwebtoken.sign(payload, vendor.inputValues.secretKey, {
algorithm: "HS256",
header: { alg: "HS256", typ: "JWT" },
});
};
/**
*
*/
const getBaseUrl = (): string => {
return vendor.inputValues.baseUrl || "https://api-beijing.klingai.com";
};
/**
* ReferenceList
* url url base64 base64 data: 前缀
*/
const extractRawBase64 = (ref: ReferenceList): string => {
if (ref.sourceType === "url") {
return ref.url;
}
return ref.base64.replace(/^data:[^;]+;base64,/, "");
};
/**
* ReferenceList base64 url
* omni-video image_url base64 url
*/
const extractImageUrl = (ref: ReferenceList): string => {
if (ref.sourceType === "url") {
return ref.url;
}
return ref.base64.startsWith("data:") ? ref.base64 : `data:image/jpeg;base64,${ref.base64}`;
};
/**
*
*/
const submitAndPoll = async (submitUrl: string, queryUrlBase: string, requestBody: any): Promise<string> => {
const token = generateAuthToken();
logger(`开始提交可灵AI视频生成任务: ${submitUrl}`);
logger(
`请求参数: ${JSON.stringify({
...requestBody,
image: requestBody.image ? "[BASE64]" : undefined,
image_tail: requestBody.image_tail ? "[BASE64]" : undefined,
image_list: requestBody.image_list ? "[IMAGES]" : undefined,
})}`,
);
const submitResp = await axios.post(submitUrl, requestBody, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
if (submitResp.data.code !== 0) {
throw new Error(`提交任务失败: ${submitResp.data.message || JSON.stringify(submitResp.data)}`);
}
const taskId = submitResp.data.data.task_id;
logger(`任务已提交任务ID: ${taskId}`);
const result = await pollTask(
async () => {
const freshToken = generateAuthToken();
const queryResp = await axios.get(`${queryUrlBase}/${taskId}`, {
headers: {
Authorization: `Bearer ${freshToken}`,
},
});
if (queryResp.data.code !== 0) {
return { completed: true, error: `查询任务失败: ${queryResp.data.message}` };
}
const taskData = queryResp.data.data;
const status = taskData.task_status;
logger(`轮询中... 任务状态: ${status}`);
if (status === "succeed") {
const videoUrl = taskData.task_result?.videos?.[0]?.url;
if (!videoUrl) {
return { completed: true, error: "任务完成但未获取到视频URL" };
}
return { completed: true, data: videoUrl };
}
if (status === "failed") {
return { completed: true, error: `视频生成失败: ${taskData.task_status_msg || "未知错误"}` };
}
return { completed: false };
},
5000,
600000,
);
if (result.error) throw new Error(result.error);
logger(`视频生成完成正在转换为Base64...`);
return await urlToBase64(result.data!);
};
// ============================================================
// 适配器函数
// ============================================================
const textRequest = (model: TextModel) => {
throw new Error("可灵AI不支持文本模型");
};
const uploadReference = async (base64: string, fileType: "image" | "audio" | "video"): Promise<ReferenceList> => {
// 可灵AI的接口直接接受 base64压缩图片后原样返回
if (fileType === "image") {
const compressed = await zipImage(base64, 10240);
return { type: "image", sourceType: "base64", base64: compressed };
}
return { type: fileType, sourceType: "base64", base64 } as ReferenceList;
};
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
throw new Error("可灵AI不支持图片模型");
};
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
if (!vendor.inputValues.accessKey) throw new Error("缺少Access Key");
if (!vendor.inputValues.secretKey) throw new Error("缺少Secret Key");
const baseUrl = getBaseUrl();
// 解析 modelName格式kling-video-o1:pro => modelName=kling-video-o1, mode=pro
const colonIdx = model.modelName.indexOf(":");
const modelName = colonIdx > -1 ? model.modelName.substring(0, colonIdx) : model.modelName;
const mode = colonIdx > -1 ? model.modelName.substring(colonIdx + 1) : "pro";
// 判断是否为 Omni 模型
const isOmniModel = modelName === "kling-video-o1" || modelName === "kling-v3-omni";
// 判断当前选中的视频生成模式
const currentMode = config.mode;
const isText = currentMode.includes("text");
const isSingleImage = currentMode.includes("singleImage");
const isStartEndRequired = currentMode.includes("startEndRequired");
const isEndFrameOptional = currentMode.includes("endFrameOptional");
const isStartFrameOptional = currentMode.includes("startFrameOptional");
const hasMultiRef = currentMode.some((m) => Array.isArray(m));
// 提取不同类型的引用
const imageRefs = (config.referenceList || []).filter((r) => r.type === "image");
const videoRefs = (config.referenceList || []).filter((r) => r.type === "video");
// =====================================================
// Omni 模型 —— 使用 /v1/videos/omni-video 接口
// =====================================================
if (isOmniModel) {
const requestBody: any = {
model_name: modelName,
mode: mode,
duration: String(config.duration),
sound: config.audio === true ? "on" : "off",
};
if (config.prompt) {
requestBody.prompt = config.prompt;
}
if (isSingleImage && imageRefs.length > 0) {
const imageUrl = extractImageUrl(imageRefs[0]);
requestBody.image_list = [{ image_url: imageUrl, type: "first_frame" }];
if (!requestBody.prompt) requestBody.prompt = "根据图片生成视频";
} else if (isStartEndRequired && imageRefs.length >= 2) {
const firstUrl = extractImageUrl(imageRefs[0]);
const endUrl = extractImageUrl(imageRefs[1]);
requestBody.image_list = [
{ image_url: firstUrl, type: "first_frame" },
{ image_url: endUrl, type: "end_frame" },
];
if (!requestBody.prompt) requestBody.prompt = "根据首尾帧图片生成过渡视频";
} else if (isEndFrameOptional && imageRefs.length >= 1) {
const firstUrl = extractImageUrl(imageRefs[0]);
requestBody.image_list = [{ image_url: firstUrl, type: "first_frame" }];
if (imageRefs.length >= 2) {
const endUrl = extractImageUrl(imageRefs[1]);
requestBody.image_list.push({ image_url: endUrl, type: "end_frame" });
}
if (!requestBody.prompt) requestBody.prompt = "根据图片生成视频";
} else if (isStartFrameOptional && imageRefs.length >= 1) {
if (imageRefs.length >= 2) {
const firstUrl = extractImageUrl(imageRefs[0]);
const endUrl = extractImageUrl(imageRefs[1]);
requestBody.image_list = [
{ image_url: firstUrl, type: "first_frame" },
{ image_url: endUrl, type: "end_frame" },
];
} else {
const endUrl = extractImageUrl(imageRefs[0]);
requestBody.image_list = [{ image_url: endUrl, type: "end_frame" }];
}
if (!requestBody.prompt) requestBody.prompt = "根据图片生成视频";
} else if (hasMultiRef && (imageRefs.length > 0 || videoRefs.length > 0)) {
requestBody.image_list = [];
for (let i = 0; i < imageRefs.length; i++) {
const imageUrl = extractImageUrl(imageRefs[i]);
requestBody.image_list.push({ image_url: imageUrl });
}
if (!requestBody.prompt) {
const refs = imageRefs.map((_, idx) => `<<<image_${idx + 1}>>>`).join("、");
requestBody.prompt = `参考${refs}生成视频`;
}
}
// 文生视频或无图片输入时需要设置宽高比
const hasImageInput = requestBody.image_list && requestBody.image_list.length > 0;
if (!hasImageInput) {
requestBody.aspect_ratio = config.aspectRatio || "16:9";
if (!requestBody.prompt) throw new Error("文生视频模式需要提供提示词");
}
const apiPath = "/v1/videos/omni-video";
return await submitAndPoll(`${baseUrl}${apiPath}`, `${baseUrl}${apiPath}`, requestBody);
}
// =====================================================
// 非 Omni 模型 —— 根据模式选择不同接口
// =====================================================
// 多图参考模式 —— 使用 /v1/videos/multi-image2video 接口(仅 kling-v1-6 支持)
if (hasMultiRef && imageRefs.length > 0) {
const imageList = [];
for (let i = 0; i < imageRefs.length; i++) {
const rawBase64 = extractRawBase64(imageRefs[i]);
imageList.push({ image: rawBase64 });
}
const requestBody: any = {
model_name: modelName,
image_list: imageList,
prompt: config.prompt || "根据参考图片生成视频",
mode: mode,
duration: String(config.duration),
aspect_ratio: config.aspectRatio || "16:9",
};
const apiPath = "/v1/videos/multi-image2video";
return await submitAndPoll(`${baseUrl}${apiPath}`, `${baseUrl}${apiPath}`, requestBody);
}
// 文生视频模式 —— 使用 /v1/videos/text2video 接口
if (isText) {
if (!config.prompt) throw new Error("文生视频模式需要提供提示词");
const requestBody: any = {
model_name: modelName,
prompt: config.prompt,
mode: mode,
duration: String(config.duration),
aspect_ratio: config.aspectRatio || "16:9",
sound: config.audio === true ? "on" : "off",
};
const apiPath = "/v1/videos/text2video";
return await submitAndPoll(`${baseUrl}${apiPath}`, `${baseUrl}${apiPath}`, requestBody);
}
// 图生视频模式(单图 / 首尾帧 / 尾帧可选等)—— 使用 /v1/videos/image2video 接口
if ((isSingleImage || isStartEndRequired || isEndFrameOptional || isStartFrameOptional) && imageRefs.length > 0) {
const requestBody: any = {
model_name: modelName,
prompt: config.prompt || "根据图片生成视频",
mode: mode,
duration: String(config.duration),
sound: config.audio === true ? "on" : "off",
};
if (isSingleImage) {
requestBody.image = extractRawBase64(imageRefs[0]);
} else if (isStartEndRequired && imageRefs.length >= 2) {
requestBody.image = extractRawBase64(imageRefs[0]);
requestBody.image_tail = extractRawBase64(imageRefs[1]);
} else if (isEndFrameOptional) {
requestBody.image = extractRawBase64(imageRefs[0]);
if (imageRefs.length >= 2) {
requestBody.image_tail = extractRawBase64(imageRefs[1]);
}
} else if (isStartFrameOptional) {
if (imageRefs.length >= 2) {
requestBody.image = extractRawBase64(imageRefs[0]);
requestBody.image_tail = extractRawBase64(imageRefs[1]);
} else {
requestBody.image = extractRawBase64(imageRefs[0]);
}
}
const apiPath = "/v1/videos/image2video";
return await submitAndPoll(`${baseUrl}${apiPath}`, `${baseUrl}${apiPath}`, requestBody);
}
throw new Error("不支持的视频生成模式或缺少必要的输入参数");
};
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
return "";
};
// ============================================================
// 导出
// ============================================================
exports.vendor = vendor;
exports.textRequest = textRequest;
exports.uploadReference = uploadReference;
exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest;
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
export {};

402
data/vendor/minimax.ts vendored Normal file
View File

@ -0,0 +1,402 @@
/**
* Toonflow AI供应商模板 - MiniMax(AI)
* @version 2.0
*/
// ============================================================
// 类型定义
// ============================================================
type VideoMode =
| "singleImage"
| "startEndRequired"
| "endFrameOptional"
| "startFrameOptional"
| "text"
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[];
interface TextModel {
name: string;
modelName: string;
type: "text";
think: boolean;
}
interface ImageModel {
name: string;
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string;
}
interface VideoModel {
name: string;
modelName: string;
type: "video";
mode: VideoMode[];
associationSkills?: string;
audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string;
modelName: string;
type: "tts";
voices: { title: string; voice: string }[];
}
interface VendorConfig {
id: string;
version: string;
name: string;
author: string;
description?: string;
icon?: string;
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[];
}
type ReferenceList =
| ({ type: "image" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }))
| ({ type: "audio" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }))
| ({ type: "video" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }));
interface ImageConfig {
prompt: string;
referenceList?: Extract<ReferenceList, { type: "image" }>[];
size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`;
}
interface VideoConfig {
duration: number;
resolution: string;
aspectRatio: "16:9" | "9:16";
prompt: string;
referenceList?: ReferenceList[];
audio?: boolean;
mode: VideoMode[];
}
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
referenceList?: Extract<ReferenceList, { type: "audio" }>[];
}
interface PollResult {
completed: boolean;
data?: string;
error?: string;
}
// ============================================================
// 全局声明
// ============================================================
declare const axios: any;
declare const logger: (msg: string) => void;
declare const jsonwebtoken: any;
declare const zipImage: (base64: string, size: number) => Promise<string>;
declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>;
declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>;
declare const urlToBase64: (url: string) => Promise<string>;
declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>;
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const exports: {
vendor: VendorConfig;
textRequest: (m: TextModel) => any;
uploadReference: (base64: string, fileType: "image" | "audio" | "video") => Promise<ReferenceList>;
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>;
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>;
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>;
checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>;
updateVendor?: () => Promise<string>;
};
// ============================================================
// 供应商配置
// ============================================================
const vendor: VendorConfig = {
id: "minimax",
version: "2.0",
author: "Toonflow",
name: "MiniMax(海螺AI)",
description: "## MiniMax官方接口适配支持M系列推理文本模型、文生图/图生图、视频生成(文生视频、图生视频、首尾帧生成)能力",
inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true },
{ key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "示例https://api.minimaxi.com" },
],
inputValues: { apiKey: "", baseUrl: "https://api.minimaxi.com" },
models: [
// 文本模型
{ name: "MiniMax-M2.7 (推理版)", modelName: "MiniMax-M2.7", type: "text", think: true },
{ name: "MiniMax-M2.7 极速版 (推理版)", 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 极速版 (推理版)", modelName: "MiniMax-M2.5-highspeed", type: "text", think: true },
{ name: "MiniMax-M2.1 (编程版)", modelName: "MiniMax-M2.1", type: "text", think: true },
{ name: "MiniMax-M2.1 极速版 (编程版)", modelName: "MiniMax-M2.1-highspeed", type: "text", think: true },
{ name: "MiniMax-M2 (Agent版)", modelName: "MiniMax-M2", type: "text", think: false },
// 图片模型
{ name: "海螺图像V1", modelName: "image-01", type: "image", mode: ["text", "singleImage"] },
{ name: "海螺图像V1 Live版", modelName: "image-01-live", type: "image", mode: ["text", "singleImage"], associationSkills: "支持自定义画风" },
// 视频模型
{
name: "海螺2.3",
modelName: "MiniMax-Hailuo-2.3",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [
{ duration: [6], resolution: ["768P", "1080P"] },
{ duration: [10], resolution: ["768P"] },
],
},
{
name: "海螺2.3极速版",
modelName: "MiniMax-Hailuo-2.3-Fast",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [
{ duration: [6], resolution: ["768P", "1080P"] },
{ duration: [10], resolution: ["768P"] },
],
},
{
name: "海螺02",
modelName: "MiniMax-Hailuo-02",
type: "video",
mode: ["text", "singleImage", "startEndRequired"],
audio: false,
durationResolutionMap: [
{ duration: [6], resolution: ["512P", "768P", "1080P"] },
{ duration: [10], resolution: ["512P", "768P"] },
],
},
],
};
// ============================================================
// 辅助工具
// ============================================================
/**
*
*/
const getHeaders = (): Record<string, string> => {
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
};
/**
*
*/
const getBaseUrl = (): string => {
return vendor.inputValues.baseUrl.replace(/\/$/, "");
};
/**
* ReferenceList base64
*/
const extractBase64WithHead = (ref: ReferenceList): string => {
if (ref.sourceType === "url") {
return ref.url;
}
return ref.base64.startsWith("data:") ? ref.base64 : `data:image/png;base64,${ref.base64}`;
};
// ============================================================
// 适配器函数
// ============================================================
const textRequest = (model: TextModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
const baseUrl = getBaseUrl();
const openaiBaseUrl = `${baseUrl}/v1`;
const extraBody = model.think ? { reasoning_split: true } : {};
return createOpenAI({ baseURL: openaiBaseUrl, apiKey, extraBody }).chat(model.modelName);
};
const uploadReference = async (base64: string, fileType: "image" | "audio" | "video"): Promise<ReferenceList> => {
// MiniMax的图片接口直接接受 base64压缩后原样返回
if (fileType === "image") {
const compressed = await zipImage(base64, 10 * 1024);
return { type: "image", sourceType: "base64", base64: compressed };
}
// 视频接口的图片参数也是 base64压缩到20MB
return { type: fileType, sourceType: "base64", base64 } as ReferenceList;
};
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const baseUrl = getBaseUrl();
const headers = getHeaders();
const reqBody: any = {
model: model.modelName,
prompt: config.prompt,
aspect_ratio: config.aspectRatio,
response_format: "base64",
n: 1,
prompt_optimizer: true,
aigc_watermark: false,
};
// 处理图生图参考
const imageRefs = config.referenceList || [];
if (imageRefs.length > 0) {
const refBase64 = extractBase64WithHead(imageRefs[0]);
reqBody.subject_reference = [{ type: "character", image_file: refBase64 }];
}
logger("开始提交MiniMax图像生成任务");
const resp = await axios.post(`${baseUrl}/v1/image_generation`, reqBody, { headers });
if (resp.data.base_resp.status_code !== 0) {
throw new Error(`图像生成失败:${resp.data.base_resp.status_msg}`);
}
if (resp.data.metadata.success_count === 0) {
throw new Error("图像生成被安全策略拦截请调整prompt或参考图");
}
const imgBase64 = resp.data.data.image_base64[0];
return imgBase64.startsWith("data:") ? imgBase64 : `data:image/png;base64,${imgBase64}`;
};
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const baseUrl = getBaseUrl();
const headers = getHeaders();
const reqBody: any = {
model: model.modelName,
prompt: config.prompt,
duration: config.duration,
resolution: config.resolution,
aigc_watermark: false,
prompt_optimizer: true,
};
// 提取图片类型的引用
const imageRefs = (config.referenceList || []).filter((r) => r.type === "image");
if (imageRefs.length > 0) {
// 压缩图片到20MB以内
const compressedImages: string[] = [];
for (const ref of imageRefs) {
const base64 = extractBase64WithHead(ref);
const compressed = await zipImage(base64, 20 * 1024);
compressedImages.push(compressed);
}
if (config.mode.includes("startEndRequired")) {
if (compressedImages.length < 2) throw new Error("首尾帧模式需要上传两张图片");
reqBody.first_frame_image = compressedImages[0];
reqBody.last_frame_image = compressedImages[1];
} else if (config.mode.includes("singleImage")) {
reqBody.first_frame_image = compressedImages[0];
}
}
logger("开始提交MiniMax视频生成任务");
const submitResp = await axios.post(`${baseUrl}/v1/video_generation`, reqBody, { headers });
if (submitResp.data.base_resp.status_code !== 0) {
throw new Error(`任务提交失败:${submitResp.data.base_resp.status_msg}`);
}
const taskId = submitResp.data.task_id;
logger(`视频任务提交成功任务ID: ${taskId}`);
// 轮询任务状态
const pollResult = await pollTask(
async () => {
const queryResp = await axios.get(`${baseUrl}/v1/query/video_generation`, {
headers: getHeaders(),
params: { task_id: taskId },
});
if (queryResp.data.base_resp.status_code !== 0) {
return { completed: true, error: queryResp.data.base_resp.status_msg };
}
const status = queryResp.data.status;
if (status === "Success") {
return { completed: true, data: queryResp.data.file_id };
}
if (status === "Fail") {
return { completed: true, error: "视频生成失败" };
}
logger(`视频任务生成中,当前状态:${status}`);
return { completed: false };
},
5000,
600000,
);
if (pollResult.error) throw new Error(pollResult.error);
const fileId = pollResult.data!;
logger(`视频任务生成成功文件ID: ${fileId}`);
// 获取下载地址
const fileResp = await axios.get(`${baseUrl}/v1/files/retrieve`, {
headers: getHeaders(),
params: { file_id: fileId },
});
if (fileResp.data.base_resp.status_code !== 0) {
throw new Error(`获取文件地址失败:${fileResp.data.base_resp.status_msg}`);
}
const downloadUrl = fileResp.data.file.download_url;
logger(`视频下载地址获取成功开始转Base64`);
return await urlToBase64(downloadUrl);
};
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
return "";
};
const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => {
return {
hasUpdate: false,
latestVersion: "2.0",
notice:
"## 新版本更新公告\n1. 适配新版模板架构,支持 ReferenceList 统一引用类型\n2. 新增 uploadReference 前置处理器\n3. 优化图片压缩和引用提取逻辑",
};
};
const updateVendor = async (): Promise<string> => {
return "";
};
// ============================================================
// 导出
// ============================================================
exports.vendor = vendor;
exports.textRequest = textRequest;
exports.uploadReference = uploadReference;
exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest;
exports.checkForUpdates = checkForUpdates;
exports.updateVendor = updateVendor;
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
export {};

347
data/vendor/null.ts vendored Normal file
View File

@ -0,0 +1,347 @@
/**
* Toonflow AI供应商模板
* @version 2.0
*/
// ============================================================
// 类型定义
// ============================================================
type VideoMode =
| "singleImage" //单图参考
| "startEndRequired" //首尾帧(两张都得有)
| "endFrameOptional" //首尾帧(尾帧可选)
| "startFrameOptional" //首尾帧(首帧可选)
| "text" //文本
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[]; //多参考(数字代表限制数量)
interface TextModel {
name: string;
modelName: string;
type: "text";
think: boolean;
}
interface ImageModel {
name: string;
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string;
}
interface VideoModel {
name: string;
modelName: string;
type: "video";
mode: VideoMode[];
associationSkills?: string;
audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string;
modelName: string;
type: "tts";
voices: { title: string; voice: string }[];
}
interface VendorConfig {
id: string; //唯一ID作为文件名存储用户磁盘上禁止符号
version: string; //版本号格式为x.y需遵守语义化版本控制
name: string; //供应商名称
author: string; //作者
description?: string; //描述支持Markdown格式
icon?: string; //图标仅支持Base64格式建议尺寸为128x128像素
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[];
}
type ReferenceList =
| ({ type: "image" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }))
| ({ type: "audio" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }))
| ({ type: "video" } & ({ sourceType: "url"; url: string } | { sourceType: "base64"; base64: string }));
interface ImageConfig {
prompt: string;
referenceList?: Extract<ReferenceList, { type: "image" }>[];
size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`;
}
interface VideoConfig {
duration: number;
resolution: string;
aspectRatio: "16:9" | "9:16";
prompt: string;
referenceList?: ReferenceList[];
audio?: boolean;
mode: VideoMode[];
}
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
referenceList?: Extract<ReferenceList, { type: "audio" }>[];
}
interface PollResult {
completed: boolean;
data?: string;
error?: string;
}
// ============================================================
// 全局声明
// ============================================================
declare const axios: any; // HTTP请求库
declare const logger: (msg: string) => void; // 日志函数
declare const jsonwebtoken: any; // JWT处理库
declare const zipImage: (base64: string, size: number) => Promise<string>; // 图片压缩函数返回有头base64字符串
declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>; // 图片分辨率调整函数返回有头base64字符串
declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>; // 图片合成函数返回有头base64字符串
declare const urlToBase64: (url: string) => Promise<string>; // URL转Base64函数返回有头base64字符串
declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>; // 轮询函数fn为异步函数interval为轮询间隔timeout为超时时间返回fn的结果
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const exports: {
vendor: VendorConfig;
textRequest: (m: TextModel) => any; //文本模型
uploadReference: (base64: string, fileType: "image" | "audio" | "video") => Promise<ReferenceList>; // reference前置处理器专门用于处理referenceList中的条目将有头base64上传并返回URL然后reference才会传入videoRequest/imageRequest/ttsRequest中
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>; //图片模型返回有头base64字符串
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>; //视频模型返回有头base64字符串
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>; //暂未开放语音模型返回有头base64字符串
checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>; //检查更新函数返回是否有更新和最新版本号和更公告支持Markdown格式
updateVendor?: () => Promise<string>; //更新函数,返回最新的代码文本
};
// ============================================================
// 供应商配置
// ============================================================
const vendor: VendorConfig = {
id: "openai",
version: "2.0",
author: "Toonflow",
name: "OpenAI标准接口",
description: "## OpenAI标准格式接口可修改请求地址并手动添加模型。",
inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true },
{ key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "示例https://api.openai.com/v1" },
],
inputValues: { apiKey: "", baseUrl: "https://api.openai.com/v1" },
models: [{ name: "GPT-4o", modelName: "gpt-4o", type: "text", think: false }],
};
// ============================================================
// 适配器函数
// ============================================================
const textRequest = (model: TextModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return createOpenAI({ baseURL: vendor.inputValues.baseUrl, apiKey }).chat(model.modelName);
};
const uploadReference = async (base64: string, fileType: "image" | "audio" | "video"): Promise<ReferenceList> => {
return { type: fileType, sourceType: "base64", base64 };
};
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
return "";
};
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
return "";
};
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
return "";
};
const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => {
return { hasUpdate: false, latestVersion: "2.0", notice: "## 新版本更新公告" };
};
const updateVendor = async (): Promise<string> => {
return "";
};
// ============================================================
// 导出
// ============================================================
exports.vendor = vendor;
exports.textRequest = textRequest;
exports.uploadReference = uploadReference;
exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest;
exports.checkForUpdates = checkForUpdates;
exports.updateVendor = updateVendor;
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
export {};
/**
* ============================================================
* AI
* ============================================================
*
*
* Toonflow AI AI
* curl API
*
*
*
* 1. API curl HeadersBody
* 2. API /
* 3. text / image / video / tts
* API
*
*
*
* 1.
* 使 import / require使
* axiosloggerjsonwebtokenzipImagezipImageResolutionmergeImages
* urlToBase64pollTask createOpenAIcreateDeepSeekcreateZhipucreateQwen
* createAnthropiccreateOpenAICompatiblecreateXaicreateMinimax
* createGoogleGenerativeAI AI SDK
*
* 2. exports.*
* const API_URL = "https://..."; const MAX_RETRY = 3;
* vendor.inputValues
* vendor.inputValues.xxx 访
* 使 exports.* 使
*
* 3. exports.*
* textRequest / uploadReference / imageRequest / videoRequest / ttsRequest
*
* Token
*
* 使
*
* 4.
* 使camelCase使 UPPER_SNAKE_CASE
*
* 5.
* VendorConfigImageConfigVideoConfig
* TTSConfigTextModelImageModelVideoModelTTSModelReferenceListPollResult
* AI 使
*
* 6.
* - textRequest(model) AI SDK chat model createOpenAI
* - uploadReference(base64, fileType)reference referenceList
* base64 ReferenceList URL
* API base64 { type: fileType, sourceType: "base64", base64 }
* { type: fileType, sourceType: "url", url: "..." }
* imageRequest / videoRequest / ttsRequest
* referenceList
* - imageRequest(config, model) base64 "data:image/png;base64,..."
* config.referenceList Extract<ReferenceList, { type: "image" }>[]
* uploadReference URL base64
* - videoRequest(config, model) base64 "data:video/mp4;base64,..."
* config.referenceList ReferenceList[] image / video / audio
* config.mode mode 使 referenceList
* - ttsRequest(config, model) base64 "data:audio/mp3;base64,..."
* config.referenceList Extract<ReferenceList, { type: "audio" }>[]
* API URL 使 urlToBase64(url)
*
* 7. ReferenceList VideoMode
* ReferenceList
* - type: "image" | "audio" | "video"
* - sourceType: "url" | "base64"
* - url base64
*
* VideoMode
* - "text"
* - "singleImage"
* - "startEndRequired"
* - "endFrameOptional"
* - "startFrameOptional"
* - ["imageReference:9", "videoReference:3", "audioReference:3"]
*
*
* videoRequest config.mode
* - config.referenceList
* - API //
*
* 8.
* 使 pollTask
* const result = await pollTask(async () => {
* const resp = await axios.get(...);
* if (resp.data.status === "SUCCESS") return { completed: true, data: resp.data.url };
* if (resp.data.status === "FAILED") return { completed: true, error: resp.data.message };
* return { completed: false };
* }, 5000, 600000); // 每5秒轮询10分钟超时
* if (result.error) throw new Error(result.error);
* return await urlToBase64(result.data!);
*
* 9.
* API Key使 throw new Error("...")
* API
*
* 10.
* 使 logger("...") "开始提交任务""任务ID: xxx""轮询中..."
* 便
*
* 11. vendor
* - id使
* - version "x.y"
* - inputs API API KeySecret
* - models type
* - VideoModel mode API 7 VideoMode
* - VideoModel audio truefalse"optional"
* - VideoModel durationResolutionMap
* - VideoModel associationSkills
* - ImageModel mode API "text" "singleImage" "multiReference"
* - TTSModel voices
*
* 12.
* - 使 zipImage(base64, maxSizeKB)
* - 使 zipImageResolution(base64, width, height)
* - 使 mergeImages(base64Arr, maxSize)
* - base64
*
* 13.
*
* []
* 线
* getHeadersgetBaseUrl
*
* 14.
* exports.xxx = xxx
* - exports.vendor
* - exports.textRequest
* - exports.uploadReference
* - exports.imageRequest
* - exports.videoRequest
* - exports.ttsRequest
* - exports.checkForUpdates
* - exports.updateVendor
* return ""
* export {};
*
*
*
* 1. curl API
* 2. API /
* 3. vendor
* 4. uploadReference API URL base64 URL
* API base64
* 5. return ""
* 6.
*/

169
data/vendor/openai.ts vendored Normal file
View File

@ -0,0 +1,169 @@
/**
* Toonflow AI供应商模板
* @version 2.0
*/
// ============================================================
// 类型定义
// ============================================================
type VideoMode =
| "singleImage"
| "startEndRequired"
| "endFrameOptional"
| "startFrameOptional"
| "text"
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[];
interface TextModel {
name: string;
modelName: string;
type: "text";
think: boolean;
}
interface ImageModel {
name: string;
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string;
}
interface VideoModel {
name: string;
modelName: string;
type: "video";
mode: VideoMode[];
associationSkills?: string;
audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string;
modelName: string;
type: "tts";
voices: { title: string; voice: string }[];
}
interface VendorConfig {
id: string;
version: string;
name: string;
author: string;
description?: string;
icon?: string;
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[];
}
interface ImageConfig {
prompt: string;
imageBase64: string[];
size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`;
}
interface VideoConfig {
duration: number;
resolution: string;
aspectRatio: "16:9" | "9:16";
prompt: string;
imageBase64?: string[];
audio?: boolean;
mode: VideoMode[];
}
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
}
interface PollResult {
completed: boolean;
data?: string;
error?: string;
}
// ============================================================
// 全局声明
// ============================================================
declare const axios: any;
declare const logger: (msg: string) => void;
declare const jsonwebtoken: any;
declare const zipImage: (base64: string, size: number) => Promise<string>;
declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>;
declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>;
declare const urlToBase64: (url: string) => Promise<string>;
declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>;
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const exports: {
vendor: VendorConfig;
textRequest: (m: TextModel) => any;
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>;
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>;
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>;
checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>;
updateVendor?: () => Promise<string>;
};
// ============================================================
// 供应商配置
// ============================================================
const vendor: VendorConfig = {
id: "openai",
version: "2.0",
author: "Toonflow",
name: "OpenAI标准接口",
description: "## 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: "https://api.openai.com/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 },
],
};
// ============================================================
// 适配器函数
// ============================================================
const textRequest = (model: TextModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return createOpenAI({ baseURL: vendor.inputValues.baseUrl, apiKey }).chat(model.modelName);
};
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
return "";
};
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
return "";
};
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
return "";
};
const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => {
return { hasUpdate: false, latestVersion: "2.0", notice: "" };
};
const updateVendor = async (): Promise<string> => {
return "";
};
// ============================================================
// 导出
// ============================================================
exports.vendor = vendor;
exports.textRequest = textRequest;
exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest;
exports.checkForUpdates = checkForUpdates;
exports.updateVendor = updateVendor;
export {};

569
data/vendor/toonflow.ts vendored Normal file
View File

@ -0,0 +1,569 @@
//如需遥测AI请使用在toonflow安装目录运行npx @ai-sdk/devtools 要求在其他设置中打开遥测功能且toonflow有权限在安装目录创建.devtools文件夹
// ==================== 类型定义 ====================
// 文本模型
interface TextModel {
name: string; // 显示名称
modelName: string;
type: "text";
think: boolean; // 前端显示用
}
// 图像模型
interface ImageModel {
name: string; // 显示名称
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string; // 关联技能,多个技能用逗号分隔
}
// 视频模型
interface VideoModel {
name: string; // 显示名称
modelName: string; //全局唯一
type: "video";
mode: (
| "singleImage" // 单图
| "startEndRequired" // 首尾帧(两张都得有)
| "endFrameOptional" // 首尾帧(尾帧可选)
| "startFrameOptional" // 首尾帧(首帧可选)
| "text" // 文本生视频
| ("videoReference" | "imageReference" | "audioReference" | "textReference")[]
)[]; // 混合参考
associationSkills?: string; // 关联技能,多个技能用逗号分隔
audio: "optional" | false | true; // 音频配置
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string; // 显示名称
modelName: string;
type: "tts";
voices: {
title: string; //显示名称
voice: string; //说话人
}[];
}
// 供应商配置
interface VendorConfig {
id: string; //供应商唯一标识,必须全局唯一
author: string;
description?: string; //md5格式
name: string;
icon?: string; //仅支持base64格式
inputs: {
key: string;
label: string;
type: "text" | "password" | "url";
required: boolean;
placeholder?: string;
}[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel)[];
}
// ==================== 全局工具函数 ====================
//Axios实例
//压缩图片大小(1MB = 1 * 1024 * 1024)
declare const zipImage: (completeBase64: string, size: number) => Promise<string>;
//压缩图片分辨率
declare const zipImageResolution: (completeBase64: string, width: number, height: number) => Promise<string>;
//多图拼接乘单图 maxSize 最大输出大小,默认为 10mb
declare const mergeImages: (completeBase64: string[], maxSize?: string) => Promise<string>;
//Url转Base64
declare const urlToBase64: (url: string) => Promise<string>;
//轮询函数
declare const pollTask: (
fn: () => Promise<{ completed: boolean; data?: string; error?: string }>,
interval?: number,
timeout?: number,
) => Promise<{ completed: boolean; data?: string; error?: string }>;
declare const axios: any;
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const logger: (logstring: string) => void;
declare const jsonwebtoken: any;
// ==================== 供应商数据 ====================
const vendor: VendorConfig = {
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"],
},
],
};
exports.vendor = vendor;
// ==================== 适配器函数 ====================
// 文本请求函数
const textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");
return createOpenAI({
baseURL: vendor.inputValues.baseUrl,
apiKey: apiKey,
}).chat(textModel.modelName);
};
exports.textRequest = textRequest;
//图片请求函数
interface ImageConfig {
prompt: string; //图片提示词
imageBase64: string[]; //输入的图片提示词
size: "1K" | "2K" | "4K"; // 图片尺寸
aspectRatio: `${number}:${number}`; // 长宽比
}
//豆包格式适配
function doubaoAdaptor(imageConfig: ImageConfig, imageModel: ImageModel) {
const size = imageConfig.size === "1K" ? "2K" : imageConfig.size;
const sizeMap: Record<string, Record<string, string>> = {
"16:9": {
"2k": "2848x1600",
"2K": "2848x1600",
"4K": "4096x2304",
"4k": "4096x2304",
},
"9:16": {
"4k": "2304x4096",
"2k": "1600x2848",
"2K": "1600x2848",
"4K": "2304x4096",
},
};
const body = {
model: imageModel.modelName,
prompt: imageConfig.prompt,
size: sizeMap[imageConfig.aspectRatio][size],
response_format: "url",
sequential_image_generation: "disabled",
stream: false,
watermark: false,
...(imageConfig.imageBase64 && { image: imageConfig.imageBase64 }),
};
return {
body,
processFn: (data) => {
return data.data[0].url;
},
};
}
// 提取图片内容
function extractFirstImageFromMd(content) {
const regex = /!\[([^\]]*)\]\((data:image\/[^;]+;base64,[A-Za-z0-9+/=]+|https?:\/\/[^\s)]+|\/\/[^\s)]+|[^\s)]+)\)/;
const match = content.match(regex);
if (!match) return null;
const raw = match[2].trim();
const url = raw.startsWith("data:") ? raw : raw.split(/\s+/)[0];
return {
alt: match[1],
url,
type: url.startsWith("data:image") ? "base64" : "url",
};
}
// gemini 图片请求适配
function geminiImageAdaptor(imageConfig: ImageConfig, imageModel: ImageModel) {
const images = [];
if (imageConfig.imageBase64 && imageConfig.imageBase64.length) {
images.push({
role: "user",
content: imageConfig.imageBase64.map((i) => ({
type: "image_url",
image_url: {
url: i,
},
})),
});
}
const imageConfigGoogle = {
aspect_ratio: imageConfig.aspectRatio,
};
// if(imageModel.ModelName == 'gemini-3-pro-image-preview-vt'){
imageConfigGoogle.image_size = imageConfig.size;
// }
const body = {
model: imageModel.modelName,
messages: [{ role: "user", content: imageConfig.prompt + `请直接输出图片` }, ...images],
extra_body: {
google: {
image_config: {
...imageConfigGoogle,
},
},
},
};
return {
body,
url: `${vendor.inputValues.baseUrl}/chat/completions`,
processFn: (data: any) => {
return extractFirstImageFromMd(data.choices[0].message.content).url;
},
};
}
function commonAdaptor(imageConfig: ImageConfig, imageModel: ImageModel) {
const defaultImageFn = [
["doubao", doubaoAdaptor],
["nano", geminiImageAdaptor],
["gemini", geminiImageAdaptor],
["seedream", doubaoAdaptor],
];
const modelName = imageModel.modelName;
const lowerName = modelName.toLowerCase();
const match = defaultImageFn.find(([key]) => lowerName.includes(key));
return match ? match[1](imageConfig, imageModel) : {};
}
const imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");
const adaptor = commonAdaptor(imageConfig, imageModel);
const requestUrl = adaptor?.url ? `${vendor.inputValues.baseUrl}/chat/completions` : vendor.inputValues.baseUrl + "/images/generations";
const response = await fetch(requestUrl, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(adaptor.body),
});
if (!response.ok) {
const errorText = await response.text(); // 获取错误信息
console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);
throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);
}
const data = await response.json();
return adaptor.processFn(data);
};
exports.imageRequest = imageRequest;
interface VideoConfig {
duration: number; //视频时长,单位秒
resolution: string; //视频分辨率,如"720p"、"1080p"
aspectRatio: "16:9" | "9:16"; //视频长宽比
prompt: string; //视频提示词
fileBase64?: string[]; // 文件base64 包含图片base64、视频base64、音频base64
audio?: boolean;
mode:
| "singleImage" // 单图
| "multiImage" // 多图模式
| "gridImage" // 网格单图(传入一张图片,但该图片是网格图)
| "startEndRequired" // 首尾帧(两张都得有)
| "endFrameOptional" // 首尾帧(尾帧可选)
| "startFrameOptional" // 首尾帧(首帧可选)
| "text" // 文本生视频
| ("videoReference" | "imageReference" | "audioReference" | "textReference")[]; // 混合参考
}
// 豆包视频
const buildDoubaoMetadata = (videoConfig: VideoConfig) => {
const metaData = {
...(typeof videoConfig.audio == "boolean" && { generate_audio: videoConfig.audio ?? false }),
ratio: videoConfig.aspectRatio,
image_roles: [],
references: [],
};
if (videoConfig.imageBase64 && videoConfig.imageBase64.length) {
videoConfig.imageBase64.forEach((i, index) => {
if (Array.isArray(videoConfig.mode)) {
metaData.references.push(i);
} else {
if (videoConfig.mode == "startEndRequired" || videoConfig.mode == "endFrameOptional" || videoConfig.mode == "startFrameOptional") {
(metaData.image_roles as string[]).push(index == 0 ? "first_frame" : "last_frame");
}
if (videoConfig.mode == "singleImage") {
(metaData.image_roles as string[]).push("reference_image");
}
}
});
}
return metaData;
};
// 万象
const buildWanMetadata = (videoConfig: VideoConfig) => {
const images = videoConfig.imageBase64 ?? [];
const metaData: Record<string, string | boolean> = {};
if (
(videoConfig.mode === "startEndRequired" || videoConfig.mode == "endFrameOptional" || videoConfig.mode == "startFrameOptional") &&
images.length == 2
) {
if (images[0]) metaData.first_frame_url = images[0];
if (images[1]) metaData.last_frame_url = images[1];
} else if (images.length) {
metaData.img_url = images[0]!;
}
if (typeof videoConfig.audio == "boolean") {
metaData.audio = videoConfig.audio;
}
return metaData;
};
// 千问视频
const buildViduMetadata = (videoConfig: VideoConfig) => ({
aspect_ratio: videoConfig.aspectRatio,
audio: videoConfig.audio ?? false,
off_peak: false,
});
// 可灵
const buildKlingAdaptor = (videoConfig: VideoConfig) => {
const metaData: any = {
aspect_ratio: videoConfig.aspectRatio,
};
if (videoConfig.imageBase64 && videoConfig.imageBase64.length) {
if (Array.isArray(videoConfig.mode)) {
metaData.reference = videoConfig.imageBase64;
}
if (videoConfig.mode == "endFrameOptional") {
metaData.image_tail = videoConfig.imageBase64[0];
}
if (videoConfig.mode == "startEndRequired") {
metaData.image_list = [
{
image_url: videoConfig.imageBase64[0],
type: "first_frame",
},
{
image_url: videoConfig.imageBase64[1],
type: "last_frame",
},
];
}
if (videoConfig.mode == "singleImage") {
metaData.image = videoConfig.imageBase64[0];
}
}
return metaData;
};
type MetadataBuilder = (config: VideoConfig) => Record<string, any>;
const METADATA_BUILDERS: Array<[string, MetadataBuilder]> = [
["doubao", buildDoubaoMetadata],
["wan", buildWanMetadata],
["vidu", buildViduMetadata],
["seedance", buildDoubaoMetadata],
["kling", buildKlingAdaptor],
];
const buildModelMetadata = (modelName: string, videoConfig: VideoConfig) => {
const lowerName = modelName.toLowerCase();
const match = METADATA_BUILDERS.find(([key]) => lowerName.includes(key));
return match ? match[1](videoConfig) : {};
};
const videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace("Bearer ", "");
try {
videoConfig.mode = JSON.parse(videoConfig.mode);
} catch (e) {
videoConfig.mode = videoConfig.mode as any;
}
// 构建每个模型对应的附加参数
const metadata = buildModelMetadata(videoModel.modelName, videoConfig);
//公共请求参数
const publicBody = {
model: videoModel.modelName,
...(videoConfig.imageBase64 && videoConfig.imageBase64.length && !Array.isArray(videoConfig.mode) ? { images: videoConfig.imageBase64 } : {}),
prompt: videoConfig.prompt,
duration: videoConfig.duration,
metadata: metadata,
};
if (videoModel.modelName.toLocaleLowerCase().includes("wan")) {
const sizeMap: Record<string, Record<string, string>> = {
"480p": {
"16:9": "832*480",
"9:16": "480*832",
},
"720p": {
"16:9": "1280*720",
"9:16": "720*1280",
},
"1080p": {
"16:9": "1920*1080",
"9:16": "1080*1920",
},
};
const size = sizeMap[videoConfig.resolution]?.[videoConfig.aspectRatio];
publicBody.size = size;
}
const requestUrl = vendor.inputValues.baseUrl + "/video/generations";
const queryUrl = vendor.inputValues.baseUrl + "/video/generations/{id}";
const response = await fetch(requestUrl, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(publicBody),
});
if (!response.ok) {
const errorText = await response.text(); // 获取错误信息
console.error("请求失败,状态码:", response.status, ", 错误信息:", errorText);
throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);
}
const data = await response.json();
const taskId = data.id;
const res = await pollTask(async () => {
const queryResponse = await fetch(queryUrl.replace("{id}", taskId), {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
});
if (!queryResponse.ok) {
const errorText = await queryResponse.text(); // 获取错误信息
console.error("请求失败,状态码:", queryResponse.status, ", 错误信息:", errorText);
throw new Error(`请求失败,状态码: ${queryResponse.status}, 错误信息: ${errorText}`);
}
const queryData = await queryResponse.json();
const status = queryData?.status ?? queryData?.data?.status;
const fail_reason = queryData?.data?.fail_reason ?? queryData?.data;
switch (status) {
case "completed":
case "SUCCESS":
case "success":
return { completed: true, data: queryData.data.result_url };
case "FAILURE":
return { completed: false, error: fail_reason || "视频生成失败" };
default:
return { completed: false };
}
});
if (res.error) throw new Error(res.error);
return res.data;
};
exports.videoRequest = videoRequest;
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
}
const ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {
return null;
};
exports.ttsRequest = ttsRequest;

527
data/vendor/volcengine.ts vendored Normal file
View File

@ -0,0 +1,527 @@
/**
* Toonflow AI供应商模板 - ()
* @version 2.0
*/
// ============================================================
// 类型定义
// ============================================================
type VideoMode =
| "singleImage"
| "startEndRequired"
| "endFrameOptional"
| "startFrameOptional"
| "text"
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[];
interface TextModel {
name: string;
modelName: string;
type: "text";
think: boolean;
}
interface ImageModel {
name: string;
modelName: string;
type: "image";
mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string;
}
interface VideoModel {
name: string;
modelName: string;
type: "video";
mode: VideoMode[];
associationSkills?: string;
audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[];
}
interface TTSModel {
name: string;
modelName: string;
type: "tts";
voices: { title: string; voice: string }[];
}
interface VendorConfig {
id: string;
version: string;
name: string;
author: string;
description?: string;
icon?: string;
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[];
}
interface ImageConfig {
prompt: string;
imageBase64: string[];
size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`;
}
interface VideoConfig {
duration: number;
resolution: string;
aspectRatio: "16:9" | "9:16";
prompt: string;
referenceList?: string[];
audio?: boolean;
mode: VideoMode[];
}
interface TTSConfig {
text: string;
voice: string;
speechRate: number;
pitchRate: number;
volume: number;
}
interface PollResult {
completed: boolean;
data?: string;
error?: string;
}
// ============================================================
// 全局声明
// ============================================================
declare const axios: any;
declare const logger: (msg: string) => void;
declare const jsonwebtoken: any;
declare const zipImage: (base64: string, size: number) => Promise<string>;
declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>;
declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>;
declare const urlToBase64: (url: string) => Promise<string>;
declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>;
declare const createOpenAI: any;
declare const createDeepSeek: any;
declare const createZhipu: any;
declare const createQwen: any;
declare const createAnthropic: any;
declare const createOpenAICompatible: any;
declare const createXai: any;
declare const createMinimax: any;
declare const createGoogleGenerativeAI: any;
declare const exports: {
vendor: VendorConfig;
textRequest: (m: TextModel) => any;
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>;
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>;
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>;
checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>;
updateVendor?: () => Promise<string>;
};
// ============================================================
// 供应商配置
// ============================================================
const vendor: VendorConfig = {
id: "volcengine-doubao",
version: "2.0",
author: "Toonflow",
name: "火山引擎(豆包)",
description: "## 火山引擎豆包大模型,支持文本、图片生成、视频生成等能力。\n\n需要在[火山引擎控制台](https://console.volcengine.com/ark)获取API密钥。",
icon: "",
inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true, placeholder: "火山引擎API Key" },
{ key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "以v3结束示例https://ark.cn-beijing.volces.com/api/v3" },
],
inputValues: {
apiKey: "",
baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
},
models: [
// ===================== 文本模型 - 推荐 =====================
{ name: "Doubao-Seed-2.0-Pro", modelName: "doubao-seed-2-0-pro-260215", type: "text", think: true },
{ name: "Doubao-Seed-2.0-Lite", modelName: "doubao-seed-2-0-lite-260215", type: "text", think: true },
{ name: "Doubao-Seed-2.0-Mini", modelName: "doubao-seed-2-0-mini-260215", type: "text", think: true },
{ name: "Doubao-Seed-2.0-Code-Preview", modelName: "doubao-seed-2-0-code-preview-260215", type: "text", think: true },
{ name: "Doubao-Seed-Character", modelName: "doubao-seed-character-251128", type: "text", think: false },
// ===================== 文本模型 - 往期 =====================
{ name: "Doubao-Seed-1.8", modelName: "doubao-seed-1-8-251228", type: "text", think: true },
{ name: "Doubao-Seed-Code-Preview", modelName: "doubao-seed-code-preview-251028", type: "text", think: true },
{ name: "Doubao-Seed-1.6-Lite", modelName: "doubao-seed-1-6-lite-251015", type: "text", think: true },
{ name: "Doubao-Seed-1.6-Flash(0828)", modelName: "doubao-seed-1-6-flash-250828", type: "text", think: true },
{ name: "Doubao-Seed-1.6-Vision", modelName: "doubao-seed-1-6-vision-250815", type: "text", think: true },
{ name: "Doubao-Seed-1.6(1015)", modelName: "doubao-seed-1-6-251015", type: "text", think: true },
{ name: "Doubao-Seed-1.6(0615)", modelName: "doubao-seed-1-6-250615", type: "text", think: true },
{ name: "Doubao-Seed-1.6-Flash(0615)", modelName: "doubao-seed-1-6-flash-250615", type: "text", think: true },
{ name: "Doubao-Seed-Translation", modelName: "doubao-seed-translation-250915", type: "text", think: false },
{ name: "Doubao-1.5-Pro-32K", modelName: "doubao-1-5-pro-32k-250115", type: "text", think: false },
{ name: "Doubao-1.5-Pro-32K-Character(0715)", modelName: "doubao-1-5-pro-32k-character-250715", type: "text", think: false },
{ name: "Doubao-1.5-Pro-32K-Character(0228)", modelName: "doubao-1-5-pro-32k-character-250228", type: "text", think: false },
{ name: "Doubao-1.5-Lite-32K", modelName: "doubao-1-5-lite-32k-250115", type: "text", think: false },
{ name: "Doubao-1.5-Vision-Pro-32K", modelName: "doubao-1-5-vision-pro-32k-250115", type: "text", think: false },
// ===================== 文本模型 - 第三方(火山引擎托管) =====================
{ name: "GLM-4-7", modelName: "glm-4-7-251222", type: "text", think: true },
{ name: "DeepSeek-V3-2", modelName: "deepseek-v3-2-251201", type: "text", think: true },
{ name: "DeepSeek-V3-1-Terminus", modelName: "deepseek-v3-1-terminus", type: "text", think: true },
{ name: "DeepSeek-V3(0324)", modelName: "deepseek-v3-250324", type: "text", think: false },
{ name: "DeepSeek-R1(0528)", modelName: "deepseek-r1-250528", type: "text", think: true },
{ name: "Qwen3-32B", modelName: "qwen3-32b-20250429", type: "text", think: false },
{ name: "Qwen3-14B", modelName: "qwen3-14b-20250429", type: "text", think: false },
{ name: "Qwen3-8B", modelName: "qwen3-8b-20250429", type: "text", think: false },
{ name: "Qwen3-0.6B", modelName: "qwen3-0-6b-20250429", type: "text", think: false },
{ name: "Qwen2.5-72B", modelName: "qwen2-5-72b-20240919", type: "text", think: false },
{ name: "GLM-4.5-Air", modelName: "glm-4-5-air", type: "text", think: false },
// ===================== 图片生成模型 =====================
{
name: "Seedream-5.0",
modelName: "doubao-seedream-5-0-260128",
type: "image",
mode: ["text", "singleImage", "multiReference"],
},
{
name: "Seedream-5.0-Lite",
modelName: "doubao-seedream-5-0-lite-260128",
type: "image",
mode: ["text", "singleImage", "multiReference"],
},
{
name: "Seedream-4.5",
modelName: "doubao-seedream-4-5-251128",
type: "image",
mode: ["text", "singleImage", "multiReference"],
},
{
name: "Seedream-4.0",
modelName: "doubao-seedream-4-0-250828",
type: "image",
mode: ["text", "singleImage", "multiReference"],
},
{
name: "Seedream-3.0-T2I",
modelName: "doubao-seedream-3-0-t2i-250415",
type: "image",
mode: ["text"],
},
// ===================== 视频生成模型 =====================
// Seedance 2.0: 多模态参考(图0~9+视频0~3+音频0~3) + 首尾帧 + 首帧 + 文生视频
{
name: "Seedance-2.0(音画同生)",
modelName: "doubao-seedance-2-0-260128",
type: "video",
mode: [
"text",
"startFrameOptional",
["imageReference:9", "videoReference:3", "audioReference:3"],
],
audio: "optional",
durationResolutionMap: [
{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["480p", "720p"] },
],
},
{
name: "Seedance-2.0-Fast(音画同生)",
modelName: "doubao-seedance-2-0-fast-260128",
type: "video",
mode: [
"text",
"startFrameOptional",
["imageReference:9", "videoReference:3", "audioReference:3"],
],
audio: "optional",
durationResolutionMap: [
{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["480p", "720p"] },
],
},
// Seedance 1.5 pro: 首尾帧 + 首帧 + 文生视频
{
name: "Seedance-1.5-Pro(音画同生)",
modelName: "doubao-seedance-1-5-pro-251215",
type: "video",
mode: ["text", "startFrameOptional"],
audio: "optional",
durationResolutionMap: [
{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] },
],
},
// Seedance 1.0 pro: 首尾帧 + 首帧 + 文生视频
{
name: "Seedance-1.0-Pro",
modelName: "doubao-seedance-1-0-pro-250528",
type: "video",
mode: ["text", "startFrameOptional"],
audio: false,
durationResolutionMap: [
{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] },
],
},
// Seedance 1.0 pro fast: 首帧 + 文生视频(不支持首尾帧)
{
name: "Seedance-1.0-Pro-Fast",
modelName: "doubao-seedance-1-0-pro-fast-251015",
type: "video",
mode: ["text", "singleImage"],
audio: false,
durationResolutionMap: [
{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] },
],
},
// Seedance 1.0 lite t2v: 仅文生视频
{
name: "Seedance-1.0-Lite-T2V",
modelName: "doubao-seedance-1-0-lite-t2v-250428",
type: "video",
mode: ["text"],
audio: false,
durationResolutionMap: [
{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] },
],
},
// Seedance 1.0 lite i2v: 参考图(1~4) + 首尾帧 + 首帧
{
name: "Seedance-1.0-Lite-I2V",
modelName: "doubao-seedance-1-0-lite-i2v-250428",
type: "video",
mode: ["startFrameOptional", ["imageReference:4"]],
audio: false,
durationResolutionMap: [
{ duration: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] },
],
},
],
};
// ============================================================
// 辅助工具
// ============================================================
const getHeaders = () => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
return {
"Content-Type": "application/json",
Authorization: `Bearer ${vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "")}`,
};
};
const getBaseUrl = () => vendor.inputValues.baseUrl.replace(/\/+$/, "");
// ============================================================
// 适配器函数
// ============================================================
/** 文本请求 - 直接使用 createOpenAI */
const textRequest = (model: TextModel) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return createOpenAI({ baseURL: getBaseUrl(), apiKey }).chat(model.modelName);
};
/** 图片生成请求 */
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
const baseUrl = getBaseUrl();
const headers = getHeaders();
// 构建 content
const content: any[] = [];
// 文本提示词
if (config.prompt) {
content.push({ type: "text", text: config.prompt });
}
// 图片输入
if (config.imageBase64 && config.imageBase64.length > 0) {
for (const base64 of config.imageBase64) {
content.push({
type: "image_url",
image_url: { url: `data:image/png;base64,${base64}` },
});
}
}
// 解析宽高比
const [w, h] = config.aspectRatio.split(":").map(Number);
// 解析尺寸到像素
const sizeMap: Record<string, { width: number; height: number }> = {
"1K": { width: 1024, height: Math.round(1024 * (h / w)) },
"2K": { width: 2048, height: Math.round(2048 * (h / w)) },
"4K": { width: 4096, height: Math.round(4096 * (h / w)) },
};
const size = sizeMap[config.size] || sizeMap["1K"];
const body = {
model: model.modelName,
content,
size: `${size.width}x${size.height}`,
response_format: "url",
};
logger(`[图片生成] 请求模型: ${model.modelName}`);
const response = await axios.post(`${baseUrl}/images/generations`, body, { headers });
const data = response.data;
if (data?.data?.[0]?.url) {
return await urlToBase64(data.data[0].url);
}
throw new Error("图片生成失败:未返回有效结果");
};
/** 视频生成请求 */
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
const baseUrl = getBaseUrl();
const headers = getHeaders();
// 构建 content
const content: any[] = [];
// 文本提示词
if (config.prompt) {
content.push({ type: "text", text: config.prompt });
}
// 判断当前使用的 mode
const activeMode = config.mode && config.mode.length > 0 ? config.mode[0] : "text";
if (typeof activeMode === "string") {
switch (activeMode) {
case "singleImage":
// 首帧模式单张图片role 为 first_frame
if (config.imageBase64 && config.imageBase64.length > 0) {
content.push({
type: "image_url",
image_url: { url: `data:image/png;base64,${config.imageBase64[0]}` },
role: "first_frame",
});
}
break;
case "startFrameOptional":
// 首帧 + 可选尾帧模式
if (config.imageBase64 && config.imageBase64.length > 0) {
content.push({
type: "image_url",
image_url: { url: `data:image/png;base64,${config.imageBase64[0]}` },
role: "first_frame",
});
if (config.imageBase64.length > 1) {
content.push({
type: "image_url",
image_url: { url: `data:image/png;base64,${config.imageBase64[1]}` },
role: "last_frame",
});
}
}
break;
case "text":
// 纯文生视频,无需额外处理
break;
}
} else if (Array.isArray(activeMode)) {
// 多模态参考模式
let imageIndex = 0;
for (const ref of activeMode) {
if (typeof ref === "string") {
if (ref.startsWith("imageReference:")) {
// 参考图片
const maxCount = parseInt(ref.split(":")[1], 10);
if (config.imageBase64) {
const images = config.imageBase64.slice(imageIndex, imageIndex + maxCount);
for (const base64 of images) {
content.push({
type: "image_url",
image_url: { url: `data:image/png;base64,${base64}` },
role: "reference_image",
});
}
imageIndex += images.length;
}
}
// videoReference 和 audioReference 需要 URL当前框架暂不支持直接传入
}
}
}
// 映射宽高比
const ratioMap: Record<string, string> = {
"16:9": "16:9",
"9:16": "9:16",
"4:3": "4:3",
"3:4": "3:4",
"1:1": "1:1",
"21:9": "21:9",
};
const ratio = ratioMap[config.aspectRatio] || "16:9";
const body: any = {
model: model.modelName,
content,
ratio,
duration: config.duration,
resolution: config.resolution || "720p",
watermark: false,
};
// 音频控制
if (model.audio === "optional") {
body.generate_audio = config.audio !== false;
} else if (model.audio === true) {
body.generate_audio = true;
} else {
body.generate_audio = false;
}
logger(`[视频生成] 提交任务, 模型: ${model.modelName}, 时长: ${config.duration}s, 分辨率: ${config.resolution}`);
// 提交创建任务
const createResponse = await axios.post(`${baseUrl}/contents/generations/tasks`, body, { headers });
const taskId = createResponse.data?.id;
if (!taskId) {
throw new Error("视频生成任务创建失败未返回任务ID");
}
logger(`[视频生成] 任务已创建, ID: ${taskId}`);
// 轮询查询任务状态
const result = await pollTask(async (): Promise<PollResult> => {
const queryResponse = await axios.get(`${baseUrl}/contents/generations/tasks/${taskId}`, { headers });
const task = queryResponse.data;
logger(`[视频生成] 任务状态: ${task.status}`);
switch (task.status) {
case "succeeded":
if (task.content?.video_url) {
return { completed: true, data: task.content.video_url };
}
return { completed: true, error: "任务成功但未返回视频URL" };
case "failed":
return { completed: true, error: task.error?.message || "视频生成失败" };
case "expired":
return { completed: true, error: "视频生成任务超时" };
case "cancelled":
return { completed: true, error: "视频生成任务已取消" };
default:
// queued / running
return { completed: false };
}
}, 10000, 600000); // 每10秒查询一次最长等待10分钟
if (result.error) {
throw new Error(result.error);
}
return result.data || "";
};
/** TTS请求火山引擎暂无TTS模型配置预留接口 */
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
return "";
};
const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => {
return { hasUpdate: false, latestVersion: "2.0", notice: "" };
};
const updateVendor = async (): Promise<string> => {
return "";
};
// ============================================================
// 导出
// ============================================================
exports.vendor = vendor;
exports.textRequest = textRequest;
exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest;
exports.checkForUpdates = checkForUpdates;
exports.updateVendor = updateVendor;
export {};

View File

@ -140,7 +140,7 @@ export default (toolCpnfig: ToolConfig) => {
},
}),
generate_deriveAsset: tool({
description: "生成衍生资产",
description: "生成衍生资产图片",
inputSchema: z.object({
ids: z.array(z.number()).describe("需要生成的 衍生资产ID"),
}),

View File

@ -1,88 +0,0 @@
import "../type";
import axios from "axios";
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
export default async (input: VideoConfig, config: AIConfig) => {
if (!config.apiKey) throw new Error("缺少API Key");
// const { owned, images, hasStartEndType } = validateVideoConfig(input, config);
const hasStartEndType = input.mode === "startEnd";
const authorization = "Bearer " + config.apiKey.replace(/^Bearer\s*/i, "").trim();
const baseUrl = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks";
const images = input.imageBase64 || [];
// 构建图片内容
const imageContent = images.map((base64, index) => {
const item: Record<string, any> = {
type: "image_url",
image_url: { url: base64 },
};
if (hasStartEndType) {
item.role = index === 0 ? "first_frame" : "last_frame";
} else {
item.role = "reference_image";
}
return item;
});
// 构建请求体
const requestBody: Record<string, any> = {
model: config.model,
content: [{ type: "text", text: input.prompt }, ...imageContent],
duration: input.duration,
resolution: input.resolution,
watermark: false,
};
// 仅当模型支持音频时才添加 generate_audio 字段
if (typeof input?.audio == "boolean") {
requestBody.generate_audio = input.audio ?? false;
}
// 创建视频生成任务
const createResponse = await axios.post(baseUrl, requestBody, {
headers: {
"Content-Type": "application/json",
Authorization: authorization,
},
});
console.log("%c Line:44 🍡 createResponse", "background:#2eafb0", createResponse.data);
const taskId = createResponse.data.id;
if (!taskId) throw new Error("视频任务创建失败");
// 轮询任务状态
return await pollTask(async () => {
const data = await axios.get(`${baseUrl}/${taskId}`, {
headers: { Authorization: authorization },
});
console.log("%c Line:62 🥕 data.data", "background:#e41a6a", data.data);
const { status, content, error } = data.data;
switch (status) {
case "succeeded":
case "completed":
return { completed: true, url: content?.video_url };
case "failed":
case "cancelled":
case "expired":
let errorMsg = "";
try {
errorMsg = typeof error === "string" ? error : JSON.stringify(error);
} catch (e) {
errorMsg = error || "";
}
return { completed: false, error: `任务${status}: ${errorMsg}` };
case "queued":
case "running":
case "unknown":
case "submit":
case "in_progress":
return { completed: false };
default:
return { completed: false, error: `未知状态: ${status}` };
}
});
};

View File

@ -14,6 +14,7 @@ import FormData from "form-data";
import jsonwebtoken from "jsonwebtoken";
import u from "@/utils";
export default function runCode(code: string, vendor?: Record<string, any>) {
code = code.replace(/export\s*\{\s*\};?/g, ""); // 去掉 export {} 以免沙盒环境报错
// 创建一个沙盒
const exports = {};
const sandbox: Record<string, any> = {

View File

@ -1,5 +1,6 @@
{
"compilerOptions": {
"ignoreDeprecations": "6.0",
"target": "ESNext",
"module": "CommonJS",
"moduleResolution": "Node",
@ -12,10 +13,19 @@
"outDir": "build",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
"@/*": [
"src/*"
]
},
"incremental": true,
"typeRoots": ["./node_modules/@types", "./src/types"],
"typeRoots": [
"./node_modules/@types",
"./src/types"
],
"resolveJsonModule": true
}
}
},
"exclude": [
"node_modules",
"data/**/*.ts"
]
}