diff --git a/poetry.lock b/poetry.lock index b989234e1d15..46aa8b702db3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2212,6 +2212,18 @@ dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2 doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +[[package]] +name = "types-pyyaml" +version = "6.0.12.8" +description = "Typing stubs for PyYAML" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-6.0.12.8.tar.gz", hash = "sha256:19304869a89d49af00be681e7b267414df213f4eb89634c4495fa62e8f942b9f"}, + {file = "types_PyYAML-6.0.12.8-py3-none-any.whl", hash = "sha256:5314a4b2580999b2ea06b2e5f9a7763d860d6e09cdf21c0e9561daa9cbd60178"}, +] + [[package]] name = "typing-extensions" version = "4.5.0" @@ -2407,4 +2419,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "ebc0a8ca9ea284d8e986306a10f13ff91e7e8aae18341c83329f5b1e1bcc66bd" +content-hash = "9acd2b7396be651321ac517873a398d1631a76918fefdb003f7f587f031d9ba1" diff --git a/pyproject.toml b/pyproject.toml index 615caf12dc98..2d2d3e4d690c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,21 @@ [tool.poetry] name = "langflow" -version = "0.0.45" +version = "0.0.46" description = "A Python package with a built-in web application" authors = ["Logspace "] -packages = [ - { include = "langflow", from = "src/backend" }, +maintainers = [ + "Gabriel Almeida ", + "Ibis Prevedello ", + "Lucas Eduoli ", + "Otávio Anovazzi ", ] -include = ["src/backend/langflow/*", "src/backend/langflow/**/*"] +repository = "https://github.com/logspace-ai/langflow" license = "MIT" readme = "README.md" +keywords = ["nlp", "langchain", "openai", "gpt", "gui"] +packages = [{ include = "langflow", from = "src/backend" }] +include = ["src/backend/langflow/*", "src/backend/langflow/**/*"] + [tool.poetry.scripts] langflow = "langflow.__main__:main" @@ -24,6 +31,7 @@ typer = "^0.7.0" gunicorn = "^20.1.0" langchain = "^0.0.113" openai = "^0.27.2" +types-pyyaml = "^6.0.12.8" [tool.poetry.group.dev.dependencies] black = "^23.1.0" diff --git a/src/backend/langflow/__main__.py b/src/backend/langflow/__main__.py index 6f848dca640a..ea9386d7f69f 100644 --- a/src/backend/langflow/__main__.py +++ b/src/backend/langflow/__main__.py @@ -1,13 +1,12 @@ +import logging import multiprocessing import platform -import re - -from langflow.main import create_app +from pathlib import Path import typer from fastapi.staticfiles import StaticFiles -from pathlib import Path -import logging + +from langflow.main import create_app logger = logging.getLogger(__name__) diff --git a/src/backend/langflow/api/endpoints.py b/src/backend/langflow/api/endpoints.py index 68238013cd2e..7214ec6a680d 100644 --- a/src/backend/langflow/api/endpoints.py +++ b/src/backend/langflow/api/endpoints.py @@ -1,8 +1,9 @@ -from fastapi import APIRouter, HTTPException -from langflow.interface.types import build_langchain_types_dict -from langflow.interface.run import process_data_graph from typing import Any, Dict +from fastapi import APIRouter, HTTPException + +from langflow.interface.run import process_data_graph +from langflow.interface.types import build_langchain_types_dict # build router router = APIRouter() diff --git a/src/backend/langflow/config.yaml b/src/backend/langflow/config.yaml new file mode 100644 index 000000000000..9b03aaeb1d39 --- /dev/null +++ b/src/backend/langflow/config.yaml @@ -0,0 +1,27 @@ +chains: + - LLMChain + - LLMMathChain + - LLMChecker + # - ConversationChain + +agents: + - ZeroShotAgent + +prompts: + - PromptTemplate + - FewShotPromptTemplate + +llms: + - OpenAI + - OpenAIChat + +tools: + - Search + - PAL-MATH + - Calculator + - Serper Search + +memories: + # - ConversationBufferMemory + +dev: false diff --git a/src/backend/langflow/interface/custom_lists.py b/src/backend/langflow/interface/custom_lists.py index 10f0e9e535c5..3e8a800f9f5f 100644 --- a/src/backend/langflow/interface/custom_lists.py +++ b/src/backend/langflow/interface/custom_lists.py @@ -1,6 +1,43 @@ +## LLM +from typing import Any + from langchain import llms from langchain.llms.openai import OpenAIChat - llm_type_to_cls_dict = llms.type_to_cls_dict llm_type_to_cls_dict["openai-chat"] = OpenAIChat + + +## Memory + +# from langchain.memory.buffer_window import ConversationBufferWindowMemory +# from langchain.memory.chat_memory import ChatMessageHistory +# from langchain.memory.combined import CombinedMemory +# from langchain.memory.entity import ConversationEntityMemory +# from langchain.memory.kg import ConversationKGMemory +# from langchain.memory.readonly import ReadOnlySharedMemory +# from langchain.memory.simple import SimpleMemory +# from langchain.memory.summary import ConversationSummaryMemory +# from langchain.memory.summary_buffer import ConversationSummaryBufferMemory + +memory_type_to_cls_dict: dict[str, Any] = { + # "CombinedMemory": CombinedMemory, + # "ConversationBufferWindowMemory": ConversationBufferWindowMemory, + # "ConversationBufferMemory": ConversationBufferMemory, + # "SimpleMemory": SimpleMemory, + # "ConversationSummaryBufferMemory": ConversationSummaryBufferMemory, + # "ConversationKGMemory": ConversationKGMemory, + # "ConversationEntityMemory": ConversationEntityMemory, + # "ConversationSummaryMemory": ConversationSummaryMemory, + # "ChatMessageHistory": ChatMessageHistory, + # "ConversationStringBufferMemory": ConversationStringBufferMemory, + # "ReadOnlySharedMemory": ReadOnlySharedMemory, +} + + +## Chain +# from langchain.chains.loading import type_to_loader_dict +# from langchain.chains.conversation.base import ConversationChain + +# chain_type_to_cls_dict = type_to_loader_dict +# chain_type_to_cls_dict["conversation_chain"] = ConversationChain diff --git a/src/backend/langflow/interface/listing.py b/src/backend/langflow/interface/listing.py index 1df2d25e7e48..21e763de42fd 100644 --- a/src/backend/langflow/interface/listing.py +++ b/src/backend/langflow/interface/listing.py @@ -1,9 +1,13 @@ -from langchain import chains, agents, prompts -from langflow.interface.custom_lists import llm_type_to_cls_dict -from langflow.custom import customs -from langflow.utils import util, allowed_components +from langchain import agents, chains, prompts from langchain.agents.load_tools import get_all_tool_names -from langchain.chains.conversation import memory as memories + +from langflow.custom import customs +from langflow.interface.custom_lists import ( + llm_type_to_cls_dict, + memory_type_to_cls_dict, +) +from langflow.settings import settings +from langflow.utils import util def list_type(object_type: str): @@ -13,18 +17,17 @@ def list_type(object_type: str): "agents": list_agents, "prompts": list_prompts, "llms": list_llms, - "tools": list_tools, "memories": list_memories, + "tools": list_tools, }.get(object_type, lambda: "Invalid type")() def list_agents(): """List all agent types""" - # return list(agents.loading.AGENT_TO_CLASS.keys()) return [ agent.__name__ for agent in agents.loading.AGENT_TO_CLASS.values() - if agent.__name__ in allowed_components.AGENTS + if agent.__name__ in settings.agents or settings.dev ] @@ -34,7 +37,7 @@ def list_prompts(): library_prompts = [ prompt.__annotations__["return"].__name__ for prompt in prompts.loading.type_to_loader_dict.values() - if prompt.__annotations__["return"].__name__ in allowed_components.PROMPTS + if prompt.__annotations__["return"].__name__ in settings.prompts or settings.dev ] return library_prompts + list(custom_prompts.keys()) @@ -46,7 +49,7 @@ def list_tools(): for tool in get_all_tool_names(): tool_params = util.get_tool_params(util.get_tools_dict(tool)) - if tool_params and tool_params["name"] in allowed_components.TOOLS: + if tool_params and tool_params["name"] in settings.tools or settings.dev: tools.append(tool_params["name"]) return tools @@ -57,7 +60,7 @@ def list_llms(): return [ llm.__name__ for llm in llm_type_to_cls_dict.values() - if llm.__name__ in allowed_components.LLMS + if llm.__name__ in settings.llms or settings.dev ] @@ -66,10 +69,14 @@ def list_chain_types(): return [ chain.__annotations__["return"].__name__ for chain in chains.loading.type_to_loader_dict.values() - if chain.__annotations__["return"].__name__ in allowed_components.CHAINS + if chain.__annotations__["return"].__name__ in settings.chains or settings.dev ] def list_memories(): """List all memory types""" - return [memory.__name__ for memory in memories.type_to_cls_dict.values()] + return [ + memory.__name__ + for memory in memory_type_to_cls_dict.values() + if memory.__name__ in settings.memories or settings.dev + ] diff --git a/src/backend/langflow/interface/loading.py b/src/backend/langflow/interface/loading.py index a8ffe42dfe1b..619264988091 100644 --- a/src/backend/langflow/interface/loading.py +++ b/src/backend/langflow/interface/loading.py @@ -1,22 +1,22 @@ import json from typing import Any, Dict, Optional -from langflow.interface.types import get_type_list -from langchain.agents.loading import load_agent_from_config -from langchain.chains.loading import load_chain_from_config -from langchain.llms.loading import load_llm_from_config -from langflow.utils import payload -from langflow.utils import util -from langchain.llms.base import BaseLLM from langchain.agents.agent import AgentExecutor -from langchain.callbacks.base import BaseCallbackManager -from langchain.agents.tools import Tool from langchain.agents.load_tools import ( _BASE_TOOLS, - _LLM_TOOLS, _EXTRA_LLM_TOOLS, _EXTRA_OPTIONAL_TOOLS, + _LLM_TOOLS, ) +from langchain.agents.loading import load_agent_from_config +from langchain.agents.tools import Tool +from langchain.callbacks.base import BaseCallbackManager +from langchain.chains.loading import load_chain_from_config +from langchain.llms.base import BaseLLM +from langchain.llms.loading import load_llm_from_config + +from langflow.interface.types import get_type_list +from langflow.utils import payload, util def load_flow_from_json(path: str): diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index 76813a98f2fe..0844e2073f5d 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -2,6 +2,7 @@ import io import re from typing import Any, Dict + from langflow.interface import loading diff --git a/src/backend/langflow/interface/signature.py b/src/backend/langflow/interface/signature.py index baa2956dce38..69e83fad6524 100644 --- a/src/backend/langflow/interface/signature.py +++ b/src/backend/langflow/interface/signature.py @@ -1,6 +1,6 @@ -from typing import Dict, Any # noqa: F401 +from typing import Any, Dict # noqa: F401 + from langchain import agents, chains, prompts -from langflow.interface.custom_lists import llm_type_to_cls_dict from langchain.agents.load_tools import ( _BASE_TOOLS, _EXTRA_LLM_TOOLS, @@ -9,8 +9,12 @@ get_all_tool_names, ) -from langflow.utils import util from langflow.custom import customs +from langflow.interface.custom_lists import ( + llm_type_to_cls_dict, + memory_type_to_cls_dict, +) +from langflow.utils import util def get_signature(name: str, object_type: str): @@ -20,6 +24,7 @@ def get_signature(name: str, object_type: str): "agents": get_agent_signature, "prompts": get_prompt_signature, "llms": get_llm_signature, + "memories": get_memory_signature, "tools": get_tool_signature, }.get(object_type, lambda name: f"Invalid type: {name}")(name) @@ -62,6 +67,14 @@ def get_llm_signature(name: str): raise ValueError("LLM not found") from exc +def get_memory_signature(name: str): + """Get the signature of a memory.""" + try: + return util.build_template_from_class(name, memory_type_to_cls_dict) + except ValueError as exc: + raise ValueError("Memory not found") from exc + + def get_tool_signature(name: str): """Get the signature of a tool.""" diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 424089584ac4..57f3a2578512 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -16,6 +16,7 @@ def get_type_list(): def build_langchain_types_dict(): """Build a dictionary of all langchain types""" + return { "chains": { chain: get_signature(chain, "chains") for chain in list_type("chains") @@ -27,5 +28,9 @@ def build_langchain_types_dict(): prompt: get_signature(prompt, "prompts") for prompt in list_type("prompts") }, "llms": {llm: get_signature(llm, "llms") for llm in list_type("llms")}, + "memories": { + memory: get_signature(memory, "memories") + for memory in list_type("memories") + }, "tools": {tool: get_signature(tool, "tools") for tool in list_type("tools")}, } diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 198cdd1e153a..a2a02465ec55 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -1,8 +1,9 @@ from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + from langflow.api.endpoints import router as endpoints_router from langflow.api.list_endpoints import router as list_router from langflow.api.signature import router as signatures_router -from fastapi.middleware.cors import CORSMiddleware def create_app(): diff --git a/src/backend/langflow/settings.py b/src/backend/langflow/settings.py new file mode 100644 index 000000000000..f4dd4ae30ef0 --- /dev/null +++ b/src/backend/langflow/settings.py @@ -0,0 +1,49 @@ +import os +from typing import List, Optional + +import yaml +from pydantic import BaseSettings, Field, root_validator + + +class Settings(BaseSettings): + chains: Optional[List[str]] = Field(...) + agents: Optional[List[str]] = Field(...) + prompts: Optional[List[str]] = Field(...) + llms: Optional[List[str]] = Field(...) + tools: Optional[List[str]] = Field(...) + memories: Optional[List[str]] = Field(...) + dev: bool = Field(...) + + class Config: + validate_assignment = True + + @root_validator + def validate_lists(cls, values): + for key, value in values.items(): + if key != "dev" and not value: + values[key] = [] + return values + + +def save_settings_to_yaml(settings: Settings, file_path: str): + with open(file_path, "w") as f: + settings_dict = settings.dict() + yaml.dump(settings_dict, f) + + +def load_settings_from_yaml(file_path: str) -> Settings: + # Check if a string is a valid path or a file name + if "/" not in file_path: + # Get current path + current_path = os.path.dirname(os.path.abspath(__file__)) + + file_path = os.path.join(current_path, file_path) + + with open(file_path, "r") as f: + settings_dict = yaml.safe_load(f) + a = Settings.parse_obj(settings_dict) + + return a + + +settings = load_settings_from_yaml("config.yaml") diff --git a/src/backend/langflow/utils/allowed_components.py b/src/backend/langflow/utils/allowed_components.py deleted file mode 100644 index f304a3d15a71..000000000000 --- a/src/backend/langflow/utils/allowed_components.py +++ /dev/null @@ -1,9 +0,0 @@ -CHAINS = ["LLMChain", "LLMMathChain", "LLMChecker"] - -AGENTS = ["ZeroShotAgent"] - -PROMPTS = ["PromptTemplate", "FewShotPromptTemplate"] - -LLMS = ["OpenAI", "OpenAIChat"] - -TOOLS = ["Search", "PAL-MATH", "Calculator", "Serper Search"] diff --git a/src/backend/langflow/utils/constants.py b/src/backend/langflow/utils/constants.py new file mode 100644 index 000000000000..73a50ce4017b --- /dev/null +++ b/src/backend/langflow/utils/constants.py @@ -0,0 +1,8 @@ +OPENAI_MODELS = [ + "text-davinci-003", + "text-davinci-002", + "text-curie-001", + "text-babbage-001", + "text-ada-001", +] +CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"] diff --git a/src/backend/langflow/utils/util.py b/src/backend/langflow/utils/util.py index 5d13e931dc8d..4710114ed50e 100644 --- a/src/backend/langflow/utils/util.py +++ b/src/backend/langflow/utils/util.py @@ -1,15 +1,17 @@ import ast +import importlib import inspect import re -import importlib +from typing import Dict, Optional from langchain.agents.load_tools import ( _BASE_TOOLS, - _LLM_TOOLS, _EXTRA_LLM_TOOLS, _EXTRA_OPTIONAL_TOOLS, + _LLM_TOOLS, ) -from typing import Optional, Dict + +from langflow.utils import constants def build_template_from_function(name: str, type_to_loader_dict: Dict): @@ -69,6 +71,7 @@ def build_template_from_class(name: str, type_to_cls_dict: Dict): if v.__name__ == name: _class = v + # Get the docstring docs = get_class_doc(_class) variables = {"_type": _type} @@ -190,11 +193,7 @@ def get_class_doc(class_name): A dictionary containing the extracted information, with keys for 'Description', 'Parameters', 'Attributes', and 'Returns'. """ - # Get the class docstring - docstring = class_name.__doc__ - - # Parse the docstring to extract information - lines = docstring.split("\n") + # Template data = { "Description": "", "Parameters": {}, @@ -203,6 +202,15 @@ def get_class_doc(class_name): "Returns": {}, } + # Get the class docstring + docstring = class_name.__doc__ + + if not docstring: + return data + + # Parse the docstring to extract information + lines = docstring.split("\n") + current_section = "Description" for line in lines: @@ -296,9 +304,9 @@ def format_dict(d, name: Optional[str] = None): # Add options to openai if name == "OpenAI" and key == "model_name": - value["options"] = ["text-davinci-003", "text-davinci-002"] + value["options"] = constants.OPENAI_MODELS elif name == "OpenAIChat" and key == "model_name": - value["options"] = ["gpt-3.5-turbo", "gpt-4"] + value["options"] = constants.CHAT_OPENAI_MODELS return d diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index dffaa46656a5..f0a40a452d72 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -22,9 +22,11 @@ "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", "axios": "^1.3.2", + "lodash": "^4.17.21", "react": "^18.2.0", "react-cookie": "^4.1.1", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.2", "react-icons": "^4.7.1", "react-laag": "^2.0.5", "react-router-dom": "^6.8.1", @@ -14959,6 +14961,17 @@ "react": "^18.2.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.2.tgz", + "integrity": "sha512-/h21OS80hQ1m/s5UVOp1JKkC8XmUo0rOTRUliGSmWtvswkbbijuQ074K0QLEHwxwwesTt7ksR74/9EHImqWo+A==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 01be611671f0..312e52051ecb 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -17,9 +17,11 @@ "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", "axios": "^1.3.2", + "lodash": "^4.17.21", "react": "^18.2.0", "react-cookie": "^4.1.1", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.2", "react-icons": "^4.7.1", "react-laag": "^2.0.5", "react-router-dom": "^6.8.1", @@ -54,5 +56,5 @@ "last 1 safari version" ] }, - "proxy": "http://localhost:7860" -} \ No newline at end of file + "proxy": "http://backend:7860" +} diff --git a/src/frontend/public/index.html b/src/frontend/public/index.html index 1aa066669716..57757fb21dfe 100644 --- a/src/frontend/public/index.html +++ b/src/frontend/public/index.html @@ -5,9 +5,6 @@ LangFLow - diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index f0944be3fc97..0e5f1887ddbb 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -9,6 +9,9 @@ import ExtraSidebar from "./components/ExtraSidebarComponent"; import { alertContext } from "./contexts/alertContext"; import { locationContext } from "./contexts/locationContext"; import TabsManagerComponent from "./pages/FlowPage/components/tabsManagerComponent"; +import { ErrorBoundary } from "react-error-boundary"; +import CrashErrorComponent from "./components/CrashErrorComponent"; +import { TabsContext } from "./contexts/tabsContext"; export default function App() { var _ = require("lodash"); @@ -21,7 +24,7 @@ export default function App() { setShowSideBar(true); setIsStackedOpen(true); }, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]); - + const {hardReset} = useContext(TabsContext) const { errorData, errorOpen, @@ -34,45 +37,62 @@ export default function App() { setSuccessOpen, } = useContext(alertContext); -// Initialize state variable for the list of alerts -const [alertsList, setAlertsList] = useState,link?:string},id:string}>>([]); + // Initialize state variable for the list of alerts + const [alertsList, setAlertsList] = useState< + Array<{ + type: string; + data: { title: string; list?: Array; link?: string }; + id: string; + }> + >([]); -// Use effect hook to update alertsList when a new alert is added -useEffect(() => { - // If there is an error alert open with data, add it to the alertsList - if (errorOpen && errorData) { - setErrorOpen(false); - setAlertsList((old) => { - let newAlertsList = [ - ...old, - { type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() }, - ]; - return newAlertsList; - }); - } - // If there is a notice alert open with data, add it to the alertsList - else if (noticeOpen && noticeData) { - setNoticeOpen(false); - setAlertsList((old) => { - let newAlertsList = [ - ...old, - { type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() }, - ]; - return newAlertsList; - }); - } - // If there is a success alert open with data, add it to the alertsList - else if (successOpen && successData) { - setSuccessOpen(false); - setAlertsList((old) => { - let newAlertsList = [ - ...old, - { type: "success", data: _.cloneDeep(successData), id: _.uniqueId() }, - ]; - return newAlertsList; - }); - } -}, [_, errorData, errorOpen, noticeData, noticeOpen, setErrorOpen, setNoticeOpen, setSuccessOpen, successData, successOpen]); + // Use effect hook to update alertsList when a new alert is added + useEffect(() => { + // If there is an error alert open with data, add it to the alertsList + if (errorOpen && errorData) { + setErrorOpen(false); + setAlertsList((old) => { + let newAlertsList = [ + ...old, + { type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() }, + ]; + return newAlertsList; + }); + } + // If there is a notice alert open with data, add it to the alertsList + else if (noticeOpen && noticeData) { + setNoticeOpen(false); + setAlertsList((old) => { + let newAlertsList = [ + ...old, + { type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() }, + ]; + return newAlertsList; + }); + } + // If there is a success alert open with data, add it to the alertsList + else if (successOpen && successData) { + setSuccessOpen(false); + setAlertsList((old) => { + let newAlertsList = [ + ...old, + { type: "success", data: _.cloneDeep(successData), id: _.uniqueId() }, + ]; + return newAlertsList; + }); + } + }, [ + _, + errorData, + errorOpen, + noticeData, + noticeOpen, + setErrorOpen, + setNoticeOpen, + setSuccessOpen, + successData, + successOpen, + ]); const removeAlert = (id: string) => { setAlertsList((prevAlertsList) => @@ -83,18 +103,27 @@ useEffect(() => { return ( //need parent component with width and height
-
-
-
- - {/* Main area */} -
- {/* Primary column */} -
- -
-
-
+
+ { + window.localStorage.removeItem("tabsData"); + window.localStorage.clear(); + hardReset() + window.location.href = window.location.href; + }} + FallbackComponent={CrashErrorComponent} + > +
+ + {/* Main area */} +
+ {/* Primary column */} +
+ +
+
+
+
{alertsList.map((alert) => ( @@ -126,7 +155,13 @@ useEffect(() => {
))}
- Created by Logspace + + Created by Logspace + ); } diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index acbdde47781e..ddb9524ee110 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,92 +1,121 @@ +import { TrashIcon } from "@heroicons/react/24/outline"; import { - TrashIcon, -} from "@heroicons/react/24/outline"; -import { - classNames, - nodeColors, - nodeIcons, - snakeToNormalCase, + classNames, + nodeColors, + nodeIcons, + snakeToNormalCase, } from "../../utils"; import ParameterComponent from "./components/parameterComponent"; import { typesContext } from "../../contexts/typesContext"; -import { useContext } from "react"; -import { NodeDataType} from "../../types/flow"; - -export default function GenericNode({ data, selected}:{data:NodeDataType,selected:boolean}) { - const {types, deleteNode} = useContext(typesContext); - const Icon = nodeIcons[types[data.type]]; +import { useContext, useRef } from "react"; +import { NodeDataType } from "../../types/flow"; +import { alertContext } from "../../contexts/alertContext"; +export default function GenericNode({ + data, + selected, +}: { + data: NodeDataType; + selected: boolean; +}) { + const { setErrorData } = useContext(alertContext); + const showError = useRef(true); + const { types, deleteNode } = useContext(typesContext); + const Icon = nodeIcons[types[data.type]]; + if (!Icon) { + console.log(data); + if (showError.current) { + setErrorData({ + title: data.type + ? `The ${data.type} node could not be rendered, please review your json file` + : "There was a node that can't be rendered, please review your json file", + }); + showError.current = false; + } + return; + } - return ( -
-
-
- -
{data.type}
-
- -
+ return ( +
+
+
+ +
{data.type}
+
+ +
-
-
- {data.node.description} -
+
+
+ {data.node.description} +
- <> - {Object.keys(data.node.template) - .filter((t) => t.charAt(0) !== "_") - .map((t:string, idx) => ( -
- {idx === 0 ? ( -
Inputs:
- ) : ( - <> - )} - {data.node.template[t].show ? ( - - ) : ( - <> - )} -
- ))} -
Output:
- - -
-
- ); + <> + {Object.keys(data.node.template) + .filter((t) => t.charAt(0) !== "_") + .map((t: string, idx) => ( +
+ {idx === 0 ? ( +
+ Inputs: +
+ ) : ( + <> + )} + {data.node.template[t].show ? ( + + ) : ( + <> + )} +
+ ))} +
+ Output: +
+ + +
+
+ ); } diff --git a/src/frontend/src/components/CrashErrorComponent/index.tsx b/src/frontend/src/components/CrashErrorComponent/index.tsx new file mode 100644 index 000000000000..7864e6d6587c --- /dev/null +++ b/src/frontend/src/components/CrashErrorComponent/index.tsx @@ -0,0 +1,31 @@ +export default function CrashErrorComponent({ error, resetErrorBoundary }) { + return ( +
+
+

Oops! An unknown error has occurred.

+

+ Please click the 'Reset Application' button + to restore the application's state. If the error persists, please + create an issue on our GitHub page. We apologize for any inconvenience + this may have caused. +

+
+ + + Create Issue + +
+
+
+ ); +} diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 27bf220f964c..037c9cdafbbe 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -97,7 +97,7 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) { setChatValue(""); addChatHistory(message, true); - sendAll({ ...reactFlowInstance.toObject(), message, chatHistory}) + sendAll({ ...reactFlowInstance.toObject(), message, chatHistory,name:flow.name,description:flow.description}) .then((r) => { addChatHistory(r.data.result, false, r.data.thought); setLockChat(false); diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 6bb581f449c0..310606ea5468 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -13,11 +13,13 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { + - {children} + {children} + diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 3f6cbbf0b5be..10c0b943e743 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -12,10 +12,11 @@ const TabsContextInitialValue: TabsContextType = { addFlow: (flowData?: any) => {}, updateFlow: (newFlow: FlowType) => {}, incrementNodeId: () => 0, - downloadFlow: () => {}, + downloadFlow: (flow:FlowType) => {}, uploadFlow: () => {}, lockChat: false, - setLockChat:(prevState:boolean)=>{} + setLockChat:(prevState:boolean)=>{}, + hardReset:()=>{} }; export const TabsContext = createContext( @@ -54,14 +55,18 @@ export function TabsProvider({ children }: { children: ReactNode }) { newNodeId.current = cookieObject.nodeId; } }, []); + function hardReset(){ + newNodeId.current=0; + setTabIndex(0);setFlows([]);setId(0); + } /** * Downloads the current flow as a JSON file */ - function downloadFlow() { + function downloadFlow(flow:FlowType) { // create a data URI with the current flow data const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( - JSON.stringify(flows[tabIndex]) + JSON.stringify(flow) )}`; // create a link element and set its properties @@ -71,7 +76,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { // simulate a click on the link element to trigger the download link.click(); - setNoticeData({title:"Warning: Critical data, including API keys, in JSON file. Keep secure and do not share."}) + setNoticeData({title:"Warning: Critical data,JSON file may including API keys."}) } /** @@ -128,10 +133,12 @@ export function TabsProvider({ children }: { children: ReactNode }) { function addFlow(flow?: FlowType) { // Get data from the flow or set it to null if there's no flow provided. const data = flow?.data ? flow.data : null; + const description = flow?.description?flow.description:"" // Create a new flow with a default name if no flow is provided. let newFlow: FlowType = { - name: flow ? flow.name : "New Flow " + (flows.length===0?"":flows.length), + description, + name: "New Flow", id: id.toString(), data, chat: flow ? flow.chat : [], @@ -158,6 +165,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { const newFlows = [...prevState]; const index = newFlows.findIndex((flow) => flow.id === newFlow.id); if (index !== -1) { + newFlows[index].description = newFlow.description??"" newFlows[index].data = newFlow.data; newFlows[index].name = newFlow.name; newFlows[index].chat = newFlow.chat; @@ -169,6 +177,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { return ( - - - - + + + + + + ); reportWebVitals(); diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx new file mode 100644 index 000000000000..c1da32946ed0 --- /dev/null +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -0,0 +1,174 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { + XMarkIcon, + ArrowDownTrayIcon, + DocumentDuplicateIcon, + ComputerDesktopIcon, +} from "@heroicons/react/24/outline"; +import { Fragment, useContext, useRef, useState } from "react"; +import { alertContext } from "../../contexts/alertContext"; +import { PopUpContext } from "../../contexts/popUpContext"; +import { TabsContext } from "../../contexts/tabsContext"; +import { removeApiKeys } from "../../utils"; + +export default function ExportModal() { + const [open, setOpen] = useState(true); + const { closePopUp } = useContext(PopUpContext); + const ref = useRef(); + const {setErrorData}= useContext(alertContext) + const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext); + function setModalOpen(x: boolean) { + setOpen(x); + if (x === false) { + setTimeout(() => { + closePopUp(); + }, 300); + } + } + const [checked,setChecked] = useState(true) + return ( + + + +
+ + +
+
+ + +
+ +
+
+
+
+
+
+ + Export as + +
+
+
+
+ + { + if(event.target.value!=""){ + let newFlow = flows[tabIndex]; + newFlow.name = event.target.value; + updateFlow(newFlow); + } + else{ + setErrorData({title:"Flow name can't be empty"}) + } + }} + type="text" + name="name" + value={flows[tabIndex].name ?? null} + placeholder="File name" + id="name" + className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500" + /> +
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/frontend/src/modals/importModal/buttonBox/index.tsx b/src/frontend/src/modals/importModal/buttonBox/index.tsx new file mode 100644 index 000000000000..084981adc8a0 --- /dev/null +++ b/src/frontend/src/modals/importModal/buttonBox/index.tsx @@ -0,0 +1,47 @@ +import React, { ReactNode } from "react"; +import { DocumentDuplicateIcon } from "@heroicons/react/solid"; +import { classNames } from "../../../utils"; + +export default function ButtonBox({ + onClick, + title, + description, + icon, + bgColor, + textColor, + deactivate +}: { + onClick: () => void; + title: string; + description: string; + icon: ReactNode; + bgColor: string; + textColor: string; + deactivate?:boolean; +}) { + return ( + + ); +} diff --git a/src/frontend/src/modals/importModal/index.tsx b/src/frontend/src/modals/importModal/index.tsx new file mode 100644 index 000000000000..c471f3955a29 --- /dev/null +++ b/src/frontend/src/modals/importModal/index.tsx @@ -0,0 +1,122 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { + XMarkIcon, + ArrowDownTrayIcon, + DocumentDuplicateIcon, + ComputerDesktopIcon, + ArrowUpTrayIcon, +} from "@heroicons/react/24/outline"; +import { Fragment, useContext, useRef, useState } from "react"; +import { PopUpContext } from "../../contexts/popUpContext"; +import { TabsContext } from "../../contexts/tabsContext"; +import ButtonBox from "./buttonBox"; + +export default function ImportModal() { + const [open, setOpen] = useState(true); + const { closePopUp } = useContext(PopUpContext); + const ref = useRef(); + const {uploadFlow} = useContext(TabsContext) + function setModalOpen(x: boolean) { + setOpen(x); + if (x === false) { + setTimeout(() => { + closePopUp(); + }, 300); + } + } + return ( + + + +
+ + +
+
+ + +
+ +
+
+
+
+
+
+ + Import from + +
+
+
+
+ + } + onClick={() => console.log("sdsds")} + textColor="text-slate-400" + title="Examples" + > + + } + onClick={() => {uploadFlow();setModalOpen(false)}} + textColor="text-blue-500" + title="Local file" + > +
+
+ +
+
+
+
+
+
+
+ ); +} diff --git a/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx index 17ff7de1a84b..5cddda2f87c6 100644 --- a/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx @@ -14,7 +14,7 @@ export default function DisclosureComponent({ {({ open }) => ( <>
-
+
@@ -27,15 +27,15 @@ export default function DisclosureComponent({ {x.Icon} ))} - +
- +
-
+
{children} diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index bea7393338cf..e489432decb1 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -55,7 +55,7 @@ export default function ExtraSidebar() { {Object.keys(data).map((d:keyof APIObjectType, i) => (
{Object.keys(data[d]).map((t: string, k) => ( @@ -63,7 +63,7 @@ export default function ExtraSidebar() {
onDragStart(event, { type: t, diff --git a/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx index 6396b753f725..71c964cbeb7e 100644 --- a/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx @@ -14,9 +14,12 @@ import { import { PopUpContext } from "../../../../contexts/popUpContext"; import AlertDropdown from "../../../../alerts/alertDropDown"; import { alertContext } from "../../../../contexts/alertContext"; +import ImportModal from "../../../../modals/importModal"; +import ExportModal from "../../../../modals/exportModal"; export default function TabsManagerComponent() { - const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = useContext(TabsContext); + const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = + useContext(TabsContext); const { openPopUp } = useContext(PopUpContext); const AlertWidth = 256; const { dark, setDark } = useContext(darkContext); @@ -50,10 +53,21 @@ export default function TabsManagerComponent() { flow={null} />
- -