no message
This commit is contained in:
parent
53e547d405
commit
e3c584ea8e
@ -16,14 +16,17 @@ export default router.post(
|
|||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { modelName, apiKey, baseURL, manufacturer } = req.body;
|
const { modelName, apiKey, baseURL, manufacturer } = req.body;
|
||||||
|
try {
|
||||||
const image =await u.ai.image({
|
const image = await u.ai.image({
|
||||||
prompt: "2D cat",
|
prompt: "生成16:9 四宫格图片,第一宫格是一只猫,第二宫格是一只狗, 第三宫格是一只老虎,第四宫格是猪。保证宫格图片标准等分",
|
||||||
imageBase64: [],
|
imageBase64: [],
|
||||||
aspectRatio: "16:9",
|
aspectRatio: "16:9",
|
||||||
size: "1K",
|
size: "1K",
|
||||||
});
|
});
|
||||||
res.status(200).send(success(image));
|
res.status(200).send(success(image));
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.status(500).send(error(e?.response?.data ?? e?.message ?? "生成失败"));
|
||||||
|
}
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// const contentStr = await u.ai.generateImage(
|
// const contentStr = await u.ai.generateImage(
|
||||||
|
|||||||
@ -1,201 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import u from "@/utils";
|
|
||||||
import FormData from "form-data";
|
|
||||||
import axiosRetry from "axios-retry";
|
|
||||||
import sharp from "sharp";
|
|
||||||
|
|
||||||
interface ImageConfig {
|
|
||||||
systemPrompt?: string;
|
|
||||||
prompt: string;
|
|
||||||
imageBase64: string[];
|
|
||||||
size: "1K" | "2K" | "4K";
|
|
||||||
aspectRatio: string;
|
|
||||||
resType?: "url" | "b64";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImageModelConfig {
|
|
||||||
model?: string;
|
|
||||||
apiKey?: string;
|
|
||||||
baseURL?: string;
|
|
||||||
manufacturer?: "openAi" | "gemini" | "volcengine" | "runninghub" | "apimart";
|
|
||||||
}
|
|
||||||
// 上传 base64 图片到 runninghub
|
|
||||||
const uploadBase64ToRunninghub = async (base64Image: string, apiKey: string, baseURL: string): Promise<string> => {
|
|
||||||
try {
|
|
||||||
apiKey = apiKey.replace("Bearer ", "");
|
|
||||||
// 移除 base64 前缀
|
|
||||||
const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, "");
|
|
||||||
let buffer = Buffer.from(base64Data, "base64");
|
|
||||||
|
|
||||||
// 压缩图片到 5MB 以下
|
|
||||||
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
|
||||||
if (buffer.length > MAX_SIZE) {
|
|
||||||
let quality = 90;
|
|
||||||
|
|
||||||
while (buffer.length > MAX_SIZE && quality > 10) {
|
|
||||||
const compressed = await sharp(buffer).jpeg({ quality, mozjpeg: true }).toBuffer();
|
|
||||||
buffer = Buffer.from(compressed);
|
|
||||||
quality -= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍然超过限制,进一步调整尺寸
|
|
||||||
if (buffer.length > MAX_SIZE) {
|
|
||||||
const metadata = await sharp(buffer).metadata();
|
|
||||||
const scale = Math.sqrt(MAX_SIZE / buffer.length);
|
|
||||||
|
|
||||||
const resized = await sharp(buffer)
|
|
||||||
.resize({
|
|
||||||
width: Math.floor((metadata.width || 1920) * scale),
|
|
||||||
height: Math.floor((metadata.height || 1080) * scale),
|
|
||||||
fit: "inside",
|
|
||||||
})
|
|
||||||
.jpeg({ quality: 80, mozjpeg: true })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
buffer = Buffer.from(resized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 FormData
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("file", buffer, {
|
|
||||||
filename: "image.jpg",
|
|
||||||
contentType: "image/jpeg",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 上传图片
|
|
||||||
const uploadRes = await axios.post(`https://www.runninghub.cn/openapi/v2/media/upload/binary`, formData, {
|
|
||||||
headers: { Authorization: `Bearer ${apiKey}` },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (uploadRes.data.code !== 0 || !uploadRes.data.data?.download_url) {
|
|
||||||
throw new Error(`图片上传失败: ${JSON.stringify(uploadRes.data)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadRes.data.data.download_url;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("上传图片时发生错误:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const urlToBase64 = async (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}`;
|
|
||||||
};
|
|
||||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
const pollTask = async (
|
|
||||||
queryFn: () => Promise<{ completed: boolean; imageUrl?: string; error?: string }>,
|
|
||||||
maxAttempts = 500,
|
|
||||||
interval = 2000,
|
|
||||||
): Promise<string> => {
|
|
||||||
for (let i = 0; i < maxAttempts; i++) {
|
|
||||||
await sleep(interval);
|
|
||||||
const { completed, imageUrl, error } = await queryFn();
|
|
||||||
if (error) throw new Error(error);
|
|
||||||
if (completed && imageUrl) return imageUrl;
|
|
||||||
}
|
|
||||||
throw new Error(`任务轮询超时,已尝试 ${maxAttempts} 次`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const generators = {
|
|
||||||
volcengine: async (config: ImageConfig, apiKey: string, baseURL: string, model: string) => {
|
|
||||||
if (config.size == "1K") config.size = "2K";
|
|
||||||
apiKey = apiKey.replace("Bearer ", "");
|
|
||||||
const body: Record<string, any> = {
|
|
||||||
model,
|
|
||||||
prompt: config.prompt,
|
|
||||||
size: config.size,
|
|
||||||
response_format: "url",
|
|
||||||
sequential_image_generation: "disabled",
|
|
||||||
stream: false,
|
|
||||||
watermark: false,
|
|
||||||
};
|
|
||||||
// 图生图:存在图片时添加 image 字段
|
|
||||||
if (config.imageBase64) {
|
|
||||||
body.image = config.imageBase64;
|
|
||||||
}
|
|
||||||
const res = await axios.post(`https://ark.cn-beijing.volces.com/api/v3/images/generations`, body, {
|
|
||||||
headers: { Authorization: `Bearer ${apiKey}` },
|
|
||||||
});
|
|
||||||
return res.data.data[0].url;
|
|
||||||
},
|
|
||||||
|
|
||||||
gemini: async (config: ImageConfig, apiKey: string, baseURL: string, model: string) => {
|
|
||||||
apiKey = apiKey.replace("Bearer ", "");
|
|
||||||
const messages = [
|
|
||||||
...(config.systemPrompt ? [{ role: "system", content: config.systemPrompt }] : []),
|
|
||||||
{ role: "user", content: config.prompt },
|
|
||||||
...config.imageBase64.map((img) => ({ role: "user", content: { image: img } })),
|
|
||||||
];
|
|
||||||
const res = await axios.post(
|
|
||||||
`${baseURL}/chat/completions`,
|
|
||||||
{ model, stream: false, messages, extra_body: { google: { image_config: { aspect_ratio: config.aspectRatio, image_size: config.size } } } },
|
|
||||||
{ headers: { Authorization: "Bearer " + apiKey } },
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.data.choices[0].message.content;
|
|
||||||
},
|
|
||||||
|
|
||||||
runninghub: async (config: ImageConfig, apiKey: string, baseURL: string) => {
|
|
||||||
apiKey = apiKey.replace("Bearer ", "");
|
|
||||||
const imageUrls = await Promise.all(config.imageBase64.map((base64Image) => uploadBase64ToRunninghub(base64Image, apiKey, baseURL)));
|
|
||||||
|
|
||||||
const endpoint = config.imageBase64.length === 0 ? "/openapi/v2/rhart-image-n-pro/text-to-image" : "/openapi/v2/rhart-image-n-pro/edit";
|
|
||||||
const taskRes = await axios.post(
|
|
||||||
`https://www.runninghub.cn${endpoint}`,
|
|
||||||
{ prompt: config.prompt, resolution: config.size, aspectRatio: config.aspectRatio, ...(imageUrls.length > 0 && { imageUrls }) },
|
|
||||||
{ headers: { Authorization: "Bearer " + apiKey } },
|
|
||||||
);
|
|
||||||
const taskId = taskRes.data.taskId;
|
|
||||||
if (!taskId) throw new Error(`任务创建失败,${JSON.stringify(taskRes.data)}`);
|
|
||||||
|
|
||||||
return pollTask(async () => {
|
|
||||||
const res = await axios.post(`https://www.runninghub.cn/task/openapi/outputs`, { taskId, apiKey: apiKey });
|
|
||||||
const { code, msg, data } = res.data;
|
|
||||||
if (code === 0 && msg === "success") return { completed: true, imageUrl: data?.[0]?.fileUrl };
|
|
||||||
if (code === 804 || code === 813) return { completed: false };
|
|
||||||
if (code === 805) return { completed: false, error: `任务失败: ${data?.[0]?.failedReason?.exception_message || "未知原因"}` };
|
|
||||||
return { completed: false, error: `未知状态: code=${code}, msg=${msg}` };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
apimart: async (config: ImageConfig, apiKey: string, baseURL: string, model: string) => {
|
|
||||||
apiKey = apiKey.replace("Bearer ", "");
|
|
||||||
const taskRes = await axios.post(
|
|
||||||
`https://api.apimart.ai/v1/images/generations`,
|
|
||||||
{ model: "gemini-3-pro-image-preview", prompt: config.prompt, size: config.aspectRatio, n: 1, resolution: config.size },
|
|
||||||
{ headers: { Authorization: apiKey } },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (taskRes.data.code !== 200 || !taskRes.data.data?.[0]?.task_id) throw new Error("任务创建失败: " + JSON.stringify(taskRes.data));
|
|
||||||
|
|
||||||
const taskId = taskRes.data.data[0].task_id;
|
|
||||||
return pollTask(async () => {
|
|
||||||
const res = await axios.get(`https://api.apimart.ai/v1/tasks/${taskId}`, { headers: { Authorization: apiKey }, params: { language: "en" } });
|
|
||||||
if (res.data.code !== 200) return { completed: false, error: `查询失败: ${JSON.stringify(res.data)}` };
|
|
||||||
const { status, result } = res.data.data;
|
|
||||||
if (status === "completed") return { completed: true, imageUrl: result?.images?.[0]?.url?.[0] };
|
|
||||||
if (status === "failed" || status === "cancelled") return { completed: false, error: `任务${status}` };
|
|
||||||
return { completed: false };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export default async (config: ImageConfig, replaceConfig?: ImageModelConfig) => {
|
|
||||||
let { model, apiKey, baseURL, manufacturer } = await u.getConfig("image");
|
|
||||||
if (replaceConfig) {
|
|
||||||
model = replaceConfig.model || model;
|
|
||||||
apiKey = replaceConfig.apiKey || apiKey;
|
|
||||||
baseURL = replaceConfig.baseURL || baseURL;
|
|
||||||
manufacturer = replaceConfig.manufacturer || manufacturer;
|
|
||||||
}
|
|
||||||
const generator = generators[manufacturer as keyof typeof generators];
|
|
||||||
if (!generator) throw new Error(`不支持的厂商: ${manufacturer}`);
|
|
||||||
|
|
||||||
let imageUrl = await generator(config, apiKey ?? "", baseURL ?? "", model ?? "");
|
|
||||||
if (!config.resType) config.resType = "b64";
|
|
||||||
if (config.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
|
|
||||||
return imageUrl;
|
|
||||||
};
|
|
||||||
@ -5,8 +5,9 @@ import axios from "axios";
|
|||||||
|
|
||||||
import volcengine from "./owned/volcengine";
|
import volcengine from "./owned/volcengine";
|
||||||
import kling from "./owned/kling";
|
import kling from "./owned/kling";
|
||||||
|
import gemini from "./owned/gemini";
|
||||||
|
import vidu from "./owned/vidu";
|
||||||
|
import runninghub from "./owned/runninghub";
|
||||||
interface AIConfig {
|
interface AIConfig {
|
||||||
model?: string;
|
model?: string;
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
@ -21,11 +22,11 @@ const urlToBase64 = async (url: string): Promise<string> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const modelInstance = {
|
const modelInstance = {
|
||||||
gemini: null,
|
gemini: gemini,
|
||||||
volcengine: volcengine,
|
volcengine: volcengine,
|
||||||
kling: kling,
|
kling: kling,
|
||||||
vidu: null,
|
vidu: vidu,
|
||||||
runninghub: null,
|
runninghub: runninghub,
|
||||||
apimart: null,
|
apimart: null,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -38,7 +39,8 @@ export default async (input: ImageConfig, config?: AIConfig) => {
|
|||||||
if (!owned) throw new Error("不支持的模型");
|
if (!owned) throw new Error("不支持的模型");
|
||||||
|
|
||||||
let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL });
|
let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL });
|
||||||
|
console.log("%c Line:41 🍅 imageUrl", "background:#ed9ec7", imageUrl);
|
||||||
if (!input.resType) input.resType = "b64";
|
if (!input.resType) input.resType = "b64";
|
||||||
if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
|
if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
|
||||||
return input;
|
return imageUrl;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -33,18 +33,6 @@ const modelList: Owned[] = [
|
|||||||
grid: true,
|
grid: true,
|
||||||
type: "ti2i",
|
type: "ti2i",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
manufacturer: "gemini",
|
|
||||||
model: "gemini-2.5-flash-image-preview",
|
|
||||||
grid: true,
|
|
||||||
type: "ti2i",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
manufacturer: "gemini",
|
|
||||||
model: "gemini-2.5-flash-image-preview-all",
|
|
||||||
grid: true,
|
|
||||||
type: "ti2i",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
manufacturer: "gemini",
|
manufacturer: "gemini",
|
||||||
model: "gemini-3-pro-image-preview",
|
model: "gemini-3-pro-image-preview",
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import "../type";
|
import "../type";
|
||||||
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
||||||
import { generateImage } from "ai";
|
import { generateText } from "ai";
|
||||||
|
|
||||||
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||||
|
console.log("%c Line:6 🌰 config", "background:#ffdd4d", config);
|
||||||
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");
|
||||||
if (!input.prompt) throw new Error("缺少提示词");
|
if (!input.prompt) throw new Error("缺少提示词");
|
||||||
@ -22,13 +23,30 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
|||||||
"4K": "4096x4096",
|
"4K": "4096x4096",
|
||||||
};
|
};
|
||||||
|
|
||||||
const { image } = await generateImage({
|
const result = await generateText({
|
||||||
model: google.image(config.model),
|
model: google.languageModel(config.model),
|
||||||
prompt: fullPrompt,
|
prompt: fullPrompt + `请直接输出图片`,
|
||||||
aspectRatio: input.aspectRatio as "1:1" | "3:4" | "4:3" | "9:16" | "16:9",
|
providerOptions: {
|
||||||
size: sizeMap[input.size] ?? "1024x1024",
|
google: {
|
||||||
|
imageConfig: {
|
||||||
|
...(config.model == "gemini-2.5-flash-image"
|
||||||
|
? { aspectRatio: input.aspectRatio }
|
||||||
|
: { aspectRatio: input.aspectRatio, imageSize: input.size }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(JSON.stringify(result.request, null, 2));
|
||||||
|
console.log(JSON.stringify(result.response.body, null, 2));
|
||||||
|
if (!result.files.length) {
|
||||||
|
console.error(JSON.stringify(result.response, null, 2));
|
||||||
|
throw new Error("图片生成失败");
|
||||||
|
}
|
||||||
|
let imageBase64;
|
||||||
|
for (const item of result.files) {
|
||||||
|
imageBase64 = `data:${item.mediaType};base64,${item.base64}`;
|
||||||
|
}
|
||||||
// 返回生成的图片 base64
|
// 返回生成的图片 base64
|
||||||
return image.base64;
|
return imageBase64!;
|
||||||
};
|
};
|
||||||
|
|||||||
93
src/utils/ai/image/owned/runninghub.ts
Normal file
93
src/utils/ai/image/owned/runninghub.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import u from "@/utils";
|
||||||
|
import FormData from "form-data";
|
||||||
|
import axiosRetry from "axios-retry";
|
||||||
|
import { OpenAIChatModel, type OpenAIChatModelOptions } from "@aigne/openai";
|
||||||
|
import sharp from "sharp";
|
||||||
|
import { pollTask } from "@/utils/ai/utils";
|
||||||
|
|
||||||
|
axiosRetry(axios, { retries: 3, retryDelay: () => 200 });
|
||||||
|
// 上传 base64 图片到 runninghub
|
||||||
|
const uploadBase64ToRunninghub = async (base64Image: string, apiKey: string, baseURL: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
apiKey = apiKey.replace("Bearer ", "");
|
||||||
|
// 移除 base64 前缀
|
||||||
|
const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, "");
|
||||||
|
let buffer = Buffer.from(base64Data, "base64");
|
||||||
|
|
||||||
|
// 压缩图片到 5MB 以下
|
||||||
|
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
|
if (buffer.length > MAX_SIZE) {
|
||||||
|
let quality = 90;
|
||||||
|
|
||||||
|
while (buffer.length > MAX_SIZE && quality > 10) {
|
||||||
|
const compressed = await sharp(buffer).jpeg({ quality, mozjpeg: true }).toBuffer();
|
||||||
|
buffer = Buffer.from(compressed);
|
||||||
|
quality -= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果仍然超过限制,进一步调整尺寸
|
||||||
|
if (buffer.length > MAX_SIZE) {
|
||||||
|
const metadata = await sharp(buffer).metadata();
|
||||||
|
const scale = Math.sqrt(MAX_SIZE / buffer.length);
|
||||||
|
|
||||||
|
const resized = await sharp(buffer)
|
||||||
|
.resize({
|
||||||
|
width: Math.floor((metadata.width || 1920) * scale),
|
||||||
|
height: Math.floor((metadata.height || 1080) * scale),
|
||||||
|
fit: "inside",
|
||||||
|
})
|
||||||
|
.jpeg({ quality: 80, mozjpeg: true })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
buffer = Buffer.from(resized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 FormData
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", buffer, {
|
||||||
|
filename: "image.jpg",
|
||||||
|
contentType: "image/jpeg",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上传图片
|
||||||
|
const uploadRes = await axios.post(`https://www.runninghub.cn/openapi/v2/media/upload/binary`, formData, {
|
||||||
|
headers: { Authorization: `Bearer ${apiKey}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uploadRes.data.code !== 0 || !uploadRes.data.data?.download_url) {
|
||||||
|
throw new Error(`图片上传失败: ${JSON.stringify(uploadRes.data)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploadRes.data.data.download_url;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("上传图片时发生错误:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||||
|
if (!config.apiKey) throw new Error("缺少API Key");
|
||||||
|
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||||
|
const baseURL = "https://www.runninghub.cn";
|
||||||
|
const imageUrls = await Promise.all(input.imageBase64.map((base64Image) => uploadBase64ToRunninghub(base64Image, apiKey, baseURL)));
|
||||||
|
|
||||||
|
const endpoint = input.imageBase64.length === 0 ? "/openapi/v2/rhart-image-n-pro/text-to-image" : "/openapi/v2/rhart-image-n-pro/edit";
|
||||||
|
const taskRes = await axios.post(
|
||||||
|
`https://www.runninghub.cn${endpoint}`,
|
||||||
|
{ prompt: input.prompt, resolution: input.size, aspectRatio: input.aspectRatio, ...(imageUrls.length > 0 && { imageUrls }) },
|
||||||
|
{ headers: { Authorization: "Bearer " + apiKey } },
|
||||||
|
);
|
||||||
|
const taskId = taskRes.data.taskId;
|
||||||
|
if (!taskId) throw new Error(`任务创建失败,${JSON.stringify(taskRes.data)}`);
|
||||||
|
|
||||||
|
return pollTask(async () => {
|
||||||
|
const res = await axios.post(`https://www.runninghub.cn/task/openapi/outputs`, { taskId, apiKey: apiKey });
|
||||||
|
const { code, msg, data } = res.data;
|
||||||
|
if (code === 0 && msg === "success") return { completed: true, imageUrl: data?.[0]?.fileUrl };
|
||||||
|
if (code === 804 || code === 813) return { completed: false };
|
||||||
|
if (code === 805) return { completed: false, error: `任务失败: ${data?.[0]?.failedReason?.exception_message || "未知原因"}` };
|
||||||
|
return { completed: false, error: `未知状态: code=${code}, msg=${msg}` };
|
||||||
|
});
|
||||||
|
};
|
||||||
94
src/utils/ai/image/owned/vidu.ts
Normal file
94
src/utils/ai/image/owned/vidu.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import "../type";
|
||||||
|
import axios from "axios";
|
||||||
|
import u from "@/utils";
|
||||||
|
import { pollTask } from "@/utils/ai/utils";
|
||||||
|
function getApiUrl(apiUrl: string) {
|
||||||
|
if (apiUrl.includes("|")) {
|
||||||
|
const parts = apiUrl.split("|");
|
||||||
|
if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) {
|
||||||
|
throw new Error("url 格式错误,请使用 url1|url2 格式");
|
||||||
|
}
|
||||||
|
return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() };
|
||||||
|
}
|
||||||
|
throw new Error("请填写正确的url");
|
||||||
|
}
|
||||||
|
function template(replaceObj: Record<string, any>, url: string) {
|
||||||
|
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||||
|
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||||
|
if (!config.model) throw new Error("缺少Model名称");
|
||||||
|
if (!config.apiKey) throw new Error("缺少API Key");
|
||||||
|
|
||||||
|
const apiKey = "Token " + config.apiKey.replace(/Bearer\s+/g, "").trim();
|
||||||
|
const viduq2Ratio = ["16:9", "9:16", "1:1", "3:4", "4:3", "21:9", "2:3", "3:2"];
|
||||||
|
const viduq1Ratio = ["16:9", "9:16", "1:1", "3:4", "4:3"];
|
||||||
|
let images: string[] = [];
|
||||||
|
const baseImages = input.imageBase64;
|
||||||
|
// 如果图片总数大于7,合并第7张及以后的图片
|
||||||
|
if (baseImages) {
|
||||||
|
if (baseImages.length > 7) {
|
||||||
|
// 前6张原图
|
||||||
|
images = baseImages.slice(0, 6);
|
||||||
|
// 第7张及以后的图片进行合并
|
||||||
|
const mergeImageList = baseImages.slice(6); // 注意此处使用slice,不会改变原数组
|
||||||
|
const mergedImage = await u.imageTools.mergeImages(mergeImageList, "10mb");
|
||||||
|
images.push(mergedImage);
|
||||||
|
} else {
|
||||||
|
// 不足7张,直接全部加入
|
||||||
|
images = baseImages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = "1080p";
|
||||||
|
if (config.model == "viduq1") {
|
||||||
|
if (!images.length) throw new Error(`viduq1 进行图片生成必须传入一张图片`);
|
||||||
|
if (!viduq1Ratio.includes(input.aspectRatio)) throw new Error("不支持的图片比例:" + input.aspectRatio);
|
||||||
|
size = "1080p";
|
||||||
|
} else {
|
||||||
|
if (input.size == "1K") size = "1080p";
|
||||||
|
else size = input.size;
|
||||||
|
if (!viduq2Ratio.includes(input.aspectRatio)) throw new Error("不支持的图片比例:" + input.aspectRatio);
|
||||||
|
}
|
||||||
|
console.log("%c Line:23 🍔 size", "background:#ffdd4d", size);
|
||||||
|
|
||||||
|
const body: Record<string, any> = {
|
||||||
|
model: config.model,
|
||||||
|
prompt: input.prompt,
|
||||||
|
aspect_ratio: input.aspectRatio,
|
||||||
|
resolution: size,
|
||||||
|
...(images.length && { images: images }),
|
||||||
|
};
|
||||||
|
console.log("%c Line:27 🍷 body", "background:#6ec1c2", body);
|
||||||
|
const urlObj = getApiUrl(config.baseURL!);
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(urlObj.requestUrl, body, { headers: { Authorization: apiKey } });
|
||||||
|
console.log("%c Line:35 🥕 data", "background:#93c0a4", data);
|
||||||
|
const queryUrl = template({ id: data.task_id }, urlObj.queryUrl);
|
||||||
|
console.log("%c Line:53 🍋 queryUrl", "background:#465975", queryUrl);
|
||||||
|
return await pollTask(async () => {
|
||||||
|
const { data: queryData } = await axios.get(queryUrl, { headers: { Authorization: apiKey } });
|
||||||
|
console.log("%c Line:42 🍐 queryData", "background:#4fff4B", queryData);
|
||||||
|
|
||||||
|
if (queryData.state !== 0) {
|
||||||
|
return { completed: false, error: queryData.message || "查询任务失败" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { state, err_code, creations } = queryData.data || {};
|
||||||
|
|
||||||
|
if (state === "failed") {
|
||||||
|
return { completed: false, error: err_code || "图片生成失败" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === "succeed") {
|
||||||
|
return { completed: true, imageUrl: creations?.[0]?.url };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { completed: false };
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
const msg = u.error(error).message || "vidu 图片生成失败";
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user