135 lines
4.1 KiB
TypeScript
135 lines
4.1 KiB
TypeScript
import { z } from "zod";
|
|
import { requireAdmin } from "@/lib/middleware/admin";
|
|
import {
|
|
getAllSystemSettings,
|
|
getSystemSetting,
|
|
upsertSystemSetting,
|
|
} from "@/lib/db/queries";
|
|
import { ChatSDKError } from "@/lib/errors";
|
|
import { probeRunwareSeedImageSupport } from "@/lib/realtime/runware-capability";
|
|
|
|
const settingSchema = z.object({
|
|
key: z.string(),
|
|
value: z.any(),
|
|
});
|
|
|
|
const DEFAULT_RUNWARE_MODEL = "runware:101@1";
|
|
const DEFAULT_RUNWARE_STYLIZE_MODEL = "runware:106@1";
|
|
const DEFAULT_RUNWARE_CONTROL_MODEL = "runware:25@1";
|
|
|
|
function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function mapProbeStatus(code: string) {
|
|
if (code === "UPSTREAM_ERROR") return 502;
|
|
return 400;
|
|
}
|
|
|
|
export async function GET(request: Request) {
|
|
try {
|
|
await requireAdmin();
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const key = searchParams.get("key");
|
|
|
|
if (key) {
|
|
const setting = await getSystemSetting({ key });
|
|
return Response.json({ setting });
|
|
}
|
|
|
|
const settings = await getAllSystemSettings();
|
|
return Response.json({ settings });
|
|
} catch (error) {
|
|
if (error instanceof ChatSDKError) {
|
|
return error.toResponse();
|
|
}
|
|
return new ChatSDKError("offline:admin").toResponse();
|
|
}
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const session = await requireAdmin();
|
|
|
|
const body = await request.json();
|
|
const { key, value } = settingSchema.parse(body);
|
|
|
|
let nextValue = value;
|
|
|
|
if (key === "realtime_canvas_config") {
|
|
if (!isObjectLike(value)) {
|
|
return new ChatSDKError("bad_request:api", "realtime_canvas_config value 必须为对象").toResponse();
|
|
}
|
|
|
|
const enabled = value.enabled !== false;
|
|
const runwareApiKey = String(value.runwareApiKey || value.falApiKey || value.apiKey || "").trim();
|
|
const runwareModel = String(value.runwareModel || DEFAULT_RUNWARE_MODEL).trim() || DEFAULT_RUNWARE_MODEL;
|
|
const runwareStylizeModel =
|
|
String(value.runwareStylizeModel || DEFAULT_RUNWARE_STYLIZE_MODEL).trim() ||
|
|
DEFAULT_RUNWARE_STYLIZE_MODEL;
|
|
const runwareControlModel =
|
|
String(value.runwareControlModel || DEFAULT_RUNWARE_CONTROL_MODEL).trim() ||
|
|
DEFAULT_RUNWARE_CONTROL_MODEL;
|
|
const runwareControlPreprocessor =
|
|
value.runwareControlPreprocessor === "lineart" || value.runwareControlPreprocessor === "depth"
|
|
? value.runwareControlPreprocessor
|
|
: "canny";
|
|
const renderMode =
|
|
value.renderMode === "strict-structure" ||
|
|
value.renderMode === "stylize" ||
|
|
value.renderMode === "balanced"
|
|
? value.renderMode
|
|
: "balanced";
|
|
|
|
if (enabled) {
|
|
if (!runwareApiKey) {
|
|
return new ChatSDKError("bad_request:api", "启用实时画布必须配置 Runware API Key").toResponse();
|
|
}
|
|
for (const modelId of [runwareModel, runwareStylizeModel].filter(Boolean)) {
|
|
const probeResult = await probeRunwareSeedImageSupport({
|
|
apiKey: runwareApiKey,
|
|
modelId,
|
|
});
|
|
if (!probeResult.supported) {
|
|
return Response.json(
|
|
{
|
|
code: probeResult.code,
|
|
message: probeResult.message,
|
|
},
|
|
{ status: mapProbeStatus(probeResult.code) }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
nextValue = {
|
|
...value,
|
|
runwareApiKey,
|
|
falApiKey: runwareApiKey,
|
|
runwareModel,
|
|
runwareStylizeModel,
|
|
runwareControlModel,
|
|
runwareControlPreprocessor,
|
|
renderMode,
|
|
};
|
|
}
|
|
|
|
const setting = await upsertSystemSetting({
|
|
key,
|
|
value: nextValue,
|
|
updatedBy: session.user.id,
|
|
});
|
|
|
|
return Response.json({ setting: setting[0] });
|
|
} catch (error) {
|
|
if (error instanceof ChatSDKError) {
|
|
return error.toResponse();
|
|
}
|
|
if (error instanceof z.ZodError) {
|
|
return new ChatSDKError("bad_request:api", error.message).toResponse();
|
|
}
|
|
return new ChatSDKError("offline:admin").toResponse();
|
|
}
|
|
}
|