griptape-nodes 0.60.3__py3-none-any.whl → 0.61.0__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.
- griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +0 -1
- griptape_nodes/common/macro_parser/__init__.py +16 -1
- griptape_nodes/common/macro_parser/core.py +15 -3
- griptape_nodes/common/macro_parser/exceptions.py +99 -0
- griptape_nodes/common/macro_parser/formats.py +13 -4
- griptape_nodes/common/macro_parser/matching.py +5 -2
- griptape_nodes/common/macro_parser/parsing.py +48 -8
- griptape_nodes/common/macro_parser/resolution.py +23 -5
- griptape_nodes/common/project_templates/__init__.py +49 -0
- griptape_nodes/common/project_templates/default_project_template.py +92 -0
- griptape_nodes/common/project_templates/defaults/README.md +36 -0
- griptape_nodes/common/project_templates/defaults/project_template.yml +89 -0
- griptape_nodes/common/project_templates/directory.py +67 -0
- griptape_nodes/common/project_templates/loader.py +341 -0
- griptape_nodes/common/project_templates/project.py +252 -0
- griptape_nodes/common/project_templates/situation.py +155 -0
- griptape_nodes/common/project_templates/validation.py +140 -0
- griptape_nodes/exe_types/core_types.py +36 -3
- griptape_nodes/exe_types/node_types.py +4 -2
- griptape_nodes/exe_types/param_components/progress_bar_component.py +57 -0
- griptape_nodes/exe_types/param_types/parameter_audio.py +243 -0
- griptape_nodes/exe_types/param_types/parameter_image.py +243 -0
- griptape_nodes/exe_types/param_types/parameter_three_d.py +215 -0
- griptape_nodes/exe_types/param_types/parameter_video.py +243 -0
- griptape_nodes/node_library/workflow_registry.py +1 -1
- griptape_nodes/retained_mode/events/execution_events.py +41 -0
- griptape_nodes/retained_mode/events/node_events.py +90 -1
- griptape_nodes/retained_mode/events/os_events.py +108 -0
- griptape_nodes/retained_mode/events/parameter_events.py +1 -1
- griptape_nodes/retained_mode/events/project_events.py +413 -0
- griptape_nodes/retained_mode/events/workflow_events.py +19 -1
- griptape_nodes/retained_mode/griptape_nodes.py +9 -1
- griptape_nodes/retained_mode/managers/agent_manager.py +18 -24
- griptape_nodes/retained_mode/managers/event_manager.py +6 -9
- griptape_nodes/retained_mode/managers/flow_manager.py +63 -0
- griptape_nodes/retained_mode/managers/library_manager.py +55 -42
- griptape_nodes/retained_mode/managers/mcp_manager.py +14 -6
- griptape_nodes/retained_mode/managers/node_manager.py +232 -0
- griptape_nodes/retained_mode/managers/os_manager.py +346 -1
- griptape_nodes/retained_mode/managers/project_manager.py +617 -0
- griptape_nodes/retained_mode/managers/settings.py +6 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +17 -71
- griptape_nodes/traits/button.py +18 -0
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/METADATA +5 -3
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/RECORD +47 -31
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/entry_points.txt +0 -0
|
@@ -72,6 +72,9 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
72
72
|
SingleNodeStepRequest,
|
|
73
73
|
SingleNodeStepResultFailure,
|
|
74
74
|
SingleNodeStepResultSuccess,
|
|
75
|
+
StartFlowFromNodeRequest,
|
|
76
|
+
StartFlowFromNodeResultFailure,
|
|
77
|
+
StartFlowFromNodeResultSuccess,
|
|
75
78
|
StartFlowRequest,
|
|
76
79
|
StartFlowResultFailure,
|
|
77
80
|
StartFlowResultSuccess,
|
|
@@ -239,6 +242,7 @@ class FlowManager:
|
|
|
239
242
|
event_manager.assign_manager_to_request_type(CreateConnectionRequest, self.on_create_connection_request)
|
|
240
243
|
event_manager.assign_manager_to_request_type(DeleteConnectionRequest, self.on_delete_connection_request)
|
|
241
244
|
event_manager.assign_manager_to_request_type(StartFlowRequest, self.on_start_flow_request)
|
|
245
|
+
event_manager.assign_manager_to_request_type(StartFlowFromNodeRequest, self.on_start_flow_from_node_request)
|
|
242
246
|
event_manager.assign_manager_to_request_type(SingleNodeStepRequest, self.on_single_node_step_request)
|
|
243
247
|
event_manager.assign_manager_to_request_type(SingleExecutionStepRequest, self.on_single_execution_step_request)
|
|
244
248
|
event_manager.assign_manager_to_request_type(
|
|
@@ -2451,6 +2455,65 @@ class FlowManager:
|
|
|
2451
2455
|
|
|
2452
2456
|
return StartFlowResultSuccess(result_details=details)
|
|
2453
2457
|
|
|
2458
|
+
async def on_start_flow_from_node_request(self, request: StartFlowFromNodeRequest) -> ResultPayload: # noqa: C901, PLR0911
|
|
2459
|
+
# which flow
|
|
2460
|
+
flow_name = request.flow_name
|
|
2461
|
+
if not flow_name:
|
|
2462
|
+
details = "Must provide flow name to start a flow."
|
|
2463
|
+
|
|
2464
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2465
|
+
# get the flow by ID
|
|
2466
|
+
try:
|
|
2467
|
+
flow = self.get_flow_by_name(flow_name)
|
|
2468
|
+
except KeyError as err:
|
|
2469
|
+
details = f"Cannot start flow. Error: {err}"
|
|
2470
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=[err], result_details=details)
|
|
2471
|
+
# Check to see if the flow is already running.
|
|
2472
|
+
if self.check_for_existing_running_flow():
|
|
2473
|
+
details = "Cannot start flow. Flow is already running."
|
|
2474
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=[], result_details=details)
|
|
2475
|
+
node_name = request.node_name
|
|
2476
|
+
if node_name is None:
|
|
2477
|
+
details = "Must provide node name to start a flow."
|
|
2478
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=[], result_details=details)
|
|
2479
|
+
start_node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(node_name, BaseNode)
|
|
2480
|
+
if not start_node:
|
|
2481
|
+
details = f"Provided node with name {node_name} does not exist"
|
|
2482
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2483
|
+
result = await self.on_validate_flow_dependencies_request(
|
|
2484
|
+
ValidateFlowDependenciesRequest(flow_name=flow_name, flow_node_name=start_node.name if start_node else None)
|
|
2485
|
+
)
|
|
2486
|
+
try:
|
|
2487
|
+
if not result.succeeded():
|
|
2488
|
+
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed"
|
|
2489
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=[], result_details=details)
|
|
2490
|
+
result = cast("ValidateFlowDependenciesResultSuccess", result)
|
|
2491
|
+
|
|
2492
|
+
if not result.validation_succeeded:
|
|
2493
|
+
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed."
|
|
2494
|
+
if len(result.exceptions) > 0:
|
|
2495
|
+
for exception in result.exceptions:
|
|
2496
|
+
details = f"{details}\n\t{exception}"
|
|
2497
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=result.exceptions, result_details=details)
|
|
2498
|
+
except Exception as e:
|
|
2499
|
+
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed: {e}"
|
|
2500
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=[e], result_details=details)
|
|
2501
|
+
# By now, it has been validated with no exceptions.
|
|
2502
|
+
try:
|
|
2503
|
+
await self.start_flow(
|
|
2504
|
+
flow,
|
|
2505
|
+
start_node,
|
|
2506
|
+
debug_mode=request.debug_mode,
|
|
2507
|
+
pickle_control_flow_result=request.pickle_control_flow_result,
|
|
2508
|
+
)
|
|
2509
|
+
except Exception as e:
|
|
2510
|
+
details = f"Failed to kick off flow with name {flow_name}. Exception occurred: {e} "
|
|
2511
|
+
return StartFlowFromNodeResultFailure(validation_exceptions=[e], result_details=details)
|
|
2512
|
+
|
|
2513
|
+
details = f"Successfully kicked off flow with name {flow_name}"
|
|
2514
|
+
|
|
2515
|
+
return StartFlowFromNodeResultSuccess(result_details=details)
|
|
2516
|
+
|
|
2454
2517
|
def on_get_flow_state_request(self, event: GetFlowStateRequest) -> ResultPayload:
|
|
2455
2518
|
flow_name = event.flow_name
|
|
2456
2519
|
if not flow_name:
|
|
@@ -169,6 +169,7 @@ class LibraryManager:
|
|
|
169
169
|
self._library_event_handler_mappings: dict[type[Payload], dict[str, LibraryManager.RegisteredEventHandler]] = {}
|
|
170
170
|
# LibraryDirectory owns the FSMs and manages library lifecycle
|
|
171
171
|
self._library_directory = LibraryDirectory()
|
|
172
|
+
self._libraries_loading_complete = asyncio.Event()
|
|
172
173
|
|
|
173
174
|
event_manager.assign_manager_to_request_type(
|
|
174
175
|
ListRegisteredLibrariesRequest, self.on_list_registered_libraries_request
|
|
@@ -204,7 +205,7 @@ class LibraryManager:
|
|
|
204
205
|
)
|
|
205
206
|
event_manager.assign_manager_to_request_type(GetAllInfoForLibraryRequest, self.get_all_info_for_library_request)
|
|
206
207
|
event_manager.assign_manager_to_request_type(
|
|
207
|
-
GetAllInfoForAllLibrariesRequest, self.
|
|
208
|
+
GetAllInfoForAllLibrariesRequest, self.on_get_all_info_for_all_libraries_request
|
|
208
209
|
)
|
|
209
210
|
event_manager.assign_manager_to_request_type(
|
|
210
211
|
LoadMetadataForAllLibrariesRequest, self.load_metadata_for_all_libraries_request
|
|
@@ -531,13 +532,13 @@ class LibraryManager:
|
|
|
531
532
|
sandbox_library_dir_as_posix = sandbox_library_dir.as_posix()
|
|
532
533
|
|
|
533
534
|
if not sandbox_library_dir.exists():
|
|
534
|
-
details = "Sandbox directory does not exist."
|
|
535
|
+
details = "Sandbox directory does not exist. If you wish to create a Sandbox directory to develop custom nodes: in the Griptape Nodes editor, go to Settings -> Libraries and navigate to the Sandbox Settings."
|
|
535
536
|
return LoadLibraryMetadataFromFileResultFailure(
|
|
536
537
|
library_path=sandbox_library_dir_as_posix,
|
|
537
538
|
library_name=LibraryManager.SANDBOX_LIBRARY_NAME,
|
|
538
539
|
status=LibraryStatus.MISSING,
|
|
539
540
|
problems=[details],
|
|
540
|
-
result_details=details,
|
|
541
|
+
result_details=ResultDetails(message=details, level=logging.INFO),
|
|
541
542
|
)
|
|
542
543
|
|
|
543
544
|
sandbox_node_candidates = self._find_files_in_dir(directory=sandbox_library_dir, extension=".py")
|
|
@@ -1170,6 +1171,13 @@ class LibraryManager:
|
|
|
1170
1171
|
)
|
|
1171
1172
|
return result
|
|
1172
1173
|
|
|
1174
|
+
async def on_get_all_info_for_all_libraries_request(
|
|
1175
|
+
self, request: GetAllInfoForAllLibrariesRequest
|
|
1176
|
+
) -> ResultPayload:
|
|
1177
|
+
"""Async handler for GetAllInfoForAllLibrariesRequest that waits for library loading to complete."""
|
|
1178
|
+
await self._libraries_loading_complete.wait()
|
|
1179
|
+
return await asyncio.to_thread(self.get_all_info_for_all_libraries_request, request)
|
|
1180
|
+
|
|
1173
1181
|
def get_all_info_for_library_request(self, request: GetAllInfoForLibraryRequest) -> ResultPayload: # noqa: PLR0911
|
|
1174
1182
|
# Does this library exist?
|
|
1175
1183
|
try:
|
|
@@ -1500,48 +1508,53 @@ class LibraryManager:
|
|
|
1500
1508
|
return node_class
|
|
1501
1509
|
|
|
1502
1510
|
async def load_all_libraries_from_config(self) -> None:
|
|
1503
|
-
|
|
1504
|
-
metadata_request = LoadMetadataForAllLibrariesRequest()
|
|
1505
|
-
metadata_result = self.load_metadata_for_all_libraries_request(metadata_request)
|
|
1506
|
-
|
|
1507
|
-
# Check if metadata loading succeeded
|
|
1508
|
-
if not isinstance(metadata_result, LoadMetadataForAllLibrariesResultSuccess):
|
|
1509
|
-
logger.error("Failed to load metadata for all libraries, skipping library registration")
|
|
1510
|
-
return
|
|
1511
|
-
|
|
1512
|
-
# Record all failed libraries in our tracking immediately
|
|
1513
|
-
for failed_library in metadata_result.failed_libraries:
|
|
1514
|
-
self._library_file_path_to_info[failed_library.library_path] = LibraryManager.LibraryInfo(
|
|
1515
|
-
library_path=failed_library.library_path,
|
|
1516
|
-
library_name=failed_library.library_name,
|
|
1517
|
-
status=failed_library.status,
|
|
1518
|
-
problems=failed_library.problems,
|
|
1519
|
-
)
|
|
1511
|
+
self._libraries_loading_complete.clear()
|
|
1520
1512
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1513
|
+
try:
|
|
1514
|
+
# Load metadata for all libraries to determine which ones can be safely loaded
|
|
1515
|
+
metadata_request = LoadMetadataForAllLibrariesRequest()
|
|
1516
|
+
metadata_result = self.load_metadata_for_all_libraries_request(metadata_request)
|
|
1517
|
+
|
|
1518
|
+
# Check if metadata loading succeeded
|
|
1519
|
+
if not isinstance(metadata_result, LoadMetadataForAllLibrariesResultSuccess):
|
|
1520
|
+
logger.error("Failed to load metadata for all libraries, skipping library registration")
|
|
1521
|
+
return
|
|
1522
|
+
|
|
1523
|
+
# Record all failed libraries in our tracking immediately
|
|
1524
|
+
for failed_library in metadata_result.failed_libraries:
|
|
1525
|
+
self._library_file_path_to_info[failed_library.library_path] = LibraryManager.LibraryInfo(
|
|
1526
|
+
library_path=failed_library.library_path,
|
|
1527
|
+
library_name=failed_library.library_name,
|
|
1528
|
+
status=failed_library.status,
|
|
1529
|
+
problems=failed_library.problems,
|
|
1532
1530
|
)
|
|
1533
|
-
register_result = await self.register_library_from_file_request(register_request)
|
|
1534
|
-
if isinstance(register_result, RegisterLibraryFromFileResultFailure):
|
|
1535
|
-
# Registration failed - the failure info is already recorded in _library_file_path_to_info
|
|
1536
|
-
# by register_library_from_file_request, so we just log it here for visibility
|
|
1537
|
-
logger.warning("Failed to register library from %s", library_result.file_path)
|
|
1538
1531
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1532
|
+
# Use metadata results to selectively load libraries
|
|
1533
|
+
for library_result in metadata_result.successful_libraries:
|
|
1534
|
+
if library_result.library_schema.name == LibraryManager.SANDBOX_LIBRARY_NAME:
|
|
1535
|
+
# Handle sandbox library - use the schema we already have
|
|
1536
|
+
await self._attempt_generate_sandbox_library_from_schema(
|
|
1537
|
+
library_schema=library_result.library_schema, sandbox_directory=library_result.file_path
|
|
1538
|
+
)
|
|
1539
|
+
else:
|
|
1540
|
+
# Handle config-based library - register it directly using the file path
|
|
1541
|
+
register_request = RegisterLibraryFromFileRequest(
|
|
1542
|
+
file_path=library_result.file_path, load_as_default_library=False
|
|
1543
|
+
)
|
|
1544
|
+
register_result = await self.register_library_from_file_request(register_request)
|
|
1545
|
+
if isinstance(register_result, RegisterLibraryFromFileResultFailure):
|
|
1546
|
+
# Registration failed - the failure info is already recorded in _library_file_path_to_info
|
|
1547
|
+
# by register_library_from_file_request, so we just log it here for visibility
|
|
1548
|
+
logger.warning("Failed to register library from %s", library_result.file_path)
|
|
1549
|
+
|
|
1550
|
+
# Print 'em all pretty
|
|
1551
|
+
self.print_library_load_status()
|
|
1552
|
+
|
|
1553
|
+
# Remove any missing libraries AFTER we've printed them for the user.
|
|
1554
|
+
user_libraries_section = "app_events.on_app_initialization_complete.libraries_to_register"
|
|
1555
|
+
self._remove_missing_libraries_from_config(config_category=user_libraries_section)
|
|
1556
|
+
finally:
|
|
1557
|
+
self._libraries_loading_complete.set()
|
|
1545
1558
|
|
|
1546
1559
|
async def on_app_initialization_complete(self, _payload: AppInitializationComplete) -> None:
|
|
1547
1560
|
# App just got init'd. See if there are library JSONs to load!
|
|
@@ -232,19 +232,27 @@ class MCPManager:
|
|
|
232
232
|
self, request: UpdateMCPServerRequest
|
|
233
233
|
) -> UpdateMCPServerResultSuccess | UpdateMCPServerResultFailure:
|
|
234
234
|
"""Handle update MCP server request."""
|
|
235
|
-
servers = self._get_mcp_servers(
|
|
236
|
-
server_config = servers[0] if servers else None
|
|
235
|
+
servers = self._get_mcp_servers()
|
|
237
236
|
|
|
238
|
-
|
|
237
|
+
# Find the server to update
|
|
238
|
+
server_index = next((i for i, server in enumerate(servers) if server.name == request.name), None)
|
|
239
|
+
|
|
240
|
+
if server_index is None:
|
|
239
241
|
return UpdateMCPServerResultFailure(
|
|
240
242
|
result_details=f"Failed to update MCP server '{request.name}' - not found"
|
|
241
243
|
)
|
|
242
244
|
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
+
# Create a backup of the original server and update a copy
|
|
246
|
+
original_server = servers[server_index]
|
|
247
|
+
updated_server = original_server.model_copy()
|
|
248
|
+
self._update_server_fields(updated_server, request)
|
|
249
|
+
|
|
250
|
+
# Create a copy of the servers list with the updated server
|
|
251
|
+
updated_servers = servers.copy()
|
|
252
|
+
updated_servers[server_index] = updated_server
|
|
245
253
|
|
|
246
254
|
try:
|
|
247
|
-
self._save_mcp_servers(
|
|
255
|
+
self._save_mcp_servers(updated_servers)
|
|
248
256
|
except Exception as e:
|
|
249
257
|
logger.error("Failed to save MCP server '%s': %s", request.name, e)
|
|
250
258
|
return UpdateMCPServerResultFailure(result_details=f"Failed to save MCP server '{request.name}': {e}")
|
|
@@ -63,6 +63,9 @@ from griptape_nodes.retained_mode.events.node_events import (
|
|
|
63
63
|
BatchSetNodeMetadataRequest,
|
|
64
64
|
BatchSetNodeMetadataResultFailure,
|
|
65
65
|
BatchSetNodeMetadataResultSuccess,
|
|
66
|
+
CanResetNodeToDefaultsRequest,
|
|
67
|
+
CanResetNodeToDefaultsResultFailure,
|
|
68
|
+
CanResetNodeToDefaultsResultSuccess,
|
|
66
69
|
CreateNodeRequest,
|
|
67
70
|
CreateNodeResultFailure,
|
|
68
71
|
CreateNodeResultSuccess,
|
|
@@ -93,6 +96,9 @@ from griptape_nodes.retained_mode.events.node_events import (
|
|
|
93
96
|
ListParametersOnNodeRequest,
|
|
94
97
|
ListParametersOnNodeResultFailure,
|
|
95
98
|
ListParametersOnNodeResultSuccess,
|
|
99
|
+
ResetNodeToDefaultsRequest,
|
|
100
|
+
ResetNodeToDefaultsResultFailure,
|
|
101
|
+
ResetNodeToDefaultsResultSuccess,
|
|
96
102
|
SendNodeMessageRequest,
|
|
97
103
|
SendNodeMessageResultFailure,
|
|
98
104
|
SendNodeMessageResultSuccess,
|
|
@@ -111,6 +117,10 @@ from griptape_nodes.retained_mode.events.node_events import (
|
|
|
111
117
|
SetNodeMetadataResultFailure,
|
|
112
118
|
SetNodeMetadataResultSuccess,
|
|
113
119
|
)
|
|
120
|
+
from griptape_nodes.retained_mode.events.object_events import (
|
|
121
|
+
RenameObjectRequest,
|
|
122
|
+
RenameObjectResultSuccess,
|
|
123
|
+
)
|
|
114
124
|
from griptape_nodes.retained_mode.events.parameter_events import (
|
|
115
125
|
AddParameterToNodeRequest,
|
|
116
126
|
AddParameterToNodeResultFailure,
|
|
@@ -171,6 +181,18 @@ class SerializedParameterValues(NamedTuple):
|
|
|
171
181
|
unique_parameter_uuid_to_values: dict[Any, Any] | None
|
|
172
182
|
|
|
173
183
|
|
|
184
|
+
class CanResetResult(NamedTuple):
|
|
185
|
+
"""Result of checking if a node can be reset to defaults.
|
|
186
|
+
|
|
187
|
+
Attributes:
|
|
188
|
+
can_reset: True if the node can be reset to defaults, False otherwise
|
|
189
|
+
editor_tooltip_reason: Optional explanation if node cannot be reset
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
can_reset: bool
|
|
193
|
+
editor_tooltip_reason: str | None
|
|
194
|
+
|
|
195
|
+
|
|
174
196
|
class NodeManager:
|
|
175
197
|
_name_to_parent_flow_name: dict[str, str]
|
|
176
198
|
|
|
@@ -233,6 +255,10 @@ class NodeManager:
|
|
|
233
255
|
event_manager.assign_manager_to_request_type(SetLockNodeStateRequest, self.on_toggle_lock_node_request)
|
|
234
256
|
event_manager.assign_manager_to_request_type(GetFlowForNodeRequest, self.on_get_flow_for_node_request)
|
|
235
257
|
event_manager.assign_manager_to_request_type(SendNodeMessageRequest, self.on_send_node_message_request)
|
|
258
|
+
event_manager.assign_manager_to_request_type(
|
|
259
|
+
CanResetNodeToDefaultsRequest, self.on_can_reset_node_to_defaults_request
|
|
260
|
+
)
|
|
261
|
+
event_manager.assign_manager_to_request_type(ResetNodeToDefaultsRequest, self.on_reset_node_to_defaults_request)
|
|
236
262
|
|
|
237
263
|
def handle_node_rename(self, old_name: str, new_name: str) -> None:
|
|
238
264
|
# Get the node itself
|
|
@@ -3414,3 +3440,209 @@ class NodeManager:
|
|
|
3414
3440
|
)
|
|
3415
3441
|
|
|
3416
3442
|
return None
|
|
3443
|
+
|
|
3444
|
+
def _check_can_reset_node(self, node: BaseNode) -> CanResetResult:
|
|
3445
|
+
"""Check if a node can be reset to defaults.
|
|
3446
|
+
|
|
3447
|
+
Args:
|
|
3448
|
+
node: The node to check
|
|
3449
|
+
|
|
3450
|
+
Returns:
|
|
3451
|
+
CanResetResult with can_reset flag and optional tooltip reason
|
|
3452
|
+
"""
|
|
3453
|
+
if node.lock:
|
|
3454
|
+
return CanResetResult(
|
|
3455
|
+
can_reset=False,
|
|
3456
|
+
editor_tooltip_reason="Node is locked. Unlock the node in order to reset it.",
|
|
3457
|
+
)
|
|
3458
|
+
|
|
3459
|
+
return CanResetResult(can_reset=True, editor_tooltip_reason=None)
|
|
3460
|
+
|
|
3461
|
+
def on_can_reset_node_to_defaults_request(self, request: CanResetNodeToDefaultsRequest) -> ResultPayload:
|
|
3462
|
+
"""Check if a node can be reset to its default state."""
|
|
3463
|
+
node_name = request.node_name
|
|
3464
|
+
node = None
|
|
3465
|
+
|
|
3466
|
+
# FAILURE CHECK: Validate node_name
|
|
3467
|
+
if node_name is None:
|
|
3468
|
+
if not GriptapeNodes.ContextManager().has_current_node():
|
|
3469
|
+
details = (
|
|
3470
|
+
"Attempted to check reset eligibility for a Node from the Current Context. "
|
|
3471
|
+
"Failed because the Current Context is empty."
|
|
3472
|
+
)
|
|
3473
|
+
return CanResetNodeToDefaultsResultFailure(result_details=details)
|
|
3474
|
+
node = GriptapeNodes.ContextManager().get_current_node()
|
|
3475
|
+
node_name = node.name
|
|
3476
|
+
|
|
3477
|
+
# FAILURE CHECK: Get source node
|
|
3478
|
+
if node is None:
|
|
3479
|
+
node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(node_name, BaseNode)
|
|
3480
|
+
if node is None:
|
|
3481
|
+
details = f"Attempted to check reset eligibility for Node '{node_name}', but no such Node was found."
|
|
3482
|
+
return CanResetNodeToDefaultsResultFailure(result_details=details)
|
|
3483
|
+
|
|
3484
|
+
# FAILURE CHECK: Get node type and library
|
|
3485
|
+
if "library" not in node.metadata:
|
|
3486
|
+
details = (
|
|
3487
|
+
f"Attempted to check reset eligibility for Node '{node_name}'. "
|
|
3488
|
+
f"Failed because node has no library information in metadata."
|
|
3489
|
+
)
|
|
3490
|
+
return CanResetNodeToDefaultsResultFailure(result_details=details)
|
|
3491
|
+
|
|
3492
|
+
# Check if node can be reset
|
|
3493
|
+
can_reset_result = self._check_can_reset_node(node)
|
|
3494
|
+
if not can_reset_result.can_reset:
|
|
3495
|
+
details = f"Node '{node_name}' cannot be reset: {can_reset_result.editor_tooltip_reason}"
|
|
3496
|
+
return CanResetNodeToDefaultsResultSuccess(
|
|
3497
|
+
can_reset=False,
|
|
3498
|
+
editor_tooltip_reason=can_reset_result.editor_tooltip_reason,
|
|
3499
|
+
result_details=details,
|
|
3500
|
+
)
|
|
3501
|
+
|
|
3502
|
+
# SUCCESS PATH: Node can be reset
|
|
3503
|
+
details = f"Node '{node_name}' can be reset to defaults."
|
|
3504
|
+
return CanResetNodeToDefaultsResultSuccess(
|
|
3505
|
+
can_reset=True,
|
|
3506
|
+
editor_tooltip_reason=None,
|
|
3507
|
+
result_details=details,
|
|
3508
|
+
)
|
|
3509
|
+
|
|
3510
|
+
def on_reset_node_to_defaults_request(self, request: ResetNodeToDefaultsRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915
|
|
3511
|
+
"""Reset a node to its default state while preserving connections where possible."""
|
|
3512
|
+
node_name = request.node_name
|
|
3513
|
+
node = None
|
|
3514
|
+
|
|
3515
|
+
# FAILURE CHECK: Validate node_name
|
|
3516
|
+
if node_name is None:
|
|
3517
|
+
if not GriptapeNodes.ContextManager().has_current_node():
|
|
3518
|
+
details = (
|
|
3519
|
+
"Attempted to reset a Node from the Current Context. Failed because the Current Context is empty."
|
|
3520
|
+
)
|
|
3521
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3522
|
+
node = GriptapeNodes.ContextManager().get_current_node()
|
|
3523
|
+
node_name = node.name
|
|
3524
|
+
|
|
3525
|
+
# FAILURE CHECK: Get source node
|
|
3526
|
+
if node is None:
|
|
3527
|
+
node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(node_name, BaseNode)
|
|
3528
|
+
if node is None:
|
|
3529
|
+
details = f"Attempted to reset Node '{node_name}', but no such Node was found."
|
|
3530
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3531
|
+
|
|
3532
|
+
# FAILURE CHECK: Get node type and library
|
|
3533
|
+
node_type = node.__class__.__name__
|
|
3534
|
+
if "library" not in node.metadata:
|
|
3535
|
+
details = (
|
|
3536
|
+
f"Attempted to reset Node '{node_name}'. Failed because node has no library information in metadata."
|
|
3537
|
+
)
|
|
3538
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3539
|
+
library_name = node.metadata["library"]
|
|
3540
|
+
|
|
3541
|
+
# FAILURE CHECK: Check if node can be reset
|
|
3542
|
+
can_reset_result = self._check_can_reset_node(node)
|
|
3543
|
+
if not can_reset_result.can_reset:
|
|
3544
|
+
details = f"Attempted to reset Node '{node_name}'. Failed because: {can_reset_result.editor_tooltip_reason}"
|
|
3545
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3546
|
+
|
|
3547
|
+
# FAILURE CHECK: Gather node information
|
|
3548
|
+
all_info_request = GetAllNodeInfoRequest(node_name=node_name)
|
|
3549
|
+
all_info_result = self.on_get_all_node_info_request(all_info_request)
|
|
3550
|
+
if not isinstance(all_info_result, GetAllNodeInfoResultSuccess):
|
|
3551
|
+
details = f"Attempted to reset Node '{node_name}'. Failed to get node information."
|
|
3552
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3553
|
+
|
|
3554
|
+
connections = all_info_result.connections
|
|
3555
|
+
|
|
3556
|
+
# FAILURE CHECK: Get parent flow name
|
|
3557
|
+
if node_name not in self._name_to_parent_flow_name:
|
|
3558
|
+
details = f"Attempted to reset Node '{node_name}'. Failed to find parent flow name."
|
|
3559
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3560
|
+
parent_flow_name = self._name_to_parent_flow_name[node_name]
|
|
3561
|
+
|
|
3562
|
+
# FAILURE CHECK: Create new node with temporary name
|
|
3563
|
+
temp_node_name = f"{node_name}_temp"
|
|
3564
|
+
create_node_request = CreateNodeRequest(
|
|
3565
|
+
node_type=node_type,
|
|
3566
|
+
specific_library_name=library_name,
|
|
3567
|
+
node_name=temp_node_name,
|
|
3568
|
+
override_parent_flow_name=parent_flow_name,
|
|
3569
|
+
create_error_proxy_on_failure=False,
|
|
3570
|
+
)
|
|
3571
|
+
create_result = self.on_create_node_request(create_node_request)
|
|
3572
|
+
if not isinstance(create_result, CreateNodeResultSuccess):
|
|
3573
|
+
details = f"Attempted to reset Node '{node_name}'. Failed to create new node of type '{node_type}'."
|
|
3574
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3575
|
+
new_node_name = create_result.node_name
|
|
3576
|
+
|
|
3577
|
+
# TODO: (griptape-nodes) Don't rely on manually copying metadata fields. https://github.com/griptape-ai/griptape-nodes/issues/2862
|
|
3578
|
+
# Copy only position and size from original node's metadata to preserve layout.
|
|
3579
|
+
# We don't copy the full metadata because it contains instance-specific data that shouldn't be transferred.
|
|
3580
|
+
original_metadata = all_info_result.metadata
|
|
3581
|
+
new_node = self.get_node_by_name(new_node_name)
|
|
3582
|
+
if "position" in original_metadata:
|
|
3583
|
+
new_node.metadata["position"] = copy.deepcopy(original_metadata["position"])
|
|
3584
|
+
if "size" in original_metadata:
|
|
3585
|
+
new_node.metadata["size"] = copy.deepcopy(original_metadata["size"])
|
|
3586
|
+
|
|
3587
|
+
# NON-FATAL: Attempt to reconnect connections
|
|
3588
|
+
failed_incoming: list[IncomingConnection] = []
|
|
3589
|
+
failed_outgoing: list[OutgoingConnection] = []
|
|
3590
|
+
|
|
3591
|
+
for incoming_connection in connections.incoming_connections:
|
|
3592
|
+
connection_request = CreateConnectionRequest(
|
|
3593
|
+
source_node_name=incoming_connection.source_node_name,
|
|
3594
|
+
source_parameter_name=incoming_connection.source_parameter_name,
|
|
3595
|
+
target_node_name=new_node_name,
|
|
3596
|
+
target_parameter_name=incoming_connection.target_parameter_name,
|
|
3597
|
+
)
|
|
3598
|
+
connection_result = GriptapeNodes.FlowManager().on_create_connection_request(connection_request)
|
|
3599
|
+
if not isinstance(connection_result, CreateConnectionResultSuccess):
|
|
3600
|
+
failed_incoming.append(incoming_connection)
|
|
3601
|
+
|
|
3602
|
+
for outgoing_connection in connections.outgoing_connections:
|
|
3603
|
+
connection_request = CreateConnectionRequest(
|
|
3604
|
+
source_node_name=new_node_name,
|
|
3605
|
+
source_parameter_name=outgoing_connection.source_parameter_name,
|
|
3606
|
+
target_node_name=outgoing_connection.target_node_name,
|
|
3607
|
+
target_parameter_name=outgoing_connection.target_parameter_name,
|
|
3608
|
+
)
|
|
3609
|
+
connection_result = GriptapeNodes.FlowManager().on_create_connection_request(connection_request)
|
|
3610
|
+
if not isinstance(connection_result, CreateConnectionResultSuccess):
|
|
3611
|
+
failed_outgoing.append(outgoing_connection)
|
|
3612
|
+
|
|
3613
|
+
# FAILURE CHECK: Delete source node
|
|
3614
|
+
delete_request = DeleteNodeRequest(node_name=node_name)
|
|
3615
|
+
delete_result = self.on_delete_node_request(delete_request)
|
|
3616
|
+
if not isinstance(delete_result, DeleteNodeResultSuccess):
|
|
3617
|
+
details = f"Attempted to reset Node '{node_name}'. Failed to delete original node."
|
|
3618
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3619
|
+
|
|
3620
|
+
# FAILURE CHECK: Rename new node to original name
|
|
3621
|
+
rename_request = RenameObjectRequest(
|
|
3622
|
+
object_name=new_node_name, requested_name=node_name, allow_next_closest_name_available=False
|
|
3623
|
+
)
|
|
3624
|
+
rename_result = GriptapeNodes.ObjectManager().on_rename_object_request(rename_request)
|
|
3625
|
+
if not isinstance(rename_result, RenameObjectResultSuccess):
|
|
3626
|
+
details = f"Attempted to reset Node '{node_name}'. Failed to rename new node to original name."
|
|
3627
|
+
return ResetNodeToDefaultsResultFailure(result_details=details)
|
|
3628
|
+
|
|
3629
|
+
# SUCCESS PATH
|
|
3630
|
+
if not failed_incoming and not failed_outgoing:
|
|
3631
|
+
details = f"Successfully reset node '{node_name}' to defaults."
|
|
3632
|
+
log_level = logging.DEBUG
|
|
3633
|
+
else:
|
|
3634
|
+
details = f"Successfully reset node '{node_name}' but one or more connections could not be restored."
|
|
3635
|
+
if failed_incoming:
|
|
3636
|
+
source_node_names = {conn.source_node_name for conn in failed_incoming}
|
|
3637
|
+
details += f" Connections FROM the following nodes were not restored: {source_node_names}."
|
|
3638
|
+
if failed_outgoing:
|
|
3639
|
+
target_node_names = {conn.target_node_name for conn in failed_outgoing}
|
|
3640
|
+
details += f" Connections TO the following nodes were not restored: {target_node_names}."
|
|
3641
|
+
log_level = logging.WARNING
|
|
3642
|
+
|
|
3643
|
+
return ResetNodeToDefaultsResultSuccess(
|
|
3644
|
+
node_name=node_name,
|
|
3645
|
+
failed_incoming_connections=failed_incoming,
|
|
3646
|
+
failed_outgoing_connections=failed_outgoing,
|
|
3647
|
+
result_details=ResultDetails(message=details, level=log_level),
|
|
3648
|
+
)
|