update branch with changes from dev
This commit is contained in:
commit
7f6385a609
347 changed files with 14550 additions and 21307 deletions
710
src/frontend/harFiles/example.har
Normal file
710
src/frontend/harFiles/example.har
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from "./constants/constants";
|
||||
import { AuthContext } from "./contexts/authContext";
|
||||
import { autoLogin, getGlobalVariables, getHealth } from "./controllers/API";
|
||||
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
||||
import Router from "./routes";
|
||||
import useAlertStore from "./stores/alertStore";
|
||||
import { useDarkStore } from "./stores/darkStore";
|
||||
|
|
@ -24,6 +25,8 @@ import { useGlobalVariablesStore } from "./stores/globalVariablesStore/globalVar
|
|||
import { useStoreStore } from "./stores/storeStore";
|
||||
import { useTypesStore } from "./stores/typesStore";
|
||||
export default function App() {
|
||||
useTrackLastVisitedPath();
|
||||
|
||||
const removeFromTempNotificationList = useAlertStore(
|
||||
(state) => state.removeFromTempNotificationList,
|
||||
);
|
||||
|
|
@ -104,7 +107,6 @@ export default function App() {
|
|||
const fetchAllData = async () => {
|
||||
setTimeout(async () => {
|
||||
await Promise.all([refreshStars(), refreshVersion(), fetchData()]);
|
||||
getFoldersApi();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
|
@ -112,6 +114,7 @@ export default function App() {
|
|||
return new Promise<void>(async (resolve, reject) => {
|
||||
if (isAuthenticated) {
|
||||
try {
|
||||
await getFoldersApi();
|
||||
await getTypes();
|
||||
await refreshFlows();
|
||||
const res = await getGlobalVariables();
|
||||
|
|
@ -208,7 +211,7 @@ export default function App() {
|
|||
<div className="flex flex-col-reverse" style={{ zIndex: 999 }}>
|
||||
{tempNotificationList.map((alert) => (
|
||||
<div key={alert.id}>
|
||||
{alert.type === "error" && (
|
||||
{alert.type === "error" ? (
|
||||
<ErrorAlert
|
||||
key={alert.id}
|
||||
title={alert.title}
|
||||
|
|
@ -216,26 +219,12 @@ export default function App() {
|
|||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="z-40 flex flex-col-reverse">
|
||||
{tempNotificationList.map((alert) => (
|
||||
<div key={alert.id}>
|
||||
{alert.type === "notice" ? (
|
||||
<NoticeAlert
|
||||
key={alert.id}
|
||||
title={alert.title}
|
||||
link={alert.link}
|
||||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
) : (
|
||||
alert.type === "success" && (
|
||||
<SuccessAlert
|
||||
alert.type === "notice" && (
|
||||
<NoticeAlert
|
||||
key={alert.id}
|
||||
title={alert.title}
|
||||
link={alert.link}
|
||||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
|
|
@ -244,6 +233,20 @@ export default function App() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="z-40 flex flex-col-reverse">
|
||||
{tempNotificationList.map((alert) => (
|
||||
<div key={alert.id}>
|
||||
{alert.type === "success" && (
|
||||
<SuccessAlert
|
||||
key={alert.id}
|
||||
title={alert.title}
|
||||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export default function sortByName(stringList: string[]): string[] {
|
||||
return stringList.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export function convertTestName(name: string): string {
|
||||
return name.replace(/ /g, "-").toLowerCase();
|
||||
return name.replace(/ /g, "-").toLowerCase();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from "../../../ui/dropdown-menu";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Node } from "reactflow";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
|
|
@ -22,11 +21,7 @@ import IconComponent from "../../../genericIconComponent";
|
|||
import ShadTooltip from "../../../shadTooltipComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
|
||||
export const MenuBar = ({
|
||||
removeFunction,
|
||||
}: {
|
||||
removeFunction: (nodes: Node[]) => void;
|
||||
}): JSX.Element => {
|
||||
export const MenuBar = ({}: {}): JSX.Element => {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
|
@ -36,7 +31,6 @@ export const MenuBar = ({
|
|||
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
||||
const [openSettings, setOpenSettings] = useState(false);
|
||||
const [openLogs, setOpenLogs] = useState(false);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const navigate = useNavigate();
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
|
|
@ -72,14 +66,6 @@ export const MenuBar = ({
|
|||
|
||||
return currentFlow ? (
|
||||
<div className="round-button-div">
|
||||
<button
|
||||
onClick={() => {
|
||||
removeFunction(nodes);
|
||||
navigate("/");
|
||||
}}
|
||||
>
|
||||
<IconComponent name="ChevronLeft" className="w-4" />
|
||||
</button>
|
||||
<div className="header-menu-bar">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ import { FaDiscord, FaGithub } from "react-icons/fa";
|
|||
import { RiTwitterXFill } from "react-icons/ri";
|
||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import AlertDropdown from "../../alerts/alertDropDown";
|
||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||
import {
|
||||
LOCATIONS_TO_RETURN,
|
||||
USER_PROJECTS_HEADER,
|
||||
} from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
import { Node } from "reactflow";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useLocationStore } from "../../stores/locationStore";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { gradients } from "../../utils/styleUtils";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
|
|
@ -29,6 +32,7 @@ import MenuBar from "./components/menuBar";
|
|||
export default function Header(): JSX.Element {
|
||||
const notificationCenter = useAlertStore((state) => state.notificationCenter);
|
||||
const location = useLocation();
|
||||
|
||||
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const removeFlow = useFlowsManagerStore((store) => store.removeFlow);
|
||||
|
|
@ -40,20 +44,56 @@ export default function Header(): JSX.Element {
|
|||
const setDark = useDarkStore((state) => state.setDark);
|
||||
const stars = useDarkStore((state) => state.stars);
|
||||
|
||||
async function checkForChanges(nodes: Node[]): Promise<void> {
|
||||
const routeHistory = useLocationStore((state) => state.routeHistory);
|
||||
|
||||
async function checkForChanges(): Promise<void> {
|
||||
if (nodes.length === 0) {
|
||||
await removeFlow(id!);
|
||||
}
|
||||
}
|
||||
|
||||
const redirectToLastLocation = () => {
|
||||
const lastFlowVisitedIndex = routeHistory
|
||||
.reverse()
|
||||
.findIndex(
|
||||
(path) => path.includes("/flow/") && path !== location.pathname,
|
||||
);
|
||||
|
||||
const lastFlowVisited = routeHistory[lastFlowVisitedIndex];
|
||||
lastFlowVisited && !location.pathname.includes("/flow")
|
||||
? navigate(lastFlowVisited)
|
||||
: navigate("/all");
|
||||
};
|
||||
|
||||
const visitedFlowPathBefore = () => {
|
||||
const lastThreeVisitedPaths = routeHistory.slice(-3);
|
||||
return lastThreeVisitedPaths.some((path) => path.includes("/flow/"));
|
||||
};
|
||||
|
||||
const showArrowReturnIcon =
|
||||
LOCATIONS_TO_RETURN.some((path) => location.pathname.includes(path)) &&
|
||||
visitedFlowPathBefore();
|
||||
|
||||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display lg:w-[30%]">
|
||||
<Link to="/" onClick={() => checkForChanges(nodes)}>
|
||||
<Link to="/all" className="cursor-pointer" onClick={checkForChanges}>
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
<MenuBar removeFunction={checkForChanges} />
|
||||
{showArrowReturnIcon && (
|
||||
<button
|
||||
onClick={() => {
|
||||
checkForChanges();
|
||||
redirectToLastLocation();
|
||||
}}
|
||||
>
|
||||
<IconComponent name="ChevronLeft" className="w-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<MenuBar />
|
||||
</div>
|
||||
|
||||
<div className="round-button-div">
|
||||
<Link to="/">
|
||||
<Button
|
||||
|
|
@ -65,9 +105,7 @@ export default function Header(): JSX.Element {
|
|||
: "secondary"
|
||||
}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
checkForChanges(nodes);
|
||||
}}
|
||||
onClick={checkForChanges}
|
||||
>
|
||||
<IconComponent name="Home" className="h-4 w-4" />
|
||||
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
|
||||
|
|
@ -80,9 +118,7 @@ export default function Header(): JSX.Element {
|
|||
className="gap-2"
|
||||
variant={location.pathname === "/store" ? "primary" : "secondary"}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
checkForChanges(nodes);
|
||||
}}
|
||||
onClick={checkForChanges}
|
||||
data-testid="button-store"
|
||||
>
|
||||
<IconComponent name="Store" className="h-4 w-4" />
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ export default function InputComponent({
|
|||
optionButton,
|
||||
objectOptions,
|
||||
isObjectOption = false,
|
||||
name,
|
||||
onChangeFolderName,
|
||||
}: InputComponentType): JSX.Element {
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
|
|
@ -53,6 +55,7 @@ export default function InputComponent({
|
|||
{isForm ? (
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
name={name}
|
||||
id={"form-" + id}
|
||||
ref={refInput}
|
||||
onBlur={onInputLostFocus}
|
||||
|
|
@ -72,6 +75,9 @@ export default function InputComponent({
|
|||
)}
|
||||
placeholder={password && editNode ? "Key" : placeholder}
|
||||
onChange={(e) => {
|
||||
if (onChangeFolderName) {
|
||||
return onChangeFolderName(e);
|
||||
}
|
||||
onChange && onChange(e.target.value);
|
||||
}}
|
||||
onCopy={(e) => {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@ type SideBarButtonsComponentProps = {
|
|||
pathname: string;
|
||||
handleOpenNewFolderModal?: () => void;
|
||||
};
|
||||
const SideBarButtonsComponent = ({
|
||||
items,
|
||||
handleOpenNewFolderModal,
|
||||
}: SideBarButtonsComponentProps) => {
|
||||
const SideBarButtonsComponent = ({ items }: SideBarButtonsComponentProps) => {
|
||||
return (
|
||||
<>
|
||||
{items.map((item) => (
|
||||
|
|
@ -26,7 +23,6 @@ const SideBarButtonsComponent = ({
|
|||
buttonVariants({ variant: "ghost" }),
|
||||
"!w-[200px] cursor-pointer justify-start gap-2 border border-transparent hover:border-border hover:bg-transparent",
|
||||
)}
|
||||
onClick={handleOpenNewFolderModal}
|
||||
>
|
||||
{item.title}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { FolderType } from "../../../../pages/MainPage/entities";
|
||||
import { addFolder, updateFolder } from "../../../../pages/MainPage/services";
|
||||
import { handleDownloadFolderFn } from "../../../../pages/MainPage/utils/handle-download-folder";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||
import { handleKeyDown } from "../../../../utils/reactflowUtils";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import DropdownButton from "../../../dropdownButtonComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../genericIconComponent";
|
||||
import { Button, buttonVariants } from "../../../ui/button";
|
||||
import { Input } from "../../../ui/input";
|
||||
import useFileDrop from "../../hooks/use-on-file-drop";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { handleDownloadFolderFn } from "../../../../pages/MainPage/utils/handle-download-folder";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
|
||||
type SideBarFoldersButtonsComponentProps = {
|
||||
folders: FolderType[];
|
||||
|
|
@ -18,22 +20,27 @@ type SideBarFoldersButtonsComponentProps = {
|
|||
handleChangeFolder?: (id: string) => void;
|
||||
handleEditFolder?: (item: FolderType) => void;
|
||||
handleDeleteFolder?: (item: FolderType) => void;
|
||||
handleAddFolder?: () => void;
|
||||
};
|
||||
const SideBarFoldersButtonsComponent = ({
|
||||
folders,
|
||||
pathname,
|
||||
handleAddFolder,
|
||||
handleChangeFolder,
|
||||
handleEditFolder,
|
||||
handleDeleteFolder,
|
||||
}: SideBarFoldersButtonsComponentProps) => {
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
const setFolders = useFolderStore((state) => state.setFolders);
|
||||
const folders = useFolderStore((state) => state.folders);
|
||||
const [foldersNames, setFoldersNames] = useState({});
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const [editFolders, setEditFolderName] = useState(
|
||||
folders.map((obj) => ({ name: obj.name, edit: false })),
|
||||
);
|
||||
const uploadFolder = useFolderStore((state) => state.uploadFolder);
|
||||
const currentFolder = pathname.split("/");
|
||||
const urlWithoutPath = pathname.split("/").length < 4;
|
||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
|
||||
|
||||
const checkPathName = (itemId: string) => {
|
||||
if (urlWithoutPath && itemId === myCollectionId) {
|
||||
|
|
@ -62,20 +69,44 @@ const SideBarFoldersButtonsComponent = ({
|
|||
handleDownloadFolderFn(id);
|
||||
};
|
||||
|
||||
function addNewFolder() {
|
||||
addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
|
||||
(res) => {
|
||||
getFoldersApi(true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleEditFolderName(e, name): void {
|
||||
const {
|
||||
target: { value },
|
||||
} = e;
|
||||
setFoldersNames((old) => ({
|
||||
...old,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
folders.map((obj) => ({ name: obj.name, edit: false }));
|
||||
}, [folders]);
|
||||
|
||||
console.log(folderId, folderIdDragging);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex shrink-0 items-center justify-between">
|
||||
<DropdownButton
|
||||
firstButtonName="New Folder"
|
||||
onFirstBtnClick={handleAddFolder!}
|
||||
options={[]}
|
||||
plusButton={true}
|
||||
dropdownOptions={false}
|
||||
/>
|
||||
<Button variant="primary" onClick={addNewFolder}>
|
||||
<ForwardedIconComponent
|
||||
name="Plus"
|
||||
className="main-page-nav-button"
|
||||
/>
|
||||
New Folder
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleUploadFlowsToFolder}
|
||||
className="px-7"
|
||||
onClick={handleUploadFlowsToFolder}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Upload"
|
||||
|
|
@ -87,81 +118,206 @@ const SideBarFoldersButtonsComponent = ({
|
|||
|
||||
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
|
||||
<>
|
||||
{folders.map((item, index) => (
|
||||
<div
|
||||
onDragOver={dragOver}
|
||||
onDragEnter={dragEnter}
|
||||
onDragLeave={dragLeave}
|
||||
onDrop={(e) => onDrop(e, item.id!)}
|
||||
key={item.id}
|
||||
data-testid={`sidebar-nav-${item.name}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
checkPathName(item.id!)
|
||||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
|
||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
||||
)}
|
||||
onClick={() => handleChangeFolder!(item.id!)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<IconComponent
|
||||
name={"folder"}
|
||||
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
|
||||
/>
|
||||
<span className="block max-w-full truncate opacity-100">
|
||||
{item.name}
|
||||
</span>
|
||||
<div className="flex-1" />
|
||||
{index > 0 && (
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleDeleteFolder!(item);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"trash"}
|
||||
className=" w-4 stroke-[1.5]"
|
||||
/>
|
||||
</Button>
|
||||
{folders.map((item, index) => {
|
||||
const editFolderName = editFolders?.filter(
|
||||
(folder) => folder.name === item.name,
|
||||
)[0];
|
||||
return (
|
||||
<div
|
||||
onDragOver={(e) => dragOver(e, item.id!)}
|
||||
onDragEnter={(e) => dragEnter(e, item.id!)}
|
||||
onDragLeave={dragLeave}
|
||||
onDrop={(e) => onDrop(e, item.id!)}
|
||||
key={item.id}
|
||||
data-testid={`sidebar-nav-${item.name}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
checkPathName(item.id!)
|
||||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
|
||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
||||
folderIdDragging === item.id! ? "bg-border" : "",
|
||||
)}
|
||||
{index > 0 && (
|
||||
onClick={() => handleChangeFolder!(item.id!)}
|
||||
>
|
||||
<div
|
||||
onDoubleClick={(event) => {
|
||||
if (item.name === "My Projects") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!foldersNames[item.name]) {
|
||||
setFoldersNames({ [item.name]: item.name });
|
||||
}
|
||||
|
||||
if (
|
||||
editFolders.find((obj) => obj.name === item.name)?.name
|
||||
) {
|
||||
const newEditFolders = editFolders.map((obj) => {
|
||||
if (obj.name === item.name) {
|
||||
return { name: item.name, edit: true };
|
||||
}
|
||||
return { name: obj.name, edit: false };
|
||||
});
|
||||
setEditFolderName(newEditFolders);
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
setEditFolderName((old) => [
|
||||
...old,
|
||||
{ name: item.name, edit: true },
|
||||
]);
|
||||
setFoldersNames((oldFolder) => ({
|
||||
...oldFolder,
|
||||
[item.name]: item.name,
|
||||
}));
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
className="flex w-full items-center gap-2"
|
||||
>
|
||||
<IconComponent
|
||||
name={"folder"}
|
||||
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
|
||||
/>
|
||||
{editFolderName?.edit ? (
|
||||
<div>
|
||||
<Input
|
||||
className="w-36"
|
||||
onChange={(e) => {
|
||||
handleEditFolderName(e, item.name);
|
||||
}}
|
||||
ref={refInput}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
const newEditFolders = editFolders.map((obj) => {
|
||||
if (obj.name === item.name) {
|
||||
return { name: item.name, edit: false };
|
||||
}
|
||||
return { name: obj.name, edit: false };
|
||||
});
|
||||
setEditFolderName(newEditFolders);
|
||||
setFoldersNames({});
|
||||
setEditFolderName(
|
||||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
})),
|
||||
);
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
refInput.current?.blur();
|
||||
}
|
||||
handleKeyDown(e, e.key, "");
|
||||
}}
|
||||
autoFocus={true}
|
||||
onBlur={async () => {
|
||||
const newEditFolders = editFolders.map((obj) => {
|
||||
if (obj.name === item.name) {
|
||||
return { name: item.name, edit: false };
|
||||
}
|
||||
return { name: obj.name, edit: false };
|
||||
});
|
||||
setEditFolderName(newEditFolders);
|
||||
if (foldersNames[item.name].trim() !== "") {
|
||||
setFoldersNames((old) => ({
|
||||
...old,
|
||||
[item.name]: foldersNames[item.name],
|
||||
}));
|
||||
const body = {
|
||||
...item,
|
||||
name: foldersNames[item.name],
|
||||
flows: item.flows?.length > 0 ? item.flows : [],
|
||||
components:
|
||||
item.components?.length > 0
|
||||
? item.components
|
||||
: [],
|
||||
};
|
||||
const updatedFolder = await updateFolder(
|
||||
body,
|
||||
item.id!,
|
||||
);
|
||||
const updateFolders = folders.filter(
|
||||
(f) => f.name !== item.name,
|
||||
);
|
||||
setFolders([...updateFolders, updatedFolder]);
|
||||
setFoldersNames({});
|
||||
setEditFolderName(
|
||||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
setFoldersNames((old) => ({
|
||||
...old,
|
||||
[item.name]: item.name,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
value={foldersNames[item.name]}
|
||||
id={`input-folder-${item.name}`}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="block max-w-full truncate opacity-100">
|
||||
{item.name}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex-1" />
|
||||
{index > 0 && (
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleDeleteFolder!(item);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"trash"}
|
||||
className=" w-4 stroke-[1.5]"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{/* {index > 0 && (
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"pencil"}
|
||||
className=" w-4 stroke-[1.5] text-white "
|
||||
/>
|
||||
</Button>
|
||||
)} */}
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleEditFolder!(item);
|
||||
handleDownloadFolder(item.id!);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"pencil"}
|
||||
name={"Download"}
|
||||
className=" w-4 stroke-[1.5] text-white "
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleDownloadFolder(item.id!);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"Download"}
|
||||
className=" w-4 stroke-[1.5] text-white "
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ import { uploadFlowsFromFolders } from "../../../pages/MainPage/services";
|
|||
import useAlertStore from "../../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../stores/foldersStore";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
import { addVersionToDuplicates } from "../../../utils/reactflowUtils";
|
||||
|
||||
const useFileDrop = (folderId, folderChangeCallback) => {
|
||||
const setFolderDragging = useFolderStore((state) => state.setFolderDragging);
|
||||
const setFolderIdDragging = useFolderStore(
|
||||
(state) => state.setFolderIdDragging,
|
||||
);
|
||||
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
|
||||
const triggerFolderChange = (folderId) => {
|
||||
|
|
@ -42,12 +45,14 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
| React.DragEvent<HTMLDivElement>
|
||||
| React.DragEvent<HTMLButtonElement>
|
||||
| React.DragEvent<HTMLAnchorElement>,
|
||||
folderId: string,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||
setFolderDragging(true);
|
||||
}
|
||||
setFolderIdDragging(folderId);
|
||||
};
|
||||
|
||||
const dragEnter = (
|
||||
|
|
@ -55,10 +60,12 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
| React.DragEvent<HTMLDivElement>
|
||||
| React.DragEvent<HTMLButtonElement>
|
||||
| React.DragEvent<HTMLAnchorElement>,
|
||||
folderId: string,
|
||||
) => {
|
||||
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||
setFolderDragging(true);
|
||||
}
|
||||
setFolderIdDragging(folderId);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
|
|
@ -71,6 +78,7 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
e.preventDefault();
|
||||
if (e.target === e.currentTarget) {
|
||||
setFolderDragging(false);
|
||||
setFolderIdDragging("");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -92,7 +100,6 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
|
||||
e.preventDefault();
|
||||
handleFileDrop(e);
|
||||
setFolderDragging(false);
|
||||
};
|
||||
|
||||
const uploadFromDragCard = (flowId, folderId) => {
|
||||
|
|
@ -101,11 +108,15 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
if (!selectedFlow) {
|
||||
throw new Error("Flow not found");
|
||||
}
|
||||
const updatedFlow = { ...selectedFlow, folder_id: folderId };
|
||||
|
||||
const newName = addVersionToDuplicates(updatedFlow, flows);
|
||||
|
||||
updatedFlow.name = newName;
|
||||
|
||||
setFolderDragging(false);
|
||||
setFolderIdDragging("");
|
||||
|
||||
const updatedFlow: FlowType = {
|
||||
...selectedFlow,
|
||||
folder_id: folderId,
|
||||
};
|
||||
updateFlowInDatabase(updatedFlow).then(() => {
|
||||
getFoldersApi(true);
|
||||
triggerFolderChange(folderId);
|
||||
|
|
@ -115,11 +126,11 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
const uploadFormData = (data) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", data);
|
||||
|
||||
setFolderDragging(false);
|
||||
setFolderIdDragging("");
|
||||
uploadFlowsFromFolders(formData).then(() => {
|
||||
getFoldersApi(true);
|
||||
triggerFolderChange(folderId);
|
||||
refreshFlows();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { cn } from "../../utils/utils";
|
|||
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
|
||||
import SideBarButtonsComponent from "./components/sideBarButtons";
|
||||
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
|
||||
import { addFolder } from "../../pages/MainPage/services";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
|
||||
type SidebarNavProps = {
|
||||
items: {
|
||||
|
|
@ -22,7 +25,6 @@ type SidebarNavProps = {
|
|||
export default function SidebarNav({
|
||||
className,
|
||||
items,
|
||||
handleOpenNewFolderModal,
|
||||
handleChangeFolder,
|
||||
handleEditFolder,
|
||||
handleDeleteFolder,
|
||||
|
|
@ -39,11 +41,7 @@ export default function SidebarNav({
|
|||
return (
|
||||
<nav className={cn(className)} {...props}>
|
||||
<HorizontalScrollFadeComponent>
|
||||
<SideBarButtonsComponent
|
||||
items={items}
|
||||
pathname={pathname}
|
||||
handleOpenNewFolderModal={handleOpenNewFolderModal}
|
||||
/>
|
||||
<SideBarButtonsComponent items={items} pathname={pathname} />
|
||||
|
||||
{!loadingFolders && folders?.length > 0 && isFolderPath && (
|
||||
<SideBarFoldersButtonsComponent
|
||||
|
|
@ -52,7 +50,6 @@ export default function SidebarNav({
|
|||
handleChangeFolder={handleChangeFolder}
|
||||
handleEditFolder={handleEditFolder}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
handleAddFolder={handleOpenNewFolderModal}
|
||||
/>
|
||||
)}
|
||||
</HorizontalScrollFadeComponent>
|
||||
|
|
|
|||
|
|
@ -32,51 +32,6 @@ const TableComponent = forwardRef<
|
|||
ref,
|
||||
) => {
|
||||
const dark = useDarkStore((state) => state.dark);
|
||||
var currentRowHeight: number;
|
||||
var minRowHeight = 25;
|
||||
|
||||
const getRowHeight = useCallback(() => {
|
||||
return currentRowHeight;
|
||||
}, []);
|
||||
|
||||
const onGridReady = useCallback((params: any) => {
|
||||
minRowHeight = params.api.getSizesForCurrentTheme().rowHeight;
|
||||
currentRowHeight = minRowHeight;
|
||||
}, []);
|
||||
|
||||
const updateRowHeight = (params: { api: any }) => {
|
||||
const bodyViewport = document.querySelector(".ag-body-viewport");
|
||||
if (!bodyViewport) {
|
||||
return;
|
||||
}
|
||||
var gridHeight = bodyViewport.clientHeight;
|
||||
var renderedRowCount = params.api.getDisplayedRowCount();
|
||||
|
||||
if (renderedRowCount * minRowHeight >= gridHeight) {
|
||||
if (currentRowHeight !== minRowHeight) {
|
||||
currentRowHeight = minRowHeight;
|
||||
params.api.resetRowHeights();
|
||||
}
|
||||
} else {
|
||||
currentRowHeight = Math.floor(gridHeight / renderedRowCount);
|
||||
params.api.resetRowHeights();
|
||||
}
|
||||
};
|
||||
|
||||
const onFirstDataRendered = useCallback(
|
||||
(params: any) => {
|
||||
updateRowHeight(params);
|
||||
},
|
||||
[updateRowHeight],
|
||||
);
|
||||
|
||||
const onGridSizeChanged = useCallback(
|
||||
(params: any) => {
|
||||
updateRowHeight(params);
|
||||
},
|
||||
[updateRowHeight],
|
||||
);
|
||||
|
||||
if (props.rowData.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center rounded-md border">
|
||||
|
|
@ -102,10 +57,6 @@ const TableComponent = forwardRef<
|
|||
<AgGridReact
|
||||
{...props}
|
||||
className={cn(props.className, "custom-scroll")}
|
||||
getRowHeight={getRowHeight}
|
||||
onGridReady={onGridReady}
|
||||
onFirstDataRendered={onFirstDataRendered}
|
||||
onGridSizeChanged={onGridSizeChanged}
|
||||
defaultColDef={{
|
||||
minWidth: 100,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -800,8 +800,19 @@ export const NATIVE_CATEGORIES = [
|
|||
"agents",
|
||||
];
|
||||
|
||||
export const AUTHORIZED_DUPLICATE_REQUESTS = [
|
||||
"/health",
|
||||
"/flows",
|
||||
"/logout",
|
||||
"/refresh",
|
||||
"/login",
|
||||
"/auto_login",
|
||||
];
|
||||
|
||||
export const SAVE_DEBOUNCE_TIME = 300;
|
||||
|
||||
export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to display right now. Please check back later.`;
|
||||
|
||||
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
||||
|
||||
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosInstance } from "axios";
|
|||
import { useContext, useEffect } from "react";
|
||||
import { Cookies } from "react-cookie";
|
||||
import { renewAccessToken } from ".";
|
||||
import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../constants/constants";
|
||||
import { BuildStatus } from "../../constants/enums";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
|
|
@ -81,16 +82,26 @@ function ApiInterceptor() {
|
|||
const requestInterceptor = api.interceptors.request.use(
|
||||
(config) => {
|
||||
const lastUrl = localStorage.getItem("lastUrlCalled");
|
||||
const lastMethodCalled = localStorage.getItem("lastMethodCalled");
|
||||
|
||||
const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
|
||||
config?.url!.includes(request),
|
||||
);
|
||||
|
||||
if (
|
||||
config?.url === lastUrl &&
|
||||
config?.url !== "/health" &&
|
||||
config?.method === "get"
|
||||
!isContained &&
|
||||
lastMethodCalled === config.method
|
||||
) {
|
||||
return Promise.reject("Duplicate request");
|
||||
}
|
||||
|
||||
localStorage.setItem("lastUrlCalled", config.url ?? "");
|
||||
localStorage.setItem("lastMethodCalled", config.method ?? "");
|
||||
localStorage.setItem(
|
||||
"lastRequestData",
|
||||
JSON.stringify(config.data) ?? "",
|
||||
);
|
||||
|
||||
const accessToken = cookies.get("access_token_lf");
|
||||
if (accessToken && !isAuthorizedURL(config?.url)) {
|
||||
|
|
|
|||
|
|
@ -173,8 +173,6 @@ export default function ParameterComponent({
|
|||
renderTooltips();
|
||||
}, [tooltipTitle, flow]);
|
||||
|
||||
console.log(left === true && type === "dict");
|
||||
|
||||
return !showNode ? (
|
||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { APITemplateType } from "../../types/api";
|
||||
|
||||
export default function getFieldTitle(
|
||||
template: APITemplateType,
|
||||
templateField: string,
|
||||
): string {
|
||||
return template[templateField].display_name
|
||||
? template[templateField].display_name!
|
||||
: template[templateField].name ?? templateField;
|
||||
}
|
||||
template: APITemplateType,
|
||||
templateField: string,
|
||||
): string {
|
||||
return template[templateField].display_name
|
||||
? template[templateField].display_name!
|
||||
: template[templateField].name ?? templateField;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,40 @@
|
|||
import { priorityFields } from "../../constants/constants";
|
||||
|
||||
export default function sortFields(a, b, fieldOrder) {
|
||||
// Early return for empty fields
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
// Normalize the case to ensure case-insensitive comparison
|
||||
const normalizedFieldA = a.toLowerCase();
|
||||
const normalizedFieldB = b.toLowerCase();
|
||||
|
||||
const aIsPriority = priorityFields.has(normalizedFieldA);
|
||||
const bIsPriority = priorityFields.has(normalizedFieldB);
|
||||
|
||||
// Sort by priority
|
||||
if (aIsPriority && !bIsPriority) return -1;
|
||||
if (!aIsPriority && bIsPriority) return 1;
|
||||
|
||||
// Check if either field is in the fieldOrder array
|
||||
const indexOfA = fieldOrder.indexOf(normalizedFieldA);
|
||||
const indexOfB = fieldOrder.indexOf(normalizedFieldB);
|
||||
|
||||
// If both fields are in fieldOrder, sort by their order in the array
|
||||
if (indexOfA !== -1 && indexOfB !== -1) {
|
||||
return indexOfA - indexOfB;
|
||||
}
|
||||
|
||||
// If only one of the fields is in fieldOrder, that field comes first
|
||||
if (indexOfA !== -1) {
|
||||
return -1;
|
||||
}
|
||||
if (indexOfB !== -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Default case for fields not in priorityFields and not found in fieldOrder
|
||||
// You might want to sort them alphabetically or in another specific manner
|
||||
return a.localeCompare(b);
|
||||
// Early return for empty fields
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
// Normalize the case to ensure case-insensitive comparison
|
||||
const normalizedFieldA = a.toLowerCase();
|
||||
const normalizedFieldB = b.toLowerCase();
|
||||
|
||||
const aIsPriority = priorityFields.has(normalizedFieldA);
|
||||
const bIsPriority = priorityFields.has(normalizedFieldB);
|
||||
|
||||
// Sort by priority
|
||||
if (aIsPriority && !bIsPriority) return -1;
|
||||
if (!aIsPriority && bIsPriority) return 1;
|
||||
|
||||
// Check if either field is in the fieldOrder array
|
||||
const indexOfA = fieldOrder.indexOf(normalizedFieldA);
|
||||
const indexOfB = fieldOrder.indexOf(normalizedFieldB);
|
||||
|
||||
// If both fields are in fieldOrder, sort by their order in the array
|
||||
if (indexOfA !== -1 && indexOfB !== -1) {
|
||||
return indexOfA - indexOfB;
|
||||
}
|
||||
|
||||
// If only one of the fields is in fieldOrder, that field comes first
|
||||
if (indexOfA !== -1) {
|
||||
return -1;
|
||||
}
|
||||
if (indexOfB !== -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Default case for fields not in priorityFields and not found in fieldOrder
|
||||
// You might want to sort them alphabetically or in another specific manner
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
|
|
|
|||
14
src/frontend/src/hooks/use-track-last-visited-path.tsx
Normal file
14
src/frontend/src/hooks/use-track-last-visited-path.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocationStore } from "../stores/locationStore";
|
||||
|
||||
function useTrackLastVisitedPath() {
|
||||
const location = useLocation();
|
||||
const setHistory = useLocationStore((state) => state.setRouteHistory);
|
||||
|
||||
useEffect(() => {
|
||||
setHistory(location.pathname);
|
||||
}, [location]);
|
||||
}
|
||||
|
||||
export default useTrackLastVisitedPath;
|
||||
|
|
@ -5,13 +5,13 @@
|
|||
* @returns {string} - The curl code
|
||||
*/
|
||||
export default function getCurlCode(
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `curl -X POST \\
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `curl -X POST \\
|
||||
${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/api/v1/run/${flowId}?stream=false \\
|
||||
|
|
@ -23,4 +23,4 @@ export default function getCurlCode(
|
|||
"input_type": "chat",
|
||||
"tweaks": ${JSON.stringify(tweaksObject, null, 2)}'
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@
|
|||
* @returns {string} - The python code
|
||||
*/
|
||||
export default function getPythonApiCode(
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
return `import requests
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
return `import requests
|
||||
from typing import Optional
|
||||
|
||||
|
||||
BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run"
|
||||
FLOW_ID = "${flowId}"
|
||||
# You can tweak the flow by adding a tweaks dictionary
|
||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
|
||||
def run_flow(message: str,
|
||||
flow_id: str,
|
||||
output_type: str = "chat",
|
||||
|
|
@ -28,14 +28,14 @@ export default function getPythonApiCode(
|
|||
api_key: Optional[str] = None) -> dict:
|
||||
"""
|
||||
Run a flow with a given message and optional tweaks.
|
||||
|
||||
|
||||
:param message: The message to send to the flow
|
||||
:param flow_id: The ID of the flow to run
|
||||
:param tweaks: Optional tweaks to customize the flow
|
||||
:return: The JSON response from the flow
|
||||
"""
|
||||
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||
|
||||
|
||||
payload = {
|
||||
"input_value": message,
|
||||
"output_type": output_type,
|
||||
|
|
@ -48,11 +48,11 @@ export default function getPythonApiCode(
|
|||
headers = {"x-api-key": api_key}
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
return response.json()
|
||||
|
||||
|
||||
# Setup any tweaks you want to apply to the flow
|
||||
message = "message"
|
||||
${!isAuth ? `api_key = "<your api key>"` : ""}
|
||||
print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${
|
||||
!isAuth ? `, api_key=api_key` : ""
|
||||
}))`;
|
||||
}
|
||||
!isAuth ? `, api_key=api_key` : ""
|
||||
}))`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@
|
|||
* @param {any[]} tweak - The tweaks
|
||||
* @returns {string} - The python code
|
||||
*/
|
||||
export default function getPythonCode(flowName: string, tweaksBuildedObject): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `from langflow.load import run_flow_from_json
|
||||
export default function getPythonCode(
|
||||
flowName: string,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `from langflow.load import run_flow_from_json
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
|
||||
result = run_flow_from_json(flow="${flowName}.json",
|
||||
input_value="message",
|
||||
fallback_to_env_vars=True, # False by default
|
||||
tweaks=TWEAKS)`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
* @returns {string} - The widget code
|
||||
*/
|
||||
export default function getWidgetCode(
|
||||
flowId: string,
|
||||
flowName: string,
|
||||
isAuth: boolean,
|
||||
): string {
|
||||
return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script>
|
||||
|
||||
flowId: string,
|
||||
flowName: string,
|
||||
isAuth: boolean,
|
||||
): string {
|
||||
return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script>
|
||||
|
||||
<langflow-chat
|
||||
window_title="${flowName}"
|
||||
flow_id="${flowId}"
|
||||
|
|
@ -19,6 +19,6 @@ export default function getWidgetCode(
|
|||
api_key="..."`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
></langflow-chat>`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,6 @@
|
|||
export default function tabsArray(codes: string[], method: number) {
|
||||
if (!method) return;
|
||||
if (method === 0) {
|
||||
return [
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes[0],
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: codes[1],
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[2],
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[3],
|
||||
},
|
||||
];
|
||||
}
|
||||
if (!method) return;
|
||||
if (method === 0) {
|
||||
return [
|
||||
{
|
||||
name: "cURL",
|
||||
|
|
@ -54,8 +20,8 @@ export default function tabsArray(codes: string[], method: number) {
|
|||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
language: "py",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[2],
|
||||
},
|
||||
{
|
||||
|
|
@ -67,12 +33,46 @@ export default function tabsArray(codes: string[], method: number) {
|
|||
language: "py",
|
||||
code: codes[3],
|
||||
},
|
||||
{
|
||||
name: "Tweaks",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[4],
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes[0],
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: codes[1],
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
language: "py",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
code: codes[2],
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[3],
|
||||
},
|
||||
{
|
||||
name: "Tweaks",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[4],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Tabs, TabsList, TabsTrigger } from "../../components/ui/tabs";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { FlowSettingsPropsType } from "../../types/components";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { FlowType, NodeDataType } from "../../types/flow";
|
||||
import BaseModal from "../baseModal";
|
||||
import TableComponent from "../../components/tableComponent";
|
||||
import { getMessagesTable, getTransactionTable } from "../../controllers/API";
|
||||
|
|
@ -12,15 +12,20 @@ import {
|
|||
ColGroupDef,
|
||||
SizeColumnsToFitGridStrategy,
|
||||
} from "ag-grid-community";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
|
||||
export default function FlowLogsModal({
|
||||
open,
|
||||
setOpen,
|
||||
}: FlowSettingsPropsType): JSX.Element {
|
||||
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||
|
||||
useEffect(() => {
|
||||
setName(currentFlow!.name);
|
||||
setDescription(currentFlow!.description);
|
||||
|
|
@ -31,6 +36,7 @@ export default function FlowLogsModal({
|
|||
const [columns, setColumns] = useState<Array<ColDef | ColGroupDef>>([]);
|
||||
const [rows, setRows] = useState<any>([]);
|
||||
const [activeTab, setActiveTab] = useState("Executions");
|
||||
const noticed = useRef(false);
|
||||
|
||||
function handleClick(): void {
|
||||
currentFlow!.name = name;
|
||||
|
|
@ -53,6 +59,25 @@ export default function FlowLogsModal({
|
|||
setRows(rows);
|
||||
});
|
||||
}
|
||||
|
||||
if (open && activeTab === "Messages" && !noticed.current) {
|
||||
const haStream = nodes
|
||||
.map((nodes) => (nodes.data as NodeDataType).node!.template)
|
||||
.some((template) => template["stream"] && template["stream"].value);
|
||||
console.log(
|
||||
haStream,
|
||||
nodes.map((nodes) => (nodes.data as NodeDataType).node!.template),
|
||||
);
|
||||
if (haStream) {
|
||||
setNoticeData({
|
||||
title: "Streamed messages will not appear in this table.",
|
||||
});
|
||||
noticed.current = true;
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
noticed.current = false;
|
||||
}
|
||||
}, [open, activeTab]);
|
||||
|
||||
const [nameLists, setNameList] = useState<string[]>([]);
|
||||
|
|
|
|||
|
|
@ -129,14 +129,14 @@ export default function ShareModal({
|
|||
title: "Error sharing " + is_component ? "component" : "flow",
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
else
|
||||
updateFlowStore(
|
||||
flow!,
|
||||
getTagsIds(selectedTags, tags),
|
||||
sharePublic,
|
||||
unavaliableNames.find((e) => e.name === name)!.id
|
||||
unavaliableNames.find((e) => e.name === name)!.id,
|
||||
).then(successShare, (err) => {
|
||||
setErrorData({
|
||||
title: "Error sharing " + is_component ? "component" : "flow",
|
||||
|
|
@ -287,7 +287,7 @@ export default function ShareModal({
|
|||
className={is_component ? "w-40" : "w-28"}
|
||||
onClick={() => {
|
||||
const isNameAvailable = !unavaliableNames.some(
|
||||
(element) => element.name === name
|
||||
(element) => element.name === name,
|
||||
);
|
||||
|
||||
if (isNameAvailable) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export default function getTagsIds(
|
||||
tags: string[],
|
||||
tagListId: { name: string; id: string }[],
|
||||
) {
|
||||
return tags
|
||||
.map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
|
||||
.map((tag) => tag!.id);
|
||||
tags: string[],
|
||||
tagListId: { name: string; id: string }[],
|
||||
) {
|
||||
return tags
|
||||
.map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
|
||||
.map((tag) => tag!.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,33 +3,33 @@ import { getRandomElement } from "../../../../../utils/reactflowUtils";
|
|||
import { toTitleCase } from "../../../../../utils/utils";
|
||||
|
||||
export default function getRandomName(
|
||||
retry: number = 0,
|
||||
noSpace: boolean = false,
|
||||
maxRetries: number = 3,
|
||||
): string {
|
||||
const left: string[] = ADJECTIVES;
|
||||
const right: string[] = NOUNS;
|
||||
|
||||
const lv = getRandomElement(left);
|
||||
const rv = getRandomElement(right);
|
||||
|
||||
// Condition to avoid "boring wozniak"
|
||||
if (lv === "boring" && rv === "wozniak") {
|
||||
if (retry < maxRetries) {
|
||||
return getRandomName(retry + 1, noSpace, maxRetries);
|
||||
} else {
|
||||
console.warn("Max retries reached, returning as is");
|
||||
}
|
||||
retry: number = 0,
|
||||
noSpace: boolean = false,
|
||||
maxRetries: number = 3,
|
||||
): string {
|
||||
const left: string[] = ADJECTIVES;
|
||||
const right: string[] = NOUNS;
|
||||
|
||||
const lv = getRandomElement(left);
|
||||
const rv = getRandomElement(right);
|
||||
|
||||
// Condition to avoid "boring wozniak"
|
||||
if (lv === "boring" && rv === "wozniak") {
|
||||
if (retry < maxRetries) {
|
||||
return getRandomName(retry + 1, noSpace, maxRetries);
|
||||
} else {
|
||||
console.warn("Max retries reached, returning as is");
|
||||
}
|
||||
|
||||
// Append a suffix if retrying and noSpace is true
|
||||
if (retry > 0 && noSpace) {
|
||||
const retrySuffix = Math.floor(Math.random() * 10);
|
||||
return `${lv}_${rv}${retrySuffix}`;
|
||||
}
|
||||
|
||||
// Construct the final name
|
||||
let final_name = noSpace ? `${lv}_${rv}` : `${lv} ${rv}`;
|
||||
// Return title case final name
|
||||
return toTitleCase(final_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Append a suffix if retrying and noSpace is true
|
||||
if (retry > 0 && noSpace) {
|
||||
const retrySuffix = Math.floor(Math.random() * 10);
|
||||
return `${lv}_${rv}${retrySuffix}`;
|
||||
}
|
||||
|
||||
// Construct the final name
|
||||
let final_name = noSpace ? `${lv}_${rv}` : `${lv} ${rv}`;
|
||||
// Return title case final name
|
||||
return toTitleCase(final_name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ import {
|
|||
nodeIconsLucide,
|
||||
nodeNames,
|
||||
} from "../../../../utils/styleUtils";
|
||||
import {
|
||||
classNames,
|
||||
removeCountFromString,
|
||||
} from "../../../../utils/utils";
|
||||
import { classNames, removeCountFromString } from "../../../../utils/utils";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import ParentDisclosureComponent from "../ParentDisclosureComponent";
|
||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||
|
|
@ -45,7 +42,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
const [search, setSearch] = useState("");
|
||||
function onDragStart(
|
||||
event: React.DragEvent<any>,
|
||||
data: { type: string; node?: APIClassType }
|
||||
data: { type: string; node?: APIClassType },
|
||||
): void {
|
||||
//start drag event
|
||||
var crt = event.currentTarget.cloneNode(true);
|
||||
|
|
@ -71,7 +68,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
let keys = Object.keys(data[d]).filter(
|
||||
(nd) =>
|
||||
nd.toLowerCase().includes(e.toLowerCase()) ||
|
||||
data[d][nd].display_name?.toLowerCase().includes(e.toLowerCase())
|
||||
data[d][nd].display_name?.toLowerCase().includes(e.toLowerCase()),
|
||||
);
|
||||
keys.forEach((element) => {
|
||||
ret[d][element] = data[d][element];
|
||||
|
|
@ -138,7 +135,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
if (filtered.some((x) => x !== "")) {
|
||||
let keys = Object.keys(dataClone[d]).filter((nd) =>
|
||||
filtered.includes(nd)
|
||||
filtered.includes(nd),
|
||||
);
|
||||
Object.keys(dataClone[d]).forEach((element) => {
|
||||
if (!keys.includes(element)) {
|
||||
|
|
@ -175,7 +172,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
if (filtered.some((x) => x !== "")) {
|
||||
let keys = Object.keys(dataClone[d]).filter((nd) =>
|
||||
filtered.includes(nd)
|
||||
filtered.includes(nd),
|
||||
);
|
||||
Object.keys(dataClone[d]).forEach((element) => {
|
||||
if (!keys.includes(element)) {
|
||||
|
|
@ -204,7 +201,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
"extra-side-bar-buttons gap-[4px] text-sm font-semibold",
|
||||
!hasApiKey || !validApiKey || !hasStore
|
||||
? "button-disable cursor-default text-muted-foreground"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
@ -213,14 +210,14 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
"-m-0.5 -ml-1 h-6 w-6",
|
||||
!hasApiKey || !validApiKey || !hasStore
|
||||
? "extra-side-bar-save-disable"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
/>
|
||||
Share
|
||||
</button>
|
||||
</ShareModal>
|
||||
),
|
||||
[hasApiKey, validApiKey, currentFlow, hasStore]
|
||||
[hasApiKey, validApiKey, currentFlow, hasStore],
|
||||
);
|
||||
|
||||
const ExportMemo = useMemo(
|
||||
|
|
@ -231,7 +228,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</button>
|
||||
</ExportModal>
|
||||
),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const getIcon = useMemo(() => {
|
||||
|
|
@ -315,8 +312,8 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
.sort((a, b) =>
|
||||
sensitiveSort(
|
||||
dataFilter[SBSectionName][a].display_name,
|
||||
dataFilter[SBSectionName][b].display_name
|
||||
)
|
||||
dataFilter[SBSectionName][b].display_name,
|
||||
),
|
||||
)
|
||||
.map((SBItemName: string, index) => (
|
||||
<ShadTooltip
|
||||
|
|
@ -360,7 +357,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
)
|
||||
),
|
||||
)}{" "}
|
||||
<ParentDisclosureComponent
|
||||
openDisc={false}
|
||||
|
|
@ -397,8 +394,8 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
.sort((a, b) =>
|
||||
sensitiveSort(
|
||||
dataFilter[SBSectionName][a].display_name,
|
||||
dataFilter[SBSectionName][b].display_name
|
||||
)
|
||||
dataFilter[SBSectionName][b].display_name,
|
||||
),
|
||||
)
|
||||
.map((SBItemName: string, index) => (
|
||||
<ShadTooltip
|
||||
|
|
@ -472,7 +469,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</ParentDisclosureComponent>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
export default function sensitiveSort(a: string, b: string): number {
|
||||
// Extract the name and number from each string using regular expressions
|
||||
const regex = /(.+) \((\w+)\)/;
|
||||
const matchA = a.match(regex);
|
||||
const matchB = b.match(regex);
|
||||
|
||||
if (matchA && matchB) {
|
||||
// Compare the names alphabetically
|
||||
const nameA = matchA[1];
|
||||
const nameB = matchB[1];
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB);
|
||||
}
|
||||
|
||||
// If the names are the same, compare the numbers numerically
|
||||
const numberA = parseInt(matchA[2]);
|
||||
const numberB = parseInt(matchB[2]);
|
||||
return numberA - numberB;
|
||||
} else {
|
||||
// Handle cases where one or both strings do not match the expected pattern
|
||||
// Simple strings are treated as pure alphabetical comparisons
|
||||
return a.localeCompare(b);
|
||||
// Extract the name and number from each string using regular expressions
|
||||
const regex = /(.+) \((\w+)\)/;
|
||||
const matchA = a.match(regex);
|
||||
const matchB = b.match(regex);
|
||||
|
||||
if (matchA && matchB) {
|
||||
// Compare the names alphabetically
|
||||
const nameA = matchA[1];
|
||||
const nameB = matchB[1];
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB);
|
||||
}
|
||||
|
||||
// If the names are the same, compare the numbers numerically
|
||||
const numberA = parseInt(matchA[2]);
|
||||
const numberB = parseInt(matchB[2]);
|
||||
return numberA - numberB;
|
||||
} else {
|
||||
// Handle cases where one or both strings do not match the expected pattern
|
||||
// Simple strings are treated as pure alphabetical comparisons
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import FlowToolbar from "../../components/chatComponent";
|
||||
import Header from "../../components/headerComponent";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
|
|
@ -16,9 +16,19 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
|||
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
|
||||
// Set flow tab id
|
||||
useEffect(() => {
|
||||
const isAnExistingFlow = flows.some((flow) => flow.id === id);
|
||||
|
||||
if (!isAnExistingFlow) {
|
||||
navigate("/all");
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentFlowId(id!);
|
||||
setOnFlowPage(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ export type FolderType = {
|
|||
id?: string | null;
|
||||
parent_id: string;
|
||||
flows: FlowType[];
|
||||
components: string[];
|
||||
};
|
||||
|
||||
export type AddFolderType = {
|
||||
name: string;
|
||||
description: string;
|
||||
id?: string | null;
|
||||
parent_id: string;
|
||||
parent_id: string | null;
|
||||
flows?: string[];
|
||||
components?: string[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { BASE_URL_API } from "../../../constants/constants";
|
||||
import { api } from "../../../controllers/API/api";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
import { AddFolderType, FolderType, StarterProjectsType } from "../entities";
|
||||
import { AddFolderType, FolderType } from "../entities";
|
||||
|
||||
export async function getFolders(): Promise<FolderType[]> {
|
||||
try {
|
||||
|
|
@ -61,15 +61,6 @@ export async function getFolderById(folderId: string): Promise<FolderType> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getStarterProjects(): Promise<StarterProjectsType> {
|
||||
try {
|
||||
const response = await api.get(`${BASE_URL_API}folders/starter-projects`);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
||||
flows: FlowType[];
|
||||
folder_name: string;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const useDarkStore = create<DarkStoreType>((set, get) => ({
|
|||
window.localStorage.setItem("githubStars", res.toString());
|
||||
window.localStorage.setItem(
|
||||
"githubStarsLastUpdated",
|
||||
new Date().toString()
|
||||
new Date().toString(),
|
||||
);
|
||||
set(() => ({ stars: res, lastUpdated: new Date() }));
|
||||
});
|
||||
|
|
@ -11,7 +11,6 @@ import {
|
|||
updateFlowInDatabase,
|
||||
uploadFlowsToDatabase,
|
||||
} from "../controllers/API";
|
||||
import { getStarterProjects } from "../pages/MainPage/services";
|
||||
import { FlowType, NodeDataType } from "../types/flow";
|
||||
import {
|
||||
FlowsManagerStoreType,
|
||||
|
|
@ -81,38 +80,40 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
return new Promise<void>((resolve, reject) => {
|
||||
set({ isLoading: true });
|
||||
|
||||
getStarterProjects().then((starterProjects) => {
|
||||
get().setExamples(starterProjects?.flows!);
|
||||
const starterFolderId = useFolderStore.getState().starterProjectId;
|
||||
|
||||
readFlowsFromDatabase()
|
||||
.then((dbData) => {
|
||||
if (dbData) {
|
||||
const { data, flows } = processFlows(dbData, false);
|
||||
const starterProjectsIds = starterProjects.flows!.map(
|
||||
(flow) => flow.id,
|
||||
);
|
||||
get().setFlows(
|
||||
flows.filter((f) => !starterProjectsIds.includes(f.id)),
|
||||
);
|
||||
useTypesStore.setState((state) => ({
|
||||
data: { ...state.data, ["saved_components"]: data },
|
||||
ComponentFields: extractFieldsFromComponenents({
|
||||
...state.data,
|
||||
["saved_components"]: data,
|
||||
}),
|
||||
}));
|
||||
set({ isLoading: false });
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
readFlowsFromDatabase()
|
||||
.then((dbData) => {
|
||||
if (dbData) {
|
||||
const { data, flows } = processFlows(dbData, false);
|
||||
const examples = flows.filter(
|
||||
(flow) => flow.folder_id === starterFolderId,
|
||||
);
|
||||
get().setExamples(examples);
|
||||
|
||||
const flowsWithoutStarterFolder = flows.filter(
|
||||
(flow) => flow.folder_id !== starterFolderId,
|
||||
);
|
||||
|
||||
get().setFlows(flowsWithoutStarterFolder);
|
||||
useTypesStore.setState((state) => ({
|
||||
data: { ...state.data, ["saved_components"]: data },
|
||||
ComponentFields: extractFieldsFromComponenents({
|
||||
...state.data,
|
||||
["saved_components"]: data,
|
||||
}),
|
||||
}));
|
||||
set({ isLoading: false });
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
});
|
||||
reject(e);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
set({ isLoading: false });
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
});
|
||||
});
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
},
|
||||
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
|
||||
|
|
@ -204,11 +205,16 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
const folder_id = useFolderStore.getState().folderUrl;
|
||||
const my_collection_id = useFolderStore.getState().myCollectionId;
|
||||
|
||||
if (override) {
|
||||
get().deleteComponent(flow!.name);
|
||||
const newFlow = createNewFlow(flowData!, flow!);
|
||||
newFlow.folder_id = useFolderStore.getState().folderUrl;
|
||||
const newFlow = createNewFlow(
|
||||
flowData!,
|
||||
flow!,
|
||||
folder_id || my_collection_id!,
|
||||
);
|
||||
const { id } = await saveFlowToDatabase(newFlow);
|
||||
newFlow.id = id;
|
||||
//setTimeout to prevent update state with wrong state
|
||||
|
|
@ -227,8 +233,12 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
// addFlowToLocalState(newFlow);
|
||||
return;
|
||||
}
|
||||
|
||||
const newFlow = createNewFlow(flowData!, flow!);
|
||||
console.log("folder id", folder_id);
|
||||
const newFlow = createNewFlow(
|
||||
flowData!,
|
||||
flow!,
|
||||
folder_id || my_collection_id!,
|
||||
);
|
||||
|
||||
const newName = addVersionToDuplicates(newFlow, get().flows);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { create } from "zustand";
|
||||
import { DEFAULT_FOLDER } from "../constants/constants";
|
||||
import { DEFAULT_FOLDER, STARTER_FOLDER_NAME } from "../constants/constants";
|
||||
import {
|
||||
getFolderById,
|
||||
getFolders,
|
||||
|
|
@ -11,24 +11,44 @@ import useFlowsManagerStore from "./flowsManagerStore";
|
|||
export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
||||
folders: [],
|
||||
getFoldersApi: (refetch = false) => {
|
||||
if (get()?.folders.length === 0 || refetch === true) {
|
||||
get().setLoading(true);
|
||||
getFolders().then(
|
||||
(res) => {
|
||||
set({ folders: res });
|
||||
const myCollectionId = res?.find(
|
||||
(f) => f.name === DEFAULT_FOLDER,
|
||||
)?.id;
|
||||
set({ myCollectionId });
|
||||
get().setLoading(false);
|
||||
useFlowsManagerStore.getState().refreshFlows();
|
||||
},
|
||||
() => {
|
||||
set({ folders: [] });
|
||||
get().setLoading(false);
|
||||
},
|
||||
);
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (get()?.folders.length === 0 || refetch === true) {
|
||||
get().setLoading(true);
|
||||
getFolders().then(
|
||||
(res) => {
|
||||
const foldersWithoutStarterProjects = res.filter(
|
||||
(folder) => folder.name !== STARTER_FOLDER_NAME,
|
||||
);
|
||||
|
||||
const starterProjects = res.find(
|
||||
(folder) => folder.name === STARTER_FOLDER_NAME,
|
||||
);
|
||||
|
||||
set({ starterProjectId: starterProjects!.id ?? "" });
|
||||
set({ folders: foldersWithoutStarterProjects });
|
||||
|
||||
const myCollectionId = res?.find(
|
||||
(f) => f.name === DEFAULT_FOLDER,
|
||||
)?.id;
|
||||
|
||||
set({ myCollectionId });
|
||||
|
||||
if (refetch === true) {
|
||||
useFlowsManagerStore.getState().refreshFlows();
|
||||
useFlowsManagerStore.getState().setAllFlows;
|
||||
}
|
||||
|
||||
get().setLoading(false);
|
||||
resolve();
|
||||
},
|
||||
() => {
|
||||
set({ folders: [] });
|
||||
get().setLoading(false);
|
||||
reject();
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
setFolders: (folders) => set(() => ({ folders: folders })),
|
||||
loading: false,
|
||||
|
|
@ -93,12 +113,12 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
|||
formData.append("file", file);
|
||||
uploadFlowsFromFolders(formData).then(() => {
|
||||
get().getFoldersApi(true);
|
||||
useFlowsManagerStore.getState().refreshFlows();
|
||||
});
|
||||
useFlowsManagerStore.getState().setAllFlows;
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
},
|
||||
starterProjectId: "",
|
||||
setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
|
||||
}));
|
||||
|
|
|
|||
21
src/frontend/src/stores/locationStore.ts
Normal file
21
src/frontend/src/stores/locationStore.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { create } from "zustand";
|
||||
import { LocationStoreType } from "../types/zustand/location";
|
||||
|
||||
export const useLocationStore = create<LocationStoreType>((set, get) => ({
|
||||
routeHistory: [],
|
||||
setRouteHistory: (location) => {
|
||||
let routeHistoryArray = get().routeHistory;
|
||||
routeHistoryArray.push(location);
|
||||
|
||||
if (routeHistoryArray?.length > 9) {
|
||||
routeHistoryArray.shift();
|
||||
set({
|
||||
routeHistory: routeHistoryArray,
|
||||
});
|
||||
}
|
||||
|
||||
set({
|
||||
routeHistory: routeHistoryArray,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
|
@ -6,6 +6,7 @@ import { ChatMessageType } from "../chat";
|
|||
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
|
||||
import { sourceHandleType, targetHandleType } from "./../flow/index";
|
||||
export type InputComponentType = {
|
||||
name?: string;
|
||||
autoFocus?: boolean;
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
||||
value?: string;
|
||||
|
|
@ -32,6 +33,7 @@ export type InputComponentType = {
|
|||
setSelectedOptions?: (value: string[]) => void;
|
||||
objectOptions?: Array<{ name: string; id: string }>;
|
||||
isObjectOption?: boolean;
|
||||
onChangeFolderName?: (e: any) => void;
|
||||
};
|
||||
export type ToggleComponentType = {
|
||||
enabled: boolean;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { FolderType } from "../../../pages/MainPage/entities";
|
|||
|
||||
export type FoldersStoreType = {
|
||||
folders: FolderType[];
|
||||
getFoldersApi: (refetch?: boolean) => void;
|
||||
getFoldersApi: (refetch?: boolean) => Promise<void>;
|
||||
setFolders: (folders: FolderType[]) => void;
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
|
|
@ -23,4 +23,6 @@ export type FoldersStoreType = {
|
|||
uploadFolder: (folderId: string) => void;
|
||||
folderIdDragging: string;
|
||||
setFolderIdDragging: (id: string) => void;
|
||||
starterProjectId: string;
|
||||
setStarterProjectId: (id: string) => void;
|
||||
};
|
||||
|
|
|
|||
4
src/frontend/src/types/zustand/location/index.ts
Normal file
4
src/frontend/src/types/zustand/location/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export type LocationStoreType = {
|
||||
routeHistory: string[];
|
||||
setRouteHistory: (location: string) => void;
|
||||
};
|
||||
|
|
@ -10,14 +10,14 @@ import { VertexLayerElementType } from "../types/zustand/flow";
|
|||
type BuildVerticesParams = {
|
||||
flowId: string; // Assuming FlowType is the type for your flow
|
||||
input_value?: any; // Replace any with the actual type if it's not any
|
||||
files?:string[];
|
||||
files?: string[];
|
||||
startNodeId?: string | null; // Assuming nodeId is of type string, and it's optional
|
||||
stopNodeId?: string | null; // Assuming nodeId is of type string, and it's optional
|
||||
onGetOrderSuccess?: () => void;
|
||||
onBuildUpdate?: (
|
||||
data: VertexBuildTypeAPI,
|
||||
status: BuildStatus,
|
||||
buildId: string
|
||||
buildId: string,
|
||||
) => void; // Replace any with the actual type if it's not any
|
||||
onBuildComplete?: (allNodesValid: boolean) => void;
|
||||
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
|
||||
|
|
@ -31,8 +31,8 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
|||
// Build VertexBuildTypeAPI
|
||||
let inactiveData = {
|
||||
results: {},
|
||||
logs:[],
|
||||
messages:[],
|
||||
logs: [],
|
||||
messages: [],
|
||||
inactive: true,
|
||||
};
|
||||
let inactiveVertexData = {
|
||||
|
|
@ -55,7 +55,7 @@ export async function updateVerticesOrder(
|
|||
startNodeId?: string | null,
|
||||
stopNodeId?: string | null,
|
||||
nodes?: Node[],
|
||||
edges?: Edge[]
|
||||
edges?: Edge[],
|
||||
): Promise<{
|
||||
verticesLayers: VertexLayerElementType[][];
|
||||
verticesIds: string[];
|
||||
|
|
@ -71,14 +71,13 @@ export async function updateVerticesOrder(
|
|||
startNodeId,
|
||||
stopNodeId,
|
||||
nodes,
|
||||
edges
|
||||
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");
|
||||
}
|
||||
|
|
@ -129,7 +128,7 @@ export async function buildVertices({
|
|||
startNodeId,
|
||||
stopNodeId,
|
||||
nodes,
|
||||
edges
|
||||
edges,
|
||||
);
|
||||
if (onValidateNodes) {
|
||||
try {
|
||||
|
|
@ -191,14 +190,14 @@ export async function buildVertices({
|
|||
onBuildUpdate(
|
||||
getInactiveVertexData(element.id),
|
||||
BuildStatus.INACTIVE,
|
||||
runId
|
||||
runId,
|
||||
);
|
||||
}
|
||||
if (element.reference) {
|
||||
onBuildUpdate(
|
||||
getInactiveVertexData(element.reference),
|
||||
BuildStatus.INACTIVE,
|
||||
runId
|
||||
runId,
|
||||
);
|
||||
}
|
||||
buildResults.push(false);
|
||||
|
|
@ -224,7 +223,7 @@ export async function buildVertices({
|
|||
if (stop) {
|
||||
return;
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
// Once the current layer is built, move to the next layer
|
||||
currentLayerIndex += 1;
|
||||
|
|
@ -253,7 +252,7 @@ async function buildVertex({
|
|||
flowId: string;
|
||||
id: string;
|
||||
input_value: string;
|
||||
files?:string[];
|
||||
files?: string[];
|
||||
onBuildUpdate?: (data: any, status: BuildStatus) => void;
|
||||
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
|
||||
verticesIds: string[];
|
||||
|
|
@ -261,7 +260,7 @@ async function buildVertex({
|
|||
stopBuild: () => void;
|
||||
}) {
|
||||
try {
|
||||
const buildRes = await postBuildVertex(flowId, id, input_value,files);
|
||||
const buildRes = await postBuildVertex(flowId, id, input_value, files);
|
||||
|
||||
const buildData: VertexBuildTypeAPI = buildRes.data;
|
||||
if (onBuildUpdate) {
|
||||
|
|
@ -269,7 +268,7 @@ async function buildVertex({
|
|||
onBuildError!(
|
||||
"Error Building Component",
|
||||
buildData.data.logs.map((log) => log.message),
|
||||
verticesIds.map((id) => ({ id }))
|
||||
verticesIds.map((id) => ({ id })),
|
||||
);
|
||||
stopBuild();
|
||||
}
|
||||
|
|
@ -281,7 +280,7 @@ async function buildVertex({
|
|||
onBuildError!(
|
||||
"Error Building Component",
|
||||
[(error as AxiosError<any>).response?.data?.detail ?? "Unknown Error"],
|
||||
verticesIds.map((id) => ({ id }))
|
||||
verticesIds.map((id) => ({ id })),
|
||||
);
|
||||
stopBuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,7 +350,12 @@ export function updateEdges(edges: Edge[]) {
|
|||
}
|
||||
|
||||
export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
|
||||
const existingNames = flows.map((item) => item.name);
|
||||
console.log("flow", flow);
|
||||
console.log("flows", flows);
|
||||
const existingNames = flows
|
||||
.filter((f) => f.folder_id === flow.folder_id)
|
||||
.map((item) => item.name);
|
||||
console.log("existingNames", existingNames);
|
||||
let newName = flow.name;
|
||||
let count = 1;
|
||||
|
||||
|
|
@ -1297,6 +1302,7 @@ export function getRandomDescription(): string {
|
|||
export const createNewFlow = (
|
||||
flowData: ReactFlowJsonObject,
|
||||
flow: FlowType,
|
||||
folderId: string,
|
||||
) => {
|
||||
return {
|
||||
description: flow?.description ?? getRandomDescription(),
|
||||
|
|
@ -1304,7 +1310,7 @@ export const createNewFlow = (
|
|||
data: flowData,
|
||||
id: "",
|
||||
is_component: flow?.is_component ?? false,
|
||||
folder_id: "",
|
||||
folder_id: folderId,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,101 +1,10 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
test("user must interact with chat with Input/Output", async ({ page }) => {
|
||||
if (!process.env.CI) {
|
||||
dotenv.config();
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
||||
}
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
let modalCount = 0;
|
||||
try {
|
||||
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||
if (modalTitleElement) {
|
||||
modalCount = await modalTitleElement.count();
|
||||
}
|
||||
} catch (error) {
|
||||
modalCount = 0;
|
||||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
//You must set the OPENAI_API_KEY on .env file to run this test
|
||||
expect(false).toBe(true);
|
||||
}
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-openai_api_key")
|
||||
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||
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();
|
||||
let valueAI = await page.getByTestId("sender_name_ai").textContent();
|
||||
|
||||
expect(valueUser).toBe("User");
|
||||
expect(valueAI).toBe("AI");
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page
|
||||
.getByTestId("textarea-input_value")
|
||||
.nth(1)
|
||||
.fill(
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
|
||||
);
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-sender_name")
|
||||
.nth(1)
|
||||
.fill("TestSenderNameUser");
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-sender_name")
|
||||
.nth(0)
|
||||
.fill("TestSenderNameAI");
|
||||
|
||||
await page.getByText("Playground", { exact: true }).click();
|
||||
await page.getByTestId("icon-LucideSend").click();
|
||||
|
||||
valueUser = await page
|
||||
.getByTestId("sender_name_testsendernameuser")
|
||||
.textContent();
|
||||
valueAI = await page
|
||||
.getByTestId("sender_name_testsendernameai")
|
||||
.textContent();
|
||||
|
||||
expect(valueUser).toBe("TestSenderNameUser");
|
||||
expect(valueAI).toBe("TestSenderNameAI");
|
||||
|
||||
expect(
|
||||
await page
|
||||
.getByText(
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
|
||||
{ exact: true },
|
||||
)
|
||||
.isVisible(),
|
||||
);
|
||||
});
|
||||
|
||||
test("chat_io_teste", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.locator("span").filter({ hasText: "My Collection" }).isVisible();
|
||||
await page.waitForTimeout(3000);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
let modalCount = 0;
|
||||
try {
|
||||
|
|
|
|||
92
src/frontend/tests/end-to-end/chatInputOutputUser.spec.ts
Normal file
92
src/frontend/tests/end-to-end/chatInputOutputUser.spec.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("user must interact with chat with Input/Output", async ({ page }) => {
|
||||
if (!process.env.CI) {
|
||||
dotenv.config();
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
||||
}
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
let modalCount = 0;
|
||||
try {
|
||||
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||
if (modalTitleElement) {
|
||||
modalCount = await modalTitleElement.count();
|
||||
}
|
||||
} catch (error) {
|
||||
modalCount = 0;
|
||||
}
|
||||
|
||||
while (modalCount === 0) {
|
||||
await page.getByText("New Project", { exact: true }).click();
|
||||
await page.waitForTimeout(5000);
|
||||
modalCount = await page.getByTestId("modal-title")?.count();
|
||||
}
|
||||
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
//You must set the OPENAI_API_KEY on .env file to run this test
|
||||
expect(false).toBe(true);
|
||||
}
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-openai_api_key")
|
||||
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||
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();
|
||||
let valueAI = await page.getByTestId("sender_name_ai").textContent();
|
||||
|
||||
expect(valueUser).toBe("User");
|
||||
expect(valueAI).toBe("AI");
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page
|
||||
.getByTestId("textarea-input_value")
|
||||
.nth(1)
|
||||
.fill(
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
|
||||
);
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-sender_name")
|
||||
.nth(1)
|
||||
.fill("TestSenderNameUser");
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-sender_name")
|
||||
.nth(0)
|
||||
.fill("TestSenderNameAI");
|
||||
|
||||
await page.getByText("Playground", { exact: true }).click();
|
||||
await page.getByTestId("icon-LucideSend").click();
|
||||
|
||||
valueUser = await page
|
||||
.getByTestId("sender_name_testsendernameuser")
|
||||
.textContent();
|
||||
valueAI = await page
|
||||
.getByTestId("sender_name_testsendernameai")
|
||||
.textContent();
|
||||
|
||||
expect(valueUser).toBe("TestSenderNameUser");
|
||||
expect(valueAI).toBe("TestSenderNameAI");
|
||||
|
||||
expect(
|
||||
await page
|
||||
.getByText(
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
|
||||
{ exact: true },
|
||||
)
|
||||
.isVisible(),
|
||||
);
|
||||
});
|
||||
|
|
@ -43,7 +43,7 @@ test("GlobalVariables", async ({ page }) => {
|
|||
const genericName = Math.random().toString();
|
||||
const credentialName = Math.random().toString();
|
||||
|
||||
await page.getByTestId("icon-Globe").nth(1).click();
|
||||
await page.getByTestId("icon-Globe").nth(0).click();
|
||||
await page.getByText("Add New Variable", { exact: true }).click();
|
||||
await page
|
||||
.getByPlaceholder("Insert a name for the variable...")
|
||||
|
|
@ -69,6 +69,9 @@ test("GlobalVariables", async ({ page }) => {
|
|||
await page.getByText("Save Variable", { exact: true }).click();
|
||||
expect(page.getByText(credentialName, { exact: true })).not.toBeNull();
|
||||
await page.getByText(credentialName, { exact: true }).isVisible();
|
||||
await page.getByText("Save Variable", { exact: true }).click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.getByText(credentialName, { exact: true })
|
||||
.hover()
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ test("InputListComponent", async ({ page }) => {
|
|||
|
||||
await page.waitForTimeout(1000);
|
||||
await page
|
||||
.getByTestId("vectorsearchAstra DB Search")
|
||||
.getByTestId("vectorstoresAstra DB")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue