Merge branch 'bug/component_share' into dev

This commit is contained in:
Lucas Oliveira 2023-12-10 22:35:20 -03:00
commit 54064737e8
16 changed files with 216 additions and 170 deletions

View file

@ -1,11 +1,12 @@
import warnings
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List
from fastapi import HTTPException from fastapi import HTTPException
from platformdirs import user_cache_dir
from langflow.services.store.schema import StoreComponentCreate from langflow.services.store.schema import StoreComponentCreate
from langflow.services.store.utils import get_lf_version_from_pypi from langflow.services.store.utils import get_lf_version_from_pypi
import warnings
from platformdirs import user_cache_dir
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.database.models.flow.model import Flow from langflow.services.database.models.flow.model import Flow
@ -62,7 +63,7 @@ def build_input_keys_response(langchain_object, artifacts):
return input_keys_response return input_keys_response
def update_frontend_node_with_template_values(frontend_node, raw_template_data): def update_frontend_node_with_template_values(frontend_node, raw_frontend_node):
""" """
Updates the given frontend node with values from the raw template data. Updates the given frontend node with values from the raw template data.
@ -70,19 +71,28 @@ def update_frontend_node_with_template_values(frontend_node, raw_template_data):
:param raw_template_data: A dict representing raw template data. :param raw_template_data: A dict representing raw template data.
:return: Updated frontend node. :return: Updated frontend node.
""" """
if not is_valid_data(frontend_node, raw_template_data): if not is_valid_data(frontend_node, raw_frontend_node):
return frontend_node return frontend_node
update_template_values(frontend_node["template"], raw_template_data.template) # Check if the display_name is different than "CustomComponent"
# if so, update the display_name in the frontend_node
if raw_frontend_node["display_name"] != "CustomComponent":
frontend_node["display_name"] = raw_frontend_node["display_name"]
update_template_values(frontend_node["template"], raw_frontend_node["template"])
return frontend_node return frontend_node
def is_valid_data(frontend_node, raw_template_data): def raw_frontend_data_is_valid(raw_frontend_data):
"""Check if the raw frontend data is valid for processing."""
return "template" in raw_frontend_data and "display_name" in raw_frontend_data
def is_valid_data(frontend_node, raw_frontend_data):
"""Check if the data is valid for processing.""" """Check if the data is valid for processing."""
return (
frontend_node and "template" in frontend_node and raw_template_data and hasattr(raw_template_data, "template") return frontend_node and "template" in frontend_node and raw_frontend_data_is_valid(raw_frontend_data)
)
def update_template_values(frontend_template, raw_template): def update_template_values(frontend_template, raw_template):

View file

