Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,144 +1,141 @@
|
|
1 |
import json
|
2 |
import os
|
|
|
|
|
3 |
import tempfile
|
4 |
-
from PIL import Image
|
5 |
import gradio as gr
|
|
|
|
|
6 |
from google import genai
|
7 |
from google.genai import types
|
8 |
import concurrent.futures
|
|
|
9 |
|
10 |
def save_binary_file(file_name, data):
|
11 |
with open(file_name, "wb") as f:
|
12 |
f.write(data)
|
13 |
|
14 |
-
def
|
15 |
-
#
|
16 |
-
non_english_chars = set("ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيپچژکگی")
|
17 |
-
return any(char in non_english_chars for char in text)
|
18 |
-
|
19 |
-
def translate_prompt(text, api_key, model="gemini-2.0-flash-exp"):
|
20 |
try:
|
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 |
for chunk in client.models.generate_content_stream(
|
47 |
model=model,
|
48 |
contents=contents,
|
49 |
config=generate_content_config,
|
50 |
):
|
51 |
-
if chunk.candidates
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
text_response += chunk.text + "\n"
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
except Exception as e:
|
57 |
-
print(f"Translation error: {str(e)}")
|
58 |
-
return text
|
59 |
-
|
60 |
-
def generate_with_api(api_key, text, file_name, model="gemini-2.0-flash-exp"):
|
61 |
-
try:
|
62 |
-
client = genai.Client(api_key=api_key.strip())
|
63 |
-
files = [client.files.upload(file=file_name)]
|
64 |
-
|
65 |
-
pre_prompt = (
|
66 |
-
"The following instruction is in English. Process it carefully. "
|
67 |
-
"Your task is to edit the image based on the instruction. "
|
68 |
-
"If the instruction asks to change text in the image, identify the existing text and replace it. "
|
69 |
-
"Ensure the new text matches the style, font, and position of the original text."
|
70 |
-
)
|
71 |
-
full_text = pre_prompt + "\n" + text
|
72 |
-
|
73 |
-
contents = [
|
74 |
-
types.Content(
|
75 |
-
role="user",
|
76 |
-
parts=[
|
77 |
-
types.Part.from_uri(
|
78 |
-
file_uri=files[0].uri,
|
79 |
-
mime_type=files[0].mime_type,
|
80 |
-
),
|
81 |
-
types.Part.from_text(text=full_text),
|
82 |
-
],
|
83 |
-
),
|
84 |
-
]
|
85 |
-
|
86 |
-
generate_content_config = types.GenerateContentConfig(
|
87 |
-
temperature=1,
|
88 |
-
top_p=0.95,
|
89 |
-
top_k=40,
|
90 |
-
max_output_tokens=8192,
|
91 |
-
response_modalities=["image", "text"],
|
92 |
-
response_mime_type="text/plain",
|
93 |
-
)
|
94 |
-
|
95 |
-
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
|
96 |
-
temp_path = tmp.name
|
97 |
-
for chunk in client.models.generate_content_stream(
|
98 |
-
model=model,
|
99 |
-
contents=contents,
|
100 |
-
config=generate_content_config,
|
101 |
-
):
|
102 |
-
if chunk.candidates and chunk.candidates[0].content and chunk.candidates[0].content.parts:
|
103 |
-
candidate = chunk.candidates[0].content.parts[0]
|
104 |
-
if candidate.inline_data:
|
105 |
-
save_binary_file(temp_path, candidate.inline_data.data)
|
106 |
-
return temp_path, ""
|
107 |
-
elif candidate.text:
|
108 |
-
return None, candidate.text
|
109 |
-
|
110 |
-
return None, "No response from API"
|
111 |
-
|
112 |
-
except Exception as e:
|
113 |
-
return None, f"API Error: {str(e)}"
|
114 |
-
finally:
|
115 |
-
if 'files' in locals():
|
116 |
-
del files
|
117 |
|
118 |
def process_single_api(api_key, prompt, file_name, model):
|
119 |
if not api_key:
|
120 |
return None, "API key not provided"
|
121 |
|
122 |
try:
|
123 |
-
|
124 |
-
|
125 |
-
processed_prompt = translate_prompt(prompt, api_key, model)
|
126 |
-
else:
|
127 |
-
processed_prompt = prompt
|
128 |
-
|
129 |
-
# Generate image with dedicated API
|
130 |
-
image_path, text_response = generate_with_api(api_key, processed_prompt, file_name, model)
|
131 |
|
132 |
if image_path:
|
133 |
result_img = Image.open(image_path)
|
134 |
if result_img.mode == "RGBA":
|
135 |
result_img = result_img.convert("RGB")
|
136 |
return result_img, ""
|
137 |
-
|
138 |
return None, text_response if text_response else "No image generated"
|
139 |
|
140 |
except Exception as e:
|
141 |
-
return None, f"
|
142 |
|
143 |
def process_image_and_prompt(composite_pil, prompt):
|
144 |
try:
|
@@ -157,13 +154,13 @@ def process_image_and_prompt(composite_pil, prompt):
|
|
157 |
result_images = []
|
158 |
error_messages = []
|
159 |
|
160 |
-
#
|
161 |
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
162 |
futures = {
|
163 |
executor.submit(
|
164 |
process_single_api,
|
165 |
api_key, prompt, composite_path, model
|
166 |
-
): api_key
|
167 |
}
|
168 |
|
169 |
for future in concurrent.futures.as_completed(futures):
|
@@ -171,18 +168,17 @@ def process_image_and_prompt(composite_pil, prompt):
|
|
171 |
if image:
|
172 |
result_images.append(image)
|
173 |
if error:
|
174 |
-
error_messages.append(
|
175 |
|
176 |
os.unlink(composite_path)
|
177 |
|
178 |
if not result_images:
|
179 |
-
|
180 |
-
return None, f"{error_msg}\n\nPlease check your command and try again."
|
181 |
|
182 |
return result_images, ""
|
183 |
|
184 |
except Exception as e:
|
185 |
-
|
186 |
|
187 |
css = """
|
188 |
footer { visibility: hidden; }
|
@@ -192,45 +188,86 @@ display: none !important;
|
|
192 |
}
|
193 |
"""
|
194 |
|
195 |
-
with gr.Blocks(css=css) as demo:
|
196 |
-
gr.HTML(
|
|
|
197 |
<div class="header-container">
|
198 |
-
<div
|
199 |
-
<
|
|
|
|
|
|
|
|
|
200 |
</div>
|
201 |
-
"""
|
|
|
202 |
|
203 |
-
with gr.Accordion("⚠️ راهنمای استفاده", open=False):
|
204 |
gr.Markdown("""
|
205 |
### راهنمای استفاده
|
206 |
- تصویر خود را آپلود کرده و دستور ویرایش را وارد کنید
|
207 |
-
-
|
208 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
""")
|
210 |
|
211 |
-
with gr.Row():
|
212 |
-
with gr.Column():
|
213 |
-
image_input = gr.Image(
|
214 |
-
|
215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
216 |
|
217 |
-
with gr.Column():
|
218 |
-
output_gallery = gr.Gallery(label="تصاویر ویرایش شده")
|
219 |
-
output_text = gr.Textbox(
|
|
|
|
|
|
|
|
|
220 |
|
221 |
submit_btn.click(
|
222 |
-
process_image_and_prompt,
|
223 |
inputs=[image_input, prompt_input],
|
224 |
-
outputs=[output_gallery, output_text]
|
225 |
)
|
226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
gr.Examples(
|
228 |
-
examples=
|
229 |
-
|
230 |
-
|
231 |
-
["examples/3.jpg", "حذف شیء قرمز رنگ از تصویر"],
|
232 |
-
],
|
233 |
-
inputs=[image_input, prompt_input]
|
234 |
)
|
235 |
|
236 |
-
demo.queue(
|
|
|
1 |
import json
|
2 |
import os
|
3 |
+
import time
|
4 |
+
import uuid
|
5 |
import tempfile
|
6 |
+
from PIL import Image, ImageDraw, ImageFont
|
7 |
import gradio as gr
|
8 |
+
import base64
|
9 |
+
import mimetypes
|
10 |
from google import genai
|
11 |
from google.genai import types
|
12 |
import concurrent.futures
|
13 |
+
import langdetect
|
14 |
|
15 |
def save_binary_file(file_name, data):
|
16 |
with open(file_name, "wb") as f:
|
17 |
f.write(data)
|
18 |
|
19 |
+
def translate_prompt_to_english(text, api_key, model="gemini-2.0-flash-exp"):
|
20 |
+
# Detect the language of the input text
|
|
|
|
|
|
|
|
|
21 |
try:
|
22 |
+
detected_lang = langdetect.detect(text)
|
23 |
+
# If the language is English, return the text as is
|
24 |
+
if detected_lang == "en":
|
25 |
+
return text
|
26 |
+
except:
|
27 |
+
pass
|
28 |
+
|
29 |
+
client = genai.Client(api_key=api_key.strip())
|
30 |
+
|
31 |
+
pre_prompt = (
|
32 |
+
"Translate the following text to English accurately and naturally, as a native English speaker would phrase it. "
|
33 |
+
"Keep the translation concise and clear, avoiding unnecessary words or literal translations. "
|
34 |
+
"For example, if the text is 'متن را به \"امیر\" تغییر بده', translate it to 'Change the text to \"Amir\"'. "
|
35 |
+
"Do not include any additional explanations or context in the translation."
|
36 |
+
)
|
37 |
+
full_text = pre_prompt + "\n" + text
|
38 |
+
|
39 |
+
contents = [
|
40 |
+
types.Content(
|
41 |
+
role="user",
|
42 |
+
parts=[
|
43 |
+
types.Part.from_text(text=full_text),
|
44 |
+
],
|
45 |
+
),
|
46 |
+
]
|
47 |
+
generate_content_config = types.GenerateContentConfig(
|
48 |
+
temperature=0.5,
|
49 |
+
top_p=0.9,
|
50 |
+
top_k=40,
|
51 |
+
max_output_tokens=8192,
|
52 |
+
response_mime_type="text/plain",
|
53 |
+
)
|
54 |
+
|
55 |
+
text_response = ""
|
56 |
+
for chunk in client.models.generate_content_stream(
|
57 |
+
model=model,
|
58 |
+
contents=contents,
|
59 |
+
config=generate_content_config,
|
60 |
+
):
|
61 |
+
if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
|
62 |
+
continue
|
63 |
+
text_response += chunk.text + "\n"
|
64 |
+
|
65 |
+
return text_response.strip()
|
66 |
+
|
67 |
+
def generate_with_api(api_key, text, file_name, model="gemini-2.0-flash-exp"):
|
68 |
+
client = genai.Client(api_key=api_key.strip())
|
69 |
+
files = [client.files.upload(file=file_name)]
|
70 |
+
|
71 |
+
pre_prompt = (
|
72 |
+
"The following instruction is in English. Process it carefully. "
|
73 |
+
"Your task is to edit the image based on the instruction. If the instruction asks to change the text in the image, identify the existing text in the image and replace it with the new text specified in the instruction. "
|
74 |
+
"For example, if the instruction says 'Change the text to \"Amir\"', find the text in the image (e.g., 'HONEY') and replace it with 'Amir'. Do not use the instruction text itself as the new text. "
|
75 |
+
"Ensure the new text matches the style, font, and position of the original text as closely as possible. Generate an edited image with the changes applied."
|
76 |
+
)
|
77 |
+
full_text = pre_prompt + "\n" + text
|
78 |
+
|
79 |
+
contents = [
|
80 |
+
types.Content(
|
81 |
+
role="user",
|
82 |
+
parts=[
|
83 |
+
types.Part.from_uri(
|
84 |
+
file_uri=files[0].uri,
|
85 |
+
mime_type=files[0].mime_type,
|
86 |
+
),
|
87 |
+
types.Part.from_text(text=full_text),
|
88 |
+
],
|
89 |
+
),
|
90 |
+
]
|
91 |
+
generate_content_config = types.GenerateContentConfig(
|
92 |
+
temperature=1,
|
93 |
+
top_p=0.95,
|
94 |
+
top_k=40,
|
95 |
+
max_output_tokens=8192,
|
96 |
+
response_modalities=["image", "text"],
|
97 |
+
response_mime_type="text/plain",
|
98 |
+
)
|
99 |
+
|
100 |
+
text_response = ""
|
101 |
+
image_path = None
|
102 |
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
|
103 |
+
temp_path = tmp.name
|
104 |
for chunk in client.models.generate_content_stream(
|
105 |
model=model,
|
106 |
contents=contents,
|
107 |
config=generate_content_config,
|
108 |
):
|
109 |
+
if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
|
110 |
+
continue
|
111 |
+
candidate = chunk.candidates[0].content.parts[0]
|
112 |
+
if candidate.inline_data:
|
113 |
+
save_binary_file(temp_path, candidate.inline_data.data)
|
114 |
+
image_path = temp_path
|
115 |
+
break
|
116 |
+
else:
|
117 |
text_response += chunk.text + "\n"
|
118 |
|
119 |
+
del files
|
120 |
+
return image_path, text_response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
|
122 |
def process_single_api(api_key, prompt, file_name, model):
|
123 |
if not api_key:
|
124 |
return None, "API key not provided"
|
125 |
|
126 |
try:
|
127 |
+
translated_prompt = translate_prompt_to_english(prompt, api_key, model)
|
128 |
+
image_path, text_response = generate_with_api(api_key, translated_prompt, file_name, model)
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
if image_path:
|
131 |
result_img = Image.open(image_path)
|
132 |
if result_img.mode == "RGBA":
|
133 |
result_img = result_img.convert("RGB")
|
134 |
return result_img, ""
|
|
|
135 |
return None, text_response if text_response else "No image generated"
|
136 |
|
137 |
except Exception as e:
|
138 |
+
return None, f"Error with API {api_key[-4:]}: {str(e)}"
|
139 |
|
140 |
def process_image_and_prompt(composite_pil, prompt):
|
141 |
try:
|
|
|
154 |
result_images = []
|
155 |
error_messages = []
|
156 |
|
157 |
+
# اجرای همزمان با 4 thread (هر API در یک thread جدا)
|
158 |
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
159 |
futures = {
|
160 |
executor.submit(
|
161 |
process_single_api,
|
162 |
api_key, prompt, composite_path, model
|
163 |
+
): api_key for api_key in api_keys
|
164 |
}
|
165 |
|
166 |
for future in concurrent.futures.as_completed(futures):
|
|
|
168 |
if image:
|
169 |
result_images.append(image)
|
170 |
if error:
|
171 |
+
error_messages.append(error)
|
172 |
|
173 |
os.unlink(composite_path)
|
174 |
|
175 |
if not result_images:
|
176 |
+
return None, "\n".join(error_messages) + "\n\n**توجه**: اگر تصویر تولید نشد، لطفاً دستور خود را واضحتر بنویسید یا دوباره امتحان کنید."
|
|
|
177 |
|
178 |
return result_images, ""
|
179 |
|
180 |
except Exception as e:
|
181 |
+
raise gr.Error(f"خطا در پردازش: {e}", duration=5)
|
182 |
|
183 |
css = """
|
184 |
footer { visibility: hidden; }
|
|
|
188 |
}
|
189 |
"""
|
190 |
|
191 |
+
with gr.Blocks(css_paths="style.css", css=css) as demo:
|
192 |
+
gr.HTML(
|
193 |
+
"""
|
194 |
<div class="header-container">
|
195 |
+
<div>
|
196 |
+
<img src="https://uploadkon.ir/uploads/4a3e22_25IMG-%DB%B2%DB%B0%DB%B2%DB%B5%DB%B0%DB%B3%DB%B2%DB%B2-%DB%B1%DB%B7%DB%B1%DB%B8%DB%B5%DB%B2.jpg" alt="Alfa AI logo">
|
197 |
+
</div>
|
198 |
+
<div>
|
199 |
+
<h1>ویرایش جادویی تصاویر با هوش مصنوعی آلفا</h1>
|
200 |
+
</div>
|
201 |
</div>
|
202 |
+
"""
|
203 |
+
)
|
204 |
|
205 |
+
with gr.Accordion("⚠️ راهنمای استفاده", open=False, elem_classes="config-accordion"):
|
206 |
gr.Markdown("""
|
207 |
### راهنمای استفاده
|
208 |
- تصویر خود را آپلود کرده و دستور ویرایش را وارد کنید
|
209 |
+
- در صورت بروز خطا، پیام مربوطه نمایش داده خواهد شد
|
210 |
+
- فقط تصاویر با فرمت PNG آپلود کنید
|
211 |
+
- از آپلود تصاویر نامناسب خودداری کنید
|
212 |
+
""")
|
213 |
+
|
214 |
+
with gr.Accordion("📌 دستورالعملهای ویرایش", open=False, elem_classes="instructions-accordion"):
|
215 |
+
gr.Markdown("""
|
216 |
+
### نمونه دستورات ویرایش
|
217 |
+
- متن تصویر را به \"متن جدید\" تغییر بده
|
218 |
+
- شیء خاصی را از تصویر حذف کن
|
219 |
+
- استایل خاصی به بخشی از تصویر اضافه کن
|
220 |
+
- تغییرات رنگی روی تصویر اعمال کن
|
221 |
""")
|
222 |
|
223 |
+
with gr.Row(elem_classes="main-content"):
|
224 |
+
with gr.Column(elem_classes="input-column"):
|
225 |
+
image_input = gr.Image(
|
226 |
+
type="pil",
|
227 |
+
label="تصویر را آپلود کنید",
|
228 |
+
image_mode="RGBA",
|
229 |
+
elem_id="image-input",
|
230 |
+
elem_classes="upload-box"
|
231 |
+
)
|
232 |
+
prompt_input = gr.Textbox(
|
233 |
+
lines=2,
|
234 |
+
placeholder="تصویر چیکار بشه؟ اینجا بنویسید...",
|
235 |
+
label="دستور ویرایش",
|
236 |
+
elem_classes="prompt-input"
|
237 |
+
)
|
238 |
+
submit_btn = gr.Button("اعمال تغییرات", elem_classes="generate-btn")
|
239 |
|
240 |
+
with gr.Column(elem_classes="output-column"):
|
241 |
+
output_gallery = gr.Gallery(label="تصاویر ویرایش شده", elem_classes="output-gallery")
|
242 |
+
output_text = gr.Textbox(
|
243 |
+
label="پیام سیستم",
|
244 |
+
placeholder="در صورت بروز خطا، پیام مربوطه اینجا نمایش داده میشود.",
|
245 |
+
elem_classes="output-text"
|
246 |
+
)
|
247 |
|
248 |
submit_btn.click(
|
249 |
+
fn=process_image_and_prompt,
|
250 |
inputs=[image_input, prompt_input],
|
251 |
+
outputs=[output_gallery, output_text],
|
252 |
)
|
253 |
|
254 |
+
gr.Markdown("## نمونههای آماده", elem_classes="gr-examples-header")
|
255 |
+
|
256 |
+
examples = [
|
257 |
+
["data/1.webp", 'متن را به "امیر" تغییر بده', ""],
|
258 |
+
["data/2.webp", "قاشق را از دست حذف کن", ""],
|
259 |
+
["data/3.webp", 'متن را به "بساز" تغییر بده', ""],
|
260 |
+
["data/1.jpg", "فقط روی صورت استایل جوکر اضافه کن", ""],
|
261 |
+
["data/1777043.jpg", "فقط روی صورت استایل جوکر اضافه کن", ""],
|
262 |
+
["data/2807615.jpg", "فقط روی لبها رژ لب اضافه کن", ""],
|
263 |
+
["data/76860.jpg", "فقط روی لبها رژ لب اضافه کن", ""],
|
264 |
+
["data/2807615.jpg", "فقط صورت را شادتر کن", ""],
|
265 |
+
]
|
266 |
+
|
267 |
gr.Examples(
|
268 |
+
examples=examples,
|
269 |
+
inputs=[image_input, prompt_input],
|
270 |
+
elem_id="examples-grid"
|
|
|
|
|
|
|
271 |
)
|
272 |
|
273 |
+
demo.queue(max_size=50).launch()
|