From 2b6342bc7fa47e8385f72da84528fe110b7145e6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 7 Mar 2026 22:09:06 +0800 Subject: [PATCH] fix: prioritize bfl input image editing --- app/api/realtime/generate/route.ts | 14 +++++++------ lib/realtime/bfl-capability.ts | 33 +++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/api/realtime/generate/route.ts b/app/api/realtime/generate/route.ts index b0d095ea9..808f0eb65 100644 --- a/app/api/realtime/generate/route.ts +++ b/app/api/realtime/generate/route.ts @@ -154,6 +154,12 @@ function toImagePromptValue(imageUrl: string) { return trimmed; } +function getBflImageFieldCandidates() { + // BFL's current FLUX/Kontext editing APIs use `input_image` as the canonical field. + // Keep `image_prompt` only as a legacy fallback in case an older compatible endpoint is used. + return ["input_image", "image_prompt"] as const; +} + type SubmitTaskResult = { taskId: string; pollingUrl: string; @@ -367,13 +373,9 @@ export async function POST(request: Request) { ); } - const preferredImageField = - model.toLowerCase().includes("kontext") || model.toLowerCase().includes("edit") - ? "input_image" - : "image_prompt"; - const imageFieldCandidates = [preferredImageField, preferredImageField === "input_image" ? "image_prompt" : "input_image"]; + const imageFieldCandidates = getBflImageFieldCandidates(); - let selectedImageField = imageFieldCandidates[0]; + let selectedImageField: "input_image" | "image_prompt" = imageFieldCandidates[0]; let submitResult: SubmitTaskResult | null = null; let fallbackWarning = ""; diff --git a/lib/realtime/bfl-capability.ts b/lib/realtime/bfl-capability.ts index 79a703627..f6ba8f44c 100644 --- a/lib/realtime/bfl-capability.ts +++ b/lib/realtime/bfl-capability.ts @@ -1,6 +1,9 @@ +import sharp from "sharp"; + const DEFAULT_BFL_BASE_URL = "https://api.bfl.ai"; const DEFAULT_BFL_MODEL_ID = "flux-2-klein-4b"; const DEFAULT_TIMEOUT_MS = 15_000; +const PROBE_IMAGE_SIZE = 64; export type BflCapabilityCode = | "SUPPORTED" @@ -68,6 +71,28 @@ function hasTaskIdentity(payload: any) { ); } +let cachedProbeImageBase64: string | null = null; + +async function getProbeImageBase64() { + if (cachedProbeImageBase64) { + return cachedProbeImageBase64; + } + + const buffer = await sharp({ + create: { + width: PROBE_IMAGE_SIZE, + height: PROBE_IMAGE_SIZE, + channels: 3, + background: { r: 255, g: 255, b: 255 }, + }, + }) + .png() + .toBuffer(); + + cachedProbeImageBase64 = buffer.toString("base64"); + return cachedProbeImageBase64; +} + export async function probeBflModelSupport(params: BflProbeParams): Promise { const apiKey = String(params.apiKey || "").trim(); const modelId = String(params.modelId || DEFAULT_BFL_MODEL_ID).trim() || DEFAULT_BFL_MODEL_ID; @@ -87,6 +112,7 @@ export async function probeBflModelSupport(params: BflProbeParams): Promise