From e67e55d94e66909a30f7748aaa77f97728609658 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 22 Jun 2023 15:12:38 -0300 Subject: [PATCH 01/18] feat(API): add UpdateTemplate function to update templates via API call --- src/frontend/src/controllers/API/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 40b132261..f19dbd5d8 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -3,6 +3,8 @@ import { PromptTypeAPI, errorsTypeAPI, InitTypeAPI, + TemplateVariableType, + APITemplateType, } from "./../../types/api/index"; import { APIObjectType, sendAllProps } from "../../types/api/index"; import axios, { AxiosResponse } from "axios"; @@ -319,3 +321,7 @@ export async function postBuildInit( ): Promise> { return await axios.post(`/api/v1/build/init`, flow); } + +export async function UpdateTemplate(type:string, template:APITemplateType):Promise>{ + return await axios.post(`/api/v1/UpdateTemplate`); +} \ No newline at end of file From ec517fd9b64307e96d1eee0d59902ad5a6846a01 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Thu, 22 Jun 2023 21:00:02 +0100 Subject: [PATCH 02/18] Endpoint dynamic_node template to test --- src/backend/langflow/main.py | 226 +++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index ad3217eb5..53749d9c8 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -4,6 +4,228 @@ from fastapi.middleware.cors import CORSMiddleware from langflow.api import router from langflow.database.base import create_db_and_tables +template_node = { + "template": { + "lc_kwargs": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "lc_kwargs", + "advanced": "true", + "type": "code", + "list": "false" + }, + "verbose": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "value": "false", + "password": "false", + "name": "verbose", + "advanced": "false", + "type": "bool", + "list": "false" + }, + "callbacks": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "callbacks", + "advanced": "false", + "type": "langchain.callbacks.base.BaseCallbackHandler", + "list": "true" + }, + "tags": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "tags", + "advanced": "false", + "type": "str", + "list": "true" + }, + "client": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "client", + "advanced": "false", + "type": "Any", + "list": "false" + }, + "model_name": { + "required": "false", + "placeholder": "", + "show": "true", + "multiline": "false", + "value": "gpt-3.5-turbo", + "password": "false", + "options": [ + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k-0613", + "gpt-3.5-turbo-16k", + "gpt-4-0613", + "gpt-4-32k-0613", + "gpt-4", + "gpt-4-32k" + ], + "name": "model_name", + "advanced": "false", + "type": "str", + "list": "true" + }, + "temperature": { + "required": "false", + "placeholder": "", + "show": "true", + "multiline": "false", + "value": 0.7, + "password": "false", + "name": "temperature", + "advanced": "false", + "type": "float", + "list": "false" + }, + "model_kwargs": { + "required": "false", + "placeholder": "", + "show": "true", + "multiline": "false", + "password": "false", + "name": "model_kwargs", + "advanced": "true", + "type": "code", + "list": "false" + }, + "openai_api_key": { + "required": "false", + "placeholder": "", + "show": "true", + "multiline": "false", + "value": "", + "password": "true", + "name": "openai_api_key", + "display_name": "OpenAI API Key", + "advanced": "false", + "type": "str", + "list": "false" + }, + "openai_api_base": { + "required": "false", + "placeholder": "", + "show": "true", + "multiline": "false", + "password": "false", + "name": "openai_api_base", + "display_name": "OpenAI API Base", + "advanced": "false", + "type": "str", + "list": "false" + }, + "openai_organization": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "openai_organization", + "display_name": "OpenAI Organization", + "advanced": "false", + "type": "str", + "list": "false" + }, + "openai_proxy": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "openai_proxy", + "display_name": "OpenAI Proxy", + "advanced": "false", + "type": "str", + "list": "false" + }, + "request_timeout": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "password": "false", + "name": "request_timeout", + "advanced": "false", + "type": "float", + "list": "false" + }, + "max_retries": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "value": 6, + "password": "false", + "name": "max_retries", + "advanced": "false", + "type": "int", + "list": "false" + }, + "streaming": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "value": "false", + "password": "false", + "name": "streaming", + "advanced": "false", + "type": "bool", + "list": "false" + }, + "n": { + "required": "false", + "placeholder": "", + "show": "false", + "multiline": "false", + "value": 1, + "password": "false", + "name": "n", + "advanced": "false", + "type": "int", + "list": "false" + }, + "max_tokens": { + "required": "false", + "placeholder": "", + "show": "true", + "multiline": "false", + "password": "true", + "name": "max_tokens", + "advanced": "false", + "type": "int", + "list": "false" + }, + "_type": "ChatOpenAI" + }, + "description": "Wrapper around OpenAI Chat large language models.", + "base_classes": [ + "BaseChatModel", + "Serializable", + "BaseLanguageModel", + "ChatOpenAI" + ], + "display_name": "ChatOpenAI" +} + def create_app(): """Create the FastAPI app and include the router.""" @@ -18,6 +240,10 @@ def create_app(): def get_health(): return {"status": "OK"} + @app.get("/dynamic_node") + def get_dynamic_nome(): + return template_node + app.add_middleware( CORSMiddleware, allow_origins=origins, From 2c256421f45e9c66067c0e727b89872b78ff68f9 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Thu, 22 Jun 2023 21:16:10 +0100 Subject: [PATCH 03/18] Remove description --- src/backend/langflow/main.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 53749d9c8..d93d0efab 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -215,15 +215,7 @@ template_node = { "list": "false" }, "_type": "ChatOpenAI" - }, - "description": "Wrapper around OpenAI Chat large language models.", - "base_classes": [ - "BaseChatModel", - "Serializable", - "BaseLanguageModel", - "ChatOpenAI" - ], - "display_name": "ChatOpenAI" + } } From a458b2d91c04b2c8e18ecd6f5e301446b6f9f3e5 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Thu, 22 Jun 2023 21:26:25 +0100 Subject: [PATCH 04/18] Change type str to bool --- src/backend/langflow/main.py | 208 +++++++++++++++++------------------ 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index d93d0efab..713c921cd 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -7,68 +7,68 @@ from langflow.database.base import create_db_and_tables template_node = { "template": { "lc_kwargs": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "lc_kwargs", - "advanced": "true", + "advanced": True, "type": "code", - "list": "false" + "list": False }, "verbose": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "value": "false", - "password": "false", + "show": False, + "multiline": False, + "value": False, + "password": False, "name": "verbose", - "advanced": "false", + "advanced": False, "type": "bool", - "list": "false" + "list": False }, "callbacks": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "callbacks", - "advanced": "false", + "advanced": False, "type": "langchain.callbacks.base.BaseCallbackHandler", - "list": "true" + "list": True }, "tags": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "tags", - "advanced": "false", + "advanced": False, "type": "str", - "list": "true" + "list": True }, "client": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "client", - "advanced": "false", + "advanced": False, "type": "Any", - "list": "false" + "list": False }, "model_name": { - "required": "false", + "required": False, "placeholder": "", - "show": "true", - "multiline": "false", + "show": True, + "multiline": False, "value": "gpt-3.5-turbo", - "password": "false", + "password": False, "options": [ "gpt-3.5-turbo-0613", "gpt-3.5-turbo", @@ -80,139 +80,139 @@ template_node = { "gpt-4-32k" ], "name": "model_name", - "advanced": "false", + "advanced": False, "type": "str", - "list": "true" + "list": True }, "temperature": { - "required": "false", + "required": False, "placeholder": "", - "show": "true", - "multiline": "false", + "show": True, + "multiline": False, "value": 0.7, - "password": "false", + "password": False, "name": "temperature", - "advanced": "false", + "advanced": False, "type": "float", - "list": "false" + "list": False }, "model_kwargs": { - "required": "false", + "required": False, "placeholder": "", - "show": "true", - "multiline": "false", - "password": "false", + "show": True, + "multiline": False, + "password": False, "name": "model_kwargs", - "advanced": "true", + "advanced": True, "type": "code", - "list": "false" + "list": False }, "openai_api_key": { - "required": "false", + "required": False, "placeholder": "", - "show": "true", - "multiline": "false", + "show": True, + "multiline": False, "value": "", - "password": "true", + "password": True, "name": "openai_api_key", "display_name": "OpenAI API Key", - "advanced": "false", + "advanced": False, "type": "str", - "list": "false" + "list": False }, "openai_api_base": { - "required": "false", + "required": False, "placeholder": "", - "show": "true", - "multiline": "false", - "password": "false", + "show": True, + "multiline": False, + "password": False, "name": "openai_api_base", "display_name": "OpenAI API Base", - "advanced": "false", + "advanced": False, "type": "str", - "list": "false" + "list": False }, "openai_organization": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "openai_organization", "display_name": "OpenAI Organization", - "advanced": "false", + "advanced": False, "type": "str", - "list": "false" + "list": False }, "openai_proxy": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "openai_proxy", "display_name": "OpenAI Proxy", - "advanced": "false", + "advanced": False, "type": "str", - "list": "false" + "list": False }, "request_timeout": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "password": "false", + "show": False, + "multiline": False, + "password": False, "name": "request_timeout", - "advanced": "false", + "advanced": False, "type": "float", - "list": "false" + "list": False }, "max_retries": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", + "show": False, + "multiline": False, "value": 6, - "password": "false", + "password": False, "name": "max_retries", - "advanced": "false", + "advanced": False, "type": "int", - "list": "false" + "list": False }, "streaming": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", - "value": "false", - "password": "false", + "show": False, + "multiline": False, + "value": False, + "password": False, "name": "streaming", - "advanced": "false", + "advanced": False, "type": "bool", - "list": "false" + "list": False }, "n": { - "required": "false", + "required": False, "placeholder": "", - "show": "false", - "multiline": "false", + "show": False, + "multiline": False, "value": 1, - "password": "false", + "password": False, "name": "n", - "advanced": "false", + "advanced": False, "type": "int", - "list": "false" + "list": False }, "max_tokens": { - "required": "false", + "required": False, "placeholder": "", - "show": "true", - "multiline": "false", - "password": "true", + "show": True, + "multiline": False, + "password": True, "name": "max_tokens", - "advanced": "false", + "advanced": False, "type": "int", - "list": "false" + "list": False }, "_type": "ChatOpenAI" } From a47dc9ae92d44a4018fcae432b4e30d1f7963f7c Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 22 Jun 2023 17:30:04 -0300 Subject: [PATCH 05/18] feat(frontend): add support for updating node template on code change in CodeAreaModal fix(API): fix UpdateTemplate function return type to match the actual response fix(vite.config.ts): add dynamic_node route to apiRoutes array to proxy requests to backend --- .../components/parameterComponent/index.tsx | 2 + .../src/CustomNodes/GenericNode/index.tsx | 2 +- .../components/codeAreaComponent/index.tsx | 10 +- src/frontend/src/controllers/API/index.ts | 4 +- .../src/modals/codeAreaModal/index.tsx | 102 +++++++++++------- src/frontend/src/types/components/index.ts | 10 ++ src/frontend/vite.config.ts | 2 +- 7 files changed, 86 insertions(+), 46 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 65f946ba2..c35fd869b 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -227,6 +227,8 @@ export default function ParameterComponent({ ) : left === true && type === "code" ? ( {data.node.template = template}} + template={data.node.template} disabled={disabled} value={data.node.template[name].value ?? ""} onChange={handleOnNewValue} diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index fb1ed5f59..e2ddc051f 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -62,7 +62,7 @@ export default function GenericNode({ } useEffect(() => {}, [closePopUp, data.node.template]); - + console.log({data}) return ( <> diff --git a/src/frontend/src/components/codeAreaComponent/index.tsx b/src/frontend/src/components/codeAreaComponent/index.tsx index 399ab40b2..a4b7231c8 100644 --- a/src/frontend/src/components/codeAreaComponent/index.tsx +++ b/src/frontend/src/components/codeAreaComponent/index.tsx @@ -3,7 +3,7 @@ import { useContext, useEffect, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; import CodeAreaModal from "../../modals/codeAreaModal"; import TextAreaModal from "../../modals/textAreaModal"; -import { TextAreaComponentType } from "../../types/components"; +import { CodeAreaComponentType, TextAreaComponentType } from "../../types/components"; import { INPUT_STYLE } from "../../constants"; export default function CodeAreaComponent({ @@ -11,7 +11,9 @@ export default function CodeAreaComponent({ onChange, disabled, editNode = false, -}: TextAreaComponentType) { + template, + setTemplate, +}: CodeAreaComponentType) { const [myValue, setMyValue] = useState(value); const { openPopUp } = useContext(PopUpContext); useEffect(() => { @@ -37,6 +39,8 @@ export default function CodeAreaComponent({ openPopUp( { setMyValue(t); onChange(t); @@ -59,7 +63,9 @@ export default function CodeAreaComponent({ onClick={() => { openPopUp( { setMyValue(t); onChange(t); diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index f19dbd5d8..60820bb7e 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -322,6 +322,6 @@ export async function postBuildInit( return await axios.post(`/api/v1/build/init`, flow); } -export async function UpdateTemplate(type:string, template:APITemplateType):Promise>{ - return await axios.post(`/api/v1/UpdateTemplate`); +export async function UpdateTemplate(type:string, template:APITemplateType):Promise>{ + return await axios.get(`/dynamic_node`); } \ No newline at end of file diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index 9f8815f4b..451946b0e 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -8,7 +8,7 @@ import "ace-builds/src-noconflict/theme-twilight"; import "ace-builds/src-noconflict/ext-language_tools"; // import "ace-builds/webpack-resolver"; import { darkContext } from "../../contexts/darkContext"; -import { postValidateCode } from "../../controllers/API"; +import { UpdateTemplate, postValidateCode } from "../../controllers/API"; import { alertContext } from "../../contexts/alertContext"; import { TabsContext } from "../../contexts/tabsContext"; import { @@ -22,16 +22,23 @@ import { } from "../../components/ui/dialog"; import { Button } from "../../components/ui/button"; import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants"; +import Loading from "../../components/ui/loading"; +import { APITemplateType } from "../../types/api"; export default function CodeAreaModal({ value, setValue, + template, + setTemplate }: { setValue: (value: string) => void; value: string; + template: APITemplateType, + setTemplate: (template: APITemplateType) => void; }) { const [open, setOpen] = useState(true); const [code, setCode] = useState(value); + const [loading, setLoading] = useState(false); const { dark } = useContext(darkContext); const { setErrorData, setSuccessData } = useContext(alertContext); const { closePopUp } = useContext(PopUpContext); @@ -44,6 +51,58 @@ export default function CodeAreaModal({ }, 300); } } + + function handleClick() { + setLoading(true); + postValidateCode(code) + .then((apiReturn) => { + setLoading(false); + if (apiReturn.data) { + let importsErrors = apiReturn.data.imports.errors; + let funcErrors = apiReturn.data.function.errors; + if (funcErrors.length === 0 && importsErrors.length === 0) { + setSuccessData({ + title: "Code is ready to run", + }); + // setValue(code); + } else { + if (funcErrors.length !== 0) { + setErrorData({ + title: "There is an error in your function", + list: funcErrors, + }); + } + if (importsErrors.length !== 0) { + setErrorData({ + title: "There is an error in your imports", + list: importsErrors, + }); + } + } + } else { + setErrorData({ + title: "Something went wrong, please try again", + }); + } + }) + .catch((_) => { + setLoading(false); + setErrorData({ + title: + "There is something wrong with this code, please review it", + }) + } + ); + UpdateTemplate('code',template).then((apiReturn) => { + const data = apiReturn.data; + if (data.template) { + console.log('updated') + setTemplate(data.template); + setModalOpen(false); + } + }) + } + return ( @@ -80,47 +139,10 @@ export default function CodeAreaModal({ diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 11f8aec07..5b31d63f1 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -1,6 +1,7 @@ import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react"; import { NodeDataType } from "../flow/index"; import { typesContextType } from "../typesContext"; +import { APITemplateType } from "../api"; export type InputComponentType = { value: string; disabled?: boolean; @@ -50,6 +51,15 @@ export type TextAreaComponentType = { editNode?: boolean; }; +export type CodeAreaComponentType = { + disabled: boolean; + onChange: (value: string[] | string) => void; + value: string; + editNode?: boolean; + template: APITemplateType; + setTemplate: (value: APITemplateType) => void; +}; + export type FileComponentType = { disabled: boolean; onChange: (value: string[] | string) => void; diff --git a/src/frontend/vite.config.ts b/src/frontend/vite.config.ts index 860c690ea..176c73aea 100644 --- a/src/frontend/vite.config.ts +++ b/src/frontend/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import svgr from "vite-plugin-svgr"; -const apiRoutes = ["^/api/v1/", "/health"]; +const apiRoutes = ["^/api/v1/", "/health","/dynamic_node"]; // Use environment variable to determine the target. const target = process.env.VITE_PROXY_TARGET || "http://127.0.0.1:7860"; From 6736288e6f5542552df58d1953a0afb0bc2a2e5b Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Thu, 22 Jun 2023 21:37:01 +0100 Subject: [PATCH 06/18] Add code section --- src/backend/langflow/main.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 713c921cd..caa70a79b 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -6,6 +6,18 @@ from langflow.database.base import create_db_and_tables template_node = { "template": { + "code": { + "required": True, + "placeholder": "", + "show": True, + "multiline": True, + "value": "\ndef my_user_python_function(text: str) -> str:\n \"\"\"This is a default python function that returns the input text\"\"\"\n return text.upper()\n", + "password": False, + "name": "code", + "advanced": False, + "type": "code", + "list": False + }, "lc_kwargs": { "required": False, "placeholder": "", From 531b3b3f7abdcebaca181384b8794395ef602861 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Thu, 22 Jun 2023 21:49:22 +0100 Subject: [PATCH 07/18] Base classes data added --- src/backend/langflow/main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index caa70a79b..801abbe3a 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -226,6 +226,13 @@ template_node = { "type": "int", "list": False }, + "base_classes": [ + "BaseChatModel", + "Serializable", + "BaseLanguageModel", + "ChatOpenAI" + ], + "description": "Wrapper around OpenAI Chat large language models.", "_type": "ChatOpenAI" } } From 37e1ea05b428f82d7c8dcb760d39f43cab8b0b9e Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Thu, 22 Jun 2023 22:15:59 +0100 Subject: [PATCH 08/18] Base classes data moved --- src/backend/langflow/main.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 801abbe3a..7f5644d69 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -226,15 +226,15 @@ template_node = { "type": "int", "list": False }, - "base_classes": [ - "BaseChatModel", - "Serializable", - "BaseLanguageModel", - "ChatOpenAI" - ], - "description": "Wrapper around OpenAI Chat large language models.", "_type": "ChatOpenAI" - } + }, + "base_classes": [ + "BaseChatModel", + "Serializable", + "BaseLanguageModel", + "ChatOpenAI" + ], + "description": "Wrapper around OpenAI Chat large language models." } From 19be2e81780d70ff32dceb29596b68d0770b5eb9 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 22 Jun 2023 18:21:41 -0300 Subject: [PATCH 09/18] refactor(parameterComponent): change setTemplate to setNodeClass to improve semantics and fix bug refactor(codeAreaComponent): change template to nodeClass and setTemplate to setNodeClass to improve semantics refactor(API): change UpdateTemplate function parameter from template to nodeClass to improve semantics refactor(codeAreaModal): change template to nodeClass and setTemplate to setNodeClass to improve semantics and fix bug refactor(types): add APIClassType to import and change APITemplateType to import in relevant files to improve semantics --- .../components/parameterComponent/index.tsx | 4 ++-- .../src/components/codeAreaComponent/index.tsx | 12 ++++++------ src/frontend/src/controllers/API/index.ts | 3 ++- .../src/modals/codeAreaModal/index.tsx | 18 +++++++++--------- src/frontend/src/types/components/index.ts | 6 +++--- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index c35fd869b..915ab41ae 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -227,8 +227,8 @@ export default function ParameterComponent({ ) : left === true && type === "code" ? ( {data.node.template = template}} - template={data.node.template} + setNodeClass={(nodeClass)=>{data.node = nodeClass}} + nodeClass={data.node} disabled={disabled} value={data.node.template[name].value ?? ""} onChange={handleOnNewValue} diff --git a/src/frontend/src/components/codeAreaComponent/index.tsx b/src/frontend/src/components/codeAreaComponent/index.tsx index a4b7231c8..14e444ccc 100644 --- a/src/frontend/src/components/codeAreaComponent/index.tsx +++ b/src/frontend/src/components/codeAreaComponent/index.tsx @@ -11,8 +11,8 @@ export default function CodeAreaComponent({ onChange, disabled, editNode = false, - template, - setTemplate, + nodeClass, + setNodeClass, }: CodeAreaComponentType) { const [myValue, setMyValue] = useState(value); const { openPopUp } = useContext(PopUpContext); @@ -39,8 +39,8 @@ export default function CodeAreaComponent({ openPopUp( { setMyValue(t); onChange(t); @@ -63,9 +63,9 @@ export default function CodeAreaComponent({ onClick={() => { openPopUp( { setMyValue(t); onChange(t); diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 60820bb7e..c7a8ee776 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -5,6 +5,7 @@ import { InitTypeAPI, TemplateVariableType, APITemplateType, + APIClassType, } from "./../../types/api/index"; import { APIObjectType, sendAllProps } from "../../types/api/index"; import axios, { AxiosResponse } from "axios"; @@ -322,6 +323,6 @@ export async function postBuildInit( return await axios.post(`/api/v1/build/init`, flow); } -export async function UpdateTemplate(type:string, template:APITemplateType):Promise>{ +export async function UpdateTemplate(type:string, nodeClass:APIClassType):Promise>{ return await axios.get(`/dynamic_node`); } \ No newline at end of file diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index 451946b0e..6448702a7 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -23,18 +23,18 @@ import { import { Button } from "../../components/ui/button"; import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants"; import Loading from "../../components/ui/loading"; -import { APITemplateType } from "../../types/api"; +import { APIClassType, APITemplateType } from "../../types/api"; export default function CodeAreaModal({ value, setValue, - template, - setTemplate + nodeClass, + setNodeClass }: { setValue: (value: string) => void; value: string; - template: APITemplateType, - setTemplate: (template: APITemplateType) => void; + nodeClass: APIClassType, + setNodeClass: (Class: APIClassType) => void; }) { const [open, setOpen] = useState(true); const [code, setCode] = useState(value); @@ -93,11 +93,11 @@ export default function CodeAreaModal({ }) } ); - UpdateTemplate('code',template).then((apiReturn) => { + UpdateTemplate('code',nodeClass).then((apiReturn) => { const data = apiReturn.data; - if (data.template) { - console.log('updated') - setTemplate(data.template); + if (data) { + console.log(data) + setNodeClass(data); setModalOpen(false); } }) diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 5b31d63f1..845b853c8 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -1,7 +1,7 @@ import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react"; import { NodeDataType } from "../flow/index"; import { typesContextType } from "../typesContext"; -import { APITemplateType } from "../api"; +import { APIClassType, APITemplateType } from "../api"; export type InputComponentType = { value: string; disabled?: boolean; @@ -56,8 +56,8 @@ export type CodeAreaComponentType = { onChange: (value: string[] | string) => void; value: string; editNode?: boolean; - template: APITemplateType; - setTemplate: (value: APITemplateType) => void; + nodeClass: APIClassType; + setNodeClass: (value: APIClassType) => void; }; export type FileComponentType = { From cf7cd979dc9ccb6dd9d00448bb3424568fed33c4 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Fri, 23 Jun 2023 02:54:43 +0100 Subject: [PATCH 10/18] feat: Add ClassCodeExtractor and is_valid_class_template functions, and custom_component endpoint This commit adds a new file called `extract_info_from_class.py` containing a `ClassCodeExtractor` class that can extract information (imports, class details and functions) from a Python class code. It also adds a function called `is_valid_class_template` that checks if a given Python class code matches a certain template. Additionally, the commit adds a new endpoint in the `/custom_component` route of `endpoints.py` that returns a dictionary of all `langchain` types created by a specific creator. Finally, the commit adds a new section to `types.py` named `template_node`, which describes a default dictionary that can be used to define a new node for Langflow's template editor. --- .../langflow/api/extract_info_from_class.py | 70 ++++++ src/backend/langflow/api/v1/endpoints.py | 12 +- src/backend/langflow/api/v1/schemas.py | 8 +- src/backend/langflow/interface/types.py | 25 ++ src/backend/langflow/main.py | 237 ++++++++++++++++++ 5 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 src/backend/langflow/api/extract_info_from_class.py diff --git a/src/backend/langflow/api/extract_info_from_class.py b/src/backend/langflow/api/extract_info_from_class.py new file mode 100644 index 000000000..4dfb24e03 --- /dev/null +++ b/src/backend/langflow/api/extract_info_from_class.py @@ -0,0 +1,70 @@ +import ast + + +class ClassCodeExtractor: + def __init__(self, code): + self.code = code + self.data = { + "imports": [], + "class": { + "inherited_classes": "", + "name": "", + "init": "" + }, + "functions": [] + } + + def _handle_import(self, node): + for alias in node.names: + module_name = getattr(node, 'module', None) + self.data['imports'].append( + f"{module_name}.{alias.name}" if module_name else alias.name) + + def _handle_class(self, node): + self.data['class'].update({ + 'name': node.name, + 'inherited_classes': [ast.unparse(base) for base in node.bases] + }) + + for inner_node in node.body: + if isinstance(inner_node, ast.FunctionDef): + self._handle_function(inner_node) + + def _handle_function(self, node): + function_name = node.name + function_args_str = ast.unparse(node.args) + function_args = function_args_str.split( + ", ") if function_args_str else [] + + return_type = ast.unparse(node.returns) if node.returns else "None" + + function_data = { + "name": function_name, + "arguments": function_args, + "return_type": return_type + } + + if function_name == "__init__": + self.data['class']['init'] = function_args_str.split( + ", ") if function_args_str else [] + else: + self.data["functions"].append(function_data) + + def extract_class_info(self): + module = ast.parse(self.code) + + for node in module.body: + if isinstance(node, (ast.Import, ast.ImportFrom)): + self._handle_import(node) + elif isinstance(node, ast.ClassDef): + self._handle_class(node) + + return self.data + + +def is_valid_class_template(code: dict) -> bool: + class_name_ok = code["class"]["name"] == "PythonFunction" + function_run_exists = len( + [f for f in code["functions"] if f["name"] == "run"]) == 1 + + return (class_name_ok and function_run_exists) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 6fea926d7..4d3ac9f5e 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -7,9 +7,13 @@ from fastapi import APIRouter, Depends, HTTPException from langflow.api.v1.schemas import ( PredictRequest, PredictResponse, + CustomComponentResponse, ) -from langflow.interface.types import build_langchain_types_dict +from langflow.interface.types import ( + build_langchain_types_dict, + build_langchain_types_dict_by_creator +) from langflow.database.base import get_session from sqlmodel import Session @@ -62,3 +66,9 @@ def get_version(): from langflow import __version__ return {"version": __version__} + + +# @router.post("/custom_component", response_model=CustomComponentResponse, status_code=200) +@router.post("/custom_component", status_code=200) +def custom_component(code: dict): + return build_langchain_types_dict_by_creator("a") diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index fc2e8c69d..145ac9365 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -69,7 +69,8 @@ class ChatResponse(ChatMessage): @validator("type") def validate_message_type(cls, v): if v not in ["start", "stream", "end", "error", "info", "file"]: - raise ValueError("type must be start, stream, end, error, info, or file") + raise ValueError( + "type must be start, stream, end, error, info, or file") return v @@ -110,3 +111,8 @@ class StreamData(BaseModel): def __str__(self) -> str: return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n" + + +class CustomComponentResponse(BaseModel): + model: str = "" + step: str = "" diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 085537756..ac1b59e6f 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -52,3 +52,28 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union if created_types[creator.type_name].values(): all_types.update(created_types) return all_types + + +# sourcery skip: dict-assign-update-to-union +def build_langchain_types_dict_by_creator(creator: str): + """Build a dictionary of all langchain types""" + + all_types = {} + + creators = [ + chain_creator, + agent_creator, + prompt_creator, + llm_creator, + memory_creator, + tool_creator, + toolkits_creator, + wrapper_creator, + embedding_creator, + vectorstore_creator, + documentloader_creator, + textsplitter_creator, + utility_creator, + ] + + return chain_creator.to_dict()['chains']['ConversationChain'] diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index ad3217eb5..7f5644d69 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -4,6 +4,239 @@ from fastapi.middleware.cors import CORSMiddleware from langflow.api import router from langflow.database.base import create_db_and_tables +template_node = { + "template": { + "code": { + "required": True, + "placeholder": "", + "show": True, + "multiline": True, + "value": "\ndef my_user_python_function(text: str) -> str:\n \"\"\"This is a default python function that returns the input text\"\"\"\n return text.upper()\n", + "password": False, + "name": "code", + "advanced": False, + "type": "code", + "list": False + }, + "lc_kwargs": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "lc_kwargs", + "advanced": True, + "type": "code", + "list": False + }, + "verbose": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "value": False, + "password": False, + "name": "verbose", + "advanced": False, + "type": "bool", + "list": False + }, + "callbacks": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "callbacks", + "advanced": False, + "type": "langchain.callbacks.base.BaseCallbackHandler", + "list": True + }, + "tags": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "tags", + "advanced": False, + "type": "str", + "list": True + }, + "client": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "client", + "advanced": False, + "type": "Any", + "list": False + }, + "model_name": { + "required": False, + "placeholder": "", + "show": True, + "multiline": False, + "value": "gpt-3.5-turbo", + "password": False, + "options": [ + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k-0613", + "gpt-3.5-turbo-16k", + "gpt-4-0613", + "gpt-4-32k-0613", + "gpt-4", + "gpt-4-32k" + ], + "name": "model_name", + "advanced": False, + "type": "str", + "list": True + }, + "temperature": { + "required": False, + "placeholder": "", + "show": True, + "multiline": False, + "value": 0.7, + "password": False, + "name": "temperature", + "advanced": False, + "type": "float", + "list": False + }, + "model_kwargs": { + "required": False, + "placeholder": "", + "show": True, + "multiline": False, + "password": False, + "name": "model_kwargs", + "advanced": True, + "type": "code", + "list": False + }, + "openai_api_key": { + "required": False, + "placeholder": "", + "show": True, + "multiline": False, + "value": "", + "password": True, + "name": "openai_api_key", + "display_name": "OpenAI API Key", + "advanced": False, + "type": "str", + "list": False + }, + "openai_api_base": { + "required": False, + "placeholder": "", + "show": True, + "multiline": False, + "password": False, + "name": "openai_api_base", + "display_name": "OpenAI API Base", + "advanced": False, + "type": "str", + "list": False + }, + "openai_organization": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "openai_organization", + "display_name": "OpenAI Organization", + "advanced": False, + "type": "str", + "list": False + }, + "openai_proxy": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "openai_proxy", + "display_name": "OpenAI Proxy", + "advanced": False, + "type": "str", + "list": False + }, + "request_timeout": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "password": False, + "name": "request_timeout", + "advanced": False, + "type": "float", + "list": False + }, + "max_retries": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "value": 6, + "password": False, + "name": "max_retries", + "advanced": False, + "type": "int", + "list": False + }, + "streaming": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "value": False, + "password": False, + "name": "streaming", + "advanced": False, + "type": "bool", + "list": False + }, + "n": { + "required": False, + "placeholder": "", + "show": False, + "multiline": False, + "value": 1, + "password": False, + "name": "n", + "advanced": False, + "type": "int", + "list": False + }, + "max_tokens": { + "required": False, + "placeholder": "", + "show": True, + "multiline": False, + "password": True, + "name": "max_tokens", + "advanced": False, + "type": "int", + "list": False + }, + "_type": "ChatOpenAI" + }, + "base_classes": [ + "BaseChatModel", + "Serializable", + "BaseLanguageModel", + "ChatOpenAI" + ], + "description": "Wrapper around OpenAI Chat large language models." +} + def create_app(): """Create the FastAPI app and include the router.""" @@ -18,6 +251,10 @@ def create_app(): def get_health(): return {"status": "OK"} + @app.get("/dynamic_node") + def get_dynamic_nome(): + return template_node + app.add_middleware( CORSMiddleware, allow_origins=origins, From 5d430f93645fed70a372be0da23bd50316898157 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 27 Jun 2023 19:25:44 +0100 Subject: [PATCH 11/18] Refactor ClassCodeExtractor to extract the entrypoint function arguments and return type\nAdd validation of correct format for custom_component code\nAdd function to build a template for custom_component with its code as a field value. --- .../langflow/api/extract_info_from_class.py | 41 +++++++++++-- src/backend/langflow/api/v1/endpoints.py | 49 ++++++++++++++-- src/backend/langflow/api/v1/schemas.py | 4 ++ src/backend/langflow/interface/types.py | 58 ++++++++++++------- 4 files changed, 122 insertions(+), 30 deletions(-) diff --git a/src/backend/langflow/api/extract_info_from_class.py b/src/backend/langflow/api/extract_info_from_class.py index 4dfb24e03..0c930e09c 100644 --- a/src/backend/langflow/api/extract_info_from_class.py +++ b/src/backend/langflow/api/extract_info_from_class.py @@ -4,6 +4,7 @@ import ast class ClassCodeExtractor: def __init__(self, code): self.code = code + self.function_entrypoint_name = "build" self.data = { "imports": [], "class": { @@ -61,10 +62,40 @@ class ClassCodeExtractor: return self.data + def get_entrypoint_function_args_and_return_type(self): + data = self.extract_class_info() + functions = data.get("functions", []) -def is_valid_class_template(code: dict) -> bool: - class_name_ok = code["class"]["name"] == "PythonFunction" - function_run_exists = len( - [f for f in code["functions"] if f["name"] == "run"]) == 1 + build_function = next( + (f for f in functions if f["name"] == + self.function_entrypoint_name), None + ) - return (class_name_ok and function_run_exists) + funtion_args = build_function.get("arguments", None) + return_type = build_function.get("return_type", None) + + return funtion_args, return_type + + +def is_valid_class_template(code: dict): + function_entrypoint_name = "build" + return_type_valid_list = ["ChainCreator", "ToolCreator"] + + class_name = code.get("class", {}).get("name", None) + if not class_name: # this will also check for None, empty string, etc. + return False + + functions = code.get("functions", []) + # use a generator and next to find if a function matching the criteria exists + build_function = next( + (f for f in functions if f["name"] == function_entrypoint_name), None + ) + + if not build_function: + return False + + # Check if the return type of the build function is valid + if build_function.get("return_type") not in return_type_valid_list: + return False + + return True diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 4d3ac9f5e..73ffc63a1 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -1,18 +1,22 @@ from langflow.database.models.flow import Flow from langflow.processing.process import process_graph_cached, process_tweaks from langflow.utils.logger import logger - +from langflow.api.extract_info_from_class import ( + ClassCodeExtractor, + is_valid_class_template +) from fastapi import APIRouter, Depends, HTTPException from langflow.api.v1.schemas import ( PredictRequest, PredictResponse, - CustomComponentResponse, + CustomComponentCode, + CustomComponentResponse ) from langflow.interface.types import ( build_langchain_types_dict, - build_langchain_types_dict_by_creator + build_langchain_template_custom_component ) from langflow.database.base import get_session from sqlmodel import Session @@ -70,5 +74,40 @@ def get_version(): # @router.post("/custom_component", response_model=CustomComponentResponse, status_code=200) @router.post("/custom_component", status_code=200) -def custom_component(code: dict): - return build_langchain_types_dict_by_creator("a") +def custom_component( + code: CustomComponentCode, + session: Session = Depends(get_session), +): + code_test = """ +from langflow.interface.chains.base import ChainCreator +from langflow.interface.tools.base import ToolCreator + + +class MyPythonClass(): + def __init__(self, title: str, author: str, year_published: int): + self.title = title + self.author = author + self.year_published = year_published + + def get_details(self): + return f"Title: {self.title}, Author: {self.author}, Year Published: {self.year_published}" + + def update_year_published(self, new_year: int): + self.year_published = new_year + print(f"The year of publication has been updated to {new_year}.") + + def build(self, name: str, id: int, other: str) -> ChainCreator: + return ChainCreator() +""" + + extractor = ClassCodeExtractor(code_test) + data = extractor.extract_class_info() + valid = is_valid_class_template(data) + + function_args, function_return_type = extractor.get_entrypoint_function_args_and_return_type() + + return build_langchain_template_custom_component( + code_test, + function_args, + function_return_type + ) diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 145ac9365..c8b6e2856 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -116,3 +116,7 @@ class StreamData(BaseModel): class CustomComponentResponse(BaseModel): model: str = "" step: str = "" + + +class CustomComponentCode(BaseModel): + code: str diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index ac1b59e6f..17c1562e4 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -54,26 +54,44 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union return all_types -# sourcery skip: dict-assign-update-to-union -def build_langchain_types_dict_by_creator(creator: str): - """Build a dictionary of all langchain types""" +def find_class_type(class_name, classes_dict): + return next( + ( + {"type": class_type, "class": class_name} + for class_type, class_list in classes_dict.items() + if class_name in class_list + ), + {"error": "class not found"}, + ) - all_types = {} - creators = [ - chain_creator, - agent_creator, - prompt_creator, - llm_creator, - memory_creator, - tool_creator, - toolkits_creator, - wrapper_creator, - embedding_creator, - vectorstore_creator, - documentloader_creator, - textsplitter_creator, - utility_creator, - ] +def build_langchain_template_custom_component(raw_code, function_args, function_return_type): + type_list = get_type_list() + type_and_class = find_class_type("Tool", type_list) - return chain_creator.to_dict()['chains']['ConversationChain'] + # Field with the Python code to allow update + code_field = { + "code": { + "required": True, + "placeholder": "", + "show": True, + "multiline": True, + "value": raw_code, + "password": False, + "name": "code", + "advanced": False, + "type": "code", + "list": False + } + } + + # TODO: Add extra fields + + # TODO: Build template result + template = chain_creator.to_dict()['chains']['ConversationChain'] + + template.get('template')['code'] = code_field.get('code') + + return template + # return globals()['tool_creator'].to_dict()[type_and_class['type']][type_and_class['class']] + # return chain_creator.to_dict()['chains']['ConversationChain'] From 2497561cd3fe634a61571764ec3c815089b933ec Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Jun 2023 15:44:55 -0300 Subject: [PATCH 12/18] =?UTF-8?q?=F0=9F=9A=80=20feat(API):=20add=20postCus?= =?UTF-8?q?tomComponent=20function=20to=20send=20custom=20component=20code?= =?UTF-8?q?=20to=20the=20server=20=F0=9F=9A=80=20feat(CodeAreaModal):=20re?= =?UTF-8?q?place=20UpdateTemplate=20function=20with=20postCustomComponent?= =?UTF-8?q?=20function=20to=20send=20custom=20component=20code=20to=20the?= =?UTF-8?q?=20server=20The=20UpdateTemplate=20function=20was=20not=20being?= =?UTF-8?q?=20used=20and=20was=20removed.=20The=20postCustomComponent=20fu?= =?UTF-8?q?nction=20was=20added=20to=20send=20custom=20component=20code=20?= =?UTF-8?q?to=20the=20server.=20The=20CodeAreaModal=20component=20was=20up?= =?UTF-8?q?dated=20to=20use=20the=20new=20postCustomComponent=20function?= =?UTF-8?q?=20instead=20of=20the=20UpdateTemplate=20function=20to=20send?= =?UTF-8?q?=20custom=20component=20code=20to=20the=20server.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/controllers/API/index.ts | 9 +++--- .../src/modals/codeAreaModal/index.tsx | 32 +++++++------------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 60820bb7e..516e34201 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -3,7 +3,6 @@ import { PromptTypeAPI, errorsTypeAPI, InitTypeAPI, - TemplateVariableType, APITemplateType, } from "./../../types/api/index"; import { APIObjectType, sendAllProps } from "../../types/api/index"; @@ -322,6 +321,8 @@ export async function postBuildInit( return await axios.post(`/api/v1/build/init`, flow); } -export async function UpdateTemplate(type:string, template:APITemplateType):Promise>{ - return await axios.get(`/dynamic_node`); -} \ No newline at end of file +export async function postCustomComponent( + code: string +): Promise> { + return await axios.post(`/api/v1/custom_component`, { code }); +} diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index 451946b0e..536404fcc 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -1,5 +1,5 @@ -import { XMarkIcon, CommandLineIcon } from "@heroicons/react/24/outline"; -import { Fragment, useContext, useRef, useState } from "react"; +import { CommandLineIcon } from "@heroicons/react/24/outline"; +import { useContext, useRef, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; import AceEditor from "react-ace"; import "ace-builds/src-noconflict/mode-python"; @@ -8,9 +8,8 @@ import "ace-builds/src-noconflict/theme-twilight"; import "ace-builds/src-noconflict/ext-language_tools"; // import "ace-builds/webpack-resolver"; import { darkContext } from "../../contexts/darkContext"; -import { UpdateTemplate, postValidateCode } from "../../controllers/API"; +import { postCustomComponent, postValidateCode } from "../../controllers/API"; import { alertContext } from "../../contexts/alertContext"; -import { TabsContext } from "../../contexts/tabsContext"; import { Dialog, DialogContent, @@ -22,18 +21,17 @@ import { } from "../../components/ui/dialog"; import { Button } from "../../components/ui/button"; import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants"; -import Loading from "../../components/ui/loading"; import { APITemplateType } from "../../types/api"; export default function CodeAreaModal({ value, setValue, template, - setTemplate + setTemplate, }: { setValue: (value: string) => void; value: string; - template: APITemplateType, + template: APITemplateType; setTemplate: (template: APITemplateType) => void; }) { const [open, setOpen] = useState(true); @@ -88,19 +86,17 @@ export default function CodeAreaModal({ .catch((_) => { setLoading(false); setErrorData({ - title: - "There is something wrong with this code, please review it", - }) - } - ); - UpdateTemplate('code',template).then((apiReturn) => { + title: "There is something wrong with this code, please review it", + }); + }); + postCustomComponent(code).then((apiReturn) => { const data = apiReturn.data; if (data.template) { - console.log('updated') + console.log("updated"); setTemplate(data.template); setModalOpen(false); } - }) + }); } return ( @@ -137,11 +133,7 @@ export default function CodeAreaModal({ - From 08b20e18cb16ca86cfbb3c54c07a411bc2a0a11a Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 27 Jun 2023 16:26:27 -0300 Subject: [PATCH 13/18] feat(API): add postCustomComponent function to create custom components fix(modals): replace UpdateTemplate function with postCustomComponent function to create custom components --- src/frontend/src/controllers/API/index.ts | 7 +++++++ src/frontend/src/modals/codeAreaModal/index.tsx | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index df285f419..217935d1f 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -325,3 +325,10 @@ export async function postBuildInit( export async function UpdateTemplate(type:string, template:APITemplateType):Promise>{ return await axios.get(`/dynamic_node`); } + +export async function postCustomComponent( + code: string, + apiClass: APIClassType +): Promise> { + return await axios.post(`/api/v1/custom_component`, { code }); +} \ No newline at end of file diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index a2015f5d2..f88bc7b38 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -92,10 +92,9 @@ export default function CodeAreaModal({ }) } ); - UpdateTemplate('code',nodeClass).then((apiReturn) => { + postCustomComponent('code',nodeClass).then((apiReturn) => { const data = apiReturn.data; if (data) { - console.log(data) setNodeClass(data); setModalOpen(false); } From f5211d4ef00c954580cafdb28aa0486e32351879 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 27 Jun 2023 16:40:00 -0300 Subject: [PATCH 14/18] bugfix: sendind string code --- src/frontend/src/modals/codeAreaModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index f88bc7b38..a0ff07ec1 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -92,7 +92,7 @@ export default function CodeAreaModal({ }) } ); - postCustomComponent('code',nodeClass).then((apiReturn) => { + postCustomComponent(code,nodeClass).then((apiReturn) => { const data = apiReturn.data; if (data) { setNodeClass(data); From c0b1a12766a96432acf75535a17879352f0dacda Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Jun 2023 17:59:57 -0300 Subject: [PATCH 15/18] =?UTF-8?q?=F0=9F=90=9B=20fix(API/index.ts):=20add?= =?UTF-8?q?=20frontend=5Fnode=20parameter=20to=20checkPrompt=20function=20?= =?UTF-8?q?to=20fix=20validation=20errors=20The=20checkPrompt=20function?= =?UTF-8?q?=20was=20not=20properly=20validating=20prompts=20due=20to=20mis?= =?UTF-8?q?sing=20frontend=5Fnode=20parameter.=20The=20fix=20adds=20the=20?= =?UTF-8?q?frontend=5Fnode=20parameter=20to=20the=20function=20and=20passe?= =?UTF-8?q?s=20it=20to=20the=20axios.post=20request.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/controllers/API/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 2651a0058..e6848d1d4 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -4,6 +4,7 @@ import { errorsTypeAPI, InitTypeAPI, UploadFileTypeAPI, + APIClassType, } from "./../../types/api/index"; import { APIObjectType, sendAllProps } from "../../types/api/index"; import axios, { AxiosResponse } from "axios"; @@ -56,9 +57,13 @@ export async function postValidateCode( * @returns {Promise>} A promise that resolves to an AxiosResponse containing the validation results. */ export async function checkPrompt( - template: string + template: string, + frontend_node: APIClassType ): Promise> { - return await axios.post("/api/v1/validate/prompt", { template }); + return await axios.post("/api/v1/validate/prompt", { + template: template, + frontend_node: frontend_node, + }); } /** From ab77792d65e63da22a6257001bda87d9f18894bd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Jun 2023 18:22:48 -0300 Subject: [PATCH 16/18] =?UTF-8?q?=F0=9F=94=A8=20refactor(parameterComponen?= =?UTF-8?q?t):=20improve=20code=20readability=20by=20adding=20line=20break?= =?UTF-8?q?s=20and=20indentation=20=F0=9F=94=A8=20refactor(promptComponent?= =?UTF-8?q?):=20add=20nodeClass=20prop=20to=20improve=20code=20consistency?= =?UTF-8?q?=20=F0=9F=94=A8=20refactor(genericModal):=20add=20nodeClass=20p?= =?UTF-8?q?rop=20to=20improve=20code=20consistency=20=F0=9F=94=A8=20refact?= =?UTF-8?q?or(types/components):=20remove=20unused=20imports=20and=20add?= =?UTF-8?q?=20nodeClass=20prop=20to=20TextAreaComponentType=20The=20change?= =?UTF-8?q?s=20made=20in=20the=20code=20aim=20to=20improve=20code=20readab?= =?UTF-8?q?ility=20and=20consistency.=20The=20ParameterComponent,=20Prompt?= =?UTF-8?q?AreaComponent,=20and=20GenericModal=20components=20now=20have?= =?UTF-8?q?=20a=20nodeClass=20prop=20to=20improve=20consistency=20with=20t?= =?UTF-8?q?he=20naming=20conventions.=20The=20TextAreaComponentType=20now?= =?UTF-8?q?=20has=20a=20nodeClass=20prop=20to=20improve=20consistency=20wi?= =?UTF-8?q?th=20the=20other=20components.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/parameterComponent/index.tsx | 5 ++++- .../src/components/promptComponent/index.tsx | 2 ++ src/frontend/src/modals/genericModal/index.tsx | 12 +++++++++--- src/frontend/src/types/components/index.ts | 11 +++-------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 21ee312b4..9b7590213 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -229,7 +229,9 @@ export default function ParameterComponent({ ) : left === true && type === "code" ? ( {data.node = nodeClass}} + setNodeClass={(nodeClass) => { + data.node = nodeClass; + }} nodeClass={data.node} disabled={disabled} value={data.node.template[name].value ?? ""} @@ -258,6 +260,7 @@ export default function ParameterComponent({ ) : left === true && type === "prompt" ? ( ); }} diff --git a/src/frontend/src/modals/genericModal/index.tsx b/src/frontend/src/modals/genericModal/index.tsx index 6f89e24a6..8f7dcefc8 100644 --- a/src/frontend/src/modals/genericModal/index.tsx +++ b/src/frontend/src/modals/genericModal/index.tsx @@ -1,9 +1,8 @@ -import { Fragment, useContext, useRef, useState } from "react"; +import { useContext, useRef, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; import { darkContext } from "../../contexts/darkContext"; import { checkPrompt } from "../../controllers/API"; import { alertContext } from "../../contexts/alertContext"; -import { TypeModal } from "../../utils"; import { Dialog, DialogContent, @@ -17,6 +16,7 @@ import { Button } from "../../components/ui/button"; import { Textarea } from "../../components/ui/textarea"; import { PROMPT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE } from "../../constants"; import { FileText } from "lucide-react"; +import { APIClassType } from "../../types/api"; export default function GenericModal({ value, @@ -24,12 +24,14 @@ export default function GenericModal({ buttonText, modalTitle, type, + nodeClass, }: { setValue: (value: string) => void; value: string; buttonText: string; modalTitle: string; type: number; + nodeClass: APIClassType; }) { const [myButtonText] = useState(buttonText); const [myModalTitle] = useState(modalTitle); @@ -97,9 +99,13 @@ export default function GenericModal({ setModalOpen(false); break; case 2: - checkPrompt(myValue) + checkPrompt(myValue, nodeClass) .then((apiReturn) => { if (apiReturn.data) { + if (apiReturn.data) { + setNodeClass(data); + setModalOpen(false); + } let inputVariables = apiReturn.data.input_variables; if (inputVariables.length === 0) { setErrorData({ diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index b72a7e003..c1cc3756c 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -1,13 +1,7 @@ -import { - ComponentType, - ForwardRefExoticComponent, - ReactElement, - ReactNode, - SVGProps, -} from "react"; +import { ReactElement, ReactNode } from "react"; import { NodeDataType } from "../flow/index"; import { typesContextType } from "../typesContext"; -import { APIClassType, APITemplateType } from "../api"; +import { APIClassType } from "../api"; export type InputComponentType = { value: string; disabled?: boolean; @@ -51,6 +45,7 @@ export type InputListComponentType = { }; export type TextAreaComponentType = { + nodeClass?: APIClassType; disabled: boolean; onChange: (value: string[] | string) => void; value: string; From 880fa5034f1ef2f658888c709c0b6c42aab67531 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Jun 2023 18:23:12 -0300 Subject: [PATCH 17/18] =?UTF-8?q?=F0=9F=9A=80=20feat(api):=20add=20support?= =?UTF-8?q?=20for=20frontend=5Fnode=20in=20validate=5Fprompt=20endpoint=20?= =?UTF-8?q?=E2=9C=A8=20feat(base.py):=20add=20frontend=5Fnode=20parameter?= =?UTF-8?q?=20to=20ValidatePromptRequest=20and=20PromptValidationResponse?= =?UTF-8?q?=20models=20The=20validate=5Fprompt=20endpoint=20now=20accepts?= =?UTF-8?q?=20a=20frontend=5Fnode=20parameter=20in=20the=20ValidatePromptR?= =?UTF-8?q?equest=20model.=20This=20parameter=20is=20used=20to=20add=20inp?= =?UTF-8?q?ut=20variables=20to=20the=20frontend=5Fnode's=20template=20fiel?= =?UTF-8?q?ds=20and=20custom=20fields.=20The=20PromptValidationResponse=20?= =?UTF-8?q?model=20now=20includes=20the=20frontend=5Fnode=20parameter=20to?= =?UTF-8?q?=20return=20the=20updated=20frontend=5Fnode=20object.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/base.py | 7 +++++-- src/backend/langflow/api/v1/validate.py | 22 +++++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/backend/langflow/api/v1/base.py b/src/backend/langflow/api/v1/base.py index d595210bb..1a4936a2f 100644 --- a/src/backend/langflow/api/v1/base.py +++ b/src/backend/langflow/api/v1/base.py @@ -1,3 +1,4 @@ +from langflow.template.frontend_node.base import FrontendNode from pydantic import BaseModel, validator from langflow.interface.utils import extract_input_variables_from_prompt @@ -12,8 +13,9 @@ class Code(BaseModel): code: str -class Prompt(BaseModel): +class ValidatePromptRequest(BaseModel): template: str + frontend_node: FrontendNode # Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}} @@ -32,6 +34,7 @@ class CodeValidationResponse(BaseModel): class PromptValidationResponse(BaseModel): input_variables: list + frontend_node: FrontendNode INVALID_CHARACTERS = { @@ -66,7 +69,7 @@ def validate_prompt(template: str): # if len(input_variables) > 1: # # If there's more than one input variable - return PromptValidationResponse(input_variables=input_variables) + return input_variables def check_input_variables(input_variables: list): diff --git a/src/backend/langflow/api/v1/validate.py b/src/backend/langflow/api/v1/validate.py index 959273a00..3046ad9ba 100644 --- a/src/backend/langflow/api/v1/validate.py +++ b/src/backend/langflow/api/v1/validate.py @@ -3,10 +3,11 @@ from fastapi import APIRouter, HTTPException from langflow.api.v1.base import ( Code, CodeValidationResponse, - Prompt, + ValidatePromptRequest, PromptValidationResponse, validate_prompt, ) +from langflow.template.field.base import TemplateField from langflow.utils.logger import logger from langflow.utils.validate import validate_code @@ -27,9 +28,24 @@ def post_validate_code(code: Code): @router.post("/prompt", status_code=200, response_model=PromptValidationResponse) -def post_validate_prompt(prompt: Prompt): +def post_validate_prompt(prompt: ValidatePromptRequest): try: - return validate_prompt(prompt.template) + input_variables = validate_prompt(prompt.template) + for variable in input_variables: + try: + template_field = TemplateField( + name=variable, field_type="str", show=True, advanced=False + ) + prompt.frontend_node.template.fields.append(template_field) + prompt.frontend_node.custom_fields.append(variable) + except Exception as exc: + logger.exception(exc) + raise HTTPException(status_code=500, detail=str(exc)) from exc + + return PromptValidationResponse( + input_variables=input_variables, + frontend_node=prompt.frontend_node, + ) except Exception as e: logger.exception(e) raise HTTPException(status_code=500, detail=str(e)) from e From 38404e12436f5ac2b2acb3c4346c67e51b053494 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 27 Jun 2023 18:23:38 -0300 Subject: [PATCH 18/18] =?UTF-8?q?=F0=9F=9A=80=20feat(frontend=5Fnode):=20a?= =?UTF-8?q?dd=20custom=5Ffields=20attribute=20to=20FrontendNode=20class=20?= =?UTF-8?q?The=20FrontendNode=20class=20now=20has=20a=20new=20attribute=20?= =?UTF-8?q?called=20custom=5Ffields,=20which=20is=20a=20list=20of=20string?= =?UTF-8?q?s.=20This=20attribute=20can=20be=20used=20to=20store=20any=20cu?= =?UTF-8?q?stom=20fields=20that=20are=20specific=20to=20a=20particular=20f?= =?UTF-8?q?rontend=20node.=20This=20change=20allows=20for=20more=20flexibi?= =?UTF-8?q?lity=20in=20the=20data=20that=20can=20be=20stored=20in=20a=20Fr?= =?UTF-8?q?ontendNode=20object.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/template/frontend_node/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/langflow/template/frontend_node/base.py b/src/backend/langflow/template/frontend_node/base.py index 4801da086..2663164e0 100644 --- a/src/backend/langflow/template/frontend_node/base.py +++ b/src/backend/langflow/template/frontend_node/base.py @@ -15,6 +15,7 @@ class FrontendNode(BaseModel): base_classes: List[str] name: str = "" display_name: str = "" + custom_fields: List[str] = [] def to_dict(self) -> dict: return { @@ -23,6 +24,7 @@ class FrontendNode(BaseModel): "description": self.description, "base_classes": self.base_classes, "display_name": self.display_name or self.name, + "custom_fields": self.custom_fields, }, }