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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +38 -29
app.py CHANGED
@@ -5,7 +5,6 @@ import gradio as gr
5
  from google import genai
6
  from google.genai import types
7
 
8
- # Helpers
9
  def save_binary_file(file_name, data):
10
  with open(file_name, "wb") as f:
11
  f.write(data)
@@ -15,6 +14,7 @@ def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
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(
@@ -33,8 +33,10 @@ def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
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,7 +53,12 @@ def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
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):
@@ -78,37 +85,42 @@ def process_image_and_prompt(pil_image, prompt, api_key, progress_callback=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'>
@@ -118,6 +130,7 @@ with gr.Blocks(css=css_style) as app:
118
  </div>
119
  </div>
120
  """)
 
121
  with gr.Row():
122
  with gr.Column(scale=3, elem_classes="sidebar"):
123
  gr.Markdown(
@@ -134,17 +147,15 @@ with gr.Blocks(css=css_style) as app:
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")
@@ -154,10 +165,8 @@ with gr.Blocks(css=css_style) as app:
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!")
 
5
  from google import genai
6
  from google.genai import types
7
 
 
8
  def save_binary_file(file_name, data):
9
  with open(file_name, "wb") as f:
10
  f.write(data)
 
14
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
15
  image_path = tmp_img.name
16
  pil_image.save(image_path)
17
+
18
  files = [client.files.upload(file=image_path)]
19
  contents = [
20
  types.Content(
 
33
  response_modalities=["image", "text"],
34
  response_mime_type="text/plain",
35
  )
36
+
37
  text_response = ""
38
  image_out_path = None
39
+
40
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_out:
41
  out_path = tmp_out.name
42
  for chunk in client.models.generate_content_stream(
 
53
  break
54
  else:
55
  text_response += chunk.text + "\n"
56
+
57
+ try:
58
+ del files
59
+ except Exception:
60
+ pass
61
+
62
  return image_out_path, text_response
63
 
64
  def process_image_and_prompt(pil_image, prompt, api_key, progress_callback=None):
 
85
  def reset_inputs(api_key_value=None):
86
  return None, "", api_key_value or "", ""
87
 
88
+ # CSS: two-column layout with responsive behavior; status bar kept separate
89
  css_style = """
90
+ :root { --bg: #0f111a; --panel: #1b1e28; --text: #e9eefc; --muted: #9fb3c8; --accent: #6a8efd; }
91
  body, .app-container { background: var(--bg); color: var(--text); }
92
+ .header-block { width: 100%; display: flex; justify-content: center; padding: 8px 0; }
93
+ .header-gradient { width: 100%; padding: 20px 0; border-radius: 12px; background: linear-gradient(90deg, #6a8efd, #44abc7); text-align: center; }
94
  .header-title { margin: 0; font-size: 2.6rem; font-weight: 900; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,.25); }
95
  .header-subtitle { margin-top: 6px; font-size: 1.05rem; color: #e8f0ff; }
96
 
97
+ .main { display: flex; gap: 20px; align-items: stretch; padding: 0 12px; }
98
+ .sidebar { width: 320px; background: #1a1e2a; padding: 14px; border-radius: 12px; min-height: 360px; box-shadow: 0 2px 10px rgb(0 0 0 / 0.25); }
99
+ .sidebar h2 { color: #8ab4ff; font-size: 1rem; margin: 6px 0; }
100
+ .sidebar ul { margin: 0; padding-left: 18px; color: #d6e2ff; line-height: 1.9; }
101
+ .sidebar a { color: #97b7ff; text-decoration: none; }
102
+ .sidebar a:hover { text-decoration: underline; }
103
 
104
+ .main-panel { flex: 1; display: flex; flex-direction: column; gap: 12px; }
 
105
 
106
+ .section-header { font-weight: 800; font-size: 1.04rem; color: #cbd5e1; margin: 6px 0; }
107
 
108
+ .layout-two-col {
109
+ display: grid;
110
+ grid-template-columns: 1fr 1fr;
111
+ gap: 14px;
112
+ }
113
+ @media (max-width: 1100px) {
114
+ .layout-two-col { grid-template-columns: 1fr; }
115
+ }
116
  .input-area, .output-area { background: #202533; border-radius: 12px; padding: 12px; }
117
+ #status-text { height: 1.4em; line-height: 1.4em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 8px; border: none; background: transparent; font-weight: 700; color: #cbd5e1; }
118
+ #output-image { display: flex; justify-content: center; align-items: center; padding: 6px; }
 
119
  #output-image img { max-width: 100%; max-height: 420px; object-fit: contain; border-radius: 12px; background: #23252b; }
120
 
 
 
 
 
121
  """
122
 
123
+ # Layout
124
  with gr.Blocks(css=css_style) as app:
125
  gr.HTML("""
126
  <div class='header-block'>
 
130
  </div>
131
  </div>
132
  """)
133
+
134
  with gr.Row():
135
  with gr.Column(scale=3, elem_classes="sidebar"):
136
  gr.Markdown(
 
147
  """
148
  )
149
  with gr.Column(scale=9, elem_classes="main-panel"):
150
+ # Step 1 & Step 3 side-by-side area
151
+ with gr.Row():
152
+ with gr.Column():
 
153
  gr.Markdown("<div class='section-header'>Step 1: Upload Image</div>")
154
  image_input = gr.Image(type="pil", label=None, image_mode="RGBA")
155
+ with gr.Column():
156
  gr.Markdown("<div class='section-header'>Step 3: Image Output</div>")
157
  output_image = gr.Image(label=None, show_label=False, type="pil")
158
+ # Step 2 inputs
 
159
  gr.Markdown("<div class='section-header'>Step 2: Enter Editing Prompt</div>")
160
  prompt_input = gr.Textbox(label="Edit Prompt", placeholder="Describe how to edit the image", lines=2)
161
  api_key_input = gr.Textbox(label="Gemini API Key (required)", placeholder="Enter your Gemini API key here", type="password")
 
165
  reset_btn = gr.Button("Reset Inputs")
166
 
167
  with gr.Row():
 
168
  status_bar = gr.Textbox(label="Status", interactive=False, lines=1, elem_id="status-text")
169
 
 
170
  def on_submit(pil_img, prompt, key, progress=None):
171
  if not key or key.strip() == "":
172
  raise gr.Error("Gemini API Key is required!")