2024-06-05 14:27:06 +08:00
import { cloneDeep } from "lodash" ;
import {
Connection ,
Edge ,
Node ,
OnSelectionChangeParams ,
ReactFlowJsonObject ,
XYPosition ,
} from "reactflow" ;
// import { downloadFlowsFromDatabase } from "../controllers/API";
import {
APIKindType ,
APIObjectType ,
APITemplateType ,
TemplateVariableType
} from "../types/api" ;
import {
FlowType ,
NodeDataType ,
NodeType ,
sourceHandleType ,
targetHandleType ,
} from "../types/flow" ;
import {
findLastNodeType ,
generateFlowType ,
unselectAllNodesType ,
updateEdgesHandleIdsType ,
} from "../types/utils/reactflowUtils" ;
import { generateUUID } from "../utils" ;
import {
getFieldTitle ,
toTitleCase
} from "./utils" ;
export const LANGFLOW_SUPPORTED_TYPES = new Set ( [
"str" ,
"bool" ,
"float" ,
"code" ,
"prompt" ,
"file" ,
"int" ,
"dict" ,
"NestedDict" ,
] ) ;
// edges (线)文档
export function cleanEdges ( nodes : Node [ ] , edges : Edge [ ] ) {
let newEdges = cloneDeep ( edges ) ;
edges . forEach ( ( edge ) = > {
// check if the source and target node still exists
const sourceNode = nodes . find ( ( node ) = > node . id === edge . source ) ;
const targetNode = nodes . find ( ( node ) = > node . id === edge . target ) ;
if ( ! sourceNode || ! targetNode ) {
newEdges = newEdges . filter ( ( edg ) = > edg . id !== edge . id ) ;
return ;
}
// check if the source and target handle still exists
const sourceHandle = edge . sourceHandle ; //right
const targetHandle = edge . targetHandle ; //left
if ( targetHandle ) {
const field = targetHandle . split ( '|' ) [ 1 ]
const targetNodeTargetHandle =
targetNode . data . node ! . template [ field ] ? . input_types . join ( ';' )
+ '|' +
field + '|' +
targetNode . data . id
// if (targetNode.data.node!.template[field]?.proxy) {
// id.proxy = targetNode.data.node!.template[field]?.proxy;
// }
if ( targetNodeTargetHandle !== targetHandle ) {
newEdges = newEdges . filter ( ( e ) = > e . id !== edge . id ) ;
}
}
if ( sourceHandle ) {
const sourceNodeSourceHandle =
sourceNode . data . type + '|' +
sourceNode . data . id + '|' +
sourceNode . data . node ! . base_classes . join ( '|' )
if ( sourceNodeSourceHandle !== sourceHandle ) {
newEdges = newEdges . filter ( ( e ) = > e . id !== edge . id ) ;
}
}
} ) ;
return newEdges ;
}
export function unselectAllNodes ( { updateNodes , data } : unselectAllNodesType ) {
let newNodes = cloneDeep ( data ) ;
newNodes . forEach ( ( node : Node ) = > {
node . selected = false ;
} ) ;
updateNodes ( newNodes ! ) ;
}
// utils中的新方法
export function isValidConnection (
{ source , target , sourceHandle , targetHandle } : Connection ,
nodes : Node [ ] ,
edges : Edge [ ]
) {
const targetHandleObject : targetHandleType = scapeJSONParse ( targetHandle ! ) ;
const sourceHandleObject : sourceHandleType = scapeJSONParse ( sourceHandle ! ) ;
if (
targetHandleObject . inputTypes ? . some (
( n ) = > n === sourceHandleObject . dataType
) ||
sourceHandleObject . baseClasses . some (
( t ) = >
targetHandleObject . inputTypes ? . some ( ( n ) = > n === t ) ||
t === targetHandleObject . type
)
) {
let targetNode = nodes . find ( ( node ) = > node . id === target ! ) ? . data ? . node ;
if ( ! targetNode ) {
if ( ! edges . find ( ( e ) = > e . targetHandle === targetHandle ) ) {
return true ;
}
} else if (
( ! targetNode . template [ targetHandleObject . fieldName ] . list &&
! edges . find ( ( e ) = > e . targetHandle === targetHandle ) ) ||
targetNode . template [ targetHandleObject . fieldName ] . list
) {
return true ;
}
}
return false ;
}
export function removeApiKeys ( flow : FlowType ) : FlowType {
let cleanFLow = cloneDeep ( flow ) ;
cleanFLow . data ! . nodes . forEach ( ( node ) = > {
for ( const key in node . data . node . template ) {
if ( node . data . node . template [ key ] . password ) {
node . data . node . template [ key ] . value = "" ;
}
}
} ) ;
return cleanFLow ;
}
export function updateTemplate (
reference : APITemplateType ,
objectToUpdate : APITemplateType
) : APITemplateType {
let clonedObject : APITemplateType = cloneDeep ( reference ) ;
// Loop through each key in the reference object
for ( const key in clonedObject ) {
// If the key is not in the object to update, add it
if ( objectToUpdate [ key ] && objectToUpdate [ key ] . value ) {
clonedObject [ key ] . value = objectToUpdate [ key ] . value ;
}
if (
objectToUpdate [ key ] &&
objectToUpdate [ key ] . advanced !== null &&
objectToUpdate [ key ] . advanced !== undefined
) {
clonedObject [ key ] . advanced = objectToUpdate [ key ] . advanced ;
}
}
return clonedObject ;
}
export const processDataFromFlow = ( flow : FlowType , refreshIds = true ) = > {
let data = flow ? . data ? flow.data : null ;
if ( data ) {
processFlowEdges ( flow ) ;
//prevent node update for now
// processFlowNodes(flow);
//add animation to text type edges
updateEdges ( data . edges ) ;
// updateNodes(data.nodes, data.edges);
if ( refreshIds ) updateIds ( data ) ; // Assuming updateIds is defined elsewhere
}
return data ;
} ;
// utils中的新方法
export function updateIds ( newFlow : ReactFlowJsonObject ) {
let idsMap = { } ;
if ( newFlow . nodes )
newFlow . nodes . forEach ( ( node : NodeType ) = > {
// Generate a unique node ID
let newId = getNodeId (
node . data . node ? . flow ? "GroupNode" : node . data . type
) ;
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 ) = > {
edge . source = idsMap [ edge . source ] ;
edge . target = idsMap [ edge . target ] ;
const sourceHandleObject : sourceHandleType = scapeJSONParse (
edge . sourceHandle !
) ;
edge . sourceHandle = scapedJSONStringfy ( {
. . . sourceHandleObject ,
id : edge.source ,
} ) ;
if ( edge . data ? . sourceHandle ? . id ) {
edge . data . sourceHandle . id = edge . source ;
}
const targetHandleObject : targetHandleType = scapeJSONParse (
edge . targetHandle !
) ;
edge . targetHandle = scapedJSONStringfy ( {
. . . targetHandleObject ,
id : edge.target ,
} ) ;
if ( edge . data ? . targetHandle ? . id ) {
edge . data . targetHandle . id = edge . target ;
}
edge . id =
"reactflow__edge-" +
edge . source +
edge . sourceHandle +
"-" +
edge . target +
edge . targetHandle ;
} ) ;
return idsMap ;
}
export function buildTweaks ( flow : FlowType ) {
return flow . data ! . nodes . reduce ( ( acc , node ) = > {
acc [ node . data . id ] = { } ;
return acc ;
} , { } ) ;
}
export function validateNode ( node : NodeType , edges : Edge [ ] ) : Array < string > {
if ( ! node . data ? . node ? . template || ! Object . keys ( node . data . node . template ) ) {
return [
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!" ,
] ;
}
const {
type ,
node : { template } ,
} = node . data ;
return Object . keys ( template ) . reduce ( ( errors : Array < string > , t ) = > {
if (
template [ t ] . required &&
template [ t ] . show &&
( template [ t ] . value === undefined ||
template [ t ] . value === null ||
template [ t ] . value === "" ) &&
! edges . some (
( edge ) = >
( scapeJSONParse ( edge . targetHandle ! ) as targetHandleType ) . fieldName ===
t &&
( scapeJSONParse ( edge . targetHandle ! ) as targetHandleType ) . id ===
node . id
)
) {
errors . push ( ` ${ type } is missing ${ getFieldTitle ( template , t ) } . ` ) ;
} else if (
template [ t ] . type === "dict" &&
template [ t ] . required &&
template [ t ] . show &&
( template [ t ] . value !== undefined ||
template [ t ] . value !== null ||
template [ t ] . value !== "" )
) {
if ( hasDuplicateKeys ( template [ t ] . value ) )
errors . push (
` ${ type } ( ${ getFieldTitle (
template ,
t
) } ) contains duplicate keys with the same values . `
) ;
if ( hasEmptyKey ( template [ t ] . value ) )
errors . push (
` ${ type } ( ${ getFieldTitle ( template , t ) } ) field must not be empty. `
) ;
}
return errors ;
} , [ ] as string [ ] ) ;
}
export function validateNodes ( nodes : Node [ ] , edges : Edge [ ] ) {
if ( nodes . length === 0 ) {
return [
"No nodes found in the flow. Please add at least one node to the flow." ,
] ;
}
return nodes . flatMap ( ( n : NodeType ) = > validateNode ( n , edges ) ) ;
}
export function updateEdges ( edges : Edge [ ] ) {
if ( edges )
edges . forEach ( ( edge ) = > {
const targetHandleObject : targetHandleType = scapeJSONParse (
edge . targetHandle !
) ;
edge . className =
( targetHandleObject . type === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 " ) + " stroke-connection" ;
edge . animated = targetHandleObject . type === "Text" ;
} ) ;
}
export function addVersionToDuplicates ( flow : FlowType , flows : FlowType [ ] ) {
const existingNames = flows . map ( ( item ) = > item . name ) ;
let newName = flow . name ;
let count = 1 ;
while ( existingNames . includes ( newName ) ) {
newName = ` ${ flow . name } ( ${ count } ) ` ;
count ++ ;
}
return newName ;
}
export function updateEdgesHandleIds ( {
edges ,
nodes ,
} : updateEdgesHandleIdsType ) : Edge [ ] {
let newEdges = cloneDeep ( edges ) ;
newEdges . forEach ( ( edge ) = > {
const sourceNodeId = edge . source ;
const targetNodeId = edge . target ;
const sourceNode = nodes . find ( ( node ) = > node . id === sourceNodeId ) ;
const targetNode = nodes . find ( ( node ) = > node . id === targetNodeId ) ;
let source = edge . sourceHandle ;
let target = edge . targetHandle ;
//right
let newSource : sourceHandleType ;
//left
let newTarget : targetHandleType ;
if ( target && targetNode ) {
let field = target . split ( "|" ) [ 1 ] ;
newTarget = {
type : targetNode . data . node ! . template [ field ] . type ,
fieldName : field ,
id : targetNode.data.id ,
inputTypes : targetNode.data.node ! . template [ field ] . input_types ,
} ;
}
if ( source && sourceNode ) {
newSource = {
id : sourceNode.data.id ,
baseClasses : sourceNode.data.node ! . base_classes ,
dataType : sourceNode.data.type ,
} ;
}
edge . sourceHandle = scapedJSONStringfy ( newSource ! ) ;
edge . targetHandle = scapedJSONStringfy ( newTarget ! ) ;
const newData = {
sourceHandle : scapeJSONParse ( edge . sourceHandle ) ,
targetHandle : scapeJSONParse ( edge . targetHandle ) ,
} ;
edge . data = newData ;
} ) ;
return newEdges ;
}
export function handleKeyDown (
e :
| React . KeyboardEvent < HTMLInputElement >
| React . KeyboardEvent < HTMLTextAreaElement > ,
inputValue : string | string [ ] | null ,
block : string
) {
//condition to fix bug control+backspace on Windows/Linux
if (
( typeof inputValue === "string" &&
( e . metaKey === true || e . ctrlKey === true ) &&
e . key === "Backspace" &&
( inputValue === block ||
inputValue ? . charAt ( inputValue ? . length - 1 ) === " " ||
/[!@#$%^&*()\-_=+[\]{}|;:'",.<>/?\\`´ ]/ . test ( inputValue ? . charAt ( inputValue ? . length - 1 ) ) ) ) ||
( navigator . userAgent . toUpperCase ( ) . includes ( "MAC" ) &&
e . ctrlKey === true &&
e . key === "Backspace" )
) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
}
if ( e . ctrlKey === true && e . key === "Backspace" && inputValue === block ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
}
}
export function handleOnlyIntegerInput (
event : React.KeyboardEvent < HTMLInputElement >
) {
if (
event . key === "." ||
event . key === "-" ||
event . key === "," ||
event . key === "e" ||
event . key === "E" ||
event . key === "+"
) {
event . preventDefault ( ) ;
}
}
export function getConnectedNodes (
edge : Edge ,
nodes : Array < NodeType >
) : Array < NodeType > {
const sourceId = edge . source ;
const targetId = edge . target ;
return nodes . filter ( ( node ) = > node . id === targetId || node . id === sourceId ) ;
}
export function convertObjToArray ( singleObject : object | string ) {
if ( typeof singleObject === "string" ) {
singleObject = JSON . parse ( singleObject ) ;
}
if ( Array . isArray ( singleObject ) ) return singleObject ;
let arrConverted : any [ ] = [ ] ;
if ( typeof singleObject === "object" ) {
for ( const key in singleObject ) {
if ( Object . prototype . hasOwnProperty . call ( singleObject , key ) ) {
const newObj = { } ;
newObj [ key ] = singleObject [ key ] ;
arrConverted . push ( newObj ) ;
}
}
}
return arrConverted ;
}
export function convertArrayToObj ( arrayOfObjects ) {
if ( ! Array . isArray ( arrayOfObjects ) ) return arrayOfObjects ;
let objConverted = { } ;
for ( const obj of arrayOfObjects ) {
for ( const key in obj ) {
if ( obj . hasOwnProperty ( key ) ) {
objConverted [ key ] = obj [ key ] ;
}
}
}
return objConverted ;
}
export function hasDuplicateKeys ( array ) {
const keys = { } ;
// Transforms an empty object into an object array without opening the 'editNode' modal to prevent the flow build from breaking.
if ( ! Array . isArray ( array ) ) array = [ { "" : "" } ] ;
for ( const obj of array ) {
for ( const key in obj ) {
if ( keys [ key ] ) {
return true ;
}
keys [ key ] = true ;
}
}
return false ;
}
export function hasEmptyKey ( objArray ) {
// Transforms an empty object into an array without opening the 'editNode' modal to prevent the flow build from breaking.
if ( ! Array . isArray ( objArray ) ) objArray = [ ] ;
for ( const obj of objArray ) {
for ( const key in obj ) {
if ( obj . hasOwnProperty ( key ) && key === "" ) {
return true ; // Found an empty key
}
}
}
return false ; // No empty keys found
}
export function convertValuesToNumbers ( arr ) {
return arr . map ( ( obj ) = > {
const newObj = { } ;
for ( const key in obj ) {
if ( obj . hasOwnProperty ( key ) ) {
let value = obj [ key ] ;
if ( /^\d+$/ . test ( value ) ) {
value = value ? . toString ( ) . trim ( ) ;
}
newObj [ key ] =
value === "" || isNaN ( value ) ? value . toString ( ) : Number ( value ) ;
}
}
return newObj ;
} ) ;
}
export function scapedJSONStringfy ( json : object ) : string {
return customStringify ( json ) . replace ( /"/g , "œ" ) ;
}
export function scapeJSONParse ( json : string ) : any {
let parsed = json . replace ( /œ/g , '"' ) ;
return JSON . parse ( parsed ) ;
}
// this function receives an array of edges and return true if any of the handles are not a json string
// 数据结构转新版本
export function checkOldEdgesHandles ( edges : Edge [ ] ) : boolean {
return edges . some (
( edge ) = >
! edge . sourceHandle ||
! edge . targetHandle ||
! edge . sourceHandle . includes ( "{" ) ||
! edge . targetHandle . includes ( "{" )
) ;
}
export function customStringify ( obj : any ) : string {
if ( typeof obj === "undefined" ) {
return "null" ;
}
if ( obj === null || typeof obj !== "object" ) {
if ( obj instanceof Date ) {
return ` " ${ obj . toISOString ( ) } " ` ;
}
return JSON . stringify ( obj ) ;
}
if ( Array . isArray ( obj ) ) {
const arrayItems = obj . map ( ( item ) = > customStringify ( item ) ) . join ( "," ) ;
return ` [ ${ arrayItems } ] ` ;
}
const keys = Object . keys ( obj ) . sort ( ) ;
const keyValuePairs = keys . map (
( key ) = > ` " ${ key } ": ${ customStringify ( obj [ key ] ) } `
) ;
return ` { ${ keyValuePairs . join ( "," ) } } ` ;
}
export function getMiddlePoint ( nodes : Node [ ] ) {
let middlePointX = 0 ;
let middlePointY = 0 ;
nodes . forEach ( ( node ) = > {
middlePointX += node . position . x ;
middlePointY += node . position . y ;
} ) ;
const totalNodes = nodes . length ;
const averageX = middlePointX / totalNodes ;
const averageY = middlePointY / totalNodes ;
return { x : averageX , y : averageY } ;
}
export function getNodeId ( nodeType : string ) {
return nodeType + "-" + generateUUID ( 5 ) ;
}
export function getHandleId (
source : string ,
sourceHandle : string ,
target : string ,
targetHandle : string
) {
return (
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
) ;
}
export function generateFlow (
selection : OnSelectionChangeParams ,
nodes : Node [ ] ,
edges : Edge [ ] ,
name : string
) : generateFlowType {
const newFlowData = { nodes , edges , viewport : { zoom : 1 , x : 0 , y : 0 } } ;
/ * r e m o v e e d g e s t h a t a r e n o t c o n n e c t e d t o s e l e c t e d n o d e s o n b o t h e n d s
* /
newFlowData . edges = selection . edges . filter (
( edge ) = >
selection . nodes . some ( ( node ) = > node . id === edge . target ) &&
selection . nodes . some ( ( node ) = > node . id === edge . source )
) ;
newFlowData . nodes = selection . nodes ;
const newFlow : FlowType = {
data : newFlowData ,
is_component : false ,
name : name ,
description : "" ,
//generating local id instead of using the id from the server, can change in the future
id : generateUUID ( 5 ) ,
status : 0 ,
write : false ,
guide_word : ""
} ;
// filter edges that are not connected to selected nodes on both ends
// using O(n²) aproach because the number of edges is small
// in the future we can use a better aproach using a set
return {
newFlow ,
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 ) = > {
// 选中的 node 有链接其他 node
if ( lastNode && edge . source === lastNode . id ) {
edge . source = groupNode . id ;
const sourceHandleArr = edge . sourceHandle . split ( '|' )
sourceHandleArr [ 1 ] = groupNode . id
edge . sourceHandle = sourceHandleArr . join ( '|' ) ;
// edge.data.sourceHandle = newSourceHandle;
}
// 选中的 node 有被链接的 node
if ( nodes . some ( ( node ) = > node . id === edge . target ) ) {
const targetNode = nodes . find ( ( node ) = > node . id === edge . target ) ! ;
const targetHandleArr = edge . targetHandle . split ( '|' )
targetHandleArr [ targetHandleArr . length - 1 ] = groupNode . id ;
targetHandleArr [ 1 ] = targetHandleArr [ 1 ] + "_" + targetNode . id ;
// const proxy = { id: targetNode.id, field: targetHandle.fieldName };
// newTargetHandle.proxy = proxy;
edge . target = groupNode . id ;
edge . targetHandle = targetHandleArr . join ( '|' ) ;
// edge.data.targetHandle = newTargetHandle;
}
} ) ;
return newEdges ;
}
export function filterFlow (
selection : OnSelectionChangeParams ,
setNodes : ( update : Node [ ] | ( ( oldState : Node [ ] ) = > Node [ ] ) ) = > void ,
setEdges : ( update : Edge [ ] | ( ( oldState : Edge [ ] ) = > Edge [ ] ) ) = > void
) {
setNodes ( ( nodes ) = > nodes . filter ( ( node ) = > ! selection . nodes . includes ( node ) ) ) ;
setEdges ( ( edges ) = > edges . filter ( ( edge ) = > ! selection . edges . includes ( edge ) ) ) ;
}
export function findLastNode ( { nodes , edges } : findLastNodeType ) {
/ *
this function receives a flow and return the last node
* /
let lastNode = nodes . find ( ( n ) = > ! edges . some ( ( e ) = > e . source === n . id ) ) ;
return lastNode ;
}
export function updateFlowPosition ( NewPosition : XYPosition , flow : FlowType ) {
const middlePoint = getMiddlePoint ( flow . data ! . nodes ) ;
let deltaPosition = {
x : NewPosition.x - middlePoint . x ,
y : NewPosition.y - middlePoint . y ,
} ;
return {
. . . flow ,
data : {
. . . flow . data ! ,
nodes : flow.data ! . nodes . map ( ( node ) = > ( {
. . . node ,
position : {
x : node.position.x + deltaPosition . x ,
y : node.position.y + deltaPosition . y ,
} ,
} ) ) ,
} ,
} ;
}
export function concatFlows (
flow : FlowType ,
setNodes : ( update : Node [ ] | ( ( oldState : Node [ ] ) = > Node [ ] ) ) = > void ,
setEdges : ( update : Edge [ ] | ( ( oldState : Edge [ ] ) = > Edge [ ] ) ) = > void
) {
const { nodes , edges } = flow . data ! ;
setNodes ( ( old ) = > [ . . . old , . . . nodes ] ) ;
setEdges ( ( old ) = > [ . . . old , . . . edges ] ) ;
}
export function validateSelection (
selection : OnSelectionChangeParams ,
edges : Edge [ ]
) : Array < string > {
//add edges to selection if selection mode selected only nodes
if ( selection . edges . length === 0 ) {
selection . edges = edges ;
}
// get only edges that are connected to the nodes in the selection
// first creates a set of all the nodes ids
let nodesSet = new Set ( selection . nodes . map ( ( n ) = > n . id ) ) ;
// then filter the edges that are connected to the nodes in the set
let connectedEdges = selection . edges . filter (
( e ) = > nodesSet . has ( e . source ) && nodesSet . has ( e . target )
) ;
// add the edges to the selection
selection . edges = connectedEdges ;
let errorsArray : Array < string > = [ ] ;
// check if there is more than one node
if ( selection . nodes . length < 2 ) {
errorsArray . push ( "Please select more than one node" ) ;
}
//check if there are two or more nodes with free outputs
if (
selection . nodes . filter (
( n ) = > ! selection . edges . some ( ( e ) = > e . source === n . id )
) . length > 1
) {
2024-06-25 14:55:48 +08:00
errorsArray . push ( "请仅选择一个具有自由输出的节点" ) ;
// errorsArray.push("Please select only one node with free outputs");
2024-06-05 14:27:06 +08:00
}
// check if there is any node that does not have any connection
if (
selection . nodes . some (
( node ) = >
! selection . edges . some ( ( edge ) = > edge . target === node . id ) &&
! selection . edges . some ( ( edge ) = > edge . source === node . id )
)
) {
2024-06-25 14:55:48 +08:00
errorsArray . push ( "请仅选择已连接的节点" ) ;
// errorsArray.push("Please select only nodes that are connected");
2024-06-05 14:27:06 +08:00
}
return errorsArray ;
}
function updateGroupNodeTemplate ( template : APITemplateType ) {
/ * t h i s f u n c t i o n r e c e i v e s a t e m p l a t e , i t e r a t e s f o r i t ' s i t e m s
updating the visibility of all basic types setting it to advanced true * /
Object . keys ( template ) . forEach ( ( key ) = > {
let type = template [ key ] . type ;
let input_types = template [ key ] . input_types ;
if (
LANGFLOW_SUPPORTED_TYPES . has ( type ) &&
! template [ key ] . required && // 非必填项group 中不展示
! input_types
) {
template [ key ] . advanced = true ;
}
//prevent code fields from showing on the group node
if ( type === "code" ) {
template [ key ] . show = false ;
}
} ) ;
return template ;
}
export function mergeNodeTemplates ( {
nodes ,
edges ,
} : {
nodes : NodeType [ ] ;
edges : Edge [ ] ;
} ) : APITemplateType {
/ * t h i s f u n c t i o n r e c e i v e s a f l o w a n d i t e r a t e t h r o w e a c h n o d e
and merge the templates with only the visible fields
if there are two keys with the same name in the flow , we will update the display name of each one
to show from which node it came from
* /
let template : APITemplateType = { } ;
nodes . forEach ( ( node ) = > {
let nodeTemplate = cloneDeep ( node . data . node ! . template ) ;
Object . keys ( nodeTemplate )
. filter ( ( field_name ) = > field_name . charAt ( 0 ) !== "_" )
. forEach ( ( key ) = > {
if ( ! isHandleConnected ( edges , key , nodeTemplate [ key ] , node . id ) ) {
template [ key + "_" + node . id ] = nodeTemplate [ key ] ;
template [ key + "_" + node . id ] . proxy = { id : node.id , field : key } ;
if ( node . type === "groupNode" ) {
template [ key + "_" + node . id ] . display_name =
node . data . node ! . flow ! . name + " - " + nodeTemplate [ key ] . name ;
} else {
template [ key + "_" + node . id ] . display_name =
//data id already has the node name on it
nodeTemplate [ key ] . display_name
? nodeTemplate [ key ] . display_name
: nodeTemplate [ key ] . name
? toTitleCase ( nodeTemplate [ key ] . name )
: toTitleCase ( key ) ;
}
}
} ) ;
} ) ;
return template ;
}
function isHandleConnected (
edges : Edge [ ] ,
key : string ,
field : TemplateVariableType ,
nodeId : string
) {
/ *
this function receives a flow and a handleId and check if there is a connection with this handle
* /
if ( field . proxy ) {
if (
edges . some (
( e ) = >
e . targetHandle ===
scapedJSONStringfy ( {
type : field . type ,
fieldName : key ,
id : nodeId ,
proxy : { id : field.proxy ! . id , field : field.proxy ! . field } ,
inputTypes : field.input_types ,
} as targetHandleType )
)
) {
return true ;
}
} else {
if ( edges . some (
( e ) = > e . targetHandle === ` ${ field . type } | ${ key } | ${ nodeId } `
) ) {
return true ;
}
}
return false ;
}
export function generateNodeTemplate ( Flow : FlowType ) {
/ *
this function receives a flow and generate a template for the group node
* /
let template = mergeNodeTemplates ( {
nodes : Flow.data ! . nodes ,
edges : Flow.data ! . edges ,
} ) ;
updateGroupNodeTemplate ( template ) ;
return template ;
}
export function generateNodeFromFlow (
flow : FlowType ,
getNodeId : ( type : string ) = > string
) : NodeType {
const { nodes } = flow . data ! ;
const outputNode = cloneDeep ( findLastNode ( flow . data ! ) ) ;
const position = getMiddlePoint ( nodes ) ;
let data = cloneDeep ( flow ) ;
const id = getNodeId ( outputNode ? . data . type ! ) ;
// 检查是否有 fileinput
const hasFileInput = flow . data . nodes . some ( ( node ) = > node . data . type === "InputFileNode" )
const newGroupNode : NodeType = {
data : {
id ,
type : hasFileInput ? 'InputFileNode' : outputNode ? . data . type ! ,
node : {
output_types : outputNode ! . data . node ! . output_types ,
display_name : "Group" ,
documentation : "" ,
base_classes : outputNode ! . data . node ! . base_classes ,
description : outputNode.data.node.description ,
template : generateNodeTemplate ( data ) ,
flow : data ,
} ,
} ,
id ,
position ,
type : "genericNode" ,
} ;
return newGroupNode ;
}
export function connectedInputNodesOnHandle (
nodeId : string ,
handleId : string ,
{ nodes , edges } : { nodes : NodeType [ ] ; edges : Edge [ ] }
) {
const connectedNodes : Array < { name : string ; id : string ; isGroup : boolean } > =
[ ] ;
// return the nodes connected to the input handle of the node
const TargetEdges = edges . filter ( ( e ) = > e . target === nodeId ) ;
TargetEdges . forEach ( ( edge ) = > {
if ( edge . targetHandle === handleId ) {
const sourceNode = nodes . find ( ( n ) = > n . id === edge . source ) ;
if ( sourceNode ) {
if ( sourceNode . type === "groupNode" ) {
let lastNode = findLastNode ( sourceNode . data . node ! . flow ! . data ! ) ;
while ( lastNode && lastNode . type === "groupNode" ) {
lastNode = findLastNode ( lastNode . data . node ! . flow ! . data ! ) ;
}
if ( lastNode ) {
connectedNodes . push ( {
name : sourceNode.data.node ! . flow ! . name ,
id : lastNode.id ,
isGroup : true ,
} ) ;
}
} else {
connectedNodes . push ( {
name : sourceNode.data.type ,
id : sourceNode.id ,
isGroup : false ,
} ) ;
}
}
}
} ) ;
return connectedNodes ;
}
function updateProxyIdsOnTemplate (
template : APITemplateType ,
idsMap : { [ key : string ] : string }
) {
Object . keys ( template ) . forEach ( ( key ) = > {
if ( template [ key ] . proxy && idsMap [ template [ key ] . proxy ! . id ] ) {
template [ key ] . proxy ! . id = idsMap [ template [ key ] . proxy ! . id ] ;
}
} ) ;
}
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 ] ;
}
edge . data . targetHandle = targetHandle ;
edge . targetHandle = scapedJSONStringfy ( targetHandle ) ;
} ) ;
}
// (新)
export function processFlowEdges ( flow : FlowType ) {
if ( ! flow . data || ! flow . data . edges ) return ;
if ( checkOldEdgesHandles ( flow . data . edges ) ) {
const newEdges = updateEdgesHandleIds ( flow . data ) ;
flow . data . edges = newEdges ;
}
//update edges colors
flow . data . edges . forEach ( ( edge ) = > {
edge . className = "" ;
edge . style = { stroke : "#555" } ;
} ) ;
}
export function expandGroupNode (
id : string ,
flow : FlowType ,
template : APITemplateType ,
nodes : Node [ ] ,
edges : Edge [ ] ,
setNodes : ( update : Node [ ] | ( ( oldState : Node [ ] ) = > Node [ ] ) ) = > void ,
setEdges : ( update : Edge [ ] | ( ( oldState : Edge [ ] ) = > Edge [ ] ) ) = > void
) {
// const idsMap = updateIds(flow!.data!);
// updateProxyIdsOnTemplate(template, idsMap);
let flowEdges = edges ;
// updateEdgesIds(flowEdges, idsMap);
const gNodes : NodeType [ ] = cloneDeep ( flow ? . data ? . nodes ! ) ;
const gEdges = cloneDeep ( flow ! . data ! . edges ) ;
//redirect edges to correct proxy node
let updatedEdges : Edge [ ] = [ ] ;
flowEdges . forEach ( ( edge ) = > {
let newEdge = cloneDeep ( edge ) ;
// group 组件输入线
if ( newEdge . target === id ) {
const targetHandleArr = newEdge . targetHandle . split ( "|" ) ;
const _index = targetHandleArr [ 1 ] . lastIndexOf ( '_' ) ;
const tempField = targetHandleArr [ 1 ] . slice ( 0 , _index )
const nodeId = targetHandleArr [ 1 ] . slice ( _index + 1 )
targetHandleArr [ 1 ] = tempField
targetHandleArr [ 2 ] = nodeId
newEdge . target = nodeId
newEdge . targetHandle = targetHandleArr . join ( '|' )
// const targetHandle: targetHandleType = newEdge.targetHandle;
// if (targetHandle.proxy) {
// let type = targetHandle.type;
// let field = targetHandle.proxy.field;
// let proxyId = targetHandle.proxy.id;
// let inputTypes = targetHandle.inputTypes;
// let node: NodeType = gNodes.find((n) => n.id === proxyId)!;
// if (node) {
// newEdge.target = proxyId;
// let newTargetHandle: targetHandleType = {
// fieldName: field,
// type,
// id: proxyId,
// inputTypes: inputTypes,
// };
// if (node.data.node?.flow) {
// newTargetHandle.proxy = {
// field: node.data.node.template[field].proxy?.field!,
// id: node.data.node.template[field].proxy?.id!,
// };
// }
// newEdge.data.targetHandle = newTargetHandle;
// newEdge.targetHandle = scapedJSONStringfy(newTargetHandle);
// }
// }
}
// group 组件输出线
if ( newEdge . source === id ) {
const lastNode = cloneDeep ( findLastNode ( flow ! . data ! ) ) ;
newEdge . source = lastNode ! . id ;
const sourceHandleArr = newEdge . sourceHandle . split ( '|' )
sourceHandleArr [ 1 ] = lastNode ! . id ;
// newEdge.data.sourceHandle = newSourceHandle;
newEdge . sourceHandle = sourceHandleArr . join ( '|' ) ;
}
if ( edge . target === id || edge . source === id ) {
updatedEdges . push ( newEdge ) ;
}
} ) ;
//update template values
Object . keys ( template ) . forEach ( ( key ) = > {
let { field , id } = template [ key ] . proxy ! ;
let nodeIndex = gNodes . findIndex ( ( n ) = > n . id === id ) ;
if ( nodeIndex !== - 1 ) {
let proxy : { id : string ; field : string } | undefined ;
let display_name : string | undefined ;
let show = gNodes [ nodeIndex ] . data . node ! . template [ field ] . show ;
let advanced = gNodes [ nodeIndex ] . data . node ! . template [ field ] . advanced ;
if ( gNodes [ nodeIndex ] . data . node ! . template [ field ] . display_name ) {
display_name =
gNodes [ nodeIndex ] . data . node ! . template [ field ] . display_name ;
} else {
display_name = gNodes [ nodeIndex ] . data . node ! . template [ field ] . name ;
}
if ( gNodes [ nodeIndex ] . data . node ! . template [ field ] . proxy ) {
proxy = gNodes [ nodeIndex ] . data . node ! . template [ field ] . proxy ;
}
gNodes [ nodeIndex ] . data . node ! . template [ field ] = template [ key ] ;
gNodes [ nodeIndex ] . data . node ! . template [ field ] . show = show ;
gNodes [ nodeIndex ] . data . node ! . template [ field ] . advanced = advanced ;
gNodes [ nodeIndex ] . data . node ! . template [ field ] . display_name = display_name ;
// keep the nodes selected after ungrouping
// gNodes[nodeIndex].selected = false;
if ( proxy ) {
gNodes [ nodeIndex ] . data . node ! . template [ field ] . proxy = proxy ;
} else {
delete gNodes [ nodeIndex ] . data . node ! . template [ field ] . proxy ;
}
}
} ) ;
const filteredNodes = [ . . . nodes . filter ( ( n ) = > n . id !== id ) , . . . gNodes ] ;
const filteredEdges = [
. . . edges . filter ( ( e ) = > e . target !== id && e . source !== id ) ,
. . . gEdges ,
. . . updatedEdges ,
] ;
setNodes ( filteredNodes ) ;
setEdges ( filteredEdges ) ;
}
export function getGroupStatus (
flow : FlowType ,
ssData : { [ key : string ] : { valid : boolean ; params : string } }
) {
let status = { valid : true , params : "Built sucessfully ✨" } ;
const { nodes } = flow . data ! ;
const ids = nodes . map ( ( n : NodeType ) = > n . data . id ) ;
ids . forEach ( ( id ) = > {
if ( ! ssData [ id ] ) {
status = ssData [ id ] ;
return ;
}
if ( ! ssData [ id ] . valid ) {
status = { valid : false , params : ssData [ id ] . params } ;
}
} ) ;
return status ;
}
export function createFlowComponent (
nodeData : NodeDataType ,
version : string
) : FlowType {
const flowNode : FlowType = {
data : {
edges : [ ] ,
nodes : [
{
data : { . . . nodeData , node : { . . . nodeData . node , official : false } } ,
id : nodeData.id ,
position : { x : 0 , y : 0 } ,
type : "genericNode" ,
} ,
] ,
viewport : { x : 1 , y : 1 , zoom : 1 } ,
} ,
description : nodeData.node?.description || "" ,
name : nodeData.node?.display_name || nodeData . type || "" ,
id : nodeData.id || "" ,
is_component : true ,
last_tested_version : version ,
status : 0 ,
write : false ,
guide_word : ""
} ;
return flowNode ;
}
export function downloadNode ( NodeFLow : any ) {
const element = document . createElement ( "a" ) ;
const file = new Blob ( [ JSON . stringify ( NodeFLow ) ] , {
type : "application/json" ,
} ) ;
element . href = URL . createObjectURL ( file ) ;
element . download = ` ${ NodeFLow . name || NodeFLow . node . display_name } .json ` ;
element . click ( ) ;
}
export function updateComponentNameAndType (
data : any ,
component : NodeDataType
) { }
export function removeFileNameFromComponents ( flow : FlowType ) {
flow . data ! . nodes . forEach ( ( node : NodeType ) = > {
Object . keys ( node . data . node ! . template ) . forEach ( ( field ) = > {
if ( node . data . node ? . template [ field ] . type === "file" ) {
node . data . node ! . template [ field ] . value = "" ;
}
} ) ;
if ( node . data . node ? . flow ) {
removeFileNameFromComponents ( node . data . node . flow ) ;
}
} ) ;
}
export function typesGenerator ( data : APIObjectType ) {
return Object . keys ( data )
. reverse ( )
. reduce ( ( acc , curr ) = > {
Object . keys ( data [ curr ] ) . forEach ( ( c : keyof APIKindType ) = > {
acc [ c ] = curr ;
// Add the base classes to the accumulator as well.
data [ curr ] [ c ] . base_classes ? . forEach ( ( b ) = > {
acc [ b ] = curr ;
} ) ;
} ) ;
return acc ;
} , { } ) ;
}
export function templatesGenerator ( data : APIObjectType ) {
return Object . keys ( data ) . reduce ( ( acc , curr ) = > {
Object . keys ( data [ curr ] ) . forEach ( ( c : keyof APIKindType ) = > {
//prevent wrong overwriting of the component template by a group of the same type
if ( ! data [ curr ] [ c ] . flow ) acc [ c ] = data [ curr ] [ c ] ;
} ) ;
return acc ;
} , { } ) ;
}
export function downloadFlow (
flow : FlowType ,
flowName : string ,
flowDescription? : string
) {
let clonedFlow = cloneDeep ( flow ) ;
removeFileNameFromComponents ( clonedFlow ) ;
// create a data URI with the current flow data
const jsonString = ` data:text/json;chatset=utf-8, ${ encodeURIComponent (
JSON . stringify ( {
. . . clonedFlow ,
name : flowName ,
description : flowDescription ,
} )
) } ` ;
// create a link element and set its properties
const link = document . createElement ( "a" ) ;
link . href = jsonString ;
link . download = ` ${ flowName && flowName != "" ? flowName : flow.name } .json ` ;
// simulate a click on the link element to trigger the download
link . click ( ) ;
}
export const createNewFlow = (
flowData : ReactFlowJsonObject ,
flow : FlowType
) = > {
return {
description : flow?.description ? ? '' ,
name : flow?.name ? flow . name : "Untitled document" ,
data : flowData ,
id : "" ,
is_component : flow?.is_component ? ? false ,
} ;
} ;