@@ -131,9 +132,6 @@ export default function StoreApiKeyModal({
diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/userManagementModal/index.tsx
similarity index 100%
rename from src/frontend/src/modals/UserManagementModal/index.tsx
rename to src/frontend/src/modals/userManagementModal/index.tsx
diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx
index 95b52b24c..1d86d652d 100644
--- a/src/frontend/src/pages/AdminPage/index.tsx
+++ b/src/frontend/src/pages/AdminPage/index.tsx
@@ -1,10 +1,10 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useRef, useState } from "react";
-import PaginatorComponent from "../../components/PaginatorComponent";
-import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import LoadingComponent from "../../components/loadingComponent";
+import PaginatorComponent from "../../components/paginatorComponent";
+import ShadTooltip from "../../components/shadTooltipComponent";
import { Button } from "../../components/ui/button";
import { CheckBoxDiv } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input";
@@ -35,8 +35,8 @@ import {
getUsersPage,
updateUser,
} from "../../controllers/API";
-import ConfirmationModal from "../../modals/ConfirmationModal";
-import UserManagementModal from "../../modals/UserManagementModal";
+import ConfirmationModal from "../../modals/confirmationModal";
+import UserManagementModal from "../../modals/userManagementModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { Users } from "../../types/api";
diff --git a/src/frontend/src/pages/ApiKeysPage/index.tsx b/src/frontend/src/pages/ApiKeysPage/index.tsx
index 9dd473637..49f38715e 100644
--- a/src/frontend/src/pages/ApiKeysPage/index.tsx
+++ b/src/frontend/src/pages/ApiKeysPage/index.tsx
@@ -1,6 +1,6 @@
import { useContext, useEffect, useRef, useState } from "react";
-import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
+import ShadTooltip from "../../components/shadTooltipComponent";
import { Button } from "../../components/ui/button";
import {
Table,
@@ -12,8 +12,8 @@ import {
} from "../../components/ui/table";
import { AuthContext } from "../../contexts/authContext";
import { deleteApiKey, getApiKey } from "../../controllers/API";
-import ConfirmationModal from "../../modals/ConfirmationModal";
-import SecretKeyModal from "../../modals/SecretKeyModal";
+import ConfirmationModal from "../../modals/confirmationModal";
+import SecretKeyModal from "../../modals/secretKeyModal";
import moment from "moment";
import Header from "../../components/headerComponent";
diff --git a/src/frontend/src/pages/deleteAccountPage/index.tsx b/src/frontend/src/pages/DeleteAccountPage/index.tsx
similarity index 100%
rename from src/frontend/src/pages/deleteAccountPage/index.tsx
rename to src/frontend/src/pages/DeleteAccountPage/index.tsx
diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
index e0b7af4f7..cb1a6e553 100644
--- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
@@ -11,13 +11,13 @@ import ReactFlow, {
SelectionDragHandler,
updateEdge,
} from "reactflow";
-import GenericNode from "../../../../CustomNodes/GenericNode";
import {
INVALID_SELECTION_ERROR_ALERT,
UPLOAD_ALERT_LIST,
UPLOAD_ERROR_ALERT,
WRONG_FILE_ERROR_ALERT,
} from "../../../../constants/alerts_constants";
+import GenericNode from "../../../../customNodes/genericNode";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
index 934d438b2..fbd0c12d1 100644
--- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
@@ -1,8 +1,8 @@
import { cloneDeep } from "lodash";
import { LinkIcon, SparklesIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
-import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
+import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
index 76713819a..062c49d0f 100644
--- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx
@@ -1,9 +1,9 @@
import _, { cloneDeep } from "lodash";
import { useEffect, useState } from "react";
import { useUpdateNodeInternals } from "reactflow";
-import ShadTooltip from "../../../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import IconComponent from "../../../../components/genericIconComponent";
+import ShadTooltip from "../../../../components/shadTooltipComponent";
import {
Select,
SelectContent,
@@ -11,8 +11,8 @@ import {
SelectTrigger,
} from "../../../../components/ui/select-custom";
import { postCustomComponent } from "../../../../controllers/API";
-import ConfirmationModal from "../../../../modals/ConfirmationModal";
-import EditNodeModal from "../../../../modals/EditNodeModal";
+import ConfirmationModal from "../../../../modals/confirmationModal";
+import EditNodeModal from "../../../../modals/editNodeModal";
import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
import { useDarkStore } from "../../../../stores/darkStore";
diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx
index 8fb792702..b69015395 100644
--- a/src/frontend/src/pages/FlowPage/index.tsx
+++ b/src/frontend/src/pages/FlowPage/index.tsx
@@ -6,18 +6,25 @@ import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
+import useFlowStore from "../../stores/flowStore";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
const version = useDarkStore((state) => state.version);
+ const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setCurrentFlowId(id!);
+ setOnFlowPage(true);
+
+ return () => {
+ setOnFlowPage(false);
+ };
}, [id]);
return (
<>
diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/LoginPage/index.tsx
similarity index 100%
rename from src/frontend/src/pages/loginPage/index.tsx
rename to src/frontend/src/pages/LoginPage/index.tsx
diff --git a/src/frontend/src/pages/MainPage/components/components/index.tsx b/src/frontend/src/pages/MainPage/components/components/index.tsx
index 996ce4c61..0a390b5f1 100644
--- a/src/frontend/src/pages/MainPage/components/components/index.tsx
+++ b/src/frontend/src/pages/MainPage/components/components/index.tsx
@@ -1,9 +1,9 @@
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
-import PaginatorComponent from "../../../../components/PaginatorComponent";
import CollectionCardComponent from "../../../../components/cardComponent";
import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import IconComponent from "../../../../components/genericIconComponent";
+import PaginatorComponent from "../../../../components/paginatorComponent";
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
import { Button } from "../../../../components/ui/button";
import {
@@ -14,7 +14,6 @@ import {
import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { FlowType } from "../../../../types/flow";
-
export default function ComponentsComponent({
is_component = true,
}: {
@@ -31,7 +30,6 @@ export default function ComponentsComponent({
const [pageIndex, setPageIndex] = useState(1);
const navigate = useNavigate();
-
const all: FlowType[] = flows
.filter((f) => (f.is_component ?? false) === is_component)
.sort((a, b) => {
@@ -87,12 +85,10 @@ export default function ComponentsComponent({
}
}
};
-
function resetFilter() {
setPageIndex(1);
setPageSize(20);
}
-
return (
@@ -167,6 +164,14 @@ export default function ComponentsComponent({
<>>
)
}
+ onClick={
+ !is_component
+ ? () => {
+ navigate("/flow/" + item.id);
+ }
+ : undefined
+ }
+ playground={!is_component}
/>
))
) : (
diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx
index e7aa678e7..08de20b31 100644
--- a/src/frontend/src/pages/MainPage/index.tsx
+++ b/src/frontend/src/pages/MainPage/index.tsx
@@ -1,7 +1,7 @@
import { Group, ToyBrick } from "lucide-react";
import { useEffect, useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
-import DropdownButton from "../../components/DropdownButtonComponent";
+import DropdownButton from "../../components/dropdownButtonComponent";
import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
@@ -11,7 +11,7 @@ import {
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER,
} from "../../constants/constants";
-import NewFlowModal from "../../modals/NewFlowModal";
+import NewFlowModal from "../../modals/newFlowModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
diff --git a/src/frontend/src/pages/Playground/index.tsx b/src/frontend/src/pages/Playground/index.tsx
new file mode 100644
index 000000000..9d538494b
--- /dev/null
+++ b/src/frontend/src/pages/Playground/index.tsx
@@ -0,0 +1,64 @@
+import { useEffect, useState } from "react";
+import { useParams } from "react-router-dom";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+import { getComponent } from "../../controllers/API";
+import cloneFLowWithParent from "../../utils/storeUtils";
+import LoadingComponent from "../../components/loadingComponent";
+import useFlowStore from "../../stores/flowStore";
+import IOModal from "../../modals/IOModal";
+
+export default function PlaygroundPage() {
+ const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
+ const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
+ const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId);
+ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
+ const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
+ const setNodes = useFlowStore((state) => state.setNodes);
+ const setEdges = useFlowStore((state) => state.setEdges);
+ const cleanFlowPool = useFlowStore((state) => state.CleanFlowPool);
+
+ const { id } = useParams();
+ const [loading, setLoading] = useState(true);
+ async function getFlowData() {
+ const res = await getComponent(id!);
+ const newFlow = cloneFLowWithParent(res, res.id, false, true);
+ return newFlow;
+ }
+
+ // Set flow tab id
+ useEffect(() => {
+ console.log("id", id);
+ if (getFlowById(id!)) {
+ setCurrentFlowId(id!);
+ }
+ else {
+ getFlowData().then((flow) => {
+ setCurrentFlow(flow);
+ });
+ }
+ }, [id]);
+
+ useEffect(() => {
+ if (currentFlow) {
+ setNodes(currentFlow?.data?.nodes ?? [], true);
+ setEdges(currentFlow?.data?.edges ?? [], true);
+ cleanFlowPool();
+ setLoading(false);
+ }
+ return () => {
+ setNodes([], true);
+ setEdges([], true);
+ cleanFlowPool();
+ };
+ }, [currentFlow]);
+
+
+ return (
+
+ {loading ?
:
+
{}} isPlayground>
+ <>>
+ }
+
+ )
+}
\ No newline at end of file
diff --git a/src/frontend/src/pages/SettingsPage/index.tsx b/src/frontend/src/pages/SettingsPage/index.tsx
new file mode 100644
index 000000000..554be79b6
--- /dev/null
+++ b/src/frontend/src/pages/SettingsPage/index.tsx
@@ -0,0 +1,91 @@
+import { useEffect } from "react";
+import { Outlet } from "react-router-dom";
+import ForwardedIconComponent from "../../components/genericIconComponent";
+import PageLayout from "../../components/pageLayout";
+import SidebarNav from "../../components/sidebarComponent";
+import useFlowsManagerStore from "../../stores/flowsManagerStore";
+
+export default function SettingsPage(): JSX.Element {
+ const pathname = location.pathname;
+ const setCurrentFlowId = useFlowsManagerStore(
+ (state) => state.setCurrentFlowId
+ );
+ useEffect(() => {
+ setCurrentFlowId("");
+ }, [pathname]);
+
+ const sidebarNavItems = [
+ {
+ title: "General",
+ href: "/settings/general",
+ icon: (
+
+ ),
+ },
+ /* {
+ title: "Theme",
+ href: "/settings/theme",
+ icon: (
+
+ ),
+ },
+ {
+ title: "Bundles",
+ href: "/settings/bundles",
+ icon: (
+
+ ),
+ },
+ {
+ title: "Integrations",
+ href: "/settings/integrations",
+ icon: (
+
+ ),
+ }, */
+ {
+ title: "Global Variables",
+ href: "/settings/global-variables",
+ icon: (
+
+ ),
+ },
+ {
+ title: "Shortcuts",
+ href: "/settings/shortcuts",
+ icon: (
+
+ ),
+ },
+ ];
+ return (
+
+
+
+ );
+}
diff --git a/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
new file mode 100644
index 000000000..7ece3678b
--- /dev/null
+++ b/src/frontend/src/pages/SettingsPage/pages/GeneralPage/index.tsx
@@ -0,0 +1,224 @@
+import * as Form from "@radix-ui/react-form";
+import { cloneDeep } from "lodash";
+import { useContext, useEffect, useState } from "react";
+import ForwardedIconComponent from "../../../../components/genericIconComponent";
+import GradientChooserComponent from "../../../../components/gradientChooserComponent";
+import InputComponent from "../../../../components/inputComponent";
+import { Button } from "../../../../components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "../../../../components/ui/card";
+import {
+ EDIT_PASSWORD_ALERT_LIST,
+ EDIT_PASSWORD_ERROR_ALERT,
+ SAVE_ERROR_ALERT,
+ SAVE_SUCCESS_ALERT,
+} from "../../../../constants/alerts_constants";
+import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
+import { AuthContext } from "../../../../contexts/authContext";
+import { resetPassword, updateUser } from "../../../../controllers/API";
+import useAlertStore from "../../../../stores/alertStore";
+import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
+import {
+ inputHandlerEventType,
+ patchUserInputStateType,
+} from "../../../../types/components";
+import { gradients } from "../../../../utils/styleUtils";
+
+export default function GeneralPage() {
+ const setCurrentFlowId = useFlowsManagerStore(
+ (state) => state.setCurrentFlowId
+ );
+
+ const [inputState, setInputState] = useState(
+ CONTROL_PATCH_USER_STATE
+ );
+
+ const { autoLogin } = useContext(AuthContext);
+
+ // set null id
+ useEffect(() => {
+ setCurrentFlowId("");
+ }, []);
+ const setSuccessData = useAlertStore((state) => state.setSuccessData);
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const { userData, setUserData } = useContext(AuthContext);
+ const { password, cnfPassword, gradient } = inputState;
+
+ async function handlePatchPassword() {
+ if (password !== cnfPassword) {
+ setErrorData({
+ title: EDIT_PASSWORD_ERROR_ALERT,
+ list: [EDIT_PASSWORD_ALERT_LIST],
+ });
+ return;
+ }
+ try {
+ if (password !== "") await resetPassword(userData!.id, { password });
+ handleInput({ target: { name: "password", value: "" } });
+ handleInput({ target: { name: "cnfPassword", value: "" } });
+ setSuccessData({ title: SAVE_SUCCESS_ALERT });
+ } catch (error) {
+ setErrorData({
+ title: SAVE_ERROR_ALERT,
+ list: [(error as any).response.data.detail],
+ });
+ }
+ }
+
+ async function handlePatchGradient() {
+ try {
+ if (gradient !== "")
+ await updateUser(userData!.id, { profile_image: gradient });
+ if (gradient !== "") {
+ let newUserData = cloneDeep(userData);
+ newUserData!.profile_image = gradient;
+
+ setUserData(newUserData);
+ }
+ setSuccessData({ title: SAVE_SUCCESS_ALERT });
+ } catch (error) {
+ setErrorData({
+ title: SAVE_ERROR_ALERT,
+ list: [(error as any).response.data.detail],
+ });
+ }
+ }
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+ return (
+
+
+
+
+ General
+
+
+
+ Manage settings related to Langflow and your account.
+
+
+
+
+
+
+
{
+ handlePatchGradient();
+ event.preventDefault();
+ }}
+ >
+
+
+ Profile Gradient
+
+ Choose the gradient that appears as your profile picture.
+
+
+
+
+ {
+ handleInput({ target: { name: "gradient", value } });
+ }}
+ />
+
+
+
+
+
+
+
+
+ {!autoLogin && (
+
{
+ handlePatchPassword();
+ event.preventDefault();
+ }}
+ >
+
+
+ Password
+
+ Type your new password and confirm it.
+
+
+
+
+
+ {
+ handleInput({ target: { name: "password", value } });
+ }}
+ value={password}
+ isForm
+ password={true}
+ placeholder="Password"
+ className="w-full"
+ />
+
+ Please enter your password
+
+
+
+ {
+ handleInput({
+ target: { name: "cnfPassword", value },
+ });
+ }}
+ value={cnfPassword}
+ isForm
+ password={true}
+ placeholder="Confirm Password"
+ className="w-full"
+ />
+
+
+ Please confirm your password
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx
new file mode 100644
index 000000000..6bf31f245
--- /dev/null
+++ b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx
@@ -0,0 +1,183 @@
+import IconComponent from "../../../../components/genericIconComponent";
+import { Button } from "../../../../components/ui/button";
+
+import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community";
+import { useEffect, useState } from "react";
+import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton";
+import Dropdown from "../../../../components/dropdownComponent";
+import ForwardedIconComponent from "../../../../components/genericIconComponent";
+import TableComponent from "../../../../components/tableComponent";
+import { Badge } from "../../../../components/ui/badge";
+import { deleteGlobalVariable } from "../../../../controllers/API";
+import useAlertStore from "../../../../stores/alertStore";
+import { useGlobalVariablesStore } from "../../../../stores/globalVariables";
+import { cn } from "../../../../utils/utils";
+
+export default function GlobalVariablesPage() {
+ const globalVariablesEntries = useGlobalVariablesStore(
+ (state) => state.globalVariablesEntries
+ );
+ const removeGlobalVariable = useGlobalVariablesStore(
+ (state) => state.removeGlobalVariable
+ );
+ const globalVariables = useGlobalVariablesStore(
+ (state) => state.globalVariables
+ );
+ const setErrorData = useAlertStore((state) => state.setErrorData);
+ const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
+
+ const BadgeRenderer = (props) => {
+ return props.value !== "" ? (
+
+
+ {props.value}
+
+
+ ) : (
+
+ );
+ };
+
+ const [rowData, setRowData] = useState<
+ {
+ type: string | undefined;
+ id: string;
+ name: string;
+ default_fields: string | undefined;
+ }[]
+ >();
+
+ useEffect(() => {
+ const rows: Array<{
+ type: string | undefined;
+ id: string;
+ name: string;
+ default_fields: string | undefined;
+ }> = [];
+ globalVariablesEntries.forEach((entrie) => {
+ const globalVariableObj = globalVariables[entrie];
+ rows.push({
+ type: globalVariableObj.type,
+ id: globalVariableObj.id,
+ default_fields: (globalVariableObj.default_fields ?? []).join(", "),
+ name: entrie,
+ });
+ });
+ setRowData(rows);
+ }, [globalVariables]);
+
+ const DropdownEditor = ({ options, value, onValueChange }) => {
+ return (
+
+
+
+ );
+ };
+ // Column Definitions: Defines the columns to be displayed.
+ const [colDefs, setColDefs] = useState<(ColDef | ColGroupDef)[]>([
+ {
+ headerCheckboxSelection: true,
+ checkboxSelection: true,
+ showDisabledCheckboxes: true,
+ headerName: "Variable Name",
+ field: "name",
+ flex: 1,
+ }, //This column will be twice as wide as the others
+ {
+ field: "type",
+ cellRenderer: BadgeRenderer,
+ cellEditor: DropdownEditor,
+ cellEditorParams: {
+ options: ["Generic", "Credential"],
+ },
+ flex: 1,
+ editable: false,
+ },
+ // {
+ // field: "value",
+ // cellEditor: "agLargeTextCellEditor",
+ // flex: 2,
+ // editable: false,
+ // },
+ {
+ headerName: "Apply To Fields",
+ field: "default_fields",
+ flex: 1,
+ editable: false,
+ },
+ ]);
+
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ async function removeVariables() {
+ const deleteGlobalVariablesPromise = selectedRows.map(async (row) => {
+ const id = getVariableId(row);
+ const deleteGlobalVariables = deleteGlobalVariable(id!);
+ await deleteGlobalVariables;
+ });
+ Promise.all(deleteGlobalVariablesPromise)
+ .then(() => {
+ selectedRows.forEach((row) => {
+ removeGlobalVariable(row);
+ });
+ })
+ .catch(() => {
+ setErrorData({
+ title: `Error deleting global variables.`,
+ });
+ });
+ }
+
+ return (
+
+
+
+
+ Global Variables
+
+
+
+ Manage global variables and assign them to fields.
+
+
+
+
+
+
+
+
+
+
+
+
{
+ setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
+ }}
+ rowSelection="multiple"
+ suppressRowClickSelection={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
new file mode 100644
index 000000000..de2c92b2d
--- /dev/null
+++ b/src/frontend/src/pages/SettingsPage/pages/ShortcutsPage/index.tsx
@@ -0,0 +1,119 @@
+import { ColDef, ColGroupDef } from "ag-grid-community";
+import { useState } from "react";
+import ForwardedIconComponent from "../../../../components/genericIconComponent";
+import TableComponent from "../../../../components/tableComponent";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "../../../../components/ui/card";
+
+export default function ShortcutsPage() {
+ const advancedShortcut = "Ctrl + Shift + A";
+ const minizmizeShortcut = "Ctrl + Shift + Q";
+ const codeShortcut = "Ctrl + Shift + C";
+ const copyShortcut = "Ctrl + C";
+ const duplicateShortcut = "Ctrl + D";
+ const shareShortcut = "Ctrl + Shift + S";
+ const docsShortcut = "Ctrl + Shift + D";
+ const saveShortcut = "Ctrl + S";
+ const deleteShortcut = "Backspace";
+ const interactionShortcut = "Ctrl + K";
+ const undoShortcut = "Ctrl + Z";
+ const redoShortcut = "Ctrl + Y";
+
+ // Column Definitions: Defines the columns to be displayed.
+ const [colDefs, setColDefs] = useState<(ColDef | ColGroupDef)[]>([
+ { headerName: "Functionality", field: "name", flex: 1, editable: false }, //This column will be twice as wide as the others
+ {
+ field: "shortcut",
+ flex: 2,
+ editable: false,
+ },
+ ]);
+
+ const [nodesRowData, setNodesRowData] = useState([
+ {
+ name: "Advanced Settings Component",
+ shortcut: advancedShortcut,
+ },
+ {
+ name: "Minimize Component",
+ shortcut: minizmizeShortcut,
+ },
+ {
+ name: "Code Component",
+ shortcut: codeShortcut,
+ },
+ {
+ name: "Copy Component",
+ shortcut: copyShortcut,
+ },
+ {
+ name: "Duplicate Component",
+ shortcut: duplicateShortcut,
+ },
+ {
+ name: "Share Component",
+ shortcut: shareShortcut,
+ },
+ {
+ name: "Docs Component",
+ shortcut: docsShortcut,
+ },
+ {
+ name: "Save Component",
+ shortcut: saveShortcut,
+ },
+ {
+ name: "Delete Component",
+ shortcut: deleteShortcut,
+ },
+ {
+ name: "Open Playground",
+ shortcut: interactionShortcut,
+ },
+ {
+ name: "Undo",
+ shortcut: undoShortcut,
+ },
+ {
+ name: "Redo",
+ shortcut: redoShortcut,
+ },
+ ]);
+
+ return (
+
+
+
+
+ Shortcuts
+
+
+
+ Manage Shortcuts for quick access to
+ frequently used actions.
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/src/pages/signUpPage/index.tsx b/src/frontend/src/pages/SignUpPage/index.tsx
similarity index 100%
rename from src/frontend/src/pages/signUpPage/index.tsx
rename to src/frontend/src/pages/SignUpPage/index.tsx
diff --git a/src/frontend/src/pages/StorePage/index.tsx b/src/frontend/src/pages/StorePage/index.tsx
index 64df241fb..37f9efb15 100644
--- a/src/frontend/src/pages/StorePage/index.tsx
+++ b/src/frontend/src/pages/StorePage/index.tsx
@@ -1,15 +1,15 @@
import { uniqueId } from "lodash";
import { useContext, useEffect, useState } from "react";
-import PaginatorComponent from "../../components/PaginatorComponent";
-import ShadTooltip from "../../components/ShadTooltipComponent";
import CollectionCardComponent from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
+import ShadTooltip from "../../components/shadTooltipComponent";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
-import { Link, useParams } from "react-router-dom";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import PaginatorComponent from "../../components/paginatorComponent";
import { TagsSelector } from "../../components/tagsSelectorComponent";
import { Badge } from "../../components/ui/badge";
import {
@@ -29,7 +29,7 @@ import {
import { STORE_DESC, STORE_TITLE } from "../../constants/constants";
import { AuthContext } from "../../contexts/authContext";
import { getStoreComponents, getStoreTags } from "../../controllers/API";
-import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
+import StoreApiKeyModal from "../../modals/storeApiKeyModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
@@ -65,6 +65,8 @@ export default function StorePage(): JSX.Element {
const [searchNow, setSearchNow] = useState("");
const [selectFilter, setSelectFilter] = useState("all");
+ const navigate = useNavigate();
+
useEffect(() => {
if (!loadingApiKey) {
if (!hasApiKey) {
@@ -371,6 +373,10 @@ export default function StorePage(): JSX.Element {
data={item}
authorized={validApiKey}
disabled={loading}
+ playground={
+ item.last_tested_version?.includes("1.0.0") &&
+ !item.is_component
+ }
/>
>
);
diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx
index fde8c28f9..434b657be 100644
--- a/src/frontend/src/routes.tsx
+++ b/src/frontend/src/routes.tsx
@@ -7,15 +7,19 @@ import { StoreGuard } from "./components/storeGuard";
import AdminPage from "./pages/AdminPage";
import LoginAdminPage from "./pages/AdminPage/LoginPage";
import ApiKeysPage from "./pages/ApiKeysPage";
+import DeleteAccountPage from "./pages/DeleteAccountPage";
import FlowPage from "./pages/FlowPage";
+import LoginPage from "./pages/LoginPage";
import HomePage from "./pages/MainPage";
import ComponentsComponent from "./pages/MainPage/components/components";
-import ProfileSettingsPage from "./pages/ProfileSettingsPage";
+import PlaygroundPage from "./pages/Playground";
+import SettingsPage from "./pages/SettingsPage";
+import GeneralPage from "./pages/SettingsPage/pages/GeneralPage";
+import GlobalVariablesPage from "./pages/SettingsPage/pages/GlobalVariablesPage";
+import ShortcutsPage from "./pages/SettingsPage/pages/ShortcutsPage";
+import SignUp from "./pages/SignUpPage";
import StorePage from "./pages/StorePage";
import ViewPage from "./pages/ViewPage";
-import DeleteAccountPage from "./pages/deleteAccountPage";
-import LoginPage from "./pages/loginPage";
-import SignUp from "./pages/signUpPage";
const Router = () => {
return (
@@ -38,6 +42,19 @@ const Router = () => {
element={}
/>
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+ } />
+
{
}
/>
-
+
+ element=
+ {
+
+
+
+ }
+ />
+ }
+
{
/>
-
-
-
- }
- />
((set, get) => ({
+ onFlowPage: false,
+ setOnFlowPage:(FlowPage=>set({onFlowPage:FlowPage})),
flowState: undefined,
flowBuildStatus: {},
nodes: [],
@@ -149,7 +152,7 @@ const useFlowStore = create((set, get) => ({
edges: applyEdgeChanges(changes, get().edges),
});
},
- setNodes: (change) => {
+ setNodes: (change,skipSave=false) => {
let newChange = typeof change === "function" ? change(get().nodes) : change;
let newEdges = cleanEdges(newChange, get().edges);
const { inputs, outputs } = getInputsAndOutputs(newChange);
@@ -164,7 +167,7 @@ const useFlowStore = create((set, get) => ({
});
const flowsManager = useFlowsManagerStore.getState();
- if (!get().isBuilding) {
+ if (!get().isBuilding && !skipSave && get().onFlowPage) {
flowsManager.autoSaveCurrentFlow(
newChange,
newEdges,
@@ -172,7 +175,7 @@ const useFlowStore = create((set, get) => ({
);
}
},
- setEdges: (change) => {
+ setEdges: (change,skipSave=false) => {
let newChange = typeof change === "function" ? change(get().edges) : change;
set({
edges: newChange,
@@ -180,7 +183,7 @@ const useFlowStore = create((set, get) => ({
});
const flowsManager = useFlowsManagerStore.getState();
- if (!get().isBuilding) {
+ if (!get().isBuilding && !skipSave && get().onFlowPage) {
flowsManager.autoSaveCurrentFlow(
get().nodes,
newChange,
@@ -565,6 +568,8 @@ const useFlowStore = create((set, get) => ({
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
},
onValidateNodes: validateSubgraph,
+ nodes: !get().onFlowPage ? get().nodes : undefined,
+ edges: !get().onFlowPage ? get().edges : undefined,
});
get().setIsBuilding(false);
get().revertBuiltStatusFromBuilding();
diff --git a/src/frontend/src/stores/flowsManagerStore.ts b/src/frontend/src/stores/flowsManagerStore.ts
index 9471889c3..b9bc69e52 100644
--- a/src/frontend/src/stores/flowsManagerStore.ts
+++ b/src/frontend/src/stores/flowsManagerStore.ts
@@ -22,6 +22,7 @@ import {
addVersionToDuplicates,
createFlowComponent,
createNewFlow,
+ extractFieldsFromComponenents,
processDataFromFlow,
processFlows,
} from "../utils/reactflowUtils";
@@ -46,6 +47,16 @@ const useFlowsManagerStore = create((set, get) => ({
set({ examples });
},
currentFlowId: "",
+ setCurrentFlow: (flow: FlowType) => {
+ set((state) => ({
+ currentFlow: flow,
+ currentFlowId: flow.id,
+ }));
+
+ },
+ getFlowById: (id: string) => {
+ return get().flows.find((flow) => flow.id === id);
+ },
setCurrentFlowId: (currentFlowId: string) => {
set((state) => ({
currentFlowId,
@@ -82,6 +93,10 @@ const useFlowsManagerStore = create((set, get) => ({
);
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
+ ComponentFields: extractFieldsFromComponenents({
+ ...state.data,
+ ["saved_components"]: data,
+ }),
}));
set({ isLoading: false });
resolve();
@@ -197,6 +212,10 @@ const useFlowsManagerStore = create((set, get) => ({
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
+ ComponentFields: extractFieldsFromComponenents({
+ ...state.data,
+ ["saved_components"]: data,
+ }),
}));
}, 200);
// addFlowToLocalState(newFlow);
@@ -219,6 +238,10 @@ const useFlowsManagerStore = create((set, get) => ({
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
+ ComponentFields: extractFieldsFromComponenents({
+ ...state.data,
+ ["saved_components"]: data,
+ }),
}));
// Return the id
@@ -248,6 +271,10 @@ const useFlowsManagerStore = create((set, get) => ({
set({ isLoading: false });
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
+ ComponentFields: extractFieldsFromComponenents({
+ ...state.data,
+ ["saved_components"]: data,
+ }),
}));
resolve();
});
diff --git a/src/frontend/src/stores/globalVariables.ts b/src/frontend/src/stores/globalVariables.ts
index e2f3e37df..3adf8cbf8 100644
--- a/src/frontend/src/stores/globalVariables.ts
+++ b/src/frontend/src/stores/globalVariables.ts
@@ -1,30 +1,45 @@
import { create } from "zustand";
import { GlobalVariablesStore } from "../types/zustand/globalVariables";
+import { getUnavailableFields } from "../utils/utils";
export const useGlobalVariablesStore = create(
(set, get) => ({
+ unavaliableFields: {},
+ setUnavaliableFields: (fields) => {
+ set({ unavaliableFields: fields });
+ },
+ removeUnavaliableField: (field) => {
+ const newFields = get().unavaliableFields;
+ delete newFields[field];
+ set({ unavaliableFields: newFields });
+ },
globalVariablesEntries: [],
globalVariables: {},
setGlobalVariables: (variables) => {
set({
globalVariables: variables,
globalVariablesEntries: Object.keys(variables),
+ unavaliableFields: getUnavailableFields(variables),
});
},
- addGlobalVariable: (name, id, type) => {
- const data = { id, type };
+ addGlobalVariable: (name, id, type, default_fields) => {
+ const data = { id, type, default_fields };
const newVariables = { ...get().globalVariables, [name]: data };
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables),
+ unavaliableFields: getUnavailableFields(newVariables),
});
},
- removeGlobalVariable: (name) => {
+ removeGlobalVariable: async (name) => {
+ const id = get().globalVariables[name]?.id;
+ if (id === undefined) return;
const newVariables = { ...get().globalVariables };
delete newVariables[name];
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables),
+ unavaliableFields: getUnavailableFields(newVariables),
});
},
getVariableId: (name) => {
diff --git a/src/frontend/src/stores/typesStore.ts b/src/frontend/src/stores/typesStore.ts
index 142118f44..c92236e03 100644
--- a/src/frontend/src/stores/typesStore.ts
+++ b/src/frontend/src/stores/typesStore.ts
@@ -2,11 +2,22 @@ import { create } from "zustand";
import { getAll } from "../controllers/API";
import { APIDataType } from "../types/api";
import { TypesStoreType } from "../types/zustand/types";
-import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils";
+import {
+ extractFieldsFromComponenents,
+ templatesGenerator,
+ typesGenerator,
+} from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import useFlowsManagerStore from "./flowsManagerStore";
export const useTypesStore = create((set, get) => ({
+ ComponentFields: new Set(),
+ setComponentFields: (fields) => {
+ set({ ComponentFields: fields });
+ },
+ addComponentField: (field) => {
+ set({ ComponentFields: get().ComponentFields.add(field) });
+ },
types: {},
templates: {},
data: {},
@@ -21,6 +32,10 @@ export const useTypesStore = create((set, get) => ({
set((old) => ({
types: typesGenerator(data),
data: { ...old.data, ...data },
+ ComponentFields: extractFieldsFromComponenents({
+ ...old.data,
+ ...data,
+ }),
templates: templatesGenerator(data),
}));
setLoading(false);
@@ -43,5 +58,6 @@ export const useTypesStore = create((set, get) => ({
setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => {
let newChange = typeof change === "function" ? change(get().data) : change;
set({ data: newChange });
+ get().setComponentFields(extractFieldsFromComponenents(newChange));
},
}));
diff --git a/src/frontend/src/style/ag-theme-shadcn.css b/src/frontend/src/style/ag-theme-shadcn.css
new file mode 100644
index 000000000..3605d53b3
--- /dev/null
+++ b/src/frontend/src/style/ag-theme-shadcn.css
@@ -0,0 +1,21 @@
+/* set the background color of many elements across the grid */
+.ag-theme-shadcn {
+ --ag-foreground-color: hsl(var(--foreground));
+ --ag-background-color: hsl(var(--background));
+ --ag-secondary-foreground-color: hsl(var(--secondary-foreground));
+ --ag-data-color: hsl(var(--foreground));
+ --ag-header-foreground-color: hsl(var(--muted-foreground));
+ --ag-header-background-color: hsl(var(--background));
+ --ag-tooltip-background-color: hsl(var(--muted));
+ --ag-disabled-foreground-color: hsl(var(--muted-foreground));
+ --ag-border-color: hsl(var(--border));
+ --ag-selected-row-background-color: hsl(var(--accent));
+ --ag-menu-background-color: hsl(var(--accent));
+ --ag-panel-background-color: hsl(var(--accent));
+ --ag-row-hover-color: hsl(var(--accent));
+ --ag-header-height: 2.5rem;
+}
+
+.ag-theme-shadcn .ag-paging-panel {
+ height: 3rem;
+}
diff --git a/src/frontend/src/style/classes.css b/src/frontend/src/style/classes.css
index 1e274135d..39b080390 100644
--- a/src/frontend/src/style/classes.css
+++ b/src/frontend/src/style/classes.css
@@ -68,3 +68,31 @@ select:-webkit-autofill:focus {
background-color: #bbb;
border-radius: 999px;
}
+
+.json-view-playground-white-left {
+ background-color: #fff !important;
+ height: fit-content !important;
+}
+
+.json-view-playground-dark {
+ background-color: #141924 !important;
+ height: fit-content !important;
+}
+
+.json-view-playground-white {
+ background-color: #f8fafc !important;
+ height: fit-content !important;
+}
+
+.json-view-playground-dark-left {
+ background-color: #0c101a !important;
+ height: fit-content !important;
+}
+
+.json-view-white {
+ background-color: #f8fafc !important;
+}
+
+.json-view-dark {
+ background-color: #141924 !important;
+}
diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts
index 4fb2ca1b7..2a3ee29b7 100644
--- a/src/frontend/src/types/components/index.ts
+++ b/src/frontend/src/types/components/index.ts
@@ -28,6 +28,8 @@ export type InputComponentType = {
optionButton?: (option: string) => ReactElement;
selectedOption?: string;
setSelectedOption?: (value: string) => void;
+ selectedOptions?: string[];
+ setSelectedOptions?: (value: string[]) => void;
};
export type ToggleComponentType = {
enabled: boolean;
@@ -45,6 +47,7 @@ export type DropDownComponentType = {
onSelect: (value: string) => void;
editNode?: boolean;
id?: string;
+ children?: ReactNode;
};
export type ParameterComponentType = {
data: NodeDataType;
@@ -69,24 +72,7 @@ export type InputListComponentType = {
disabled: boolean;
editNode?: boolean;
componentName?: string;
-};
-
-export type InputGlobalComponentType = {
- disabled: boolean;
- onChange: (value: string) => void;
- setDb: (value: boolean) => void;
- name: string;
- data: NodeDataType;
- editNode?: boolean;
-};
-
-export type InputGlobalComponentType = {
- disabled: boolean;
- onChange: (value: string) => void;
- setDb: (value: boolean) => void;
- name: string;
- data: NodeDataType;
- editNode?: boolean;
+ playgroundDisabled?: boolean;
};
export type KeyPairListComponentType = {
@@ -102,9 +88,11 @@ export type KeyPairListComponentType = {
export type DictComponentType = {
value: any;
onChange: (value) => void;
- disabled: boolean;
+ disabled?: boolean;
editNode?: boolean;
id?: string;
+ left?: boolean;
+ output?: boolean;
};
export type TextAreaComponentType = {
@@ -601,6 +589,8 @@ export type IOModalPropsType = {
open: boolean;
setOpen: (open: boolean) => void;
disable?: boolean;
+ isPlayground?: boolean;
+ cleanOnClose?: boolean;
};
export type buttonBoxPropsType = {
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index d88504909..c975eec8d 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -45,6 +45,8 @@ export type FlowPoolType = {
};
export type FlowStoreType = {
+ onFlowPage: boolean;
+ setOnFlowPage: (onFlowPage: boolean) => void;
flowPool: FlowPoolType;
inputs: Array<{ type: string; id: string; displayName: string }>;
outputs: Array<{ type: string; id: string; displayName: string }>;
@@ -74,8 +76,8 @@ export type FlowStoreType = {
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
- setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void;
- setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void;
+ setNodes: (update: Node[] | ((oldState: Node[]) => Node[]),skipSave?:boolean) => void;
+ setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[]),skipSave?:boolean) => void;
setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void;
getNode: (id: string) => Node | undefined;
deleteNode: (nodeId: string | Array) => void;
diff --git a/src/frontend/src/types/zustand/flowsManager/index.ts b/src/frontend/src/types/zustand/flowsManager/index.ts
index 817ddd3d1..16985105a 100644
--- a/src/frontend/src/types/zustand/flowsManager/index.ts
+++ b/src/frontend/src/types/zustand/flowsManager/index.ts
@@ -2,6 +2,7 @@ import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { FlowType } from "../../flow";
export type FlowsManagerStoreType = {
+ getFlowById: (id: string) => FlowType | undefined;
flows: Array;
setFlows: (flows: FlowType[]) => void;
currentFlow: FlowType | undefined;
@@ -50,6 +51,7 @@ export type FlowsManagerStoreType = {
takeSnapshot: () => void;
examples: Array;
setExamples: (examples: FlowType[]) => void;
+ setCurrentFlow: (flow: FlowType) => void;
};
export type UseUndoRedoOptions = {
diff --git a/src/frontend/src/types/zustand/globalVariables/index.ts b/src/frontend/src/types/zustand/globalVariables/index.ts
index 3e651179e..752336c19 100644
--- a/src/frontend/src/types/zustand/globalVariables/index.ts
+++ b/src/frontend/src/types/zustand/globalVariables/index.ts
@@ -1,10 +1,31 @@
export type GlobalVariablesStore = {
globalVariablesEntries: Array;
- globalVariables: { [name: string]: { id: string; type?: string } };
+ globalVariables: {
+ [name: string]: {
+ id: string;
+ type?: string;
+ default_fields?: string[];
+ value?: string;
+ };
+ };
setGlobalVariables: (variables: {
- [name: string]: { id: string; type?: string };
+ [name: string]: {
+ id: string;
+ type?: string;
+ default_fields?: string[];
+ value?: string;
+ };
}) => void;
- addGlobalVariable: (name: string, id: string, type?: string) => void;
- removeGlobalVariable: (name: string) => void;
+ addGlobalVariable: (
+ name: string,
+ id: string,
+ type?: string,
+ default_fields?: string[],
+ value?: string
+ ) => void;
+ removeGlobalVariable: (name: string) => Promise;
getVariableId: (name: string) => string | undefined;
+ unavaliableFields: {[name: string]: string};
+ setUnavaliableFields: (fields: {[name: string]: string}) => void;
+ removeUnavaliableField: (field: string) => void;
};
diff --git a/src/frontend/src/types/zustand/types/index.ts b/src/frontend/src/types/zustand/types/index.ts
index 133afbda1..4f817de47 100644
--- a/src/frontend/src/types/zustand/types/index.ts
+++ b/src/frontend/src/types/zustand/types/index.ts
@@ -8,4 +8,7 @@ export type TypesStoreType = {
data: APIDataType;
setData: (newState: {}) => void;
getTypes: () => Promise;
+ ComponentFields: Set;
+ setComponentFields: (fields: Set) => void;
+ addComponentField: (field: string) => void;
};
diff --git a/src/frontend/src/utils/buildUtils.ts b/src/frontend/src/utils/buildUtils.ts
index 6b462fe04..2f5d557c5 100644
--- a/src/frontend/src/utils/buildUtils.ts
+++ b/src/frontend/src/utils/buildUtils.ts
@@ -1,4 +1,5 @@
import { AxiosError } from "axios";
+import { Edge, Node } from "reactflow";
import { BuildStatus } from "../constants/enums";
import { getVerticesOrder, postBuildVertex } from "../controllers/API";
import useAlertStore from "../stores/alertStore";
@@ -21,6 +22,8 @@ type BuildVerticesParams = {
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
onBuildStart?: (idList: VertexLayerElementType[]) => void;
onValidateNodes?: (nodes: string[]) => void;
+ nodes?: Node[];
+ edges?: Edge[];
};
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
@@ -48,7 +51,9 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
export async function updateVerticesOrder(
flowId: string,
startNodeId?: string | null,
- stopNodeId?: string | null
+ stopNodeId?: string | null,
+ nodes?: Node[],
+ edges?: Edge[]
): Promise<{
verticesLayers: VertexLayerElementType[][];
verticesIds: string[];
@@ -59,12 +64,19 @@ export async function updateVerticesOrder(
const setErrorData = useAlertStore.getState().setErrorData;
let orderResponse;
try {
- orderResponse = await getVerticesOrder(flowId, startNodeId, stopNodeId);
+ orderResponse = await getVerticesOrder(
+ flowId,
+ startNodeId,
+ stopNodeId,
+ nodes,
+ edges
+ );
} catch (error: any) {
setErrorData({
title: "Oops! Looks like you missed something",
list: [error.response?.data?.detail ?? "Unknown Error"],
});
+ debugger;
useFlowStore.getState().setIsBuilding(false);
throw new Error("Invalid nodes");
}
@@ -101,6 +113,8 @@ export async function buildVertices({
onBuildError,
onBuildStart,
onValidateNodes,
+ nodes,
+ edges,
}: BuildVerticesParams) {
let verticesBuild = useFlowStore.getState().verticesBuild;
// if startNodeId and stopNodeId are provided
@@ -113,7 +127,9 @@ export async function buildVertices({
let verticesOrderResponse = await updateVerticesOrder(
flowId,
startNodeId,
- stopNodeId
+ stopNodeId,
+ nodes,
+ edges
);
if (onValidateNodes) {
try {
diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts
index 4cd19bf7e..deb470f80 100644
--- a/src/frontend/src/utils/reactflowUtils.ts
+++ b/src/frontend/src/utils/reactflowUtils.ts
@@ -1234,6 +1234,22 @@ export function templatesGenerator(data: APIObjectType) {
}, {});
}
+export function extractFieldsFromComponenents(data: APIObjectType) {
+ const fields = new Set();
+ Object.keys(data).forEach((key) => {
+ Object.keys(data[key]).forEach((kind) => {
+ Object.keys(data[key][kind].template).forEach((field) => {
+ if (
+ data[key][kind].template[field].display_name &&
+ data[key][kind].template[field].show
+ )
+ fields.add(data[key][kind].template[field].display_name!);
+ });
+ });
+ });
+ return fields;
+}
+
export function downloadFlow(
flow: FlowType,
flowName: string,
diff --git a/src/frontend/src/utils/storeUtils.ts b/src/frontend/src/utils/storeUtils.ts
index e51cf8933..ad4d04529 100644
--- a/src/frontend/src/utils/storeUtils.ts
+++ b/src/frontend/src/utils/storeUtils.ts
@@ -1,4 +1,4 @@
-import { cloneDeep } from "lodash";
+import { cloneDeep, uniqueId } from "lodash";
import { Node } from "reactflow";
import { FlowType, NodeDataType } from "../types/flow";
import { isInputNode, isOutputNode } from "./reactflowUtils";
@@ -6,11 +6,17 @@ import { isInputNode, isOutputNode } from "./reactflowUtils";
export default function cloneFLowWithParent(
flow: FlowType,
parent: string,
- is_component: boolean
+ is_component: boolean,
+ keepId=false
) {
let childFLow = cloneDeep(flow);
childFLow.parent = parent;
- childFLow.id = "";
+ if(!keepId){
+ childFLow.id = "";
+ }
+ else{
+ childFLow.id = uniqueId()+"-"+childFLow.id;
+ }
childFLow.is_component = is_component;
return childFLow;
}
diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts
index a0c9c9756..70bea00d4 100644
--- a/src/frontend/src/utils/styleUtils.ts
+++ b/src/frontend/src/utils/styleUtils.ts
@@ -5,6 +5,7 @@ import {
ArrowUpToLine,
Bell,
Binary,
+ Blocks,
BookMarked,
BookmarkPlus,
Bot,
@@ -67,6 +68,7 @@ import {
Home,
Info,
Key,
+ Keyboard,
Laptop2,
Layers,
Link,
@@ -86,6 +88,7 @@ import {
MoreHorizontal,
Network,
Package2,
+ Palette,
Paperclip,
Pencil,
PencilLine,
@@ -108,6 +111,7 @@ import {
Share2,
Shield,
Sliders,
+ SlidersHorizontal,
Snowflake,
Sparkles,
Square,
@@ -136,6 +140,7 @@ import {
X,
XCircle,
Zap,
+ PlaySquare
} from "lucide-react";
import { FaApple, FaGithub } from "react-icons/fa";
import { AWSIcon } from "../icons/AWS";
@@ -144,7 +149,7 @@ import { AnthropicIcon } from "../icons/Anthropic";
import { AstraDBIcon } from "../icons/AstraDB";
import { AzureIcon } from "../icons/Azure";
import { BingIcon } from "../icons/Bing";
-import { BotMessageSquareIcon } from "../icons/BotMessageSquare";
+import { BotMessageSquareIcon} from "../icons/BotMessageSquare";
import { ChromaIcon } from "../icons/ChromaIcon";
import { CohereIcon } from "../icons/Cohere";
import { ElasticsearchIcon } from "../icons/ElasticsearchStore";
@@ -292,7 +297,8 @@ export const nodeIconsLucide: iconsType = {
ListFlows: Group,
ClearMessageHistory: FileClock,
Python: PythonIcon,
- ChatOutput: BotMessageSquareIcon,
+ ChatOutput: MessagesSquare,
+ BotMessageSquareIcon,
ChatInput: MessagesSquare,
inputs: Download,
outputs: Upload,
@@ -374,6 +380,7 @@ export const nodeIconsLucide: iconsType = {
tools: Hammer,
advanced: Laptop2,
chat: MessageCircle,
+ Keyboard: Keyboard,
embeddings: Binary,
saved_components: GradientSave,
documentloaders: Paperclip,
@@ -412,6 +419,9 @@ export const nodeIconsLucide: iconsType = {
MoonIcon,
Bell,
ChevronLeft,
+ SlidersHorizontal,
+ Palette,
+ Blocks,
ChevronDown,
ArrowLeft,
Shield,
diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts
index afcc264f6..43838bb27 100644
--- a/src/frontend/src/utils/utils.ts
+++ b/src/frontend/src/utils/utils.ts
@@ -61,7 +61,7 @@ export function normalCaseToSnakeCase(str: string): string {
export function toTitleCase(
str: string | undefined,
- isNodeField?: boolean
+ isNodeField?: boolean,
): string {
if (!str) return "";
let result = str
@@ -70,7 +70,7 @@ export function toTitleCase(
if (isNodeField) return word;
if (index === 0) {
return checkUpperWords(
- word[0].toUpperCase() + word.slice(1).toLowerCase()
+ word[0].toUpperCase() + word.slice(1).toLowerCase(),
);
}
return checkUpperWords(word.toLowerCase());
@@ -83,7 +83,7 @@ export function toTitleCase(
if (isNodeField) return word;
if (index === 0) {
return checkUpperWords(
- word[0].toUpperCase() + word.slice(1).toLowerCase()
+ word[0].toUpperCase() + word.slice(1).toLowerCase(),
);
}
return checkUpperWords(word.toLowerCase());
@@ -91,6 +91,20 @@ export function toTitleCase(
.join(" ");
}
+export function getUnavailableFields(variables: {
+ [key: string]: { default_fields?: string[] };
+}): { [name: string]: string } {
+ const unVariables: { [name: string]: string } = {};
+ Object.keys(variables).forEach((key) => {
+ if (variables[key].default_fields) {
+ variables[key].default_fields!.forEach((field) => {
+ unVariables[field] = key;
+ });
+ }
+ });
+ return unVariables;
+}
+
export const upperCaseWords: string[] = ["llm", "uri"];
export function checkUpperWords(str: string): string {
const words = str.split(" ").map((word) => {
@@ -109,7 +123,7 @@ export function groupByFamily(
data: APIDataType,
baseClasses: string,
left: boolean,
- flow?: NodeType[]
+ flow?: NodeType[],
): groupedObjType[] {
const baseClassesSet = new Set(baseClasses.split("\n"));
let arrOfPossibleInputs: Array<{
@@ -135,7 +149,7 @@ export function groupByFamily(
baseClassesSet.has(template.type)) ||
(template.input_types &&
template.input_types.some((inputType) =>
- baseClassesSet.has(inputType)
+ baseClassesSet.has(inputType),
)))
);
};
@@ -155,7 +169,7 @@ export function groupByFamily(
hasBaseClassInBaseClasses:
foundNode?.hasBaseClassInBaseClasses ||
nodeData.node!.base_classes.some((baseClass) =>
- baseClassesSet.has(baseClass)
+ baseClassesSet.has(baseClass),
), //seta como anterior ou verifica se o node tem base class
displayName: nodeData.node?.display_name,
});
@@ -172,10 +186,10 @@ export function groupByFamily(
if (!foundNode) {
foundNode = {
hasBaseClassInTemplate: Object.values(node!.template).some(
- checkBaseClass
+ checkBaseClass,
),
hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) =>
- baseClassesSet.has(baseClass)
+ baseClassesSet.has(baseClass),
),
displayName: node?.display_name,
};
@@ -232,7 +246,7 @@ export function getRandomDescription(): string {
export function getRandomName(
retry: number = 0,
noSpace: boolean = false,
- maxRetries: number = 3
+ maxRetries: number = 3,
): string {
const left: string[] = ADJECTIVES;
const right: string[] = NOUNS;
@@ -311,7 +325,7 @@ export function getChatInputField(flowState?: FlowState) {
export function getPythonApiCode(
flow: FlowType,
isAuth: boolean,
- tweak?: any[]
+ tweak?: any[],
): string {
const flowId = flow.id;
@@ -379,7 +393,7 @@ print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${
export function getCurlCode(
flow: FlowType,
isAuth: boolean,
- tweak?: any[]
+ tweak?: any[],
): string {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
@@ -449,7 +463,7 @@ result = run_flow_from_json(flow="${flowName}.json",
export function getWidgetCode(
flow: FlowType,
isAuth: boolean,
- flowState?: FlowState
+ flowState?: FlowState,
): string {
const flowId = flow.id;
const flowName = flow.name;
@@ -579,7 +593,7 @@ export function checkLocalStorageKey(key: string): boolean {
export function IncrementObjectKey(
object: object,
- key: string
+ key: string,
): { newKey: string; increment: number } {
let count = 1;
const type = removeCountFromString(key);
@@ -651,7 +665,7 @@ export function getSetFromObject(obj: object, key?: string): Set {
export function getFieldTitle(
template: APITemplateType,
- templateField: string
+ templateField: string,
): string {
return template[templateField].display_name
? template[templateField].display_name!
@@ -701,3 +715,7 @@ export function freezeObject(obj: any) {
if (!obj) return obj;
return JSON.parse(JSON.stringify(obj));
}
+
+export function convertTestName(name: string): string {
+ return name.replace(/ /g, "-").toLowerCase();
+}
diff --git a/src/frontend/tests/end-to-end/assets/ChatTest.json b/src/frontend/tests/end-to-end/assets/ChatTest.json
index f29d398d1..cadb3fe12 100644
--- a/src/frontend/tests/end-to-end/assets/ChatTest.json
+++ b/src/frontend/tests/end-to-end/assets/ChatTest.json
@@ -5,7 +5,10 @@
{
"id": "ChatOutput-xPeM1",
"type": "genericNode",
- "position": { "x": 231.45405028405742, "y": -109.00715949940081 },
+ "position": {
+ "x": 231.45405028405742,
+ "y": -109.00715949940081
+ },
"data": {
"type": "ChatOutput",
"node": {
@@ -17,7 +20,7 @@
"list": false,
"show": true,
"multiline": true,
- "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build_with_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template or \"\",\n )\n",
+ "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build_with_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template or \"\",\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
@@ -41,7 +44,9 @@
"name": "input_value",
"display_name": "Message",
"advanced": false,
- "input_types": ["Text"],
+ "input_types": [
+ "Text"
+ ],
"dynamic": false,
"info": "",
"load_from_db": false,
@@ -65,7 +70,9 @@
"info": "In case of Message being a Record, this template will be used to convert it to text.",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"return_record": {
"type": "bool",
@@ -97,7 +104,10 @@
"fileTypes": [],
"file_path": "",
"password": false,
- "options": ["Machine", "User"],
+ "options": [
+ "Machine",
+ "User"
+ ],
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
@@ -105,7 +115,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"sender_name": {
"type": "str",
@@ -125,7 +137,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"session_id": {
"type": "str",
@@ -144,13 +158,20 @@
"info": "If provided, the message will be stored in the memory.",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"_type": "CustomComponent"
},
- "description": "Display a chat message in the Interaction Panel.",
+ "description": "Display a chat message in the Playground.",
"icon": "ChatOutput",
- "base_classes": ["object", "Record", "str", "Text"],
+ "base_classes": [
+ "object",
+ "Record",
+ "str",
+ "Text"
+ ],
"display_name": "Chat Output",
"documentation": "",
"custom_fields": {
@@ -161,7 +182,10 @@
"return_record": null,
"record_template": null
},
- "output_types": ["Text", "Record"],
+ "output_types": [
+ "Text",
+ "Record"
+ ],
"field_formatters": {},
"frozen": false,
"field_order": [],
@@ -181,7 +205,10 @@
{
"id": "ChatInput-XYvUc",
"type": "genericNode",
- "position": { "x": -389.67919096408036, "y": 10.79598792234681 },
+ "position": {
+ "x": -389.67919096408036,
+ "y": 10.79598792234681
+ },
"data": {
"type": "ChatInput",
"node": {
@@ -193,7 +220,7 @@
"list": false,
"show": true,
"multiline": true,
- "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Interaction Panel.\"\n icon = \"ChatInput\"\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n",
+ "value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"ChatInput\"\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build_no_record(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
@@ -253,7 +280,10 @@
"fileTypes": [],
"file_path": "",
"password": false,
- "options": ["Machine", "User"],
+ "options": [
+ "Machine",
+ "User"
+ ],
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
@@ -261,7 +291,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"sender_name": {
"type": "str",
@@ -281,7 +313,9 @@
"info": "",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"session_id": {
"type": "str",
@@ -300,13 +334,20 @@
"info": "If provided, the message will be stored in the memory.",
"load_from_db": false,
"title_case": false,
- "input_types": ["Text"]
+ "input_types": [
+ "Text"
+ ]
},
"_type": "CustomComponent"
},
- "description": "Get chat inputs from the Interaction Panel.",
+ "description": "Get chat inputs from the Playground.",
"icon": "ChatInput",
- "base_classes": ["object", "Record", "str", "Text"],
+ "base_classes": [
+ "object",
+ "Record",
+ "str",
+ "Text"
+ ],
"display_name": "Chat Input",
"documentation": "",
"custom_fields": {
@@ -316,7 +357,10 @@
"session_id": null,
"return_record": null
},
- "output_types": ["Text", "Record"],
+ "output_types": [
+ "Text",
+ "Record"
+ ],
"field_formatters": {},
"frozen": false,
"field_order": [],
@@ -339,16 +383,25 @@
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-xPeM1",
- "inputTypes": ["Text"],
+ "inputTypes": [
+ "Text"
+ ],
"type": "str"
},
"sourceHandle": {
- "baseClasses": ["object", "Record", "str", "Text"],
+ "baseClasses": [
+ "object",
+ "Record",
+ "str",
+ "Text"
+ ],
"dataType": "ChatInput",
"id": "ChatInput-XYvUc"
}
},
- "style": { "stroke": "#555" },
+ "style": {
+ "stroke": "#555"
+ },
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-XYvUc{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-XYvUcœ}-ChatOutput-xPeM1{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-xPeM1œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
}
@@ -363,4 +416,4 @@
"name": "ChatTest",
"last_tested_version": "1.0.0a14",
"is_component": false
-}
+}
\ No newline at end of file
diff --git a/src/frontend/tests/end-to-end/chatInputOutput.spec.ts b/src/frontend/tests/end-to-end/chatInputOutput.spec.ts
index bc507efb7..50548ab21 100644
--- a/src/frontend/tests/end-to-end/chatInputOutput.spec.ts
+++ b/src/frontend/tests/end-to-end/chatInputOutput.spec.ts
@@ -45,7 +45,7 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
await page
.getByTestId("input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
- await page.getByText("Run", { exact: true }).click();
+ await page.getByText("Playground", { exact: true }).click();
await page.getByPlaceholder("Send a message...").fill("Hello, how are you?");
await page.getByTestId("icon-LucideSend").click();
let valueUser = await page.getByTestId("sender_name_user").textContent();
@@ -65,7 +65,7 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
await page.getByTestId("input-sender_name").nth(1).fill("TestSenderNameUser");
await page.getByTestId("input-sender_name").nth(0).fill("TestSenderNameAI");
- await page.getByText("Run", { exact: true }).click();
+ await page.getByText("Playground", { exact: true }).click();
await page.getByTestId("icon-LucideSend").click();
valueUser = await page
@@ -138,7 +138,7 @@ test("chat_io_teste", async ({ page }) => {
}
);
await page.getByLabel("fit view").click();
- await page.getByText("Run", { exact: true }).click();
+ await page.getByText("Playground", { exact: true }).click();
await page.getByPlaceholder("Send a message...").click();
await page.getByPlaceholder("Send a message...").fill("teste");
await page.getByRole("button").nth(1).click();
diff --git a/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts b/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts
new file mode 100644
index 000000000..ecb590aa9
--- /dev/null
+++ b/src/frontend/tests/end-to-end/deleteComponentFlows.spec.ts
@@ -0,0 +1,46 @@
+import { test } from "@playwright/test";
+
+test("shoud delete a flow", async ({ page }) => {
+ await page.goto("/");
+ await page.waitForTimeout(2000);
+ await page.getByText("Store").nth(0).click();
+ await page.getByTestId("install-Website Content QA").click();
+ await page.waitForTimeout(5000);
+ await page.getByText("My Collection").nth(0).click();
+ await page.getByText("Website Content QA").first().isVisible();
+ await page
+ .getByTestId("card-website-content-qa")
+ .first()
+ .hover()
+ .then(async () => {
+ await page.getByTestId("icon-Trash2").first().click();
+ await page.waitForTimeout(2000);
+ });
+ await page.getByText("Confirm deletion of component?").isVisible();
+ await page.getByText("Delete").nth(1).click();
+ await page.waitForTimeout(1000);
+ await page.getByText("Successfully").first().isVisible();
+});
+
+test("shoud delete a component", async ({ page }) => {
+ await page.goto("/");
+ await page.waitForTimeout(2000);
+ await page.getByText("Store").nth(0).click();
+ await page.getByTestId("install-Basic RAG").click();
+ await page.waitForTimeout(5000);
+ await page.getByText("My Collection").nth(0).click();
+ await page.getByText("Components").first().click();
+ await page.getByText("Basic RAG").first().isVisible();
+ await page
+ .getByTestId("card-basic-rag")
+ .first()
+ .hover()
+ .then(async () => {
+ await page.getByTestId("icon-Trash2").first().click();
+ await page.waitForTimeout(2000);
+ });
+ await page.getByText("Confirm deletion of component?").isVisible();
+ await page.getByText("Delete").nth(1).click();
+ await page.waitForTimeout(1000);
+ await page.getByText("Successfully").first().isVisible();
+});
diff --git a/src/frontend/tests/end-to-end/fileUploadComponent.spec.ts b/src/frontend/tests/end-to-end/fileUploadComponent.spec.ts
index 7141c6a7f..e38ff1bc0 100644
--- a/src/frontend/tests/end-to-end/fileUploadComponent.spec.ts
+++ b/src/frontend/tests/end-to-end/fileUploadComponent.spec.ts
@@ -79,7 +79,7 @@ test("dropDownComponent", async ({ page }) => {
// Release the mouse
await page.mouse.up();
- await page.getByText("Run", { exact: true }).click();
+ await page.getByText("Playground", { exact: true }).click();
await page.getByText("Run Flow", { exact: true }).click();
await page.waitForTimeout(3000);
const textOutput = await page.getByPlaceholder("Empty").first().inputValue();
diff --git a/src/frontend/tests/end-to-end/textInputOutput.spec.ts b/src/frontend/tests/end-to-end/textInputOutput.spec.ts
index 5312cb96b..9ac25bd8f 100644
--- a/src/frontend/tests/end-to-end/textInputOutput.spec.ts
+++ b/src/frontend/tests/end-to-end/textInputOutput.spec.ts
@@ -117,7 +117,7 @@ test("TextInputOutputComponent", async ({ page }) => {
await page
.getByTestId("input-openai_api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
- await page.getByText("Run", { exact: true }).click();
+ await page.getByText("Playground", { exact: true }).click();
await page.getByText("Run Flow", { exact: true }).click();
await page.waitForTimeout(5000);
@@ -138,7 +138,7 @@ test("TextInputOutputComponent", async ({ page }) => {
.getByTestId("input-input_value")
.nth(0)
.fill("This is a test, again just to be sure!");
- await page.getByText("Run", { exact: true }).click();
+ await page.getByText("Playground", { exact: true }).click();
await page.getByText("Run Flow", { exact: true }).click();
await page.waitForTimeout(5000);
diff --git a/src/frontend/tests/end-to-end/userSettings.spec.ts b/src/frontend/tests/end-to-end/userSettings.spec.ts
new file mode 100644
index 000000000..6f90bb19c
--- /dev/null
+++ b/src/frontend/tests/end-to-end/userSettings.spec.ts
@@ -0,0 +1,91 @@
+import { test } from "@playwright/test";
+
+test("should see general profile gradient", async ({ page }) => {
+ await page.goto("/");
+ await page.waitForTimeout(2000);
+ await page.getByTestId("user-profile-settings").click();
+ await page.getByText("Settings").click();
+ await page.getByText("General").nth(2).isVisible();
+ await page.getByText("Profile Gradient").isVisible();
+});
+
+test("should interact with global variables", async ({ page }) => {
+ const randomName = Math.random().toString(36).substring(2);
+
+ await page.goto("/");
+ await page.waitForTimeout(2000);
+ await page.getByTestId("user-profile-settings").click();
+ await page.getByText("Settings").click();
+ await page.getByText("Global Variables").click();
+ await page.getByText("Global Variables").nth(2);
+ await page.getByText("Global Variables", { exact: true }).nth(1).isVisible();
+ await page.getByText("Add New").click();
+ await page
+ .getByPlaceholder("Insert a name for the variable...")
+ .fill(randomName);
+ await page.getByTestId("popover-anchor-type-global-variables").click();
+ await page.getByPlaceholder("Search options...").fill("Generic");
+ await page.waitForTimeout(2000);
+ await page.getByText("Generic", { exact: true }).last().isVisible();
+ await page.getByText("Generic", { exact: true }).last().click();
+
+ await page.getByTestId("popover-anchor-type-global-variables").click();
+ await page.waitForTimeout(2000);
+ await page.getByPlaceholder("Search options...").fill("Generic");
+ await page.getByText("Generic", { exact: true }).last().isVisible();
+ await page.getByText("Generic", { exact: true }).last().click();
+
+ await page
+ .getByPlaceholder("Insert a value for the variable...")
+ .fill("testtesttesttesttesttesttesttest");
+ await page.getByTestId("popover-anchor-apply-to-fields").click();
+ await page.waitForTimeout(2000);
+
+ await page.getByPlaceholder("Search options...").fill("System Message");
+
+ await page.getByText("System Message").first().click();
+
+ await page.getByPlaceholder("Search options...").fill("openAI");
+
+ await page.getByText("OpenAI API Base").first().click();
+
+ await page.getByPlaceholder("Search options...").fill("llama");
+
+ await page.getByText("Ollama").first().click();
+
+ await page.keyboard.press("Escape");
+ await page.getByText("Save Variable", { exact: true }).click();
+
+ await page.getByText(randomName).isVisible();
+
+ await page
+ .getByLabel("Press Space to toggle all rows selection (unchecked)")
+ .nth(0)
+ .click();
+ await page.getByTestId("icon-Trash2").click();
+ await page.getByText("No Rows To Show").isVisible();
+});
+
+test("should see shortcuts", async ({ page }) => {
+ await page.goto("/");
+ await page.waitForTimeout(2000);
+ await page.getByTestId("user-profile-settings").click();
+ await page.getByText("Settings").click();
+ await page.getByText("General").nth(2).isVisible();
+ await page.getByText("Shortcuts").nth(0).click();
+ await page.getByText("Shortcuts", { exact: true }).nth(1).isVisible();
+ await page
+ .getByText("Advanced Settings Component", { exact: true })
+ .isVisible();
+ await page.getByText("Minimize Component", { exact: true }).isVisible();
+ await page.getByText("Code Component", { exact: true }).isVisible();
+ await page.getByText("Copy Component", { exact: true }).isVisible();
+ await page.getByText("Duplicate Component", { exact: true }).isVisible();
+ await page.getByText("Share Component", { exact: true }).isVisible();
+ await page.getByText("Docs Component", { exact: true }).isVisible();
+ await page.getByText("Save Component", { exact: true }).isVisible();
+ await page.getByText("Delete Component", { exact: true }).isVisible();
+ await page.getByText("Open Playground", { exact: true }).isVisible();
+ await page.getByText("Undo", { exact: true }).isVisible();
+ await page.getByText("Redo", { exact: true }).isVisible();
+});