Zhofang commited on
Commit
194691e
·
verified ·
1 Parent(s): a27dc0b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +184 -72
app.py CHANGED
@@ -6,79 +6,153 @@ import os
6
  from PIL import Image
7
  from deep_translator import GoogleTranslator
8
 
 
9
  # os.makedirs('assets', exist_ok=True)
 
 
10
  if not os.path.exists('icon.jpg'):
11
- os.system("wget -O icon.jpg https://i.pinimg.com/564x/64/49/88/644988c59447eb00286834c2e70fdd6b.jpg")
 
 
 
 
 
 
 
 
 
 
 
 
12
  API_URL_DEV = "https://lol-v2.mxflower.eu.org/api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev"
13
  API_URL = "https://lol-v2.mxflower.eu.org/api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell"
14
  timeout = 100
15
 
16
- def query(prompt, is_negative=False, steps=30, cfg_scale=7, sampler="DPM++ 2M Karras", seed=-1, strength=0.7, huggingface_api_key=None, use_dev=False):
17
- # Determine which API URL to use
18
  api_url = API_URL_DEV if use_dev else API_URL
19
 
20
- # Check if the request is an API call by checking for the presence of the huggingface_api_key
21
- is_api_call = huggingface_api_key is not None
22
-
23
- if is_api_call:
24
- # Use the environment variable for the API key in GUI mode
25
- API_TOKEN = os.getenv("HF_READ_TOKEN")
26
- headers = {"Authorization": f"Bearer {API_TOKEN}"}
27
  else:
28
- # Validate the API key if it's an API call
29
- if huggingface_api_key == "":
30
- raise gr.Error("API key is required for API calls.")
31
- headers = {"Authorization": f"Bearer {huggingface_api_key}"}
 
 
32
 
33
- if prompt == "" or prompt is None:
34
- return None
35
 
36
- key = random.randint(0, 999)
 
37
 
38
- prompt = GoogleTranslator(source='ru', target='en').translate(prompt)
39
- print(f'\033[1mGeneration {key} translation:\033[0m {prompt}')
40
 
41
- prompt = f"{prompt} | ultra detail, ultra elaboration, ultra quality, perfect."
42
- print(f'\033[1mGeneration {key}:\033[0m {prompt}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
- # If seed is -1, generate a random seed and use it
45
  if seed == -1:
46
  seed = random.randint(1, 1000000000)
47
 
48
  payload = {
49
- "inputs": prompt,
50
- "is_negative": is_negative,
51
- "steps": steps,
52
- "cfg_scale": cfg_scale,
53
- "seed": seed,
54
- "strength": strength
 
 
 
55
  }
56
 
57
- response = requests.post(api_url, headers=headers, json=payload, timeout=timeout)
58
- if response.status_code != 200:
59
- print(f"Error: Failed to get image. Response status: {response.status_code}")
60
- print(f"Response content: {response.text}")
61
- if response.status_code == 503:
62
- raise gr.Error(f"{response.status_code} : The model is being loaded")
63
- raise gr.Error(f"{response.status_code}")
64
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  try:
66
  image_bytes = response.content
67
  image = Image.open(io.BytesIO(image_bytes))
68
- print(f'\033[1mGeneration {key} completed!\033[0m ({prompt})')
69
 
70
  # Save the image to a file and return the file path and seed
71
- output_path = f"./output_{key}.png"
 
 
72
  image.save(output_path)
73
 
74
  return output_path, seed
 
 
 
75
  except Exception as e:
76
- print(f"Error when trying to open the image: {e}")
77
- return None, None
 
 
 
 
78
 
79
  css = """
80
  #app-container {
81
- max-width: 600px;
82
  margin-left: auto;
83
  margin-right: auto;
84
  }
@@ -86,51 +160,89 @@ css = """
86
  display: flex;
87
  align-items: center;
88
  justify-content: center;
 
89
  }
90
  #title-icon {
91
- width: 32px; /* Adjust the width of the icon as needed */
92
  height: auto;
