jimeng-free-api-all/src/api/routes/images.ts

279 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from "fs";
import _ from "lodash";
import Request from "@/lib/request/Request.ts";
import { generateImages, generateImageComposition } from "@/api/controllers/images.ts";
import { tokenSplit } from "@/api/controllers/core.ts";
import util from "@/lib/util.ts";
export default {
prefix: "/v1/images",
post: {
"/generations": async (request: Request) => {
// 检查是否使用了不支持的参数
const unsupportedParams = ['size', 'width', 'height'];
const bodyKeys = Object.keys(request.body);
const foundUnsupported = unsupportedParams.filter(param => bodyKeys.includes(param));
if (foundUnsupported.length > 0) {
throw new Error(`不支持的参数: ${foundUnsupported.join(', ')}。请使用 ratio 和 resolution 参数控制图像尺寸。`);
}
const contentType = request.headers['content-type'] || '';
const isMultiPart = contentType.startsWith('multipart/form-data');
// 根据请求类型进行不同的参数验证
if (isMultiPart) {
request
.validate("body.model", v => _.isUndefined(v) || _.isString(v))
.validate("body.prompt", _.isString)
.validate("body.negative_prompt", v => _.isUndefined(v) || _.isString(v))
.validate("body.ratio", v => _.isUndefined(v) || _.isString(v))
.validate("body.resolution", v => _.isUndefined(v) || _.isString(v))
.validate("body.intelligent_ratio", v => _.isUndefined(v) || (typeof v === 'string' && (v === 'true' || v === 'false')) || _.isBoolean(v))
.validate("body.sample_strength", v => _.isUndefined(v) || (typeof v === 'string' && !isNaN(parseFloat(v))) || _.isFinite(v))
.validate("body.response_format", v => _.isUndefined(v) || _.isString(v))
.validate("headers.authorization", _.isString);
} else {
request
.validate("body.model", v => _.isUndefined(v) || _.isString(v))
.validate("body.prompt", _.isString)
.validate("body.images", v => _.isUndefined(v) || _.isArray(v))
.validate("body.negative_prompt", v => _.isUndefined(v) || _.isString(v))
.validate("body.ratio", v => _.isUndefined(v) || _.isString(v))
.validate("body.resolution", v => _.isUndefined(v) || _.isString(v))
.validate("body.intelligent_ratio", v => _.isUndefined(v) || _.isBoolean(v))
.validate("body.sample_strength", v => _.isUndefined(v) || _.isFinite(v))
.validate("body.response_format", v => _.isUndefined(v) || _.isString(v))
.validate("headers.authorization", _.isString);
}
// 处理图片数据(如果提供)
let images: (string | Buffer)[] | null = null;
if (isMultiPart) {
const files = (request.files as any)?.images;
if (files) {
const imageFiles = Array.isArray(files) ? files : [files];
if (imageFiles.length > 0) {
if (imageFiles.length > 10) {
throw new Error("最多支持10张输入图片");
}
images = imageFiles.map((file: any) => fs.readFileSync(file.filepath));
}
}
} else {
const bodyImages = request.body.images;
if (bodyImages && Array.isArray(bodyImages) && bodyImages.length > 0) {
if (bodyImages.length > 10) {
throw new Error("最多支持10张输入图片");
}
bodyImages.forEach((image: any, index: number) => {
if (!_.isString(image) && !_.isObject(image)) {
throw new Error(`图片 ${index + 1} 格式不正确应为URL字符串或包含url字段的对象`);
}
if (_.isObject(image) && !(image as any).url) {
throw new Error(`图片 ${index + 1} 缺少url字段`);
}
});
images = bodyImages.map((image: any) => _.isString(image) ? image : (image as any).url);
}
}
// refresh_token切分
const tokens = tokenSplit(request.headers.authorization);
// 随机挑选一个refresh_token
const token = _.sample(tokens);
const {
model,
prompt,
negative_prompt: negativePrompt,
ratio,
resolution,
intelligent_ratio: intelligentRatio,
sample_strength: sampleStrength,
response_format,
} = request.body;
// 如果是 multipart/form-data需要将字符串转换为数字和布尔值
const finalSampleStrength = isMultiPart && typeof sampleStrength === 'string'
? parseFloat(sampleStrength)
: sampleStrength;
const finalIntelligentRatio = isMultiPart && typeof intelligentRatio === 'string'
? intelligentRatio === 'true'
: intelligentRatio;
const responseFormat = _.defaultTo(response_format, "url");
// 根据是否有图片数据决定调用文生图还是图生图
let imageUrls: string[];
let resultData: any = {
created: util.unixTimestamp(),
};
if (images && images.length > 0) {
// 图生图模式
imageUrls = await generateImageComposition(model, prompt, images, {
ratio,
resolution,
sampleStrength: finalSampleStrength,
negativePrompt,
intelligentRatio: finalIntelligentRatio,
}, token);
resultData.input_images = images.length;
resultData.composition_type = "multi_image_synthesis";
} else {
// 文生图模式
imageUrls = await generateImages(model, prompt, {
ratio,
resolution,
sampleStrength: finalSampleStrength,
negativePrompt,
intelligentRatio: finalIntelligentRatio,
}, token);
}
let data = [];
if (responseFormat == "b64_json") {
data = (
await Promise.all(imageUrls.map((url) => util.fetchFileBASE64(url)))
).map((b64) => ({ b64_json: b64 }));
} else {
data = imageUrls.map((url) => ({
url,
}));
}
resultData.data = data;
return resultData;
},
// 图片合成路由(图生图)
"/compositions": async (request: Request) => {
// 检查是否使用了不支持的参数
const unsupportedParams = ['size', 'width', 'height'];
const bodyKeys = Object.keys(request.body);
const foundUnsupported = unsupportedParams.filter(param => bodyKeys.includes(param));
if (foundUnsupported.length > 0) {
throw new Error(`不支持的参数: ${foundUnsupported.join(', ')}。请使用 ratio 和 resolution 参数控制图像尺寸。`);
}
const contentType = request.headers['content-type'] || '';
const isMultiPart = contentType.startsWith('multipart/form-data');
if (isMultiPart) {
request
.validate("body.model", v => _.isUndefined(v) || _.isString(v))
.validate("body.prompt", _.isString)
.validate("body.negative_prompt", v => _.isUndefined(v) || _.isString(v))
.validate("body.ratio", v => _.isUndefined(v) || _.isString(v))
.validate("body.resolution", v => _.isUndefined(v) || _.isString(v))
.validate("body.intelligent_ratio", v => _.isUndefined(v) || (typeof v === 'string' && (v === 'true' || v === 'false')) || _.isBoolean(v))
.validate("body.sample_strength", v => _.isUndefined(v) || (typeof v === 'string' && !isNaN(parseFloat(v))) || _.isFinite(v))
.validate("body.response_format", v => _.isUndefined(v) || _.isString(v))
.validate("headers.authorization", _.isString);
} else {
request
.validate("body.model", v => _.isUndefined(v) || _.isString(v))
.validate("body.prompt", _.isString)
.validate("body.images", _.isArray)
.validate("body.negative_prompt", v => _.isUndefined(v) || _.isString(v))
.validate("body.ratio", v => _.isUndefined(v) || _.isString(v))
.validate("body.resolution", v => _.isUndefined(v) || _.isString(v))
.validate("body.intelligent_ratio", v => _.isUndefined(v) || _.isBoolean(v))
.validate("body.sample_strength", v => _.isUndefined(v) || _.isFinite(v))
.validate("body.response_format", v => _.isUndefined(v) || _.isString(v))
.validate("headers.authorization", _.isString);
}
let images: (string | Buffer)[] = [];
if (isMultiPart) {
const files = (request.files as any)?.images;
if (!files) {
throw new Error("在form-data中缺少 'images' 字段");
}
const imageFiles = Array.isArray(files) ? files : [files];
if (imageFiles.length === 0) {
throw new Error("至少需要提供1张输入图片");
}
if (imageFiles.length > 10) {
throw new Error("最多支持10张输入图片");
}
images = imageFiles.map((file: any) => fs.readFileSync(file.filepath));
} else {
const bodyImages = request.body.images;
if (!bodyImages || bodyImages.length === 0) {
throw new Error("至少需要提供1张输入图片");
}
if (bodyImages.length > 10) {
throw new Error("最多支持10张输入图片");
}
bodyImages.forEach((image: any, index: number) => {
if (!_.isString(image) && !_.isObject(image)) {
throw new Error(`图片 ${index + 1} 格式不正确应为URL字符串或包含url字段的对象`);
}
if (_.isObject(image) && !(image as any).url) {
throw new Error(`图片 ${index + 1} 缺少url字段`);
}
});
images = bodyImages.map((image: any) => _.isString(image) ? image : (image as any).url);
}
// refresh_token切分
const tokens = tokenSplit(request.headers.authorization);
// 随机挑选一个refresh_token
const token = _.sample(tokens);
const {
model,
prompt,
negative_prompt: negativePrompt,
ratio,
resolution,
intelligent_ratio: intelligentRatio,
sample_strength: sampleStrength,
response_format,
} = request.body;
// 如果是 multipart/form-data需要将字符串转换为数字和布尔值
const finalSampleStrength = isMultiPart && typeof sampleStrength === 'string'
? parseFloat(sampleStrength)
: sampleStrength;
const finalIntelligentRatio = isMultiPart && typeof intelligentRatio === 'string'
? intelligentRatio === 'true'
: intelligentRatio;
const responseFormat = _.defaultTo(response_format, "url");
const resultUrls = await generateImageComposition(model, prompt, images, {
ratio,
resolution,
sampleStrength: finalSampleStrength,
negativePrompt,
intelligentRatio: finalIntelligentRatio,
}, token);
let data = [];
if (responseFormat == "b64_json") {
data = (
await Promise.all(resultUrls.map((url) => util.fetchFileBASE64(url)))
).map((b64) => ({ b64_json: b64 }));
} else {
data = resultUrls.map((url) => ({
url,
}));
}
return {
created: util.unixTimestamp(),
data,
input_images: images.length,
composition_type: "multi_image_synthesis",
};
},
},
};