Opera8 commited on
Commit
a7a30dc
·
verified ·
1 Parent(s): dc95380

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +409 -205
app.py CHANGED
@@ -2,22 +2,22 @@ import os
2
  import time
3
  import asyncio
4
  import uvicorn
5
- from fastapi import FastAPI, Form, File, UploadFile
6
- from fastapi.responses import HTMLResponse, StreamingResponse
7
  from playwright.async_api import async_playwright
8
 
9
  app = FastAPI()
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">
17
  <head>
18
  <meta charset="UTF-8">
19
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
- <title>Google AI Free Chatbot + Stealth Streaming</title>
21
  <style>
22
  :root {
23
  --bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
@@ -31,48 +31,217 @@ HTML_CHAT_INTERFACE = """
31
 
32
  body {
33
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Tahoma, sans-serif;
34
- margin: 0; padding: 0; background: var(--bg-gradient); color: var(--text-main);
35
- height: 100vh; display: flex; align-items: center; justify-content: center; overflow: hidden;
 
 
 
 
 
 
 
36
  }
37
 
38
  .chat-container {
39
- width: 100%; max-width: 850px; height: 85vh; background: var(--glass-bg);
40
- backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
41
- border: 1px solid var(--glass-border); border-radius: 24px;
42
- display: flex; flex-direction: column; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); margin: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
 
45
- .chat-header { padding: 20px 24px; border-bottom: 1px solid var(--glass-border); display: flex; align-items: center; gap: 12px; }
46
- .chat-header .status-dot { width: 10px; height: 10px; background: #4ade80; border-radius: 50%; box-shadow: 0 0 10px #4ade80; }
47
- .chat-header h2 { margin: 0; font-size: 18px; font-weight: 600; color: var(--accent-color); }
48
- .messages-box { flex: 1; padding: 24px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; }
49
  .messages-box::-webkit-scrollbar { width: 6px; }
50
  .messages-box::-webkit-scrollbar-track { background: transparent; }
51
  .messages-box::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); border-radius: 10px; }
52
 
53
- .message { max-width: 75%; padding: 14px 18px; border-radius: 18px; font-size: 15px; line-height: 1.6; word-wrap: break-word; animation: fadeIn 0.3s ease forwards; }
54
- .message.user { background: var(--user-bubble); align-self: flex-start; border-bottom-right-radius: 4px; box-shadow: 0 4px 12px rgba(2, 132, 199, 0.2); }
55
- .message.ai { background: var(--ai-bubble); border: 1px solid var(--glass-border); align-self: flex-end; border-bottom-left-radius: 4px; white-space: pre-wrap; }
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- .input-wrapper { border-top: 1px solid var(--glass-border); background: rgba(15, 23, 42, 0.4); border-bottom-left-radius: 24px; border-bottom-right-radius: 24px; padding: 15px 24px; display: flex; flex-direction: column; gap: 10px; }
58
- .preview-container { display: none; align-items: center; gap: 10px; background: rgba(255, 255, 255, 0.05); padding: 8px 12px; border-radius: 10px; width: fit-content; border: 1px solid var(--glass-border); }
59
- .preview-container img { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; }
60
- .preview-container .remove-btn { color: #ef4444; cursor: pointer; font-weight: bold; font-size: 14px; }
 
 
 
61
 
62
- .input-area { display: flex; gap: 12px; align-items: center; }
63
- .input-area input[type="text"] { flex: 1; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--glass-border); padding: 14px 20px; border-radius: 14px; color: white; font-size: 15px; outline: none; transition: all 0.2s; }
64
- .input-area input[type="text"]:focus { border-color: var(--accent-color); background: rgba(255, 255, 255, 0.08); box-shadow: 0 0 12px rgba(56, 189, 248, 0.15); }
65
-
66
- .upload-label { background: rgba(255, 255, 255, 0.05); border: 1px solid var(--glass-border); padding: 12px; border-radius: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
67
- .upload-label:hover { background: rgba(255, 255, 255, 0.1); border-color: var(--accent-color); }
68
- .upload-label svg { width: 22px; height: 22px; fill: var(--text-main); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- .input-area button { background: var(--accent-color); color: #0f172a; border: none; padding: 14px 24px; border-radius: 14px; font-size: 15px; font-weight: bold; cursor: pointer; transition: all 0.2s; }
71
- .input-area button:hover { background: #7dd3fc; transform: translateY(-1px); }
72
- .input-area button:disabled { background: #64748b; color: #1e293b; cursor: not-allowed; transform: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
- .typing-indicator { display: flex; gap: 5px; padding: 12px 18px; background: var(--ai-bubble); border-radius: 12px; align-self: flex-end; border: 1px solid var(--glass-border); }
75
- .typing-indicator span { width: 8px; height: 8px; background: var(--accent-color); border-radius: 50%; animation: bounce 1.4s infinite ease-in-out both; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
77
  .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
78
 
@@ -85,10 +254,10 @@ HTML_CHAT_INTERFACE = """
85
  <div class="chat-container">
86
  <div class="chat-header">
87
  <div class="status-dot"></div>
88
- <h2>دستیار مالتی‌مدال گوگل (متن + تصویر زنده و سریع)</h2>
89
  </div>
90
  <div class="messages-box" id="messagesBox">
91
- <div class="message ai">سلام! من متصل به هوشواره گوگل هستم. کلمات را به صورت کلمه به کلمه و زنده تایپ می‌کنم. چطور می‌توانم کمکتان کنم؟</div>
92
  </div>
93
 
94
  <div class="input-wrapper">
@@ -117,18 +286,27 @@ HTML_CHAT_INTERFACE = """
117
  const previewContainer = document.getElementById('previewContainer');
118
  const imagePreview = document.getElementById('imagePreview');
119
 
120
- function handleKeyPress(e) { if (e.key === 'Enter') sendMessage(); }
 
 
121
 
122
  function previewImage(event) {
123
  const file = event.target.files[0];
124
  if (file) {
125
  const reader = new FileReader();
126
- reader.onload = function(e) { imagePreview.src = e.target.result; previewContainer.style.display = 'flex'; }
 
 
 
127
  reader.readAsDataURL(file);
128
  }
129
  }
130
 
131
- function removeSelectedImage() { fileInput.value = ''; previewContainer.style.display = 'none'; imagePreview.src = ''; }
 
 
 
 
132
 
133
  async function sendMessage() {
134
  const text = userInput.value.trim();
@@ -146,63 +324,61 @@ HTML_CHAT_INTERFACE = """
146
  formData.append('message', text || "این تصویر را تحلیل کن");
147
  if (file) formData.append('image', file);
148
 
149
- const aiMessageDiv = document.createElement('div');
150
- aiMessageDiv.classList.add('message', 'ai');
151
- messagesBox.appendChild(aiMessageDiv);
152
-
153
  try {
154
  const response = await fetch('/api/chat', { method: 'POST', body: formData });
155
- if (!response.ok) {
156
- aiMessageDiv.innerText = "خطا در ارتباط با گوگل یا مسدود شدن آی‌پی سرور.";
157
- setLoading(false);
158
- return;
159
- }
160
-
161
- const reader = response.body.getReader();
162
- const decoder = new TextDecoder("utf-8");
163
- let firstChunk = true;
164
-
165
- while (true) {
166
- const { value, done } = await reader.read();
167
- if (done) break;
168
-
169
- const chunk = decoder.decode(value, { stream: true });
170
- if (chunk) {
171
- if (firstChunk) {
172
- const indicator = document.getElementById('typingIndicator');
173
- if (indicator) indicator.remove();
174
- firstChunk = false;
175
- }
176
- aiMessageDiv.innerText += chunk;
177
- messagesBox.scrollTop = messagesBox.scrollHeight;
178
- }
179
  }
180
  } catch (error) {
181
- aiMessageDiv.innerText = "خطای شبکه در دریافت استریم زنده.";
182
  } finally {
183
  setLoading(false);
184
  }
185
  }
