Merge branch 'shortcuts_settings' of github.com:langflow-ai/langflow into shortcuts_settings
|
|
@ -86,6 +86,10 @@ def update_frontend_node_with_template_values(frontend_node, raw_frontend_node):
|
||||||
|
|
||||||
update_template_values(frontend_node["template"], raw_frontend_node["template"])
|
update_template_values(frontend_node["template"], raw_frontend_node["template"])
|
||||||
|
|
||||||
|
old_code = raw_frontend_node["template"]["code"]["value"]
|
||||||
|
new_code = frontend_node["template"]["code"]["value"]
|
||||||
|
frontend_node["edited"] = old_code != new_code
|
||||||
|
|
||||||
return frontend_node
|
return frontend_node
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import hashlib
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
|
@ -99,6 +100,47 @@ async def download_image(file_name: str, flow_id: UUID, storage_service: Storage
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/profile_pictures/{folder_name}/{file_name}")
|
||||||
|
async def download_profile_picture(
|
||||||
|
folder_name: str,
|
||||||
|
file_name: str,
|
||||||
|
storage_service: StorageService = Depends(get_storage_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
extension = file_name.split(".")[-1]
|
||||||
|
config_dir = get_storage_service().settings_service.settings.config_dir
|
||||||
|
config_path = Path(config_dir)
|
||||||
|
folder_path = config_path / 'profile_pictures' / folder_name
|
||||||
|
content_type = build_content_type_from_extension(extension)
|
||||||
|
file_content = await storage_service.get_file(flow_id=folder_path, file_name=file_name)
|
||||||
|
return StreamingResponse(BytesIO(file_content), media_type=content_type)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/profile_pictures/list")
|
||||||
|
async def list_profile_pictures(storage_service: StorageService = Depends(get_storage_service)):
|
||||||
|
try:
|
||||||
|
config_dir = get_storage_service().settings_service.settings.config_dir
|
||||||
|
config_path = Path(config_dir)
|
||||||
|
|
||||||
|
people_path = config_path / "profile_pictures/People"
|
||||||
|
space_path = config_path / "profile_pictures/Space"
|
||||||
|
|
||||||
|
people = await storage_service.list_files(flow_id=people_path)
|
||||||
|
space = await storage_service.list_files(flow_id=space_path)
|
||||||
|
|
||||||
|
files = [Path("People") / i for i in people]
|
||||||
|
files += [Path("Space") / i for i in space]
|
||||||
|
|
||||||
|
return {"files": files}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/list/{flow_id}")
|
@router.get("/list/{flow_id}")
|
||||||
async def list_files(
|
async def list_files(
|
||||||
flow_id: UUID = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
|
flow_id: UUID = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,21 @@ async def get_transactions(
|
||||||
dicts = monitor_service.get_transactions(
|
dicts = monitor_service.get_transactions(
|
||||||
source=source, target=target, status=status, order_by=order_by, flow_id=flow_id
|
source=source, target=target, status=status, order_by=order_by, flow_id=flow_id
|
||||||
)
|
)
|
||||||
return [TransactionModelResponse(**d) for d in dicts]
|
result = []
|
||||||
|
for d in dicts:
|
||||||
|
d = TransactionModelResponse(
|
||||||
|
index=d["index"],
|
||||||
|
timestamp=d["timestamp"],
|
||||||
|
vertex_id=d["vertex_id"],
|
||||||
|
inputs=d["inputs"],
|
||||||
|
outputs=d["outputs"],
|
||||||
|
status=d["status"],
|
||||||
|
error=d["error"],
|
||||||
|
flow_id=d["flow_id"],
|
||||||
|
source=d["vertex_id"],
|
||||||
|
target=d["target_id"],
|
||||||
|
)
|
||||||
|
result.append(d)
|
||||||
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
|
||||||
|
|
@ -769,11 +769,13 @@ class Graph:
|
||||||
next_runnable_vertices, top_level_vertices = await self.get_next_and_top_level_vertices(
|
next_runnable_vertices, top_level_vertices = await self.get_next_and_top_level_vertices(
|
||||||
lock, set_cache_coro, vertex
|
lock, set_cache_coro, vertex
|
||||||
)
|
)
|
||||||
log_transaction(vertex, status="success")
|
flow_id = self.flow_id
|
||||||
|
log_transaction(flow_id, vertex, status="success")
|
||||||
return next_runnable_vertices, top_level_vertices, result_dict, params, valid, artifacts, vertex
|
return next_runnable_vertices, top_level_vertices, result_dict, params, valid, artifacts, vertex
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(f"Error building vertex: {exc}")
|
logger.exception(f"Error building vertex: {exc}")
|
||||||
log_transaction(vertex, status="failure", error=str(exc))
|
flow_id = self.flow_id
|
||||||
|
log_transaction(flow_id, vertex, status="failure", error=str(exc))
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
async def get_next_and_top_level_vertices(
|
async def get_next_and_top_level_vertices(
|
||||||
|
|
|
||||||
|
|
@ -529,12 +529,13 @@ class Vertex:
|
||||||
Returns:
|
Returns:
|
||||||
The built result if use_result is True, else the built object.
|
The built result if use_result is True, else the built object.
|
||||||
"""
|
"""
|
||||||
|
flow_id = self.graph.flow_id
|
||||||
if not self._built:
|
if not self._built:
|
||||||
log_transaction(vertex=self, target=requester, status="error")
|
log_transaction(flow_id, vertex=self, target=requester, status="error")
|
||||||
raise ValueError(f"Component {self.display_name} has not been built yet")
|
raise ValueError(f"Component {self.display_name} has not been built yet")
|
||||||
|
|
||||||
result = self._built_result if self.use_result else self._built_object
|
result = self._built_result if self.use_result else self._built_object
|
||||||
log_transaction(vertex=self, target=requester, status="success")
|
log_transaction(flow_id, vertex=self, target=requester, status="success")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def _build_vertex_and_update_params(self, key, vertex: "Vertex"):
|
async def _build_vertex_and_update_params(self, key, vertex: "Vertex"):
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
@ -20,7 +21,7 @@ from langflow.services.database.models.user.crud import get_user_by_username
|
||||||
from langflow.services.deps import get_settings_service, session_scope
|
from langflow.services.deps import get_settings_service, session_scope
|
||||||
|
|
||||||
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
|
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
|
||||||
from langflow.services.deps import get_variable_service
|
from langflow.services.deps import get_variable_service, get_storage_service
|
||||||
|
|
||||||
|
|
||||||
STARTER_FOLDER_NAME = "Starter Projects"
|
STARTER_FOLDER_NAME = "Starter Projects"
|
||||||
|
|
@ -104,6 +105,25 @@ def load_starter_projects() -> list[tuple[Path, dict]]:
|
||||||
return starter_projects
|
return starter_projects
|
||||||
|
|
||||||
|
|
||||||
|
def copy_profile_pictures():
|
||||||
|
config_dir = get_storage_service().settings_service.settings.config_dir
|
||||||
|
origin = Path(__file__).parent / "profile_pictures"
|
||||||
|
target = Path(config_dir) / "profile_pictures"
|
||||||
|
|
||||||
|
if not os.path.exists(origin):
|
||||||
|
raise ValueError(f"The source folder '{origin}' does not exist.")
|
||||||
|
|
||||||
|
if not os.path.exists(target):
|
||||||
|
os.makedirs(target)
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.copytree(origin, target, dirs_exist_ok=True)
|
||||||
|
logger.debug(f"Folder copied from '{origin}' to '{target}'")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error copying the folder: {e}")
|
||||||
|
|
||||||
|
|
||||||
def get_project_data(project):
|
def get_project_data(project):
|
||||||
project_name = project.get("name")
|
project_name = project.get("name")
|
||||||
project_description = project.get("description")
|
project_description = project.get("description")
|
||||||
|
|
@ -286,6 +306,7 @@ def create_or_update_starter_projects():
|
||||||
new_folder = create_starter_folder(session)
|
new_folder = create_starter_folder(session)
|
||||||
starter_projects = load_starter_projects()
|
starter_projects = load_starter_projects()
|
||||||
delete_start_projects(session, new_folder.id)
|
delete_start_projects(session, new_folder.id)
|
||||||
|
copy_profile_pictures()
|
||||||
for project_path, project in starter_projects:
|
for project_path, project in starter_projects:
|
||||||
(
|
(
|
||||||
project_name,
|
project_name,
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,9 @@ def update_params_with_load_from_db_fields(
|
||||||
logger.info(f"Using environment variable {params[field]} for {field}")
|
logger.info(f"Using environment variable {params[field]} for {field}")
|
||||||
if key is None:
|
if key is None:
|
||||||
logger.warning(f"Could not get value for {field}. Setting it to None.")
|
logger.warning(f"Could not get value for {field}. Setting it to None.")
|
||||||
params[field] = key
|
|
||||||
|
if field != "session_id":
|
||||||
|
params[field] = key
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Failed to get value for {field} from custom component. Setting it to None. Error: {exc}")
|
logger.error(f"Failed to get value for {field} from custom component. Setting it to None. Error: {exc}")
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ class TransactionModel(BaseModel):
|
||||||
vertex_id: str
|
vertex_id: str
|
||||||
target_id: str | None = None
|
target_id: str | None = None
|
||||||
inputs: dict
|
inputs: dict
|
||||||
outputs: dict
|
outputs: Optional[dict] = None
|
||||||
status: str
|
status: str
|
||||||
error: Optional[str] = None
|
error: Optional[str] = None
|
||||||
|
flow_id: Optional[str] = Field(default=None, alias="flow_id")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
@ -41,9 +42,12 @@ class TransactionModelResponse(BaseModel):
|
||||||
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
||||||
vertex_id: str
|
vertex_id: str
|
||||||
inputs: dict
|
inputs: dict
|
||||||
outputs: dict
|
outputs: Optional[dict] = None
|
||||||
status: str
|
status: str
|
||||||
error: Optional[str] = None
|
error: Optional[str] = None
|
||||||
|
flow_id: Optional[str] = Field(default=None, alias="flow_id")
|
||||||
|
source: Optional[str] = None
|
||||||
|
target: Optional[str] = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,9 @@ class MonitorService(Service):
|
||||||
order_by: Optional[str] = "timestamp",
|
order_by: Optional[str] = "timestamp",
|
||||||
flow_id: Optional[str] = None,
|
flow_id: Optional[str] = None,
|
||||||
):
|
):
|
||||||
query = "SELECT index,flow_id, source, target, target_args, status, error, timestamp FROM transactions"
|
query = (
|
||||||
|
"SELECT index,flow_id, status, error, timestamp, vertex_id, inputs, outputs, target_id FROM transactions"
|
||||||
|
)
|
||||||
conditions = []
|
conditions = []
|
||||||
if source:
|
if source:
|
||||||
conditions.append(f"source = '{source}'")
|
conditions.append(f"source = '{source}'")
|
||||||
|
|
@ -183,7 +185,7 @@ class MonitorService(Service):
|
||||||
query += " WHERE " + " AND ".join(conditions)
|
query += " WHERE " + " AND ".join(conditions)
|
||||||
|
|
||||||
if order_by:
|
if order_by:
|
||||||
query += f" ORDER BY {order_by}"
|
query += f" ORDER BY {order_by} DESC"
|
||||||
with duckdb.connect(str(self.db_path)) as conn:
|
with duckdb.connect(str(self.db_path)) as conn:
|
||||||
df = conn.execute(query).df()
|
df = conn.execute(query).df()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ def build_clean_params(target: "Vertex") -> dict:
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
def log_transaction(vertex: "Vertex", status, target: Optional["Vertex"] = None, error=None):
|
def log_transaction(flow_id, vertex: "Vertex", status, target: Optional["Vertex"] = None, error=None):
|
||||||
try:
|
try:
|
||||||
monitor_service = get_monitor_service()
|
monitor_service = get_monitor_service()
|
||||||
clean_params = build_clean_params(vertex)
|
clean_params = build_clean_params(vertex)
|
||||||
|
|
@ -186,10 +186,11 @@ def log_transaction(vertex: "Vertex", status, target: Optional["Vertex"] = None,
|
||||||
"vertex_id": str(vertex.id),
|
"vertex_id": str(vertex.id),
|
||||||
"target_id": str(target.id) if target else None,
|
"target_id": str(target.id) if target else None,
|
||||||
"inputs": clean_params,
|
"inputs": clean_params,
|
||||||
"outputs": vertex.result.model_dump_json(),
|
"outputs": vertex.result.model_dump_json() if vertex.result else None,
|
||||||
"timestamp": monitor_service.get_timestamp(),
|
"timestamp": monitor_service.get_timestamp(),
|
||||||
"status": status,
|
"status": status,
|
||||||
"error": error,
|
"error": error,
|
||||||
|
"flow_id": flow_id,
|
||||||
}
|
}
|
||||||
monitor_service.add_row(table_name="transactions", data=data)
|
monitor_service.add_row(table_name="transactions", data=data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -174,3 +174,12 @@ body {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* selected */
|
||||||
|
.react-flow__edge.selected .react-flow__edge-path {
|
||||||
|
stroke: var(--selected) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-flow__edge .react-flow__edge-path {
|
||||||
|
stroke: var(--connection) !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import SwitchOutputView from "./components/switchOutputView";
|
||||||
|
|
||||||
export default function OutputModal({ open, setOpen, nodeId }): JSX.Element {
|
export default function OutputModal({ open, setOpen, nodeId }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<BaseModal open={open} setOpen={setOpen} size="medium">
|
<BaseModal open={open} setOpen={setOpen} size="medium-tall">
|
||||||
<BaseModal.Header description="Inspect the output of the component below.">
|
<BaseModal.Header description="Inspect the output of the component below.">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="pr-2">Component Output</span>
|
<span className="pr-2">Component Output</span>
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,9 @@ export default function ParameterComponent({
|
||||||
: "Please build the component first"
|
: "Please build the component first"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button
|
<Button
|
||||||
|
variant="none"
|
||||||
|
size="none"
|
||||||
disabled={!displayOutputPreview || unknownOutput}
|
disabled={!displayOutputPreview || unknownOutput}
|
||||||
onClick={() => setOpenOutputModal(true)}
|
onClick={() => setOpenOutputModal(true)}
|
||||||
data-testid={`output-inspection-${title.toLowerCase()}`}
|
data-testid={`output-inspection-${title.toLowerCase()}`}
|
||||||
|
|
@ -288,7 +290,7 @@ export default function ParameterComponent({
|
||||||
)}
|
)}
|
||||||
name={"ScanEye"}
|
name={"ScanEye"}
|
||||||
/>
|
/>
|
||||||
</button>
|
</Button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
RUN_TIMESTAMP_PREFIX,
|
RUN_TIMESTAMP_PREFIX,
|
||||||
STATUS_BUILD,
|
STATUS_BUILD,
|
||||||
STATUS_BUILDING,
|
STATUS_BUILDING,
|
||||||
|
TOOLTIP_OUTDATED_NODE,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { BuildStatus } from "../../constants/enums";
|
import { BuildStatus } from "../../constants/enums";
|
||||||
import { countHandlesFn } from "../helpers/count-handles";
|
import { countHandlesFn } from "../helpers/count-handles";
|
||||||
|
|
@ -35,6 +36,8 @@ import getFieldTitle from "../utils/get-field-title";
|
||||||
import sortFields from "../utils/sort-fields";
|
import sortFields from "../utils/sort-fields";
|
||||||
import isWrappedWithClass from "../../pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class";
|
import isWrappedWithClass from "../../pages/FlowPage/components/PageComponent/utils/is-wrapped-with-class";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
|
import { postCustomComponent } from "../../controllers/API";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
|
|
@ -204,6 +207,33 @@ export default function GenericNode({
|
||||||
setShowNode(data.showNode ?? true);
|
setShowNode(data.showNode ?? true);
|
||||||
}, [data.showNode]);
|
}, [data.showNode]);
|
||||||
|
|
||||||
|
const [loadingUpdate, setLoadingUpdate] = useState(false);
|
||||||
|
|
||||||
|
const handleUpdateCode = () => {
|
||||||
|
setLoadingUpdate(true);
|
||||||
|
takeSnapshot();
|
||||||
|
// to update we must get the code from the templates in useTypesStore
|
||||||
|
const thisNodeTemplate = templates[data.type]?.template;
|
||||||
|
// if the template does not have a code key
|
||||||
|
// return
|
||||||
|
if (!thisNodeTemplate?.code) return;
|
||||||
|
|
||||||
|
const currentCode = thisNodeTemplate.code.value;
|
||||||
|
if (data.node) {
|
||||||
|
postCustomComponent(currentCode, data.node)
|
||||||
|
.then((apiReturn) => {
|
||||||
|
const { data } = apiReturn;
|
||||||
|
if (data && updateNodeCode) {
|
||||||
|
updateNodeCode(data, currentCode, "code");
|
||||||
|
setLoadingUpdate(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const memoizedNodeToolbarComponent = useMemo(() => {
|
const memoizedNodeToolbarComponent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<NodeToolbar>
|
<NodeToolbar>
|
||||||
|
|
@ -226,8 +256,6 @@ export default function GenericNode({
|
||||||
showNode={showNode}
|
showNode={showNode}
|
||||||
openAdvancedModal={false}
|
openAdvancedModal={false}
|
||||||
onCloseAdvancedModal={() => {}}
|
onCloseAdvancedModal={() => {}}
|
||||||
updateNodeCode={updateNodeCode}
|
|
||||||
isOutdated={isOutdated}
|
|
||||||
selected={selected}
|
selected={selected}
|
||||||
/>
|
/>
|
||||||
</NodeToolbar>
|
</NodeToolbar>
|
||||||
|
|
@ -448,54 +476,71 @@ export default function GenericNode({
|
||||||
</div>
|
</div>
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<>
|
<>
|
||||||
<ShadTooltip
|
<div className="flex flex-shrink-0 items-center gap-1">
|
||||||
content={
|
{isOutdated && (
|
||||||
buildStatus === BuildStatus.BUILDING ? (
|
<ShadTooltip content={TOOLTIP_OUTDATED_NODE}>
|
||||||
<span> {STATUS_BUILDING} </span>
|
<Button
|
||||||
) : !validationStatus ? (
|
onClick={handleUpdateCode}
|
||||||
<span className="flex">{STATUS_BUILD}</span>
|
variant="secondary"
|
||||||
) : (
|
className={"h-9 px-1.5"}
|
||||||
<div className="max-h-100 p-2">
|
loading={loadingUpdate}
|
||||||
<div>
|
>
|
||||||
{lastRunTime && (
|
<IconComponent
|
||||||
<div className="justify-left flex font-normal text-muted-foreground">
|
name="AlertTriangle"
|
||||||
<div>{RUN_TIMESTAMP_PREFIX}</div>
|
className="h-5 w-5 text-status-yellow"
|
||||||
<div className="ml-1 text-status-blue">
|
/>
|
||||||
{lastRunTime}
|
</Button>
|
||||||
|
</ShadTooltip>
|
||||||
|
)}
|
||||||
|
<ShadTooltip
|
||||||
|
content={
|
||||||
|
buildStatus === BuildStatus.BUILDING ? (
|
||||||
|
<span> {STATUS_BUILDING} </span>
|
||||||
|
) : !validationStatus ? (
|
||||||
|
<span className="flex">{STATUS_BUILD}</span>
|
||||||
|
) : (
|
||||||
|
<div className="max-h-100 p-2">
|
||||||
|
<div>
|
||||||
|
{lastRunTime && (
|
||||||
|
<div className="justify-left flex font-normal text-muted-foreground">
|
||||||
|
<div>{RUN_TIMESTAMP_PREFIX}</div>
|
||||||
|
<div className="ml-1 text-status-blue">
|
||||||
|
{lastRunTime}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="justify-left flex font-normal text-muted-foreground">
|
||||||
|
<div>Duration:</div>
|
||||||
|
<div className="ml-1 text-status-blue">
|
||||||
|
{validationStatus?.data.duration}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="justify-left flex font-normal text-muted-foreground">
|
|
||||||
<div>Duration:</div>
|
|
||||||
<div className="ml-1 text-status-blue">
|
|
||||||
{validationStatus?.data.duration}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}
|
||||||
}
|
side="bottom"
|
||||||
side="bottom"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
|
||||||
return;
|
|
||||||
setValidationStatus(null);
|
|
||||||
buildFlow({ stopNodeId: data.id });
|
|
||||||
}}
|
|
||||||
variant="secondary"
|
|
||||||
className={"group h-9 px-1.5"}
|
|
||||||
>
|
>
|
||||||
<div
|
<Button
|
||||||
data-testid={
|
onClick={() => {
|
||||||
`button_run_` + data?.node?.display_name.toLowerCase()
|
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
||||||
}
|
return;
|
||||||
|
setValidationStatus(null);
|
||||||
|
buildFlow({ stopNodeId: data.id });
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
className={"group h-9 px-1.5"}
|
||||||
>
|
>
|
||||||
{renderIconStatus()}
|
<div
|
||||||
</div>
|
data-testid={
|
||||||
</Button>
|
`button_run_` + data?.node?.display_name.toLowerCase()
|
||||||
</ShadTooltip>
|
}
|
||||||
|
>
|
||||||
|
{renderIconStatus()}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</ShadTooltip>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,11 @@ const useCheckCodeValidity = (
|
||||||
if (!thisNodeTemplate.code) return;
|
if (!thisNodeTemplate.code) return;
|
||||||
const currentCode = thisNodeTemplate.code?.value;
|
const currentCode = thisNodeTemplate.code?.value;
|
||||||
const thisNodesCode = data.node!.template?.code?.value;
|
const thisNodesCode = data.node!.template?.code?.value;
|
||||||
const componentsToIgnore = ["Custom Component", "Prompt"];
|
const componentsToIgnore = ["CustomComponent", "Prompt"];
|
||||||
if (
|
if (
|
||||||
currentCode !== thisNodesCode &&
|
currentCode !== thisNodesCode &&
|
||||||
!componentsToIgnore.includes(data.node!.display_name)
|
!componentsToIgnore.includes(data.type) &&
|
||||||
|
!(data.node?.edited ?? false)
|
||||||
) {
|
) {
|
||||||
setIsOutdated(true);
|
setIsOutdated(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const useUpdateNodeCode = (
|
||||||
node: newNodeClass,
|
node: newNodeClass,
|
||||||
description: newNodeClass.description ?? dataNode.description,
|
description: newNodeClass.description ?? dataNode.description,
|
||||||
display_name: newNodeClass.display_name ?? dataNode.display_name,
|
display_name: newNodeClass.display_name ?? dataNode.display_name,
|
||||||
|
edited: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
newNode.data.node.template[name].value = code;
|
newNode.data.node.template[name].value = code;
|
||||||
|
|
|
||||||
BIN
src/frontend/src/assets/profile-circle.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -1,10 +1,12 @@
|
||||||
|
import TableAutoCellRender from "../tableComponent/components/tableAutoCellRender";
|
||||||
|
|
||||||
export default function ArrayReader({ array }: { array: any[] }): JSX.Element {
|
export default function ArrayReader({ array }: { array: any[] }): JSX.Element {
|
||||||
//TODO check array type
|
//TODO check array type
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
{array.map((item, index) => (
|
{array.map((item, index) => (
|
||||||
<li key={index}>{item}</li>
|
<li key={index}>{<TableAutoCellRender value={item} />}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ export default function CollectionCardComponent({
|
||||||
data-testid={`card-${convertTestName(data.name)}`}
|
data-testid={`card-${convertTestName(data.name)}`}
|
||||||
//TODO check color schema
|
//TODO check color schema
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
|
"group relative flex h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
|
||||||
disabled ? "pointer-events-none opacity-50" : "",
|
disabled ? "pointer-events-none opacity-50" : "",
|
||||||
onClick ? "cursor-pointer" : "",
|
onClick ? "cursor-pointer" : "",
|
||||||
isSelectedCard ? "border border-selected" : "",
|
isSelectedCard ? "border border-selected" : "",
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { gradients } from "../../utils/styleUtils";
|
|
||||||
|
|
||||||
export default function GradientChooserComponent({ value, onChange }) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-wrap items-center justify-start gap-2">
|
|
||||||
{gradients.map((gradient, idx) => (
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
onChange(gradient);
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
"duration-400 h-12 w-12 cursor-pointer rounded-full transition-all " +
|
|
||||||
gradient +
|
|
||||||
(value === gradient ? " shadow-lg ring-2 ring-primary" : "")
|
|
||||||
}
|
|
||||||
key={idx}
|
|
||||||
></div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
import { IS_MAC, SAVED_HOVER } from "../../../../constants/constants";
|
||||||
import ExportModal from "../../../../modals/exportModal";
|
import ExportModal from "../../../../modals/exportModal";
|
||||||
import FlowLogsModal from "../../../../modals/flowLogsModal";
|
import FlowLogsModal from "../../../../modals/flowLogsModal";
|
||||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||||
|
|
@ -115,7 +115,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
title: UPLOAD_ERROR_ALERT,
|
title: UPLOAD_ERROR_ALERT,
|
||||||
list: [error],
|
list: [error],
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -139,7 +139,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
>
|
>
|
||||||
<IconComponent name="Undo" className="header-menu-options " />
|
<IconComponent name="Undo" className="header-menu-options " />
|
||||||
Undo
|
Undo
|
||||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
{IS_MAC ? (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Command"
|
name="Command"
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
|
@ -161,7 +161,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
>
|
>
|
||||||
<IconComponent name="Redo" className="header-menu-options " />
|
<IconComponent name="Redo" className="header-menu-options " />
|
||||||
Redo
|
Redo
|
||||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
{IS_MAC ? (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Command"
|
name="Command"
|
||||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||||
|
|
@ -201,7 +201,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4",
|
"h-4 w-4",
|
||||||
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
|
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{printByBuildStatus()}
|
{printByBuildStatus()}
|
||||||
|
|
|
||||||