selfitcamera
Initial commit - Encrypted version with i18n protection
0ec120e
raw
history blame
66.3 kB
import gradio as gr
import threading
import os
import shutil
import tempfile
import time
import json
from util import process_image_edit, download_and_check_result_nsfw, GoodWebsiteUrl
from nfsw import NSFWDetector
# Google Gemini URL for restricted languages
GOOGLE_GEMINI_URL = "https://aistudio.google.com/models/gemini-2-5-flash-image"
# i18n - Load from encrypted modules
import sys
from pathlib import Path
# Add i18n module to path
_i18n_module_path = Path(__file__).parent / "i18n"
if str(_i18n_module_path) not in sys.path:
sys.path.insert(0, str(_i18n_module_path))
# Import encrypted i18n loader
from i18n import translations as _translations
translations = _translations
def load_translations():
"""Compatibility function - translations are already loaded"""
return translations
def t(key, lang="en"):
return translations.get(lang, {}).get(key, key)
# Configuration parameters
# a = b
TIP_TRY_N = 3 # Show like button tip after 12 tries
FREE_TRY_N = 6 # Free phase: first 15 tries without restrictions
SLOW_TRY_N = 10 # Slow phase start: 25 tries
SLOW2_TRY_N = 14 # Slow phase start: 32 tries
RATE_LIMIT_60 = 16 # Full restriction: blocked after 40 tries
# Time window configuration (minutes)
PHASE_1_WINDOW = 5 # 15-25 tries: 5 minutes
PHASE_2_WINDOW = 10 # 25-32 tries: 10 minutes
PHASE_3_WINDOW = 20 # 32-40 tries: 20 minutes
MAX_IMAGES_PER_WINDOW = 2 # Max images per time window
high_priority_n = 2 # 每个ip只有第一个任务是高优先级的
IP_Dict = {}
# IP generation statistics and time window tracking
IP_Generation_Count = {} # Record total generation count for each IP
IP_Rate_Limit_Track = {} # Record generation count and timestamp in current time window for each IP
IP_Country_Cache = {} # Cache IP country information to avoid repeated queries
# Country usage statistics
Country_Usage_Stats = {} # Track usage count by country
Total_Request_Count = 0 # Total request counter for periodic printing
PRINT_STATS_INTERVAL = 10 # Print stats every N requests
# Async IP query tracking
IP_Query_Results = {} # Track async query results
# Restricted countries list (these countries have lower usage limits)
RESTRICTED_COUNTRIES = ["印度", "巴基斯坦", "俄罗斯", "中国", "伊朗"]
RESTRICTED_COUNTRY_LIMIT = 1 # Max usage for restricted countries
country_dict = {
"zh": ["中国"],
"hi": ["印度"],
"fi": ["芬兰"],
"en": ["美国", "澳大利亚", "英国", "加拿大", "新西兰", "爱尔兰"],
"es": ["西班牙", "墨西哥", "阿根廷", "哥伦比亚", "智利", "秘鲁"],
"pt": ["葡萄牙", "巴西"],
"fr": ["法国", "摩纳哥"],
"de": ["德国", "奥地利", ],
"it": ["意大利", "圣马力诺", "梵蒂冈"],
"ja": ["日本"],
"ru": ["俄罗斯"],
"uk": ["乌克兰"],
"ar": ["沙特阿拉伯", "埃及", "阿拉伯联合酋长国", "摩洛哥"],
"nl":["荷兰"],
"no":["挪威"],
"sv":["瑞典"],
"id":["印度尼西亚"],
"vi": ["越南"],
"he": ["以色列"],
"tr": ["土耳其"],
"da": ["丹麦"],
}
def query_ip_country(client_ip):
"""
Query IP address geo information with robust error handling
Features:
- 3 second timeout limit
- Comprehensive error handling
- Automatic fallback to default values
- Cache mechanism to avoid repeated queries
Returns:
dict: {"country": str, "region": str, "city": str}
"""
# Check cache first - no API call for subsequent visits
if client_ip in IP_Country_Cache:
print(f"Using cached IP data for {client_ip}")
return IP_Country_Cache[client_ip]
# Validate IP address
if not client_ip or client_ip in ["127.0.0.1", "localhost", "::1"]:
print(f"Invalid or local IP address: {client_ip}, using default")
default_geo = {"country": "Unknown", "region": "Unknown", "city": "Unknown"}
IP_Country_Cache[client_ip] = default_geo
return default_geo
# First time visit - query API with robust error handling
print(f"Querying IP geolocation for {client_ip}...")
try:
import requests
from requests.exceptions import Timeout, ConnectionError, RequestException
api_url = f"https://api.vore.top/api/IPdata?ip={client_ip}"
# Make request with 3 second timeout
response = requests.get(api_url, timeout=3)
if response.status_code == 200:
data = response.json()
if data.get("code") == 200 and "ipdata" in data:
ipdata = data["ipdata"]
geo_info = {
"country": ipdata.get("info1", "Unknown"),
"region": ipdata.get("info2", "Unknown"),
"city": ipdata.get("info3", "Unknown")
}
IP_Country_Cache[client_ip] = geo_info
print(f"Successfully detected location for {client_ip}: {geo_info['country']}")
return geo_info
else:
print(f"API returned invalid data for {client_ip}: {data}")
else:
print(f"API request failed with status {response.status_code} for {client_ip}")
except Timeout:
print(f"Timeout (>3s) querying IP location for {client_ip}, using default")
except ConnectionError:
print(f"Network connection error for IP {client_ip}, using default")
except RequestException as e:
print(f"Request error for IP {client_ip}: {e}, using default")
except Exception as e:
print(f"Unexpected error querying IP {client_ip}: {e}, using default")
# All failures lead here - cache default and return
default_geo = {"country": "Unknown", "region": "Unknown", "city": "Unknown"}
IP_Country_Cache[client_ip] = default_geo
print(f"Cached default location for {client_ip}")
return default_geo
def query_ip_country_async(client_ip):
"""
Async version that returns immediately with default, then updates cache in background
Returns:
tuple: (immediate_lang, geo_info_or_none)
"""
# If already cached, return immediately
if client_ip in IP_Country_Cache:
geo_info = IP_Country_Cache[client_ip]
lang = get_lang_from_country(geo_info["country"])
return lang, geo_info
# Return default immediately, query in background
return "en", None
def get_lang_from_country(country):
"""
Map country name to language code with comprehensive validation
Features:
- Handles invalid/empty input
- Case-insensitive matching
- Detailed logging
- Always returns valid language code
Args:
country (str): Country name
Returns:
str: Language code (always valid, defaults to "en")
"""
# Input validation
if not country or not isinstance(country, str) or country.strip() == "":
print(f"Invalid country provided: '{country}', defaulting to English")
return "en"
# Normalize country name
country = country.strip()
if country.lower() == "unknown":
print(f"Unknown country, defaulting to English")
return "en"
try:
# Search in country dictionary with case-sensitive match first
for lang, countries in country_dict.items():
if country in countries:
print(f"Matched country '{country}' to language '{lang}'")
return lang
# If no exact match, try case-insensitive match
country_lower = country.lower()
for lang, countries in country_dict.items():
for country_variant in countries:
if country_variant.lower() == country_lower:
print(f"Case-insensitive match: country '{country}' to language '{lang}'")
return lang
# No match found
print(f"Country '{country}' not found in country_dict, defaulting to English")
return "en"
except Exception as e:
print(f"Error matching country '{country}': {e}, defaulting to English")
return "en"
def get_lang_from_ip(client_ip):
"""
Get language based on IP geolocation with comprehensive error handling
Features:
- Validates input IP address
- Handles all possible exceptions
- Always returns a valid language code
- Defaults to English on any failure
- Includes detailed logging
Args:
client_ip (str): Client IP address
Returns:
str: Language code (always valid, defaults to "en")
"""
# Input validation
if not client_ip or not isinstance(client_ip, str):
print(f"Invalid IP address provided: {client_ip}, defaulting to English")
return "en"
try:
# Query geolocation info (has its own error handling and 3s timeout)
geo_info = query_ip_country(client_ip)
if not geo_info or not isinstance(geo_info, dict):
print(f"No geolocation data for {client_ip}, defaulting to English")
return "en"
# Extract country with fallback
country = geo_info.get("country", "Unknown")
if not country or country == "Unknown":
print(f"Unknown country for IP {client_ip}, defaulting to English")
return "en"
# Map country to language
detected_lang = get_lang_from_country(country)
# Validate language code
if not detected_lang or not isinstance(detected_lang, str) or len(detected_lang) != 2:
print(f"Invalid language code '{detected_lang}' for {client_ip}, defaulting to English")
return "en"
print(f"IP {client_ip} -> Country: {country} -> Language: {detected_lang}")
return detected_lang
except Exception as e:
print(f"Unexpected error getting language from IP {client_ip}: {e}, defaulting to English")
return "en" # Always return a valid language code
def is_restricted_country_ip(client_ip):
"""
Check if IP is from a restricted country
Returns:
bool: True if from restricted country
"""
geo_info = query_ip_country(client_ip)
country = geo_info["country"]
return country in RESTRICTED_COUNTRIES
def get_ip_max_limit(client_ip):
"""
Get max usage limit for IP based on country
Returns:
int: Max usage limit
"""
if is_restricted_country_ip(client_ip):
return RESTRICTED_COUNTRY_LIMIT
else:
return RATE_LIMIT_60
def get_ip_generation_count(client_ip):
"""
Get IP generation count
"""
if client_ip not in IP_Generation_Count:
IP_Generation_Count[client_ip] = 0
return IP_Generation_Count[client_ip]
def increment_ip_generation_count(client_ip):
"""
Increment IP generation count
"""
if client_ip not in IP_Generation_Count:
IP_Generation_Count[client_ip] = 0
IP_Generation_Count[client_ip] += 1
return IP_Generation_Count[client_ip]
def get_ip_phase(client_ip):
"""
Get current phase for IP
Returns:
str: 'free', 'rate_limit_1', 'rate_limit_2', 'rate_limit_3', 'blocked'
"""
count = get_ip_generation_count(client_ip)
max_limit = get_ip_max_limit(client_ip)
# For restricted countries, check if they've reached their limit
if is_restricted_country_ip(client_ip):
if count >= max_limit:
return 'blocked'
elif count >= max_limit - 2: # Last 2 attempts
return 'rate_limit_3'
elif count >= max_limit - 3: # 3rd attempt from end
return 'rate_limit_2'
elif count >= max_limit - 4: # 4th attempt from end
return 'rate_limit_1'
else:
return 'free'
# For normal countries, use standard limits
if count < FREE_TRY_N:
return 'free'
elif count < SLOW_TRY_N:
return 'rate_limit_1' # NSFW blur + 5 minutes 2 images
elif count < SLOW2_TRY_N:
return 'rate_limit_2' # NSFW blur + 10 minutes 2 images
elif count < max_limit:
return 'rate_limit_3' # NSFW blur + 20 minutes 2 images
else:
return 'blocked' # Generation blocked
def check_rate_limit_for_phase(client_ip, phase):
"""
Check rate limit for specific phase
Returns:
tuple: (is_limited, wait_time_minutes, current_count)
"""
if phase not in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3']:
return False, 0, 0
# Determine time window
if phase == 'rate_limit_1':
window_minutes = PHASE_1_WINDOW
elif phase == 'rate_limit_2':
window_minutes = PHASE_2_WINDOW
else: # rate_limit_3
window_minutes = PHASE_3_WINDOW
current_time = time.time()
window_key = f"{client_ip}_{phase}"
# Clean expired records
if window_key in IP_Rate_Limit_Track:
track_data = IP_Rate_Limit_Track[window_key]
# Check if within current time window
if current_time - track_data['start_time'] > window_minutes * 60:
# Time window expired, reset
IP_Rate_Limit_Track[window_key] = {
'count': 0,
'start_time': current_time,
'last_generation': current_time
}
else:
# Initialize
IP_Rate_Limit_Track[window_key] = {
'count': 0,
'start_time': current_time,
'last_generation': current_time
}
track_data = IP_Rate_Limit_Track[window_key]
# Check if exceeded limit
if track_data['count'] >= MAX_IMAGES_PER_WINDOW:
# Calculate remaining wait time
elapsed = current_time - track_data['start_time']
wait_time = (window_minutes * 60) - elapsed
wait_minutes = max(0, wait_time / 60)
return True, wait_minutes, track_data['count']
return False, 0, track_data['count']
def update_country_stats(client_ip):
"""
Update country usage statistics and print periodically
"""
global Total_Request_Count, Country_Usage_Stats
# Get country info
geo_info = IP_Country_Cache.get(client_ip, {"country": "Unknown", "region": "Unknown", "city": "Unknown"})
country = geo_info["country"]
# Update country stats
if country not in Country_Usage_Stats:
Country_Usage_Stats[country] = 0
Country_Usage_Stats[country] += 1
# Increment total request counter
Total_Request_Count += 1
# Print stats every N requests
if Total_Request_Count % PRINT_STATS_INTERVAL == 0:
print("\n" + "="*60)
print(f"📊 国家使用统计 (总请求数: {Total_Request_Count})")
print("="*60)
# Sort by usage count (descending)
sorted_stats = sorted(Country_Usage_Stats.items(), key=lambda x: x[1], reverse=True)
for country_name, count in sorted_stats:
percentage = (count / Total_Request_Count) * 100
print(f" {country_name}: {count} 次 ({percentage:.1f}%)")
print("="*60 + "\n")
def record_generation_attempt(client_ip, phase):
"""
Record generation attempt
"""
# Increment total count
increment_ip_generation_count(client_ip)
# Update country statistics
update_country_stats(client_ip)
# Record time window count
if phase in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3']:
window_key = f"{client_ip}_{phase}"
current_time = time.time()
if window_key in IP_Rate_Limit_Track:
IP_Rate_Limit_Track[window_key]['count'] += 1
IP_Rate_Limit_Track[window_key]['last_generation'] = current_time
else:
IP_Rate_Limit_Track[window_key] = {
'count': 1,
'start_time': current_time,
'last_generation': current_time
}
def apply_gaussian_blur_to_image_url(image_url, blur_strength=50):
"""
Apply Gaussian blur to image URL
Args:
image_url (str): Original image URL
blur_strength (int): Blur strength, default 50 (heavy blur)
Returns:
PIL.Image: Blurred PIL Image object
"""
try:
import requests
from PIL import Image, ImageFilter
import io
# Download image
response = requests.get(image_url, timeout=30)
if response.status_code != 200:
return None
# Convert to PIL Image
image_data = io.BytesIO(response.content)
image = Image.open(image_data)
# Apply heavy Gaussian blur
blurred_image = image.filter(ImageFilter.GaussianBlur(radius=blur_strength))
return blurred_image
except Exception as e:
print(f"⚠️ Failed to apply Gaussian blur: {e}")
return None
# Initialize NSFW detector (download from Hugging Face)
try:
nsfw_detector = NSFWDetector() # Auto download falconsai_yolov9_nsfw_model_quantized.pt from Hugging Face
print("✅ NSFW detector initialized successfully")
except Exception as e:
print(f"❌ NSFW detector initialization failed: {e}")
nsfw_detector = None
def edit_image_interface(input_image, prompt, lang, request: gr.Request, progress=gr.Progress()):
"""
Interface function for processing image editing with phase-based limitations
"""
try:
# Extract user IP
client_ip = request.client.host
x_forwarded_for = dict(request.headers).get('x-forwarded-for')
if x_forwarded_for:
client_ip = x_forwarded_for
if client_ip not in IP_Dict:
IP_Dict[client_ip] = 0
IP_Dict[client_ip] += 1
if input_image is None:
return None, t("error_upload_first", lang), gr.update(visible=False)
if not prompt or prompt.strip() == "":
return None, t("error_enter_prompt", lang), gr.update(visible=False)
# Check if prompt length is greater than 3 characters
if len(prompt.strip()) <= 3:
return None, t("error_prompt_too_short", lang), gr.update(visible=False)
except Exception as e:
print(f"⚠️ Request preprocessing error: {e}")
return None, t("error_request_processing", lang), gr.update(visible=False)
# Get user current phase
current_phase = get_ip_phase(client_ip)
current_count = get_ip_generation_count(client_ip)
geo_info = IP_Country_Cache.get(client_ip, {"country": "Unknown", "region": "Unknown", "city": "Unknown"})
is_restricted = is_restricted_country_ip(client_ip)
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}")
# Check if user reached the like button tip threshold
# For restricted countries, show like tip from the first attempt
show_like_tip = (current_count >= 1) if is_restricted else (current_count >= TIP_TRY_N)
# Check if completely blocked
if current_phase == 'blocked':
# Generate blocked limit button with different URL for restricted countries
if is_restricted or lang in ["hi", "ru", "zh"]:
blocked_url = GOOGLE_GEMINI_URL
else:
blocked_url = 'https://omnicreator.net/#generator'
blocked_button_html = f"""
<div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
<a href='{blocked_url}' target='_blank' style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16px 32px;
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
color: white;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-align: center;
min-width: 200px;
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
transition: all 0.3s ease;
border: none;
'>&#128640; Unlimited Generation</a>
</div>
"""
# Use same message for all users to avoid discrimination perception
blocked_message = t("error_free_limit_reached", lang)
return None, blocked_message, gr.update(value=blocked_button_html, visible=True)
# Check rate limit (applies to rate_limit phases)
if current_phase in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3']:
is_limited, wait_minutes, window_count = check_rate_limit_for_phase(client_ip, current_phase)
if is_limited:
wait_minutes_int = int(wait_minutes) + 1
# Generate rate limit button with different URL for restricted countries
if is_restricted or lang in ["hi", "ru", "zh"]:
rate_limit_url = GOOGLE_GEMINI_URL
else:
rate_limit_url = 'https://omnicreator.net/#generator'
rate_limit_button_html = f"""
<div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
<a href='{rate_limit_url}' target='_blank' style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16px 32px;
background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%);
color: white;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-align: center;
min-width: 200px;
box-shadow: 0 4px 15px rgba(243, 156, 18, 0.4);
transition: all 0.3s ease;
border: none;
'>⏰ Skip Wait - Unlimited Generation</a>
</div>
"""
return None, t("error_free_limit_wait", lang).format(wait_minutes_int=wait_minutes_int), gr.update(value=rate_limit_button_html, visible=True)
# Handle NSFW detection based on phase
is_nsfw_task = False # Track if this task involves NSFW content
# Skip NSFW detection in free phase
if current_phase != 'free' and nsfw_detector is not None and input_image is not None:
try:
nsfw_result = nsfw_detector.predict_pil_label_only(input_image)
if nsfw_result.lower() == "nsfw":
is_nsfw_task = True
print(f"🔍 Input NSFW detected in {current_phase} phase: ❌❌❌ {nsfw_result} - IP: {client_ip} (will blur result)")
else:
print(f"🔍 Input NSFW check passed: ✅✅✅ {nsfw_result} - IP: {client_ip}")
except Exception as e:
print(f"⚠️ Input NSFW detection failed: {e}")
# Allow continuation when detection fails
result_url = None
status_message = ""
def progress_callback(message):
try:
nonlocal status_message
status_message = message
# Add error handling to prevent progress update failure
if progress is not None:
# Enhanced progress display with better formatting
if "Queue:" in message or "tasks ahead" in message:
# Queue status - show with different progress value to indicate waiting
progress(0.1, desc=message)
elif "Processing" in message or "AI is processing" in message:
# Processing status
progress(0.7, desc=message)
elif "Generating" in message or "Almost done" in message:
# Generation status
progress(0.9, desc=message)
else:
# Default status
progress(0.5, desc=message)
except Exception as e:
print(f"⚠️ Progress update failed: {e}")
try:
# Determine priority before recording generation attempt
# First high_priority_n tasks for each IP get priority=1
task_priority = 1 if current_count < high_priority_n else 0
# Record generation attempt (before actual generation to ensure correct count)
record_generation_attempt(client_ip, current_phase)
updated_count = get_ip_generation_count(client_ip)
print(f"✅ Processing started - IP: {client_ip}, phase: {current_phase}, total count: {updated_count}, priority: {task_priority}, prompt: {prompt.strip()}", flush=True)
# Call image editing processing function with priority
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)
# Check if HF user limit exceeded
if message and message.startswith("HF_LIMIT_EXCEEDED:"):
error_message = message.replace("HF_LIMIT_EXCEEDED:", "")
# Generate HF limit exceeded button (similar to blocked status)
hf_limit_url = 'https://omnicreator.net/#generator'
hf_limit_button_html = f"""
<div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
<a href='{hf_limit_url}' target='_blank' style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16px 32px;
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
color: white;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-align: center;
min-width: 200px;
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4);
transition: all 0.3s ease;
border: none;
'>&#128640; Unlimited Generation</a>
</div>
"""
# Use translated message or default
limit_message = error_message if error_message else t("error_free_limit_reached", lang)
return None, limit_message, gr.update(value=hf_limit_button_html, visible=True)
if result_url:
print(f"✅ Processing completed successfully - IP: {client_ip}, result_url: {result_url}, task_uuid: {task_uuid}", flush=True)
# Detect result image NSFW content (only in rate limit phases)
if nsfw_detector is not None and current_phase != 'free':
try:
if progress is not None:
progress(0.9, desc=t("status_checking_result", lang))
is_nsfw, nsfw_error = download_and_check_result_nsfw(result_url, nsfw_detector)
if nsfw_error:
print(f"⚠️ Result image NSFW detection error - IP: {client_ip}, error: {nsfw_error}")
elif is_nsfw:
is_nsfw_task = True # Mark task as NSFW
print(f"🔍 Result image NSFW detected in {current_phase} phase: ❌❌❌ - IP: {client_ip} (will blur result)")
else:
print(f"🔍 Result image NSFW check passed: ✅✅✅ - IP: {client_ip}")
except Exception as e:
print(f"⚠️ Result image NSFW detection exception - IP: {client_ip}, error: {str(e)}")
# Apply blur if this is an NSFW task in rate limit phases
should_blur = False
if current_phase in ['rate_limit_1', 'rate_limit_2', 'rate_limit_3'] and is_nsfw_task:
should_blur = True
# Apply blur processing
if should_blur:
if progress is not None:
progress(0.95, desc=t("status_applying_filter", lang))
blurred_image = apply_gaussian_blur_to_image_url(result_url)
if blurred_image is not None:
final_result = blurred_image # Return PIL Image object
final_message = t("warning_content_filter", lang)
print(f"🔒 Applied Gaussian blur for NSFW content - IP: {client_ip}")
else:
# Blur failed, return original URL with warning
final_result = result_url
final_message = t("warning_content_review", lang)
# Generate NSFW button for blurred content with different URL for restricted countries
if is_restricted or lang in ["hi", "ru", "zh"]:
nsfw_url = GOOGLE_GEMINI_URL
else:
nsfw_url = 'https://omnicreator.net/#generator'
nsfw_action_buttons_html = f"""
<div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
<a href='{nsfw_url}' target='_blank' style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16px 32px;
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
color: white;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-align: center;
min-width: 200px;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
transition: all 0.3s ease;
border: none;
'>🔥 Unlimited Creative Generation</a>
</div>
"""
return final_result, final_message, gr.update(value=nsfw_action_buttons_html, visible=True)
else:
final_result = result_url
final_message = t("status_completed_message", lang).format(message=message)
try:
if progress is not None:
progress(1.0, desc=t("status_processing_completed", lang))
except Exception as e:
print(f"⚠️ Final progress update failed: {e}")
# Generate action buttons HTML
action_buttons_html = ""
# For restricted countries, only show like tip (no action buttons)
if is_restricted:
if show_like_tip:
action_buttons_html = """
<div style='display: flex; justify-content: center; margin: 15px 0 5px 0; padding: 0px;'>
<div style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 24px;
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
color: white;
border-radius: 10px;
font-weight: 600;
font-size: 14px;
text-align: center;
max-width: 400px;
box-shadow: 0 3px 12px rgba(255, 107, 107, 0.3);
border: none;
'>👉 Click the ❤️ Like button to unlock more free trial attempts!</div>
</div>
"""
else:
# For non-restricted countries, show normal buttons
if task_uuid and lang not in ["zh", "hi", "ru"]:
# Create task detail URL for downloading HD image
task_detail_url = f"https://omnicreator.net/my-creations/task/{task_uuid}"
# Create i2v URL with input and output images and prompt
from urllib.parse import quote
encoded_prompt = quote(prompt.strip())
i2v_url = f"https://omnicreator.net/image-to-video?input_image={input_image_url}&end_image={result_url}&prompt={encoded_prompt}"
action_buttons_html = f"""
<div style='display: flex; justify-content: center; gap: 15px; margin: 10px 0 5px 0; padding: 0px;'>
<a href='{i2v_url}' target='_blank' style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16px 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-align: center;
min-width: 160px;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
transition: all 0.3s ease;
border: none;
'>&#127909; Convert to Video</a>
<a href='{task_detail_url}' target='_blank' style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16px 32px;
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-align: center;
min-width: 160px;
box-shadow: 0 4px 15px rgba(17, 153, 142, 0.4);
transition: all 0.3s ease;
border: none;
'>&#128190; Download HD Image</a>
</div>
"""
# Add popup script if needed (using different approach)
if show_like_tip or lang in ["zh", "hi", "ru"]:
action_buttons_html += """
<div style='display: flex; justify-content: center; margin: 15px 0 5px 0; padding: 0px;'>
<div style='
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 24px;
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
color: white;
border-radius: 10px;
font-weight: 600;
font-size: 14px;
text-align: center;
max-width: 400px;
box-shadow: 0 3px 12px rgba(255, 107, 107, 0.3);
border: none;
'>👉 Click the ❤️ Like button to unlock more free trial attempts!</div>
</div>
"""
return final_result, final_message, gr.update(value=action_buttons_html, visible=True)
else:
print(f"❌ Processing failed - IP: {client_ip}, error: {message}", flush=True)
return None, t("error_processing_failed", lang).format(message=message), gr.update(visible=False)
except Exception as e:
print(f"❌ Processing exception - IP: {client_ip}, error: {str(e)}")
return None, t("error_processing_exception", lang).format(error=str(e)), gr.update(visible=False)
# Create Gradio interface
def create_app():
with gr.Blocks(
title="AI Image Editor",
theme=gr.themes.Soft(),
css="""
.main-container {
max-width: 1200px;
margin: 0 auto;
}
.news-banner-row {
margin: 10px auto 15px auto;
padding: 0 10px;
max-width: 1200px;
width: 100% !important;
}
.news-banner-row .gr-row {
display: flex !important;
align-items: center !important;
width: 100% !important;
}
.news-banner-row .gr-column:first-child {
flex: 1 !important; /* 占据所有剩余空间 */
display: flex !important;
justify-content: center !important; /* 在其空间内居中 */
}
.banner-lang-selector {
margin-left: auto !important;
display: flex !important;
justify-content: flex-end !important;
align-items: center !important;
position: relative !important;
z-index: 10 !important;
}
.banner-lang-selector .gr-dropdown {
background: white !important;
border: 1px solid #ddd !important;
border-radius: 8px !important;
padding: 8px 16px !important;
font-size: 14px !important;
font-weight: 500 !important;
color: #333 !important;
cursor: pointer !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
min-width: 140px !important;
max-width: 160px !important;
transition: all 0.2s ease !important;
}
.banner-lang-selector .gr-dropdown:hover {
border-color: #999 !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
}
@media (max-width: 768px) {
.news-banner-row {
padding: 0 15px !important;
}
.news-banner-row .gr-row {
display: flex !important;
flex-direction: column !important;
gap: 10px !important;
position: static !important;
}
.news-banner-row .gr-column:first-child {
position: static !important;
pointer-events: auto !important;
}
.banner-lang-selector {
margin-left: 0 !important;
justify-content: center !important;
}
}
.upload-area {
border: 2px dashed #ccc;
border-radius: 10px;
padding: 20px;
text-align: center;
}
.result-area {
margin-top: 20px;
padding: 20px;
border-radius: 10px;
background-color: #f8f9fa;
}
.use-as-input-btn {
margin-top: 10px;
width: 100%;
}
""",
# Improve concurrency performance configuration
head="""
<script>
// Reduce client-side state update frequency, avoid excessive SSE connections
if (window.gradio) {
window.gradio.update_frequency = 2000; // Update every 2 seconds
}
</script>
"""
) as app:
lang_state = gr.State("en")
# Main title - centered
header_title = gr.HTML(f"""
<div style="text-align: center; margin: 20px auto 10px auto; max-width: 800px;">
<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);">
{t('header_title', 'en')}
</h1>
</div>
""")
with gr.Row(elem_classes=["news-banner-row"]):
with gr.Column(scale=1, min_width=400):
# Banner is initially visible (will be hidden for zh/hi/ru languages on load)
news_banner = gr.HTML(f"""
<style>
@keyframes breathe {{
0%, 100% {{ transform: scale(1); }}
50% {{ transform: scale(1.02); }}
}}
.breathing-banner {{
animation: breathe 3s ease-in-out infinite;
}}
</style>
<div class="breathing-banner" style="
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0 auto;
padding: 8px 40px;
border-radius: 20px;
max-width: 800px;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
text-align: center;
width: fit-content;
">
<span style="color: white; font-weight: 600; font-size: 1.0em;">
🎉 NEW:
<a href="https://huggingface.co/spaces/Selfit/Multi-Image-Edit" target="_blank" style="
color: white;
text-decoration: none;
border-bottom: 1px solid rgba(255,255,255,0.5);
transition: all 0.3s ease;
margin: 0 8px;
" onmouseover="this.style.borderBottom='1px solid white'"
onmouseout="this.style.borderBottom='1px solid rgba(255,255,255,0.5)'">
Multi-Image-Edit
</a>
|
<a href="https://huggingface.co/spaces/Selfit/Text-to-Image-Pro" target="_blank" style="
color: white;
text-decoration: none;
border-bottom: 1px solid rgba(255,255,255,0.5);
transition: all 0.3s ease;
margin: 0 8px;
" onmouseover="this.style.borderBottom='1px solid white'"
onmouseout="this.style.borderBottom='1px solid rgba(255,255,255,0.5)'">
Text-to-Image
</a>
</span>
</div>
""", visible=True)
with gr.Column(scale=0, min_width=160, elem_classes=["banner-lang-selector"]):
lang_dropdown = gr.Dropdown(
choices=[
("English", "en"),
("हिंदी", "hi"),
("中文", "zh"),
("Suomi", "fi"),
("Español", "es"),
("Português", "pt"),
("Français", "fr"),
("Deutsch", "de"),
("Italiano", "it"),
("日本語", "ja"),
("Русский", "ru"),
("Українська", "uk"),
("العربية", "ar"),
("Nederlands", "nl"),
("Norsk", "no"),
("Svenska", "sv"),
("Indonesian", "id"),
("Tiếng Việt", "vi"),
("עברית", "he"),
("Türkçe", "tr"),
("Dansk", "da")
],
value="en",
label="🌐",
show_label=True,
interactive=True,
container=False
)
with gr.Tabs() as tabs:
with gr.Tab(t("global_editor_tab", "en")) as global_tab:
with gr.Row():
with gr.Column(scale=1):
upload_image_header = gr.Markdown(t("upload_image_header", "en"))
input_image = gr.Image(
label=t("upload_image_label", "en"),
type="pil",
height=512,
elem_classes=["upload-area"]
)
editing_instructions_header = gr.Markdown(t("editing_instructions_header", "en"))
prompt_input = gr.Textbox(
label=t("prompt_input_label", "en"),
placeholder=t("prompt_input_placeholder", "en"),
lines=3,
max_lines=5
)
edit_button = gr.Button(
t("start_editing_button", "en"),
variant="primary",
size="lg"
)
with gr.Column(scale=1):
editing_result_header = gr.Markdown(t("editing_result_header", "en"))
output_image = gr.Image(
label=t("output_image_label", "en"),
height=320,
elem_classes=["result-area"]
)
use_as_input_btn = gr.Button(
t("use_as_input_button", "en"),
variant="secondary",
size="sm",
elem_classes=["use-as-input-btn"]
)
status_output = gr.Textbox(
label=t("status_output_label", "en"),
lines=2,
max_lines=3,
interactive=False
)
action_buttons = gr.HTML(visible=False)
prompt_examples_header = gr.Markdown(t("prompt_examples_header", "en"))
with gr.Row():
example_prompts = [
"Set the background to a grand opera stage with red curtains",
"Change the outfit into a traditional Chinese hanfu with flowing sleeves",
"Give the character blue dragon-like eyes with glowing pupils",
"Change lighting to soft dreamy pastel glow",
"Change pose to sitting cross-legged on the ground"
]
for prompt in example_prompts:
gr.Button(
prompt,
size="sm"
).click(
lambda p=prompt: p,
outputs=prompt_input
)
edit_button.click(
fn=edit_image_interface,
inputs=[input_image, prompt_input, lang_state],
outputs=[output_image, status_output, action_buttons],
show_progress=True,
concurrency_limit=20
)
def simple_use_as_input(output_img):
if output_img is not None:
return output_img
return None
use_as_input_btn.click(
fn=simple_use_as_input,
inputs=[output_image],
outputs=[input_image]
)
# SEO Content Section
seo_html = gr.HTML()
def get_seo_html(lang):
# 中文、印度语、俄语不显示SEO部分
if lang in ["zh", "hi", "ru"]:
return ""
return f"""
<div style="width: 100%; margin: 50px 0; padding: 0 20px;">
<div style="text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; border-radius: 20px; margin: 40px 0;">
<h2 style="margin: 0 0 20px 0; font-size: 2.2em; font-weight: 700;">
&#127912; {t('seo_unlimited_title', lang)}
</h2>
<p style="margin: 0 0 25px 0; font-size: 1.2em; opacity: 0.95; line-height: 1.6;">
{t('seo_unlimited_desc', lang)}
</p>
<div style="display: flex; justify-content: center; gap: 25px; flex-wrap: wrap; margin: 30px 0;">
<a href="https://omnicreator.net/#generator" target="_blank" style="
display: inline-flex;
align-items: center;
justify-content: center;
padding: 20px 40px;
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
color: white;
text-decoration: none;
border-radius: 15px;
font-weight: 700;
font-size: 18px;
text-align: center;
min-width: 250px;
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4);
transition: all 0.3s ease;
border: none;
transform: scale(1);
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
&#128640; {t('seo_unlimited_button', lang)}
</a>
</div>
<p style="color: rgba(255,255,255,0.9); font-size: 1em; margin: 20px 0 0 0;">
{t('seo_unlimited_footer', lang)}
</p>
</div>
<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);">
<h2 style="color: #2c3e50; margin: 0 0 20px 0; font-size: 1.9em; font-weight: 700;">
&#11088; {t('seo_professional_title', lang)}
</h2>
<p style="color: #555; font-size: 1.1em; line-height: 1.6; margin: 0 0 20px 0; padding: 0 20px;">
{t('seo_professional_desc', lang)}
</p>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 25px; margin: 40px 0;">
<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;">
<h3 style="color: #e74c3c; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
&#127919; {t('seo_feature1_title', lang)}
</h3>
<p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
{t('seo_feature1_desc', lang)}
</p>
</div>
<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;">
<h3 style="color: #3498db; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
🔓 {t('seo_feature2_title', lang)}
</h3>
<p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
{t('seo_feature2_desc', lang)}
</p>
</div>
<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;">
<h3 style="color: #27ae60; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
&#9889; {t('seo_feature3_title', lang)}
</h3>
<p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
{t('seo_feature3_desc', lang)}
</p>
</div>
<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;">
<h3 style="color: #9b59b6; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
&#127912; {t('seo_feature4_title', lang)}
</h3>
<p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
{t('seo_feature4_desc', lang)}
</p>
</div>
<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;">
<h3 style="color: #f39c12; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
&#128142; {t('seo_feature5_title', lang)}
</h3>
<p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
{t('seo_feature5_desc', lang)}
</p>
</div>
<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;">
<h3 style="color: #34495e; margin: 0 0 15px 0; font-size: 1.4em; font-weight: 600;">
🌍 {t('seo_feature6_title', lang)}
</h3>
<p style="color: #666; margin: 0; line-height: 1.6; font-size: 1em;">
{t('seo_feature6_desc', lang)}
</p>
</div>
</div>
<div style="background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%); padding: 30px; border-radius: 15px; margin: 40px 0;">
<h3 style="color: #8b5cf6; text-align: center; margin: 0 0 25px 0; font-size: 1.5em; font-weight: 700;">
&#128161; {t('seo_protips_title', lang)}
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 18px;">
<div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
<strong style="color: #8b5cf6; font-size: 1.1em;">📝 {t('seo_protip1_title', lang)}</strong>
<p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip1_desc', lang)}</p>
</div>
<div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
<strong style="color: #8b5cf6; font-size: 1.1em;">&#127919; {t('seo_protip2_title', lang)}</strong>
<p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip2_desc', lang)}</p>
</div>
<div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
<strong style="color: #8b5cf6; font-size: 1.1em;">&#9889; {t('seo_protip3_title', lang)}</strong>
<p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip3_desc', lang)}</p>
</div>
<div style="background: rgba(255,255,255,0.85); padding: 18px; border-radius: 12px;">
<strong style="color: #8b5cf6; font-size: 1.1em;">&#128444; {t('seo_protip4_title', lang)}</strong>
<p style="color: #555; margin: 5px 0 0 0; line-height: 1.5;">{t('seo_protip4_desc', lang)}</p>
</div>
</div>
</div>
<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);">
<h2 style="color: #2c3e50; margin: 0 0 20px 0; font-size: 1.8em; font-weight: 700;">
&#128640; {t('seo_needs_title', lang)}
</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 25px 0; text-align: left;">
<div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
<h4 style="color: #e74c3c; margin: 0 0 10px 0;">🎨 {t('seo_needs_art_title', lang)}</h4>
<ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
<li>{t('seo_needs_art_item1', lang)}</li>
<li>{t('seo_needs_art_item2', lang)}</li>
<li>{t('seo_needs_art_item3', lang)}</li>
<li>{t('seo_needs_art_item4', lang)}</li>
</ul>
</div>
<div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
<h4 style="color: #3498db; margin: 0 0 10px 0;">📸 {t('seo_needs_photo_title', lang)}</h4>
<ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
<li>{t('seo_needs_photo_item1', lang)}</li>
<li>{t('seo_needs_photo_item2', lang)}</li>
<li>{t('seo_needs_photo_item3', lang)}</li>
<li>{t('seo_needs_photo_item4', lang)}</li>
</ul>
</div>
<div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
<h4 style="color: #27ae60; margin: 0 0 10px 0;">🛍️ {t('seo_needs_ecom_title', lang)}</h4>
<ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
<li>{t('seo_needs_ecom_item1', lang)}</li>
<li>{t('seo_needs_ecom_item2', lang)}</li>
<li>{t('seo_needs_ecom_item3', lang)}</li>
<li>{t('seo_needs_ecom_item4', lang)}</li>
</ul>
</div>
<div style="background: rgba(255,255,255,0.8); padding: 20px; border-radius: 12px;">
<h4 style="color: #9b59b6; margin: 0 0 10px 0;">📱 {t('seo_needs_social_title', lang)}</h4>
<ul style="color: #555; margin: 0; padding-left: 18px; line-height: 1.6;">
<li>{t('seo_needs_social_item1', lang)}</li>
<li>{t('seo_needs_social_item2', lang)}</li>
<li>{t('seo_needs_social_item3', lang)}</li>
<li>{t('seo_needs_social_item4', lang)}</li>
</ul>
</div>
</div>
</div>
</div>
"""
all_ui_components = [
header_title, news_banner,
global_tab, upload_image_header, input_image, editing_instructions_header, prompt_input, edit_button,
editing_result_header, output_image, use_as_input_btn, status_output, prompt_examples_header,
seo_html,
]
def update_ui_lang(lang):
# Hide banner for zh, hi, ru languages
show_banner = lang not in ["zh", "hi", "ru"]
return {
header_title: gr.update(value=f"""
<div style="text-align: center; margin: 20px auto 10px auto; max-width: 800px;">
<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);">
{t('header_title', lang)}
</h1>
</div>"""),
news_banner: gr.update(visible=show_banner),
global_tab: gr.update(label=t("global_editor_tab", lang)),
upload_image_header: gr.update(value=t("upload_image_header", lang)),
input_image: gr.update(label=t("upload_image_label", lang)),
editing_instructions_header: gr.update(value=t("editing_instructions_header", lang)),
prompt_input: gr.update(label=t("prompt_input_label", lang), placeholder=t("prompt_input_placeholder", lang)),
edit_button: gr.update(value=t("start_editing_button", lang)),
editing_result_header: gr.update(value=t("editing_result_header", lang)),
output_image: gr.update(label=t("output_image_label", lang)),
use_as_input_btn: gr.update(value=t("use_as_input_button", lang)),
status_output: gr.update(label=t("status_output_label", lang)),
prompt_examples_header: gr.update(value=t("prompt_examples_header", lang)),
seo_html: gr.update(value=get_seo_html(lang)),
}
def on_lang_change(lang):
return lang, *update_ui_lang(lang).values()
lang_dropdown.change(
on_lang_change,
inputs=[lang_dropdown],
outputs=[lang_state] + all_ui_components
)
# IP query state for async loading
ip_query_state = gr.State({"status": "pending", "ip": None, "lang": "en"})
def on_load_immediate(request: gr.Request):
"""
Load page with language based on robust IP detection
Features:
- Multiple fallback layers for IP extraction
- Comprehensive error handling
- Always returns valid language (defaults to English)
- Detailed logging for debugging
"""
# Extract client IP with multiple fallback methods
client_ip = None
try:
# Primary method: direct client host
client_ip = request.client.host
# Secondary method: check forwarded headers
headers = dict(request.headers) if hasattr(request, 'headers') else {}
x_forwarded_for = headers.get('x-forwarded-for') or headers.get('X-Forwarded-For')
if x_forwarded_for:
# Take first IP from comma-separated list
client_ip = x_forwarded_for.split(',')[0].strip()
# Alternative headers
if not client_ip or client_ip in ["127.0.0.1", "localhost"]:
client_ip = headers.get('x-real-ip') or headers.get('X-Real-IP') or client_ip
except Exception as e:
print(f"Error extracting client IP: {e}, using default")
client_ip = "unknown"
# Validate extracted IP
if not client_ip:
client_ip = "unknown"
print(f"Loading page for IP: {client_ip}")
# Determine language with robust error handling
try:
# Check if IP is already cached (second+ visit)
if client_ip in IP_Country_Cache:
# Use cached data - very fast
cached_lang = get_lang_from_ip(client_ip)
# Validate cached language
if cached_lang and len(cached_lang) == 2:
print(f"Using cached language: {cached_lang} for IP: {client_ip}")
query_state = {"ip": client_ip, "cached": True}
return cached_lang, cached_lang, query_state, *update_ui_lang(cached_lang).values()
# First visit: Query IP and determine language (max 3s timeout built-in)
print(f"First visit - detecting language for IP: {client_ip}")
detected_lang = get_lang_from_ip(client_ip)
# Double-check the detected language is valid
if not detected_lang or len(detected_lang) != 2:
print(f"Invalid detected language '{detected_lang}', using English")
detected_lang = "en"
print(f"First visit - Final language: {detected_lang} for IP: {client_ip}")
query_state = {"ip": client_ip, "cached": False}
return detected_lang, detected_lang, query_state, *update_ui_lang(detected_lang).values()
except Exception as e:
# Ultimate fallback - always works
print(f"Critical error in language detection for {client_ip}: {e}")
print("Using English as ultimate fallback")
query_state = {"ip": client_ip or "unknown", "cached": False, "error": str(e)}
return "en", "en", query_state, *update_ui_lang("en").values()
app.load(
on_load_immediate,
inputs=None,
outputs=[lang_state, lang_dropdown, ip_query_state] + all_ui_components,
)
return app
if __name__ == "__main__":
app = create_app()
# Improve queue configuration to handle high concurrency and prevent SSE connection issues
app.queue(
default_concurrency_limit=20, # Default concurrency limit
max_size=50, # Maximum queue size
api_open=False # Close API access to reduce resource consumption
)
app.launch(
server_name="0.0.0.0",
show_error=True, # Show detailed error information
quiet=False, # Keep log output
max_threads=40, # Increase thread pool size
height=800,
favicon_path=None # Reduce resource loading
)