refactor(validate.py): extract build_graph function to langflow.inter… (#204)

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-04-26 01:29:09 -03:00 committed by GitHub
commit 63e10f8786
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 266 additions and 149 deletions

View file

@ -7,6 +7,7 @@ from langflow.api.base import (
PromptValidationResponse,
validate_prompt,
)
from langflow.interface.run import build_graph
from langflow.utils.logger import logger
from langflow.utils.validate import validate_code
@ -33,3 +34,20 @@ def post_validate_prompt(prompt: Prompt):
except Exception as e:
logger.exception(e)
raise HTTPException(status_code=500, detail=str(e)) from e
# validate node
@router.post("/node/{node_id}", status_code=200)
def post_validate_node(node_id: str, data: dict):
try:
# build graph
graph = build_graph(data)
# validate node
node = graph.get_node(node_id)
if node is not None:
_ = node.build()
return str(node.params)
raise Exception(f"Node {node_id} not found")
except Exception as e:
logger.exception(e)
raise HTTPException(status_code=500, detail=str(e)) from e

View file

@ -39,16 +39,16 @@ def build_langchain_object_with_caching(data_graph):
"""
logger.debug("Building langchain object")
nodes = data_graph["nodes"]
# Add input variables
# nodes = payload.extract_input_variables(nodes)
# Nodes, edges and root node
edges = data_graph["edges"]
graph = Graph(nodes, edges)
graph = build_graph(data_graph)
return graph.build()
def build_graph(data_graph):
nodes = data_graph["nodes"]
edges = data_graph["edges"]
return Graph(nodes, edges)
def build_langchain_object(data_graph):
"""
Build langchain object from data_graph.

View file

@ -40,6 +40,7 @@
"reactflow": "^11.5.5",
"tailwindcss": "^3.2.6",
"typescript": "^4.9.5",
"use-debounce": "^9.0.4",
"web-vitals": "^2.1.4"
}
},
@ -17044,6 +17045,17 @@
"requires-port": "^1.0.0"
}
},
"node_modules/use-debounce": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-9.0.4.tgz",
"integrity": "sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==",
"engines": {
"node": ">= 10.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",

View file

@ -35,6 +35,7 @@
"reactflow": "^11.5.5",
"tailwindcss": "^3.2.6",
"typescript": "^4.9.5",
"use-debounce": "^9.0.4",
"web-vitals": "^2.1.4"
},
"scripts": {

View file

@ -1,136 +1,203 @@
import { TrashIcon } from "@heroicons/react/24/outline";
import { useDebouncedCallback } from "use-debounce";
import {
classNames,
nodeColors,
nodeIcons,
snakeToNormalCase,
classNames,
nodeColors,
nodeIcons,
snakeToNormalCase,
} from "../../utils";
import ParameterComponent from "./components/parameterComponent";
import { typesContext } from "../../contexts/typesContext";
import { useContext, useRef } from "react";
import { useContext, useState, useEffect, useRef } from "react";
import { NodeDataType } from "../../types/flow";
import { alertContext } from "../../contexts/alertContext";
import { useCallback } from "react";
export default function GenericNode({
data,
selected,
data,
selected,
}: {
data: NodeDataType;
selected: boolean;
data: NodeDataType;
selected: boolean;
}) {
const { setErrorData } = useContext(alertContext);
const showError = useRef(true);
const { types, deleteNode } = useContext(typesContext);
const Icon = nodeIcons[types[data.type]];
if (!Icon) {
if (showError.current) {
setErrorData({
title: data.type
? `The ${data.type} node could not be rendered, please review your json file`
: "There was a node that can't be rendered, please review your json file",
});
showError.current = false;
}
deleteNode(data.id);
return;
}
return (
<div
className={classNames(
selected ? "border border-blue-500" : "border dark:border-gray-700",
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center drop-shadow-[0_10px_10px_rgba(0,0,0,0.25)]"
)}
>
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
<div className="w-full flex items-center truncate gap-4 text-lg">
<Icon
className="w-10 h-10 p-1 rounded"
style={{
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}}
/>
<div className="truncate">{data.type}</div>
</div>
<button
onClick={() => {
deleteNode(data.id);
}}
>
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
</button>
</div>
const { setErrorData } = useContext(alertContext);
const showError = useRef(true);
const { types, deleteNode } = useContext(typesContext);
const Icon = nodeIcons[types[data.type]];
const [validationStatus, setValidationStatus] = useState("idle");
// State for outline color
const [isGreenOutline, setIsGreenOutline] = useState(false);
const [isRedOutline, setIsRedOutline] = useState(false);
const { reactFlowInstance } = useContext(typesContext);
<div className="w-full h-full py-5">
<div className="w-full text-gray-500 px-5 text-sm">
{data.node.description}
</div>
const debouncedValidateNode = useDebouncedCallback(async () => {
// Check if the validationStatus is "success"
if (validationStatus === "success") return;
<>
{Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => (
<div key={idx}>
{idx === 0 ? (
<div
className={classNames(
"px-5 py-2 mt-2 dark:text-white text-center",
Object.keys(data.node.template).filter(
(key) =>
!key.startsWith("_") && data.node.template[key].show
).length === 0
? "hidden"
: ""
)}
>
Inputs
</div>
) : (
<></>
)}
{data.node.template[t].show ? (
<ParameterComponent
data={data}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors.unknown
}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? snakeToNormalCase(data.node.template[t].name)
: snakeToNormalCase(t)
}
name={t}
tooltipTitle={
"Type: " +
data.node.template[t].type +
(data.node.template[t].list ? " list" : "")
}
required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id}
left={true}
type={data.node.template[t].type}
/>
) : (
<></>
)}
</div>
))}
<div className="px-5 py-2 mt-2 dark:text-white text-center">
Output
</div>
<ParameterComponent
data={data}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={data.type}
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
id={[data.type, data.id, ...data.node.base_classes].join("|")}
type={data.node.base_classes.join("|")}
left={false}
/>
</>
</div>
</div>
);
try {
const response = await fetch(`/validate/node/${data.id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(reactFlowInstance.toObject()),
});
if (response.status === 200) {
setValidationStatus("success");
} else if (response.status === 500) {
setValidationStatus("error");
}
} catch (error) {
console.error("Error validating node:", error);
setValidationStatus("error");
}
}, 1000);
const validateNode = useCallback(() => {
debouncedValidateNode();
}, [debouncedValidateNode]);
useEffect(() => {
validateNode();
}, [
validateNode,
...Object.values(data.node.template).flatMap((t) => Object.values(t)),
]);
useEffect(() => {
if (validationStatus === "success") {
setIsGreenOutline(true);
setIsRedOutline(false);
setTimeout(() => {
setIsGreenOutline(false);
}, 1000);
} else if (validationStatus === "error") {
setIsRedOutline(true);
setIsGreenOutline(false);
} else {
setIsGreenOutline(false);
setIsRedOutline(false);
}
}, [validationStatus]);
const outlineColor = isGreenOutline
? "animate-pulse-green"
: isRedOutline
? "border-red-outline"
: "";
if (!Icon) {
if (showError.current) {
setErrorData({
title: data.type
? `The ${data.type} node could not be rendered, please review your json file`
: "There was a node that can't be rendered, please review your json file",
});
showError.current = false;
}
deleteNode(data.id);
return;
}
return (
<div
className={classNames(
outlineColor,
selected ? "border border-blue-500" : "border dark:border-gray-700",
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
)}
>
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
<div className="w-full flex items-center truncate gap-4 text-lg">
<Icon
className="w-10 h-10 p-1 rounded"
style={{
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}}
/>
<div className="truncate">{data.type}</div>
</div>
<button
onClick={() => {
deleteNode(data.id);
}}
>
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
</button>
</div>
<div className="w-full h-full py-5">
<div className="w-full text-gray-500 px-5 text-sm">
{data.node.description}
</div>
<>
{Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => (
<div key={idx}>
{idx === 0 ? (
<div
className={classNames(
"px-5 py-2 mt-2 dark:text-white text-center",
Object.keys(data.node.template).filter(
(key) =>
!key.startsWith("_") && data.node.template[key].show
).length === 0
? "hidden"
: ""
)}
>
Inputs
</div>
) : (
<></>
)}
{data.node.template[t].show ? (
<ParameterComponent
data={data}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors.unknown
}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? snakeToNormalCase(data.node.template[t].name)
: snakeToNormalCase(t)
}
name={t}
tooltipTitle={
"Type: " +
data.node.template[t].type +
(data.node.template[t].list ? " list" : "")
}
required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id}
left={true}
type={data.node.template[t].type}
/>
) : (
<></>
)}
</div>
))}
<div className="px-5 py-2 mt-2 dark:text-white text-center">
Output
</div>
<ParameterComponent
data={data}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={data.type}
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
id={[data.type, data.id, ...data.node.base_classes].join("|")}
type={data.node.base_classes.join("|")}
left={false}
/>
</>
</div>
</div>
);
}

View file

@ -1,36 +1,55 @@
/** @type {import('tailwindcss').Config} */
const plugin = require('tailwindcss/plugin')
const plugin = require("tailwindcss/plugin");
module.exports = {
content: ["./src/**/*.{js,ts,tsx,jsx}"],
darkMode: 'class',
important:true,
darkMode: "class",
important: true,
theme: {
extend: {},
extend: {
borderColor: {
"red-outline": "rgba(255, 0, 0, 0.8)",
"green-outline": "rgba(72, 187, 120, 0.7)",
},
boxShadow: {
"red-outline": "0 0 5px rgba(255, 0, 0, 0.5)",
"green-outline": "0 0 5px rgba(72, 187, 120, 0.7)",
},
animation: {
"pulse-green": "pulseGreen 1s linear",
},
keyframes: {
pulseGreen: {
"0%": { boxShadow: "0 0 0 0 rgba(72, 187, 120, 0.7)" },
"100%": { boxShadow: "0 0 0 10px rgba(72, 187, 120, 0)" },
},
},
},
},
plugins: [
require("@tailwindcss/forms")({
strategy: 'class', // only generate classes
strategy: "class", // only generate classes
}),
plugin(function ({ addUtilities }) {
addUtilities({
'.scrollbar-hide': {
".scrollbar-hide": {
/* IE and Edge */
'-ms-overflow-style': 'none',
"-ms-overflow-style": "none",
/* Firefox */
'scrollbar-width': 'none',
"scrollbar-width": "none",
/* Safari and Chrome */
'&::-webkit-scrollbar': {
display: 'none'
}
},
'.arrow-hide':{
'&::-webkit-inner-spin-button':{
'-webkit-appearance': 'none',
'margin': 0
"&::-webkit-scrollbar": {
display: "none",
},
'&::-webkit-outer-spin-button':{
'-webkit-appearance': 'none',
'margin': 0
},
".arrow-hide": {
"&::-webkit-inner-spin-button": {
"-webkit-appearance": "none",
margin: 0,
},
"&::-webkit-outer-spin-button": {
"-webkit-appearance": "none",
margin: 0,
},
},
'.password':{
@ -56,4 +75,4 @@ module.exports = {
})
}),require('@tailwindcss/line-clamp')
],
}
};