fix zustand

This commit is contained in:
cristhianzl 2024-03-28 11:59:32 -03:00
commit f13d7ecab4
119 changed files with 2517 additions and 2077 deletions

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,7 @@
"base64-js": "^1.5.1",
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"dompurify": "^3.0.5",
"esbuild": "^0.17.19",
"framer-motion": "^11.0.6",

View file

@ -15,11 +15,12 @@ import {
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { AuthContext } from "./contexts/authContext";
import { getHealth } from "./controllers/API";
import { getGlobalVariables, getHealth } from "./controllers/API";
import Router from "./routes";
import useAlertStore from "./stores/alertStore";
import { useDarkStore } from "./stores/darkStore";
import useFlowsManagerStore from "./stores/flowsManagerStore";
import { useGlobalVariablesStore } from "./stores/globalVariables";
import { useStoreStore } from "./stores/storeStore";
import { useTypesStore } from "./stores/typesStore";
@ -43,6 +44,9 @@ export default function App() {
const getTypes = useTypesStore((state) => state.getTypes);
const refreshVersion = useDarkStore((state) => state.refreshVersion);
const refreshStars = useDarkStore((state) => state.refreshStars);
const setGlobalVariables = useGlobalVariablesStore(
(state) => state.setGlobalVariables
);
const checkHasStore = useStoreStore((state) => state.checkHasStore);
const navigate = useNavigate();
@ -58,6 +62,9 @@ export default function App() {
getTypes().then(() => {
refreshFlows();
});
getGlobalVariables().then((res) => {
setGlobalVariables(res);
});
checkHasStore();
fetchApiData();
}

View file

@ -6,9 +6,9 @@ import CodeAreaComponent from "../../../../components/codeAreaComponent";
import DictComponent from "../../../../components/dictComponent";
import Dropdown from "../../../../components/dropdownComponent";
import FloatComponent from "../../../../components/floatComponent";
import IconComponent from "../../../../components/genericIconComponent";
import InputComponent from "../../../../components/inputComponent";
import { default as IconComponent } from "../../../../components/genericIconComponent";
import InputFileComponent from "../../../../components/inputFileComponent";
import InputGlobalComponent from "../../../../components/inputGlobalComponent";
import InputListComponent from "../../../../components/inputListComponent";
import IntComponent from "../../../../components/intComponent";
import KeypairListComponent from "../../../../components/keypairListComponent";
@ -71,6 +71,7 @@ export default function ParameterComponent({
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
const [isLoading, setIsLoading] = useState(false);
const flow = currentFlow?.data?.nodes ?? null;
@ -400,9 +401,8 @@ export default function ParameterComponent({
<>
<div
className={
"w-full truncate text-sm" +
(left ? "" : " flex items-center justify-end gap-2") +
(info !== "" ? " flex items-center" : "")
"flex w-full items-center truncate text-sm" +
(left ? "" : " justify-end")
}
>
{!left && data.node?.frozen && (
@ -559,12 +559,21 @@ export default function ParameterComponent({
(data.node?.template[name].refresh_button ? "w-5/6" : "")
}
>
<InputComponent
id={"input-" + name}
<InputGlobalComponent
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
setDb={(value) => {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name].load_from_db = value;
return newNode;
});
}}
name={name}
data={data}
/>
</div>
{data.node?.template[name].refresh_button && (

View file

@ -182,9 +182,16 @@ export default function IOView({
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{node.data.node.display_name}
</Badge>
<ShadTooltip
content={input.id}
styleClasses="z-50"
>
<div>
<Badge variant="gray" size="md">
{node.data.node.display_name}
</Badge>
</div>
</ShadTooltip>
<div
className="-mb-1 pr-4"
onClick={(event) => {

View file

@ -0,0 +1,99 @@
import { useState } from "react";
import { registerGlobalVariable } from "../../controllers/API";
import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariables";
import { ResponseErrorDetailAPI } from "../../types/api";
import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Textarea } from "../ui/textarea";
//TODO IMPLEMENT FORM LOGIC
export default function AddNewVariableButton({ children }): JSX.Element {
const [key, setKey] = useState("");
const [value, setValue] = useState("");
const [type, setType] = useState("");
const [open, setOpen] = useState(false);
const setErrorData = useAlertStore((state) => state.setErrorData);
const addGlobalVariable = useGlobalVariablesStore(
(state) => state.addGlobalVariable
);
function handleSaveVariable() {
let data: { name: string; value: string; type?: string } = {
name: key,
type,
value,
};
registerGlobalVariable(data)
.then((res) => {
const { name, id, type } = res.data;
addGlobalVariable(name, id, type);
setKey("");
setValue("");
setType("");
setOpen(false);
})
.catch((error) => {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error creating variable",
list: [responseError.response.data.detail ?? "Unknown error"],
});
});
}
return (
<BaseModal open={open} setOpen={setOpen} size="x-small">
<BaseModal.Header
description={
"This variable will be encrypted and will be available for you to use in any of your projects."
}
>
<span className="pr-2"> Create Variable </span>
<ForwardedIconComponent
name="Globe"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Content>
<div className="flex h-full w-full flex-col gap-4 align-middle">
<Label>Variable Name</Label>
<Input
value={key}
onChange={(e) => {
setKey(e.target.value);
}}
placeholder="Insert a name for the variable..."
></Input>
<Label>Type (optional)</Label>
<InputComponent
setSelectedOption={(e) => {
setType(e);
}}
selectedOption={type}
password={false}
options={["Variable", "Credential"]}
placeholder="Choose a type for the variable..."
></InputComponent>
<Label>Value</Label>
<Textarea
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
placeholder="Insert a value for the variable..."
className="w-full resize-none custom-scroll"
/>
</div>
</BaseModal.Content>
<BaseModal.Footer>
<Button onClick={handleSaveVariable}>Save variable</Button>
</BaseModal.Footer>
</BaseModal>
);
}

View file

@ -250,7 +250,7 @@ export default function CollectionCardComponent({
>
<Button
variant="ghost"
size="xs"
size="icon"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
@ -275,7 +275,7 @@ export default function CollectionCardComponent({
<Button
disabled={loadingLike}
variant="ghost"
size="xs"
size="icon"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
@ -312,7 +312,7 @@ export default function CollectionCardComponent({
<Button
disabled={loading}
variant="ghost"
size="xs"
size="icon"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "") +

View file

@ -6,7 +6,6 @@ import AccordionComponent from "../../components/AccordionComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
import InputComponent from "../../components/inputComponent";
import InputFileComponent from "../../components/inputFileComponent";
import InputListComponent from "../../components/inputListComponent";
import IntComponent from "../../components/intComponent";
@ -37,8 +36,10 @@ import {
hasDuplicateKeys,
} from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import ShadTooltip from "../ShadTooltipComponent";
import DictComponent from "../dictComponent";
import IconComponent from "../genericIconComponent";
import InputGlobalComponent from "../inputGlobalComponent";
import KeypairListComponent from "../keypairListComponent";
export default function CodeTabsComponent({
@ -54,6 +55,7 @@ export default function CodeTabsComponent({
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
const dark = useDarkStore((state) => state.dark);
const unselectAll = useFlowStore((state) => state.unselectAll);
const setNode = useFlowStore((state) => state.setNode);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
@ -215,14 +217,22 @@ export default function CodeTabsComponent({
node["data"]["id"]
) && (
<AccordionComponent
trigger={node["data"]["id"]}
trigger={
<ShadTooltip
side="top"
styleClasses="z-50"
content={node["data"]["id"]}
>
<div>{node["data"]["node"]["display_name"]}</div>
</ShadTooltip>
}
open={openAccordion}
keyValue={node["data"]["id"]}
>
<div className="api-modal-table-arrangement">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="h-10 border-input text-xs font-medium text-ring">
<TableRow className="dark:border-b-muted">
<TableRow className="">
<TableHead className="h-7 text-center">
PARAM
</TableHead>
@ -245,10 +255,7 @@ export default function CodeTabsComponent({
)
.map((templateField, indx) => {
return (
<TableRow
key={indx}
className="h-10 dark:border-b-muted"
>
<TableRow key={indx} className="h-10">
<TableCell className="p-0 text-center text-sm text-foreground">
{templateField}
</TableCell>
@ -347,38 +354,31 @@ export default function CodeTabsComponent({
/>
</div>
) : (
<InputComponent
<InputGlobalComponent
editNode={true}
disabled={false}
password={
node.data.node.template[
templateField
].password ?? false
}
value={
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: node.data.node
.template[
templateField
].value
}
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList![
i
].data.node.template[
templateField
].value = target;
return newInputList;
});
if (node.data) {
setNode(
node.data.id,
(oldNode) => {
let newNode =
cloneDeep(
oldNode
);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[
templateField
].value = target;
return newNode;
}
);
}
tweaks.buildTweakObject!(
node["data"]["id"],
target,
@ -387,6 +387,25 @@ export default function CodeTabsComponent({
]
);
}}
setDb={(value) => {
setNode(
node.data.id,
(oldNode) => {
let newNode =
cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[
templateField
].load_from_db =
value;
return newNode;
}
);
}}
name={templateField}
data={node.data}
/>
)}
</div>
@ -505,7 +524,6 @@ export default function CodeTabsComponent({
<div className="mx-auto">
<Dropdown
editNode={true}
apiModal={true}
options={
node.data.node.template[
templateField

View file

@ -1,8 +1,21 @@
import { Listbox, Transition } from "@headlessui/react";
import { Fragment, useEffect, useState } from "react";
import { useRef, useState } from "react";
import { DropDownComponentType } from "../../types/components";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
import { cn } from "../../utils/utils";
import { default as ForwardedIconComponent } from "../genericIconComponent";
import { Button } from "../ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../ui/command";
import {
Popover,
PopoverContentWithoutPortal,
PopoverTrigger,
} from "../ui/popover";
export default function Dropdown({
disabled,
@ -11,124 +24,77 @@ export default function Dropdown({
options,
onSelect,
editNode = false,
numberOfOptions = 0,
apiModal = false,
id = "",
}: DropDownComponentType): JSX.Element {
let [internalValue, setInternalValue] = useState(
value === "" || !value ? "Choose an option" : value
);
const [open, setOpen] = useState(false);
useEffect(() => {
setInternalValue(value === "" || !value ? "Choose an option" : value);
}, [value]);
const refButton = useRef<HTMLButtonElement>(null);
return (
<>
{Object.keys(options)?.length > 0 ? (
<>
<Listbox
value={internalValue}
disabled={disabled}
onChange={(value) => {
setInternalValue(value);
onSelect(value);
}}
>
{({ open }) => (
<>
<div className={"relative mt-1"}>
<Listbox.Button
data-test={`${id ?? ""}`}
className={
editNode
? "dropdown-component-outline"
: "dropdown-component-false-outline"
}
>
<span
className="dropdown-component-display"
data-testid={`${id ?? ""}-display`}
>
{internalValue}
</span>
<span className={"dropdown-component-arrow"}>
<IconComponent
name="ChevronsUpDown"
className="dropdown-component-arrow-color"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options
className={classNames(
editNode
? "dropdown-component-true-options nowheel custom-scroll"
: "dropdown-component-false-options nowheel custom-scroll",
apiModal ? "mb-2 w-[250px]" : "absolute w-full"
)}
>
{options?.map((option, id) => (
<Listbox.Option
key={id}
className={({ active }) =>
classNames(
active ? " bg-accent" : "",
editNode
? "dropdown-component-false-option"
: "dropdown-component-true-option"
)
}
value={option}
>
{({ selected, active }) => (
<>
<span
className={classNames(
selected ? "font-semibold" : "font-normal",
"block truncate "
)}
data-testid={`${option}-${id ?? ""}-option`}
>
{option}
</span>
{selected ? (
<span
className={classNames(
active ? "text-background " : "",
"dropdown-component-choosal"
)}
>
<IconComponent
name="Check"
className={
active
? "dropdown-component-check-icon"
: "dropdown-component-check-icon"
}
aria-hidden="true"
/>
</span>
) : null}
</>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
disabled={disabled}
variant="primary"
size="xs"
role="combobox"
ref={refButton}
aria-expanded={open}
data-test={`${id ?? ""}`}
className={cn(
editNode
? "dropdown-component-outline"
: "dropdown-component-false-outline",
"w-full justify-between font-normal",
editNode ? "input-edit-node" : "py-2"
)}
>
{value
? options.find((option) => option === value)
: "Choose an option..."}
<ForwardedIconComponent
name="ChevronsUpDown"
className="ml-2 h-4 w-4 shrink-0 opacity-50"
/>
</Button>
</PopoverTrigger>
<PopoverContentWithoutPortal
className="nocopy nowheel nopan nodelete nodrag noundo w-full p-0"
style={{ minWidth: refButton?.current?.clientWidth ?? "200px" }}
>
<Command>
<CommandInput placeholder="Search options..." className="h-9" />
<CommandList>
<CommandEmpty>No values found.</CommandEmpty>
<CommandGroup defaultChecked={false}>
{options?.map((option, id) => (
<CommandItem
key={id}
value={option}
onSelect={(currentValue) => {
onSelect(currentValue);
setOpen(false);
}}
data-testid={`${option}-${id ?? ""}-option`}
>
{option}
<ForwardedIconComponent
name="Check"
className={cn(
"ml-auto h-4 w-4 text-primary",
value === option ? "opacity-100" : "opacity-0"
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</Popover>
</>
) : (
<>

View file

@ -1,15 +1,25 @@
import * as Form from "@radix-ui/react-form";
import { PopoverAnchor } from "@radix-ui/react-popover";
import { useEffect, useRef, useState } from "react";
import useAlertStore from "../../stores/alertStore";
import { InputComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import { classNames, cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "../ui/command";
import { Input } from "../ui/input";
import { Popover, PopoverContentWithoutPortal } from "../ui/popover";
export default function InputComponent({
autoFocus = false,
onBlur,
value,
value = "",
onChange,
disabled,
required = false,
@ -20,17 +30,30 @@ export default function InputComponent({
className,
id = "",
blurOnEnter = false,
optionsIcon = "ChevronsUpDown",
selectedOption,
setSelectedOption,
options = [],
optionsPlaceholder = "Search options...",
optionsButton,
optionButton,
}: InputComponentType): JSX.Element {
const setErrorData = useAlertStore.getState().setErrorData;
const [pwdVisible, setPwdVisible] = useState(false);
const refInput = useRef<HTMLInputElement>(null);
const [showOptions, setShowOptions] = useState<boolean>(false);
// Clear component state
useEffect(() => {
if (disabled && value !== "") {
if (disabled && value && onChange && value !== "") {
onChange("");
}
}, [disabled]);
function onInputLostFocus(event): void {
if (onBlur) onBlur(event);
}
return (
<div className="relative w-full">
{isForm ? (
@ -38,7 +61,7 @@ export default function InputComponent({
<Input
id={"form-" + id}
ref={refInput}
onBlur={onBlur}
onBlur={onInputLostFocus}
autoFocus={autoFocus}
type={password && !pwdVisible ? "password" : "text"}
value={value}
@ -55,7 +78,7 @@ export default function InputComponent({
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
onChange(e.target.value);
onChange && onChange(e.target.value);
}}
onCopy={(e) => {
e.preventDefault();
@ -67,54 +90,168 @@ export default function InputComponent({
/>
</Form.Control>
) : (
<Input
id={id}
ref={refInput}
type="text"
onBlur={onBlur}
value={value}
autoFocus={autoFocus}
disabled={disabled}
required={required}
className={classNames(
password && !pwdVisible && value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "",
className!
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
// if the user copies a password from another input
// it might come as ••••••••••• it causes errors
// in ascii encoding, so we need to handle it
if (password && e.target.value.length > 0) {
// check if all chars are •
if (e.target.value.split("").every((char) => char === "•")) {
setErrorData({
title: `Invalid characters: ${e.target.value}`,
list: [
"It seems you are trying to paste a password. Make sure the value is visible before copying from another field.",
],
});
}
<>
<Popover modal open={showOptions} onOpenChange={setShowOptions}>
<PopoverAnchor>
<Input
id={id}
ref={refInput}
type="text"
onBlur={onInputLostFocus}
value={
selectedOption !== "" || !onChange ? selectedOption : value
}
autoFocus={autoFocus}
disabled={disabled}
onClick={() => {
(selectedOption !== "" || !onChange) && setShowOptions(true);
}}
required={required}
className={classNames(
password &&
selectedOption === "" &&
!pwdVisible &&
value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && selectedOption === "" && editNode ? "pr-8" : "",
password && selectedOption === "" && !editNode ? "pr-10" : "",
className!
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
// if the user copies a password from another input
// it might come as ••••••••••• it causes errors
// in ascii encoding, so we need to handle it
if (password) {
// check if all chars are •
if (
e.target.value.split("").every((char) => char === "•") &&
e.target.value !== ""
) {
setErrorData({
title: `Invalid characters: ${e.target.value}`,
list: [
"It seems you are trying to paste a password. Make sure the value is visible before copying from another field.",
],
});
}
}
onChange && onChange(e.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter")
refInput.current?.blur();
}}
data-testid={editNode ? id + "-edit" : id}
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"
>
<Command
filter={(value, search) => {
if (value.includes(search) || value.includes("doNotFilter-"))
return 1; // ensures items arent filtered
return 0;
}}
>
<CommandInput placeholder={optionsPlaceholder} />
<CommandList>
<CommandGroup defaultChecked={false}>
{options.map((option, id) => (
<CommandItem
className="group"
key={option + id}
value={option}
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption
? ""
: currentValue
);
setShowOptions(false);
}}
>
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
<ForwardedIconComponent
name="Check"
className={cn(
"mr-2 h-4 w-4 text-primary",
selectedOption === option
? "opacity-100"
: "opacity-0"
)}
aria-hidden="true"
/>
{option}
</div>
{optionButton && optionButton(option)}
</div>
</CommandItem>
))}
{optionsButton && optionsButton}
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</Popover>
<div
className={cn(
"pointer-events-auto absolute inset-y-0 h-full w-full cursor-pointer",
selectedOption !== "" || !onChange ? "" : "hidden"
)}
onClick={
selectedOption !== "" || !onChange
? (e) => {
setShowOptions((old) => !old);
e.preventDefault();
e.stopPropagation();
}
: () => {}
}
onChange(e.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
data-testid={editNode ? id + "-edit" : id}
/>
></div>
</>
)}
{password && (
<span
className={cn(
password && selectedOption === "" ? "right-8" : "right-0",
"absolute inset-y-0 flex items-center pr-2.5"
)}
>
<button
onClick={() => {
setShowOptions(!showOptions);
}}
className={cn(
selectedOption !== ""
? "text-medium-indigo"
: "text-muted-foreground",
"hover:text-accent-foreground"
)}
>
<ForwardedIconComponent
name={optionsIcon}
className={"h-4 w-4"}
aria-hidden="true"
/>
</button>
</span>
{password && selectedOption === "" && (
<button
type="button"
tabIndex={-1}
className={classNames(
"mb-px",
editNode
? "input-component-true-button"
: "input-component-false-button"
@ -125,6 +262,7 @@ export default function InputComponent({
}}
>
{password &&
selectedOption === "" &&
(pwdVisible ? (
<svg
xmlns="http://www.w3.org/2000/svg"

View file

@ -0,0 +1,134 @@
import { useEffect } from "react";
import { deleteGlobalVariable } from "../../controllers/API";
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
import useAlertStore from "../../stores/alertStore";
import { useGlobalVariablesStore } from "../../stores/globalVariables";
import { ResponseErrorDetailAPI } from "../../types/api";
import { InputGlobalComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton";
import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent";
import { CommandItem } from "../ui/command";
export default function InputGlobalComponent({
disabled,
onChange,
setDb,
name,
data,
editNode = false,
}: InputGlobalComponentType): JSX.Element {
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries
);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable
);
const setErrorData = useAlertStore((state) => state.setErrorData);
useEffect(() => {
if (data.node?.template[name])
if (
!globalVariablesEntries.includes(data.node?.template[name].value) &&
data.node?.template[name].load_from_db
) {
onChange("");
setDb(false);
}
}, [globalVariablesEntries]);
function handleDelete(key: string) {
const id = getVariableId(key);
if (id !== undefined) {
deleteGlobalVariable(id)
.then((_) => {
removeGlobalVariable(key);
if (
data?.node?.template[name].value === key &&
data?.node?.template[name].load_from_db
) {
onChange("");
setDb(false);
}
})
.catch((error) => {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error deleting variable",
list: [responseError.response.data.detail ?? "Unknown error"],
});
});
} else {
setErrorData({
title: "Error deleting variable",
list: [cn("ID not found for variable: ", key)],
});
}
}
return (
<InputComponent
id={"input-" + name}
editNode={editNode}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
options={globalVariablesEntries}
optionsPlaceholder={"Global Variables"}
optionsIcon="Globe"
optionsButton={
<AddNewVariableButton>
<CommandItem value="doNotFilter-addNewVariable">
<ForwardedIconComponent
name="Plus"
className={cn("mr-2 h-4 w-4 text-primary")}
aria-hidden="true"
/>
<span>Add New Variable</span>
</CommandItem>
</AddNewVariableButton>
}
optionButton={(option) => (
<DeleteConfirmationModal
onConfirm={(e) => {
e.stopPropagation();
e.preventDefault();
handleDelete(option);
}}
description={'variable "' + option + '"'}
asChild
>
<button
onClick={(e) => {
e.stopPropagation();
}}
className="pr-1"
>
<ForwardedIconComponent
name="Trash2"
className={cn(
"h-4 w-4 text-primary opacity-0 hover:text-status-red group-hover:opacity-100"
)}
aria-hidden="true"
/>
</button>
</DeleteConfirmationModal>
)}
selectedOption={
data?.node?.template[name].load_from_db ?? false
? data?.node?.template[name].value
: ""
}
setSelectedOption={(value) => {
onChange(value);
setDb(value !== "" ? true : false);
}}
onChange={(value) => {
onChange(value);
setDb(false);
}}
/>
);
}

View file

@ -23,8 +23,9 @@ const buttonVariants = cva(
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
xs: "py-1 px-1 rounded-md",
xs: "py-0.5 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "py-1 px-1 rounded-md",
},
},
defaultVariants: {

View file

@ -117,7 +117,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
className
)}
{...props}

View file

@ -27,4 +27,21 @@ const PopoverContent = React.forwardRef<
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverContent, PopoverTrigger };
const PopoverContentWithoutPortal = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
));
PopoverContentWithoutPortal.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverContent, PopoverContentWithoutPortal, PopoverTrigger };

View file

@ -859,6 +859,50 @@ export async function requestLogout() {
}
}
export async function getGlobalVariables(): Promise<{
[key: string]: { id: string; type: string };
}> {
const globalVariables = {};
(await api.get(`${BASE_URL_API}variables/`)).data.forEach((element) => {
globalVariables[element.name] = {
id: element.id,
type: element.type,
};
});
return globalVariables;
}
export async function registerGlobalVariable({
name,
value,
type,
}: {
name: string;
value: string;
type?: string;
}): Promise<AxiosResponse<{ name: string; id: string; type: string }>> {
return await api.post(`${BASE_URL_API}variables/`, {
name,
value,
type,
});
}
export async function deleteGlobalVariable(id: string) {
api.delete(`${BASE_URL_API}variables/${id}`);
}
export async function updateGlobalVariable(
name: string,
value: string,
id: string
) {
api.patch(`${BASE_URL_API}variables/${id}`, {
name,
value,
});
}
export async function getVerticesOrder(
flowId: string,
startNodeId?: string | null,

View file

@ -1,16 +1,28 @@
const AstraSVG = (props) => (
<svg width="96" height="96" viewBox="12 33 72 29" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clip-path="url(#clip0_702_1449)">
{/* <rect width="96" height="96" rx="6" fill="white"/> */}
<path d="M38.0469 33H12V62.1892H38.0469L44.5902 57.1406V38.0485L38.0469 33ZM17.0478 38.0485H39.5424V57.1459H17.0478V38.0485Z" fill="black"/>
<path d="M82.0705 38.2605V33.3243H58.2546L51.788 38.2605V45.038L58.2546 49.9742H79.0107V56.9286H53.076V61.8648H77.5334L84 56.9286V49.9742L77.5334 45.038H56.7772V38.2605H82.0705Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_702_1449">
<rect width="96" height="96" fill="white"/>
</clipPath>
</defs>
</svg>
<svg
width="96"
height="96"
viewBox="12 33 72 29"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g clip-path="url(#clip0_702_1449)">
{/* <rect width="96" height="96" rx="6" fill="white"/> */}
<path
d="M38.0469 33H12V62.1892H38.0469L44.5902 57.1406V38.0485L38.0469 33ZM17.0478 38.0485H39.5424V57.1459H17.0478V38.0485Z"
fill="black"
/>
<path
d="M82.0705 38.2605V33.3243H58.2546L51.788 38.2605V45.038L58.2546 49.9742H79.0107V56.9286H53.076V61.8648H77.5334L84 56.9286V49.9742L77.5334 45.038H56.7772V38.2605H82.0705Z"
fill="black"
/>
</g>
<defs>
<clipPath id="clip0_702_1449">
<rect width="96" height="96" fill="white" />
</clipPath>
</defs>
</svg>
);
export default AstraSVG;

View file

@ -225,7 +225,7 @@ const ApiModal = forwardRef(
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
<span className="pr-2">Code</span>
<span className="pr-2">API</span>
<IconComponent
name="Code2"
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"

View file

@ -14,14 +14,18 @@ export default function DeleteConfirmationModal({
children,
onConfirm,
description,
asChild,
}: {
children: JSX.Element;
onConfirm: () => void;
onConfirm: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
description?: string;
asChild?: boolean;
}) {
return (
<Dialog>
<DialogTrigger tabIndex={-1}>{children}</DialogTrigger>
<DialogTrigger asChild={asChild} tabIndex={-1}>
{children}
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
@ -39,16 +43,21 @@ export default function DeleteConfirmationModal({
Note: This action is irreversible.
</span>
<DialogFooter>
<DialogClose>
<Button className="mr-3" variant="outline">
<DialogClose asChild>
<Button
onClick={(e) => e.stopPropagation()}
className="mr-3"
variant="outline"
>
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button
type="submit"
variant="destructive"
onClick={() => {
onConfirm();
onClick={(e) => {
onConfirm(e);
}}
>
Delete

View file

@ -6,8 +6,8 @@ import DictComponent from "../../components/dictComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
import IconComponent from "../../components/genericIconComponent";
import InputComponent from "../../components/inputComponent";
import InputFileComponent from "../../components/inputFileComponent";
import InputGlobalComponent from "../../components/inputGlobalComponent";
import InputListComponent from "../../components/inputListComponent";
import IntComponent from "../../components/intComponent";
import KeypairListComponent from "../../components/keypairListComponent";
@ -28,7 +28,9 @@ import {
LANGFLOW_SUPPORTED_TYPES,
limitScrollFieldsModal,
} from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import { useGlobalVariablesStore } from "../../stores/globalVariables";
import { NodeDataType } from "../../types/flow";
import {
convertObjToArray,
@ -58,6 +60,10 @@ const EditNodeModal = forwardRef(
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries
);
function changeAdvanced(n) {
setMyData((old) => {
@ -254,28 +260,23 @@ const EditNodeModal = forwardRef(
}}
/>
) : (
<InputComponent
id={
"input-" +
myData.node.template[templateParam]
.name
}
editNode={true}
<InputGlobalComponent
disabled={disabled}
password={
myData.node.template[templateParam]
.password ?? false
editNode={true}
onChange={(value) =>
handleOnNewValue(value, templateParam)
}
value={
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value) => {
handleOnNewValue(
value,
templateParam
);
setDb={(value) => {
setMyData((oldData) => {
let newData = cloneDeep(oldData);
newData.node!.template[
templateParam
].load_from_db = value;
return newData;
});
}}
name={templateParam}
data={myData}
/>
)}
</div>
@ -403,7 +404,6 @@ const EditNodeModal = forwardRef(
.options ? (
<div className="mx-auto">
<Dropdown
numberOfOptions={nodeLength}
editNode={true}
options={
myData.node.template[templateParam]

View file

@ -617,12 +617,14 @@ export default function NodeToolbarComponent({
open={showModalAdvanced}
setOpen={setShowModalAdvanced}
/>
{showconfirmShare&&<ShareModal
open={showconfirmShare}
setOpen={setShowconfirmShare}
is_component={true}
component={flowComponent!}
/>}
{showconfirmShare && (
<ShareModal
open={showconfirmShare}
setOpen={setShowconfirmShare}
is_component={true}
component={flowComponent!}
/>
)}
{hasCode && (
<div className="hidden">
<CodeAreaComponent

View file

@ -505,6 +505,17 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
);
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
const verticesIds = get().verticesBuild?.verticesIds;
const newFlowBuildStatus = { ...get().flowBuildStatus };
// filter out the vertices that are not status
const verticesToUpdate = verticesIds?.filter(
(id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT
);
if (verticesToUpdate) {
useFlowStore.getState().updateBuildStatus(verticesToUpdate, status);
}
}
await buildVertices({
input_value,
@ -591,7 +602,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
},
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
const newFlowBuildStatus = { ...get().flowBuildStatus };
nodeIdList.forEach((id) => {
newFlowBuildStatus[id] = {
status,

View file

@ -0,0 +1,34 @@
import { create } from "zustand";
import { GlobalVariablesStore } from "../types/zustand/globalVariables";
export const useGlobalVariablesStore = create<GlobalVariablesStore>(
(set, get) => ({
globalVariablesEntries: [],
globalVariables: {},
setGlobalVariables: (variables) => {
set({
globalVariables: variables,
globalVariablesEntries: Object.keys(variables),
});
},
addGlobalVariable: (name, id, type) => {
const data = { id, type };
const newVariables = { ...get().globalVariables, [name]: data };
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables),
});
},
removeGlobalVariable: (name) => {
const newVariables = { ...get().globalVariables };
delete newVariables[name];
set({
globalVariables: newVariables,
globalVariablesEntries: Object.keys(newVariables),
});
},
getVariableId: (name) => {
return get().globalVariables[name]?.id;
},
})
);

View file

@ -450,7 +450,7 @@
@apply text-muted-foreground;
}
.dropdown-component-arrow {
@apply pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2;
@apply pointer-events-none absolute inset-y-0 flex items-center pr-2;
}
.dropdown-component-arrow-color {
@apply h-5 w-5 text-accent-foreground;
@ -480,7 +480,7 @@
@apply absolute inset-y-0 right-0 flex items-center pr-4;
}
.dropdown-component-check-icon {
@apply h-5 w-5 text-black;
@apply h-5 w-5 text-primary;
}
.edit-flow-arrangement {

View file

@ -177,3 +177,6 @@ export type CodeErrorDataTypeAPI = {
export type ResponseErrorTypeAPI = {
response: { data: { detail: CodeErrorDataTypeAPI } };
};
export type ResponseErrorDetailAPI = {
response: { data: { detail: string } };
};

View file

@ -7,9 +7,9 @@ import { sourceHandleType, targetHandleType } from "./../flow/index";
export type InputComponentType = {
autoFocus?: boolean;
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
value: string;
value?: string;
disabled?: boolean;
onChange: (value: string) => void;
onChange?: (value: string) => void;
password: boolean;
required?: boolean;
isForm?: boolean;
@ -20,6 +20,13 @@ export type InputComponentType = {
className?: string;
id?: string;
blurOnEnter?: boolean;
optionsIcon?: string;
optionsPlaceholder?: string;
options?: string[];
optionsButton?: ReactElement;
optionButton?: (option: string) => ReactElement;
selectedOption?: string;
setSelectedOption?: (value: string) => void;
};
export type ToggleComponentType = {
enabled: boolean;
@ -36,8 +43,6 @@ export type DropDownComponentType = {
options: string[];
onSelect: (value: string) => void;
editNode?: boolean;
apiModal?: boolean;
numberOfOptions?: number;
id?: string;
};
export type ParameterComponentType = {
@ -65,6 +70,15 @@ export type InputListComponentType = {
componentName?: string;
};
export type InputGlobalComponentType = {
disabled: boolean;
onChange: (value: string) => void;
setDb: (value: boolean) => void;
name: string;
data: NodeDataType;
editNode?: boolean;
};
export type KeyPairListComponentType = {
value: any;
onChange: (value: Object[]) => void;

View file

@ -0,0 +1,10 @@
export type GlobalVariablesStore = {
globalVariablesEntries: Array<string>;
globalVariables: { [name: string]: { id: string; type?: string } };
setGlobalVariables: (variables: {
[name: string]: { id: string; type?: string };
}) => void;
addGlobalVariable: (name: string, id: string, type?: string) => void;
removeGlobalVariable: (name: string) => void;
getVariableId: (name: string) => string | undefined;
};

View file

@ -58,6 +58,7 @@ import {
GitBranchPlus,
GitFork,
GithubIcon,
Globe,
Group,
Hammer,
Heart,
@ -418,6 +419,7 @@ export const nodeIconsLucide: iconsType = {
PlusCircle,
PlusSquare,
Code2,
Globe,
Variable,
Snowflake,
Store,

View file

@ -305,10 +305,7 @@
"_type": "initialize_agent"
},
"description": "Construct a zero shot agent from an LLM and tools.",
"base_classes": [
"AgentExecutor",
"function"
],
"base_classes": ["AgentExecutor", "function"],
"display_name": "AgentInitializer"
},
"id": "AgentInitializer-QiQ4x",
@ -372,9 +369,7 @@
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": [
"Tool"
],
"base_classes": ["Tool"],
"display_name": "PythonFunctionTool"
},
"id": "PythonFunctionTool-kX99N",
@ -434,9 +429,7 @@
"selected": false,
"data": {
"sourceHandle": {
"baseClasses": [
"Tool"
],
"baseClasses": ["Tool"],
"dataType": "PythonFunctionTool",
"id": "PythonFunctionTool-kX99N"
},
@ -822,10 +815,7 @@
"_type": "initialize_agent"
},
"description": "Construct a zero shot agent from an LLM and tools.",
"base_classes": [
"AgentExecutor",
"function"
],
"base_classes": ["AgentExecutor", "function"],
"display_name": "AgentInitializer"
},
"id": "AgentInitializer-YJgqs",
@ -889,9 +879,7 @@
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": [
"Tool"
],
"base_classes": ["Tool"],
"display_name": "PythonFunctionTool"
},
"id": "PythonFunctionTool-gqQDg",
@ -951,9 +939,7 @@
"selected": false,
"data": {
"sourceHandle": {
"baseClasses": [
"Tool"
],
"baseClasses": ["Tool"],
"dataType": "PythonFunctionTool",
"id": "PythonFunctionTool-gqQDg"
},
@ -1339,10 +1325,7 @@
"_type": "initialize_agent"
},
"description": "Construct a zero shot agent from an LLM and tools.",
"base_classes": [
"AgentExecutor",
"function"
],
"base_classes": ["AgentExecutor", "function"],
"display_name": "AgentInitializer"
},
"id": "AgentInitializer-grV0u",
@ -1406,9 +1389,7 @@
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": [
"Tool"
],
"base_classes": ["Tool"],
"display_name": "PythonFunctionTool"
},
"id": "PythonFunctionTool-SctM2",
@ -1468,9 +1449,7 @@
"selected": false,
"data": {
"sourceHandle": {
"baseClasses": [
"Tool"
],
"baseClasses": ["Tool"],
"dataType": "PythonFunctionTool",
"id": "PythonFunctionTool-SctM2"
},
@ -2210,10 +2189,7 @@
"_type": "initialize_agent"
},
"description": "Construct a zero shot agent from an LLM and tools.",
"base_classes": [
"AgentExecutor",
"function"
],
"base_classes": ["AgentExecutor", "function"],
"display_name": "AgentInitializer"
},
"id": "AgentInitializer-EE8R4",
@ -2277,9 +2253,7 @@
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": [
"Tool"
],
"base_classes": ["Tool"],
"display_name": "PythonFunctionTool"
},
"id": "PythonFunctionTool-YKkDL",
@ -2339,9 +2313,7 @@
"selected": false,
"data": {
"sourceHandle": {
"baseClasses": [
"Tool"
],
"baseClasses": ["Tool"],
"dataType": "PythonFunctionTool",
"id": "PythonFunctionTool-YKkDL"
},
@ -2727,10 +2699,7 @@
"_type": "initialize_agent"
},
"description": "Construct a zero shot agent from an LLM and tools.",
"base_classes": [
"AgentExecutor",
"function"
],
"base_classes": ["AgentExecutor", "function"],
"display_name": "AgentInitializer"
},
"id": "AgentInitializer-goPm2",
@ -2794,9 +2763,7 @@
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": [
"Tool"
],
"base_classes": ["Tool"],
"display_name": "PythonFunctionTool"
},
"id": "PythonFunctionTool-SQikY",
@ -2856,9 +2823,7 @@
"selected": false,
"data": {
"sourceHandle": {
"baseClasses": [
"Tool"
],
"baseClasses": ["Tool"],
"dataType": "PythonFunctionTool",
"id": "PythonFunctionTool-SQikY"
},
@ -2881,4 +2846,4 @@
"user_id": "6f6ebc2c-9e7a-4c35-84fc-51760db10d9b"
}
]
}
}

View file

@ -46,9 +46,7 @@
"dynamic": false,
"info": "",
"title_case": false,
"input_types": [
"Text"
]
"input_types": ["Text"]
},
"name": {
"type": "str",
@ -66,9 +64,7 @@
"dynamic": false,
"info": "",
"title_case": false,
"input_types": [
"Text"
]
"input_types": ["Text"]
},
"return_direct": {
"type": "bool",
@ -90,10 +86,7 @@
"_type": "PythonFunctionTool"
},
"description": "Python function to be executed.",
"base_classes": [
"BaseTool",
"Tool"
],
"base_classes": ["BaseTool", "Tool"],
"display_name": "PythonFunctionTool",
"documentation": "",
"custom_fields": {},
@ -208,9 +201,7 @@
"dynamic": false,
"info": "",
"title_case": false,
"input_types": [
"Text"
]
"input_types": ["Text"]
},
"code": {
"type": "code",
@ -269,10 +260,7 @@
"max_iterations": null,
"memory": null
},
"output_types": [
"AgentExecutor",
"Callable"
],
"output_types": ["AgentExecutor", "Callable"],
"field_formatters": {},
"pinned": false,
"beta": true
@ -372,9 +360,7 @@
"dynamic": false,
"info": "",
"title_case": false,
"input_types": [
"Text"
]
"input_types": ["Text"]
},
"openai_api_base": {
"type": "str",
@ -392,9 +378,7 @@
"dynamic": false,
"info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\n\nYou can change this to use other APIs like JinaChat, LocalAI and Prem.",
"title_case": false,
"input_types": [
"Text"
]
"input_types": ["Text"]
},
"openai_api_key": {
"type": "str",
@ -412,9 +396,7 @@
"dynamic": false,
"info": "",
"title_case": false,
"input_types": [
"Text"
]
"input_types": ["Text"]
},
"temperature": {
"type": "float",
@ -462,10 +444,7 @@
"openai_api_key": null,
"temperature": null
},
"output_types": [
"BaseLanguageModel",
"BaseLLM"
],
"output_types": ["BaseLanguageModel", "BaseLLM"],
"field_formatters": {},
"pinned": false,
"beta": true
@ -528,10 +507,7 @@
"type": "Tool"
},
"sourceHandle": {
"baseClasses": [
"BaseTool",
"Tool"
],
"baseClasses": ["BaseTool", "Tool"],
"dataType": "PythonFunctionTool",
"id": "PythonFunctionTool-RfJui"
}
@ -553,4 +529,4 @@
"name": "Untitled document (20)",
"last_tested_version": "0.7.0a0",
"is_component": false
}
}

View file

@ -62,27 +62,21 @@
"name": "text_input",
"display_name": "Text Input",
"advanced": false,
"input_types": [
"str"
],
"input_types": ["str"],
"dynamic": false,
"info": "",
"value": ""
},
"_type": "CustomComponent"
},
"base_classes": [
"str"
],
"base_classes": ["str"],
"display_name": "text checkpoint",
"documentation": "http://docs.langflow.org/components/custom",
"custom_fields": {
"save_path": null,
"text_input": null
},
"output_types": [
"str"
],
"output_types": ["str"],
"field_formatters": {},
"beta": true
},
@ -161,18 +155,14 @@
"_type": "CustomComponent"
},
"description": "Converts audio to text using OpenAI's Whisper.",
"base_classes": [
"str"
],
"base_classes": ["str"],
"display_name": "Whisper Transcriber",
"documentation": "",
"custom_fields": {
"OpenAIKey": null,
"audio": null
},
"output_types": [
"str"
],
"output_types": ["str"],
"field_formatters": {},
"beta": true
},
@ -196,15 +186,11 @@
"targetHandle": {
"fieldName": "text_input",
"id": "CustomComponent-MtJjl",
"inputTypes": [
"str"
],
"inputTypes": ["str"],
"type": "str"
},
"sourceHandle": {
"baseClasses": [
"str"
],
"baseClasses": ["str"],
"dataType": "CustomComponent",
"id": "CustomComponent-7NQoq"
}
@ -228,4 +214,4 @@
"folder": null,
"id": "1b0814b7-2964-4e09-9b4b-f7413c4fb50b",
"user_id": "8b5cf798-f1b8-4108-88fd-d7274d08d471"
}
}

View file

@ -1,5 +1,4 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "fs";
test.describe("group node test", () => {
/// <reference lib="dom"/>
@ -7,71 +6,24 @@ test.describe("group node test", () => {
await page.goto("http:localhost:3000/");
await page.locator('//*[@id="new-project-btn"]').click();
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Read your file into a buffer.
const jsonContent = readFileSync(
"tests/end-to-end/assets/flow_group_test.json",
"utf-8"
);
// Create the DataTransfer and File
const dataTransfer = await page.evaluateHandle((data) => {
const dt = new DataTransfer();
// Convert the buffer to a hex array
const file = new File([data], "flow_group_test.json", {
type: "application/json",
});
dt.items.add(file);
return dt;
}, jsonContent);
await page.waitForTimeout(1000);
// Now dispatch
await page.dispatchEvent(
"//*[@id='react-flow-id']/div[1]/div[1]/div",
"drop",
{
dataTransfer,
}
);
const genericNoda = page.getByTestId("div-generic-node");
const elementCount = await genericNoda.count();
if (elementCount > 0) {
expect(true).toBeTruthy();
}
await page.getByRole("heading", { name: "Data Ingestion" }).click();
await page.waitForTimeout(2000);
await page.getByLabel("fit view").click();
await page.keyboard.down("Control");
await page
.locator('//*[@id="react-flow-id"]/div[1]/div[2]/button[3]')
.click();
await page.getByTestId("title-Agent Initializer").click({
modifiers: ["Control"],
});
await page.getByTestId("title-PythonFunctionTool").click({
modifiers: ["Control"],
});
await page.getByTestId("title-ChatOpenAI").click({
modifiers: ["Control"],
});
.getByTestId("title-OpenAIEmbeddings")
.click({ modifiers: ["Control"] });
await page.getByTestId("title-URL").click({ modifiers: ["Control"] });
await page
.getByTestId("title-Recursive Character Text Splitter")
.click({ modifiers: ["Control"] });
await page.keyboard.up("Control");
await page.getByRole("button", { name: "Group" }).click();
const textArea = page.getByTestId("div-textarea-description");
const elementCountText = await textArea.count();
if (elementCountText > 0) {
expect(true).toBeTruthy();
}
const groupNode = page.getByTestId("title-Group");
const elementGroup = await groupNode.count();
if (elementGroup > 0) {
expect(true).toBeTruthy();
}
// Now dispatch
await page.getByTestId(/input-collection_name_Chroma-.*/).click();
await page.getByTestId(/input-collection_name_Chroma-.*/).fill("test");
await page.getByTestId("title-Group").click();
await page.keyboard.press("Control+g");
const value = await page.getByTestId("input-collection_name").inputValue();
expect(value).toBe("test");
});
});