186
 
187
  function appendMessage(text, sender) {
188
- const msgDiv = document.createElement('div'); msgDiv.classList.add('message', sender);
189
- msgDiv.innerText = text; messagesBox.appendChild(msgDiv); messagesBox.scrollTop = messagesBox.scrollHeight;
 
 
 
190
  }
191
 
192
  function appendImageMessage(src, sender) {
193
- const msgDiv = document.createElement('div'); msgDiv.classList.add('message', sender); msgDiv.style.padding = '8px';
194
- const img = document.createElement('img'); img.src = src; img.style.maxWidth = '200px'; img.style.maxHeight = '200px'; img.style.borderRadius = '12px'; img.style.display = 'block';
195
- msgDiv.appendChild(img); messagesBox.appendChild(msgDiv); messagesBox.scrollTop = messagesBox.scrollHeight;
 
 
 
 
 
 
 
 
 
196
  }
197
 
198
  function setLoading(isLoading) {
199
  if (isLoading) {
200
- userInput.disabled = true; sendBtn.disabled = true;
201
- const indicator = document.createElement('div'); indicator.classList.add('typing-indicator'); indicator.id = 'typingIndicator';
202
- indicator.innerHTML = '<span></span><span></span><span></span>'; messagesBox.appendChild(indicator); messagesBox.scrollTop = messagesBox.scrollHeight;
 
 
 
 
 
203
  } else {
204
- userInput.disabled = false; sendBtn.disabled = false;
205
- const indicator = document.getElementById('typingIndicator'); if (indicator) indicator.remove(); userInput.focus();
 
 
 
206
  }
207
  }
