Merge branch 'fix/minor_bugs' into fix/api_settings

This commit is contained in:
Lucas Oliveira 2024-06-05 10:17:36 -03:00
commit ed845f5eb5
30 changed files with 1123 additions and 834 deletions

View file

@ -40,6 +40,7 @@
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"debounce-promise": "^3.1.2",
"dompurify": "^3.0.5",
"dotenv": "^16.4.5",
"esbuild": "^0.17.19",
@ -5670,6 +5671,11 @@
"node": ">=12"
}
},
"node_modules/debounce-promise": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
"integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",

View file

@ -35,6 +35,7 @@
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"debounce-promise": "^3.1.2",
"dompurify": "^3.0.5",
"dotenv": "^16.4.5",
"esbuild": "^0.17.19",

View file

@ -222,12 +222,19 @@ export default function App() {
id={alert.id}
removeAlert={removeAlert}
/>
) : alert.type === "notice" ? (
<NoticeAlert
key={alert.id}
title={alert.title}
link={alert.link}
id={alert.id}
removeAlert={removeAlert}
/>
) : (
alert.type === "notice" && (
<NoticeAlert
alert.type === "success" && (
<SuccessAlert
key={alert.id}
title={alert.title}
link={alert.link}
id={alert.id}
removeAlert={removeAlert}
/>
@ -236,20 +243,6 @@ 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>
);

View file

@ -91,6 +91,7 @@ export default function IOModal({
await buildFlow({
input_value: chatValue,
startNodeId: chatInput?.id,
silent: true,
}).catch((err) => {
console.error(err);
setLockChat(false);

View file

@ -0,0 +1,67 @@
export const switchCaseModalSize = (size: string) => {
let minWidth: string;
let height: string;
switch (size) {
case "x-small":
minWidth = "min-w-[20vw]";
height = "h-full";
break;
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[11rem]";
break;
case "smaller-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "small":
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "small-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "medium-h-full":
minWidth = "min-w-[60vw]";
height = "h-full";
break;
case "large":
minWidth = "min-w-[85vw]";
height = "h-[80vh]";
break;
case "three-cards":
minWidth = "min-w-[1066px]";
height = "h-fit";
break;
case "large-thin":
minWidth = "min-w-[65vw]";
height = "h-[80vh]";
break;
case "md-thin":
minWidth = "min-w-[85vw]";
height = "h-[70vh]";
break;
case "sm-thin":
minWidth = "min-w-[65vw]";
height = "h-[70vh]";
break;
case "large-h-full":
minWidth = "min-w-[80vw]";
height = "h-full";
break;
default:
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
break;
}
return { minWidth, height };
};

View file

@ -16,10 +16,10 @@ import {
} from "../../components/ui/dialog-with-no-close";
import { DialogClose } from "@radix-ui/react-dialog";
import ForwardedIconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { modalHeaderType } from "../../types/components";
import { cn } from "../../utils/utils";
import { switchCaseModalSize } from "./helpers/switch-case-size";
import * as Form from "@radix-ui/react-form";
type ContentProps = { children: ReactNode };
@ -149,71 +149,7 @@ function BaseModal({
(child) => (child as React.ReactElement).type === Footer,
);
let minWidth: string;
let height: string;
switch (size) {
case "x-small":
minWidth = "min-w-[20vw]";
height = "h-full";
break;
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[11rem]";
break;
case "smaller-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "small":
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "small-h-full":
minWidth = "min-w-[40vw]";
height = "h-full";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "medium-h-full":
minWidth = "min-w-[60vw]";
height = "h-full";
break;
case "large":
minWidth = "min-w-[85vw]";
height = "h-[80vh]";
break;
case "three-cards":
minWidth = "min-w-[1066px]";
height = "h-fit";
break;
case "large-thin":
minWidth = "min-w-[65vw]";
height = "h-[80vh]";
break;
case "md-thin":
minWidth = "min-w-[85vw]";
height = "h-[70vh]";
break;
case "sm-thin":
minWidth = "min-w-[65vw]";
height = "h-[70vh]";
break;
case "large-h-full":
minWidth = "min-w-[80vw]";
height = "h-full";
break;
default:
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
break;
}
let { minWidth, height } = switchCaseModalSize(size);
useEffect(() => {
if (onChangeOpenModal) {

View file

@ -16,41 +16,15 @@ 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);
}, [currentFlow!.name, currentFlow!.description, open]);
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
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;
currentFlow!.description = description;
saveFlow(currentFlow!)
?.then(() => {
setOpen(false);
})
.catch((err) => {
useAlertStore.getState().setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).response?.data.detail ?? ""],
});
console.error(err);
});
}
useEffect(() => {
if (activeTab === "Executions") {
getTransactionTable(currentFlowId, "union").then((data) => {
@ -86,16 +60,6 @@ export default function FlowLogsModal({
}
}, [open, activeTab]);
const [nameLists, setNameList] = useState<string[]>([]);
useEffect(() => {
const tempNameList: string[] = [];
flows.forEach((flow: FlowType) => {
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
}, [flows]);
return (
<BaseModal open={open} setOpen={setOpen} size="large">
<BaseModal.Header description="Inspect component executions and monitor sent messages in the playground.">

View file

@ -23,14 +23,16 @@ export default function FlowSettingsModal({
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
const [endpoint_name, setEndpointName] = useState(currentFlow!.endpoint_name);
const [isSaving, setIsSaving] = useState(false);
function handleClick(): void {
setIsSaving(true);
currentFlow!.name = name;
currentFlow!.description = description;
currentFlow!.endpoint_name = endpoint_name;
saveFlow(currentFlow!)
?.then(() => {
setOpen(false);
setIsSaving(false);
})
.catch((err) => {
useAlertStore.getState().setErrorData({
@ -79,6 +81,7 @@ export default function FlowSettingsModal({
label: "Save",
disabled: nameLists.includes(name) && name !== currentFlow!.name,
dataTestId: "save-flow-settings",
loading: isSaving,
}}
/>
</BaseModal>

View file

@ -0,0 +1,23 @@
import ForwardedIconComponent from "../../../../../../components/genericIconComponent";
const GeneralPageHeaderComponent = () => {
return (
<>
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
<div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
General
<ForwardedIconComponent
name="SlidersHorizontal"
className="ml-2 h-5 w-5 text-primary"
/>
</h2>
<p className="text-sm text-muted-foreground">
Manage settings related to Langflow and your account.
</p>
</div>
</div>
</>
);
};
export default GeneralPageHeaderComponent;

View file

@ -0,0 +1,93 @@
import * as Form from "@radix-ui/react-form";
import InputComponent from "../../../../../../components/inputComponent";
import { Button } from "../../../../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../../../../components/ui/card";
type PasswordFormComponentProps = {
password: string;
cnfPassword: string;
handleInput: (event: any) => void;
handlePatchPassword: (
password: string,
cnfPassword: string,
handleInput: any,
) => void;
};
const PasswordFormComponent = ({
password,
cnfPassword,
handleInput,
handlePatchPassword,
}: PasswordFormComponentProps) => {
return (
<>
<Form.Root
onSubmit={(event) => {
handlePatchPassword(password, cnfPassword, handleInput);
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-2">
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Type your new password and confirm it.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full gap-4">
<Form.Field name="password" className="w-full">
<InputComponent
id="pasword"
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
placeholder="Password"
className="w-full"
/>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your password
</Form.Message>
</Form.Field>
<Form.Field name="cnfPassword" className="w-full">
<InputComponent
id="cnfPassword"
onChange={(value) => {
handleInput({
target: { name: "cnfPassword", value },
});
}}
value={cnfPassword}
isForm
password={true}
placeholder="Confirm Password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
</>
);
};
export default PasswordFormComponent;

View file

@ -0,0 +1,68 @@
import * as Form from "@radix-ui/react-form";
import GradientChooserComponent from "../../../../../../components/gradientChooserComponent";
import { Button } from "../../../../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../../../../components/ui/card";
import { gradients } from "../../../../../../utils/styleUtils";
type ProfileGradientFormComponentProps = {
gradient: string;
handleInput: (event: any) => void;
handlePatchGradient: (gradient: string) => void;
userData: any;
};
const ProfileGradientFormComponent = ({
gradient,
handleInput,
handlePatchGradient,
userData,
}: ProfileGradientFormComponentProps) => {
return (
<>
<Form.Root
onSubmit={(event) => {
handlePatchGradient(gradient);
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Gradient</CardTitle>
<CardDescription>
Choose the gradient that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<GradientChooserComponent
value={
gradient == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: gradient
}
onChange={(value) => {
handleInput({ target: { name: "gradient", value } });
}}
/>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
</>
);
};
export default ProfileGradientFormComponent;

View file

@ -0,0 +1,102 @@
import * as Form from "@radix-ui/react-form";
import InputComponent from "../../../../../../components/inputComponent";
import { Button } from "../../../../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../../../../components/ui/card";
import {
CREATE_API_KEY,
INSERT_API_KEY,
INVALID_API_KEY,
NO_API_KEY,
} from "../../../../../../constants/constants";
type StoreApiKeyFormComponentProps = {
apikey: string;
handleInput: (event: any) => void;
handleSaveKey: (apikey: string, handleInput: any) => void;
loadingApiKey: boolean;
validApiKey: boolean;
hasApiKey: boolean;
};
const StoreApiKeyFormComponent = ({
apikey,
handleInput,
handleSaveKey,
loadingApiKey,
validApiKey,
hasApiKey,
}: StoreApiKeyFormComponentProps) => {
return (
<>
<Form.Root
onSubmit={(event) => {
event.preventDefault();
handleSaveKey(apikey, handleInput);
}}
>
<Card x-chunk="dashboard-04-chunk-2" id="api">
<CardHeader>
<CardTitle>Store API Key</CardTitle>
<CardDescription>
{(hasApiKey && !validApiKey
? INVALID_API_KEY
: !hasApiKey
? NO_API_KEY
: "") + INSERT_API_KEY}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full flex-col gap-3">
<div className="flex w-full gap-4">
<Form.Field name="apikey" className="w-full">
<InputComponent
id="apikey"
onChange={(value) => {
handleInput({ target: { name: "apikey", value } });
}}
value={apikey}
isForm
password={true}
placeholder="Insert your API Key"
className="w-full"
/>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your API Key
</Form.Message>
</Form.Field>
</div>
<span className="pr-1 text-xs text-muted-foreground">
{CREATE_API_KEY}{" "}
<a
className="text-high-indigo underline"
href="https://langflow.store/"
target="_blank"
>
langflow.store
</a>
</span>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button
loading={loadingApiKey}
type="submit"
data-testid="api-key-save-button-store"
>
Save
</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
</>
);
};
export default StoreApiKeyFormComponent;

View file

@ -1,25 +1,6 @@
import * as Form from "@radix-ui/react-form";
import { useContext, useState } from "react";
import { useParams } from "react-router-dom";
import ForwardedIconComponent from "../../../../components/genericIconComponent";
import GradientChooserComponent from "../../../../components/gradientChooserComponent";
import InputComponent from "../../../../components/inputComponent";
import { Button } from "../../../../components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../../components/ui/card";
import {
CONTROL_PATCH_USER_STATE,
CREATE_API_KEY,
INSERT_API_KEY,
INVALID_API_KEY,
NO_API_KEY,
} from "../../../../constants/constants";
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
import { AuthContext } from "../../../../contexts/authContext";
import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
@ -28,21 +9,26 @@ import {
inputHandlerEventType,
patchUserInputStateType,
} from "../../../../types/components";
import { gradients } from "../../../../utils/styleUtils";
import usePatchGradient from "../hooks/use-patch-gradient";
import usePatchPassword from "../hooks/use-patch-password";
import useSaveKey from "../hooks/use-save-key";
import useScrollToElement from "../hooks/use-scroll-to-element";
import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
import PasswordFormComponent from "./components/PasswordForm";
import ProfileGradientFormComponent from "./components/ProfileGradientForm";
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
export default function GeneralPage() {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
(state) => state.setCurrentFlowId,
);
const { scrollId } = useParams();
const [inputState, setInputState] = useState<patchUserInputStateType>(
CONTROL_PATCH_USER_STATE,
CONTROL_PATCH_USER_STATE,
);
const { autoLogin } = useContext(AuthContext);
@ -91,192 +77,33 @@ export default function GeneralPage() {
return (
<div className="flex h-full w-full flex-col gap-6">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
<div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
General
<ForwardedIconComponent
name="SlidersHorizontal"
className="ml-2 h-5 w-5 text-primary"
/>
</h2>
<p className="text-sm text-muted-foreground">
Manage settings related to Langflow and your account.
</p>
</div>
</div>
<GeneralPageHeaderComponent />
<div className="grid gap-6">
<Form.Root
onSubmit={(event) => {
handlePatchGradient(gradient);
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-1">
<CardHeader>
<CardTitle>Profile Gradient</CardTitle>
<CardDescription>
Choose the gradient that appears as your profile picture.
</CardDescription>
</CardHeader>
<CardContent>
<div className="py-2">
<GradientChooserComponent
value={
gradient == ""
? userData?.profile_image ??
gradients[
parseInt(userData?.id ?? "", 30) % gradients.length
]
: gradient
}
onChange={(value) => {
handleInput({ target: { name: "gradient", value } });
}}
/>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
{!autoLogin && (
<Form.Root
onSubmit={(event) => {
handlePatchPassword(password, cnfPassword, handleInput);
event.preventDefault();
}}
>
<Card x-chunk="dashboard-04-chunk-2">
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Type your new password and confirm it.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full gap-4">
<Form.Field name="password" className="w-full">
<InputComponent
id="pasword"
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
placeholder="Password"
className="w-full"
/>
<Form.Message
match="valueMissing"
className="field-invalid"
>
Please enter your password
</Form.Message>
</Form.Field>
<Form.Field name="cnfPassword" className="w-full">
<InputComponent
id="cnfPassword"
onChange={(value) => {
handleInput({
target: { name: "cnfPassword", value },
});
}}
value={cnfPassword}
isForm
password={true}
placeholder="Confirm Password"
className="w-full"
/>
<ProfileGradientFormComponent
gradient={gradient}
handleInput={handleInput}
handlePatchGradient={handlePatchGradient}
userData={userData}
/>
<Form.Message
className="field-invalid"
match="valueMissing"
>
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button type="submit">Save</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
{!autoLogin && (
<PasswordFormComponent
password={password}
cnfPassword={cnfPassword}
handleInput={handleInput}
handlePatchPassword={handlePatchPassword}
/>
)}
{hasStore && (
<Form.Root
onSubmit={(event) => {
event.preventDefault();
handleSaveKey(apikey, handleInput);
}}
>
<Card x-chunk="dashboard-04-chunk-2" id="api">
<CardHeader>
<CardTitle>Store API Key</CardTitle>
<CardDescription>
{(hasApiKey && !validApiKey
? INVALID_API_KEY
: !hasApiKey
? NO_API_KEY
: "") + INSERT_API_KEY}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex w-full flex-col gap-3">
<div className="flex w-full gap-4">
<Form.Field name="apikey" className="w-full">
<InputComponent
id="apikey"
onChange={(value) => {
handleInput({ target: { name: "apikey", value } });
}}
value={apikey}
isForm
password={true}
placeholder="Insert your API Key"
className="w-full"
/>
<Form.Message
match="valueMissing"
className="field-invalid"
>
Please enter your API Key
</Form.Message>
</Form.Field>
</div>
<span className="pr-1 text-xs text-muted-foreground">
{CREATE_API_KEY}{" "}
<a
className="text-high-indigo underline"
href="https://langflow.store/"
target="_blank"
>
langflow.store
</a>
</span>
</div>
</CardContent>
<CardFooter className="border-t px-6 py-4">
<Form.Submit asChild>
<Button
loading={loadingApiKey}
type="submit"
data-testid="api-key-save-button-store"
>
Save
</Button>
</Form.Submit>
</CardFooter>
</Card>
</Form.Root>
<StoreApiKeyFormComponent
apikey={apikey}
handleInput={handleInput}
handleSaveKey={handleSaveKey}
loadingApiKey={loadingApiKey}
validApiKey={validApiKey}
hasApiKey={hasApiKey}
/>
)}
</div>
</div>

View file

@ -430,10 +430,12 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
startNodeId,
stopNodeId,
input_value,
silent,
}: {
startNodeId?: string;
stopNodeId?: string;
input_value?: string;
silent?: boolean;
}) => {
get().setIsBuilding(true);
const currentFlow = useFlowsManagerStore.getState().currentFlow;
@ -536,19 +538,23 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
startNodeId,
stopNodeId,
onGetOrderSuccess: () => {
setNoticeData({ title: "Running components" });
if (!silent) {
setNoticeData({ title: "Running components" });
}
},
onBuildComplete: (allNodesValid) => {
const nodeId = startNodeId || stopNodeId;
if (nodeId && allNodesValid) {
setSuccessData({
title: `${
get().nodes.find((node) => node.id === nodeId)?.data.node
?.display_name
} built successfully`,
});
} else {
setSuccessData({ title: FLOW_BUILD_SUCCESS_ALERT });
if (!silent) {
if (nodeId && allNodesValid) {
setSuccessData({
title: `${
get().nodes.find((node) => node.id === nodeId)?.data.node
?.display_name
} built successfully`,
});
} else {
setSuccessData({ title: FLOW_BUILD_SUCCESS_ALERT });
}
}
get().setIsBuilding(false);
},

View file

@ -1,4 +1,5 @@
import { cloneDeep, debounce } from "lodash";
import { cloneDeep } from "lodash";
import * as debounce from "debounce-promise";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand";
import { SAVE_DEBOUNCE_TIME } from "../constants/constants";

View file

@ -106,11 +106,12 @@ export type FlowStoreType = {
startNodeId,
stopNodeId,
input_value,
silent,
}: {
nodeId?: string;
startNodeId?: string;
stopNodeId?: string;
input_value?: string;
silent?: boolean;
}) => Promise<void>;
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
updateVerticesBuild: (