93
  margin-right: 10px; /* Space between icon and title */
94
  }
95
  #title-text {
96
- font-size: 24px; /* Adjust font size as needed */
 
 
 
97
  font-weight: bold;
98
  }
99
  """
100
 
101
- with gr.Blocks(theme='Nymbo/Nymbo_Theme', css=css) as app:
102
- gr.HTML("""
103
- <center>
104
- <div id="title-container">
105
- <img id="title-icon" src="icon.jpg" alt="Icon">
106
- <h1 id="title-text">FLUX Capacitor</h1>
107
- </div>
108
- </center>
109
- """)
110
 
111
  with gr.Column(elem_id="app-container"):
 
 
112
  with gr.Row():
113
- with gr.Column(elem_id="prompt-container"):
114
- with gr.Row():
115
- text_prompt = gr.Textbox(label="Prompt", placeholder="Enter a prompt here", lines=2, elem_id="prompt-text-input")
116
- with gr.Row():
117
- with gr.Accordion("Advanced Settings", open=False):
118
- 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")
119
- steps = gr.Slider(label="Sampling steps", value=35, minimum=1, maximum=100, step=1)
120
- cfg = gr.Slider(label="CFG Scale", value=7, minimum=1, maximum=20, step=1)
121
- method = gr.Radio(label="Sampling method", value="DPM++ 2M Karras", choices=["DPM++ 2M Karras", "DPM++ SDE Karras", "Euler", "Euler a", "Heun", "DDIM"])
122
- strength = gr.Slider(label="Strength", value=0.7, minimum=0, maximum=1, step=0.001)
123
- seed = gr.Slider(label="Seed", value=-1, minimum=-1, maximum=1000000000, step=1)
124
- huggingface_api_key = gr.Textbox(label="Hugging Face API Key (required for API calls)", placeholder="Enter your Hugging Face API Key here", type="password", elem_id="api-key")
125
- use_dev = gr.Checkbox(label="Use Dev API", value=False, elem_id="use-dev-checkbox")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  with gr.Row():
128
- text_button = gr.Button("Run", variant='primary', elem_id="gen-button")
 
 
129
  with gr.Row():
130
- image_output = gr.Image(type="pil", label="Image Output", elem_id="gallery")
131
- seed_output = gr.Textbox(label="Seed Used", elem_id="seed-output")
132
 
133
- # Adjust the click function to include the API key and use_dev as inputs
134
- text_button.click(query, inputs=[text_prompt, negative_prompt, steps, cfg, method, seed, strength, huggingface_api_key, use_dev], outputs=[image_output, seed_output])
135
-
136
- app.launch(show_api=True, share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  from PIL import Image
7
  from deep_translator import GoogleTranslator
8
 
9
+ # Create assets directory if it doesn't exist (though not strictly needed by this script anymore)
10
  # os.makedirs('assets', exist_ok=True)
11
+
12
+ # Download icon if it doesn't exist
13
  if not os.path.exists('icon.jpg'):
14
+ print("Downloading icon...")
15
+ try:
16
+ icon_url = "https://i.pinimg.com/564x/64/49/88/644988c59447eb00286834c2e70fdd6b.jpg"
17
+ response = requests.get(icon_url)
18
+ response.raise_for_status() # Raise an exception for HTTP errors
19
+ with open('icon.jpg', 'wb') as f:
20
+ f.write(response.content)
21
+ print("Icon downloaded successfully.")
22
+ except requests.exceptions.RequestException as e:
23
+ print(f"Failed to download icon.jpg: {e}. Please ensure you have internet access or place icon.jpg manually.")
24
+ # As a fallback, we can proceed without the icon if download fails.
25
+ # The gr.Image for the icon will show a broken image if 'icon.jpg' is missing.
26
+
27
  API_URL_DEV = "https://lol-v2.mxflower.eu.org/api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev"
28
  API_URL = "https://lol-v2.mxflower.eu.org/api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell"
29
  timeout = 100
30
 
31
+ def query(prompt_text, negative_prompt_text, steps=30, cfg_scale=7, sampler="DPM++ 2M Karras", seed=-1, strength=0.7, huggingface_api_key_ui=None, use_dev=False):
 
32
  api_url = API_URL_DEV if use_dev else API_URL
33
 
34
+ # Determine the API token to use
35
+ final_api_key = None
36
+ if huggingface_api_key_ui and huggingface_api_key_ui.strip(): # Check if textbox has a non-empty value
37
+ final_api_key = huggingface_api_key_ui.strip()
38
+ print("Using API key from Gradio UI input.")
 
 
39
  else:
40
+ env_token = os.getenv("HF_READ_TOKEN")
41
+ if env_token and env_token.strip():
42
+ final_api_key = env_token.strip()
43
+ print("Using API key from HF_READ_TOKEN environment variable.")
44
+ else:
45
+ raise gr.Error("Hugging Face API Key is required. Please provide it in the 'Hugging Face API Key' field or set the HF_READ_TOKEN environment variable.")
46
 
47
+ headers = {"Authorization": f"Bearer {final_api_key}"}
 
48
 
49
+ if not prompt_text or prompt_text.strip() == "":
50
+ raise gr.Error("Prompt cannot be empty.")
51
 
52
+ key = random.randint(0, 99999) # Increased range for more uniqueness
 
53
 
54
+ # Translate prompt if it seems to be in Russian (basic check, can be improved)
55
+ # For simplicity, let's assume we always try to translate.
56
+ # If it's already English, GoogleTranslator often returns it as is.
57
+ try:
58
+ translated_prompt = GoogleTranslator(source='auto', target='en').translate(prompt_text)
59
+ if translated_prompt:
60
+ print(f'\033[1mGeneration {key} translation (auto -> en):\033[0m {translated_prompt}')
61
+ prompt_to_use = translated_prompt
62
+ else:
63
+ print(f'\033[1mGeneration {key} (no translation needed or failed, using original):\033[0m {prompt_text}')
64
+ prompt_to_use = prompt_text
65
+ except Exception as e:
66
+ print(f"Error during translation: {e}. Using original prompt.")
67
+ prompt_to_use = prompt_text
68
+
69
+
70
+ # Add quality enhancers
71
+ prompt_to_use = f"{prompt_to_use} | ultra detail, ultra elaboration, ultra quality, perfect."
72
+ print(f'\033[1mGeneration {key} (final prompt):\033[0m {prompt_to_use}')
73
 
 
74
  if seed == -1:
75
  seed = random.randint(1, 1000000000)
76
 
77
  payload = {
78
+ "inputs": prompt_to_use,
79
+ "steps": int(steps), # Ensure steps is an int
80
+ "cfg_scale": float(cfg_scale), # Ensure cfg_scale is a float
81
+ "seed": int(seed), # Ensure seed is an int
82
+ "strength": float(strength) # Ensure strength is a float
83
+ # The 'sampler' parameter is not standard in basic HF Inference API for diffusers.
84
+ # It's often part of "parameters" or specific to certain model endpoints.
85
+ # For now, we'll omit it unless the custom proxy explicitly handles it.
86
+ # If "sampler" is needed, it would typically be: "parameters": {"scheduler": sampler} or similar.
87
  }
88
 
89
+ # Add negative_prompt to payload if provided
90
+ if negative_prompt_text and negative_prompt_text.strip():
91
+ payload["negative_prompt"] = negative_prompt_text.strip()
92
+ print(f'\033[1mGeneration {key} (negative prompt):\033[0m {negative_prompt_text.strip()}')
93
+
94
+
95
+ print(f"Sending payload to {api_url}: {payload}")
96
+
97
+ try:
98
+ response = requests.post(api_url, headers=headers, json=payload, timeout=timeout)
99
+ response.raise_for_status() # This will raise an HTTPError for bad responses (4xx or 5xx)
100
+ except requests.exceptions.Timeout:
101
+ raise gr.Error(f"Request timed out after {timeout} seconds. The model might be too busy or the request too complex.")
102
+ except requests.exceptions.HTTPError as e:
103
+ status_code = e.response.status_code
104
+ error_message = f"API Error: {status_code}."
105
+ try:
106
+ error_detail = e.response.json() # Try to get JSON error detail
107
+ if 'error' in error_detail:
108
+ error_message += f" Detail: {error_detail['error']}"
109
+ if 'warnings' in error_detail:
110
+ error_message += f" Warnings: {error_detail['warnings']}"
111
+ except ValueError: # If response is not JSON
112
+ error_message += f" Content: {e.response.text[:200]}" # Show first 200 chars of text response
113
+
114
+ if status_code == 503: # Model loading
115
+ error_message = f"{status_code}: The model is currently loading. Please try again in a few moments."
116
+ elif status_code == 401: # Unauthorized
117
+ error_message = f"{status_code}: Unauthorized. Check your API Key."
118
+ elif status_code == 422: # Unprocessable Entity
119
+ error_message = f"{status_code}: Unprocessable Entity. There might be an issue with the prompt or parameters. Details: {e.response.text[:200]}"
120
+
121
+ print(f"Error: Failed to get image. Response status: {status_code}")
122
+ print(f"Response content: {e.response.text}")
123
+ raise gr.Error(error_message)
124
+ except requests.exceptions.RequestException as e:
125
+ # For other network errors (DNS failure, connection refused, etc.)
126
+ print(f"Network error: {e}")
127
+ raise gr.Error(f"A network error occurred: {e}")
128
+
129
+
130
  try:
131
  image_bytes = response.content
132
  image = Image.open(io.BytesIO(image_bytes))
133
+ print(f'\033[1mGeneration {key} completed!\033[0m (Prompt: {prompt_to_use})')
134
 
135
  # Save the image to a file and return the file path and seed
136
+ # Create output directory if it doesn't exist
137
+ os.makedirs('outputs', exist_ok=True)
138
+ output_path = f"./outputs/flux_output_{key}_{seed}.png"
139
  image.save(output_path)
140
 
141
  return output_path, seed
142
+ except UnidentifiedImageError:
143
+ print(f"Error: The response from the API was not a valid image. Response text: {response.text[:500]}")
144
+ raise gr.Error("The API did not return a valid image. This might happen if the model is still loading or if there was an error with the request.")
145
  except Exception as e:
146
+ print(f"Error when trying to open or save the image: {e}")
147
+ # Log the raw response if it's not an image for debugging
148
+ if 'image_bytes' not in locals(): # if response.content was never assigned
149
+ print(f"Raw response was: {response.text[:500]}")
150
+ raise gr.Error(f"An error occurred while processing the image: {e}")
151
+
152
 
153
  css = """
