scmlewis commited on
Commit
56cc452
·
verified ·
1 Parent(s): 85c6816

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -172
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import os
2
  import tempfile
3
- import time
4
  from PIL import Image
5
  import gradio as gr
6
  from google import genai
@@ -12,15 +11,10 @@ def save_binary_file(file_name, data):
12
  f.write(data)
13
 
14
  def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
15
- # Initialize client
16
  client = genai.Client(api_key=(api_key.strip() if api_key and api_key.strip() != "" else os.environ.get("GEMINI_API_KEY")))
17
-
18
- # Save image to a temp path for upload
19
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
20
  image_path = tmp_img.name
21
  pil_image.save(image_path)
22
-
23
- # Upload and prepare content
24
  files = [client.files.upload(file=image_path)]
25
  contents = [
26
  types.Content(
@@ -31,8 +25,6 @@ def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
31
  ],
32
  ),
33
  ]
34
-
35
- # Config with image + text modalities
36
  generate_content_config = types.GenerateContentConfig(
37
  temperature=1,
38
  top_p=0.95,
@@ -41,11 +33,8 @@ def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
41
  response_modalities=["image", "text"],
42
  response_mime_type="text/plain",
43
  )
44
-
45
  text_response = ""
46
  image_out_path = None
47
-
48
- # Streamed generation to capture inline image data
49
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_out:
50
  out_path = tmp_out.name
