Skip to content

Commit af5ca81

Browse files
committed
feat: add Flux (Black Forest Lab) provider support
1 parent 7b42db0 commit af5ca81

File tree

4 files changed

+220
-1
lines changed

4 files changed

+220
-1
lines changed

src/app/i18n/locales/en.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,36 @@
225225
"description": "FLUX.1 Kontext [pro] handles both text and reference images as inputs, seamlessly enabling targeted, local edits and complex transformations of entire scenes."
226226
}
227227
}
228+
},
229+
"flux": {
230+
"description": "High-quality AI image generation by Black Forest Labs",
231+
"settings": {
232+
"apiKey": {
233+
"title": "API Key",
234+
"description": "Your BFL API key for authentication",
235+
"placeholder": "Enter your BFL API key"
236+
}
237+
},
238+
"models": {
239+
"flux-kontext-pro": {
240+
"description": "FLUX.1 Kontext Pro - Advanced image-to-image generation with superior prompt adherence and editing capabilities."
241+
},
242+
"flux-kontext-max": {
243+
"description": "FLUX.1 Kontext Max - Premium image-to-image generation with maximum quality and consistency for professional editing."
244+
},
245+
"flux-dev": {
246+
"description": "FLUX.1 Dev - High-quality text-to-image generation model optimized for development and experimentation."
247+
},
248+
"flux-pro": {
249+
"description": "FLUX.1 Pro - Professional-grade text-to-image generation with excellent prompt adherence and image quality."
250+
},
251+
"flux-pro-1.1": {
252+
"description": "FLUX.1 Pro 1.1 - Enhanced version of FLUX Pro with improved performance and quality."
253+
},
254+
"flux-pro-1.1-ultra": {
255+
"description": "FLUX.1 Pro 1.1 Ultra - The most advanced FLUX model with ultra-high quality generation and precision."
256+
}
257+
}
228258
}
229259
}
230260
}

src/app/i18n/locales/zh.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,36 @@
225225
"description": "FLUX.1 Kontext [pro] 将文本和参考图像作为输入进行处理,无缝地实现了对整个场景的有针对性的局部编辑和复杂转换。"
226226
}
227227
}
228+
},
229+
"flux": {
230+
"description": "由Black Forest Labs提供的高质量AI图像生成",
231+
"settings": {
232+
"apiKey": {
233+
"title": "API密钥",
234+
"description": "用于身份验证的BFL API密钥",
235+
"placeholder": "输入您的BFL API密钥"
236+
}
237+
},
238+
"models": {
239+
"flux-kontext-pro": {
240+
"description": "FLUX.1 Kontext Pro - 高级图像到图像生成,具有卓越的提示遵循和编辑能力。"
241+
},
242+
"flux-kontext-max": {
243+
"description": "FLUX.1 Kontext Max - 顶级图像到图像生成,为专业编辑提供最高质量和一致性。"
244+
},
245+
"flux-dev": {
246+
"description": "FLUX.1 Dev - 为开发和实验优化的高质量文本到图像生成模型。"
247+
},
248+
"flux-pro": {
249+
"description": "FLUX.1 Pro - 专业级文本到图像生成,具有出色的提示遵循和图像质量。"
250+
},
251+
"flux-pro-1.1": {
252+
"description": "FLUX.1 Pro 1.1 - FLUX Pro的增强版本,提升了性能和质量。"
253+
},
254+
"flux-pro-1.1-ultra": {
255+
"description": "FLUX.1 Pro 1.1 Ultra - 最先进的FLUX模型,具有超高质量生成和精度。"
256+
}
257+
}
228258
}
229259
}
230260
}