@ -3,6 +3,9 @@ from typing import Annotated, Optional, Union
import sqlalchemy as sa import sqlalchemy as sa
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
from loguru import logger
from sqlmodel import select
from langflow.api.utils import update_frontend_node_with_template_values from langflow.api.utils import update_frontend_node_with_template_values
from langflow.api.v1.schemas import ( from langflow.api.v1.schemas import (
CustomComponentCode, CustomComponentCode,
@ -20,8 +23,6 @@ from langflow.services.cache.utils import save_uploaded_file
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_session_service, get_settings_service, get_task_service from langflow.services.deps import get_session, get_session_service, get_settings_service, get_task_service
from loguru import logger
from sqlmodel import select
try: try:
from langflow.worker import process_graph_cached_task from langflow.worker import process_graph_cached_task
@ -31,9 +32,10 @@ except ImportError:
raise NotImplementedError("Celery is not installed") raise NotImplementedError("Celery is not installed")
from langflow.services.task.service import TaskService
from sqlmodel import Session from sqlmodel import Session
from langflow.services.task.service import TaskService
# build router # build router
router = APIRouter(tags=["Base"]) router = APIRouter(tags=["Base"])
@ -218,7 +220,7 @@ async def custom_component(
built_frontend_node = build_custom_component_template(component, user_id=user.id) built_frontend_node = build_custom_component_template(component, user_id=user.id)
built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code) built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node)
return built_frontend_node return built_frontend_node

View file

@ -3,11 +3,12 @@ from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from uuid import UUID from uuid import UUID
from pydantic import BaseModel, Field, field_validator
from langflow.services.database.models.api_key.model import ApiKeyRead from langflow.services.database.models.api_key.model import ApiKeyRead
from langflow.services.database.models.base import orjson_dumps from langflow.services.database.models.base import orjson_dumps
from langflow.services.database.models.flow import FlowCreate, FlowRead from langflow.services.database.models.flow import FlowCreate, FlowRead
from langflow.services.database.models.user import UserRead from langflow.services.database.models.user import UserRead
from pydantic import BaseModel, Field, field_validator
class BuildStatus(Enum): class BuildStatus(Enum):
@ -157,7 +158,7 @@ class StreamData(BaseModel):
class CustomComponentCode(BaseModel): class CustomComponentCode(BaseModel):
code: str code: str
field: Optional[str] = None field: Optional[str] = None
template: Optional[dict] = None frontend_node: Optional[dict] = None
class CustomComponentResponseError(BaseModel): class CustomComponentResponseError(BaseModel):

View file

@ -3,8 +3,6 @@ from langflow.field_typing import Data
class Component(CustomComponent): class Component(CustomComponent):
display_name: str = "Custom Component"
description: str = "Create any custom component you want!"
documentation: str = "http://docs.langflow.org/components/custom" documentation: str = "http://docs.langflow.org/components/custom"
def build_config(self): def build_config(self):

View file

@ -19,8 +19,8 @@ from langflow.utils import validate
class CustomComponent(Component): class CustomComponent(Component):
display_name: Optional[str] = "Custom Component" display_name: Optional[str] = None
description: Optional[str] = "Custom Component" description: Optional[str] = None
code: Optional[str] = None code: Optional[str] = None
field_config: dict = {} field_config: dict = {}
code_class_base_inheritance: ClassVar[str] = "CustomComponent" code_class_base_inheritance: ClassVar[str] = "CustomComponent"

View file

@ -3,7 +3,6 @@ from typing import Optional
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template from langflow.template.template.base import Template
from pydantic import field_serializer
DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent
from typing import Optional, List, Dict, Union from typing import Optional, List, Dict, Union
@ -47,7 +46,7 @@ class Component(CustomComponent):
class CustomComponentFrontendNode(FrontendNode): class CustomComponentFrontendNode(FrontendNode):
name: str = "CustomComponent" name: str = "CustomComponent"
display_name: str = "Custom Component" display_name: Optional[str] = "CustomComponent"
beta: bool = True beta: bool = True
template: Template = Template( template: Template = Template(
type_name="CustomComponent", type_name="CustomComponent",
@ -67,9 +66,3 @@ class CustomComponentFrontendNode(FrontendNode):
) )
description: Optional[str] = None description: Optional[str] = None
base_classes: list[str] = [] base_classes: list[str] = []
@field_serializer("display_name")
def process_display_name(self, display_name: str) -> str:
"""Sets the display name of the frontend node."""
return display_name

View file

@ -156,11 +156,16 @@ export default function ParameterComponent({
}; };
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => { const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
if (!data.node) return;
if (data.node!.template[name].value !== newNodeClass.template[name].value) { if (data.node!.template[name].value !== newNodeClass.template[name].value) {
takeSnapshot(); takeSnapshot();
} }
data.node = newNodeClass; data.node! = {
data.node.template[name].value = code; ...newNodeClass,
description: newNodeClass.description ?? data.node!.description,
display_name: newNodeClass.display_name ?? data.node!.display_name,
};
data.node!.template[name].value = code;
updateNodeInternals(data.id); updateNodeInternals(data.id);
// Set state to pending // Set state to pending
//@ts-ignore //@ts-ignore
@ -174,19 +179,22 @@ export default function ParameterComponent({
} }
renderTooltips(); renderTooltips();
let flow = flows.find((flow) => flow.id === tabId); let flow = flows.find((flow) => flow.id === tabId);
if (reactFlowInstance && flow && flow.data) { setTimeout(() => {
cleanEdges({ //timeout necessary because ReactFlow updates are not async
flow: { if (reactFlowInstance && flow && flow.data) {
edges: flow.data!.edges, cleanEdges({
nodes: flow.data!.nodes, flow: {
}, edges: flow.data!.edges,
updateEdge: (edge) => { nodes: flow.data!.nodes,
reactFlowInstance.setEdges(edge); },
updateNodeInternals(data.id); updateEdge: (edge) => {
}, reactFlowInstance.setEdges(edge);
}); updateNodeInternals(data.id);
updateFlow(flow); },
} });
updateFlow(flow);
}
}, 50);
}; };
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);