208
  </script>
@@ -210,51 +386,6 @@ HTML_CHAT_INTERFACE = """
210
  </html>
211
  """
212
 
213
- # الگوریتم تصفیه رادیکال پاسخ هوش مصنوعی
214
- def clean_text(full_text: str, message: str) -> str:
215
- if "ترافیک غیر عادی" in full_text or "Our systems have detected unusual traffic" in full_text:
216
- return "🚨 سیستم امنیتی گوگل فعال شد و آی‌پی سرور هاگینگ‌فیس را مسدود کرد (کپچا رخ داد)."
217
-
218
- lines = full_text.split('\n')
219
-
220
- # کلمات کلیدی و جملات خوش‌آمدگویی پیش‌فرض گوگل که کلاً باید فیلتر شوند
221
- junk_keywords = [
222
- "حالت هوشواره", "هوژواره", "ایمیل کنید", "تصاویر", "ویدیو", "ویدئو", "اخبار",
223
- "نقشه", "خرید", "کتاب", "مالی", "موضوع‌محور", "ورود", "ارسال", "همه",
224
- "آماده است", "items removed", "بیشتر بدانید", "محتمل است", "هویشواره", "هرچه می‌خواهید",
225
- "پاسخ به سوالات علمی", "نگارش، ویرایش یا ترجمه", "برنامه‌نویسی و حل مسائل",
226
- "ایده‌پردازی و کارهای خلاقانه", "لطفاً موضوع مورد نظرتون", "روزتون بخیر. من دستیار هوش مصنوعی",
227
- "امروز چطور می‌تونم کمکتون کنم", "نیاز به راهنمایی دارید", "در چه زمینه‌ای نیاز",
228
- "شما تصویر، متن یا فایلی ارسال نکردید", "متوجه منظور دقیق شما نشدم"
229
- ]
230
-
231
- # فاز ۱: فیلتر کلمات هدر و متوقف شدن حلقه در ابتدای بدنه فوتر سلب مسئولیت
232
- filtered_lines = []
233
- for line in lines:
234
- line_str = line.strip()
235
- if not line_str:
236
- continue
237
- if "هوشواره ممکن است اشتباه" in line_str or "در پاسخ‌های" in line_str or "بیشتر بدانید" in line_str or "پاسخ «حالت" in line_str:
238
- break
239
- if any(junk in line_str for junk in junk_keywords):
240
- continue
241
- filtered_lines.append(line_str)
242
-
243
- # فاز ۲: برش قطعی لایه‌های بالای متن پاسخ بر اساس تطابق لنگر پیام کاربر
244
- final_lines = []
245
- found_query_anchor = False
246
- for line in filtered_lines:
247
- if not found_query_anchor and (message.lower() in line.lower() or line.lower() in message.lower()):
248
- found_query_anchor = True
249
- final_lines = []
250
- continue
251
- final_lines.append(line)
252
-
253
- if not final_lines:
254
- final_lines = filtered_lines
255
-
256
- return "\n".join(final_lines).strip()
257
-
258
  @app.get("/", response_class=HTMLResponse)