154
  #app-container {
155
+ max-width: 700px; /* Slightly wider for better layout */
156
  margin-left: auto;
157
  margin-right: auto;
158
  }
 
160
  display: flex;
161
  align-items: center;
162
  justify-content: center;
163
+ margin-bottom: 10px; /* Add some space below title */
164
  }
165
  #title-icon {
166
+ width: 40px; /* Adjusted icon size */
167
  height: auto;
168
  margin-right: 10px; /* Space between icon and title */
169
  }
170
  #title-text {
171
+ font-size: 28px; /* Adjusted font size */
172
+ font-weight: bold;
173
+ }
174
+ .gr-input-label { /* Style labels for better visibility */
175
  font-weight: bold;
176
  }
177
  """
178
 
179
+ with gr.Blocks(theme='gradio/soft', css=css) as app: # Using a default theme for broader compatibility
180
+ with gr.Row(elem_id="title-container"):
181
+ if os.path.exists('icon.jpg'):
182
+ gr.Image(value='icon.jpg', width=40, height=40, show_label=False, interactive=False, elem_id="title-icon", container=False)
183
+ else:
184
+ gr.HTML("<span>🎨</span>", elem_id="title-icon") # Fallback if icon not found
185
+ gr.HTML("<h1 id='title-text'>FLUX Capacitor</h1>")
 
 
186
 
187
  with gr.Column(elem_id="app-container"):
188
+ gr.Markdown("Generate images using FLUX.1 models via a Hugging Face Inference API endpoint.")
189
+
190
  with gr.Row():
191
+ with gr.Column(scale=2): # Prompt column takes more space
192
+ text_prompt = gr.Textbox(
193
+ label="Prompt",
194
+ placeholder="Enter your creative vision here...",
195
+ lines=3,
196
+ elem_id="prompt-text-input"
197
+ )
198
+ negative_prompt = gr.Textbox(
199
+ label="Negative Prompt",
200
+ placeholder="Describe what to avoid in the image...",
201
+ 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",
202
+ lines=3,
203
+ elem_id="negative-prompt-text-input"
204
+ )
205
+ with gr.Column(scale=1): # Settings column
206
+ with gr.Accordion("Advanced Settings & API Configuration", open=False):
207
+ steps = gr.Slider(label="Sampling steps", value=30, minimum=1, maximum=100, step=1) # Adjusted default based on FLUX recommendations
208
+ cfg = gr.Slider(label="CFG Scale (Guidance Scale)", value=7.0, minimum=0.0, maximum=20.0, step=0.1) # FLUX often uses lower CFG
209
+ # Sampler method is often not directly controllable via basic HF Inf API unless proxy supports it
210
+ # method = gr.Radio(label="Sampling method", value="DPM++ 2M Karras", choices=["DPM++ 2M Karras", "DPM++ SDE Karras", "Euler", "Euler a", "Heun", "DDIM"])
211
+ strength = gr.Slider(label="Strength (for img2img/variation, less relevant for txt2img)", value=0.7, minimum=0.0, maximum=1.0, step=0.01, info="Primarily for image-to-image tasks. May have limited effect here.")
212
+ seed = gr.Slider(label="Seed", value=-1, minimum=-1, maximum=2147483647, step=1, info="Use -1 for a random seed.")
213
+ huggingface_api_key = gr.Textbox(
214
+ label="Hugging Face API Key",
215
+ placeholder="hf_xxx (Optional, uses HF_READ_TOKEN if empty)",
216
+ type="password",
217
+ elem_id="api-key"
218
+ )
219
+ use_dev = gr.Checkbox(label="Use FLUX.1-dev API (experimental, potentially slower)", value=False, elem_id="use-dev-checkbox")
220
 
221
  with gr.Row():
222
+ text_button = gr.Button("Generate Image", variant='primary', elem_id="gen-button", scale=2)
223
+
224
+ gr.Markdown("### Output")
225
  with gr.Row():
226
+ image_output = gr.Image(type="filepath", label="Generated Image", elem_id="gallery", height=512) # filepath is good for saved images
227
+ seed_output = gr.Textbox(label="Seed Used", elem_id="seed-output", interactive=False)
228
 
229
+ text_button.click(
230
+ query,
231
+ inputs=[text_prompt, negative_prompt, steps, cfg, seed, strength, huggingface_api_key, use_dev], # Removed 'method' as it's not used in payload
232
+ outputs=[image_output, seed_output]
233
+ )
234
+
235
+ gr.Markdown(
236
+ """
237
+ ---
238
+ *Notes:*
239
+ *- If the 'Hugging Face API Key' field is empty, the application will try to use the `HF_READ_TOKEN` environment variable.*
240
+ *- The `FLUX.1-schnell` model is used by default. Check 'Use FLUX.1-dev API' for the development version.*
241
+ *- Images are saved to an `outputs` subfolder in the directory where you run this script.*
242
+ *- Translation from any language to English is attempted for the prompt.*
243
+ """
244
+ )
245
+
246
+ # Ensure the app uses show_api=False if you don't intend to expose the function as an API endpoint through Gradio
247
+ # If you want to call this Gradio app's functions programmatically via its own API, set show_api=True
248
+ app.launch(show_api=False, share=False)