griptape-nodes 0.34.3__py3-none-any.whl → 0.35.1__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 +56 -41
- griptape_nodes/app/app.py +3 -3
- griptape_nodes/bootstrap/bootstrap_script.py +5 -3
- griptape_nodes/drivers/__init__.py +1 -0
- griptape_nodes/drivers/storage/__init__.py +4 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +38 -0
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +85 -0
- griptape_nodes/drivers/storage/local_storage_driver.py +49 -0
- griptape_nodes/exe_types/core_types.py +57 -1
- griptape_nodes/exe_types/flow.py +3 -1
- griptape_nodes/exe_types/node_types.py +34 -0
- griptape_nodes/machines/node_resolution.py +9 -1
- griptape_nodes/node_library/library_registry.py +2 -0
- griptape_nodes/retained_mode/events/execution_events.py +2 -0
- griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +1 -1
- griptape_nodes/retained_mode/events/node_events.py +13 -16
- griptape_nodes/retained_mode/events/parameter_events.py +10 -3
- griptape_nodes/retained_mode/events/static_file_events.py +26 -0
- griptape_nodes/retained_mode/griptape_nodes.py +3 -1
- griptape_nodes/retained_mode/managers/context_manager.py +123 -87
- griptape_nodes/retained_mode/managers/flow_manager.py +126 -86
- griptape_nodes/retained_mode/managers/library_manager.py +279 -95
- griptape_nodes/retained_mode/managers/node_manager.py +230 -159
- griptape_nodes/retained_mode/managers/settings.py +6 -1
- griptape_nodes/retained_mode/managers/static_files_manager.py +77 -54
- griptape_nodes/retained_mode/managers/workflow_manager.py +62 -12
- {griptape_nodes-0.34.3.dist-info → griptape_nodes-0.35.1.dist-info}/METADATA +1 -1
- {griptape_nodes-0.34.3.dist-info → griptape_nodes-0.35.1.dist-info}/RECORD +31 -26
- {griptape_nodes-0.34.3.dist-info → griptape_nodes-0.35.1.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.34.3.dist-info → griptape_nodes-0.35.1.dist-info}/entry_points.txt +0 -0
- {griptape_nodes-0.34.3.dist-info → griptape_nodes-0.35.1.dist-info}/licenses/LICENSE +0 -0
griptape_nodes/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ with console.status("Loading Griptape Nodes...") as status:
|
|
|
9
9
|
import argparse
|
|
10
10
|
import importlib.metadata
|
|
11
11
|
import json
|
|
12
|
+
import os
|
|
12
13
|
import shutil
|
|
13
14
|
import sys
|
|
14
15
|
import tarfile
|
|
@@ -61,27 +62,37 @@ def main() -> None:
|
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def _run_init(
|
|
64
|
-
*,
|
|
65
|
+
*, workspace_directory: str | None = None, api_key: str | None = None, register_advanced_library: bool | None = None
|
|
65
66
|
) -> None:
|
|
66
|
-
"""Runs through the engine init steps
|
|
67
|
+
"""Runs through the engine init steps.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
workspace_directory (str | None): The workspace directory to set.
|
|
71
|
+
api_key (str | None): The API key to set.
|
|
72
|
+
register_advanced_library (bool | None): Whether to register the advanced library.
|
|
73
|
+
"""
|
|
67
74
|
__init_system_config()
|
|
68
|
-
_prompt_for_workspace(
|
|
75
|
+
_prompt_for_workspace(workspace_directory=workspace_directory)
|
|
69
76
|
_prompt_for_api_key(api_key=api_key)
|
|
70
77
|
_prompt_for_libraries_to_register(register_advanced_library=register_advanced_library)
|
|
71
78
|
_sync_assets()
|
|
72
79
|
console.print("[bold green]Initialization complete![/bold green]")
|
|
73
80
|
|
|
74
81
|
|
|
75
|
-
def _start_engine(*, no_update: bool) -> None:
|
|
82
|
+
def _start_engine(*, no_update: bool = False) -> None:
|
|
76
83
|
"""Starts the Griptape Nodes engine.
|
|
77
84
|
|
|
78
85
|
Args:
|
|
79
|
-
no_update: If True, skips the auto-update check.
|
|
86
|
+
no_update (bool): If True, skips the auto-update check.
|
|
80
87
|
"""
|
|
81
88
|
if not CONFIG_DIR.exists():
|
|
82
89
|
# Default init flow if there is no config directory
|
|
83
90
|
console.print("[bold green]Config directory not found. Initializing...[/bold green]")
|
|
84
|
-
_run_init(
|
|
91
|
+
_run_init(
|
|
92
|
+
workspace_directory=os.getenv("GTN_WORKSPACE_DIRECTORY"),
|
|
93
|
+
api_key=os.getenv("GTN_API_KEY"),
|
|
94
|
+
register_advanced_library=os.getenv("GTN_REGISTER_ADVANCED_LIBRARY", "false").lower() == "true",
|
|
95
|
+
)
|
|
85
96
|
|
|
86
97
|
# Confusing double negation -- If `no_update` is set, we want to skip the update
|
|
87
98
|
if not no_update:
|
|
@@ -115,15 +126,17 @@ def _get_args() -> argparse.Namespace:
|
|
|
115
126
|
init_parser.add_argument(
|
|
116
127
|
"--api-key",
|
|
117
128
|
help="Set the Griptape Nodes API key.",
|
|
129
|
+
default=os.getenv("GTN_API_KEY", None),
|
|
118
130
|
)
|
|
119
131
|
init_parser.add_argument(
|
|
120
132
|
"--workspace-directory",
|
|
121
133
|
help="Set the Griptape Nodes workspace directory.",
|
|
134
|
+
default=os.getenv("GTN_WORKSPACE_DIRECTORY", None),
|
|
122
135
|
)
|
|
136
|
+
register_advanced_library = os.getenv("GTN_REGISTER_ADVANCED_LIBRARY", None)
|
|
123
137
|
init_parser.add_argument(
|
|
124
138
|
"--register-advanced-library",
|
|
125
|
-
|
|
126
|
-
default=None,
|
|
139
|
+
default=register_advanced_library.lower() == "true" if register_advanced_library is not None else None,
|
|
127
140
|
help="Install the Griptape Nodes Advanced Image Library.",
|
|
128
141
|
)
|
|
129
142
|
|
|
@@ -181,47 +194,46 @@ def _prompt_for_api_key(api_key: str | None = None) -> None:
|
|
|
181
194
|
Once the key is generated, copy and paste its value here to proceed."""
|
|
182
195
|
console.print(Panel(explainer, expand=False))
|
|
183
196
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
show_default=True,
|
|
192
|
-
)
|
|
197
|
+
default_api_key = secrets_manager.get_secret("GT_CLOUD_API_KEY", should_error_on_not_found=False)
|
|
198
|
+
while api_key is None:
|
|
199
|
+
api_key = Prompt.ask(
|
|
200
|
+
"Griptape API Key",
|
|
201
|
+
default=default_api_key,
|
|
202
|
+
show_default=True,
|
|
203
|
+
)
|
|
193
204
|
|
|
194
|
-
secrets_manager.set_secret("GT_CLOUD_API_KEY",
|
|
205
|
+
secrets_manager.set_secret("GT_CLOUD_API_KEY", api_key)
|
|
195
206
|
console.print("[bold green]Griptape API Key set")
|
|
196
207
|
|
|
197
208
|
|
|
198
|
-
def _prompt_for_workspace(*,
|
|
209
|
+
def _prompt_for_workspace(*, workspace_directory: str | None) -> None:
|
|
199
210
|
"""Prompts the user for their workspace directory and stores it in config directory."""
|
|
200
|
-
if
|
|
211
|
+
if workspace_directory is None:
|
|
201
212
|
explainer = """[bold cyan]Workspace Directory[/bold cyan]
|
|
202
213
|
Select the workspace directory. This is the location where Griptape Nodes will store your saved workflows.
|
|
203
214
|
You may enter a custom directory or press Return to accept the default workspace directory"""
|
|
204
215
|
console.print(Panel(explainer, expand=False))
|
|
205
216
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
default_workspace_directory = workspace_directory or config_manager.get_config_value("workspace_directory")
|
|
218
|
+
while workspace_directory is None:
|
|
219
|
+
try:
|
|
220
|
+
workspace_to_test = Prompt.ask(
|
|
221
|
+
"Workspace Directory",
|
|
222
|
+
default=default_workspace_directory,
|
|
223
|
+
show_default=True,
|
|
224
|
+
)
|
|
225
|
+
# Try to resolve the path to check if it exists
|
|
226
|
+
if workspace_to_test is not None:
|
|
227
|
+
Path(workspace_to_test).expanduser().resolve()
|
|
228
|
+
workspace_directory = workspace_to_test
|
|
229
|
+
except OSError as e:
|
|
230
|
+
console.print(f"[bold red]Invalid workspace directory: {e}[/bold red]")
|
|
231
|
+
except json.JSONDecodeError as e:
|
|
232
|
+
console.print(f"[bold red]Error reading config file: {e}[/bold red]")
|
|
233
|
+
|
|
234
|
+
workspace_path = Path(workspace_directory).expanduser().resolve()
|
|
235
|
+
config_manager.set_config_value("workspace_directory", str(workspace_path))
|
|
219
236
|
|
|
220
|
-
valid_workspace = True
|
|
221
|
-
except OSError as e:
|
|
222
|
-
console.print(f"[bold red]Invalid workspace directory: {e}[/bold red]")
|
|
223
|
-
except json.JSONDecodeError as e:
|
|
224
|
-
console.print(f"[bold red]Error reading config file: {e}[/bold red]")
|
|
225
237
|
console.print(f"[bold green]Workspace directory set to: {config_manager.workspace_path}[/bold green]")
|
|
226
238
|
|
|
227
239
|
|
|
@@ -429,6 +441,7 @@ def _uninstall_self() -> None:
|
|
|
429
441
|
# Remove config and data directories
|
|
430
442
|
console.print("[bold]Removing config and data directories...[/bold]")
|
|
431
443
|
dirs = [(CONFIG_DIR, "Config Dir"), (DATA_DIR, "Data Dir")]
|
|
444
|
+
caveats = []
|
|
432
445
|
for dir_path, dir_name in dirs:
|
|
433
446
|
if dir_path.exists():
|
|
434
447
|
console.print(f"[bold]Removing {dir_name} '{dir_path}'...[/bold]")
|
|
@@ -436,10 +449,12 @@ def _uninstall_self() -> None:
|
|
|
436
449
|
shutil.rmtree(dir_path)
|
|
437
450
|
except OSError as exc:
|
|
438
451
|
console.print(f"[red]Error removing {dir_name} '{dir_path}': {exc}[/red]")
|
|
452
|
+
caveats.append(
|
|
453
|
+
f"- [red]Error removing {dir_name} '{dir_path}'. You may want remove this directory manually.[/red]"
|
|
454
|
+
)
|
|
439
455
|
else:
|
|
440
456
|
console.print(f"[yellow]{dir_name} '{dir_path}' does not exist; skipping.[/yellow]")
|
|
441
457
|
|
|
442
|
-
caveats = []
|
|
443
458
|
# Handle any remaining config files not removed by design
|
|
444
459
|
remaining_config_files = config_manager.config_files
|
|
445
460
|
if remaining_config_files:
|
|
@@ -461,8 +476,8 @@ def _uninstall_self() -> None:
|
|
|
461
476
|
def _process_args(args: argparse.Namespace) -> None: # noqa: C901, PLR0912
|
|
462
477
|
if args.command == "init":
|
|
463
478
|
_run_init(
|
|
464
|
-
api_key=args.api_key,
|
|
465
479
|
workspace_directory=args.workspace_directory,
|
|
480
|
+
api_key=args.api_key,
|
|
466
481
|
register_advanced_library=args.register_advanced_library,
|
|
467
482
|
)
|
|
468
483
|
elif args.command == "engine":
|
|
@@ -529,7 +544,7 @@ def __init_system_config() -> None:
|
|
|
529
544
|
for file_name in files_to_create:
|
|
530
545
|
file_path = CONFIG_DIR / file_name[0]
|
|
531
546
|
if not file_path.exists():
|
|
532
|
-
with Path.open(file_path, "w") as file:
|
|
547
|
+
with Path.open(file_path, "w", encoding="utf-8") as file:
|
|
533
548
|
file.write(file_name[1])
|
|
534
549
|
|
|
535
550
|
|
griptape_nodes/app/app.py
CHANGED
|
@@ -16,7 +16,7 @@ from urllib.parse import urljoin
|
|
|
16
16
|
import httpx
|
|
17
17
|
import uvicorn
|
|
18
18
|
from dotenv import get_key
|
|
19
|
-
from fastapi import FastAPI, HTTPException, Request
|
|
19
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
20
20
|
from fastapi.middleware.cors import CORSMiddleware
|
|
21
21
|
from fastapi.staticfiles import StaticFiles
|
|
22
22
|
from griptape.events import (
|
|
@@ -154,15 +154,15 @@ def _serve_static_server() -> None:
|
|
|
154
154
|
return {"url": url}
|
|
155
155
|
|
|
156
156
|
@app.put("/static-uploads/{file_name:str}")
|
|
157
|
-
async def create_static_file(
|
|
157
|
+
async def create_static_file(request: Request, file_name: str) -> dict:
|
|
158
158
|
"""Upload a static file to the static server."""
|
|
159
159
|
if not STATIC_SERVER_ENABLED:
|
|
160
160
|
msg = "Static server is not enabled. Please set STATIC_SERVER_ENABLED to True."
|
|
161
161
|
raise ValueError(msg)
|
|
162
162
|
|
|
163
|
-
data = await file.read()
|
|
164
163
|
if not static_dir.exists():
|
|
165
164
|
static_dir.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
data = await request.body()
|
|
166
166
|
try:
|
|
167
167
|
Path(static_dir / file_name).write_bytes(data)
|
|
168
168
|
except binascii.Error as e:
|
|
@@ -17,6 +17,7 @@ from register_libraries_script import ( # type: ignore[import] - This import is
|
|
|
17
17
|
register_libraries,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
from griptape_nodes.exe_types.flow import ControlFlow
|
|
20
21
|
from griptape_nodes.exe_types.node_types import EndNode, StartNode
|
|
21
22
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
22
23
|
AppEvent,
|
|
@@ -63,9 +64,9 @@ def _load_user_workflow(path_to_workflow: str) -> None:
|
|
|
63
64
|
spec.loader.exec_module(module)
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def _load_flow_for_workflow() ->
|
|
67
|
+
def _load_flow_for_workflow() -> ControlFlow:
|
|
67
68
|
context_manager = GriptapeNodes.ContextManager()
|
|
68
|
-
return context_manager.
|
|
69
|
+
return context_manager.get_current_flow()
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
def _set_workflow_context(workflow_name: str) -> None:
|
|
@@ -208,7 +209,8 @@ def run(workflow_name: str, flow_input: Any) -> None:
|
|
|
208
209
|
# or nothing works. The name can be anything, but how about the workflow_name.
|
|
209
210
|
_set_workflow_context(workflow_name=workflow_name)
|
|
210
211
|
_load_user_workflow("workflow.py")
|
|
211
|
-
|
|
212
|
+
flow = _load_flow_for_workflow()
|
|
213
|
+
flow_name = flow.name
|
|
212
214
|
# Now let's set the input to the flow
|
|
213
215
|
_set_input_for_flow(flow_name=flow_name, flow_input=flow_input)
|
|
214
216
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Package for griptape nodes drivers."""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CreateSignedUploadUrlResponse(TypedDict):
|
|
6
|
+
"""Response type for create_signed_upload_url method."""
|
|
7
|
+
|
|
8
|
+
url: str
|
|
9
|
+
headers: dict
|
|
10
|
+
method: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseStorageDriver(ABC):
|
|
14
|
+
"""Base class for storage drivers."""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def create_signed_upload_url(self, file_name: str) -> CreateSignedUploadUrlResponse:
|
|
18
|
+
"""Create a signed upload URL for the given file name.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
file_name: The name of the file to create a signed URL for.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
CreateSignedUploadUrlResponse: A dictionary containing the signed URL, headers, and operation type.
|
|
25
|
+
"""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def create_signed_download_url(self, file_name: str) -> str:
|
|
30
|
+
"""Create a signed download URL for the given file name.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
file_name: The name of the file to create a signed URL for.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
str: The signed URL for downloading the file.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from urllib.parse import urljoin
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from griptape_nodes.drivers.storage.base_storage_driver import BaseStorageDriver, CreateSignedUploadUrlResponse
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("griptape_nodes")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GriptapeCloudStorageDriver(BaseStorageDriver):
|
|
13
|
+
"""Stores files using the Griptape Cloud's Asset APIs."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
*,
|
|
18
|
+
bucket_id: str | None = None,
|
|
19
|
+
base_url: str | None = None,
|
|
20
|
+
api_key: str | None = None,
|
|
21
|
+
headers: dict | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Initialize the GriptapeCloudStorageDriver.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
bucket_id: The ID of the bucket to use. If not provided, a new bucket will be provisioned.
|
|
27
|
+
base_url: The base URL for the Griptape Cloud API. If not provided, it will be retrieved from the environment variable "GT_CLOUD_BASE_URL" or default to "https://cloud.griptape.ai".
|
|
28
|
+
api_key: The API key for authentication. If not provided, it will be retrieved from the environment variable "GT_CLOUD_API_KEY".
|
|
29
|
+
headers: Additional headers to include in the requests. If not provided, the default headers will be used.
|
|
30
|
+
"""
|
|
31
|
+
self.base_url = (
|
|
32
|
+
base_url if base_url is not None else os.environ.get("GT_CLOUD_BASE_URL", "https://cloud.griptape.ai")
|
|
33
|
+
)
|
|
34
|
+
self.api_key = api_key if api_key is not None else os.environ.get("GT_CLOUD_API_KEY")
|
|
35
|
+
self.headers = (
|
|
36
|
+
headers
|
|
37
|
+
if headers is not None
|
|
38
|
+
else {
|
|
39
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.bucket_id = bucket_id
|
|
44
|
+
|
|
45
|
+
def create_signed_upload_url(self, file_name: str) -> CreateSignedUploadUrlResponse:
|
|
46
|
+
self._create_asset(file_name)
|
|
47
|
+
|
|
48
|
+
url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/asset-urls/{file_name}")
|
|
49
|
+
try:
|
|
50
|
+
response = httpx.post(url, json={"operation": "PUT"}, headers=self.headers)
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
except httpx.HTTPStatusError as e:
|
|
53
|
+
msg = f"Failed to create presigned URL for file {file_name}: {e}"
|
|
54
|
+
logger.error(msg)
|
|
55
|
+
raise RuntimeError(msg) from e
|
|
56
|
+
|
|
57
|
+
response_data = response.json()
|
|
58
|
+
|
|
59
|
+
return {"url": response_data["url"], "headers": response_data.get("headers", {}), "method": "PUT"}
|
|
60
|
+
|
|
61
|
+
def create_signed_download_url(self, file_name: str) -> str:
|
|
62
|
+
url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/asset-urls/{file_name}")
|
|
63
|
+
try:
|
|
64
|
+
response = httpx.post(url, json={"method": "GET"}, headers=self.headers)
|
|
65
|
+
response.raise_for_status()
|
|
66
|
+
except httpx.HTTPStatusError as e:
|
|
67
|
+
msg = f"Failed to create presigned URL for file {file_name}: {e}"
|
|
68
|
+
logger.error(msg)
|
|
69
|
+
raise RuntimeError(msg) from e
|
|
70
|
+
|
|
71
|
+
response_data = response.json()
|
|
72
|
+
|
|
73
|
+
return response_data["url"]
|
|
74
|
+
|
|
75
|
+
def _create_asset(self, asset_name: str) -> str:
|
|
76
|
+
url = urljoin(self.base_url, f"/api/buckets/{self.bucket_id}/assets")
|
|
77
|
+
try:
|
|
78
|
+
response = httpx.put(url=url, json={"name": asset_name}, headers=self.headers)
|
|
79
|
+
response.raise_for_status()
|
|
80
|
+
except httpx.HTTPStatusError as e:
|
|
81
|
+
msg = str(e)
|
|
82
|
+
logger.error(msg)
|
|
83
|
+
raise ValueError(msg) from e
|
|
84
|
+
|
|
85
|
+
return response.json()["name"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from griptape_nodes.app.app import STATIC_SERVER_ENABLED, STATIC_SERVER_HOST, STATIC_SERVER_PORT, STATIC_SERVER_URL
|
|
7
|
+
from griptape_nodes.drivers.storage.base_storage_driver import BaseStorageDriver, CreateSignedUploadUrlResponse
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("griptape_nodes")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LocalStorageDriver(BaseStorageDriver):
|
|
13
|
+
"""Stores files using the engine's local static server."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, base_url: str | None = None) -> None:
|
|
16
|
+
"""Initialize the LocalStorageDriver.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
base_url: The base URL for the static file server. If not provided, it will be constructed
|
|
20
|
+
"""
|
|
21
|
+
if not STATIC_SERVER_ENABLED:
|
|
22
|
+
msg = "Static server is not enabled. Please set STATIC_SERVER_ENABLED to True."
|
|
23
|
+
raise ValueError(msg)
|
|
24
|
+
if base_url is None:
|
|
25
|
+
self.base_url = f"http://{STATIC_SERVER_HOST}:{STATIC_SERVER_PORT}{STATIC_SERVER_URL}"
|
|
26
|
+
else:
|
|
27
|
+
self.base_url = base_url
|
|
28
|
+
|
|
29
|
+
def create_signed_upload_url(self, file_name: str) -> CreateSignedUploadUrlResponse:
|
|
30
|
+
static_url = urljoin(self.base_url, "/static-upload-urls")
|
|
31
|
+
try:
|
|
32
|
+
response = httpx.post(static_url, json={"file_name": file_name})
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
except httpx.HTTPStatusError as e:
|
|
35
|
+
msg = f"Failed to create presigned URL for file {file_name}: {e}"
|
|
36
|
+
logger.error(msg)
|
|
37
|
+
raise RuntimeError(msg) from e
|
|
38
|
+
|
|
39
|
+
response_data = response.json()
|
|
40
|
+
url = response_data.get("url")
|
|
41
|
+
if url is None:
|
|
42
|
+
msg = f"Failed to create presigned URL for file {file_name}: {response_data}"
|
|
43
|
+
logger.error(msg)
|
|
44
|
+
raise ValueError(msg)
|
|
45
|
+
|
|
46
|
+
return {"url": url, "headers": response_data.get("headers", {}), "method": "PUT"}
|
|
47
|
+
|
|
48
|
+
def create_signed_download_url(self, file_name: str) -> str:
|
|
49
|
+
return urljoin(self.base_url, f"/static/{file_name}")
|
|
@@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from enum import Enum, auto
|
|
8
|
-
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, Self, TypeVar
|
|
8
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, NamedTuple, Self, TypeVar
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from collections.abc import Callable
|
|
@@ -272,6 +272,62 @@ class BaseNodeElement:
|
|
|
272
272
|
return cls._stack[-1] if cls._stack else None
|
|
273
273
|
|
|
274
274
|
|
|
275
|
+
@dataclass(kw_only=True)
|
|
276
|
+
class ParameterMessage(BaseNodeElement):
|
|
277
|
+
"""Represents a UI message element, such as a warning or informational text."""
|
|
278
|
+
|
|
279
|
+
# Define default titles as a class-level constant
|
|
280
|
+
DEFAULT_TITLES: ClassVar[dict[str, str]] = {
|
|
281
|
+
"info": "Info",
|
|
282
|
+
"warning": "Warning",
|
|
283
|
+
"error": "Error",
|
|
284
|
+
"success": "Success",
|
|
285
|
+
"tip": "Tip",
|
|
286
|
+
"none": "",
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# Create a type alias using the keys from DEFAULT_TITLES
|
|
290
|
+
type VariantType = Literal["info", "warning", "error", "success", "tip", "none"]
|
|
291
|
+
|
|
292
|
+
element_type: str = field(default_factory=lambda: ParameterMessage.__name__)
|
|
293
|
+
variant: VariantType
|
|
294
|
+
title: str | None = None
|
|
295
|
+
value: str
|
|
296
|
+
button_link: str | None = None
|
|
297
|
+
button_text: str | None = None
|
|
298
|
+
full_width: bool = False
|
|
299
|
+
ui_options: dict = field(default_factory=dict)
|
|
300
|
+
|
|
301
|
+
def to_dict(self) -> dict[str, Any]:
|
|
302
|
+
data = super().to_dict()
|
|
303
|
+
|
|
304
|
+
# Use class-level default titles
|
|
305
|
+
title = self.title or self.DEFAULT_TITLES.get(str(self.variant), "")
|
|
306
|
+
|
|
307
|
+
# Merge the UI options with the message-specific options
|
|
308
|
+
merged_ui_options = {
|
|
309
|
+
**self.ui_options,
|
|
310
|
+
**{
|
|
311
|
+
k: v
|
|
312
|
+
for k, v in {
|
|
313
|
+
"title": title,
|
|
314
|
+
"variant": self.variant,
|
|
315
|
+
"button_link": self.button_link,
|
|
316
|
+
"button_text": self.button_text,
|
|
317
|
+
"full_width": self.full_width,
|
|
318
|
+
}.items()
|
|
319
|
+
if v is not None
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
data["name"] = self.name
|
|
324
|
+
data["value"] = self.value
|
|
325
|
+
data["default_value"] = self.value # for compatibility
|
|
326
|
+
data["ui_options"] = merged_ui_options
|
|
327
|
+
|
|
328
|
+
return data
|
|
329
|
+
|
|
330
|
+
|
|
275
331
|
@dataclass(kw_only=True)
|
|
276
332
|
class ParameterGroup(BaseNodeElement):
|
|
277
333
|
"""UI element for a group of parameters."""
|
griptape_nodes/exe_types/flow.py
CHANGED
|
@@ -30,13 +30,15 @@ class CurrentNodes(NamedTuple):
|
|
|
30
30
|
|
|
31
31
|
# The flow will own all of the nodes and the connections
|
|
32
32
|
class ControlFlow:
|
|
33
|
+
name: str
|
|
33
34
|
connections: Connections
|
|
34
35
|
nodes: dict[str, BaseNode]
|
|
35
36
|
control_flow_machine: ControlFlowMachine
|
|
36
37
|
single_node_resolution: bool
|
|
37
38
|
flow_queue: Queue[BaseNode]
|
|
38
39
|
|
|
39
|
-
def __init__(self) -> None:
|
|
40
|
+
def __init__(self, name: str) -> None:
|
|
41
|
+
self.name = name
|
|
40
42
|
self.connections = Connections()
|
|
41
43
|
self.nodes = {}
|
|
42
44
|
self.control_flow_machine = ControlFlowMachine(self)
|
|
@@ -17,6 +17,7 @@ from griptape_nodes.exe_types.core_types import (
|
|
|
17
17
|
ParameterDictionary,
|
|
18
18
|
ParameterGroup,
|
|
19
19
|
ParameterList,
|
|
20
|
+
ParameterMessage,
|
|
20
21
|
ParameterMode,
|
|
21
22
|
ParameterTypeBuiltin,
|
|
22
23
|
)
|
|
@@ -204,6 +205,11 @@ class BaseNode(ABC):
|
|
|
204
205
|
# Default behavior is to do nothing, and indicate no other modified Parameters.
|
|
205
206
|
return None # noqa: RET501
|
|
206
207
|
|
|
208
|
+
def after_settings_changed(self, modified_parameters_set: set[str]) -> None: # noqa: ARG002
|
|
209
|
+
"""Callback for when the settings of this Node are changed."""
|
|
210
|
+
# Waiting for https://github.com/griptape-ai/griptape-nodes/issues/1309
|
|
211
|
+
return
|
|
212
|
+
|
|
207
213
|
def on_griptape_event(self, event: BaseEvent) -> None: # noqa: ARG002
|
|
208
214
|
"""Callback for when a Griptape Event comes destined for this Node."""
|
|
209
215
|
return
|
|
@@ -265,6 +271,34 @@ class BaseNode(ABC):
|
|
|
265
271
|
if parameter is not None:
|
|
266
272
|
parameter._ui_options["hide"] = not visible
|
|
267
273
|
|
|
274
|
+
def get_message_by_name_or_element_id(self, element: str) -> ParameterMessage | None:
|
|
275
|
+
element_items = self.root_ui_element.find_elements_by_type(ParameterMessage)
|
|
276
|
+
for element_item in element_items:
|
|
277
|
+
if element in (element_item.name, element_item.element_id):
|
|
278
|
+
return element_item
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
def _set_message_visibility(self, names: str | list[str], *, visible: bool) -> None:
|
|
282
|
+
"""Sets the visibility of one or more messages.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
names (str or list of str): The message name(s) to update.
|
|
286
|
+
visible (bool): Whether to show (True) or hide (False) the messages.
|
|
287
|
+
"""
|
|
288
|
+
if isinstance(names, str):
|
|
289
|
+
names = [names]
|
|
290
|
+
|
|
291
|
+
for name in names:
|
|
292
|
+
message = self.get_message_by_name_or_element_id(name)
|
|
293
|
+
if message is not None:
|
|
294
|
+
message.ui_options["hide"] = not visible
|
|
295
|
+
|
|
296
|
+
def hide_message_by_name(self, names: str | list[str]) -> None:
|
|
297
|
+
self._set_message_visibility(names, visible=False)
|
|
298
|
+
|
|
299
|
+
def show_message_by_name(self, names: str | list[str]) -> None:
|
|
300
|
+
self._set_message_visibility(names, visible=True)
|
|
301
|
+
|
|
268
302
|
def hide_parameter_by_name(self, names: str | list[str]) -> None:
|
|
269
303
|
"""Hides one or more parameters by name."""
|
|
270
304
|
self._set_parameter_visibility(names, visible=False)
|
|
@@ -13,6 +13,7 @@ from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
|
13
13
|
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
14
14
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
15
15
|
from griptape_nodes.machines.fsm import FSM, State
|
|
16
|
+
from griptape_nodes.node_library.library_registry import LibraryRegistry
|
|
16
17
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
17
18
|
ExecutionEvent,
|
|
18
19
|
ExecutionGriptapeNodeEvent,
|
|
@@ -216,7 +217,7 @@ class ExecuteNodeState(State):
|
|
|
216
217
|
modified_parameter = current_node.get_parameter_by_name(modified_parameter_name)
|
|
217
218
|
if modified_parameter is not None:
|
|
218
219
|
modified_request = AlterParameterEvent.create(
|
|
219
|
-
|
|
220
|
+
node=current_node, parameter=modified_parameter
|
|
220
221
|
)
|
|
221
222
|
EventBus.publish_event(
|
|
222
223
|
ExecutionGriptapeNodeEvent(ExecutionEvent(payload=modified_request))
|
|
@@ -339,12 +340,19 @@ class ExecuteNodeState(State):
|
|
|
339
340
|
)
|
|
340
341
|
|
|
341
342
|
# Output values should already be saved!
|
|
343
|
+
library = LibraryRegistry.get_libraries_with_node_type(current_node.__class__.__name__)
|
|
344
|
+
if len(library) == 1:
|
|
345
|
+
library_name = library[0]
|
|
346
|
+
else:
|
|
347
|
+
library_name = None
|
|
342
348
|
EventBus.publish_event(
|
|
343
349
|
ExecutionGriptapeNodeEvent(
|
|
344
350
|
wrapped_event=ExecutionEvent(
|
|
345
351
|
payload=NodeResolvedEvent(
|
|
346
352
|
node_name=current_node.name,
|
|
347
353
|
parameter_output_values=TypeValidator.safe_serialize(current_node.parameter_output_values),
|
|
354
|
+
node_type=current_node.__class__.__name__,
|
|
355
|
+
specific_library_name=library_name,
|
|
348
356
|
)
|
|
349
357
|
)
|
|
350
358
|
)
|
|
@@ -26,5 +26,5 @@ for payload_class_name, payload_class in payload_dict.items():
|
|
|
26
26
|
else:
|
|
27
27
|
logger.info("Skipping %s as it is not a RequestPayload.", payload_class_name)
|
|
28
28
|
|
|
29
|
-
with Path("request_payload_schemas.json").open("w+") as file:
|
|
29
|
+
with Path("request_payload_schemas.json").open("w+", encoding="utf-8") as file:
|
|
30
30
|
file.write(json.dumps(list(payload_type_to_schema.values()), indent=2))
|