semantio 0.0.5__py3-none-any.whl → 0.0.6__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- semantio/__init__.py +4 -0
- semantio/tools/web_browser.py +318 -150
- {semantio-0.0.5.dist-info → semantio-0.0.6.dist-info}/METADATA +1 -1
- {semantio-0.0.5.dist-info → semantio-0.0.6.dist-info}/RECORD +8 -8
- {semantio-0.0.5.dist-info → semantio-0.0.6.dist-info}/LICENSE +0 -0
- {semantio-0.0.5.dist-info → semantio-0.0.6.dist-info}/WHEEL +0 -0
- {semantio-0.0.5.dist-info → semantio-0.0.6.dist-info}/entry_points.txt +0 -0
- {semantio-0.0.5.dist-info → semantio-0.0.6.dist-info}/top_level.txt +0 -0
semantio/__init__.py
CHANGED
semantio/tools/web_browser.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# web_browser.py
|
2
|
-
from typing import Dict, Any, List, Optional
|
2
|
+
from typing import Dict, Any, List, Optional, Callable
|
3
3
|
from pydantic import Field, BaseModel
|
4
4
|
from selenium import webdriver
|
5
5
|
from selenium.webdriver.common.by import By
|
6
|
+
from selenium.webdriver.common.action_chains import ActionChains
|
7
|
+
from selenium.webdriver.remote.webelement import WebElement
|
6
8
|
from selenium.webdriver.support.ui import WebDriverWait
|
7
9
|
from selenium.webdriver.support import expected_conditions as EC
|
8
10
|
from selenium.webdriver.chrome.options import Options
|
@@ -13,6 +15,8 @@ import json
|
|
13
15
|
import time
|
14
16
|
import re
|
15
17
|
import logging
|
18
|
+
import os
|
19
|
+
import difflib
|
16
20
|
from .base_tool import BaseTool
|
17
21
|
|
18
22
|
logger = logging.getLogger(__name__)
|
@@ -26,99 +30,140 @@ class BrowserPlan(BaseModel):
|
|
26
30
|
class WebBrowserTool(BaseTool):
|
27
31
|
name: str = Field("WebBrowser", description="Name of the tool")
|
28
32
|
description: str = Field(
|
29
|
-
"
|
33
|
+
"Highly advanced universal web automation tool with advanced element identification, AJAX waiting, modal dismissal, multi-tab support, and custom JS injection.",
|
30
34
|
description="Tool description"
|
31
35
|
)
|
32
36
|
|
37
|
+
default_timeout: int = 15 # Default wait timeout in seconds
|
38
|
+
max_retries: int = 3 # Increased maximum retries for any task
|
39
|
+
|
33
40
|
def execute(self, input: Dict[str, Any]) -> Dict[str, Any]:
|
34
|
-
"""Execute dynamic web automation workflow"""
|
41
|
+
"""Execute an advanced dynamic web automation workflow."""
|
35
42
|
driver = None
|
43
|
+
overall_start = time.time()
|
36
44
|
try:
|
37
|
-
|
45
|
+
headless = input.get("headless", False)
|
46
|
+
self.default_timeout = int(input.get("timeout", self.default_timeout))
|
47
|
+
self.max_retries = int(input.get("max_retries", self.max_retries))
|
48
|
+
driver = self._init_browser(headless)
|
38
49
|
results = []
|
39
50
|
current_url = ""
|
40
51
|
|
41
|
-
|
42
|
-
|
52
|
+
plan = self._generate_plan(input.get('query', ''), current_url)
|
53
|
+
if not plan.tasks:
|
54
|
+
raise ValueError("No valid tasks in the generated plan.")
|
55
|
+
|
56
|
+
# Dynamic mapping: action name to handler function.
|
57
|
+
action_map: Dict[str, Callable[[webdriver.Chrome, Dict[str, Any]], Dict[str, Any]]] = {
|
58
|
+
"navigate": lambda d, task: self._handle_navigation(d, task.get("value", "")),
|
59
|
+
"click": lambda d, task: self._handle_click(d, task.get("selector", "")),
|
60
|
+
"type": lambda d, task: self._handle_typing(d, task.get("selector", ""), task.get("value", ""), task),
|
61
|
+
"wait": lambda d, task: self._handle_wait(task.get("value", "")),
|
62
|
+
"wait_for_ajax": lambda d, task: self._handle_wait_for_ajax(d, task.get("value", "30")),
|
63
|
+
"scroll": lambda d, task: self._handle_scroll(d, task.get("selector", "")),
|
64
|
+
"hover": lambda d, task: self._handle_hover(d, task.get("selector", "")),
|
65
|
+
"screenshot": lambda d, task: self._handle_screenshot(d, task.get("value", "screenshot.png")),
|
66
|
+
"switch_tab": lambda d, task: self._handle_switch_tab(d, task.get("value", "0")),
|
67
|
+
"execute_script": lambda d, task: self._handle_execute_script(d, task.get("value", "")),
|
68
|
+
"drag_and_drop": lambda d, task: self._handle_drag_and_drop(d, task.get("selector", ""), task.get("value", "")),
|
69
|
+
}
|
43
70
|
|
44
71
|
for task in plan.tasks:
|
45
|
-
|
72
|
+
# Before each action, dismiss modals/overlays.
|
73
|
+
self._dismiss_unwanted_modals(driver)
|
74
|
+
action = task.get("action", "").lower()
|
75
|
+
logger.info(f"Executing task: {task.get('description', action)}")
|
76
|
+
start_time = time.time()
|
77
|
+
handler = action_map.get(action)
|
78
|
+
if not handler:
|
79
|
+
results.append({
|
80
|
+
"action": action,
|
81
|
+
"success": False,
|
82
|
+
"message": f"Unsupported action: {action}"
|
83
|
+
})
|
84
|
+
continue
|
85
|
+
|
86
|
+
result = self._execute_with_retries(driver, task, handler)
|
87
|
+
elapsed = time.time() - start_time
|
88
|
+
result["elapsed"] = elapsed
|
89
|
+
logger.info(f"Action '{action}' completed in {elapsed:.2f} seconds.")
|
46
90
|
results.append(result)
|
47
|
-
|
48
|
-
if not result
|
91
|
+
|
92
|
+
if not result.get('success', False):
|
93
|
+
logger.error(f"Task failed: {result.get('message')}")
|
94
|
+
self._capture_failure_screenshot(driver, action)
|
49
95
|
break
|
50
|
-
|
51
|
-
# Update context for next tasks
|
96
|
+
|
52
97
|
current_url = driver.current_url
|
53
98
|
|
54
|
-
|
99
|
+
overall_elapsed = time.time() - overall_start
|
100
|
+
logger.info(f"Total execution time: {overall_elapsed:.2f} seconds.")
|
101
|
+
return {"status": "success", "results": results, "total_time": overall_elapsed}
|
55
102
|
|
56
103
|
except Exception as e:
|
104
|
+
logger.exception("Execution error:")
|
57
105
|
return {"status": "error", "message": str(e)}
|
58
106
|
finally:
|
59
107
|
if driver:
|
60
108
|
driver.quit()
|
61
109
|
|
62
110
|
def _init_browser(self, headless: bool) -> webdriver.Chrome:
|
63
|
-
"""Initialize browser with advanced options"""
|
111
|
+
"""Initialize browser with advanced options."""
|
64
112
|
options = Options()
|
65
113
|
options.add_argument("--start-maximized")
|
66
114
|
options.add_argument("--disable-blink-features=AutomationControlled")
|
67
115
|
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
68
|
-
|
69
116
|
if headless:
|
70
117
|
options.add_argument("--headless=new")
|
71
|
-
|
72
118
|
return webdriver.Chrome(
|
73
119
|
service=Service(ChromeDriverManager().install()),
|
74
120
|
options=options
|
75
121
|
)
|
76
122
|
|
77
123
|
def _generate_plan(self, query: str, current_url: str) -> BrowserPlan:
|
78
|
-
"""Generate adaptive execution plan using LLM"""
|
124
|
+
"""Generate an adaptive execution plan using an LLM or other dynamic planner."""
|
79
125
|
prompt = f"""Generate browser automation plan for: {query}
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
126
|
+
|
127
|
+
Current URL: {current_url or 'No page loaded yet'}
|
128
|
+
|
129
|
+
Required JSON format:
|
130
|
+
{{
|
131
|
+
"tasks": [
|
84
132
|
{{
|
85
|
-
"
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
"value": "input text/URL/seconds",
|
90
|
-
"description": "action purpose"
|
91
|
-
}}
|
92
|
-
]
|
133
|
+
"action": "navigate|click|type|wait|wait_for_ajax|scroll|hover|screenshot|switch_tab|execute_script|drag_and_drop",
|
134
|
+
"selector": "CSS selector (optional)",
|
135
|
+
"value": "input text/URL/seconds/filename/target-selector",
|
136
|
+
"description": "action purpose"
|
93
137
|
}}
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
138
|
+
]
|
139
|
+
}}
|
140
|
+
|
141
|
+
Guidelines:
|
142
|
+
1. Prefer IDs in selectors (#element-id) and semantic attributes.
|
143
|
+
2. Include wait steps after navigation and wait for AJAX where applicable.
|
144
|
+
3. Dismiss any modals/pop-ups that are not part of the task.
|
145
|
+
4. For drag_and_drop, use source selector in 'selector' and target selector in 'value'.
|
146
|
+
5. For execute_script, 'value' should contain valid JavaScript.
|
147
|
+
6. For switch_tab, 'value' should be an index or keyword 'new'.
|
148
|
+
"""
|
103
149
|
response = self.llm.generate(prompt=prompt)
|
104
150
|
return self._parse_plan(response)
|
105
151
|
|
106
152
|
def _parse_plan(self, response: str) -> BrowserPlan:
|
107
|
-
"""Robust JSON parsing with multiple fallback strategies"""
|
153
|
+
"""Robust JSON parsing with multiple fallback strategies."""
|
108
154
|
try:
|
109
|
-
# Try extracting JSON from markdown code block
|
110
155
|
json_match = re.search(r'```json\n?(.+?)\n?```', response, re.DOTALL)
|
111
156
|
if json_match:
|
112
157
|
plan_data = json.loads(json_match.group(1).strip())
|
113
158
|
else:
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
# Validate tasks structure
|
159
|
+
json_str_match = re.search(r'\{.*\}', response, re.DOTALL)
|
160
|
+
if not json_str_match:
|
161
|
+
raise ValueError("No JSON object found in the response.")
|
162
|
+
plan_data = json.loads(json_str_match.group())
|
119
163
|
validated_tasks = []
|
120
164
|
for task in plan_data.get("tasks", []):
|
121
165
|
if not all(key in task for key in ["action", "description"]):
|
166
|
+
logger.warning(f"Skipping task due to missing keys: {task}")
|
122
167
|
continue
|
123
168
|
validated_tasks.append({
|
124
169
|
"action": task["action"],
|
@@ -126,146 +171,269 @@ class WebBrowserTool(BaseTool):
|
|
126
171
|
"value": task.get("value", ""),
|
127
172
|
"description": task["description"]
|
128
173
|
})
|
129
|
-
|
130
174
|
return BrowserPlan(tasks=validated_tasks)
|
131
|
-
|
132
|
-
except (json.JSONDecodeError, AttributeError) as e:
|
175
|
+
except (json.JSONDecodeError, AttributeError, ValueError) as e:
|
133
176
|
logger.error(f"Plan parsing failed: {e}")
|
134
177
|
return BrowserPlan(tasks=[])
|
135
178
|
|
136
|
-
def
|
137
|
-
|
179
|
+
def _execute_with_retries(self, driver: webdriver.Chrome, task: Dict[str, Any],
|
180
|
+
handler: Callable[[webdriver.Chrome, Dict[str, Any]], Dict[str, Any]]) -> Dict[str, Any]:
|
181
|
+
"""Execute a task with retry logic and exponential backoff."""
|
182
|
+
attempts = 0
|
183
|
+
result = {}
|
184
|
+
while attempts < self.max_retries:
|
185
|
+
result = self._execute_safe_task(driver, task, handler)
|
186
|
+
if result.get("success", False):
|
187
|
+
return result
|
188
|
+
attempts += 1
|
189
|
+
logger.info(f"Retrying task '{task.get('action')}' (attempt {attempts + 1}/{self.max_retries})")
|
190
|
+
time.sleep(1 * attempts)
|
191
|
+
return result
|
192
|
+
|
193
|
+
def _execute_safe_task(self, driver: webdriver.Chrome, task: Dict[str, Any],
|
194
|
+
handler: Callable[[webdriver.Chrome, Dict[str, Any]], Dict[str, Any]]) -> Dict[str, Any]:
|
195
|
+
"""Execute a task with comprehensive error handling."""
|
138
196
|
try:
|
139
|
-
|
140
|
-
selector = task.get("selector", "")
|
141
|
-
value = task.get("value", "")
|
142
|
-
|
143
|
-
if action == "navigate":
|
144
|
-
return self._handle_navigation(driver, value)
|
145
|
-
|
146
|
-
elif action == "click":
|
147
|
-
return self._handle_click(driver, selector)
|
148
|
-
|
149
|
-
elif action == "type":
|
150
|
-
return self._handle_typing(driver, selector, value)
|
151
|
-
|
152
|
-
elif action == "wait":
|
153
|
-
return self._handle_wait(value)
|
154
|
-
|
155
|
-
elif action == "scroll":
|
156
|
-
return self._handle_scroll(driver, selector)
|
157
|
-
|
158
|
-
return {
|
159
|
-
"action": action,
|
160
|
-
"success": False,
|
161
|
-
"message": f"Unsupported action: {action}"
|
162
|
-
}
|
163
|
-
|
197
|
+
return handler(driver, task)
|
164
198
|
except Exception as e:
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
"message": f"Critical error: {str(e)}"
|
169
|
-
}
|
199
|
+
action = task.get("action", "unknown")
|
200
|
+
logger.exception(f"Error executing task '{action}':")
|
201
|
+
return {"action": action, "success": False, "message": f"Critical error: {str(e)}"}
|
170
202
|
|
171
|
-
def
|
172
|
-
"""
|
203
|
+
def _dismiss_unwanted_modals(self, driver: webdriver.Chrome):
|
204
|
+
"""
|
205
|
+
Dismiss or remove unwanted modals, overlays, or pop-ups.
|
206
|
+
First attempts to click a close button; if not available, removes the element via JS.
|
207
|
+
"""
|
208
|
+
try:
|
209
|
+
modal_selectors = [".modal", ".popup", '[role="dialog"]', ".overlay", ".lightbox"]
|
210
|
+
for selector in modal_selectors:
|
211
|
+
elements = driver.find_elements(By.CSS_SELECTOR, selector)
|
212
|
+
for modal in elements:
|
213
|
+
if modal.is_displayed():
|
214
|
+
close_selectors = [".close", ".btn-close", "[aria-label='Close']", "[data-dismiss='modal']"]
|
215
|
+
dismissed = False
|
216
|
+
for close_sel in close_selectors:
|
217
|
+
try:
|
218
|
+
close_button = modal.find_element(By.CSS_SELECTOR, close_sel)
|
219
|
+
if close_button.is_displayed():
|
220
|
+
close_button.click()
|
221
|
+
dismissed = True
|
222
|
+
logger.info(f"Dismissed modal using selector {close_sel}")
|
223
|
+
time.sleep(1)
|
224
|
+
break
|
225
|
+
except Exception:
|
226
|
+
continue
|
227
|
+
if not dismissed:
|
228
|
+
# Remove overlay by setting display to none
|
229
|
+
driver.execute_script("arguments[0].remove();", modal)
|
230
|
+
logger.info(f"Removed overlay/modal with selector {selector}")
|
231
|
+
except Exception as e:
|
232
|
+
logger.debug(f"Modal dismissal error: {e}")
|
233
|
+
|
234
|
+
def _advanced_find_element(self, driver: webdriver.Chrome, keyword: str) -> Optional[WebElement]:
|
235
|
+
"""
|
236
|
+
Advanced fallback for finding an element.
|
237
|
+
Searches across multiple attributes and inner text using fuzzy matching.
|
238
|
+
"""
|
239
|
+
candidates = driver.find_elements(By.CSS_SELECTOR, "input, textarea, button, a, div")
|
240
|
+
best_match = None
|
241
|
+
best_ratio = 0.0
|
242
|
+
for candidate in candidates:
|
243
|
+
combined_text = " ".join([
|
244
|
+
candidate.get_attribute("id") or "",
|
245
|
+
candidate.get_attribute("name") or "",
|
246
|
+
candidate.get_attribute("placeholder") or "",
|
247
|
+
candidate.get_attribute("aria-label") or "",
|
248
|
+
candidate.text or "",
|
249
|
+
])
|
250
|
+
ratio = difflib.SequenceMatcher(None, combined_text.lower(), keyword.lower()).ratio()
|
251
|
+
if ratio > best_ratio:
|
252
|
+
best_ratio = ratio
|
253
|
+
best_match = candidate
|
254
|
+
if best_ratio > 0.5:
|
255
|
+
logger.info(f"Advanced fallback detected element with similarity {best_ratio:.2f} for keyword '{keyword}'")
|
256
|
+
return best_match
|
257
|
+
return None
|
258
|
+
|
259
|
+
def _handle_navigation(self, driver: webdriver.Chrome, url: str) -> Dict[str, Any]:
|
260
|
+
"""Handle navigation with URL correction."""
|
173
261
|
if not url.startswith(("http://", "https://")):
|
174
262
|
url = f"https://{url}"
|
175
|
-
|
176
263
|
try:
|
177
264
|
driver.get(url)
|
178
|
-
WebDriverWait(driver,
|
179
|
-
|
180
|
-
)
|
181
|
-
return {
|
182
|
-
"action": "navigate",
|
183
|
-
"success": True,
|
184
|
-
"message": f"Navigated to {url}"
|
185
|
-
}
|
265
|
+
WebDriverWait(driver, self.default_timeout).until(EC.presence_of_element_located((By.TAG_NAME, "body")))
|
266
|
+
return {"action": "navigate", "success": True, "message": f"Navigated to {url}"}
|
186
267
|
except Exception as e:
|
187
|
-
|
188
|
-
|
189
|
-
"success": False,
|
190
|
-
"message": f"Navigation failed: {str(e)}"
|
191
|
-
}
|
268
|
+
logger.error(f"Navigation to {url} failed: {e}")
|
269
|
+
return {"action": "navigate", "success": False, "message": f"Navigation failed: {str(e)}"}
|
192
270
|
|
193
|
-
def _handle_click(self, driver, selector: str) -> Dict[str, Any]:
|
194
|
-
"""
|
271
|
+
def _handle_click(self, driver: webdriver.Chrome, selector: str) -> Dict[str, Any]:
|
272
|
+
"""Handle click actions with fallback using JS if needed."""
|
195
273
|
try:
|
196
|
-
element = WebDriverWait(driver,
|
274
|
+
element = WebDriverWait(driver, self.default_timeout).until(
|
197
275
|
EC.element_to_be_clickable((By.CSS_SELECTOR, selector))
|
198
276
|
)
|
199
|
-
driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth'});", element)
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
"
|
204
|
-
|
205
|
-
}
|
277
|
+
driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", element)
|
278
|
+
try:
|
279
|
+
element.click()
|
280
|
+
except Exception:
|
281
|
+
driver.execute_script("arguments[0].click();", element)
|
282
|
+
return {"action": "click", "success": True, "message": f"Clicked element: {selector}"}
|
206
283
|
except Exception as e:
|
207
|
-
|
208
|
-
|
209
|
-
"success": False,
|
210
|
-
"message": f"Click failed: {str(e)}"
|
211
|
-
}
|
284
|
+
logger.error(f"Click action failed on selector {selector}: {e}")
|
285
|
+
return {"action": "click", "success": False, "message": f"Click failed: {str(e)}"}
|
212
286
|
|
213
|
-
def _handle_typing(self, driver, selector: str, text: str) -> Dict[str, Any]:
|
214
|
-
"""
|
287
|
+
def _handle_typing(self, driver: webdriver.Chrome, selector: str, text: str, task: Dict[str, Any]) -> Dict[str, Any]:
|
288
|
+
"""
|
289
|
+
Handle typing into an element.
|
290
|
+
If the primary selector fails, attempt advanced fallback detection.
|
291
|
+
"""
|
215
292
|
try:
|
216
|
-
element = WebDriverWait(driver,
|
293
|
+
element = WebDriverWait(driver, self.default_timeout).until(
|
217
294
|
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
|
218
295
|
)
|
296
|
+
except Exception as e:
|
297
|
+
# If the task seems to involve search or similar text, use advanced fallback.
|
298
|
+
if "search" in task.get("description", "").lower() or "search" in selector.lower():
|
299
|
+
logger.info("Primary selector failed; using advanced fallback for element detection.")
|
300
|
+
element = self._advanced_find_element(driver, "search")
|
301
|
+
if not element:
|
302
|
+
return {"action": "type", "success": False, "message": f"Typing failed: No search-like element found; error: {str(e)}"}
|
303
|
+
else:
|
304
|
+
return {"action": "type", "success": False, "message": f"Typing failed: {str(e)}"}
|
305
|
+
try:
|
219
306
|
element.clear()
|
220
307
|
element.send_keys(text)
|
221
|
-
return {
|
222
|
-
"action": "type",
|
223
|
-
"success": True,
|
224
|
-
"message": f"Typed '{text}' into {selector}"
|
225
|
-
}
|
308
|
+
return {"action": "type", "success": True, "message": f"Typed '{text}' into element."}
|
226
309
|
except Exception as e:
|
227
|
-
|
228
|
-
|
229
|
-
"success": False,
|
230
|
-
"message": f"Typing failed: {str(e)}"
|
231
|
-
}
|
310
|
+
logger.error(f"Typing action failed: {e}")
|
311
|
+
return {"action": "type", "success": False, "message": f"Typing failed: {str(e)}"}
|
232
312
|
|
233
313
|
def _handle_wait(self, seconds: str) -> Dict[str, Any]:
|
234
|
-
"""
|
314
|
+
"""Handle a simple wait."""
|
235
315
|
try:
|
236
316
|
wait_time = float(seconds)
|
317
|
+
logger.info(f"Waiting for {wait_time} seconds")
|
237
318
|
time.sleep(wait_time)
|
238
|
-
return {
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
319
|
+
return {"action": "wait", "success": True, "message": f"Waited {wait_time} seconds"}
|
320
|
+
except ValueError as e:
|
321
|
+
logger.error(f"Invalid wait time provided: {seconds}")
|
322
|
+
return {"action": "wait", "success": False, "message": "Invalid wait time"}
|
323
|
+
|
324
|
+
def _handle_wait_for_ajax(self, driver: webdriver.Chrome, seconds: str) -> Dict[str, Any]:
|
325
|
+
"""
|
326
|
+
Wait until AJAX/network activity has subsided.
|
327
|
+
This implementation first checks for jQuery, then falls back to a generic check.
|
328
|
+
"""
|
329
|
+
try:
|
330
|
+
timeout = int(seconds)
|
331
|
+
logger.info(f"Waiting for AJAX/network activity for up to {timeout} seconds.")
|
332
|
+
end_time = time.time() + timeout
|
333
|
+
while time.time() < end_time:
|
334
|
+
ajax_complete = driver.execute_script("""
|
335
|
+
return (window.jQuery ? jQuery.active === 0 : true) &&
|
336
|
+
(typeof window.fetch === 'function' ? true : true);
|
337
|
+
""")
|
338
|
+
if ajax_complete:
|
339
|
+
break
|
340
|
+
time.sleep(0.5)
|
341
|
+
return {"action": "wait_for_ajax", "success": True, "message": "AJAX/network activity subsided."}
|
342
|
+
except Exception as e:
|
343
|
+
logger.error(f"Wait for AJAX failed: {e}")
|
344
|
+
return {"action": "wait_for_ajax", "success": False, "message": f"Wait for AJAX failed: {str(e)}"}
|
249
345
|
|
250
|
-
def _handle_scroll(self, driver, selector: str) -> Dict[str, Any]:
|
251
|
-
"""
|
346
|
+
def _handle_scroll(self, driver: webdriver.Chrome, selector: str) -> Dict[str, Any]:
|
347
|
+
"""Handle scrolling to a specific element or page bottom."""
|
252
348
|
try:
|
253
349
|
if selector:
|
254
|
-
element = WebDriverWait(driver,
|
350
|
+
element = WebDriverWait(driver, self.default_timeout).until(
|
255
351
|
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
|
256
352
|
)
|
257
|
-
driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth'});", element)
|
353
|
+
driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", element)
|
354
|
+
scroll_target = selector
|
258
355
|
else:
|
259
356
|
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
|
260
|
-
|
261
|
-
return {
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
357
|
+
scroll_target = "page bottom"
|
358
|
+
return {"action": "scroll", "success": True, "message": f"Scrolled to {scroll_target}"}
|
359
|
+
except Exception as e:
|
360
|
+
logger.error(f"Scroll action failed on selector {selector}: {e}")
|
361
|
+
return {"action": "scroll", "success": False, "message": f"Scroll failed: {str(e)}"}
|
362
|
+
|
363
|
+
def _handle_hover(self, driver: webdriver.Chrome, selector: str) -> Dict[str, Any]:
|
364
|
+
"""Handle mouse hover action."""
|
365
|
+
try:
|
366
|
+
element = WebDriverWait(driver, self.default_timeout).until(
|
367
|
+
EC.visibility_of_element_located((By.CSS_SELECTOR, selector))
|
368
|
+
)
|
369
|
+
ActionChains(driver).move_to_element(element).perform()
|
370
|
+
return {"action": "hover", "success": True, "message": f"Hovered over {selector}"}
|
371
|
+
except Exception as e:
|
372
|
+
logger.error(f"Hover action failed on selector {selector}: {e}")
|
373
|
+
return {"action": "hover", "success": False, "message": f"Hover failed: {str(e)}"}
|
374
|
+
|
375
|
+
def _handle_screenshot(self, driver: webdriver.Chrome, filename: str) -> Dict[str, Any]:
|
376
|
+
"""Capture a screenshot of the current browser state."""
|
377
|
+
try:
|
378
|
+
driver.save_screenshot(filename)
|
379
|
+
return {"action": "screenshot", "success": True, "message": f"Screenshot saved as {filename}"}
|
380
|
+
except Exception as e:
|
381
|
+
logger.error(f"Screenshot capture failed: {e}")
|
382
|
+
return {"action": "screenshot", "success": False, "message": f"Screenshot failed: {str(e)}"}
|
383
|
+
|
384
|
+
def _handle_switch_tab(self, driver: webdriver.Chrome, value: str) -> Dict[str, Any]:
|
385
|
+
"""
|
386
|
+
Switch between tabs. 'value' can be an index or the keyword 'new'.
|
387
|
+
"""
|
388
|
+
try:
|
389
|
+
handles = driver.window_handles
|
390
|
+
if value.lower() == "new":
|
391
|
+
target_handle = handles[-1]
|
392
|
+
else:
|
393
|
+
idx = int(value)
|
394
|
+
if idx < len(handles):
|
395
|
+
target_handle = handles[idx]
|
396
|
+
else:
|
397
|
+
return {"action": "switch_tab", "success": False, "message": f"Tab index {value} out of range"}
|
398
|
+
driver.switch_to.window(target_handle)
|
399
|
+
return {"action": "switch_tab", "success": True, "message": f"Switched to tab {value}"}
|
400
|
+
except Exception as e:
|
401
|
+
logger.error(f"Switch tab failed: {e}")
|
402
|
+
return {"action": "switch_tab", "success": False, "message": f"Switch tab failed: {str(e)}"}
|
403
|
+
|
404
|
+
def _handle_execute_script(self, driver: webdriver.Chrome, script: str) -> Dict[str, Any]:
|
405
|
+
"""
|
406
|
+
Execute arbitrary JavaScript code.
|
407
|
+
"""
|
408
|
+
try:
|
409
|
+
result = driver.execute_script(script)
|
410
|
+
return {"action": "execute_script", "success": True, "message": "Script executed successfully", "result": result}
|
411
|
+
except Exception as e:
|
412
|
+
logger.error(f"Execute script failed: {e}")
|
413
|
+
return {"action": "execute_script", "success": False, "message": f"Script execution failed: {str(e)}"}
|
414
|
+
|
415
|
+
def _handle_drag_and_drop(self, driver: webdriver.Chrome, source_selector: str, target_selector: str) -> Dict[str, Any]:
|
416
|
+
"""
|
417
|
+
Simulate a drag-and-drop operation.
|
418
|
+
"""
|
419
|
+
try:
|
420
|
+
source = WebDriverWait(driver, self.default_timeout).until(
|
421
|
+
EC.presence_of_element_located((By.CSS_SELECTOR, source_selector))
|
422
|
+
)
|
423
|
+
target = WebDriverWait(driver, self.default_timeout).until(
|
424
|
+
EC.presence_of_element_located((By.CSS_SELECTOR, target_selector))
|
425
|
+
)
|
426
|
+
ActionChains(driver).drag_and_drop(source, target).perform()
|
427
|
+
return {"action": "drag_and_drop", "success": True, "message": f"Dragged element from {source_selector} to {target_selector}"}
|
428
|
+
except Exception as e:
|
429
|
+
logger.error(f"Drag and drop failed from {source_selector} to {target_selector}: {e}")
|
430
|
+
return {"action": "drag_and_drop", "success": False, "message": f"Drag and drop failed: {str(e)}"}
|
431
|
+
|
432
|
+
def _capture_failure_screenshot(self, driver: webdriver.Chrome, action: str):
|
433
|
+
"""Capture a screenshot for debugging when an error occurs."""
|
434
|
+
filename = f"failure_{action}_{int(time.time())}.png"
|
435
|
+
try:
|
436
|
+
driver.save_screenshot(filename)
|
437
|
+
logger.info(f"Failure screenshot captured: {filename}")
|
266
438
|
except Exception as e:
|
267
|
-
|
268
|
-
"action": "scroll",
|
269
|
-
"success": False,
|
270
|
-
"message": f"Scroll failed: {str(e)}"
|
271
|
-
}
|
439
|
+
logger.error(f"Failed to capture screenshot: {e}")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
semantio/__init__.py,sha256=
|
1
|
+
semantio/__init__.py,sha256=RIeSI07dGyWBK-STKIk4IeB4bkn_3-QEKQklzSvR7hQ,82
|
2
2
|
semantio/agent.py,sha256=uPFz1WP2eb-z-tryQOX8necS8_tv4Il6qxNmZux9hNk,31709
|
3
3
|
semantio/memory.py,sha256=en9n3UySnj4rA0x3uR1sEdEzA7EkboQNbEHQ5KuEehw,2115
|
4
4
|
semantio/models.py,sha256=7hmP-F_aSU8WvsG3NGeC_hep-rUbiSbjUFMDVbpKxQE,289
|
@@ -30,16 +30,16 @@ semantio/tools/base_tool.py,sha256=xBNSa_8a8WmA4BGRLG2dE7wj9GnBcZo7-P2SyD86GvY,5
|
|
30
30
|
semantio/tools/crypto.py,sha256=mut1ztvpPcUUP3b563dh_FmKtP68KmNis3Qm8WENj8w,5559
|
31
31
|
semantio/tools/duckduckgo.py,sha256=6mGn0js0cIsVxQlAgB8AYNLP05H8WmJKnSVosiO9iH0,5034
|
32
32
|
semantio/tools/stocks.py,sha256=BVuK61O9OmWQjj0YdiCJY6TzpiFJ_An1UJB2RkDfX2k,5393
|
33
|
-
semantio/tools/web_browser.py,sha256=
|
33
|
+
semantio/tools/web_browser.py,sha256=ZjE-nrRtUEUoh6LNutMjdn9_bTlnVGU2PM2w6orjUWA,22172
|
34
34
|
semantio/utils/__init__.py,sha256=Lx4X4iJpRhZzRmpQb80XXh5Ve8ZMOkadWAxXSmHpO_8,244
|
35
35
|
semantio/utils/config.py,sha256=ZTwUTqxjW3-w94zoU7GzivWyJe0JJGvBfuB4RUOuEs8,1198
|
36
36
|
semantio/utils/date_utils.py,sha256=x3oqRGv6ee_KCJ0LvCqqZh_FSgS6YGOHBwZQS4TJetY,1471
|
37
37
|
semantio/utils/file_utils.py,sha256=b_cMuJINEGk9ikNuNHSn9lsmICWwvtnCDZ03ndH_S2I,1779
|
38
38
|
semantio/utils/logger.py,sha256=TmGbP8BRjLMWjXi2GWzZ0RIXt70x9qX3FuIqghCNlwM,510
|
39
39
|
semantio/utils/validation_utils.py,sha256=iwoxEb4Q5ILqV6tbesMjPWPCCoL3AmPLejGUy6q8YvQ,1284
|
40
|
-
semantio-0.0.
|
41
|
-
semantio-0.0.
|
42
|
-
semantio-0.0.
|
43
|
-
semantio-0.0.
|
44
|
-
semantio-0.0.
|
45
|
-
semantio-0.0.
|
40
|
+
semantio-0.0.6.dist-info/LICENSE,sha256=mziLlfb9hZ8HKxm9V6BiHpmgJvmcDvswu1QBlDB-6vU,1074
|
41
|
+
semantio-0.0.6.dist-info/METADATA,sha256=SeDpiywG59La7HcAQxZHxk8uXLFoyEv3otMFYkJl5W4,6913
|
42
|
+
semantio-0.0.6.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
|
43
|
+
semantio-0.0.6.dist-info/entry_points.txt,sha256=zbPgevSLwcLpdRHqI_atE8EOt8lK2vRF1AoDflDTo18,53
|
44
|
+
semantio-0.0.6.dist-info/top_level.txt,sha256=Yte_6mb-bh-I_lQwMjk1GijZkxPoX4Zmp3kBftC1ZlA,9
|
45
|
+
semantio-0.0.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|