184 lines
4.7 KiB
TypeScript
184 lines
4.7 KiB
TypeScript
import { auth } from "@/app/(auth)/auth";
|
||
import { getSystemSetting } from "@/lib/db/queries";
|
||
|
||
type RealtimeCanvasConfig = {
|
||
enabled?: boolean;
|
||
runwareApiKey?: string;
|
||
runwareModel?: string;
|
||
runwareControlModel?: string;
|
||
runwareControlPreprocessor?: "canny" | "lineart" | "depth";
|
||
renderMode?: "strict-structure" | "balanced" | "stylize";
|
||
falApiKey?: string;
|
||
apiKey?: string;
|
||
strictSketch?: boolean;
|
||
throttleInterval?: number;
|
||
maxBuffering?: number;
|
||
maxInputResolution?: number;
|
||
jpegQuality?: number;
|
||
defaults?: {
|
||
steps?: number;
|
||
cfgScale?: number;
|
||
strength?: number;
|
||
scheduleMu?: number;
|
||
feedback?: number;
|
||
imageSize?: "square" | "square_hd";
|
||
};
|
||
};
|
||
|
||
const defaultConfig = {
|
||
session: {
|
||
idleTimeoutMs: 120000,
|
||
},
|
||
transport: {
|
||
throttleInterval: 100,
|
||
maxBuffering: 2,
|
||
},
|
||
image: {
|
||
maxInputResolution: 1024,
|
||
jpegQuality: 0.55,
|
||
},
|
||
model: {
|
||
id: "runware:101@1",
|
||
defaults: {
|
||
steps: 8,
|
||
cfgScale: 7,
|
||
strength: 0.32,
|
||
imageSize: "square" as const,
|
||
},
|
||
limits: {
|
||
steps: { min: 1, max: 12 },
|
||
cfgScale: { min: 1, max: 14 },
|
||
strength: { min: 0.05, max: 1.0 },
|
||
},
|
||
},
|
||
features: {
|
||
history: true,
|
||
abCompare: false,
|
||
},
|
||
};
|
||
|
||
function clamp(value: number, min: number, max: number) {
|
||
return Math.min(max, Math.max(min, value));
|
||
}
|
||
|
||
export async function GET() {
|
||
const session = await auth();
|
||
if (!session?.user?.id) {
|
||
return Response.json({ code: "UNAUTHORIZED", message: "请先登录" }, { status: 401 });
|
||
}
|
||
|
||
const setting = await getSystemSetting({ key: "realtime_canvas_config" }).catch(() => null);
|
||
const config = (setting?.value || {}) as RealtimeCanvasConfig;
|
||
|
||
if (config.enabled === false) {
|
||
return Response.json(
|
||
{ code: "FEATURE_DISABLED", message: "实时画布功能未开启" },
|
||
{ status: 403 }
|
||
);
|
||
}
|
||
|
||
const runwareApiKey = String(
|
||
config.runwareApiKey ||
|
||
config.falApiKey ||
|
||
config.apiKey ||
|
||
process.env.RUNWARE_API_KEY ||
|
||
process.env.RUNWARE_KEY ||
|
||
""
|
||
).trim();
|
||
|
||
if (!runwareApiKey) {
|
||
return Response.json(
|
||
{
|
||
code: "RUNWARE_KEY_MISSING",
|
||
message: "未配置 Runware API Key(请在服务器环境变量或设置中配置)",
|
||
},
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
|
||
const throttleInterval = clamp(
|
||
Number(config.throttleInterval || defaultConfig.transport.throttleInterval),
|
||
60,
|
||
260
|
||
);
|
||
const maxBuffering = clamp(
|
||
Number(config.maxBuffering || defaultConfig.transport.maxBuffering),
|
||
1,
|
||
6
|
||
);
|
||
const maxInputResolution = clamp(
|
||
Number(config.maxInputResolution || defaultConfig.image.maxInputResolution),
|
||
512,
|
||
1536
|
||
);
|
||
const jpegQuality = clamp(
|
||
Number(config.jpegQuality || defaultConfig.image.jpegQuality),
|
||
0.35,
|
||
0.82
|
||
);
|
||
const steps = clamp(
|
||
Number(config.defaults?.steps || defaultConfig.model.defaults.steps),
|
||
defaultConfig.model.limits.steps.min,
|
||
defaultConfig.model.limits.steps.max
|
||
);
|
||
const legacyScheduleMu = Number(config.defaults?.scheduleMu);
|
||
const cfgScaleBase = Number(
|
||
config.defaults?.cfgScale ??
|
||
(Number.isFinite(legacyScheduleMu) ? legacyScheduleMu * 2.5 : defaultConfig.model.defaults.cfgScale)
|
||
);
|
||
const cfgScale = clamp(
|
||
cfgScaleBase,
|
||
defaultConfig.model.limits.cfgScale.min,
|
||
defaultConfig.model.limits.cfgScale.max
|
||
);
|
||
const strength = clamp(
|
||
Number(config.defaults?.strength ?? defaultConfig.model.defaults.strength),
|
||
defaultConfig.model.limits.strength.min,
|
||
defaultConfig.model.limits.strength.max
|
||
);
|
||
const imageSize = config.defaults?.imageSize === "square_hd" ? "square_hd" : "square";
|
||
const modelId = String(
|
||
config.runwareModel || process.env.RUNWARE_MODEL || defaultConfig.model.id
|
||
).trim();
|
||
|
||
return Response.json({
|
||
...defaultConfig,
|
||
provider: {
|
||
type: "runware",
|
||
},
|
||
transport: {
|
||
throttleInterval,
|
||
maxBuffering,
|
||
},
|
||
image: {
|
||
maxInputResolution,
|
||
jpegQuality,
|
||
},
|
||
model: {
|
||
...defaultConfig.model,
|
||
id: modelId || defaultConfig.model.id,
|
||
defaults: {
|
||
steps,
|
||
cfgScale,
|
||
strength,
|
||
imageSize,
|
||
},
|
||
},
|
||
behavior: {
|
||
strictSketch: config.strictSketch !== false,
|
||
renderMode:
|
||
config.renderMode === "strict-structure" || config.renderMode === "stylize"
|
||
? config.renderMode
|
||
: "balanced",
|
||
},
|
||
control: {
|
||
model: String(config.runwareControlModel || "runware:25@1").trim() || "runware:25@1",
|
||
preprocessor:
|
||
config.runwareControlPreprocessor === "lineart" ||
|
||
config.runwareControlPreprocessor === "depth"
|
||
? config.runwareControlPreprocessor
|
||
: "canny",
|
||
},
|
||
});
|
||
}
|