no message
This commit is contained in:
parent
1c65d00881
commit
6b85599c2e
35
src/utils/ai/image/adapter/volcengine.ts
Normal file
35
src/utils/ai/image/adapter/volcengine.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import "../type";
|
||||||
|
|
||||||
|
export function buildReqBody(input: ImageConfig, config: AIConfig) {
|
||||||
|
const size = input.size === "1K" ? "2K" : input.size;
|
||||||
|
const sizeMap: Record<string, Record<string, string>> = {
|
||||||
|
"16:9": {
|
||||||
|
"2K": "2848x1600",
|
||||||
|
"4K": "4096x2304",
|
||||||
|
},
|
||||||
|
"9:16": {
|
||||||
|
"2K": "1600x2848",
|
||||||
|
"4K": "2304x4096",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||||
|
|
||||||
|
const requestBody: Record<string, any> = {
|
||||||
|
model: config.model,
|
||||||
|
prompt: fullPrompt,
|
||||||
|
size: sizeMap[input.aspectRatio][size],
|
||||||
|
response_format: "url",
|
||||||
|
sequential_image_generation: "disabled",
|
||||||
|
stream: false,
|
||||||
|
watermark: false,
|
||||||
|
...(input.imageBase64 && { image: input.imageBase64 }),
|
||||||
|
};
|
||||||
|
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReqUrl(baseUrl: string) {
|
||||||
|
return {
|
||||||
|
requestUrl: `${baseUrl}/v1/images/generations`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -4,16 +4,10 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|||||||
import { pollTask } from "@/utils/ai/utils";
|
import { pollTask } from "@/utils/ai/utils";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
function getApiUrl(apiUrl: string) {
|
import * as volcengine from "../adapter/volcengine";
|
||||||
if (apiUrl.includes("|")) {
|
const modelFn = {
|
||||||
const parts = apiUrl.split("|");
|
volcengine,
|
||||||
if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) {
|
} as const;
|
||||||
throw new Error("url 格式错误,请使用 url1|url2 格式");
|
|
||||||
}
|
|
||||||
return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() };
|
|
||||||
}
|
|
||||||
throw new Error("请填写正确的url");
|
|
||||||
}
|
|
||||||
function template(replaceObj: Record<string, any>, url: string) {
|
function template(replaceObj: Record<string, any>, url: string) {
|
||||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||||
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||||
@ -23,83 +17,37 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
|||||||
if (!config.model) throw new Error("缺少Model名称");
|
if (!config.model) throw new Error("缺少Model名称");
|
||||||
if (!config.apiKey) throw new Error("缺少API Key");
|
if (!config.apiKey) throw new Error("缺少API Key");
|
||||||
|
|
||||||
const defaultBaseURL = "http://192.168.0.74:3000/imagegenerator/task|http://192.168.0.74:3000/imagegenerator/task/{id}";
|
const { requestUrl, queryUrl = null } = modelFn["volcengine"].buildReqUrl("http://192.168.0.74:33332");
|
||||||
const { requestUrl, queryUrl } = getApiUrl(config.baseURL! ?? defaultBaseURL);
|
const taskBody = modelFn["volcengine"].buildReqBody(input, config);
|
||||||
// 根据 size 配置映射到具体尺寸
|
|
||||||
const sizeMap: Record<string, Record<string, string>> = {
|
|
||||||
"1K": {
|
|
||||||
"16:9": "1664x928",
|
|
||||||
"9:16": "928x1664",
|
|
||||||
},
|
|
||||||
"2K": {
|
|
||||||
"16:9": "2048x1152",
|
|
||||||
"9:16": "1152x2048",
|
|
||||||
},
|
|
||||||
"4K": {
|
|
||||||
"16:9": "2048x1152",
|
|
||||||
"9:16": "1328*1328",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const modelSizeMap = {
|
|
||||||
"Qwen-Image": {
|
|
||||||
"16:9": "1664*928",
|
|
||||||
"9:16": "928*1664",
|
|
||||||
},
|
|
||||||
"Z-Image-Turbo": {
|
|
||||||
"16:9": "1024*768",
|
|
||||||
"9:16": "768*1024",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// 构建完整的提示词
|
|
||||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
|
||||||
|
|
||||||
let mergedImage = input.imageBase64;
|
|
||||||
if (mergedImage && mergedImage.length) {
|
|
||||||
const smallImage = await u.imageTools.mergeImages(mergedImage, "5mb");
|
|
||||||
mergedImage = [smallImage];
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = modelSizeMap?.[config.model]?.[input.size]?.[input.aspectRatio] ?? modelSizeMap?.[config.model]?.[input.size] ?? "1024*1024";
|
|
||||||
|
|
||||||
const taskBody: Record<string, any> = {
|
|
||||||
model: config.model,
|
|
||||||
input: {
|
|
||||||
prompt: fullPrompt,
|
|
||||||
...(input.imageBase64 && input.imageBase64.length ? { images: input.imageBase64 } : {}),
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
size:"1600*2848",
|
|
||||||
},
|
|
||||||
// negative_prompt: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
||||||
console.log("%c Line:70 🥪 data", "background:#ed9ec7", data);
|
|
||||||
|
|
||||||
if (data.code != "success") throw new Error(`任务提交失败: ${data || "未知错误"}`);
|
if (queryUrl) {
|
||||||
const taskId = data.data;
|
if (data.code != "success") throw new Error(`任务提交失败: ${data || "未知错误"}`);
|
||||||
|
const taskId = data.data;
|
||||||
|
|
||||||
return await pollTask(async () => {
|
return await pollTask(async () => {
|
||||||
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
||||||
headers: { Authorization: `Bearer ${apiKey}` },
|
headers: { Authorization: `Bearer ${apiKey}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status, result_url, fail_reason } = queryData.data || {};
|
||||||
|
|
||||||
|
if (status === "FAILURE") {
|
||||||
|
return { completed: false, error: fail_reason ?? "图片生成失败" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "SUCCESS") {
|
||||||
|
return { completed: true, url: result_url };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { completed: false };
|
||||||
});
|
});
|
||||||
console.log("%c Line:77 🍧 data", "background:#f5ce50", data);
|
} else {
|
||||||
console.log("%c Line:76 🥑 queryData", "background:#2eafb0", queryData);
|
return data.data[0]?.url;
|
||||||
|
}
|
||||||
const { status, result_url, fail_reason } = queryData.data || {};
|
|
||||||
|
|
||||||
if (status === "FAILURE") {
|
|
||||||
return { completed: false, error: fail_reason ?? "图片生成失败" };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === "SUCCESS") {
|
|
||||||
return { completed: true, url: result_url };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { completed: false };
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const msg = u.error(error).message || "图片生成失败";
|
const msg = u.error(error).message || "图片生成失败";
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
|
|||||||
39
src/utils/ai/video/adapter/openai.ts
Normal file
39
src/utils/ai/video/adapter/openai.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import sharp from "sharp";
|
||||||
|
import "../type";
|
||||||
|
import FormData from "form-data";
|
||||||
|
|
||||||
|
export async function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||||
|
const sizeMap: Record<string, string> = {
|
||||||
|
"16:9": "1280x720",
|
||||||
|
"9:16": "720x1280",
|
||||||
|
};
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("model", config.model!);
|
||||||
|
formData.append("prompt", input.prompt);
|
||||||
|
formData.append("seconds", String(input.duration));
|
||||||
|
|
||||||
|
const size = sizeMap[input.aspectRatio] || "1280x720";
|
||||||
|
formData.append("size", size);
|
||||||
|
if (input.imageBase64 && input.imageBase64.length) {
|
||||||
|
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||||
|
const buffer = Buffer.from(base64Data, "base64");
|
||||||
|
|
||||||
|
// 解析尺寸
|
||||||
|
const [width, height] = size.split("x").map(Number);
|
||||||
|
|
||||||
|
// 使用 sharp 调整图片尺寸
|
||||||
|
const resizedBuffer = await sharp(buffer).resize(width, height, { fit: "cover" }).jpeg({ quality: 100 }).toBuffer();
|
||||||
|
|
||||||
|
formData.append("input_reference", resizedBuffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string; downLoadUrl: string } {
|
||||||
|
return {
|
||||||
|
requestUrl: `${baseUrl}/v1/videos`,
|
||||||
|
queryUrl: `${baseUrl}/v1/videos/{id}`,
|
||||||
|
downLoadUrl: `${baseUrl}/v1/videos/{id}/content`,
|
||||||
|
};
|
||||||
|
}
|
||||||
23
src/utils/ai/video/adapter/vidu.ts
Normal file
23
src/utils/ai/video/adapter/vidu.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import "../type";
|
||||||
|
|
||||||
|
export function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||||
|
const requestBody: any = {
|
||||||
|
model: config.model,
|
||||||
|
...(input.imageBase64 && input.imageBase64.length ? { images: input.imageBase64 } : {}),
|
||||||
|
prompt: input.prompt,
|
||||||
|
duration: input.duration,
|
||||||
|
resolution: input.resolution,
|
||||||
|
audio: input?.audio ?? false,
|
||||||
|
aspect_ratio: input.aspectRatio,
|
||||||
|
off_peak: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string } {
|
||||||
|
return {
|
||||||
|
requestUrl: `${baseUrl}/v1/video/generations`,
|
||||||
|
queryUrl: `${baseUrl}/v1/video/generations/{id}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
53
src/utils/ai/video/adapter/volcengine.ts
Normal file
53
src/utils/ai/video/adapter/volcengine.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import "../type";
|
||||||
|
|
||||||
|
export function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||||
|
const hasStartEndType = input.mode === "startEnd";
|
||||||
|
const images = input.imageBase64 || [];
|
||||||
|
// 判断是否为首尾帧模式(需要两张图且类型支持首尾帧)
|
||||||
|
const isStartEndMode = images.length === 2 && hasStartEndType;
|
||||||
|
|
||||||
|
// 构建图片内容
|
||||||
|
const imageContent = images.map((base64, index) => {
|
||||||
|
const item: Record<string, any> = {
|
||||||
|
type: "image_url",
|
||||||
|
image_url: { url: base64 },
|
||||||
|
};
|
||||||
|
if (isStartEndMode) {
|
||||||
|
item.role = index === 0 ? "first_frame" : "last_frame";
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
// };
|
||||||
|
const requestBody: any = {
|
||||||
|
model: config.model,
|
||||||
|
...(input.imageBase64 && input.imageBase64.length ? { images: input.imageBase64 } : {}),
|
||||||
|
prompt: input.prompt,
|
||||||
|
duration: input.duration,
|
||||||
|
size: input.resolution,
|
||||||
|
metadata: {
|
||||||
|
generate_audio: input?.audio ?? false,
|
||||||
|
image_roles: ["first_frame", "last_frame"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// // 仅当模型支持音频时才添加 generate_audio 字段
|
||||||
|
// if (typeof input.audio == "boolean") {
|
||||||
|
// requestBody.generate_audio = input.audio ?? false;
|
||||||
|
// }
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string } {
|
||||||
|
return {
|
||||||
|
requestUrl: `${baseUrl}/v1/video/generations`,
|
||||||
|
queryUrl: `${baseUrl}/v1/video/generations/{id}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
62
src/utils/ai/video/adapter/wan.ts
Normal file
62
src/utils/ai/video/adapter/wan.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import "../type";
|
||||||
|
|
||||||
|
export function buildReqBody(input: VideoConfig, config: AIConfig) {
|
||||||
|
const images = input.imageBase64 || [];
|
||||||
|
|
||||||
|
// 构建图片内容
|
||||||
|
const imageContent = images.map((base64, index) => {
|
||||||
|
const item: Record<string, any> = {
|
||||||
|
type: "image_url",
|
||||||
|
image: { url: base64 },
|
||||||
|
};
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
const sizeMap: Record<string, Record<string, string>> = {
|
||||||
|
"480p": {
|
||||||
|
"16:9": "832*480",
|
||||||
|
"9:16": "480*832",
|
||||||
|
},
|
||||||
|
"720p": {
|
||||||
|
"16:9": "1280*720",
|
||||||
|
"9:16": "720*1280",
|
||||||
|
},
|
||||||
|
"1080p": {
|
||||||
|
"16:9": "1920*1080",
|
||||||
|
"9:16": "1080*1920",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const hasStartEnd = input.mode == "startEnd";
|
||||||
|
console.log("%c Line:29 🎂 hasStartEnd", "background:#2eafb0", hasStartEnd);
|
||||||
|
const imageReq: Record<string, string> = {};
|
||||||
|
if (hasStartEnd && Array.isArray(images) && images.length) {
|
||||||
|
if (images[0]) imageReq.img_url = images[0];
|
||||||
|
if (images[1]) imageReq.last_frame_url = images[1];
|
||||||
|
} else if (!hasStartEnd && Array.isArray(images) && images[0]) {
|
||||||
|
console.log("%c Line:35 🍤", "background:#f5ce50");
|
||||||
|
imageReq.img_url = images[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolutionKey = input.resolution.toLowerCase();
|
||||||
|
console.log("%c Line:43 🍑 resolutionKey", "background:#e41a6a", resolutionKey);
|
||||||
|
const size = sizeMap[resolutionKey]?.[input.aspectRatio];
|
||||||
|
|
||||||
|
const requestBody: any = {
|
||||||
|
model: config.model,
|
||||||
|
...(imageReq?.img_url ? { input_reference: imageReq.img_url } : {}),
|
||||||
|
prompt: input.prompt,
|
||||||
|
duration: input.duration,
|
||||||
|
size: !images.length ? size : input.resolution.toUpperCase(),
|
||||||
|
metadata: {
|
||||||
|
...imageReq,
|
||||||
|
audio: input?.audio ?? false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return requestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReqUrl(baseUrl: string): { requestUrl: string; queryUrl: string } {
|
||||||
|
return {
|
||||||
|
requestUrl: `${baseUrl}/v1/video/generations`,
|
||||||
|
queryUrl: `${baseUrl}/v1/video/generations/{id}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -159,15 +159,6 @@ const modelList: Owned[] = [
|
|||||||
audio: false,
|
audio: false,
|
||||||
},
|
},
|
||||||
// ================== ViduQ3系列 ==================
|
// ================== ViduQ3系列 ==================
|
||||||
// viduq3-pro 文生视频
|
|
||||||
{
|
|
||||||
manufacturer: "vidu",
|
|
||||||
model: "viduq3-pro",
|
|
||||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], resolution: ["540p", "720p", "1080p"] }],
|
|
||||||
aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"],
|
|
||||||
type: ["text"],
|
|
||||||
audio: true,
|
|
||||||
},
|
|
||||||
// viduq3-pro 图生视频
|
// viduq3-pro 图生视频
|
||||||
{
|
{
|
||||||
manufacturer: "vidu",
|
manufacturer: "vidu",
|
||||||
@ -187,14 +178,6 @@ const modelList: Owned[] = [
|
|||||||
audio: false,
|
audio: false,
|
||||||
},
|
},
|
||||||
// viduq2-pro 文生视频
|
// viduq2-pro 文生视频
|
||||||
{
|
|
||||||
manufacturer: "vidu",
|
|
||||||
model: "viduq2-pro",
|
|
||||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],
|
|
||||||
aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"],
|
|
||||||
type: ["text"],
|
|
||||||
audio: false,
|
|
||||||
},
|
|
||||||
// viduq2-pro 图生视频
|
// viduq2-pro 图生视频
|
||||||
{
|
{
|
||||||
manufacturer: "vidu",
|
manufacturer: "vidu",
|
||||||
@ -205,14 +188,6 @@ const modelList: Owned[] = [
|
|||||||
audio: false,
|
audio: false,
|
||||||
},
|
},
|
||||||
// viduq2-turbo 文生视频
|
// viduq2-turbo 文生视频
|
||||||
{
|
|
||||||
manufacturer: "vidu",
|
|
||||||
model: "viduq2-turbo",
|
|
||||||
durationResolutionMap: [{ duration: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], resolution: ["540p", "720p", "1080p"] }],
|
|
||||||
aspectRatio: ["16:9", "9:16", "3:4", "4:3", "1:1"],
|
|
||||||
type: ["text"],
|
|
||||||
audio: false,
|
|
||||||
},
|
|
||||||
// viduq2-turbo 图生视频
|
// viduq2-turbo 图生视频
|
||||||
{
|
{
|
||||||
manufacturer: "vidu",
|
manufacturer: "vidu",
|
||||||
@ -223,14 +198,6 @@ const modelList: Owned[] = [
|
|||||||
audio: false,
|
audio: false,
|
||||||
},
|
},
|
||||||
// viduq1 文生视频
|
// viduq1 文生视频
|
||||||
{
|
|
||||||
manufacturer: "vidu",
|
|
||||||
model: "viduq1",
|
|
||||||
durationResolutionMap: [{ duration: [5], resolution: ["1080p"] }],
|
|
||||||
aspectRatio: ["16:9", "9:16", "1:1"],
|
|
||||||
type: ["text"],
|
|
||||||
audio: false,
|
|
||||||
},
|
|
||||||
// viduq1 图生视频
|
// viduq1 图生视频
|
||||||
{
|
{
|
||||||
manufacturer: "vidu",
|
manufacturer: "vidu",
|
||||||
@ -465,7 +432,7 @@ const modelList: Owned[] = [
|
|||||||
type: ["singleImage", "text"],
|
type: ["singleImage", "text"],
|
||||||
audio: false,
|
audio: false,
|
||||||
},
|
},
|
||||||
// ================== Apimart 系列 ==================
|
// ================== Apimart 系列 ==================
|
||||||
// sora
|
// sora
|
||||||
{
|
{
|
||||||
manufacturer: "apimart",
|
manufacturer: "apimart",
|
||||||
|
|||||||
@ -1,18 +1,74 @@
|
|||||||
import "../type";
|
import "../type";
|
||||||
import { generateImage, generateText, ModelMessage } from "ai";
|
|
||||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
||||||
import { pollTask } from "@/utils/ai/utils";
|
import { pollTask } from "@/utils/ai/utils";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
function getApiUrl(apiUrl: string) {
|
import path from "path";
|
||||||
if (apiUrl.includes("|")) {
|
|
||||||
const parts = apiUrl.split("|");
|
import * as volcengine from "../adapter/volcengine";
|
||||||
if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) {
|
import * as openai from "../adapter/openai";
|
||||||
throw new Error("url 格式错误,请使用 url1|url2 格式");
|
import * as vidu from "../adapter/vidu";
|
||||||
}
|
import * as wan from "../adapter/wan";
|
||||||
return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() };
|
|
||||||
|
// 适配器映射
|
||||||
|
const modelFn = {
|
||||||
|
volcengine,
|
||||||
|
vidu,
|
||||||
|
openai,
|
||||||
|
wan,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// 模型名称到适配器的映射(精确匹配)
|
||||||
|
const modelMapping: Record<string, keyof typeof modelFn> = {
|
||||||
|
// Volcengine 火山引擎模型
|
||||||
|
"doubao-seedance-1-5-pro-251215": "volcengine",
|
||||||
|
"doubao-seedance-1-0-pro-250528": "volcengine",
|
||||||
|
"Seedance-2.0": "volcengine",
|
||||||
|
// Vidu 模型
|
||||||
|
ViduQ2: "vidu",
|
||||||
|
"ViduQ2-turbo": "vidu",
|
||||||
|
"ViduQ2-pro": "vidu",
|
||||||
|
"ViduQ3-pro": "vidu",
|
||||||
|
// OpenAI 模型
|
||||||
|
sora2: "openai",
|
||||||
|
"sora2-pro": "openai",
|
||||||
|
"gpt-video": "openai",
|
||||||
|
// 万象/Wan 模型
|
||||||
|
"Wan2.6-T2V": "wan",
|
||||||
|
"Wan2.6-I2V": "wan",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模型名称关键字到适配器的映射(模糊匹配)
|
||||||
|
const modelKeywords: Array<{ keywords: string[]; adapter: keyof typeof modelFn }> = [
|
||||||
|
{ keywords: ["doubao", "volcengine", "seedance"], adapter: "volcengine" },
|
||||||
|
{ keywords: ["vidu"], adapter: "vidu" },
|
||||||
|
{ keywords: ["sora", "openai", "gpt"], adapter: "openai" },
|
||||||
|
{ keywords: ["wan", "wanx"], adapter: "wan" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据模型名称获取对应的适配器
|
||||||
|
*/
|
||||||
|
function getModelAdapter(modelName: string) {
|
||||||
|
// 1. 先尝试精确匹配
|
||||||
|
const exactMatch = modelMapping[modelName.toLowerCase()];
|
||||||
|
if (exactMatch) {
|
||||||
|
return modelFn[exactMatch];
|
||||||
}
|
}
|
||||||
throw new Error("请填写正确的url");
|
|
||||||
|
// 2. 尝试关键字模糊匹配
|
||||||
|
const lowerModelName = modelName.toLowerCase();
|
||||||
|
for (const { keywords, adapter } of modelKeywords) {
|
||||||
|
if (keywords.some((kw) => lowerModelName.includes(kw.toLowerCase()))) {
|
||||||
|
return modelFn[adapter];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 如果模型名称本身就是适配器名称
|
||||||
|
if (modelName in modelFn) {
|
||||||
|
return modelFn[modelName as keyof typeof modelFn];
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelFn["wan"];
|
||||||
}
|
}
|
||||||
function template(replaceObj: Record<string, any>, url: string) {
|
function template(replaceObj: Record<string, any>, url: string) {
|
||||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||||
@ -23,77 +79,78 @@ export default async (input: VideoConfig, config: AIConfig): Promise<string> =>
|
|||||||
if (!config.model) throw new Error("缺少Model名称");
|
if (!config.model) throw new Error("缺少Model名称");
|
||||||
if (!config.apiKey) throw new Error("缺少API Key");
|
if (!config.apiKey) throw new Error("缺少API Key");
|
||||||
|
|
||||||
const defaultBaseURL = "http://192.168.0.74:3000/videogenerator/generate|http://192.168.0.74:3000/videogenerator/generate/{id}";
|
// 根据模型名称获取对应的适配器
|
||||||
const { requestUrl, queryUrl } = getApiUrl(config.baseURL! ?? defaultBaseURL);
|
const modelAdapter = getModelAdapter(config.model);
|
||||||
// 根据 size 配置映射到具体尺寸
|
|
||||||
const sizeMap: Record<string, Record<string, string>> = {
|
|
||||||
"480P": {
|
|
||||||
"16:9": "832*480",
|
|
||||||
"9:16": "480*332",
|
|
||||||
},
|
|
||||||
"720P": {
|
|
||||||
"16:9": "1280*720",
|
|
||||||
"9:16": "720*1280",
|
|
||||||
},
|
|
||||||
"1080P": {
|
|
||||||
"16:9": "1920*1080",
|
|
||||||
"9:16": "1080*1920",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// 构建完整的提示词
|
|
||||||
let mergedImage = input.imageBase64;
|
|
||||||
if (mergedImage && mergedImage.length) {
|
|
||||||
const smallImage = await u.imageTools.mergeImages(mergedImage, "5mb");
|
|
||||||
mergedImage = [smallImage];
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = sizeMap[input.resolution]?.[input.aspectRatio] ?? "1280*720";
|
const { requestUrl, queryUrl, downLoadUrl = null } = modelAdapter.buildReqUrl("http://192.168.0.74:33332");
|
||||||
const imageCount: { type: string; image_url: string }[] = [];
|
const taskBody = await modelAdapter.buildReqBody(input, config);
|
||||||
if (input.imageBase64 && input.imageBase64.length) {
|
|
||||||
input.imageBase64.forEach((i, index) => {
|
|
||||||
imageCount.push({
|
|
||||||
type: "image_url",
|
|
||||||
image_url: { url: i },
|
|
||||||
role: index === 0 ? "first_frame" : "last_frame",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const taskBody: Record<string, any> = {
|
|
||||||
model: config.model,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: input.prompt,
|
|
||||||
},
|
|
||||||
...imageCount,
|
|
||||||
],
|
|
||||||
// parameters: {
|
|
||||||
// aspect_ratio: input.aspectRatio,
|
|
||||||
// size: input.resolution,
|
|
||||||
// duration: input.duration,
|
|
||||||
// },
|
|
||||||
// ...(typeof input.audio === "boolean" ? { generate_audio: input.audio } : {}),
|
|
||||||
};
|
|
||||||
console.log("%c Line:62 🥑 taskBody", "background:#ea7e5c", taskBody);
|
|
||||||
|
|
||||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
||||||
console.log("%c Line:70 🥪 data", "background:#ed9ec7", data);
|
console.log("%c Line:91 🌽 data", "background:#3f7cff", data);
|
||||||
console.log("%c Line:84 🍐 data.code != uccess", "background:#e41a6a", data.code != "success");
|
|
||||||
console.log("%c Line:83 🍇 data.code", "background:#b03734", data.code);
|
|
||||||
|
|
||||||
if (data.code != "success") throw new Error(`任务提交失败: ${data || "未知错误"}`);
|
const taskId = data.id ?? data.taskId ?? data.task_id ?? data.data;
|
||||||
const taskId = data.data;
|
|
||||||
|
if (!taskId) throw new Error(`任务提交失败: ${data ? JSON.stringify(data) : "未知错误"}`);
|
||||||
|
|
||||||
return await pollTask(async () => {
|
return await pollTask(async () => {
|
||||||
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
||||||
headers: { Authorization: `Bearer ${apiKey}` },
|
headers: { Authorization: `Bearer ${apiKey}` },
|
||||||
});
|
});
|
||||||
console.log("%c Line:77 🍧 data", "background:#f5ce50", queryData);
|
console.log("%c Line:99 🥝 queryData", "background:#e41a6a", queryData);
|
||||||
|
|
||||||
const { status, result_url, fail_reason } = queryData.data || {};
|
// const { status, result_url, fail_reason } = queryData.data || {};
|
||||||
|
|
||||||
|
const status = queryData?.status ?? queryData?.data?.status;
|
||||||
|
const result_url = queryData?.metadata?.url ?? queryData?.data?.result_url;
|
||||||
|
const fail_reason = queryData?.data?.fail_reason ?? queryData?.data;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case "completed":
|
||||||
|
case "SUCCESS":
|
||||||
|
case "success":
|
||||||
|
if (downLoadUrl) {
|
||||||
|
// 下载视频,带重试机制
|
||||||
|
let videoRes;
|
||||||
|
let retries = 3;
|
||||||
|
let lastError;
|
||||||
|
|
||||||
|
for (let i = 0; i < retries; i++) {
|
||||||
|
try {
|
||||||
|
// 构建下载URL
|
||||||
|
const finalDownloadUrl = downLoadUrl
|
||||||
|
? template({ id: taskId }, downLoadUrl)
|
||||||
|
: queryData.video_url || queryData.url || queryData.metadata.url; // 从响应中获取视频URL
|
||||||
|
|
||||||
|
videoRes = await axios.get(finalDownloadUrl, {
|
||||||
|
headers: { Authorization: `Bearer ${apiKey}` },
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
timeout: 60 * 1000 * 10, // 60秒超时
|
||||||
|
});
|
||||||
|
break; // 成功则跳出循环
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
console.error(`视频下载失败,第 ${i + 1}/${retries} 次尝试:`, error);
|
||||||
|
if (i < retries - 1) {
|
||||||
|
// 等待后重试,使用指数退避
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!videoRes) {
|
||||||
|
throw new Error(`视频下载失败,已重试 ${retries} 次: ${lastError}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将视频buffer转换为base64或直接返回buffer
|
||||||
|
const savePath = input.savePath.endsWith(".mp4") ? input.savePath : path.join(input.savePath, `other_${Date.now()}.mp4`);
|
||||||
|
await u.oss.writeFile(input.savePath, videoRes.data);
|
||||||
|
|
||||||
|
return { completed: true, url: savePath };
|
||||||
|
} else {
|
||||||
|
return { completed: true, url: result_url };
|
||||||
|
}
|
||||||
|
}
|
||||||
if (status === "FAILURE") {
|
if (status === "FAILURE") {
|
||||||
return { completed: false, error: fail_reason ? fail_reason : "视频生成失败" };
|
return { completed: false, error: fail_reason ? fail_reason : "视频生成失败" };
|
||||||
}
|
}
|
||||||
@ -105,16 +162,8 @@ export default async (input: VideoConfig, config: AIConfig): Promise<string> =>
|
|||||||
return { completed: false };
|
return { completed: false };
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log("%c Line:105 🍖 error", "background:#ed9ec7", error);
|
|
||||||
const msg = u.error(error).message || "图片生成失败";
|
const msg = u.error(error).message || "图片生成失败";
|
||||||
console.log("%c Line:107 🌽 u.error(error)", "background:#ea7e5c", u.error(error));
|
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function urlToBase64(url: string): Promise<string> {
|
|
||||||
const res = await axios.get(url, { responseType: "arraybuffer" });
|
|
||||||
const base64 = Buffer.from(res.data).toString("base64");
|
|
||||||
const mimeType = res.headers["content-type"] || "image/png";
|
|
||||||
return `data:${mimeType};base64,${base64}`;
|
|
||||||
}
|
|
||||||
|
|||||||
146
src/utils/ai/video/owned/other copy.ts
Normal file
146
src/utils/ai/video/owned/other copy.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import "../type";
|
||||||
|
import axios from "axios";
|
||||||
|
import sharp from "sharp";
|
||||||
|
import FormData from "form-data";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import u from "@/utils";
|
||||||
|
|
||||||
|
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||||
|
function template(replaceObj: Record<string, any>, url: string) {
|
||||||
|
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||||
|
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default async (input: VideoConfig, config: AIConfig) => {
|
||||||
|
if (!config.apiKey) throw new Error("缺少API Key");
|
||||||
|
if (!config.baseURL) throw new Error("缺少baseURL");
|
||||||
|
// const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
||||||
|
|
||||||
|
const authorization = `Bearer ${config.apiKey}`;
|
||||||
|
const urls = config.baseURL.split("|");
|
||||||
|
const isThreeUrlMode = urls.length === 3;
|
||||||
|
console.log("%c Line:24 🌭 isThreeUrlMode", "background:#ed9ec7", isThreeUrlMode);
|
||||||
|
|
||||||
|
let requestUrl: string, queryUrl: string, downLoadUrl: string | undefined;
|
||||||
|
|
||||||
|
if (isThreeUrlMode) {
|
||||||
|
[requestUrl, queryUrl, downLoadUrl] = urls;
|
||||||
|
} else {
|
||||||
|
[requestUrl, queryUrl] = urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 aspectRatio 设置 size
|
||||||
|
const sizeMap: Record<string, string> = {
|
||||||
|
"16:9": "1280x720",
|
||||||
|
"9:16": "720x1280",
|
||||||
|
};
|
||||||
|
let resData;
|
||||||
|
let taskId = "";
|
||||||
|
if (isThreeUrlMode) {
|
||||||
|
// 三个地址:使用 FormData 方式
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("model", config.model);
|
||||||
|
formData.append("prompt", input.prompt);
|
||||||
|
formData.append("seconds", String(input.duration));
|
||||||
|
|
||||||
|
const size = sizeMap[input.aspectRatio] || "1280x720";
|
||||||
|
formData.append("size", size);
|
||||||
|
|
||||||
|
if (input.imageBase64 && input.imageBase64.length) {
|
||||||
|
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||||
|
const buffer = Buffer.from(base64Data, "base64");
|
||||||
|
|
||||||
|
// 解析尺寸
|
||||||
|
const [width, height] = size.split("x").map(Number);
|
||||||
|
|
||||||
|
// 使用 sharp 调整图片尺寸
|
||||||
|
const resizedBuffer = await sharp(buffer).resize(width, height, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
|
||||||
|
|
||||||
|
formData.append("input_reference", resizedBuffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post(requestUrl, formData, {
|
||||||
|
headers: { Authorization: authorization, ...formData.getHeaders() },
|
||||||
|
});
|
||||||
|
|
||||||
|
taskId = response.data?.task_id || response.data?.id;
|
||||||
|
resData = response.data;
|
||||||
|
} else {
|
||||||
|
// 两个地址:使用 JSON 方式
|
||||||
|
|
||||||
|
const requestBody: any = {
|
||||||
|
model: config.model,
|
||||||
|
prompt: input.prompt,
|
||||||
|
aspect_ratio: input.aspectRatio || "16:9",
|
||||||
|
size: "720p",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (input.imageBase64 && input.imageBase64.length) {
|
||||||
|
requestBody.images = input.imageBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post(requestUrl, JSON.stringify(requestBody), {
|
||||||
|
headers: {
|
||||||
|
Authorization: authorization,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
taskId = response.data.id;
|
||||||
|
resData = response.data;
|
||||||
|
}
|
||||||
|
console.log("%c Line:87 🥒 taskId", "background:#f5ce50", taskId);
|
||||||
|
|
||||||
|
if (!taskId) throw new Error(`任务提交失败: ${resData ? JSON.stringify(resData) : "未知错误"}`);
|
||||||
|
|
||||||
|
return await pollTask(async () => {
|
||||||
|
// 构建查询URL,两个地址模式时使用URL参数
|
||||||
|
const finalQueryUrl = isThreeUrlMode ? template({ id: taskId }, queryUrl) : `${queryUrl}?id=${taskId}`;
|
||||||
|
|
||||||
|
const { data: queryData } = await axios.get(finalQueryUrl, {
|
||||||
|
headers: { Authorization: authorization },
|
||||||
|
});
|
||||||
|
console.log("%c Line:100 🥑 queryData", "background:#42b983", queryData);
|
||||||
|
|
||||||
|
if (queryData.status === "completed") {
|
||||||
|
// 下载视频,带重试机制
|
||||||
|
let videoRes;
|
||||||
|
let retries = 3;
|
||||||
|
let lastError;
|
||||||
|
|
||||||
|
for (let i = 0; i < retries; i++) {
|
||||||
|
try {
|
||||||
|
// 构建下载URL
|
||||||
|
const finalDownloadUrl = isThreeUrlMode && downLoadUrl ? template({ id: taskId }, downLoadUrl) : queryData.video_url || queryData.url; // 从响应中获取视频URL
|
||||||
|
|
||||||
|
videoRes = await axios.get(finalDownloadUrl, {
|
||||||
|
headers: isThreeUrlMode ? { Authorization: authorization } : {},
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
timeout: 60 * 1000 * 10, // 60秒超时
|
||||||
|
});
|
||||||
|
break; // 成功则跳出循环
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
console.error(`视频下载失败,第 ${i + 1}/${retries} 次尝试:`, error);
|
||||||
|
if (i < retries - 1) {
|
||||||
|
// 等待后重试,使用指数退避
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!videoRes) {
|
||||||
|
throw new Error(`视频下载失败,已重试 ${retries} 次: ${lastError}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将视频buffer转换为base64或直接返回buffer
|
||||||
|
const savePath = input.savePath.endsWith(".mp4") ? input.savePath : path.join(input.savePath, `other_${Date.now()}.mp4`);
|
||||||
|
await u.oss.writeFile(input.savePath, videoRes.data);
|
||||||
|
|
||||||
|
return { completed: true, url: savePath };
|
||||||
|
}
|
||||||
|
if (queryData.status === "failed") return { completed: false, error: `任务失败: ${queryData.error || "未知错误"}` };
|
||||||
|
// if (queryData.status === "QUEUED" || queryData.status === "RUNNING") return { completed: false };
|
||||||
|
return { completed: false };
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -71,15 +71,29 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
|
|
||||||
const requestBody: any = {
|
const requestBody: any = {
|
||||||
model: config.model,
|
model: config.model,
|
||||||
prompt: input.prompt,
|
images: [
|
||||||
aspect_ratio: input.aspectRatio || "16:9",
|
"https://prod-ss-images.s3.cn-northwest-1.amazonaws.com.cn/vidu-maas/template/startend2video-1.jpeg",
|
||||||
size: "720p",
|
"https://prod-ss-images.s3.cn-northwest-1.amazonaws.com.cn/vidu-maas/template/startend2video-2.jpeg",
|
||||||
|
],
|
||||||
|
prompt:
|
||||||
|
"The camera zooms in on the bird, which then flies to the right. With its flight being smooth and natural, the bird soars in the sky. with a red light effect following and surrounding it from behind.",
|
||||||
|
duration: 5,
|
||||||
|
seed: 0,
|
||||||
|
resolution: "1080p",
|
||||||
|
audio: true,
|
||||||
|
off_peak: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (input.imageBase64 && input.imageBase64.length) {
|
if (input.imageBase64 && input.imageBase64.length) {
|
||||||
requestBody.images = input.imageBase64;
|
requestBody.images = [
|
||||||
|
"https://prod-ss-images.s3.cn-northwest-1.amazonaws.com.cn/vidu-maas/template/startend2video-1.jpeg",
|
||||||
|
"https://prod-ss-images.s3.cn-northwest-1.amazonaws.com.cn/vidu-maas/template/startend2video-2.jpeg",
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("%c Line:86 🍷 requestUrl", "background:#4fff4B", requestUrl);
|
||||||
|
console.log("%c Line:89 🥪 authorization", "background:#3f7cff", authorization);
|
||||||
|
|
||||||
const response = await axios.post(requestUrl, JSON.stringify(requestBody), {
|
const response = await axios.post(requestUrl, JSON.stringify(requestBody), {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: authorization,
|
Authorization: authorization,
|
||||||
@ -88,6 +102,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
});
|
});
|
||||||
taskId = response.data.id;
|
taskId = response.data.id;
|
||||||
resData = response.data;
|
resData = response.data;
|
||||||
|
console.log("%c Line:91 🍎 resData", "background:#ed9ec7", resData);
|
||||||
}
|
}
|
||||||
console.log("%c Line:87 🥒 taskId", "background:#f5ce50", taskId);
|
console.log("%c Line:87 🥒 taskId", "background:#f5ce50", taskId);
|
||||||
|
|
||||||
@ -95,7 +110,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
|
|
||||||
return await pollTask(async () => {
|
return await pollTask(async () => {
|
||||||
// 构建查询URL,两个地址模式时使用URL参数
|
// 构建查询URL,两个地址模式时使用URL参数
|
||||||
const finalQueryUrl = isThreeUrlMode ? template({ id: taskId }, queryUrl) : `${queryUrl}?id=${taskId}`;
|
const finalQueryUrl = isThreeUrlMode ? template({ id: taskId }, queryUrl) : template({ id: taskId }, queryUrl);
|
||||||
|
|
||||||
const { data: queryData } = await axios.get(finalQueryUrl, {
|
const { data: queryData } = await axios.get(finalQueryUrl, {
|
||||||
headers: { Authorization: authorization },
|
headers: { Authorization: authorization },
|
||||||
@ -111,7 +126,8 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
for (let i = 0; i < retries; i++) {
|
for (let i = 0; i < retries; i++) {
|
||||||
try {
|
try {
|
||||||
// 构建下载URL
|
// 构建下载URL
|
||||||
const finalDownloadUrl = isThreeUrlMode && downLoadUrl ? template({ id: taskId }, downLoadUrl) : queryData.video_url || queryData.url; // 从响应中获取视频URL
|
const finalDownloadUrl =
|
||||||
|
isThreeUrlMode && downLoadUrl ? template({ id: taskId }, downLoadUrl) : queryData.video_url || queryData.url || queryData.metadata.url; // 从响应中获取视频URL
|
||||||
|
|
||||||
videoRes = await axios.get(finalDownloadUrl, {
|
videoRes = await axios.get(finalDownloadUrl, {
|
||||||
headers: isThreeUrlMode ? { Authorization: authorization } : {},
|
headers: isThreeUrlMode ? { Authorization: authorization } : {},
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
type: "image_url",
|
type: "image_url",
|
||||||
image_url: { url: base64 },
|
image_url: { url: base64 },
|
||||||
};
|
};
|
||||||
if (isStartEndMode) {
|
if (isStartEndMode) {
|
||||||
item.role = index === 0 ? "first_frame" : "last_frame";
|
item.role = index === 0 ? "first_frame" : "last_frame";
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
@ -46,7 +46,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
Authorization: authorization,
|
Authorization: authorization,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log("%c Line:44 🍡 createResponse", "background:#2eafb0", createResponse);
|
console.log("%c Line:44 🍡 createResponse", "background:#2eafb0", createResponse);
|
||||||
|
|
||||||
const taskId = createResponse.data.id;
|
const taskId = createResponse.data.id;
|
||||||
|
|
||||||
@ -54,14 +54,16 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
|
|
||||||
// 轮询任务状态
|
// 轮询任务状态
|
||||||
return await pollTask(async () => {
|
return await pollTask(async () => {
|
||||||
const data = await axios.get(`${baseUrl}/${taskId}`, {
|
const data = await axios.get(`${baseUrl}/query/${taskId}`, {
|
||||||
headers: { Authorization: authorization },
|
headers: { Authorization: authorization },
|
||||||
});
|
});
|
||||||
|
console.log("%c Line:62 🥕 data.data", "background:#e41a6a", data.data);
|
||||||
|
|
||||||
const { status, content, error } = data.data;
|
const { status, content, error } = data.data;
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "succeeded":
|
case "succeeded":
|
||||||
|
case "completed":
|
||||||
return { completed: true, url: content?.video_url };
|
return { completed: true, url: content?.video_url };
|
||||||
case "failed":
|
case "failed":
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
@ -75,6 +77,9 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
return { completed: false, error: `任务${status}: ${errorMsg}` };
|
return { completed: false, error: `任务${status}: ${errorMsg}` };
|
||||||
case "queued":
|
case "queued":
|
||||||
case "running":
|
case "running":
|
||||||
|
case "unknown":
|
||||||
|
case "submit":
|
||||||
|
case "in_progress":
|
||||||
return { completed: false };
|
return { completed: false };
|
||||||
default:
|
default:
|
||||||
return { completed: false, error: `未知状态: ${status}` };
|
return { completed: false, error: `未知状态: ${status}` };
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user