sunholo 0.74.6__tar.gz → 0.74.8__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.
- {sunholo-0.74.6 → sunholo-0.74.8}/PKG-INFO +2 -2
- {sunholo-0.74.6 → sunholo-0.74.8}/setup.py +1 -1
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/tools/web_browser.py +108 -44
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo.egg-info/PKG-INFO +2 -2
- {sunholo-0.74.6 → sunholo-0.74.8}/LICENSE.txt +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/MANIFEST.in +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/README.md +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/setup.cfg +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/flask/qna_routes.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/flask/vac_routes.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/langserve.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/route.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/agents/swagger.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/archive/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/archive/archive.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/auth/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/auth/run.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/bots/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/bots/discord.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/bots/webapp.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/images.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/publish.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/cli.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/configs.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/deploy.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/embedder.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/cli/swagger.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/components/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/components/llm.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/components/retriever.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/alloydb.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/database.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/lancedb.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/database/uuid.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/gcs/add_file.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/invoke/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/llamaindex/generate.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/logging.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/patches/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/patches/langchain/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/patches/langchain/lancedb.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/patches/langchain/vertexai.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/qna/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/qna/parsers.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/qna/retry.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/tools/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/api_key.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/big_context.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/config.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/config_class.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/gcp.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/parsers.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/utils/version.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/vertex/init.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo/vertex/safety.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo.egg-info/SOURCES.txt +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo.egg-info/requires.txt +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/tests/test_chat_history.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/tests/test_chunker.py +0 -0
- {sunholo-0.74.6 → sunholo-0.74.8}/tests/test_config.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.74.
|
|
3
|
+
Version: 0.74.8
|
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
|
5
5
|
Home-page: https://github.com/sunholo-data/sunholo-py
|
|
6
|
-
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.74.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.74.8.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
import urllib.parse
|
|
6
6
|
import time
|
|
7
|
+
from io import BytesIO
|
|
7
8
|
|
|
8
9
|
from ..logging import log
|
|
9
10
|
|
|
@@ -115,7 +116,14 @@ class BrowseWebWithImagePromptsBot:
|
|
|
115
116
|
```
|
|
116
117
|
"""
|
|
117
118
|
#class BrowseWebWithImagePromptsBot:
|
|
118
|
-
def __init__(
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
website_name:str,
|
|
122
|
+
session_id: str=None,
|
|
123
|
+
browser_type:str='chromium',
|
|
124
|
+
headless:bool=True,
|
|
125
|
+
max_steps:int=10
|
|
126
|
+
):
|
|
119
127
|
try:
|
|
120
128
|
from playwright.sync_api import sync_playwright
|
|
121
129
|
except ImportError as err:
|
|
@@ -125,12 +133,19 @@ class BrowseWebWithImagePromptsBot:
|
|
|
125
133
|
if not sync_playwright:
|
|
126
134
|
raise ImportError("playright needed for BrowseWebWithImagePromptsBot class - install via `pip install sunholo[tools]`")
|
|
127
135
|
|
|
128
|
-
|
|
136
|
+
log.info(f"Starting BrowseWebWithImagePromptsBot with {website_name=}, {session_id=}, {browser_type=}, {headless=}, {max_steps=}")
|
|
137
|
+
|
|
138
|
+
# Assign session_id if it is None or 'None'
|
|
139
|
+
if not session_id or session_id == 'None':
|
|
140
|
+
self.session_id = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
141
|
+
else:
|
|
142
|
+
self.session_id = session_id
|
|
143
|
+
|
|
129
144
|
self.website_name = website_name
|
|
130
|
-
self.browser_type = browser_type
|
|
131
|
-
self.max_steps = max_steps
|
|
145
|
+
self.browser_type = browser_type or 'chromium'
|
|
146
|
+
self.max_steps = int(max_steps)
|
|
132
147
|
self.steps = 0
|
|
133
|
-
self.screenshot_dir = f"browser_tool/{get_clean_website_name(website_name)}/{session_id}"
|
|
148
|
+
self.screenshot_dir = f"browser_tool/{get_clean_website_name(website_name)}/{self.session_id}"
|
|
134
149
|
os.makedirs(self.screenshot_dir, exist_ok=True)
|
|
135
150
|
self.cookie_file = os.path.join(self.screenshot_dir, "cookies.json")
|
|
136
151
|
self.action_log_file = os.path.join(self.screenshot_dir, "action_log.json")
|
|
@@ -189,27 +204,57 @@ class BrowseWebWithImagePromptsBot:
|
|
|
189
204
|
if status != 200:
|
|
190
205
|
log.error(f"Failed to navigate to {url}: HTTP {status}")
|
|
191
206
|
self.action_log.append(f"Tried to navigate to {url} but failed: HTTP {status} - browsing back to {previous_url}")
|
|
192
|
-
|
|
193
|
-
self.page.goto(previous_url)
|
|
207
|
+
self.page.go_back()
|
|
194
208
|
|
|
195
209
|
self.page.wait_for_load_state()
|
|
196
|
-
log.info(f'Navigated to {url}')
|
|
197
|
-
self.action_log.append(f"Navigated to {url}")
|
|
210
|
+
log.info(f'Navigated to {self.page.url}')
|
|
211
|
+
self.action_log.append(f"Navigated to {self.page.url}")
|
|
198
212
|
|
|
199
213
|
except Exception as err:
|
|
200
214
|
log.warning(f"navigate failed with {str(err)}")
|
|
201
215
|
self.action_log.append(f"Tried to navigate to {url} but got an error")
|
|
216
|
+
|
|
217
|
+
def get_locator_via_roles_and_text(self, selector: str):
|
|
218
|
+
interactive_roles = ["button", "link", "menuitem", "menuitemcheckbox", "menuitemradio", "tab", "option"]
|
|
219
|
+
|
|
220
|
+
for role in interactive_roles:
|
|
221
|
+
log.info(f'Trying role {role} for selector {selector}')
|
|
222
|
+
elements = self.page.get_by_role(role).get_by_text(selector).locator("visible=true").all()
|
|
223
|
+
if elements:
|
|
224
|
+
log.info(f"Got {len(elements)} elements for selector {selector} with role {role}")
|
|
225
|
+
for element in elements:
|
|
226
|
+
try:
|
|
227
|
+
log.info(f"Trying {selector} with element.hover locator: {element}")
|
|
228
|
+
try:
|
|
229
|
+
element.hover(timeout=10000, trial=True)
|
|
230
|
+
self.action_log.append(f"Successfully found element via selector: {selector}")
|
|
231
|
+
|
|
232
|
+
return element
|
|
233
|
+
|
|
234
|
+
except Exception as err:
|
|
235
|
+
log.warning(f"Could not hover over element: {element} {str(err)} - trying next element")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
log.error(f"Failed to get locator for selector '{selector}' with role {role}: {str(e)}")
|
|
238
|
+
|
|
239
|
+
time.sleep(0.5) # Wait for a bit before retrying
|
|
240
|
+
|
|
241
|
+
log.info(f"No elements for '{selector}' within role '{role}'")
|
|
242
|
+
|
|
243
|
+
self.action_log.append(f"FAILED: Using page.get_by_role('role').locator('text={selector}').locator('visible=true') could not find any valid element. Try something else.")
|
|
244
|
+
return None
|
|
202
245
|
|
|
203
246
|
def get_locator(self, selector, by_text=True):
|
|
204
247
|
if by_text:
|
|
205
|
-
|
|
206
|
-
if elements:
|
|
207
|
-
return elements[0]
|
|
208
|
-
else:
|
|
209
|
-
log.warning(f"No elements found with text: {selector}")
|
|
210
|
-
return None
|
|
248
|
+
element = self.get_locator_via_roles_and_text(selector)
|
|
211
249
|
else:
|
|
212
|
-
|
|
250
|
+
element = self.page.locator(selector)
|
|
251
|
+
|
|
252
|
+
if element:
|
|
253
|
+
return element
|
|
254
|
+
|
|
255
|
+
log.error(f"Failed to get locator for selector {selector}")
|
|
256
|
+
|
|
257
|
+
return None
|
|
213
258
|
|
|
214
259
|
def click(self, selector, by_text=True):
|
|
215
260
|
(x,y)=(0,0)
|
|
@@ -230,14 +275,14 @@ class BrowseWebWithImagePromptsBot:
|
|
|
230
275
|
try:
|
|
231
276
|
element.click()
|
|
232
277
|
self.page.wait_for_load_state()
|
|
233
|
-
log.info(f"Clicked on element with
|
|
234
|
-
self.action_log.append(f"Clicked on element with
|
|
278
|
+
log.info(f"Clicked on {element=} with {selector=} at {x=},{y=}")
|
|
279
|
+
self.action_log.append(f"Clicked on {element=} with {selector=} at {x=},{y=}")
|
|
235
280
|
|
|
236
281
|
return (x,y)
|
|
237
282
|
|
|
238
283
|
except Exception as err:
|
|
239
284
|
log.warning(f"click failed with {str(err)}")
|
|
240
|
-
self.action_log.append(f"Tried to click on element with
|
|
285
|
+
self.action_log.append(f"Tried to click on {element=} with {selector=} at {x=},{y=} but got an error")
|
|
241
286
|
|
|
242
287
|
return (x,y)
|
|
243
288
|
|
|
@@ -262,7 +307,7 @@ class BrowseWebWithImagePromptsBot:
|
|
|
262
307
|
(x,y)=(0,0)
|
|
263
308
|
element = self.get_locator(selector, by_text=by_text)
|
|
264
309
|
if element is None:
|
|
265
|
-
self.action_log.append(f"Tried to type {text} via website text: {selector} but it was not a valid
|
|
310
|
+
self.action_log.append(f"Tried to type {text} via website text: {selector} but it was not a valid element to add text")
|
|
266
311
|
return (x,y)
|
|
267
312
|
|
|
268
313
|
try:
|
|
@@ -276,32 +321,38 @@ class BrowseWebWithImagePromptsBot:
|
|
|
276
321
|
try:
|
|
277
322
|
element.fill(text)
|
|
278
323
|
self.page.wait_for_load_state()
|
|
279
|
-
log.info(f"Typed text '{text}' into element with
|
|
280
|
-
self.action_log.append(f"Typed text '{text}' into element with
|
|
324
|
+
log.info(f"Typed text '{text}' into {element=} with {selector=} at {x=},{y=}")
|
|
325
|
+
self.action_log.append(f"Typed text '{text}' into {element=} with {selector=} at {x=},{y=}")
|
|
281
326
|
|
|
282
327
|
return (x, y)
|
|
283
328
|
|
|
284
329
|
except Exception as err:
|
|
285
330
|
log.warning(f"Typed text failed with {str(err)}")
|
|
286
|
-
self.action_log.append(f"Tried to type text '{text}' into element with
|
|
331
|
+
self.action_log.append(f"Tried to type text '{text}' into {element=} with {selector=} at {x=},{y=} but got an error")
|
|
287
332
|
|
|
288
333
|
return (x, y)
|
|
289
334
|
|
|
290
|
-
def take_screenshot(self,
|
|
335
|
+
def take_screenshot(self, full_page=False, mark_action=None):
|
|
336
|
+
|
|
337
|
+
from PIL import Image
|
|
338
|
+
|
|
291
339
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
292
340
|
parsed_url = urllib.parse.urlparse(self.page.url)
|
|
293
341
|
|
|
294
342
|
url_path = parsed_url.path
|
|
295
343
|
if url_path == "/":
|
|
296
344
|
url_path = "index.html"
|
|
297
|
-
if final:
|
|
298
|
-
screenshot_path = os.path.join(self.screenshot_dir, f"final/{timestamp}_{url_path}.png")
|
|
299
345
|
else:
|
|
300
|
-
|
|
301
|
-
|
|
346
|
+
url_path = url_path.replace("/","_")
|
|
347
|
+
screenshot_path = os.path.join(self.screenshot_dir, f"{timestamp}_{url_path}.png")
|
|
348
|
+
screenshot_bytes = self.page.screenshot(full_page=full_page, scale='css')
|
|
302
349
|
|
|
303
350
|
if mark_action:
|
|
304
|
-
self.mark_screenshot(
|
|
351
|
+
image = self.mark_screenshot(screenshot_bytes, mark_action)
|
|
352
|
+
else:
|
|
353
|
+
image = Image.open(BytesIO(screenshot_bytes))
|
|
354
|
+
|
|
355
|
+
image.save(screenshot_path, format='png')
|
|
305
356
|
|
|
306
357
|
log.info(f"Screenshot {self.page.url} taken and saved to {screenshot_path}")
|
|
307
358
|
#self.action_log.append(f"Screenshot {self.page.url} taken and saved to {screenshot_path}")
|
|
@@ -309,17 +360,17 @@ class BrowseWebWithImagePromptsBot:
|
|
|
309
360
|
|
|
310
361
|
return screenshot_path
|
|
311
362
|
|
|
312
|
-
def mark_screenshot(self,
|
|
363
|
+
def mark_screenshot(self, screenshot_bytes, mark_action):
|
|
313
364
|
"""
|
|
314
365
|
Marks the screenshot with the specified action.
|
|
315
366
|
|
|
316
367
|
Parameters:
|
|
317
|
-
|
|
368
|
+
screenshot_bytes (bytes): The bytes of the screenshot.
|
|
318
369
|
mark_action (dict): Action details for marking the screenshot.
|
|
319
370
|
"""
|
|
320
371
|
from PIL import Image, ImageDraw
|
|
321
|
-
|
|
322
|
-
image = Image.open(
|
|
372
|
+
time.sleep(1) # maybe pass in bytes to avoid waiting for file to be written
|
|
373
|
+
image = Image.open(BytesIO(screenshot_bytes))
|
|
323
374
|
draw = ImageDraw.Draw(image)
|
|
324
375
|
|
|
325
376
|
if mark_action['type'] == 'click':
|
|
@@ -330,7 +381,7 @@ class BrowseWebWithImagePromptsBot:
|
|
|
330
381
|
x, y = mark_action['position']
|
|
331
382
|
draw.rectangle((x-5, y-5, x+5, y+5), outline='blue', width=3)
|
|
332
383
|
|
|
333
|
-
image
|
|
384
|
+
return image
|
|
334
385
|
|
|
335
386
|
def get_latest_screenshot_path(self):
|
|
336
387
|
screenshots = sorted(
|
|
@@ -357,12 +408,18 @@ class BrowseWebWithImagePromptsBot:
|
|
|
357
408
|
output = json.loads(response)
|
|
358
409
|
elif isinstance(response, list):
|
|
359
410
|
log.warning(f'Response was a list, assuming its only new_instructions: {response=}')
|
|
360
|
-
output
|
|
361
|
-
|
|
362
|
-
|
|
411
|
+
output = {
|
|
412
|
+
'new_instructions' : response,
|
|
413
|
+
'status': 'in-progress',
|
|
414
|
+
'message': 'No message was received, which is a mistake by the assistant'
|
|
415
|
+
}
|
|
363
416
|
else:
|
|
364
417
|
log.warning(f'Unknown response: {response=} {type(response)}')
|
|
365
418
|
output = None
|
|
419
|
+
|
|
420
|
+
if not output:
|
|
421
|
+
log.error(f'Got no output from response: {response=}')
|
|
422
|
+
return None
|
|
366
423
|
|
|
367
424
|
if 'status' not in output:
|
|
368
425
|
log.error(f'Response did not contain status')
|
|
@@ -395,6 +452,8 @@ This method should be implemented by subclasses: `def send_prompt_to_llm(self, p
|
|
|
395
452
|
""")
|
|
396
453
|
|
|
397
454
|
def close(self):
|
|
455
|
+
log.info(f"Session {self.session_id} finished")
|
|
456
|
+
self.take_screenshot()
|
|
398
457
|
self.save_cookies()
|
|
399
458
|
self.browser.close()
|
|
400
459
|
self.playwright.stop()
|
|
@@ -443,7 +502,7 @@ This method should be implemented by subclasses: `def send_prompt_to_llm(self, p
|
|
|
443
502
|
|
|
444
503
|
return next_browser_instructions
|
|
445
504
|
|
|
446
|
-
def create_gif_from_pngs(self, frame_duration=
|
|
505
|
+
def create_gif_from_pngs(self, frame_duration=300):
|
|
447
506
|
"""
|
|
448
507
|
Creates a GIF from a folder of PNG images.
|
|
449
508
|
|
|
@@ -500,9 +559,17 @@ This method should be implemented by subclasses: `def send_prompt_to_llm(self, p
|
|
|
500
559
|
log.error('Browser status: "in-progress" but no new_instructions')
|
|
501
560
|
last_message = next_instructions['message']
|
|
502
561
|
self.action_log.append(last_message)
|
|
503
|
-
|
|
504
|
-
next_instructions
|
|
505
|
-
|
|
562
|
+
try:
|
|
563
|
+
next_instructions = self.execute_instructions(
|
|
564
|
+
next_instructions['new_instructions'],
|
|
565
|
+
last_message=last_message)
|
|
566
|
+
except Exception as err:
|
|
567
|
+
log.error(f'session aborted due to: {str(err)}')
|
|
568
|
+
next_instructions = {
|
|
569
|
+
'status': 'error',
|
|
570
|
+
'message': f'session errored with: {str(err)}'
|
|
571
|
+
}
|
|
572
|
+
break
|
|
506
573
|
else:
|
|
507
574
|
log.info(f'Session finished due to status={next_instructions["status"]}')
|
|
508
575
|
in_session=False
|
|
@@ -512,9 +579,6 @@ This method should be implemented by subclasses: `def send_prompt_to_llm(self, p
|
|
|
512
579
|
in_session=False
|
|
513
580
|
break
|
|
514
581
|
|
|
515
|
-
log.info("Session finished")
|
|
516
|
-
self.take_screenshot()
|
|
517
|
-
|
|
518
582
|
self.close()
|
|
519
583
|
|
|
520
584
|
answer = None
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.74.
|
|
3
|
+
Version: 0.74.8
|
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
|
5
5
|
Home-page: https://github.com/sunholo-data/sunholo-py
|
|
6
|
-
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.74.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.74.8.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|