259
  async def chat_interface():
260
  return HTML_CHAT_INTERFACE
@@ -262,115 +393,188 @@ async def chat_interface():
262
  @app.post("/api/chat")
263
  async def chat_endpoint(message: str = Form(...), image: UploadFile = File(None)):
264
  saved_image_path = None
 
265
  if image:
266
  try:
267
  saved_image_path = os.path.join(UPLOAD_DIR, f"{int(time.time())}_{image.filename}")
268
  with open(saved_image_path, "wb") as buffer:
269
  buffer.write(await image.read())
270
  except Exception as e:
271
- return StreamingResponse(iter([f"خطا در ذخیره تصویر روی داکر: {str(e)}"]), media_type="text/plain")
272
 
273
- async def response_generator():
274
- async with async_playwright() as p:
 
 
 
275
  browser = await p.chromium.launch(
276
  headless=True,
277
  args=[
278
  "--no-sandbox",
279
  "--disable-setuid-sandbox",
280
  "--disable-dev-shm-usage",
281
- "--disable-blink-features=AutomationControlled",
282
- "--disable-web-security"
283
  ]
284
  )
285
 
286
  context = await browser.new_context(
287
- user_agent="Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
288
  viewport={"width": 412, "height": 915},
289
- locale="fa-IR", timezone_id="Asia/Tehran",
290
- extra_http_headers={
291
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
292
- "Accept-Language": "fa-IR,fa;q=0.9,en-US;q=0.8,en;q=0.7",
293
- "Upgrade-Insecure-Requests": "1"
294
- }
295
  )
 
296
  page = await context.new_page()
 
 
297
 
298
- try:
299
- # مانیتورینگ مسیر ترکیبی بر اساس وجود فایل مالتی‌مدال
300
- if saved_image_path and os.path.exists(saved_image_path):
301
- # اگر تصویر وجود دارد: ابتدا لود کردن کادر چت اصلی گوگل هوشواره
302
- await page.goto("https://www.google.com/search?q=سلام&udm=50", wait_until="domcontentloaded", timeout=30000)
303
- await page.wait_for_timeout(1000)
304
-
305
- # فعال‌سازی کادر چت با کلیک اجباری (بدون ارور تایم‌اوت)
306
- init_input = await page.query_selector("textarea, input[type='text'], [contenteditable='true']")
307
- if init_input:
308
- await init_input.click(force=True, timeout=2000)
309
- await page.wait_for_timeout(500)
310
-
311
- # تزریق مستقیم فایل به لایه آپلو��ر بومی
312
  file_input = await page.query_selector("input[type='file']")
313
  if file_input:
314
  await file_input.set_input_files(saved_image_path)
