Fixed Copy and Paste, refactored tabsContext code

This commit is contained in:
Lucas Oliveira 2023-05-22 19:54:32 -03:00
commit 610beb18dd
4 changed files with 629 additions and 543 deletions

View file

@ -6,13 +6,14 @@ import {
ReactNode, ReactNode,
useContext, useContext,
} from "react"; } from "react";
import { FlowType } from "../types/flow"; import { FlowType, NodeType } from "../types/flow";
import { LangFlowState, TabsContextType } from "../types/tabs"; import { LangFlowState, TabsContextType } from "../types/tabs";
import { normalCaseToSnakeCase, updateObject, updateTemplate } from "../utils"; import { normalCaseToSnakeCase, updateObject, updateTemplate } from "../utils";
import { alertContext } from "./alertContext"; import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext"; import { typesContext } from "./typesContext";
import { APITemplateType, TemplateVariableType } from "../types/api"; import { APITemplateType, TemplateVariableType } from "../types/api";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { addEdge } from "reactflow";
const TabsContextInitialValue: TabsContextType = { const TabsContextInitialValue: TabsContextType = {
save: () => {}, save: () => {},
@ -22,12 +23,14 @@ const TabsContextInitialValue: TabsContextType = {
removeFlow: (id: string) => {}, removeFlow: (id: string) => {},
addFlow: (flowData?: any) => {}, addFlow: (flowData?: any) => {},
updateFlow: (newFlow: FlowType) => {}, updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => 0, incrementNodeId: () => uuidv4(),
downloadFlow: (flow: FlowType) => {}, downloadFlow: (flow: FlowType) => {},
uploadFlow: () => {}, uploadFlow: () => {},
hardReset: () => {}, hardReset: () => {},
disableCP:false, disableCP:false,
setDisableCP:(state:boolean)=>{}, setDisableCP:(state:boolean)=>{},
getNodeId: () => "",
paste: (selection: {nodes: any, edges: any}, position: {x: number, y: number}) => {},
}; };
export const TabsContext = createContext<TabsContextType>( export const TabsContext = createContext<TabsContextType>(
@ -39,22 +42,23 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const [flows, setFlows] = useState<Array<FlowType>>([]); const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uuidv4()); const [id, setId] = useState(uuidv4());
const { templates } = useContext(typesContext); const { templates, reactFlowInstance } = useContext(typesContext);
const newNodeId = useRef(0); const newNodeId = useRef(uuidv4());
function incrementNodeId() { function incrementNodeId() {
newNodeId.current = newNodeId.current + 1; newNodeId.current = uuidv4();
return newNodeId.current; return newNodeId.current;
} }
function save() { function save() {
if (flows.length !== 0) if (flows.length !== 0)
window.localStorage.setItem( window.localStorage.setItem(
"tabsData", "tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) JSON.stringify({ tabIndex, flows, id})
); );
} }
useEffect(() => { useEffect(() => {
//save tabs locally //save tabs locally
// console.log(id)
save(); save();
}, [flows, id, tabIndex, newNodeId]); }, [flows, id, tabIndex, newNodeId]);
@ -64,46 +68,26 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (cookie && Object.keys(templates).length > 0) { if (cookie && Object.keys(templates).length > 0) {
let cookieObject: LangFlowState = JSON.parse(cookie); let cookieObject: LangFlowState = JSON.parse(cookie);
cookieObject.flows.forEach((flow) => { cookieObject.flows.forEach((flow) => {
// check if flow.data is null
if (flow.data) {
flow.data.nodes.forEach((node) => { flow.data.nodes.forEach((node) => {
// check if node.data.type is in templates if (Object.keys(templates[node.data.type]["template"]).length > 0) {
if (
Object.keys(templates).includes(node.data.type) &&
Object.keys(templates[node.data.type]["template"]).length > 0
) {
node.data.node.base_classes =
templates[node.data.type]["base_classes"];
flow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
.slice(0, 2)
.concat(templates[node.data.type]["base_classes"])
.join("|");
}
});
node.data.node.description =
templates[node.data.type]["description"];
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
templates[node.data.type][ templates[node.data.type][
"template" "template"
] as unknown as APITemplateType, ] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
); );
} }
}); });
}
}); });
setTabIndex(cookieObject.tabIndex); setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows); setFlows(cookieObject.flows);
setId(cookieObject.id); setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId;
} }
}, [templates]); }, [templates]);
function hardReset() { function hardReset() {
newNodeId.current = 0; newNodeId.current = uuidv4();
setTabIndex(0); setTabIndex(0);
setFlows([]); setFlows([]);
setId(uuidv4()); setId(uuidv4());
@ -130,6 +114,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}); });
} }
function getNodeId() {
return `dndnode_` + incrementNodeId();
}
/** /**
* Creates a file input and listens to a change event to upload a JSON flow file. * Creates a file input and listens to a change event to upload a JSON flow file.
* If the file type is application/json, the file is read and parsed into a JSON object. * If the file type is application/json, the file is read and parsed into a JSON object.
@ -183,6 +171,90 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* Add a new flow to the list of flows. * Add a new flow to the list of flows.
* @param flow Optional flow to add. * @param flow Optional flow to add.
*/ */
function paste(selectionInstance, position){
console.log(position);
console.log(selectionInstance)
let minimumX = Infinity;
let minimumY = Infinity;
let idsMap = {};
let nodes = reactFlowInstance.getNodes();
let edges = reactFlowInstance.getEdges();
selectionInstance.nodes.forEach((n) => {
if (n.position.y < minimumY) {
minimumY = n.position.y;
}
if (n.position.x < minimumX) {
minimumX = n.position.x;
}
});
const insidePosition = reactFlowInstance.project(position);
selectionInstance.nodes.forEach((n) => {
// Generate a unique node ID
let newId = getNodeId();
idsMap[n.id] = newId;
// Create a new node object
const newNode: NodeType = {
id: newId,
type: "genericNode",
position: {
x: insidePosition.x + n.position.x - minimumX,
y: insidePosition.y + n.position.y - minimumY,
},
data: {
...n.data,
id: newId,
},
};
// Add the new node to the list of nodes in state
nodes = nodes
.map((e) => ({ ...e, selected: false }))
.concat({ ...newNode, selected: false })
console.log(nodes);
});
reactFlowInstance.setNodes(nodes);
selectionInstance.edges.forEach((e) => {
let source = idsMap[e.source];
let target = idsMap[e.target];
let sourceHandleSplitted = e.sourceHandle.split("|");
let sourceHandle =
sourceHandleSplitted[0] +
"|" +
source +
"|" +
sourceHandleSplitted.slice(2).join("|");
let targetHandleSplitted = e.targetHandle.split("|");
let targetHandle =
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
let id =
"reactflow__edge-" +
source +
sourceHandle +
"-" +
target +
targetHandle;
edges = addEdge(
{
source,
target,
sourceHandle,
targetHandle,
id,
className: "animate-pulse",
selected: false,
},
edges.map((e) => ({ ...e, selected: false }))
);
console.log(edges);
});
reactFlowInstance.setEdges(edges);
};
function addFlow(flow?: FlowType) { function addFlow(flow?: FlowType) {
// Get data from the flow or set it to null if there's no flow provided. // Get data from the flow or set it to null if there's no flow provided.
const data = flow?.data ? flow.data : null; const data = flow?.data ? flow.data : null;
@ -191,25 +263,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (data) { if (data) {
data.nodes.forEach((node) => { data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) { if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.base_classes =
templates[node.data.type]["base_classes"];
data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
.slice(0, 2)
.concat(templates[node.data.type]["base_classes"])
.join("|");
}
});
node.data.node.description = templates[node.data.type]["description"];
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
templates[node.data.type]["template"] as unknown as APITemplateType, templates[node.data.type]["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
); );
} }
}); });
console.log(data);
} }
// Create a new flow with a default name if no flow is provided. // Create a new flow with a default name if no flow is provided.
let newFlow: FlowType = { let newFlow: FlowType = {
@ -223,6 +282,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
setId(uuidv4()); setId(uuidv4());
// Add the new flow to the list of flows. // Add the new flow to the list of flows.
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState, newFlow]; const newFlows = [...prevState, newFlow];
return newFlows; return newFlows;
@ -265,6 +325,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
updateFlow, updateFlow,
downloadFlow, downloadFlow,
uploadFlow, uploadFlow,
getNodeId,
paste,
}} }}
> >
{children} {children}