51
  for chunk in client.models.generate_content_stream(
@@ -62,17 +51,11 @@ def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
62
  break
63
  else:
64
  text_response += chunk.text + "\n"
65
-
66
- # Cleanup
67
- try:
68
- del files
69
- except Exception:
70
- pass
71
  return image_out_path, text_response
72
 
73
  def process_image_and_prompt(pil_image, prompt, api_key, progress_callback=None):
74
  try:
75
- # Indicate starting
76
  if progress_callback:
77
  progress_callback("Generating…")
78
  image_path, text_out = generate_edit(prompt, pil_image, api_key)
@@ -80,12 +63,10 @@ def process_image_and_prompt(pil_image, prompt, api_key, progress_callback=None)
80
  img = Image.open(image_path)
81
  if img.mode == "RGBA":
82
  img = img.convert("RGB")
83
- # success
84
  if progress_callback:
85
  progress_callback("Done ✓")
86
  return img, "Image generated successfully!", None
87
  else:
88
- # fail to generate image
89
  if progress_callback:
90
  progress_callback("Failed to generate image")
91
  return None, f"⚠️ {text_out.strip()}", None
@@ -97,146 +78,47 @@ def process_image_and_prompt(pil_image, prompt, api_key, progress_callback=None)
97
  def reset_inputs(api_key_value=None):
98
  return None, "", api_key_value or "", ""
99
 
100
- # Styles
101
  css_style = """
102
- :root {
103
- --bg: #14161c;
104
- --panel: #1e1f25;
105
- --text: #e8eaf6;
106
- --muted: #a0aec0;
107
- --accent: #6a8efd;
108
- }
109
- body, .app-container {
110
- background: var(--bg);
111
- color: var(--text);
112
- }
113
- .header-block {
114
- width: 100%;
115
- display: flex;
116
- align-items: center;
117
- justify-content: center;
118
- padding: 18px;
119
- }
120
- .header-gradient {
121
- width: 100%;
122
- padding: 28px 0;
123
- border-radius: 14px;
124
- background: linear-gradient(90deg, #6a8efd, #44abc7);
125
- box-shadow: 0 2px 12px rgb(50 50 70 / 12%);
126
- text-align: center;
127
- }
128
- .header-title {
129
- margin: 0;
130
- font-size: 2.8rem;
131
- font-weight: 900;
132
- color: #fff;
133
- text-shadow: 1px 3px 12px rgba(0,0,0,.25);
134
- }
135
- .header-subtitle {
136
- margin-top: 6px;
137
- font-size: 1.05rem;
138
- color: #e8f2ff;
139
- }
140
- .gradient-button {
141
- background: linear-gradient(90deg, #44abc7, #6a8efd);
142
- color: white;
143
- font-weight: 700;
144
- border: none;
145
- padding: 12px 28px;
146
- border-radius: 10px;
147
- cursor: pointer;
148
- transition: background 0.25s ease;
149
- }
150
- .gradient-button:hover {
151
- background: linear-gradient(90deg, #6a8efd, #44abc7);
152
- }
153
- .main {
154
- display: flex;
155
- gap: 22px;
156
- }
157
- .sidebar {
158
- background: #1f2230;
159
- padding: 20px;
160
- border-radius: 12px;
161
- min-height: 360px;
162
- width: 320px;
163
- box-shadow: 0 2px 10px rgb(0 0 0 / 0.25);
164
- }
165
- .sidebar h2 {
166
- color: #8ab4ff;
167
- font-size: 1rem;
168
- margin: 6px 0 8px;
169
- }
170
- .sidebar ul {
171
- margin: 0;
172
- padding-left: 18px;
173
- color: #dbeafe;
174
- line-height: 1.8;
175
- }
176
- .sidebar a { color: #97b7ff; text-decoration: none; }
177
- .sidebar a:hover { text-decoration: underline; }
178
 
179
- .main-panel {
180
- flex: 1;
181
- min-width: 0;
182
- }
183
- .section-header {
184
- font-size: 1.15rem;
185
- font-weight: 700;
186
- color: #cbd5e1;
187
- margin: 8px 0;
188
- }
189
- .input-area, .output-area {
190
- background: #1b1e28;
191
- border-radius: 12px;
192
- padding: 14px;
193
- box-shadow: inset 0 0 0 rgba(0,0,0,0.0);
194
- }
195
- .input-area { margin-bottom: 12px; }
196
- .output-area { margin-top: 6px; text-align: center; }
197
- #status-text {
198
- height: 1.2em;
199
- line-height: 1.2em;
200
- font-weight: 600;
201
- text-align: left;
202
- overflow: hidden;
203
- white-space: nowrap;
204
- padding: 0;
205
- border: none;
206
- background: transparent;
207
- color: #cbd5e1;
208
- }
209
- #output-image {
210
- display: flex;
211
- justify-content: center;
212
- align-items: center;
213
- }
214
- #output-image img {
215
- max-width: 100%;
216
- max-height: 420px;
217
- width: auto;
218
- height: auto;
219
- object-fit: contain;
220
- border-radius: 12px;
221
- background: #23252b;
222
  }
223
  """
224
 
225
- # Layout
226
  with gr.Blocks(css=css_style) as app:
227
- gr.HTML(
228
- """
229
- <div class='header-block'>
230
- <div class='header-gradient'>
231
- <h1 class='header-title'>🖼️ Image Editor <span style="font-size:1.1em;">(Powered by Gemini)</span> 🔮</h1>
232
- <div class='header-subtitle'>Step-by-step prompts with a persistent status banner and progress feedback</div>
233
- </div>
234
- </div>
235
- """
236
- )
237
-
238
  with gr.Row():
239
- # Sidebar (instructions)
240
  with gr.Column(scale=3, elem_classes="sidebar"):
241
  gr.Markdown(
242
  """
@@ -251,28 +133,31 @@ with gr.Blocks(css=css_style) as app:
251
  <div>Get your key here: <a href="https://aistudio.google.com/apikey" target="_blank">Get your Google API key</a></div>
252
  """
253
  )
254
- # Main panel (steps and outputs)
255
  with gr.Column(scale=9, elem_classes="main-panel"):
256
- with gr.Column():
257
- # Step 1: Upload Image
258
- gr.Markdown("<div class='section-header'>Step 1: Upload Image</div>")
259
- image_input = gr.Image(type="pil", label=None, image_mode="RGBA")
 
 
 
 
 
260
 
261
- # Step 2: Prompt + API Key
262
- gr.Markdown("<div class='section-header'>Step 2: Enter Editing Prompt</div>")
263
- prompt_input = gr.Textbox(label="Edit Prompt", placeholder="Describe how to edit the image", lines=2)
264
- api_key_input = gr.Textbox(label="Gemini API Key (required)", placeholder="Enter your Gemini API key here", type="password")
265
 
266
- with gr.Row():
267
- submit_btn = gr.Button("Generate Edit", elem_classes="gradient-button")
268
- reset_btn = gr.Button("Reset Inputs")
269
 
270
- # Step 3: Output
271
- gr.Markdown("<div class='section-header'>Step 3: Image Output</div>")
272
- output_image = gr.Image(label=None, show_label=False, type="pil")
273
- status_text = gr.Textbox(label="Status", interactive=False, lines=1, elem_id="status-text")
274
 
275
- # Callback wiring
276
  def on_submit(pil_img, prompt, key, progress=None):
277
  if not key or key.strip() == "":
278
  raise gr.Error("Gemini API Key is required!")
@@ -282,13 +167,13 @@ with gr.Blocks(css=css_style) as app:
282
  submit_btn.click(
283
  fn=on_submit,
284
  inputs=[image_input, prompt_input, api_key_input],
285
- outputs=[output_image, status_text]
286
  )
287
 
288
  reset_btn.click(
289
  fn=reset_inputs,
290
  inputs=[api_key_input],
291
- outputs=[image_input, prompt_input, api_key_input, status_text]
292
  )
293
 
294
  app.launch()
 
1
  import os
2
  import tempfile
 
3
  from PIL import Image
4
  import gradio as gr
5
  from google import genai
 
11
  f.write(data)
12
 
13
  def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
 
14
  client = genai.Client(api_key=(api_key.strip() if api_key and api_key.strip() != "" else os.environ.get("GEMINI_API_KEY")))
 
 
15
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
16
  image_path = tmp_img.name
17
  pil_image.save(image_path)
 
 
18
  files = [client.files.upload(file=image_path)]
19
  contents = [
20
  types.Content(
 
25
  ],
26
  ),
27
  ]
 
 
