diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f480d3214..b5424b367 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -80,7 +80,10 @@ jobs: langflowai/langflow-frontend:1.0-alpha restart-space: + name: Restart HuggingFace Spaces + if: ${{ inputs.release_type == 'main' }} runs-on: ubuntu-latest + needs: docker_build strategy: matrix: python-version: diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 8cb0bc90e..286a7a921 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -35,6 +35,10 @@ jobs: with: python-version: "3.10" cache: "poetry" + - name: Set up Nodejs 20 + uses: actions/setup-node@v4 + with: + node-version: "20" - name: Check Version id: check-version run: | diff --git a/docker/build_and_push.Dockerfile b/docker/build_and_push.Dockerfile index bd177e9bc..29f9294a6 100644 --- a/docker/build_and_push.Dockerfile +++ b/docker/build_and_push.Dockerfile @@ -1,6 +1,14 @@ # syntax=docker/dockerfile:1 # Keep this syntax directive! It's used to enable Docker BuildKit +FROM node:20-bookworm-slim as builder-node +WORKDIR /app +COPY src/frontend/package.json src/frontend/package-lock.json ./ +RUN npm install +COPY src/frontend/ ./ +RUN npm run build + + ################################ # BUILDER-BASE # Used to build deps + create our virtual environment @@ -48,10 +56,10 @@ COPY pyproject.toml poetry.lock README.md ./ COPY src/ ./src COPY scripts/ ./scripts RUN python -m pip install requests --user && cd ./scripts && python update_dependencies.py +COPY --from=builder-node /app/build ./src/backend/base/langflow/frontend RUN $POETRY_HOME/bin/poetry lock --no-update \ - && $POETRY_HOME/bin/poetry install --no-interaction --no-ansi -E deploy \ && $POETRY_HOME/bin/poetry build -f wheel \ - && $POETRY_HOME/bin/poetry run pip install dist/*.whl + && $POETRY_HOME/bin/poetry run pip install dist/*.whl --force-reinstall ################################ # RUNTIME diff --git a/poetry.lock b/poetry.lock index c2cf1b2d5..4bf30b352 100644 --- a/poetry.lock +++ b/poetry.lock @@ -698,13 +698,13 @@ graph = ["gremlinpython (==3.4.6)"] [[package]] name = "cassio" -version = "0.1.7" +version = "0.1.8" description = "A framework-agnostic Python library to seamlessly integrate Apache Cassandra(R) with ML/LLM/genAI workloads." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "cassio-0.1.7-py3-none-any.whl", hash = "sha256:08d1028a20d09bd207de0e17eaf7ae821b3c8e4788555e2d337aa440e0846d87"}, - {file = "cassio-0.1.7.tar.gz", hash = "sha256:44f705dff8a9a1c48527db2c9e968686358c960fa21ba940d9e66de00639ad78"}, + {file = "cassio-0.1.8-py3-none-any.whl", hash = "sha256:c09e7c884ba7227ff5277c86f3b0f31c523672ea407f56d093c7227e69c54d94"}, + {file = "cassio-0.1.8.tar.gz", hash = "sha256:4e09929506cb3dd6fad217e89846d0a1a59069afd24b82c72526ef6f2e9271af"}, ] [package.dependencies] @@ -4327,7 +4327,7 @@ types-requests = ">=2.31.0.2,<3.0.0.0" [[package]] name = "langflow-base" -version = "0.0.58" +version = "0.0.59" description = "A Python package with a built-in web application" optional = false python-versions = ">=3.10,<3.13" @@ -5588,13 +5588,13 @@ sympy = "*" [[package]] name = "openai" -version = "1.31.2" +version = "1.32.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.31.2-py3-none-any.whl", hash = "sha256:203cf21294f347c3d7b591e0ccbe18389d6f8967d4237214b926ea76b1e1781c"}, - {file = "openai-1.31.2.tar.gz", hash = "sha256:966ab3165b926cb5ec091d2434c90613e2ea8b73ffad984f7fec34bde971725a"}, + {file = "openai-1.32.0-py3-none-any.whl", hash = "sha256:953d57669f309002044fd2f678aba9f07a43256d74b3b00cd04afb5b185568ea"}, + {file = "openai-1.32.0.tar.gz", hash = "sha256:a6df15a7ab9344b1bc2bc8d83639f68b7a7e2453c0f5e50c1666547eee86f0bd"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 2fc313e24..7e29d83c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow" -version = "1.0.0a46" +version = "1.0.0a48" description = "A Python package with a built-in web application" authors = ["Langflow "] maintainers = [ diff --git a/src/backend/base/langflow/components/vectorstores/__init__.py b/src/backend/base/langflow/components/vectorstores/__init__.py index d38b0a735..e69de29bb 100644 --- a/src/backend/base/langflow/components/vectorstores/__init__.py +++ b/src/backend/base/langflow/components/vectorstores/__init__.py @@ -1,28 +0,0 @@ -from .AstraDB import AstraDBVectorStoreComponent -from .Chroma import ChromaComponent -from .FAISS import FAISSComponent -from .MongoDBAtlasVector import MongoDBAtlasComponent -from .Pinecone import PineconeComponent -from .Qdrant import QdrantComponent -from .Redis import RedisComponent -from .SupabaseVectorStore import SupabaseComponent -from .Vectara import VectaraComponent -from .Weaviate import WeaviateVectorStoreComponent -from .pgvector import PGVectorComponent -from .Couchbase import CouchbaseComponent - -__all__ = [ - "AstraDBVectorStoreComponent", - "ChromaComponent", - "CouchbaseComponent", - "FAISSComponent", - "MongoDBAtlasComponent", - "PineconeComponent", - "QdrantComponent", - "RedisComponent", - "SupabaseComponent", - "VectaraComponent", - "WeaviateVectorStoreComponent", - "base", - "PGVectorComponent", -] diff --git a/src/backend/base/langflow/custom/code_parser/code_parser.py b/src/backend/base/langflow/custom/code_parser/code_parser.py index 17fe12896..705e779f4 100644 --- a/src/backend/base/langflow/custom/code_parser/code_parser.py +++ b/src/backend/base/langflow/custom/code_parser/code_parser.py @@ -297,7 +297,7 @@ class CodeParser: bases = self.execute_and_inspect_classes(self.code) except Exception as e: # If the code cannot be executed, return an empty list - logger.exception(e) + logger.debug(e) bases = [] raise e return bases diff --git a/src/backend/base/langflow/custom/directory_reader/directory_reader.py b/src/backend/base/langflow/custom/directory_reader/directory_reader.py index b9f55f21f..52a310314 100644 --- a/src/backend/base/langflow/custom/directory_reader/directory_reader.py +++ b/src/backend/base/langflow/custom/directory_reader/directory_reader.py @@ -78,7 +78,8 @@ class DirectoryReader: component_tuple = (*build_component(component), component) components.append(component_tuple) except Exception as e: - logger.error(f"Error while loading component { component['name']}: {e}") + logger.debug(f"Error while loading component { component['name']}") + logger.debug(e) continue items.append({"name": menu["name"], "path": menu["path"], "components": components}) filtered = [menu for menu in items if menu["components"]] @@ -266,8 +267,7 @@ class DirectoryReader: if validation_result: try: output_types = self.get_output_types_from_code(result_content) - except Exception as exc: - logger.exception(f"Error while getting output types from code: {str(exc)}") + except Exception: output_types = [component_name_camelcase] else: output_types = [component_name_camelcase] diff --git a/src/backend/base/langflow/services/monitor/service.py b/src/backend/base/langflow/services/monitor/service.py index ab5a87f08..a59d60ac7 100644 --- a/src/backend/base/langflow/services/monitor/service.py +++ b/src/backend/base/langflow/services/monitor/service.py @@ -132,7 +132,7 @@ class MonitorService(Service): order: Optional[str] = "DESC", limit: Optional[int] = None, ): - query = "SELECT index, flow_id, sender_name, sender, session_id, message, artifacts, timestamp FROM messages" + query = "SELECT index, flow_id, sender_name, sender, session_id, message, timestamp FROM messages" conditions = [] if sender: conditions.append(f"sender = '{sender}'") diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index 39319be9a..f2df7364f 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow-base" -version = "0.0.58" +version = "0.0.59" description = "A Python package with a built-in web application" authors = ["Langflow "] maintainers = [ diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/constants.ts b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/constants.ts similarity index 100% rename from src/frontend/src/customNodes/genericNode/components/parameterComponent/constants.ts rename to src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/constants.ts diff --git a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx similarity index 99% rename from src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx rename to src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index f618e449c..fc94b7b87 100644 --- a/src/frontend/src/customNodes/genericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -21,6 +21,7 @@ import { LANGFLOW_SUPPORTED_TYPES, TOOLTIP_EMPTY, } from "../../../../constants/constants"; +import OutputModal from "../../../../customNodes/genericNode/components/outputModal"; import { Case } from "../../../../shared/components/caseComponent"; import useFlowStore from "../../../../stores/flowStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; @@ -44,7 +45,6 @@ import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount"; import useHandleOnNewValue from "../../../hooks/use-handle-new-value"; import useHandleNodeClass from "../../../hooks/use-handle-node-class"; import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons"; -import OutputModal from "../outputModal"; import TooltipRenderComponent from "../tooltipRenderComponent"; import { TEXT_FIELD_TYPES } from "./constants"; @@ -284,7 +284,7 @@ export default function ParameterComponent({ "h-5 w-5 rounded-md", displayOutputPreview && !unknownOutput ? " hover:bg-secondary-foreground/5 hover:text-medium-indigo" - : " cursor-not-allowed text-muted-foreground", + : " cursor-not-allowed text-muted-foreground" )} name={"ScanEye"} /> diff --git a/src/frontend/src/customNodes/genericNode/components/tooltipRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/tooltipRenderComponent/index.tsx similarity index 100% rename from src/frontend/src/customNodes/genericNode/components/tooltipRenderComponent/index.tsx rename to src/frontend/src/CustomNodes/GenericNode/components/tooltipRenderComponent/index.tsx diff --git a/src/frontend/src/customNodes/genericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx similarity index 99% rename from src/frontend/src/customNodes/genericNode/index.tsx rename to src/frontend/src/CustomNodes/GenericNode/index.tsx index 395a9e022..ca1fe5aa2 100644 --- a/src/frontend/src/customNodes/genericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -11,6 +11,8 @@ import { STATUS_BUILDING, } from "../../constants/constants"; import { BuildStatus } from "../../constants/enums"; +import { countHandlesFn } from "../../customNodes/helpers/count-handles"; +import { getSpecificClassFromBuildStatus } from "../../customNodes/helpers/get-class-from-build-status"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import useAlertStore from "../../stores/alertStore"; import { useDarkStore } from "../../stores/darkStore"; @@ -22,8 +24,6 @@ import { NodeDataType } from "../../types/flow"; import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, cn } from "../../utils/utils"; -import { countHandlesFn } from "../helpers/count-handles"; -import { getSpecificClassFromBuildStatus } from "../helpers/get-class-from-build-status"; import useCheckCodeValidity from "../hooks/use-check-code-validity"; import useIconNodeRender from "../hooks/use-icon-render"; import useIconStatus from "../hooks/use-icons-status"; @@ -127,7 +127,7 @@ export default function GenericNode({ const names = classNames( baseBorderClass, nodeSizeClass, - "generic-node-div", + "generic-node-div group/node", specificClassFromBuildStatus ); return names; diff --git a/src/frontend/src/customNodes/hooks/use-check-code-validity.tsx b/src/frontend/src/CustomNodes/hooks/use-check-code-validity.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-check-code-validity.tsx rename to src/frontend/src/CustomNodes/hooks/use-check-code-validity.tsx diff --git a/src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx b/src/frontend/src/CustomNodes/hooks/use-fetch-data-on-mount.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-fetch-data-on-mount.tsx rename to src/frontend/src/CustomNodes/hooks/use-fetch-data-on-mount.tsx diff --git a/src/frontend/src/customNodes/hooks/use-handle-new-value.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-handle-new-value.tsx rename to src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx diff --git a/src/frontend/src/customNodes/hooks/use-handle-node-class.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-handle-node-class.tsx rename to src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx diff --git a/src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-refresh-buttons.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-handle-refresh-buttons.tsx rename to src/frontend/src/CustomNodes/hooks/use-handle-refresh-buttons.tsx diff --git a/src/frontend/src/customNodes/hooks/use-icon-render.tsx b/src/frontend/src/CustomNodes/hooks/use-icon-render.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-icon-render.tsx rename to src/frontend/src/CustomNodes/hooks/use-icon-render.tsx diff --git a/src/frontend/src/customNodes/hooks/use-icons-status.tsx b/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-icons-status.tsx rename to src/frontend/src/CustomNodes/hooks/use-icons-status.tsx diff --git a/src/frontend/src/customNodes/hooks/use-update-node-code.tsx b/src/frontend/src/CustomNodes/hooks/use-update-node-code.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-update-node-code.tsx rename to src/frontend/src/CustomNodes/hooks/use-update-node-code.tsx diff --git a/src/frontend/src/customNodes/hooks/use-update-validation-status.tsx b/src/frontend/src/CustomNodes/hooks/use-update-validation-status.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-update-validation-status.tsx rename to src/frontend/src/CustomNodes/hooks/use-update-validation-status.tsx diff --git a/src/frontend/src/customNodes/hooks/use-validation-status-string.tsx b/src/frontend/src/CustomNodes/hooks/use-validation-status-string.tsx similarity index 100% rename from src/frontend/src/customNodes/hooks/use-validation-status-string.tsx rename to src/frontend/src/CustomNodes/hooks/use-validation-status-string.tsx diff --git a/src/frontend/src/customNodes/utils/get-field-title.tsx b/src/frontend/src/CustomNodes/utils/get-field-title.tsx similarity index 100% rename from src/frontend/src/customNodes/utils/get-field-title.tsx rename to src/frontend/src/CustomNodes/utils/get-field-title.tsx diff --git a/src/frontend/src/customNodes/utils/sort-fields.tsx b/src/frontend/src/CustomNodes/utils/sort-fields.tsx similarity index 100% rename from src/frontend/src/customNodes/utils/sort-fields.tsx rename to src/frontend/src/CustomNodes/utils/sort-fields.tsx diff --git a/src/frontend/src/alerts/error/index.tsx b/src/frontend/src/alerts/error/index.tsx index 3690590b9..b70a5ae45 100644 --- a/src/frontend/src/alerts/error/index.tsx +++ b/src/frontend/src/alerts/error/index.tsx @@ -51,13 +51,15 @@ export default function ErrorAlert({ />
-

