Merge remote-tracking branch 'origin/zustand/io/migration' into globalVariables

This commit is contained in:
Lucas Oliveira 2024-03-21 15:35:52 +01:00
commit 99561d0d4c
26 changed files with 1381 additions and 887 deletions

View file

@ -1,4 +1,3 @@
import { cloneDeep } from "lodash";
import useFlowStore from "../../stores/flowStore";
import { IOOutputProps } from "../../types/components";
import { Textarea } from "../ui/textarea";

View file

@ -105,7 +105,13 @@ export default function IOView({
return (
<BaseModal
size={haveChat ? (selectedTab === 0 ? "large-thin" : "large") : "small"}
size={
haveChat || selectedViewField
? selectedTab === 0
? "large-thin"
: "large"
: "small"
}
open={open}
setOpen={setOpen}
disable={disable}
@ -123,13 +129,13 @@ export default function IOView({
</div>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex h-full flex-col">
<div className="flex h-full flex-col overflow-hidden">
<div className="flex-max-width mt-2 h-full">
{selectedTab !== 0 && (
<div
className={cn(
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start",
haveChat ? "w-2/6" : "w-full"
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300",
haveChat || selectedViewField ? "w-2/6" : "w-full"
)}
>
<Tabs
@ -179,20 +185,18 @@ export default function IOView({
<Badge variant="gray" size="md">
{node.data.node.display_name}
</Badge>
{haveChat && (
<div
className="-mb-1 pr-4"
onClick={(event) => {
event.stopPropagation();
setSelectedViewField(input);
}}
>
<IconComponent
className="h-4 w-4"
name="ExternalLink"
></IconComponent>
</div>
)}
<div
className="-mb-1 pr-4"
onClick={(event) => {
event.stopPropagation();
setSelectedViewField(input);
}}
>
<IconComponent
className="h-4 w-4"
name="ExternalLink"
></IconComponent>
</div>
</div>
}
key={index}
@ -248,20 +252,18 @@ export default function IOView({
</Badge>
</div>
</ShadTooltip>
{haveChat && (
<div
className="-mb-1 pr-4"
onClick={(event) => {
event.stopPropagation();
setSelectedViewField(output);
}}
>
<IconComponent
className="h-4 w-4"
name="ExternalLink"
></IconComponent>
</div>
)}
<div
className="-mb-1 pr-4"
onClick={(event) => {
event.stopPropagation();
setSelectedViewField(output);
}}
>
<IconComponent
className="h-4 w-4"
name="ExternalLink"
></IconComponent>
</div>
</div>
}
key={index}
@ -287,12 +289,12 @@ export default function IOView({
</div>
)}
{haveChat ? (
{haveChat || selectedViewField ? (
<div className="flex h-full min-w-96 flex-grow">
{selectedViewField && (
<div
className={cn(
"flex h-full w-full flex-col items-start gap-4 p-4",
"flex h-full w-full flex-col items-start gap-4 pt-4",
!selectedViewField ? "hidden" : ""
)}
>
@ -347,7 +349,7 @@ export default function IOView({
</BaseModal.Content>
<BaseModal.Footer>
{!haveChat && (
<div className="flex w-full justify-end pt-6">
<div className="flex w-full justify-end pt-2">
<Button
variant={"outline"}
className="flex gap-2 px-3"

View file

@ -29,6 +29,7 @@ export const MenuBar = ({
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const undo = useFlowsManagerStore((state) => state.undo);
const redo = useFlowsManagerStore((state) => state.redo);
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
@ -38,11 +39,21 @@ export const MenuBar = ({
const navigate = useNavigate();
const isBuilding = useFlowStore((state) => state.isBuilding);
function handleAddFlow() {
function handleAddFlow(duplicate?: boolean) {
try {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
if (duplicate) {
if (!currentFlow) {
throw new Error("No flow to duplicate");
}
addFlow(true, currentFlow).then((id) => {
setSuccessData({ title: "Flow duplicated successfully" });
navigate("/flow/" + id);
});
} else {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}
} catch (err) {
setErrorData(err as { title: string; list?: Array<string> });
}
@ -62,7 +73,7 @@ export const MenuBar = ({
<button
onClick={() => {
removeFunction(nodes);
navigate(-1);
navigate("/");
}}
>
<IconComponent name="ChevronLeft" className="w-4" />
@ -88,6 +99,15 @@ export const MenuBar = ({
<IconComponent name="Plus" className="header-menu-options" />
New
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
handleAddFlow(true);
}}
className="cursor-pointer"
>
<IconComponent name="Copy" className="header-menu-options" />
Duplicate
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {

View file

@ -1,6 +1,7 @@
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, cn } from "../../utils/utils";
@ -37,6 +38,7 @@ export default function InputComponent({
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);
@ -119,8 +121,23 @@ export default function InputComponent({
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
onChange && onChange(e.target.value);
}}
// 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 === "•")) {
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")

View file

@ -29,7 +29,7 @@ export default function RenameLabel(props) {
}
resizeInput();
return () => {
if(isRename) document.removeEventListener("keydown", () => {});
if (isRename) document.removeEventListener("keydown", () => {});
};
}, [isRename]);

View file

@ -1,7 +1,6 @@
// src/constants/constants.ts
import { languageMap } from "../types/components";
import { FlowType } from "../types/flow";
/**
* invalid characters for flow name
@ -750,75 +749,5 @@ export const NATIVE_CATEGORIES = [
"helpers",
"experimental",
];
/*
Data ingestion
Basic Prompting
Chat com memória
Working with data (file/website)
API requests
Vector Store
Assistant
*/
export const EXAMPLES_MOCK: FlowType[] = [
{
name: "Working with data",
id: "Working with data Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "Basic Prompting",
id: "Basic Prompting Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "Chat with memory",
id: "Chat with memory Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "API requests",
id: "API requests Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "Assistant",
id: "Assistant Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
];
export const SAVE_DEBOUNCE_TIME = 500;

View file

@ -10,6 +10,7 @@ import {
DialogTrigger,
} from "../../components/ui/dialog";
import { modalHeaderType } from "../../types/components";
import { cn } from "../../utils/utils";
type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string };
@ -157,11 +158,15 @@ function BaseModal({
return (
<Dialog open={open} onOpenChange={setOpen}>
{triggerChild}
<DialogContent className={minWidth}>
<DialogContent className={cn(minWidth, "duration-300")}>
<div className="truncate-doubleline word-break-break-word">
{headerChild}
</div>
<div className={`flex flex-col ${height!} w-full `}>{ContentChild}</div>
<div
className={`flex flex-col ${height!} w-full transition-all duration-300`}
>
{ContentChild}
</div>
{ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div>
)}

View file

@ -146,75 +146,83 @@ export default function CodeAreaModal({
setCode(value);
}, [value, open]);
const handlePreventEsc = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
}
};
return (
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
<span className="pr-2"> {EDIT_CODE_TITLE} </span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<Input
value={code}
readOnly
className="absolute left-[500%] top-[500%]"
id="codeValue"
/>
<div className="flex h-full w-full flex-col transition-all">
<div className="h-full w-full">
<AceEditor
readOnly={readonly}
value={code}
mode="python"
setOptions={{ fontFamily: "monospace" }}
height={height ?? "100%"}
highlightActiveLine={true}
showPrintMargin={false}
fontSize={14}
showGutter
enableLiveAutocompletion
theme={dark ? "twilight" : "github"}
name="CodeEditor"
onChange={(value) => {
setCode(value);
}}
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
/>
</div>
<div
className={
"whitespace-break-spaces transition-all delay-500" +
(error?.detail?.error !== undefined ? "h-2/6" : "h-0")
}
>
<div className="mt-1 h-full max-h-[10rem] w-full overflow-y-auto overflow-x-clip text-left custom-scroll">
<h1 className="text-lg text-destructive">
{error?.detail?.error}
</h1>
<div className="ml-2 w-full text-sm text-status-red word-break-break-word">
<span className="w-full word-break-break-word">
{error?.detail?.traceback}
</span>
<div onKeyDown={(e) => handlePreventEsc(e)}>
<BaseModal open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={CODE_PROMPT_DIALOG_SUBTITLE}>
<span className="pr-2"> {EDIT_CODE_TITLE} </span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<Input
value={code}
readOnly
className="absolute left-[500%] top-[500%]"
id="codeValue"
/>
<div className="flex h-full w-full flex-col transition-all">
<div className="h-full w-full">
<AceEditor
readOnly={readonly}
value={code}
mode="python"
setOptions={{ fontFamily: "monospace" }}
height={height ?? "100%"}
highlightActiveLine={true}
showPrintMargin={false}
fontSize={14}
showGutter
enableLiveAutocompletion
theme={dark ? "twilight" : "github"}
name="CodeEditor"
onChange={(value) => {
setCode(value);
}}
className="h-full w-full rounded-lg border-[1px] border-gray-300 custom-scroll dark:border-gray-600"
/>
</div>
<div
className={
"whitespace-break-spaces transition-all delay-500" +
(error?.detail?.error !== undefined ? "h-2/6" : "h-0")
}
>
<div className="mt-1 h-full max-h-[10rem] w-full overflow-y-auto overflow-x-clip text-left custom-scroll">
<h1 className="text-lg text-destructive">
{error?.detail?.error}
</h1>
<div className="ml-2 w-full text-sm text-status-red word-break-break-word">
<span className="w-full word-break-break-word">
{error?.detail?.traceback}
</span>
</div>
</div>
</div>
<div className="flex h-fit w-full justify-end">
<Button
className="mt-3"
onClick={handleClick}
type="submit"
id="checkAndSaveBtn"
disabled={readonly}
>
Check & Save
</Button>
</div>
</div>
<div className="flex h-fit w-full justify-end">
<Button
className="mt-3"
onClick={handleClick}
type="submit"
id="checkAndSaveBtn"
disabled={readonly}
>
Check & Save
</Button>
</div>
</div>
</BaseModal.Content>
</BaseModal>
</BaseModal.Content>
</BaseModal>
</div>
);
}

View file

@ -1,4 +1,4 @@
import _ from "lodash";
import _, { cloneDeep } from "lodash";
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
import ReactFlow, {
Background,
@ -32,6 +32,7 @@ import {
isValidConnection,
reconnectEdges,
scapeJSONParse,
updateIds,
validateSelection,
} from "../../../../utils/reactflowUtils";
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
@ -97,6 +98,56 @@ export default function Page({
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
const selectedNode = nodes.filter((obj) => obj.selected);
if (
selectionMenuVisible &&
(event.ctrlKey || event.metaKey) &&
event.key === "g"
) {
event.preventDefault();
takeSnapshot();
if (validateSelection(lastSelection!, edges).length === 0) {
const clonedNodes = cloneDeep(nodes);
const clonedEdges = cloneDeep(edges);
const clonedSelection = cloneDeep(lastSelection);
updateIds(
{ nodes: clonedNodes, edges: clonedEdges },
clonedSelection!
);
const { newFlow, removedEdges } = generateFlow(
clonedSelection!,
clonedNodes,
clonedEdges,
getRandomName()
);
const newGroupNode = generateNodeFromFlow(newFlow, getNodeId);
const newEdges = reconnectEdges(newGroupNode, removedEdges);
setNodes([
...clonedNodes.filter(
(oldNodes) =>
!clonedSelection?.nodes.some(
(selectionNode) => selectionNode.id === oldNodes.id
)
),
newGroupNode,
]);
setEdges([
...clonedEdges.filter(
(oldEdge) =>
!clonedSelection!.nodes.some(
(selectionNode) =>
selectionNode.id === oldEdge.target ||
selectionNode.id === oldEdge.source
)
),
...newEdges,
]);
} else {
setErrorData({
title: INVALID_SELECTION_ERROR_ALERT,
list: validateSelection(lastSelection!, edges),
});
}
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "p" &&
@ -201,7 +252,7 @@ export default function Page({
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mousemove", handleMouseMove);
};
}, [lastCopiedSelection, lastSelection, takeSnapshot]);
}, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
useEffect(() => {
if (reactFlowInstance && currentFlowId) {
@ -437,10 +488,17 @@ export default function Page({
if (
validateSelection(lastSelection!, edges).length === 0
) {
const clonedNodes = cloneDeep(nodes);
const clonedEdges = cloneDeep(edges);
const clonedSelection = cloneDeep(lastSelection);
updateIds(
{ nodes: clonedNodes, edges: clonedEdges },
clonedSelection!
);
const { newFlow, removedEdges } = generateFlow(
lastSelection!,
nodes,
edges,
clonedSelection!,
clonedNodes,
clonedEdges,
getRandomName()
);
const newGroupNode = generateNodeFromFlow(
@ -451,20 +509,20 @@ export default function Page({
newGroupNode,
removedEdges
);
setNodes((oldNodes) => [
...oldNodes.filter(
setNodes([
...clonedNodes.filter(
(oldNodes) =>
!lastSelection?.nodes.some(
!clonedSelection?.nodes.some(
(selectionNode) =>
selectionNode.id === oldNodes.id
)
),
newGroupNode,
]);
setEdges((oldEdges) => [
...oldEdges.filter(
setEdges([
...clonedEdges.filter(
(oldEdge) =>
!lastSelection!.nodes.some(
!clonedSelection!.nodes.some(
(selectionNode) =>
selectionNode.id === oldEdge.target ||
selectionNode.id === oldEdge.source

View file

@ -1,4 +1,4 @@
import { cloneDeep } from "lodash";
import { cloneDeep, zip } from "lodash";
import {
Edge,
EdgeChange,
@ -26,6 +26,7 @@ import {
ChatOutputType,
FlowPoolObjectType,
FlowStoreType,
VertexLayerElementType,
chatInputType,
} from "../types/zustand/flow";
import { buildVertices } from "../utils/buildUtils";
@ -36,6 +37,7 @@ import {
getNodeId,
scapeJSONParse,
scapedJSONStringfy,
updateGroupRecursion,
validateNodes,
} from "../utils/reactflowUtils";
import { getInputsAndOutputs } from "../utils/storeUtils";
@ -221,6 +223,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
);
},
paste: (selection, position) => {
function updateGroup() {}
if (
selection.nodes.some((node) => node.data.type === "ChatInput") &&
checkChatInput(get().nodes)
@ -257,6 +261,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
updateGroupRecursion(node, selection.edges);
// Create a new node object
const newNode: NodeType = {
id: newId,
@ -459,9 +465,18 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
// verticesLayers is a list of list of vertices ids, where each list is a layer of vertices
// we want to add a new layer (next_vertices_ids) to the list of layers (verticesLayers)
// and the values of next_vertices_ids to the list of vertices ids (verticesIds)
// const nextVertices will be the zip of vertexBuildData.next_vertices_ids and
// vertexBuildData.top_level_vertices
// the VertexLayerElementType as {id: next_vertices_id, layer: top_level_vertex}
const nextVertices: VertexLayerElementType[] = zip(
vertexBuildData.next_vertices_ids,
vertexBuildData.top_level_vertices
).map(([id, reference]) => ({ id: id!, reference }));
const newLayers = [
...get().verticesBuild!.verticesLayers,
vertexBuildData.next_vertices_ids,
nextVertices,
];
const newIds = [
...get().verticesBuild!.verticesIds,
@ -473,7 +488,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
runId: runId,
});
get().updateBuildStatus(
vertexBuildData.next_vertices_ids,
vertexBuildData.top_level_vertices,
BuildStatus.TO_BUILD
);
}
@ -508,12 +523,19 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().setIsBuilding(false);
},
onBuildUpdate: handleBuildUpdate,
onBuildError: (title, list, idList) => {
onBuildError: (title: string, list: string[], elementList) => {
const idList = elementList
.map((element) => element.id)
.filter(Boolean) as string[];
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILT);
setErrorData({ list, title });
get().setIsBuilding(false);
},
onBuildStart: (idList) => {
onBuildStart: (elementList) => {
const idList = elementList
// reference is the id of the vertex or the id of the parent in a group node
.map((element) => element.reference)
.filter(Boolean) as string[];
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
},
validateNodes: validateSubgraph,
@ -531,7 +553,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
updateVerticesBuild: (
vertices: {
verticesIds: string[];
verticesLayers: string[][];
verticesLayers: VertexLayerElementType[][];
runId: string;
} | null
) => {
@ -562,6 +584,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
},
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
const newFlowBuildStatus = { ...get().flowBuildStatus };
console.log("newFlowBuildStatus", newFlowBuildStatus);
nodeIdList.forEach((id) => {
newFlowBuildStatus[id] = {
status,

View file

@ -1,8 +1,11 @@
import { AxiosError } from "axios";
import { cloneDeep } from "lodash";
import { cloneDeep, debounce } from "lodash";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand";
import { STARTER_FOLDER_NAME } from "../constants/constants";
import {
SAVE_DEBOUNCE_TIME,
STARTER_FOLDER_NAME,
} from "../constants/constants";
import {
deleteFlowFromDatabase,
readFlowsFromDatabase,
@ -92,22 +95,18 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
});
});
},
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
// Clear the previous timeout if it exists.
if (saveTimeoutId) {
clearTimeout(saveTimeoutId);
}
set({ saveLoading: true });
// Set up a new timeout.
saveTimeoutId = setTimeout(() => {
autoSaveCurrentFlow: debounce(
(nodes: Node[], edges: Edge[], viewport: Viewport) => {
set({ saveLoading: true });
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
true
);
}
}, 500); // Delay of 500ms because chat message depends on it.
},
},
SAVE_DEBOUNCE_TIME
),
saveFlow: (flow: FlowType, silent?: boolean) => {
set({ saveLoading: true });
return new Promise<void>((resolve, reject) => {

View file

@ -146,6 +146,7 @@ export type VertexBuildTypeAPI = {
id: string;
inactivated_vertices: Array<string> | null;
next_vertices_ids: Array<string>;
top_level_vertices: Array<string>;
run_id: string;
valid: boolean;
params: string;

View file

@ -35,6 +35,11 @@ export type FlowPoolObjectType = {
buildId: string;
};
export type VertexLayerElementType = {
id: string;
reference?: string;
};
export type FlowPoolType = {
[key: string]: Array<FlowPoolObjectType>;
};
@ -103,7 +108,7 @@ export type FlowStoreType = {
updateVerticesBuild: (
vertices: {
verticesIds: string[];
verticesLayers: string[][];
verticesLayers: VertexLayerElementType[][];
runId: string;
} | null
) => void;
@ -111,7 +116,7 @@ export type FlowStoreType = {
removeFromVerticesBuild: (vertices: string[]) => void;
verticesBuild: {
verticesIds: string[];
verticesLayers: string[][];
verticesLayers: VertexLayerElementType[][];
runId: string;
} | null;
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;

View file

@ -4,6 +4,7 @@ import { getVerticesOrder, postBuildVertex } from "../controllers/API";
import useAlertStore from "../stores/alertStore";
import useFlowStore from "../stores/flowStore";
import { VertexBuildTypeAPI } from "../types/api";
import { VertexLayerElementType } from "../types/zustand/flow";
type BuildVerticesParams = {
flowId: string; // Assuming FlowType is the type for your flow
@ -17,8 +18,8 @@ type BuildVerticesParams = {
buildId: string
) => void; // Replace any with the actual type if it's not any
onBuildComplete?: (allNodesValid: boolean) => void;
onBuildError?: (title, list, idList: string[]) => void;
onBuildStart?: (idList: string[]) => void;
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
onBuildStart?: (idList: VertexLayerElementType[]) => void;
validateNodes?: (nodes: string[]) => void;
};
@ -35,6 +36,7 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
inactivated_vertices: null,
run_id: "",
next_vertices_ids: [],
top_level_vertices: [],
inactive_vertices: null,
valid: false,
timestamp: new Date().toISOString(),
@ -48,7 +50,7 @@ export async function updateVerticesOrder(
startNodeId?: string | null,
stopNodeId?: string | null
): Promise<{
verticesLayers: string[][];
verticesLayers: VertexLayerElementType[][];
verticesIds: string[];
runId: string;
}> {
@ -66,7 +68,14 @@ export async function updateVerticesOrder(
useFlowStore.getState().setIsBuilding(false);
throw new Error("Invalid nodes");
}
let verticesLayers: Array<Array<string>> = [orderResponse.data.ids];
// orderResponse.data.ids,
// for each id we need to build the VertexLayerElementType object as
// {id: id, reference: id}
let verticesLayers: Array<Array<VertexLayerElementType>> =
orderResponse.data.ids.map((id: string) => {
return [{ id: id, reference: id }];
});
const runId = orderResponse.data.run_id;
// if (nodeId) {
// for (let i = 0; i < verticesOrder.length; i += 1) {
@ -160,17 +169,17 @@ export async function buildVertices({
if (onBuildStart) onBuildStart(currentLayer);
// Build each vertex in the current layer
await Promise.all(
currentLayer.map(async (vertexId) => {
currentLayer.map(async (element) => {
// Check if id is in the list of inactive nodes
if (
!useFlowStore
.getState()
.verticesBuild?.verticesIds.includes(vertexId) &&
.verticesBuild?.verticesIds.includes(element.id) &&
onBuildUpdate
) {
// If it is, skip building and set the state to inactive
onBuildUpdate(
getInactiveVertexData(vertexId),
getInactiveVertexData(element.id),
BuildStatus.INACTIVE,
runId
);
@ -181,7 +190,7 @@ export async function buildVertices({
// Build the vertex
await buildVertex({
flowId,
id: vertexId,
id: element.id,
input_value,
onBuildUpdate: (data: VertexBuildTypeAPI, status: BuildStatus) => {
if (onBuildUpdate) onBuildUpdate(data, status, runId);
@ -226,7 +235,7 @@ async function buildVertex({
id: string;
input_value: string;
onBuildUpdate?: (data: any, status: BuildStatus) => void;
onBuildError?: (title, list, idList: string[]) => void;
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
verticesIds: string[];
buildResults: boolean[];
stopBuild: () => void;
@ -240,7 +249,7 @@ async function buildVertex({
onBuildError!(
"Error Building Component",
[buildData.params],
verticesIds
verticesIds.map((id) => ({ id }))
);
stopBuild();
}
@ -251,7 +260,7 @@ async function buildVertex({
onBuildError!(
"Error Building Component",
[(error as AxiosError<any>).response?.data?.detail ?? "Unknown Error"],
verticesIds
verticesIds.map((id) => ({ id }))
);
stopBuild();
}

View file

@ -204,23 +204,33 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
return data;
};
export function updateIds(newFlow: ReactFlowJsonObject) {
export function updateIds(
{ edges, nodes }: { edges: Edge[]; nodes: Node[] },
selection?: { edges: Edge[]; nodes: Node[] }
) {
let idsMap = {};
if (newFlow.nodes)
newFlow.nodes.forEach((node: NodeType) => {
const selectionIds = selection?.nodes.map((n) => n.id);
if (nodes) {
nodes.forEach((node: NodeType) => {
// Generate a unique node ID
let newId = getNodeId(
node.data.node?.flow ? "GroupNode" : node.data.type
);
let newId = getNodeId(node.data.type);
if (selection && !selectionIds?.includes(node.id)) {
newId = node.id;
}
idsMap[node.id] = newId;
node.id = newId;
node.data.id = newId;
// Add the new node to the list of nodes in state
});
if (newFlow.edges)
newFlow.edges.forEach((edge: Edge) => {
selection?.nodes.forEach((sNode: NodeType) => {
let newId = idsMap[sNode.id];
sNode.id = newId;
sNode.data.id = newId;
});
}
const concatedEdges = [...edges, ...(selection?.edges ?? [])];
if (concatedEdges)
concatedEdges.forEach((edge: Edge) => {
edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
@ -273,6 +283,8 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
node: { template },
} = node.data;
const displayName = node.data.node.display_name;
return Object.keys(template).reduce((errors: Array<string>, t) => {
if (
template[t].required &&
@ -288,7 +300,9 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
node.id
)
) {
errors.push(`${type} is missing ${getFieldTitle(template, t)}.`);
errors.push(
`${displayName || type} is missing ${getFieldTitle(template, t)}.`
);
} else if (
template[t].type === "dict" &&
template[t].required &&
@ -633,8 +647,8 @@ export function generateFlow(
}
export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
let newEdges = cloneDeep(excludedEdges);
if (!groupNode.data.node!.flow) return [];
let newEdges = cloneDeep(excludedEdges);
const { nodes, edges } = groupNode.data.node!.flow!.data!;
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
newEdges.forEach((edge) => {
@ -951,7 +965,7 @@ export function connectedInputNodesOnHandle(
return connectedNodes;
}
function updateProxyIdsOnTemplate(
export function updateProxyIdsOnTemplate(
template: APITemplateType,
idsMap: { [key: string]: string }
) {
@ -962,12 +976,16 @@ function updateProxyIdsOnTemplate(
});
}
function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) {
export function updateEdgesIds(
edges: Edge[],
idsMap: { [key: string]: string }
) {
edges.forEach((edge) => {
let targetHandle: targetHandleType = edge.data.targetHandle;
if (targetHandle.proxy && idsMap[targetHandle.proxy!.id]) {
targetHandle.proxy!.id = idsMap[targetHandle.proxy!.id];
}
console.log("edge", edge);
edge.data.targetHandle = targetHandle;
edge.targetHandle = scapedJSONStringfy(targetHandle);
});
@ -1255,3 +1273,18 @@ export function isInputType(type: string): boolean {
export function isOutputType(type: string): boolean {
return OUTPUT_TYPES.has(type);
}
export function updateGroupRecursion(groupNode: NodeType, edges: Edge[]) {
if (groupNode.data.node?.flow) {
groupNode.data.node.flow.data!.nodes.forEach((node) => {
if (node.data.node?.flow) {
updateGroupRecursion(node, node.data.node.flow.data!.edges);
}
});
let newFlow = groupNode.data.node!.flow;
const idsMap = updateIds(newFlow.data!);
updateProxyIdsOnTemplate(groupNode.data.node!.template, idsMap);
let flowEdges = edges;
updateEdgesIds(flowEdges, idsMap);
}
}

View file

@ -705,3 +705,8 @@ export function sortFields(a, b, fieldOrder) {
// You might want to sort them alphabetically or in another specific manner
return a.localeCompare(b);
}
export function freezeObject(obj: any) {
if (!obj) return obj;
return JSON.parse(JSON.stringify(obj));
}