Your Name 598cdfd736
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: async canvas image generation queue and stabilize runtime
2026-02-24 12:00:51 +08:00

152 lines
3.5 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;
falApiKey?: string;
apiKey?: string;
throttleInterval?: number;
maxBuffering?: number;
maxInputResolution?: number;
jpegQuality?: number;
defaults?: {
steps?: number;
scheduleMu?: number;
feedback?: number;
imageSize?: "square" | "square_hd";
};
};
type AngleEditConfig = {
falApiKey?: string;
};
const defaultConfig = {
session: {
idleTimeoutMs: 120000,
},
transport: {
throttleInterval: 100,
maxBuffering: 2,
},
image: {
maxInputResolution: 1024,
jpegQuality: 0.55,
},
model: {
id: "fal-ai/flux-2/klein/realtime",
defaults: {
steps: 3,
scheduleMu: 2.3,
feedback: 0.95,
imageSize: "square" as const,
},
limits: {
steps: { min: 1, max: 6 },
feedback: { min: 0.7, 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, angleEditSetting] = await Promise.all([
getSystemSetting({ key: "realtime_canvas_config" }).catch(() => null),
getSystemSetting({ key: "angle_edit_config" }).catch(() => null),
]);
const config = (setting?.value || {}) as RealtimeCanvasConfig;
const angleEditConfig = (angleEditSetting?.value || {}) as AngleEditConfig;
if (config.enabled === false) {
return Response.json(
{ code: "FEATURE_DISABLED", message: "实时画布功能未开启" },
{ status: 403 }
);
}
const falApiKey = String(
config.falApiKey ||
config.apiKey ||
angleEditConfig.falApiKey ||
process.env.FAL_KEY ||
process.env.FAL_API_KEY ||
""
).trim();
if (!falApiKey) {
return Response.json(
{
code: "FAL_KEY_MISSING",
message: "未配置实时画布 API Key(请在本地AI设置中配置)",
},
{ 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), 1, 6);
const scheduleMu = clamp(
Number(config.defaults?.scheduleMu || defaultConfig.model.defaults.scheduleMu),
1.5,
3.5
);
const feedback = clamp(
Number(config.defaults?.feedback || defaultConfig.model.defaults.feedback),
0.7,
1.0
);
const imageSize = config.defaults?.imageSize === "square_hd" ? "square_hd" : "square";
return Response.json({
...defaultConfig,
transport: {
throttleInterval,
maxBuffering,
},
image: {
maxInputResolution,
jpegQuality,
},
model: {
...defaultConfig.model,
defaults: {
steps,
scheduleMu,
feedback,
imageSize,
},
},
});
}