Nymbo commited on
Commit
c64e303
·
verified ·
1 Parent(s): 3e70bc9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -294
app.py CHANGED
@@ -4,51 +4,13 @@ import os
4
  from PIL import Image
5
  from typing import Optional
6
  from huggingface_hub import InferenceClient
7
- import tempfile
8
- import json
9
- import uuid
10
- import re
11
 
12
- # Project by Nymbo - Converted to MCP Server
13
 
14
- # Configuration
15
  API_TOKEN = os.getenv("HF_READ_TOKEN")
16
  timeout = 100
17
 
18
- # Helper: build a public base URL for the running app (HF Spaces or local)
19
- def _slugify_for_subdomain(s: str) -> str:
20
- s = s.strip().lower()
21
- s = s.replace(".", "-").replace("_", "-").replace(" ", "-")
22
- s = re.sub(r"[^a-z0-9-]", "-", s)
23
- s = re.sub(r"-+", "-", s).strip("-")
24
- return s
25
-
26
- def _build_public_base_url() -> str:
27
- # Allow explicit override via env
28
- for var in ("HF_SPACE_URL", "SPACE_URL", "PUBLIC_SPACE_URL"):
29
- val = os.getenv(var)
30
- if val:
31
- return val.rstrip("/")
32
-
33
- space_id = os.getenv("SPACE_ID")
34
- if space_id:
35
- # If a full URL was provided, use it directly
36
- if space_id.startswith("http://") or space_id.startswith("https://"):
37
- return space_id.rstrip("/")
38
- # Typical HF Spaces SPACE_ID is "owner/space-name"
39
- if "/" in space_id:
40
- owner, space = space_id.split("/", 1)
41
- sub = f"{_slugify_for_subdomain(owner)}-{_slugify_for_subdomain(space)}"
42
- else:
43
- # Fall back to slugifying the whole string
44
- sub = _slugify_for_subdomain(space_id)
45
- return f"https://{sub}.hf.space"
46
-
47
- # Local fallback
48
- host = os.getenv("GRADIO_SERVER_NAME", "localhost")
49
- port = os.getenv("GRADIO_SERVER_PORT", os.getenv("PORT", "7860"))
50
- return f"http://{host}:{port}"
51
-
52
  def flux_krea_generate(
53
  prompt: str,
54
  negative_prompt: str = "(deformed, distorted, disfigured), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, (mutated hands and fingers), disconnected limbs, mutation, mutated, ugly, disgusting, blurry, amputation, misspellings, typos",
@@ -59,44 +21,39 @@ def flux_krea_generate(
59
  strength: float = 0.7,
60
  width: int = 1024,
61
  height: int = 1024
62
- ) -> str:
63
  """
64
- Generate high-quality professional images using FLUX.1-Krea-dev via HuggingFace Serverless Inference.
65
-
66
- This MCP tool creates realistic, professional-quality images from text prompts using the
67
- FLUX.1-Krea-dev model through HuggingFace's serverless inference infrastructure. The model
68
- excels at generating natural-looking images without typical AI artifacts, making it ideal
69
- for product photography, e-commerce visuals, concept art, fashion photography, and stock images.
70
 
71
  Args:
72
- prompt: Detailed text description of the image to generate. Best results with approximately 60-70 words max. Include technical photography terms, specific lighting, and material textures for photorealistic results.
73
- negative_prompt: Elements to avoid in the generated image (deformities, poor anatomy, etc.).
74
- steps: Number of denoising steps (1-100). Higher values generally improve quality but increase generation time.
75
- cfg_scale: Classifier-free guidance scale (1-20). Higher values make the model follow the prompt more closely but may reduce creativity.
76
- sampler: Sampling method for diffusion process. Options: "DPM++ 2M Karras", "DPM++ SDE Karras", "Euler", "Euler a", "Heun", "DDIM".
77
- seed: Random seed for reproducible results. Use -1 for random generation each time.
78
- strength: Generation strength parameter (0-1). Controls the influence of the generation process.
79
- width: Output image width in pixels (64-1216). Recommend multiples of 32 for best results.
80
- height: Output image height in pixels (64-1216). Recommend multiples of 32 for best results.
81
 
82
  Returns:
83
- JSON string containing the image URL and generation metadata that can be accessed by LLMs and MCP clients.
84
  """
85
- if not prompt or prompt.strip() == "":
86
- return json.dumps({
87
- "success": False,
88
- "error": "Prompt is required and cannot be empty",
89
- "image_url": None
90
- })
91
 
92
- generation_id = random.randint(0, 999)
93
 
94
- # Enhance prompt for better quality
95
  enhanced_prompt = f"{prompt} | ultra detail, ultra elaboration, ultra quality, perfect."
96
- print(f'\033[1mMCP Generation {generation_id}:\033[0m {enhanced_prompt}')
97
 
98
  try:
99
- # Initialize the Hugging Face Inference Client with fallback providers
 
100
  providers = ["auto", "replicate", "fal-ai"]
101
 
102
  for provider in providers:
@@ -106,7 +63,7 @@ def flux_krea_generate(
106
  provider=provider
107
  )
108
 
109
- # Generate the image using serverless inference
110
  image = client.text_to_image(
111
  prompt=enhanced_prompt,
112
  negative_prompt=negative_prompt,
@@ -118,265 +75,92 @@ def flux_krea_generate(
118
  seed=seed if seed != -1 else random.randint(1, 1000000000)
119
  )
120
 
121
- # Save the image and return a Gradio-accessible URL
122
- if image:
123
- # Save to a temporary file that Gradio can serve
124
- temp_file = tempfile.NamedTemporaryFile(
125
- delete=False,
126
- suffix=".png",
127
- prefix=f"flux_krea_mcp_{generation_id}_"
128
- )
129
- image.save(temp_file.name)
130
- temp_file.close()
131
-
132
- # Create the Gradio file URL that will be accessible to MCP clients
133
- # This matches the format you saw: /gradio_api/file=<file_path>
134
- gradio_file_url = f"/gradio_api/file={temp_file.name}"
135
-
136
- # Build a proper public base URL (HF Spaces or local)
137
- base_url = _build_public_base_url()
138
- full_url = f"{base_url}{gradio_file_url}"
139
-
140
- print(f'\033[1mMCP Generation {generation_id} completed with {provider}!\033[0m')
141
- print(f'🌐 Image accessible at: {full_url}')
142
-
143
- # Return JSON with accessible URLs and metadata
144
- result = {
145
- "success": True,
146
- "image_url": full_url,
147
- "gradio_file_url": gradio_file_url,
148
- "local_path": temp_file.name,
149
- "generation_id": generation_id,
150
- "provider": provider,
151
- "model": "black-forest-labs/FLUX.1-Krea-dev",
152
- "prompt": enhanced_prompt,
153
- "parameters": {
154
- "width": width,
155
- "height": height,
156
- "steps": steps,
157
- "cfg_scale": cfg_scale,
158
- "seed": seed if seed != -1 else "random",
159
- "sampler": sampler
160
- },
161
- "metadata": {
162
- "tool": "flux_krea_generate",
163
- "timestamp": str(generation_id),
164
- "mcp_compatible": True,
165
- "accessible_url": full_url
166
- }
167
- }
168
-
169
- return json.dumps(result)
170
 
171
  except Exception as provider_error:
172
  print(f"Provider {provider} failed: {provider_error}")
173
- if provider == providers[-1]: # Last provider failed
174
  raise provider_error
175
  continue
176
 
177
  except Exception as e:
178
- print(f"Error during MCP image generation: {e}")
179
- error_message = "Image generation failed due to an unknown error."
180
-
181
  if "404" in str(e):
182
- error_message = "Model not found. Please ensure the FLUX.1-Krea-dev model is accessible with your API token."
183
  elif "503" in str(e):
184
- error_message = "The model is currently being loaded. Please try again in a moment."
185
  elif "401" in str(e) or "403" in str(e):
186
- error_message = "Authentication failed. Please check your HF_READ_TOKEN environment variable."
187
-
188
- return json.dumps({
189
- "success": False,
190
- "error": error_message,
191
- "image_url": None,
192
- "gradio_file_url": None,
193
- "local_path": None,
194
- "generation_id": generation_id,
195
- "metadata": {
196
- "tool": "flux_krea_generate",
197
- "mcp_compatible": True,
198
- "error": True
199
- }
200
- })
201
-
202
- # For UI compatibility - this function returns a PIL Image for the Gradio interface
203
- def flux_krea_generate_ui(*args) -> Optional[Image.Image]:
204
- """UI wrapper that returns PIL Image for Gradio interface"""
205
- result_json = flux_krea_generate(*args)
206
- try:
207
- result = json.loads(result_json)
208
- if result.get("success") and result.get("local_path"):
209
- # Return the PIL Image for the UI
210
- return Image.open(result["local_path"])
211
- except Exception as e:
212
- print(f"UI wrapper error: {e}")
213
- pass
214
- return None
215
 
216
- # CSS for improved styling
217
  css = """
218
  #app-container {
219
- max-width: 900px;
220
  margin-left: auto;
221
  margin-right: auto;
222
  }
223
- .mcp-badge {
224
- background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
225
- color: white;
226
- padding: 5px 10px;
227
- border-radius: 15px;
228
- font-size: 12px;
229
- font-weight: bold;
230
- }
231
  """
232
 
233
- # Build the Gradio app
234
- with gr.Blocks(theme='Nymbo/Nymbo_Theme', css=css, title="FLUX.1-Krea MCP Server") as app:
235
- # Header
236
- gr.HTML("""
237
- <center>
238
- <h1>🚀 FLUX.1-Krea-dev <span class="mcp-badge">MCP SERVER</span></h1>
239
- <p>High-quality serverless image generation via Model Context Protocol</p>
240
- <p><em>Professional-grade images • No AI artifacts • MCP-compatible</em></p>
241
- </center>
242
- """)
243
 
244
- # Main interface
245
  with gr.Column(elem_id="app-container"):
246
- # Prompt input
247
- with gr.Row():
248
- text_prompt = gr.Textbox(
249
- label="Image Prompt",
250
- placeholder="Describe the image you want to generate (60-70 words recommended)",
251
- lines=3,
252
- elem_id="prompt-text-input"
253
- )
254
-
255
- # Advanced settings accordion
256
  with gr.Row():
257
- with gr.Accordion("🔧 Advanced Generation Settings", open=False):
258
- negative_prompt = gr.Textbox(
259
- label="Negative Prompt",
260
- placeholder="What should NOT be in the image",
261
- value="(deformed, distorted, disfigured), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, (mutated hands and fingers), disconnected limbs, mutation, mutated, ugly, disgusting, blurry, amputation, misspellings, typos",
262
- lines=3,
263
- elem_id="negative-prompt-text-input"
264
- )
265
-
266
- with gr.Row():
267
- width = gr.Slider(
268
- label="Width",
269
- value=1024,
270
- minimum=64,
271
- maximum=1216,
272
- step=32,
273
- info="Output width in pixels"
274
- )
275
- height = gr.Slider(
276
- label="Height",
277
- value=1024,
278
- minimum=64,
279
- maximum=1216,
280
- step=32,
281
- info="Output height in pixels"
282
- )
283
-
284
  with gr.Row():
285
- steps = gr.Slider(
286
- label="Sampling Steps",
287
- value=35,
288
- minimum=1,
289
- maximum=100,
290
- step=1,
291
- info="More steps = higher quality, longer generation time"
292
- )
293
- cfg = gr.Slider(
294
- label="CFG Scale",
295
- value=7,
296
- minimum=1,
297
- maximum=20,
298
- step=1,
299
- info="How closely to follow the prompt"
300
- )
301
 
 
302
  with gr.Row():
303
- strength = gr.Slider(
304
- label="Strength",
305
- value=0.7,
306
- minimum=0,
307
- maximum=1,
308
- step=0.01,
309
- info="Generation strength parameter"
310
- )
311
- seed = gr.Slider(
312
- label="Seed",
313
- value=-1,
314
- minimum=-1,
315
- maximum=1000000000,
316
- step=1,
317
- info="Use -1 for random seed"
318
- )
319
-
320
- sampler = gr.Radio(
321
- label="Sampling Method",
322
- value="DPM++ 2M Karras",
323
- choices=["DPM++ 2M Karras", "DPM++ SDE Karras", "Euler", "Euler a", "Heun", "DDIM"],
324
- info="Algorithm used for image generation"
325
- )
326
-
327
- # Generation button
328
  with gr.Row():
329
- generate_button = gr.Button("🎨 Generate Image", variant='primary', elem_id="gen-button", scale=1)
330
 
331
- # Output area
332
  with gr.Row():
333
- image_output = gr.Image(
334
- label="Generated Image",
335
- elem_id="gallery",
336
- show_share_button=True,
337
- show_download_button=True
338
- )
339
 
340
- # MCP Information
341
- with gr.Row():
342
- gr.HTML("""
343
- <div style="background: #f0f0f0; padding: 15px; border-radius: 10px; margin-top: 20px;">
344
- <h3>📡 MCP Server Information</h3>
345
- <p><strong>Server Endpoint:</strong> <code>/gradio_api/mcp/sse</code></p>
346
- <p><strong>Tool Name:</strong> <code>flux_krea_generate</code></p>
347
- <p><strong>Image URLs:</strong> Returns accessible Gradio file URLs like <code>/gradio_api/file=&lt;path&gt;</code></p>
348
- <p>This server exposes the image generation function as an MCP tool that returns JSON with accessible image URLs for LLM integration.</p>
349
- <p><em>✅ Fixed: LLMs can now access generated images via proper Gradio file URLs</em></p>
350
- </div>
351
- """)
352
-
353
- # Wire up the UI event (this is separate from the MCP tool)
354
- generate_button.click(
355
- flux_krea_generate_ui,
356
- inputs=[text_prompt, negative_prompt, steps, cfg, sampler, seed, strength, width, height],
357
  outputs=image_output,
358
- show_api=False # Hide from API docs since we have dedicated MCP tool
 
359
  )
360
 
361
- # Expose the MCP tool with clear API documentation
 
362
  gr.api(
363
  flux_krea_generate,
364
- api_name="flux_krea_generate",
365
  api_description=(
366
- "MCP Tool: Generate professional-quality images using FLUX.1-Krea-dev via serverless inference. "
367
- "Returns JSON with image URL and metadata for MCP clients and LLMs. "
368
- "Optimized for natural, realistic images without AI artifacts."
369
- )
370
  )
371
 
372
- # Launch with MCP server enabled
373
- if __name__ == "__main__":
374
- # Enable MCP server functionality
375
- app.launch(
376
- mcp_server=True,
377
- show_api=True,
378
- share=False,
379
- server_name="0.0.0.0",
380
- server_port=7860,
381
- show_error=True
382
- )
 
4
  from PIL import Image
5
  from typing import Optional
6
  from huggingface_hub import InferenceClient
 
 
 
 
7
 
8
+ # Project by Nymbo
9
 
 
10
  API_TOKEN = os.getenv("HF_READ_TOKEN")
11
  timeout = 100
12
 
13
+ # Function to query the API and return the generated image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  def flux_krea_generate(
15
  prompt: str,
16
  negative_prompt: str = "(deformed, distorted, disfigured), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, (mutated hands and fingers), disconnected limbs, mutation, mutated, ugly, disgusting, blurry, amputation, misspellings, typos",
 
21
  strength: float = 0.7,
22
  width: int = 1024,
23
  height: int = 1024
24
+ ) -> Optional[Image.Image]:
25
  """
26
+ Text-to-image generation with FLUX.1-Krea-dev (no input image required).
27
+
28
+ This tool generates a single image from a text prompt using the
29
+ black-forest-labs/FLUX.1-Krea-dev model on Hugging Face Inference.
 
 
30
 
31
  Args:
32
+ prompt: Text description of the image to generate.
33
+ negative_prompt: What should NOT appear in the image.
34
+ steps: Number of denoising steps (1-100). Higher is slower but can improve quality.
35
+ cfg_scale: Classifier-free guidance scale (1-20). Higher = follow the prompt more closely.
36
+ sampler: Sampling method to use. One of: "DPM++ 2M Karras", "DPM++ SDE Karras", "Euler", "Euler a", "Heun", "DDIM".
37
+ seed: Random seed for reproducible results. Use -1 for a random seed per call.
38
+ strength: Generation strength (0-1). Kept for parity; not an input image strength.
39
+ width: Output width in pixels (64-1216, multiple of 32 recommended).
40
+ height: Output height in pixels (64-1216, multiple of 32 recommended).
41
 
42
  Returns:
43
+ A PIL.Image of the generated result. No input image is expected or required.
44
  """
45
+ if prompt == "" or prompt is None:
46
+ return None
 
 
 
 
47
 
48
+ key = random.randint(0, 999)
49
 
50
+ # Add some extra flair to the prompt
51
  enhanced_prompt = f"{prompt} | ultra detail, ultra elaboration, ultra quality, perfect."
52
+ print(f'\033[1mGeneration {key}:\033[0m {enhanced_prompt}')
53
 
54
  try:
55
+ # Initialize the Hugging Face Inference Client
56
+ # Try different providers in order of preference
57
  providers = ["auto", "replicate", "fal-ai"]
58
 
59
  for provider in providers:
 
63
  provider=provider
64
  )
