diamond-in commited on
Commit
2ea0e96
·
verified ·
1 Parent(s): 0d14f3e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +8 -1912
app.py CHANGED
@@ -1,1922 +1,18 @@
 
 
 
1
  import gradio as gr
2
- from selenium import webdriver
3
- from selenium.webdriver.chrome.service import Service
4
- from selenium.webdriver.chrome.options import Options
5
- from selenium.webdriver.common.by import By
6
- from selenium.webdriver.support.ui import WebDriverWait
7
- from selenium.webdriver.support import expected_conditions as EC
8
- from selenium.webdriver.common.keys import Keys
9
- from selenium.webdriver.common.action_chains import ActionChains
10
- from webdriver_manager.chrome import ChromeDriverManager
11
- from webdriver_manager.core.os_manager import ChromeType
12
- import time
13
  import logging
14
- import os
15
- import json
16
- import base64
17
- from typing import Dict, List, Any, Tuple
18
- import hashlib
19
- from datetime import datetime
20
- import re
21
- import requests
22
 
23
  # Set up logging
24
  logging.basicConfig(level=logging.INFO)
25
  logger = logging.getLogger(__name__)
26
 
27
- # Global browser session (optional - for persistent sessions)
28
- _driver = None
29
-
30
- # Request history storage
31
- _request_history = []
32
-
33
- # Chat history storage
34
- _chat_history = []
35
-
36
- # Get Gemini API key from secrets
37
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
38
-
39
- def chat_with_gemini(message: str, history: List[List[str]]) -> Tuple[str, List[List[str]]]:
40
- """Chat with Gemini API"""
41
- global _chat_history
42
-
43
- if not GEMINI_API_KEY:
44
- return "Please set your GEMINI_API_KEY in the Hugging Face secrets settings.", history
45
-
46
- try:
47
- # Gemini API endpoint
48
- url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_API_KEY}"
49
-
50
- # Build conversation history
51
- contents = []
52
-
53
- # Add previous messages from history
54
- for user_msg, assistant_msg in history:
55
- if user_msg:
56
- contents.append({
57
- "role": "user",
58
- "parts": [{"text": user_msg}]
59
- })
60
- if assistant_msg:
61
- contents.append({
62
- "role": "model",
63
- "parts": [{"text": assistant_msg}]
64
- })
65
-
66
- # Add current message
67
- contents.append({
68
- "role": "user",
69
- "parts": [{"text": message}]
70
- })
71
-
72
- # Prepare request
73
- data = {
74
- "contents": contents,
75
- "generationConfig": {
76
- "temperature": 0.7,
77
- "topK": 40,
78
- "topP": 0.95,
79
- "maxOutputTokens": 1024,
80
- },
81
- "safetySettings": [
82
- {
83
- "category": "HARM_CATEGORY_HARASSMENT",
84
- "threshold": "BLOCK_MEDIUM_AND_ABOVE"
85
- },
86
- {
87
- "category": "HARM_CATEGORY_HATE_SPEECH",
88
- "threshold": "BLOCK_MEDIUM_AND_ABOVE"
89
- },
90
- {
91
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
92
- "threshold": "BLOCK_MEDIUM_AND_ABOVE"
93
- },
94
- {
95
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
96
- "threshold": "BLOCK_MEDIUM_AND_ABOVE"
97
- }
98
- ]
99
- }
100
-
101
- # Make request
102
- headers = {"Content-Type": "application/json"}
103
- response = requests.post(url, headers=headers, json=data)
104
- response.raise_for_status()
105
-
106
- # Parse response
107
- result = response.json()
108
-
109
- # Extract text from response
110
- if "candidates" in result and len(result["candidates"]) > 0:
111
- candidate = result["candidates"][0]
112
- if "content" in candidate and "parts" in candidate["content"]:
113
- parts = candidate["content"]["parts"]
114
- if len(parts) > 0 and "text" in parts[0]:
115
- bot_response = parts[0]["text"]
116
-
117
- # Update history
118
- history.append([message, bot_response])
119
- _chat_history = history
120
-
121
- return bot_response, history
122
-
123
- return "I couldn't generate a response. Please try again.", history
124
-
125
- except requests.exceptions.HTTPError as e:
126
- if e.response.status_code == 403:
127
- return "API key is invalid or doesn't have access to Gemini. Please check your GEMINI_API_KEY.", history
128
- else:
129
- return f"API Error: {e.response.status_code} - {e.response.text}", history
130
- except Exception as e:
131
- return f"Error: {str(e)}", history
132
-
133
- def clear_chat():
134
- """Clear chat history"""
135
- global _chat_history
136
- _chat_history = []
137
- return None, []
138
-
139
- def _make_driver(persistent=False):
140
- """Create Chrome driver with proper configuration"""
141
- global _driver
142
-
143
- # If persistent and driver exists, return it
144
- if persistent and _driver:
145
- try:
146
- # Check if driver is still alive
147
- _driver.title
148
- return _driver
149
- except:
150
- # Driver is dead, create new one
151
- _driver = None
152
-
153
- try:
154
- chrome_options = Options()
155
-
156
- # Detect Chromium binary location
157
- chromium_paths = [
158
- "/usr/bin/chromium",
159
- "/usr/bin/chromium-browser",
160
- "/usr/bin/google-chrome",
161
- "/usr/bin/google-chrome-stable"
162
- ]
163
-
164
- chromium_binary = None
165
- for path in chromium_paths:
166
- if os.path.exists(path):
167
- chromium_binary = path
168
- logger.info(f"Found Chromium at: {path}")
169
- break
170
-
171
- if chromium_binary:
172
- chrome_options.binary_location = chromium_binary
173
-
174
- # Essential headless options for Hugging Face Spaces
175
- chrome_options.add_argument("--headless")
176
- chrome_options.add_argument("--no-sandbox")
177
- chrome_options.add_argument("--disable-dev-shm-usage")
178
- chrome_options.add_argument("--disable-gpu")
179
- chrome_options.add_argument("--disable-web-security")
180
- chrome_options.add_argument("--disable-features=VizDisplayCompositor")
181
- chrome_options.add_argument("--disable-setuid-sandbox")
182
-
183
- # Performance options
184
- chrome_options.add_argument("--memory-pressure-off")
185
- chrome_options.add_argument("--max_old_space_size=4096")
186
- chrome_options.add_argument("--disable-background-timer-throttling")
187
- chrome_options.add_argument("--disable-renderer-backgrounding")
188
- chrome_options.add_argument("--disable-features=TranslateUI")
189
- chrome_options.add_argument("--disable-ipc-flooding-protection")
190
-
191
- # Window size
192
- chrome_options.add_argument("--window-size=1920,1080")
193
-
194
- # Enable automation
195
- chrome_options.add_argument("--enable-automation")
196
-
197
- # User agent to appear more like a real browser
198
- chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
199
-
200
- # Enable Chrome DevTools Protocol
201
- chrome_options.add_argument("--enable-logging")
202
- chrome_options.add_argument("--v=1")
203
-
204
- # Try multiple driver installation methods
205
- try:
206
- # Method 1: Use system chromium-driver if available
207
- if os.path.exists("/usr/bin/chromedriver"):
208
- service = Service("/usr/bin/chromedriver")
209
- logger.info("Using system chromedriver")
210
- else:
211
- # Method 2: Use webdriver-manager with ChromeType.CHROMIUM
212
- service = Service(ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install())
213
- logger.info("Using webdriver-manager chromedriver")
214
- except Exception as e:
215
- logger.warning(f"ChromeType.CHROMIUM failed: {e}")
216
- # Method 3: Fallback to regular Chrome driver
217
- service = Service(ChromeDriverManager().install())
218
- logger.info("Using fallback Chrome driver")
219
-
220
- driver = webdriver.Chrome(service=service, options=chrome_options)
221
- driver.set_page_load_timeout(30)
222
- driver.implicitly_wait(10)
223
-
224
- if persistent:
225
- _driver = driver
226
-
227
- return driver
228
-
229
- except Exception as e:
230
- logger.error(f"Failed to create driver: {e}")
231
- raise
232
-
233
- def close_persistent_driver():
234
- """Close the persistent driver if it exists"""
235
- global _driver
236
- if _driver:
237
- try:
238
- _driver.quit()
239
- except:
240
- pass
241
- _driver = None
242
- return "Persistent browser closed"
243
- return "No persistent browser to close"
244
-
245
- def browse_and_extract(url: str, selector: str = "body", use_persistent: bool = False) -> str:
246
- """Browse URL and extract text from selector"""
247
- driver = None
248
- try:
249
- driver = _make_driver(persistent=use_persistent)
250
- logger.info(f"Navigating to: {url}")
251
- driver.get(url)
252
-
253
- # Wait for element to be present
254
- wait = WebDriverWait(driver, 10)
255
- elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
256
-
257
- text = elem.text
258
- logger.info(f"Extracted {len(text)} characters")
259
-
260
- if not use_persistent and driver:
261
- driver.quit()
262
-
263
- return text
264
- except Exception as e:
265
- logger.error(f"Error in browse_and_extract: {e}")
266
- if not use_persistent and driver:
267
- try:
268
- driver.quit()
269
- except:
270
- pass
271
- return f"Error: {e}"
272
-
273
- def screenshot(url: str, full_page: bool = False, use_persistent: bool = False) -> str:
274
- """Take screenshot of URL"""
275
- driver = None
276
- try:
277
- driver = _make_driver(persistent=use_persistent)
278
- logger.info(f"Taking screenshot of: {url}")
279
- driver.get(url)
280
-
281
- # Wait for page load
282
- time.sleep(2)
283
-
284
- path = "/tmp/screenshot.png"
285
-
286
- if full_page:
287
- # Get full page dimensions
288
- total_height = driver.execute_script("return document.body.scrollHeight")
289
- driver.set_window_size(1920, total_height)
290
- time.sleep(1)
291
-
292
- driver.save_screenshot(path)
293
- logger.info(f"Screenshot saved to: {path}")
294
-
295
- if not use_persistent and driver:
296
- driver.quit()
297
-
298
- return path
299
- except Exception as e:
300
- logger.error(f"Error in screenshot: {e}")
301
- if not use_persistent and driver:
302
- try:
303
- driver.quit()
304
- except:
305
- pass
306
- return None
307
-
308
- def click(url: str, selector: str, use_persistent: bool = False) -> str:
309
- """Click element on page"""
310
- driver = None
311
- try:
312
- driver = _make_driver(persistent=use_persistent)
313
-
314
- # Navigate to URL if not persistent or new URL
315
- if not use_persistent or driver.current_url != url:
316
- logger.info(f"Navigating to: {url}")
317
- driver.get(url)
318
-
319
- wait = WebDriverWait(driver, 10)
320
- elem = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector)))
321
- elem.click()
322
-
323
- time.sleep(2)
324
- title = driver.title
325
- current_url = driver.current_url
326
-
327
- if not use_persistent and driver:
328
- driver.quit()
329
-
330
- return f"Clicked {selector}\nNew title: {title}\nCurrent URL: {current_url}"
331
- except Exception as e:
332
- logger.error(f"Error in click: {e}")
333
- if not use_persistent and driver:
334
- try:
335
- driver.quit()
336
- except:
337
- pass
338
- return f"Error: {e}"
339
-
340
- def fill(url: str, selector: str, text: str, use_persistent: bool = False) -> str:
341
- """Fill text into form field"""
342
- driver = None
343
- try:
344
- driver = _make_driver(persistent=use_persistent)
345
-
346
- # Navigate to URL if not persistent or new URL
347
- if not use_persistent or driver.current_url != url:
348
- logger.info(f"Navigating to: {url}")
349
- driver.get(url)
350
-
351
- wait = WebDriverWait(driver, 10)
352
- elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
353
- elem.clear()
354
- elem.send_keys(text)
355
-
356
- if not use_persistent and driver:
357
- driver.quit()
358
-
359
- return f"Filled {selector} with '{text}'"
360
- except Exception as e:
361
- logger.error(f"Error in fill: {e}")
362
- if not use_persistent and driver:
363
- try:
364
- driver.quit()
365
- except:
366
- pass
367
- return f"Error: {e}"
368
-
369
- def submit_form(url: str, form_data: str, submit_button: str = "", use_persistent: bool = False) -> str:
370
- """Fill and submit a form with multiple fields"""
371
- driver = None
372
- try:
373
- driver = _make_driver(persistent=use_persistent)
374
-
375
- # Navigate to URL if not persistent or new URL
376
- if not use_persistent or driver.current_url != url:
377
- logger.info(f"Navigating to: {url}")
378
- driver.get(url)
379
-
380
- # Parse form data (expected format: "selector1:value1;selector2:value2")
381
- wait = WebDriverWait(driver, 10)
382
- filled_fields = []
383
-
384
- for field in form_data.split(";"):
385
- if ":" in field:
386
- selector, value = field.split(":", 1)
387
- selector = selector.strip()
388
- value = value.strip()
389
-
390
- elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
391
- elem.clear()
392
- elem.send_keys(value)
393
- filled_fields.append(f"{selector}={value}")
394
-
395
- # Submit form
396
- if submit_button:
397
- submit_elem = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, submit_button)))
398
- submit_elem.click()
399
- else:
400
- # Try to find and submit the first form
401
- driver.execute_script("document.forms[0].submit();")
402
-
403
- time.sleep(2)
404
- new_url = driver.current_url
405
-
406
- if not use_persistent and driver:
407
- driver.quit()
408
-
409
- return f"Form submitted\nFilled fields: {', '.join(filled_fields)}\nNew URL: {new_url}"
410
- except Exception as e:
411
- logger.error(f"Error in submit_form: {e}")
412
- if not use_persistent and driver:
413
- try:
414
- driver.quit()
415
- except:
416
- pass
417
- return f"Error: {e}"
418
-
419
- def execute_js(url: str, script: str, use_persistent: bool = False) -> str:
420
- """Execute JavaScript on page"""
421
- driver = None
422
- try:
423
- driver = _make_driver(persistent=use_persistent)
424
-
425
- # Navigate to URL if not persistent or new URL
426
- if not use_persistent or driver.current_url != url:
427
- logger.info(f"Navigating to: {url}")
428
- driver.get(url)
429
-
430
- result = driver.execute_script(script)
431
-
432
- if not use_persistent and driver:
433
- driver.quit()
434
-
435
- return f"Script executed. Result: {result}"
436
- except Exception as e:
437
- logger.error(f"Error in execute_js: {e}")
438
- if not use_persistent and driver:
439
- try:
440
- driver.quit()
441
- except:
442
- pass
443
- return f"Error: {e}"
444
-
445
- def get_cookies(url: str, use_persistent: bool = False) -> str:
446
- """Get all cookies from current domain"""
447
- driver = None
448
- try:
449
- driver = _make_driver(persistent=use_persistent)
450
-
451
- if not use_persistent or driver.current_url == "data:,":
452
- logger.info(f"Navigating to: {url}")
453
- driver.get(url)
454
-
455
- cookies = driver.get_cookies()
456
-
457
- if not use_persistent and driver:
458
- driver.quit()
459
-
460
- return json.dumps(cookies, indent=2)
461
- except Exception as e:
462
- logger.error(f"Error in get_cookies: {e}")
463
- if not use_persistent and driver:
464
- try:
465
- driver.quit()
466
- except:
467
- pass
468
- return f"Error: {e}"
469
-
470
- def set_cookies(url: str, cookies_json: str, use_persistent: bool = False) -> str:
471
- """Set cookies from JSON string"""
472
- driver = None
473
- try:
474
- driver = _make_driver(persistent=use_persistent)
475
-
476
- # Navigate to domain first
477
- logger.info(f"Navigating to: {url}")
478
- driver.get(url)
479
-
480
- # Parse and add cookies
481
- cookies = json.loads(cookies_json)
482
- for cookie in cookies:
483
- # Remove expiry if present (can cause issues)
484
- if 'expiry' in cookie:
485
- del cookie['expiry']
486
- driver.add_cookie(cookie)
487
-
488
- # Refresh page to apply cookies
489
- driver.refresh()
490
-
491
- if not use_persistent and driver:
492
- driver.quit()
493
-
494
- return f"Set {len(cookies)} cookies successfully"
495
- except Exception as e:
496
- logger.error(f"Error in set_cookies: {e}")
497
- if not use_persistent and driver:
498
- try:
499
- driver.quit()
500
- except:
501
- pass
502
- return f"Error: {e}"
503
-
504
- def wait_for_element(url: str, selector: str, timeout: int = 10, use_persistent: bool = False) -> str:
505
- """Wait for element to appear on page"""
506
- driver = None
507
- try:
508
- driver = _make_driver(persistent=use_persistent)
509
-
510
- if not use_persistent or driver.current_url != url:
511
- logger.info(f"Navigating to: {url}")
512
- driver.get(url)
513
-
514
- wait = WebDriverWait(driver, timeout)
515
- elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
516
-
517
- text = elem.text[:100] + "..." if len(elem.text) > 100 else elem.text
518
-
519
- if not use_persistent and driver:
520
- driver.quit()
521
-
522
- return f"Element found: {selector}\nText preview: {text}"
523
- except Exception as e:
524
- logger.error(f"Error in wait_for_element: {e}")
525
- if not use_persistent and driver:
526
- try:
527
- driver.quit()
528
- except:
529
- pass
530
- return f"Error: Element not found within {timeout} seconds"
531
-
532
- def scroll_page(url: str, direction: str = "bottom", pixels: int = 0, use_persistent: bool = False) -> str:
533
- """Scroll page in specified direction"""
534
- driver = None
535
- try:
536
- driver = _make_driver(persistent=use_persistent)
537
-
538
- if not use_persistent or driver.current_url != url:
539
- logger.info(f"Navigating to: {url}")
540
- driver.get(url)
541
-
542
- if direction == "bottom":
543
- driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
544
- elif direction == "top":
545
- driver.execute_script("window.scrollTo(0, 0);")
546
- elif direction == "down":
547
- driver.execute_script(f"window.scrollBy(0, {pixels or 500});")
548
- elif direction == "up":
549
- driver.execute_script(f"window.scrollBy(0, -{pixels or 500});")
550
-
551
- time.sleep(1)
552
-
553
- # Get current scroll position
554
- scroll_pos = driver.execute_script("return window.pageYOffset;")
555
-
556
- if not use_persistent and driver:
557
- driver.quit()
558
-
559
- return f"Scrolled {direction}. Current position: {scroll_pos}px from top"
560
- except Exception as e:
561
- logger.error(f"Error in scroll_page: {e}")
562
- if not use_persistent and driver:
563
- try:
564
- driver.quit()
565
- except:
566
- pass
567
- return f"Error: {e}"
568
-
569
- def hover_element(url: str, selector: str, use_persistent: bool = False) -> str:
570
- """Hover over an element"""
571
- driver = None
572
- try:
573
- driver = _make_driver(persistent=use_persistent)
574
-
575
- if not use_persistent or driver.current_url != url:
576
- logger.info(f"Navigating to: {url}")
577
- driver.get(url)
578
-
579
- wait = WebDriverWait(driver, 10)
580
- elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
581
-
582
- actions = ActionChains(driver)
583
- actions.move_to_element(elem).perform()
584
-
585
- time.sleep(1)
586
-
587
- if not use_persistent and driver:
588
- driver.quit()
589
-
590
- return f"Hovered over element: {selector}"
591
- except Exception as e:
592
- logger.error(f"Error in hover_element: {e}")
593
- if not use_persistent and driver:
594
- try:
595
- driver.quit()
596
- except:
597
- pass
598
- return f"Error: {e}"
599
-
600
- def press_key(url: str, key: str, selector: str = "body", use_persistent: bool = False) -> str:
601
- """Press a keyboard key on element"""
602
- driver = None
603
- try:
604
- driver = _make_driver(persistent=use_persistent)
605
-
606
- if not use_persistent or driver.current_url != url:
607
- logger.info(f"Navigating to: {url}")
608
- driver.get(url)
609
-
610
- wait = WebDriverWait(driver, 10)
611
- elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
612
-
613
- # Map common key names to Selenium keys
614
- key_map = {
615
- "enter": Keys.ENTER,
616
- "tab": Keys.TAB,
617
- "escape": Keys.ESCAPE,
618
- "backspace": Keys.BACKSPACE,
619
- "delete": Keys.DELETE,
620
- "space": Keys.SPACE,
621
- "up": Keys.ARROW_UP,
622
- "down": Keys.ARROW_DOWN,
623
- "left": Keys.ARROW_LEFT,
624
- "right": Keys.ARROW_RIGHT,
625
- "home": Keys.HOME,
626
- "end": Keys.END,
627
- "pageup": Keys.PAGE_UP,
628
- "pagedown": Keys.PAGE_DOWN,
629
- }
630
-
631
- selenium_key = key_map.get(key.lower(), key)
632
- elem.send_keys(selenium_key)
633
-
634
- if not use_persistent and driver:
635
- driver.quit()
636
-
637
- return f"Pressed key '{key}' on {selector}"
638
- except Exception as e:
639
- logger.error(f"Error in press_key: {e}")
640
- if not use_persistent and driver:
641
- try:
642
- driver.quit()
643
- except:
644
- pass
645
- return f"Error: {e}"
646
-
647
- def get_page_info(url: str, use_persistent: bool = False) -> str:
648
- """Get comprehensive page information"""
649
- driver = None
650
- try:
651
- driver = _make_driver(persistent=use_persistent)
652
-
653
- if not use_persistent or driver.current_url != url:
654
- logger.info(f"Navigating to: {url}")
655
- driver.get(url)
656
-
657
- info = {
658
- "title": driver.title,
659
- "url": driver.current_url,
660
- "page_source_length": len(driver.page_source),
661
- "cookies_count": len(driver.get_cookies()),
662
- "viewport": driver.execute_script("return {width: window.innerWidth, height: window.innerHeight};"),
663
- "scroll_height": driver.execute_script("return document.body.scrollHeight;"),
664
- "ready_state": driver.execute_script("return document.readyState;"),
665
- "links_count": len(driver.find_elements(By.TAG_NAME, "a")),
666
- "images_count": len(driver.find_elements(By.TAG_NAME, "img")),
667
- "forms_count": len(driver.find_elements(By.TAG_NAME, "form"))
668
- }
669
-
670
- if not use_persistent and driver:
671
- driver.quit()
672
-
673
- return json.dumps(info, indent=2)
674
- except Exception as e:
675
- logger.error(f"Error in get_page_info: {e}")
676
- if not use_persistent and driver:
677
- try:
678
- driver.quit()
679
- except:
680
- pass
681
- return f"Error: {e}"
682
-
683
- def get_html_source(url: str, use_persistent: bool = False) -> str:
684
- """Get full HTML source code of the page"""
685
- driver = None
686
- try:
687
- driver = _make_driver(persistent=use_persistent)
688
-
689
- if not use_persistent or driver.current_url != url:
690
- logger.info(f"Navigating to: {url}")
691
- driver.get(url)
692
-
693
- # Wait for page to fully load
694
- WebDriverWait(driver, 10).until(
695
- lambda d: d.execute_script("return document.readyState") == "complete"
696
- )
697
-
698
- # Get the full HTML
699
- html = driver.page_source
700
-
701
- if not use_persistent and driver:
702
- driver.quit()
703
-
704
- return html
705
- except Exception as e:
706
- logger.error(f"Error in get_html_source: {e}")
707
- if not use_persistent and driver:
708
- try:
709
- driver.quit()
710
- except:
711
- pass
712
- return f"Error: {e}"
713
-
714
- def make_http_request(url: str, method: str = "GET", headers: str = "", data: str = "", use_persistent: bool = False) -> str:
715
- """Make HTTP request with custom method, headers, and data (like Postman)"""
716
- global _request_history
717
- driver = None
718
- try:
719
- driver = _make_driver(persistent=use_persistent)
720
-
721
- # Parse headers if provided
722
- headers_dict = {}
723
- if headers:
724
- try:
725
- headers_dict = json.loads(headers)
726
- except:
727
- # Try to parse as key:value lines
728
- for line in headers.strip().split('\n'):
729
- if ':' in line:
730
- key, value = line.split(':', 1)
731
- headers_dict[key.strip()] = value.strip()
732
-
733
- # Build the JavaScript to make the request
734
- js_code = f"""
735
- async function makeRequest() {{
736
- const options = {{
737
- method: '{method}',
738
- headers: {json.dumps(headers_dict)},
739
- }};
740
-
741
- if (['{method}'].includes('POST') || ['{method}'].includes('PUT') || ['{method}'].includes('PATCH')) {{
742
- options.body = {json.dumps(data) if data else '""'};
743
- }}
744
-
745
- try {{
746
- const startTime = performance.now();
747
- const response = await fetch('{url}', options);
748
- const endTime = performance.now();
749
-
750
- const responseHeaders = {{}};
751
- response.headers.forEach((value, key) => {{
752
- responseHeaders[key] = value;
753
- }});
754
-
755
- let responseBody;
756
- const contentType = response.headers.get('content-type');
757
- if (contentType && contentType.includes('application/json')) {{
758
- responseBody = await response.json();
759
- }} else {{
760
- responseBody = await response.text();
761
- }}
762
-
763
- return {{
764
- status: response.status,
765
- statusText: response.statusText,
766
- headers: responseHeaders,
767
- body: responseBody,
768
- url: response.url,
769
- type: response.type,
770
- redirected: response.redirected,
771
- responseTime: Math.round(endTime - startTime)
772
- }};
773
- }} catch (error) {{
774
- return {{
775
- error: error.message,
776
- type: 'NetworkError'
777
- }};
778
- }}
779
- }}
780
-
781
- return makeRequest();
782
- """
783
-
784
- # Navigate to a blank page first to avoid CORS issues
785
- driver.get("about:blank")
786
-
787
- # Execute the request
788
- result = driver.execute_script(js_code)
789
-
790
- # If result is a promise, wait for it
791
- if isinstance(result, dict) and 'then' in str(type(result)):
792
- result = driver.execute_async_script("""
793
- const callback = arguments[arguments.length - 1];
794
- arguments[0].then(callback).catch(e => callback({error: e.toString()}));
795
- """, result)
796
-
797
- # Save to history
798
- _request_history.append({
799
- "timestamp": datetime.now().isoformat(),
800
- "method": method,
801
- "url": url,
802
- "headers": headers_dict,
803
- "data": data,
804
- "response": result
805
- })
806
-
807
- # Keep only last 50 requests
808
- if len(_request_history) > 50:
809
- _request_history = _request_history[-50:]
810
-
811
- if not use_persistent and driver:
812
- driver.quit()
813
-
814
- return json.dumps(result, indent=2, default=str)
815
- except Exception as e:
816
- logger.error(f"Error in make_http_request: {e}")
817
- if not use_persistent and driver:
818
- try:
819
- driver.quit()
820
- except:
821
- pass
822
- return f"Error: {e}"
823
-
824
- def save_html_to_file(url: str, filename: str = "", use_persistent: bool = False) -> str:
825
- """Save HTML source to file"""
826
- driver = None
827
- try:
828
- driver = _make_driver(persistent=use_persistent)
829
-
830
- if not use_persistent or driver.current_url != url:
831
- logger.info(f"Navigating to: {url}")
832
- driver.get(url)
833
-
834
- # Wait for page to fully load
835
- WebDriverWait(driver, 10).until(
836
- lambda d: d.execute_script("return document.readyState") == "complete"
837
- )
838
-
839
- # Get the full HTML
840
- html = driver.page_source
841
-
842
- # Generate filename if not provided
843
- if not filename:
844
- safe_url = re.sub(r'[^\w\s-]', '', url.replace('https://', '').replace('http://', ''))[:50]
845
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
846
- filename = f"{safe_url}_{timestamp}.html"
847
-
848
- filepath = f"/tmp/{filename}"
849
- with open(filepath, 'w', encoding='utf-8') as f:
850
- f.write(html)
851
-
852
- if not use_persistent and driver:
853
- driver.quit()
854
-
855
- return filepath
856
- except Exception as e:
857
- logger.error(f"Error in save_html_to_file: {e}")
858
- if not use_persistent and driver:
859
- try:
860
- driver.quit()
861
- except:
862
- pass
863
- return f"Error: {e}"
864
-
865
- def export_as_curl(method: str, url: str, headers: str = "", data: str = "") -> str:
866
- """Convert HTTP request to cURL command"""
867
- try:
868
- curl_cmd = f"curl -X {method}"
869
-
870
- # Add headers
871
- if headers:
872
- headers_dict = {}
873
- try:
874
- headers_dict = json.loads(headers)
875
- except:
876
- for line in headers.strip().split('\n'):
877
- if ':' in line:
878
- key, value = line.split(':', 1)
879
- headers_dict[key.strip()] = value.strip()
880
-
881
- for key, value in headers_dict.items():
882
- curl_cmd += f' -H "{key}: {value}"'
883
-
884
- # Add data
885
- if data and method in ['POST', 'PUT', 'PATCH']:
886
- curl_cmd += f" -d '{data}'"
887
-
888
- # Add URL
889
- curl_cmd += f' "{url}"'
890
-
891
- return curl_cmd
892
- except Exception as e:
893
- return f"Error generating cURL command: {e}"
894
-
895
- def get_request_history() -> str:
896
- """Get history of HTTP requests"""
897
- if not _request_history:
898
- return "No requests in history"
899
-
900
- return json.dumps(_request_history, indent=2, default=str)
901
-
902
- def monitor_network_requests(url: str, duration: int = 5, use_persistent: bool = False) -> str:
903
- """Monitor network requests made by a page"""
904
- driver = None
905
- try:
906
- driver = _make_driver(persistent=use_persistent)
907
-
908
- # Inject JavaScript to monitor network
909
- monitor_script = """
910
- window.networkRequests = [];
911
- const originalFetch = window.fetch;
912
- const originalXHR = window.XMLHttpRequest.prototype.open;
913
-
914
- // Monitor fetch requests
915
- window.fetch = function(...args) {
916
- const request = {
917
- method: args[1]?.method || 'GET',
918
- url: args[0],
919
- timestamp: new Date().toISOString(),
920
- type: 'fetch'
921
- };
922
- window.networkRequests.push(request);
923
- return originalFetch.apply(this, args);
924
- };
925
-
926
- // Monitor XHR requests
927
- window.XMLHttpRequest.prototype.open = function(method, url) {
928
- const request = {
929
- method: method,
930
- url: url,
931
- timestamp: new Date().toISOString(),
932
- type: 'xhr'
933
- };
934
- window.networkRequests.push(request);
935
- return originalXHR.apply(this, arguments);
936
- };
937
- """
938
-
939
- # Navigate to page and inject monitoring
940
- driver.get(url)
941
- driver.execute_script(monitor_script)
942
-
943
- # Wait and collect requests
944
- time.sleep(duration)
945
-
946
- # Get collected requests
947
- requests = driver.execute_script("return window.networkRequests;")
948
-
949
- # Also get performance entries
950
- performance_entries = driver.execute_script("""
951
- return performance.getEntriesByType('resource').map(entry => ({
952
- name: entry.name,
953
- type: entry.initiatorType,
954
- duration: entry.duration,
955
- size: entry.transferSize,
956
- startTime: entry.startTime
957
- }));
958
- """)
959
-
960
- result = {
961
- "intercepted_requests": requests,
962
- "performance_entries": performance_entries,
963
- "total_requests": len(requests) + len(performance_entries)
964
- }
965
-
966
- if not use_persistent and driver:
967
- driver.quit()
968
-
969
- return json.dumps(result, indent=2, default=str)
970
- except Exception as e:
971
- logger.error(f"Error in monitor_network_requests: {e}")
972
- if not use_persistent and driver:
973
- try:
974
- driver.quit()
975
- except:
976
- pass
977
- return f"Error: {e}"
978
-
979
- def extract_structured_data(url: str, use_persistent: bool = False) -> str:
980
- """Extract structured data (JSON-LD, microdata, meta tags) from page"""
981
- driver = None
982
- try:
983
- driver = _make_driver(persistent=use_persistent)
984
-
985
- if not use_persistent or driver.current_url != url:
986
- logger.info(f"Navigating to: {url}")
987
- driver.get(url)
988
-
989
- # Extract various types of structured data
990
- structured_data = driver.execute_script("""
991
- const data = {
992
- jsonld: [],
993
- meta: {},
994
- opengraph: {},
995
- twitter: {},
996
- microdata: []
997
- };
998
-
999
- // Extract JSON-LD
1000
- document.querySelectorAll('script[type="application/ld+json"]').forEach(script => {
1001
- try {
1002
- data.jsonld.push(JSON.parse(script.textContent));
1003
- } catch(e) {}
1004
- });
1005
-
1006
- // Extract meta tags
1007
- document.querySelectorAll('meta').forEach(meta => {
1008
- const name = meta.getAttribute('name') || meta.getAttribute('property');
1009
- const content = meta.getAttribute('content');
1010
- if (name && content) {
1011
- if (name.startsWith('og:')) {
1012
- data.opengraph[name] = content;
1013
- } else if (name.startsWith('twitter:')) {
1014
- data.twitter[name] = content;
1015
- } else {
1016
- data.meta[name] = content;
1017
- }
1018
- }
1019
- });
1020
-
1021
- // Extract microdata
1022
- document.querySelectorAll('[itemscope]').forEach(item => {
1023
- const itemData = {
1024
- type: item.getAttribute('itemtype'),
1025
- properties: {}
1026
- };
1027
- item.querySelectorAll('[itemprop]').forEach(prop => {
1028
- const propName = prop.getAttribute('itemprop');
1029
- const propValue = prop.getAttribute('content') || prop.textContent.trim();
1030
- itemData.properties[propName] = propValue;
1031
- });
1032
- data.microdata.push(itemData);
1033
- });
1034
-
1035
- return data;
1036
- """)
1037
-
1038
- if not use_persistent and driver:
1039
- driver.quit()
1040
-
1041
- return json.dumps(structured_data, indent=2)
1042
- except Exception as e:
1043
- logger.error(f"Error in extract_structured_data: {e}")
1044
- if not use_persistent and driver:
1045
- try:
1046
- driver.quit()
1047
- except:
1048
- pass
1049
- return f"Error: {e}"
1050
-
1051
- def visual_regression_test(url1: str, url2: str, threshold: float = 0.98) -> str:
1052
- """Compare two URLs visually for differences"""
1053
- driver = None
1054
- try:
1055
- driver = _make_driver(persistent=False)
1056
-
1057
- # Take screenshot of first URL
1058
- driver.get(url1)
1059
- time.sleep(2)
1060
- screenshot1_path = "/tmp/screenshot1.png"
1061
- driver.save_screenshot(screenshot1_path)
1062
-
1063
- # Take screenshot of second URL
1064
- driver.get(url2)
1065
- time.sleep(2)
1066
- screenshot2_path = "/tmp/screenshot2.png"
1067
- driver.save_screenshot(screenshot2_path)
1068
-
1069
- # Compare using JavaScript in browser
1070
- comparison_result = driver.execute_script("""
1071
- async function compareImages() {
1072
- const loadImage = (src) => new Promise((resolve, reject) => {
1073
- const img = new Image();
1074
- img.onload = () => resolve(img);
1075
- img.onerror = reject;
1076
- img.src = src;
1077
- });
1078
-
1079
- const canvas1 = document.createElement('canvas');
1080
- const canvas2 = document.createElement('canvas');
1081
- const ctx1 = canvas1.getContext('2d');
1082
- const ctx2 = canvas2.getContext('2d');
1083
-
1084
- // For demonstration, we'll return a similarity score
1085
- // In real implementation, you'd load and compare actual images
1086
- return {
1087
- similarity: Math.random() * 0.2 + 0.8, // 80-100% similarity
1088
- differences: Math.floor(Math.random() * 100),
1089
- message: "Visual comparison completed"
1090
- };
1091
- }
1092
-
1093
- return compareImages();
1094
- """)
1095
-
1096
- driver.quit()
1097
-
1098
- result = {
1099
- "url1": url1,
1100
- "url2": url2,
1101
- "comparison": comparison_result,
1102
- "screenshots": {
1103
- "screenshot1": screenshot1_path,
1104
- "screenshot2": screenshot2_path
1105
- },
1106
- "threshold": threshold,
1107
- "passed": comparison_result.get("similarity", 0) >= threshold
1108
- }
1109
-
1110
- return json.dumps(result, indent=2)
1111
- except Exception as e:
1112
- logger.error(f"Error in visual_regression_test: {e}")
1113
- if driver:
1114
- try:
1115
- driver.quit()
1116
- except:
1117
- pass
1118
- return f"Error: {e}"
1119
-
1120
- def accessibility_audit(url: str, use_persistent: bool = False) -> str:
1121
- """Perform basic accessibility audit on a page"""
1122
- driver = None
1123
- try:
1124
- driver = _make_driver(persistent=use_persistent)
1125
-
1126
- if not use_persistent or driver.current_url != url:
1127
- logger.info(f"Navigating to: {url}")
1128
- driver.get(url)
1129
-
1130
- # Run accessibility checks
1131
- audit_results = driver.execute_script("""
1132
- const results = {
1133
- images_without_alt: [],
1134
- form_inputs_without_labels: [],
1135
- low_contrast_elements: [],
1136
- missing_landmarks: [],
1137
- heading_structure: [],
1138
- interactive_elements_without_text: []
1139
- };
1140
-
1141
- // Check images without alt text
1142
- document.querySelectorAll('img:not([alt])').forEach(img => {
1143
- results.images_without_alt.push(img.src || 'no-src');
1144
- });
1145
-
1146
- // Check form inputs without labels
1147
- document.querySelectorAll('input, select, textarea').forEach(input => {
1148
- const id = input.id;
1149
- if (id) {
1150
- const label = document.querySelector(`label[for="${id}"]`);
1151
- if (!label) {
1152
- results.form_inputs_without_labels.push({
1153
- type: input.type,
1154
- name: input.name,
1155
- id: id
1156
- });
1157
- }
1158
- }
1159
- });
1160
-
1161
- // Check heading structure
1162
- const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
1163
- let lastLevel = 0;
1164
- headings.forEach(heading => {
1165
- const level = parseInt(heading.tagName.substring(1));
1166
- results.heading_structure.push({
1167
- level: level,
1168
- text: heading.textContent.trim().substring(0, 50),
1169
- issue: level > lastLevel + 1 ? 'skipped_level' : null
1170
- });
1171
- lastLevel = level;
1172
- });
1173
-
1174
- // Check for landmarks
1175
- const landmarks = {
1176
- header: document.querySelector('header'),
1177
- nav: document.querySelector('nav'),
1178
- main: document.querySelector('main'),
1179
- footer: document.querySelector('footer')
1180
- };
1181
-
1182
- for (const [role, element] of Object.entries(landmarks)) {
1183
- if (!element) {
1184
- results.missing_landmarks.push(role);
1185
- }
1186
- }
1187
-
1188
- // Check interactive elements without text
1189
- document.querySelectorAll('button, a').forEach(elem => {
1190
- const text = elem.textContent.trim();
1191
- const ariaLabel = elem.getAttribute('aria-label');
1192
- if (!text && !ariaLabel) {
1193
- results.interactive_elements_without_text.push({
1194
- tag: elem.tagName,
1195
- class: elem.className
1196
- });
1197
- }
1198
- });
1199
-
1200
- return results;
1201
- """)
1202
-
1203
- # Calculate score
1204
- issues_count = sum(len(v) if isinstance(v, list) else 0 for v in audit_results.values())
1205
- score = max(0, 100 - (issues_count * 5)) # Deduct 5 points per issue
1206
-
1207
- result = {
1208
- "url": url,
1209
- "accessibility_score": score,
1210
- "issues": audit_results,
1211
- "total_issues": issues_count,
1212
- "timestamp": datetime.now().isoformat()
1213
- }
1214
-
1215
- if not use_persistent and driver:
1216
- driver.quit()
1217
-
1218
- return json.dumps(result, indent=2)
1219
- except Exception as e:
1220
- logger.error(f"Error in accessibility_audit: {e}")
1221
- if not use_persistent and driver:
1222
- try:
1223
- driver.quit()
1224
- except:
1225
- pass
1226
- return f"Error: {e}"
1227
-
1228
- def extract_all_links(url: str, include_external: bool = True, use_persistent: bool = False) -> str:
1229
- """Extract all links from a page with categorization"""
1230
- driver = None
1231
- try:
1232
- driver = _make_driver(persistent=use_persistent)
1233
-
1234
- if not use_persistent or driver.current_url != url:
1235
- logger.info(f"Navigating to: {url}")
1236
- driver.get(url)
1237
-
1238
- # Extract and categorize links
1239
- links_data = driver.execute_script(f"""
1240
- const currentDomain = new URL(window.location.href).hostname;
1241
- const links = {{
1242
- internal: [],
1243
- external: [],
1244
- email: [],
1245
- phone: [],
1246
- javascript: [],
1247
- anchor: []
1248
- }};
1249
-
1250
- document.querySelectorAll('a[href]').forEach(a => {{
1251
- const href = a.getAttribute('href');
1252
- const text = a.textContent.trim();
1253
- const linkData = {{
1254
- href: href,
1255
- text: text.substring(0, 100),
1256
- title: a.title
1257
- }};
1258
-
1259
- if (href.startsWith('mailto:')) {{
1260
- links.email.push(linkData);
1261
- }} else if (href.startsWith('tel:')) {{
1262
- links.phone.push(linkData);
1263
- }} else if (href.startsWith('javascript:')) {{
1264
- links.javascript.push(linkData);
1265
- }} else if (href.startsWith('#')) {{
1266
- links.anchor.push(linkData);
1267
- }} else {{
1268
- try {{
1269
- const linkUrl = new URL(href, window.location.href);
1270
- if (linkUrl.hostname === currentDomain) {{
1271
- links.internal.push({{...linkData, absoluteUrl: linkUrl.href}});
1272
- }} else if ({str(include_external).lower()}) {{
1273
- links.external.push({{...linkData, absoluteUrl: linkUrl.href}});
1274
- }}
1275
- }} catch(e) {{
1276
- // Invalid URL
1277
- }}
1278
- }}
1279
- }});
1280
-
1281
- return {{
1282
- links: links,
1283
- summary: {{
1284
- total: document.querySelectorAll('a[href]').length,
1285
- internal: links.internal.length,
1286
- external: links.external.length,
1287
- email: links.email.length,
1288
- phone: links.phone.length,
1289
- javascript: links.javascript.length,
1290
- anchor: links.anchor.length
1291
- }}
1292
- }};
1293
- """)
1294
-
1295
- if not use_persistent and driver:
1296
- driver.quit()
1297
-
1298
- return json.dumps(links_data, indent=2)
1299
- except Exception as e:
1300
- logger.error(f"Error in extract_all_links: {e}")
1301
- if not use_persistent and driver:
1302
- try:
1303
- driver.quit()
1304
- except:
1305
- pass
1306
- return f"Error: {e}"
1307
-
1308
- # Gradio Interface with Sidebar
1309
- with gr.Blocks(theme=gr.themes.Soft(), css="""
1310
- .gradio-container {
1311
- max-width: 100% !important;
1312
- }
1313
- .main-container {
1314
- display: flex;
1315
- gap: 20px;
1316
- }
1317
- .sidebar {
1318
- min-width: 350px;
1319
- max-width: 400px;
1320
- }
1321
- .main-content {
1322
- flex: 1;
1323
- }
1324
- .chat-container {
1325
- height: 600px;
1326
- }
1327
- """) as demo:
1328
- with gr.Row(elem_classes="main-container"):
1329
- # Main content area
1330
- with gr.Column(elem_classes="main-content"):
1331
- gr.Markdown("""
1332
- # 🌐 Ultimate MCP Browser (Selenium + Chromium)
1333
-
1334
- **Full-featured browser automation** with advanced features including accessibility audits, visual testing, and network monitoring!
1335
-
1336
- ✅ Core Features: Auto ChromeDriver | Persistent sessions | JavaScript | Cookies | Forms | HTTP Requests
1337
-
1338
- 🚀 Advanced Features: Accessibility Audit | Visual Testing | Network Monitor | Structured Data | Link Extraction
1339
- """)
1340
-
1341
- with gr.Tab("🔍 Extract Text"):
1342
- with gr.Row():
1343
- url_in = gr.Textbox(label="URL", value="https://example.com")
1344
- sel_in = gr.Textbox(label="CSS Selector", value="body")
1345
- persist_check = gr.Checkbox(label="Use Persistent Browser", value=False)
1346
- out = gr.Textbox(label="Extracted Text", lines=10)
1347
- gr.Button("Extract", variant="primary").click(browse_and_extract, [url_in, sel_in, persist_check], out)
1348
-
1349
- with gr.Tab("📸 Screenshot"):
1350
- with gr.Row():
1351
- url_in2 = gr.Textbox(label="URL", value="https://example.com")
1352
- full_page_check = gr.Checkbox(label="Full Page Screenshot", value=False)
1353
- persist_check2 = gr.Checkbox(label="Use Persistent Browser", value=False)
1354
- out_img = gr.Image(label="Screenshot")
1355
- gr.Button("Take Screenshot", variant="primary").click(screenshot, [url_in2, full_page_check, persist_check2], out_img)
1356
-
1357
- with gr.Tab("👆 Click"):
1358
- with gr.Row():
1359
- url_in3 = gr.Textbox(label="URL")
1360
- sel_in3 = gr.Textbox(label="CSS Selector", placeholder="button#submit")
1361
- persist_check3 = gr.Checkbox(label="Use Persistent Browser", value=False)
1362
- out3 = gr.Textbox(label="Result", lines=4)
1363
- gr.Button("Click", variant="primary").click(click, [url_in3, sel_in3, persist_check3], out3)
1364
-
1365
- with gr.Tab("✏️ Fill"):
1366
- with gr.Row():
1367
- url_in4 = gr.Textbox(label="URL")
1368
- sel_in4 = gr.Textbox(label="CSS Selector", placeholder="input#username")
1369
- txt_in4 = gr.Textbox(label="Text to Fill")
1370
- persist_check4 = gr.Checkbox(label="Use Persistent Browser", value=False)
1371
- out4 = gr.Textbox(label="Result", lines=2)
1372
- gr.Button("Fill", variant="primary").click(fill, [url_in4, sel_in4, txt_in4, persist_check4], out4)
1373
-
1374
- with gr.Tab("📝 Form Submit"):
1375
- with gr.Row():
1376
- url_in_form = gr.Textbox(label="URL")
1377
- persist_check_form = gr.Checkbox(label="Use Persistent Browser", value=False)
1378
- form_data_in = gr.Textbox(
1379
- label="Form Data",
1380
- placeholder="selector1:value1;selector2:value2",
1381
- lines=3,
1382
- info="Format: CSS_selector:value pairs separated by semicolons"
1383
- )
1384
- submit_btn_in = gr.Textbox(label="Submit Button Selector (optional)", placeholder="button[type='submit']")
1385
- out_form = gr.Textbox(label="Result", lines=4)
1386
- gr.Button("Submit Form", variant="primary").click(
1387
- submit_form,
1388
- [url_in_form, form_data_in, submit_btn_in, persist_check_form],
1389
- out_form
1390
- )
1391
-
1392
- with gr.Tab("💻 Execute JS"):
1393
- with gr.Row():
1394
- url_in5 = gr.Textbox(label="URL", value="https://example.com")
1395
- persist_check5 = gr.Checkbox(label="Use Persistent Browser", value=False)
1396
- script_in5 = gr.Textbox(
1397
- label="JavaScript Code",
1398
- lines=5,
1399
- value="return {title: document.title, url: document.URL, links: document.links.length};"
1400
- )
1401
- out5 = gr.Textbox(label="Result", lines=3)
1402
- gr.Button("Execute", variant="primary").click(execute_js, [url_in5, script_in5, persist_check5], out5)
1403
-
1404
- with gr.Tab("🍪 Cookies"):
1405
- with gr.Row():
1406
- with gr.Column():
1407
- gr.Markdown("### Get Cookies")
1408
- url_in_cookies = gr.Textbox(label="URL")
1409
- persist_check_cookies = gr.Checkbox(label="Use Persistent Browser", value=False)
1410
- cookies_out = gr.Textbox(label="Cookies (JSON)", lines=10)
1411
- gr.Button("Get Cookies").click(get_cookies, [url_in_cookies, persist_check_cookies], cookies_out)
1412
-
1413
- with gr.Column():
1414
- gr.Markdown("### Set Cookies")
1415
- url_in_set_cookies = gr.Textbox(label="URL")
1416
- cookies_in = gr.Textbox(label="Cookies JSON", lines=8, placeholder='[{"name": "cookie_name", "value": "cookie_value"}]')
1417
- persist_check_set_cookies = gr.Checkbox(label="Use Persistent Browser", value=False)
1418
- set_cookies_out = gr.Textbox(label="Result")
1419
- gr.Button("Set Cookies").click(set_cookies, [url_in_set_cookies, cookies_in, persist_check_set_cookies], set_cookies_out)
1420
-
1421
- with gr.Tab("⏳ Wait for Element"):
1422
- with gr.Row():
1423
- url_in_wait = gr.Textbox(label="URL")
1424
- sel_in_wait = gr.Textbox(label="CSS Selector", placeholder=".dynamic-content")
1425
- timeout_in = gr.Slider(minimum=1, maximum=60, value=10, label="Timeout (seconds)")
1426
- persist_check_wait = gr.Checkbox(label="Use Persistent Browser", value=False)
1427
- out_wait = gr.Textbox(label="Result", lines=3)
1428
- gr.Button("Wait for Element", variant="primary").click(
1429
- wait_for_element,
1430
- [url_in_wait, sel_in_wait, timeout_in, persist_check_wait],
1431
- out_wait
1432
- )
1433
-
1434
- with gr.Tab("📜 Scroll"):
1435
- with gr.Row():
1436
- url_in_scroll = gr.Textbox(label="URL")
1437
- direction_in = gr.Radio(["bottom", "top", "down", "up"], value="bottom", label="Direction")
1438
- pixels_in = gr.Number(label="Pixels (for up/down)", value=500)
1439
- persist_check_scroll = gr.Checkbox(label="Use Persistent Browser", value=False)
1440
- out_scroll = gr.Textbox(label="Result", lines=2)
1441
- gr.Button("Scroll", variant="primary").click(
1442
- scroll_page,
1443
- [url_in_scroll, direction_in, pixels_in, persist_check_scroll],
1444
- out_scroll
1445
- )
1446
-
1447
- with gr.Tab("🎯 Hover"):
1448
- with gr.Row():
1449
- url_in_hover = gr.Textbox(label="URL")
1450
- sel_in_hover = gr.Textbox(label="CSS Selector", placeholder=".menu-item")
1451
- persist_check_hover = gr.Checkbox(label="Use Persistent Browser", value=False)
1452
- out_hover = gr.Textbox(label="Result", lines=2)
1453
- gr.Button("Hover", variant="primary").click(hover_element, [url_in_hover, sel_in_hover, persist_check_hover], out_hover)
1454
-
1455
- with gr.Tab("⌨️ Press Key"):
1456
- with gr.Row():
1457
- url_in_key = gr.Textbox(label="URL")
1458
- key_in = gr.Textbox(label="Key", placeholder="enter, escape, tab, etc.")
1459
- sel_in_key = gr.Textbox(label="Target Element", value="body")
1460
- persist_check_key = gr.Checkbox(label="Use Persistent Browser", value=False)
1461
- out_key = gr.Textbox(label="Result", lines=2)
1462
- gr.Button("Press Key", variant="primary").click(
1463
- press_key,
1464
- [url_in_key, key_in, sel_in_key, persist_check_key],
1465
- out_key
1466
- )
1467
-
1468
- with gr.Tab("ℹ️ Page Info"):
1469
- with gr.Row():
1470
- url_in_info = gr.Textbox(label="URL")
1471
- persist_check_info = gr.Checkbox(label="Use Persistent Browser", value=False)
1472
- out_info = gr.Textbox(label="Page Information", lines=15)
1473
- gr.Button("Get Info", variant="primary").click(get_page_info, [url_in_info, persist_check_info], out_info)
1474
-
1475
- with gr.Tab("📄 HTML Source"):
1476
- with gr.Row():
1477
- url_in_html = gr.Textbox(label="URL", value="https://example.com")
1478
- persist_check_html = gr.Checkbox(label="Use Persistent Browser", value=False)
1479
- with gr.Row():
1480
- with gr.Column():
1481
- out_html = gr.Code(label="HTML Source Code", language="html", lines=20)
1482
- gr.Button("Get HTML", variant="primary").click(get_html_source, [url_in_html, persist_check_html], out_html)
1483
- with gr.Column():
1484
- filename_in = gr.Textbox(label="Filename (optional)", placeholder="auto-generated if empty")
1485
- save_out = gr.File(label="Saved HTML File")
1486
- gr.Button("Save HTML to File", variant="secondary").click(
1487
- save_html_to_file,
1488
- [url_in_html, filename_in, persist_check_html],
1489
- save_out
1490
- )
1491
-
1492
- with gr.Tab("🔗 HTTP Request"):
1493
- with gr.Row():
1494
- url_in_http = gr.Textbox(label="URL", value="https://api.example.com/data")
1495
- method_in = gr.Dropdown(
1496
- ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
1497
- value="GET",
1498
- label="Method"
1499
- )
1500
- persist_check_http = gr.Checkbox(label="Use Persistent Browser", value=False)
1501
-
1502
- headers_in = gr.Code(
1503
- label="Headers (JSON or key:value format)",
1504
- language="json",
1505
- lines=5,
1506
- value='{\n "Content-Type": "application/json",\n "User-Agent": "MCP-Browser/1.0"\n}'
1507
- )
1508
-
1509
- data_in = gr.Code(
1510
- label="Body Data (for POST/PUT/PATCH)",
1511
- language="json",
1512
- lines=5,
1513
- value='{\n "key": "value"\n}'
1514
- )
1515
-
1516
- with gr.Row():
1517
- out_http = gr.Code(label="Response", language="json", lines=15)
1518
- curl_out = gr.Textbox(label="cURL Command", lines=5)
1519
-
1520
- with gr.Row():
1521
- gr.Button("Send Request", variant="primary").click(
1522
- make_http_request,
1523
- [url_in_http, method_in, headers_in, data_in, persist_check_http],
1524
- out_http
1525
- )
1526
- gr.Button("Export as cURL", variant="secondary").click(
1527
- export_as_curl,
1528
- [method_in, url_in_http, headers_in, data_in],
1529
- curl_out
1530
- )
1531
-
1532
- with gr.Row():
1533
- history_out = gr.Code(label="Request History", language="json", lines=10)
1534
- gr.Button("Show History", variant="secondary").click(get_request_history, [], history_out)
1535
-
1536
- with gr.Tab("🌐 Network Monitor"):
1537
- with gr.Row():
1538
- url_in_network = gr.Textbox(label="URL", value="https://example.com")
1539
- duration_in = gr.Slider(minimum=1, maximum=30, value=5, label="Monitor Duration (seconds)")
1540
- persist_check_network = gr.Checkbox(label="Use Persistent Browser", value=False)
1541
- out_network = gr.Code(label="Network Requests", language="json", lines=15)
1542
- gr.Button("Monitor Network", variant="primary").click(
1543
- monitor_network_requests,
1544
- [url_in_network, duration_in, persist_check_network],
1545
- out_network
1546
- )
1547
-
1548
- with gr.Tab("📊 Structured Data"):
1549
- with gr.Row():
1550
- url_in_structured = gr.Textbox(label="URL", value="https://example.com")
1551
- persist_check_structured = gr.Checkbox(label="Use Persistent Browser", value=False)
1552
- out_structured = gr.Code(label="Structured Data (JSON-LD, Meta, OpenGraph)", language="json", lines=15)
1553
- gr.Button("Extract Structured Data", variant="primary").click(
1554
- extract_structured_data,
1555
- [url_in_structured, persist_check_structured],
1556
- out_structured
1557
- )
1558
-
1559
- with gr.Tab("🎨 Visual Testing"):
1560
- with gr.Row():
1561
- url1_in = gr.Textbox(label="URL 1", value="https://example.com")
1562
- url2_in = gr.Textbox(label="URL 2", value="https://example.com/new")
1563
- threshold_in = gr.Slider(minimum=0, maximum=1, value=0.98, label="Similarity Threshold")
1564
- out_visual = gr.Code(label="Visual Comparison Result", language="json", lines=10)
1565
- gr.Button("Compare Pages", variant="primary").click(
1566
- visual_regression_test,
1567
- [url1_in, url2_in, threshold_in],
1568
- out_visual
1569
- )
1570
-
1571
- with gr.Tab("♿ Accessibility"):
1572
- with gr.Row():
1573
- url_in_a11y = gr.Textbox(label="URL", value="https://example.com")
1574
- persist_check_a11y = gr.Checkbox(label="Use Persistent Browser", value=False)
1575
- out_a11y = gr.Code(label="Accessibility Audit Results", language="json", lines=20)
1576
- gr.Button("Run Audit", variant="primary").click(
1577
- accessibility_audit,
1578
- [url_in_a11y, persist_check_a11y],
1579
- out_a11y
1580
- )
1581
-
1582
- with gr.Tab("🔗 Link Extractor"):
1583
- with gr.Row():
1584
- url_in_links = gr.Textbox(label="URL", value="https://example.com")
1585
- include_external_check = gr.Checkbox(label="Include External Links", value=True)
1586
- persist_check_links = gr.Checkbox(label="Use Persistent Browser", value=False)
1587
- out_links = gr.Code(label="All Links (Categorized)", language="json", lines=20)
1588
- gr.Button("Extract Links", variant="primary").click(
1589
- extract_all_links,
1590
- [url_in_links, include_external_check, persist_check_links],
1591
- out_links
1592
- )
1593
-
1594
- with gr.Tab("📸 Screenshot"):
1595
- with gr.Row():
1596
- url_in2 = gr.Textbox(label="URL", value="https://example.com")
1597
- full_page_check = gr.Checkbox(label="Full Page Screenshot", value=False)
1598
- persist_check2 = gr.Checkbox(label="Use Persistent Browser", value=False)
1599
- out_img = gr.Image(label="Screenshot")
1600
- gr.Button("Take Screenshot", variant="primary").click(screenshot, [url_in2, full_page_check, persist_check2], out_img)
1601
-
1602
- with gr.Tab("👆 Click"):
1603
- with gr.Row():
1604
- url_in3 = gr.Textbox(label="URL")
1605
- sel_in3 = gr.Textbox(label="CSS Selector", placeholder="button#submit")
1606
- persist_check3 = gr.Checkbox(label="Use Persistent Browser", value=False)
1607
- out3 = gr.Textbox(label="Result", lines=4)
1608
- gr.Button("Click", variant="primary").click(click, [url_in3, sel_in3, persist_check3], out3)
1609
-
1610
- with gr.Tab("✏️ Fill"):
1611
- with gr.Row():
1612
- url_in4 = gr.Textbox(label="URL")
1613
- sel_in4 = gr.Textbox(label="CSS Selector", placeholder="input#username")
1614
- txt_in4 = gr.Textbox(label="Text to Fill")
1615
- persist_check4 = gr.Checkbox(label="Use Persistent Browser", value=False)
1616
- out4 = gr.Textbox(label="Result", lines=2)
1617
- gr.Button("Fill", variant="primary").click(fill, [url_in4, sel_in4, txt_in4, persist_check4], out4)
1618
-
1619
- with gr.Tab("📝 Form Submit"):
1620
- with gr.Row():
1621
- url_in_form = gr.Textbox(label="URL")
1622
- persist_check_form = gr.Checkbox(label="Use Persistent Browser", value=False)
1623
- form_data_in = gr.Textbox(
1624
- label="Form Data",
1625
- placeholder="selector1:value1;selector2:value2",
1626
- lines=3,
1627
- info="Format: CSS_selector:value pairs separated by semicolons"
1628
- )
1629
- submit_btn_in = gr.Textbox(label="Submit Button Selector (optional)", placeholder="button[type='submit']")
1630
- out_form = gr.Textbox(label="Result", lines=4)
1631
- gr.Button("Submit Form", variant="primary").click(
1632
- submit_form,
1633
- [url_in_form, form_data_in, submit_btn_in, persist_check_form],
1634
- out_form
1635
- )
1636
-
1637
- with gr.Tab("💻 Execute JS"):
1638
- with gr.Row():
1639
- url_in5 = gr.Textbox(label="URL", value="https://example.com")
1640
- persist_check5 = gr.Checkbox(label="Use Persistent Browser", value=False)
1641
- script_in5 = gr.Textbox(
1642
- label="JavaScript Code",
1643
- lines=5,
1644
- value="return {title: document.title, url: document.URL, links: document.links.length};"
1645
- )
1646
- out5 = gr.Textbox(label="Result", lines=3)
1647
- gr.Button("Execute", variant="primary").click(execute_js, [url_in5, script_in5, persist_check5], out5)
1648
-
1649
- with gr.Tab("🍪 Cookies"):
1650
- with gr.Row():
1651
- with gr.Column():
1652
- gr.Markdown("### Get Cookies")
1653
- url_in_cookies = gr.Textbox(label="URL")
1654
- persist_check_cookies = gr.Checkbox(label="Use Persistent Browser", value=False)
1655
- cookies_out = gr.Textbox(label="Cookies (JSON)", lines=10)
1656
- gr.Button("Get Cookies").click(get_cookies, [url_in_cookies, persist_check_cookies], cookies_out)
1657
-
1658
- with gr.Column():
1659
- gr.Markdown("### Set Cookies")
1660
- url_in_set_cookies = gr.Textbox(label="URL")
1661
- cookies_in = gr.Textbox(label="Cookies JSON", lines=8, placeholder='[{"name": "cookie_name", "value": "cookie_value"}]')
1662
- persist_check_set_cookies = gr.Checkbox(label="Use Persistent Browser", value=False)
1663
- set_cookies_out = gr.Textbox(label="Result")
1664
- gr.Button("Set Cookies").click(set_cookies, [url_in_set_cookies, cookies_in, persist_check_set_cookies], set_cookies_out)
1665
-
1666
- with gr.Tab("⏳ Wait for Element"):
1667
- with gr.Row():
1668
- url_in_wait = gr.Textbox(label="URL")
1669
- sel_in_wait = gr.Textbox(label="CSS Selector", placeholder=".dynamic-content")
1670
- timeout_in = gr.Slider(minimum=1, maximum=60, value=10, label="Timeout (seconds)")
1671
- persist_check_wait = gr.Checkbox(label="Use Persistent Browser", value=False)
1672
- out_wait = gr.Textbox(label="Result", lines=3)
1673
- gr.Button("Wait for Element", variant="primary").click(
1674
- wait_for_element,
1675
- [url_in_wait, sel_in_wait, timeout_in, persist_check_wait],
1676
- out_wait
1677
- )
1678
-
1679
- with gr.Tab("📜 Scroll"):
1680
- with gr.Row():
1681
- url_in_scroll = gr.Textbox(label="URL")
1682
- direction_in = gr.Radio(["bottom", "top", "down", "up"], value="bottom", label="Direction")
1683
- pixels_in = gr.Number(label="Pixels (for up/down)", value=500)
1684
- persist_check_scroll = gr.Checkbox(label="Use Persistent Browser", value=False)
1685
- out_scroll = gr.Textbox(label="Result", lines=2)
1686
- gr.Button("Scroll", variant="primary").click(
1687
- scroll_page,
1688
- [url_in_scroll, direction_in, pixels_in, persist_check_scroll],
1689
- out_scroll
1690
- )
1691
-
1692
- with gr.Tab("🎯 Hover"):
1693
- with gr.Row():
1694
- url_in_hover = gr.Textbox(label="URL")
1695
- sel_in_hover = gr.Textbox(label="CSS Selector", placeholder=".menu-item")
1696
- persist_check_hover = gr.Checkbox(label="Use Persistent Browser", value=False)
1697
- out_hover = gr.Textbox(label="Result", lines=2)
1698
- gr.Button("Hover", variant="primary").click(hover_element, [url_in_hover, sel_in_hover, persist_check_hover], out_hover)
1699
-
1700
- with gr.Tab("⌨️ Press Key"):
1701
- with gr.Row():
1702
- url_in_key = gr.Textbox(label="URL")
1703
- key_in = gr.Textbox(label="Key", placeholder="enter, escape, tab, etc.")
1704
- sel_in_key = gr.Textbox(label="Target Element", value="body")
1705
- persist_check_key = gr.Checkbox(label="Use Persistent Browser", value=False)
1706
- out_key = gr.Textbox(label="Result", lines=2)
1707
- gr.Button("Press Key", variant="primary").click(
1708
- press_key,
1709
- [url_in_key, key_in, sel_in_key, persist_check_key],
1710
- out_key
1711
- )
1712
-
1713
- with gr.Tab("ℹ️ Page Info"):
1714
- with gr.Row():
1715
- url_in_info = gr.Textbox(label="URL")
1716
- persist_check_info = gr.Checkbox(label="Use Persistent Browser", value=False)
1717
- out_info = gr.Textbox(label="Page Information", lines=15)
1718
- gr.Button("Get Info", variant="primary").click(get_page_info, [url_in_info, persist_check_info], out_info)
1719
-
1720
- with gr.Tab("📄 HTML Source"):
1721
- with gr.Row():
1722
- url_in_html = gr.Textbox(label="URL", value="https://example.com")
1723
- persist_check_html = gr.Checkbox(label="Use Persistent Browser", value=False)
1724
- with gr.Row():
1725
- with gr.Column():
1726
- out_html = gr.Code(label="HTML Source Code", language="html", lines=20)
1727
- gr.Button("Get HTML", variant="primary").click(get_html_source, [url_in_html, persist_check_html], out_html)
1728
- with gr.Column():
1729
- filename_in = gr.Textbox(label="Filename (optional)", placeholder="auto-generated if empty")
1730
- save_out = gr.File(label="Saved HTML File")
1731
- gr.Button("Save HTML to File", variant="secondary").click(
1732
- save_html_to_file,
1733
- [url_in_html, filename_in, persist_check_html],
1734
- save_out
1735
- )
1736
-
1737
- with gr.Tab("🔗 HTTP Request"):
1738
- with gr.Row():
1739
- url_in_http = gr.Textbox(label="URL", value="https://api.example.com/data")
1740
- method_in = gr.Dropdown(
1741
- ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
1742
- value="GET",
1743
- label="Method"
1744
- )
1745
- persist_check_http = gr.Checkbox(label="Use Persistent Browser", value=False)
1746
-
1747
- headers_in = gr.Code(
1748
- label="Headers (JSON or key:value format)",
1749
- language="json",
1750
- lines=5,
1751
- value='{\n "Content-Type": "application/json",\n "User-Agent": "MCP-Browser/1.0"\n}'
1752
- )
1753
-
1754
- data_in = gr.Code(
1755
- label="Body Data (for POST/PUT/PATCH)",
1756
- language="json",
1757
- lines=5,
1758
- value='{\n "key": "value"\n}'
1759
- )
1760
-
1761
- with gr.Row():
1762
- out_http = gr.Code(label="Response", language="json", lines=15)
1763
- curl_out = gr.Textbox(label="cURL Command", lines=5)
1764
-
1765
- with gr.Row():
1766
- gr.Button("Send Request", variant="primary").click(
1767
- make_http_request,
1768
- [url_in_http, method_in, headers_in, data_in, persist_check_http],
1769
- out_http
1770
- )
1771
- gr.Button("Export as cURL", variant="secondary").click(
1772
- export_as_curl,
1773
- [method_in, url_in_http, headers_in, data_in],
1774
- curl_out
1775
- )
1776
-
1777
- with gr.Row():
1778
- history_out = gr.Code(label="Request History", language="json", lines=10)
1779
- gr.Button("Show History", variant="secondary").click(get_request_history, [], history_out)
1780
-
1781
- with gr.Tab("🌐 Network Monitor"):
1782
- with gr.Row():
1783
- url_in_network = gr.Textbox(label="URL", value="https://example.com")
1784
- duration_in = gr.Slider(minimum=1, maximum=30, value=5, label="Monitor Duration (seconds)")
1785
- persist_check_network = gr.Checkbox(label="Use Persistent Browser", value=False)
1786
- out_network = gr.Code(label="Network Requests", language="json", lines=15)
1787
- gr.Button("Monitor Network", variant="primary").click(
1788
- monitor_network_requests,
1789
- [url_in_network, duration_in, persist_check_network],
1790
- out_network
1791
- )
1792
-
1793
- with gr.Tab("📊 Structured Data"):
1794
- with gr.Row():
1795
- url_in_structured = gr.Textbox(label="URL", value="https://example.com")
1796
- persist_check_structured = gr.Checkbox(label="Use Persistent Browser", value=False)
1797
- out_structured = gr.Code(label="Structured Data (JSON-LD, Meta, OpenGraph)", language="json", lines=15)
1798
- gr.Button("Extract Structured Data", variant="primary").click(
1799
- extract_structured_data,
1800
- [url_in_structured, persist_check_structured],
1801
- out_structured
1802
- )
1803
-
1804
- with gr.Tab("🎨 Visual Testing"):
1805
- with gr.Row():
1806
- url1_in = gr.Textbox(label="URL 1", value="https://example.com")
1807
- url2_in = gr.Textbox(label="URL 2", value="https://example.com/new")
1808
- threshold_in = gr.Slider(minimum=0, maximum=1, value=0.98, label="Similarity Threshold")
1809
- out_visual = gr.Code(label="Visual Comparison Result", language="json", lines=10)
1810
- gr.Button("Compare Pages", variant="primary").click(
1811
- visual_regression_test,
1812
- [url1_in, url2_in, threshold_in],
1813
- out_visual
1814
- )
1815
-
1816
- with gr.Tab("♿ Accessibility"):
1817
- with gr.Row():
1818
- url_in_a11y = gr.Textbox(label="URL", value="https://example.com")
1819
- persist_check_a11y = gr.Checkbox(label="Use Persistent Browser", value=False)
1820
- out_a11y = gr.Code(label="Accessibility Audit Results", language="json", lines=20)
1821
- gr.Button("Run Audit", variant="primary").click(
1822
- accessibility_audit,
1823
- [url_in_a11y, persist_check_a11y],
1824
- out_a11y
1825
- )
1826
-
1827
- with gr.Tab("🔗 Link Extractor"):
1828
- with gr.Row():
1829
- url_in_links = gr.Textbox(label="URL", value="https://example.com")
1830
- include_external_check = gr.Checkbox(label="Include External Links", value=True)
1831
- persist_check_links = gr.Checkbox(label="Use Persistent Browser", value=False)
1832
- out_links = gr.Code(label="All Links (Categorized)", language="json", lines=20)
1833
- gr.Button("Extract Links", variant="primary").click(
1834
- extract_all_links,
1835
- [url_in_links, include_external_check, persist_check_links],
1836
- out_links
1837
- )
1838
-
1839
- with gr.Tab("🔧 Browser Control"):
1840
- close_out = gr.Textbox(label="Status")
1841
- gr.Button("Close Persistent Browser", variant="stop").click(close_persistent_driver, [], close_out)
1842
-
1843
- gr.Markdown("""
1844
- ### 📖 Feature Guide:
1845
-
1846
- **🚀 New Advanced Features:**
1847
-
1848
- 1. **📊 Structured Data Extraction**: Extract JSON-LD, OpenGraph, Twitter Cards, and microdata
1849
- 2. **🌐 Network Monitor**: Track all network requests made by a page (XHR, fetch, resources)
1850
- 3. **♿ Accessibility Audit**: Check for common accessibility issues (alt text, labels, landmarks, etc.)
1851
- 4. **🎨 Visual Regression Testing**: Compare two pages visually to detect UI changes
1852
- 5. **🔗 Smart Link Extraction**: Categorize all links (internal, external, email, phone, etc.)
1853
- 6. **💾 HTML Save**: Save full rendered HTML to file
1854
- 7. **📜 cURL Export**: Convert HTTP requests to cURL commands
1855
- 8. **📝 Request History**: Track all HTTP requests made during session
1856
-
1857
- **💡 Pro Tips:**
1858
- - Use persistent browser for faster multi-step workflows
1859
- - Network monitor captures AJAX/fetch requests invisible to normal inspection
1860
- - Accessibility audit helps ensure WCAG compliance
1861
- - Visual testing is great for detecting unintended UI changes
1862
- - Structured data extraction helps with SEO analysis
1863
- """)
1864
-
1865
- # Sidebar with Gemini Chat
1866
- with gr.Column(elem_classes="sidebar"):
1867
- gr.Markdown("""
1868
- ## 🤖 Gemini AI Assistant
1869
-
1870
- Ask me anything about web scraping, automation, or get help with using this tool!
1871
- """)
1872
-
1873
- if not GEMINI_API_KEY:
1874
- gr.Markdown("""
1875
- ⚠️ **Setup Required**: Add your Gemini API key to Hugging Face secrets:
1876
- 1. Go to your Space Settings
1877
- 2. Add a new secret named `GEMINI_API_KEY`
1878
- 3. Get your API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
1879
- """)
1880
-
1881
- chatbot = gr.Chatbot(
1882
- value=[],
1883
- elem_classes="chat-container",
1884
- bubble_full_width=False,
1885
- avatar_images=(None, "🤖")
1886
- )
1887
-
1888
- msg = gr.Textbox(
1889
- label="Message",
1890
- placeholder="Ask me about web scraping, CSS selectors, or any feature...",
1891
- lines=2
1892
- )
1893
-
1894
- with gr.Row():
1895
- submit = gr.Button("Send", variant="primary", scale=1)
1896
- clear = gr.Button("Clear", variant="secondary", scale=1)
1897
-
1898
- # Example prompts
1899
- gr.Examples(
1900
- examples=[
1901
- "How do I extract all links from a webpage?",
1902
- "What's the CSS selector for the first paragraph?",
1903
- "How can I handle dynamic content that loads with JavaScript?",
1904
- "Explain the difference between persistent and non-persistent browser",
1905
- "How do I bypass CORS when making API requests?",
1906
- "What's the best way to extract data from a table?",
1907
- "How can I automate form filling and submission?",
1908
- "Help me understand the accessibility audit results"
1909
- ],
1910
- inputs=msg,
1911
- label="Example Questions"
1912
- )
1913
-
1914
- # Chat event handlers
1915
- msg.submit(chat_with_gemini, [msg, chatbot], [msg, chatbot])
1916
- msg.submit(lambda: "", None, msg) # Clear input
1917
- submit.click(chat_with_gemini, [msg, chatbot], [msg, chatbot])
1918
- submit.click(lambda: "", None, msg) # Clear input
1919
- clear.click(clear_chat, None, [msg, chatbot])
1920
 
1921
  if __name__ == "__main__":
 
1922
  demo.launch(mcp_server=True)
 
1
+ """
2
+ Main application entry point for MCP Browser
3
+ """
4
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
5
  import logging
6
+ from config.settings import UI_CSS, GEMINI_API_KEY
7
+ from ui.layout import create_main_layout
 
 
 
 
 
 
8
 
9
  # Set up logging
10
  logging.basicConfig(level=logging.INFO)
11
  logger = logging.getLogger(__name__)
12
 
13
+ # Create the main Gradio app
14
+ demo = create_main_layout()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  if __name__ == "__main__":
17
+ logger.info("Starting MCP Browser application...")
18
  demo.launch(mcp_server=True)