315
- # شلیک دستی رویداد change به کامپوننت لنز گوگل تا فورا متوجه حضور عکس بشود
316
- await page.evaluate("el => el.dispatchEvent(new Event('change', { bubbles: true }))", file_input)
317
- await page.wait_for_timeout(2000)
318
-
319
- # نوشتن پیام متنی کاربر
320
- input_box = await page.query_selector("textarea, input[type='text'], [contenteditable='true']")
321
- if input_box:
322
- await input_box.focus()
323
- await input_box.fill(message)
324
- await page.wait_for_timeout(300)
325
- await input_box.press("Enter")
326
- else:
327
- # اگر فقط متن است: لود سریع و مستقیم از طریق URL کوئری (سرعت زیر ۲ ثانیه)
328
- url = f"https://www.google.com/search?q={message}&udm=50"
329
- await page.goto(url, wait_until="domcontentloaded", timeout=30000)
330
-
331
- # ⚡ حلقه شنود استریم فوق سریع
332
- last_sent_text = ""
333
- no_change_count = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
- for _ in range(50):
336
- await page.wait_for_timeout(300)
337
- full_text = await page.evaluate("() => document.body.innerText")
338
- current_clean_text = clean_text(full_text, message)
339
-
340
- if "سیستم امنیتی گوگل فعال شد" in current_clean_text:
341
- yield current_clean_text
342
- break
343
-
344
- # تزریق آنلاین کلمات جدید به صورت کلمه به کلمه به صفحه چت کاربر
345
- if len(current_clean_text) > len(last_sent_text):
346
- new_chunk = current_clean_text[len(last_sent_text):]
347
- yield new_chunk
348
- last_sent_text = current_clean_text
349
- no_change_count = 0
350
- else:
351
- if len(current_clean_text) > 0:
352
- no_change_count += 1
353
-
354
- if "هوشواره ممکن است اشتباه" in full_text or "بیشتر بدانید" in full_text or "پاسخ‌های مربوط به افراد" in full_text:
355
- full_text = await page.evaluate("() => document.body.innerText")
356
- current_clean_text = clean_text(full_text, message)
357
- if len(current_clean_text) > len(last_sent_text):
358
- yield current_clean_text[len(last_sent_text):]
359
- break
360
-
361
- if no_change_count > 8 and len(last_sent_text) > 0:
362
- break
363
-
364
- except Exception as e:
365
- yield f"خطای پردازش مرورگر: {str(e)}"
366
- finally:
367
- await context.close()
 
 
 
 
 
 
368
  await browser.close()
369
- if saved_image_path and os.path.exists(saved_image_path):
370
- try: os.remove(saved_image_path)
371
- except: pass
372
-
373
- return StreamingResponse(response_generator(), media_type="text/event-stream")
 
 
 
 
 
374
 
375
  if __name__ == "__main__":
376
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
2
  import time
3
  import asyncio
4
  import uvicorn
5
+ from fastapi import FastAPI, Form, File, UploadFile, HTTPException
6
+ from fastapi.responses import HTMLResponse, JSONResponse
7
  from playwright.async_api import async_playwright
8
 
9
  app = FastAPI()
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">
17
  <head>
18
  <meta charset="UTF-8">
19
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>Google AI Free Chatbot + Vision</title>
21
  <style>
22
  :root {
23
  --bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
 
31
 
32
  body {
33
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Tahoma, sans-serif;
34
+ margin: 0;
35
+ padding: 0;
36
+ background: var(--bg-gradient);
37
+ color: var(--text-main);
38
+ height: 100vh;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ overflow: hidden;
43
  }
44
 
45
  .chat-container {
46
+ width: 100%;
47
+ max-width: 850px;
48
+ height: 85vh;
49
+ background: var(--glass-bg);
50
+ backdrop-filter: blur(16px);
51
+ -webkit-backdrop-filter: blur(16px);
52
+ border: 1px solid var(--glass-border);
53
+ border-radius: 24px;
54
+ display: flex;
55
+ flex-direction: column;
56
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
57
+ margin: 20px;
58
+ }
59
+
60
+ .chat-header {
61
+ padding: 20px 24px;
62
+ border-bottom: 1px solid var(--glass-border);
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 12px;
66
+ }
67
+
68
+ .chat-header .status-dot {
69
+ width: 10px;
70
+ height: 10px;
71
+ background: #4ade80;
72
+ border-radius: 50%;
73
+ box-shadow: 0 0 10px #4ade80;
74
+ }
75
+
76
+ .chat-header h2 {
77
+ margin: 0;
78
+ font-size: 18px;
79
+ font-weight: 600;
80
+ color: var(--accent-color);
81
+ }
82
+
83
+ .messages-box {
84
+ flex: 1;
85
+ padding: 24px;
86
+ overflow-y: auto;
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 16px;
90
  }
91
 
 
 
 
 
92
  .messages-box::-webkit-scrollbar { width: 6px; }
93
  .messages-box::-webkit-scrollbar-track { background: transparent; }
94
  .messages-box::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); border-radius: 10px; }
95
 
96
+ .message {
97
+ max-width: 75%;
98
+ padding: 14px 18px;
99
+ border-radius: 18px;
100
+ font-size: 15px;
101
+ line-height: 1.6;
102
+ word-wrap: break-word;
103
+ animation: fadeIn 0.3s ease forwards;
104
+ }
105
+
106
+ .message.user {
107
+ background: var(--user-bubble);
108
+ align-self: flex-start;
109
+ border-bottom-right-radius: 4px;
110
+ box-shadow: 0 4px 12px rgba(2, 132, 199, 0.2);
111
+ }
112
 
