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", }, }); }