@@ -136,7 +139,7 @@ export default function ParameterComponent({
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
return (
- <>
+
{index === 0 && (
{left
@@ -183,7 +186,7 @@ export default function ParameterComponent({
- >
+
);
});
} else {
@@ -204,33 +207,46 @@ export default function ParameterComponent({
type === "code" ||
type === "prompt" ||
type === "file" ||
- type === "int") &&
+ type === "int" ||
+ type === "dict" ||
+ type === "NestedDict") &&
!optionalHandle ? (
<>>
) : (
-
-
- isValidConnection(connection, reactFlowInstance!)
- }
- className={classNames(
- left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
- "h-3 w-3 rounded-full border-2 bg-background"
- )}
- style={{
- borderColor: color,
- top: position,
- }}
- >
-
+
)
) : (
- {title}
+ {proxy ? (
+
{proxy.id}}>
+ {title}
+
+ ) : (
+ title
+ )}
{required ? " *" : ""}
{info !== "" && (
@@ -285,7 +307,11 @@ export default function ParameterComponent({
isValidConnection(connection, reactFlowInstance!)
}
@@ -326,9 +352,11 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
+ id={"textarea-" + index}
/>
) : (
{
@@ -368,6 +397,11 @@ export default function ParameterComponent({
) : left === true && type === "code" ? (
{
data.node = nodeClass;
@@ -376,6 +410,7 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
+ id={"code-input-" + index}
/>
) : left === true && type === "file" ? (
@@ -388,7 +423,6 @@ export default function ParameterComponent({
suffixes={data.node?.template[name].suffixes}
onFileChange={(filePath: string) => {
data.node!.template[name].file_path = filePath;
- save();
}}
>
@@ -398,11 +432,13 @@ export default function ParameterComponent({
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
+ id={"int-input-" + index}
/>
) : left === true && type === "prompt" ? (
{
data.node = nodeClass;
@@ -416,6 +452,7 @@ export default function ParameterComponent({
onChange={(e) => {
handleOnNewValue(e);
}}
+ id={"prompt-input-" + index}
/>
) : left === true && type === "NestedDict" ? (
@@ -424,7 +461,8 @@ export default function ParameterComponent({
disabled={disabled}
editNode={false}
value={
- data.node!.template[name].value.toString() === "{}"
+ !data.node!.template[name].value ||
+ data.node!.template[name].value?.toString() === "{}"
? {
yourkey: "value",
}
diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx
index 524b54ead..a58f7a92c 100644
--- a/src/frontend/src/CustomNodes/GenericNode/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx
@@ -4,23 +4,33 @@ import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
import IconComponent from "../../components/genericIconComponent";
+import InputComponent from "../../components/inputComponent";
+import { Textarea } from "../../components/ui/textarea";
import { useSSE } from "../../contexts/SSEContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
-import { cleanEdges } from "../../utils/reactflowUtils";
+import {
+ cleanEdges,
+ handleKeyDown,
+ scapedJSONStringfy,
+} from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, toTitleCase } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({
data: olddata,
+ xPos,
+ yPos,
selected,
}: {
data: NodeDataType;
selected: boolean;
+ xPos: number;
+ yPos: number;
}): JSX.Element {
const [data, setData] = useState(olddata);
const { updateFlow, flows, tabId } = useContext(TabsContext);
@@ -28,6 +38,12 @@ export default function GenericNode({
const { types, deleteNode, reactFlowInstance, setFilterEdge, getFilterEdge } =
useContext(typesContext);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
+ const [inputName, setInputName] = useState(true);
+ const [nodeName, setNodeName] = useState(data.node!.display_name);
+ const [inputDescription, setInputDescription] = useState(false);
+ const [nodeDescription, setNodeDescription] = useState(
+ data.node?.description!
+ );
const [validationStatus, setValidationStatus] =
useState
(null);
const [showNode, setShowNode] = useState(true);
@@ -111,6 +127,7 @@ export default function GenericNode({
<>
{showNode && (
-
-
- {data.node?.display_name}
+ {data.node?.flow && inputName ? (
+
+ {
+ setInputName(false);
+ if (nodeName.trim() !== "") {
+ setNodeName(nodeName);
+ data.node!.display_name = nodeName;
+ } else {
+ setNodeName(data.node!.display_name);
+ }
+ }}
+ value={nodeName}
+ onChange={setNodeName}
+ password={false}
+ blurOnEnter={true}
+ />
-
+ ) : (
+
+ setInputName(true)}
+ >
+ {data.node?.display_name}
+
+
+ )}
)}
@@ -178,16 +219,15 @@ export default function GenericNode({
data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced && (
)
)}
- {data.node?.description !== "" && showNode && (
-
+ {data.node?.description !== "" &&
+ showNode &&
+ data.node?.flow &&
+ inputDescription ? (
+
@@ -86,7 +86,7 @@ export const EditFlowSettings: React.FC = ({
name="description"
id="description"
onChange={handleDescriptionChange}
- value={description}
+ value={description!}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}
diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx
index ec15b6404..5ecee1f29 100644
--- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx
+++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx
@@ -80,61 +80,54 @@ export default function BuildTrigger({
const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource
let validationResults: boolean[] = [];
- let finished = false;
const apiUrl = `/api/v1/build/stream/${flowId}`;
- const eventSource = new EventSource(apiUrl);
+ return new Promise((resolve, reject) => {
+ const eventSource = new EventSource(apiUrl);
- eventSource.onmessage = (event) => {
- // If the event is parseable, return
- if (!event.data) {
- return;
- }
- const parsedData = JSON.parse(event.data);
- // if the event is the end of the stream, close the connection
- if (parsedData.end_of_stream) {
- // Close the connection and finish
- finished = true;
+ eventSource.onmessage = (event) => {
+ // If the event is parseable, return
+ if (!event.data) {
+ return;
+ }
+ const parsedData = JSON.parse(event.data);
+ // if the event is the end of the stream, close the connection
+ if (parsedData.end_of_stream) {
+ eventSource.close();
+ resolve(validationResults.every((result) => result));
+ } else if (parsedData.log) {
+ // If the event is a log, log it
+ setSuccessData({ title: parsedData.log });
+ } else if (parsedData.input_keys !== undefined) {
+ //@ts-ignore
+ setTabsState((old: TabsState) => {
+ return {
+ ...old,
+ [flowId]: {
+ ...old[flowId],
+ formKeysData: parsedData,
+ },
+ };
+ });
+ } else {
+ // Otherwise, process the data
+ const isValid = processStreamResult(parsedData);
+ setProgress(parsedData.progress);
+ validationResults.push(isValid);
+ }
+ };
+
+ eventSource.onerror = (error: any) => {
+ console.error("EventSource failed:", error);
+
+ if (error.data) {
+ const parsedData = JSON.parse(error.data);
+ setErrorData({ title: parsedData.error });
+ setIsBuilding(false);
+ }
eventSource.close();
-
- return;
- } else if (parsedData.log) {
- // If the event is a log, log it
- setSuccessData({ title: parsedData.log });
- } else if (parsedData.input_keys !== undefined) {
- //@ts-ignore
- setTabsState((old: TabsState) => {
- return {
- ...old,
- [flowId]: {
- ...old[flowId],
- formKeysData: parsedData,
- },
- };
- });
- } else {
- // Otherwise, process the data
- const isValid = processStreamResult(parsedData);
- setProgress(parsedData.progress);
- validationResults.push(isValid);
- }
- };
-
- eventSource.onerror = (error: any) => {
- console.error("EventSource failed:", error);
- eventSource.close();
- if (error.data) {
- const parsedData = JSON.parse(error.data);
- setErrorData({ title: parsedData.error });
- setIsBuilding(false);
- }
- };
- // Step 3: Wait for the stream to finish
- while (!finished) {
- await new Promise((resolve) => setTimeout(resolve, 100));
- finished = validationResults.length === flow.data!.nodes.length;
- }
- // Step 4: Return true if all nodes are valid, false otherwise
- return validationResults.every((result) => result);
+ reject(new Error("Streaming failed"));
+ };
+ });
}
function processStreamResult(parsedData: parsedDataType) {
diff --git a/src/frontend/src/components/codeAreaComponent/index.tsx b/src/frontend/src/components/codeAreaComponent/index.tsx
index a402fb3ed..8c459f64b 100644
--- a/src/frontend/src/components/codeAreaComponent/index.tsx
+++ b/src/frontend/src/components/codeAreaComponent/index.tsx
@@ -12,6 +12,8 @@ export default function CodeAreaComponent({
nodeClass,
dynamic,
setNodeClass,
+ id = "",
+ readonly = false,
}: CodeAreaComponentType) {
const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value)
@@ -30,6 +32,7 @@ export default function CodeAreaComponent({
return (
{
- if (tweaks) {
+ if (tweaks && data) {
unselectAllNodes({
data,
updateNodes: (nodes) => {
@@ -604,6 +604,14 @@ export default function CodeTabsComponent({
].type === "prompt" ? (
+
{
function handleAddFlow() {
try {
- addFlow(undefined, true).then((id) => {
+ addFlow(true).then((id) => {
navigate("/flow/" + id);
});
// saveFlowStyleInDataBase();
diff --git a/src/frontend/src/components/inputComponent/index.tsx b/src/frontend/src/components/inputComponent/index.tsx
index 490be0fb0..10ed8d791 100644
--- a/src/frontend/src/components/inputComponent/index.tsx
+++ b/src/frontend/src/components/inputComponent/index.tsx
@@ -1,11 +1,13 @@
import * as Form from "@radix-ui/react-form";
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { InputComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import { Input } from "../ui/input";
export default function InputComponent({
+ autoFocus = false,
+ onBlur,
value,
onChange,
disabled,
@@ -15,9 +17,11 @@ export default function InputComponent({
editNode = false,
placeholder = "Type something...",
className,
+ id = "",
+ blurOnEnter = false,
}: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false);
-
+ const refInput = useRef(null);
// Clear component state
useEffect(() => {
if (disabled) {
@@ -30,6 +34,10 @@ export default function InputComponent({
{isForm ? (
{
handleKeyDown(e, value, "");
+ if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
/>
) : (
{
handleKeyDown(e, value, "");
+ if (blurOnEnter && e.key === "Enter") refInput.current?.blur();
}}
/>
)}
diff --git a/src/frontend/src/components/intComponent/index.tsx b/src/frontend/src/components/intComponent/index.tsx
index dffb1b1f8..f246516ec 100644
--- a/src/frontend/src/components/intComponent/index.tsx
+++ b/src/frontend/src/components/intComponent/index.tsx
@@ -8,6 +8,7 @@ export default function IntComponent({
onChange,
disabled,
editNode = false,
+ id = "",
}: FloatComponentType): JSX.Element {
const min = 0;
@@ -21,6 +22,7 @@ export default function IntComponent({
return (
{
if (
event.key !== "Backspace" &&
diff --git a/src/frontend/src/components/keypairListComponent/index.tsx b/src/frontend/src/components/keypairListComponent/index.tsx
index f1e93e7fc..ca63c362c 100644
--- a/src/frontend/src/components/keypairListComponent/index.tsx
+++ b/src/frontend/src/components/keypairListComponent/index.tsx
@@ -12,6 +12,8 @@ export default function KeypairListComponent({
disabled,
editNode = false,
duplicateKey,
+ advanced = false,
+ dataValue,
}: KeyPairListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
@@ -29,32 +31,31 @@ export default function KeypairListComponent({
}, [value]);
const handleChangeKey = (event, idx) => {
- const newInputList = _.cloneDeep(ref.current);
- const oldKey = Object.keys(newInputList[idx])[0];
- const updatedObj = { [event.target.value]: newInputList[idx][oldKey] };
- newInputList[idx] = updatedObj;
- onChange(newInputList);
+ const oldKey = Object.keys(ref.current[idx])[0];
+ const updatedObj = { [event.target.value]: ref.current[idx][oldKey] };
+ ref.current[idx] = updatedObj;
+ onChange(ref.current);
};
const handleChangeValue = (newValue, idx) => {
- const newInputList = _.cloneDeep(ref.current);
- const key = Object.keys(newInputList[idx])[0];
- newInputList[idx][key] = newValue;
- onChange(newInputList);
+ const key = Object.keys(ref.current[idx])[0];
+ ref.current[idx][key] = newValue;
+ onChange(ref.current);
};
return (
1 && editNode ? "my-1" : "",
+ ref.current?.length > 1 && editNode ? "mx-2 my-1" : "",
"flex h-full flex-col gap-3"
)}
>
{ref.current?.map((obj, index) => {
return Object.keys(obj).map((key, idx) => {
return (
-
+
{
if (disabled) {
onChange("");
@@ -22,7 +24,8 @@ export default function PromptAreaComponent({
}, [disabled]);
useEffect(() => {
- if (value !== "" && !editNode) {
+ //prevent update from prompt template after group node if prompt is wrongly marked as not dynamic
+ if (value !== "" && !editNode && !readonly && !nodeClass?.flow) {
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
if (apiReturn.data) {
setNodeClass!(apiReturn.data.frontend_node);
@@ -35,6 +38,8 @@ export default function PromptAreaComponent({
return (
{
);
diff --git a/src/frontend/src/components/textAreaComponent/index.tsx b/src/frontend/src/components/textAreaComponent/index.tsx
index fec59f40e..0be68a294 100644
--- a/src/frontend/src/components/textAreaComponent/index.tsx
+++ b/src/frontend/src/components/textAreaComponent/index.tsx
@@ -10,6 +10,7 @@ export default function TextAreaComponent({
onChange,
disabled,
editNode = false,
+ id = "",
}: TextAreaComponentType): JSX.Element {
// Clear text area
useEffect(() => {
@@ -21,6 +22,7 @@ export default function TextAreaComponent({
return (
(
-
-
+
+
{children}
@@ -27,7 +26,7 @@ const DialogOverlay = React.forwardRef<
(
return (
diff --git a/src/frontend/src/components/ui/rename-label.tsx b/src/frontend/src/components/ui/rename-label.tsx
index 4930aedca..d9e77f4cf 100644
--- a/src/frontend/src/components/ui/rename-label.tsx
+++ b/src/frontend/src/components/ui/rename-label.tsx
@@ -57,7 +57,7 @@ export default function RenameLabel(props) {
ref={inputRef}
onInput={resizeInput}
className={cn(
- "nopan nodrag noundo nocopy rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
+ "nopan nodelete nodrag noundo nocopy rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
props.className
)}
onBlur={() => {
diff --git a/src/frontend/src/components/ui/textarea.tsx b/src/frontend/src/components/ui/textarea.tsx
index cfad1cf72..1f2970545 100644
--- a/src/frontend/src/components/ui/textarea.tsx
+++ b/src/frontend/src/components/ui/textarea.tsx
@@ -8,7 +8,10 @@ const Textarea = React.forwardRef(
({ className, ...props }, ref) => {
return (
diff --git a/src/frontend/src/components/ui/tooltip.tsx b/src/frontend/src/components/ui/tooltip.tsx
index 8ea9a9505..d1f2c8c77 100644
--- a/src/frontend/src/components/ui/tooltip.tsx
+++ b/src/frontend/src/components/ui/tooltip.tsx
@@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
- "overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
+ "z-50 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}
diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts
index d6ba802ee..ea01d5ff6 100644
--- a/src/frontend/src/constants/constants.ts
+++ b/src/frontend/src/constants/constants.ts
@@ -529,7 +529,13 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"http://localhost:7860/login",
];
-export const skipNodeUpdate = ["CustomComponent"];
+export const skipNodeUpdate = [
+ "CustomComponent",
+ "PromptTemplate",
+ "ChatMessagePromptTemplate",
+ "SystemMessagePromptTemplate",
+ "HumanMessagePromptTemplate",
+];
export const CONTROL_INPUT_STATE = {
password: "",
diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx
index 2d62fed07..11e7df856 100644
--- a/src/frontend/src/contexts/authContext.tsx
+++ b/src/frontend/src/contexts/authContext.tsx
@@ -66,7 +66,10 @@ export function AuthProvider({ children }): React.ReactElement {
const isSuperUser = user!.is_superuser;
setIsAdmin(isSuperUser);
})
- .catch((error) => {});
+ .catch((error) => {
+ console.log("auth context");
+ setLoading(false);
+ });
} else {
setLoading(false);
}
diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx
index 88f5005fe..cba2376b5 100644
--- a/src/frontend/src/contexts/tabsContext.tsx
+++ b/src/frontend/src/contexts/tabsContext.tsx
@@ -21,10 +21,20 @@ import {
} from "../controllers/API";
import { APIClassType, APITemplateType } from "../types/api";
import { tweakType } from "../types/components";
-import { FlowType, NodeDataType, NodeType } from "../types/flow";
+import {
+ FlowType,
+ NodeDataType,
+ NodeType,
+ sourceHandleType,
+ targetHandleType,
+} from "../types/flow";
import { TabsContextType, TabsState } from "../types/tabs";
import {
addVersionToDuplicates,
+ checkOldEdgesHandles,
+ scapeJSONParse,
+ scapedJSONStringfy,
+ updateEdgesHandleIds,
updateIds,
updateTemplate,
} from "../utils/reactflowUtils";
@@ -36,13 +46,12 @@ import { typesContext } from "./typesContext";
const uid = new ShortUniqueId({ length: 5 });
const TabsContextInitialValue: TabsContextType = {
- save: () => {},
tabId: "",
setTabId: (index: string) => {},
isLoading: true,
flows: [],
removeFlow: (id: string) => {},
- addFlow: async (flowData?: any) => "",
+ addFlow: async (newProject: boolean, flowData?: FlowType) => "",
updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => uid(),
downloadFlow: (flow: FlowType) => {},
@@ -101,29 +110,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
return newNodeId.current;
}
- function save() {
- // added clone deep to avoid mutating the original object
- let Saveflows = _.cloneDeep(flows);
- if (Saveflows.length !== 0) {
- Saveflows.forEach((flow) => {
- if (flow.data && flow.data?.nodes)
- flow.data?.nodes.forEach((node) => {
- //looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
- Object.keys(node.data.node.template).forEach((key) => {
- if (node.data.node.template[key].type === "file") {
- node.data.node.template[key].content = null;
- node.data.node.template[key].value = "";
- }
- });
- });
- });
- window.localStorage.setItem(
- "tabsData",
- JSON.stringify({ tabId, flows: Saveflows, id })
- );
- }
- }
-
function refreshFlows() {
setIsLoading(true);
getTabsDataFromDB().then((DbData) => {
@@ -158,14 +144,18 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (!flow.data) {
return;
}
- processFlowEdges(flow);
- processFlowNodes(flow);
+ processDataFromFlow(flow, false);
} catch (e) {}
});
}
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" };
@@ -183,6 +173,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
function processFlowNodes(flow: FlowType) {
if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node: NodeType) => {
+ if (node.data.node?.flow) return;
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
@@ -192,6 +183,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (Object.keys(template["template"]).length > 0) {
updateDisplay_name(node, template);
updateNodeBaseClasses(node, template);
+ //update baseclasses in edges
updateNodeEdges(flow, node, template);
updateNodeDescription(node, template);
updateNodeTemplate(node, template);
@@ -211,11 +203,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
) {
flow.data!.edges.forEach((edge) => {
if (edge.source === node.id) {
- edge.sourceHandle = edge.sourceHandle
- ?.split("|")
- .slice(0, 2)
- .concat(template["base_classes"])
- .join("|");
+ let sourceHandleObject: sourceHandleType = scapeJSONParse(
+ edge.sourceHandle!
+ );
+ sourceHandleObject.baseClasses = template["base_classes"];
+ edge.data.sourceHandle = sourceHandleObject;
+ edge.sourceHandle = scapedJSONStringfy(sourceHandleObject);
}
});
}
@@ -267,9 +260,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// simulate a click on the link element to trigger the download
link.click();
- setNoticeData({
- title: "Warning: Critical data, JSON file may include API keys.",
- });
}
function downloadFlows() {
@@ -298,16 +288,22 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* The resulting JSON object is passed to the addFlow function.
*/
async function uploadFlow(
- newProject?: boolean,
+ newProject: boolean,
file?: File
): Promise {
let id;
if (file) {
let text = await file.text();
+ let fileData = JSON.parse(text);
+ if (fileData.flows) {
+ fileData.flows.forEach((flow: FlowType) => {
+ id = addFlow(newProject, flow);
+ });
+ }
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
- id = await addFlow(flow, newProject);
+ id = await addFlow(newProject, flow);
} else {
// create a file input
const input = document.createElement("input");
@@ -322,7 +318,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let flow: FlowType = JSON.parse(text);
- const flowId = await addFlow(flow, newProject);
+ const flowId = await addFlow(newProject, flow);
resolve(flowId);
}
};
@@ -373,7 +369,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* Add a new flow to the list of flows.
* @param flow Optional flow to add.
*/
-
function paste(
selectionInstance: { nodes: Node[]; edges: Edge[] },
position: { x: number; y: number; paneX?: number; paneY?: number }
@@ -422,19 +417,27 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
reactFlowInstance!.setNodes(nodes);
- selectionInstance.edges.forEach((edge) => {
+ selectionInstance.edges.forEach((edge: Edge) => {
let source = idsMap[edge.source];
let target = idsMap[edge.target];
- let sourceHandleSplitted = edge.sourceHandle!.split("|");
- let sourceHandle =
- sourceHandleSplitted[0] +
- "|" +
- source +
- "|" +
- sourceHandleSplitted.slice(2).join("|");
- let targetHandleSplitted = edge.targetHandle!.split("|");
- let targetHandle =
- targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
+ const sourceHandleObject: sourceHandleType = scapeJSONParse(
+ edge.sourceHandle!
+ );
+ let sourceHandle = scapedJSONStringfy({
+ ...sourceHandleObject,
+ id: source,
+ });
+ sourceHandleObject.id = source;
+ edge.data.sourceHandle = sourceHandleObject;
+ const targetHandleObject: targetHandleType = scapeJSONParse(
+ edge.targetHandle!
+ );
+ let targetHandle = scapedJSONStringfy({
+ ...targetHandleObject,
+ id: target,
+ });
+ targetHandleObject.id = target;
+ edge.data.targetHandle = targetHandleObject;
let id =
"reactflow__edge-" +
source +
@@ -451,10 +454,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
id,
style: { stroke: "#555" },
className:
- targetHandle.split("|")[0] === "Text"
+ targetHandleObject.type === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ",
- animated: targetHandle.split("|")[0] === "Text",
+ animated: targetHandleObject.type === "Text",
selected: false,
},
edges.map((edge) => ({ ...edge, selected: false }))
@@ -464,19 +467,16 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
const addFlow = async (
- flow?: FlowType,
- newProject?: Boolean
+ newProject: Boolean,
+ flow?: FlowType
): Promise => {
if (newProject) {
- let flowData = extractDataFromFlow(flow!);
- if (flowData.description == "") {
- flowData.description = getRandomDescription();
- }
+ let flowData = flow
+ ? processDataFromFlow(flow)
+ : { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
// Create a new flow with a default name if no flow is provided.
const newFlow = createNewFlow(flowData, flow!);
- processFlowEdges(newFlow);
- processFlowNodes(newFlow);
const flowName = addVersionToDuplicates(newFlow, flows);
@@ -504,31 +504,36 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
};
- const extractDataFromFlow = (flow: FlowType) => {
+ const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
let data = flow?.data ? flow.data : null;
- const description = flow?.description ? flow.description : "";
-
if (data) {
+ processFlowEdges(flow);
+ processFlowNodes(flow);
+ //add animation to text type edges
updateEdges(data.edges);
- updateNodes(data.nodes, data.edges);
- updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
+ // updateNodes(data.nodes, data.edges);
+ if (refreshIds) updateIds(data, getNodeId); // Assuming updateIds is defined elsewhere
}
- return { data, description };
+ return data;
};
const updateEdges = (edges: Edge[]) => {
edges.forEach((edge) => {
+ const targetHandleObject: targetHandleType = scapeJSONParse(
+ edge.targetHandle!
+ );
edge.className =
- (edge.targetHandle!.split("|")[0] === "Text"
+ (targetHandleObject.type === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ") + " stroke-connection";
- edge.animated = edge.targetHandle!.split("|")[0] === "Text";
+ edge.animated = targetHandleObject.type === "Text";
});
};
const updateNodes = (nodes: Node[], edges: Edge[]) => {
nodes.forEach((node) => {
+ if (node.data.node?.flow) return;
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
@@ -538,12 +543,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
if (Object.keys(template["template"]).length > 0) {
node.data.node.base_classes = template["base_classes"];
edges.forEach((edge) => {
+ let sourceHandleObject: sourceHandleType = scapeJSONParse(
+ edge.sourceHandle!
+ );
if (edge.source === node.id) {
- edge.sourceHandle = edge
- .sourceHandle!.split("|")
- .slice(0, 2)
- .concat(template["base_classes"])
- .join("|");
+ let newSourceHandle = sourceHandleObject;
+ newSourceHandle.baseClasses.concat(template["base_classes"]);
+ edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
}
});
node.data.node.description = template["description"];
@@ -556,12 +562,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
};
const createNewFlow = (
- flowData: { data: ReactFlowJsonObject | null; description: string },
+ flowData: ReactFlowJsonObject | null,
flow: FlowType
) => ({
- description: flowData.description,
+ description: flow?.description ?? getRandomDescription(),
name: flow?.name ?? getRandomName(),
- data: flowData.data,
+ data: flowData,
id: "",
});
@@ -640,7 +646,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
tabId,
setTabId,
flows,
- save,
incrementNodeId,
removeFlow,
addFlow,
diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx
index 7caf5eccc..0ec5eaa3b 100644
--- a/src/frontend/src/contexts/typesContext.tsx
+++ b/src/frontend/src/contexts/typesContext.tsx
@@ -5,7 +5,7 @@ import {
useEffect,
useState,
} from "react";
-import { Node, ReactFlowInstance } from "reactflow";
+import { Edge, Node, ReactFlowInstance } from "reactflow";
import { getAll, getHealth } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
@@ -28,6 +28,7 @@ const initialValue: typesContextType = {
fetchError: false,
setFilterEdge: (filter) => {},
getFilterEdge: [],
+ deleteEdge: () => {},
};
export const typesContext = createContext(initialValue);
@@ -92,19 +93,38 @@ export function TypesProvider({ children }: { children: ReactNode }) {
}
}
- function deleteNode(idx: string) {
+ function deleteNode(idx: string | Array) {
reactFlowInstance!.setNodes(
- reactFlowInstance!.getNodes().filter((node: Node) => node.id !== idx)
+ reactFlowInstance!
+ .getNodes()
+ .filter((node: Node) =>
+ typeof idx === "string" ? node.id !== idx : !idx.includes(node.id)
+ )
);
reactFlowInstance!.setEdges(
reactFlowInstance!
.getEdges()
- .filter((edge) => edge.source !== idx && edge.target !== idx)
+ .filter((edge) =>
+ typeof idx === "string"
+ ? edge.source !== idx && edge.target !== idx
+ : !idx.includes(edge.source) && !idx.includes(edge.target)
+ )
);
}
+ function deleteEdge(idx: string | Array) {
+ reactFlowInstance!.setEdges(
+ reactFlowInstance!
+ .getEdges()
+ .filter((edge: Edge) =>
+ typeof idx === "string" ? edge.id !== idx : !idx.includes(edge.id)
+ )
+ );
+ }
+
return (
{
const [modalOpen, setModalOpen] = useState(open ?? false);
+ const updateNodeInternals = useUpdateNodeInternals();
const myData = useRef(data);
@@ -76,21 +79,29 @@ const EditNodeModal = forwardRef(
function changeAdvanced(n) {
myData.current.node!.template[n].advanced =
!myData.current.node!.template[n].advanced;
+ setAdv(!adv);
}
const handleOnNewValue = (newValue: any, name) => {
myData.current.node!.template[name].value = newValue;
+ setDataValue(newValue);
+ updateNodeInternals(data.id);
};
useEffect(() => {
- myData.current = data; // reset data to what it is on node when opening modal
- onClose!(modalOpen);
+ if (modalOpen) {
+ myData.current = data; // reset data to what it is on node when opening modal
+ onClose!(modalOpen);
+ }
}, [modalOpen]);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
+ const [adv, setAdv] = useState(null);
+ const [dataValue, setDataValue] = useState(data);
return (
(
- {myData.current.node?.template[templateParam].name
- ? myData.current.node.template[templateParam]
- .name
- : myData.current.node?.template[templateParam]
- .display_name}
+
+
+ {myData.current.node?.template[templateParam]
+ .display_name
+ ? myData.current.node.template[
+ templateParam
+ ].display_name
+ : myData.current.node?.template[
+ templateParam
+ ].name}
+
+
{myData.current.node?.template[templateParam]
@@ -198,6 +225,7 @@ const EditNodeModal = forwardRef(
templateParam
].multiline ? (
) : (
) : myData.current.node?.template[templateParam]
.type === "dict" ? (
-
+
1
+ ? "my-3"
+ : ""
+ )}
+ >
{" "}
{
handleOnNewValue(value, templateParam);
}}
+ id={"prompt-area-edit" + index}
/>
) : myData.current.node?.template[templateParam]
.type === "code" ? (
{
handleOnNewValue(value, templateParam);
}}
+ id={"code-area-edit" + index}
/>
) : myData.current.node?.template[templateParam]
@@ -452,6 +506,11 @@ const EditNodeModal = forwardRef(