113
+ .message.ai {
114
+ background: var(--ai-bubble);
115
+ border: 1px solid var(--glass-border);
116
+ align-self: flex-end;
117
+ border-bottom-left-radius: 4px;
118
+ white-space: pre-wrap;
119
+ }
120
 
121
+ .input-wrapper {
122
+ border-top: 1px solid var(--glass-border);
123
+ background: rgba(15, 23, 42, 0.4);
124
+ border-bottom-left-radius: 24px;
125
+ border-bottom-right-radius: 24px;
126
+ padding: 15px 24px;
127
+ display: flex;
128
+ flex-direction: column;
129
+ gap: 10px;
130
+ }
131
+
132
+ .preview-container {
133
+ display: none;
134
+ align-items: center;
135
+ gap: 10px;
136
+ background: rgba(255, 255, 255, 0.05);
137
+ padding: 8px 12px;
138
+ border-radius: 10px;
139
+ width: fit-content;
140
+ border: 1px solid var(--glass-border);
141
+ }
142
+
143
+ .preview-container img {
144
+ width: 40px;
145
+ height: 40px;
146
+ object-fit: cover;
147
+ border-radius: 6px;
148
+ }
149
 
150
+ .preview-container .remove-btn {
151
+ color: #ef4444;
152
+ cursor: pointer;
153
+ font-weight: bold;
154
+ font-size: 14px;
155
+ }
156
+
157
+ .input-area {
158
+ display: flex;
159
+ gap: 12px;
160
+ align-items: center;
161
+ }
162
+
163
+ .input-area input[type="text"] {
164
+ flex: 1;
165
+ background: rgba(255, 255, 255, 0.05);
166
+ border: 1px solid var(--glass-border);
167
+ padding: 14px 20px;
168
+ border-radius: 14px;
169
+ color: white;
170
+ font-size: 15px;
171
+ outline: none;
172
+ transition: all 0.2s;
173
+ }
174
+
175
+ .input-area input[type="text"]:focus {
176
+ border-color: var(--accent-color);
177
+ background: rgba(255, 255, 255, 0.08);
178
+ box-shadow: 0 0 12px rgba(56, 189, 248, 0.15);
179
+ }
180
+
181
+ .upload-label {
182
+ background: rgba(255, 255, 255, 0.05);
183
+ border: 1px solid var(--glass-border);
184
+ padding: 12px;
185
+ border-radius: 14px;
186
+ cursor: pointer;
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ transition: all 0.2s;
191
+ }
192
+
193
+ .upload-label:hover {
194
+ background: rgba(255, 255, 255, 0.1);
195
+ border-color: var(--accent-color);
196
+ }
197
+
198
+ .upload-label svg {
199
+ width: 22px;
200
+ height: 22px;
201
+ fill: var(--text-main);
202
+ }
203
+
204
+ .input-area button {
205
+ background: var(--accent-color);
206
+ color: #0f172a;
207
+ border: none;
208
+ padding: 14px 24px;
209
+ border-radius: 14px;
210
+ font-size: 15px;
211
+ font-weight: bold;
212
+ cursor: pointer;
213
+ transition: all 0.2s;
214
+ }
215
+
216
+ .input-area button:hover {
217
+ background: #7dd3fc;
218
+ transform: translateY(-1px);
219
+ }
220
 
221
+ .input-area button:disabled {
222
+ background: #64748b;
223
+ color: #1e293b;
224
+ cursor: not-allowed;
225
+ transform: none;
226
+ }
227
+
228
+ .typing-indicator {
229
+ display: flex;
230
+ gap: 5px;
231
+ padding: 12px 18px;
232
+ background: var(--ai-bubble);
233
+ border-radius: 12px;
234
+ align-self: flex-end;
235
+ border: 1px solid var(--glass-border);
236
+ }
237
+
238
+ .typing-indicator span {
239
+ width: 8px;
240
+ height: 8px;
241
+ background: var(--accent-color);
242
+ border-radius: 50%;
243
+ animation: bounce 1.4s infinite ease-in-out both;
244
+ }
245
  .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