28
  generate_content_config = types.GenerateContentConfig(
29
  temperature=1,
30
  top_p=0.95,
 
33
  response_modalities=["image", "text"],
34
  response_mime_type="text/plain",
35
  )
 
36
  text_response = ""
37
  image_out_path = None
 
 
38
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_out:
39
  out_path = tmp_out.name
40
  for chunk in client.models.generate_content_stream(
 
51
  break
52
  else:
53
  text_response += chunk.text + "\n"
54
+ del files
 
 
 
 
 
55
  return image_out_path, text_response
56
 
57
  def process_image_and_prompt(pil_image, prompt, api_key, progress_callback=None):
58
  try:
 
59
  if progress_callback:
60
  progress_callback("Generating…")
61
  image_path, text_out = generate_edit(prompt, pil_image, api_key)
 
63
  img = Image.open(image_path)
64
  if img.mode == "RGBA":
65
  img = img.convert("RGB")
 
66
  if progress_callback:
67
  progress_callback("Done ✓")
68
  return img, "Image generated successfully!", None
69
  else:
 
70
  if progress_callback:
71
  progress_callback("Failed to generate image")
72
  return None, f"⚠️ {text_out.strip()}", None
 
78
  def reset_inputs(api_key_value=None):
79
  return None, "", api_key_value or "", ""
80
 
 
81
  css_style = """
82
+ :root { --bg:#0f111a; --panel:#151a24; --text:#e9eefc; --muted:#9fb3c8; --accent:#6a8efd; }
83
+ body, .app-container { background: var(--bg); color: var(--text); }
84
+ .header-block { width: 100%; display: flex; justify-content: center; padding: 12px 0; }
85
+ .header-gradient { width: 100%; padding: 22px 0; border-radius: 12px; background: linear-gradient(90deg, #6a8efd, #44abc7); box-shadow: 0 2px 12px rgb(50 50 70 / 12%); text-align: center; }
86
+ .header-title { margin: 0; font-size: 2.6rem; font-weight: 900; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,.25); }
87
+ .header-subtitle { margin-top: 6px; font-size: 1.05rem; color: #e8f0ff; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
+ .main { display: flex; gap: 20px; align-items: stretch; padding: 0 16px; }
90
+ .sidebar { width: 320px; background: #1b1f2b; padding: 16px; border-radius: 12px; }
91
+ .sidebar h2 { color: #89b4ff; font-size: 1rem; margin: 8px 0; }
92
+ .sidebar ul { margin: 0; padding-left: 20px; color: #d6e3ff; line-height: 1.9; }
93
+ .main-panel { flex: 1; display: flex; flex-direction: column; gap: 12px; }
94
+
95
+ .layout-row { display: flex; gap: 16px; align-items: flex-start; }
96
+ .left, .right { flex: 1; min-width: 0; }
97
+
98
+ .section-header { font-weight: 800; font-size: 1.05rem; color: #cbd5e1; margin: 0 0 6px 0; }
99
+
100
+ .input-area, .output-area { background: #202533; border-radius: 12px; padding: 12px; }
101
+ #status-text { height: 1.6em; line-height: 1.6em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 8px; border: none; background: transparent; color: #cbd5e1; font-weight: 700; }
102
+ #output-viewport { display: flex; justify-content: center; align-items: center; min-height: 260px; }
103
+ #output-image { width: 100%; display: flex; justify-content: center; align-items: center; }
104
+ #output-image img { max-width: 100%; max-height: 420px; object-fit: contain; border-radius: 12px; background: #23252b; }
105
+
106
+ @media (max-width: 1100px) {
107
+ .main { flex-direction: column; }
108
+ .sidebar { display: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
  """
111
 
 
112
  with gr.Blocks(css=css_style) as app:
113
+ gr.HTML("""
114
+ <div class='header-block'>
115
+ <div class='header-gradient'>
116
+ <h1 class='header-title'>🖼️ Image Editor <span style="font-size:1.1em;">(Powered by Gemini)</span> 🔮</h1>
117
+ <div class='header-subtitle'>Step-by-step prompts with a persistent status banner and progress feedback</div>
118
+ </div>
119
+ </div>
120
+ """)
 
 
 
121
  with gr.Row():
 
122
  with gr.Column(scale=3, elem_classes="sidebar"):
123
  gr.Markdown(
124
  """
 
133
  <div>Get your key here: <a href="https://aistudio.google.com/apikey" target="_blank">Get your Google API key</a></div>
134
  """
135
  )
 
136
  with gr.Column(scale=9, elem_classes="main-panel"):
137
+ # Step 1: Upload on left; Step 3: Output on right
138
+ # We’ll implement side-by-side using a two-column sub-layout
139
+ with gr.Row(class_name="layout-row"):
140
+ with gr.Column(class_name="left"):
141
+ gr.Markdown("<div class='section-header'>Step 1: Upload Image</div>")
142
+ image_input = gr.Image(type="pil", label=None, image_mode="RGBA")
143
+ with gr.Column(class_name="right"):
144
+ gr.Markdown("<div class='section-header'>Step 3: Image Output</div>")
145
+ output_image = gr.Image(label=None, show_label=False, type="pil")
146
 
147
+ # Step 2 area (prompt + API)
148
+ gr.Markdown("<div class='section-header'>Step 2: Enter Editing Prompt</div>")
149
+ prompt_input = gr.Textbox(label="Edit Prompt", placeholder="Describe how to edit the image", lines=2)
150
+ api_key_input = gr.Textbox(label="Gemini API Key (required)", placeholder="Enter your Gemini API key here", type="password")
151
 
152
+ with gr.Row():
153
+ submit_btn = gr.Button("Generate Edit", elem_classes="gradient-button")
154
+ reset_btn = gr.Button("Reset Inputs")
155
 
156
+ with gr.Row():
157
+ # Status bar (persistent)
158
+ status_bar = gr.Textbox(label="Status", interactive=False, lines=1, elem_id="status-text")
 
159
 
160
+ # Wire callbacks
161
  def on_submit(pil_img, prompt, key, progress=None):
162
  if not key or key.strip() == "":
163
  raise gr.Error("Gemini API Key is required!")
 
167
  submit_btn.click(
168
  fn=on_submit,
169
  inputs=[image_input, prompt_input, api_key_input],
170
+ outputs=[output_image, status_bar]
171
  )
172
 
173
  reset_btn.click(
174
  fn=reset_inputs,
175
  inputs=[api_key_input],
176
+ outputs=[image_input, prompt_input, api_key_input, status_bar]
177
  )
178
 
179
  app.launch()