2026-02-05 10:16:06 +08:00

94 lines
3.7 KiB
TypeScript

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}` };
});
};