246
  .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
247
 
 
254
  <div class="chat-container">
255
  <div class="chat-header">
256
  <div class="status-dot"></div>
257
+ <h2>دستیار مالتی‌مدال گوگل (متن + تصویر کاملاً رایگان)</h2>
258
  </div>
259
  <div class="messages-box" id="messagesBox">
260
+ <div class="message ai">سلام! من متصل به هوشواره گوگل هستم. می‌توانید علاوه بر متن، تصویر هم برایم بفرستید تا بررسی کنم.</div>
261
  </div>
262
 
263
  <div class="input-wrapper">
 
286
  const previewContainer = document.getElementById('previewContainer');
287
  const imagePreview = document.getElementById('imagePreview');
288
 
289
+ function handleKeyPress(e) {
290
+ if (e.key === 'Enter') sendMessage();
291
+ }
292
 
293
  function previewImage(event) {
294
  const file = event.target.files[0];
295
  if (file) {
296
  const reader = new FileReader();
297
+ reader.onload = function(e) {
298
+ imagePreview.src = e.target.result;
299
+ previewContainer.style.display = 'flex';
300
+ }
301
  reader.readAsDataURL(file);
302
  }
303
  }
304
 
305
+ function removeSelectedImage() {
306
+ fileInput.value = '';
307
+ previewContainer.style.display = 'none';
308
+ imagePreview.src = '';
309
+ }
310
 
311
  async function sendMessage() {
312
  const text = userInput.value.trim();
 
324
  formData.append('message', text || "این تصویر را تحلیل کن");
325
  if (file) formData.append('image', file);
326
 
 
 
 
 
327
  try {
328
  const response = await fetch('/api/chat', { method: 'POST', body: formData });
329
+ const data = await response.json();
330
+
331
+ if (response.ok) {
332
+ appendMessage(data.response, 'ai');
333
+ } else {
334
+ appendMessage("خطا: " + (data.detail || "مشکلی در دریافت پاسخ رخ داد."), 'ai');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  }
336
  } catch (error) {
337
+ appendMessage("خطای شبکه: دریافت پاسخ از سرور انجام نشد.", 'ai');
338
  } finally {
339
  setLoading(false);
340
  }
341
  }
342
 
343
  function appendMessage(text, sender) {
344
+ const msgDiv = document.createElement('div');
345
+ msgDiv.classList.add('message', sender);
346
+ msgDiv.innerText = text;
347
+ messagesBox.appendChild(msgDiv);
348
+ messagesBox.scrollTop = messagesBox.scrollHeight;
349
  }
350
 
351
  function appendImageMessage(src, sender) {
352
+ const msgDiv = document.createElement('div');
353
+ msgDiv.classList.add('message', sender);
354
+ msgDiv.style.padding = '8px';
355
+ const img = document.createElement('img');
356
+ img.src = src;
357
+ img.style.maxWidth = '200px';
358
+ img.style.maxHeight = '200px';
359
+ img.style.borderRadius = '12px';
360
+ img.style.display = 'block';
361
+ msgDiv.appendChild(img);
362
+ messagesBox.appendChild(msgDiv);
363
+ messagesBox.scrollTop = messagesBox.scrollHeight;
364
  }
365
 
366
  function setLoading(isLoading) {
367
  if (isLoading) {
368
+ userInput.disabled = true;
369
+ sendBtn.disabled = true;
370
+ const indicator = document.createElement('div');
371
+ indicator.classList.add('typing-indicator');
372
+ indicator.id = 'typingIndicator';
373
+ indicator.innerHTML = '<span></span><span></span><span></span>';
374
+ messagesBox.appendChild(indicator);
375
+ messagesBox.scrollTop = messagesBox.scrollHeight;
376
  } else {
377
+ userInput.disabled = false;
378
+ sendBtn.disabled = false;
379
+ const indicator = document.getElementById('typingIndicator');
380
+ if (indicator) indicator.remove();
381
+ userInput.focus();
382
  }
383
  }
