Your Name 77f1401333
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
Add Replicate realtime canvas provider
2026-03-16 16:23:01 +08:00

161 lines
4.1 KiB
TypeScript

import { auth } from "@/app/(auth)/auth";
import { getSystemSetting } from "@/lib/db/queries";
import {
DEFAULT_BFL_BASE_URL,
RealtimeCanvasConfig,
resolveRealtimeProviders,
} from "@/lib/realtime/config";
const defaultConfig = {
session: {
idleTimeoutMs: 120000,
},
transport: {
throttleInterval: 100,
maxBuffering: 2,
},
image: {
maxInputResolution: 1024,
jpegQuality: 0.55,
},
model: {
id: "flux-2-klein-4b",
defaults: {
steps: 3,
cfgScale: 7,
strength: 0.32,
imageSize: "square" as const,
},
limits: {
steps: { min: 1, max: 6 },
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 providers = resolveRealtimeProviders(config);
const activeProvider = providers[0];
if (!activeProvider) {
return Response.json(
{
code: "BFL_KEY_MISSING",
message: "未配置可用的实时画布服务(请在服务器环境变量或设置中配置)",
},
{ 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 = activeProvider.model;
return Response.json({
...defaultConfig,
provider: {
type: activeProvider.type,
baseUrl: activeProvider.baseUrl || DEFAULT_BFL_BASE_URL,
},
failover: {
enabled: providers.length > 1,
fallbackProvider:
providers.length > 1
? {
type: providers[1].type,
baseUrl: providers[1].baseUrl,
model: providers[1].model,
}
: null,
},
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",
},
});
}