支持兼容dalle3绘图调用接口

This commit is contained in:
Vinlic 2024-03-27 10:36:36 +08:00
parent 96a80f5f32
commit ea49bd3023
5 changed files with 957 additions and 548 deletions

View File

@ -5,5 +5,6 @@ export default {
API_TOKEN_EXPIRES: [-2002, 'Token已失效'], API_TOKEN_EXPIRES: [-2002, 'Token已失效'],
API_FILE_URL_INVALID: [-2003, '远程文件URL非法'], API_FILE_URL_INVALID: [-2003, '远程文件URL非法'],
API_FILE_EXECEEDS_SIZE: [-2004, '远程文件超出大小'], API_FILE_EXECEEDS_SIZE: [-2004, '远程文件超出大小'],
API_CHAT_STREAM_PUSHING: [-2005, '已有对话流正在输出'] API_CHAT_STREAM_PUSHING: [-2005, '已有对话流正在输出'],
API_CONTENT_FILTERED: [-2006, '内容由于合规问题已被阻止生成']
} }

File diff suppressed because it is too large Load Diff

39
src/api/routes/images.ts Normal file
View File

@ -0,0 +1,39 @@
import _ from "lodash";
import Request from "@/lib/request/Request.ts";
import chat from "@/api/controllers/chat.ts";
import util from "@/lib/util.ts";
export default {
prefix: "/v1/images",
post: {
"/generations": async (request: Request) => {
request
.validate("body.prompt", _.isString)
.validate("headers.authorization", _.isString);
// refresh_token切分
const tokens = chat.tokenSplit(request.headers.authorization);
// 随机挑选一个refresh_token
const token = _.sample(tokens);
const prompt = request.body.prompt;
const responseFormat = _.defaultTo(request.body.response_format, "url");
const model = request.body.model;
const imageUrls = await chat.generateImages(model, prompt, 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,
}));
}
return {
created: util.unixTimestamp(),
data,
};
},
},
};

View File

@ -1,7 +1,9 @@
import chat from "./chat.ts"; import chat from "./chat.ts";
import images from "./images.ts";
import ping from "./ping.ts"; import ping from "./ping.ts";
export default [ export default [
chat, chat,
images,
ping ping
]; ];

View File

