Your Name 80e826039f
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix(realtime-canvas): use runware seedImage-compatible model
2026-02-25 15:48:16 +08:00

168 lines
3.9 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;
falApiKey?: string;
apiKey?: string;
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: 4,
cfgScale: 3.5,
strength: 0.85,
imageSize: "square" as const,
},
limits: {
steps: { min: 1, max: 8 },
cfgScale: { min: 1, max: 12 },
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 cfgScale = clamp(
Number(
config.defaults?.cfgScale ??
config.defaults?.scheduleMu ??
defaultConfig.model.defaults.cfgScale
),
defaultConfig.model.limits.cfgScale.min,
defaultConfig.model.limits.cfgScale.max
);
const strength = clamp(
Number(
config.defaults?.strength ??
config.defaults?.feedback ??
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,
},
},
});
}