(JsonInput.py): add a new component called "JsonInput" to handle JSON input data in the language flow system

 (KeyPairInput.py): add a new component called "KeyPairInput" to handle dictionary input data in the language flow system
 (CSVOutput.py): add a new component called "CSVOutput" to handle CSV output data in the language flow system
 (ImageOutput.py): add a new component called "ImageOutput" to handle image output data in the language flow system
 (JsonOutput.py): add a new component called "JsonOutput" to handle JSON output data in the language flow system
 (PDFOutput.py): add a new component called "PDFOutput" to handle PDF output data in the language flow system
📝 (App.css): update CSS to improve readability and formatting
📝 (parameterComponent/index.tsx): fix a bug where rangeSpec is not properly accessed in the FloatComponent
📝 (cardComponent/index.tsx): update the CollectionCardComponent to include a new button for opening the playground and handle the opening and closing of the IOModal

📝 (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
This commit is contained in:
cristhianzl 2024-04-29 18:07:53 -03:00
commit b06501a4c9
19 changed files with 386 additions and 39 deletions

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -618,7 +618,7 @@ export default function ParameterComponent({
<FloatComponent
disabled={disabled}
value={data.node?.template[name].value ?? ""}
rangeSpec={data.node?.template[name].rangeSpec}
rangeSpec={data.node?.template[name]?.rangeSpec}
onChange={handleOnNewValue}
/>
</div>

View file

@ -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({
</div>
)}
{button && button}
{playground &&
{playground && (
<Button
tabIndex={-1}
variant="outline"
size="sm"
className="whitespace-nowrap "
data-testid={
"playground-flow-button-" + data.id
}
data-testid={"playground-flow-button-" + data.id}
onClick={() => {
setCurrentFlowId(data.id);
setOpenPlayground(true);
@ -381,14 +381,16 @@ export default function CollectionCardComponent({
className="main-page-nav-button select-none"
/>
Playground
{openPlayground && < IOModal open={openPlayground} setOpen={setOpenPlayground}>
<></>
</IOModal>}
{openPlayground && (
<IOModal open={openPlayground} setOpen={setOpenPlayground}>
<></>
</IOModal>
)}
</Button>
}
)}
</div>
</div>
</CardFooter>
</Card >
</Card>
);
}

View file

@ -267,7 +267,7 @@ export default function CodeTabsComponent({
<div className="mx-auto">
{node.data.node.template[
templateField
].list ? (
]?.list ? (
<InputListComponent
componentName={
templateField
@ -745,7 +745,7 @@ export default function CodeTabsComponent({
isList={
node.data.node!.template[
templateField
].list ?? false
]?.list ?? false
}
/>
</div>

View file

@ -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<any>([]);
ref.current =
!value || value?.length === 0 ? [{ "": "" }] : checkValueType(value);
useEffect(() => {
if (JSON.stringify(value) !== JSON.stringify(ref.current)) {

View file

@ -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 =

View file

@ -203,7 +203,7 @@ const EditNodeModal = forwardRef(
!myData.node.template[templateParam].options ? (
<div className="mx-auto">
{myData.node.template[templateParam]
.list ? (
?.list ? (
<InputListComponent
componentName={templateParam}
editNode={true}
@ -345,7 +345,7 @@ const EditNodeModal = forwardRef(
}}
isList={
data.node?.template[templateParam]
.list ?? false
?.list ?? false
}
/>
</div>
@ -420,6 +420,10 @@ const EditNodeModal = forwardRef(
.type === "int" ? (
<div className="mx-auto">
<IntComponent
rangeSpec={
data.node?.template[templateParam]
?.rangeSpec
}
id={
"edit-int-input-" +
myData.node.template[templateParam].name

View file

@ -0,0 +1,45 @@
import { useEffect, useRef } from "react";
import JsonView from "react18-json-view";
import { useDarkStore } from "../../../../../../stores/darkStore";
import { DictComponentType } from "../../../../../../types/components";
export default function IoJsonInput({
value = [],
onChange,
left,
output,
}: DictComponentType): JSX.Element {
useEffect(() => {
if (value) onChange(value);
}, [value]);
const isDark = useDarkStore((state) => state.dark);
const ref = useRef<any>(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 (
<div className="w-full">
<JsonView
className={getClassNames()}
theme="vscode"
dark={isDark}
editable={!output}
enableClipboard
onEdit={(edit) => {
ref.current = edit["src"];
}}
onChange={(edit) => {
ref.current = edit["src"];
}}
src={ref.current}
/>
</div>
);
}

View file

@ -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<any>([]);
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 (
<>
<div className={classNames("flex h-full flex-col gap-3")}>
{ref.current?.map((obj, index) => {
return Object.keys(obj).map((key, idx) => {
return (
<div key={idx} className="flex w-full gap-2">
<Input
type="text"
value={key.trim()}
className={classNames(duplicateKey ? "input-invalid" : "")}
placeholder="Type key..."
onChange={(event) => handleChangeKey(event, index)}
/>
<Input
type="text"
value={obj[key]}
placeholder="Type a value..."
onChange={(event) =>
handleChangeValue(event.target.value, index)
}
/>
{isList && index === ref.current.length - 1 ? (
<button
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
newInputList.push({ "": "" });
onChange(newInputList);
}}
>
<IconComponent
name="Plus"
className={"h-4 w-4 hover:text-accent-foreground"}
/>
</button>
) : isList ? (
<button
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
newInputList.splice(index, 1);
onChange(newInputList);
}}
>
<IconComponent
name="X"
className="h-4 w-4 hover:text-status-red"
/>
</button>
) : (
""
)}
</div>
);
});
})}
</div>
</>
);
};
export default IOKeyPairInput;

View file

@ -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 (
<IOKeyPairInput
value={node.data.node!.template["input_value"]?.value}
onChange={(e) => {
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 (
<IoJsonInput
value={node.data.node!.template["input_value"]?.value}
onChange={(e) => {
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value = e;
setNode(node.id, newNode);
}
}}
left={left}
/>
);
default:
return (
<Textarea
@ -169,6 +210,22 @@ export default function IOFieldView({
/>
);
case "JsonOutput":
return (
<IoJsonInput
value={node.data.node!.template["input_value"]?.value}
onChange={(e) => {
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value = e;
setNode(node.id, newNode);
}
}}
left={left}
output
/>
);
default:
return (
<Textarea

View file

@ -11,6 +11,7 @@ import "react18-json-view/src/style.css";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { CODE_DICT_DIALOG_SUBTITLE } from "../../constants/constants";
import { useDarkStore } from "../../stores/darkStore";
import BaseModal from "../baseModal";
export default function DictAreaModal({
@ -19,7 +20,7 @@ export default function DictAreaModal({
value,
}): JSX.Element {
const [open, setOpen] = useState(false);
const isDark = useDarkStore((state) => state.dark);
const ref = useRef(value);
useEffect(() => {
@ -41,7 +42,8 @@ export default function DictAreaModal({
<div className="flex h-full w-full flex-col transition-all ">
<JsonView
theme="vscode"
dark={true}
dark={isDark}
className={!isDark ? "json-view-white" : "json-view-dark"}
editable
enableClipboard
onEdit={(edit) => {

View file

@ -68,3 +68,31 @@ select:-webkit-autofill:focus {
background-color: #bbb;
border-radius: 999px;
}
.json-view-playground-white-left {
background-color: #fff !important;
height: fit-content !important;
}
.json-view-playground-dark {
background-color: #141924 !important;
height: fit-content !important;
}
.json-view-playground-white {
background-color: #f8fafc !important;
height: fit-content !important;
}
.json-view-playground-dark-left {
background-color: #0c101a !important;
height: fit-content !important;
}
.json-view-white {
background-color: #f8fafc !important;
}
.json-view-dark {
background-color: #141924 !important;
}

View file

@ -80,15 +80,6 @@ export type InputGlobalComponentType = {
editNode?: boolean;
};
export type InputGlobalComponentType = {
disabled: boolean;
onChange: (value: string) => void;
setDb: (value: boolean) => void;
name: string;
data: NodeDataType;
editNode?: boolean;
};
export type KeyPairListComponentType = {
value: any;
onChange: (value: Object[]) => void;
@ -102,9 +93,11 @@ export type KeyPairListComponentType = {
export type DictComponentType = {
value: any;
onChange: (value) => void;
disabled: boolean;
disabled?: boolean;
editNode?: boolean;
id?: string;
left?: boolean;
output?: boolean;
};
export type TextAreaComponentType = {