View file

@ -16,7 +16,7 @@ import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow"; import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, getFieldTitle } from "../../utils/utils"; import { classNames, cn, getFieldTitle } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent"; import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({ export default function GenericNode({
@ -107,6 +107,8 @@ export default function GenericNode({
const showNode = data.showNode ?? true; const showNode = data.showNode ?? true;
const nameEditable = data.node?.flow || data.type === "CustomComponent";
return ( return (
<> <>
<NodeToolbar> <NodeToolbar>
@ -164,7 +166,7 @@ export default function GenericNode({
/> />
{showNode && ( {showNode && (
<div className="generic-node-tooltip-div"> <div className="generic-node-tooltip-div">
{data.node?.flow && inputName ? ( {nameEditable && inputName ? (
<div> <div>
<InputComponent <InputComponent
onBlur={() => { onBlur={() => {
@ -172,6 +174,7 @@ export default function GenericNode({
if (nodeName.trim() !== "") { if (nodeName.trim() !== "") {
setNodeName(nodeName); setNodeName(nodeName);
data.node!.display_name = nodeName; data.node!.display_name = nodeName;
updateNodeInternals(data.id);
} else { } else {
setNodeName(data.node!.display_name); setNodeName(data.node!.display_name);
} }
@ -194,7 +197,7 @@ export default function GenericNode({
<div className="generic-node-tooltip-div pr-2 text-primary"> <div className="generic-node-tooltip-div pr-2 text-primary">
{data.node?.display_name} {data.node?.display_name}
</div> </div>
{data.node?.flow && ( {nameEditable && (
<IconComponent <IconComponent
name="Pencil" name="Pencil"
className="h-4 w-4 text-ring" className="h-4 w-4 text-ring"
@ -361,57 +364,62 @@ export default function GenericNode({
<div <div
className={ className={
showNode showNode
? "generic-node-desc overflow-hidden " + ? "overflow-hidden " +
(data.node?.description !== "" ? "py-5" : "pb-5") (data.node?.description === "" && !nameEditable
? "pb-5"
: "py-5")
: "" : ""
} }
> >
{data.node?.description !== "" && <div className="generic-node-desc">
showNode && {showNode && nameEditable && inputDescription ? (
data.node?.flow && <Textarea
inputDescription ? ( autoFocus
<Textarea onBlur={() => {
autoFocus setInputDescription(false);
onBlur={() => {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
setNodeDescription(nodeDescription); setNodeDescription(nodeDescription);
data.node!.description = nodeDescription; data.node!.description = nodeDescription;
} else { updateNodeInternals(data.id);
setNodeDescription(data.node!.description); }}
} value={nodeDescription}
}} onChange={(e) => setNodeDescription(e.target.value)}
value={nodeDescription} onKeyDown={(e) => {
onChange={(e) => setNodeDescription(e.target.value)} handleKeyDown(e, nodeDescription, "");
onKeyDown={(e) => { if (
handleKeyDown(e, nodeDescription, ""); e.key === "Enter" &&
if ( e.shiftKey === false &&
e.key === "Enter" && e.ctrlKey === false &&
e.shiftKey === false && e.altKey === false
e.ctrlKey === false && ) {
e.altKey === false setInputDescription(false);
) {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
setNodeDescription(nodeDescription); setNodeDescription(nodeDescription);
data.node!.description = nodeDescription; data.node!.description = nodeDescription;
} else { updateNodeInternals(data.id);
setNodeDescription(data.node!.description);
} }
} }}
}} />
/> ) : (
) : ( <div
<div className={cn(
className="generic-node-desc-text truncate-multiline word-break-break-word" "generic-node-desc-text truncate-multiline word-break-break-word",
onDoubleClick={() => { (data.node?.description === "" ||
setInputDescription(true); !data.node?.description) &&
takeSnapshot(); nameEditable
}} ? "font-light italic"
> : ""
{data.node?.description} )}
</div> onDoubleClick={() => {
)} setInputDescription(true);
takeSnapshot();
}}
>
{(data.node?.description === "" || !data.node?.description) &&
nameEditable
? "Double Click to Edit Description"
: data.node?.description}
</div>
)}
</div>
<> <>
{Object.keys(data.node!.template) {Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_") .filter((templateField) => templateField.charAt(0) !== "_")

View file

@ -3,6 +3,7 @@ import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label"; import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import { InputProps } from "../../types/components"; import { InputProps } from "../../types/components";
import { cn } from "../../utils/utils";
export const EditFlowSettings: React.FC<InputProps> = ({ export const EditFlowSettings: React.FC<InputProps> = ({
name, name,
@ -21,47 +22,65 @@ export const EditFlowSettings: React.FC<InputProps> = ({
} else { } else {
setIsMaxLength(false); setIsMaxLength(false);
} }
setName(value); setName!(value);
}; };
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => { const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setDescription(event.target.value); setDescription!(event.target.value);
}; };
return ( return (
<> <>
<Label> <Label>
<div className="edit-flow-arrangement"> <div className="edit-flow-arrangement">
<span className="font-medium">Name</span>{" "} <span className="font-medium">Name{setName ? "" : ":"}</span>{" "}
{isMaxLength && ( {isMaxLength && (
<span className="edit-flow-span">Character limit reached</span> <span className="edit-flow-span">Character limit reached</span>
)} )}
</div> </div>
<Input {setName ? (
className="nopan nodelete nodrag noundo nocopy mt-2 font-normal" <Input
onChange={handleNameChange} className="nopan nodelete nodrag noundo nocopy mt-2 font-normal"
type="text" onChange={handleNameChange}
name="name" type="text"
value={name ?? ""} name="name"
placeholder="Flow name" value={name ?? ""}
id="name" placeholder="Flow name"
maxLength={maxLength} id="name"
/> maxLength={maxLength}
/>
) : (
<span className="font-normal text-muted-foreground word-break-break-word">
{name}
</span>
)}
</Label> </Label>
<Label> <Label>
<div className="edit-flow-arrangement mt-3"> <div className="edit-flow-arrangement mt-3">
<span className="font-medium ">Description (optional)</span> <span className="font-medium ">
Description{setDescription ? " (optional)" : ":"}
</span>
</div> </div>
{setDescription ? (
<Textarea <Textarea
name="description" name="description"
id="description" id="description"
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
value={description!} value={description!}
placeholder="Flow description" placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal" className="mt-2 max-h-[100px] font-normal"
rows={3} rows={3}
/> />
) : (
<span
className={cn(
"font-normal text-muted-foreground word-break-break-word",
description === "" ? "font-light italic" : ""
)}
>
{description === "" ? "No description" : description}
</span>
)}
</Label> </Label>
</> </>
); );

View file

@ -357,10 +357,10 @@ export async function postCustomComponent(
code: string, code: string,
apiClass: APIClassType apiClass: APIClassType
): Promise<AxiosResponse<APIClassType>> { ): Promise<AxiosResponse<APIClassType>> {
let template = apiClass.template; // let template = apiClass.template;
return await api.post(`${BASE_URL_API}custom_component`, { return await api.post(`${BASE_URL_API}custom_component`, {
code, code,
template, frontend_node: apiClass,
}); });
} }

View file

@ -44,11 +44,9 @@ export default function ShareModal({
const { setSuccessData, setErrorData } = useContext(alertContext); const { setSuccessData, setErrorData } = useContext(alertContext);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [name, setName] = useState(component?.name ?? "");
const [description, setDescription] = useState(component?.description ?? "");
const [internalOpen, internalSetOpen] = useState(children ? false : true); const [internalOpen, internalSetOpen] = useState(children ? false : true);
const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
const nameComponent = is_component ? "Component" : "Flow"; const nameComponent = is_component ? "component" : "flow";
const [tags, setTags] = useState<{ id: string; name: string }[]>([]); const [tags, setTags] = useState<{ id: string; name: string }[]>([]);
const [loadingTags, setLoadingTags] = useState<boolean>(false); const [loadingTags, setLoadingTags] = useState<boolean>(false);
@ -61,6 +59,9 @@ export default function ShareModal({
const [loadingNames, setLoadingNames] = useState(false); const [loadingNames, setLoadingNames] = useState(false);
const name = component?.name ?? "";
const description = component?.description ?? "";
useEffect(() => { useEffect(() => {
if (open || internalOpen) { if (open || internalOpen) {
if (hasApiKey && hasStore) { if (hasApiKey && hasStore) {
@ -94,11 +95,6 @@ export default function ShareModal({
}); });
} }
useEffect(() => {
setName(component?.name ?? "");
setDescription(component?.description ?? "");
}, [component, open, internalOpen]);
const handleShareComponent = async (update = false) => { const handleShareComponent = async (update = false) => {
//remove file names from flows before sharing //remove file names from flows before sharing
removeFileNameFromComponents(component); removeFileNameFromComponents(component);
@ -120,11 +116,9 @@ export default function ShareModal({
is_component: is_component, is_component: is_component,
}); });
await saveFlow(flow!, true);
function successShare() { function successShare() {
if (is_component) { if (!is_component) {
addFlow(true, flow); saveFlow(flow!, true);
} }
setSuccessData({ setSuccessData({
title: `${nameComponent} shared successfully`, title: `${nameComponent} shared successfully`,
@ -223,13 +217,9 @@ export default function ShareModal({
/> />
</BaseModal.Header> </BaseModal.Header>
<BaseModal.Content> <BaseModal.Content>
<EditFlowSettings <div className="w-full rounded-lg border border-border p-4">
name={name} <EditFlowSettings name={name} description={description} />
invalidNameList={unavaliableNames.map((element) => element.name)} </div>
description={description}
setName={setName}
setDescription={setDescription}
/>
<div className="mt-3 flex h-8 w-full"> <div className="mt-3 flex h-8 w-full">
<TagsSelector <TagsSelector
tags={tags} tags={tags}
@ -297,8 +287,8 @@ export default function ShareModal({
</> </>
) : ( ) : (
<> <>
{is_component && !loadingNames ? "Save and " : ""}Share{" "} Share{" "}
{!is_component && !loadingNames ? "Flow" : ""} {!loadingNames && (!is_component ? "Flow" : "Component")}
</> </>
)} )}
</Button> </Button>

View file

@ -282,6 +282,17 @@ export default function Page({
let newX = _.cloneDeep(node); let newX = _.cloneDeep(node);
return newX; return newX;
}); });
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
saveCurrentFlowTimeout();
}, },
[setEdges, setNodes, takeSnapshot, addEdge] [setEdges, setNodes, takeSnapshot, addEdge]
); );

View file

@ -77,7 +77,15 @@ export default function NodeToolbarComponent({
useEffect(() => { useEffect(() => {
setFlowComponent(createFlowComponent(cloneDeep(data), version)); setFlowComponent(createFlowComponent(cloneDeep(data), version));
}, [data, data.node, showModalAdvanced]); }, [
data,
data.node,
data.node?.display_name,
data.node?.description,
data.node?.template,
showModalAdvanced,
showconfirmShare,
]);
const handleSelectChange = (event) => { const handleSelectChange = (event) => {
switch (event) { switch (event) {

View file

@ -311,10 +311,10 @@
@apply hover:text-accent-foreground hover:transition-all; @apply hover:text-accent-foreground hover:transition-all;
} }
.generic-node-desc { .generic-node-desc {
@apply h-full w-full text-foreground; @apply h-full px-5 mb-4 w-full text-foreground;
} }
.generic-node-desc-text { .generic-node-desc-text {
@apply w-full px-5 mb-4 text-sm text-muted-foreground; @apply w-full text-sm text-muted-foreground;
} }
.alert-icon { .alert-icon {

View file

@ -240,8 +240,8 @@ export type InputProps = {
name: string | null; name: string | null;
description: string | null; description: string | null;
maxLength?: number; maxLength?: number;
setName: (name: string) => void; setName?: (name: string) => void;
setDescription: (description: string) => void; setDescription?: (description: string) => void;
invalidNameList?: string[]; invalidNameList?: string[];
}; };

View file

@ -41,37 +41,35 @@ export function cleanEdges({
const targetNode = nodes.find((node) => node.id === edge.target); const targetNode = nodes.find((node) => node.id === edge.target);
if (!sourceNode || !targetNode) { if (!sourceNode || !targetNode) {
newEdges = newEdges.filter((edg) => edg.id !== edge.id); newEdges = newEdges.filter((edg) => edg.id !== edge.id);
return;
} }
// check if the source and target handle still exists // check if the source and target handle still exists
if (sourceNode && targetNode) { const sourceHandle = edge.sourceHandle; //right
const sourceHandle = edge.sourceHandle; //right const targetHandle = edge.targetHandle; //left
const targetHandle = edge.targetHandle; //left if (targetHandle) {
if (targetHandle) { const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle);
const targetHandleObject: targetHandleType = const field = targetHandleObject.fieldName;
scapeJSONParse(targetHandle); const id: targetHandleType = {
const field = targetHandleObject.fieldName; type: targetNode.data.node!.template[field]?.type,
const id: targetHandleType = { fieldName: field,
type: targetNode.data.node!.template[field]?.type, id: targetNode.data.id,
fieldName: field, inputTypes: targetNode.data.node!.template[field]?.input_types,
id: targetNode.data.id, };
inputTypes: targetNode.data.node!.template[field]?.input_types, if (targetNode.data.node!.template[field]?.proxy) {
}; id.proxy = targetNode.data.node!.template[field]?.proxy;
if (targetNode.data.node!.template[field]?.proxy) {
id.proxy = targetNode.data.node!.template[field]?.proxy;
}
if (scapedJSONStringfy(id) !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
} }
if (sourceHandle) { if (scapedJSONStringfy(id) !== targetHandle) {
const id: sourceHandleType = { newEdges = newEdges.filter((e) => e.id !== edge.id);
id: sourceNode.data.id, }
baseClasses: sourceNode.data.node!.base_classes, }
dataType: sourceNode.data.type, if (sourceHandle) {
}; const id: sourceHandleType = {
if (scapedJSONStringfy(id) !== sourceHandle) { id: sourceNode.data.id,
newEdges = newEdges.filter((e) => e.id !== edge.id); baseClasses: sourceNode.data.node!.base_classes,
} dataType: sourceNode.data.type,
};
if (scapedJSONStringfy(id) !== sourceHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
} }
} }
}); });
@ -761,7 +759,7 @@ export function generateNodeFromFlow(
display_name: "Group", display_name: "Group",
documentation: "", documentation: "",
base_classes: outputNode!.data.node!.base_classes, base_classes: outputNode!.data.node!.base_classes,
description: "double click to edit description", description: "",
template: generateNodeTemplate(data), template: generateNodeTemplate(data),
flow: data, flow: data,
}, },