selfitcamera commited on
Commit
0ec120e
·
0 Parent(s):

Initial commit - Encrypted version with i18n protection

Browse files
.DS_Store ADDED
Binary file (8.2 kB). View file
 
.gitattributes ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
37
+ *.webp filter=lfs diff=lfs merge=lfs -text
38
+ *.jpg filter=lfs diff=lfs merge=lfs -text
39
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ *.jpg
2
+ *.png
3
+ hf_cache/
4
+ models/
5
+ __pycache__/
README.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ImageEditPro
3
+ emoji: 🐨
4
+ colorFrom: green
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 5.44.1
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ python_version: "3.13"
12
+ short_description: AI-powered image editing tool
13
+ ---
14
+
15
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
16
+
__lib__/__init__.py ADDED
File without changes
__lib__/app.py ADDED
@@ -0,0 +1,1501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import threading
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ import time
7
+ import json
8
+ from util import process_image_edit, download_and_check_result_nsfw, GoodWebsiteUrl
9
+ from nfsw import NSFWDetector
10
+
11
+ # Google Gemini URL for restricted languages
12
+ GOOGLE_GEMINI_URL = "https://aistudio.google.com/models/gemini-2-5-flash-image"
13
+
14
+ # i18n - Load from encrypted modules
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ # Add i18n module to path
19
+ _i18n_module_path = Path(__file__).parent / "i18n"
20
+ if str(_i18n_module_path) not in sys.path:
21
+ sys.path.insert(0, str(_i18n_module_path))
22
+
23
+ # Import encrypted i18n loader
24
+ from i18n import translations as _translations
25
+ translations = _translations
26
+
27
+ def load_translations():
28
+ """Compatibility function - translations are already loaded"""
29
+ return translations
30
+
31
+ def t(key, lang="en"):
32
+ return translations.get(lang, {}).get(key, key)
33
+
34
+ # Configuration parameters
35
+ # a = b
36
+ TIP_TRY_N = 3 # Show like button tip after 12 tries
37
+ FREE_TRY_N = 6 # Free phase: first 15 tries without restrictions
38
+ SLOW_TRY_N = 10 # Slow phase start: 25 tries
39
+ SLOW2_TRY_N = 14 # Slow phase start: 32 tries
40
+ RATE_LIMIT_60 = 16 # Full restriction: blocked after 40 tries
41
+
42
+ # Time window configuration (minutes)
43
+ PHASE_1_WINDOW = 5 # 15-25 tries: 5 minutes
44
+ PHASE_2_WINDOW = 10 # 25-32 tries: 10 minutes
45
+ PHASE_3_WINDOW = 20 # 32-40 tries: 20 minutes
46
+ MAX_IMAGES_PER_WINDOW = 2 # Max images per time window
47
+ high_priority_n = 2 # 每个ip只有第一个任务是高优先级的
48
+
49
+ IP_Dict = {}
50
+ # IP generation statistics and time window tracking
51
+ IP_Generation_Count = {} # Record total generation count for each IP
52
+ IP_Rate_Limit_Track = {} # Record generation count and timestamp in current time window for each IP
53
+ IP_Country_Cache = {} # Cache IP country information to avoid repeated queries
54
+
55
+ # Country usage statistics
56
+ Country_Usage_Stats = {} # Track usage count by country
57
+ Total_Request_Count = 0 # Total request counter for periodic printing
58
+ PRINT_STATS_INTERVAL = 10 # Print stats every N requests
59
+
60
+ # Async IP query tracking
61
+ IP_Query_Results = {} # Track async query results
62
+
63
+ # Restricted countries list (these countries have lower usage limits)
64
+ RESTRICTED_COUNTRIES = ["印度", "巴基斯坦", "俄罗斯", "中国", "伊朗"]
65
+ RESTRICTED_COUNTRY_LIMIT = 1 # Max usage for restricted countries
66
+
67
+ country_dict = {
68
+ "zh": ["中国"],
69
+ "hi": ["印度"],
70
+ "fi": ["芬兰"],
71
+ "en": ["美国", "澳大利亚", "英国", "加拿大", "新西兰", "爱尔兰"],
72
+ "es": ["西班牙", "墨西哥", "阿根廷", "哥伦比亚", "智利", "秘鲁"],
73
+ "pt": ["葡萄牙", "巴西"],
74
+ "fr": ["法国", "摩纳哥"],
75
+ "de": ["德国", "奥地利", ],
76
+ "it": ["意大利", "圣马力诺", "梵蒂冈"],
77
+ "ja": ["日本"],
78
+ "ru": ["俄罗斯"],
79
+ "uk": ["乌克兰"],
80
+ "ar": ["沙特阿拉伯", "埃及", "阿拉伯联合酋长国", "摩洛哥"],
81
+ "nl":["荷兰"],
82
+ "no":["挪威"],
83
+ "sv":["瑞典"],
84
+ "id":["印度尼西亚"],
85
+ "vi": ["越南"],
86
+ "he": ["以色列"],
87
+ "tr": ["土耳其"],
88
+ "da": ["丹麦"],
89
+ }
90
+
91
+ def query_ip_country(client_ip):
92
+ """
93
+ Query IP address geo information with robust error handling
94
+
95
+ Features:
96
+ - 3 second timeout limit
97
+ - Comprehensive error handling
98
+ - Automatic fallback to default values
99
+ - Cache mechanism to avoid repeated queries
100
+
101
+ Returns:
102
+ dict: {"country": str, "region": str, "city": str}
103
+ """
104
+ # Check cache first - no API call for subsequent visits
105
+ if client_ip in IP_Country_Cache:
106
+ print(f"Using cached IP data for {client_ip}")
107
+ return IP_Country_Cache[client_ip]
108
+
109
+ # Validate IP address
110
+ if not client_ip or client_ip in ["127.0.0.1", "localhost", "::1"]:
111
+ print(f"Invalid or local IP address: {client_ip}, using default")
112
+ default_geo = {"country": "Unknown", "region": "Unknown", "city": "Unknown"}
113
+ IP_Country_Cache[client_ip] = default_geo
114
+ return default_geo
115
+
116
+ # First time visit - query API with robust error handling
117
+ print(f"Querying IP geolocation for {client_ip}...")
118
+
119
+ try:
120
+ import requests
121
+ from requests.exceptions import Timeout, ConnectionError, RequestException
122
+
123
+ api_url = f"https://api.vore.top/api/IPdata?ip={client_ip}"
124
+
125
+ # Make request with 3 second timeout
126
+ response = requests.get(api_url, timeout=3)
127
+
128
+ if response.status_code == 200:
129
+ data = response.json()
130
+ if data.get("code") == 200 and "ipdata" in data:
131
+ ipdata = data["ipdata"]
132
+ geo_info = {
133
+ "country": ipdata.get("info1", "Unknown"),
134
+ "region": ipdata.get("info2", "Unknown"),
135
+ "city": ipdata.get("info3", "Unknown")
136
+ }
137
+ IP_Country_Cache[client_ip] = geo_info
138
+ print(f"Successfully detected location for {client_ip}: {geo_info['country']}")
139
+ return geo_info
140
+ else:
141
+ print(f"API returned invalid data for {client_ip}: {data}")
142
+ else:
143
+ print(f"API request failed with status {response.status_code} for {client_ip}")
144
+
145
+ except Timeout:
146
+ print(f"Timeout (>3s) querying IP location for {client_ip}, using default")
147
+ except ConnectionError:
148
+ print(f"Network connection error for IP {client_ip}, using default")
149
+ except RequestException as e:
150
+ print(f"Request error for IP {client_ip}: {e}, using default")
151
+ except Exception as e:
152
+ print(f"Unexpected error querying IP {client_ip}: {e}, using default")
153
+
154
+ # All failures lead here - cache default and return
155
+ default_geo = {"country": "Unknown", "region": "Unknown", "city": "Unknown"}
156
+ IP_Country_Cache[client_ip] = default_geo
157
+ print(f"Cached default location for {client_ip}")
158
+ return default_geo
159
+
160
+ def query_ip_country_async(client_ip):
161
+ """
162
+ Async version that returns immediately with default, then updates cache in background
163
+
164
+ Returns:
165
+ tuple: (immediate_lang, geo_info_or_none)
166
+ """
167
+ # If already cached, return immediately
168
+ if client_ip in IP_Country_Cache:
169
+ geo_info = IP_Country_Cache[client_ip]
170
+ lang = get_lang_from_country(geo_info["country"])
171
+ return lang, geo_info
172
+
173
+ # Return default immediately, query in background
174
+ return "en", None
175
+
176
+ def get_lang_from_country(country):
177
+ """
178
+ Map country name to language code with comprehensive validation
179
+
180
+ Features:
181
+ - Handles invalid/empty input
182
+ - Case-insensitive matching
183
+ - Detailed logging
184
+ - Always returns valid language code
185
+
186
+ Args:
187
+ country (str): Country name
188
+
189
+ Returns:
190
+ str: Language code (always valid, defaults to "en")
191
+ """
192
+ # Input validation
193
+ if not country or not isinstance(country, str) or country.strip() == "":
194
+ print(f"Invalid country provided: '{country}', defaulting to English")
195
+ return "en"
196
+
197
+ # Normalize country name
198
+ country = country.strip()
199
+ if country.lower() == "unknown":
200
+ print(f"Unknown country, defaulting to English")
201
+ return "en"
202
+
203
+ try:
204
+ # Search in country dictionary with case-sensitive match first
205
+ for lang, countries in country_dict.items():
206
+ if country in countries:
207
+ print(f"Matched country '{country}' to language '{lang}'")
208
+ return lang
209
+
210
+ # If no exact match, try case-insensitive match
211
+ country_lower = country.lower()
212
+ for lang, countries in country_dict.items():
213
+ for country_variant in countries:
214
+ if country_variant.lower() == country_lower:
215
+ print(f"Case-insensitive match: country '{country}' to language '{lang}'")
216
+ return lang
217
+
218
+ # No match found
219
+ print(f"Country '{country}' not found in country_dict, defaulting to English")
220
+ return "en"
221
+
222
+ except Exception as e:
223
+ print(f"Error matching country '{country}': {e}, defaulting to English")
224
+ return "en"
225
+
226
+ def get_lang_from_ip(client_ip):
227
+ """
228
+ Get language based on IP geolocation with comprehensive error handling
229
+
230
+ Features:
231
+ - Validates input IP address
232
+ - Handles all possible exceptions
233
+ - Always returns a valid language code
234
+ - Defaults to English on any failure
235
+ - Includes detailed logging
236
+
237
+ Args:
238
+ client_ip (str): Client IP address
239
+
240
+ Returns:
241
+ str: Language code (always valid, defaults to "en")
242
+ """
243
+ # Input validation
244
+ if not client_ip or not isinstance(client_ip, str):
245
+ print(f"Invalid IP address provided: {client_ip}, defaulting to English")
246
+ return "en"
247
+
248
+ try:
249
+ # Query geolocation info (has its own error handling and 3s timeout)
250
+ geo_info = query_ip_country(client_ip)
251
+
252
+ if not geo_info or not isinstance(geo_info, dict):
253
+ print(f"No geolocation data for {client_ip}, defaulting to English")
254
+ return "en"
255
+
256
+ # Extract country with fallback
257
+ country = geo_info.get("country", "Unknown")
258
+ if not country or country == "Unknown":
259
+ print(f"Unknown country for IP {client_ip}, defaulting to English")
260
+ return "en"
261
+
262
+ # Map country to language
263
+ detected_lang = get_lang_from_country(country)
264
+
265
+ # Validate language code
266
+ if not detected_lang or not isinstance(detected_lang, str) or len(detected_lang) != 2:
267
+ print(f"Invalid language code '{detected_lang}' for {client_ip}, defaulting to English")
268
+ return "en"
269
+
270
+ print(f"IP {client_ip} -> Country: {country} -> Language: {detected_lang}")
271
+ return detected_lang
272
+
273
+ except Exception as e:
274
+ print(f"Unexpected error getting language from IP {client_ip}: {e}, defaulting to English")
275
+ return "en" # Always return a valid language code
276
+
277
+ def is_restricted_country_ip(client_ip):
278
+ """
279
+ Check if IP is from a restricted country
280
+
281
+ Returns:
282
+ bool: True if from restricted country
283
+ """
284
+ geo_info = query_ip_country(client_ip)
285
+ country = geo_info["country"]
286
+ return country in RESTRICTED_COUNTRIES
287
+
288
+ def get_ip_max_limit(client_ip):
289
+ """
290
+ Get max usage limit for IP based on country
291
+
292
+ Returns:
293
+ int: Max usage limit
294
+ """
295
+ if is_restricted_country_ip(client_ip):
296
+ return RESTRICTED_COUNTRY_LIMIT
297
+ else:
298
+ return RATE_LIMIT_60
299
+
300
+ def get_ip_generation_count(client_ip):
301
+ """
302
+ Get IP generation count
303
+ """
304
+ if client_ip not in IP_Generation_Count:
305
+ IP_Generation_Count[client_ip] = 0
306
+ return IP_Generation_Count[client_ip]
307
+
308
+ def increment_ip_generation_count(client_ip):
309
+ """
310
+ Increment IP generation count
311
+ """
312
+ if client_ip not in IP_Generation_Count:
313
+ IP_Generation_Count[client_ip] = 0
314
+ IP_Generation_Count[client_ip] += 1
315
+ return IP_Generation_Count[client_ip]
316
+
317
+ def get_ip_phase(client_ip):
318
+ """
319
+ Get current phase for IP
320
+
321
+ Returns:
322
+ str: 'free', 'rate_limit_1', 'rate_limit_2', 'rate_limit_3', 'blocked'
323
+ """
324
+ count = get_ip_generation_count(client_ip)
325
+ max_limit = get_ip_max_limit(client_ip)
326
+
327
+ # For restricted countries, check if they've reached their limit
328
+ if is_restricted_country_ip(client_ip):
329
+ if count >= max_limit:
330
+ return 'blocked'
331
+ elif count >= max_limit - 2: # Last 2 attempts
332
+ return 'rate_limit_3'
333
+ elif count >= max_limit - 3: # 3rd attempt from end
334
+ return 'rate_limit_2'
335
+ elif count >= max_limit - 4: # 4th attempt from end
336
+ return 'rate_limit_1'
337
+ else:
338
+ return 'free'
339
+
340
+ # For normal countries, use standard limits
341
+ if count < FREE_TRY_N:
342
+ return 'free'
343
+ elif count < SLOW_TRY_N:
344
+ return 'rate_limit_1' # NSFW blur + 5 minutes 2 images
345
+ elif count < SLOW2_TRY_N:
346
+ return 'rate_limit_2' # NSFW blur + 10 minutes 2 images
347
+ elif count < max_limit:
348
+ return 'rate_limit_3' # NSFW blur + 20 minutes 2 images
349
+ else:
350
+ return 'blocked' # Generation blocked
351
+
352
+ def check_rate_limit_for_phase(client_ip, phase):
353
+ """
354
+ Check rate limit for specific phase
355
+
356
+ Returns:
357
+ tuple: (is_limited, wait_time_minutes, current_count)
358
+ """
359
+ if phase not in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3']:
360
+ return False, 0, 0
361
+
362
+ # Determine time window
363
+ if phase == 'rate_limit_1':
364
+ window_minutes = PHASE_1_WINDOW
365
+ elif phase == 'rate_limit_2':
366
+ window_minutes = PHASE_2_WINDOW
367
+ else: # rate_limit_3
368
+ window_minutes = PHASE_3_WINDOW
369
+
370
+ current_time = time.time()
371
+ window_key = f"{client_ip}_{phase}"
372
+
373
+ # Clean expired records
374
+ if window_key in IP_Rate_Limit_Track:
375
+ track_data = IP_Rate_Limit_Track[window_key]
376
+ # Check if within current time window
377
+ if current_time - track_data['start_time'] > window_minutes * 60:
378
+ # Time window expired, reset
379
+ IP_Rate_Limit_Track[window_key] = {
380
+ 'count': 0,
381
+ 'start_time': current_time,
382
+ 'last_generation': current_time
383
+ }
384
+ else:
385
+ # Initialize
386
+ IP_Rate_Limit_Track[window_key] = {
387
+ 'count': 0,
388
+ 'start_time': current_time,
389
+ 'last_generation': current_time
390
+ }
391
+
392
+ track_data = IP_Rate_Limit_Track[window_key]
393
+
394
+ # Check if exceeded limit
395
+ if track_data['count'] >= MAX_IMAGES_PER_WINDOW:
396
+ # Calculate remaining wait time
397
+ elapsed = current_time - track_data['start_time']
398
+ wait_time = (window_minutes * 60) - elapsed
399
+ wait_minutes = max(0, wait_time / 60)
400
+ return True, wait_minutes, track_data['count']
401
+
402
+ return False, 0, track_data['count']
403
+
404
+ def update_country_stats(client_ip):
405
+ """
406
+ Update country usage statistics and print periodically
407
+ """
408
+ global Total_Request_Count, Country_Usage_Stats
409
+
410
+ # Get country info
411
+ geo_info = IP_Country_Cache.get(client_ip, {"country": "Unknown", "region": "Unknown", "city": "Unknown"})
412
+ country = geo_info["country"]
413
+
414
+ # Update country stats
415
+ if country not in Country_Usage_Stats:
416
+ Country_Usage_Stats[country] = 0
417
+ Country_Usage_Stats[country] += 1
418
+
419
+ # Increment total request counter
420
+ Total_Request_Count += 1
421
+
422
+ # Print stats every N requests
423
+ if Total_Request_Count % PRINT_STATS_INTERVAL == 0:
424
+ print("\n" + "="*60)
425
+ print(f"📊 国家使用统计 (总请求数: {Total_Request_Count})")
426
+ print("="*60)
427
+
428
+ # Sort by usage count (descending)
429
+ sorted_stats = sorted(Country_Usage_Stats.items(), key=lambda x: x[1], reverse=True)
430
+
431
+ for country_name, count in sorted_stats:
432
+ percentage = (count / Total_Request_Count) * 100
433
+ print(f" {country_name}: {count} 次 ({percentage:.1f}%)")
434
+
435
+ print("="*60 + "\n")
436
+
437
+ def record_generation_attempt(client_ip, phase):
438
+ """
439
+ Record generation attempt
440
+ """
441
+ # Increment total count
442
+ increment_ip_generation_count(client_ip)
443
+
444
+ # Update country statistics
445
+ update_country_stats(client_ip)
446
+
447
+ # Record time window count
448
+ if phase in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3']:
449
+ window_key = f"{client_ip}_{phase}"
450
+ current_time = time.time()
451
+
452
+ if window_key in IP_Rate_Limit_Track:
453
+ IP_Rate_Limit_Track[window_key]['count'] += 1
454
+ IP_Rate_Limit_Track[window_key]['last_generation'] = current_time
455
+ else:
456
+ IP_Rate_Limit_Track[window_key] = {
457
+ 'count': 1,
458
+ 'start_time': current_time,
459
+ 'last_generation': current_time
460
+ }
461
+
462
+ def apply_gaussian_blur_to_image_url(image_url, blur_strength=50):
463
+ """
464
+ Apply Gaussian blur to image URL
465
+
466
+ Args:
467
+ image_url (str): Original image URL
468
+ blur_strength (int): Blur strength, default 50 (heavy blur)
469
+
470
+ Returns:
471
+ PIL.Image: Blurred PIL Image object
472
+ """
473
+ try:
474
+ import requests
475
+ from PIL import Image, ImageFilter
476
+ import io
477
+
478
+ # Download image
479
+ response = requests.get(image_url, timeout=30)
480
+ if response.status_code != 200:
481
+ return None
482
+
483
+ # Convert to PIL Image
484
+ image_data = io.BytesIO(response.content)
485
+ image = Image.open(image_data)
486
+
487
+ # Apply heavy Gaussian blur
488
+ blurred_image = image.filter(ImageFilter.GaussianBlur(radius=blur_strength))
489
+
490
+ return blurred_image
491
+
492
+ except Exception as e:
493
+ print(f"⚠️ Failed to apply Gaussian blur: {e}")
494
+ return None
495
+
496
+ # Initialize NSFW detector (download from Hugging Face)
497
+ try:
498
+ nsfw_detector = NSFWDetector() # Auto download falconsai_yolov9_nsfw_model_quantized.pt from Hugging Face
499
+ print("✅ NSFW detector initialized successfully")
500
+ except Exception as e:
501
+ print(f"❌ NSFW detector initialization failed: {e}")
502
+ nsfw_detector = None
503
+
504
+ def edit_image_interface(input_image, prompt, lang, request: gr.Request, progress=gr.Progress()):
505
+ """
506
+ Interface function for processing image editing with phase-based limitations
507
+ """
508
+ try:
509
+ # Extract user IP
510
+ client_ip = request.client.host
511
+ x_forwarded_for = dict(request.headers).get('x-forwarded-for')
512
+ if x_forwarded_for:
513
+ client_ip = x_forwarded_for
514
+ if client_ip not in IP_Dict:
515
+ IP_Dict[client_ip] = 0
516
+ IP_Dict[client_ip] += 1
517
+
518
+ if input_image is None:
519
+ return None, t("error_upload_first", lang), gr.update(visible=False)
520
+
521
+ if not prompt or prompt.strip() == "":
522
+ return None, t("error_enter_prompt", lang), gr.update(visible=False)
523
+
524
+ # Check if prompt length is greater than 3 characters
525
+ if len(prompt.strip()) <= 3:
526
+ return None, t("error_prompt_too_short", lang), gr.update(visible=False)
527
+ except Exception as e:
528
+ print(f"⚠️ Request preprocessing error: {e}")
529
+ return None, t("error_request_processing", lang), gr.update(visible=False)
530
+
531
+ # Get user current phase
532
+ current_phase = get_ip_phase(client_ip)
533
+ current_count = get_ip_generation_count(client_ip)
534
+ geo_info = IP_Country_Cache.get(client_ip, {"country": "Unknown", "region": "Unknown", "city": "Unknown"})
535
+ is_restricted = is_restricted_country_ip(client_ip)
536
+
537
+ print(f"📊 User phase info - IP: {client_ip}, Location: {geo_info['country']}/{geo_info['region']}/{geo_info['city']}, Phase: {current_phase}, Count: {current_count}, Restricted: {is_restricted}")
538
+
539
+ # Check if user reached the like button tip threshold
540
+ # For restricted countries, show like tip from the first attempt
541
+ show_like_tip = (current_count >= 1) if is_restricted else (current_count >= TIP_TRY_N)
542
+
543
+ # Check if completely blocked
544
+ if current_phase == 'blocked':
545
+ # Generate blocked limit button with different URL for restricted countries
546
+ if is_restricted or lang in ["hi", "ru", "zh"]:
547
+ blocked_url = GOOGLE_GEMINI_URL
548
+ else:
549
+ blocked_url = 'https://omnicreator.net/#generator'
550
+
551
+ blocked_button_html = f"""
552
+ <div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
553
+ <a href='{blocked_url}' target='_blank' style='
554
+ display: inline-flex;
555
+ align-items: center;
556
+ justify-content: center;
557
+ padding: 16px 32px;
558
+ background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
559
+ color: white;
560
+ text-decoration: none;
561
+ border-radius: 12px;
562
+ font-weight: 600;
563
+ font-size: 16px;
564
+ text-align: center;
565
+ min-width: 200px;
566
+ box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
567
+ transition: all 0.3s ease;
568
+ border: none;
569
+ '>&#128640; Unlimited Generation</a>
570
+ </div>
571
+ """
572
+
573
+ # Use same message for all users to avoid discrimination perception
574
+ blocked_message = t("error_free_limit_reached", lang)
575
+
576
+ return None, blocked_message, gr.update(value=blocked_button_html, visible=True)
577
+
578
+ # Check rate limit (applies to rate_limit phases)
579
+ if current_phase in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3']:
580
+ is_limited, wait_minutes, window_count = check_rate_limit_for_phase(client_ip, current_phase)
581
+ if is_limited:
582
+ wait_minutes_int = int(wait_minutes) + 1
583
+ # Generate rate limit button with different URL for restricted countries
584
+ if is_restricted or lang in ["hi", "ru", "zh"]:
585
+ rate_limit_url = GOOGLE_GEMINI_URL
586
+ else:
587
+ rate_limit_url = 'https://omnicreator.net/#generator'
588
+
589
+ rate_limit_button_html = f"""
590
+ <div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
591
+ <a href='{rate_limit_url}' target='_blank' style='
592
+ display: inline-flex;
593
+ align-items: center;
594
+ justify-content: center;
595
+ padding: 16px 32px;
596
+ background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%);
597
+ color: white;
598
+ text-decoration: none;
599
+ border-radius: 12px;
600
+ font-weight: 600;
601
+ font-size: 16px;
602
+ text-align: center;
603
+ min-width: 200px;
604
+ box-shadow: 0 4px 15px rgba(243, 156, 18, 0.4);
605
+ transition: all 0.3s ease;
606
+ border: none;
607
+ '>⏰ Skip Wait - Unlimited Generation</a>
608
+ </div>
609
+ """
610
+ return None, t("error_free_limit_wait", lang).format(wait_minutes_int=wait_minutes_int), gr.update(value=rate_limit_button_html, visible=True)
611
+
612
+ # Handle NSFW detection based on phase
613
+ is_nsfw_task = False # Track if this task involves NSFW content
614
+
615
+ # Skip NSFW detection in free phase
616
+ if current_phase != 'free' and nsfw_detector is not None and input_image is not None:
617
+ try:
618
+ nsfw_result = nsfw_detector.predict_pil_label_only(input_image)
619
+
620
+ if nsfw_result.lower() == "nsfw":
621
+ is_nsfw_task = True
622
+ print(f"🔍 Input NSFW detected in {current_phase} phase: ❌❌❌ {nsfw_result} - IP: {client_ip} (will blur result)")
623
+ else:
624
+ print(f"🔍 Input NSFW check passed: ✅✅✅ {nsfw_result} - IP: {client_ip}")
625
+
626
+ except Exception as e:
627
+ print(f"⚠️ Input NSFW detection failed: {e}")
628
+ # Allow continuation when detection fails
629
+
630
+ result_url = None
631
+ status_message = ""
632
+
633
+ def progress_callback(message):
634
+ try:
635
+ nonlocal status_message
636
+ status_message = message
637
+ # Add error handling to prevent progress update failure
638
+ if progress is not None:
639
+ # Enhanced progress display with better formatting
640
+ if "Queue:" in message or "tasks ahead" in message:
641
+ # Queue status - show with different progress value to indicate waiting
642
+ progress(0.1, desc=message)
643
+ elif "Processing" in message or "AI is processing" in message:
644
+ # Processing status
645
+ progress(0.7, desc=message)
646
+ elif "Generating" in message or "Almost done" in message:
647
+ # Generation status
648
+ progress(0.9, desc=message)
649
+ else:
650
+ # Default status
651
+ progress(0.5, desc=message)
652
+ except Exception as e:
653
+ print(f"⚠️ Progress update failed: {e}")
654
+
655
+ try:
656
+ # Determine priority before recording generation attempt
657
+ # First high_priority_n tasks for each IP get priority=1
658
+ task_priority = 1 if current_count < high_priority_n else 0
659
+
660
+ # Record generation attempt (before actual generation to ensure correct count)
661
+ record_generation_attempt(client_ip, current_phase)
662
+ updated_count = get_ip_generation_count(client_ip)
663
+
664
+ print(f"✅ Processing started - IP: {client_ip}, phase: {current_phase}, total count: {updated_count}, priority: {task_priority}, prompt: {prompt.strip()}", flush=True)
665
+
666
+ # Call image editing processing function with priority
667
+ input_image_url, result_url, message, task_uuid = process_image_edit(input_image, prompt.strip(), None, progress_callback, priority=task_priority, client_ip=client_ip)
668
+
669
+ # Check if HF user limit exceeded
670
+ if message and message.startswith("HF_LIMIT_EXCEEDED:"):
671
+ error_message = message.replace("HF_LIMIT_EXCEEDED:", "")
672
+ # Generate HF limit exceeded button (similar to blocked status)
673
+ hf_limit_url = 'https://omnicreator.net/#generator'
674
+
675
+ hf_limit_button_html = f"""
676
+ <div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
677
+ <a href='{hf_limit_url}' target='_blank' style='
678
+ display: inline-flex;
679
+ align-items: center;
680
+ justify-content: center;
681
+ padding: 16px 32px;
682
+ background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
683
+ color: white;
684
+ text-decoration: none;
685
+ border-radius: 12px;
686
+ font-weight: 600;
687
+ font-size: 16px;
688
+ text-align: center;
689
+ min-width: 200px;
690
+ box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
691
+ transition: all 0.3s ease;
692
+ border: none;
693
+ '>&#128640; Unlimited Generation</a>
694
+ </div>
695
+ """
696
+
697
+ # Use translated message or default
698
+ limit_message = error_message if error_message else t("error_free_limit_reached", lang)
699
+
700
+ return None, limit_message, gr.update(value=hf_limit_button_html, visible=True)
701
+
702
+ if result_url:
703
+ print(f"✅ Processing completed successfully - IP: {client_ip}, result_url: {result_url}, task_uuid: {task_uuid}", flush=True)
704
+
705
+ # Detect result image NSFW content (only in rate limit phases)
706
+ if nsfw_detector is not None and current_phase != 'free':
707
+ try:
708
+ if progress is not None:
709
+ progress(0.9, desc=t("status_checking_result", lang))
710
+
711
+ is_nsfw, nsfw_error = download_and_check_result_nsfw(result_url, nsfw_detector)
712
+
713
+ if nsfw_error:
714
+ print(f"⚠️ Result image NSFW detection error - IP: {client_ip}, error: {nsfw_error}")
715
+ elif is_nsfw:
716
+ is_nsfw_task = True # Mark task as NSFW
717
+ print(f"🔍 Result image NSFW detected in {current_phase} phase: ❌❌❌ - IP: {client_ip} (will blur result)")
718
+ else:
719
+ print(f"🔍 Result image NSFW check passed: ✅✅✅ - IP: {client_ip}")
720
+
721
+ except Exception as e:
722
+ print(f"⚠️ Result image NSFW detection exception - IP: {client_ip}, error: {str(e)}")
723
+
724
+ # Apply blur if this is an NSFW task in rate limit phases
725
+ should_blur = False
726
+
727
+ if current_phase in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3'] and is_nsfw_task:
728
+ should_blur = True
729
+
730
+ # Apply blur processing
731
+ if should_blur:
732
+ if progress is not None:
733
+ progress(0.95, desc=t("status_applying_filter", lang))
734
+
735
+ blurred_image = apply_gaussian_blur_to_image_url(result_url)
736
+ if blurred_image is not None:
737
+ final_result = blurred_image # Return PIL Image object
738
+ final_message = t("warning_content_filter", lang)
739
+ print(f"🔒 Applied Gaussian blur for NSFW content - IP: {client_ip}")
740
+ else:
741
+ # Blur failed, return original URL with warning
742
+ final_result = result_url
743
+ final_message = t("warning_content_review", lang)
744
+
745
+ # Generate NSFW button for blurred content with different URL for restricted countries
746
+ if is_restricted or lang in ["hi", "ru", "zh"]:
747
+ nsfw_url = GOOGLE_GEMINI_URL
748
+ else:
749
+ nsfw_url = 'https://omnicreator.net/#generator'
750
+
751
+ nsfw_action_buttons_html = f"""
752
+ <div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
753
+ <a href='{nsfw_url}' target='_blank' style='
754
+ display: inline-flex;
755
+ align-items: center;
756
+ justify-content: center;
757
+ padding: 16px 32px;
758
+ background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
759
+ color: white;
760
+ text-decoration: none;
761
+ border-radius: 12px;
762
+ font-weight: 600;
763
+ font-size: 16px;
764
+ text-align: center;
765
+ min-width: 200px;
766
+ box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
767
+ transition: all 0.3s ease;
768
+ border: none;
769
+ '>🔥 Unlimited Creative Generation</a>
770
+ </div>
771
+ """
772
+ return final_result, final_message, gr.update(value=nsfw_action_buttons_html, visible=True)
773
+ else:
774
+ final_result = result_url
775
+ final_message = t("status_completed_message", lang).format(message=message)
776
+
777
+ try:
778
+ if progress is not None:
779
+ progress(1.0, desc=t("status_processing_completed", lang))
780
+ except Exception as e:
781
+ print(f"⚠️ Final progress update failed: {e}")
782
+
783
+ # Generate action buttons HTML
784
+ action_buttons_html = ""
785
+
786
+ # For restricted countries, only show like tip (no action buttons)
787
+ if is_restricted:
788
+ if show_like_tip:
789
+ action_buttons_html = """
790
+ <div style='display: flex; justify-content: center; margin: 15px 0 5px 0; padding: 0px;'>
791
+ <div style='
792
+ display: inline-flex;
793
+ align-items: center;
794
+ justify-content: center;
795
+ padding: 12px 24px;
796
+ background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
797
+ color: white;
798
+ border-radius: 10px;
799
+ font-weight: 600;
800
+ font-size: 14px;
801
+ text-align: center;
802
+ max-width: 400px;
803
+ box-shadow: 0 3px 12px rgba(255, 107, 107, 0.3);
804
+ border: none;
805
+ '>👉 Click the ❤️ Like button to unlock more free trial attempts!</div>
806
+ </div>
807
+ """
808
+ else:
809
+ # For non-restricted countries, show normal buttons
810
+ if task_uuid and lang not in ["zh", "hi", "ru"]:
811
+ # Create task detail URL for downloading HD image
812
+ task_detail_url = f"https://omnicreator.net/my-creations/task/{task_uuid}"
813
+ # Create i2v URL with input and output images and prompt
814
+ from urllib.parse import quote
815
+ encoded_prompt = quote(prompt.strip())
816
+ i2v_url = f"https://omnicreator.net/image-to-video?input_image={input_image_url}&end_image={result_url}&prompt={encoded_prompt}"
817
+ action_buttons_html = f"""
818
+ <div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
819
+ <a href='{i2v_url}' target='_blank' style='
820
+ display: inline-flex;
821
+ align-items: center;
822
+ justify-content: center;
823
+ padding: 16px 32px;
824
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
825
+ color: white;
826
+ text-decoration: none;
827
+ border-radius: 12px;
828
+ font-weight: 600;
829
+ font-size: 16px;
830
+ text-align: center;
831
+ min-width: 160px;
832
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
833
+ transition: all 0.3s ease;
834
+ border: none;
835
+ '>&#127909; Convert to Video</a>
836
+ <a href='{task_detail_url}' target='_blank' style='
837
+ display: inline-flex;
838
+ align-items: center;
839
+ justify-content: center;
840
+ padding: 16px 32px;
841
+ background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
842
+ color: white;
843
+ text-decoration: none;
844
+ border-radius: 12px;
845
+ font-weight: 600;
846
+ font-size: 16px;
847
+ text-align: center;
848
+ min-width: 160px;
849
+ box-shadow: 0 4px 15px rgba(17, 153, 142, 0.4);
850
+ transition: all 0.3s ease;
851
+ border: none;
852
+ '>&#128190; Download HD Image</a>
853
+ </div>
854
+ """
855
+
856
+ # Add popup script if needed (using different approach)
857
+ if show_like_tip or lang in ["zh", "hi", "ru"]:
858
+ action_buttons_html += """
859
+ <div style='display: flex; justify-content: center; margin: 15px 0 5px 0; padding: 0px;'>
860
+ <div style='
861
+ display: inline-flex;
862
+ align-items: center;
863
+ justify-content: center;
864
+ padding: 12px 24px;
865
+ background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
866
+ color: white;
867
+ border-radius: 10px;
868
+ font-weight: 600;
869
+ font-size: 14px;
870
+ text-align: center;
871
+ max-width: 400px;
872
+ box-shadow: 0 3px 12px rgba(255, 107, 107, 0.3);
873
+ border: none;
874
+ '>👉 Click the ❤️ Like button to unlock more free trial attempts!</div>
875
+ </div>
876
+ """
877
+
878
+ return final_result, final_message, gr.update(value=action_buttons_html, visible=True)
879
+ else:
880
+ print(f"❌ Processing failed - IP: {client_ip}, error: {message}", flush=True)
881
+ return None, t("error_processing_failed", lang).format(message=message), gr.update(visible=False)
882
+
883
+ except Exception as e:
884
+ print(f"❌ Processing exception - IP: {client_ip}, error: {str(e)}")
885
+ return None, t("error_processing_exception", lang).format(error=str(e)), gr.update(visible=False)
886
+
887
+ # Create Gradio interface
888
+ def create_app():
889
+ with gr.Blocks(
890
+ title="AI Image Editor",
891
+ theme=gr.themes.Soft(),
892
+ css="""
893
+ .main-container {
894
+ max-width: 1200px;
895
+ margin: 0 auto;
896
+ }
897
+ .news-banner-row {
898
+ margin: 10px auto 15px auto;
899
+ padding: 0 10px;
900
+ max-width: 1200px;
901
+ width: 100% !important;
902
+ }
903
+ .news-banner-row .gr-row {
904
+ display: flex !important;
905
+ align-items: center !important;
906
+ width: 100% !important;
907
+ }
908
+ .news-banner-row .gr-column:first-child {
909
+ flex: 1 !important; /* 占据所有剩余空间 */
910
+ display: flex !important;
911
+ justify-content: center !important; /* 在其空间内居中 */
912
+ }
913
+ .banner-lang-selector {
914
+ margin-left: auto !important;
915
+ display: flex !important;
916
+ justify-content: flex-end !important;
917
+ align-items: center !important;
918
+ position: relative !important;
919
+ z-index: 10 !important;
920
+ }
921
+ .banner-lang-selector .gr-dropdown {
922
+ background: white !important;
923
+ border: 1px solid #ddd !important;
924
+ border-radius: 8px !important;
925
+ padding: 8px 16px !important;
926
+ font-size: 14px !important;
927
+ font-weight: 500 !important;
928
+ color: #333 !important;
929
+ cursor: pointer !important;
930
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
931
+ min-width: 140px !important;
932
+ max-width: 160px !important;
933
+ transition: all 0.2s ease !important;
934
+ }
935
+ .banner-lang-selector .gr-dropdown:hover {
936
+ border-color: #999 !important;
937
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
938
+ }
939
+ @media (max-width: 768px) {
940
+ .news-banner-row {
941
+ padding: 0 15px !important;
942
+ }
943
+ .news-banner-row .gr-row {
944
+ display: flex !important;
945
+ flex-direction: column !important;
946
+ gap: 10px !important;
947
+ position: static !important;
948
+ }
949
+ .news-banner-row .gr-column:first-child {
950
+ position: static !important;
951
+ pointer-events: auto !important;
952
+ }
953
+ .banner-lang-selector {
954
+ margin-left: 0 !important;
955
+ justify-content: center !important;
956
+ }
957
+ }
958
+ .upload-area {
959
+ border: 2px dashed #ccc;
960
+ border-radius: 10px;
961
+ padding: 20px;
962
+ text-align: center;
963
+ }
964
+ .result-area {
965
+ margin-top: 20px;
966
+ padding: 20px;
967
+ border-radius: 10px;
968
+ background-color: #f8f9fa;
969
+ }
970
+ .use-as-input-btn {
971
+ margin-top: 10px;
972
+ width: 100%;
973
+ }
974
+ """,
975
+ # Improve concurrency performance configuration
976
+ head="""
977
+ <script>
978
+ // Reduce client-side state update frequency, avoid excessive SSE connections
979
+ if (window.gradio) {
980
+ window.gradio.update_frequency = 2000; // Update every 2 seconds
981
+ }
982
+ </script>
983
+ """
984
+ ) as app:
985
+
986
+ lang_state = gr.State("en")
987
+
988
+ # Main title - centered
989
+ header_title = gr.HTML(f"""
990
+ <div style="text-align: center; margin: 20px auto 10px auto; max-width: 800px;">
991
+ <h1 style="color: #2c3e50; margin: 0; font-size: 3.5em; font-weight: 800; letter-spacing: 3px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
992
+ {t('header_title', 'en')}
993
+ </h1>
994
+ </div>
995
+ """)
996
+
997
+ with gr.Row(elem_classes=["news-banner-row"]):
998
+ with gr.Column(scale=1, min_width=400):
999
+ # Banner is initially visible (will be hidden for zh/hi/ru languages on load)
1000
+ news_banner = gr.HTML(f"""
1001
+ <style>
1002
+ @keyframes breathe {{
1003
+ 0%, 100% {{ transform: scale(1); }}
1004
+ 50% {{ transform: scale(1.02); }}
1005
+ }}
1006
+ .breathing-banner {{
1007
+ animation: breathe 3s ease-in-out infinite;
1008
+ }}
1009
+ </style>
1010
+ <div class="breathing-banner" style="
1011
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1012
+ margin: 0 auto;
1013
+ padding: 8px 40px;
1014
+ border-radius: 20px;
1015
+ max-width: 800px;
1016
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
1017
+ text-align: center;
1018
+ width: fit-content;
1019
+ ">
1020
+ <span style="color: white; font-weight: 600; font-size: 1.0em;">
1021
+ 🎉 NEW:
1022
+ <a href="https://huggingface.co/spaces/Selfit/Multi-Image-Edit" target="_blank" style="
1023
+ color: white;
1024
+ text-decoration: none;
1025
+ border-bottom: 1px solid rgba(255,255,255,0.5);
1026
+ transition: all 0.3s ease;
1027
+ margin: 0 8px;
1028
+ " onmouseover="this.style.borderBottom='1px solid white'"
1029
+ onmouseout="this.style.borderBottom='1px solid rgba(255,255,255,0.5)'">
1030
+ Multi-Image-Edit
1031
+ </a>
1032
+ |
1033
+ <a href="https://huggingface.co/spaces/Selfit/Text-to-Image-Pro" target="_blank" style="
1034
+ color: white;
1035
+ text-decoration: none;
1036
+ border-bottom: 1px solid rgba(255,255,255,0.5);
1037
+ transition: all 0.3s ease;
1038
+ margin: 0 8px;
1039
+ " onmouseover="this.style.borderBottom='1px solid white'"
1040
+ onmouseout="this.style.borderBottom='1px solid rgba(255,255,255,0.5)'">
1041
+ Text-to-Image
1042
+ </a>
1043
+ </span>
1044
+ </div>
1045
+ """, visible=True)
1046
+
1047
+ with gr.Column(scale=0, min_width=160, elem_classes=["banner-lang-selector"]):
1048
+ lang_dropdown = gr.Dropdown(
1049
+ choices=[
1050
+ ("English", "en"),
1051
+ ("हिंदी", "hi"),
1052
+ ("中文", "zh"),
1053
+ ("Suomi", "fi"),
1054
+ ("Español", "es"),
1055
+ ("Português", "pt"),
1056
+ ("Français", "fr"),
1057
+ ("Deutsch", "de"),
1058
+ ("Italiano", "it"),
1059
+ ("日本語", "ja"),
1060
+ ("Русский", "ru"),
1061
+ ("Українська", "uk"),
1062
+ ("العربية", "ar"),
1063
+ ("Nederlands", "nl"),
1064
+ ("Norsk", "no"),
1065
+ ("Svenska", "sv"),
1066
+ ("Indonesian", "id"),
1067
+ ("Tiếng Việt", "vi"),
1068
+ ("עברית", "he"),
1069
+ ("Türkçe", "tr"),
1070
+ ("Dansk", "da")
1071
+ ],
1072
+ value="en",
1073
+ label="🌐",
1074
+ show_label=True,
1075
+ interactive=True,
1076
+ container=False
1077
+ )
1078
+
1079
+ with gr.Tabs() as tabs:
1080
+ with gr.Tab(t("global_editor_tab", "en")) as global_tab:
1081
+ with gr.Row():
1082
+ with gr.Column(scale=1):
1083
+ upload_image_header = gr.Markdown(t("upload_image_header", "en"))
1084
+ input_image = gr.Image(
1085
+ label=t("upload_image_label", "en"),
1086
+ type="pil",
1087
+ height=512,
1088
+ elem_classes=["upload-area"]
1089
+ )
1090
+
1091
+ editing_instructions_header = gr.Markdown(t("editing_instructions_header", "en"))
1092
+ prompt_input = gr.Textbox(
1093
+ label=t("prompt_input_label", "en"),
1094
+ placeholder=t("prompt_input_placeholder", "en"),
1095
+ lines=3,
1096
+ max_lines=5
1097
+ )
1098
+
1099
+ edit_button = gr.Button(
1100
+ t("start_editing_button", "en"),
1101
+ variant="primary",
1102
+ size="lg"
1103
+ )
1104
+
1105
+ with gr.Column(scale=1):
1106
+ editing_result_header = gr.Markdown(t("editing_result_header", "en"))
1107
+ output_image = gr.Image(
1108
+ label=t("output_image_label", "en"),
1109
+ height=320,
1110
+ elem_classes=["result-area"]
1111
+ )
1112
+
1113
+ use_as_input_btn = gr.Button(
1114
+ t("use_as_input_button", "en"),
1115
+ variant="secondary",
1116
+ size="sm",
1117
+ elem_classes=["use-as-input-btn"]
1118
+ )
1119
+
1120
+ status_output = gr.Textbox(
1121
+ label=t("status_output_label", "en"),
1122
+ lines=2,
1123
+ max_lines=3,
1124
+ interactive=False
1125
+ )
1126
+
1127
+ action_buttons = gr.HTML(visible=False)
1128
+
1129
+ prompt_examples_header = gr.Markdown(t("prompt_examples_header", "en"))
1130
+ with gr.Row():
1131
+ example_prompts = [
1132
+ "Set the background to a grand opera stage with red curtains",
1133
+ "Change the outfit into a traditional Chinese hanfu with flowing sleeves",
1134
+ "Give the character blue dragon-like eyes with glowing pupils",
1135
+ "Change lighting to soft dreamy pastel glow",
1136
+ "Change pose to sitting cross-legged on the ground"
1137
+ ]
1138
+
1139
+ for prompt in example_prompts:
1140
+ gr.Button(
1141
+ prompt,
1142
+ size="sm"
1143
+ ).click(
1144
+ lambda p=prompt: p,
1145
+ outputs=prompt_input
1146
+ )
1147
+
1148
+ edit_button.click(
1149
+ fn=edit_image_interface,
1150
+ inputs=[input_image, prompt_input, lang_state],
1151
+ outputs=[output_image, status_output, action_buttons],
1152
+ show_progress=True,
1153
+ concurrency_limit=20
1154
+ )
1155
+
1156
+ def simple_use_as_input(output_img):
1157
+ if output_img is not None:
1158
+ return output_img
1159
+ return None
1160
+
1161
+ use_as_input_btn.click(
1162
+ fn=simple_use_as_input,
1163
+ inputs=[output_image],
1164
+ outputs=[input_image]
1165
+ )
1166
+
1167
+ # SEO Content Section
1168
+ seo_html = gr.HTML()
1169
+
1170
+ def get_seo_html(lang):
1171
+ # 中文、印度语、俄语不显示SEO部分
1172
+ if lang in ["zh", "hi", "ru"]:
1173
+ return ""
1174
+
1175
+ return f"""
1176
+ <div style="width: 100%; margin: 50px 0; padding: 0 20px;">
1177
+
1178
+ <div style="text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; border-radius: 20px; margin: 40px 0;">
1179
+ <h2 style="margin: 0 0 20px 0; font-size: 2.2em; font-weight: 700;">
1180
+ &#127912; {t('seo_unlimited_title', lang)}
1181
+ </h2>
1182
+ <p style="margin: 0 0 25px 0; font-size: 1.2em; opacity: 0.95; line-height: 1.6;">
1183
+ {t('seo_unlimited_desc', lang)}
1184
+ </p>
1185
+
1186
+ <div style="display: flex; justify-content: center; gap: 25px; flex-wrap: wrap; margin: 30px 0;">
1187
+ <a href="https://omnicreator.net/#generator" target="_blank" style="
1188
+ display: inline-flex;
1189
+ align-items: center;
1190
+ justify-content: center;
1191
+ padding: 20px 40px;
1192
+ background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
1193
+ color: white;
1194
+ text-decoration: none;
1195
+ border-radius: 15px;
1196
+ font-weight: 700;
1197
+ font-size: 18px;
1198
+ text-align: center;
1199
+ min-width: 250px;
1200
+ box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4);
1201
+ transition: all 0.3s ease;
1202
+ border: none;
1203
+ transform: scale(1);
1204
+ " onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
1205
+ &#128640; {t('seo_unlimited_button', lang)}
1206
+ </a>
1207
+
1208
+ </div>
1209
+
1210
+ <p style="color: rgba(255,255,255,0.9); font-size: 1em; margin: 20px 0 0 0;">
1211
+ {t('seo_unlimited_footer', lang)}
1212
+ </p>
1213
+ </div>
1214
+
1215
+ <div style="text-align: center; margin: 25px auto; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); padding: 35px; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
1216
+ <h2 style="color: #2c3e50; margin: 0 0 20px 0; font-size: 1.9em; font-weight: 700;">
1217
+ &#11088; {t('seo_professional_title', lang)}
1218
+ </h2>
1219
+ <p style="color: #555; font-size: 1.1em; line-height: 1.6; margin: 0 0 20px 0; padding: 0 20px;">
1220
+ {t('seo_professional_desc', lang)}
1221
+ </p>
1222
+ </div>
1223
+
1224
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 25px; margin: 40px 0;">
1225
+
1226
+ <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); border-left: 5px solid #e74c3c;">
1227
+ <h3 style="color: #e74c3c; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
1228
+ &#127919; {t('seo_feature1_title', lang)}
1229
+ </h3>
1230
+ <p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
1231
+ {t('seo_feature1_desc', lang)}
1232
+ </p>
1233
+ </div>
1234
+
1235
+ <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); border-left: 5px solid #3498db;">
1236
+ <h3 style="color: #3498db; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
1237
+ 🔓 {t('seo_feature2_title', lang)}
1238
+ </h3>
1239
+ <p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
1240
+ {t('seo_feature2_desc', lang)}
1241
+ </p>
1242
+ </div>
1243
+
1244
+ <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); border-left: 5px solid #27ae60;">
1245
+ <h3 style="color: #27ae60; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
1246
+ &#9889; {t('seo_feature3_title', lang)}
1247
+ </h3>
1248
+ <p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
1249
+ {t('seo_feature3_desc', lang)}
1250
+ </p>
1251
+ </div>
1252
+
1253
+ <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); border-left: 5px solid #9b59b6;">
1254
+ <h3 style="color: #9b59b6; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
1255
+ &#127912; {t('seo_feature4_title', lang)}
1256
+ </h3>
1257
+ <p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
1258
+ {t('seo_feature4_desc', lang)}
1259
+ </p>
1260
+ </div>
1261
+
1262
+ <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); border-left: 5px solid #f39c12;">
1263
+ <h3 style="color: #f39c12; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
1264
+ &#128142; {t('seo_feature5_title', lang)}
1265
+ </h3>
1266
+ <p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
1267
+ {t('seo_feature5_desc', lang)}
1268
+ </p>
1269
+ </div>
1270
+
1271
+ <div style="background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.08); border-left: 5px solid #34495e;">
1272
+ <h3 style="color: #34495e; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
1273
+ 🌍 {t('seo_feature6_title', lang)}
1274
+ </h3>
1275
+ <p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
1276
+ {t('seo_feature6_desc', lang)}
1277
+ </p>
1278
+ </div>
1279
+
1280
+ </div>
1281
+
1282
+ <div style="background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%); padding: 30px; border-radius: 15px; margin: 40px 0;">
1283
+ <h3 style="color: #8b5cf6; text-align: center; margin: 0 0 25px 0; font-size: 1.5em; font-weight: 700;">
1284
+ &#128161; {t('seo_protips_title', lang)}
1285
+ </h3>
1286
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 18px;">
1287
+
1288
+ <div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
1289
+ <strong style="color: #8b5cf6; font-size: 1.1em;">📝 {t('seo_protip1_title', lang)}</strong>
1290
+ <p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip1_desc', lang)}</p>
1291
+ </div>
1292
+
1293
+ <div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
1294
+ <strong style="color: #8b5cf6; font-size: 1.1em;">&#127919; {t('seo_protip2_title', lang)}</strong>
1295
+ <p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip2_desc', lang)}</p>
1296
+ </div>
1297
+
1298
+ <div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
1299
+ <strong style="color: #8b5cf6; font-size: 1.1em;">&#9889; {t('seo_protip3_title', lang)}</strong>
1300
+ <p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip3_desc', lang)}</p>
1301
+ </div>
1302
+
1303
+ <div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
1304
+ <strong style="color: #8b5cf6; font-size: 1.1em;">&#128444; {t('seo_protip4_title', lang)}</strong>
1305
+ <p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip4_desc', lang)}</p>
1306
+ </div>
1307
+
1308
+ </div>
1309
+ </div>
1310
+
1311
+ <div style="text-align: center; margin: 25px auto; background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); padding: 35px; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
1312
+ <h2 style="color: #2c3e50; margin: 0 0 20px 0; font-size: 1.8em; font-weight: 700;">
1313
+ &#128640; {t('seo_needs_title', lang)}
1314
+ </h2>
1315
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 25px 0; text-align: left;">
1316
+
1317
+ <div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
1318
+ <h4 style="color: #e74c3c; margin: 0 0 10px 0;">🎨 {t('seo_needs_art_title', lang)}</h4>
1319
+ <ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
1320
+ <li>{t('seo_needs_art_item1', lang)}</li>
1321
+ <li>{t('seo_needs_art_item2', lang)}</li>
1322
+ <li>{t('seo_needs_art_item3', lang)}</li>
1323
+ <li>{t('seo_needs_art_item4', lang)}</li>
1324
+ </ul>
1325
+ </div>
1326
+
1327
+ <div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
1328
+ <h4 style="color: #3498db; margin: 0 0 10px 0;">📸 {t('seo_needs_photo_title', lang)}</h4>
1329
+ <ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
1330
+ <li>{t('seo_needs_photo_item1', lang)}</li>
1331
+ <li>{t('seo_needs_photo_item2', lang)}</li>
1332
+ <li>{t('seo_needs_photo_item3', lang)}</li>
1333
+ <li>{t('seo_needs_photo_item4', lang)}</li>
1334
+ </ul>
1335
+ </div>
1336
+
1337
+ <div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
1338
+ <h4 style="color: #27ae60; margin: 0 0 10px 0;">🛍️ {t('seo_needs_ecom_title', lang)}</h4>
1339
+ <ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
1340
+ <li>{t('seo_needs_ecom_item1', lang)}</li>
1341
+ <li>{t('seo_needs_ecom_item2', lang)}</li>
1342
+ <li>{t('seo_needs_ecom_item3', lang)}</li>
1343
+ <li>{t('seo_needs_ecom_item4', lang)}</li>
1344
+ </ul>
1345
+ </div>
1346
+
1347
+ <div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
1348
+ <h4 style="color: #9b59b6; margin: 0 0 10px 0;">📱 {t('seo_needs_social_title', lang)}</h4>
1349
+ <ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
1350
+ <li>{t('seo_needs_social_item1', lang)}</li>
1351
+ <li>{t('seo_needs_social_item2', lang)}</li>
1352
+ <li>{t('seo_needs_social_item3', lang)}</li>
1353
+ <li>{t('seo_needs_social_item4', lang)}</li>
1354
+ </ul>
1355
+ </div>
1356
+
1357
+ </div>
1358
+ </div>
1359
+
1360
+ </div>
1361
+ """
1362
+
1363
+ all_ui_components = [
1364
+ header_title, news_banner,
1365
+ global_tab, upload_image_header, input_image, editing_instructions_header, prompt_input, edit_button,
1366
+ editing_result_header, output_image, use_as_input_btn, status_output, prompt_examples_header,
1367
+ seo_html,
1368
+ ]
1369
+
1370
+ def update_ui_lang(lang):
1371
+ # Hide banner for zh, hi, ru languages
1372
+ show_banner = lang not in ["zh", "hi", "ru"]
1373
+
1374
+ return {
1375
+ header_title: gr.update(value=f"""
1376
+ <div style="text-align: center; margin: 20px auto 10px auto; max-width: 800px;">
1377
+ <h1 style="color: #2c3e50; margin: 0; font-size: 3.5em; font-weight: 800; letter-spacing: 3px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
1378
+ {t('header_title', lang)}
1379
+ </h1>
1380
+ </div>"""),
1381
+ news_banner: gr.update(visible=show_banner),
1382
+ global_tab: gr.update(label=t("global_editor_tab", lang)),
1383
+ upload_image_header: gr.update(value=t("upload_image_header", lang)),
1384
+ input_image: gr.update(label=t("upload_image_label", lang)),
1385
+ editing_instructions_header: gr.update(value=t("editing_instructions_header", lang)),
1386
+ prompt_input: gr.update(label=t("prompt_input_label", lang), placeholder=t("prompt_input_placeholder", lang)),
1387
+ edit_button: gr.update(value=t("start_editing_button", lang)),
1388
+ editing_result_header: gr.update(value=t("editing_result_header", lang)),
1389
+ output_image: gr.update(label=t("output_image_label", lang)),
1390
+ use_as_input_btn: gr.update(value=t("use_as_input_button", lang)),
1391
+ status_output: gr.update(label=t("status_output_label", lang)),
1392
+ prompt_examples_header: gr.update(value=t("prompt_examples_header", lang)),
1393
+ seo_html: gr.update(value=get_seo_html(lang)),
1394
+ }
1395
+
1396
+ def on_lang_change(lang):
1397
+ return lang, *update_ui_lang(lang).values()
1398
+
1399
+ lang_dropdown.change(
1400
+ on_lang_change,
1401
+ inputs=[lang_dropdown],
1402
+ outputs=[lang_state] + all_ui_components
1403
+ )
1404
+
1405
+ # IP query state for async loading
1406
+ ip_query_state = gr.State({"status": "pending", "ip": None, "lang": "en"})
1407
+
1408
+ def on_load_immediate(request: gr.Request):
1409
+ """
1410
+ Load page with language based on robust IP detection
1411
+
1412
+ Features:
1413
+ - Multiple fallback layers for IP extraction
1414
+ - Comprehensive error handling
1415
+ - Always returns valid language (defaults to English)
1416
+ - Detailed logging for debugging
1417
+ """
1418
+ # Extract client IP with multiple fallback methods
1419
+ client_ip = None
1420
+ try:
1421
+ # Primary method: direct client host
1422
+ client_ip = request.client.host
1423
+
1424
+ # Secondary method: check forwarded headers
1425
+ headers = dict(request.headers) if hasattr(request, 'headers') else {}
1426
+ x_forwarded_for = headers.get('x-forwarded-for') or headers.get('X-Forwarded-For')
1427
+ if x_forwarded_for:
1428
+ # Take first IP from comma-separated list
1429
+ client_ip = x_forwarded_for.split(',')[0].strip()
1430
+
1431
+ # Alternative headers
1432
+ if not client_ip or client_ip in ["127.0.0.1", "localhost"]:
1433
+ client_ip = headers.get('x-real-ip') or headers.get('X-Real-IP') or client_ip
1434
+
1435
+ except Exception as e:
1436
+ print(f"Error extracting client IP: {e}, using default")
1437
+ client_ip = "unknown"
1438
+
1439
+ # Validate extracted IP
1440
+ if not client_ip:
1441
+ client_ip = "unknown"
1442
+
1443
+ print(f"Loading page for IP: {client_ip}")
1444
+
1445
+ # Determine language with robust error handling
1446
+ try:
1447
+ # Check if IP is already cached (second+ visit)
1448
+ if client_ip in IP_Country_Cache:
1449
+ # Use cached data - very fast
1450
+ cached_lang = get_lang_from_ip(client_ip)
1451
+ # Validate cached language
1452
+ if cached_lang and len(cached_lang) == 2:
1453
+ print(f"Using cached language: {cached_lang} for IP: {client_ip}")
1454
+ query_state = {"ip": client_ip, "cached": True}
1455
+ return cached_lang, cached_lang, query_state, *update_ui_lang(cached_lang).values()
1456
+
1457
+ # First visit: Query IP and determine language (max 3s timeout built-in)
1458
+ print(f"First visit - detecting language for IP: {client_ip}")
1459
+ detected_lang = get_lang_from_ip(client_ip)
1460
+
1461
+ # Double-check the detected language is valid
1462
+ if not detected_lang or len(detected_lang) != 2:
1463
+ print(f"Invalid detected language '{detected_lang}', using English")
1464
+ detected_lang = "en"
1465
+
1466
+ print(f"First visit - Final language: {detected_lang} for IP: {client_ip}")
1467
+ query_state = {"ip": client_ip, "cached": False}
1468
+ return detected_lang, detected_lang, query_state, *update_ui_lang(detected_lang).values()
1469
+
1470
+ except Exception as e:
1471
+ # Ultimate fallback - always works
1472
+ print(f"Critical error in language detection for {client_ip}: {e}")
1473
+ print("Using English as ultimate fallback")
1474
+ query_state = {"ip": client_ip or "unknown", "cached": False, "error": str(e)}
1475
+ return "en", "en", query_state, *update_ui_lang("en").values()
1476
+
1477
+
1478
+ app.load(
1479
+ on_load_immediate,
1480
+ inputs=None,
1481
+ outputs=[lang_state, lang_dropdown, ip_query_state] + all_ui_components,
1482
+ )
1483
+
1484
+ return app
1485
+
1486
+ if __name__ == "__main__":
1487
+ app = create_app()
1488
+ # Improve queue configuration to handle high concurrency and prevent SSE connection issues
1489
+ app.queue(
1490
+ default_concurrency_limit=20, # Default concurrency limit
1491
+ max_size=50, # Maximum queue size
1492
+ api_open=False # Close API access to reduce resource consumption
1493
+ )
1494
+ app.launch(
1495
+ server_name="0.0.0.0",
1496
+ show_error=True, # Show detailed error information
1497
+ quiet=False, # Keep log output
1498
+ max_threads=40, # Increase thread pool size
1499
+ height=800,
1500
+ favicon_path=None # Reduce resource loading
1501
+ )
__lib__/i18n/__init__.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ i18n loader for encrypted translation files
3
+ """
4
+ import sys
5
+ import importlib.util
6
+ from pathlib import Path
7
+
8
+ def load_pyc_module(module_name, pyc_path):
9
+ """Load a .pyc module using importlib"""
10
+ spec = importlib.util.spec_from_file_location(module_name, pyc_path)
11
+ if spec is None or spec.loader is None:
12
+ raise ImportError(f"Cannot load module {module_name} from {pyc_path}")
13
+ module = importlib.util.module_from_spec(spec)
14
+ sys.modules[module_name] = module
15
+ spec.loader.exec_module(module)
16
+ return module
17
+
18
+ def load_translations():
19
+ """Load all encrypted translation files"""
20
+ translations = {}
21
+ i18n_dir = Path(__file__).parent
22
+
23
+ # List all .pyc files in i18n directory
24
+ for pyc_file in i18n_dir.glob("*.pyc"):
25
+ lang = pyc_file.stem # Get language code from filename
26
+ try:
27
+ module = load_pyc_module(f"i18n_{lang}", pyc_file)
28
+ if hasattr(module, 'data'):
29
+ translations[lang] = module.data
30
+ except Exception as e:
31
+ print(f"Failed to load {pyc_file.name}: {e}")
32
+
33
+ return translations
34
+
35
+ # Auto-load translations when module is imported
36
+ translations = load_translations()
__lib__/i18n/ar.pyc ADDED
Binary file (12.3 kB). View file
 
__lib__/i18n/da.pyc ADDED
Binary file (9.79 kB). View file
 
__lib__/i18n/de.pyc ADDED
Binary file (10.3 kB). View file
 
__lib__/i18n/en.pyc ADDED
Binary file (9.08 kB). View file
 
__lib__/i18n/es.pyc ADDED
Binary file (10.3 kB). View file
 
__lib__/i18n/fi.pyc ADDED
Binary file (9.78 kB). View file
 
__lib__/i18n/fr.pyc ADDED
Binary file (10.8 kB). View file
 
__lib__/i18n/he.pyc ADDED
Binary file (11.5 kB). View file
 
__lib__/i18n/hi.pyc ADDED
Binary file (16.9 kB). View file
 
__lib__/i18n/id.pyc ADDED
Binary file (9.73 kB). View file
 
__lib__/i18n/it.pyc ADDED
Binary file (10.1 kB). View file
 
__lib__/i18n/ja.pyc ADDED
Binary file (11 kB). View file
 
__lib__/i18n/nl.pyc ADDED
Binary file (9.84 kB). View file
 
__lib__/i18n/no.pyc ADDED
Binary file (9.68 kB). View file
 
__lib__/i18n/pt.pyc ADDED
Binary file (10.2 kB). View file
 
__lib__/i18n/ru.pyc ADDED
Binary file (15 kB). View file
 
__lib__/i18n/sv.pyc ADDED
Binary file (9.76 kB). View file
 
__lib__/i18n/tr.pyc ADDED
Binary file (10.3 kB). View file
 
__lib__/i18n/uk.pyc ADDED
Binary file (14.5 kB). View file
 
__lib__/i18n/vi.pyc ADDED
Binary file (11.5 kB). View file
 
__lib__/i18n/zh.pyc ADDED
Binary file (8.95 kB). View file
 
__lib__/nfsw.pyc ADDED
Binary file (10 kB). View file
 
__lib__/util.pyc ADDED
Binary file (17.2 kB). View file
 
app.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Minimal app loader for ImageEditSpace
3
+ This app loads the compiled, obfuscated modules from __lib__
4
+ """
5
+ import sys
6
+ from pathlib import Path
7
+ import importlib.util
8
+
9
+ # Add __lib__ to path to import compiled modules
10
+ lib_dir = Path(__file__).parent / "__lib__"
11
+ if not lib_dir.exists():
12
+ raise RuntimeError(f"Compiled library directory not found: {lib_dir}")
13
+
14
+ sys.path.insert(0, str(lib_dir))
15
+
16
+ def load_pyc_module(module_name, pyc_path):
17
+ """Load a .pyc module using importlib"""
18
+ spec = importlib.util.spec_from_file_location(module_name, pyc_path)
19
+ if spec is None or spec.loader is None:
20
+ raise ImportError(f"Cannot load module {module_name} from {pyc_path}")
21
+ module = importlib.util.module_from_spec(spec)
22
+ sys.modules[module_name] = module
23
+ spec.loader.exec_module(module)
24
+ return module
25
+
26
+ try:
27
+ # Load compiled modules
28
+ util_module = load_pyc_module("util", lib_dir / "util.pyc")
29
+ nfsw_module = load_pyc_module("nfsw", lib_dir / "nfsw.pyc")
30
+
31
+ # Import app module (source file)
32
+ import app as app_module
33
+
34
+ # Create and launch app
35
+ app = app_module.create_app()
36
+ app.queue(
37
+ default_concurrency_limit=20,
38
+ max_size=50,
39
+ api_open=False
40
+ )
41
+ app.launch(
42
+ server_name="0.0.0.0",
43
+ show_error=True,
44
+ quiet=False,
45
+ max_threads=40,
46
+ height=800,
47
+ favicon_path=None
48
+ )
49
+
50
+ except ImportError as e:
51
+ print(f"Failed to import compiled modules: {e}")
52
+ print("Make sure to run build_encrypted.py first to compile the modules")
53
+ import traceback
54
+ traceback.print_exc()
55
+ sys.exit(1)
56
+ except Exception as e:
57
+ print(f"Error running app: {e}")
58
+ import traceback
59
+ traceback.print_exc()
60
+ sys.exit(1)
datas/cat01.webp ADDED

Git LFS Details

  • SHA256: 1bf3d4cb894df32d7175b6ba091b21f7efe28178561ef68b8d0cb90f84adcb83
  • Pointer size: 130 Bytes
  • Size of remote file: 56.1 kB
datas/panda01.jpeg ADDED

Git LFS Details

  • SHA256: ce5abe529cdd94a469fc1f86515e8f45a4d7b7594f9eadb318b8a597fd769a81
  • Pointer size: 131 Bytes
  • Size of remote file: 112 kB
datas/panda01m.jpeg ADDED

Git LFS Details

  • SHA256: 659dfb517584cf3bc4ab3f5b444db325917a2f6badc97ec8f277d500e75c8d40
  • Pointer size: 130 Bytes
  • Size of remote file: 21.4 kB
labels.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "0": "normal",
3
+ "1": "nsfw"
4
+ }
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.42.0
2
+ opencv-python>=4.8.0
3
+ requests>=2.28.0
4
+ func-timeout>=4.3.5
5
+ numpy>=1.24.0
6
+ boto3
7
+ botocore
8
+ onnxruntime
9
+ huggingface_hub>=0.16.0
10
+ Pillow>=9.0.0