griptape-nodes 0.43.1__py3-none-any.whl → 0.45.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/__init__.py +46 -52
- griptape_nodes/app/.python-version +0 -0
- griptape_nodes/app/__init__.py +0 -0
- griptape_nodes/app/api.py +37 -41
- griptape_nodes/app/app.py +70 -3
- griptape_nodes/app/watch.py +5 -2
- griptape_nodes/bootstrap/__init__.py +0 -0
- griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +7 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +90 -0
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +7 -1
- griptape_nodes/drivers/__init__.py +0 -0
- griptape_nodes/drivers/storage/__init__.py +0 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +90 -0
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +48 -0
- griptape_nodes/drivers/storage/local_storage_driver.py +37 -0
- griptape_nodes/drivers/storage/storage_backend.py +0 -0
- griptape_nodes/exe_types/__init__.py +0 -0
- griptape_nodes/exe_types/connections.py +0 -0
- griptape_nodes/exe_types/core_types.py +222 -17
- griptape_nodes/exe_types/flow.py +0 -0
- griptape_nodes/exe_types/node_types.py +20 -5
- griptape_nodes/exe_types/type_validator.py +0 -0
- griptape_nodes/machines/__init__.py +0 -0
- griptape_nodes/machines/control_flow.py +5 -4
- griptape_nodes/machines/fsm.py +0 -0
- griptape_nodes/machines/node_resolution.py +110 -74
- griptape_nodes/mcp_server/__init__.py +0 -0
- griptape_nodes/mcp_server/server.py +16 -8
- griptape_nodes/mcp_server/ws_request_manager.py +0 -0
- griptape_nodes/node_library/__init__.py +0 -0
- griptape_nodes/node_library/advanced_node_library.py +0 -0
- griptape_nodes/node_library/library_registry.py +0 -0
- griptape_nodes/node_library/workflow_registry.py +29 -0
- griptape_nodes/py.typed +0 -0
- griptape_nodes/retained_mode/__init__.py +0 -0
- griptape_nodes/retained_mode/events/__init__.py +0 -0
- griptape_nodes/retained_mode/events/agent_events.py +0 -0
- griptape_nodes/retained_mode/events/app_events.py +3 -8
- griptape_nodes/retained_mode/events/arbitrary_python_events.py +0 -0
- griptape_nodes/retained_mode/events/base_events.py +15 -7
- griptape_nodes/retained_mode/events/config_events.py +0 -0
- griptape_nodes/retained_mode/events/connection_events.py +0 -0
- griptape_nodes/retained_mode/events/context_events.py +0 -0
- griptape_nodes/retained_mode/events/execution_events.py +0 -0
- griptape_nodes/retained_mode/events/flow_events.py +2 -1
- griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
- griptape_nodes/retained_mode/events/library_events.py +0 -0
- griptape_nodes/retained_mode/events/logger_events.py +0 -0
- griptape_nodes/retained_mode/events/node_events.py +36 -0
- griptape_nodes/retained_mode/events/object_events.py +0 -0
- griptape_nodes/retained_mode/events/os_events.py +98 -6
- griptape_nodes/retained_mode/events/parameter_events.py +0 -0
- griptape_nodes/retained_mode/events/payload_registry.py +0 -0
- griptape_nodes/retained_mode/events/secrets_events.py +0 -0
- griptape_nodes/retained_mode/events/static_file_events.py +0 -0
- griptape_nodes/retained_mode/events/sync_events.py +60 -0
- griptape_nodes/retained_mode/events/validation_events.py +0 -0
- griptape_nodes/retained_mode/events/workflow_events.py +231 -0
- griptape_nodes/retained_mode/griptape_nodes.py +9 -4
- griptape_nodes/retained_mode/managers/__init__.py +0 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +0 -0
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
- griptape_nodes/retained_mode/managers/config_manager.py +1 -1
- griptape_nodes/retained_mode/managers/context_manager.py +0 -0
- griptape_nodes/retained_mode/managers/engine_identity_manager.py +0 -0
- griptape_nodes/retained_mode/managers/event_manager.py +0 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +6 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -0
- griptape_nodes/retained_mode/managers/library_manager.py +8 -26
- griptape_nodes/retained_mode/managers/node_manager.py +78 -7
- griptape_nodes/retained_mode/managers/object_manager.py +0 -0
- griptape_nodes/retained_mode/managers/operation_manager.py +7 -0
- griptape_nodes/retained_mode/managers/os_manager.py +133 -8
- griptape_nodes/retained_mode/managers/secrets_manager.py +0 -0
- griptape_nodes/retained_mode/managers/session_manager.py +0 -0
- griptape_nodes/retained_mode/managers/settings.py +5 -0
- griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +498 -0
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +0 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +736 -33
- griptape_nodes/retained_mode/retained_mode.py +23 -0
- griptape_nodes/retained_mode/utils/__init__.py +0 -0
- griptape_nodes/retained_mode/utils/engine_identity.py +0 -0
- griptape_nodes/retained_mode/utils/name_generator.py +0 -0
- griptape_nodes/traits/__init__.py +0 -0
- griptape_nodes/traits/add_param_button.py +0 -0
- griptape_nodes/traits/button.py +0 -0
- griptape_nodes/traits/clamp.py +0 -0
- griptape_nodes/traits/compare.py +0 -0
- griptape_nodes/traits/compare_images.py +0 -0
- griptape_nodes/traits/file_system_picker.py +18 -0
- griptape_nodes/traits/minmax.py +0 -0
- griptape_nodes/traits/options.py +0 -0
- griptape_nodes/traits/slider.py +0 -0
- griptape_nodes/traits/trait_registry.py +0 -0
- griptape_nodes/traits/traits.json +0 -0
- griptape_nodes/updater/__init__.py +4 -2
- griptape_nodes/updater/__main__.py +0 -0
- griptape_nodes/utils/__init__.py +0 -0
- griptape_nodes/utils/dict_utils.py +0 -0
- griptape_nodes/utils/image_preview.py +0 -0
- griptape_nodes/utils/metaclasses.py +0 -0
- griptape_nodes/utils/uv_utils.py +18 -0
- griptape_nodes/utils/version_utils.py +51 -0
- griptape_nodes/version_compatibility/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +0 -0
- {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/METADATA +2 -1
- {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/RECORD +42 -47
- {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/WHEEL +1 -1
- griptape_nodes/bootstrap/bootstrap_script.py +0 -54
- griptape_nodes/bootstrap/post_build_install_script.sh +0 -3
- griptape_nodes/bootstrap/pre_build_install_script.sh +0 -4
- griptape_nodes/bootstrap/register_libraries_script.py +0 -32
- griptape_nodes/bootstrap/structure_config.yaml +0 -15
- griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -1
- griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -28
- griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -237
- griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +0 -62
- griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -11
- {griptape_nodes-0.43.1.dist-info → griptape_nodes-0.45.0.dist-info}/entry_points.txt +0 -0
|
@@ -135,6 +135,37 @@ class GriptapeCloudStorageDriver(BaseStorageDriver):
|
|
|
135
135
|
logger.info("Created new Griptape Cloud bucket '%s' with ID: %s", bucket_name, bucket_id)
|
|
136
136
|
return bucket_id
|
|
137
137
|
|
|
138
|
+
def list_files(self) -> list[str]:
|
|
139
|
+
"""List all files in storage.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
A list of file names in storage.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
RuntimeError: If file listing fails.
|
|
146
|
+
"""
|
|
147
|
+
url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/assets")
|
|
148
|
+
try:
|
|
149
|
+
response = httpx.get(url, headers=self.headers, params={"prefix": self.static_files_directory or ""})
|
|
150
|
+
response.raise_for_status()
|
|
151
|
+
except httpx.HTTPStatusError as e:
|
|
152
|
+
msg = f"Failed to list files in bucket {self.bucket_id}: {e}"
|
|
153
|
+
logger.error(msg)
|
|
154
|
+
raise RuntimeError(msg) from e
|
|
155
|
+
|
|
156
|
+
response_data = response.json()
|
|
157
|
+
assets = response_data.get("assets", [])
|
|
158
|
+
|
|
159
|
+
file_names = []
|
|
160
|
+
for asset in assets:
|
|
161
|
+
name = asset.get("name", "")
|
|
162
|
+
# Remove the static files directory prefix if it exists
|
|
163
|
+
if self.static_files_directory and name.startswith(f"{self.static_files_directory}/"):
|
|
164
|
+
name = name[len(f"{self.static_files_directory}/") :]
|
|
165
|
+
file_names.append(name)
|
|
166
|
+
|
|
167
|
+
return file_names
|
|
168
|
+
|
|
138
169
|
@staticmethod
|
|
139
170
|
def list_buckets(*, base_url: str, api_key: str) -> list[dict]:
|
|
140
171
|
"""List all buckets in Griptape Cloud.
|
|
@@ -158,3 +189,20 @@ class GriptapeCloudStorageDriver(BaseStorageDriver):
|
|
|
158
189
|
raise RuntimeError(msg) from e
|
|
159
190
|
|
|
160
191
|
return response.json().get("buckets", [])
|
|
192
|
+
|
|
193
|
+
def delete_file(self, file_name: str) -> None:
|
|
194
|
+
"""Delete a file from the bucket.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
file_name: The name of the file to delete.
|
|
198
|
+
"""
|
|
199
|
+
full_file_path = self._get_full_file_path(file_name)
|
|
200
|
+
url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/assets/{full_file_path}")
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
response = httpx.delete(url, headers=self.headers)
|
|
204
|
+
response.raise_for_status()
|
|
205
|
+
except httpx.HTTPStatusError as e:
|
|
206
|
+
msg = f"Failed to delete file {file_name}: {e}"
|
|
207
|
+
logger.error(msg)
|
|
208
|
+
raise RuntimeError(msg) from e
|
|
@@ -53,3 +53,40 @@ class LocalStorageDriver(BaseStorageDriver):
|
|
|
53
53
|
# Add a cache-busting query parameter to the URL so that the browser always reloads the file
|
|
54
54
|
cache_busted_url = f"{url}?t={int(time.time())}"
|
|
55
55
|
return cache_busted_url
|
|
56
|
+
|
|
57
|
+
def delete_file(self, file_name: str) -> None:
|
|
58
|
+
"""Delete a file from local storage.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
file_name: The name of the file to delete.
|
|
62
|
+
"""
|
|
63
|
+
# Use the static server's delete endpoint
|
|
64
|
+
delete_url = urljoin(self.base_url, f"/static-files/{file_name}")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
response = httpx.delete(delete_url)
|
|
68
|
+
response.raise_for_status()
|
|
69
|
+
except httpx.HTTPStatusError as e:
|
|
70
|
+
msg = f"Failed to delete file {file_name}: {e}"
|
|
71
|
+
logger.error(msg)
|
|
72
|
+
raise RuntimeError(msg) from e
|
|
73
|
+
|
|
74
|
+
def list_files(self) -> list[str]:
|
|
75
|
+
"""List all files in local storage.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
A list of file names in storage.
|
|
79
|
+
"""
|
|
80
|
+
# Use the static server's list endpoint
|
|
81
|
+
list_url = urljoin(self.base_url, "/static-uploads/")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
response = httpx.get(list_url)
|
|
85
|
+
response.raise_for_status()
|
|
86
|
+
except httpx.HTTPStatusError as e:
|
|
87
|
+
msg = f"Failed to list files: {e}"
|
|
88
|
+
logger.error(msg)
|
|
89
|
+
raise RuntimeError(msg) from e
|
|
90
|
+
|
|
91
|
+
response_data = response.json()
|
|
92
|
+
return response_data.get("files", [])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -244,6 +244,11 @@ class BaseNodeElement:
|
|
|
244
244
|
"name": self.name,
|
|
245
245
|
"node_name": self._node_context.name,
|
|
246
246
|
}
|
|
247
|
+
# If ui_options changed, send the complete ui_options from to_dict()
|
|
248
|
+
complete_dict = self.to_dict()
|
|
249
|
+
if "ui_options" in complete_dict:
|
|
250
|
+
self._changes["ui_options"] = complete_dict["ui_options"]
|
|
251
|
+
|
|
247
252
|
event_data.update(self._changes)
|
|
248
253
|
|
|
249
254
|
# Publish the event
|
|
@@ -295,11 +300,28 @@ class BaseNodeElement:
|
|
|
295
300
|
self._node_context._emit_parameter_lifecycle_event(child)
|
|
296
301
|
|
|
297
302
|
def remove_child(self, child: BaseNodeElement | str) -> None:
|
|
303
|
+
"""Remove a child element from the hierarchy.
|
|
304
|
+
|
|
305
|
+
This method recursively searches through the element hierarchy to find and remove
|
|
306
|
+
the specified child. When the child is found in a descendant container (e.g., a
|
|
307
|
+
ParameterList), it delegates to that container's remove_child() method to ensure
|
|
308
|
+
proper cleanup and event handling (like marking parent nodes as unresolved).
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
child: The child element to remove, either as an object or by name string
|
|
312
|
+
"""
|
|
298
313
|
ui_elements: list[BaseNodeElement] = [self]
|
|
299
314
|
for ui_element in ui_elements:
|
|
300
315
|
if child in ui_element._children:
|
|
301
|
-
|
|
302
|
-
|
|
316
|
+
# Delegate to the actual parent container's remove_child method.
|
|
317
|
+
# This ensures specialized containers (like ParameterList) can perform
|
|
318
|
+
# their specific cleanup logic (e.g., marking parent nodes as unresolved).
|
|
319
|
+
if ui_element is not self:
|
|
320
|
+
ui_element.remove_child(child)
|
|
321
|
+
else:
|
|
322
|
+
# We are the direct parent, so handle removal directly
|
|
323
|
+
child._parent = None
|
|
324
|
+
ui_element._children.remove(child)
|
|
303
325
|
break
|
|
304
326
|
ui_elements.extend(ui_element._children)
|
|
305
327
|
if self._node_context is not None and isinstance(child, BaseNodeElement):
|
|
@@ -366,8 +388,23 @@ class BaseNodeElement:
|
|
|
366
388
|
return event_data
|
|
367
389
|
|
|
368
390
|
|
|
369
|
-
|
|
370
|
-
|
|
391
|
+
class UIOptionsMixin:
|
|
392
|
+
"""Mixin providing UI options update functionality for classes with ui_options."""
|
|
393
|
+
|
|
394
|
+
def update_ui_options_key(self, key: str, value: Any) -> None:
|
|
395
|
+
"""Update a single UI option key."""
|
|
396
|
+
ui_options = self.ui_options
|
|
397
|
+
ui_options[key] = value
|
|
398
|
+
self.ui_options = ui_options
|
|
399
|
+
|
|
400
|
+
def update_ui_options(self, updates: dict[str, Any]) -> None:
|
|
401
|
+
"""Update multiple UI options at once."""
|
|
402
|
+
ui_options = self.ui_options
|
|
403
|
+
ui_options.update(updates)
|
|
404
|
+
self.ui_options = ui_options
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class ParameterMessage(BaseNodeElement, UIOptionsMixin):
|
|
371
408
|
"""Represents a UI message element, such as a warning or informational text."""
|
|
372
409
|
|
|
373
410
|
# Define default titles as a class-level constant
|
|
@@ -384,13 +421,97 @@ class ParameterMessage(BaseNodeElement):
|
|
|
384
421
|
type VariantType = Literal["info", "warning", "error", "success", "tip", "none"]
|
|
385
422
|
|
|
386
423
|
element_type: str = field(default_factory=lambda: ParameterMessage.__name__)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
424
|
+
_variant: VariantType = field(init=False)
|
|
425
|
+
_title: str | None = field(default=None, init=False)
|
|
426
|
+
_value: str = field(init=False)
|
|
427
|
+
_button_link: str | None = field(default=None, init=False)
|
|
428
|
+
_button_text: str | None = field(default=None, init=False)
|
|
429
|
+
_full_width: bool = field(default=False, init=False)
|
|
430
|
+
_ui_options: dict = field(default_factory=dict, init=False)
|
|
431
|
+
|
|
432
|
+
def __init__( # noqa: PLR0913
|
|
433
|
+
self,
|
|
434
|
+
variant: VariantType,
|
|
435
|
+
value: str,
|
|
436
|
+
*,
|
|
437
|
+
title: str | None = None,
|
|
438
|
+
button_link: str | None = None,
|
|
439
|
+
button_text: str | None = None,
|
|
440
|
+
full_width: bool = False,
|
|
441
|
+
ui_options: dict | None = None,
|
|
442
|
+
**kwargs,
|
|
443
|
+
):
|
|
444
|
+
super().__init__(element_type=ParameterMessage.__name__, **kwargs)
|
|
445
|
+
self._variant = variant
|
|
446
|
+
self._title = title
|
|
447
|
+
self._value = value
|
|
448
|
+
self._button_link = button_link
|
|
449
|
+
self._button_text = button_text
|
|
450
|
+
self._full_width = full_width
|
|
451
|
+
self._ui_options = ui_options or {}
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def variant(self) -> VariantType:
|
|
455
|
+
return self._variant
|
|
456
|
+
|
|
457
|
+
@variant.setter
|
|
458
|
+
@BaseNodeElement.emits_update_on_write
|
|
459
|
+
def variant(self, value: VariantType) -> None:
|
|
460
|
+
self._variant = value
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def title(self) -> str | None:
|
|
464
|
+
return self._title
|
|
465
|
+
|
|
466
|
+
@title.setter
|
|
467
|
+
@BaseNodeElement.emits_update_on_write
|
|
468
|
+
def title(self, value: str | None) -> None:
|
|
469
|
+
self._title = value
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def value(self) -> str:
|
|
473
|
+
return self._value
|
|
474
|
+
|
|
475
|
+
@value.setter
|
|
476
|
+
@BaseNodeElement.emits_update_on_write
|
|
477
|
+
def value(self, value: str) -> None:
|
|
478
|
+
self._value = value
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def button_link(self) -> str | None:
|
|
482
|
+
return self._button_link
|
|
483
|
+
|
|
484
|
+
@button_link.setter
|
|
485
|
+
@BaseNodeElement.emits_update_on_write
|
|
486
|
+
def button_link(self, value: str | None) -> None:
|
|
487
|
+
self._button_link = value
|
|
488
|
+
|
|
489
|
+
@property
|
|
490
|
+
def button_text(self) -> str | None:
|
|
491
|
+
return self._button_text
|
|
492
|
+
|
|
493
|
+
@button_text.setter
|
|
494
|
+
@BaseNodeElement.emits_update_on_write
|
|
495
|
+
def button_text(self, value: str | None) -> None:
|
|
496
|
+
self._button_text = value
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def full_width(self) -> bool:
|
|
500
|
+
return self._full_width
|
|
501
|
+
|
|
502
|
+
@full_width.setter
|
|
503
|
+
@BaseNodeElement.emits_update_on_write
|
|
504
|
+
def full_width(self, value: bool) -> None:
|
|
505
|
+
self._full_width = value
|
|
506
|
+
|
|
507
|
+
@property
|
|
508
|
+
def ui_options(self) -> dict:
|
|
509
|
+
return self._ui_options
|
|
510
|
+
|
|
511
|
+
@ui_options.setter
|
|
512
|
+
@BaseNodeElement.emits_update_on_write
|
|
513
|
+
def ui_options(self, value: dict) -> None:
|
|
514
|
+
self._ui_options = value
|
|
394
515
|
|
|
395
516
|
def to_dict(self) -> dict[str, Any]:
|
|
396
517
|
data = super().to_dict()
|
|
@@ -429,11 +550,21 @@ class ParameterMessage(BaseNodeElement):
|
|
|
429
550
|
return event_data
|
|
430
551
|
|
|
431
552
|
|
|
432
|
-
|
|
433
|
-
class ParameterGroup(BaseNodeElement):
|
|
553
|
+
class ParameterGroup(BaseNodeElement, UIOptionsMixin):
|
|
434
554
|
"""UI element for a group of parameters."""
|
|
435
555
|
|
|
436
|
-
ui_options: dict =
|
|
556
|
+
def __init__(self, name: str, ui_options: dict | None = None, **kwargs):
|
|
557
|
+
super().__init__(name=name, **kwargs)
|
|
558
|
+
self._ui_options = ui_options or {}
|
|
559
|
+
|
|
560
|
+
@property
|
|
561
|
+
def ui_options(self) -> dict:
|
|
562
|
+
return self._ui_options
|
|
563
|
+
|
|
564
|
+
@ui_options.setter
|
|
565
|
+
@BaseNodeElement.emits_update_on_write
|
|
566
|
+
def ui_options(self, value: dict) -> None:
|
|
567
|
+
self._ui_options = value
|
|
437
568
|
|
|
438
569
|
def to_dict(self) -> dict[str, Any]:
|
|
439
570
|
"""Returns a nested dictionary representation of this node and its children.
|
|
@@ -541,7 +672,7 @@ class ParameterBase(BaseNodeElement, ABC):
|
|
|
541
672
|
pass
|
|
542
673
|
|
|
543
674
|
|
|
544
|
-
class Parameter(BaseNodeElement):
|
|
675
|
+
class Parameter(BaseNodeElement, UIOptionsMixin):
|
|
545
676
|
# This is the list of types that the Parameter can accept, either externally or when internally treated as a property.
|
|
546
677
|
# Today, we can accept multiple types for input, but only a single output type.
|
|
547
678
|
tooltip: str | list[dict] # Default tooltip, can be string or list of dicts
|
|
@@ -552,7 +683,11 @@ class Parameter(BaseNodeElement):
|
|
|
552
683
|
tooltip_as_input: str | list[dict] | None = None
|
|
553
684
|
tooltip_as_property: str | list[dict] | None = None
|
|
554
685
|
tooltip_as_output: str | list[dict] | None = None
|
|
686
|
+
|
|
687
|
+
# "settable" here means whether it can be assigned to during regular business operation.
|
|
688
|
+
# During save/load, this value IS still serialized to save its proper state.
|
|
555
689
|
settable: bool = True
|
|
690
|
+
|
|
556
691
|
user_defined: bool = False
|
|
557
692
|
_allowed_modes: set = field(
|
|
558
693
|
default_factory=lambda: {
|
|
@@ -594,7 +729,7 @@ class Parameter(BaseNodeElement):
|
|
|
594
729
|
if not element_id:
|
|
595
730
|
element_id = str(uuid.uuid4().hex)
|
|
596
731
|
if not element_type:
|
|
597
|
-
element_type =
|
|
732
|
+
element_type = self.__class__.__name__
|
|
598
733
|
super().__init__(element_id=element_id, element_type=element_type)
|
|
599
734
|
self.name = name
|
|
600
735
|
self.tooltip = tooltip
|
|
@@ -748,7 +883,10 @@ class Parameter(BaseNodeElement):
|
|
|
748
883
|
ui_options = ui_options | trait.ui_options_for_trait()
|
|
749
884
|
ui_options = ui_options | self._ui_options
|
|
750
885
|
if self._parent is not None and isinstance(self._parent, ParameterGroup):
|
|
751
|
-
|
|
886
|
+
# Access the field value directly for ParameterGroup
|
|
887
|
+
parent_ui_options = getattr(self._parent, "ui_options", {})
|
|
888
|
+
if isinstance(parent_ui_options, dict):
|
|
889
|
+
ui_options = ui_options | parent_ui_options
|
|
752
890
|
return ui_options
|
|
753
891
|
|
|
754
892
|
@ui_options.setter
|
|
@@ -951,6 +1089,7 @@ class ControlParameter(Parameter, ABC):
|
|
|
951
1089
|
traits: set[Trait.__class__ | Trait] | None = None,
|
|
952
1090
|
converters: list[Callable[[Any], Any]] | None = None,
|
|
953
1091
|
validators: list[Callable[[Parameter, Any], None]] | None = None,
|
|
1092
|
+
ui_options: dict | None = None,
|
|
954
1093
|
*,
|
|
955
1094
|
user_defined: bool = False,
|
|
956
1095
|
):
|
|
@@ -970,6 +1109,7 @@ class ControlParameter(Parameter, ABC):
|
|
|
970
1109
|
traits=traits,
|
|
971
1110
|
converters=converters,
|
|
972
1111
|
validators=validators,
|
|
1112
|
+
ui_options=ui_options,
|
|
973
1113
|
user_defined=user_defined,
|
|
974
1114
|
element_type=self.__class__.__name__,
|
|
975
1115
|
)
|
|
@@ -980,6 +1120,7 @@ class ControlParameterInput(ControlParameter):
|
|
|
980
1120
|
self,
|
|
981
1121
|
tooltip: str | list[dict] = "Connection from previous node in the execution chain",
|
|
982
1122
|
name: str = "exec_in",
|
|
1123
|
+
display_name: str | None = "Flow In",
|
|
983
1124
|
tooltip_as_input: str | list[dict] | None = None,
|
|
984
1125
|
tooltip_as_property: str | list[dict] | None = None,
|
|
985
1126
|
tooltip_as_output: str | list[dict] | None = None,
|
|
@@ -992,6 +1133,11 @@ class ControlParameterInput(ControlParameter):
|
|
|
992
1133
|
allowed_modes = {ParameterMode.INPUT}
|
|
993
1134
|
input_types = [ParameterTypeBuiltin.CONTROL_TYPE.value]
|
|
994
1135
|
|
|
1136
|
+
if display_name is None:
|
|
1137
|
+
ui_options = None
|
|
1138
|
+
else:
|
|
1139
|
+
ui_options = {"display_name": display_name}
|
|
1140
|
+
|
|
995
1141
|
# Call parent with a few explicit tweaks.
|
|
996
1142
|
super().__init__(
|
|
997
1143
|
name=name,
|
|
@@ -1005,6 +1151,7 @@ class ControlParameterInput(ControlParameter):
|
|
|
1005
1151
|
traits=traits,
|
|
1006
1152
|
converters=converters,
|
|
1007
1153
|
validators=validators,
|
|
1154
|
+
ui_options=ui_options,
|
|
1008
1155
|
user_defined=user_defined,
|
|
1009
1156
|
)
|
|
1010
1157
|
|
|
@@ -1014,6 +1161,7 @@ class ControlParameterOutput(ControlParameter):
|
|
|
1014
1161
|
self,
|
|
1015
1162
|
tooltip: str | list[dict] = "Connection to the next node in the execution chain",
|
|
1016
1163
|
name: str = "exec_out",
|
|
1164
|
+
display_name: str | None = "Flow Out",
|
|
1017
1165
|
tooltip_as_input: str | list[dict] | None = None,
|
|
1018
1166
|
tooltip_as_property: str | list[dict] | None = None,
|
|
1019
1167
|
tooltip_as_output: str | list[dict] | None = None,
|
|
@@ -1026,6 +1174,11 @@ class ControlParameterOutput(ControlParameter):
|
|
|
1026
1174
|
allowed_modes = {ParameterMode.OUTPUT}
|
|
1027
1175
|
output_type = ParameterTypeBuiltin.CONTROL_TYPE.value
|
|
1028
1176
|
|
|
1177
|
+
if display_name is None:
|
|
1178
|
+
ui_options = None
|
|
1179
|
+
else:
|
|
1180
|
+
ui_options = {"display_name": display_name}
|
|
1181
|
+
|
|
1029
1182
|
# Call parent with a few explicit tweaks.
|
|
1030
1183
|
super().__init__(
|
|
1031
1184
|
name=name,
|
|
@@ -1039,6 +1192,7 @@ class ControlParameterOutput(ControlParameter):
|
|
|
1039
1192
|
traits=traits,
|
|
1040
1193
|
converters=converters,
|
|
1041
1194
|
validators=validators,
|
|
1195
|
+
ui_options=ui_options,
|
|
1042
1196
|
user_defined=user_defined,
|
|
1043
1197
|
)
|
|
1044
1198
|
|
|
@@ -1093,6 +1247,23 @@ class ParameterContainer(Parameter, ABC):
|
|
|
1093
1247
|
element_type=element_type,
|
|
1094
1248
|
)
|
|
1095
1249
|
|
|
1250
|
+
def __bool__(self) -> bool:
|
|
1251
|
+
"""Parameter containers are always truthy, even when empty.
|
|
1252
|
+
|
|
1253
|
+
This overrides Python's default truthiness behavior for containers with __len__().
|
|
1254
|
+
By default, Python makes objects with __len__() falsy when len() == 0, which
|
|
1255
|
+
caused bugs where empty ParameterList/ParameterDictionary objects would fail
|
|
1256
|
+
'if param' checks and fall back to stale cached values instead of computing
|
|
1257
|
+
fresh empty results.
|
|
1258
|
+
|
|
1259
|
+
Unlike standard Python containers, ParameterContainer objects represent
|
|
1260
|
+
parameter structure/definitions rather than just data, so they remain
|
|
1261
|
+
meaningful even when empty.
|
|
1262
|
+
|
|
1263
|
+
See: https://github.com/griptape-ai/griptape-nodes/issues/1799
|
|
1264
|
+
"""
|
|
1265
|
+
return True
|
|
1266
|
+
|
|
1096
1267
|
@abstractmethod
|
|
1097
1268
|
def add_child_parameter(self) -> Parameter:
|
|
1098
1269
|
pass
|
|
@@ -1224,6 +1395,40 @@ class ParameterList(ParameterContainer):
|
|
|
1224
1395
|
|
|
1225
1396
|
return param
|
|
1226
1397
|
|
|
1398
|
+
def add_child(self, child: BaseNodeElement) -> None:
|
|
1399
|
+
"""Override to mark parent node as unresolved when children are added.
|
|
1400
|
+
|
|
1401
|
+
When a ParameterList gains a child parameter, the parent node needs to be
|
|
1402
|
+
marked as unresolved to trigger re-evaluation of the node's state and outputs.
|
|
1403
|
+
"""
|
|
1404
|
+
super().add_child(child)
|
|
1405
|
+
|
|
1406
|
+
# Mark the parent node as unresolved since the parameter structure changed
|
|
1407
|
+
if self._node_context is not None:
|
|
1408
|
+
# Import at runtime to avoid circular import
|
|
1409
|
+
from griptape_nodes.exe_types.node_types import NodeResolutionState
|
|
1410
|
+
|
|
1411
|
+
self._node_context.make_node_unresolved(
|
|
1412
|
+
current_states_to_trigger_change_event={NodeResolutionState.RESOLVED, NodeResolutionState.RESOLVING}
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1415
|
+
def remove_child(self, child: BaseNodeElement | str) -> None:
|
|
1416
|
+
"""Override to mark parent node as unresolved when children are removed.
|
|
1417
|
+
|
|
1418
|
+
When a ParameterList loses a child parameter, the parent node needs to be
|
|
1419
|
+
marked as unresolved to trigger re-evaluation of the node's state and outputs.
|
|
1420
|
+
"""
|
|
1421
|
+
super().remove_child(child)
|
|
1422
|
+
|
|
1423
|
+
# Mark the parent node as unresolved since the parameter structure changed
|
|
1424
|
+
if self._node_context is not None:
|
|
1425
|
+
# Import at runtime to avoid circular import
|
|
1426
|
+
from griptape_nodes.exe_types.node_types import NodeResolutionState
|
|
1427
|
+
|
|
1428
|
+
self._node_context.make_node_unresolved(
|
|
1429
|
+
current_states_to_trigger_change_event={NodeResolutionState.RESOLVED, NodeResolutionState.RESOLVING}
|
|
1430
|
+
)
|
|
1431
|
+
|
|
1227
1432
|
|
|
1228
1433
|
class ParameterKeyValuePair(Parameter):
|
|
1229
1434
|
def __init__( # noqa: PLR0913
|
griptape_nodes/exe_types/flow.py
CHANGED
|
File without changes
|
|
@@ -68,6 +68,7 @@ class BaseNode(ABC):
|
|
|
68
68
|
_entry_control_parameter: Parameter | None = (
|
|
69
69
|
None # The control input parameter used to enter this node during execution
|
|
70
70
|
)
|
|
71
|
+
lock: bool = False # When lock is true, the node is locked and can't be modified. When lock is false, the node is unlocked and can be modified.
|
|
71
72
|
|
|
72
73
|
@property
|
|
73
74
|
def parameters(self) -> list[Parameter]:
|
|
@@ -367,8 +368,10 @@ class BaseNode(ABC):
|
|
|
367
368
|
"""
|
|
368
369
|
parameter = self.get_parameter_by_name(param)
|
|
369
370
|
if parameter is not None:
|
|
370
|
-
trait
|
|
371
|
-
|
|
371
|
+
# Find the Options trait by type since element_id is a UUID
|
|
372
|
+
traits = parameter.find_elements_by_type(Options)
|
|
373
|
+
if traits:
|
|
374
|
+
trait = traits[0] # Take the first Options trait
|
|
372
375
|
trait.choices = choices
|
|
373
376
|
|
|
374
377
|
if default in choices:
|
|
@@ -377,6 +380,13 @@ class BaseNode(ABC):
|
|
|
377
380
|
else:
|
|
378
381
|
msg = f"Default model '{default}' is not in the provided choices."
|
|
379
382
|
raise ValueError(msg)
|
|
383
|
+
|
|
384
|
+
# Update the manually set UI options to include the new simple_dropdown
|
|
385
|
+
if hasattr(parameter, "_ui_options") and parameter._ui_options:
|
|
386
|
+
parameter._ui_options["simple_dropdown"] = choices
|
|
387
|
+
else:
|
|
388
|
+
msg = f"No Options trait found for parameter '{param}'."
|
|
389
|
+
raise ValueError(msg)
|
|
380
390
|
else:
|
|
381
391
|
msg = f"Parameter '{param}' not found for updating model choices."
|
|
382
392
|
raise ValueError(msg)
|
|
@@ -392,9 +402,14 @@ class BaseNode(ABC):
|
|
|
392
402
|
"""
|
|
393
403
|
parameter = self.get_parameter_by_name(param)
|
|
394
404
|
if parameter is not None:
|
|
395
|
-
trait
|
|
396
|
-
|
|
405
|
+
# Find the Options trait by type since element_id is a UUID
|
|
406
|
+
traits = parameter.find_elements_by_type(Options)
|
|
407
|
+
if traits:
|
|
408
|
+
trait = traits[0] # Take the first Options trait
|
|
397
409
|
parameter.remove_trait(trait)
|
|
410
|
+
else:
|
|
411
|
+
msg = f"No Options trait found for parameter '{param}'."
|
|
412
|
+
raise ValueError(msg)
|
|
398
413
|
else:
|
|
399
414
|
msg = f"Parameter '{param}' not found for removing options trait."
|
|
400
415
|
raise ValueError(msg)
|
|
@@ -575,7 +590,7 @@ class BaseNode(ABC):
|
|
|
575
590
|
param = self.get_parameter_by_name(param_name)
|
|
576
591
|
if param and isinstance(param, ParameterContainer):
|
|
577
592
|
value = handle_container_parameter(self, param)
|
|
578
|
-
if value:
|
|
593
|
+
if value is not None:
|
|
579
594
|
return value
|
|
580
595
|
if param_name in self.parameter_values:
|
|
581
596
|
return self.parameter_values[param_name]
|
|
File without changes
|
|
File without changes
|
|
@@ -88,11 +88,12 @@ class ResolveNodeState(State):
|
|
|
88
88
|
return CompleteState
|
|
89
89
|
|
|
90
90
|
# Mark the node unresolved, and broadcast an event to the GUI.
|
|
91
|
-
context.current_node.
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
if not context.current_node.lock:
|
|
92
|
+
context.current_node.make_node_unresolved(
|
|
93
|
+
current_states_to_trigger_change_event=set(
|
|
94
|
+
{NodeResolutionState.UNRESOLVED, NodeResolutionState.RESOLVED, NodeResolutionState.RESOLVING}
|
|
95
|
+
)
|
|
94
96
|
)
|
|
95
|
-
)
|
|
96
97
|
# Now broadcast that we have a current control node.
|
|
97
98
|
EventBus.publish_event(
|
|
98
99
|
ExecutionGriptapeNodeEvent(
|
griptape_nodes/machines/fsm.py
CHANGED
|
File without changes
|