117 lines
4.9 KiB
TypeScript
117 lines
4.9 KiB
TypeScript
import express from "express";
|
|
import { success, error } from "@/lib/responseFormat";
|
|
import { validateFields } from "@/middleware/middleware";
|
|
import u from "@/utils";
|
|
import { z } from "zod";
|
|
import { transform } from "sucrase";
|
|
const router = express.Router();
|
|
const vendorConfigSchema = z.object({
|
|
version: z.number(),
|
|
icon: z.string().optional(),
|
|
name: z.string(),
|
|
inputs: z.array(
|
|
z.object({
|
|
key: z.string(),
|
|
label: z.string(),
|
|
type: z.enum(["text", "password", "url"]),
|
|
required: z.boolean(),
|
|
placeholder: z.string().optional(),
|
|
}),
|
|
),
|
|
inputValues: z.record(z.string(), z.string()),
|
|
models: z.array(
|
|
z.discriminatedUnion("type", [
|
|
z.object({
|
|
name: z.string(),
|
|
modelName: z.string(),
|
|
type: z.literal("text"),
|
|
multimodal: z.boolean(),
|
|
tool: z.boolean(),
|
|
}),
|
|
z.object({
|
|
name: z.string(),
|
|
modelName: z.string(),
|
|
type: z.literal("image"),
|
|
mode: z.array(z.enum(["text", "singleImage", "multiReference"])),
|
|
}),
|
|
z.object({
|
|
name: z.string(),
|
|
modelName: z.string(),
|
|
type: z.literal("video"),
|
|
mode: z.array(
|
|
z.enum([
|
|
"singleImage",
|
|
"multiImage",
|
|
"gridImage",
|
|
"startEndRequired",
|
|
"endFrameOptional",
|
|
"startFrameOptional",
|
|
"text",
|
|
"audioReference",
|
|
"videoReference",
|
|
]),
|
|
),
|
|
audio: z.union([z.literal("optional"), z.boolean()]),
|
|
durationResolutionMap: z.array(
|
|
z.object({
|
|
duration: z.array(z.number()),
|
|
resolution: z.array(z.string()),
|
|
}),
|
|
),
|
|
}),
|
|
]),
|
|
),
|
|
});
|
|
export default router.post("/",
|
|
validateFields({
|
|
id: z.number(),
|
|
key: z.string(),
|
|
}),
|
|
async (req, res) => {
|
|
const { id, key } = req.body;
|
|
const data = await u.db("o_vendorConfig").where("id", id).select("code", "inputValues").first();
|
|
let inputValues = JSON.parse(data?.inputValues || "{}");
|
|
inputValues.apiKey = key;
|
|
const jsCode = transform(data?.code!, { transforms: ["typescript"] }).code;
|
|
const exports = u.vm(jsCode);
|
|
if (!exports) return res.status(400).send(success("脚本文件必须导出对象"));
|
|
if (!exports.textRequest) return res.status(400).send(success("脚本文件必须导出文本请求对象"));
|
|
if (!exports.imageRequest) return res.status(400).send(success("脚本文件必须导出图像请求对象"));
|
|
if (!exports.videoRequest) return res.status(400).send(success("脚本文件必须导出视频请求对象"));
|
|
if (!exports.vendor) return res.status(400).send(success("脚本文件必须导出vendor对象"));
|
|
const vendor = exports.vendor;
|
|
const result = vendorConfigSchema.safeParse(vendor);
|
|
if (!result.success) {
|
|
const errorMsg = result.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
|
|
return res.status(400).send(error(`vendor配置校验失败: ${errorMsg}`));
|
|
}
|
|
const replaceBlockValue = (code: string, key: string, newValue: string): string => {
|
|
const open = newValue.trimStart()[0] as "[" | "{";
|
|
const close = open === "[" ? "]" : "}";
|
|
const keyMatch = code.match(new RegExp(`\\b${key}\\s*:\\s*[\\[{]`));
|
|
if (!keyMatch || keyMatch.index === undefined) return code;
|
|
const valueStart = keyMatch.index + keyMatch[0].length - 1;
|
|
let depth = 0;
|
|
let valueEnd = -1;
|
|
for (let i = valueStart; i < code.length; i++) {
|
|
if (code[i] === open) depth++;
|
|
else if (code[i] === close) {
|
|
depth--;
|
|
if (depth === 0) {
|
|
valueEnd = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (valueEnd === -1) return code;
|
|
return code.slice(0, valueStart) + newValue + code.slice(valueEnd + 1);
|
|
};
|
|
let updatedTsCode = data?.code!;
|
|
updatedTsCode = replaceBlockValue(updatedTsCode, "inputValues", JSON.stringify(inputValues ?? vendor.inputValues, null, 2));
|
|
await u.db("o_vendorConfig").where("id", id).update({
|
|
inputValues: inputValues ? JSON.stringify(inputValues) : JSON.stringify(vendor.inputValues),
|
|
code: updatedTsCode,
|
|
});
|
|
res.status(200).send(success("保存成功"));
|
|
});
|