optexity 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- optexity/examples/__init__.py +0 -0
- optexity/examples/add_example.py +88 -0
- optexity/examples/download_pdf_url.py +29 -0
- optexity/examples/extract_price_stockanalysis.py +44 -0
- optexity/examples/file_upload.py +59 -0
- optexity/examples/i94.py +126 -0
- optexity/examples/i94_travel_history.py +126 -0
- optexity/examples/peachstate_medicaid.py +201 -0
- optexity/examples/supabase_login.py +75 -0
- optexity/inference/__init__.py +0 -0
- optexity/inference/agents/__init__.py +0 -0
- optexity/inference/agents/error_handler/__init__.py +0 -0
- optexity/inference/agents/error_handler/error_handler.py +39 -0
- optexity/inference/agents/error_handler/prompt.py +60 -0
- optexity/inference/agents/index_prediction/__init__.py +0 -0
- optexity/inference/agents/index_prediction/action_prediction_locator_axtree.py +45 -0
- optexity/inference/agents/index_prediction/prompt.py +14 -0
- optexity/inference/agents/select_value_prediction/__init__.py +0 -0
- optexity/inference/agents/select_value_prediction/prompt.py +20 -0
- optexity/inference/agents/select_value_prediction/select_value_prediction.py +39 -0
- optexity/inference/agents/two_fa_extraction/__init__.py +0 -0
- optexity/inference/agents/two_fa_extraction/prompt.py +23 -0
- optexity/inference/agents/two_fa_extraction/two_fa_extraction.py +47 -0
- optexity/inference/child_process.py +251 -0
- optexity/inference/core/__init__.py +0 -0
- optexity/inference/core/interaction/__init__.py +0 -0
- optexity/inference/core/interaction/handle_agentic_task.py +79 -0
- optexity/inference/core/interaction/handle_check.py +57 -0
- optexity/inference/core/interaction/handle_click.py +79 -0
- optexity/inference/core/interaction/handle_command.py +261 -0
- optexity/inference/core/interaction/handle_input.py +76 -0
- optexity/inference/core/interaction/handle_keypress.py +16 -0
- optexity/inference/core/interaction/handle_select.py +109 -0
- optexity/inference/core/interaction/handle_select_utils.py +132 -0
- optexity/inference/core/interaction/handle_upload.py +59 -0
- optexity/inference/core/interaction/utils.py +81 -0
- optexity/inference/core/logging.py +406 -0
- optexity/inference/core/run_assertion.py +55 -0
- optexity/inference/core/run_automation.py +463 -0
- optexity/inference/core/run_extraction.py +240 -0
- optexity/inference/core/run_interaction.py +254 -0
- optexity/inference/core/run_python_script.py +20 -0
- optexity/inference/core/run_two_fa.py +120 -0
- optexity/inference/core/two_factor_auth/__init__.py +0 -0
- optexity/inference/infra/__init__.py +0 -0
- optexity/inference/infra/browser.py +455 -0
- optexity/inference/infra/browser_extension.py +20 -0
- optexity/inference/models/__init__.py +22 -0
- optexity/inference/models/gemini.py +113 -0
- optexity/inference/models/human.py +20 -0
- optexity/inference/models/llm_model.py +210 -0
- optexity/inference/run_local.py +200 -0
- optexity/schema/__init__.py +0 -0
- optexity/schema/actions/__init__.py +0 -0
- optexity/schema/actions/assertion_action.py +66 -0
- optexity/schema/actions/extraction_action.py +143 -0
- optexity/schema/actions/interaction_action.py +330 -0
- optexity/schema/actions/misc_action.py +18 -0
- optexity/schema/actions/prompts.py +27 -0
- optexity/schema/actions/two_fa_action.py +24 -0
- optexity/schema/automation.py +432 -0
- optexity/schema/callback.py +16 -0
- optexity/schema/inference.py +87 -0
- optexity/schema/memory.py +100 -0
- optexity/schema/task.py +212 -0
- optexity/schema/token_usage.py +48 -0
- optexity/utils/__init__.py +0 -0
- optexity/utils/settings.py +54 -0
- optexity/utils/utils.py +76 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/METADATA +1 -1
- optexity-0.1.3.dist-info/RECORD +80 -0
- optexity-0.1.2.dist-info/RECORD +0 -11
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/WHEEL +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/entry_points.txt +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {optexity-0.1.2.dist-info → optexity-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from optexity.inference.core.run_extraction import handle_llm_extraction
|
|
5
|
+
from optexity.inference.infra.browser import Browser
|
|
6
|
+
from optexity.inference.models import GeminiModels, get_llm_model
|
|
7
|
+
from optexity.schema.actions.assertion_action import AssertionAction, LLMAssertion
|
|
8
|
+
from optexity.schema.memory import Memory
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
llm_model = get_llm_model(GeminiModels.GEMINI_2_5_FLASH, True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def run_assertion_action(
|
|
16
|
+
assertion_action: AssertionAction, memory: Memory, browser: Browser
|
|
17
|
+
):
|
|
18
|
+
logger.debug(
|
|
19
|
+
f"---------Running assertion action {assertion_action.model_dump_json()}---------"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if assertion_action.llm:
|
|
23
|
+
await handle_llm_assertion(assertion_action.llm, memory, browser)
|
|
24
|
+
elif assertion_action.network_call:
|
|
25
|
+
raise ValueError("Network call assertions are not supported yet")
|
|
26
|
+
# await handle_network_call_assertion(
|
|
27
|
+
# assertion_action.network_call, memory, browser
|
|
28
|
+
# )
|
|
29
|
+
elif assertion_action.python_script:
|
|
30
|
+
raise ValueError("Python script assertions are not supported yet")
|
|
31
|
+
# await handle_python_script_assertion(
|
|
32
|
+
# assertion_action.python_script, memory, browser
|
|
33
|
+
# )
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def handle_llm_assertion(
|
|
37
|
+
llm_assertion: LLMAssertion, memory: Memory, browser: Browser
|
|
38
|
+
):
|
|
39
|
+
extra_instruction = """You are a helpful assistant that verifies if the condition is met.
|
|
40
|
+
Use the info supplied below to verify the condition.
|
|
41
|
+
The assertion_reason should be a short explanation of why the condition was met or not met.
|
|
42
|
+
The assertion_result should be True if the condition is met, False otherwise.
|
|
43
|
+
"""
|
|
44
|
+
llm_assertion_new = deepcopy(llm_assertion)
|
|
45
|
+
llm_assertion_new.extraction_instructions = (
|
|
46
|
+
extra_instruction + "\n" + llm_assertion_new.extraction_instructions
|
|
47
|
+
)
|
|
48
|
+
output_data = await handle_llm_extraction(llm_assertion_new, memory, browser)
|
|
49
|
+
|
|
50
|
+
if output_data.json_data["assertion_result"]:
|
|
51
|
+
return True
|
|
52
|
+
else:
|
|
53
|
+
raise AssertionError(
|
|
54
|
+
f"Assertion failed on node {memory.automation_state.step_index}: {output_data.json_data['assertion_reason']}"
|
|
55
|
+
)
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
import traceback
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
from patchright._impl._errors import TimeoutError as PatchrightTimeoutError
|
|
8
|
+
from playwright._impl._errors import TimeoutError as PlaywrightTimeoutError
|
|
9
|
+
|
|
10
|
+
from optexity.inference.core.interaction.utils import clean_download
|
|
11
|
+
from optexity.inference.core.logging import (
|
|
12
|
+
complete_task_in_server,
|
|
13
|
+
delete_local_data,
|
|
14
|
+
initiate_callback,
|
|
15
|
+
save_downloads_in_server,
|
|
16
|
+
save_latest_memory_state_locally,
|
|
17
|
+
save_output_data_in_server,
|
|
18
|
+
save_trajectory_in_server,
|
|
19
|
+
start_task_in_server,
|
|
20
|
+
)
|
|
21
|
+
from optexity.inference.core.run_assertion import run_assertion_action
|
|
22
|
+
from optexity.inference.core.run_extraction import run_extraction_action
|
|
23
|
+
from optexity.inference.core.run_interaction import (
|
|
24
|
+
handle_download_url_as_pdf,
|
|
25
|
+
run_interaction_action,
|
|
26
|
+
)
|
|
27
|
+
from optexity.inference.core.run_python_script import run_python_script_action
|
|
28
|
+
from optexity.inference.core.run_two_fa import run_two_fa_action
|
|
29
|
+
from optexity.inference.infra.browser import Browser
|
|
30
|
+
from optexity.schema.actions.interaction_action import DownloadUrlAsPdfAction
|
|
31
|
+
from optexity.schema.automation import ActionNode, ForLoopNode, IfElseNode
|
|
32
|
+
from optexity.schema.memory import BrowserState, ForLoopStatus, Memory, OutputData
|
|
33
|
+
from optexity.schema.task import Task
|
|
34
|
+
from optexity.utils.settings import settings
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
# TODO: static check that index for all replacement of input variables are within the bounds of the input variables
|
|
39
|
+
|
|
40
|
+
# TODO: static check that all for loop expansion for generated variables have some place where generated variables are added to the memory
|
|
41
|
+
|
|
42
|
+
# TODO: Check that all for loop expansion for generated variables have some place where generated variables are added to the memory
|
|
43
|
+
|
|
44
|
+
# TODO: give a warning where any variable of type {variable_name[index]} is used but variable_name is not in the memory in generated variables or in input variables
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def run_automation(task: Task, child_process_id: int):
|
|
48
|
+
file_handler = logging.FileHandler(str(task.log_file_path))
|
|
49
|
+
file_handler.setLevel(logging.DEBUG)
|
|
50
|
+
|
|
51
|
+
current_module = __name__.split(".")[0] # top-level module/package
|
|
52
|
+
logging.getLogger(current_module).addHandler(file_handler)
|
|
53
|
+
logging.getLogger("browser_use").setLevel(logging.INFO)
|
|
54
|
+
|
|
55
|
+
logger.info(f"Task {task.task_id} started running")
|
|
56
|
+
memory = None
|
|
57
|
+
browser = None
|
|
58
|
+
try:
|
|
59
|
+
await start_task_in_server(task)
|
|
60
|
+
memory = Memory()
|
|
61
|
+
browser = Browser(
|
|
62
|
+
memory=memory,
|
|
63
|
+
headless=False,
|
|
64
|
+
channel=task.automation.browser_channel,
|
|
65
|
+
debug_port=9222 + child_process_id,
|
|
66
|
+
use_proxy=task.use_proxy,
|
|
67
|
+
proxy_session_id=task.proxy_session_id(
|
|
68
|
+
settings.PROXY_PROVIDER if task.use_proxy else None
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
await browser.start()
|
|
72
|
+
|
|
73
|
+
automation = task.automation
|
|
74
|
+
|
|
75
|
+
memory.automation_state.step_index = -1
|
|
76
|
+
memory.automation_state.try_index = 0
|
|
77
|
+
|
|
78
|
+
if task.use_proxy:
|
|
79
|
+
|
|
80
|
+
await browser.go_to_url("https://ipinfo.io/json")
|
|
81
|
+
page = await browser.get_current_page()
|
|
82
|
+
|
|
83
|
+
ip_info = await page.evaluate(
|
|
84
|
+
"""
|
|
85
|
+
async () => {
|
|
86
|
+
const res = await fetch("https://ipinfo.io/json");
|
|
87
|
+
return await res.json();
|
|
88
|
+
}
|
|
89
|
+
"""
|
|
90
|
+
)
|
|
91
|
+
if isinstance(ip_info, dict):
|
|
92
|
+
memory.variables.output_data.append(
|
|
93
|
+
OutputData(unique_identifier="ip_info", json_data=ip_info)
|
|
94
|
+
)
|
|
95
|
+
elif isinstance(ip_info, str):
|
|
96
|
+
memory.variables.output_data.append(
|
|
97
|
+
OutputData(unique_identifier="ip_info", text=ip_info)
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
try:
|
|
101
|
+
memory.variables.output_data.append(
|
|
102
|
+
OutputData(unique_identifier="ip_info", text=str(ip_info))
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Error getting IP info: {e}")
|
|
106
|
+
|
|
107
|
+
await browser.go_to_url(task.automation.url)
|
|
108
|
+
|
|
109
|
+
full_automation = []
|
|
110
|
+
|
|
111
|
+
for node in automation.nodes:
|
|
112
|
+
if isinstance(node, ForLoopNode):
|
|
113
|
+
await handle_for_loop_node(node, memory, task, browser, full_automation)
|
|
114
|
+
elif isinstance(node, IfElseNode):
|
|
115
|
+
await handle_if_else_node(node, memory, task, browser, full_automation)
|
|
116
|
+
else:
|
|
117
|
+
full_automation.append(node.model_dump())
|
|
118
|
+
await run_action_node(
|
|
119
|
+
node,
|
|
120
|
+
task,
|
|
121
|
+
memory,
|
|
122
|
+
browser,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
task.status = "success"
|
|
126
|
+
except AssertionError as e:
|
|
127
|
+
logger.error(f"Assertion error: {e}")
|
|
128
|
+
task.error = str(e)
|
|
129
|
+
task.status = "failed"
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error running automation: {traceback.format_exc()}")
|
|
132
|
+
task.error = str(e)
|
|
133
|
+
task.status = "failed"
|
|
134
|
+
finally:
|
|
135
|
+
if task and memory:
|
|
136
|
+
await run_final_downloads_check(task, memory, browser)
|
|
137
|
+
if memory and browser:
|
|
138
|
+
await run_final_logging(task, memory, browser, child_process_id)
|
|
139
|
+
if browser:
|
|
140
|
+
await browser.stop()
|
|
141
|
+
|
|
142
|
+
logger.info(f"Task {task.task_id} completed with status {task.status}")
|
|
143
|
+
logging.getLogger(current_module).removeHandler(file_handler)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def run_final_downloads_check(task: Task, memory: Memory, browser: Browser):
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
logger.debug("Running final downloads check")
|
|
150
|
+
max_timeout = 10.0
|
|
151
|
+
start = time.monotonic()
|
|
152
|
+
await asyncio.wait_for(
|
|
153
|
+
browser.all_active_downloads_done.wait(), timeout=max_timeout
|
|
154
|
+
)
|
|
155
|
+
max_timeout = max(0.0, max_timeout - (time.monotonic() - start))
|
|
156
|
+
|
|
157
|
+
for temp_download_path, (
|
|
158
|
+
is_downloaded,
|
|
159
|
+
download,
|
|
160
|
+
) in memory.raw_downloads.items():
|
|
161
|
+
if is_downloaded:
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
download_path = task.downloads_directory / download.suggested_filename
|
|
165
|
+
await download.save_as(download_path)
|
|
166
|
+
memory.downloads.append(download_path)
|
|
167
|
+
await clean_download(download_path)
|
|
168
|
+
memory.raw_downloads[temp_download_path] = (True, download)
|
|
169
|
+
|
|
170
|
+
while max_timeout > 0:
|
|
171
|
+
if (
|
|
172
|
+
len(memory.urls_to_downloads) + len(memory.downloads)
|
|
173
|
+
>= task.automation.expected_downloads
|
|
174
|
+
):
|
|
175
|
+
break
|
|
176
|
+
interval = min(1, max_timeout)
|
|
177
|
+
await asyncio.sleep(interval)
|
|
178
|
+
max_timeout = max(0.0, max_timeout - interval)
|
|
179
|
+
|
|
180
|
+
for url, filename in memory.urls_to_downloads:
|
|
181
|
+
download_path = task.downloads_directory / filename
|
|
182
|
+
await handle_download_url_as_pdf(
|
|
183
|
+
DownloadUrlAsPdfAction(url=url, download_filename=filename),
|
|
184
|
+
task,
|
|
185
|
+
memory,
|
|
186
|
+
browser,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Error running final downloads check: {e}")
|
|
191
|
+
|
|
192
|
+
logger.warning(
|
|
193
|
+
f"Found {len(memory.downloads)} downloads, expected {task.automation.expected_downloads}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async def run_final_logging(
|
|
198
|
+
task: Task, memory: Memory, browser: Browser, child_process_id: int
|
|
199
|
+
):
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
await complete_task_in_server(task, memory.token_usage, child_process_id)
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
memory.automation_state.step_index += 1
|
|
206
|
+
browser_state_summary = await browser.get_browser_state_summary()
|
|
207
|
+
memory.browser_states.append(
|
|
208
|
+
BrowserState(
|
|
209
|
+
url=browser_state_summary.url,
|
|
210
|
+
screenshot=browser_state_summary.screenshot,
|
|
211
|
+
title=browser_state_summary.title,
|
|
212
|
+
axtree=browser_state_summary.dom_state.llm_representation(),
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
memory.final_screenshot = await browser.get_screenshot(full_page=True)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(f"Error getting final screenshot: {e}")
|
|
219
|
+
|
|
220
|
+
await save_output_data_in_server(task, memory)
|
|
221
|
+
await save_downloads_in_server(task, memory)
|
|
222
|
+
await save_latest_memory_state_locally(task, memory, None)
|
|
223
|
+
await save_trajectory_in_server(task, memory)
|
|
224
|
+
await initiate_callback(task)
|
|
225
|
+
await delete_local_data(task)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"Error running final logging: {e}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
async def run_action_node(
|
|
232
|
+
action_node: ActionNode,
|
|
233
|
+
task: Task,
|
|
234
|
+
memory: Memory,
|
|
235
|
+
browser: Browser,
|
|
236
|
+
):
|
|
237
|
+
|
|
238
|
+
await asyncio.sleep(action_node.before_sleep_time)
|
|
239
|
+
await browser.handle_new_tabs(0)
|
|
240
|
+
|
|
241
|
+
memory.automation_state.step_index += 1
|
|
242
|
+
memory.automation_state.try_index = 0
|
|
243
|
+
|
|
244
|
+
await action_node.replace_variables(task.input_parameters)
|
|
245
|
+
await action_node.replace_variables(task.secure_parameters)
|
|
246
|
+
await action_node.replace_variables(memory.variables.generated_variables)
|
|
247
|
+
|
|
248
|
+
# ## TODO: optimize this by taking screenshot and axtree only if needed
|
|
249
|
+
# browser_state_summary = await browser.get_browser_state_summary()
|
|
250
|
+
|
|
251
|
+
memory.browser_states.append(
|
|
252
|
+
BrowserState(
|
|
253
|
+
url=await browser.get_current_page_url(),
|
|
254
|
+
screenshot=None,
|
|
255
|
+
title=await browser.get_current_page_title(),
|
|
256
|
+
axtree=None,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
logger.debug(f"-----Running node {memory.automation_state.step_index}-----")
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
if action_node.interaction_action:
|
|
264
|
+
## Assuming network calls are only made during interaction actions and not during extraction actions
|
|
265
|
+
await browser.clear_network_calls()
|
|
266
|
+
await browser.attach_network_listeners()
|
|
267
|
+
|
|
268
|
+
await run_interaction_action(
|
|
269
|
+
action_node.interaction_action, task, memory, browser, 2
|
|
270
|
+
)
|
|
271
|
+
elif action_node.extraction_action:
|
|
272
|
+
await run_extraction_action(
|
|
273
|
+
action_node.extraction_action, memory, browser, task
|
|
274
|
+
)
|
|
275
|
+
elif action_node.two_fa_action:
|
|
276
|
+
await run_two_fa_action(action_node.two_fa_action, memory)
|
|
277
|
+
elif action_node.python_script_action:
|
|
278
|
+
await run_python_script_action(
|
|
279
|
+
action_node.python_script_action, memory, browser
|
|
280
|
+
)
|
|
281
|
+
elif action_node.assertion_action:
|
|
282
|
+
await run_assertion_action(action_node.assertion_action, memory, browser)
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.error(f"Error running node {memory.automation_state.step_index}: {e}")
|
|
286
|
+
raise e
|
|
287
|
+
finally:
|
|
288
|
+
await save_latest_memory_state_locally(task, memory, action_node)
|
|
289
|
+
|
|
290
|
+
if action_node.expect_new_tab:
|
|
291
|
+
found_new_tab, total_time = await browser.handle_new_tabs(
|
|
292
|
+
action_node.max_new_tab_wait_time
|
|
293
|
+
)
|
|
294
|
+
if not found_new_tab:
|
|
295
|
+
logger.warning(
|
|
296
|
+
f"No new tab found after {action_node.max_new_tab_wait_time} seconds, even though expect_new_tab is True"
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
logger.debug(f"Switched to new tab after {total_time} seconds, as expected")
|
|
300
|
+
|
|
301
|
+
else:
|
|
302
|
+
await sleep_for_page_to_load(browser, action_node.end_sleep_time)
|
|
303
|
+
|
|
304
|
+
logger.debug(f"-----Finished node {memory.automation_state.step_index}-----")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
async def sleep_for_page_to_load(browser: Browser, sleep_time: float):
|
|
308
|
+
await asyncio.sleep(0.1)
|
|
309
|
+
|
|
310
|
+
sleep_time = max(0.0, sleep_time - 0.1)
|
|
311
|
+
|
|
312
|
+
if float(sleep_time) == 0.0:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
page = await browser.get_current_page()
|
|
316
|
+
if page is None:
|
|
317
|
+
return
|
|
318
|
+
try:
|
|
319
|
+
await page.wait_for_load_state("load", timeout=sleep_time * 1000)
|
|
320
|
+
except TimeoutError as e:
|
|
321
|
+
pass
|
|
322
|
+
except PatchrightTimeoutError as e:
|
|
323
|
+
pass
|
|
324
|
+
except PlaywrightTimeoutError as e:
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def evaluate_condition(condition: str, memory: Memory, task: Task) -> bool:
|
|
329
|
+
return eval(
|
|
330
|
+
condition,
|
|
331
|
+
{},
|
|
332
|
+
{**task.input_parameters, **memory.variables.generated_variables},
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
async def handle_if_else_node(
|
|
337
|
+
if_else_node: IfElseNode,
|
|
338
|
+
memory: Memory,
|
|
339
|
+
task: Task,
|
|
340
|
+
browser: Browser,
|
|
341
|
+
full_automation: list[ActionNode],
|
|
342
|
+
):
|
|
343
|
+
logger.debug(
|
|
344
|
+
f"Handling if else node {if_else_node.condition} with if nodes {if_else_node.if_nodes} and else nodes {if_else_node.else_nodes}"
|
|
345
|
+
)
|
|
346
|
+
condition_result = evaluate_condition(if_else_node.condition, memory, task)
|
|
347
|
+
if condition_result:
|
|
348
|
+
nodes = if_else_node.if_nodes
|
|
349
|
+
else:
|
|
350
|
+
nodes = if_else_node.else_nodes
|
|
351
|
+
|
|
352
|
+
for node in nodes:
|
|
353
|
+
if isinstance(node, ActionNode):
|
|
354
|
+
full_automation.append(node.model_dump())
|
|
355
|
+
await run_action_node(
|
|
356
|
+
node,
|
|
357
|
+
task,
|
|
358
|
+
memory,
|
|
359
|
+
browser,
|
|
360
|
+
)
|
|
361
|
+
elif isinstance(node, IfElseNode):
|
|
362
|
+
await handle_if_else_node(node, memory, task, browser, full_automation)
|
|
363
|
+
elif isinstance(node, ForLoopNode):
|
|
364
|
+
await handle_for_loop_node(node, memory, task, browser, full_automation)
|
|
365
|
+
|
|
366
|
+
logger.debug(f"Finished handling if else node {if_else_node.condition}")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
async def handle_for_loop_node(
|
|
370
|
+
for_loop_node: ForLoopNode,
|
|
371
|
+
memory: Memory,
|
|
372
|
+
task: Task,
|
|
373
|
+
browser: Browser,
|
|
374
|
+
full_automation: list[ActionNode],
|
|
375
|
+
):
|
|
376
|
+
if for_loop_node.variable_name in task.input_parameters:
|
|
377
|
+
values = task.input_parameters[for_loop_node.variable_name]
|
|
378
|
+
elif for_loop_node.variable_name in memory.variables.generated_variables:
|
|
379
|
+
values = memory.variables.generated_variables[for_loop_node.variable_name]
|
|
380
|
+
else:
|
|
381
|
+
raise ValueError(
|
|
382
|
+
f"Variable name {for_loop_node.variable_name} not found in input variables or generated variables"
|
|
383
|
+
)
|
|
384
|
+
memory.variables.for_loop_status.append([])
|
|
385
|
+
for index in range(len(values)):
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
for node in for_loop_node.nodes:
|
|
389
|
+
new_node = deepcopy(node)
|
|
390
|
+
new_node.replace(
|
|
391
|
+
f"{{{for_loop_node.variable_name}[index]}}",
|
|
392
|
+
f"{{{for_loop_node.variable_name}[{index}]}}",
|
|
393
|
+
)
|
|
394
|
+
new_node.replace(
|
|
395
|
+
f"{{index_of({for_loop_node.variable_name})}}", f"{index}"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
if isinstance(new_node, IfElseNode):
|
|
399
|
+
await handle_if_else_node(
|
|
400
|
+
new_node, memory, task, browser, full_automation
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
else:
|
|
404
|
+
full_automation.append(new_node.model_dump())
|
|
405
|
+
await run_action_node(
|
|
406
|
+
new_node,
|
|
407
|
+
task,
|
|
408
|
+
memory,
|
|
409
|
+
browser,
|
|
410
|
+
)
|
|
411
|
+
memory.variables.for_loop_status[-1].append(
|
|
412
|
+
ForLoopStatus(
|
|
413
|
+
variable_name=for_loop_node.variable_name,
|
|
414
|
+
index=index,
|
|
415
|
+
value=values[index],
|
|
416
|
+
status="success",
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
except Exception as e:
|
|
420
|
+
logger.error(
|
|
421
|
+
f"Error running for loop node {for_loop_node.variable_name}: {e}"
|
|
422
|
+
)
|
|
423
|
+
memory.variables.for_loop_status[-1].append(
|
|
424
|
+
ForLoopStatus(
|
|
425
|
+
variable_name=for_loop_node.variable_name,
|
|
426
|
+
index=index,
|
|
427
|
+
value=values[index],
|
|
428
|
+
status="error",
|
|
429
|
+
error=str(e),
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
if for_loop_node.on_error_in_loop == "continue":
|
|
433
|
+
continue
|
|
434
|
+
elif for_loop_node.on_error_in_loop == "break":
|
|
435
|
+
for index2 in range(index + 1, len(values)):
|
|
436
|
+
memory.variables.for_loop_status[-1].append(
|
|
437
|
+
ForLoopStatus(
|
|
438
|
+
variable_name=for_loop_node.variable_name,
|
|
439
|
+
index=index2,
|
|
440
|
+
value=values[index2],
|
|
441
|
+
status="skipped",
|
|
442
|
+
)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
break
|
|
446
|
+
else:
|
|
447
|
+
raise e
|
|
448
|
+
|
|
449
|
+
if index < len(values) - 1:
|
|
450
|
+
for node in for_loop_node.reset_nodes:
|
|
451
|
+
if isinstance(node, IfElseNode):
|
|
452
|
+
await handle_if_else_node(
|
|
453
|
+
node, memory, task, browser, full_automation
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
else:
|
|
457
|
+
full_automation.append(node.model_dump())
|
|
458
|
+
await run_action_node(
|
|
459
|
+
node,
|
|
460
|
+
task,
|
|
461
|
+
memory,
|
|
462
|
+
browser,
|
|
463
|
+
)
|