simplex 1.2.0__tar.gz → 1.2.2__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.0 → simplex-1.2.2}/PKG-INFO +2 -11
- {simplex-1.2.0 → simplex-1.2.2}/setup.py +1 -1
- {simplex-1.2.0 → simplex-1.2.2}/simplex/simplex.py +56 -10
- {simplex-1.2.0 → simplex-1.2.2}/simplex.egg-info/PKG-INFO +2 -11
- {simplex-1.2.0 → simplex-1.2.2}/simplex.egg-info/SOURCES.txt +0 -1
- {simplex-1.2.0 → simplex-1.2.2}/tests/test_local.py +74 -32
- simplex-1.2.0/simplex/constants.py +0 -1
- {simplex-1.2.0 → simplex-1.2.2}/LICENSE +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/README.md +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/pyproject.toml +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/setup.cfg +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/simplex/__init__.py +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/simplex/utils.py +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/simplex.egg-info/dependency_links.txt +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/simplex.egg-info/entry_points.txt +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/simplex.egg-info/requires.txt +0 -0
- {simplex-1.2.0 → simplex-1.2.2}/simplex.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: simplex
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Official Python SDK for Simplex API
|
|
5
5
|
Home-page: https://github.com/shreyka/simplex-python
|
|
6
6
|
Author: Simplex Labs, Inc.
|
|
@@ -21,15 +21,6 @@ Requires-Dist: rich>=13.0.0
|
|
|
21
21
|
Requires-Dist: prompt_toolkit>=3.0.0
|
|
22
22
|
Requires-Dist: playwright>=1.0.0
|
|
23
23
|
Requires-Dist: Pillow>=9.0.0
|
|
24
|
-
Dynamic: author
|
|
25
|
-
Dynamic: author-email
|
|
26
|
-
Dynamic: classifier
|
|
27
|
-
Dynamic: description
|
|
28
|
-
Dynamic: description-content-type
|
|
29
|
-
Dynamic: home-page
|
|
30
|
-
Dynamic: requires-dist
|
|
31
|
-
Dynamic: requires-python
|
|
32
|
-
Dynamic: summary
|
|
33
24
|
|
|
34
25
|
# Simplex AI Python SDK
|
|
35
26
|
|
|
@@ -3,7 +3,6 @@ from PIL import Image
|
|
|
3
3
|
import requests
|
|
4
4
|
from typing import List
|
|
5
5
|
import io
|
|
6
|
-
import time
|
|
7
6
|
|
|
8
7
|
from .utils import center_bbox, screenshot_to_image
|
|
9
8
|
|
|
@@ -24,10 +23,12 @@ class Simplex:
|
|
|
24
23
|
if browser is None:
|
|
25
24
|
self.playwright = sync_playwright().start()
|
|
26
25
|
self.browser = self.playwright.chromium.launch(headless=True)
|
|
26
|
+
self.driver = self.browser.new_page()
|
|
27
27
|
else:
|
|
28
28
|
self.playwright = None
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
self.driver = self.browser.contexts[0].pages[0]
|
|
30
|
+
self.driver.set_default_navigation_timeout(0)
|
|
31
|
+
self.driver.set_default_timeout(0)
|
|
31
32
|
|
|
32
33
|
def find_element(self, element_description: str, state: Image.Image | None = None, annotate: bool = True) -> List[int]:
|
|
33
34
|
"""
|
|
@@ -40,6 +41,7 @@ class Simplex:
|
|
|
40
41
|
Returns:
|
|
41
42
|
bounding_box (tuple): [x1, y1, x2, y2] bounding box of the found element
|
|
42
43
|
"""
|
|
44
|
+
print(f"Finding element \"{element_description}\"...")
|
|
43
45
|
if state is None:
|
|
44
46
|
state = self.take_stable_screenshot()
|
|
45
47
|
|
|
@@ -56,6 +58,7 @@ class Simplex:
|
|
|
56
58
|
'element_description': (None, element_description),
|
|
57
59
|
'api_key': (None, self.api_key)
|
|
58
60
|
}
|
|
61
|
+
print("Sending screenshot to server...")
|
|
59
62
|
# Make the request
|
|
60
63
|
response = requests.post(
|
|
61
64
|
endpoint,
|
|
@@ -83,20 +86,43 @@ class Simplex:
|
|
|
83
86
|
// Create new overlay
|
|
84
87
|
const overlay = document.createElement('div');
|
|
85
88
|
overlay.id = 'simplex-bbox-overlay';
|
|
86
|
-
overlay.style.position = '
|
|
87
|
-
overlay.style.border = '2px
|
|
89
|
+
overlay.style.position = 'fixed';
|
|
90
|
+
overlay.style.border = '2px dashed rgba(74, 144, 226, 1)';
|
|
91
|
+
overlay.style.background = 'rgba(74, 144, 226, 0.1)';
|
|
92
|
+
overlay.style.animation = 'marching-ants 0.5s linear infinite';
|
|
88
93
|
overlay.style.left = bbox[0] + 'px';
|
|
89
94
|
overlay.style.top = bbox[1] + 'px';
|
|
90
95
|
overlay.style.width = (bbox[2] - bbox[0]) + 'px';
|
|
91
96
|
overlay.style.height = (bbox[3] - bbox[1]) + 'px';
|
|
92
97
|
overlay.style.pointerEvents = 'none';
|
|
93
98
|
overlay.style.zIndex = '10000';
|
|
99
|
+
overlay.style.margin = '0';
|
|
100
|
+
overlay.style.padding = '0';
|
|
101
|
+
|
|
102
|
+
// Add marching ants animation keyframes
|
|
103
|
+
if (!document.querySelector('#marching-ants-keyframes')) {
|
|
104
|
+
const style = document.createElement('style');
|
|
105
|
+
style.id = 'marching-ants-keyframes';
|
|
106
|
+
style.textContent = `
|
|
107
|
+
@keyframes marching-ants {
|
|
108
|
+
0% { border-style: dashed; }
|
|
109
|
+
50% { border-style: solid; }
|
|
110
|
+
100% { border-style: dashed; }
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
document.head.appendChild(style);
|
|
114
|
+
}
|
|
94
115
|
|
|
95
116
|
document.body.appendChild(overlay);
|
|
117
|
+
|
|
118
|
+
// Remove overlay after 3 second
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
overlay.remove();
|
|
121
|
+
}, 2000);
|
|
96
122
|
}
|
|
97
123
|
""", bbox)
|
|
98
124
|
self.driver.wait_for_selector('#simplex-bbox-overlay')
|
|
99
|
-
|
|
125
|
+
print(f"Found element \"{element_description}\" at pixel coordinates {bbox}.")
|
|
100
126
|
return bbox
|
|
101
127
|
else:
|
|
102
128
|
print("Error:", response.text)
|
|
@@ -147,10 +173,18 @@ class Simplex:
|
|
|
147
173
|
print(response.text)
|
|
148
174
|
return []
|
|
149
175
|
|
|
150
|
-
def goto(self, url: str) -> None:
|
|
176
|
+
def goto(self, url: str, new_tab: bool = False) -> None:
|
|
151
177
|
"""
|
|
152
178
|
Navigate to a URL
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
url (str): URL to navigate to
|
|
182
|
+
new_tab (bool): Whether to open a new tab or use the current tab
|
|
153
183
|
"""
|
|
184
|
+
if new_tab:
|
|
185
|
+
self.driver = self.browser.new_page()
|
|
186
|
+
self.driver.wait_for_load_state()
|
|
187
|
+
print(f"Navigating to URL {url}...")
|
|
154
188
|
self.driver.goto(url)
|
|
155
189
|
|
|
156
190
|
def execute_action(self, action: List[List[str]], state: Image.Image | None = None, annotate: bool = True) -> None:
|
|
@@ -160,10 +194,8 @@ class Simplex:
|
|
|
160
194
|
Args:
|
|
161
195
|
action (List[List[str]]): List of actions to perform
|
|
162
196
|
"""
|
|
197
|
+
print(f"Executing action {action}...")
|
|
163
198
|
action_type, description = action
|
|
164
|
-
if state is None:
|
|
165
|
-
state = self.take_stable_screenshot()
|
|
166
|
-
|
|
167
199
|
try:
|
|
168
200
|
if action_type == "CLICK":
|
|
169
201
|
bbox = self.find_element(description, state, annotate=annotate)
|
|
@@ -176,13 +208,18 @@ class Simplex:
|
|
|
176
208
|
self.driver.mouse.move(center_x, center_y)
|
|
177
209
|
|
|
178
210
|
elif action_type == "TYPE":
|
|
211
|
+
print(f"Typing \"{description}\"...")
|
|
179
212
|
self.driver.keyboard.type(description)
|
|
180
213
|
|
|
214
|
+
elif action_type == "ENTER":
|
|
215
|
+
self.driver.keyboard.press("Enter")
|
|
216
|
+
|
|
181
217
|
elif action_type == "SCROLL":
|
|
182
218
|
self.driver.mouse.wheel(0, int(description))
|
|
183
219
|
|
|
184
220
|
elif action_type == "WAIT":
|
|
185
221
|
self.driver.wait_for_timeout(int(description))
|
|
222
|
+
|
|
186
223
|
|
|
187
224
|
except Exception as e:
|
|
188
225
|
print(f"Error executing action: {e}")
|
|
@@ -196,6 +233,14 @@ class Simplex:
|
|
|
196
233
|
actions = self.step_to_action(step_description, state)
|
|
197
234
|
for action in actions:
|
|
198
235
|
self.execute_action(action, annotate=annotate)
|
|
236
|
+
|
|
237
|
+
def extract(self, step_description: str, annotate: bool = True) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Extract an element from the page
|
|
240
|
+
"""
|
|
241
|
+
state = self.take_stable_screenshot()
|
|
242
|
+
|
|
243
|
+
# self.execute_action(action, annotate=annotate)
|
|
199
244
|
|
|
200
245
|
def take_stable_screenshot(self) -> Image.Image:
|
|
201
246
|
"""
|
|
@@ -204,6 +249,7 @@ class Simplex:
|
|
|
204
249
|
Returns:
|
|
205
250
|
PIL.Image.Image: Screenshot of the current page
|
|
206
251
|
"""
|
|
252
|
+
print("Taking screenshot of the page...")
|
|
207
253
|
self.driver.wait_for_load_state('networkidle')
|
|
208
254
|
return screenshot_to_image(self.driver.screenshot())
|
|
209
255
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: simplex
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Official Python SDK for Simplex API
|
|
5
5
|
Home-page: https://github.com/shreyka/simplex-python
|
|
6
6
|
Author: Simplex Labs, Inc.
|
|
@@ -21,15 +21,6 @@ Requires-Dist: rich>=13.0.0
|
|
|
21
21
|
Requires-Dist: prompt_toolkit>=3.0.0
|
|
22
22
|
Requires-Dist: playwright>=1.0.0
|
|
23
23
|
Requires-Dist: Pillow>=9.0.0
|
|
24
|
-
Dynamic: author
|
|
25
|
-
Dynamic: author-email
|
|
26
|
-
Dynamic: classifier
|
|
27
|
-
Dynamic: description
|
|
28
|
-
Dynamic: description-content-type
|
|
29
|
-
Dynamic: home-page
|
|
30
|
-
Dynamic: requires-dist
|
|
31
|
-
Dynamic: requires-python
|
|
32
|
-
Dynamic: summary
|
|
33
24
|
|
|
34
25
|
# Simplex AI Python SDK
|
|
35
26
|
|
|
@@ -9,7 +9,9 @@ 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
|
|
14
|
+
from hyperbrowser.models.session import CreateSessionParams
|
|
13
15
|
|
|
14
16
|
from dotenv import load_dotenv
|
|
15
17
|
|
|
@@ -35,15 +37,7 @@ def screenshot_tests():
|
|
|
35
37
|
print(action)
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
playwright = sync_playwright().start()
|
|
40
|
-
browser = playwright.chromium.launch(headless=False)
|
|
41
|
-
driver = browser.new_page()
|
|
42
|
-
|
|
43
|
-
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"), driver=driver)
|
|
44
|
-
simplex.goto("https://www.netflix.com/")
|
|
45
|
-
actions = [['CLICK', 'email field'], ['TYPE', 'email address']]
|
|
46
|
-
simplex.execute_action(actions[0])
|
|
40
|
+
|
|
47
41
|
|
|
48
42
|
|
|
49
43
|
def cgtrader_test():
|
|
@@ -51,8 +45,8 @@ def cgtrader_test():
|
|
|
51
45
|
urls = []
|
|
52
46
|
|
|
53
47
|
with sync_playwright() as p:
|
|
54
|
-
|
|
55
|
-
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"),
|
|
48
|
+
browser = p.chromium.launch(headless=False)
|
|
49
|
+
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"), browser=browser)
|
|
56
50
|
simplex.goto("https://www.cgtrader.com/")
|
|
57
51
|
|
|
58
52
|
for asset in assets:
|
|
@@ -60,7 +54,7 @@ def cgtrader_test():
|
|
|
60
54
|
simplex.do(f"search for {asset}")
|
|
61
55
|
simplex.do("click on search button")
|
|
62
56
|
simplex.do(f"click on the first product")
|
|
63
|
-
driver.wait_for_timeout(3000)
|
|
57
|
+
simplex.driver.wait_for_timeout(3000)
|
|
64
58
|
|
|
65
59
|
urls.append(simplex.driver.url)
|
|
66
60
|
|
|
@@ -158,47 +152,95 @@ def test_find_element():
|
|
|
158
152
|
|
|
159
153
|
|
|
160
154
|
def test_find_element_2():
|
|
161
|
-
with sync_playwright() as p:
|
|
162
|
-
driver = p.chromium.launch(headless=False).new_page(viewport={"width": 1920, "height": 1080})
|
|
163
|
-
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"), driver=driver)
|
|
164
|
-
simplex.goto("https://www.cgtrader.com/")
|
|
165
|
-
|
|
166
|
-
state = simplex.take_stable_screenshot()
|
|
167
|
-
bbox = simplex.find_element("search bar")
|
|
168
|
-
|
|
169
|
-
print(bbox)
|
|
170
155
|
|
|
156
|
+
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"))
|
|
157
|
+
simplex.goto("https://www.cgtrader.com/")
|
|
158
|
+
simplex.find_element("search bar")
|
|
159
|
+
import requests
|
|
171
160
|
def test_hyperbrowser_integration():
|
|
172
161
|
"""Test Simplex integration with Hyperbrowser"""
|
|
162
|
+
import time
|
|
163
|
+
start_time = time.time()
|
|
164
|
+
|
|
165
|
+
bb = Browserbase(api_key=os.getenv("BROWSERBASE_API_KEY"))
|
|
166
|
+
session_start = time.time()
|
|
167
|
+
session = bb.sessions.create(project_id=os.getenv("BROWSERBASE_PROJECT_ID"))
|
|
168
|
+
print(session.id)
|
|
169
|
+
print(f"Session creation took {time.time() - session_start:.2f}s")
|
|
173
170
|
|
|
174
171
|
# Initialize Hyperbrowser client
|
|
175
|
-
client = Hyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))
|
|
172
|
+
# client = Hyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))
|
|
176
173
|
|
|
177
|
-
# Create
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
# Create session params with CAPTCHA solving enabled
|
|
175
|
+
session_params = CreateSessionParams(
|
|
176
|
+
solve_captchas=True,
|
|
177
|
+
use_stealth=True # Recommended when solving CAPTCHAs
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Create a new session with params
|
|
181
|
+
# session = client.sessions.create(session_params)
|
|
182
|
+
ws_endpoint = session.connect_url
|
|
180
183
|
|
|
181
184
|
try:
|
|
182
185
|
with sync_playwright() as p:
|
|
183
186
|
# Connect browser to Hyperbrowser session
|
|
187
|
+
browser_start = time.time()
|
|
184
188
|
browser = p.chromium.connect_over_cdp(ws_endpoint)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
print(f"Browser connection took {time.time() - browser_start:.2f}s")
|
|
190
|
+
url = f"https://api.browserbase.com/v1/sessions/{session.id}/debug"
|
|
191
|
+
headers = {"X-BB-API-Key": os.getenv("BROWSERBASE_API_KEY")}
|
|
192
|
+
response = requests.get(url, headers=headers)
|
|
193
|
+
print(response.text)
|
|
194
|
+
print(response.json()["pages"][0]["debuggerUrl"])
|
|
195
|
+
|
|
188
196
|
# Initialize Simplex with the Hyperbrowser-connected page
|
|
189
197
|
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"), browser=browser)
|
|
190
198
|
|
|
191
199
|
# Test basic functionality
|
|
192
|
-
|
|
193
|
-
simplex.
|
|
200
|
+
nav_start = time.time()
|
|
201
|
+
simplex.goto("https://www.turbosquid.com/")
|
|
202
|
+
print(f"Navigation took {time.time() - nav_start:.2f}s")
|
|
203
|
+
|
|
204
|
+
# search_start = time.time()
|
|
205
|
+
# simplex.do("search for iphone")
|
|
206
|
+
# print(f"Search action took {time.time() - search_start:.2f}s")
|
|
194
207
|
|
|
195
208
|
# Verify the search worked by finding the search results
|
|
196
|
-
|
|
209
|
+
find_start = time.time()
|
|
210
|
+
bbox = simplex.find_element("search results", annotate=True)
|
|
211
|
+
print(f"Finding results took {time.time() - find_start:.2f}s")
|
|
197
212
|
assert bbox is not None, "Search results not found"
|
|
198
213
|
|
|
199
214
|
finally:
|
|
200
215
|
# Always stop the Hyperbrowser session
|
|
201
|
-
client.sessions.stop(session.id)
|
|
216
|
+
# client.sessions.stop(session.id)
|
|
217
|
+
browser.close()
|
|
218
|
+
print(f"Total test time: {time.time() - start_time:.2f}s")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def execute_action_test():
|
|
223
|
+
playwright = sync_playwright().start()
|
|
224
|
+
browser = playwright.chromium.launch(headless=False)
|
|
225
|
+
simplex = Simplex(api_key=os.getenv("SIMPLEX_API_KEY"), browser=browser)
|
|
226
|
+
simplex.goto("https://www.mit.edu/")
|
|
227
|
+
simplex.find_element('search bar')
|
|
228
|
+
|
|
229
|
+
time.sleep(3)
|
|
230
|
+
|
|
231
|
+
# Save HTML content
|
|
232
|
+
html_content = simplex.driver.content()
|
|
233
|
+
with open('page.html', 'w', encoding='utf-8') as f:
|
|
234
|
+
f.write(html_content)
|
|
235
|
+
|
|
236
|
+
# Keep browser open until user interrupts with Ctrl+C
|
|
237
|
+
try:
|
|
238
|
+
print("Browser is kept open. Press Ctrl+C to exit...")
|
|
239
|
+
while True:
|
|
240
|
+
time.sleep(1)
|
|
241
|
+
except KeyboardInterrupt:
|
|
242
|
+
browser.close()
|
|
243
|
+
playwright.stop()
|
|
202
244
|
|
|
203
245
|
if __name__ == "__main__":
|
|
204
246
|
test_hyperbrowser_integration()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
BASE_URL = "https://u3mvtbirxf.us-east-1.awsapprunner.com"
|
|
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
|