merging all branches/feat

This commit is contained in:
cristhianzl 2024-06-06 21:43:59 -03:00
commit 02cca793eb
44 changed files with 309 additions and 192 deletions

View file

@ -80,7 +80,10 @@ jobs:
langflowai/langflow-frontend:1.0-alpha langflowai/langflow-frontend:1.0-alpha
restart-space: restart-space:
name: Restart HuggingFace Spaces
if: ${{ inputs.release_type == 'main' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: docker_build
strategy: strategy:
matrix: matrix:
python-version: python-version:

View file

@ -35,6 +35,10 @@ jobs:
with: with:
python-version: "3.10" python-version: "3.10"
cache: "poetry" cache: "poetry"
- name: Set up Nodejs 20
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Check Version - name: Check Version
id: check-version id: check-version
run: | run: |

View file

@ -1,6 +1,14 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# Keep this syntax directive! It's used to enable Docker BuildKit # 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 # BUILDER-BASE
# Used to build deps + create our virtual environment # Used to build deps + create our virtual environment
@ -48,10 +56,10 @@ COPY pyproject.toml poetry.lock README.md ./
COPY src/ ./src COPY src/ ./src
COPY scripts/ ./scripts COPY scripts/ ./scripts
RUN python -m pip install requests --user && cd ./scripts && python update_dependencies.py 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 \ 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 build -f wheel \
&& $POETRY_HOME/bin/poetry run pip install dist/*.whl && $POETRY_HOME/bin/poetry run pip install dist/*.whl --force-reinstall
################################ ################################
# RUNTIME # RUNTIME

14
poetry.lock generated
View file

@ -698,13 +698,13 @@ graph = ["gremlinpython (==3.4.6)"]
[[package]] [[package]]
name = "cassio" 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." description = "A framework-agnostic Python library to seamlessly integrate Apache Cassandra(R) with ML/LLM/genAI workloads."
optional = false optional = false
python-versions = "<4.0,>=3.8" python-versions = "<4.0,>=3.8"
files = [ files = [
{file = "cassio-0.1.7-py3-none-any.whl", hash = "sha256:08d1028a20d09bd207de0e17eaf7ae821b3c8e4788555e2d337aa440e0846d87"}, {file = "cassio-0.1.8-py3-none-any.whl", hash = "sha256:c09e7c884ba7227ff5277c86f3b0f31c523672ea407f56d093c7227e69c54d94"},
{file = "cassio-0.1.7.tar.gz", hash = "sha256:44f705dff8a9a1c48527db2c9e968686358c960fa21ba940d9e66de00639ad78"}, {file = "cassio-0.1.8.tar.gz", hash = "sha256:4e09929506cb3dd6fad217e89846d0a1a59069afd24b82c72526ef6f2e9271af"},
] ]
[package.dependencies] [package.dependencies]
@ -4327,7 +4327,7 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]] [[package]]
name = "langflow-base" name = "langflow-base"
version = "0.0.58" version = "0.0.59"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
optional = false optional = false
python-versions = ">=3.10,<3.13" python-versions = ">=3.10,<3.13"
@ -5588,13 +5588,13 @@ sympy = "*"
[[package]] [[package]]
name = "openai" name = "openai"
version = "1.31.2" version = "1.32.0"
description = "The official Python library for the openai API" description = "The official Python library for the openai API"
optional = false optional = false
python-versions = ">=3.7.1" python-versions = ">=3.7.1"
files = [ files = [
{file = "openai-1.31.2-py3-none-any.whl", hash = "sha256:203cf21294f347c3d7b591e0ccbe18389d6f8967d4237214b926ea76b1e1781c"}, {file = "openai-1.32.0-py3-none-any.whl", hash = "sha256:953d57669f309002044fd2f678aba9f07a43256d74b3b00cd04afb5b185568ea"},
{file = "openai-1.31.2.tar.gz", hash = "sha256:966ab3165b926cb5ec091d2434c90613e2ea8b73ffad984f7fec34bde971725a"}, {file = "openai-1.32.0.tar.gz", hash = "sha256:a6df15a7ab9344b1bc2bc8d83639f68b7a7e2453c0f5e50c1666547eee86f0bd"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "1.0.0a46" version = "1.0.0a48"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

View file

@ -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",
]

View file

@ -297,7 +297,7 @@ class CodeParser:
bases = self.execute_and_inspect_classes(self.code) bases = self.execute_and_inspect_classes(self.code)
except Exception as e: except Exception as e:
# If the code cannot be executed, return an empty list # If the code cannot be executed, return an empty list
logger.exception(e) logger.debug(e)
bases = [] bases = []
raise e raise e
return bases return bases

View file

@ -78,7 +78,8 @@ class DirectoryReader:
component_tuple = (*build_component(component), component) component_tuple = (*build_component(component), component)
components.append(component_tuple) components.append(component_tuple)
except Exception as e: 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 continue
items.append({"name": menu["name"], "path": menu["path"], "components": components}) items.append({"name": menu["name"], "path": menu["path"], "components": components})
filtered = [menu for menu in items if menu["components"]] filtered = [menu for menu in items if menu["components"]]
@ -266,8 +267,7 @@ class DirectoryReader:
if validation_result: if validation_result:
try: try:
output_types = self.get_output_types_from_code(result_content) output_types = self.get_output_types_from_code(result_content)
except Exception as exc: except Exception:
logger.exception(f"Error while getting output types from code: {str(exc)}")
output_types = [component_name_camelcase] output_types = [component_name_camelcase]
else: else:
output_types = [component_name_camelcase] output_types = [component_name_camelcase]

View file

@ -132,7 +132,7 @@ class MonitorService(Service):
order: Optional[str] = "DESC", order: Optional[str] = "DESC",
limit: Optional[int] = None, 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 = [] conditions = []
if sender: if sender:
conditions.append(f"sender = '{sender}'") conditions.append(f"sender = '{sender}'")

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow-base" name = "langflow-base"
version = "0.0.58" version = "0.0.59"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

View file

@ -21,6 +21,7 @@ import {
LANGFLOW_SUPPORTED_TYPES, LANGFLOW_SUPPORTED_TYPES,
TOOLTIP_EMPTY, TOOLTIP_EMPTY,
} from "../../../../constants/constants"; } from "../../../../constants/constants";
import OutputModal from "../../../../customNodes/genericNode/components/outputModal";
import { Case } from "../../../../shared/components/caseComponent"; import { Case } from "../../../../shared/components/caseComponent";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; 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 useHandleOnNewValue from "../../../hooks/use-handle-new-value";
import useHandleNodeClass from "../../../hooks/use-handle-node-class"; import useHandleNodeClass from "../../../hooks/use-handle-node-class";
import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons"; import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons";
import OutputModal from "../outputModal";
import TooltipRenderComponent from "../tooltipRenderComponent"; import TooltipRenderComponent from "../tooltipRenderComponent";
import { TEXT_FIELD_TYPES } from "./constants"; import { TEXT_FIELD_TYPES } from "./constants";
@ -284,7 +284,7 @@ export default function ParameterComponent({
"h-5 w-5 rounded-md", "h-5 w-5 rounded-md",
displayOutputPreview && !unknownOutput displayOutputPreview && !unknownOutput
? " hover:bg-secondary-foreground/5 hover:text-medium-indigo" ? " hover:bg-secondary-foreground/5 hover:text-medium-indigo"
: " cursor-not-allowed text-muted-foreground", : " cursor-not-allowed text-muted-foreground"
)} )}
name={"ScanEye"} name={"ScanEye"}
/> />

View file

@ -11,6 +11,8 @@ import {
STATUS_BUILDING, STATUS_BUILDING,
} from "../../constants/constants"; } from "../../constants/constants";
import { BuildStatus } from "../../constants/enums"; 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 NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore"; import { useDarkStore } from "../../stores/darkStore";
@ -22,8 +24,6 @@ import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, cn } from "../../utils/utils"; 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 useCheckCodeValidity from "../hooks/use-check-code-validity";
import useIconNodeRender from "../hooks/use-icon-render"; import useIconNodeRender from "../hooks/use-icon-render";
import useIconStatus from "../hooks/use-icons-status"; import useIconStatus from "../hooks/use-icons-status";
@ -127,7 +127,7 @@ export default function GenericNode({
const names = classNames( const names = classNames(
baseBorderClass, baseBorderClass,
nodeSizeClass, nodeSizeClass,
"generic-node-div", "generic-node-div group/node",
specificClassFromBuildStatus specificClassFromBuildStatus
); );
return names; return names;

View file

@ -51,13 +51,15 @@ export default function ErrorAlert({
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="error-build-foreground">{title}</h3> <h3 className="error-build-foreground line-clamp-2">{title}</h3>
{list?.length !== 0 && {list?.length !== 0 &&
list?.some((item) => item !== null && item !== undefined) ? ( list?.some((item) => item !== null && item !== undefined) ? (
<div className="error-build-message-div"> <div className="error-build-message-div">
<ul className="error-build-message-list"> <ul className="error-build-message-list">
{list.map((item, index) => ( {list.map((item, index) => (
<li key={index}>{item}</li> <li key={index} className="line-clamp-5">
{item}
</li>
))} ))}
</ul> </ul>
</div> </div>

View file

@ -47,7 +47,7 @@ export default function NoticeAlert({
/> />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground word-break-break-word"> <p className="line-clamp-2 text-sm text-info-foreground word-break-break-word">
{title} {title}
</p> </p>
<p className="mt-3 text-sm md:ml-6 md:mt-0"> <p className="mt-3 text-sm md:ml-6 md:mt-0">

View file

@ -45,7 +45,7 @@ export default function SuccessAlert({
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="success-alert-message">{title}</p> <p className="success-alert-message line-clamp-2">{title}</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -56,7 +56,7 @@ export default function Header(): JSX.Element {
const lastFlowVisitedIndex = routeHistory const lastFlowVisitedIndex = routeHistory
.reverse() .reverse()
.findIndex( .findIndex(
(path) => path.includes("/flow/") && path !== location.pathname (path) => path.includes("/flow/") && path !== location.pathname,
); );
const lastFlowVisited = routeHistory[lastFlowVisitedIndex]; const lastFlowVisited = routeHistory[lastFlowVisitedIndex];
@ -200,6 +200,28 @@ export default function Header(): JSX.Element {
/> />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{!autoLogin && (
<>
<DropdownMenuLabel>
<div className="flex items-center gap-3">
<div
className={
"h-5 w-5 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
(userData?.id
? gradients[
parseInt(userData?.id ?? "", 30) %
gradients.length
]
: "bg-gray-500"))
}
/>
{userData?.username ?? "User"}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuLabel>General</DropdownMenuLabel> <DropdownMenuLabel>General</DropdownMenuLabel>
<DropdownMenuItem <DropdownMenuItem
className="cursor-pointer" className="cursor-pointer"

View file

@ -13,6 +13,7 @@ import IconComponent, {
import { Button, buttonVariants } from "../../../ui/button"; import { Button, buttonVariants } from "../../../ui/button";
import { Input } from "../../../ui/input"; import { Input } from "../../../ui/input";
import useFileDrop from "../../hooks/use-on-file-drop"; import useFileDrop from "../../hooks/use-on-file-drop";
import useAlertStore from "../../../../stores/alertStore";
type SideBarFoldersButtonsComponentProps = { type SideBarFoldersButtonsComponentProps = {
folders: FolderType[]; folders: FolderType[];
@ -51,6 +52,7 @@ const SideBarFoldersButtonsComponent = ({
const location = useLocation(); const location = useLocation();
const folderId = location?.state?.folderId ?? myCollectionId; const folderId = location?.state?.folderId ?? myCollectionId;
const getFolderById = useFolderStore((state) => state.getFolderById); const getFolderById = useFolderStore((state) => state.getFolderById);
const setErrorData = useAlertStore((state) => state.setErrorData);
const handleFolderChange = (folderId: string) => { const handleFolderChange = (folderId: string) => {
getFolderById(folderId); getFolderById(folderId);
@ -62,7 +64,17 @@ const SideBarFoldersButtonsComponent = ({
); );
const handleUploadFlowsToFolder = () => { 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) => { const handleDownloadFolder = (id: string) => {

View file

@ -668,7 +668,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications";
export const SUCCESS_BUILD = "Built sucessfully ✨"; export const SUCCESS_BUILD = "Built sucessfully ✨";
export const ALERT_SAVE_WITH_API = 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 SAVE_WITH_API_CHECKBOX = "Save with my API keys";
export const EDIT_TEXT_MODAL_TITLE = "Edit Text"; export const EDIT_TEXT_MODAL_TITLE = "Edit Text";

View file

@ -45,7 +45,7 @@ const ExportModal = forwardRef(
is_component: false, is_component: false,
}, },
name!, name!,
description description,
); );
setNoticeData({ setNoticeData({
title: API_WARNING_NOTICE_ALERT, title: API_WARNING_NOTICE_ALERT,
@ -61,7 +61,7 @@ const ExportModal = forwardRef(
is_component: false, is_component: false,
}), }),
name!, name!,
description description,
); );
setOpen(false); setOpen(false);
}} }}
@ -94,7 +94,7 @@ const ExportModal = forwardRef(
{SAVE_WITH_API_CHECKBOX} {SAVE_WITH_API_CHECKBOX}
</label> </label>
</div> </div>
<span className=" text-xs text-destructive "> <span className="mt-1 text-xs text-destructive ">
{ALERT_SAVE_WITH_API} {ALERT_SAVE_WITH_API}
</span> </span>
</BaseModal.Content> </BaseModal.Content>
@ -102,6 +102,6 @@ const ExportModal = forwardRef(
<BaseModal.Footer submit={{ label: "Download Flow" }} /> <BaseModal.Footer submit={{ label: "Download Flow" }} />
</BaseModal> </BaseModal>
); );
} },
); );
export default ExportModal; export default ExportModal;

View file

@ -4,7 +4,9 @@ import { FormProvider, useForm, useWatch } from "react-hook-form";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import CollectionCardComponent from "../../../../components/cardComponent"; import CollectionCardComponent from "../../../../components/cardComponent";
import CardsWrapComponent from "../../../../components/cardsWrapComponent"; import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../components/genericIconComponent";
import PaginatorComponent from "../../../../components/paginatorComponent"; import PaginatorComponent from "../../../../components/paginatorComponent";
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
import { Button } from "../../../../components/ui/button"; 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 { sortFlows } from "../../utils/sort-flows";
import EmptyComponent from "../emptyComponent"; import EmptyComponent from "../emptyComponent";
import HeaderComponent from "../headerComponent"; 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({ export default function ComponentsComponent({
type = "all", type = "all",
@ -31,22 +36,22 @@ export default function ComponentsComponent({
const allFlows = useFlowsManagerStore((state) => state.allFlows); const allFlows = useFlowsManagerStore((state) => state.allFlows);
const flowsFromFolder = useFolderStore( const flowsFromFolder = useFolderStore(
(state) => state.selectedFolder?.flows (state) => state.selectedFolder?.flows,
); );
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const searchFlowsComponents = useFlowsManagerStore( const searchFlowsComponents = useFlowsManagerStore(
(state) => state.searchFlowsComponents (state) => state.searchFlowsComponents,
); );
const setSelectedFlowsComponentsCards = useFlowsManagerStore( const setSelectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.setSelectedFlowsComponentsCards (state) => state.setSelectedFlowsComponentsCards,
); );
const selectedFlowsComponentsCards = useFlowsManagerStore( const selectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.selectedFlowsComponentsCards (state) => state.selectedFlowsComponentsCards,
); );
const [handleFileDrop] = useFileDrop(uploadFlow, type)!; const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
@ -82,7 +87,7 @@ export default function ComponentsComponent({
f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) || f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) ||
f.description f.description
.toLowerCase() .toLowerCase()
.includes(searchFlowsComponents.toLowerCase()) .includes(searchFlowsComponents.toLowerCase()),
); );
if (searchFlowsComponents === "") { if (searchFlowsComponents === "") {
@ -129,6 +134,8 @@ export default function ComponentsComponent({
setOpenDelete(true); setOpenDelete(true);
} else if (action === "duplicate") { } else if (action === "duplicate") {
handleDuplicate(); handleDuplicate();
} else if (action === "export") {
handleExport();
} }
}; };
@ -137,9 +144,9 @@ export default function ComponentsComponent({
selectedFlowsComponentsCards.map((selectedFlow) => selectedFlowsComponentsCards.map((selectedFlow) =>
addFlow( addFlow(
true, true,
allFlows.find((flow) => flow.id === selectedFlow) allFlows.find((flow) => flow.id === selectedFlow),
) ),
) ),
).then(() => { ).then(() => {
resetFilter(); resetFilter();
getFoldersApi(true); 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 = () => { const handleDeleteMultiple = () => {
removeFlow(selectedFlowsComponentsCards) removeFlow(selectedFlowsComponentsCards)
.then(() => { .then(() => {
@ -161,7 +209,7 @@ export default function ComponentsComponent({
getFolderById(folderId ? folderId : myCollectionId); getFolderById(folderId ? folderId : myCollectionId);
} }
setSuccessData({ setSuccessData({
title: "Selected items deleted successfully!", title: "Selected items deleted successfully",
}); });
}) })
.catch(() => { .catch(() => {
@ -180,7 +228,7 @@ export default function ComponentsComponent({
return true; return true;
} }
return false; return false;
} },
); );
setSelectedFlowsComponentsCards(selectedFlows); setSelectedFlowsComponentsCards(selectedFlows);
@ -215,20 +263,23 @@ export default function ComponentsComponent({
if (type === "all") return allFlows?.length; if (type === "all") return allFlows?.length;
return allFlows?.filter( return allFlows?.filter(
(f) => (f.is_component ?? false) === (type === "component") (f) => (f.is_component ?? false) === (type === "component"),
)?.length; )?.length;
}; };
return ( return (
<> <>
{allFlows?.length > 0 && ( <div className="flex w-full gap-4 pb-5">
<HeaderComponent {allFlows?.length > 0 && (
handleDelete={() => handleSelectOptionsChange("delete")} <HeaderComponent
handleSelectAll={handleSelectAll} handleDelete={() => handleSelectOptionsChange("delete")}
handleDuplicate={() => handleSelectOptionsChange("duplicate")} handleSelectAll={handleSelectAll}
disableFunctions={!(selectedFlowsComponentsCards?.length > 0)} handleDuplicate={() => handleSelectOptionsChange("duplicate")}
/> handleExport={() => handleSelectOptionsChange("export")}
)} disableFunctions={!(selectedFlowsComponentsCards?.length > 0)}
/>
)}
</div>
<CardsWrapComponent <CardsWrapComponent
onFileDrop={handleFileDrop} onFileDrop={handleFileDrop}

View file

@ -13,7 +13,7 @@ const EmptyComponent = ({}: EmptyComponentProps) => {
return ( return (
<> <>
<div className="mt-6 flex w-full items-center justify-center text-center"> <div className="mt-2 flex w-full items-center justify-center text-center">
<div className="flex-max-width h-full flex-col"> <div className="flex-max-width h-full flex-col">
<div className="align-center flex w-full justify-center gap-1 "> <div className="align-center flex w-full justify-center gap-1 ">
<span className="text-muted-foreground"> <span className="text-muted-foreground">

View file

@ -1,13 +1,17 @@
import { useState } from "react"; import { useState } from "react";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../components/genericIconComponent";
import ShadTooltip from "../../../../components/shadTooltipComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Checkbox } from "../../../../components/ui/checkbox"; import { Checkbox } from "../../../../components/ui/checkbox";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import { Button } from "../../../../components/ui/button";
type HeaderComponentProps = { type HeaderComponentProps = {
handleSelectAll: (select) => void; handleSelectAll: (select) => void;
handleDelete: () => void; handleDelete: () => void;
handleDuplicate: () => void; handleDuplicate: () => void;
handleExport: () => void;
disableFunctions: boolean; disableFunctions: boolean;
}; };
@ -15,6 +19,7 @@ const HeaderComponent = ({
handleSelectAll, handleSelectAll,
handleDelete, handleDelete,
handleDuplicate, handleDuplicate,
handleExport,
disableFunctions, disableFunctions,
}: HeaderComponentProps) => { }: HeaderComponentProps) => {
const [shouldSelectAll, setShouldSelectAll] = useState(true); const [shouldSelectAll, setShouldSelectAll] = useState(true);
@ -26,29 +31,46 @@ const HeaderComponent = ({
return ( return (
<> <>
<div className="grid grid-cols-3 pb-5"> <div className="flex w-full items-center justify-between gap-4">
<div className="col-auto grid-cols-1 self-center justify-self-start"> <div className="flex items-center justify-self-start">
<a onClick={handleClick} className="text-sm"> <Button
<div className="header-menu-bar-display "> variant="none"
<div size="none"
className="header-menu-flow-name" onClick={handleClick}
data-testid="select_all_collection" className="text-sm"
> >
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Checkbox checked={!shouldSelectAll} id="terms" /> <Checkbox checked={!shouldSelectAll} id="terms" />
<label <span className="label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
onClick={handleClick} {shouldSelectAll ? "Select All" : "Unselect All"}
htmlFor="terms" </span>
className="label cursor-pointer text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{shouldSelectAll ? "Select All" : "Unselect All"}
</label>
</div>
</div>
</div> </div>
</a> </Button>
</div> </div>
<div className="col-span-2 flex grid-cols-1 gap-2 justify-self-end"> <div className="flex items-center gap-2">
<div>
<ShadTooltip
content={
disableFunctions ? (
<span>Select items to export</span>
) : (
<span>Export selected items</span>
)
}
>
<Button
variant="none"
size="none"
onClick={handleExport}
disabled={disableFunctions}
>
<IconComponent
name="FileDown"
className={cn("h-5 w-5 text-primary transition-all")}
/>
</Button>
</ShadTooltip>
</div>
<div> <div>
<ShadTooltip <ShadTooltip
content={ content={
@ -59,12 +81,17 @@ const HeaderComponent = ({
) )
} }
> >
<button onClick={handleDuplicate} disabled={disableFunctions}> <Button
variant="none"
size="none"
onClick={handleDuplicate}
disabled={disableFunctions}
>
<IconComponent <IconComponent
name="Copy" name="Copy"
className={cn("h-5 w-5 text-primary transition-all")} className={cn("h-5 w-5 text-primary transition-all")}
/> />
</button> </Button>
</ShadTooltip> </ShadTooltip>
</div> </div>
<div> <div>
@ -77,15 +104,20 @@ const HeaderComponent = ({
) )
} }
> >
<button onClick={handleDelete} disabled={disableFunctions}> <Button
variant="none"
size="none"
onClick={handleDelete}
disabled={disableFunctions}
>
<IconComponent <IconComponent
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-5 w-5 text-primary transition-all", "h-5 w-5 text-primary transition-all",
disableFunctions ? "" : "hover:text-destructive" disableFunctions ? "" : "hover:text-destructive",
)} )}
/> />
</button> </Button>
</ShadTooltip> </ShadTooltip>
</div> </div>
</div> </div>

View file

@ -8,7 +8,7 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function SettingsPage(): JSX.Element { export default function SettingsPage(): JSX.Element {
const pathname = location.pathname; const pathname = location.pathname;
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId (state) => state.setCurrentFlowId,
); );
useEffect(() => { useEffect(() => {
setCurrentFlowId(""); setCurrentFlowId("");
@ -59,7 +59,10 @@ export default function SettingsPage(): JSX.Element {
title: "Messages", title: "Messages",
href: "/settings/messages", href: "/settings/messages",
icon: ( icon: (
<ForwardedIconComponent name="Keyboard" className="w-5 stroke-[1.5]" /> <ForwardedIconComponent
name="MessagesSquare"
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/>
), ),
}, },
]; ];
@ -72,8 +75,10 @@ export default function SettingsPage(): JSX.Element {
<aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]"> <aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]">
<SidebarNav items={sidebarNavItems} /> <SidebarNav items={sidebarNavItems} />
</aside> </aside>
<div className="h-full w-full flex-1 pb-8"> <div className="flex h-full w-full flex-1 flex-col">
<Outlet /> <div className="flex-1 pb-8">
<Outlet />
</div>
</div> </div>
</div> </div>
</PageLayout> </PageLayout>

View file

@ -52,23 +52,17 @@ export default function ApiKeysPage() {
/> />
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> overlayNoRowsTemplate="No data available"
<TableComponent onSelectionChanged={(event: SelectionChangedEvent) => {
overlayNoRowsTemplate="No data available" setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
onSelectionChanged={(event: SelectionChangedEvent) => { }}
setSelectedRows( rowSelection="multiple"
event.api.getSelectedRows().map((row) => row.id), suppressRowClickSelection={true}
); pagination={true}
}} columnDefs={columnDefs}
rowSelection="multiple" rowData={keysList.current}
suppressRowClickSelection={true} />
pagination={true}
columnDefs={columnDefs}
rowData={keysList.current}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -16,13 +16,13 @@ import { cn } from "../../../../utils/utils";
export default function GlobalVariablesPage() { export default function GlobalVariablesPage() {
const globalVariablesEntries = useGlobalVariablesStore( const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries (state) => state.globalVariablesEntries,
); );
const removeGlobalVariable = useGlobalVariablesStore( const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable (state) => state.removeGlobalVariable,
); );
const globalVariables = useGlobalVariablesStore( const globalVariables = useGlobalVariablesStore(
(state) => state.globalVariables (state) => state.globalVariables,
); );
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
@ -154,7 +154,7 @@ export default function GlobalVariablesPage() {
<IconComponent <IconComponent
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-5 w-5 text-destructive group-disabled:text-primary" "h-5 w-5 text-destructive group-disabled:text-primary",
)} )}
/> />
</Button> </Button>
@ -168,23 +168,17 @@ export default function GlobalVariablesPage() {
</div> </div>
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> overlayNoRowsTemplate="No data available"
<TableComponent onSelectionChanged={(event: SelectionChangedEvent) => {
overlayNoRowsTemplate="No data available" setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
onSelectionChanged={(event: SelectionChangedEvent) => { }}
setSelectedRows( rowSelection="multiple"
event.api.getSelectedRows().map((row) => row.name) suppressRowClickSelection={true}
); pagination={true}
}} columnDefs={colDefs}
rowSelection="multiple" rowData={rowData}
suppressRowClickSelection={true} />
pagination={true}
columnDefs={colDefs}
rowData={rowData}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -115,15 +115,11 @@ export default function ShortcutsPage() {
</div> </div>
</div> </div>
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> pagination={false}
<TableComponent columnDefs={colDefs}
pagination={false} rowData={nodesRowData}
columnDefs={colDefs} />
rowData={nodesRowData}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -27,7 +27,7 @@ export default function MessagesPage() {
setSelectedRows, setSelectedRows,
setSuccessData, setSuccessData,
setErrorData, setErrorData,
selectedRows selectedRows,
); );
const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData); const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData);
@ -52,29 +52,25 @@ export default function MessagesPage() {
handleRemoveMessages={handleRemoveMessages} handleRemoveMessages={handleRemoveMessages}
/> />
<div className="flex h-full w-full flex-col justify-between pb-8"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> readOnlyEdit
<TableComponent onCellEditRequest={(event) => {
readOnlyEdit handleUpdateMessage(event);
onCellEditRequest={(event) => { }}
handleUpdateMessage(event); editable={["Sender Name", "Message"]}
}} overlayNoRowsTemplate="No data available"
editable={["Sender Name", "Message"]} onSelectionChanged={(event: SelectionChangedEvent) => {
overlayNoRowsTemplate="No data available" setSelectedRows(
onSelectionChanged={(event: SelectionChangedEvent) => { event.api.getSelectedRows().map((row) => row.index),
setSelectedRows( );
event.api.getSelectedRows().map((row) => row.index) }}
); rowSelection="multiple"
}} suppressRowClickSelection={true}
rowSelection="multiple" pagination={true}
suppressRowClickSelection={true} columnDefs={columns}
pagination={true} rowData={messages}
columnDefs={columns} />
rowData={messages}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -7,6 +7,7 @@ import {
} from "../pages/MainPage/services"; } from "../pages/MainPage/services";
import { FoldersStoreType } from "../types/zustand/folders"; import { FoldersStoreType } from "../types/zustand/folders";
import useFlowsManagerStore from "./flowsManagerStore"; import useFlowsManagerStore from "./flowsManagerStore";
import { uploadFlowsToDatabase } from "../controllers/API";
export const useFolderStore = create<FoldersStoreType>((set, get) => ({ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
folders: [], folders: [],
@ -17,18 +18,18 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
getFolders().then( getFolders().then(
(res) => { (res) => {
const foldersWithoutStarterProjects = res.filter( const foldersWithoutStarterProjects = res.filter(
(folder) => folder.name !== STARTER_FOLDER_NAME (folder) => folder.name !== STARTER_FOLDER_NAME,
); );
const starterProjects = res.find( const starterProjects = res.find(
(folder) => folder.name === STARTER_FOLDER_NAME (folder) => folder.name === STARTER_FOLDER_NAME,
); );
set({ starterProjectId: starterProjects!.id ?? "" }); set({ starterProjectId: starterProjects!.id ?? "" });
set({ folders: foldersWithoutStarterProjects }); set({ folders: foldersWithoutStarterProjects });
const myCollectionId = res?.find( const myCollectionId = res?.find(
(f) => f.name === DEFAULT_FOLDER (f) => f.name === DEFAULT_FOLDER,
)?.id; )?.id;
set({ myCollectionId }); set({ myCollectionId });
@ -45,7 +46,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
set({ folders: [] }); set({ folders: [] });
get().setLoading(false); get().setLoading(false);
reject(); reject();
} },
); );
} }
}); });
@ -65,7 +66,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
}, },
() => { () => {
get().setLoadingById(false); get().setLoadingById(false);
} },
); );
} }
}, },
@ -100,7 +101,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
folderIdDragging: "", folderIdDragging: "",
setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })), setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })),
uploadFolder: () => { uploadFolder: () => {
return new Promise<void>(() => { return new Promise<void>((resolve, reject) => {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.onchange = (event: Event) => { input.onchange = (event: Event) => {
@ -111,8 +112,31 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
const file = (event.target as HTMLInputElement).files![0]; const file = (event.target as HTMLInputElement).files![0];
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
uploadFlowsFromFolders(formData).then(() => { file.text().then((text) => {
get().getFoldersApi(true); 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);
});
}
}); });
} }
}; };

View file

@ -20,7 +20,7 @@ export type FoldersStoreType = {
setFolderUrl: (folderUrl: string) => void; setFolderUrl: (folderUrl: string) => void;
folderDragging: boolean; folderDragging: boolean;
setFolderDragging: (set: boolean) => void; setFolderDragging: (set: boolean) => void;
uploadFolder: (folderId: string) => void; uploadFolder: (folderId: string) => Promise<void>;
folderIdDragging: string; folderIdDragging: string;
setFolderIdDragging: (id: string) => void; setFolderIdDragging: (id: string) => void;
starterProjectId: string; starterProjectId: string;

View file

@ -56,6 +56,7 @@ import {
FolderIcon, FolderIcon,
FolderPlus, FolderPlus,
FolderPlusIcon, FolderPlusIcon,
FolderUp,
FormInput, FormInput,
Forward, Forward,
Gift, Gift,
@ -433,6 +434,7 @@ export const nodeIconsLucide: iconsType = {
ChevronLeft, ChevronLeft,
SlidersHorizontal, SlidersHorizontal,
Palette, Palette,
FolderUp,
Blocks, Blocks,
ChevronDown, ChevronDown,
ArrowLeft, ArrowLeft,