src/server/ai/provider/flux.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { fetchUrlToDataURI } from "@/server/lib/util";
2+
import type { AiProvider, ApiProviderSettings, ApiProviderSettingsItem } from "../types/provider";
3+
import { type ProviderSettingsType, chooseAblility, doParseSettings, findModel } from "../types/provider";
4+
5+
const fluxSettingsSchema = [
6+
{
7+
key: "apiKey",
8+
type: "password",
9+
required: true,
10+
},
11+
] as const satisfies ApiProviderSettingsItem[];
12+
13+
// Automatically generate type from schema
14+
export type FluxSettings = ProviderSettingsType<typeof fluxSettingsSchema>;
15+
16+
interface FluxSubmitResponse {
17+
id: string;
18+
polling_url: string;
19+
}
20+
21+
interface FluxPollResponse {
22+
id: string;
23+
status: "Pending" | "Running" | "Ready" | "Error" | "Failed";
24+
result?: {
25+
sample: string;
26+
};
27+
error?: string;
28+
}
29+
30+
const Flux: AiProvider = {
31+
id: "flux",
32+
name: "Flux",
33+
supportCors: false,
34+
enabledByDefault: true,
35+
settings: fluxSettingsSchema,
36+
models: [
37+
{
38+
id: "flux-kontext-max",
39+
name: "FLUX.1 Kontext [max]",
40+
ability: "i2i",
41+
enabledByDefault: true,
42+
},
43+
{
44+
id: "flux-kontext-pro",
45+
name: "FLUX.1 Kontext [pro]",
46+
ability: "i2i",
47+
enabledByDefault: true,
48+
},
49+
{
50+
id: "flux-pro-1.1-ultra",
51+
name: "FLUX1.1 [pro] Ultra",
52+
ability: "t2i",
53+
enabledByDefault: true,
54+
},
55+
{
56+
id: "flux-pro-1.1",
57+
name: "FLUX1.1 [pro]",
58+
ability: "t2i",
59+
enabledByDefault: true,
60+
},
61+
{
62+
id: "flux-pro",
63+
name: "FLUX.1 [pro]",
64+
ability: "t2i",
65+
enabledByDefault: true,
66+
},
67+
{
68+
id: "flux-dev",
69+
name: "FLUX.1 [dev]",
70+
ability: "t2i",
71+
enabledByDefault: true,
72+
},
73+
],
74+
parseSettings: <FluxSettings>(settings: ApiProviderSettings) => {
75+
return doParseSettings(settings, fluxSettingsSchema) as FluxSettings;
76+
},
77+
generate: async (request, settings) => {
78+
const { apiKey } = Flux.parseSettings<FluxSettings>(settings);
79+
80+
const model = findModel(Flux, request.modelId);
81+
const genType = chooseAblility(request, model.ability);
82+
83+
const requestBody: any = {
84+
prompt: request.prompt,
85+
};
86+
if (genType === "i2i" && request.images?.[0]) {
87+
requestBody.image_url = request.images[0];
88+
}
89+
90+
const submitResponse = await fetch(`https://api.bfl.ai/v1/${request.modelId}`, {
91+
method: "POST",
92+
headers: {
93+
accept: "application/json",
94+
"x-key": apiKey,
95+
"Content-Type": "application/json",
96+
},
97+
body: JSON.stringify(requestBody),
98+
});
99+
100+
if (!submitResponse.ok) {
101+
if (submitResponse.status === 403) {
102+
return {
103+
errorReason: "CONFIG_ERROR",
104+
images: [],
105+
};
106+
}
107+
throw new Error(`Flux API error: ${submitResponse.status} ${submitResponse.statusText}`);
108+
}
109+
110+
const submitData: FluxSubmitResponse = await submitResponse.json();
111+
const { id: requestId, polling_url: pollingUrl } = submitData;
112+
113+
let attempts = 0;
114+
const maxAttempts = 120;
115+
116+
while (attempts < maxAttempts) {
117+
await new Promise((resolve) => setTimeout(resolve, 500));
118+
attempts++;
119+
120+
const pollUrl = new URL(pollingUrl);
121+
pollUrl.searchParams.set("id", requestId);
122+
123+
const pollResponse = await fetch(pollUrl.toString(), {
124+
method: "GET",
125+
headers: {
126+
accept: "application/json",
127+
"x-key": apiKey,
128+
},
129+
});
130+
131+
if (!pollResponse.ok) {
132+
throw new Error(`Flux polling error: ${pollResponse.status} ${pollResponse.statusText}`);
133+
}
134+
135+
const pollData: FluxPollResponse = await pollResponse.json();
136+
137+
if (pollData.status === "Ready" && pollData.result?.sample) {
138+
try {
139+
const imageDataUri = await fetchUrlToDataURI(pollData.result.sample);
140+
return {
141+
images: [imageDataUri],
142+
};
143+
} catch (error) {
144+
console.error("Flux image fetch error:", error);
145+
return {
146+
images: [],
147+
};
148+
}
149+
} else if (pollData.status === "Error" || pollData.status === "Failed") {
150+
throw new Error(`Flux generation failed: ${pollData.error || "Unknown error"}`);
151+
}
152+
}
153+
154+
throw new Error("Flux generation timeout - exceeded maximum polling attempts");
155+
},
156+
};
157+
158+
export default Flux;

src/server/ai/provider/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { ServiceException } from "@/server/lib/exception";
44
import type { AiProvider } from "../types/provider";
55
import { default as cloudflare } from "./cloudflare";
66
import { default as fal } from "./fal";
7+
import { default as flux } from "./flux";
78
import { default as openAI } from "./openai";
89

9-
export const AI_PROVIDERS = [cloudflare, openAI, fal].map(enhancedProvider);
10+
export const AI_PROVIDERS = [cloudflare, openAI, fal, flux].map(enhancedProvider);
1011

1112
export function getDefaultProvider() {
1213
return AI_PROVIDERS[0]!;

0 commit comments

Comments
 (0)