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)}" |