65
 
66
+ # Generate the image using the proper client
67
  image = client.text_to_image(
68
  prompt=enhanced_prompt,
69
  negative_prompt=negative_prompt,
 
75
  seed=seed if seed != -1 else random.randint(1, 1000000000)
76
  )
77
 
78
+ print(f'\033[1mGeneration {key} completed with {provider}!\033[0m ({enhanced_prompt})')
79
+ return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  except Exception as provider_error:
82
  print(f"Provider {provider} failed: {provider_error}")
83
+ if provider == providers[-1]: # Last provider
84
  raise provider_error
85
  continue
86
 
87
  except Exception as e:
88
+ print(f"Error during image generation: {e}")
 
 
89
  if "404" in str(e):
90
+ raise gr.Error("Model not found. Please ensure the FLUX.1-Krea-dev model is accessible with your API token.")
91
  elif "503" in str(e):
92
+ raise gr.Error("The model is currently being loaded. Please try again in a moment.")
93
  elif "401" in str(e) or "403" in str(e):
94
+ raise gr.Error("Authentication failed. Please check your HF_READ_TOKEN environment variable.")
95
+ else:
96
+ raise gr.Error(f"Image generation failed: {str(e)}")
97
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
+ # CSS to style the app
100
  css = """
101
  #app-container {
102
+ max-width: 800px;
103
  margin-left: auto;
104
  margin-right: auto;
105
  }
 
 
 
 
 
 
 
 
106
  """