384
  </script>
 
386
  </html>
387
  """
388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  @app.get("/", response_class=HTMLResponse)
390
  async def chat_interface():
391
  return HTML_CHAT_INTERFACE
 
393
  @app.post("/api/chat")
394
  async def chat_endpoint(message: str = Form(...), image: UploadFile = File(None)):
395
  saved_image_path = None
396
+
397
  if image:
398
  try:
399
  saved_image_path = os.path.join(UPLOAD_DIR, f"{int(time.time())}_{image.filename}")
400
  with open(saved_image_path, "wb") as buffer:
401
  buffer.write(await image.read())
402
  except Exception as e:
403
+ raise HTTPException(status_code=500, detail=f"خطا در ذخیره‌سازی تصویر: {str(e)}")
404
 
405
+ url = "https://www.google.com/search?q=سلام&udm=50"
406
+ ai_response = ""
407
+
408
+ async with async_playwright() as p:
409
+ try:
410
  browser = await p.chromium.launch(
411
  headless=True,
412
  args=[
413
  "--no-sandbox",
414
  "--disable-setuid-sandbox",
415
  "--disable-dev-shm-usage",
416
+ "--disable-blink-features=AutomationControlled"
 
417
  ]
418
  )
419
 
420
  context = await browser.new_context(
421
+ user_agent="Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
422
  viewport={"width": 412, "height": 915},
423
+ locale="fa-IR",
424
+ timezone_id="Asia/Tehran"
 
 
 
 
425
  )
426
+
427
  page = await context.new_page()
428
+ await page.goto(url, wait_until="networkidle", timeout=45000)
429
+ await page.wait_for_timeout(3000)
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(3000)
441
+ uploaded = True
442
+ print("Direct file input injection succeeded.")
443
+ except Exception as e:
444
+ print(f"Direct injection bypassed: {e}")
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
+ if await el.is_visible():
465
+ aria = (await el.get_attribute("aria-label") or "").lower()
466
+ html = (await el.inner_html() or "").lower()
467
+ text = (await el.inner_text() or "").lower()
468
+ combined = aria + html + text
469
+
470
+ if any(k in combined for k in ["+", "plus", "camera", "lens", "دوربین", "تصویر", "عکس"]):
471
+ async with page.expect_file_chooser(timeout=3000) as fc_info:
472
+ await el.click(timeout=2000) # تایم‌اوت امن ۲ ثانیه‌ای به جای ۳۰ ثانیه معطلی
473
+ file_chooser = await fc_info.value
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"Bottom sheet workflow failed: {e}")
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
+ if await el.is_visible():
512
+ placeholder = await el.get_attribute("placeholder") or ""
513
+ if any(w in placeholder for w in ["بپرسید", "ask", "پیام", "چطور", "هرچه"]):
514
+ input_box = el
515
+ break
516
+ except:
517
+ continue
518
+
519
+ if not input_box:
520
+ input_box = await page.query_selector("textarea") or await page.query_selector("input[type='text']")
521
+
522
+ if input_box:
523
+ await input_box.focus()
524
+ await input_box.fill(message)
525
+ await page.wait_for_timeout(500)
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(9000)
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
+ skip_headers = ["تصاویر", "ویدیوها", "اخبار", "نقشه‌ها", "خرید کردن", "کتاب‌ها", "مالی", "حالت موضوع‌محور", "ورود"]
550
+
551
+ for line in lines:
552
+ line_str = line.strip()
553
+ if not line_str:
554
+ continue
555
+ if any(header in line_str for header in skip_headers):
556
+ continue
557
+ if "در پاسخ‌های" in line_str or "بیشتر بدانید" in line_str:
558
+ continue
559
+ cleaned_lines.append(line_str)
560
+
561
+ ai_response = "\n".join(cleaned_lines)
562
+ if not ai_response:
563
+ ai_response = full_text
564
+
565
+ except Exception as e:
566
+ if 'browser' in locals():
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)
574
+ except:
575
+ pass
576
+
577
+ return JSONResponse(content={"response": ai_response})
578
 
579
  if __name__ == "__main__":
580
  uvicorn.run(app, host="0.0.0.0", port=7860)