simplex 1.2.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simplex might be problematic. Click here for more details.
- {simplex-1.2.1 → simplex-1.2.3}/PKG-INFO +1 -1
- {simplex-1.2.1 → simplex-1.2.3}/setup.py +1 -1
- {simplex-1.2.1 → simplex-1.2.3}/simplex/simplex.py +100 -21
- {simplex-1.2.1 → simplex-1.2.3}/simplex.egg-info/PKG-INFO +1 -1
- {simplex-1.2.1 → simplex-1.2.3}/tests/test_local.py +67 -10
- {simplex-1.2.1 → simplex-1.2.3}/LICENSE +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/README.md +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/pyproject.toml +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/setup.cfg +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex/__init__.py +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex/constants.py +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex/utils.py +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex.egg-info/SOURCES.txt +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex.egg-info/dependency_links.txt +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex.egg-info/entry_points.txt +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex.egg-info/requires.txt +0 -0
- {simplex-1.2.1 → simplex-1.2.3}/simplex.egg-info/top_level.txt +0 -0
|
@@ -26,12 +26,11 @@ class Simplex:
|
|
|
26
26
|
self.driver = self.browser.new_page()
|
|
27
27
|
else:
|
|
28
28
|
self.playwright = None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
self.driver = self.browser.new_page()
|
|
29
|
+
self.driver = self.browser.contexts[0].pages[0]
|
|
30
|
+
self.driver.set_default_navigation_timeout(0)
|
|
31
|
+
self.driver.set_default_timeout(0)
|
|
33
32
|
|
|
34
|
-
def
|
|
33
|
+
def extract_bbox(self, element_description: str, state: Image.Image | None = None, annotate: bool = True) -> List[int]:
|
|
35
34
|
"""
|
|
36
35
|
Find an element in the screenshot using the element description
|
|
37
36
|
|
|
@@ -42,6 +41,7 @@ class Simplex:
|
|
|
42
41
|
Returns:
|
|
43
42
|
bounding_box (tuple): [x1, y1, x2, y2] bounding box of the found element
|
|
44
43
|
"""
|
|
44
|
+
print(f"[SIMPLEX] Finding element \"{element_description}\"...")
|
|
45
45
|
if state is None:
|
|
46
46
|
state = self.take_stable_screenshot()
|
|
47
47
|
|
|
@@ -58,7 +58,7 @@ class Simplex:
|
|
|
58
58
|
'element_description': (None, element_description),
|
|
59
59
|
'api_key': (None, self.api_key)
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
print("[SIMPLEX] Sending screenshot to server...")
|
|
62
62
|
# Make the request
|
|
63
63
|
response = requests.post(
|
|
64
64
|
endpoint,
|
|
@@ -67,7 +67,6 @@ class Simplex:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
# Print the results
|
|
70
|
-
print(f"Status Code: {response.status_code}")
|
|
71
70
|
if response.status_code == 200:
|
|
72
71
|
res = response.json()
|
|
73
72
|
bbox = [int(res['x1']), int(res['y1']), int(res['x2']), int(res['y2'])]
|
|
@@ -87,7 +86,7 @@ class Simplex:
|
|
|
87
86
|
const overlay = document.createElement('div');
|
|
88
87
|
overlay.id = 'simplex-bbox-overlay';
|
|
89
88
|
overlay.style.position = 'fixed';
|
|
90
|
-
overlay.style.border = '2px dashed rgba(
|
|
89
|
+
overlay.style.border = '2px dashed rgba(0, 255, 0, 1)';
|
|
91
90
|
overlay.style.background = 'rgba(74, 144, 226, 0.1)';
|
|
92
91
|
overlay.style.animation = 'marching-ants 0.5s linear infinite';
|
|
93
92
|
overlay.style.left = bbox[0] + 'px';
|
|
@@ -124,7 +123,7 @@ class Simplex:
|
|
|
124
123
|
self.driver.wait_for_selector('#simplex-bbox-overlay')
|
|
125
124
|
return bbox
|
|
126
125
|
else:
|
|
127
|
-
print("Error:", response.text)
|
|
126
|
+
print("[SIMPLEX] Error:", response.text)
|
|
128
127
|
|
|
129
128
|
def step_to_action(self, step_description: str, state: Image.Image | None = None) -> List[List[str]]:
|
|
130
129
|
"""
|
|
@@ -168,7 +167,7 @@ class Simplex:
|
|
|
168
167
|
actions = [[action.strip() for action in action_pair] for action_pair in actions]
|
|
169
168
|
return actions
|
|
170
169
|
else:
|
|
171
|
-
print(f"Error: {response.status_code}")
|
|
170
|
+
print(f"[SIMPLEX] Error: {response.status_code}")
|
|
172
171
|
print(response.text)
|
|
173
172
|
return []
|
|
174
173
|
|
|
@@ -182,8 +181,40 @@ class Simplex:
|
|
|
182
181
|
"""
|
|
183
182
|
if new_tab:
|
|
184
183
|
self.driver = self.browser.new_page()
|
|
184
|
+
self.driver.wait_for_load_state()
|
|
185
|
+
print(f"[SIMPLEX] Navigating to URL {url}...")
|
|
185
186
|
self.driver.goto(url)
|
|
186
187
|
|
|
188
|
+
def click(self, element_description: str, annotate: bool = True) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Click on an element
|
|
191
|
+
"""
|
|
192
|
+
self.execute_action(["CLICK", element_description], annotate=annotate)
|
|
193
|
+
|
|
194
|
+
def type(self, text: str) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Type text into an element
|
|
197
|
+
"""
|
|
198
|
+
self.execute_action(["TYPE", text])
|
|
199
|
+
|
|
200
|
+
def press_enter(self, annotate: bool = True) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Press enter
|
|
203
|
+
"""
|
|
204
|
+
self.execute_action(["ENTER", ""], annotate=annotate)
|
|
205
|
+
|
|
206
|
+
def scroll(self, scroll_amount: int, annotate: bool = True) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Scroll the page
|
|
209
|
+
"""
|
|
210
|
+
self.execute_action(["SCROLL", scroll_amount], annotate=annotate)
|
|
211
|
+
|
|
212
|
+
def wait(self, wait_time: int, annotate: bool = True) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Wait for a given amount of time
|
|
215
|
+
"""
|
|
216
|
+
self.execute_action(["WAIT", wait_time], annotate=annotate)
|
|
217
|
+
|
|
187
218
|
def execute_action(self, action: List[List[str]], state: Image.Image | None = None, annotate: bool = True) -> None:
|
|
188
219
|
"""
|
|
189
220
|
Execute an action with playwright driver
|
|
@@ -192,34 +223,30 @@ class Simplex:
|
|
|
192
223
|
action (List[List[str]]): List of actions to perform
|
|
193
224
|
"""
|
|
194
225
|
action_type, description = action
|
|
195
|
-
if state is None:
|
|
196
|
-
state = self.take_stable_screenshot()
|
|
197
|
-
|
|
198
226
|
try:
|
|
199
227
|
if action_type == "CLICK":
|
|
200
|
-
bbox = self.
|
|
228
|
+
bbox = self.extract_bbox(description, state, annotate=annotate)
|
|
201
229
|
center_x, center_y = center_bbox(bbox)
|
|
202
230
|
self.driver.mouse.click(center_x, center_y)
|
|
203
|
-
|
|
204
|
-
elif action_type == "HOVER":
|
|
205
|
-
bbox = self.find_element(description, state, annotate=annotate)
|
|
206
|
-
center_x, center_y = center_bbox(bbox)
|
|
207
|
-
self.driver.mouse.move(center_x, center_y)
|
|
208
|
-
|
|
231
|
+
print(f"[SIMPLEX] Clicked on element \"{description}\"")
|
|
209
232
|
elif action_type == "TYPE":
|
|
210
233
|
self.driver.keyboard.type(description)
|
|
234
|
+
print(f"[SIMPLEX] Typed \"{description}\"")
|
|
211
235
|
|
|
212
236
|
elif action_type == "ENTER":
|
|
213
237
|
self.driver.keyboard.press("Enter")
|
|
238
|
+
print(f"[SIMPLEX] Pressed enter")
|
|
214
239
|
|
|
215
240
|
elif action_type == "SCROLL":
|
|
216
241
|
self.driver.mouse.wheel(0, int(description))
|
|
242
|
+
print(f"[SIMPLEX] Scrolled {description} pixels")
|
|
217
243
|
|
|
218
244
|
elif action_type == "WAIT":
|
|
219
245
|
self.driver.wait_for_timeout(int(description))
|
|
246
|
+
print(f"[SIMPLEX] Waited {description} seconds")
|
|
220
247
|
|
|
221
248
|
except Exception as e:
|
|
222
|
-
print(f"Error executing action: {e}")
|
|
249
|
+
print(f"[SIMPLEX] Error executing action: {e}")
|
|
223
250
|
return None
|
|
224
251
|
|
|
225
252
|
def do(self, step_description: str, annotate: bool = True) -> None:
|
|
@@ -230,6 +257,57 @@ class Simplex:
|
|
|
230
257
|
actions = self.step_to_action(step_description, state)
|
|
231
258
|
for action in actions:
|
|
232
259
|
self.execute_action(action, annotate=annotate)
|
|
260
|
+
|
|
261
|
+
def extract_text(self, element_description: str, state: Image.Image | None = None) -> List[str] | None:
|
|
262
|
+
"""
|
|
263
|
+
Extract an element text from the page
|
|
264
|
+
"""
|
|
265
|
+
print(f"[SIMPLEX] Finding element \"{element_description}\"...")
|
|
266
|
+
if state is None:
|
|
267
|
+
state = self.take_stable_screenshot()
|
|
268
|
+
|
|
269
|
+
endpoint = f"{BASE_URL}/extract-text"
|
|
270
|
+
|
|
271
|
+
# Convert PIL Image to bytes
|
|
272
|
+
img_byte_arr = io.BytesIO()
|
|
273
|
+
state.save(img_byte_arr, format='PNG')
|
|
274
|
+
img_byte_arr = img_byte_arr.getvalue()
|
|
275
|
+
|
|
276
|
+
# Prepare multipart form data
|
|
277
|
+
files = {
|
|
278
|
+
'image_data': ('screenshot.png', img_byte_arr, 'image/png'),
|
|
279
|
+
'element_description': (None, element_description),
|
|
280
|
+
'api_key': (None, self.api_key)
|
|
281
|
+
}
|
|
282
|
+
print("[SIMPLEX] Sending screenshot to server...")
|
|
283
|
+
|
|
284
|
+
# Make the request
|
|
285
|
+
response = requests.post(
|
|
286
|
+
endpoint,
|
|
287
|
+
files=files
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Print the results
|
|
291
|
+
if response.status_code == 200:
|
|
292
|
+
res = response.json()
|
|
293
|
+
text = res['text']
|
|
294
|
+
return text
|
|
295
|
+
else:
|
|
296
|
+
print("[SIMPLEX] Error:", response.text)
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
def extract_image(self, element_description: str, state: Image.Image | None = None) -> Image.Image | None:
|
|
300
|
+
"""
|
|
301
|
+
Extract an element image from the page
|
|
302
|
+
"""
|
|
303
|
+
if state is None:
|
|
304
|
+
state = self.take_stable_screenshot()
|
|
305
|
+
|
|
306
|
+
bbox = self.extract_bbox(element_description, state)
|
|
307
|
+
cropped_state = state.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
|
|
308
|
+
return cropped_state
|
|
309
|
+
|
|
310
|
+
|
|
233
311
|
|
|
234
312
|
def take_stable_screenshot(self) -> Image.Image:
|
|
235
313
|
"""
|
|
@@ -238,6 +316,7 @@ class Simplex:
|
|
|
238
316
|
Returns:
|
|
239
317
|
PIL.Image.Image: Screenshot of the current page
|
|
240
318
|
"""
|
|
319
|
+
print("[SIMPLEX] Taking screenshot of the page...")
|
|
241
320
|
self.driver.wait_for_load_state('networkidle')
|
|
242
321
|
return screenshot_to_image(self.driver.screenshot())
|
|
243
322
|
|
|
@@ -9,6 +9,7 @@ from playwright.sync_api import sync_playwright
|
|
|
9
9
|
from PIL import Image
|
|
10
10
|
import time
|
|
11
11
|
import os
|
|
12
|
+
from browserbase import Browserbase
|
|
12
13
|
from hyperbrowser import Hyperbrowser
|
|
13
14
|
from hyperbrowser.models.session import CreateSessionParams
|
|
14
15
|
|
|
@@ -153,14 +154,32 @@ def test_find_element():
|
|
|
153
154
|
def test_find_element_2():
|
|
154
155
|
|
|
155
156
|
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"))
|
|
156
|
-
simplex.goto("https://www.cgtrader.com/")
|
|
157
|
-
|
|
157
|
+
# simplex.goto("https://www.cgtrader.com/")
|
|
158
|
+
state = Image.open("nvidia-stock.png")
|
|
159
|
+
bbox = simplex.extract_bbox("nvidia stock graph", state)
|
|
160
|
+
print(bbox)
|
|
161
|
+
|
|
162
|
+
state.crop(bbox)
|
|
163
|
+
state.save("cropped_state.png")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
158
167
|
|
|
168
|
+
|
|
169
|
+
import requests
|
|
159
170
|
def test_hyperbrowser_integration():
|
|
160
171
|
"""Test Simplex integration with Hyperbrowser"""
|
|
172
|
+
import time
|
|
173
|
+
start_time = time.time()
|
|
174
|
+
|
|
175
|
+
bb = Browserbase(api_key=os.getenv("BROWSERBASE_API_KEY"))
|
|
176
|
+
session_start = time.time()
|
|
177
|
+
session = bb.sessions.create(project_id=os.getenv("BROWSERBASE_PROJECT_ID"))
|
|
178
|
+
print(session.id)
|
|
179
|
+
print(f"Session creation took {time.time() - session_start:.2f}s")
|
|
161
180
|
|
|
162
181
|
# Initialize Hyperbrowser client
|
|
163
|
-
client = Hyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))
|
|
182
|
+
# client = Hyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))
|
|
164
183
|
|
|
165
184
|
# Create session params with CAPTCHA solving enabled
|
|
166
185
|
session_params = CreateSessionParams(
|
|
@@ -169,28 +188,45 @@ def test_hyperbrowser_integration():
|
|
|
169
188
|
)
|
|
170
189
|
|
|
171
190
|
# Create a new session with params
|
|
172
|
-
session = client.sessions.create(session_params)
|
|
173
|
-
ws_endpoint = session.
|
|
191
|
+
# session = client.sessions.create(session_params)
|
|
192
|
+
ws_endpoint = session.connect_url
|
|
174
193
|
|
|
175
194
|
try:
|
|
176
195
|
with sync_playwright() as p:
|
|
177
196
|
# Connect browser to Hyperbrowser session
|
|
197
|
+
browser_start = time.time()
|
|
178
198
|
browser = p.chromium.connect_over_cdp(ws_endpoint)
|
|
179
|
-
|
|
199
|
+
print(f"Browser connection took {time.time() - browser_start:.2f}s")
|
|
200
|
+
url = f"https://api.browserbase.com/v1/sessions/{session.id}/debug"
|
|
201
|
+
headers = {"X-BB-API-Key": os.getenv("BROWSERBASE_API_KEY")}
|
|
202
|
+
response = requests.get(url, headers=headers)
|
|
203
|
+
print(response.text)
|
|
204
|
+
print(response.json()["pages"][0]["debuggerUrl"])
|
|
205
|
+
|
|
180
206
|
# Initialize Simplex with the Hyperbrowser-connected page
|
|
181
207
|
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"), browser=browser)
|
|
182
208
|
|
|
183
209
|
# Test basic functionality
|
|
210
|
+
nav_start = time.time()
|
|
184
211
|
simplex.goto("https://www.turbosquid.com/")
|
|
185
|
-
|
|
212
|
+
print(f"Navigation took {time.time() - nav_start:.2f}s")
|
|
213
|
+
|
|
214
|
+
# search_start = time.time()
|
|
215
|
+
# simplex.do("search for iphone")
|
|
216
|
+
# print(f"Search action took {time.time() - search_start:.2f}s")
|
|
186
217
|
|
|
187
218
|
# Verify the search worked by finding the search results
|
|
188
|
-
|
|
219
|
+
find_start = time.time()
|
|
220
|
+
bbox = simplex.find_element("search results", annotate=True)
|
|
221
|
+
print(f"Finding results took {time.time() - find_start:.2f}s")
|
|
189
222
|
assert bbox is not None, "Search results not found"
|
|
190
223
|
|
|
191
224
|
finally:
|
|
192
225
|
# Always stop the Hyperbrowser session
|
|
193
|
-
client.sessions.stop(session.id)
|
|
226
|
+
# client.sessions.stop(session.id)
|
|
227
|
+
browser.close()
|
|
228
|
+
print(f"Total test time: {time.time() - start_time:.2f}s")
|
|
229
|
+
|
|
194
230
|
|
|
195
231
|
|
|
196
232
|
def execute_action_test():
|
|
@@ -216,7 +252,28 @@ def execute_action_test():
|
|
|
216
252
|
browser.close()
|
|
217
253
|
playwright.stop()
|
|
218
254
|
|
|
255
|
+
|
|
256
|
+
def test_extract_text():
|
|
257
|
+
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"))
|
|
258
|
+
simplex.goto("https://www.cgtrader.com/3d-models/electronics/other/apple-watch-ultra-2022-hq-3d-model")
|
|
259
|
+
time.sleep(2)
|
|
260
|
+
start = time.time()
|
|
261
|
+
text = simplex.extract_text("type of license")
|
|
262
|
+
end = time.time()
|
|
263
|
+
print(f"Time taken: {end - start:.2f}s")
|
|
264
|
+
print(text)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_extract_image():
|
|
268
|
+
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"))
|
|
269
|
+
state = Image.open("nvidia-stock.png")
|
|
270
|
+
image = simplex.extract_image("stock trends and market summary", state)
|
|
271
|
+
image.save("cropped_image.png")
|
|
272
|
+
|
|
273
|
+
print(type(simplex.driver))
|
|
274
|
+
|
|
275
|
+
|
|
219
276
|
if __name__ == "__main__":
|
|
220
|
-
|
|
277
|
+
test_extract_image()
|
|
221
278
|
|
|
222
279
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|