File size: 7,644 Bytes
7441163
 
2890c7d
7441163
 
 
9ee7032
8aa68a4
7441163
 
 
9ee7032
7441163
 
 
 
 
e55ab8a
7441163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e55ab8a
7441163
4cf5765
e55ab8a
7441163
 
 
 
 
 
 
 
 
 
 
 
4cf5765
7441163
 
 
8aa68a4
4cf5765
9ee7032
e55ab8a
7441163
 
 
 
 
 
e55ab8a
7441163
e55ab8a
 
7441163
e55ab8a
 
cbdc574
4cf5765
e55ab8a
 
426cbf1
46b8f96
7441163
e02d183
46b8f96
 
 
 
 
e02d183
 
56cc452
 
4cf5765
e02d183
 
 
8aa68a4
e02d183
 
56cc452
e02d183
56cc452
e02d183
56cc452
e02d183
 
 
 
 
 
 
 
56cc452
8aa68a4
 
 
7441163
cbdc574
e02d183
7441163
56cc452
 
 
 
e55ab8a
56cc452
 
 
e55ab8a
4cf5765
7441163
 
 
 
 
4cf5765
 
 
7441163
4cf5765
7441163
 
 
 
 
8aa68a4
e02d183
 
56cc452
 
e02d183
56cc452
 
8aa68a4
56cc452
 
 
7441163
56cc452
 
 
e55ab8a
 
7441163
e55ab8a
7441163
 
e55ab8a
 
7441163
 
 
 
e55ab8a
7441163
 
 
 
 
e55ab8a
7441163
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import os
import tempfile
from PIL import Image
import gradio as gr
from google import genai
from google.genai import types

# Helpers
def save_binary_file(file_name, data):
    with open(file_name, "wb") as f:
        f.write(data)

def generate_edit(prompt, pil_image, api_key, model="gemini-2.0-flash-exp"):
    client = genai.Client(api_key=(api_key.strip() if api_key and api_key.strip() != "" else os.environ.get("GEMINI_API_KEY")))
    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
        image_path = tmp_img.name
        pil_image.save(image_path)

    files = [client.files.upload(file=image_path)]
    contents = [
        types.Content(
            role="user",
            parts=[
                types.Part.from_uri(file_uri=files[0].uri, mime_type=files[0].mime_type),
                types.Part.from_text(text=prompt),
            ],
        ),
    ]
    generate_content_config = types.GenerateContentConfig(
        temperature=1,
        top_p=0.95,
        top_k=40,
        max_output_tokens=8192,
        response_modalities=["image", "text"],
        response_mime_type="text/plain",
    )

    text_response = ""
    image_out_path = None

    with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_out:
        out_path = tmp_out.name
        for chunk in client.models.generate_content_stream(
            model=model,
            contents=contents,
            config=generate_content_config,
        ):
            if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
                continue
            candidate = chunk.candidates[0].content.parts[0]
            if candidate.inline_data:
                save_binary_file(out_path, candidate.inline_data.data)
                image_out_path = out_path
                break
            else:
                text_response += chunk.text + "\n"
    del files
    return image_out_path, text_response

def process_image_and_prompt(pil_image, prompt, api_key):
    try:
        image_path, text_out = generate_edit(prompt, pil_image, api_key)
        if image_path:
            img = Image.open(image_path)
            if img.mode == "RGBA":
                img = img.convert("RGB")
            return img # Return only the image on success
        else:
            # If no image generated, raise an error for Gradio popup
            raise gr.Error(f"โš ๏ธ Image generation failed: {text_out.strip() if text_out.strip() else 'No specific error message.'}")
    except Exception as e:
        # Catch any other exceptions and re-raise as Gradio error
        raise gr.Error(f"โŒ Generation failed: {str(e)}")

def reset_inputs(api_key_value=None):
    # Reset all inputs, keeping API key unchanged
    return None, "", api_key_value or ""

