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 (