Update app.py
Browse files
app.py
CHANGED
|
@@ -10,7 +10,7 @@ app = FastAPI()
|
|
| 10 |
UPLOAD_DIR = "temp_uploads"
|
| 11 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 12 |
|
| 13 |
-
# لایه فرانتاند چتباکس
|
| 14 |
HTML_CHAT_INTERFACE = """
|
| 15 |
<!DOCTYPE html>
|
| 16 |
<html lang="fa" dir="rtl">
|
|
@@ -425,94 +425,56 @@ async def chat_endpoint(message: str = Form(...), image: UploadFile = File(None)
|
|
| 425 |
)
|
| 426 |
|
| 427 |
page = await context.new_page()
|
| 428 |
-
|
| 429 |
-
await page.
|
| 430 |
|
| 431 |
-
# پ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
if saved_image_path and os.path.exists(saved_image_path):
|
| 433 |
uploaded = False
|
| 434 |
-
|
| 435 |
-
# استراتژی ۱: تلاش برای تزریق مستقیم به لایه بارگذاری فایل (بدون نیاز به کلیک و باز کردن منو)
|
| 436 |
try:
|
| 437 |
file_input = await page.query_selector("input[type='file']")
|
| 438 |
if file_input:
|
| 439 |
await file_input.set_input_files(saved_image_path)
|
| 440 |
-
await page.wait_for_timeout(
|
| 441 |
uploaded = True
|
| 442 |
-
print("Direct file input injection succeeded.")
|
| 443 |
except Exception as e:
|
| 444 |
-
print(f"Direct injection
|
| 445 |
|
| 446 |
-
# استراتژی ۲: اگر تزریق مستقیم عمل نکرد، کلیک روی آیکون پلاس با تایماوت کوتاه و باز کردن منوی گالری
|
| 447 |
if not uploaded:
|
| 448 |
try:
|
| 449 |
-
|
| 450 |
-
chat_inputs = await page.query_selector_all("textarea, input[type='text'], [contenteditable='true']")
|
| 451 |
-
for inp in chat_inputs:
|
| 452 |
-
try:
|
| 453 |
-
if await inp.is_visible():
|
| 454 |
-
await inp.click(timeout=2000)
|
| 455 |
-
await page.wait_for_timeout(500)
|
| 456 |
-
break
|
| 457 |
-
except:
|
| 458 |
-
continue
|
| 459 |
-
|
| 460 |
-
# پیدا کردن آیکون مثبت یا دوربین و فعالسازی شبیهساز فایل چوزر
|
| 461 |
-
elements = await page.query_selector_all("button, div[role='button'], svg, [aria-label]")
|
| 462 |
for el in elements:
|
| 463 |
try:
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
await file_chooser.set_files(saved_image_path)
|
| 475 |
-
await page.wait_for_timeout(3000)
|
| 476 |
-
uploaded = True
|
| 477 |
-
print("Uploaded via main UI icon trigger.")
|
| 478 |
-
break
|
| 479 |
-
except:
|
| 480 |
-
continue
|
| 481 |
-
except Exception as e:
|
| 482 |
-
print(f"UI icon flow failed: {e}")
|
| 483 |
-
|
| 484 |
-
# استراتژی ۳: کلیک مستقیم روی منوی پاپآپ پایینی (گالری یا فایلها) در صورت باز شدن لایه شیت گوگل
|
| 485 |
-
if not uploaded:
|
| 486 |
-
try:
|
| 487 |
-
menu_items = await page.query_selector_all("div, span, button, p")
|
| 488 |
-
for item in menu_items:
|
| 489 |
-
try:
|
| 490 |
-
item_text = (await item.inner_text() or "").strip()
|
| 491 |
-
if item_text in ["گالری", "فایلها", "Gallery", "Files"]:
|
| 492 |
-
if await item.is_visible():
|
| 493 |
-
async with page.expect_file_chooser(timeout=3000) as fc_info:
|
| 494 |
-
await item.click(timeout=2000)
|
| 495 |
-
file_chooser = await fc_info.value
|
| 496 |
-
await file_chooser.set_files(saved_image_path)
|
| 497 |
-
await page.wait_for_timeout(3000)
|
| 498 |
-
uploaded = True
|
| 499 |
-
print("Uploaded via bottom sheet item click.")
|
| 500 |
-
break
|
| 501 |
except:
|
| 502 |
continue
|
| 503 |
except Exception as e:
|
| 504 |
-
print(f"
|
| 505 |
|
| 506 |
-
#
|
| 507 |
input_box = None
|
| 508 |
candidates = await page.query_selector_all("textarea, input, [contenteditable='true']")
|
| 509 |
for el in candidates:
|
| 510 |
try:
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
break
|
| 516 |
except:
|
| 517 |
continue
|
| 518 |
|
|
@@ -522,40 +484,56 @@ async def chat_endpoint(message: str = Form(...), image: UploadFile = File(None)
|
|
| 522 |
if input_box:
|
| 523 |
await input_box.focus()
|
| 524 |
await input_box.fill(message)
|
| 525 |
-
await page.wait_for_timeout(
|
| 526 |
await input_box.press("Enter")
|
| 527 |
-
|
| 528 |
-
# کلیک بر روی آیکون ارسال فرم (فلش آبی بالا) با استفاده از زمانبندی محدود ۲ ثانیهای
|
| 529 |
-
try:
|
| 530 |
-
send_buttons = await page.query_selector_all("button, [role='button']")
|
| 531 |
-
for btn in send_buttons:
|
| 532 |
-
if await btn.is_visible():
|
| 533 |
-
aria = (await btn.get_attribute("aria-label") or "").lower()
|
| 534 |
-
if any(w in aria for w in ["send", "ارسال", "arrow", "up"]):
|
| 535 |
-
await btn.click(timeout=2000)
|
| 536 |
-
break
|
| 537 |
-
except:
|
| 538 |
-
pass
|
| 539 |
|
| 540 |
-
# ا
|
| 541 |
-
await page.wait_for_timeout(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
|
| 543 |
full_text = await page.evaluate("() => document.body.innerText")
|
| 544 |
await browser.close()
|
| 545 |
|
| 546 |
-
#
|
| 547 |
lines = full_text.split('\n')
|
| 548 |
cleaned_lines = []
|
| 549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
for line in lines:
|
| 552 |
line_str = line.strip()
|
| 553 |
if not line_str:
|
| 554 |
continue
|
| 555 |
-
|
|
|
|
|
|
|
| 556 |
continue
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
continue
|
|
|
|
| 559 |
cleaned_lines.append(line_str)
|
| 560 |
|
| 561 |
ai_response = "\n".join(cleaned_lines)
|
|
@@ -567,7 +545,6 @@ async def chat_endpoint(message: str = Form(...), image: UploadFile = File(None)
|
|
| 567 |
await browser.close()
|
| 568 |
raise HTTPException(status_code=500, detail=f"خطا در رندر سرور هوش مصنوعی: {str(e)}")
|
| 569 |
finally:
|
| 570 |
-
# پاکسازی حتمی فایل تصویر از روی سرور داکر
|
| 571 |
if saved_image_path and os.path.exists(saved_image_path):
|
| 572 |
try:
|
| 573 |
os.remove(saved_image_path)
|
|
|
|
| 10 |
UPLOAD_DIR = "temp_uploads"
|
| 11 |
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 12 |
|
| 13 |
+
# لایه فرانتاند چتباکس با استایل شیشهای (Glassmorphism)
|
| 14 |
HTML_CHAT_INTERFACE = """
|
| 15 |
<!DOCTYPE html>
|
| 16 |
<html lang="fa" dir="rtl">
|
|
|
|
| 425 |
)
|
| 426 |
|
| 427 |
page = await context.new_page()
|
| 428 |
+
# لود اولیه سبک domcontentloaded به جای networkidle جهت افزایش سرعت
|
| 429 |
+
await page.goto(url, wait_until="domcontentloaded", timeout=30000)
|
| 430 |
|
| 431 |
+
# پیدا کردن کادر متن فرم برای آمادگی تزریق
|
| 432 |
+
try:
|
| 433 |
+
await page.wait_for_selector("textarea, input[type='text']", timeout=4000)
|
| 434 |
+
except:
|
| 435 |
+
pass
|
| 436 |
+
|
| 437 |
+
# پروسه مالتیمدال خطاپذیر بدون قفل شدن مرورگر
|
| 438 |
if saved_image_path and os.path.exists(saved_image_path):
|
| 439 |
uploaded = False
|
|
|
|
|
|
|
| 440 |
try:
|
| 441 |
file_input = await page.query_selector("input[type='file']")
|
| 442 |
if file_input:
|
| 443 |
await file_input.set_input_files(saved_image_path)
|
| 444 |
+
await page.wait_for_timeout(1500) # زمان کوتاه فقط برای متصل شدن فایل به فرم گوگل
|
| 445 |
uploaded = True
|
|
|
|
| 446 |
except Exception as e:
|
| 447 |
+
print(f"Direct injection warning: {e}")
|
| 448 |
|
|
|
|
| 449 |
if not uploaded:
|
| 450 |
try:
|
| 451 |
+
elements = await page.query_selector_all("button, div[role='button'], svg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
for el in elements:
|
| 453 |
try:
|
| 454 |
+
aria = (await el.get_attribute("aria-label") or "").lower()
|
| 455 |
+
html = (await el.inner_html() or "").lower()
|
| 456 |
+
combined = aria + html
|
| 457 |
+
if any(k in combined for k in ["+", "plus", "camera", "lens", "دوربین", "تصویر"]):
|
| 458 |
+
async with page.expect_file_chooser(timeout=2000) as fc_info:
|
| 459 |
+
await el.click(timeout=1500)
|
| 460 |
+
file_chooser = await fc_info.value
|
| 461 |
+
await file_chooser.set_files(saved_image_path)
|
| 462 |
+
await page.wait_for_timeout(1500)
|
| 463 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
except:
|
| 465 |
continue
|
| 466 |
except Exception as e:
|
| 467 |
+
print(f"UI upload sequence warning: {e}")
|
| 468 |
|
| 469 |
+
# درج و ارسال نهایی پیام کاربر
|
| 470 |
input_box = None
|
| 471 |
candidates = await page.query_selector_all("textarea, input, [contenteditable='true']")
|
| 472 |
for el in candidates:
|
| 473 |
try:
|
| 474 |
+
placeholder = await el.get_attribute("placeholder") or ""
|
| 475 |
+
if any(w in placeholder for w in ["بپرسید", "ask", "پیام", "چطور", "هرچه"]):
|
| 476 |
+
input_box = el
|
| 477 |
+
break
|
|
|
|
| 478 |
except:
|
| 479 |
continue
|
| 480 |
|
|
|
|
| 484 |
if input_box:
|
| 485 |
await input_box.focus()
|
| 486 |
await input_box.fill(message)
|
| 487 |
+
await page.wait_for_timeout(300)
|
| 488 |
await input_box.press("Enter")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
|
| 490 |
+
# 🚀 الگوریتم پایش داینامیک قد کشیدن متن (حذف ۹ ثانیه خواب اجباری)
|
| 491 |
+
await page.wait_for_timeout(2500) # مهلت اولیه برای استارت خوردن استریم جواب توسط گوگل
|
| 492 |
+
|
| 493 |
+
last_len = 0
|
| 494 |
+
stable_count = 0
|
| 495 |
+
for _ in range(20): # حداکثر ۱۰ ثانیه تلاش مجدد در گامهای ۵۰۰ میلیثانیهای
|
| 496 |
+
current_text = await page.evaluate("() => document.body.innerText")
|
| 497 |
+
current_len = len(current_text or "")
|
| 498 |
+
|
| 499 |
+
if current_len == last_len and current_len > 0:
|
| 500 |
+
stable_count += 1
|
| 501 |
+
if stable_count >= 2: # اگر طول متن در ۲ دوره متوالی ثابت ماند، استریم به پایان رسیده است
|
| 502 |
+
break
|
| 503 |
+
else:
|
| 504 |
+
stable_count = 0
|
| 505 |
+
last_len = current_len
|
| 506 |
+
await page.wait_for_timeout(500)
|
| 507 |
|
| 508 |
full_text = await page.evaluate("() => document.body.innerText")
|
| 509 |
await browser.close()
|
| 510 |
|
| 511 |
+
# 🎯 الگوریتم پاکسازی کدهای اضافی و جداسازی پاسخ خالص هوش مصنوعی
|
| 512 |
lines = full_text.split('\n')
|
| 513 |
cleaned_lines = []
|
| 514 |
+
|
| 515 |
+
exclude_keywords = [
|
| 516 |
+
"حالت هوشوارهای", "ویدئوها", "نقشهها", "اخبار", "تصاویر", "خرید کردن", "کتابها", "مالی",
|
| 517 |
+
"All items removed from input context", "ارسال", "هرچه میخواهید بپرسید", "بیشتر بدانید",
|
| 518 |
+
"اگر امکان دارد آن را به فارسی ایمیل کنید", "پاسخ «حالت هوشوارهای» آماده است", "همه"
|
| 519 |
+
]
|
| 520 |
|
| 521 |
for line in lines:
|
| 522 |
line_str = line.strip()
|
| 523 |
if not line_str:
|
| 524 |
continue
|
| 525 |
+
|
| 526 |
+
# حذف خط اکو شدهی خود کلمه سرچ شده کاربر در ابتدای بدنه
|
| 527 |
+
if line_str == message.strip():
|
| 528 |
continue
|
| 529 |
+
|
| 530 |
+
# نقطه قطع سابمیت: رسیدن به دکمههای لایک/دیسلایک یا کادر کپیرایت انتهای چت گوگل
|
| 531 |
+
if any(stop_word in line_str for stop_word in ["در پاسخهای هوشواره", "هوشواره ممکن است اشتباه", "پاسخهای مربوط به افراد"]):
|
| 532 |
+
break
|
| 533 |
+
|
| 534 |
+
if any(ex in line_str for ex in exclude_keywords):
|
| 535 |
continue
|
| 536 |
+
|
| 537 |
cleaned_lines.append(line_str)
|
| 538 |
|
| 539 |
ai_response = "\n".join(cleaned_lines)
|
|
|
|
| 545 |
await browser.close()
|
| 546 |
raise HTTPException(status_code=500, detail=f"خطا در رندر سرور هوش مصنوعی: {str(e)}")
|
| 547 |
finally:
|
|
|
|
| 548 |
if saved_image_path and os.path.exists(saved_image_path):
|
| 549 |
try:
|
| 550 |
os.remove(saved_image_path)
|