# Styles with gradient background for body/app container
css_style = """
:root { --bg: #0f111a; --panel: #1b1e28; --text: #e9eefc; --muted: #9fb3c8; --accent: #6a8efd; }
body, .app-container {
  /* Gradient background instead of solid */
  background: linear-gradient(135deg, #2a3a67, #1b254b);
  color: var(--text);
}
.header-block { width: 100%; display: flex; justify-content: center; padding: 8px 0; }
.header-gradient { width: 100%; padding: 20px 0; border-radius: 12px; background: linear-gradient(90deg, #6a8efd, #44abc7); text-align: center; }
.header-title { margin: 0; font-size: 2.6rem; font-weight: 900; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,.25); }
.header-subtitle { margin-top: 6px; font-size: 1.05rem; color: #e8f0ff; }

.main { display: flex; gap: 20px; align-items: stretch; padding: 0 12px; }
.sidebar { width: 320px; background: #1a1e2a; padding: 14px; border-radius: 12px; min-height: 360px; box-shadow: 0 2px 10px rgb(0 0 0 / 0.25); }
.sidebar h2 { color: #8ab4ff; font-size: 1rem; margin: 6px 0; }
.sidebar ul { margin: 0; padding-left: 18px; color: #d6e3ff; line-height: 1.9; }
.sidebar a { color: #97b7ff; text-decoration: none; }
.sidebar a:hover { text-decoration: underline; }

.main-panel { flex: 1; display: flex; flex-direction: column; gap: 12px; }

.section-header { font-weight: 800; font-size: 1.04rem; color: #cbd5e1; margin: 6px 0; }

.layout-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (max-width: 1100px) {
  .layout-two-col { grid-template-columns: 1fr; }
}

#output-viewport { display: flex; justify-content: center; align-items: center; min-height: 260px; }
#output-image { display: flex; justify-content: center; align-items: center; }
#output-image img { max-width: 100%; max-height: 420px; object-fit: contain; border-radius: 12px; background: #23252b; }
"""

# Layout
with gr.Blocks(css=css_style) as app:
    gr.HTML("""
    <div class='header-block'>
      <div class='header-gradient'>
        <h1 class='header-title'>๐Ÿ–ผ๏ธ Image Editor <span style="font-size:1.1em;">(Powered by Gemini)</span> ๐Ÿ”ฎ</h1>
        <div class='header-subtitle'>Step-by-step prompts for image editing</div>
      </div>
    </div>
    """)

    with gr.Row():
        with gr.Column(scale=3, elem_classes="sidebar"):
            gr.Markdown(
                """
                <h2>๐Ÿ“– How to Use</h2>
                <ul>
                  <li>Step-by-step prompts guide the editing process.</li>
                  <li>Upload a PNG image, enter a prompt, then generate.</li>
                  <li>Keep your Gemini API key secure.</li>
                </ul>
                <hr>
                <h2>๐Ÿ”‘ API Key</h2>
                <div>Get your key here: <a href="https://aistudio.google.com/apikey" target="_blank">Get your Google API key</a></div>
                """
            )
        with gr.Column(scale=9, elem_classes="main-panel"):
            # Step 1 & Step 3 side-by-side
            with gr.Row():
                with gr.Column():
                    gr.Markdown("<div class='section-header'>Step 1: Upload Image</div>")
                    image_input = gr.Image(type="pil", label=None, image_mode="RGBA")
                with gr.Column():
                    gr.Markdown("<div class='section-header'>Step 3: Image Output</div>")
                    output_image = gr.Image(label=None, show_label=False, type="pil")
            # Step 2: Prompt + API
            gr.Markdown("<div class='section-header'>Step 2: Enter Editing Prompt</div>")
            prompt_input = gr.Textbox(label="Edit Prompt", placeholder="Describe how to edit the image", lines=2)
            api_key_input = gr.Textbox(label="Gemini API Key (required)", placeholder="Enter your Gemini API key here", type="password")

            with gr.Row():
                submit_btn = gr.Button("Generate Edit", elem_classes="gradient-button")
                reset_btn = gr.Button("Reset Inputs")
            
            # Note: Status bar elements removed as requested. Errors will now show as Gradio popups.

            def on_submit(pil_img, prompt, key):
                if not key or key.strip() == "":
                    raise gr.Error("Gemini API Key is required!")
                # process_image_and_prompt now raises gr.Error directly for failures
                return process_image_and_prompt(pil_img, prompt, key)

            submit_btn.click(
                fn=on_submit,
                inputs=[image_input, prompt_input, api_key_input],
                outputs=[output_image] # Only output the image
            )

            reset_btn.click(
                fn=reset_inputs,
                inputs=[api_key_input],
                outputs=[image_input, prompt_input, api_key_input] # Remove status_bar from outputs
            )

app.launch()