Your Name f6979f0196
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: 完善无限画布3D生成与实时画布能力
2026-02-28 22:33:16 +08:00

184 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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