From b06501a4c93db07d06a459247a7e6f935a34f58a Mon Sep 17 00:00:00 2001 From: cristhianzl Date: Mon, 29 Apr 2024 18:07:53 -0300 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20(JsonInput.py):=20add=20a=20new?= =?UTF-8?q?=20component=20called=20"JsonInput"=20to=20handle=20JSON=20inpu?= =?UTF-8?q?t=20data=20in=20the=20language=20flow=20system=20=E2=9C=A8=20(K?= =?UTF-8?q?eyPairInput.py):=20add=20a=20new=20component=20called=20"KeyPai?= =?UTF-8?q?rInput"=20to=20handle=20dictionary=20input=20data=20in=20the=20?= =?UTF-8?q?language=20flow=20system=20=E2=9C=A8=20(CSVOutput.py):=20add=20?= =?UTF-8?q?a=20new=20component=20called=20"CSVOutput"=20to=20handle=20CSV?= =?UTF-8?q?=20output=20data=20in=20the=20language=20flow=20system=20?= =?UTF-8?q?=E2=9C=A8=20(ImageOutput.py):=20add=20a=20new=20component=20cal?= =?UTF-8?q?led=20"ImageOutput"=20to=20handle=20image=20output=20data=20in?= =?UTF-8?q?=20the=20language=20flow=20system=20=E2=9C=A8=20(JsonOutput.py)?= =?UTF-8?q?:=20add=20a=20new=20component=20called=20"JsonOutput"=20to=20ha?= =?UTF-8?q?ndle=20JSON=20output=20data=20in=20the=20language=20flow=20syst?= =?UTF-8?q?em=20=E2=9C=A8=20(PDFOutput.py):=20add=20a=20new=20component=20?= =?UTF-8?q?called=20"PDFOutput"=20to=20handle=20PDF=20output=20data=20in?= =?UTF-8?q?=20the=20language=20flow=20system=20=F0=9F=93=9D=20(App.css):?= =?UTF-8?q?=20update=20CSS=20to=20improve=20readability=20and=20formatting?= =?UTF-8?q?=20=F0=9F=93=9D=20(parameterComponent/index.tsx):=20fix=20a=20b?= =?UTF-8?q?ug=20where=20rangeSpec=20is=20not=20properly=20accessed=20in=20?= =?UTF-8?q?the=20FloatComponent=20=F0=9F=93=9D=20(cardComponent/index.tsx)?= =?UTF-8?q?:=20update=20the=20CollectionCardComponent=20to=20include=20a?= =?UTF-8?q?=20new=20button=20for=20opening=20the=20playground=20and=20hand?= =?UTF-8?q?le=20the=20opening=20and=20closing=20of=20the=20IOModal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📝 (codeTabsComponent/index.tsx): fix conditional rendering of InputListComponent by using optional chaining operator to check if the list property exists in the template object 📝 (keypairListComponent/index.tsx): refactor the initialization of the ref variable to handle both empty and non-empty values correctly 📝 (constants.ts): add "JsonInput" and "JsonOutput" to the INPUT_TYPES and OUTPUT_TYPES constants to support JSON input and output components 📝 (editNodeModal/index.tsx): fix conditional rendering of InputListComponent by using optional chaining operator to check if the list property exists in the template object 📝 (ioFieldView/components/JSONInput/index.tsx): add JSONInput component to handle JSON input in the IOFieldView component 📝 (ioFieldView/components/keyPairInput/index.tsx): add keyPairInput component to handle key-value pair input in the IOFieldView component ✨ (IOFieldView/index.tsx): add useState import to use state hook in functional component ✨ (IOFieldView/index.tsx): add support for KeyPairInput component in IOFieldView ✨ (IOFieldView/index.tsx): add support for JsonInput component in IOFieldView ✨ (IOFieldView/index.tsx): add support for JsonOutput component in IOFieldView 📝 (dictAreaModal/index.tsx): add useDarkStore import to use dark mode state in DictAreaModal component 📝 (classes.css): add CSS classes for json-view component in different themes 📝 (types/components/index.ts): remove duplicate InputGlobalComponentType definition --- .../langflow/components/inputs/JsonInput.py | 17 ++++ .../components/inputs/KeyPairInput.py | 19 ++++ .../langflow/components/outputs/CSVOutput.py | 17 ++++ .../components/outputs/ImageOutput.py | 15 +++ .../langflow/components/outputs/JsonOutput.py | 17 ++++ .../langflow/components/outputs/PDFOutput.py | 16 ++++ src/frontend/src/App.css | 14 ++- .../components/parameterComponent/index.tsx | 2 +- .../src/components/cardComponent/index.tsx | 40 ++++---- .../components/codeTabsComponent/index.tsx | 4 +- .../components/keypairListComponent/index.tsx | 8 +- src/frontend/src/constants/constants.ts | 8 +- .../src/modals/EditNodeModal/index.tsx | 8 +- .../components/JSONInput/index.tsx | 45 +++++++++ .../components/keyPairInput/index.tsx | 91 +++++++++++++++++++ .../IOModal/components/IOFieldView/index.tsx | 57 ++++++++++++ .../src/modals/dictAreaModal/index.tsx | 6 +- src/frontend/src/style/classes.css | 28 ++++++ src/frontend/src/types/components/index.ts | 13 +-- 19 files changed, 386 insertions(+), 39 deletions(-) create mode 100644 src/backend/base/langflow/components/inputs/JsonInput.py create mode 100644 src/backend/base/langflow/components/inputs/KeyPairInput.py create mode 100644 src/backend/base/langflow/components/outputs/CSVOutput.py create mode 100644 src/backend/base/langflow/components/outputs/ImageOutput.py create mode 100644 src/backend/base/langflow/components/outputs/JsonOutput.py create mode 100644 src/backend/base/langflow/components/outputs/PDFOutput.py create mode 100644 src/frontend/src/modals/IOModal/components/IOFieldView/components/JSONInput/index.tsx create mode 100644 src/frontend/src/modals/IOModal/components/IOFieldView/components/keyPairInput/index.tsx diff --git a/src/backend/base/langflow/components/inputs/JsonInput.py b/src/backend/base/langflow/components/inputs/JsonInput.py new file mode 100644 index 000000000..713868147 --- /dev/null +++ b/src/backend/base/langflow/components/inputs/JsonInput.py @@ -0,0 +1,17 @@ +from langflow.base.io.text import TextComponent +from langflow.field_typing.constants import Data, NestedDict + +class JsonInput(TextComponent): + display_name = "JSON Input" + description = "JSON Input." + + def build_config(self): + return { + "input_value": { + "display_name": "JSON", + "field_type": "NestedDict" + } + } + + def build(self, input_value: NestedDict) -> NestedDict: + return input_value diff --git a/src/backend/base/langflow/components/inputs/KeyPairInput.py b/src/backend/base/langflow/components/inputs/KeyPairInput.py new file mode 100644 index 000000000..c48d0a0e1 --- /dev/null +++ b/src/backend/base/langflow/components/inputs/KeyPairInput.py @@ -0,0 +1,19 @@ +from langflow.base.io.text import TextComponent +from langflow.field_typing.constants import Data + + +class KeyPairInput(TextComponent): + display_name = "Dictionary Input" + description = "Dictionary Input." + + def build_config(self): + return { + "input_value": { + "display_name": "Dictionaries", + "field_type": "dict", + "list": True + } + } + + def build(self, input_value: dict) -> dict: + return input_value diff --git a/src/backend/base/langflow/components/outputs/CSVOutput.py b/src/backend/base/langflow/components/outputs/CSVOutput.py new file mode 100644 index 000000000..711a6ab96 --- /dev/null +++ b/src/backend/base/langflow/components/outputs/CSVOutput.py @@ -0,0 +1,17 @@ +from typing import Optional + +from langflow.base.io.text import TextComponent +from langflow.field_typing import Text, Data + + +class CSVOutput(TextComponent): + display_name = "CSV Output" + description = "Used view csv files" + + field_config = { + "input_value": {"display_name": "csv","info":"A csv blob","input_types":["Data"]}, + "separator": {"display_name": "separator","info":"The separator used in the csv file","input_types":["Text"], "field_type":"Text","default_value":";","options":[";", ",", "|"]}, + } + + def build(self, input_value: Data, separator) -> Data: + return {"data": input_value, "separator": separator} diff --git a/src/backend/base/langflow/components/outputs/ImageOutput.py b/src/backend/base/langflow/components/outputs/ImageOutput.py new file mode 100644 index 000000000..f50fb7bf7 --- /dev/null +++ b/src/backend/base/langflow/components/outputs/ImageOutput.py @@ -0,0 +1,15 @@ +from typing import Optional + +from langflow.base.io.text import TextComponent +from langflow.field_typing import Text + +class ImageOutput(TextComponent): + display_name = "Image Output" + description = "Used view image files" + + field_config = { + "input_value": {"display_name": "image","info":"A image url","input_types":["Text"]}, + } + + def build(self, input_value: Text) -> Text: + return input_value \ No newline at end of file diff --git a/src/backend/base/langflow/components/outputs/JsonOutput.py b/src/backend/base/langflow/components/outputs/JsonOutput.py new file mode 100644 index 000000000..cbbc10ddc --- /dev/null +++ b/src/backend/base/langflow/components/outputs/JsonOutput.py @@ -0,0 +1,17 @@ +from langflow.base.io.text import TextComponent +from langflow.field_typing.constants import Data, NestedDict + +class JsonOutput(TextComponent): + display_name = "JSON Output" + description = "JSON Output." + + def build_config(self): + return { + "input_value": { + "display_name": "JSON", + "field_type": "NestedDict" + } + } + + def build(self, input_value: NestedDict) -> NestedDict: + return input_value diff --git a/src/backend/base/langflow/components/outputs/PDFOutput.py b/src/backend/base/langflow/components/outputs/PDFOutput.py new file mode 100644 index 000000000..75e1fabf4 --- /dev/null +++ b/src/backend/base/langflow/components/outputs/PDFOutput.py @@ -0,0 +1,16 @@ +from typing import Optional + +from langflow.base.io.text import TextComponent +from langflow.field_typing import Text + + +class PDFOutput(TextComponent): + display_name = "PDF Output" + description = "Used view pdf files" + + field_config = { + "input_value": {"display_name": "pdf","info":"A pdf url","input_types":["Text"]}, + } + + def build(self, input_value: Text) -> Text: + return input_value diff --git a/src/frontend/src/App.css b/src/frontend/src/App.css index 6aa681415..22a596730 100644 --- a/src/frontend/src/App.css +++ b/src/frontend/src/App.css @@ -96,6 +96,18 @@ body { } .custom-hover:hover { - background-color: rgba(99, 102, 241, 0.1); /* Medium indigo color with 20% opacity */ + background-color: rgba( + 99, + 102, + 241, + 0.1 + ); /* Medium indigo color with 20% opacity */ } +.json-view-playground .json-view { + background-color: #fff !important; +} + +.json-view-flow .json-view { + background-color: #bbb !important; +} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index eef861758..ad00225b8 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -618,7 +618,7 @@ export default function ParameterComponent({ diff --git a/src/frontend/src/components/cardComponent/index.tsx b/src/frontend/src/components/cardComponent/index.tsx index 21701f459..06e87602f 100644 --- a/src/frontend/src/components/cardComponent/index.tsx +++ b/src/frontend/src/components/cardComponent/index.tsx @@ -1,7 +1,9 @@ import { useEffect, useState } from "react"; import { getComponent, postLikeComponent } from "../../controllers/API"; import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal"; +import IOModal from "../../modals/IOModal"; import useAlertStore from "../../stores/alertStore"; +import useFlowStore from "../../stores/flowStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { useStoreStore } from "../../stores/storeStore"; import { storeComponent } from "../../types/store"; @@ -18,8 +20,6 @@ import { CardHeader, CardTitle, } from "../ui/card"; -import IOModal from "../../modals/IOModal"; -import useFlowStore from "../../stores/flowStore"; export default function CollectionCardComponent({ data, @@ -27,7 +27,7 @@ export default function CollectionCardComponent({ disabled = false, button, onDelete, - playground + playground, }: { data: storeComponent; authorized?: boolean; @@ -55,9 +55,9 @@ export default function CollectionCardComponent({ const setNodes = useFlowStore((state) => state.setNodes); const setEdges = useFlowStore((state) => state.setEdges); const [openPlayground, setOpenPlayground] = useState(false); - const setCurrentFlowId = useFlowsManagerStore((state) => state.setCurrentFlowId); - - + const setCurrentFlowId = useFlowsManagerStore( + (state) => state.setCurrentFlowId + ); const name = data.is_component ? "Component" : "Flow"; @@ -86,16 +86,18 @@ export default function CollectionCardComponent({ addFlow(true, newFlow) .then((id) => { setSuccessData({ - title: `${name} ${isStore ? "Downloaded" : "Installed" - } Successfully.`, + title: `${name} ${ + isStore ? "Downloaded" : "Installed" + } Successfully.`, }); setLoading(false); }) .catch((error) => { setLoading(false); setErrorData({ - title: `Error ${isStore ? "downloading" : "installing" - } the ${name}`, + title: `Error ${ + isStore ? "downloading" : "installing" + } the ${name}`, list: [error["response"]["data"]["detail"]], }); }); @@ -362,15 +364,13 @@ export default function CollectionCardComponent({ )} {button && button} - {playground && + {playground && ( - } + )} - + ); } diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 78c344714..3666c61a6 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -267,7 +267,7 @@ export default function CodeTabsComponent({
{node.data.node.template[ templateField - ].list ? ( + ]?.list ? (
diff --git a/src/frontend/src/components/keypairListComponent/index.tsx b/src/frontend/src/components/keypairListComponent/index.tsx index fcd35e01e..2fa12e3b8 100644 --- a/src/frontend/src/components/keypairListComponent/index.tsx +++ b/src/frontend/src/components/keypairListComponent/index.tsx @@ -20,7 +20,13 @@ export default function KeypairListComponent({ } }, [disabled]); - const ref = useRef(value.length === 0 ? [{ "": "" }] : value); + const checkValueType = (value) => { + return Array.isArray(value) ? value : [value]; + }; + + const ref = useRef([]); + ref.current = + !value || value?.length === 0 ? [{ "": "" }] : checkValueType(value); useEffect(() => { if (JSON.stringify(value) !== JSON.stringify(ref.current)) { diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 49483875c..f8d45da02 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -711,13 +711,19 @@ export const LANGFLOW_SUPPORTED_TYPES = new Set([ export const priorityFields = new Set(["code", "template"]); -export const INPUT_TYPES = new Set(["ChatInput", "TextInput", "KeyPairInput"]); +export const INPUT_TYPES = new Set([ + "ChatInput", + "TextInput", + "KeyPairInput", + "JsonInput", +]); export const OUTPUT_TYPES = new Set([ "ChatOutput", "TextOutput", "PDFOutput", "ImageOutput", "CSVOutput", + "JsonOutput", ]); export const CHAT_FIRST_INITIAL_TEXT = diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index 17da8c73b..87c72d1df 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -203,7 +203,7 @@ const EditNodeModal = forwardRef( !myData.node.template[templateParam].options ? (
{myData.node.template[templateParam] - .list ? ( + ?.list ? (
@@ -420,6 +420,10 @@ const EditNodeModal = forwardRef( .type === "int" ? (
{ + if (value) onChange(value); + }, [value]); + const isDark = useDarkStore((state) => state.dark); + + const ref = useRef(null); + ref.current = value; + + const getClassNames = () => { + if (!isDark && !left) return "json-view-playground-white"; + if (!isDark && left) return "json-view-playground-white-left"; + if (isDark && left) return "json-view-playground-dark-left"; + if (isDark && !left) return "json-view-playground-dark"; + }; + + return ( +
+ { + ref.current = edit["src"]; + }} + onChange={(edit) => { + ref.current = edit["src"]; + }} + src={ref.current} + /> +
+ ); +} diff --git a/src/frontend/src/modals/IOModal/components/IOFieldView/components/keyPairInput/index.tsx b/src/frontend/src/modals/IOModal/components/IOFieldView/components/keyPairInput/index.tsx new file mode 100644 index 000000000..b0b557c55 --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/IOFieldView/components/keyPairInput/index.tsx @@ -0,0 +1,91 @@ +import _ from "lodash"; +import { useRef } from "react"; +import IconComponent from "../../../../../../components/genericIconComponent"; +import { Input } from "../../../../../../components/ui/input"; +import { classNames } from "../../../../../../utils/utils"; + +const IOKeyPairInput = ({ value, onChange, duplicateKey, isList = true }) => { + const checkValueType = (value) => { + return Array.isArray(value) ? value : [value]; + }; + + const ref = useRef([]); + ref.current = + !value || value?.length === 0 ? [{ "": "" }] : checkValueType(value); + + const handleChangeKey = (event, idx) => { + 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 key = Object.keys(ref.current[idx])[0]; + ref.current[idx][key] = newValue; + onChange(ref.current); + }; + + return ( + <> +
+ {ref.current?.map((obj, index) => { + return Object.keys(obj).map((key, idx) => { + return ( +
+ handleChangeKey(event, index)} + /> + + + handleChangeValue(event.target.value, index) + } + /> + + {isList && index === ref.current.length - 1 ? ( + + ) : isList ? ( + + ) : ( + "" + )} +
+ ); + }); + })} +
+ + ); +}; + +export default IOKeyPairInput; diff --git a/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx b/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx index dd02d19ca..468b090fe 100644 --- a/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx +++ b/src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx @@ -1,4 +1,5 @@ import { cloneDeep } from "lodash"; +import { useState } from "react"; import ImageViewer from "../../../../components/ImageViewer"; import CsvOutputComponent from "../../../../components/csvOutputComponent"; import PdfViewer from "../../../../components/pdfViewer"; @@ -15,7 +16,13 @@ import { PDFViewConstant } from "../../../../constants/constants"; import { InputOutput } from "../../../../constants/enums"; import useFlowStore from "../../../../stores/flowStore"; import { IOFieldViewProps } from "../../../../types/components"; +import { + convertValuesToNumbers, + hasDuplicateKeys, +} from "../../../../utils/reactflowUtils"; import IOFileInput from "./components/FileInput"; +import IoJsonInput from "./components/JSONInput"; +import IOKeyPairInput from "./components/keyPairInput"; export default function IOFieldView({ type, @@ -39,6 +46,7 @@ export default function IOFieldView({ } } }; + const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); function handleOutputType() { if (!node) return <>"No node found!"; @@ -78,6 +86,39 @@ export default function IOFieldView({ /> ); + case "KeyPairInput": + return ( + { + if (node) { + let newNode = cloneDeep(node); + newNode.data.node!.template["input_value"].value = e; + setNode(node.id, newNode); + } + const valueToNumbers = convertValuesToNumbers(e); + setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers)); + }} + duplicateKey={errorDuplicateKey} + isList={node.data.node!.template["input_value"]?.list ?? false} + /> + ); + + case "JsonInput": + return ( + { + if (node) { + let newNode = cloneDeep(node); + newNode.data.node!.template["input_value"].value = e; + setNode(node.id, newNode); + } + }} + left={left} + /> + ); + default: return (