{title}

+

{title}

{list?.length !== 0 && list?.some((item) => item !== null && item !== undefined) ? (
    {list.map((item, index) => ( -
  • {item}
  • +
  • + {item} +
  • ))}
diff --git a/src/frontend/src/alerts/notice/index.tsx b/src/frontend/src/alerts/notice/index.tsx index fb29954ea..dcb034691 100644 --- a/src/frontend/src/alerts/notice/index.tsx +++ b/src/frontend/src/alerts/notice/index.tsx @@ -47,7 +47,7 @@ export default function NoticeAlert({ />
-

+

{title}

diff --git a/src/frontend/src/alerts/success/index.tsx b/src/frontend/src/alerts/success/index.tsx index db62c8432..270ae5515 100644 --- a/src/frontend/src/alerts/success/index.tsx +++ b/src/frontend/src/alerts/success/index.tsx @@ -45,7 +45,7 @@ export default function SuccessAlert({ />

-

{title}

+

{title}

diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 50a0c8cf8..e1b8ce2c5 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -56,7 +56,7 @@ export default function Header(): JSX.Element { const lastFlowVisitedIndex = routeHistory .reverse() .findIndex( - (path) => path.includes("/flow/") && path !== location.pathname + (path) => path.includes("/flow/") && path !== location.pathname, ); const lastFlowVisited = routeHistory[lastFlowVisitedIndex]; @@ -200,6 +200,28 @@ export default function Header(): JSX.Element { /> + {!autoLogin && ( + <> + +
+
+ {userData?.username ?? "User"} +
+ + + + )} General state.getFolderById); + const setErrorData = useAlertStore((state) => state.setErrorData); const handleFolderChange = (folderId: string) => { getFolderById(folderId); @@ -62,7 +64,17 @@ const SideBarFoldersButtonsComponent = ({ ); const handleUploadFlowsToFolder = () => { - uploadFolder(folderId); + uploadFolder(folderId) + .then(() => { + getFolderById(folderId); + }) + .catch((err) => { + console.log(err); + setErrorData({ + title: `Error on upload`, + list: [err["response"]["data"]], + }); + }); }; const handleDownloadFolder = (id: string) => { diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index d9ce00554..a113b3911 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -668,7 +668,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications"; export const SUCCESS_BUILD = "Built sucessfully ✨"; export const ALERT_SAVE_WITH_API = - "Caution: Uncheck this box only removes API keys from fields specifically designated for API keys."; + "Caution: Unchecking this box only removes API keys from fields specifically designated for API keys."; export const SAVE_WITH_API_CHECKBOX = "Save with my API keys"; export const EDIT_TEXT_MODAL_TITLE = "Edit Text"; diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index a04891821..9cd81c123 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -45,7 +45,7 @@ const ExportModal = forwardRef( is_component: false, }, name!, - description + description, ); setNoticeData({ title: API_WARNING_NOTICE_ALERT, @@ -61,7 +61,7 @@ const ExportModal = forwardRef( is_component: false, }), name!, - description + description, ); setOpen(false); }} @@ -94,7 +94,7 @@ const ExportModal = forwardRef( {SAVE_WITH_API_CHECKBOX}
- + {ALERT_SAVE_WITH_API} @@ -102,6 +102,6 @@ const ExportModal = forwardRef( ); - } + }, ); export default ExportModal; diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx index f7d52c611..fa5524891 100644 --- a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx @@ -4,7 +4,9 @@ import { FormProvider, useForm, useWatch } from "react-hook-form"; import { Link, useLocation, useNavigate } from "react-router-dom"; import CollectionCardComponent from "../../../../components/cardComponent"; import CardsWrapComponent from "../../../../components/cardsWrapComponent"; -import IconComponent from "../../../../components/genericIconComponent"; +import IconComponent, { + ForwardedIconComponent, +} from "../../../../components/genericIconComponent"; import PaginatorComponent from "../../../../components/paginatorComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { Button } from "../../../../components/ui/button"; @@ -18,6 +20,9 @@ import { getNameByType } from "../../utils/get-name-by-type"; import { sortFlows } from "../../utils/sort-flows"; import EmptyComponent from "../emptyComponent"; import HeaderComponent from "../headerComponent"; +import { downloadFlow, removeApiKeys } from "../../../../utils/reactflowUtils"; +import { useDarkStore } from "../../../../stores/darkStore"; +import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants"; export default function ComponentsComponent({ type = "all", @@ -31,22 +36,22 @@ export default function ComponentsComponent({ const allFlows = useFlowsManagerStore((state) => state.allFlows); const flowsFromFolder = useFolderStore( - (state) => state.selectedFolder?.flows + (state) => state.selectedFolder?.flows, ); const setSuccessData = useAlertStore((state) => state.setSuccessData); const setErrorData = useAlertStore((state) => state.setErrorData); const [openDelete, setOpenDelete] = useState(false); const searchFlowsComponents = useFlowsManagerStore( - (state) => state.searchFlowsComponents + (state) => state.searchFlowsComponents, ); const setSelectedFlowsComponentsCards = useFlowsManagerStore( - (state) => state.setSelectedFlowsComponentsCards + (state) => state.setSelectedFlowsComponentsCards, ); const selectedFlowsComponentsCards = useFlowsManagerStore( - (state) => state.selectedFlowsComponentsCards + (state) => state.selectedFlowsComponentsCards, ); const [handleFileDrop] = useFileDrop(uploadFlow, type)!; @@ -82,7 +87,7 @@ export default function ComponentsComponent({ f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) || f.description .toLowerCase() - .includes(searchFlowsComponents.toLowerCase()) + .includes(searchFlowsComponents.toLowerCase()), ); if (searchFlowsComponents === "") { @@ -129,6 +134,8 @@ export default function ComponentsComponent({ setOpenDelete(true); } else if (action === "duplicate") { handleDuplicate(); + } else if (action === "export") { + handleExport(); } }; @@ -137,9 +144,9 @@ export default function ComponentsComponent({ selectedFlowsComponentsCards.map((selectedFlow) => addFlow( true, - allFlows.find((flow) => flow.id === selectedFlow) - ) - ) + allFlows.find((flow) => flow.id === selectedFlow), + ), + ), ).then(() => { resetFilter(); getFoldersApi(true); @@ -152,6 +159,47 @@ export default function ComponentsComponent({ }); }; + const handleImport = () => { + uploadFlow({ newProject: true, isComponent: false }) + .then(() => { + resetFilter(); + getFoldersApi(true); + if (!folderId || folderId === myCollectionId) { + getFolderById(folderId ? folderId : myCollectionId); + } + setSelectedFlowsComponentsCards([]); + + setSuccessData({ title: "Flows imported successfully" }); + }) + .catch((error) => { + setErrorData({ + title: UPLOAD_ERROR_ALERT, + list: [error], + }); + }); + }; + + const version = useDarkStore((state) => state.version); + + const handleExport = () => { + selectedFlowsComponentsCards.map((selectedFlowId) => { + const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId); + downloadFlow( + removeApiKeys({ + id: selectedFlow!.id, + data: selectedFlow!.data!, + description: selectedFlow!.description, + name: selectedFlow!.name, + last_tested_version: version, + is_component: false, + }), + selectedFlow!.name, + selectedFlow!.description, + ); + }); + setSuccessData({ title: "Flows exported successfully" }); + }; + const handleDeleteMultiple = () => { removeFlow(selectedFlowsComponentsCards) .then(() => { @@ -161,7 +209,7 @@ export default function ComponentsComponent({ getFolderById(folderId ? folderId : myCollectionId); } setSuccessData({ - title: "Selected items deleted successfully!", + title: "Selected items deleted successfully", }); }) .catch(() => { @@ -180,7 +228,7 @@ export default function ComponentsComponent({ return true; } return false; - } + }, ); setSelectedFlowsComponentsCards(selectedFlows); @@ -215,20 +263,23 @@ export default function ComponentsComponent({ if (type === "all") return allFlows?.length; return allFlows?.filter( - (f) => (f.is_component ?? false) === (type === "component") + (f) => (f.is_component ?? false) === (type === "component"), )?.length; }; return ( <> - {allFlows?.length > 0 && ( - handleSelectOptionsChange("delete")} - handleSelectAll={handleSelectAll} - handleDuplicate={() => handleSelectOptionsChange("duplicate")} - disableFunctions={!(selectedFlowsComponentsCards?.length > 0)} - /> - )} +
+ {allFlows?.length > 0 && ( + handleSelectOptionsChange("delete")} + handleSelectAll={handleSelectAll} + handleDuplicate={() => handleSelectOptionsChange("duplicate")} + handleExport={() => handleSelectOptionsChange("export")} + disableFunctions={!(selectedFlowsComponentsCards?.length > 0)} + /> + )} +
{ return ( <> -
+
diff --git a/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx b/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx index 2d2e43a2a..527c8d423 100644 --- a/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/headerComponent/index.tsx @@ -1,13 +1,17 @@ import { useState } from "react"; -import IconComponent from "../../../../components/genericIconComponent"; +import IconComponent, { + ForwardedIconComponent, +} from "../../../../components/genericIconComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent"; import { Checkbox } from "../../../../components/ui/checkbox"; import { cn } from "../../../../utils/utils"; +import { Button } from "../../../../components/ui/button"; type HeaderComponentProps = { handleSelectAll: (select) => void; handleDelete: () => void; handleDuplicate: () => void; + handleExport: () => void; disableFunctions: boolean; }; @@ -15,6 +19,7 @@ const HeaderComponent = ({ handleSelectAll, handleDelete, handleDuplicate, + handleExport, disableFunctions, }: HeaderComponentProps) => { const [shouldSelectAll, setShouldSelectAll] = useState(true); @@ -26,29 +31,46 @@ const HeaderComponent = ({ return ( <> -
-
- -
-
-
- - -
-
+
+ -
+
+
+ Select items to export + ) : ( + Export selected items + ) + } + > + + +
- +
@@ -77,15 +104,20 @@ const HeaderComponent = ({ ) } > - +
diff --git a/src/frontend/src/pages/SettingsPage/index.tsx b/src/frontend/src/pages/SettingsPage/index.tsx index d956f20f4..d201d2596 100644 --- a/src/frontend/src/pages/SettingsPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/index.tsx @@ -8,7 +8,7 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore"; export default function SettingsPage(): JSX.Element { const pathname = location.pathname; const setCurrentFlowId = useFlowsManagerStore( - (state) => state.setCurrentFlowId + (state) => state.setCurrentFlowId, ); useEffect(() => { setCurrentFlowId(""); @@ -59,7 +59,10 @@ export default function SettingsPage(): JSX.Element { title: "Messages", href: "/settings/messages", icon: ( - + ), }, ]; @@ -72,8 +75,10 @@ export default function SettingsPage(): JSX.Element { -
- +
+
+ +
diff --git a/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx index 8efa2e3f9..cc19346c3 100644 --- a/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/ApiKeysPage/index.tsx @@ -52,23 +52,17 @@ export default function ApiKeysPage() { />
- - - { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.id), - ); - }} - rowSelection="multiple" - suppressRowClickSelection={true} - pagination={true} - columnDefs={columnDefs} - rowData={keysList.current} - /> - - + { + setSelectedRows(event.api.getSelectedRows().map((row) => row.id)); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + pagination={true} + columnDefs={columnDefs} + rowData={keysList.current} + />
); diff --git a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx index d33761a17..554bfdd28 100644 --- a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx @@ -16,13 +16,13 @@ import { cn } from "../../../../utils/utils"; export default function GlobalVariablesPage() { const globalVariablesEntries = useGlobalVariablesStore( - (state) => state.globalVariablesEntries + (state) => state.globalVariablesEntries, ); const removeGlobalVariable = useGlobalVariablesStore( - (state) => state.removeGlobalVariable + (state) => state.removeGlobalVariable, ); const globalVariables = useGlobalVariablesStore( - (state) => state.globalVariables + (state) => state.globalVariables, ); const setErrorData = useAlertStore((state) => state.setErrorData); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); @@ -154,7 +154,7 @@ export default function GlobalVariablesPage() { @@ -168,23 +168,17 @@ export default function GlobalVariablesPage() {
- - - { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.name) - ); - }} - rowSelection="multiple" - suppressRowClickSelection={true} - pagination={true} - columnDefs={colDefs} - rowData={rowData} - /> - - + { + setSelectedRows(event.api.getSelectedRows().map((row) => row.name)); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + pagination={true} + columnDefs={colDefs} + rowData={rowData} + />
); diff --git a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx index c6a5235d4..ebc0b5ed9 100644 --- a/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx @@ -115,15 +115,11 @@ export default function ShortcutsPage() {
- - - - - +
); diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx index 2fff6f360..840460959 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx @@ -27,7 +27,7 @@ export default function MessagesPage() { setSelectedRows, setSuccessData, setErrorData, - selectedRows + selectedRows, ); const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData); @@ -52,29 +52,25 @@ export default function MessagesPage() { handleRemoveMessages={handleRemoveMessages} /> -
- - - { - handleUpdateMessage(event); - }} - editable={["Sender Name", "Message"]} - overlayNoRowsTemplate="No data available" - onSelectionChanged={(event: SelectionChangedEvent) => { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.index) - ); - }} - rowSelection="multiple" - suppressRowClickSelection={true} - pagination={true} - columnDefs={columns} - rowData={messages} - /> - - +
+ { + handleUpdateMessage(event); + }} + editable={["Sender Name", "Message"]} + overlayNoRowsTemplate="No data available" + onSelectionChanged={(event: SelectionChangedEvent) => { + setSelectedRows( + event.api.getSelectedRows().map((row) => row.index), + ); + }} + rowSelection="multiple" + suppressRowClickSelection={true} + pagination={true} + columnDefs={columns} + rowData={messages} + />
); diff --git a/src/frontend/src/stores/foldersStore.tsx b/src/frontend/src/stores/foldersStore.tsx index ce063e032..9284c426f 100644 --- a/src/frontend/src/stores/foldersStore.tsx +++ b/src/frontend/src/stores/foldersStore.tsx @@ -7,6 +7,7 @@ import { } from "../pages/MainPage/services"; import { FoldersStoreType } from "../types/zustand/folders"; import useFlowsManagerStore from "./flowsManagerStore"; +import { uploadFlowsToDatabase } from "../controllers/API"; export const useFolderStore = create((set, get) => ({ folders: [], @@ -17,18 +18,18 @@ export const useFolderStore = create((set, get) => ({ getFolders().then( (res) => { const foldersWithoutStarterProjects = res.filter( - (folder) => folder.name !== STARTER_FOLDER_NAME + (folder) => folder.name !== STARTER_FOLDER_NAME, ); const starterProjects = res.find( - (folder) => folder.name === STARTER_FOLDER_NAME + (folder) => folder.name === STARTER_FOLDER_NAME, ); set({ starterProjectId: starterProjects!.id ?? "" }); set({ folders: foldersWithoutStarterProjects }); const myCollectionId = res?.find( - (f) => f.name === DEFAULT_FOLDER + (f) => f.name === DEFAULT_FOLDER, )?.id; set({ myCollectionId }); @@ -45,7 +46,7 @@ export const useFolderStore = create((set, get) => ({ set({ folders: [] }); get().setLoading(false); reject(); - } + }, ); } }); @@ -65,7 +66,7 @@ export const useFolderStore = create((set, get) => ({ }, () => { get().setLoadingById(false); - } + }, ); } }, @@ -100,7 +101,7 @@ export const useFolderStore = create((set, get) => ({ folderIdDragging: "", setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })), uploadFolder: () => { - return new Promise(() => { + return new Promise((resolve, reject) => { const input = document.createElement("input"); input.type = "file"; input.onchange = (event: Event) => { @@ -111,8 +112,31 @@ export const useFolderStore = create((set, get) => ({ const file = (event.target as HTMLInputElement).files![0]; const formData = new FormData(); formData.append("file", file); - uploadFlowsFromFolders(formData).then(() => { - get().getFoldersApi(true); + file.text().then((text) => { + const data = JSON.parse(text); + if (data.data?.nodes) { + useFlowsManagerStore + .getState() + .addFlow(true, data) + .then(() => { + resolve(); + }) + .catch((error) => { + reject(error); + }); + } else { + uploadFlowsFromFolders(formData) + .then(() => { + get() + .getFoldersApi(true) + .then(() => { + resolve(); + }); + }) + .catch((error) => { + reject(error); + }); + } }); } }; diff --git a/src/frontend/src/types/zustand/folders/index.ts b/src/frontend/src/types/zustand/folders/index.ts index 352be3baf..2c5b2b96b 100644 --- a/src/frontend/src/types/zustand/folders/index.ts +++ b/src/frontend/src/types/zustand/folders/index.ts @@ -20,7 +20,7 @@ export type FoldersStoreType = { setFolderUrl: (folderUrl: string) => void; folderDragging: boolean; setFolderDragging: (set: boolean) => void; - uploadFolder: (folderId: string) => void; + uploadFolder: (folderId: string) => Promise; folderIdDragging: string; setFolderIdDragging: (id: string) => void; starterProjectId: string; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 54bbda3a6..ece2e6539 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -56,6 +56,7 @@ import { FolderIcon, FolderPlus, FolderPlusIcon, + FolderUp, FormInput, Forward, Gift, @@ -433,6 +434,7 @@ export const nodeIconsLucide: iconsType = { ChevronLeft, SlidersHorizontal, Palette, + FolderUp, Blocks, ChevronDown, ArrowLeft,