Spaces:
Build error
Build error
| """ | |
| Tools for the leaderboard agent. | |
| """ | |
| from selenium import webdriver | |
| from selenium.webdriver.common.by import By | |
| from selenium.webdriver.common.keys import Keys | |
| from selenium.webdriver.common.action_chains import ActionChains | |
| import re | |
| import time | |
| import helium | |
| from smolagents import tool | |
| def search_item_ctrl_f(text: str, nth_result: int = 1) -> str: | |
| """ | |
| Searches for text on the current page via Ctrl + F and jumps to the nth occurrence. | |
| Args: | |
| text: The text to search for | |
| nth_result: Which occurrence to jump to (default: 1) | |
| """ | |
| from src.agents.browser import driver | |
| elements = driver.find_elements(By.XPATH, f"//*[contains(text(), '{text}')]") | |
| if nth_result > len(elements): | |
| raise Exception(f"Match n°{nth_result} not found (only {len(elements)} matches found)") | |
| result = f"Found {len(elements)} matches for '{text}'." | |
| elem = elements[nth_result - 1] | |
| driver.execute_script("arguments[0].scrollIntoView(true);", elem) | |
| result += f"Focused on element {nth_result} of {len(elements)}" | |
| return result | |
| def go_back() -> str: | |
| """ | |
| Navigate back to the previous page. | |
| """ | |
| from src.agents.browser import driver | |
| driver.back() | |
| time.sleep(2) # Wait for page to load | |
| return "Navigated back to previous page" | |
| def close_popups() -> str: | |
| """ | |
| Closes any popup/modal dialogs that might be open on the page. | |
| Useful when pop-ups appear (cookies, login prompts, etc.) that block interaction. | |
| """ | |
| from src.agents.browser import driver | |
| # Try to find common popup elements | |
| popup_selectors = [ | |
| "//button[contains(text(), 'Accept')]", | |
| "//button[contains(text(), 'Close')]", | |
| "//button[contains(text(), 'Fermer')]", | |
| "//button[contains(text(), 'OK')]", | |
| "//button[contains(text(), 'Got it')]", | |
| "//button[contains(@class, 'close')]", | |
| "//div[contains(@class, 'popup')]//button", | |
| "//div[contains(@class, 'modal')]//button", | |
| "//div[contains(@class, 'dialog')]//button" | |
| ] | |
| found = False | |
| for selector in popup_selectors: | |
| try: | |
| popup_elements = driver.find_elements(By.XPATH, selector) | |
| for elem in popup_elements: | |
| if elem.is_displayed(): | |
| elem.click() | |
| found = True | |
| time.sleep(0.5) # Wait for popup to disappear | |
| except Exception as e: | |
| pass # Ignore errors, try next selector | |
| return "Closed popup dialogs" if found else "No popup dialogs found" | |
| def extract_table_data(table_caption: str = None, table_index: int = 1) -> str: | |
| """ | |
| Extracts data from a table on the page. Can find a table by caption/title or by index. | |
| Args: | |
| table_caption: Text in or near the table to find (default: None - will use index) | |
| table_index: The index of the table if caption is not provided (1-based) | |
| """ | |
| from src.agents.browser import driver | |
| tables = driver.find_elements(By.TAG_NAME, "table") | |
| if not tables: | |
| return "No tables found on the page." | |
| result = f"Found {len(tables)} table(s) on the page.\n" | |
| for i, table in enumerate(tables): | |
| result += f"\nTable {i+1}:\n" | |
| # Try to get headers | |
| headers = table.find_elements(By.TAG_NAME, "th") | |
| if headers: | |
| header_texts = [header.text for header in headers] | |
| result += f"Headers: {', '.join(header_texts)}\n" | |
| # Get rows | |
| rows = table.find_elements(By.TAG_NAME, "tr") | |
| result += f"Found {len(rows)} rows.\n" | |
| # Get first 5 rows as sample | |
| for j, row in enumerate(rows[:5]): | |
| cells = row.find_elements(By.TAG_NAME, "td") | |
| if cells: | |
| cell_texts = [cell.text for cell in cells] | |
| result += f"Row {j+1}: {' | '.join(cell_texts)}\n" | |
| return result | |
| def find_leaderboard_elements() -> str: | |
| """ | |
| Find key elements of a leaderboard: title, evaluation criteria, and model rankings. | |
| Returns a structured description of what was found. | |
| """ | |
| from src.agents.browser import driver | |
| result = "" | |
| # Check for tables first | |
| tables = driver.find_elements(By.TAG_NAME, "table") | |
| if tables: | |
| result += f"Found {len(tables)} table(s) that might contain leaderboard data.\n" | |
| # Check for ordered lists | |
| ol_elements = driver.find_elements(By.TAG_NAME, "ol") | |
| if ol_elements: | |
| result += f"Found {len(ol_elements)} ordered list(s) that might contain rankings.\n" | |
| # Check for div elements with grid or flex display that might be custom leaderboards | |
| grid_elements = driver.find_elements(By.XPATH, "//div[contains(@class, 'grid') or contains(@class, 'flex') or contains(@class, 'table') or contains(@class, 'rank') or contains(@class, 'leaderboard')]") | |
| if grid_elements: | |
| result += f"Found {len(grid_elements)} div elements with grid/flex/table classes that might be custom leaderboards.\n" | |
| # Look for elements with rank or position indicators | |
| rank_elements = driver.find_elements(By.XPATH, "//*[contains(@class, 'rank') or contains(@class, 'position') or contains(@class, 'standing')]") | |
| if rank_elements: | |
| result += f"Found {len(rank_elements)} elements with rank/position classes.\n" | |
| if not result: | |
| return "Could not find any obvious leaderboard elements. Try scrolling or navigating to the correct section." | |
| return result | |
| def map_clickable_elements(keyword: str = None) -> str: | |
| """ | |
| Displays a list of all clickable elements on the page with their coordinates. | |
| Args: | |
| keyword: Optional keyword to filter elements. If specified, only elements containing this keyword will be displayed. | |
| Returns: | |
| A string listing all clickable elements with their coordinates. | |
| """ | |
| from src.agents.browser import driver | |
| clickable_selectors = [ | |
| "a", "button", "input[type='button']", "input[type='submit']", | |
| ".clickable", "[role='button']", "[onclick]" | |
| ] | |
| result = "Éléments cliquables détectés:\n" | |
| total = 0 | |
| for selector in clickable_selectors: | |
| elements = driver.find_elements(By.CSS_SELECTOR, selector) | |
| for i, element in enumerate(elements): | |
| try: | |
| text = element.text.strip() | |
| if not text and element.get_attribute("value"): | |
| text = element.get_attribute("value") | |
| # Ignorer les éléments vides ou non visibles | |
| if not text or not element.is_displayed(): | |
| continue | |
| # Filtrer par mot-clé si spécifié | |
| if keyword and keyword.lower() not in text.lower(): | |
| continue | |
| rect = element.rect | |
| x = int(rect['x'] + rect['width']/2) | |
| y = int(rect['y'] + rect['height']/2) | |
| result += f"{total+1}. '{text}' ({selector}) - coords: x={x}, y={y}\n" | |
| total += 1 | |
| except: | |
| continue | |
| result += f"\nTotal: {total} éléments cliquables" + (" contenant '" + keyword + "'" if keyword else "") | |
| return result | |
| def copy_link_from_element(text_to_find: str, link_position: int = 1) -> str: | |
| """ | |
| Find elements with specified text and return the URL if it's a link or has a parent link. | |
| Args: | |
| text_to_find: Text to search for | |
| link_position: If multiple matches, which one to use (1-based) | |
| """ | |
| from src.agents.browser import driver | |
| try: | |
| # Try to find an element with the given text | |
| element = driver.find_element_by_xpath(f"//*[contains(text(), '{text_to_find}')]") | |
| if not element: | |
| return f"No element containing the text '{text_to_find}' was found." | |
| # Try to find URL directly from the element | |
| href = element.get_attribute("href") | |
| if href: | |
| return f"URL found: {href}" | |
| # Try to find a parent that is a link | |
| parent = element.find_element_by_xpath("./ancestor::a") | |
| if parent: | |
| href = parent.get_attribute("href") | |
| if href: | |
| return f"URL found in parent element: {href}" | |
| # Try to find a child that is a link | |
| child = element.find_element_by_xpath(".//a") | |
| if child: | |
| href = child.get_attribute("href") | |
| if href: | |
| return f"URL found in child element: {href}" | |
| # Méthode 4: Essayer le clic droit et "Copier l'adresse du lien" | |
| actions = ActionChains(driver) | |
| actions.context_click(element).perform() | |
| # Attendre un peu pour que le menu contextuel s'affiche | |
| import time | |
| time.sleep(1) | |
| # Essayer de trouver et cliquer sur "Copier l'adresse du lien" ou équivalent | |
| # Note: Cette partie est très dépendante du navigateur et de la langue | |
| copy_link_texts = ["Copy link address", "Copier l'adresse du lien", "Copy Link", "Copier le lien"] | |
| for text in copy_link_texts: | |
| try: | |
| link_option = driver.find_element(By.XPATH, f"//div[contains(text(), '{text}')]") | |
| link_option.click() | |
| return f"Action 'Copier l'adresse du lien' effectuée pour '{text_to_find}'" | |
| except: | |
| continue | |
| # Annuler le menu contextuel | |
| webdriver.ActionChains(driver).send_keys(Keys.ESCAPE).perform() | |
| return f"Impossible de trouver un lien pour l'élément '{text_to_find}' avec les méthodes disponibles." | |
| except Exception as e: | |
| return f"Erreur lors de la recherche du lien: {str(e)}" | |
| def validate_json_results(result: dict) -> tuple[bool, str]: | |
| """ | |
| Checks that the results do not contain generic placeholders. | |
| Args: | |
| result: The result to validate | |
| Returns: | |
| A tuple containing a boolean indicating if the result is valid and a message | |
| explaining why the result is invalid if it is not valid. | |
| """ | |
| if not result or not isinstance(result, dict): | |
| return False, "Invalid result" | |
| if "top_models" not in result or len(result.get("top_models", [])) < 3: | |
| return False, "Less than 3 models found" | |
| # Check for duplicate models | |
| seen_models = set() | |
| for model in result.get("top_models", []): | |
| model_name = model.get("name", "").lower() | |
| if model_name in seen_models: | |
| return False, f"Duplicate model '{model.get('name')}' found. Please ensure each model is unique." | |
| seen_models.add(model_name) | |
| # Check for generic names | |
| generic_names = ["model a", "model b", "model c", "model 1", "model 2", "model 3", "model name", "unavailable"] | |
| model_names = [m.get("name", "").lower() for m in result.get("top_models", [])] | |
| if any(name in generic_names for name in model_names): | |
| return False, "Generic model names detected" | |
| # Check for unwanted suffixes in model names | |
| unwanted_suffix_pattern = r"\(.*\)$" | |
| for model in result.get("top_models", []): | |
| if re.search(unwanted_suffix_pattern, model.get("name", "")): | |
| return False, f"Model name '{model.get('name')}' contains unwanted suffixes. Please remove them if you think they are not part of the model name. If it's a version number or a date, keep it." | |
| # Check for generic URLs | |
| generic_urls = ["example.com", "example.org"] | |
| model_urls = [m.get("url", "").lower() for m in result.get("top_models", []) if m.get("url") is not None] | |
| if any(generic in url for url in model_urls for generic in generic_urls): | |
| return False, "Generic URLs detected" | |
| # Check for submatch between model name and URL | |
| for model in result.get("top_models", []): | |
| name = model.get("name", "").lower() | |
| url = model.get("url") | |
| # Skip validation if URL is None or empty - this is acceptable, so no warning | |
| if not url: | |
| continue | |
| url = url.lower() | |
| if url and not any(name[i:i+4] in url for i in range(len(name) - 3)): | |
| return False, f"URL for model '{model.get('name')}' does not have a valid submatch with the name. This is probably a wrong URL. Please check the URL and try again." | |
| # Check the evaluation criterion | |
| if "evaluation_criteria" not in result or len(result.get("evaluation_criteria", "")) < 10: | |
| return False, "Evaluation criterion missing or too short" | |
| return True, "Valid results" | |
| def find_model_links(model_name: str) -> str: | |
| """ | |
| Search for links that might point to a model based on their URL | |
| and their match with the model name. | |
| Args: | |
| model_name: The name of the model to search for | |
| Returns: | |
| A list of potential links to the model | |
| """ | |
| from src.agents.browser import driver | |
| try: | |
| # 1. Retrieve all links on the page | |
| all_links = driver.find_elements(By.TAG_NAME, "a") | |
| if not all_links: | |
| return "No links were found on the page." | |
| # 2. Known patterns for model repositories | |
| model_url_patterns = [ | |
| r'huggingface\.co/[^/]+/[^/]+', # Hugging Face model repo | |
| r'github\.com/[^/]+/[^/]+', # GitHub repo | |
| ] | |
| model_links = [] | |
| model_name_lower = model_name.lower() | |
| for link in all_links: | |
| try: | |
| # Check if the link is visible and has an href attribute | |
| if not link.is_displayed() or not link.get_attribute('href'): | |
| continue | |
| link_url = link.get_attribute('href') | |
| link_text = link.text.strip() | |
| # Ignore links to non-relevant resources | |
| if link_url.endswith(('.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.css', '.js')): | |
| continue | |
| # Check if the URL matches a known pattern | |
| matches_pattern = any(re.search(pattern, link_url, re.IGNORECASE) for pattern in model_url_patterns) | |
| if matches_pattern: | |
| # Check for a 3-character submatch between the model name and the URL | |
| url_lower = link_url.lower() | |
| has_submatch = False | |
| # Search for a 3-character submatch in the model name | |
| for i in range(len(model_name_lower) - 4): | |
| if model_name_lower[i:i+5] in url_lower and model_name_lower[i:i+5] in link_text.lower(): | |
| has_submatch = True | |
| break | |
| if has_submatch: | |
| # Calculate the confidence based on character matches | |
| confidence = sum(1 for c in model_name_lower if c in link_text.lower()) | |
| model_links.append({ | |
| 'url': link_url, | |
| 'text': link_text, | |
| 'confidence': confidence | |
| }) | |
| except Exception as e: | |
| continue # Ignore errors and continue | |
| # 3. Format the result | |
| if not model_links: | |
| return f"No potential links to the model '{model_name}' were found." | |
| result = f"Found {len(model_links)} potential links for the model '{model_name}':\n\n" | |
| for i, link in enumerate(model_links): | |
| result += f"Candidate {i+1}:\n" | |
| result += f"URL: {link['url']}\n" | |
| result += f"Text: {link['text']}\n" | |
| result += f"Confidence: {link['confidence']}\n\n" | |
| # 4. Suggest the best candidate (the one with the highest confidence) | |
| if model_links: | |
| best_candidate = max(model_links, key=lambda x: x['confidence']) | |
| result += f"Best candidate for '{model_name}':\nURL: {best_candidate['url']}\nText: {best_candidate['text']} " | |
| return result | |
| except Exception as e: | |
| return f"Error while searching for links for the model '{model_name}': {str(e)}" | |
| def click_at_coordinates(x: int, y: int) -> str: | |
| """ | |
| Clicks at the specified x,y coordinates on the page. | |
| This is useful when other targeting methods fail or when dealing with complex UI elements. | |
| Args: | |
| x: The x-coordinate to click at | |
| y: The y-coordinate to click at | |
| Returns: | |
| A message confirming the click action | |
| """ | |
| from src.agents.browser import driver | |
| try: | |
| # Using ActionChains for precise coordinate clicks | |
| actions = ActionChains(driver) | |
| actions.move_by_offset(x, y).click().perform() | |
| actions.reset_actions() # Reset position after click | |
| # Alternative approach using Helium | |
| # helium.click_at_point(x, y) | |
| time.sleep(1) # Wait a moment for any reactions to the click | |
| return f"Successfully clicked at coordinates ({x}, {y})" | |
| except Exception as e: | |
| return f"Failed to click at coordinates ({x}, {y}): {str(e)}" |