View file

@ -47,8 +47,8 @@ export default function TabComponent({
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28" className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
onBlur={() => { onBlur={() => {
setIsRename(false); setIsRename(false);
if (value !== "") {
setDisableCP(false); setDisableCP(false);
if (value !== "") {
let newFlow = _.cloneDeep(flow); let newFlow = _.cloneDeep(flow);
newFlow.name = value; newFlow.name = value;
updateFlow(newFlow); updateFlow(newFlow);

View file

@ -11,11 +11,13 @@ import ReactFlow, {
Connection, Connection,
Edge, Edge,
useKeyPress, useKeyPress,
useOnSelectionChange,
NodeDragHandler, NodeDragHandler,
OnEdgesDelete, OnEdgesDelete,
OnNodesDelete, OnNodesDelete,
SelectionDragHandler, SelectionDragHandler,
useOnViewportChange,
OnSelectionChangeParams,
OnNodesChange,
} from "reactflow"; } from "reactflow";
import _ from "lodash"; import _ from "lodash";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
@ -28,16 +30,17 @@ import { typesContext } from "../../contexts/typesContext";
import ConnectionLineComponent from "./components/ConnectionLineComponent"; import ConnectionLineComponent from "./components/ConnectionLineComponent";
import { FlowType, NodeType } from "../../types/flow"; import { FlowType, NodeType } from "../../types/flow";
import { APIClassType } from "../../types/api"; import { APIClassType } from "../../types/api";
import { isValidConnection } from "../../utils"; import {
isValidConnection,
} from "../../utils";
import useUndoRedo from "./hooks/useUndoRedo"; import useUndoRedo from "./hooks/useUndoRedo";
const nodeTypes = { const nodeTypes = {
genericNode: GenericNode, genericNode: GenericNode,
}; };
export default function FlowPage({ flow }: { flow: FlowType }) { export default function FlowPage({ flow }: { flow: FlowType }) {
let { updateFlow, incrementNodeId, disableCP} = let { updateFlow, disableCP, addFlow, getNodeId, paste } =
useContext(TabsContext); useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance, templates } = const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext); useContext(typesContext);
@ -45,86 +48,65 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo(); const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if ((event.ctrlKey || event.metaKey) && (event.key === 'c') && lastSelection && !disableCP) {
event.preventDefault();
setLastCopiedSelection(lastSelection);
}
if ((event.ctrlKey || event.metaKey) && (event.key === 'v') && lastCopiedSelection && !disableCP) {
event.preventDefault();
paste();
}
}
const [lastSelection, setLastSelection] = useState(null);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const [position, setPosition] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams>(null);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
useEffect(() => {
// this effect is used to attach the global event handlers
const onKeyDown = (event: KeyboardEvent) => {
console.log("keydownou", lastCopiedSelection, position)
if (
(event.ctrlKey || event.metaKey) &&
event.key === "c" &&
lastSelection &&
!disableCP
) {
event.preventDefault();
setLastCopiedSelection(_.cloneDeep(lastSelection));
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "v" &&
lastCopiedSelection &&
!disableCP
) {
event.preventDefault();
let bounds = reactFlowWrapper.current.getBoundingClientRect();
paste(lastCopiedSelection, {
x: position.x - bounds.left,
y: position.y - bounds.top,
});
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "g" &&
lastSelection
) {
event.preventDefault();
// addFlow(newFlow, false);
}
};
const handleMouseMove = (event) => { const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY }); setPosition({ x: event.clientX, y: event.clientY });
}; };
useOnSelectionChange({ document.addEventListener('keydown', onKeyDown);
onChange: (flow) => { setLastSelection(flow); }, document.addEventListener('mousemove', handleMouseMove);
})
let paste = () => { return () => {
let minimumX = Infinity; document.removeEventListener('keydown', onKeyDown);
let minimumY = Infinity; document.removeEventListener('mousemove', handleMouseMove);
let idsMap = {};
lastCopiedSelection.nodes.forEach((n) => {
if (n.position.y < minimumY) {
minimumY = n.position.y
}
if (n.position.x < minimumX) {
minimumX = n.position.x;
}
});
const bounds = reactFlowWrapper.current.getBoundingClientRect();
const insidePosition = reactFlowInstance.project({
x: position.x - bounds.left,
y: position.y - bounds.top
});
lastCopiedSelection.nodes.forEach((n) => {
// Generate a unique node ID
let newId = getId();
idsMap[n.id] = newId;
// Create a new node object
const newNode: NodeType = {
id: newId,
type: "genericNode",
position: {
x: insidePosition.x + n.position.x - minimumX,
y: insidePosition.y + n.position.y - minimumY,
},
data: {
...n.data,
id: newId,
},
}; };
}, [position, lastCopiedSelection, lastSelection]);
// Add the new node to the list of nodes in state
setNodes((nds) => nds.map((e) => ({ ...e, selected: false })).concat({ ...newNode, selected: false }));
})
lastCopiedSelection.edges.forEach((e) => { const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
let source = idsMap[e.source];
let target = idsMap[e.target];
let sourceHandleSplitted = e.sourceHandle.split('|');
let sourceHandle = sourceHandleSplitted[0] + '|' + source + '|' + sourceHandleSplitted.slice(2).join('|');
let targetHandleSplitted = e.targetHandle.split('|');
let targetHandle = targetHandleSplitted.slice(0, -1).join('|') + '|' + target;
let id = "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle;
setEdges((eds) =>
addEdge({ source, target, sourceHandle, targetHandle, id, className: "animate-pulse", selected: false }, eds.map((e) => ({ ...e, selected: false })))
);
})
}
const { setExtraComponent, setExtraNavigation } = useContext(locationContext); const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
@ -137,11 +119,6 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
); );
const { setViewport } = useReactFlow(); const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true); const edgeUpdateSuccessful = useRef(true);
function getId() {
return `dndnode_` + incrementNodeId();
}
useEffect(() => { useEffect(() => {
if (reactFlowInstance && flow) { if (reactFlowInstance && flow) {
flow.data = reactFlowInstance.toObject(); flow.data = reactFlowInstance.toObject();
@ -178,7 +155,21 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
(params: Connection) => { (params: Connection) => {
takeSnapshot(); takeSnapshot();
setEdges((eds) => setEdges((eds) =>
addEdge({ ...params, className: "animate-pulse" }, eds) addEdge(
{
...params,
style:
params.targetHandle.split("|")[0] === "Text"
? { stroke: "#333333", strokeWidth: 2 }
: { stroke: "#222222" },
className:
params.targetHandle.split("|")[0] === "Text"
? ""
: "animate-pulse",
animated: params.targetHandle.split("|")[0] === "Text",
},
eds
)
); );
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
@ -199,7 +190,6 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
takeSnapshot(); takeSnapshot();
}, [takeSnapshot]); }, [takeSnapshot]);
const onEdgesDelete: OnEdgesDelete = useCallback(() => { const onEdgesDelete: OnEdgesDelete = useCallback(() => {
// 👇 make deleting edges undoable // 👇 make deleting edges undoable
takeSnapshot(); takeSnapshot();
@ -224,11 +214,6 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
); );
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node // If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
if (
data.type !== "chatInput" ||
(data.type === "chatInput" &&
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
) {
// Calculate the position where the node should be created // Calculate the position where the node should be created
const position = reactFlowInstance.project({ const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left, x: event.clientX - reactflowBounds.left,
@ -236,10 +221,24 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
}); });
// Generate a unique node ID // Generate a unique node ID
let newId = getId(); let newId = getNodeId();
let newNode: NodeType;
if (data.type !== "groupNode") {
// Create a new node object // Create a new node object
const newNode: NodeType = { newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
} else {
// Create a new node object
newNode = {
id: newId, id: newId,
type: "genericNode", type: "genericNode",
position, position,
@ -251,27 +250,24 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
}; };
// Add the new node to the list of nodes in state // Add the new node to the list of nodes in state
setNodes((nds) => nds.concat(newNode));
} else {
// If a chat input node already exists, set an error message
setErrorData({
title: "Error creating node",
list: ["There can't be more than one chat input."],
});
} }
setNodes((nds) => nds.concat(newNode));
}, },
// Specify dependencies for useCallback // Specify dependencies for useCallback
[incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot] [getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
); );
const onDelete = useCallback((mynodes) => { const onDelete = useCallback(
(mynodes) => {
takeSnapshot(); takeSnapshot();
setEdges( setEdges(
edges.filter( edges.filter(
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id) (ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
) )
); );
}, [takeSnapshot, edges, setEdges]); },
[takeSnapshot, edges, setEdges]
);
const onEdgeUpdateStart = useCallback(() => { const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false; edgeUpdateSuccessful.current = false;
@ -295,19 +291,43 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
edgeUpdateSuccessful.current = true; edgeUpdateSuccessful.current = true;
}, []); }, []);
const [selectionEnded, setSelectionEnded] = useState(false);
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
}, []);
const onSelectionStart = useCallback(() => {
setSelectionEnded(false);
}, []);
// Workaround to show the menu only after the selection has ended.
useEffect(() => {
if (selectionEnded && lastSelection && lastSelection.nodes.length > 1) {
setSelectionMenuVisible(true);
} else {
setSelectionMenuVisible(false);
}
}, [selectionEnded, lastSelection]);
const onSelectionChange = useCallback((flow) => {
setLastSelection(flow);
}, []);
return ( return (
<div className="w-full h-full" onMouseMove={handleMouseMove} ref={reactFlowWrapper}> <div
className="w-full h-full"
ref={reactFlowWrapper}
>
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? ( {Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
<> <>
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
onMove={() => onMove={() => {
updateFlow({ ...flow, data: reactFlowInstance.toObject() }) updateFlow({ ...flow, data: reactFlowInstance.toObject() });
} }}
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod} onEdgesChange={onEdgesChangeMod}
onKeyDown={(e) => onKeyDown(e)}
onConnect={onConnect} onConnect={onConnect}
onLoad={setReactFlowInstance} onLoad={setReactFlowInstance}
onInit={setReactFlowInstance} onInit={setReactFlowInstance}
@ -317,16 +337,18 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
onEdgeUpdateEnd={onEdgeUpdateEnd} onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
onSelectionDragStart={onSelectionDragStart} onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
onEdgesDelete={onEdgesDelete} onEdgesDelete={onEdgesDelete}
connectionLineComponent={ConnectionLineComponent} connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver} onDragOver={onDragOver}
onDrop={onDrop} onDrop={onDrop}
onNodesDelete={onDelete} onNodesDelete={onDelete}
onSelectionChange={onSelectionChange}
selectNodesOnDrag={false} selectNodesOnDrag={false}
> >
<Background className="dark:bg-gray-900" /> <Background className="dark:bg-gray-900" />
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"> <Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
</Controls>
</ReactFlow> </ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} /> <Chat flow={flow} reactFlowInstance={reactFlowInstance} />
</> </>

View file

@ -6,15 +6,17 @@ export type TabsContextType = {
setTabIndex: (index: number) => void; setTabIndex: (index: number) => void;
flows: Array<FlowType>; flows: Array<FlowType>;
removeFlow: (id: string) => void; removeFlow: (id: string) => void;
addFlow: (flowData?: FlowType) => void; addFlow: (flowData?: FlowType,newFlow?:boolean) => void;
updateFlow: (newFlow: FlowType) => void; updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => number; incrementNodeId: () => string;
downloadFlow: (flow: FlowType) => void; downloadFlow: (flow: FlowType) => void;
uploadFlow: () => void; uploadFlow: (newFlow?:boolean) => void;
hardReset: () => void; hardReset: () => void;
//disable CopyPaste //disable CopyPaste
disableCP: boolean; disableCP: boolean;
setDisableCP: (value: boolean) => void; setDisableCP: (value: boolean) => void;
getNodeId: () => string;
paste: (selection: {nodes: any, edges: any}, position: {x: number, y: number}) => void;
}; };
export type LangFlowState = { export type LangFlowState = {