@ -1,258 +1,307 @@
import os from 'os'; import os from "os";
import path from 'path'; import path from "path";
import crypto from 'crypto'; import crypto from "crypto";
import { Readable, Writable } from 'stream'; import { Readable, Writable } from "stream";
import 'colors'; import "colors";
import mime from 'mime'; import mime from "mime";
import fs from 'fs-extra'; import axios from "axios";
import { v1 as uuid } from 'uuid'; import fs from "fs-extra";
import { format as dateFormat } from 'date-fns'; import { v1 as uuid } from "uuid";
import CRC32 from 'crc-32'; import { format as dateFormat } from "date-fns";
import randomstring from 'randomstring'; import CRC32 from "crc-32";
import _ from 'lodash'; import randomstring from "randomstring";
import { CronJob } from 'cron'; import _ from "lodash";
import { CronJob } from "cron";
import HTTP_STATUS_CODE from './http-status-codes.ts'; import HTTP_STATUS_CODE from "./http-status-codes.ts";
const autoIdMap = new Map(); const autoIdMap = new Map();
const util = { const util = {
is2DArrays(value: any) {
return (
_.isArray(value) &&
(!value[0] || (_.isArray(value[0]) && _.isArray(value[value.length - 1])))
);
},
is2DArrays(value: any) { uuid: (separator = true) => (separator ? uuid() : uuid().replace(/\-/g, "")),
return _.isArray(value) && (!value[0] || (_.isArray(value[0]) && _.isArray(value[value.length - 1])));
},
uuid: (separator = true) => separator ? uuid() : uuid().replace(/\-/g, ""), autoId: (prefix = "") => {
let index = autoIdMap.get(prefix);
if (index > 999999) index = 0; //超过最大数字则重置为0
autoIdMap.set(prefix, (index || 0) + 1);
return `${prefix}${index || 1}`;
},
autoId: (prefix = '') => { ignoreJSONParse(value: string) {
let index = autoIdMap.get(prefix); const result = _.attempt(() => JSON.parse(value));
if(index > 999999) index = 0; //超过最大数字则重置为0 if (_.isError(result)) return null;
autoIdMap.set(prefix, (index || 0) + 1); return result;
return `${prefix}${index || 1}`; },
},
ignoreJSONParse(value: string) { generateRandomString(options: any): string {
const result = _.attempt(() => JSON.parse(value)); return randomstring.generate(options);
if(_.isError(result)) },
return null;
return result;
},
generateRandomString(options: any): string { getResponseContentType(value: any): string | null {
return randomstring.generate(options); return value.headers
}, ? value.headers["content-type"] || value.headers["Content-Type"]
: null;
},
getResponseContentType(value: any): string | null { mimeToExtension(value: string) {
return value.headers ? (value.headers["content-type"] || value.headers["Content-Type"]) : null; let extension = mime.getExtension(value);
}, if (extension == "mpga") return "mp3";
return extension;
},
mimeToExtension(value: string) { extractURLExtension(value: string) {
let extension = mime.getExtension(value); const extname = path.extname(new URL(value).pathname);
if(extension == "mpga") return extname.substring(1).toLowerCase();
return "mp3"; },
return extension;
},
extractURLExtension(value: string) { createCronJob(cronPatterns: any, callback?: Function) {
const extname = path.extname(new URL(value).pathname); if (!_.isFunction(callback))
return extname.substring(1).toLowerCase(); throw new Error("callback must be an Function");
}, return new CronJob(
cronPatterns,
() => callback(),
null,
false,
"Asia/Shanghai"
);
},
createCronJob(cronPatterns: any, callback?: Function) { getDateString(format = "yyyy-MM-dd", date = new Date()) {
if(!_.isFunction(callback)) throw new Error("callback must be an Function"); return dateFormat(date, format);
return new CronJob(cronPatterns, () => callback(), null, false, "Asia/Shanghai"); },
},
getDateString(format = "yyyy-MM-dd", date = new Date()) { getIPAddressesByIPv4(): string[] {
return dateFormat(date, format); const interfaces = os.networkInterfaces();
}, const addresses = [];
for (let name in interfaces) {
const networks = interfaces[name];
const results = networks.filter(
(network) =>
network.family === "IPv4" &&
network.address !== "127.0.0.1" &&
!network.internal
);
if (results[0] && results[0].address) addresses.push(results[0].address);
}
return addresses;
},
getIPAddressesByIPv4(): string[] { getMACAddressesByIPv4(): string[] {
const interfaces = os.networkInterfaces(); const interfaces = os.networkInterfaces();
const addresses = []; const addresses = [];
for (let name in interfaces) { for (let name in interfaces) {
const networks = interfaces[name]; const networks = interfaces[name];
const results = networks.filter(network => network.family === "IPv4" && network.address !== "127.0.0.1" && !network.internal); const results = networks.filter(
if (results[0] && results[0].address) (network) =>
addresses.push(results[0].address); network.family === "IPv4" &&
} network.address !== "127.0.0.1" &&
return addresses; !network.internal
}, );
if (results[0] && results[0].mac) addresses.push(results[0].mac);
}
return addresses;
},
getMACAddressesByIPv4(): string[] { generateSSEData(event?: string, data?: string, retry?: number) {
const interfaces = os.networkInterfaces(); return `event: ${event || "message"}\ndata: ${(data || "")
const addresses = []; .replace(/\n/g, "\\n")
for (let name in interfaces) { .replace(/\s/g, "\\s")}\nretry: ${retry || 3000}\n\n`;
const networks = interfaces[name]; },
const results = networks.filter(network => network.family === "IPv4" && network.address !== "127.0.0.1" && !network.internal);
if (results[0] && results[0].mac)
addresses.push(results[0].mac);
}
return addresses;
},
generateSSEData(event?: string, data?: string, retry?: number) { buildDataBASE64(type, ext, buffer) {
return `event: ${event || "message"}\ndata: ${(data || "").replace(/\n/g, "\\n").replace(/\s/g, "\\s")}\nretry: ${retry || 3000}\n\n`; return `data:${type}/${ext.replace("jpg", "jpeg")};base64,${buffer.toString(
}, "base64"
)}`;
},
buildDataBASE64(type, ext, buffer) { isLinux() {
return `data:${type}/${ext.replace("jpg", "jpeg")};base64,${buffer.toString("base64")}`; return os.platform() !== "win32";
}, },
isLinux() { isIPAddress(value) {
return os.platform() !== "win32"; return (
}, _.isString(value) &&
(/^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/.test(
value
) ||
/\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*/.test(
value
))
);
},
isIPAddress(value) { isPort(value) {
return _.isString(value) && (/^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$/.test(value) || /\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*/.test(value)); return _.isNumber(value) && value > 0 && value < 65536;
}, },
isPort(value) { isReadStream(value): boolean {
return _.isNumber(value) && value > 0 && value < 65536; return (
}, value &&
(value instanceof Readable || "readable" in value || value.readable)
);
},
isReadStream(value): boolean { isWriteStream(value): boolean {
return value && (value instanceof Readable || "readable" in value || value.readable); return (
}, value &&
(value instanceof Writable || "writable" in value || value.writable)
);
},
isWriteStream(value): boolean { isHttpStatusCode(value) {
return value && (value instanceof Writable || "writable" in value || value.writable); return _.isNumber(value) && Object.values(HTTP_STATUS_CODE).includes(value);
}, },
isHttpStatusCode(value) { isURL(value) {
return _.isNumber(value) && Object.values(HTTP_STATUS_CODE).includes(value); return !_.isUndefined(value) && /^(http|https)/.test(value);
}, },
isURL(value) { isSrc(value) {
return !_.isUndefined(value) && /^(http|https)/.test(value); return !_.isUndefined(value) && /^\/.+\.[0-9a-zA-Z]+(\?.+)?$/.test(value);
}, },
isSrc(value) { isBASE64(value) {
return !_.isUndefined(value) && /^\/.+\.[0-9a-zA-Z]+(\?.+)?$/.test(value); return !_.isUndefined(value) && /^[a-zA-Z0-9\/\+]+(=?)+$/.test(value);
}, },
isBASE64(value) { isBASE64Data(value) {
return !_.isUndefined(value) && /^[a-zA-Z0-9\/\+]+(=?)+$/.test(value); return /^data:/.test(value);
}, },
isBASE64Data(value) { extractBASE64DataFormat(value): string | null {
return /^data:/.test(value); const match = value.trim().match(/^data:(.+);base64,/);
}, if (!match) return null;
return match[1];
},
extractBASE64DataFormat(value): string | null { removeBASE64DataHeader(value): string {
const match = value.trim().match(/^data:(.+);base64,/); return value.replace(/^data:(.+);base64,/, "");
if(!match) return null; },
return match[1];
},
removeBASE64DataHeader(value): string { isDataString(value): boolean {
return value.replace(/^data:(.+);base64,/, ""); return /^(base64|json):/.test(value);
}, },
isDataString(value): boolean { isStringNumber(value) {
return /^(base64|json):/.test(value); return _.isFinite(Number(value));
}, },
isStringNumber(value) { isUnixTimestamp(value) {
return _.isFinite(Number(value)); return /^[0-9]{10}$/.test(`${value}`);
}, },
isUnixTimestamp(value) { isTimestamp(value) {
return /^[0-9]{10}$/.test(`${value}`); return /^[0-9]{13}$/.test(`${value}`);
}, },
isTimestamp(value) { isEmail(value) {
return /^[0-9]{13}$/.test(`${value}`); return /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(
}, value
);
},
isEmail(value) { isAsyncFunction(value) {
return /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/.test(value); return Object.prototype.toString.call(value) === "[object AsyncFunction]";
}, },
isAsyncFunction(value) { async isAPNG(filePath) {
return Object.prototype.toString.call(value) === "[object AsyncFunction]"; let head;
}, const readStream = fs.createReadStream(filePath, { start: 37, end: 40 });
const readPromise = new Promise((resolve, reject) => {
readStream.once("end", resolve);
readStream.once("error", reject);
});
readStream.once("data", (data) => (head = data));
await readPromise;
return head.compare(Buffer.from([0x61, 0x63, 0x54, 0x4c])) === 0;
},
async isAPNG(filePath) { unixTimestamp() {
let head; return parseInt(`${Date.now() / 1000}`);
const readStream = fs.createReadStream(filePath, { start: 37, end: 40 }); },
const readPromise = new Promise((resolve, reject) => {
readStream.once("end", resolve);
readStream.once("error", reject);
});
readStream.once("data", data => head = data);
await readPromise;
return head.compare(Buffer.from([0x61, 0x63, 0x54, 0x4c])) === 0;
},
unixTimestamp() { timestamp() {
return parseInt(`${Date.now() / 1000}`); return Date.now();
}, },
timestamp() { urlJoin(...values) {
return Date.now(); let url = "";
}, for (let i = 0; i < values.length; i++)
url += `${i > 0 ? "/" : ""}${values[i]
.replace(/^\/*/, "")
.replace(/\/*$/, "")}`;
return url;
},
urlJoin(...values) { millisecondsToHmss(milliseconds) {
let url = ""; if (_.isString(milliseconds)) return milliseconds;
for (let i = 0; i < values.length; i++) milliseconds = parseInt(milliseconds);
url += `${i > 0 ? "/" : ""}${values[i].replace(/^\/*/, "").replace(/\/*$/, "")}`; const sec = Math.floor(milliseconds / 1000);
return url; const hours = Math.floor(sec / 3600);
}, const minutes = Math.floor((sec - hours * 3600) / 60);
const seconds = sec - hours * 3600 - minutes * 60;
const ms = (milliseconds % 60000) - seconds * 1000;
return `${hours > 9 ? hours : "0" + hours}:${
minutes > 9 ? minutes : "0" + minutes
}:${seconds > 9 ? seconds : "0" + seconds}.${ms}`;
},
millisecondsToHmss(milliseconds) { millisecondsToTimeString(milliseconds) {
if (_.isString(milliseconds)) return milliseconds; if (milliseconds < 1000) return `${milliseconds}ms`;
milliseconds = parseInt(milliseconds); if (milliseconds < 60000)
const sec = Math.floor(milliseconds / 1000); return `${parseFloat((milliseconds / 1000).toFixed(2))}s`;
const hours = Math.floor(sec / 3600); return `${Math.floor(milliseconds / 1000 / 60)}m${Math.floor(
const minutes = Math.floor((sec - hours * 3600) / 60); (milliseconds / 1000) % 60
const seconds = sec - hours * 3600 - minutes * 60; )}s`;
const ms = milliseconds % 60000 - seconds * 1000; },
return `${hours > 9 ? hours : "0" + hours}:${minutes > 9 ? minutes : "0" + minutes}:${seconds > 9 ? seconds : "0" + seconds}.${ms}`;
},
millisecondsToTimeString(milliseconds) { rgbToHex(r, g, b): string {
if(milliseconds < 1000) return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
return `${milliseconds}ms`; },
if(milliseconds < 60000)
return `${parseFloat((milliseconds / 1000).toFixed(2))}s`;
return `${Math.floor(milliseconds / 1000 / 60)}m${Math.floor(milliseconds / 1000 % 60)}s`;
},
rgbToHex(r, g, b): string { hexToRgb(hex) {
return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); const value = parseInt(hex.replace(/^#/, ""), 16);
}, return [(value >> 16) & 255, (value >> 8) & 255, value & 255];
},
hexToRgb(hex) { md5(value) {
const value = parseInt(hex.replace(/^#/, ""), 16); return crypto.createHash("md5").update(value).digest("hex");
return [(value >> 16) & 255, (value >> 8) & 255, value & 255]; },
},
md5(value) { crc32(value) {
return crypto.createHash("md5").update(value).digest("hex"); return _.isBuffer(value) ? CRC32.buf(value) : CRC32.str(value);
}, },
crc32(value) { arrayParse(value): any[] {
return _.isBuffer(value) ? CRC32.buf(value) : CRC32.str(value); return _.isArray(value) ? value : [value];
}, },
arrayParse(value): any[] { booleanParse(value) {
return _.isArray(value) ? value : [value]; return value === "true" || value === true ? true : false;
}, },
booleanParse(value) { encodeBASE64(value) {
return value === "true" || value === true ? true : false return Buffer.from(value).toString("base64");
}, },
encodeBASE64(value) { decodeBASE64(value) {
return Buffer.from(value).toString("base64"); return Buffer.from(value, "base64").toString();
}, },
decodeBASE64(value) {
return Buffer.from(value, "base64").toString();
},
async fetchFileBASE64(url: string) {
const result = await axios.get(url, {
responseType: "arraybuffer",
});
return result.data.toString("base64");
},
}; };
export default util; export default util;