Merge branch 'develop'
# Conflicts: # data/web/index.html # yarn.lock
This commit is contained in:
commit
c1e789a5a3
36
NOTICES.txt
36
NOTICES.txt
@ -70,6 +70,12 @@ Repository: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: @types/graphlib
|
||||
License: MIT
|
||||
Repository: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: @types/jsonwebtoken
|
||||
License: MIT
|
||||
Repository: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
@ -142,18 +148,18 @@ Repository: https://github.com/motdotla/dotenv
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: dotenv
|
||||
License: BSD-2-Clause
|
||||
Repository: https://github.com/motdotla/dotenv
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: electron-builder
|
||||
License: MIT
|
||||
Repository: https://github.com/electron-userland/electron-builder
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: electron-rebuild
|
||||
License: MIT
|
||||
Repository: https://github.com/electron/electron-rebuild
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: electron
|
||||
License: MIT
|
||||
Repository: https://github.com/electron/electron
|
||||
@ -244,15 +250,15 @@ Repository: https://github.com/remy/nodemon
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: qwen-ai-provider-v5
|
||||
License: Apache-2.0
|
||||
Repository: https://github.com/bolechen/qwen-ai-provider-v5
|
||||
Name: p-limit
|
||||
License: MIT
|
||||
Repository: https://github.com/sindresorhus/p-limit
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: serialize-error
|
||||
License: MIT
|
||||
Repository: https://github.com/sindresorhus/serialize-error
|
||||
Name: qwen-ai-provider-v5
|
||||
License: Apache-2.0
|
||||
Repository: https://github.com/bolechen/qwen-ai-provider-v5
|
||||
|
||||
-----------------------------
|
||||
|
||||
@ -304,6 +310,12 @@ Repository: https://github.com/uuidjs/uuid
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: vercel-minimax-ai-provider
|
||||
License: Apache-2.0
|
||||
Repository: https://github.com/MiniMax-AI/vercel-minimax-ai-provider
|
||||
|
||||
-----------------------------
|
||||
|
||||
Name: vm2
|
||||
License: MIT
|
||||
Repository: https://github.com/patriksimek/vm2
|
||||
|
||||
@ -23,12 +23,12 @@ description: >-
|
||||
| 操作 | 调用 |
|
||||
|------|------|
|
||||
| 读取资产列表 | `get_flowData("assets")` |
|
||||
| 生成资产图片 | `generate_assets_images({ ids: [资产id列表] })` |
|
||||
| 生成资产图片 | `generate_deriveAsset({ ids: [资产id列表] })` |
|
||||
|
||||
### 执行流程
|
||||
|
||||
1. 获取 `assets`,收集所有需要生成图片的资产 id
|
||||
2. 调用 `generate_assets_images({ ids: [资产id列表] })` 生成图片(异步,发起即返回)
|
||||
2. 调用 `generate_deriveAsset({ ids: [资产id列表] })` 生成图片(异步,发起即返回)
|
||||
|
||||
### 约束
|
||||
|
||||
|
||||
318
data/vendor/grsai.ts
vendored
Normal file
318
data/vendor/grsai.ts
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
/**
|
||||
* 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: "base64"; base64: string }
|
||||
| { type: "audio"; sourceType: "base64"; base64: string }
|
||||
| { type: "video"; 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, t: boolean, tl: 0 | 1 | 2 | 3) => any; //文本模型
|
||||
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: "grsai",
|
||||
version: "2.0",
|
||||
author: "Toonflow",
|
||||
name: "Grsai",
|
||||
description: "Grsai AI平台适配,支持文生图、图生图、文生视频、Gemini兼容文本模型 \n [前往中转平台](https://tf.grsai.ai/zh)",
|
||||
inputs: [
|
||||
{ key: "apiKey", label: "API密钥", type: "password", required: true },
|
||||
{ key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "示例:https://grsai.dakka.com.cn" },
|
||||
],
|
||||
inputValues: { apiKey: "", baseUrl: "https://grsai.dakka.com.cn" },
|
||||
models: [
|
||||
{ name: "Nano Banana Fast", modelName: "nano-banana-fast", type: "image", mode: ["text", "singleImage", "multiReference"] },
|
||||
{ name: "Nano Banana 2", modelName: "nano-banana-2", type: "image", mode: ["text", "singleImage", "multiReference"] },
|
||||
{ name: "Nano Banana Pro", modelName: "nano-banana-pro", type: "image", mode: ["text", "singleImage", "multiReference"] },
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 辅助工具
|
||||
// ============================================================
|
||||
|
||||
const getHeaders = () => {
|
||||
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
};
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 适配器函数
|
||||
// ============================================================
|
||||
|
||||
const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
|
||||
return createGoogleGenerativeAI({
|
||||
baseURL: `${vendor.inputValues.baseUrl}/v1beta`,
|
||||
apiKey,
|
||||
}).chat(model.modelName);
|
||||
};
|
||||
|
||||
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const baseUrl = vendor.inputValues.baseUrl;
|
||||
const headers = getHeaders();
|
||||
|
||||
// 构造请求参数
|
||||
const requestBody: any = {
|
||||
model: model.modelName,
|
||||
prompt: config.prompt,
|
||||
aspectRatio: config.aspectRatio,
|
||||
webHook: "-1",
|
||||
shutProgress: true,
|
||||
};
|
||||
|
||||
// 补充模型专属参数
|
||||
if (model.modelName.startsWith("nano-banana")) {
|
||||
requestBody.imageSize = config.size;
|
||||
} else {
|
||||
requestBody.size = config.aspectRatio;
|
||||
requestBody.variants = 1;
|
||||
}
|
||||
|
||||
// 处理参考图
|
||||
if (config.referenceList && config.referenceList.length > 0) {
|
||||
requestBody.urls = config.referenceList.map((img) => img.base64);
|
||||
}
|
||||
|
||||
// 选择接口路径
|
||||
const apiPath = model.modelName.startsWith("nano-banana") ? "/v1/draw/nano-banana" : "/v1/draw/completions";
|
||||
|
||||
logger(`开始提交图片生成任务,模型:${model.modelName}`);
|
||||
const submitResp = await axios.post(`${baseUrl}${apiPath}`, requestBody, { headers });
|
||||
if (submitResp.data.code !== 0) throw new Error(`任务提交失败:${submitResp.data.msg}`);
|
||||
|
||||
const taskId = submitResp.data.data.id;
|
||||
logger(`图片任务提交成功,任务ID:${taskId}`);
|
||||
|
||||
// 轮询结果
|
||||
const pollResult = await pollTask(
|
||||
async () => {
|
||||
const resp = await axios.post(`${baseUrl}/v1/draw/result`, { id: taskId }, { headers });
|
||||
if (resp.data.code !== 0) return { completed: true, error: resp.data.msg };
|
||||
|
||||
const taskData = resp.data.data;
|
||||
if (taskData.status === "failed") return { completed: true, error: taskData.failure_reason || taskData.error };
|
||||
if (taskData.status === "succeeded") {
|
||||
const imgUrl = taskData.results?.[0]?.url || taskData.url;
|
||||
return { completed: true, data: imgUrl };
|
||||
}
|
||||
logger(`图片任务生成中,进度:${taskData.progress}%`);
|
||||
return { completed: false };
|
||||
},
|
||||
3000,
|
||||
600000,
|
||||
);
|
||||
|
||||
if (pollResult.error) throw new Error(pollResult.error);
|
||||
logger(`图片生成完成,开始转换Base64`);
|
||||
return await urlToBase64(pollResult.data!);
|
||||
};
|
||||
|
||||
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const baseUrl = vendor.inputValues.baseUrl;
|
||||
const headers = getHeaders();
|
||||
|
||||
// 构造请求参数
|
||||
const requestBody: any = {
|
||||
model: model.modelName,
|
||||
prompt: config.prompt,
|
||||
aspectRatio: config.aspectRatio,
|
||||
webHook: "-1",
|
||||
shutProgress: true,
|
||||
};
|
||||
|
||||
// 处理参考资源
|
||||
if (config.referenceList && config.referenceList.length > 0) {
|
||||
const imageRefs = config.referenceList.filter((item) => item.type === "image") as Extract<ReferenceList, { type: "image" }>[];
|
||||
if (config.mode.includes("endFrameOptional") && imageRefs.length >= 1) {
|
||||
requestBody.firstFrameUrl = imageRefs[0].base64;
|
||||
if (imageRefs.length >= 2) requestBody.lastFrameUrl = imageRefs[1].base64;
|
||||
} else if (config.mode.some((m) => Array.isArray(m) && m.includes("imageReference:3"))) {
|
||||
requestBody.urls = imageRefs.map((img) => img.base64);
|
||||
}
|
||||
}
|
||||
|
||||
logger(`开始提交视频生成任务,模型:${model.modelName}`);
|
||||
const submitResp = await axios.post(`${baseUrl}/v1/video/veo`, requestBody, { headers });
|
||||
if (submitResp.data.code !== 0) throw new Error(`任务提交失败:${submitResp.data.msg}`);
|
||||
|
||||
const taskId = submitResp.data.data.id;
|
||||
logger(`视频任务提交成功,任务ID:${taskId}`);
|
||||
|
||||
// 轮询结果
|
||||
const pollResult = await pollTask(
|
||||
async () => {
|
||||
const resp = await axios.post(`${baseUrl}/v1/draw/result`, { id: taskId }, { headers });
|
||||
if (resp.data.code !== 0) return { completed: true, error: resp.data.msg };
|
||||
|
||||
const taskData = resp.data.data;
|
||||
if (taskData.status === "failed") return { completed: true, error: taskData.failure_reason || taskData.error };
|
||||
if (taskData.status === "succeeded") {
|
||||
return { completed: true, data: taskData.url };
|
||||
}
|
||||
logger(`视频任务生成中,进度:${taskData.progress}%`);
|
||||
return { completed: false };
|
||||
},
|
||||
5000,
|
||||
1800000,
|
||||
);
|
||||
|
||||
if (pollResult.error) throw new Error(pollResult.error);
|
||||
logger(`视频生成完成,开始转换Base64`);
|
||||
return await urlToBase64(pollResult.data!);
|
||||
};
|
||||
|
||||
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => {
|
||||
return { hasUpdate: false, latestVersion: "1.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 {};
|
||||
638
data/vendor/klingai.ts
vendored
Normal file
638
data/vendor/klingai.ts
vendored
Normal file
@ -0,0 +1,638 @@
|
||||
/**
|
||||
* 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: "base64"; base64: string }
|
||||
| { type: "audio"; sourceType: "base64"; base64: string }
|
||||
| { type: "video"; 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, t: boolean, tl: 0 | 1 | 2 | 3) => 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: "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)\n\n获取 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: true, 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 => {
|
||||
return ref.base64.replace(/^data:[^;]+;base64,/, "");
|
||||
};
|
||||
|
||||
/**
|
||||
* 从 ReferenceList 条目中提取带头的 base64 或 url
|
||||
* 用于 omni-video 接口,该接口的 image_url 支持带前缀的 base64 和 url
|
||||
*/
|
||||
const extractImageUrl = (ref: ReferenceList): string => {
|
||||
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, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
throw new Error("可灵AI不支持文本模型");
|
||||
};
|
||||
|
||||
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.imageRequest = imageRequest;
|
||||
exports.videoRequest = videoRequest;
|
||||
exports.ttsRequest = ttsRequest;
|
||||
|
||||
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
|
||||
export {};
|
||||
399
data/vendor/minimax.ts
vendored
Normal file
399
data/vendor/minimax.ts
vendored
Normal file
@ -0,0 +1,399 @@
|
||||
/**
|
||||
* 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: "base64"; base64: string }
|
||||
| { type: "audio"; sourceType: "base64"; base64: string }
|
||||
| { type: "video"; 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, t: boolean, tl: 0 | 1 | 2 | 3) => 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系列推理文本模型、文生图/图生图、视频生成(文生视频、图生视频、首尾帧生成)能力 \n [前往平台](https://minimaxi.com/)",
|
||||
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 => {
|
||||
return ref.base64.startsWith("data:") ? ref.base64 : `data:image/png;base64,${ref.base64}`;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 适配器函数
|
||||
// ============================================================
|
||||
|
||||
const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
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 {};
|
||||
334
data/vendor/null.ts
vendored
Normal file
334
data/vendor/null.ts
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
/**
|
||||
* 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: "base64"; base64: string }
|
||||
| { type: "audio"; sourceType: "base64"; base64: string }
|
||||
| { type: "video"; 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, t: boolean, tl: 0 | 1 | 2 | 3) => any; //文本模型
|
||||
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: "null",
|
||||
version: "2.0",
|
||||
author: "Toonflow",
|
||||
name: "空模板",
|
||||
description: "## 开发模板,您可以使用此模板进行Vibe Coding",
|
||||
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, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
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 {};
|
||||
|
||||
/**
|
||||
* ============================================================
|
||||
* AI 代码生成指南
|
||||
* ============================================================
|
||||
*
|
||||
* 【概述】
|
||||
* 本文件是 Toonflow AI 供应商适配模板。AI 在生成新供应商适配代码时,
|
||||
* 必须严格遵守以下规则,并要求用户提供目标平台的 curl 调用示例或 API 文档信息作为输入依据。
|
||||
*
|
||||
* 【前置要求】
|
||||
* 在生成代码前,请向用户索要以下信息(至少其一):
|
||||
* 1. 目标 API 的 curl 请求示例(包含请求地址、Headers、Body 结构、响应结构)
|
||||
* 2. 目标 API 的官方文档链接或文档截图/文本内容
|
||||
* 3. 需要适配的模型类型(text / image / video / tts)及其能力说明
|
||||
* 没有足够信息时,应主动追问,不要凭空编造 API 结构。
|
||||
*
|
||||
* 【代码规则】
|
||||
*
|
||||
* 1. 禁止引入任何外部包
|
||||
* 不可使用 import / require,仅能使用本文件「全局声明」区域中已声明的方法和对象,
|
||||
* 包括:axios、logger、jsonwebtoken、zipImage、zipImageResolution、mergeImages、
|
||||
* urlToBase64、pollTask,以及 createOpenAI、createDeepSeek、createZhipu、createQwen、
|
||||
* createAnthropic、createOpenAICompatible、createXai、createMinimax、
|
||||
* createGoogleGenerativeAI 等 AI SDK 工厂函数。
|
||||
*
|
||||
* 2. 禁止在 exports.* 函数外部声明离散的全大写常量
|
||||
* 错误示例:const API_URL = "https://..."; const MAX_RETRY = 3;
|
||||
* 如果确实需要可配置的常量值,必须将其声明在 vendor.inputValues 中,
|
||||
* 通过 vendor.inputValues.xxx 访问,让用户可在界面上配置。
|
||||
* 如果是纯逻辑内部使用的临时变量,应内联在对应的 exports.* 函数体内部,使用小驼峰命名。
|
||||
*
|
||||
* 3. 逻辑尽量聚合在 exports.* 对应的函数内部
|
||||
* 每个适配函数(textRequest / imageRequest / videoRequest / ttsRequest)
|
||||
* 应自包含,将请求构造、发送、轮询、结果解析等逻辑写在函数体内,避免拆分出大量外部辅助函数。
|
||||
* 如果多个函数确实存在公共逻辑(如签名计算、Token 生成、请求头构造),
|
||||
* 可提取为文件内的小驼峰命名函数,放在「适配器函数」区块之前的「辅助工具」区块中,
|
||||
* 且不可使用全大写命名。
|
||||
*
|
||||
* 4. 命名规范
|
||||
* 所有变量、函数一律使用小驼峰命名(camelCase),禁止使用 UPPER_SNAKE_CASE。
|
||||
*
|
||||
* 5. 不需要重新声明类型
|
||||
* 本文件顶部已完整定义了所有接口和类型(VendorConfig、ImageConfig、VideoConfig、
|
||||
* TTSConfig、TextModel、ImageModel、VideoModel、TTSModel、ReferenceList、PollResult 等),
|
||||
* AI 生成代码时直接使用即可,不要重复声明。
|
||||
*
|
||||
* 6. 返回值规范
|
||||
* - textRequest(model):返回 AI SDK 的 chat model 实例(通过 createOpenAI 等工厂函数创建)。
|
||||
* - imageRequest(config, model):返回有头 base64 字符串(如 "data:image/png;base64,...")。
|
||||
* config.referenceList 为 Extract<ReferenceList, { type: "image" }>[] 类型,
|
||||
* 每个引用条目均为 base64 形式(sourceType 固定为 "base64")。
|
||||
* - videoRequest(config, model):返回有头 base64 字符串(如 "data:video/mp4;base64,...")。
|
||||
* config.referenceList 为 ReferenceList[] 类型,可包含 image / video / audio 三种引用,
|
||||
* 每个引用条目均为 base64 形式(sourceType 固定为 "base64")。
|
||||
* 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: "base64"(当前模板固定为 base64)
|
||||
* - 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 Key、Secret、请求地址等)。
|
||||
* - models:根据目标平台支持的模型列表填写,注意正确设置 type 和各模型特有字段。
|
||||
* - VideoModel 的 mode 对应 API 支持的输入模式(参见规则 7 的 VideoMode 说明)。
|
||||
* - VideoModel 的 audio 字段:true(始终生成音频)、false(不生成)、"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. 文件结构
|
||||
* 生成的代码必须保持本模板的整体结构:
|
||||
* 类型定义区 → 全局声明区 → 供应商配置区 → [辅助工具区(可选)] → 适配器函数区 → 导出区
|
||||
* 不要打乱顺序,不要删除已有的结构注释分隔线。
|
||||
* 辅助工具区用于放置多个适配器函数共享的小驼峰命名辅助函数(如 getHeaders、getBaseUrl)。
|
||||
*
|
||||
* 14. 导出规范
|
||||
* 必须导出以下字段(通过 exports.xxx = xxx 赋值):
|
||||
* - exports.vendor(必须)
|
||||
* - exports.textRequest(必须)
|
||||
* - exports.imageRequest(必须)
|
||||
* - exports.videoRequest(必须)
|
||||
* - exports.ttsRequest(必须)
|
||||
* - exports.checkForUpdates(可选)
|
||||
* - exports.updateVendor(可选)
|
||||
* 未实现的适配器函数保留空实现(return ""),不可省略导出。
|
||||
* 文件末尾必须包含 export {}; 以确保文件被识别为模块。
|
||||
*
|
||||
* 【生成流程】
|
||||
* 当用户请求生成新的供应商适配时:
|
||||
* 1. 确认用户已提供 curl 示例或 API 文档。
|
||||
* 2. 分析 API 的认证方式、端点地址、请求/响应结构。
|
||||
* 3. 基于本模板结构,填充 vendor 配置和对应的适配器函数。
|
||||
* 4. 根据当前模板的 ReferenceList 定义,按 base64 形式构造和消费 referenceList。
|
||||
* 5. 仅实现用户需要的模型类型,未用到的函数保留空实现(return "")。
|
||||
* 6. 生成完整可用的代码,确保无语法错误、无遗漏导出。
|
||||
*/
|
||||
169
data/vendor/openai.ts
vendored
Normal file
169
data/vendor/openai.ts
vendored
Normal 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, t: boolean, tl: 0 | 1 | 2 | 3) => 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, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
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 {};
|
||||
522
data/vendor/toonflow.ts
vendored
Normal file
522
data/vendor/toonflow.ts
vendored
Normal file
@ -0,0 +1,522 @@
|
||||
/**
|
||||
* Toonflow官方中转平台 供应商适配
|
||||
* @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: "base64"; base64: string }
|
||||
| { type: "audio"; sourceType: "base64"; base64: string }
|
||||
| { type: "video"; 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, t: boolean, tl: 0 | 1 | 2 | 3) => 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: "toonflow",
|
||||
version: "2.0",
|
||||
author: "Toonflow",
|
||||
name: "Toonflow官方中转平台",
|
||||
description:
|
||||
"## Toonflow官方中转平台\n\nToonflow官方中转平台,提供**文本、图像、视频、音频**等多模态生成能力的中转服务,支持接入多个大模型供应商,方便用户统一管理和调用不同供应商的生成能力。\n\n🔗 [前往中转平台](https://api.toonflow.net/)\n\n如果这个项目对你有帮助,可以考虑支持一下我们的开发工作 ☕",
|
||||
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",
|
||||
mode: ["text", "endFrameOptional"],
|
||||
durationResolutionMap: [{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12], resolution: ["480p", "720p", "1080p"] }],
|
||||
audio: true,
|
||||
},
|
||||
{
|
||||
name: "vidu2 turbo",
|
||||
type: "video",
|
||||
modelName: "ViduQ2-turbo",
|
||||
mode: ["singleImage", "startEndRequired"],
|
||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],
|
||||
audio: false,
|
||||
},
|
||||
{
|
||||
name: "ViduQ3 pro",
|
||||
type: "video",
|
||||
modelName: "ViduQ3-pro",
|
||||
mode: ["singleImage", "startEndRequired"],
|
||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }],
|
||||
audio: false,
|
||||
},
|
||||
{
|
||||
name: "ViduQ2 pro",
|
||||
type: "video",
|
||||
modelName: "ViduQ2-pro",
|
||||
mode: ["singleImage", "startEndRequired"],
|
||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],
|
||||
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"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 辅助工具
|
||||
// ============================================================
|
||||
|
||||
// 从 markdown 内容中提取第一张图片
|
||||
function extractFirstImageFromMd(content: string) {
|
||||
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" };
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 适配器函数
|
||||
// ============================================================
|
||||
|
||||
const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
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> => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
|
||||
const baseUrl = vendor.inputValues.baseUrl;
|
||||
const lowerName = model.modelName.toLowerCase();
|
||||
const imageBase64List = (config.referenceList ?? []).map((r) => r.base64);
|
||||
|
||||
// Gemini / nano 系模型:走 chat/completions 接口,从返回的 markdown 中提取图片
|
||||
if (lowerName.includes("gemini") || lowerName.includes("nano")) {
|
||||
const imageConfigGoogle: Record<string, string> = {
|
||||
aspect_ratio: config.aspectRatio,
|
||||
image_size: config.size,
|
||||
};
|
||||
const messages: any[] = [];
|
||||
if (imageBase64List.length) {
|
||||
messages.push({
|
||||
role: "user",
|
||||
content: imageBase64List.map((b) => ({ type: "image_url", image_url: { url: b } })),
|
||||
});
|
||||
}
|
||||
messages.push({ role: "user", content: config.prompt + "请直接输出图片" });
|
||||
const body = {
|
||||
model: model.modelName,
|
||||
messages,
|
||||
extra_body: { google: { image_config: imageConfigGoogle } },
|
||||
};
|
||||
logger(`[imageRequest] 使用 gemini 适配器,模型: ${model.modelName}`);
|
||||
const response = await fetch(`${baseUrl}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const imageResult = extractFirstImageFromMd(data.choices[0].message.content);
|
||||
if (!imageResult) throw new Error("未能从响应中提取图片");
|
||||
if (imageResult.type === "base64") return imageResult.url;
|
||||
return await urlToBase64(imageResult.url);
|
||||
}
|
||||
|
||||
// 豆包 / seedream 系模型:走 images/generations 接口
|
||||
if (lowerName.includes("doubao") || lowerName.includes("seedream")) {
|
||||
const effectiveSize = config.size === "1K" ? "2K" : config.size;
|
||||
const sizeMap: Record<string, Record<string, string>> = {
|
||||
"16:9": { "2K": "2848x1600", "4K": "4096x2304" },
|
||||
"9:16": { "2K": "1600x2848", "4K": "2304x4096" },
|
||||
};
|
||||
const resolvedSize = sizeMap[config.aspectRatio]?.[effectiveSize];
|
||||
const body: Record<string, any> = {
|
||||
model: model.modelName,
|
||||
prompt: config.prompt,
|
||||
size: resolvedSize,
|
||||
response_format: "url",
|
||||
sequential_image_generation: "disabled",
|
||||
stream: false,
|
||||
watermark: false,
|
||||
...(imageBase64List.length && { image: imageBase64List }),
|
||||
};
|
||||
logger(`[imageRequest] 使用 doubao 适配器,模型: ${model.modelName}`);
|
||||
const response = await fetch(`${baseUrl}/images/generations`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const resultUrl = data.data[0].url;
|
||||
return await urlToBase64(resultUrl);
|
||||
}
|
||||
|
||||
throw new Error(`不支持的图像模型: ${model.modelName}`);
|
||||
};
|
||||
|
||||
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
|
||||
const baseUrl = vendor.inputValues.baseUrl;
|
||||
const lowerName = model.modelName.toLowerCase();
|
||||
|
||||
// 当前激活的单一 VideoMode(取第一个非数组模式,或数组模式)
|
||||
const activeMode = config.mode[0];
|
||||
const imageRefs = (config.referenceList ?? []).filter((r) => r.type === "image").map((r) => r.base64);
|
||||
const videoRefs = (config.referenceList ?? []).filter((r) => r.type === "video").map((r) => r.base64);
|
||||
const audioRefs = (config.referenceList ?? []).filter((r) => r.type === "audio").map((r) => r.base64);
|
||||
|
||||
// 构建模型专属 metadata
|
||||
let metadata: Record<string, any> = {};
|
||||
|
||||
if (lowerName.includes("wan")) {
|
||||
// 万象系列
|
||||
if (
|
||||
(activeMode === "startEndRequired" || activeMode === "endFrameOptional" || activeMode === "startFrameOptional") &&
|
||||
imageRefs.length >= 2
|
||||
) {
|
||||
if (imageRefs[0]) metadata.first_frame_url = imageRefs[0];
|
||||
if (imageRefs[1]) metadata.last_frame_url = imageRefs[1];
|
||||
} else if (imageRefs.length) {
|
||||
metadata.img_url = imageRefs[0];
|
||||
}
|
||||
if (typeof config.audio === "boolean") metadata.audio = config.audio;
|
||||
|
||||
// 万象需要额外传 size 字段
|
||||
const wanSizeMap: 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 wanSize = wanSizeMap[config.resolution]?.[config.aspectRatio];
|
||||
const body: Record<string, any> = {
|
||||
model: model.modelName,
|
||||
prompt: config.prompt,
|
||||
duration: config.duration,
|
||||
size: wanSize,
|
||||
metadata,
|
||||
};
|
||||
logger(`[videoRequest] 提交万象视频任务,模型: ${model.modelName}`);
|
||||
const response = await fetch(`${baseUrl}/video/generations`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const taskId = data.id;
|
||||
logger(`[videoRequest] 万象任务ID: ${taskId}`);
|
||||
const res = await pollTask(async () => {
|
||||
const queryResponse = await fetch(`${baseUrl}/video/generations/${taskId}`, {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
});
|
||||
if (!queryResponse.ok) {
|
||||
const errorText = await queryResponse.text();
|
||||
throw new Error(`轮询失败,状态码: ${queryResponse.status}, 错误信息: ${errorText}`);
|
||||
}
|
||||
const queryData = await queryResponse.json();
|
||||
const status = queryData?.status ?? queryData?.data?.status;
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "SUCCESS":
|
||||
case "success":
|
||||
return { completed: true, data: queryData.data.result_url };
|
||||
case "FAILURE":
|
||||
case "failed":
|
||||
return { completed: true, error: queryData?.data?.fail_reason ?? "视频生成失败" };
|
||||
default:
|
||||
return { completed: false };
|
||||
}
|
||||
});
|
||||
if (res.error) throw new Error(res.error);
|
||||
return await urlToBase64(res.data!);
|
||||
}
|
||||
|
||||
if (lowerName.includes("doubao") || lowerName.includes("seedance")) {
|
||||
// 豆包/Seedance 系列
|
||||
metadata = {
|
||||
...(typeof config.audio === "boolean" && { generate_audio: config.audio }),
|
||||
ratio: config.aspectRatio,
|
||||
image_roles: [] as string[],
|
||||
references: [] as string[],
|
||||
};
|
||||
if (Array.isArray(activeMode)) {
|
||||
// 多参考模式
|
||||
imageRefs.forEach((b) => metadata.references.push(b));
|
||||
videoRefs.forEach((b) => metadata.references.push(b));
|
||||
audioRefs.forEach((b) => metadata.references.push(b));
|
||||
} else if (activeMode === "startEndRequired" || activeMode === "endFrameOptional" || activeMode === "startFrameOptional") {
|
||||
imageRefs.forEach((_, i) => (metadata.image_roles as string[]).push(i === 0 ? "first_frame" : "last_frame"));
|
||||
} else if (activeMode === "singleImage") {
|
||||
imageRefs.forEach(() => (metadata.image_roles as string[]).push("reference_image"));
|
||||
}
|
||||
} else if (lowerName.includes("vidu")) {
|
||||
// Vidu 系列
|
||||
metadata = {
|
||||
aspect_ratio: config.aspectRatio,
|
||||
audio: config.audio ?? false,
|
||||
off_peak: false,
|
||||
};
|
||||
} else if (lowerName.includes("kling")) {
|
||||
// 可灵系列
|
||||
metadata = { aspect_ratio: config.aspectRatio };
|
||||
if (Array.isArray(activeMode)) {
|
||||
metadata.reference = [...imageRefs, ...videoRefs, ...audioRefs];
|
||||
} else if (activeMode === "endFrameOptional" && imageRefs.length) {
|
||||
metadata.image_tail = imageRefs[0];
|
||||
} else if (activeMode === "startEndRequired" && imageRefs.length >= 2) {
|
||||
metadata.image_list = [
|
||||
{ image_url: imageRefs[0], type: "first_frame" },
|
||||
{ image_url: imageRefs[1], type: "last_frame" },
|
||||
];
|
||||
} else if (activeMode === "singleImage" && imageRefs.length) {
|
||||
metadata.image = imageRefs[0];
|
||||
}
|
||||
}
|
||||
|
||||
// 公共请求体(非万象通用路径)
|
||||
const publicBody: Record<string, any> = {
|
||||
model: model.modelName,
|
||||
...(!Array.isArray(activeMode) && imageRefs.length ? { images: imageRefs } : {}),
|
||||
prompt: config.prompt,
|
||||
duration: config.duration,
|
||||
metadata,
|
||||
};
|
||||
|
||||
logger(`[videoRequest] 提交视频任务,模型: ${model.modelName}`);
|
||||
const response = await fetch(`${baseUrl}/video/generations`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(publicBody),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`请求失败,状态码: ${response.status}, 错误信息: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const taskId = data.id;
|
||||
logger(`[videoRequest] 任务ID: ${taskId}`);
|
||||
|
||||
const res = await pollTask(async () => {
|
||||
const queryResponse = await fetch(`${baseUrl}/video/generations/${taskId}`, {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
});
|
||||
if (!queryResponse.ok) {
|
||||
const errorText = await queryResponse.text();
|
||||
throw new Error(`轮询失败,状态码: ${queryResponse.status}, 错误信息: ${errorText}`);
|
||||
}
|
||||
const queryData = await queryResponse.json();
|
||||
const status = queryData?.status ?? queryData?.data?.status;
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "SUCCESS":
|
||||
case "success":
|
||||
return { completed: true, data: queryData.data.result_url };
|
||||
case "FAILURE":
|
||||
case "failed":
|
||||
return { completed: true, error: queryData?.data?.fail_reason ?? "视频生成失败" };
|
||||
default:
|
||||
return { completed: false };
|
||||
}
|
||||
});
|
||||
|
||||
if (res.error) throw new Error(res.error);
|
||||
return await urlToBase64(res.data!);
|
||||
};
|
||||
|
||||
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 {};
|
||||
368
data/vendor/vidu.ts
vendored
Normal file
368
data/vendor/vidu.ts
vendored
Normal file
@ -0,0 +1,368 @@
|
||||
//如需遥测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: "vidu",
|
||||
author: "搬砖的Coder",
|
||||
description:
|
||||
"Vidu 官方视频生成平台。 [前往平台](https://platform.vidu.cn/login/)",
|
||||
name: "Vidu 开放平台",
|
||||
inputs: [
|
||||
{ key: "apiKey", label: "API密钥", type: "password", required: true, placeholder: "请到Vidu官方申请" },
|
||||
{ key: "baseUrl", label: "接口路径", type: "url", required: true, 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"],
|
||||
},
|
||||
],
|
||||
};
|
||||
exports.vendor = vendor;
|
||||
|
||||
// ==================== 适配器函数 ====================
|
||||
|
||||
// 文本请求函数
|
||||
const textRequest: (textModel: TextModel) => { url: string; model: string } = (textModel) => {
|
||||
throw new Error("当前供应商仅支持视频大模型,谢谢!");
|
||||
};
|
||||
exports.textRequest = textRequest;
|
||||
|
||||
//图片请求函数
|
||||
interface ImageConfig {
|
||||
prompt: string; //图片提示词
|
||||
imageBase64: string[]; //输入的图片提示词
|
||||
size: "1K" | "2K" | "4K"; // 图片尺寸
|
||||
aspectRatio: `${number}:${number}`; // 长宽比
|
||||
}
|
||||
const imageRequest = async (imageConfig: ImageConfig, imageModel: ImageModel) => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = vendor.inputValues.apiKey.replace("Token ", "");
|
||||
|
||||
const size = imageConfig.size === "1K" ? "2K" : imageConfig.size;
|
||||
const sizeMap: Record<string, Record<string, string>> = {
|
||||
"16:9": {
|
||||
"1k": "1920x1080",
|
||||
"2K": "2848x1600",
|
||||
"4K": "4096x2304",
|
||||
},
|
||||
"9:16": {
|
||||
"1k": "1920x1080",
|
||||
"2K": "1600x2848",
|
||||
"4K": "2304x4096",
|
||||
},
|
||||
};
|
||||
|
||||
const body: Record<string, any> = {
|
||||
model: imageModel.modelName,
|
||||
prompt: imageConfig.prompt,
|
||||
aspect_ratio: sizeMap[imageConfig.aspectRatio][size],
|
||||
seed: 0,
|
||||
resolution: size,
|
||||
...(imageConfig.imageBase64 && { image: imageConfig.imageBase64 }),
|
||||
};
|
||||
|
||||
const createImageUrl = vendor.inputValues.baseUrl + "/reference2image";
|
||||
const response = await fetch(createImageUrl, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Token ${apiKey}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(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();
|
||||
const res = await checkTaskResult(data.task_id);
|
||||
if (!res.data) {
|
||||
throw new Error("图片未能生成");
|
||||
}
|
||||
const list = JSON.parse(JSON.stringify(res.data));
|
||||
return list[0].url;
|
||||
};
|
||||
exports.imageRequest = imageRequest;
|
||||
|
||||
interface VideoConfig {
|
||||
duration: number;
|
||||
resolution: string;
|
||||
aspectRatio: "16:9" | "9:16";
|
||||
prompt: string;
|
||||
imageBase64?: string[];
|
||||
audio?: boolean;
|
||||
mode:
|
||||
| "singleImage" // 单图
|
||||
| "multiImage" // 多图模式
|
||||
| "gridImage" // 网格单图(传入一张图片,但该图片是网格图)
|
||||
| "startEndRequired" // 首尾帧(两张都得有)
|
||||
| "endFrameOptional" // 首尾帧(尾帧可选)
|
||||
| "startFrameOptional" // 首尾帧(首帧可选)
|
||||
| "text" // 文本生视频
|
||||
| ("video" | "image" | "audio" | "text")[]; // 混合参考
|
||||
}
|
||||
|
||||
// 构建 各个平台的metadata参数
|
||||
|
||||
const buildViduMetadata = (videoConfig: VideoConfig) => ({
|
||||
aspect_ratio: videoConfig.aspectRatio,
|
||||
audio: videoConfig.audio ?? false,
|
||||
off_peak: false,
|
||||
});
|
||||
|
||||
type MetadataBuilder = (config: VideoConfig) => Record<string, any>;
|
||||
const METADATA_BUILDERS: Array<[string, MetadataBuilder]> = [["vidu", buildViduMetadata]];
|
||||
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 checkTaskResult = async (taskId: string) => {
|
||||
const queryUrl = vendor.inputValues.baseUrl + "/tasks/{id}/creations";
|
||||
const apiKey = vendor.inputValues.apiKey;
|
||||
const res = await pollTask(async () => {
|
||||
const queryResponse = await fetch(queryUrl.replace("{id}", taskId), {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Token ${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?.state ?? queryData?.data?.state;
|
||||
const fail_reason = queryData?.data?.err_code ?? queryData?.data;
|
||||
switch (status) {
|
||||
case "completed":
|
||||
case "SUCCESS":
|
||||
case "success":
|
||||
return { completed: true, data: queryData.creations };
|
||||
case "FAILURE":
|
||||
case "failed":
|
||||
return { completed: false, error: fail_reason || "生成失败" };
|
||||
default:
|
||||
return { completed: false };
|
||||
}
|
||||
});
|
||||
if (res.error) throw new Error(res.error);
|
||||
return res;
|
||||
};
|
||||
|
||||
const videoRequest = async (videoConfig: VideoConfig, videoModel: VideoModel) => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = vendor.inputValues.apiKey.replace("Token ", "");
|
||||
|
||||
// 构建每个模型对应的附加参数
|
||||
const metadata = buildModelMetadata(videoModel.modelName, videoConfig);
|
||||
|
||||
//公共请求参数
|
||||
const publicBody = {
|
||||
model: videoModel.modelName,
|
||||
...(videoConfig.imageBase64 && videoConfig.imageBase64.length ? { images: videoConfig.imageBase64 } : {}),
|
||||
prompt: videoConfig.prompt,
|
||||
size: videoConfig.resolution,
|
||||
duration: videoConfig.duration,
|
||||
metadata: metadata,
|
||||
};
|
||||
|
||||
const requestUrl = vendor.inputValues.baseUrl + "/start-end2video";
|
||||
const response = await fetch(requestUrl, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Token ${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 result = await checkTaskResult(taskId);
|
||||
return result.data;
|
||||
};
|
||||
exports.videoRequest = videoRequest;
|
||||
|
||||
interface TTSConfig {
|
||||
text: string;
|
||||
voice: string;
|
||||
speechRate: number;
|
||||
pitchRate: number;
|
||||
volume: number;
|
||||
}
|
||||
const ttsRequest = async (ttsConfig: TTSConfig, ttsModel: TTSModel) => {
|
||||
throw new Error("Vidu 暂不支持语音合成(TTS)");
|
||||
};
|
||||
578
data/vendor/volcengine.ts
vendored
Normal file
578
data/vendor/volcengine.ts
vendored
Normal file
@ -0,0 +1,578 @@
|
||||
/**
|
||||
* 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)[];
|
||||
}
|
||||
|
||||
type ReferenceList =
|
||||
| { type: "image"; sourceType: "base64"; base64: string }
|
||||
| { type: "audio"; sourceType: "base64"; base64: string }
|
||||
| { type: "video"; 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, t: boolean, tl: 0 | 1 | 2 | 3) => 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",
|
||||
version: "2.0",
|
||||
author: "leeqi",
|
||||
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"],
|
||||
},
|
||||
// ===================== 视频生成模型 =====================
|
||||
{
|
||||
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"] }],
|
||||
},
|
||||
{
|
||||
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"] }],
|
||||
},
|
||||
{
|
||||
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"] }],
|
||||
},
|
||||
{
|
||||
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"] }],
|
||||
},
|
||||
{
|
||||
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"] }],
|
||||
},
|
||||
{
|
||||
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(/\/+$/, "");
|
||||
|
||||
// ============================================================
|
||||
// 适配器函数
|
||||
// ============================================================
|
||||
|
||||
const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
|
||||
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
|
||||
|
||||
const effortMap: Record<number, string> = {
|
||||
0: "minimal",
|
||||
1: "low",
|
||||
2: "medium",
|
||||
3: "high",
|
||||
};
|
||||
|
||||
return createOpenAI({
|
||||
baseURL: getBaseUrl(),
|
||||
apiKey,
|
||||
compatibility: "compatible",
|
||||
fetch: async (url: string, options?: RequestInit) => {
|
||||
const rawBody = JSON.parse((options?.body as string) ?? "{}");
|
||||
const modifiedBody = {
|
||||
...rawBody,
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
},
|
||||
reasoning_effort: effortMap[thinkLevel],
|
||||
};
|
||||
return await fetch(url, {
|
||||
...options,
|
||||
body: JSON.stringify(modifiedBody),
|
||||
});
|
||||
},
|
||||
}).chat(model.modelName);
|
||||
};
|
||||
|
||||
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
|
||||
const baseUrl = getBaseUrl();
|
||||
const headers = getHeaders();
|
||||
|
||||
const content: any[] = [];
|
||||
|
||||
if (config.prompt) {
|
||||
content.push({ type: "text", text: config.prompt });
|
||||
}
|
||||
|
||||
if (config.referenceList && config.referenceList.length > 0) {
|
||||
for (const ref of config.referenceList) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: ref.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();
|
||||
|
||||
const content: any[] = [];
|
||||
|
||||
if (config.prompt) {
|
||||
content.push({ type: "text", text: config.prompt });
|
||||
}
|
||||
|
||||
const activeMode = config.mode && config.mode.length > 0 ? config.mode[0] : "text";
|
||||
|
||||
if (typeof activeMode === "string") {
|
||||
switch (activeMode) {
|
||||
case "singleImage": {
|
||||
const firstImage = config.referenceList?.find((r) => r.type === "image");
|
||||
if (firstImage) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: firstImage.base64 },
|
||||
role: "first_frame",
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "startFrameOptional": {
|
||||
const images = config.referenceList?.filter((r) => r.type === "image") ?? [];
|
||||
if (images.length > 0) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: images[0].base64 },
|
||||
role: "first_frame",
|
||||
});
|
||||
if (images.length > 1) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: images[1].base64 },
|
||||
role: "last_frame",
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "startEndRequired": {
|
||||
const images = config.referenceList?.filter((r) => r.type === "image") ?? [];
|
||||
if (images.length >= 2) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: images[0].base64 },
|
||||
role: "first_frame",
|
||||
});
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: images[1].base64 },
|
||||
role: "last_frame",
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "endFrameOptional": {
|
||||
const images = config.referenceList?.filter((r) => r.type === "image") ?? [];
|
||||
if (images.length > 0) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: images[0].base64 },
|
||||
role: "first_frame",
|
||||
});
|
||||
if (images.length > 1) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: images[1].base64 },
|
||||
role: "last_frame",
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "text":
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (Array.isArray(activeMode)) {
|
||||
// 多模态参考模式:按类型分别提取并添加
|
||||
const imageRefs = config.referenceList?.filter((r) => r.type === "image") ?? [];
|
||||
const videoRefs = config.referenceList?.filter((r) => r.type === "video") ?? [];
|
||||
const audioRefs = config.referenceList?.filter((r) => r.type === "audio") ?? [];
|
||||
|
||||
for (const refDef of activeMode) {
|
||||
if (typeof refDef === "string") {
|
||||
if (refDef.startsWith("imageReference:")) {
|
||||
const maxCount = parseInt(refDef.split(":")[1], 10);
|
||||
for (const ref of imageRefs.slice(0, maxCount)) {
|
||||
content.push({
|
||||
type: "image_url",
|
||||
image_url: { url: ref.base64 },
|
||||
role: "reference_image",
|
||||
});
|
||||
}
|
||||
} else if (refDef.startsWith("videoReference:")) {
|
||||
const maxCount = parseInt(refDef.split(":")[1], 10);
|
||||
for (const ref of videoRefs.slice(0, maxCount)) {
|
||||
content.push({
|
||||
type: "video_url",
|
||||
video_url: { url: ref.base64 },
|
||||
role: "reference_video",
|
||||
});
|
||||
}
|
||||
} else if (refDef.startsWith("audioReference:")) {
|
||||
const maxCount = parseInt(refDef.split(":")[1], 10);
|
||||
for (const ref of audioRefs.slice(0, maxCount)) {
|
||||
content.push({
|
||||
type: "audio_url",
|
||||
audio_url: { url: ref.base64 },
|
||||
role: "reference_audio",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const body: any = {
|
||||
model: model.modelName,
|
||||
content,
|
||||
ratio: config.aspectRatio,
|
||||
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:
|
||||
return { completed: false };
|
||||
}
|
||||
},
|
||||
10000,
|
||||
600000,
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
return await urlToBase64(result.data!);
|
||||
};
|
||||
|
||||
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 {};
|
||||
@ -1 +1 @@
|
||||
1.1.2
|
||||
1.1.3
|
||||
2857
data/web/index.html
2857
data/web/index.html
File diff suppressed because one or more lines are too long
14
nodemon.json
Normal file
14
nodemon.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"data/*",
|
||||
"build/*",
|
||||
"dist/*",
|
||||
"router.ts",
|
||||
"database.d.ts"
|
||||
],
|
||||
"events": {
|
||||
"restart": ""
|
||||
},
|
||||
"delay": 0
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "toonflow",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。",
|
||||
"author": "HBAI-Ltd <ltlctools@outlook.com>",
|
||||
"license": "Apache-2.0",
|
||||
@ -49,7 +49,6 @@
|
||||
"better-sqlite3": "^12.8.0",
|
||||
"compressing": "^2.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"custom-electron-titlebar": "^4.2.8",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"express-ws": "^5.0.2",
|
||||
|
||||
@ -61,7 +61,34 @@ checker.init({ start: process.cwd() }, (err: Error, packages: Record<string, any
|
||||
|
||||
// 排除名单过滤
|
||||
const filteredDeclare = needDeclare.filter((pkg) => pkg.name && !excludeNames.some((exName) => pkg.name.startsWith(exName)));
|
||||
const content = filteredDeclare
|
||||
|
||||
// 去重:同一个 name@version 只保留一条,并合并 licenses
|
||||
const dedupedDeclare = Array.from(
|
||||
filteredDeclare
|
||||
.reduce((acc, pkg) => {
|
||||
const key = `${pkg.name}@${pkg.version}`;
|
||||
const licenseList = Array.isArray(pkg.licenses) ? pkg.licenses : [pkg.licenses];
|
||||
const existing = acc.get(key);
|
||||
|
||||
if (!existing) {
|
||||
acc.set(key, {
|
||||
...pkg,
|
||||
licenses: [...new Set(licenseList.filter(Boolean))],
|
||||
});
|
||||
return acc;
|
||||
}
|
||||
|
||||
const existingLicenses = Array.isArray(existing.licenses) ? existing.licenses : [existing.licenses];
|
||||
existing.licenses = [...new Set([...existingLicenses, ...licenseList].filter(Boolean))];
|
||||
if (!existing.repository && pkg.repository) {
|
||||
existing.repository = pkg.repository;
|
||||
}
|
||||
return acc;
|
||||
}, new Map<string, PackageInfo>())
|
||||
.values()
|
||||
);
|
||||
|
||||
const content = dedupedDeclare
|
||||
.map(
|
||||
(pkg) =>
|
||||
`Name: ${pkg.name}\nLicense: ${Array.isArray(pkg.licenses) ? pkg.licenses.join(", ") : pkg.licenses}\nRepository: ${pkg.repository ?? "N/A"}`
|
||||
|
||||
@ -7,7 +7,7 @@ import Module from "module";
|
||||
app.commandLine.appendSwitch("disable-gpu-shader-disk-cache");
|
||||
app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
|
||||
|
||||
const TARGET_ENTRIES = new Set(["assets", "models", "serve", "skills", "web"]);
|
||||
const TARGET_ENTRIES = new Set(["assets", "models", "serve", "skills", "web", "vendor"]);
|
||||
|
||||
function copyDir(src: string, dest: string): void {
|
||||
if (!fs.existsSync(src)) return;
|
||||
|
||||
@ -86,7 +86,12 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
description: "新增或更新衍生资产",
|
||||
inputSchema: z.object({
|
||||
assetsId: z.number().describe("关联的资产ID"),
|
||||
id: z.number().nullable().describe("衍生资产ID,如果新增则为空"),
|
||||
id: z.preprocess(
|
||||
(val) => {
|
||||
if (val === "null" || val === "" || val === undefined) return null;
|
||||
return val;
|
||||
},
|
||||
z.number().nullable().describe("衍生资产ID,如果新增则为空")),
|
||||
name: z.string().describe("衍生资产名称"),
|
||||
desc: z.string().describe("衍生资产描述"),
|
||||
}),
|
||||
@ -140,7 +145,7 @@ export default (toolCpnfig: ToolConfig) => {
|
||||
},
|
||||
}),
|
||||
generate_deriveAsset: tool({
|
||||
description: "生成衍生资产",
|
||||
description: "生成衍生资产图片",
|
||||
inputSchema: z.object({
|
||||
ids: z.array(z.number()).describe("需要生成的 衍生资产ID"),
|
||||
}),
|
||||
|
||||
21
src/app.ts
21
src/app.ts
@ -12,19 +12,6 @@ import fs from "fs";
|
||||
import u from "@/utils";
|
||||
import jwt from "jsonwebtoken";
|
||||
import socketInit from "@/socket/index";
|
||||
import path from "path";
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
const APP_VERSION: string = (() => {
|
||||
if (typeof __APP_VERSION__ !== "undefined") {
|
||||
return __APP_VERSION__;
|
||||
}
|
||||
// 开发环境回退:从 package.json 读取
|
||||
const pkgPath = path.resolve(process.cwd(), "package.json");
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
||||
return pkg.version;
|
||||
})();
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
@ -49,7 +36,7 @@ export default async function startServe(randomPort: Boolean = false) {
|
||||
fs.mkdirSync(ossDir, { recursive: true });
|
||||
}
|
||||
console.log("文件目录:", ossDir);
|
||||
app.use("/oss", express.static(ossDir));
|
||||
app.use("/oss", express.static(ossDir, { acceptRanges: false }));
|
||||
// skills 静态资源
|
||||
const skillsDir = u.getPath("skills");
|
||||
if (!fs.existsSync(skillsDir)) {
|
||||
@ -62,7 +49,7 @@ export default async function startServe(randomPort: Boolean = false) {
|
||||
(req, res, next) => {
|
||||
/\.(jpe?g|png|gif|webp|svg|ico|bmp)$/i.test(req.path) ? next() : res.status(403).end();
|
||||
},
|
||||
express.static(skillsDir),
|
||||
express.static(skillsDir, { acceptRanges: false }),
|
||||
);
|
||||
|
||||
// assets 静态资源
|
||||
@ -71,13 +58,13 @@ export default async function startServe(randomPort: Boolean = false) {
|
||||
fs.mkdirSync(assetsDir, { recursive: true });
|
||||
}
|
||||
console.log("文件目录:", assetsDir);
|
||||
app.use("/assets", express.static(assetsDir));
|
||||
app.use("/assets", express.static(assetsDir, { acceptRanges: false }));
|
||||
|
||||
// data/web 静态网站
|
||||
const webDir = u.getPath("web");
|
||||
if (fs.existsSync(webDir)) {
|
||||
console.log("静态网站目录:", webDir);
|
||||
app.use(express.static(webDir));
|
||||
app.use(express.static(webDir, { acceptRanges: false }));
|
||||
} else {
|
||||
console.warn("静态网站目录不存在:", webDir);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
536
src/router.ts
536
src/router.ts
@ -1,4 +1,4 @@
|
||||
// @routes-hash 5f94013d5d29facaed754713858ed40b
|
||||
// @routes-hash 62534cff632db5d31442f1bca1932925
|
||||
import { Express } from "express";
|
||||
|
||||
import route1 from "./routes/agents/clearMemory";
|
||||
@ -8,137 +8,142 @@ import route4 from "./routes/artStyle/editArtStyle";
|
||||
import route5 from "./routes/artStyle/extractStylePrompt";
|
||||
import route6 from "./routes/artStyle/getArtStyle";
|
||||
import route7 from "./routes/assets/addAssets";
|
||||
import route8 from "./routes/assets/batchDelete";
|
||||
import route9 from "./routes/assets/batchGenerationData";
|
||||
import route10 from "./routes/assets/delAssets";
|
||||
import route11 from "./routes/assets/delImage";
|
||||
import route12 from "./routes/assets/getAssetsApi";
|
||||
import route13 from "./routes/assets/getImage";
|
||||
import route14 from "./routes/assets/getMaterialData";
|
||||
import route15 from "./routes/assets/pollingImageAssets";
|
||||
import route16 from "./routes/assets/pollingPromptAssets";
|
||||
import route17 from "./routes/assets/saveAssets";
|
||||
import route18 from "./routes/assets/updateAssets";
|
||||
import route19 from "./routes/assets/uploadClip";
|
||||
import route20 from "./routes/assetsGenerate/batchGenerateImageAssets";
|
||||
import route21 from "./routes/assetsGenerate/batchPolishAssetsPrompt";
|
||||
import route22 from "./routes/assetsGenerate/cancelGenerate";
|
||||
import route23 from "./routes/assetsGenerate/generateAssets";
|
||||
import route24 from "./routes/assetsGenerate/polishAssetsPrompt";
|
||||
import route25 from "./routes/cornerScape/getAllAssets";
|
||||
import route26 from "./routes/general/generalStatistics";
|
||||
import route27 from "./routes/general/getSingleProject";
|
||||
import route28 from "./routes/general/updateProject";
|
||||
import route29 from "./routes/login/login";
|
||||
import route30 from "./routes/migrate/migrateData";
|
||||
import route31 from "./routes/modelSelect/getModelDetail";
|
||||
import route32 from "./routes/modelSelect/getModelList";
|
||||
import route33 from "./routes/novel/addNovel";
|
||||
import route34 from "./routes/novel/batchDeleteNovel";
|
||||
import route35 from "./routes/novel/delNovel";
|
||||
import route36 from "./routes/novel/event/batchDeleteEvent";
|
||||
import route37 from "./routes/novel/event/deletEvent";
|
||||
import route38 from "./routes/novel/event/generateEvents";
|
||||
import route39 from "./routes/novel/event/getEvent";
|
||||
import route40 from "./routes/novel/getNovel";
|
||||
import route41 from "./routes/novel/getNovelData";
|
||||
import route42 from "./routes/novel/getNovelEventState";
|
||||
import route43 from "./routes/novel/getNovelIndex";
|
||||
import route44 from "./routes/novel/updateNovel";
|
||||
import route45 from "./routes/other/deleteAllData";
|
||||
import route46 from "./routes/other/getVersion";
|
||||
import route47 from "./routes/production/assets/batchGenerateAssetsImage";
|
||||
import route48 from "./routes/production/assets/deleteAssetsDireve";
|
||||
import route49 from "./routes/production/assets/getAssetsData";
|
||||
import route50 from "./routes/production/assets/pollingImage";
|
||||
import route51 from "./routes/production/assets/updateAssetsUrl";
|
||||
import route52 from "./routes/production/editImage/generateFlowImage";
|
||||
import route53 from "./routes/production/editImage/getImageDefaultModle";
|
||||
import route54 from "./routes/production/editImage/getImageFlow";
|
||||
import route55 from "./routes/production/editImage/saveImageFlow";
|
||||
import route56 from "./routes/production/editImage/updateImageFlow";
|
||||
import route57 from "./routes/production/editImage/uploadImage";
|
||||
import route58 from "./routes/production/getFlowData";
|
||||
import route59 from "./routes/production/getProductionData";
|
||||
import route60 from "./routes/production/getStoryboardData";
|
||||
import route61 from "./routes/production/saveFlowData";
|
||||
import route62 from "./routes/production/storyboard/addStoryboard";
|
||||
import route63 from "./routes/production/storyboard/batchAddStoryboardInfo";
|
||||
import route64 from "./routes/production/storyboard/batchGenerateImage";
|
||||
import route65 from "./routes/production/storyboard/downPreviewImage";
|
||||
import route66 from "./routes/production/storyboard/editStoryboardInfo";
|
||||
import route67 from "./routes/production/storyboard/getStoryboardData";
|
||||
import route68 from "./routes/production/storyboard/pollingImage";
|
||||
import route69 from "./routes/production/storyboard/previewImage";
|
||||
import route70 from "./routes/production/storyboard/removeFrame";
|
||||
import route71 from "./routes/production/storyboard/updateStoryboardUrl";
|
||||
import route72 from "./routes/production/workbench/addTrack";
|
||||
import route73 from "./routes/production/workbench/deleteTrack";
|
||||
import route74 from "./routes/production/workbench/delVideo";
|
||||
import route75 from "./routes/production/workbench/generateVideo";
|
||||
import route76 from "./routes/production/workbench/generateVideoPrompt";
|
||||
import route77 from "./routes/production/workbench/getGenerateData";
|
||||
import route78 from "./routes/production/workbench/getVideoList";
|
||||
import route79 from "./routes/production/workbench/getVideoModelDetail";
|
||||
import route80 from "./routes/production/workbench/selectVideo";
|
||||
import route81 from "./routes/production/workbench/updateVideoPrompt";
|
||||
import route82 from "./routes/project/addDirectorManual";
|
||||
import route83 from "./routes/project/addProject";
|
||||
import route84 from "./routes/project/addVisualManual";
|
||||
import route85 from "./routes/project/deleteDirectorManual";
|
||||
import route86 from "./routes/project/deleteVisualManual";
|
||||
import route87 from "./routes/project/delProject";
|
||||
import route88 from "./routes/project/editDirectorlManual";
|
||||
import route89 from "./routes/project/editProject";
|
||||
import route90 from "./routes/project/editVisualManual";
|
||||
import route91 from "./routes/project/getProject";
|
||||
import route92 from "./routes/project/getVisualManual";
|
||||
import route93 from "./routes/project/queryDirectorManual";
|
||||
import route94 from "./routes/project/visualManual";
|
||||
import route95 from "./routes/script/addScript";
|
||||
import route96 from "./routes/script/batchAddScript";
|
||||
import route97 from "./routes/script/delScript";
|
||||
import route98 from "./routes/script/exportScript";
|
||||
import route99 from "./routes/script/extractAssets";
|
||||
import route100 from "./routes/script/getScrptApi";
|
||||
import route101 from "./routes/script/pollScriptAssets";
|
||||
import route102 from "./routes/script/updateScript";
|
||||
import route103 from "./routes/scriptAgent/getPlanData";
|
||||
import route104 from "./routes/scriptAgent/setPlanData";
|
||||
import route105 from "./routes/scriptAgent/updateData";
|
||||
import route106 from "./routes/setting/about/checkUpdate";
|
||||
import route107 from "./routes/setting/about/downloadApp";
|
||||
import route108 from "./routes/setting/agentDeploy/agentSetKey";
|
||||
import route109 from "./routes/setting/agentDeploy/deployAgentModel";
|
||||
import route110 from "./routes/setting/agentDeploy/getAgentDeploy";
|
||||
import route111 from "./routes/setting/dbConfig/clearData";
|
||||
import route112 from "./routes/setting/dev/getSwitchAiDevTool";
|
||||
import route113 from "./routes/setting/dev/updateSwitchAiDevTool";
|
||||
import route114 from "./routes/setting/fileManagement/openFolder";
|
||||
import route115 from "./routes/setting/getTextModel";
|
||||
import route116 from "./routes/setting/loginConfig/getUser";
|
||||
import route117 from "./routes/setting/loginConfig/updateUserPwd";
|
||||
import route118 from "./routes/setting/memoryConfig/delAllMemory";
|
||||
import route119 from "./routes/setting/memoryConfig/getMemory";
|
||||
import route120 from "./routes/setting/memoryConfig/sureMemory";
|
||||
import route121 from "./routes/setting/promptManage/getPrompt";
|
||||
import route122 from "./routes/setting/promptManage/updatePrompt";
|
||||
import route123 from "./routes/setting/skillManagement/getSkillContent";
|
||||
import route124 from "./routes/setting/skillManagement/getSkillList";
|
||||
import route125 from "./routes/setting/skillManagement/saveSkillContent";
|
||||
import route126 from "./routes/setting/vendorConfig/addVendor";
|
||||
import route127 from "./routes/setting/vendorConfig/deleteVendor";
|
||||
import route128 from "./routes/setting/vendorConfig/enableVendor";
|
||||
import route129 from "./routes/setting/vendorConfig/getCodeByLink";
|
||||
import route130 from "./routes/setting/vendorConfig/getVendorList";
|
||||
import route131 from "./routes/setting/vendorConfig/modelTest";
|
||||
import route132 from "./routes/setting/vendorConfig/updateCode";
|
||||
import route133 from "./routes/setting/vendorConfig/updateVendor";
|
||||
import route134 from "./routes/task/getProject";
|
||||
import route135 from "./routes/task/getTaskApi";
|
||||
import route136 from "./routes/task/getTaskCategories";
|
||||
import route137 from "./routes/task/taskDetails";
|
||||
import route138 from "./routes/test/test";
|
||||
import route8 from "./routes/assets/addAudioAssets";
|
||||
import route9 from "./routes/assets/batchDelete";
|
||||
import route10 from "./routes/assets/batchGenerationData";
|
||||
import route11 from "./routes/assets/delAssets";
|
||||
import route12 from "./routes/assets/delImage";
|
||||
import route13 from "./routes/assets/getAssetsApi";
|
||||
import route14 from "./routes/assets/getImage";
|
||||
import route15 from "./routes/assets/getMaterialData";
|
||||
import route16 from "./routes/assets/pollingImageAssets";
|
||||
import route17 from "./routes/assets/pollingPromptAssets";
|
||||
import route18 from "./routes/assets/saveAssets";
|
||||
import route19 from "./routes/assets/updateAssets";
|
||||
import route20 from "./routes/assets/updateAudioAssets";
|
||||
import route21 from "./routes/assets/uploadClip";
|
||||
import route22 from "./routes/assetsGenerate/batchGenerateImageAssets";
|
||||
import route23 from "./routes/assetsGenerate/batchPolishAssetsPrompt";
|
||||
import route24 from "./routes/assetsGenerate/cancelGenerate";
|
||||
import route25 from "./routes/assetsGenerate/generateAssets";
|
||||
import route26 from "./routes/assetsGenerate/polishAssetsPrompt";
|
||||
import route27 from "./routes/cornerScape/batchBindAudio";
|
||||
import route28 from "./routes/cornerScape/getAllAssets";
|
||||
import route29 from "./routes/cornerScape/updateAssetsAudio";
|
||||
import route30 from "./routes/general/generalStatistics";
|
||||
import route31 from "./routes/general/getSingleProject";
|
||||
import route32 from "./routes/general/updateProject";
|
||||
import route33 from "./routes/login/login";
|
||||
import route34 from "./routes/migrate/migrateData";
|
||||
import route35 from "./routes/modelSelect/getModelDetail";
|
||||
import route36 from "./routes/modelSelect/getModelList";
|
||||
import route37 from "./routes/novel/addNovel";
|
||||
import route38 from "./routes/novel/batchDeleteNovel";
|
||||
import route39 from "./routes/novel/delNovel";
|
||||
import route40 from "./routes/novel/event/batchDeleteEvent";
|
||||
import route41 from "./routes/novel/event/deletEvent";
|
||||
import route42 from "./routes/novel/event/generateEvents";
|
||||
import route43 from "./routes/novel/event/getEvent";
|
||||
import route44 from "./routes/novel/getNovel";
|
||||
import route45 from "./routes/novel/getNovelData";
|
||||
import route46 from "./routes/novel/getNovelEventState";
|
||||
import route47 from "./routes/novel/getNovelIndex";
|
||||
import route48 from "./routes/novel/updateNovel";
|
||||
import route49 from "./routes/other/deleteAllData";
|
||||
import route50 from "./routes/other/getVersion";
|
||||
import route51 from "./routes/production/assets/batchGenerateAssetsImage";
|
||||
import route52 from "./routes/production/assets/deleteAssetsDireve";
|
||||
import route53 from "./routes/production/assets/getAssetsData";
|
||||
import route54 from "./routes/production/assets/pollingImage";
|
||||
import route55 from "./routes/production/assets/updateAssetsUrl";
|
||||
import route56 from "./routes/production/editImage/generateFlowImage";
|
||||
import route57 from "./routes/production/editImage/getImageDefaultModle";
|
||||
import route58 from "./routes/production/editImage/getImageFlow";
|
||||
import route59 from "./routes/production/editImage/saveImageFlow";
|
||||
import route60 from "./routes/production/editImage/updateImageFlow";
|
||||
import route61 from "./routes/production/editImage/uploadImage";
|
||||
import route62 from "./routes/production/getFlowData";
|
||||
import route63 from "./routes/production/getStoryboardData";
|
||||
import route64 from "./routes/production/saveFlowData";
|
||||
import route65 from "./routes/production/storyboard/addStoryboard";
|
||||
import route66 from "./routes/production/storyboard/batchAddStoryboardInfo";
|
||||
import route67 from "./routes/production/storyboard/batchGenerateImage";
|
||||
import route68 from "./routes/production/storyboard/downPreviewImage";
|
||||
import route69 from "./routes/production/storyboard/editStoryboardInfo";
|
||||
import route70 from "./routes/production/storyboard/getStoryboardData";
|
||||
import route71 from "./routes/production/storyboard/pollingImage";
|
||||
import route72 from "./routes/production/storyboard/previewImage";
|
||||
import route73 from "./routes/production/storyboard/removeFrame";
|
||||
import route74 from "./routes/production/storyboard/updateStoryboardUrl";
|
||||
import route75 from "./routes/production/workbench/addTrack";
|
||||
import route76 from "./routes/production/workbench/deleteTrack";
|
||||
import route77 from "./routes/production/workbench/delVideo";
|
||||
import route78 from "./routes/production/workbench/generateVideo";
|
||||
import route79 from "./routes/production/workbench/generateVideoPrompt";
|
||||
import route80 from "./routes/production/workbench/getGenerateData";
|
||||
import route81 from "./routes/production/workbench/getVideoList";
|
||||
import route82 from "./routes/production/workbench/selectVideo";
|
||||
import route83 from "./routes/production/workbench/updateVideoPrompt";
|
||||
import route84 from "./routes/project/addDirectorManual";
|
||||
import route85 from "./routes/project/addProject";
|
||||
import route86 from "./routes/project/addVisualManual";
|
||||
import route87 from "./routes/project/deleteDirectorManual";
|
||||
import route88 from "./routes/project/deleteVisualManual";
|
||||
import route89 from "./routes/project/delProject";
|
||||
import route90 from "./routes/project/editDirectorlManual";
|
||||
import route91 from "./routes/project/editProject";
|
||||
import route92 from "./routes/project/editVisualManual";
|
||||
import route93 from "./routes/project/getProject";
|
||||
import route94 from "./routes/project/getVisualManual";
|
||||
import route95 from "./routes/project/queryDirectorManual";
|
||||
import route96 from "./routes/project/visualManual";
|
||||
import route97 from "./routes/script/addScript";
|
||||
import route98 from "./routes/script/batchAddScript";
|
||||
import route99 from "./routes/script/delScript";
|
||||
import route100 from "./routes/script/exportScript";
|
||||
import route101 from "./routes/script/extractAssets";
|
||||
import route102 from "./routes/script/getScrptApi";
|
||||
import route103 from "./routes/script/pollScriptAssets";
|
||||
import route104 from "./routes/script/updateScript";
|
||||
import route105 from "./routes/scriptAgent/getPlanData";
|
||||
import route106 from "./routes/scriptAgent/setPlanData";
|
||||
import route107 from "./routes/scriptAgent/updateData";
|
||||
import route108 from "./routes/setting/about/checkUpdate";
|
||||
import route109 from "./routes/setting/about/downloadApp";
|
||||
import route110 from "./routes/setting/agentDeploy/agentSetKey";
|
||||
import route111 from "./routes/setting/agentDeploy/deployAgentModel";
|
||||
import route112 from "./routes/setting/agentDeploy/getAgentDeploy";
|
||||
import route113 from "./routes/setting/dbConfig/clearData";
|
||||
import route114 from "./routes/setting/dev/getSwitchAiDevTool";
|
||||
import route115 from "./routes/setting/dev/updateSwitchAiDevTool";
|
||||
import route116 from "./routes/setting/fileManagement/openFolder";
|
||||
import route117 from "./routes/setting/getTextModel";
|
||||
import route118 from "./routes/setting/loginConfig/getUser";
|
||||
import route119 from "./routes/setting/loginConfig/updateUserPwd";
|
||||
import route120 from "./routes/setting/memoryConfig/delAllMemory";
|
||||
import route121 from "./routes/setting/memoryConfig/getMemory";
|
||||
import route122 from "./routes/setting/memoryConfig/sureMemory";
|
||||
import route123 from "./routes/setting/promptManage/getPrompt";
|
||||
import route124 from "./routes/setting/promptManage/updatePrompt";
|
||||
import route125 from "./routes/setting/skillManagement/getSkillContent";
|
||||
import route126 from "./routes/setting/skillManagement/getSkillList";
|
||||
import route127 from "./routes/setting/skillManagement/saveSkillContent";
|
||||
import route128 from "./routes/setting/vendorConfig/addVendor";
|
||||
import route129 from "./routes/setting/vendorConfig/addVendorModel";
|
||||
import route130 from "./routes/setting/vendorConfig/deleteVendor";
|
||||
import route131 from "./routes/setting/vendorConfig/delVendorModel";
|
||||
import route132 from "./routes/setting/vendorConfig/enableVendor";
|
||||
import route133 from "./routes/setting/vendorConfig/getCodeByLink";
|
||||
import route134 from "./routes/setting/vendorConfig/getVendorList";
|
||||
import route135 from "./routes/setting/vendorConfig/modelTest";
|
||||
import route136 from "./routes/setting/vendorConfig/updateCode";
|
||||
import route137 from "./routes/setting/vendorConfig/updateVendorInputs";
|
||||
import route138 from "./routes/setting/vendorConfig/upVendorModel";
|
||||
import route139 from "./routes/task/getProject";
|
||||
import route140 from "./routes/task/getTaskApi";
|
||||
import route141 from "./routes/task/getTaskCategories";
|
||||
import route142 from "./routes/task/taskDetails";
|
||||
import route143 from "./routes/test/test";
|
||||
|
||||
export default async (app: Express) => {
|
||||
app.use("/api/agents/clearMemory", route1);
|
||||
@ -148,135 +153,140 @@ export default async (app: Express) => {
|
||||
app.use("/api/artStyle/extractStylePrompt", route5);
|
||||
app.use("/api/artStyle/getArtStyle", route6);
|
||||
app.use("/api/assets/addAssets", route7);
|
||||
app.use("/api/assets/batchDelete", route8);
|
||||
app.use("/api/assets/batchGenerationData", route9);
|
||||
app.use("/api/assets/delAssets", route10);
|
||||
app.use("/api/assets/delImage", route11);
|
||||
app.use("/api/assets/getAssetsApi", route12);
|
||||
app.use("/api/assets/getImage", route13);
|
||||
app.use("/api/assets/getMaterialData", route14);
|
||||
app.use("/api/assets/pollingImageAssets", route15);
|
||||
app.use("/api/assets/pollingPromptAssets", route16);
|
||||
app.use("/api/assets/saveAssets", route17);
|
||||
app.use("/api/assets/updateAssets", route18);
|
||||
app.use("/api/assets/uploadClip", route19);
|
||||
app.use("/api/assetsGenerate/batchGenerateImageAssets", route20);
|
||||
app.use("/api/assetsGenerate/batchPolishAssetsPrompt", route21);
|
||||
app.use("/api/assetsGenerate/cancelGenerate", route22);
|
||||
app.use("/api/assetsGenerate/generateAssets", route23);
|
||||
app.use("/api/assetsGenerate/polishAssetsPrompt", route24);
|
||||
app.use("/api/cornerScape/getAllAssets", route25);
|
||||
app.use("/api/general/generalStatistics", route26);
|
||||
app.use("/api/general/getSingleProject", route27);
|
||||
app.use("/api/general/updateProject", route28);
|
||||
app.use("/api/login/login", route29);
|
||||
app.use("/api/migrate/migrateData", route30);
|
||||
app.use("/api/modelSelect/getModelDetail", route31);
|
||||
app.use("/api/modelSelect/getModelList", route32);
|
||||
app.use("/api/novel/addNovel", route33);
|
||||
app.use("/api/novel/batchDeleteNovel", route34);
|
||||
app.use("/api/novel/delNovel", route35);
|
||||
app.use("/api/novel/event/batchDeleteEvent", route36);
|
||||
app.use("/api/novel/event/deletEvent", route37);
|
||||
app.use("/api/novel/event/generateEvents", route38);
|
||||
app.use("/api/novel/event/getEvent", route39);
|
||||
app.use("/api/novel/getNovel", route40);
|
||||
app.use("/api/novel/getNovelData", route41);
|
||||
app.use("/api/novel/getNovelEventState", route42);
|
||||
app.use("/api/novel/getNovelIndex", route43);
|
||||
app.use("/api/novel/updateNovel", route44);
|
||||
app.use("/api/other/deleteAllData", route45);
|
||||
app.use("/api/other/getVersion", route46);
|
||||
app.use("/api/production/assets/batchGenerateAssetsImage", route47);
|
||||
app.use("/api/production/assets/deleteAssetsDireve", route48);
|
||||
app.use("/api/production/assets/getAssetsData", route49);
|
||||
app.use("/api/production/assets/pollingImage", route50);
|
||||
app.use("/api/production/assets/updateAssetsUrl", route51);
|
||||
app.use("/api/production/editImage/generateFlowImage", route52);
|
||||
app.use("/api/production/editImage/getImageDefaultModle", route53);
|
||||
app.use("/api/production/editImage/getImageFlow", route54);
|
||||
app.use("/api/production/editImage/saveImageFlow", route55);
|
||||
app.use("/api/production/editImage/updateImageFlow", route56);
|
||||
app.use("/api/production/editImage/uploadImage", route57);
|
||||
app.use("/api/production/getFlowData", route58);
|
||||
app.use("/api/production/getProductionData", route59);
|
||||
app.use("/api/production/getStoryboardData", route60);
|
||||
app.use("/api/production/saveFlowData", route61);
|
||||
app.use("/api/production/storyboard/addStoryboard", route62);
|
||||
app.use("/api/production/storyboard/batchAddStoryboardInfo", route63);
|
||||
app.use("/api/production/storyboard/batchGenerateImage", route64);
|
||||
app.use("/api/production/storyboard/downPreviewImage", route65);
|
||||
app.use("/api/production/storyboard/editStoryboardInfo", route66);
|
||||
app.use("/api/production/storyboard/getStoryboardData", route67);
|
||||
app.use("/api/production/storyboard/pollingImage", route68);
|
||||
app.use("/api/production/storyboard/previewImage", route69);
|
||||
app.use("/api/production/storyboard/removeFrame", route70);
|
||||
app.use("/api/production/storyboard/updateStoryboardUrl", route71);
|
||||
app.use("/api/production/workbench/addTrack", route72);
|
||||
app.use("/api/production/workbench/deleteTrack", route73);
|
||||
app.use("/api/production/workbench/delVideo", route74);
|
||||
app.use("/api/production/workbench/generateVideo", route75);
|
||||
app.use("/api/production/workbench/generateVideoPrompt", route76);
|
||||
app.use("/api/production/workbench/getGenerateData", route77);
|
||||
app.use("/api/production/workbench/getVideoList", route78);
|
||||
app.use("/api/production/workbench/getVideoModelDetail", route79);
|
||||
app.use("/api/production/workbench/selectVideo", route80);
|
||||
app.use("/api/production/workbench/updateVideoPrompt", route81);
|
||||
app.use("/api/project/addDirectorManual", route82);
|
||||
app.use("/api/project/addProject", route83);
|
||||
app.use("/api/project/addVisualManual", route84);
|
||||
app.use("/api/project/deleteDirectorManual", route85);
|
||||
app.use("/api/project/deleteVisualManual", route86);
|
||||
app.use("/api/project/delProject", route87);
|
||||
app.use("/api/project/editDirectorlManual", route88);
|
||||
app.use("/api/project/editProject", route89);
|
||||
app.use("/api/project/editVisualManual", route90);
|
||||
app.use("/api/project/getProject", route91);
|
||||
app.use("/api/project/getVisualManual", route92);
|
||||
app.use("/api/project/queryDirectorManual", route93);
|
||||
app.use("/api/project/visualManual", route94);
|
||||
app.use("/api/script/addScript", route95);
|
||||
app.use("/api/script/batchAddScript", route96);
|
||||
app.use("/api/script/delScript", route97);
|
||||
app.use("/api/script/exportScript", route98);
|
||||
app.use("/api/script/extractAssets", route99);
|
||||
app.use("/api/script/getScrptApi", route100);
|
||||
app.use("/api/script/pollScriptAssets", route101);
|
||||
app.use("/api/script/updateScript", route102);
|
||||
app.use("/api/scriptAgent/getPlanData", route103);
|
||||
app.use("/api/scriptAgent/setPlanData", route104);
|
||||
app.use("/api/scriptAgent/updateData", route105);
|
||||
app.use("/api/setting/about/checkUpdate", route106);
|
||||
app.use("/api/setting/about/downloadApp", route107);
|
||||
app.use("/api/setting/agentDeploy/agentSetKey", route108);
|
||||
app.use("/api/setting/agentDeploy/deployAgentModel", route109);
|
||||
app.use("/api/setting/agentDeploy/getAgentDeploy", route110);
|
||||
app.use("/api/setting/dbConfig/clearData", route111);
|
||||
app.use("/api/setting/dev/getSwitchAiDevTool", route112);
|
||||
app.use("/api/setting/dev/updateSwitchAiDevTool", route113);
|
||||
app.use("/api/setting/fileManagement/openFolder", route114);
|
||||
app.use("/api/setting/getTextModel", route115);
|
||||
app.use("/api/setting/loginConfig/getUser", route116);
|
||||
app.use("/api/setting/loginConfig/updateUserPwd", route117);
|
||||
app.use("/api/setting/memoryConfig/delAllMemory", route118);
|
||||
app.use("/api/setting/memoryConfig/getMemory", route119);
|
||||
app.use("/api/setting/memoryConfig/sureMemory", route120);
|
||||
app.use("/api/setting/promptManage/getPrompt", route121);
|
||||
app.use("/api/setting/promptManage/updatePrompt", route122);
|
||||
app.use("/api/setting/skillManagement/getSkillContent", route123);
|
||||
app.use("/api/setting/skillManagement/getSkillList", route124);
|
||||
app.use("/api/setting/skillManagement/saveSkillContent", route125);
|
||||
app.use("/api/setting/vendorConfig/addVendor", route126);
|
||||
app.use("/api/setting/vendorConfig/deleteVendor", route127);
|
||||
app.use("/api/setting/vendorConfig/enableVendor", route128);
|
||||
app.use("/api/setting/vendorConfig/getCodeByLink", route129);
|
||||
app.use("/api/setting/vendorConfig/getVendorList", route130);
|
||||
app.use("/api/setting/vendorConfig/modelTest", route131);
|
||||
app.use("/api/setting/vendorConfig/updateCode", route132);
|
||||
app.use("/api/setting/vendorConfig/updateVendor", route133);
|
||||
app.use("/api/task/getProject", route134);
|
||||
app.use("/api/task/getTaskApi", route135);
|
||||
app.use("/api/task/getTaskCategories", route136);
|
||||
app.use("/api/task/taskDetails", route137);
|
||||
app.use("/api/test/test", route138);
|
||||
app.use("/api/assets/addAudioAssets", route8);
|
||||
app.use("/api/assets/batchDelete", route9);
|
||||
app.use("/api/assets/batchGenerationData", route10);
|
||||
app.use("/api/assets/delAssets", route11);
|
||||
app.use("/api/assets/delImage", route12);
|
||||
app.use("/api/assets/getAssetsApi", route13);
|
||||
app.use("/api/assets/getImage", route14);
|
||||
app.use("/api/assets/getMaterialData", route15);
|
||||
app.use("/api/assets/pollingImageAssets", route16);
|
||||
app.use("/api/assets/pollingPromptAssets", route17);
|
||||
app.use("/api/assets/saveAssets", route18);
|
||||
app.use("/api/assets/updateAssets", route19);
|
||||
app.use("/api/assets/updateAudioAssets", route20);
|
||||
app.use("/api/assets/uploadClip", route21);
|
||||
app.use("/api/assetsGenerate/batchGenerateImageAssets", route22);
|
||||
app.use("/api/assetsGenerate/batchPolishAssetsPrompt", route23);
|
||||
app.use("/api/assetsGenerate/cancelGenerate", route24);
|
||||
app.use("/api/assetsGenerate/generateAssets", route25);
|
||||
app.use("/api/assetsGenerate/polishAssetsPrompt", route26);
|
||||
app.use("/api/cornerScape/batchBindAudio", route27);
|
||||
app.use("/api/cornerScape/getAllAssets", route28);
|
||||
app.use("/api/cornerScape/updateAssetsAudio", route29);
|
||||
app.use("/api/general/generalStatistics", route30);
|
||||
app.use("/api/general/getSingleProject", route31);
|
||||
app.use("/api/general/updateProject", route32);
|
||||
app.use("/api/login/login", route33);
|
||||
app.use("/api/migrate/migrateData", route34);
|
||||
app.use("/api/modelSelect/getModelDetail", route35);
|
||||
app.use("/api/modelSelect/getModelList", route36);
|
||||
app.use("/api/novel/addNovel", route37);
|
||||
app.use("/api/novel/batchDeleteNovel", route38);
|
||||
app.use("/api/novel/delNovel", route39);
|
||||
app.use("/api/novel/event/batchDeleteEvent", route40);
|
||||
app.use("/api/novel/event/deletEvent", route41);
|
||||
app.use("/api/novel/event/generateEvents", route42);
|
||||
app.use("/api/novel/event/getEvent", route43);
|
||||
app.use("/api/novel/getNovel", route44);
|
||||
app.use("/api/novel/getNovelData", route45);
|
||||
app.use("/api/novel/getNovelEventState", route46);
|
||||
app.use("/api/novel/getNovelIndex", route47);
|
||||
app.use("/api/novel/updateNovel", route48);
|
||||
app.use("/api/other/deleteAllData", route49);
|
||||
app.use("/api/other/getVersion", route50);
|
||||
app.use("/api/production/assets/batchGenerateAssetsImage", route51);
|
||||
app.use("/api/production/assets/deleteAssetsDireve", route52);
|
||||
app.use("/api/production/assets/getAssetsData", route53);
|
||||
app.use("/api/production/assets/pollingImage", route54);
|
||||
app.use("/api/production/assets/updateAssetsUrl", route55);
|
||||
app.use("/api/production/editImage/generateFlowImage", route56);
|
||||
app.use("/api/production/editImage/getImageDefaultModle", route57);
|
||||
app.use("/api/production/editImage/getImageFlow", route58);
|
||||
app.use("/api/production/editImage/saveImageFlow", route59);
|
||||
app.use("/api/production/editImage/updateImageFlow", route60);
|
||||
app.use("/api/production/editImage/uploadImage", route61);
|
||||
app.use("/api/production/getFlowData", route62);
|
||||
app.use("/api/production/getStoryboardData", route63);
|
||||
app.use("/api/production/saveFlowData", route64);
|
||||
app.use("/api/production/storyboard/addStoryboard", route65);
|
||||
app.use("/api/production/storyboard/batchAddStoryboardInfo", route66);
|
||||
app.use("/api/production/storyboard/batchGenerateImage", route67);
|
||||
app.use("/api/production/storyboard/downPreviewImage", route68);
|
||||
app.use("/api/production/storyboard/editStoryboardInfo", route69);
|
||||
app.use("/api/production/storyboard/getStoryboardData", route70);
|
||||
app.use("/api/production/storyboard/pollingImage", route71);
|
||||
app.use("/api/production/storyboard/previewImage", route72);
|
||||
app.use("/api/production/storyboard/removeFrame", route73);
|
||||
app.use("/api/production/storyboard/updateStoryboardUrl", route74);
|
||||
app.use("/api/production/workbench/addTrack", route75);
|
||||
app.use("/api/production/workbench/deleteTrack", route76);
|
||||
app.use("/api/production/workbench/delVideo", route77);
|
||||
app.use("/api/production/workbench/generateVideo", route78);
|
||||
app.use("/api/production/workbench/generateVideoPrompt", route79);
|
||||
app.use("/api/production/workbench/getGenerateData", route80);
|
||||
app.use("/api/production/workbench/getVideoList", route81);
|
||||
app.use("/api/production/workbench/selectVideo", route82);
|
||||
app.use("/api/production/workbench/updateVideoPrompt", route83);
|
||||
app.use("/api/project/addDirectorManual", route84);
|
||||
app.use("/api/project/addProject", route85);
|
||||
app.use("/api/project/addVisualManual", route86);
|
||||
app.use("/api/project/deleteDirectorManual", route87);
|
||||
app.use("/api/project/deleteVisualManual", route88);
|
||||
app.use("/api/project/delProject", route89);
|
||||
app.use("/api/project/editDirectorlManual", route90);
|
||||
app.use("/api/project/editProject", route91);
|
||||
app.use("/api/project/editVisualManual", route92);
|
||||
app.use("/api/project/getProject", route93);
|
||||
app.use("/api/project/getVisualManual", route94);
|
||||
app.use("/api/project/queryDirectorManual", route95);
|
||||
app.use("/api/project/visualManual", route96);
|
||||
app.use("/api/script/addScript", route97);
|
||||
app.use("/api/script/batchAddScript", route98);
|
||||
app.use("/api/script/delScript", route99);
|
||||
app.use("/api/script/exportScript", route100);
|
||||
app.use("/api/script/extractAssets", route101);
|
||||
app.use("/api/script/getScrptApi", route102);
|
||||
app.use("/api/script/pollScriptAssets", route103);
|
||||
app.use("/api/script/updateScript", route104);
|
||||
app.use("/api/scriptAgent/getPlanData", route105);
|
||||
app.use("/api/scriptAgent/setPlanData", route106);
|
||||
app.use("/api/scriptAgent/updateData", route107);
|
||||
app.use("/api/setting/about/checkUpdate", route108);
|
||||
app.use("/api/setting/about/downloadApp", route109);
|
||||
app.use("/api/setting/agentDeploy/agentSetKey", route110);
|
||||
app.use("/api/setting/agentDeploy/deployAgentModel", route111);
|
||||
app.use("/api/setting/agentDeploy/getAgentDeploy", route112);
|
||||
app.use("/api/setting/dbConfig/clearData", route113);
|
||||
app.use("/api/setting/dev/getSwitchAiDevTool", route114);
|
||||
app.use("/api/setting/dev/updateSwitchAiDevTool", route115);
|
||||
app.use("/api/setting/fileManagement/openFolder", route116);
|
||||
app.use("/api/setting/getTextModel", route117);
|
||||
app.use("/api/setting/loginConfig/getUser", route118);
|
||||
app.use("/api/setting/loginConfig/updateUserPwd", route119);
|
||||
app.use("/api/setting/memoryConfig/delAllMemory", route120);
|
||||
app.use("/api/setting/memoryConfig/getMemory", route121);
|
||||
app.use("/api/setting/memoryConfig/sureMemory", route122);
|
||||
app.use("/api/setting/promptManage/getPrompt", route123);
|
||||
app.use("/api/setting/promptManage/updatePrompt", route124);
|
||||
app.use("/api/setting/skillManagement/getSkillContent", route125);
|
||||
app.use("/api/setting/skillManagement/getSkillList", route126);
|
||||
app.use("/api/setting/skillManagement/saveSkillContent", route127);
|
||||
app.use("/api/setting/vendorConfig/addVendor", route128);
|
||||
app.use("/api/setting/vendorConfig/addVendorModel", route129);
|
||||
app.use("/api/setting/vendorConfig/deleteVendor", route130);
|
||||
app.use("/api/setting/vendorConfig/delVendorModel", route131);
|
||||
app.use("/api/setting/vendorConfig/enableVendor", route132);
|
||||
app.use("/api/setting/vendorConfig/getCodeByLink", route133);
|
||||
app.use("/api/setting/vendorConfig/getVendorList", route134);
|
||||
app.use("/api/setting/vendorConfig/modelTest", route135);
|
||||
app.use("/api/setting/vendorConfig/updateCode", route136);
|
||||
app.use("/api/setting/vendorConfig/updateVendorInputs", route137);
|
||||
app.use("/api/setting/vendorConfig/upVendorModel", route138);
|
||||
app.use("/api/task/getProject", route139);
|
||||
app.use("/api/task/getTaskApi", route140);
|
||||
app.use("/api/task/getTaskCategories", route141);
|
||||
app.use("/api/task/taskDetails", route142);
|
||||
app.use("/api/test/test", route143);
|
||||
}
|
||||
|
||||
66
src/routes/assets/addAudioAssets.ts
Normal file
66
src/routes/assets/addAudioAssets.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 新增资产
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
name: z.string(),
|
||||
describe: z.string(),
|
||||
projectId: z.number(),
|
||||
assetsItem: z.array(
|
||||
z.object({
|
||||
base64: z.string(),
|
||||
prompt: z.string(),
|
||||
describe: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { name, describe, projectId, assetsItem } = req.body;
|
||||
await Promise.all(
|
||||
assetsItem.map(async (i: { src?: string; base64: string; prompt: string }) => {
|
||||
if (i.base64) {
|
||||
const savePath = `/${projectId}/assets/audio/${u.uuid()}.mp4`;
|
||||
await u.oss.writeFile(savePath, i.base64);
|
||||
i.src = savePath;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const [id] = await u.db("o_assets").insert({
|
||||
name,
|
||||
describe,
|
||||
type: "audio",
|
||||
projectId,
|
||||
startTime: Date.now(),
|
||||
});
|
||||
for (const item of assetsItem) {
|
||||
const [assetsId] = await u.db("o_assets").insert({
|
||||
prompt: item.prompt,
|
||||
assetsId: id,
|
||||
type: "audio",
|
||||
describe: item.describe,
|
||||
name: item.name,
|
||||
projectId,
|
||||
startTime: Date.now(),
|
||||
});
|
||||
const [imageId] = await u.db("o_image").insert({
|
||||
filePath: item.src,
|
||||
type: "audio",
|
||||
assetsId,
|
||||
state: "已完成",
|
||||
});
|
||||
await u.db("o_assets").where("id", assetsId).update({
|
||||
imageId,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).send(success({ message: "新增资产成功" }));
|
||||
},
|
||||
);
|
||||
@ -28,6 +28,7 @@ export default router.post(
|
||||
}
|
||||
await u.db("o_image").where({ assetsId: id }).delete();
|
||||
await u.db("o_assets").where({ id }).delete();
|
||||
await u.db("o_assets").where("assetsId", id).delete();
|
||||
res.status(200).send(success({ message: "删除资产成功" }));
|
||||
},
|
||||
);
|
||||
|
||||
@ -57,6 +57,7 @@ export default router.post(
|
||||
...parent,
|
||||
sonAssets: childAssetsWithSrc.filter((child) => child.assetsId === parent.id),
|
||||
src: parent.filePath && (await u.oss.getFileUrl(parent.filePath!)),
|
||||
...(parent.type == "audio" ? { sex: parent.describe?.split("|")[0], describe: parent.describe?.split("|")[1] } : {}),
|
||||
})),
|
||||
);
|
||||
|
||||
|
||||
@ -18,8 +18,7 @@ export default router.post(
|
||||
.db("o_assets")
|
||||
.leftJoin("o_image", "o_assets.id", "=", "o_image.assetsId")
|
||||
.where("o_assets.type", "clip")
|
||||
.andWhere("projectId", projectId)
|
||||
.andWhere("scriptId", scriptId)
|
||||
.andWhere("o_assets.projectId", projectId)
|
||||
.select("*");
|
||||
const data = await Promise.all(
|
||||
list.map(async (item) => ({
|
||||
@ -35,16 +34,30 @@ export default router.post(
|
||||
filePath: ending,
|
||||
type: "clip",
|
||||
});
|
||||
// 查询o_video表
|
||||
const videoRows = await u.db("o_video").where("state", "生成成功").andWhere("scriptId", scriptId).andWhere("projectId", projectId).select("*");
|
||||
// 处理并返回结果
|
||||
// 查询视频轨道
|
||||
const trackRows = await u
|
||||
.db("o_videoTrack")
|
||||
.where("o_videoTrack.scriptId", scriptId)
|
||||
.andWhere("o_videoTrack.projectId", projectId)
|
||||
.select("o_videoTrack.id as trackId","o_videoTrack.videoId");
|
||||
// 按轨道分组处理视频
|
||||
const video = await Promise.all(
|
||||
videoRows.map(async (row) => ({
|
||||
id: row.id,
|
||||
filePath: row.filePath ? await u.oss.getFileUrl(row.filePath) : "",
|
||||
videoTrackId: row.videoTrackId,
|
||||
})),
|
||||
);
|
||||
trackRows.map(async (track) => {
|
||||
const videoItems = await u.db("o_video").where("o_video.videoTrackId", track.trackId).andWhere("o_video.state", "生成成功").select("*");
|
||||
const videoList = await Promise.all(
|
||||
videoItems.map(async (v) => ({
|
||||
id: v.id,
|
||||
filePath: v.filePath ? await u.oss.getFileUrl(v.filePath) : "",
|
||||
videoTrackId: v.videoTrackId,
|
||||
})),
|
||||
);
|
||||
return {
|
||||
id: track.trackId,
|
||||
videoId: track.videoId,
|
||||
video: videoList,
|
||||
};
|
||||
}),
|
||||
).then((tracks) => tracks.filter((track) => track.video.length > 0));
|
||||
|
||||
res.status(200).send(success({ data, video }));
|
||||
},
|
||||
|
||||
98
src/routes/assets/updateAudioAssets.ts
Normal file
98
src/routes/assets/updateAudioAssets.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 新增资产
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
describe: z.string(),
|
||||
projectId: z.number(),
|
||||
assetsItem: z.array(
|
||||
z.object({
|
||||
src: z.string().optional(),
|
||||
id: z.number().optional(),
|
||||
base64: z.string().optional(),
|
||||
prompt: z.string(),
|
||||
describe: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, name, describe, projectId, assetsItem } = req.body;
|
||||
await Promise.all(
|
||||
assetsItem.map(async (i: { src?: string; id?: number; base64: string; prompt: string }) => {
|
||||
if (i.src) {
|
||||
i.src = u.replaceUrl(i.src);
|
||||
}
|
||||
if (i.base64) {
|
||||
const savePath = `/${projectId}/assets/audio/${u.uuid()}.mp4`;
|
||||
await u.oss.writeFile(savePath, i.base64);
|
||||
i.src = savePath;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await u.db("o_assets").where("id", id).update({
|
||||
name,
|
||||
describe,
|
||||
});
|
||||
|
||||
// 删除不在 assetsItem 中的子项
|
||||
const existingItems = await u.db("o_assets").where("assetsId", id).select("id");
|
||||
const existingIds = existingItems.map((i: { id?: number }) => i.id!);
|
||||
const incomingIds = assetsItem.filter((i: { id?: number }) => i.id).map((i: { id?: number }) => i.id);
|
||||
const toDeleteIds = existingIds.filter((eid: number) => !incomingIds.includes(eid));
|
||||
if (toDeleteIds.length > 0) {
|
||||
const deleteItems = await u.db("o_assets").whereIn("id", toDeleteIds).select("imageId");
|
||||
const deleteImageIds = deleteItems.map((i: { imageId?: number | null }) => i.imageId!).filter(Boolean);
|
||||
// 先将 o_assets.imageId 置空,解除外键约束,再删除 o_image,最后删除 o_assets
|
||||
await u.db("o_assets").whereIn("id", toDeleteIds).update({ imageId: null });
|
||||
if (deleteImageIds.length > 0) {
|
||||
await u.db("o_image").whereIn("id", deleteImageIds).delete();
|
||||
}
|
||||
await u.db("o_assets").whereIn("id", toDeleteIds).delete();
|
||||
}
|
||||
|
||||
for (const item of assetsItem) {
|
||||
if (item.id) {
|
||||
await u.db("o_assets").where("id", item.id).update({
|
||||
prompt: item.prompt,
|
||||
describe: item.describe,
|
||||
name: item.name,
|
||||
});
|
||||
const itemData = await u.db("o_assets").where("id", item.id).select("imageId").first();
|
||||
await u.db("o_image").where("id", itemData?.imageId).update({
|
||||
filePath: item.src,
|
||||
});
|
||||
} else {
|
||||
const [assetsId] = await u.db("o_assets").insert({
|
||||
prompt: item.prompt,
|
||||
assetsId: id,
|
||||
type: "audio",
|
||||
projectId,
|
||||
describe: item.describe,
|
||||
name: item.name,
|
||||
startTime: Date.now(),
|
||||
});
|
||||
const [imageId] = await u.db("o_image").insert({
|
||||
filePath: item.src,
|
||||
type: "audio",
|
||||
assetsId,
|
||||
state: "已完成",
|
||||
});
|
||||
await u.db("o_assets").where("id", assetsId).update({
|
||||
imageId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).send(success({ message: "新增资产成功" }));
|
||||
},
|
||||
);
|
||||
@ -116,7 +116,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res)
|
||||
await aiImage.run(
|
||||
{
|
||||
prompt: userPrompt,
|
||||
imageBase64: item.base64 ? [item.base64] : [],
|
||||
referenceList: item.base64 ? [{ base64: item.base64, type: "image" }] : [],
|
||||
size: resolution,
|
||||
aspectRatio: "16:9",
|
||||
},
|
||||
|
||||
@ -100,7 +100,7 @@ export default router.post("/", validateFields(requestSchema), async (req, res)
|
||||
await aiImage.run(
|
||||
{
|
||||
prompt: userPrompt,
|
||||
imageBase64: base64 ? [base64] : [],
|
||||
referenceList: base64 ? [{ type: "image", base64 }] : [],
|
||||
size: resolution,
|
||||
aspectRatio: "16:9",
|
||||
},
|
||||
|
||||
75
src/routes/cornerScape/batchBindAudio.ts
Normal file
75
src/routes/cornerScape/batchBindAudio.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { tool } from "ai";
|
||||
const router = express.Router();
|
||||
|
||||
// 获取资产
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
projectId: z.number(),
|
||||
assetsIds: z.array(z.number()),
|
||||
concurrentCount: z.number().min(1).optional(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { projectId, assetsIds, concurrentCount } = req.body;
|
||||
const assetsData = await u
|
||||
.db("o_assets")
|
||||
.where("type", "audio")
|
||||
.whereIn("id", assetsIds)
|
||||
.andWhere("projectId", projectId)
|
||||
.select("id", "name", "describe");
|
||||
console.log("%c Line:20 🍎 assetsData", "background:#b03734", assetsData);
|
||||
|
||||
const audioData = await u.db("o_assets").where("type", "audio").whereNull("assetsId").andWhere("projectId", projectId).select("id", "name", "describe");
|
||||
console.log("%c Line:26 🍋 audioData", "background:#ea7e5c", audioData);
|
||||
async function processGroup() {
|
||||
try {
|
||||
const resultTool = tool({
|
||||
description: "返回结果时必须调用这个工具",
|
||||
inputSchema: z.object({
|
||||
result: z.array(z.object({
|
||||
id: z.number(),
|
||||
audioIds: z.array(z.number()).describe("适配的音频id 无适配内容可以为 空数组")
|
||||
})).describe("适配的音色列表,id为资产id,audioIds为适配的音频id 无适配内容可以为 空数组")
|
||||
}),
|
||||
execute: async ({ result }) => {
|
||||
console.log("[tools] extractAssets result", result);
|
||||
for (const item of result) {
|
||||
await u.db("o_assetsRole2Audio").where("assetsRoleId", item.id).delete()
|
||||
if (item.audioIds.length)
|
||||
await u.db("o_assetsRole2Audio").insert(item.audioIds.map(i => ({ assetsRoleId: item.id, assetsAudioId: i })))
|
||||
}
|
||||
return "无需回复用户任何内容";
|
||||
},
|
||||
});
|
||||
|
||||
const { text } = await u.Ai.Text("universalAi").invoke({
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `请根据提供的资产内容描述 与 提供的音色 进行匹配,返回适配的音色,结果必须调用 resultTool 工具返回, 调用工具之后你无需回复用户任何内容。`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: `音频内容:${audioData.map(i => `Id:${i.id},音色名称:${i.name},描述:${i.describe}`).join("\n")}\n\n
|
||||
资产内容:${assetsData.map(i => `ID:${i.id},名称:${i.name},描述:${i.describe}`).join("\n")}\n\n
|
||||
请根据提供的资产内容描述 与 对应已有的音色 进行匹配,返回适配的音色`,
|
||||
},
|
||||
],
|
||||
tools: { resultTool },
|
||||
});
|
||||
console.log("%c Line:44 🍞 text", "background:#f5ce50", text);
|
||||
|
||||
} catch (e) {
|
||||
console.error(`提取失败:`, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await processGroup()
|
||||
res.status(200).send(success());
|
||||
},
|
||||
);
|
||||
@ -32,6 +32,20 @@ export default router.post(
|
||||
if (type && type.length > 0) qb.whereIn("o_assets.type", type);
|
||||
})
|
||||
.orderByRaw(`CASE o_assets.type WHEN 'role' THEN 1 WHEN 'scene' THEN 2 WHEN 'tool' THEN 3 ELSE 4 END`);
|
||||
const assets2AudioData = await u
|
||||
.db("o_assetsRole2Audio")
|
||||
.leftJoin("o_assets", "o_assets.id", "o_assetsRole2Audio.assetsRoleId")
|
||||
.whereIn(
|
||||
"assetsRoleId",
|
||||
data.map((i:any) => i.id!),
|
||||
)
|
||||
.select( "o_assets.id", "o_assets.name");
|
||||
console.log("%c Line:36 🍎 assets2AudioData", "background:#3f7cff", assets2AudioData);
|
||||
const repleAssets:Record<number,{id:number;name:string}[]> = {};
|
||||
assets2AudioData.forEach((item) => {
|
||||
if (!repleAssets[item.id]) repleAssets[item.id] = [item];
|
||||
else repleAssets[item.id].push(item);
|
||||
});
|
||||
const result = await Promise.all(
|
||||
data.map(async (parent: any) => {
|
||||
const historyImages = await u.db("o_image").where("assetsId", parent.id).andWhere("state", "已完成").select("id", "filePath");
|
||||
@ -45,6 +59,7 @@ export default router.post(
|
||||
...parent,
|
||||
filePath: parent.filePath && (await u.oss.getFileUrl(parent.filePath!)),
|
||||
historyImages: historyImagesWithUrl,
|
||||
relepedAudio:repleAssets[parent.id] ?? []
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
23
src/routes/cornerScape/updateAssetsAudio.ts
Normal file
23
src/routes/cornerScape/updateAssetsAudio.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 新增资产
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
assetsId: z.number(),
|
||||
audioIds: z.array(z.number()),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { assetsId, audioIds } = req.body;
|
||||
await u.db("o_assetsRole2Audio").where("assetsRoleId", assetsId).delete();
|
||||
if (audioIds.length) {
|
||||
await u.db("o_assetsRole2Audio").insert(audioIds.map((i: number) => ({ assetsRoleId: assetsId, assetsAudioId: i })));
|
||||
}
|
||||
res.status(200).send(success({ message: "更新音频成功" }));
|
||||
},
|
||||
);
|
||||
@ -13,11 +13,7 @@ export default router.post(
|
||||
async (req, res) => {
|
||||
const { modelId } = req.body;
|
||||
const [id, name] = modelId.split(":");
|
||||
const data = await u.db("o_vendorConfig").where("id", id).andWhere("enable", 1).select("models").first();
|
||||
if (!data) {
|
||||
return res.status(404).send({ error: "模型未找到" });
|
||||
}
|
||||
const models = JSON.parse(data.models!);
|
||||
const models = await u.vendor.getModelList(id);
|
||||
const findData = models.find((i: any) => i.modelName == name);
|
||||
res.status(200).send(success(findData));
|
||||
},
|
||||
|
||||
@ -12,24 +12,28 @@ export default router.post(
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { type } = req.body;
|
||||
const dataList = await u.db("o_vendorConfig").select("id", "models", "name").where("enable", 1);
|
||||
const dataList = await u.db("o_vendorConfig").select("id").where("enable", 1);
|
||||
if (!dataList || dataList.length === 0) {
|
||||
return res.status(404).send({ error: "模型未找到" });
|
||||
}
|
||||
const result = dataList.flatMap((data) => {
|
||||
const models = JSON.parse(data.models!);
|
||||
const filtered =
|
||||
type === "all"
|
||||
? models.filter((item: { type: string }) => item.type !== "video")
|
||||
: models.filter((item: { type: string }) => item.type === type);
|
||||
return filtered.map((item: { name: string; modelName: string; type: string }) => ({
|
||||
id: data.id,
|
||||
label: item.name,
|
||||
value: item.modelName,
|
||||
type: item.type,
|
||||
name: data.name,
|
||||
}));
|
||||
});
|
||||
res.status(200).send(success(result));
|
||||
const modelList = await Promise.all(dataList.map((i) => u.vendor.getModelList(i.id!)));
|
||||
const result = await Promise.all(
|
||||
dataList.map(async (data, index) => {
|
||||
const vendorData = await u.vendor.getVendor(data.id!);
|
||||
const models = modelList[index];
|
||||
const filtered =
|
||||
type === "all"
|
||||
? models.filter((item: { type: string }) => item.type !== "video")
|
||||
: models.filter((item: { type: string }) => item.type === type);
|
||||
return filtered.map((item: { name: string; modelName: string; type: string }) => ({
|
||||
id: data.id,
|
||||
label: item.name,
|
||||
value: item.modelName,
|
||||
type: item.type,
|
||||
name: vendorData.name,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
res.status(200).send(success(result.flat()));
|
||||
},
|
||||
);
|
||||
|
||||
@ -101,7 +101,7 @@ export default router.post(
|
||||
};
|
||||
const imageCls = await u.Ai.Image(projectSettingData?.imageModel as `${string}:${string}`).run(
|
||||
{
|
||||
imageBase64: imageBase64 ? [imageBase64] : [],
|
||||
referenceList: imageBase64 ? [{ type: "image", base64: imageBase64 }] : [],
|
||||
...repeloadObj,
|
||||
},
|
||||
{
|
||||
|
||||
@ -28,7 +28,13 @@ export default router.post(
|
||||
const imageClass = await u.Ai.Image(model).run(
|
||||
{
|
||||
prompt: prompt,
|
||||
imageBase64: references && references.length ? await Promise.all(references.map((url: string) => urlToBase64(url))) : [],
|
||||
referenceList: await (async () => {
|
||||
const list: { type: "image"; base64: string }[] = [];
|
||||
for (const url of references) {
|
||||
list.push({ type: "image" as const, base64: await urlToBase64(url) });
|
||||
}
|
||||
return list;
|
||||
})(),
|
||||
size: quality,
|
||||
aspectRatio: ratio,
|
||||
},
|
||||
|
||||
@ -73,6 +73,7 @@ export default router.post(
|
||||
storyboardTable: "",
|
||||
storyboard: [],
|
||||
//todo:矫正workbench数据
|
||||
//@ts-ignore
|
||||
workbench: {
|
||||
videoList: [],
|
||||
},
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
ids: z.array(z.number()),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { ids } = req.body;
|
||||
|
||||
//查询分镜配置
|
||||
const storyboardConfigs = await u.db("o_videoConfig").whereIn("storyboardId", ids).select("*");
|
||||
|
||||
//查询视频数据
|
||||
const videos = await u.db("o_video").whereIn("storyboardId", ids).select("*");
|
||||
|
||||
//组装数据
|
||||
const data = await Promise.all(
|
||||
ids.map(async (storyboardId: number) => {
|
||||
// 处理配置
|
||||
const configRow = storyboardConfigs.find((item) => item.storyboardId === storyboardId) || null;
|
||||
let config = null;
|
||||
if (configRow?.data) {
|
||||
const parsedData = JSON.parse(configRow.data);
|
||||
const dataWithFilePath = await Promise.all(
|
||||
parsedData.map(async (d: { type: string; id: number }) => {
|
||||
if (d.type === "assets" && d.id) {
|
||||
const row = await u
|
||||
.db("o_assets")
|
||||
.where("o_assets.id", d.id)
|
||||
.leftJoin("o_image", "o_assets.imageId", "o_image.id")
|
||||
.select("o_image.filePath as imageFilePath")
|
||||
.first();
|
||||
if (row?.imageFilePath) {
|
||||
return { id: d.id, type: "assets", url: await u.oss.getFileUrl(row.imageFilePath) };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (d.type === "storyboard" && d.id) {
|
||||
const row = await u.db("o_storyboard").where("id", d.id).select("filePath").first();
|
||||
if (row?.filePath) {
|
||||
return { id: d.id, type: "storyboard", url: await u.oss.getFileUrl(row.filePath) };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
config = { ...configRow, data: dataWithFilePath };
|
||||
}
|
||||
|
||||
// 处理视频
|
||||
const storyboardVideos = videos.filter((v) => v.storyboardId === storyboardId);
|
||||
const videosList = await Promise.all(
|
||||
storyboardVideos.map(async (item) => ({
|
||||
...item,
|
||||
filePath: item.filePath ? await u.oss.getFileUrl(item.filePath) : null,
|
||||
})),
|
||||
);
|
||||
|
||||
return {
|
||||
id: storyboardId,
|
||||
config,
|
||||
videos: videosList,
|
||||
};
|
||||
}),
|
||||
);
|
||||
return res.status(200).send(success(data));
|
||||
},
|
||||
);
|
||||
@ -55,7 +55,6 @@ export default router.post(
|
||||
.leftJoin("o_assets2Storyboard", "o_assets.id", "o_assets2Storyboard.assetId")
|
||||
.whereIn("o_assets2Storyboard.storyboardId", finalStoryboardIds)
|
||||
.select("o_assets2Storyboard.storyboardId", "o_assets.imageId");
|
||||
console.log("%c Line:42 🥪 assetData", "background:#ea7e5c", assetData);
|
||||
|
||||
const assetRecord: Record<number, number[]> = {};
|
||||
assetData.forEach((item: any) => {
|
||||
@ -88,7 +87,7 @@ export default router.post(
|
||||
await u.Ai.Image(projectSettingData?.imageModel as `${string}:${string}`)
|
||||
.run(
|
||||
{
|
||||
imageBase64: await getAssetsImageBase64(assetRecord[item.id!] || []),
|
||||
referenceList: await getAssetsImageBase64(assetRecord[item.id!] || []),
|
||||
...repeloadObj,
|
||||
},
|
||||
{
|
||||
@ -152,5 +151,5 @@ async function getAssetsImageBase64(imageIds: number[]) {
|
||||
}),
|
||||
);
|
||||
// 保留顺序,并且过滤掉无效项
|
||||
return imageUrls.filter(Boolean) as string[];
|
||||
return (imageUrls.filter(Boolean) as string[]).map((url) => ({ type: "image" as const, base64: url }));
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ export default router.post(
|
||||
async (req, res) => {
|
||||
const { id } = req.body;
|
||||
await u.db("o_videoTrack").where("id", id).delete();
|
||||
await u.db("o_storyboard").where("trackId", id).delete();
|
||||
res.status(200).send(success({ message: "视频段删除成功" }));
|
||||
},
|
||||
);
|
||||
|
||||
@ -95,10 +95,10 @@ export default router.post(
|
||||
await aiVideo.run(
|
||||
{
|
||||
prompt,
|
||||
imageBase64: base64.filter((item) => item !== null) as string[],
|
||||
referenceList: base64.filter((item) => item !== null).map((item) => ({ type: "image" as const, base64: item! })),
|
||||
mode: modeData.length > 0 ? modeData : mode,
|
||||
duration,
|
||||
aspectRatio: (ratio?.videoRatio as `${number}:${number}`) || "16:9",
|
||||
aspectRatio: (ratio?.videoRatio as "16:9" | "9:16") || "16:9",
|
||||
resolution,
|
||||
audio,
|
||||
},
|
||||
|
||||
@ -42,8 +42,7 @@ export default router.post(
|
||||
return res.status(400).json(success("项目未配置视频模型"));
|
||||
}
|
||||
const [videoId, videoModelName] = projectData.videoModel.split(":");
|
||||
const vendorData = await u.db("o_vendorConfig").where("id", videoId).select("models").first();
|
||||
const models = JSON.parse(vendorData!.models!);
|
||||
const models = await u.vendor.getModelList(videoId);
|
||||
const findData = models.find((i: any) => i.modelName == videoModelName);
|
||||
const isRef = findData.mode.every((i: any) => Array.isArray(i));
|
||||
|
||||
@ -62,6 +61,7 @@ export default router.post(
|
||||
sources: "storyboard",
|
||||
...(i.prompt != null ? { prompt: i.videoDesc } : {}),
|
||||
...(i.id != null ? { id: i.id } : {}),
|
||||
index: i.index,
|
||||
});
|
||||
} else {
|
||||
storyboardTrackRecord[i.trackId!] = [
|
||||
@ -71,6 +71,7 @@ export default router.post(
|
||||
sources: "storyboard",
|
||||
...(i.prompt != null ? { prompt: i.videoDesc } : {}),
|
||||
...(i.id != null ? { id: i.id } : {}),
|
||||
index: i.index,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -130,7 +131,10 @@ export default router.post(
|
||||
seenAssetIds.add(a.id);
|
||||
return true;
|
||||
});
|
||||
return [...storyboardMedias, ...uniqueAssets];
|
||||
const hasImageAssetData = uniqueAssets.filter(i => i.src)
|
||||
const notHasImageAssetData = uniqueAssets.filter(i => !i.src)
|
||||
|
||||
return [...hasImageAssetData, ...storyboardMedias, ...notHasImageAssetData];
|
||||
})(),
|
||||
videoList: await Promise.all(
|
||||
videoList
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post("/", async (req, res) => {
|
||||
const { type } = req.body;
|
||||
const vendorData = await u.db("o_vendorConfig").select("id", "models", "name");
|
||||
if (!vendorData) {
|
||||
return res.status(404).send({ error: "模型未找到" });
|
||||
}
|
||||
for (const item of vendorData) {
|
||||
const modelsData = JSON.parse(item.models! ?? "[]");
|
||||
const filterData = modelsData.filter((item: { type: string }) => item.type === type);
|
||||
if (filterData.length > 0) {
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -4,6 +4,6 @@ import u from "@/utils";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post("/", async (req, res) => {
|
||||
const data = await u.db("o_agentDeploy").leftJoin("o_vendorConfig", "o_vendorConfig.id", "o_agentDeploy.vendorId").select("o_agentDeploy.*", "o_vendorConfig.icon");
|
||||
const data = await u.db("o_agentDeploy").leftJoin("o_vendorConfig", "o_vendorConfig.id", "o_agentDeploy.vendorId").select("o_agentDeploy.*");
|
||||
res.status(200).send(success(data));
|
||||
});
|
||||
|
||||
@ -43,7 +43,7 @@ const vendorConfigSchema = z.object({
|
||||
mode: z.array(
|
||||
z.union([
|
||||
z.enum(["singleImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text", "audioReference", "videoReference"]),
|
||||
z.array(z.enum(["videoReference", "imageReference", "audioReference", "textReference"])),
|
||||
z.array(z.string().regex(/^(videoReference|imageReference|audioReference):\d+$/)),
|
||||
]),
|
||||
),
|
||||
audio: z.union([z.literal("optional"), z.boolean()]),
|
||||
@ -75,26 +75,39 @@ export default router.post(
|
||||
const vendor = exports.vendor;
|
||||
const result = vendorConfigSchema.safeParse(vendor);
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
|
||||
return res.status(400).send(error(`vendor配置校验失败: ${errorMsg}`));
|
||||
const issueLines = result.error.issues.map((issue, index) => {
|
||||
const path = issue.path.length ? issue.path.join(".") : "root";
|
||||
let detail = issue.message;
|
||||
|
||||
if (issue.code === "invalid_union") {
|
||||
const unionDetails = [
|
||||
...new Set(
|
||||
issue.errors
|
||||
.flat()
|
||||
.map((e) => e.message)
|
||||
.filter(Boolean),
|
||||
),
|
||||
];
|
||||
if (unionDetails.length > 0) {
|
||||
detail = `${issue.message}(${unionDetails.join(";")})`;
|
||||
}
|
||||
}
|
||||
return `${index + 1}. ${path}: ${detail}`;
|
||||
});
|
||||
|
||||
return res.status(400).send(error(`vendor配置校验失败,共 ${issueLines.length} 处:\n${issueLines.join("\n")}`));
|
||||
}
|
||||
|
||||
if ((vendor.id as string).includes(":")) return res.status(400).send(error("id不能包含英文冒号"));
|
||||
const data = await u.db("o_vendorConfig").where("id", vendor.id).first();
|
||||
if (data) return res.status(500).send(error("供应商id已存在"));
|
||||
await u.db("o_vendorConfig").insert({
|
||||
const [id] = await u.db("o_vendorConfig").insert({
|
||||
id: vendor.id,
|
||||
author: vendor.author,
|
||||
description: vendor.description || "",
|
||||
name: vendor.name,
|
||||
icon: vendor.icon || "",
|
||||
inputs: JSON.stringify(vendor.inputs ?? []),
|
||||
inputValues: JSON.stringify(vendor.inputValues ?? {}),
|
||||
models: JSON.stringify(vendor.models ?? []),
|
||||
code: tsCode,
|
||||
createTime: Date.now(),
|
||||
models: JSON.stringify([]),
|
||||
enable: vendor.id == "toonflow" ? 1 : 0,
|
||||
});
|
||||
u.vendor.writeCode(vendor.id, tsCode);
|
||||
res.status(200).send(success(result.data));
|
||||
},
|
||||
);
|
||||
|
||||
61
src/routes/setting/vendorConfig/addVendorModel.ts
Normal file
61
src/routes/setting/vendorConfig/addVendorModel.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import express from "express";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.string(),
|
||||
model: z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("text"),
|
||||
think: z.boolean(),
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("image"),
|
||||
mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("video"),
|
||||
mode: z.array(
|
||||
z.union([
|
||||
z.enum(["singleImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text", "audioReference", "videoReference"]),
|
||||
z.array(z.string().regex(/^(videoReference|imageReference|audioReference):\d+$/)),
|
||||
]),
|
||||
),
|
||||
audio: z.union([z.literal("optional"), z.boolean()]),
|
||||
durationResolutionMap: z.array(
|
||||
z.object({
|
||||
duration: z.array(z.number()),
|
||||
resolution: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, model } = req.body;
|
||||
|
||||
const models = await u.db("o_vendorConfig").where("id", id).first("models");
|
||||
if (models?.models) {
|
||||
const existingModels = JSON.parse(models.models);
|
||||
existingModels.push(model);
|
||||
await u
|
||||
.db("o_vendorConfig")
|
||||
.where("id", id)
|
||||
.update({
|
||||
models: JSON.stringify(existingModels),
|
||||
});
|
||||
}
|
||||
res.status(200).send(success("更新成功"));
|
||||
},
|
||||
);
|
||||
33
src/routes/setting/vendorConfig/delVendorModel.ts
Normal file
33
src/routes/setting/vendorConfig/delVendorModel.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import express from "express";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.string(),
|
||||
modelName: z.string(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, modelName } = req.body;
|
||||
|
||||
const models = await u.db("o_vendorConfig").where("id", id).first("models");
|
||||
if (models?.models) {
|
||||
const existingModels = JSON.parse(models.models);
|
||||
if (!existingModels.some((model: any) => model.modelName === modelName)) {
|
||||
return res.status(400).send(error("基本模型不允许删除"));
|
||||
}
|
||||
const updatedModels = existingModels.filter((model: any) => model.modelName !== modelName);
|
||||
await u
|
||||
.db("o_vendorConfig")
|
||||
.where("id", id)
|
||||
.update({
|
||||
models: JSON.stringify(updatedModels),
|
||||
});
|
||||
}
|
||||
res.status(200).send(success("更新成功"));
|
||||
},
|
||||
);
|
||||
@ -1,6 +1,8 @@
|
||||
import express from "express";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
const router = express.Router();
|
||||
@ -16,6 +18,7 @@ export default router.post(
|
||||
model: null,
|
||||
vendorId: null,
|
||||
});
|
||||
fs.rmSync(path.join(u.getPath("vendor"), `${id}.ts`), { recursive: true, force: true });
|
||||
res.status(200).send(success("删除成功"));
|
||||
},
|
||||
);
|
||||
|
||||
@ -12,9 +12,7 @@ export default router.post(
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, enable } = req.body;
|
||||
await u.db("o_vendorConfig").where("id", id).update({
|
||||
enable,
|
||||
});
|
||||
await u.db("o_vendorConfig").where("id", id).update({ enable });
|
||||
res.status(200).send(success("更新成功"));
|
||||
},
|
||||
);
|
||||
|
||||
@ -6,11 +6,23 @@ const router = express.Router();
|
||||
export default router.post("/", async (req, res) => {
|
||||
const data = await u.db("o_vendorConfig").select("*");
|
||||
|
||||
const list = data.map((item) => ({
|
||||
...item,
|
||||
inputs: JSON.parse(item.inputs ?? "{}"),
|
||||
inputValues: JSON.parse(item.inputValues ?? "{}"),
|
||||
models: JSON.parse(item.models ?? "[]"),
|
||||
}));
|
||||
const list = await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const vendor = u.vendor.getVendor(item.id!);
|
||||
return {
|
||||
...item,
|
||||
inputValues: JSON.parse(item.inputValues ?? "{}"),
|
||||
models: await u.vendor.getModelList(item.id!),
|
||||
code: u.vendor.getCode(item.id!),
|
||||
description: vendor.description,
|
||||
inputs: vendor.inputs,
|
||||
author: vendor.author,
|
||||
name: vendor.name,
|
||||
version: vendor.version ?? "1.0",
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
list.sort((a, b) => (a.id === "toonflow" ? -1 : b.id === "toonflow" ? 1 : 0));
|
||||
res.status(200).send(success(list));
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ export default router.post(
|
||||
modelData: {
|
||||
prompt:
|
||||
"一张16:9比例的图片,完美等分为2x2四宫格布局,各区域无缝衔接:\n左上宫格:一只可爱的猫,毛发蓬松,眼睛明亮,姿态俏皮\n右上宫格:一只友善的狗,金毛犬,表情愉悦,摇着尾巴\n左下宫格:一头健壮的牛,田园背景,目光温和,皮毛光泽\n右下宫格:一匹骏马,姿态优雅,鬃毛飘逸,肌肉健美\n风格要求:四个宫格风格统一,色彩鲜艳饱和,高清画质,细节清晰锐利,专业插画风格,线条干净,统一的左上方光源,柔和阴影,和谐配色,卡通/半写实风格,宫格间用白色或浅灰细线分隔", //图片提示词
|
||||
imageBase64: [], //输入的图片提示词
|
||||
referenceList: [], //输入的图片提示词
|
||||
size: "1K", // 图片尺寸
|
||||
aspectRatio: "16:9",
|
||||
},
|
||||
@ -37,7 +37,7 @@ export default router.post(
|
||||
if (!vendorConfigData) return res.status(500).send(error("未找到该供应商配置"));
|
||||
if (!vendorConfigData.models) return res.status(500).send(error("未找到模型列表"));
|
||||
|
||||
const modelList = JSON.parse(vendorConfigData.models);
|
||||
const modelList = await u.vendor.getModelList(vendorConfigData.id!);
|
||||
|
||||
const selectedModel = modelList.find((i: any) => i.modelName == modelName);
|
||||
if (type == "video") {
|
||||
@ -46,8 +46,9 @@ export default router.post(
|
||||
duration: selectedModel.durationResolutionMap[0].duration[0],
|
||||
resolution: selectedModel.durationResolutionMap[0].resolution[0],
|
||||
aspectRatio: "16:9",
|
||||
prompt: "生成一个卖火柴的小女孩,保持镜头稳定,从远景到近景",
|
||||
imageBase64: [],
|
||||
prompt:
|
||||
"A shirtless middle-aged man with a horse head is standing in a supermarket, carefully comparing two identical bottles of shampoo for 3 seconds, then suddenly bursts into tears, drops to his knees dramatically, a flock of pigeons explodes out of nowhere from behind him, the supermarket lights flicker, an old grandma nearby continues shopping completely unbothered, the horse head man instantly stops crying, puts both shampoo bottles back, and moonwalks away disappearing into the vegetable section. Security camera footage style, slightly grainy, 5 seconds.",
|
||||
referenceList: [],
|
||||
audio: false,
|
||||
mode: "text",
|
||||
};
|
||||
|
||||
66
src/routes/setting/vendorConfig/upVendorModel.ts
Normal file
66
src/routes/setting/vendorConfig/upVendorModel.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import express from "express";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.string(),
|
||||
modelName: z.string(),
|
||||
model: z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("text"),
|
||||
think: z.boolean(),
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("image"),
|
||||
mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("video"),
|
||||
mode: z.array(
|
||||
z.union([
|
||||
z.enum(["singleImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text", "audioReference", "videoReference"]),
|
||||
z.array(z.string().regex(/^(videoReference|imageReference|audioReference):\d+$/)),
|
||||
]),
|
||||
),
|
||||
audio: z.union([z.literal("optional"), z.boolean()]),
|
||||
durationResolutionMap: z.array(
|
||||
z.object({
|
||||
duration: z.array(z.number()),
|
||||
resolution: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, modelName, model } = req.body;
|
||||
|
||||
const models = await u.db("o_vendorConfig").where("id", id).first("models");
|
||||
if (models?.models) {
|
||||
const existingModels = JSON.parse(models.models);
|
||||
const modelIndex = existingModels.findIndex((m: any) => m.modelName !== modelName);
|
||||
if (modelIndex === -1) {
|
||||
existingModels.push(model);
|
||||
}
|
||||
existingModels[modelIndex] = model;
|
||||
await u
|
||||
.db("o_vendorConfig")
|
||||
.where("id", id)
|
||||
.update({
|
||||
models: JSON.stringify(existingModels),
|
||||
});
|
||||
}
|
||||
res.status(200).send(success("更新成功"));
|
||||
},
|
||||
);
|
||||
@ -44,7 +44,7 @@ const vendorConfigSchema = z.object({
|
||||
mode: z.array(
|
||||
z.union([
|
||||
z.enum(["singleImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text", "audioReference", "videoReference"]),
|
||||
z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])),
|
||||
z.array(z.string().regex(/^(videoReference|imageReference|audioReference):\d+$/)),
|
||||
]),
|
||||
),
|
||||
audio: z.union([z.literal("optional"), z.boolean()]),
|
||||
@ -85,16 +85,11 @@ export default router.post(
|
||||
.db("o_vendorConfig")
|
||||
.where("id", id)
|
||||
.update({
|
||||
author: vendor.author,
|
||||
description: vendor.description || "",
|
||||
name: vendor.name,
|
||||
icon: vendor.icon || "",
|
||||
inputs: JSON.stringify(vendor.inputs ?? []),
|
||||
inputValues: JSON.stringify(vendor.inputValues ?? {}),
|
||||
models: JSON.stringify(vendor.models ?? []),
|
||||
code: tsCode,
|
||||
createTime: Date.now(),
|
||||
});
|
||||
u.vendor.writeCode(id, tsCode);
|
||||
|
||||
res.status(200).send(success(result.data));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import express from "express";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { transform } from "sucrase";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.string(),
|
||||
inputValues: z.record(z.string(), z.string()),
|
||||
inputs: z.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
label: z.string(),
|
||||
type: z.enum(["text", "password", "url"]),
|
||||
required: z.boolean(),
|
||||
placeholder: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
models: z.array(
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("text"),
|
||||
think: z.boolean(),
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("image"),
|
||||
mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
|
||||
}),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
modelName: z.string(),
|
||||
type: z.literal("video"),
|
||||
mode: z.array(
|
||||
z.union([
|
||||
z.enum(["singleImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text"]),
|
||||
z.array(z.enum(["audioReference", "videoReference", "textReference", "imageReference"])),
|
||||
]),
|
||||
),
|
||||
audio: z.union([z.literal("optional"), z.boolean()]),
|
||||
durationResolutionMap: z.array(
|
||||
z.object({
|
||||
duration: z.array(z.number()),
|
||||
resolution: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
]),
|
||||
),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, models, inputs, inputValues } = req.body;
|
||||
|
||||
await u
|
||||
.db("o_vendorConfig")
|
||||
.where("id", id)
|
||||
.update({
|
||||
inputs: JSON.stringify(inputs),
|
||||
inputValues: JSON.stringify(inputValues),
|
||||
models: JSON.stringify(models),
|
||||
});
|
||||
res.status(200).send(success("更新成功"));
|
||||
},
|
||||
);
|
||||
26
src/routes/setting/vendorConfig/updateVendorInputs.ts
Normal file
26
src/routes/setting/vendorConfig/updateVendorInputs.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import express from "express";
|
||||
import { success, error } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { transform } from "sucrase";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.string(),
|
||||
inputValues: z.record(z.string(), z.string()),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id, inputValues } = req.body;
|
||||
|
||||
await u
|
||||
.db("o_vendorConfig")
|
||||
.where("id", id)
|
||||
.update({
|
||||
inputValues: JSON.stringify(inputValues),
|
||||
});
|
||||
res.status(200).send(success("更新成功"));
|
||||
},
|
||||
);
|
||||
@ -12,6 +12,7 @@ import { getPrompts } from "@/utils/getPrompts";
|
||||
import { getArtPrompt } from "@/utils/getArtPrompt";
|
||||
import replaceUrl from "@/utils/replaceUrl";
|
||||
import writeVersion from "@/utils/writeVersion";
|
||||
import * as vendor from "@/utils/vendor";
|
||||
|
||||
export default {
|
||||
db,
|
||||
@ -28,4 +29,5 @@ export default {
|
||||
getArtPrompt,
|
||||
replaceUrl,
|
||||
writeVersion,
|
||||
vendor,
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { generateText, streamText, wrapLanguageModel, stepCountIs } from "ai";
|
||||
import { generateText, streamText, wrapLanguageModel, stepCountIs, extractReasoningMiddleware } from "ai";
|
||||
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
||||
import axios from "axios";
|
||||
import { transform } from "sucrase";
|
||||
@ -17,14 +17,20 @@ async function resolveModelName(value: AiType | `${string}:${string}`): Promise<
|
||||
return value as `${number}:${string}`;
|
||||
}
|
||||
|
||||
async function getVendorTemplateFn(fnName: FnName, modelName: `${string}:${string}`) {
|
||||
async function getVendorTemplateFn(
|
||||
fnName: "textRequest",
|
||||
modelName: `${string}:${string}`,
|
||||
): Promise<(think?: boolean, thinkLevel?: 0 | 1 | 2 | 3) => any>;
|
||||
async function getVendorTemplateFn(fnName: Exclude<FnName, "textRequest">, modelName: `${string}:${string}`): Promise<(input: any) => any>;
|
||||
async function getVendorTemplateFn(fnName: FnName, modelName: `${string}:${string}`): Promise<any> {
|
||||
const [id, name] = modelName.split(":");
|
||||
const vendorConfigData = await u.db("o_vendorConfig").where("id", id).first();
|
||||
if (!vendorConfigData) throw new Error(`未找到供应商配置 id=${id}`);
|
||||
const modelList = JSON.parse(vendorConfigData.models ?? "[]");
|
||||
const modelList = await u.vendor.getModelList(id);
|
||||
const selectedModel = modelList.find((i: any) => i.modelName == name);
|
||||
if (!selectedModel) throw new Error(`未找到模型 ${name} id=${id}`);
|
||||
const jsCode = transform(vendorConfigData.code!, { transforms: ["typescript"] }).code;
|
||||
const code = u.vendor.getCode(id);
|
||||
const jsCode = transform(code, { transforms: ["typescript"] }).code;
|
||||
const running = u.vm(jsCode);
|
||||
if (running.vendor) {
|
||||
Object.assign(running.vendor.inputValues, JSON.parse(vendorConfigData.inputValues ?? "{}"));
|
||||
@ -32,7 +38,11 @@ async function getVendorTemplateFn(fnName: FnName, modelName: `${string}:${strin
|
||||
}
|
||||
const fn = running[fnName];
|
||||
if (!fn) throw new Error(`未找到供应商配置中的函数 ${fnName} id=${id}`);
|
||||
if (fnName == "textRequest") return fn(selectedModel);
|
||||
if (fnName == "textRequest")
|
||||
return (think?: boolean, thinkLevel: 0 | 1 | 2 | 3 = 0) => {
|
||||
const effectiveThink = think ?? !!selectedModel.think;
|
||||
return fn(selectedModel, effectiveThink, thinkLevel);
|
||||
};
|
||||
else return <T>(input: T) => fn(input, selectedModel);
|
||||
}
|
||||
|
||||
@ -42,13 +52,13 @@ async function withTaskRecord<T>(
|
||||
describe: string,
|
||||
relatedObjects: string,
|
||||
projectId: number,
|
||||
fn: (modelName: `${string}:${string}`) => Promise<T>,
|
||||
fn: (modelName: `${string}:${string}`, think: Boolean, thinkLevel: 0 | 1 | 2 | 3) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const modelName = await resolveModelName(modelKey);
|
||||
const [id, model] = modelName.split(":");
|
||||
const taskRecord = await u.task(projectId, taskClass, model, { describe: describe, content: relatedObjects });
|
||||
try {
|
||||
const result = await fn(modelName);
|
||||
const result = await fn(modelName, false, 0);
|
||||
taskRecord(1);
|
||||
return result;
|
||||
} catch (e) {
|
||||
@ -72,46 +82,73 @@ async function urlToBase64(url: string, retries = 3, delay = 1000): Promise<stri
|
||||
}
|
||||
class AiText {
|
||||
private AiType: AiType | `${string}:${string}`;
|
||||
constructor(AiType: AiType | `${string}:${string}`) {
|
||||
private think?: boolean;
|
||||
private thinkLevel: 0 | 1 | 2 | 3;
|
||||
constructor(AiType: AiType | `${string}:${string}`, think?: boolean, thinkLevel: 0 | 1 | 2 | 3 = 0) {
|
||||
this.AiType = AiType;
|
||||
this.think = think;
|
||||
this.thinkLevel = thinkLevel;
|
||||
}
|
||||
async invoke(input: Omit<Parameters<typeof generateText>[0], "model">) {
|
||||
const switchAiDevTool = await u.db("o_setting").where("key", "switchAiDevTool").first();
|
||||
const modelName = await resolveModelName(this.AiType);
|
||||
const sdkFn = await getVendorTemplateFn("textRequest", modelName);
|
||||
return generateText({
|
||||
...(input.tools && { stopWhen: stepCountIs(Object.keys(input.tools).length * 50) }),
|
||||
...input,
|
||||
model:
|
||||
switchAiDevTool?.value === "1"
|
||||
? wrapLanguageModel({
|
||||
model: await getVendorTemplateFn("textRequest", modelName),
|
||||
model: await sdkFn(this.think, this.thinkLevel),
|
||||
middleware: devToolsMiddleware(),
|
||||
})
|
||||
: await getVendorTemplateFn("textRequest", modelName),
|
||||
: await sdkFn(this.think, this.thinkLevel),
|
||||
} as Parameters<typeof generateText>[0]);
|
||||
}
|
||||
async stream(input: Omit<Parameters<typeof streamText>[0], "model">) {
|
||||
const switchAiDevTool = await u.db("o_setting").where("key", "switchAiDevTool").first();
|
||||
const modelName = await resolveModelName(this.AiType);
|
||||
const sdkFn = await getVendorTemplateFn("textRequest", modelName);
|
||||
return streamText({
|
||||
...(input.tools && { stopWhen: stepCountIs(Object.keys(input.tools).length * 50) }),
|
||||
...input,
|
||||
model:
|
||||
switchAiDevTool?.value == "1"
|
||||
? wrapLanguageModel({
|
||||
model: await getVendorTemplateFn("textRequest", modelName),
|
||||
middleware: devToolsMiddleware(),
|
||||
model: sdkFn(this.think, this.thinkLevel),
|
||||
middleware: [
|
||||
devToolsMiddleware(),
|
||||
extractReasoningMiddleware({
|
||||
tagName: "reasoning_content",
|
||||
}),
|
||||
],
|
||||
})
|
||||
: await getVendorTemplateFn("textRequest", modelName),
|
||||
: wrapLanguageModel({
|
||||
model: sdkFn(this.think, this.thinkLevel),
|
||||
middleware: extractReasoningMiddleware({
|
||||
tagName: "reasoning_content",
|
||||
}),
|
||||
}),
|
||||
} as Parameters<typeof streamText>[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function referenceList2imageBase642(id: string, input: any) {
|
||||
const version = u.vendor.getVendor(id).version;
|
||||
if (!version || isNaN(parseFloat(version)) || parseFloat(version) < 2.0) {
|
||||
input.imageBase64 = input.referenceList.map((item: any) => item.base64);
|
||||
return input;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
type ReferenceList = { type: "image"; base64: string } | { type: "audio"; base64: string } | { type: "video"; base64: string };
|
||||
|
||||
interface ImageConfig {
|
||||
prompt: string; //图片提示词
|
||||
imageBase64: string[]; //输入的图片提示词
|
||||
size: "1K" | "2K" | "4K"; // 图片尺寸
|
||||
aspectRatio: `${number}:${number}`; // 长宽比
|
||||
prompt: string;
|
||||
referenceList?: Extract<ReferenceList, { type: "image" }>[];
|
||||
size: "1K" | "2K" | "4K";
|
||||
aspectRatio: `${number}:${number}`;
|
||||
}
|
||||
|
||||
interface TaskRecord {
|
||||
@ -131,6 +168,7 @@ class AiImage {
|
||||
const modelName = await resolveModelName(this.key);
|
||||
const exec = async (mn: `${string}:${string}`) => {
|
||||
const fn = await getVendorTemplateFn("imageRequest", mn);
|
||||
await referenceList2imageBase642(mn.split(":")[0], input);
|
||||
this.result = await fn(input);
|
||||
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
|
||||
return this;
|
||||
@ -145,14 +183,23 @@ class AiImage {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
type VideoMode =
|
||||
| "singleImage" //单图参考
|
||||
| "startEndRequired" //首尾帧(两张都得有)
|
||||
| "endFrameOptional" //首尾帧(尾帧可选)
|
||||
| "startFrameOptional" //首尾帧(首帧可选)
|
||||
| "text" //文本
|
||||
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[]; //多参考(数字代表限制数量)
|
||||
|
||||
interface VideoConfig {
|
||||
prompt: string; //视频提示词
|
||||
imageBase64: string[]; //输入的图片提示词
|
||||
aspectRatio: `${number}:${number}`; // 长宽比
|
||||
mode: string; //模式
|
||||
duration: number; // 视频时长,单位秒
|
||||
resolution: string; // 视频分辨率
|
||||
audio: boolean; // 是否需要配音
|
||||
duration: number;
|
||||
resolution: string;
|
||||
aspectRatio: "16:9" | "9:16";
|
||||
prompt: string;
|
||||
referenceList?: ReferenceList[];
|
||||
audio?: boolean;
|
||||
mode: VideoMode[];
|
||||
}
|
||||
|
||||
class AiVideo {
|
||||
@ -165,6 +212,7 @@ class AiVideo {
|
||||
const modelName = await resolveModelName(this.key);
|
||||
const exec = async (mn: `${string}:${string}`) => {
|
||||
const fn = await getVendorTemplateFn("videoRequest", mn);
|
||||
await referenceList2imageBase642(mn.split(":")[0], input);
|
||||
this.result = await fn(input);
|
||||
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
|
||||
return this;
|
||||
@ -189,6 +237,7 @@ class AiAudio {
|
||||
const modelName = await resolveModelName(this.key);
|
||||
const exec = async (mn: `${string}:${string}`) => {
|
||||
const fn = await getVendorTemplateFn("ttsRequest", mn);
|
||||
await referenceList2imageBase642(mn.split(":")[0], input);
|
||||
this.result = await fn(input);
|
||||
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
|
||||
return this;
|
||||
@ -205,7 +254,7 @@ class AiAudio {
|
||||
}
|
||||
|
||||
export default {
|
||||
Text: (AiType: AiType | `${string}:${string}`) => new AiText(AiType),
|
||||
Text: (AiType: AiType | `${string}:${string}`, think?: boolean, thinkLevel?: 0 | 1 | 2 | 3) => new AiText(AiType, think, thinkLevel),
|
||||
Image: (key: `${string}:${string}`) => new AiImage(key),
|
||||
Video: (key: `${string}:${string}`) => new AiVideo(key),
|
||||
Audio: (key: `${string}:${string}`) => new AiAudio(key),
|
||||
|
||||
@ -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}` };
|
||||
}
|
||||
});
|
||||
};
|
||||
41
src/utils/vendor.ts
Normal file
41
src/utils/vendor.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { transform } from "sucrase";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import u from "@/utils";
|
||||
|
||||
export function writeCode(id: string | number, tsCode: string) {
|
||||
const rootDir = u.getPath("vendor")
|
||||
fs.mkdirSync(rootDir, { recursive: true })
|
||||
if (fs.existsSync(path.join(rootDir, `${id}.ts`))) {
|
||||
fs.writeFileSync(path.join(rootDir, `${id}.ts`), tsCode);
|
||||
}
|
||||
fs.writeFileSync(path.join(rootDir, `${id}.ts`), tsCode);
|
||||
}
|
||||
|
||||
export function getCode(id: string): string {
|
||||
const rootDir = u.getPath("vendor");
|
||||
const targetFile = path.join(rootDir, `${id}.ts`);
|
||||
if (!fs.existsSync(targetFile)) return "";
|
||||
return fs.readFileSync(targetFile, "utf-8");
|
||||
}
|
||||
|
||||
export async function getModelList(id: string): Promise<Array<any>> {
|
||||
const models = await u.db("o_vendorConfig").where("id", id).select("models").first();
|
||||
if (!models || !models.models) return [];
|
||||
const code = getCode(id);
|
||||
const jsCode = transform(code, { transforms: ["typescript"] }).code;
|
||||
const vendorData = u.vm(jsCode);
|
||||
const combined = [...JSON.parse(JSON.stringify(vendorData.vendor.models)), ...JSON.parse(models?.models ?? "[]")];
|
||||
const map = new Map<string, any>();
|
||||
for (const m of combined) {
|
||||
map.set(m.modelName, m);
|
||||
}
|
||||
return [...map.values()];
|
||||
}
|
||||
|
||||
export function getVendor(id: string) {
|
||||
const code = getCode(id);
|
||||
const jsCode = transform(code, { transforms: ["typescript"] }).code;
|
||||
const vendorData = u.vm(jsCode);
|
||||
return vendorData.vendor;
|
||||
}
|
||||
@ -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> = {
|
||||
@ -31,7 +32,7 @@ export default function runCode(code: string, vendor?: Record<string, any>) {
|
||||
urlToBase64,
|
||||
mergeImages,
|
||||
pollTask,
|
||||
fetch,
|
||||
fetch: fetch,
|
||||
exports,
|
||||
axios,
|
||||
FormData,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "5.0",
|
||||
"target": "ESNext",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
@ -12,10 +13,21 @@
|
||||
"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",
|
||||
"dist",
|
||||
"build"
|
||||
]
|
||||
}
|
||||
@ -1869,11 +1869,14 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.6:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
<<<<<<< HEAD
|
||||
custom-electron-titlebar@^4.2.8:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmmirror.com/custom-electron-titlebar/-/custom-electron-titlebar-4.4.1.tgz"
|
||||
integrity sha512-I+sOGBdslrGpuCWlhda8P0vtRAZK+W2NzjHLsxTiE2bNmhAIs9YLDe6iRBExwU1xVZt+J1hSXzUT67BlAuMWLA==
|
||||
|
||||
=======
|
||||
>>>>>>> develop
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user