Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,169 +4,117 @@ import numpy as np
|
|
| 4 |
import spaces
|
| 5 |
import torch
|
| 6 |
import random
|
| 7 |
-
import
|
| 8 |
-
import json
|
| 9 |
-
from datetime import datetime
|
| 10 |
-
from PIL import Image, ImageFilter
|
| 11 |
from typing import Iterable
|
| 12 |
-
from gradio.themes import
|
| 13 |
-
from gradio.themes.utils import colors, fonts, sizes
|
| 14 |
from deep_translator import GoogleTranslator
|
| 15 |
from transformers import pipeline
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
# ---
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
"""ایجاد جدول برای ذخیره تعداد استفاده کاربران"""
|
| 22 |
-
conn = sqlite3.connect(DB_NAME)
|
| 23 |
-
c = conn.cursor()
|
| 24 |
-
c.execute('''
|
| 25 |
-
CREATE TABLE IF NOT EXISTS usage_logs (
|
| 26 |
-
user_id TEXT,
|
| 27 |
-
date TEXT,
|
| 28 |
-
count INTEGER,
|
| 29 |
-
PRIMARY KEY (user_id, date)
|
| 30 |
-
)
|
| 31 |
-
''')
|
| 32 |
-
conn.commit()
|
| 33 |
-
conn.close()
|
| 34 |
-
|
| 35 |
-
init_db()
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
کاربران رایگان: حداکثر 5 تصویر در روز
|
| 42 |
-
"""
|
| 43 |
-
if not user_id:
|
| 44 |
-
return True, "Guest" # اگر یوزر آیدی نبود، سختگیری نمیکنیم (یا میتوان محدود کرد)
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
current_count = result[0] if result else 0
|
| 58 |
-
limit = 5
|
| 59 |
-
|
| 60 |
-
conn.close()
|
| 61 |
-
|
| 62 |
-
if current_count >= limit:
|
| 63 |
-
return False, f"0 از {limit}"
|
| 64 |
-
|
| 65 |
-
return True, f"{limit - current_count} از {limit}"
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
c.execute("INSERT OR IGNORE INTO usage_logs (user_id, date, count) VALUES (?, ?, 0)", (user_id, today))
|
| 77 |
-
c.execute("UPDATE usage_logs SET count = count + 1 WHERE user_id = ? AND date = ?", (user_id, today))
|
| 78 |
-
|
| 79 |
-
conn.commit()
|
| 80 |
-
conn.close()
|
| 81 |
-
|
| 82 |
-
# --- تعریف تم ---
|
| 83 |
-
colors.steel_blue = colors.Color(
|
| 84 |
-
name="steel_blue",
|
| 85 |
-
c50="#EBF3F8",
|
| 86 |
-
c100="#D3E5F0",
|
| 87 |
-
c200="#A8CCE1",
|
| 88 |
-
c300="#7DB3D2",
|
| 89 |
-
c400="#529AC3",
|
| 90 |
-
c500="#4682B4",
|
| 91 |
-
c600="#3E72A0",
|
| 92 |
-
c700="#36638C",
|
| 93 |
-
c800="#2E5378",
|
| 94 |
-
c900="#264364",
|
| 95 |
-
c950="#1E3450",
|
| 96 |
-
)
|
| 97 |
|
| 98 |
-
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 99 |
|
| 100 |
-
# --- بارگذاری سیستم امنیتی دوگانه (
|
| 101 |
print("Loading Safety Checkers...")
|
| 102 |
-
|
| 103 |
-
# مدل اول: سریع و استاندارد
|
| 104 |
safety_classifier_1 = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
|
| 105 |
-
|
| 106 |
-
# مدل دوم: بسیار دقیق و سختگیر (ViT)
|
| 107 |
safety_classifier_2 = pipeline("image-classification", model="AdamCodd/vit-base-nsfw-detector", device=-1)
|
| 108 |
|
| 109 |
def is_image_nsfw(image):
|
| 110 |
if image is None: return False
|
| 111 |
try:
|
| 112 |
-
# بررسی با مدل اول
|
| 113 |
results1 = safety_classifier_1(image)
|
| 114 |
for result in results1:
|
| 115 |
if result['label'] == 'nsfw' and result['score'] > 0.5:
|
| 116 |
print(f"Safety Check 1 Failed: {result['score']}")
|
| 117 |
return True
|
| 118 |
-
|
| 119 |
-
# بررسی با مدل دوم
|
| 120 |
results2 = safety_classifier_2(image)
|
| 121 |
for result in results2:
|
| 122 |
-
label = result['label'].lower()
|
| 123 |
-
score = result['score']
|
| 124 |
-
|
| 125 |
if label == 'nsfw' and score > 0.3:
|
| 126 |
print(f"Safety Check 2 (NSFW) Failed: {score}")
|
| 127 |
return True
|
| 128 |
-
|
| 129 |
if label in ['sexy', 'porn', 'hentai'] and score > 0.4:
|
| 130 |
print(f"Safety Check 2 (Partial) Failed: {label} - {score}")
|
| 131 |
return True
|
| 132 |
-
|
| 133 |
return False
|
| 134 |
except Exception as e:
|
| 135 |
print(f"Safety check error: {e}")
|
| 136 |
return True
|
| 137 |
|
| 138 |
-
# --- لیست کلمات ممنوعه ---
|
| 139 |
BANNED_WORDS = [
|
| 140 |
-
"nsfw", "nude", "naked", "sex", "porn", "erotic", "xxx", "18+", "uncensored",
|
| 141 |
-
"breast", "nipple", "areola", "cleavage", "topless", "open chest",
|
| 142 |
-
"genital", "vagina", "penis", "dick", "cock", "pussy", "ass", "butt", "anus",
|
| 143 |
-
"lingerie", "bikini", "swimwear", "underwear", "panties", "bra",
|
| 144 |
-
"fetish", "bdsm", "bondage", "exhibitionism", "voyeur",
|
| 145 |
-
"hentai", "ecchi", "ahegao", "paizuri",
|
| 146 |
-
"undressed", "stripping", "naked body", "exposed skin", "sheer", "see-through",
|
| 147 |
-
"rape", "violence", "blood", "gore", "sexual"
|
| 148 |
]
|
| 149 |
|
| 150 |
def check_text_safety(text):
|
| 151 |
if not text: return True
|
| 152 |
text_lower = text.lower()
|
| 153 |
for word in BANNED_WORDS:
|
| 154 |
-
if word in text_lower:
|
| 155 |
print(f"Banned word found: {word}")
|
| 156 |
return False
|
| 157 |
return True
|
| 158 |
|
| 159 |
def translate_prompt(text):
|
| 160 |
-
if not text:
|
| 161 |
-
return ""
|
| 162 |
try:
|
| 163 |
-
|
| 164 |
-
return translated
|
| 165 |
except Exception as e:
|
| 166 |
print(f"Translation Error: {e}")
|
| 167 |
return text
|
| 168 |
|
| 169 |
-
# --- بارگذاری مدل اصلی ---
|
| 170 |
from diffusers import FlowMatchEulerDiscreteScheduler
|
| 171 |
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
| 172 |
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
|
|
@@ -179,15 +127,11 @@ print("Loading Generation Pipeline...")
|
|
| 179 |
pipe = QwenImageEditPlusPipeline.from_pretrained(
|
| 180 |
"Qwen/Qwen-Image-Edit-2509",
|
| 181 |
transformer=QwenImageTransformer2DModel.from_pretrained(
|
| 182 |
-
"linoyts/Qwen-Image-Edit-Rapid-AIO",
|
| 183 |
-
subfolder='transformer',
|
| 184 |
-
torch_dtype=dtype,
|
| 185 |
-
device_map='cuda'
|
| 186 |
),
|
| 187 |
torch_dtype=dtype
|
| 188 |
).to(device)
|
| 189 |
|
| 190 |
-
# بارگذاری LoRA ها
|
| 191 |
pipe.load_lora_weights("autoweeb/Qwen-Image-Edit-2509-Photo-to-Anime", weight_name="Qwen-Image-Edit-2509-Photo-to-Anime_000001000.safetensors", adapter_name="anime")
|
| 192 |
pipe.load_lora_weights("dx8152/Qwen-Edit-2509-Multiple-angles", weight_name="镜头转换.safetensors", adapter_name="multiple-angles")
|
| 193 |
pipe.load_lora_weights("dx8152/Qwen-Image-Edit-2509-Light_restoration", weight_name="移除光影.safetensors", adapter_name="light-restoration")
|
|
@@ -201,530 +145,406 @@ pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
|
|
| 201 |
MAX_SEED = np.iinfo(np.int32).max
|
| 202 |
|
| 203 |
LORA_MAPPING = {
|
| 204 |
-
"تبدیل عکس به انیمه": "anime",
|
| 205 |
-
"تغییر زاویه دید": "multiple-angles",
|
| 206 |
-
"اصلاح نور و سایه": "light-restoration",
|
| 207 |
-
"نورپردازی مجدد (Relight)": "relight",
|
| 208 |
-
"نورپردازی چند زاویهای": "multi-angle-lighting",
|
| 209 |
-
"روتوش پوست": "edit-skin",
|
| 210 |
-
"صحنه بعدی (سینمایی)": "next-scene",
|
| 211 |
-
"افزایش کیفیت (Upscale)": "upscale-image"
|
| 212 |
}
|
| 213 |
-
|
| 214 |
ASPECT_RATIOS_LIST = [
|
| 215 |
-
"خودکار (پیشفرض)",
|
| 216 |
-
"۱:۱ (مربع - 1024x1024)",
|
| 217 |
-
"۱۶:۹ (افقی - 1344x768)",
|
| 218 |
-
"۹:۱۶ (عمودی - 768x1344)",
|
| 219 |
-
"شخصیسازی (Custom)"
|
| 220 |
]
|
| 221 |
-
|
| 222 |
ASPECT_RATIOS_MAP = {
|
| 223 |
-
"خودکار (پیشفرض)": "Auto",
|
| 224 |
-
"۱:۱ (مربع - 1024x1024)": (1024, 1024),
|
| 225 |
-
"۱۶:۹ (افقی - 1344x768)": (1344, 768),
|
| 226 |
-
"۹:۱۶ (عمودی - 768x1344)": (768, 1344),
|
| 227 |
-
"شخصیسازی (Custom)": "Custom"
|
| 228 |
}
|
| 229 |
|
| 230 |
def update_dimensions_on_upload(image):
|
| 231 |
-
if image is None:
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
new_width = 1024
|
| 236 |
-
aspect_ratio = original_height / original_width
|
| 237 |
-
new_height = int(new_width * aspect_ratio)
|
| 238 |
-
else:
|
| 239 |
-
new_height = 1024
|
| 240 |
-
aspect_ratio = original_width / original_height
|
| 241 |
-
new_width = int(new_height * aspect_ratio)
|
| 242 |
-
new_width = (new_width // 8) * 8
|
| 243 |
-
new_height = (new_height // 8) * 8
|
| 244 |
-
return new_width, new_height
|
| 245 |
|
| 246 |
def get_error_html(message):
|
| 247 |
-
return f"""
|
| 248 |
-
<div style="background-color: #fee2e2; border: 1px solid #ef4444; color: #b91c1c; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px;">
|
| 249 |
-
<span style="font-size: 1.2em;">⛔</span>
|
| 250 |
-
{message}
|
| 251 |
-
</div>
|
| 252 |
-
"""
|
| 253 |
|
| 254 |
def get_success_html(message):
|
| 255 |
-
return f"""
|
| 256 |
-
<div style="background-color: #dcfce7; border: 1px solid #22c55e; color: #15803d; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px;">
|
| 257 |
-
<span style="font-size: 1.2em;">✅</span>
|
| 258 |
-
{message}
|
| 259 |
-
</div>
|
| 260 |
-
"""
|
| 261 |
-
|
| 262 |
-
def get_limit_error_html():
|
| 263 |
-
return f"""
|
| 264 |
-
<div style="background-color: #fff3cd; border: 1px solid #ffc107; color: #856404; padding: 16px; border-radius: 12px; text-align: center; margin-bottom: 15px; direction: rtl;">
|
| 265 |
-
<h3 style="margin: 0 0 10px 0;">🚫 محدودیت اعتبار روزانه</h3>
|
| 266 |
-
<p style="margin: 0;">شما از سهمیه ۵ تصویر رایگان امروز خود استفاده کردهاید.</p>
|
| 267 |
-
<p style="margin: 8px 0 0 0; font-size: 0.9em;">برای استفاده نامحدود، حساب خود را به نسخه ویژه ارتقا دهید.</p>
|
| 268 |
-
<button onclick="window.parent.postMessage({{type: 'NAVIGATE_TO_PREMIUM'}}, '*')" style="margin-top: 12px; background: linear-gradient(90deg, #FFD700, #FFA500); border: none; padding: 8px 16px; border-radius: 20px; color: black; font-weight: bold; cursor: pointer;">ارتقا به نسخه ویژه ⭐️</button>
|
| 269 |
-
</div>
|
| 270 |
-
"""
|
| 271 |
|
| 272 |
-
@spaces.GPU(duration=
|
| 273 |
def infer(
|
| 274 |
-
input_image,
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
seed,
|
| 278 |
-
randomize_seed,
|
| 279 |
-
guidance_scale,
|
| 280 |
-
steps,
|
| 281 |
-
aspect_ratio_selection,
|
| 282 |
-
custom_width,
|
| 283 |
-
custom_height,
|
| 284 |
-
user_id_hidden, # دریافت شناسه کاربر از فرانت
|
| 285 |
-
is_premium_hidden, # دریافت وضعیت پریمیوم از فرانت
|
| 286 |
progress=gr.Progress(track_tqdm=True)
|
| 287 |
):
|
| 288 |
-
# --- بررسی اعتبار
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
-
if not allowed:
|
| 293 |
-
return None, seed, get_limit_error_html()
|
| 294 |
-
|
| 295 |
-
# --- بررسی وجود تصویر ---
|
| 296 |
-
if input_image is None:
|
| 297 |
-
return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید.")
|
| 298 |
-
|
| 299 |
-
# --- بررسی امنیتی ورودی ---
|
| 300 |
-
if is_image_nsfw(input_image):
|
| 301 |
-
return None, seed, get_error_html("تصویر ورودی دارای محتوای نامناسب است و پردازش نمیشود.")
|
| 302 |
-
|
| 303 |
-
# --- ترجمه و بررسی متن ---
|
| 304 |
english_prompt = translate_prompt(prompt)
|
| 305 |
-
if not check_text_safety(english_prompt):
|
| 306 |
-
return None, seed, get_error_html("متن درخواست شامل کلمات غیرمجاز یا غیراخلاقی است.")
|
| 307 |
|
| 308 |
adapter_internal_name = LORA_MAPPING.get(lora_adapter_persian)
|
| 309 |
-
if adapter_internal_name:
|
| 310 |
-
pipe.set_adapters([adapter_internal_name], adapter_weights=[1.0])
|
| 311 |
|
| 312 |
-
if randomize_seed:
|
| 313 |
-
seed = random.randint(0, MAX_SEED)
|
| 314 |
-
|
| 315 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 316 |
|
| 317 |
-
safety_negative =
|
| 318 |
-
|
| 319 |
-
"vagina, penis, ass, lingerie, bikini, swimwear, underwear, fetish, "
|
| 320 |
-
"topless, open chest, revealing clothes, cleavage, see-through, sheer, "
|
| 321 |
-
"gore, violence, blood, navel, midriff, exposed skin, erotic, ecchi, hentai, "
|
| 322 |
-
"uncensored, stripping"
|
| 323 |
-
)
|
| 324 |
-
base_negative = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
|
| 325 |
-
final_negative_prompt = f"{safety_negative}, {base_negative}"
|
| 326 |
|
| 327 |
original_image = input_image.convert("RGB")
|
| 328 |
-
|
| 329 |
selection_value = ASPECT_RATIOS_MAP.get(aspect_ratio_selection)
|
| 330 |
|
| 331 |
if selection_value == "Custom":
|
| 332 |
-
width = (int(custom_width) // 8) * 8
|
| 333 |
-
|
| 334 |
-
elif selection_value == "Auto" or selection_value is None:
|
| 335 |
width, height = update_dimensions_on_upload(original_image)
|
| 336 |
else:
|
| 337 |
width, height = selection_value
|
| 338 |
|
| 339 |
try:
|
| 340 |
-
result = pipe(
|
| 341 |
-
|
| 342 |
-
prompt=english_prompt,
|
| 343 |
-
negative_prompt=final_negative_prompt,
|
| 344 |
-
height=height,
|
| 345 |
-
width=width,
|
| 346 |
-
num_inference_steps=steps,
|
| 347 |
-
generator=generator,
|
| 348 |
-
true_cfg_scale=guidance_scale,
|
| 349 |
-
).images[0]
|
| 350 |
-
|
| 351 |
-
# --- بررسی امنیتی خروجی ---
|
| 352 |
-
if is_image_nsfw(result):
|
| 353 |
-
return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
|
| 354 |
-
|
| 355 |
-
# --- کسر اعتبار (فقط برای کاربران رایگان) ---
|
| 356 |
-
increment_usage(user_id_hidden, is_premium)
|
| 357 |
-
|
| 358 |
return result, seed, get_success_html("تصویر با موفقیت ویرایش شد.")
|
| 359 |
-
|
| 360 |
except Exception as e:
|
| 361 |
error_str = str(e)
|
| 362 |
-
if "quota" in error_str.lower() or "exceeded" in error_str.lower():
|
| 363 |
-
raise e
|
| 364 |
return None, seed, get_error_html(f"خطا در پردازش: {error_str}")
|
| 365 |
|
| 366 |
@spaces.GPU(duration=30)
|
| 367 |
def infer_example(input_image, prompt, lora_adapter):
|
| 368 |
-
#
|
| 369 |
-
res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیشفرض)", 1024, 1024,
|
| 370 |
return res, s, status
|
| 371 |
|
| 372 |
-
# --- جاوااسکریپت برای
|
| 373 |
-
js_download_func = ""
|
| 374 |
-
async (image) => {
|
| 375 |
-
if (!image) {
|
| 376 |
-
alert("لطفاً ابتدا تصویر را تولید کنید.");
|
| 377 |
-
return;
|
| 378 |
-
}
|
| 379 |
-
let fileUrl = image.url;
|
| 380 |
-
if (fileUrl && !fileUrl.startsWith('http')) {
|
| 381 |
-
fileUrl = window.location.origin + fileUrl;
|
| 382 |
-
} else if (!fileUrl && image.path) {
|
| 383 |
-
fileUrl = window.location.origin + "/file=" + image.path;
|
| 384 |
-
}
|
| 385 |
-
window.parent.postMessage({
|
| 386 |
-
type: 'DOWNLOAD_REQUEST',
|
| 387 |
-
url: fileUrl
|
| 388 |
-
}, '*');
|
| 389 |
-
}
|
| 390 |
-
"""
|
| 391 |
|
| 392 |
-
# --- جاوااسکریپت
|
| 393 |
-
|
| 394 |
<script>
|
|
|
|
| 395 |
document.addEventListener('DOMContentLoaded', () => {
|
| 396 |
-
|
| 397 |
-
//
|
| 398 |
-
//
|
| 399 |
-
const
|
| 400 |
-
|
| 401 |
-
let
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
-
if (
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
// Check if premium page ID exists in allowed pages
|
| 417 |
-
if (pages.includes(PREMIUM_PAGE_ID) || pages.includes(parseInt(PREMIUM_PAGE_ID))) {
|
| 418 |
-
isPremium = true;
|
| 419 |
-
}
|
| 420 |
-
}
|
| 421 |
-
} catch (e) {
|
| 422 |
-
console.error("Error parsing user data:", e);
|
| 423 |
-
}
|
| 424 |
}
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
|
|
|
| 443 |
}
|
| 444 |
-
badge.style.display = 'inline-flex';
|
| 445 |
}
|
| 446 |
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
|
| 453 |
-
if (
|
| 454 |
-
|
| 455 |
-
idInput.dispatchEvent(new Event('input', { bubbles: true }));
|
| 456 |
}
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
}
|
| 462 |
|
| 463 |
-
//
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
// 2. Force Light Mode
|
| 470 |
-
// --------------------------------------------------------
|
| 471 |
-
const forceLight = () => {
|
| 472 |
-
const body = document.querySelector('body');
|
| 473 |
-
if (body) {
|
| 474 |
-
body.classList.remove('dark');
|
| 475 |
-
body.style.backgroundColor = '#f5f7fa';
|
| 476 |
-
body.style.color = '#333333';
|
| 477 |
-
}
|
| 478 |
-
document.querySelectorAll('.dark').forEach(el => el.classList.remove('dark'));
|
| 479 |
-
};
|
| 480 |
-
forceLight();
|
| 481 |
-
setInterval(forceLight, 1000);
|
| 482 |
-
|
| 483 |
-
// --------------------------------------------------------
|
| 484 |
-
// 3. Retry Functionality
|
| 485 |
-
// --------------------------------------------------------
|
| 486 |
-
window.retryGeneration = function() {
|
| 487 |
-
const modal = document.getElementById('custom-quota-modal');
|
| 488 |
-
if (modal) modal.remove();
|
| 489 |
-
const runBtn = document.getElementById('run-btn');
|
| 490 |
-
if(runBtn) runBtn.click();
|
| 491 |
-
};
|
| 492 |
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
|
|
|
|
|
|
| 497 |
|
| 498 |
-
//
|
| 499 |
-
// 4. Quota Modal (IP Reset Guide)
|
| 500 |
-
// --------------------------------------------------------
|
| 501 |
const showQuotaModal = () => {
|
| 502 |
if (document.getElementById('custom-quota-modal')) return;
|
| 503 |
-
|
| 504 |
-
const modalHtml = `
|
| 505 |
-
<div id="custom-quota-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); z-index: 99999; display: flex; align-items: center; justify-content: center; font-family: 'Vazirmatn', sans-serif;">
|
| 506 |
-
<div class="ip-reset-guide-container">
|
| 507 |
-
<div class="guide-header">
|
| 508 |
-
<svg class="guide-header-icon" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
| 509 |
-
<defs><lineargradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color: #667eea; stop-opacity: 1;"></stop><stop offset="100%" style="stop-color: #764ba2; stop-opacity: 1;"></stop></lineargradient></defs>
|
| 510 |
-
<circle cx="50" cy="50" r="45" fill="url(#grad1)" opacity="0.1"></circle>
|
| 511 |
-
<circle cx="50" cy="50" r="35" fill="none" stroke="url(#grad1)" stroke-width="2" opacity="0.3"></circle>
|
| 512 |
-
<path d="M35 50 L45 60 L65 40" stroke="url(#grad1)" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"></path>
|
| 513 |
-
<circle cx="65" cy="35" r="8" fill="#fee140"></circle>
|
| 514 |
-
<path d="M62 35 L68 35 M65 32 L65 38" stroke="white" stroke-width="2" stroke-linecap="round"></path>
|
| 515 |
-
</svg>
|
| 516 |
-
<div>
|
| 517 |
-
<h2>یک قدم تا ساخت تصاویر جدید</h2>
|
| 518 |
-
<p>نیازمند تغییر نقطه دستیابی</p>
|
| 519 |
-
</div>
|
| 520 |
-
</div>
|
| 521 |
-
<div class="guide-content">
|
| 522 |
-
<div class="info-card">
|
| 523 |
-
<div class="info-card-header">
|
| 524 |
-
<svg class="info-card-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="#667eea" opacity="0.2"></path><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="#667eea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
| 525 |
-
<span class="info-card-title">راه حل سریع</span>
|
| 526 |
-
</div>
|
| 527 |
-
<p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
|
| 528 |
-
</div>
|
| 529 |
-
<div class="summary-section">
|
| 530 |
-
<div class="summary-header">
|
| 531 |
-
<svg class="summary-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#56ab2f" opacity="0.2"></circle><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" stroke="#56ab2f" stroke-width="2"></path><path d="M9 12l2 2 4-4" stroke="#56ab2f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
| 532 |
-
<span class="summary-title">خلاصه راهنما</span>
|
| 533 |
-
</div>
|
| 534 |
-
<div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیمکارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید.</div>
|
| 535 |
-
</div>
|
| 536 |
-
<div class="video-button-container">
|
| 537 |
-
<button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210' }, '*')" class="elegant-video-button">
|
| 538 |
-
<svg class="elegant-video-button-icon" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg>
|
| 539 |
-
<span>دیدن ویدیو آموزشی استفاده نامحدود</span>
|
| 540 |
-
</button>
|
| 541 |
-
</div>
|
| 542 |
-
</div>
|
| 543 |
-
<div class="guide-actions">
|
| 544 |
-
<button class="action-button back-button" onclick="window.closeErrorModal()">
|
| 545 |
-
<svg class="action-button-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
| 546 |
-
<span>بازگشت</span>
|
| 547 |
-
</button>
|
| 548 |
-
<button class="action-button retry-button" onclick="window.retryGeneration()">
|
| 549 |
-
<svg class="action-button-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23 4v6h-6M1 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
| 550 |
-
<span>تلاش مجدد</span>
|
| 551 |
-
</button>
|
| 552 |
-
</div>
|
| 553 |
-
</div>
|
| 554 |
-
</div>
|
| 555 |
-
`;
|
| 556 |
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
| 557 |
-
setTimeout(() => { window.closeErrorModal(); }, 10000);
|
| 558 |
};
|
| 559 |
-
|
| 560 |
-
// --------------------------------------------------------
|
| 561 |
-
// 5. Error Scanner
|
| 562 |
-
// --------------------------------------------------------
|
| 563 |
setInterval(() => {
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
showQuotaModal();
|
| 569 |
-
el.style.display = 'none';
|
| 570 |
-
el.style.opacity = '0';
|
| 571 |
-
el.innerText = '';
|
| 572 |
-
const parentWrap = el.closest('.toast-wrap');
|
| 573 |
-
if(parentWrap) parentWrap.style.display = 'none';
|
| 574 |
}
|
| 575 |
});
|
| 576 |
}, 100);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
});
|
| 578 |
</script>
|
| 579 |
-
"""
|
| 580 |
-
|
| 581 |
-
# --- CSS Updated ---
|
| 582 |
-
css_code = """
|
| 583 |
<style>
|
|
|
|
| 584 |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
|
| 585 |
-
|
| 586 |
-
:
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 590 |
}
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
justify-content: center;
|
| 597 |
-
padding: 8px 16px;
|
| 598 |
-
border-radius: 50px;
|
| 599 |
-
font-size: 0.95em;
|
| 600 |
font-weight: 700;
|
| 601 |
-
margin: 10px
|
| 602 |
-
width: fit-content;
|
| 603 |
-
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
| 604 |
-
animation: fadeInBadge 0.5s ease-out;
|
| 605 |
}
|
| 606 |
-
|
| 607 |
-
.
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
}
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
color: #
|
| 617 |
-
border: 1px solid #cbd5e1;
|
| 618 |
}
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
}
|
|
|
|
| 624 |
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
width: 90%; max-width: 420px; max-height: 90vh; overflow-y: auto;
|
| 631 |
-
font-family: 'Vazirmatn', sans-serif !important;
|
| 632 |
}
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
.
|
| 636 |
-
.
|
| 637 |
-
.
|
| 638 |
-
.info-card { background: #f0f4ff; border: 1px solid #c3dafe; }
|
| 639 |
-
.summary-section { background: #f0fff4; border: 1px solid #c6f6d5; }
|
| 640 |
-
.video-button-container { text-align: center; margin-top: 15px; }
|
| 641 |
-
.elegant-video-button { padding: 8px 16px; border-radius: 20px; border: 1px solid #ddd; background: white; cursor: pointer; display: inline-flex; align-items: center; }
|
| 642 |
-
.guide-actions { display: flex; gap: 10px; margin-top: 20px; }
|
| 643 |
-
.action-button { flex: 1; padding: 10px; border-radius: 10px; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-weight: bold; }
|
| 644 |
-
.back-button { background: white; border: 1px solid #ddd; }
|
| 645 |
-
.retry-button { background: #667eea; color: white; }
|
| 646 |
-
|
| 647 |
-
/* Main UI Tweaks */
|
| 648 |
-
#col-container { max-width: 980px; margin: 0 auto; direction: rtl; text-align: right; background: white; padding: 20px; border-radius: 20px; }
|
| 649 |
-
#main-title h1 { text-align: center; color: #2563eb; }
|
| 650 |
-
#main-description { text-align: center; color: #4b5563; }
|
| 651 |
-
.primary-btn { background: linear-gradient(135deg, #10b981, #059669); color: white; border: none; }
|
| 652 |
-
footer { display: none !important; }
|
| 653 |
</style>
|
| 654 |
"""
|
| 655 |
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
gr.HTML(combined_html)
|
| 660 |
|
| 661 |
with gr.Column(elem_id="col-container"):
|
| 662 |
gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
|
| 663 |
gr.Markdown("با هوش مصنوعی آلفا تصاویر تونو به مدل های مختلف ویرایش کنید.", elem_id="main-description")
|
| 664 |
-
|
| 665 |
-
# بج وضعیت کاربر (HTML خالی که توسط JS پر میشود)
|
| 666 |
-
gr.HTML('<div id="user-status-badge" class="status-badge"></div>')
|
| 667 |
|
| 668 |
-
#
|
| 669 |
-
|
| 670 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
|
| 672 |
with gr.Row(equal_height=True):
|
| 673 |
with gr.Column():
|
| 674 |
input_image = gr.Image(label="بارگذاری تصویر", type="pil", height=320)
|
| 675 |
prompt = gr.Text(label="دستور ویرایش (به فارسی)", placeholder="مثال: تصویر را به سبک انیمه تبدیل کن...", rtl=True, lines=3)
|
| 676 |
status_box = gr.HTML(label="وضعیت")
|
| 677 |
-
run_button = gr.Button("✨ شروع پردازش", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
|
| 678 |
|
| 679 |
with gr.Column():
|
| 680 |
output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
|
| 681 |
download_button = gr.Button("📥 دانلود و ذخیره تصویر", variant="secondary", elem_id="download-btn", elem_classes="primary-btn")
|
| 682 |
-
|
| 683 |
-
with gr.Row():
|
| 684 |
-
lora_adapter = gr.Dropdown(label="انتخاب سبک", choices=list(LORA_MAPPING.keys()), value="تبدیل عکس به انیمه")
|
| 685 |
|
| 686 |
with gr.Accordion("تنظیمات پیشرفته", open=False):
|
| 687 |
-
aspect_ratio_selection = gr.Dropdown(label="ابعاد", choices=ASPECT_RATIOS_LIST, value="خودکار (پیشفرض)")
|
| 688 |
with gr.Row(visible=False) as custom_dims_row:
|
| 689 |
custom_width = gr.Slider(label="عرض", minimum=256, maximum=2048, step=8, value=1024)
|
| 690 |
custom_height = gr.Slider(label="ارتفاع", minimum=256, maximum=2048, step=8, value=1024)
|
| 691 |
-
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
|
| 692 |
-
randomize_seed = gr.Checkbox(label="Seed تصادفی", value=True)
|
| 693 |
-
guidance_scale = gr.Slider(label="
|
| 694 |
-
steps = gr.Slider(label="
|
| 695 |
|
| 696 |
-
# لاجیک نمایش تنظیمات ابعاد
|
| 697 |
def toggle_row(choice):
|
| 698 |
-
return gr.update(visible=
|
| 699 |
-
|
| 700 |
aspect_ratio_selection.change(fn=toggle_row, inputs=aspect_ratio_selection, outputs=custom_dims_row)
|
| 701 |
|
| 702 |
-
# اجرای مدل
|
| 703 |
-
run_button.click(
|
| 704 |
-
fn=infer,
|
| 705 |
-
inputs=[
|
| 706 |
-
input_image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps,
|
| 707 |
-
aspect_ratio_selection, custom_width, custom_height,
|
| 708 |
-
user_id_storage, premium_status_storage # ارسال اطلاعات کاربر به تابع infer
|
| 709 |
-
],
|
| 710 |
-
outputs=[output_image, seed, status_box],
|
| 711 |
-
api_name="predict"
|
| 712 |
-
)
|
| 713 |
-
|
| 714 |
-
download_button.click(fn=None, inputs=[output_image], outputs=None, js=js_download_func)
|
| 715 |
-
|
| 716 |
-
# نمونهها
|
| 717 |
gr.Examples(
|
| 718 |
-
examples=[
|
| 719 |
-
|
| 720 |
-
["examples/5.jpg", "سایهها را حذف کن.", "اصلاح نور و سایه"],
|
| 721 |
-
],
|
| 722 |
-
inputs=[input_image, prompt, lora_adapter],
|
| 723 |
-
outputs=[output_image, seed, status_box],
|
| 724 |
-
fn=infer_example,
|
| 725 |
-
cache_examples=False,
|
| 726 |
-
label="نمونهها"
|
| 727 |
)
|
| 728 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
if __name__ == "__main__":
|
| 730 |
demo.queue(max_size=30).launch(show_error=True)
|
|
|
|
| 4 |
import spaces
|
| 5 |
import torch
|
| 6 |
import random
|
| 7 |
+
from PIL import Image
|
|
|
|
|
|
|
|
|
|
| 8 |
from typing import Iterable
|
| 9 |
+
from gradio.themes.utils import colors
|
|
|
|
| 10 |
from deep_translator import GoogleTranslator
|
| 11 |
from transformers import pipeline
|
| 12 |
+
import datetime
|
| 13 |
+
import time
|
| 14 |
+
from collections import defaultdict
|
| 15 |
+
import threading
|
| 16 |
|
| 17 |
+
# --- تعریف تم (بدون تغییر) ---
|
| 18 |
+
colors.steel_blue = colors.Color(
|
| 19 |
+
name="steel_blue", c50="#EBF3F8", c100="#D3E5F0", c200="#A8CCE1", c300="#7DB3D2", c400="#529AC3", c500="#4682B4", c600="#3E72A0", c700="#36638C", c800="#2E5378", c900="#264364", c950="#1E3450",
|
| 20 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
# --- سیستم مدیریت اعتبار کاربران (بخش جدید) ---
|
| 23 |
+
USER_DATA = {}
|
| 24 |
+
USER_LOCKS = defaultdict(threading.Lock)
|
| 25 |
+
DAILY_CREDIT_LIMIT = 5
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
def check_and_use_credit(fingerprint: str) -> bool:
|
| 28 |
+
"""اعتبار کاربر را بررسی و در صورت وجود، یکی کم میکند."""
|
| 29 |
+
if not fingerprint:
|
| 30 |
+
return False
|
| 31 |
+
with USER_LOCKS[fingerprint]:
|
| 32 |
+
today = datetime.date.today().isoformat()
|
| 33 |
+
user = USER_DATA.get(fingerprint, {'credits': DAILY_CREDIT_LIMIT, 'last_reset': today})
|
| 34 |
+
|
| 35 |
+
# اگر تاریخ گذشته بود، اعتبار را ریست کن
|
| 36 |
+
if user['last_reset'] != today:
|
| 37 |
+
user['credits'] = DAILY_CREDIT_LIMIT
|
| 38 |
+
user['last_reset'] = today
|
| 39 |
+
|
| 40 |
+
if user['credits'] > 0:
|
| 41 |
+
user['credits'] -= 1
|
| 42 |
+
USER_DATA[fingerprint] = user
|
| 43 |
+
print(f"Credit used for {fingerprint}. Remaining: {user['credits']}")
|
| 44 |
+
return True
|
| 45 |
+
else:
|
| 46 |
+
print(f"No credits left for {fingerprint}.")
|
| 47 |
+
return False
|
| 48 |
|
| 49 |
+
def get_user_status_api(fingerprint: str) -> dict:
|
| 50 |
+
"""وضعیت فعلی کاربر را برای نمایش در UI برمیگرداند."""
|
| 51 |
+
if not fingerprint:
|
| 52 |
+
return {'credits': 0, 'next_reset_timestamp': 0}
|
| 53 |
+
|
| 54 |
+
with USER_LOCKS[fingerprint]:
|
| 55 |
+
today = datetime.date.today()
|
| 56 |
+
user = USER_DATA.get(fingerprint, {'credits': DAILY_CREDIT_LIMIT, 'last_reset': today.isoformat()})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
if user['last_reset'] != today.isoformat():
|
| 59 |
+
user['credits'] = DAILY_CREDIT_LIMIT
|
| 60 |
+
user['last_reset'] = today.isoformat()
|
| 61 |
+
USER_DATA[fingerprint] = user
|
| 62 |
|
| 63 |
+
tomorrow = today + datetime.timedelta(days=1)
|
| 64 |
+
next_reset_timestamp = int(time.mktime(tomorrow.timetuple()))
|
| 65 |
+
|
| 66 |
+
return {'credits': user['credits'], 'next_reset_timestamp': next_reset_timestamp}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
|
|
|
| 68 |
|
| 69 |
+
# --- بارگذاری سیستم امنیتی دوگانه (بدون تغییر) ---
|
| 70 |
print("Loading Safety Checkers...")
|
|
|
|
|
|
|
| 71 |
safety_classifier_1 = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
|
|
|
|
|
|
|
| 72 |
safety_classifier_2 = pipeline("image-classification", model="AdamCodd/vit-base-nsfw-detector", device=-1)
|
| 73 |
|
| 74 |
def is_image_nsfw(image):
|
| 75 |
if image is None: return False
|
| 76 |
try:
|
|
|
|
| 77 |
results1 = safety_classifier_1(image)
|
| 78 |
for result in results1:
|
| 79 |
if result['label'] == 'nsfw' and result['score'] > 0.5:
|
| 80 |
print(f"Safety Check 1 Failed: {result['score']}")
|
| 81 |
return True
|
|
|
|
|
|
|
| 82 |
results2 = safety_classifier_2(image)
|
| 83 |
for result in results2:
|
| 84 |
+
label, score = result['label'].lower(), result['score']
|
|
|
|
|
|
|
| 85 |
if label == 'nsfw' and score > 0.3:
|
| 86 |
print(f"Safety Check 2 (NSFW) Failed: {score}")
|
| 87 |
return True
|
|
|
|
| 88 |
if label in ['sexy', 'porn', 'hentai'] and score > 0.4:
|
| 89 |
print(f"Safety Check 2 (Partial) Failed: {label} - {score}")
|
| 90 |
return True
|
|
|
|
| 91 |
return False
|
| 92 |
except Exception as e:
|
| 93 |
print(f"Safety check error: {e}")
|
| 94 |
return True
|
| 95 |
|
|
|
|
| 96 |
BANNED_WORDS = [
|
| 97 |
+
"nsfw", "nude", "naked", "sex", "porn", "erotic", "xxx", "18+", "uncensored", "breast", "nipple", "areola", "cleavage", "topless", "open chest", "genital", "vagina", "penis", "dick", "cock", "pussy", "ass", "butt", "anus", "lingerie", "bikini", "swimwear", "underwear", "panties", "bra", "fetish", "bdsm", "bondage", "exhibitionism", "voyeur", "hentai", "ecchi", "ahegao", "paizuri", "undressed", "stripping", "naked body", "exposed skin", "sheer", "see-through", "rape", "violence", "blood", "gore", "sexual"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
]
|
| 99 |
|
| 100 |
def check_text_safety(text):
|
| 101 |
if not text: return True
|
| 102 |
text_lower = text.lower()
|
| 103 |
for word in BANNED_WORDS:
|
| 104 |
+
if word in text_lower:
|
| 105 |
print(f"Banned word found: {word}")
|
| 106 |
return False
|
| 107 |
return True
|
| 108 |
|
| 109 |
def translate_prompt(text):
|
| 110 |
+
if not text: return ""
|
|
|
|
| 111 |
try:
|
| 112 |
+
return GoogleTranslator(source='auto', target='en').translate(text)
|
|
|
|
| 113 |
except Exception as e:
|
| 114 |
print(f"Translation Error: {e}")
|
| 115 |
return text
|
| 116 |
|
| 117 |
+
# --- بارگذاری مدل اصلی ویرایش تصویر (بدون تغییر) ---
|
| 118 |
from diffusers import FlowMatchEulerDiscreteScheduler
|
| 119 |
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
| 120 |
from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
|
|
|
|
| 127 |
pipe = QwenImageEditPlusPipeline.from_pretrained(
|
| 128 |
"Qwen/Qwen-Image-Edit-2509",
|
| 129 |
transformer=QwenImageTransformer2DModel.from_pretrained(
|
| 130 |
+
"linoyts/Qwen-Image-Edit-Rapid-AIO", subfolder='transformer', torch_dtype=dtype, device_map='cuda'
|
|
|
|
|
|
|
|
|
|
| 131 |
),
|
| 132 |
torch_dtype=dtype
|
| 133 |
).to(device)
|
| 134 |
|
|
|
|
| 135 |
pipe.load_lora_weights("autoweeb/Qwen-Image-Edit-2509-Photo-to-Anime", weight_name="Qwen-Image-Edit-2509-Photo-to-Anime_000001000.safetensors", adapter_name="anime")
|
| 136 |
pipe.load_lora_weights("dx8152/Qwen-Edit-2509-Multiple-angles", weight_name="镜头转换.safetensors", adapter_name="multiple-angles")
|
| 137 |
pipe.load_lora_weights("dx8152/Qwen-Image-Edit-2509-Light_restoration", weight_name="移除光影.safetensors", adapter_name="light-restoration")
|
|
|
|
| 145 |
MAX_SEED = np.iinfo(np.int32).max
|
| 146 |
|
| 147 |
LORA_MAPPING = {
|
| 148 |
+
"تبدیل عکس به انیمه": "anime", "تغییر زاویه دید": "multiple-angles", "اصلاح نور و سایه": "light-restoration", "نورپردازی مجدد (Relight)": "relight", "نورپردازی چند زاویهای": "multi-angle-lighting", "روتوش پوست": "edit-skin", "صحنه بعدی (سینمایی)": "next-scene", "افزایش کیفیت (Upscale)": "upscale-image"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
}
|
|
|
|
| 150 |
ASPECT_RATIOS_LIST = [
|
| 151 |
+
"خودکار (پیشفرض)", "۱:۱ (مربع - 1024x1024)", "۱۶:۹ (افقی - 1344x768)", "۹:۱۶ (عمودی - 768x1344)", "شخصیسازی (Custom)"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
]
|
|
|
|
| 153 |
ASPECT_RATIOS_MAP = {
|
| 154 |
+
"خودکار (پیشفرض)": "Auto", "۱:۱ (مربع - 1024x1024)": (1024, 1024), "۱۶:۹ (افقی - 1344x768)": (1344, 768), "۹:۱۶ (عمودی - 768x1344)": (768, 1344), "شخصیسازی (Custom)": "Custom"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
}
|
| 156 |
|
| 157 |
def update_dimensions_on_upload(image):
|
| 158 |
+
if image is None: return 1024, 1024
|
| 159 |
+
w, h = image.size
|
| 160 |
+
new_w, new_h = (1024, int(1024 * h / w)) if w > h else (int(1024 * w / h), 1024)
|
| 161 |
+
return (new_w // 8) * 8, (new_h // 8) * 8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
def get_error_html(message):
|
| 164 |
+
return f"""<div style="background-color: #fee2e2; border: 1px solid #ef4444; color: #b91c1c; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px;"><span style="font-size: 1.2em;">⛔</span> {message}</div>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
def get_success_html(message):
|
| 167 |
+
return f"""<div style="background-color: #dcfce7; border: 1px solid #22c55e; color: #15803d; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px;"><span style="font-size: 1.2em;">✅</span> {message}</div>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
+
@spaces.GPU(duration=45)
|
| 170 |
def infer(
|
| 171 |
+
input_image, prompt, lora_adapter_persian, seed, randomize_seed,
|
| 172 |
+
guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height,
|
| 173 |
+
is_paid_user, user_fingerprint, # ورودیهای جدید برای مدیریت اعتبار
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
progress=gr.Progress(track_tqdm=True)
|
| 175 |
):
|
| 176 |
+
# --- بخش جدید: بررسی اعتبار قبل از هر چیز ---
|
| 177 |
+
if not is_paid_user:
|
| 178 |
+
can_generate = check_and_use_credit(user_fingerprint)
|
| 179 |
+
if not can_generate:
|
| 180 |
+
return None, seed, get_error_html("اعتبار رایگان روزانه شما تمام شده است. اعتبار فردا مجدداً شارژ خواهد شد.")
|
| 181 |
+
|
| 182 |
+
if input_image is None: return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید.")
|
| 183 |
+
if is_image_nsfw(input_image): return None, seed, get_error_html("تصویر ورودی دارای محتوای نامناسب است.")
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
english_prompt = translate_prompt(prompt)
|
| 186 |
+
if not check_text_safety(english_prompt): return None, seed, get_error_html("متن درخواست شامل کلمات غیرمجاز است.")
|
|
|
|
| 187 |
|
| 188 |
adapter_internal_name = LORA_MAPPING.get(lora_adapter_persian)
|
| 189 |
+
if adapter_internal_name: pipe.set_adapters([adapter_internal_name], adapter_weights=[1.0])
|
|
|
|
| 190 |
|
| 191 |
+
if randomize_seed: seed = random.randint(0, MAX_SEED)
|
|
|
|
|
|
|
| 192 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 193 |
|
| 194 |
+
safety_negative = "nsfw, nude, naked, porn, sexual, xxx, breast, nipple, areola, genital, vagina, penis, ass, lingerie, bikini, swimwear, underwear, fetish, topless, open chest, revealing clothes, cleavage, see-through, sheer, gore, violence, blood, navel, midriff, exposed skin, erotic, ecchi, hentai, uncensored, stripping"
|
| 195 |
+
final_negative_prompt = f"{safety_negative}, worst quality, low quality"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
original_image = input_image.convert("RGB")
|
|
|
|
| 198 |
selection_value = ASPECT_RATIOS_MAP.get(aspect_ratio_selection)
|
| 199 |
|
| 200 |
if selection_value == "Custom":
|
| 201 |
+
width, height = (int(custom_width) // 8) * 8, (int(custom_height) // 8) * 8
|
| 202 |
+
elif selection_value == "Auto":
|
|
|
|
| 203 |
width, height = update_dimensions_on_upload(original_image)
|
| 204 |
else:
|
| 205 |
width, height = selection_value
|
| 206 |
|
| 207 |
try:
|
| 208 |
+
result = pipe(image=original_image, prompt=english_prompt, negative_prompt=final_negative_prompt, height=height, width=width, num_inference_steps=steps, generator=generator, true_cfg_scale=guidance_scale).images[0]
|
| 209 |
+
if is_image_nsfw(result): return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
return result, seed, get_success_html("تصویر با موفقیت ویرایش شد.")
|
|
|
|
| 211 |
except Exception as e:
|
| 212 |
error_str = str(e)
|
| 213 |
+
if "quota" in error_str.lower() or "exceeded" in error_str.lower(): raise e
|
|
|
|
| 214 |
return None, seed, get_error_html(f"خطا در پردازش: {error_str}")
|
| 215 |
|
| 216 |
@spaces.GPU(duration=30)
|
| 217 |
def infer_example(input_image, prompt, lora_adapter):
|
| 218 |
+
# مثالها اعتبار کم نمیکنند
|
| 219 |
+
res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیشفرض)", 1024, 1024, True, "example_user")
|
| 220 |
return res, s, status
|
| 221 |
|
| 222 |
+
# --- جاوااسکریپت برای دانلود (بدون تغییر) ---
|
| 223 |
+
js_download_func = "async(e)=>{if(!e){return void alert('لطفاً ابتدا تصویر را تولید کنید.')};let o=e.url;o&&!o.startsWith('http')?o=window.location.origin+o:!o&&e.path&&(o=window.location.origin+'/file='+e.path),window.parent.postMessage({type:'DOWNLOAD_REQUEST',url:o},'*')}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
+
# --- جاوااسکریپت و CSS اصلی (با تغییرات زیاد) ---
|
| 226 |
+
js_and_css_code = """
|
| 227 |
<script>
|
| 228 |
+
// --- START OF JS LOGIC FOR USER AUTH & CREDITS ---
|
| 229 |
document.addEventListener('DOMContentLoaded', () => {
|
| 230 |
+
|
| 231 |
+
// --- بخش جدید: مدیریت وضعیت کاربر و اعتبار ---
|
| 232 |
+
const PREMIUM_PAGE_ID = '1149636'; // شناسه صفحه کاربران ویژه
|
| 233 |
+
const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
|
| 234 |
+
|
| 235 |
+
let userFingerprint = null;
|
| 236 |
+
let isPaidUser = false;
|
| 237 |
+
let countdownInterval = null;
|
| 238 |
+
|
| 239 |
+
// توابع UI برای نمایش وضعیت
|
| 240 |
+
const statusContainer = document.getElementById('user-status-section');
|
| 241 |
+
const badgeEl = document.getElementById('subscription-badge');
|
| 242 |
+
const creditTextEl = document.getElementById('credit-status-text');
|
| 243 |
+
const countdownEl = document.getElementById('countdown-timer');
|
| 244 |
+
const upgradeBtn = document.getElementById('upgrade-btn');
|
| 245 |
+
const runBtn = document.getElementById('run-btn');
|
| 246 |
+
|
| 247 |
+
// تابع اصلی برای آپدیت UI بر اساس وضعیت کاربر
|
| 248 |
+
function updateUIForUserStatus(statusData) {
|
| 249 |
+
statusContainer.style.display = 'block';
|
| 250 |
+
if (countdownInterval) clearInterval(countdownInterval);
|
| 251 |
+
|
| 252 |
+
if (isPaidUser) {
|
| 253 |
+
badgeEl.textContent = 'کاربر ویژه';
|
| 254 |
+
badgeEl.className = 'paid-badge';
|
| 255 |
+
creditTextEl.innerHTML = 'دسترسی شما <strong>نامحدود</strong> است.';
|
| 256 |
+
countdownEl.style.display = 'none';
|
| 257 |
+
upgradeBtn.style.display = 'none';
|
| 258 |
+
runBtn.disabled = false;
|
| 259 |
+
} else {
|
| 260 |
+
badgeEl.textContent = 'کاربر رایگان';
|
| 261 |
+
badgeEl.className = 'free-badge';
|
| 262 |
+
const credits = statusData.credits || 0;
|
| 263 |
|
| 264 |
+
if (credits > 0) {
|
| 265 |
+
creditTextEl.innerHTML = `شما <strong>${credits} اعتبار</strong> ساخت تصویر برای امروز دارید.`;
|
| 266 |
+
runBtn.disabled = false;
|
| 267 |
+
upgradeBtn.style.display = 'none';
|
| 268 |
+
} else {
|
| 269 |
+
creditTextEl.innerHTML = 'اعتبار رایگان امروز شما تمام شده است.';
|
| 270 |
+
runBtn.disabled = true;
|
| 271 |
+
upgradeBtn.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
}
|
| 273 |
+
|
| 274 |
+
// شروع شمارش معکوس
|
| 275 |
+
const resetTimestamp = statusData.next_reset_timestamp;
|
| 276 |
+
const updateTimer = () => {
|
| 277 |
+
const now = Date.now() / 1000;
|
| 278 |
+
const timeLeft = Math.max(0, resetTimestamp - now);
|
| 279 |
+
if (timeLeft === 0) {
|
| 280 |
+
clearInterval(countdownInterval);
|
| 281 |
+
initializeApp(); // برای گرفتن اعتبار جدید
|
| 282 |
+
return;
|
| 283 |
+
}
|
| 284 |
+
const h = Math.floor(timeLeft / 3600);
|
| 285 |
+
const m = Math.floor((timeLeft % 3600) / 60);
|
| 286 |
+
const s = Math.floor(timeLeft % 60);
|
| 287 |
+
countdownEl.querySelector('span').textContent = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
| 288 |
+
};
|
| 289 |
+
updateTimer();
|
| 290 |
+
countdownInterval = setInterval(updateTimer, 1000);
|
| 291 |
+
countdownEl.style.display = 'block';
|
| 292 |
}
|
|
|
|
| 293 |
}
|
| 294 |
|
| 295 |
+
// ارتباط با برنامه اصلی (Parent)
|
| 296 |
+
window.addEventListener('message', (event) => {
|
| 297 |
+
if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
|
| 298 |
+
try {
|
| 299 |
+
const userObject = JSON.parse(event.data.payload || '{}');
|
| 300 |
+
isPaidUser = userObject && userObject.isLogin && userObject.accessible_pages &&
|
| 301 |
+
(userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
|
| 302 |
+
|
| 303 |
+
// آپدیت کردن کامپوننتهای مخفی گرادیو
|
| 304 |
+
const fingerprintInput = document.querySelector('#fingerprint-input-for-backend textarea');
|
| 305 |
+
const isPaidInput = document.querySelector('#is-paid-input-for-backend textarea');
|
| 306 |
+
if(fingerprintInput) fingerprintInput.value = userFingerprint;
|
| 307 |
+
if(isPaidInput) isPaidInput.value = isPaidUser ? 'true' : 'false';
|
| 308 |
+
|
| 309 |
+
// ایجاد یک Event برای اینکه گرادیو تغییر را تشخیص دهد
|
| 310 |
+
const eventToDispatch = new Event('input', { bubbles: true });
|
| 311 |
+
if(fingerprintInput) fingerprintInput.dispatchEvent(eventToDispatch);
|
| 312 |
+
if(isPaidInput) isPaidInput.dispatchEvent(eventToDispatch);
|
| 313 |
+
|
| 314 |
+
// حالا که اطلاعات کاربر را داریم، از بکاند گرادیو وضعیت اعتبار را میپرسیم
|
| 315 |
+
// با کلیک کردن روی دکمه مخفی، تابع پایتون را فراخوانی میکنیم
|
| 316 |
+
const statusCheckBtn = document.getElementById('status-check-btn-hidden');
|
| 317 |
+
if(statusCheckBtn) statusCheckBtn.click();
|
| 318 |
+
|
| 319 |
+
} catch (e) {
|
| 320 |
+
console.error("Error processing user status:", e);
|
| 321 |
+
isPaidUser = false; // در صورت خطا، کاربر رایگان در نظر گرفته میشود
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
|
| 325 |
+
if (event.data.type === 'DOWNLOAD_REQUEST' && event.data.url) {
|
| 326 |
+
// این بخش برای دکمه دانلود است که در HTML دیگر تعریف شده
|
|
|
|
| 327 |
}
|
| 328 |
+
|
| 329 |
+
if (event.data.type === 'NAVIGATE_TO_URL' && event.data.url) {
|
| 330 |
+
window.top.location.href = event.data.url;
|
| 331 |
}
|
| 332 |
+
});
|
| 333 |
+
|
| 334 |
+
// تابع برای ساخت اثرانگشت مرورگر
|
| 335 |
+
async function getBrowserFingerprint() {
|
| 336 |
+
const components = [navigator.userAgent, navigator.language, screen.width+'x'+screen.height, new Date().getTimezoneOffset()];
|
| 337 |
+
const fingerprintString = components.join('~~~');
|
| 338 |
+
let hash = 0;
|
| 339 |
+
for (let i = 0; i < fingerprintString.length; i++) {
|
| 340 |
+
const char = fingerprintString.charCodeAt(i);
|
| 341 |
+
hash = ((hash << 5) - hash) + char;
|
| 342 |
+
hash |= 0;
|
| 343 |
+
}
|
| 344 |
+
return 'fp_' + Math.abs(hash).toString(16);
|
| 345 |
}
|
| 346 |
|
| 347 |
+
// تابع اصلی برای شروع
|
| 348 |
+
async function initializeApp() {
|
| 349 |
+
userFingerprint = await getBrowserFingerprint();
|
| 350 |
+
// درخواست اطلاعات کاربر از برنامه اصلی
|
| 351 |
+
parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
|
| 352 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
+
// اتصال دکمه ارتقا
|
| 355 |
+
if(upgradeBtn) {
|
| 356 |
+
upgradeBtn.addEventListener('click', () => {
|
| 357 |
+
parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
|
| 358 |
+
});
|
| 359 |
+
}
|
| 360 |
|
| 361 |
+
// --- مدیریت خطای Quota (بدون تغییر) ---
|
|
|
|
|
|
|
| 362 |
const showQuotaModal = () => {
|
| 363 |
if (document.getElementById('custom-quota-modal')) return;
|
| 364 |
+
const modalHtml = `<div id="custom-quota-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); z-index: 99999; display: flex; align-items: center; justify-content: center; font-family: 'Vazirmatn', sans-serif;"><div class="ip-reset-guide-container"><div class="guide-header"><h2>یک قدم تا ساخت تصاویر جدید</h2><p>نیازمند تغییر نقطه دستیابی</p></div><div class="guide-content"><p>برای استفاده نامحدود از هوش مصنوعی، لطفاً نقطه اتصال اینترنت (IP) خود را تغییر دهید (مثلاً با خاموش/روشن کردن حالت پرواز) و دوباره تلاش کنید.</p><button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149635/...'}, '*')">دیدن ویدیو آموزشی</button></div><div class="guide-actions"><button class="action-button back-button" onclick="this.closest('#custom-quota-modal').remove()">بازگشت</button><button class="action-button retry-button" onclick="document.getElementById('run-btn').click(); this.closest('#custom-quota-modal').remove();">تلاش مجدد</button></div></div></div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
|
|
| 366 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
setInterval(() => {
|
| 368 |
+
document.querySelectorAll('.toast-body, .error').forEach(el => {
|
| 369 |
+
if ((el.innerText || "").toLowerCase().includes('quota')) {
|
| 370 |
+
showQuotaModal();
|
| 371 |
+
el.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
}
|
| 373 |
});
|
| 374 |
}, 100);
|
| 375 |
+
|
| 376 |
+
initializeApp();
|
| 377 |
+
|
| 378 |
+
// این تابع مخصوص گرادیو است تا وقتی خروجی JSON آپدیت شد، UI را آپدیت کند
|
| 379 |
+
window.updateUIFromGradio = function(jsonData) {
|
| 380 |
+
updateUIForUserStatus(jsonData);
|
| 381 |
+
}
|
| 382 |
});
|
| 383 |
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
<style>
|
| 385 |
+
/* --- استایلهای اصلی (مشابه قبل با اضافات جدید) --- */
|
| 386 |
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
|
| 387 |
+
body, .gradio-container { font-family: 'Vazirmatn', sans-serif !important; background-color: #f5f7fa !important; color-scheme: light !important; }
|
| 388 |
+
#col-container { direction: rtl; text-align: right; max-width: 980px; margin: 0 auto; padding: 30px; background: #ffffff; border-radius: 24px; box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08); }
|
| 389 |
+
#main-title h1 { text-align: center; font-size: 2.4em !important; font-weight: 800; background: -webkit-linear-gradient(45deg, #2563eb, #1e40af); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
| 390 |
+
#main-description { text-align: center; font-size: 1.15em; color: #4b5563 !important; margin-bottom: 25px; line-height: 1.6; }
|
| 391 |
+
footer, .flagging { display: none !important; }
|
| 392 |
+
|
| 393 |
+
/* --- استایلهای جدید برای بخش وضعیت کاربر --- */
|
| 394 |
+
#user-status-section {
|
| 395 |
+
display: none; /* Initially hidden */
|
| 396 |
+
text-align: center;
|
| 397 |
+
margin-bottom: 25px;
|
| 398 |
+
padding: 15px;
|
| 399 |
+
background-color: #f8f9fa;
|
| 400 |
+
border-radius: 14px;
|
| 401 |
+
border: 1px solid #e9ecef;
|
| 402 |
}
|
| 403 |
+
#subscription-badge {
|
| 404 |
+
display: inline-block;
|
| 405 |
+
padding: 6px 16px;
|
| 406 |
+
border-radius: 20px;
|
| 407 |
+
font-size: 0.9em;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
font-weight: 700;
|
| 409 |
+
margin-bottom: 10px;
|
|
|
|
|
|
|
|
|
|
| 410 |
}
|
| 411 |
+
#subscription-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; }
|
| 412 |
+
#subscription-badge.paid-badge { background: linear-gradient(45deg, #198754, #157347); color: white; }
|
| 413 |
+
#credit-status-text {
|
| 414 |
+
font-size: 1em;
|
| 415 |
+
color: #495057;
|
| 416 |
+
margin-bottom: 10px;
|
| 417 |
}
|
| 418 |
+
#credit-status-text strong { color: #0d6efd; }
|
| 419 |
+
#countdown-timer {
|
| 420 |
+
font-size: 0.9em;
|
| 421 |
+
color: #6c757d;
|
|
|
|
| 422 |
}
|
| 423 |
+
#countdown-timer span { font-weight: bold; font-variant-numeric: tabular-nums; }
|
| 424 |
+
#upgrade-btn {
|
| 425 |
+
display: none; /* Initially hidden */
|
| 426 |
+
margin-top: 15px;
|
| 427 |
+
padding: 10px 20px;
|
| 428 |
+
font-family: 'Vazirmatn', sans-serif;
|
| 429 |
+
font-weight: 700;
|
| 430 |
+
font-size: 1em;
|
| 431 |
+
color: white;
|
| 432 |
+
background: linear-gradient(135deg, #ffc107, #ff9800);
|
| 433 |
+
border: none;
|
| 434 |
+
border-radius: 12px;
|
| 435 |
+
cursor: pointer;
|
| 436 |
+
transition: all 0.2s ease;
|
| 437 |
+
box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3);
|
| 438 |
}
|
| 439 |
+
#upgrade-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(255, 193, 7, 0.4); }
|
| 440 |
|
| 441 |
+
.primary-btn:disabled {
|
| 442 |
+
background: #adb5bd !important;
|
| 443 |
+
cursor: not-allowed;
|
| 444 |
+
box-shadow: none !important;
|
| 445 |
+
transform: none !important;
|
|
|
|
|
|
|
| 446 |
}
|
| 447 |
+
|
| 448 |
+
/* --- بقیه استایلها (بدون تغییر) --- */
|
| 449 |
+
.primary-btn { background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; border:none !important; color:white !important; font-weight:700 !important; font-size:1.1em !important; padding:14px 28px !important; border-radius:14px !important; box-shadow:0 4px 15px rgba(16,185,129,0.3) !important; transition:all 0.3s ease !important; cursor:pointer !important; width:100%; margin-top:15px; }
|
| 450 |
+
.primary-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(16,185,129,0.45) !important; }
|
| 451 |
+
.ip-reset-guide-container { direction: rtl; ... } /* Styles for quota modal */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
</style>
|
| 453 |
"""
|
| 454 |
|
| 455 |
+
# استفاده از gr.Blocks
|
| 456 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue=colors.steel_blue)) as demo:
|
| 457 |
+
gr.HTML(js_and_css_code)
|
|
|
|
| 458 |
|
| 459 |
with gr.Column(elem_id="col-container"):
|
| 460 |
gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
|
| 461 |
gr.Markdown("با هوش مصنوعی آلفا تصاویر تونو به مدل های مختلف ویرایش کنید.", elem_id="main-description")
|
|
|
|
|
|
|
|
|
|
| 462 |
|
| 463 |
+
# --- بخش جدید: نمایش وضعیت کاربر ---
|
| 464 |
+
gr.HTML("""
|
| 465 |
+
<div id="user-status-section">
|
| 466 |
+
<div id="subscription-badge">...</div>
|
| 467 |
+
<p id="credit-status-text">در حال بررسی وضعیت حساب...</p>
|
| 468 |
+
<div id="countdown-timer" style="display: none;">
|
| 469 |
+
زمان تا شارژ مجدد اعتبار: <span></span>
|
| 470 |
+
</div>
|
| 471 |
+
<button id="upgrade-btn">⭐️ ارتقا به نسخه کامل و نامحدود</button>
|
| 472 |
+
</div>
|
| 473 |
+
""")
|
| 474 |
+
|
| 475 |
+
# --- بخش جدید: کامپوننتهای مخفی برای ارتباط JS و Python ---
|
| 476 |
+
with gr.Row(visible=False):
|
| 477 |
+
fingerprint_input = gr.Textbox(elem_id="fingerprint-input-for-backend", label="FP")
|
| 478 |
+
is_paid_input = gr.Textbox(elem_id="is-paid-input-for-backend", label="Paid")
|
| 479 |
+
status_check_btn = gr.Button(elem_id="status-check-btn-hidden")
|
| 480 |
+
status_json_output = gr.JSON(label="Status JSON")
|
| 481 |
|
| 482 |
with gr.Row(equal_height=True):
|
| 483 |
with gr.Column():
|
| 484 |
input_image = gr.Image(label="بارگذاری تصویر", type="pil", height=320)
|
| 485 |
prompt = gr.Text(label="دستور ویرایش (به فارسی)", placeholder="مثال: تصویر را به سبک انیمه تبدیل کن...", rtl=True, lines=3)
|
| 486 |
status_box = gr.HTML(label="وضعیت")
|
| 487 |
+
run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
|
| 488 |
|
| 489 |
with gr.Column():
|
| 490 |
output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
|
| 491 |
download_button = gr.Button("📥 دانلود و ذخیره تصویر", variant="secondary", elem_id="download-btn", elem_classes="primary-btn")
|
| 492 |
+
lora_adapter = gr.Dropdown(label="انتخاب سبک ویرایش (LoRA)", choices=list(LORA_MAPPING.keys()), value="تبدیل عکس به انیمه")
|
|
|
|
|
|
|
| 493 |
|
| 494 |
with gr.Accordion("تنظیمات پیشرفته", open=False):
|
| 495 |
+
aspect_ratio_selection = gr.Dropdown(label="ابعاد تصویر خروجی", choices=ASPECT_RATIOS_LIST, value="خودکار (پیشفرض)")
|
| 496 |
with gr.Row(visible=False) as custom_dims_row:
|
| 497 |
custom_width = gr.Slider(label="عرض", minimum=256, maximum=2048, step=8, value=1024)
|
| 498 |
custom_height = gr.Slider(label="ارتفاع", minimum=256, maximum=2048, step=8, value=1024)
|
| 499 |
+
seed = gr.Slider(label="دانه تصادفی (Seed)", minimum=0, maximum=MAX_SEED, step=1, value=0)
|
| 500 |
+
randomize_seed = gr.Checkbox(label="استفاده از Seed تصادفی", value=True)
|
| 501 |
+
guidance_scale = gr.Slider(label="وفاداری به متن", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
|
| 502 |
+
steps = gr.Slider(label="مراحل پردازش", minimum=1, maximum=50, step=1, value=4)
|
| 503 |
|
|
|
|
| 504 |
def toggle_row(choice):
|
| 505 |
+
return gr.update(visible=choice == "شخصیسازی (Custom)")
|
|
|
|
| 506 |
aspect_ratio_selection.change(fn=toggle_row, inputs=aspect_ratio_selection, outputs=custom_dims_row)
|
| 507 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 508 |
gr.Examples(
|
| 509 |
+
examples=[["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"], ["examples/5.jpg", "سایهها را حذف کن و نورپردازی نرم بده.", "اصلاح نور و سایه"]],
|
| 510 |
+
inputs=[input_image, prompt, lora_adapter], outputs=[output_image, seed, status_box], fn=infer_example, cache_examples=False, label="نمونهها"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
)
|
| 512 |
|
| 513 |
+
# --- اتصال رویدادها ---
|
| 514 |
+
run_button.click(
|
| 515 |
+
fn=infer,
|
| 516 |
+
inputs=[input_image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height, is_paid_input, fingerprint_input],
|
| 517 |
+
outputs=[output_image, seed, status_box],
|
| 518 |
+
api_name="predict"
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
# رویداد دکمه مخفی برای گرفتن وضعیت کاربر
|
| 522 |
+
status_check_btn.click(
|
| 523 |
+
fn=get_user_status_api,
|
| 524 |
+
inputs=[fingerprint_input],
|
| 525 |
+
outputs=[status_json_output]
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
# وقتی خروجی JSON آپدیت شد، تابع جاوااسکریپت را صدا بزن
|
| 529 |
+
status_json_output.change(
|
| 530 |
+
fn=None,
|
| 531 |
+
inputs=[status_json_output],
|
| 532 |
+
js="""
|
| 533 |
+
(jsonData) => {
|
| 534 |
+
// Check if the function exists on window before calling
|
| 535 |
+
if (window.updateUIFromGradio) {
|
| 536 |
+
window.updateUIFromGradio(jsonData);
|
| 537 |
+
} else {
|
| 538 |
+
console.error("Gradio UI update function not found!");
|
| 539 |
+
}
|
| 540 |
+
}
|
| 541 |
+
"""
|
| 542 |
+
)
|
| 543 |
+
|
| 544 |
+
download_button.click(fn=None, inputs=[output_image], outputs=None, js=js_download_func)
|
| 545 |
+
|
| 546 |
+
# --- تعریف API برای ارتباط مستقیم (در صورت نیاز) ---
|
| 547 |
+
demo.api(name="get_status")(get_user_status_api)
|
| 548 |
+
|
| 549 |
if __name__ == "__main__":
|
| 550 |
demo.queue(max_size=30).launch(show_error=True)
|