merge fix

This commit is contained in:
cristhianzl 2024-02-26 14:24:34 -03:00
commit 652780a6ee
33 changed files with 757 additions and 467 deletions

View file

@ -44,7 +44,7 @@ export default function GenericNode({
const [nodeDescription, setNodeDescription] = useState(
data.node?.description!
);
const buildStatus = useFlowStore((state) =>state.flowBuildStatus[data.id]);
const buildStatus = useFlowStore((state) => state.flowBuildStatus[data.id]);
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
const [handles, setHandles] = useState<number>(0);
@ -122,7 +122,6 @@ export default function GenericNode({
}, [flowPool, data.id]);
const showNode = data.showNode ?? true;
const pinned = data.node?.pinned ?? false;
const nameEditable = data.node?.flow || data.type === "CustomComponent";
@ -162,7 +161,7 @@ export default function GenericNode({
const getIconPlayOrPauseComponent = (name, className) => (
<IconComponent
name={name}
className={`absolute h-5 stroke-2 ${className} ml-0.5`}
className={`h-4 fill-current stroke-2 ${className}`}
/>
);
@ -181,15 +180,18 @@ export default function GenericNode({
return "red-status";
} else if (!validationStatus && buildStatus === BuildStatus.TO_BUILD) {
return "green-status";
} else if (buildStatus === BuildStatus.BUILDING) {
return "status-build-animation";
} else {
return "green-status";
return "yellow-status";
}
};
const isDark = useDarkStore((state) => state.dark);
console.log(isDark);
const renderIconStatusComponents = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
) => {
const className = getStatusClassName(buildStatus, validationStatus);
return <>{getIconPlayOrPauseComponent("CircleDot", className)}</>;
};
const renderIconPlayOrPauseComponents = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
@ -197,8 +199,12 @@ export default function GenericNode({
if (buildStatus === BuildStatus.BUILDING) {
return <Loading />;
} else {
const className = getStatusClassName(buildStatus, validationStatus);
return <>{getIconPlayOrPauseComponent("Play", className)}</>;
return (
<IconComponent
name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground hover:text-medium-indigo"
/>
);
}
};
@ -332,28 +338,37 @@ export default function GenericNode({
</div>
) : (
<ShadTooltip content={data.node?.display_name}>
<div
className="flex items-center gap-2"
onDoubleClick={(event) => {
if (nameEditable) {
setInputName(true);
}
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
>
<div
data-testid={"title-" + data.node?.display_name}
className="generic-node-tooltip-div text-primary"
>
{data.node?.display_name}
</div>
<div className="group flex items-center gap-2.5">
<ShadTooltip content={data.node?.display_name}>
<div
onDoubleClick={(event) => {
if (nameEditable) {
setInputName(true);
}
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
data-testid={"title-" + data.node?.display_name}
className="generic-node-tooltip-div text-primary"
>
{data.node?.display_name}
</div>
</ShadTooltip>
{nameEditable && (
<IconComponent
name="Pencil"
className="h-4 w-4 text-ring"
/>
<div
onClick={(event) => {
setInputName(true);
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
>
<IconComponent
name="Pencil"
className="hidden h-4 w-4 animate-pulse text-status-blue group-hover:block"
/>
</div>
)}
</div>
</ShadTooltip>
@ -451,44 +466,11 @@ export default function GenericNode({
</div>
{showNode && (
<Button
variant="outline"
className="h-9 px-1.5"
onClick={() => {
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
pinned: old.data?.node?.pinned ? false : true,
},
},
}));
}}
>
<Tooltip
title={<span>{pinned ? "Pin Output" : "Unpin Output"}</span>}
>
<div className="generic-node-status-position flex items-center">
<IconComponent
name={"Pin"}
className={cn(
"h-5 fill-transparent stroke-chat-trigger stroke-2 transition-all",
pinned ? "animate-wiggle fill-chat-trigger" : ""
)}
/>
</div>
</Tooltip>
</Button>
)}
{showNode && (
<Button
variant="outline"
className={"h-9 px-1.5"}
variant="secondary"
className={"group h-9 px-1.5"}
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
buildFlow(data.id);
}}
>
@ -529,6 +511,32 @@ export default function GenericNode({
</div>
</Button>
)}
<div className="">
<Tooltip
title={
data?.buildStatus === BuildStatus.BUILDING ? (
<span>Building...</span>
) : !validationStatus ? (
<span className="flex">Build to validate status.</span>
) : (
<div className="max-h-96 overflow-auto">
{typeof validationStatus.params === "string"
? `${durationString}\n${validationStatus.params}`
.split("\n")
.map((line, index) => <div key={index}>{line}</div>)
: durationString}
</div>
)
}
>
<div>
{renderIconStatusComponents(
data?.buildStatus,
validationStatus
)}
</div>
</Tooltip>
</div>
</div>
</div>

View file

@ -63,7 +63,7 @@ export default function IOInputField({
}
}
return (
<div className="font-xl flex h-full w-full flex-col gap-4 p-4 font-semibold">
<div className="font-xl flex items-start h-full w-full flex-col gap-4 p-4 font-semibold">
{inputType}
{handleInputType()}
</div>

View file

@ -44,7 +44,7 @@ export default function IOOutputView({
}
}
return (
<div className="font-xl flex h-full w-full flex-col gap-4 p-4 font-semibold">
<div className="font-xl flex h-full items-start w-full flex-col gap-4 p-4 font-semibold">
{outputType}
{handleOutputType()}
</div>

View file

@ -1,9 +1,7 @@
import { ReactNode, useState } from "react";
import { useEffect, useState } from "react";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import BaseModal from "../../modals/baseModal";
import useFlowStore from "../../stores/flowStore";
import { NodeType } from "../../types/flow";
import { isInputType, isOutputType } from "../../utils/reactflowUtils";
import { cn } from "../../utils/utils";
import AccordionComponent from "../AccordionComponent";
import IOInputField from "../IOInputField";
@ -12,62 +10,40 @@ import IconComponent from "../genericIconComponent";
import NewChatView from "../newChatView";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
export default function IOView({ children, open, setOpen }): JSX.Element {
const inputs = useFlowStore((state) => state.inputs);
const outputs = useFlowStore((state) => state.outputs);
const inputIds = inputs.map((obj) => obj.id);
const outputIds = outputs.map((obj) => obj.id);
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const categories = getCategories();
const [selectedCategory, setSelectedCategory] = useState<number>(0);
const [showChat, setShowChat] = useState<boolean>(false);
const [selectedView, setSelectedView] = useState<{
type: string;
id?: string;
}>(handleInitialView());
const inputs = useFlowStore((state) => state.inputs).filter(
(input) => input.type !== "ChatInput"
);
const outputs = useFlowStore((state) => state.outputs).filter(
(output) => output.type !== "ChatOutput"
);
const nodes = useFlowStore((state) => state.nodes).filter(
(node) =>
(inputs.some((input) => input.id === node.id) ||
outputs.some((output) => output.id === node.id)) &&
node.type !== "ChatInput" &&
node.type !== "ChatOutput"
);
const haveChat = useFlowStore((state) => state.outputs).some(
(output) => output.type === "ChatOutput"
);
const [selectedTab, setSelectedTab] = useState(
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0
);
const [selectedViewField, setSelectedViewField] = useState<
{ type: string; id: string } | undefined
>(undefined);
type CategoriesType = { name: string; icon: string };
function handleInitialView() {
if (
outputs.map((output) => output.type).includes("ChatOutput") ||
inputs.map((input) => input.type).includes("ChatInput")
) {
return { type: "ChatOutput" };
}
return { type: "" };
}
function getCategories() {
const categories: CategoriesType[] = [];
if (inputs.filter((input) => input.type !== "ChatInput").length > 0)
categories.push({ name: "Inputs", icon: "TextCursorInput" });
if (outputs.filter((output) => output.type !== "ChatOutput").length > 0)
categories.push({ name: "Outputs", icon: "TerminalSquare" });
return categories;
}
function handleSelectChange(): ReactNode {
const { type, id } = selectedView;
if (type === "ChatOutput") return <NewChatView />;
if (isInputType(type))
return <IOInputField inputId={id!} inputType={type} />;
if (isOutputType(type))
return <IOOutputView outputId={id!} outputType={type} />;
else return undefined;
}
function UpdateAccordion() {
return (categories[selectedCategory]?.name ?? "Inputs") === "Inputs"
? inputs
: outputs;
}
useEffect(() => {
setSelectedViewField(undefined);
setSelectedTab(inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0);
}, [inputs.length, outputs.length]);
return (
<BaseModal
size={handleSelectChange() ? "large" : "small"}
size={haveChat ? "large" : "small"}
open={open}
setOpen={setOpen}
>
@ -85,125 +61,173 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
</BaseModal.Header>
<BaseModal.Content>
<div className="flex-max-width mt-2 h-[80vh]">
<div
className={cn(
"mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide",
handleSelectChange() ? "w-2/6" : "w-full"
)}
>
<div className="flex w-full items-center justify-between py-2">
<div className="flex items-start gap-4">
{categories.map((category, index) => {
return (
//hide chat button if chat is alredy on the view
<Button
onClick={() => setSelectedCategory(index)}
variant={
index === selectedCategory ? "primary" : "secondary"
}
key={index}
>
<IconComponent
name={category.icon}
className=" file-component-variable"
/>
<span className="file-component-variables-span text-md">
{category.name}
</span>
</Button>
);
})}
</div>
{(outputs.map((output) => output.type).includes("ChatOutput") ||
inputs.map((output) => output.type).includes("chatInput")) &&
selectedView.type !== "ChatOutput" && (
<Button
onClick={() => setSelectedView({ type: "ChatOutput" })}
variant="outline"
key={"chat"}
className="self-end px-2.5"
>
<IconComponent
name="MessageSquareMore"
className="h-5 w-5"
/>
</Button>
)}
</div>
<div className="mx-2 mb-2 mt-4 flex items-center gap-2 font-semibold">
{categories[selectedCategory]?.name === "Inputs" && (
<>
<IconComponent name={"FormInput"} />
Text Inputs
</>
{selectedTab !== 0 && (
<div
className={cn(
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start overflow-auto scrollbar-hide",
haveChat ? "w-2/6" : "w-full"
)}
{categories[selectedCategory]?.name === "Outputs" && (
<>
<IconComponent name={"ChevronRightSquare"} />
Prompt Outputs
</>
)}
</div>
{UpdateAccordion()
.filter(
(input) =>
input.type !== "ChatInput" && input.type !== "ChatOutput"
)
.map((input, index) => {
const node: NodeType = nodes.find(
(node) => node.id === input.id
)!;
return (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{input.id}
</Badge>
<div
className="-mb-1 pr-4"
onClick={(event) => {
event.stopPropagation();
setSelectedView({
type: input.type,
id: input.id,
});
}}
>
<IconComponent
className="h-4 w-4"
name="ExternalLink"
></IconComponent>
</div>
</div>
}
key={index}
keyValue={input.id}
>
<div className="file-component-tab-column">
<div className="">
{node &&
(categories[selectedCategory]?.name === "Inputs" ? (
<IOInputField
inputType={input.type}
inputId={input.id}
/>
) : (
<IOOutputView
outputType={input.type}
outputId={input.id}
/>
))}
</div>
</div>
</AccordionComponent>
>
<Tabs
value={selectedTab.toString()}
className={"api-modal-tabs "}
onValueChange={(value) => {
setSelectedTab(Number(value));
}}
>
<div className="api-modal-tablist-div">
<TabsList>
{inputs.length > 0 && (
<TabsTrigger value={"1"}>Inputs</TabsTrigger>
)}
{outputs.length > 0 && (
<TabsTrigger value={"2"}>Outputs</TabsTrigger>
)}
</TabsList>
</div>
<TabsContent
value={"1"}
className="api-modal-tabs-content mt-4"
>
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
<IconComponent className="h-4 w-4" name={"Type"} />
Text Inputs
</div>
);
})}
</div>
{handleSelectChange() ? (
handleSelectChange()
{nodes
.filter((node) =>
inputs.some((input) => input.id === node.id)
)
.map((node, index) => {
const input = inputs.find(
(input) => input.id === node.id
)!;
return (
<div
className="file-component-accordion-div"
key={index}
>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{input.id}
</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>
}
key={index}
keyValue={input.id}
>
<div className="file-component-tab-column">
<div className="">
{input && (
<IOInputField
inputType={input.type}
inputId={input.id}
/>
)}
</div>
</div>
</AccordionComponent>
</div>
);
})}
</TabsContent>
<TabsContent
value={"2"}
className="api-modal-tabs-content mt-4"
>
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
<IconComponent className="h-4 w-4" name={"Braces"} />
Prompt Outputs
</div>
{nodes
.filter((node) =>
outputs.some((output) => output.id === node.id)
)
.map((node, index) => {
const output = outputs.find(
(output) => output.id === node.id
)!;
return (
<div
className="file-component-accordion-div"
key={index}
>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{output.id}
</Badge>
{haveChat && (
<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}
keyValue={output.id}
>
<div className="file-component-tab-column">
<div className="">
{output && (
<IOOutputView
outputType={output.type}
outputId={output.id}
/>
)}
</div>
</div>
</AccordionComponent>
</div>
);
})}
</TabsContent>
</Tabs>
</div>
)}
{haveChat ? (
selectedViewField ? (
inputs.some((input) => input.id === selectedViewField.id) ? (
<IOInputField
inputType={selectedViewField.type!}
inputId={selectedViewField.id!}
/>
) : (
<IOOutputView
outputType={selectedViewField.type!}
outputId={selectedViewField.id!}
/>
)
) : (
<NewChatView />
)
) : (
<div className="absolute bottom-8 right-8">
<Button className="px-3">

View file

@ -14,7 +14,7 @@ import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { cn } from "../../../../utils/utils";
import Tooltip from "../../../TooltipComponent";
import ShadTooltip from "../../../ShadTooltipComponent";
import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
@ -125,8 +125,8 @@ export const MenuBar = ({
setOpen={setOpenSettings}
></FlowSettingsModal>
</div>
<Tooltip
title={
<ShadTooltip
content={
"Last saved at " +
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
hour: "numeric",
@ -134,8 +134,10 @@ export const MenuBar = ({
second: "numeric",
})
}
side="bottom"
styleClasses="cursor-default"
>
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
<div className="flex cursor-default items-center gap-1.5 text-sm text-muted-foreground">
<IconComponent
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
className={cn(
@ -145,7 +147,7 @@ export const MenuBar = ({
/>
{printByBuildStatus()}
</div>
</Tooltip>
</ShadTooltip>
</div>
) : (
<></>

View file

@ -17,7 +17,7 @@ import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
export default function newChatView(): JSX.Element {
export default function NewChatView(): JSX.Element {
const [chatValue, setChatValue] = useState("");
const {
flowPool,

View file

@ -681,4 +681,4 @@ export const LANGFLOW_SUPPORTED_TYPES = new Set([
export const priorityFields = new Set(["code", "template"]);
export const INPUT_TYPES = new Set(["ChatInput", "TextInput"]);
export const OUTPUT_TYPES = new Set(["ChatOutput"]);
export const OUTPUT_TYPES = new Set(["ChatOutput", "PromptTemplate"]);

View file

@ -182,7 +182,7 @@ export default function GenericModal({
{myModalTitle}
</span>
<IconComponent
name="FileText"
name={myModalTitle === "Edit Prompt" ? "TerminalSquare" : "FileText"}
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
@ -255,7 +255,7 @@ export default function GenericModal({
>
<div className="flex flex-wrap items-center">
<IconComponent
name="Variable"
name="Braces"
className=" -ml-px mr-1 flex h-4 w-4 text-primary"
/>
<span className="text-md font-semibold text-primary">

View file

@ -24,6 +24,7 @@ import {
generateNodeFromFlow,
getNodeId,
isValidConnection,
reconnectEdges,
validateSelection,
} from "../../../../utils/reactflowUtils";
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
@ -394,7 +395,7 @@ export default function Page({
if (
validateSelection(lastSelection!, edges).length === 0
) {
const { newFlow } = generateFlow(
const { newFlow, removedEdges } = generateFlow(
lastSelection!,
nodes,
edges,
@ -404,6 +405,10 @@ export default function Page({
newFlow,
getNodeId
);
const newEdges = reconnectEdges(
newGroupNode,
removedEdges
);
setNodes((oldNodes) => [
...oldNodes.filter(
(oldNodes) =>
@ -414,16 +419,17 @@ export default function Page({
),
newGroupNode,
]);
setEdges((oldEdges) =>
oldEdges.filter(
setEdges((oldEdges) => [
...oldEdges.filter(
(oldEdge) =>
!lastSelection!.nodes.some(
(selectionNode) =>
selectionNode.id === oldEdge.target ||
selectionNode.id === oldEdge.source
)
)
);
),
...newEdges,
]);
} else {
setErrorData({
title: "Invalid selection",

View file

@ -25,7 +25,7 @@ import {
expandGroupNode,
updateFlowPosition,
} from "../../../../utils/reactflowUtils";
import { classNames } from "../../../../utils/utils";
import { classNames, cn } from "../../../../utils/utils";
export default function NodeToolbarComponent({
data,
@ -60,6 +60,7 @@ export default function NodeToolbarComponent({
const isMinimal = numberOfHandles <= 1;
const isGroup = data.node?.flow ? true : false;
const pinned = data.node?.pinned ?? false;
const paste = useFlowStore((state) => state.paste);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
@ -107,6 +108,9 @@ export default function NodeToolbarComponent({
takeSnapshot();
setShowNode(data.showNode ?? true ? false : true);
break;
case "Share":
if (hasApiKey || hasStore) setShowconfirmShare(true);
break;
case "Download":
downloadNode(flowComponent!);
break;
@ -222,7 +226,7 @@ export default function NodeToolbarComponent({
id={"code-input-node-toolbar-" + name}
/>
</div>
<IconComponent name="Code2" className="h-4 w-4" />
<IconComponent name="TerminalSquare" className="h-4 w-4" />
</button>
</ShadTooltip>
) : (
@ -269,22 +273,35 @@ export default function NodeToolbarComponent({
<IconComponent name="Copy" className="h-4 w-4" />
</button>
</ShadTooltip>
{hasStore && (
<ShadTooltip content="Share" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
!hasApiKey || !validApiKey ? " text-muted-foreground" : ""
<ShadTooltip content="Pin" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
)}
onClick={(event) => {
event.preventDefault();
setNode(data.id, (old) => ({
...old,
data: {
...old.data,
node: {
...old.data.node,
pinned: old.data?.node?.pinned ? false : true,
},
},
}));
}}
>
<IconComponent
name="Pin"
className={cn(
"h-4 w-4 transition-all",
pinned ? "animate-wiggle fill-current" : ""
)}
onClick={(event) => {
event.preventDefault();
if (hasApiKey || hasStore) setShowconfirmShare(true);
}}
>
<IconComponent name="Share3" className="-m-1 h-6 w-6" />
</button>
</ShadTooltip>
)}
/>
</button>
</ShadTooltip>
<Select onValueChange={handleSelectChange} value="">
<ShadTooltip content="More" side="top">
@ -340,6 +357,20 @@ export default function NodeToolbarComponent({
</SelectItem>
)
)}
{hasStore && (
<SelectItem
value={"Share"}
disabled={!hasApiKey || !validApiKey}
>
<div className="flex" data-testid="save-button-modal">
<IconComponent
name="Share3"
className="relative top-0.5 -m-1 mr-1 h-6 w-6"
/>{" "}
Share{" "}
</div>{" "}
</SelectItem>
)}
{!hasStore && (
<SelectItem value={"Download"}>
<div className="flex">

View file

@ -845,7 +845,7 @@
@apply flex h-full max-w-full flex-col overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] md:w-[75vw] lg:w-[75vw] xl:w-[76vw] 2xl:w-full;
}
.api-modal-tablist-div {
@apply flex items-center justify-between px-2;
@apply flex items-center justify-between px-2 py-2;
}
.api-modal-tabs-content {
@apply -mt-1 h-full w-full overflow-hidden px-4 pb-4;

View file

@ -597,8 +597,7 @@ export function generateFlow(
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
const uid = new ShortUniqueId({ length: 5 });
/* remove edges that are not connected to selected nodes on both ends
in future we can save this edges to when ungrouping reconect to the old nodes
*/
*/
newFlowData.edges = selection.edges.filter(
(edge) =>
selection.nodes.some((node) => node.id === edge.target) &&
@ -619,12 +618,48 @@ export function generateFlow(
// in the future we can use a better aproach using a set
return {
newFlow,
removedEdges: selection.edges.filter(
(edge) => !newFlowData.edges.includes(edge)
removedEdges: edges.filter(
(edge) =>
(selection.nodes.some((node) => node.id === edge.target) ||
selection.nodes.some((node) => node.id === edge.source)) &&
newFlowData.edges.every((e) => e.id !== edge.id)
),
};
}
export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
let newEdges = cloneDeep(excludedEdges);
if (!groupNode.data.node!.flow) return [];
const { nodes, edges } = groupNode.data.node!.flow!.data!;
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
newEdges.forEach((edge) => {
if (lastNode && edge.source === lastNode.id) {
edge.source = groupNode.id;
let newSourceHandle: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
newSourceHandle.id = groupNode.id;
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
edge.data.sourceHandle = newSourceHandle;
}
if (nodes.some((node) => node.id === edge.target)) {
const targetNode = nodes.find((node) => node.id === edge.target)!;
console.log("targetNode", targetNode);
const targetHandle: targetHandleType = scapeJSONParse(edge.targetHandle!);
console.log("targetHandle", targetHandle);
const proxy = { id: targetNode.id, field: targetHandle.fieldName };
let newTargetHandle: targetHandleType = cloneDeep(targetHandle);
newTargetHandle.id = groupNode.id;
newTargetHandle.proxy = proxy;
edge.target = groupNode.id;
newTargetHandle.fieldName = targetHandle.fieldName + "_" + targetNode.id;
edge.targetHandle = scapedJSONStringfy(newTargetHandle);
edge.data.targetHandle = newTargetHandle;
}
});
return newEdges;
}
export function filterFlow(
selection: OnSelectionChangeParams,
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,

View file

@ -6,6 +6,7 @@ import {
BookmarkPlus,
Bot,
Boxes,
Braces,
Cable,
Check,
CheckCircle2,
@ -18,6 +19,7 @@ import {
ChevronsRight,
ChevronsUpDown,
Circle,
CircleDot,
Clipboard,
Code,
Code2,
@ -102,6 +104,7 @@ import {
TextCursorInput,
ToyBrick,
Trash2,
Type,
Undo,
Ungroup,
Unplug,
@ -345,6 +348,7 @@ export const nodeIconsLucide: iconsType = {
FileSearch2,
ChevronRight,
Circle,
CircleDot,
Clipboard,
Code2,
Variable,
@ -414,6 +418,8 @@ export const nodeIconsLucide: iconsType = {
Sliders,
ScreenShare,
Code,
Type,
Braces,
FlaskConical,
AlertCircle,
Bot,