152 lines
3.5 KiB
TypeScript
152 lines
3.5 KiB
TypeScript
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,
|
||
},
|
||
},
|
||
});
|
||
}
|