107
 
108
+ # Build the Gradio UI with Blocks
109
+ with gr.Blocks(theme='Nymbo/Nymbo_Theme', css=css) as app:
110
+ # Add a title to the app
111
+ gr.HTML("<center><h1>FLUX.1-Krea-dev</h1></center>")
112
+ gr.HTML("<center><p>High-quality image generation via Model Context Protocol</p></center>")
 
 
 
 
 
113
 
114
+ # Container for all the UI elements
115
  with gr.Column(elem_id="app-container"):
116
+ # Add a text input for the main prompt
 
 
 
 
 
 
 
 
 
117
  with gr.Row():
118
+ with gr.Column(elem_id="prompt-container"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  with gr.Row():
120
+ text_prompt = gr.Textbox(label="Prompt", placeholder="Enter a prompt here", lines=2, elem_id="prompt-text-input")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ # Accordion for advanced settings
123
  with gr.Row():
124
+ with gr.Accordion("Advanced Settings", open=False):
125
+ negative_prompt = gr.Textbox(label="Negative Prompt", placeholder="What should not be in the image", value="(deformed, distorted, disfigured), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, (mutated hands and fingers), disconnected limbs, mutation, mutated, ugly, disgusting, blurry, amputation, misspellings, typos", lines=3, elem_id="negative-prompt-text-input")
126
+ with gr.Row():
127
+ width = gr.Slider(label="Width", value=1024, minimum=64, maximum=1216, step=32)
128
+ height = gr.Slider(label="Height", value=1024, minimum=64, maximum=1216, step=32)
129
+ steps = gr.Slider(label="Sampling steps", value=35, minimum=1, maximum=100, step=1)
130
+ cfg = gr.Slider(label="CFG Scale", value=7, minimum=1, maximum=20, step=1)
131
+ strength = gr.Slider(label="Strength", value=0.7, minimum=0, maximum=1, step=0.001)
132
+ seed = gr.Slider(label="Seed", value=-1, minimum=-1, maximum=1000000000, step=1) # Setting the seed to -1 will make it random
133
+ method = gr.Radio(label="Sampling method", value="DPM++ 2M Karras", choices=["DPM++ 2M Karras", "DPM++ SDE Karras", "Euler", "Euler a", "Heun", "DDIM"])
134
+
135
+ # Add a button to trigger the image generation
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  with gr.Row():
137
+ text_button = gr.Button("Run", variant='primary', elem_id="gen-button")
138
 
139
+ # Image output area to display the generated image
140
  with gr.Row():
141
+ # Output component only; no input image is required by the tool
142
+ image_output = gr.Image(label="Image Output", elem_id="gallery")
 
 
 
 
143
 
144
+ # Bind the button to the flux_krea_generate function for the UI only
145
+ # Hide this event as an MCP tool to avoid schema confusion (UI wires image output)
146
+ text_button.click(
147
+ flux_krea_generate,
148
+ inputs=[text_prompt, negative_prompt, steps, cfg, method, seed, strength, width, height],
 
 
 
 
 
 
 
 
 
 
 
 
149
  outputs=image_output,
150
+ show_api=False,
151
+ api_description=False,
152
  )
153
 
154
+ # Expose a dedicated MCP/API endpoint with a clear schema (text-to-image only)
155
+ # This avoids clients misinterpreting the UI event as requiring an input image.
156
  gr.api(
157
  flux_krea_generate,
158
+ api_name="generate_image",
159
  api_description=(
160
+ "Generate an image from a text prompt using FLUX.1-Krea-dev. "
161
+ "Inputs are text and numeric parameters only; no input image is required."
162
+ ),
 
163
  )
164
 
165
+ # Launch the Gradio app with MCP server enabled
166
+ app.launch(show_api=True, share=False, mcp_server=True)