diff --git a/src/backend/langflow/api/v1/base.py b/src/backend/langflow/api/v1/base.py index 7d91cab01..28be40ae0 100644 --- a/src/backend/langflow/api/v1/base.py +++ b/src/backend/langflow/api/v1/base.py @@ -71,30 +71,68 @@ def validate_prompt(template: str): except Exception as exc: raise ValueError(str(exc)) from exc - # if len(input_variables) > 1: - # # If there's more than one input variable - return input_variables def check_input_variables(input_variables: list): invalid_chars = [] fixed_variables = [] + wrong_variables = [] + empty_variables = [] for variable in input_variables: new_var = variable - for char in INVALID_CHARACTERS: - if char in variable: - invalid_chars.append(char) - new_var = new_var.replace(char, "") + + # if variable is empty, then we should add that to the wrong variables + if not variable: + empty_variables.append(variable) + continue + + # if variable starts with a number we should add that to the invalid chars + # and wrong variables + if variable[0].isdigit(): + invalid_chars.append(variable[0]) + new_var = new_var.replace(variable[0], "") + wrong_variables.append(variable) + else: + for char in INVALID_CHARACTERS: + if char in variable: + invalid_chars.append(char) + new_var = new_var.replace(char, "") + wrong_variables.append(variable) fixed_variables.append(new_var) - if new_var != variable: - input_variables.remove(variable) - input_variables.append(new_var) # If any of the input_variables is not in the fixed_variables, then it means that # there are invalid characters in the input_variables - if any(var not in fixed_variables for var in input_variables): - raise ValueError( - f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead." - ) + if any(var not in fixed_variables for var in input_variables): + error_message = build_error_message( + input_variables, + invalid_chars, + wrong_variables, + fixed_variables, + empty_variables, + ) + raise ValueError(error_message) return input_variables + + +def build_error_message( + input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables +): + input_variables_str = ", ".join([f"'{var}'" for var in input_variables]) + error_string = f"Invalid input variables: {input_variables_str}. " + + if wrong_variables and invalid_chars: + # fix the wrong variables replacing invalid chars and find them in the fixed variables + error_string_vars = "You can fix them by replacing the invalid characters: " + wvars = wrong_variables.copy() + for i, wrong_var in enumerate(wvars): + for char in invalid_chars: + wrong_var = wrong_var.replace(char, "") + if wrong_var in fixed_variables: + error_string_vars += f"'{wrong_variables[i]}' -> '{wrong_var}'" + error_string += error_string_vars + elif empty_variables: + error_string += f" There are {len(empty_variables)} empty variable{'s' if len(empty_variables) > 1 else ''}." + elif len(set(fixed_variables)) != len(fixed_variables): + error_string += "There are duplicate variables." + return error_string diff --git a/src/backend/langflow/api/v1/callback.py b/src/backend/langflow/api/v1/callback.py index b58393d7b..03f76543e 100644 --- a/src/backend/langflow/api/v1/callback.py +++ b/src/backend/langflow/api/v1/callback.py @@ -1,22 +1,107 @@ import asyncio -from typing import Any from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langflow.api.v1.schemas import ChatResponse +from typing import Any, Dict, List, Union +from fastapi import WebSocket + + +from langchain.schema import AgentAction, LLMResult, AgentFinish + + # https://github.com/hwchase17/chat-langchain/blob/master/callback.py class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): """Callback handler for streaming LLM responses.""" - def __init__(self, websocket): + def __init__(self, websocket: WebSocket): self.websocket = websocket async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: resp = ChatResponse(message=token, type="stream", intermediate_steps="") await self.websocket.send_json(resp.dict()) + async def on_llm_start( + self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any + ) -> Any: + """Run when LLM starts running.""" + + async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any: + """Run when LLM ends running.""" + + async def on_llm_error( + self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any + ) -> Any: + """Run when LLM errors.""" + + async def on_chain_start( + self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any + ) -> Any: + """Run when chain starts running.""" + + async def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any: + """Run when chain ends running.""" + + async def on_chain_error( + self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any + ) -> Any: + """Run when chain errors.""" + + async def on_tool_start( + self, serialized: Dict[str, Any], input_str: str, **kwargs: Any + ) -> Any: + """Run when tool starts running.""" + resp = ChatResponse( + message="", + type="stream", + intermediate_steps=f"Tool input: {input_str}", + ) + await self.websocket.send_json(resp.dict()) + + async def on_tool_end(self, output: str, **kwargs: Any) -> Any: + """Run when tool ends running.""" + resp = ChatResponse( + message="", + type="stream", + intermediate_steps=f"Tool output: {output}", + ) + await self.websocket.send_json(resp.dict()) + + async def on_tool_error( + self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any + ) -> Any: + """Run when tool errors.""" + + async def on_text(self, text: str, **kwargs: Any) -> Any: + """Run on arbitrary text.""" + # This runs when first sending the prompt + # to the LLM, adding it will send the final prompt + # to the frontend + + async def on_agent_action(self, action: AgentAction, **kwargs: Any): + log = f"Thought: {action.log}" + # if there are line breaks, split them and send them + # as separate messages + if "\n" in log: + logs = log.split("\n") + for log in logs: + resp = ChatResponse(message="", type="stream", intermediate_steps=log) + await self.websocket.send_json(resp.dict()) + else: + resp = ChatResponse(message="", type="stream", intermediate_steps=log) + await self.websocket.send_json(resp.dict()) + + async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any: + """Run on agent end.""" + resp = ChatResponse( + message="", + type="stream", + intermediate_steps=finish.log, + ) + await self.websocket.send_json(resp.dict()) + class StreamingLLMCallbackHandler(BaseCallbackHandler): """Callback handler for streaming LLM responses.""" diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 2423d1431..164dd2dd5 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -160,7 +160,13 @@ async def stream_build(flow_id: str): input_keys_response = build_input_keys_response( langchain_object, artifacts ) - yield str(StreamData(event="message", data=input_keys_response)) + else: + input_keys_response = { + "input_keys": {}, + "memory_keys": [], + "handle_keys": [], + } + yield str(StreamData(event="message", data=input_keys_response)) chat_manager.set_cache(flow_id, langchain_object) # We need to reset the chat history diff --git a/src/backend/langflow/api/v1/validate.py b/src/backend/langflow/api/v1/validate.py index aa3748785..2a5bdd673 100644 --- a/src/backend/langflow/api/v1/validate.py +++ b/src/backend/langflow/api/v1/validate.py @@ -73,12 +73,14 @@ def add_new_variables_to_template(input_variables, prompt_request): advanced=False, multiline=True, input_types=["Document", "BaseOutputParser"], + value="", # Set the value to empty string ) if variable in prompt_request.frontend_node.template: # Set the new field with the old value template_field.value = prompt_request.frontend_node.template[variable][ "value" ] + prompt_request.frontend_node.template[variable] = template_field.to_dict() # Check if variable is not already in the list before appending diff --git a/src/backend/langflow/chat/utils.py b/src/backend/langflow/chat/utils.py index 5047438f2..d070a7457 100644 --- a/src/backend/langflow/chat/utils.py +++ b/src/backend/langflow/chat/utils.py @@ -23,7 +23,7 @@ async def process_graph( try: logger.debug("Generating result and thought") result, intermediate_steps = await get_result_and_steps( - langchain_object, chat_inputs.message or "", websocket=websocket + langchain_object, chat_inputs.message, websocket=websocket ) logger.debug("Generated result and intermediate_steps") return result, intermediate_steps diff --git a/src/backend/langflow/config.yaml b/src/backend/langflow/config.yaml index 5d3f69dc1..63b6c0249 100644 --- a/src/backend/langflow/config.yaml +++ b/src/backend/langflow/config.yaml @@ -164,8 +164,6 @@ memories: prompts: PromptTemplate: documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/" - ZeroShotPrompt: - documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent" textsplitters: CharacterTextSplitter: documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter" diff --git a/src/backend/langflow/custom/customs.py b/src/backend/langflow/custom/customs.py index 1c51ae32f..62b59e108 100644 --- a/src/backend/langflow/custom/customs.py +++ b/src/backend/langflow/custom/customs.py @@ -2,9 +2,9 @@ from langflow.template import frontend_node # These should always be instantiated CUSTOM_NODES = { - "prompts": { - "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(), - }, + # "prompts": { + # "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(), + # }, "tools": { "PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(), "PythonFunction": frontend_node.tools.PythonFunctionNode(), diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index 66361e48a..9a11ff6a7 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -183,6 +183,8 @@ class Vertex: # and return the instance try: + if self.base_type is None: + raise ValueError(f"Base type for node {self.vertex_type} not found") result = loading.instantiate_class( node_type=self.vertex_type, base_type=self.base_type, @@ -224,4 +226,5 @@ class Vertex: return id(self) def _built_object_repr(self): - return repr(self._built_object) + # Add a message with an emoji, stars for sucess, + return "Built sucessfully ✨" if self._built_object else "Failed to build 😵‍💫" diff --git a/src/backend/langflow/graph/vertex/types.py b/src/backend/langflow/graph/vertex/types.py index 9cb6851f8..e6a1868db 100644 --- a/src/backend/langflow/graph/vertex/types.py +++ b/src/backend/langflow/graph/vertex/types.py @@ -201,6 +201,15 @@ class PromptVertex(Vertex): self._build() return self._built_object + def _built_object_repr(self): + if self.artifacts and hasattr(self._built_object, "format"): + # We'll build the prompt with the artifacts + # to show the user what the prompt looks like + # with the variables filled in + return self._built_object.format(**self.artifacts) + else: + return super()._built_object_repr() + class OutputParserVertex(Vertex): def __init__(self, data: Dict): diff --git a/src/backend/langflow/interface/agents/base.py b/src/backend/langflow/interface/agents/base.py index d404f845d..b272144bc 100644 --- a/src/backend/langflow/interface/agents/base.py +++ b/src/backend/langflow/interface/agents/base.py @@ -6,13 +6,20 @@ from langflow.custom.customs import get_custom_nodes from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.base import LangChainTypeCreator from langflow.settings import settings +from langflow.template.frontend_node.agents import AgentFrontendNode from langflow.utils.logger import logger -from langflow.utils.util import build_template_from_class +from langflow.utils.util import build_template_from_class, build_template_from_method class AgentCreator(LangChainTypeCreator): type_name: str = "agents" + from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"} + + @property + def frontend_node_class(self) -> type[AgentFrontendNode]: + return AgentFrontendNode + @property def type_to_loader_dict(self) -> Dict: if self.type_dict is None: @@ -27,6 +34,13 @@ class AgentCreator(LangChainTypeCreator): try: if name in get_custom_nodes(self.type_name).keys(): return get_custom_nodes(self.type_name)[name] + elif name in self.from_method_nodes: + return build_template_from_method( + name, + type_to_cls_dict=self.type_to_loader_dict, + add_function=True, + method_name=self.from_method_nodes[name], + ) return build_template_from_class( name, self.type_to_loader_dict, add_function=True ) diff --git a/src/backend/langflow/interface/initialize/loading.py b/src/backend/langflow/interface/initialize/loading.py index 2c2bd3e9d..29b22c880 100644 --- a/src/backend/langflow/interface/initialize/loading.py +++ b/src/backend/langflow/interface/initialize/loading.py @@ -19,6 +19,8 @@ from langflow.interface.importing.utils import ( import_by_type, ) from langflow.interface.custom_lists import CUSTOM_NODES +from langflow.interface.importing.utils import get_function, import_by_type +from langflow.interface.agents.base import agent_creator from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.chains.base import chain_creator from langflow.interface.output_parsers.base import output_parser_creator @@ -65,7 +67,7 @@ def convert_kwargs(params): def instantiate_based_on_type(class_object, base_type, node_type, params): if base_type == "agents": - return instantiate_agent(class_object, params) + return instantiate_agent(node_type, class_object, params) elif base_type == "prompts": return instantiate_prompt(node_type, class_object, params) elif base_type == "tools": @@ -122,6 +124,12 @@ def instantiate_llm(node_type, class_object, params: Dict): def instantiate_memory(node_type, class_object, params): + # process input_key and output_key to remove them if + # they are empty strings + for key in ["input_key", "output_key"]: + if key in params and not params[key]: + params.pop(key) + try: if "retriever" in params and hasattr(params["retriever"], "as_retriever"): params["retriever"] = params["retriever"].as_retriever() @@ -164,7 +172,16 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict): return class_object(**params) -def instantiate_agent(class_object: Type[agent_module.Agent], params: Dict): +def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict): + if node_type in agent_creator.from_method_nodes: + method = agent_creator.from_method_nodes[node_type] + if class_method := getattr(class_object, method, None): + agent = class_method(**params) + tools = params.get("tools", []) + return AgentExecutor.from_agent_and_tools( + agent=agent, + tools=tools, + ) return load_agent_executor(class_object, params) diff --git a/src/backend/langflow/interface/tools/base.py b/src/backend/langflow/interface/tools/base.py index d6b114e4c..027224a3a 100644 --- a/src/backend/langflow/interface/tools/base.py +++ b/src/backend/langflow/interface/tools/base.py @@ -90,7 +90,7 @@ class ToolCreator(LangChainTypeCreator): def get_signature(self, name: str) -> Optional[Dict]: """Get the signature of a tool.""" - base_classes = ["Tool"] + base_classes = ["Tool", "BaseTool"] fields = [] params = [] tool_params = {} diff --git a/src/backend/langflow/template/frontend_node/agents.py b/src/backend/langflow/template/frontend_node/agents.py index f692a7d6c..5cae6a346 100644 --- a/src/backend/langflow/template/frontend_node/agents.py +++ b/src/backend/langflow/template/frontend_node/agents.py @@ -13,6 +13,17 @@ NON_CHAT_AGENTS = { } +class AgentFrontendNode(FrontendNode): + @staticmethod + def format_field(field: TemplateField, name: Optional[str] = None) -> None: + if field.name in ["suffix", "prefix"]: + field.show = True + if field.name == "Tools" and name == "ZeroShotAgent": + # field. + field.type_name = "BaseTool" + field.is_list = True + + class SQLAgentNode(FrontendNode): name: str = "SQLAgent" template: Template = Template( diff --git a/src/backend/langflow/template/frontend_node/tools.py b/src/backend/langflow/template/frontend_node/tools.py index 07db16e3c..16e6955aa 100644 --- a/src/backend/langflow/template/frontend_node/tools.py +++ b/src/backend/langflow/template/frontend_node/tools.py @@ -56,7 +56,7 @@ class ToolNode(FrontendNode): ], ) description: str = "Converts a chain, agent or function into a tool." - base_classes: list[str] = ["Tool"] + base_classes: list[str] = ["Tool", "BaseTool"] def to_dict(self): return super().to_dict() diff --git a/src/backend/langflow/utils/util.py b/src/backend/langflow/utils/util.py index 4769563bd..c5db6052e 100644 --- a/src/backend/langflow/utils/util.py +++ b/src/backend/langflow/utils/util.py @@ -243,7 +243,11 @@ def format_dict(d, name: Optional[str] = None): # Check for list type if "List" in _type or "Sequence" in _type or "Set" in _type: - _type = _type.replace("List[", "")[:-1] + _type = ( + _type.replace("List[", "") + .replace("Sequence[", "") + .replace("Set[", "")[:-1] + ) value["list"] = True else: value["list"] = False diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index acf9a1865..1bd962f0a 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -37,7 +37,7 @@ "base64-js": "^1.5.1", "class-variance-authority": "^0.6.0", "clsx": "^1.2.1", - "dompurify": "^3.0.3", + "dompurify": "^3.0.4", "esbuild": "^0.17.18", "lodash": "^4.17.21", "lucide-react": "^0.233.0", @@ -5142,9 +5142,9 @@ } }, "node_modules/dompurify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz", - "integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.4.tgz", + "integrity": "sha512-ae0mA+Qiqp6C29pqZX3fQgK+F91+F7wobM/v8DRzDqJdZJELXiFUx4PP4pK/mzUS0xkiSEx3Ncd9gr69jg3YsQ==" }, "node_modules/electron-to-chromium": { "version": "1.4.440", diff --git a/src/frontend/package.json b/src/frontend/package.json index b856cd5b6..aab383997 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -32,7 +32,7 @@ "base64-js": "^1.5.1", "class-variance-authority": "^0.6.0", "clsx": "^1.2.1", - "dompurify": "^3.0.3", + "dompurify": "^3.0.4", "esbuild": "^0.17.18", "lodash": "^4.17.21", "lucide-react": "^0.233.0", diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index da0caa62c..a9cf13d51 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -102,7 +102,7 @@ export default function ParameterComponent({ refHtml.current = groupedObj.map((item, i) => { const Icon: any = nodeIconsLucide[item.family]; - + return ( {" "} - {item.type == "" ? '' : ' - '} + {item.type == "" ? "" : " - "} {item.type.split(", ").length > 2 ? item.type.split(", ").map((el, i) => ( diff --git a/src/frontend/src/components/ExtraSidebarComponent/index.tsx b/src/frontend/src/components/ExtraSidebarComponent/index.tsx index 7efef4ec0..5273b7147 100644 --- a/src/frontend/src/components/ExtraSidebarComponent/index.tsx +++ b/src/frontend/src/components/ExtraSidebarComponent/index.tsx @@ -42,7 +42,7 @@ export default function ExtraSidebar() { item.href.split("/")[2] === current[4] ? "text-ring" : "text-ring group-hover:text-accent-foreground", - "mr-3 flex-shrink-0 h-6 w-6" + "mr-3 h-6 w-6 flex-shrink-0" )} /> {item.name} @@ -71,8 +71,8 @@ export default function ExtraSidebar() { {item.name} {onDelete && ( - + - - - - Are you sure absolutely sure? - - This action cannot be undone. Are you sure you want to permanently - delete this file from our servers? - - - - - - - + + + + + + Are you sure absolutely sure? + + This action cannot be undone. Are you sure you want to + permanently delete this file from our servers? + + + + + + + )} diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 0482262eb..8b7b1ca0e 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -8,7 +8,6 @@ import { useSSE } from "../../../contexts/SSEContext"; import { typesContext } from "../../../contexts/typesContext"; import { alertContext } from "../../../contexts/alertContext"; import { postBuildInit } from "../../../controllers/API"; -import ShadTooltip from "../../ShadTooltipComponent"; import RadialProgressComponent from "../../RadialProgress"; import { TabsContext } from "../../../contexts/tabsContext"; @@ -96,10 +95,9 @@ export default function BuildTrigger({ [flowId]: { ...old[flowId], formKeysData: parsedData, - - } + }, }; - }) + }); } else { // Otherwise, process the data const isValid = processStreamResult(parsedData); @@ -168,7 +166,7 @@ export default function BuildTrigger({ leaveFrom="translate-y-0" leaveTo="translate-y-96" > -
+
{ @@ -189,7 +187,10 @@ export default function BuildTrigger({ ) : isBuilding ? ( ) : ( - + )}
diff --git a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx index 116b03916..14fe4f73d 100644 --- a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx @@ -3,18 +3,30 @@ import { MessagesSquare } from "lucide-react"; import { alertContext } from "../../../contexts/alertContext"; import { useContext } from "react"; -import ShadTooltip from "../../ShadTooltipComponent"; +import { + CHAT_CANNOT_OPEN_DESCRIPTION, + CHAT_CANNOT_OPEN_TITLE, + FLOW_NOT_BUILT_DESCRIPTION, + FLOW_NOT_BUILT_TITLE, +} from "../../../constants"; -export default function ChatTrigger({ open, setOpen, isBuilt }) { +export default function ChatTrigger({ open, setOpen, isBuilt, canOpen }) { const { setErrorData } = useContext(alertContext); function handleClick() { if (isBuilt) { - setOpen(true); + if (canOpen) { + setOpen(true); + } else { + setErrorData({ + title: CHAT_CANNOT_OPEN_TITLE, + list: [CHAT_CANNOT_OPEN_DESCRIPTION], + }); + } } else { setErrorData({ - title: "Flow not built", - list: ["Please build the flow before chatting"], + title: FLOW_NOT_BUILT_TITLE, + list: [FLOW_NOT_BUILT_DESCRIPTION], }); } } @@ -30,15 +42,26 @@ export default function ChatTrigger({ open, setOpen, isBuilt }) { leaveFrom="translate-y-0" leaveTo="translate-y-96" > - + ); } diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 3c01e9c8b..9130be694 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -13,6 +13,7 @@ import * as _ from "lodash"; export default function Chat({ flow }: ChatType) { const [open, setOpen] = useState(false); const [isBuilt, setIsBuilt] = useState(false); + const [canOpen, setCanOpen] = useState(false); const { tabsState } = useContext(TabsContext); useEffect(() => { @@ -58,6 +59,17 @@ export default function Chat({ flow }: ChatType) { ) { setIsBuilt(false); } + if ( + tabsState && + tabsState[flow.id] && + tabsState[flow.id].formKeysData && + tabsState[flow.id].formKeysData.input_keys && + Object.keys(tabsState[flow.id].formKeysData.input_keys).length > 0 + ) { + setCanOpen(true); + } else { + setCanOpen(false); + } prevNodesRef.current = currentNodes; }, [tabsState]); @@ -71,10 +83,15 @@ export default function Chat({ flow }: ChatType) { setIsBuilt={setIsBuilt} isBuilt={isBuilt} /> - {isBuilt && ( + {isBuilt && canOpen && ( )} - +
); diff --git a/src/frontend/src/components/codeAreaComponent/index.tsx b/src/frontend/src/components/codeAreaComponent/index.tsx index 83861dbd0..a4583f565 100644 --- a/src/frontend/src/components/codeAreaComponent/index.tsx +++ b/src/frontend/src/components/codeAreaComponent/index.tsx @@ -1,9 +1,7 @@ import { useContext, useEffect, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; import CodeAreaModal from "../../modals/codeAreaModal/v2"; -import TextAreaModal from "../../modals/textAreaModal"; -import { CodeAreaComponentType, TextAreaComponentType } from "../../types/components"; - +import { CodeAreaComponentType } from "../../types/components"; import { ExternalLink } from "lucide-react"; @@ -54,7 +52,8 @@ export default function CodeAreaComponent({ className={ editNode ? "input-edit-node input-dialog " - : "input-primary input-dialog " + (disabled ? "input-disable" : "") + : "input-dialog input-primary " + + (disabled ? "input-disable" : "") } > {myValue !== "" ? myValue : "Type something..."} @@ -63,9 +62,9 @@ export default function CodeAreaComponent({ onClick={() => { openPopUp( { @@ -77,7 +76,10 @@ export default function CodeAreaComponent({ }} > {!editNode && ( - + )}
diff --git a/src/frontend/src/components/dropdownComponent/index.tsx b/src/frontend/src/components/dropdownComponent/index.tsx index 67447a21f..b275f83f7 100644 --- a/src/frontend/src/components/dropdownComponent/index.tsx +++ b/src/frontend/src/components/dropdownComponent/index.tsx @@ -39,8 +39,8 @@ export default function Dropdown({ diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 342054b10..f60f65f61 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -75,7 +75,7 @@ export default function Header() { href="https://github.com/logspace-ai/langflow" target="_blank" rel="noreferrer" - className="inline-flex shadow-sm items-center justify-center text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-muted-foreground border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md" + className="inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 text-sm font-medium text-muted-foreground shadow-sm ring-offset-background hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" > Star @@ -114,7 +114,7 @@ export default function Header() { )} + )} + + ); diff --git a/src/frontend/src/components/textAreaComponent/index.tsx b/src/frontend/src/components/textAreaComponent/index.tsx index 8773c47b2..c62af4b30 100644 --- a/src/frontend/src/components/textAreaComponent/index.tsx +++ b/src/frontend/src/components/textAreaComponent/index.tsx @@ -27,12 +27,11 @@ export default function TextAreaComponent({ }, [closePopUp]); return ( -
+
{myValue !== "" ? myValue : "Type something..."} +
diff --git a/src/frontend/src/components/ui/accordion.tsx b/src/frontend/src/components/ui/accordion.tsx index 684b257fc..5e3e76f33 100644 --- a/src/frontend/src/components/ui/accordion.tsx +++ b/src/frontend/src/components/ui/accordion.tsx @@ -27,7 +27,7 @@ const AccordionTrigger = React.forwardRef< svg]:rotate-180", + "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180", className )} {...props} diff --git a/src/frontend/src/components/ui/badge.tsx b/src/frontend/src/components/ui/badge.tsx index 1516bf273..36c44ac4d 100644 --- a/src/frontend/src/components/ui/badge.tsx +++ b/src/frontend/src/components/ui/badge.tsx @@ -9,8 +9,7 @@ const badgeVariants = cva( variant: { default: "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground", - gray: - "bg-border hover:bg-border/80 text-secondary-foreground", + gray: "bg-border hover:bg-border/80 text-secondary-foreground", secondary: "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", destructive: @@ -21,7 +20,7 @@ const badgeVariants = cva( sm: "h-4 text-xs", md: "h-5 text-sm", lg: "h-6 text-base", - } + }, }, defaultVariants: { variant: "default", @@ -35,7 +34,10 @@ export interface BadgeProps function Badge({ className, variant, size, ...props }: BadgeProps) { return ( -
+
); } diff --git a/src/frontend/src/components/ui/dialog.tsx b/src/frontend/src/components/ui/dialog.tsx index f17576064..a02379087 100644 --- a/src/frontend/src/components/ui/dialog.tsx +++ b/src/frontend/src/components/ui/dialog.tsx @@ -44,7 +44,7 @@ const DialogContent = React.forwardRef< diff --git a/src/frontend/src/constants.tsx b/src/frontend/src/constants.tsx index b26789b06..56adef9de 100644 --- a/src/frontend/src/constants.tsx +++ b/src/frontend/src/constants.tsx @@ -50,6 +50,15 @@ export const CODE_PROMPT_DIALOG_SUBTITLE = export const PROMPT_DIALOG_SUBTITLE = "Create your prompt. Prompts can help guide the behavior of a Language Model."; +export const CHAT_CANNOT_OPEN_TITLE = "Chat Cannot Open"; + +export const CHAT_CANNOT_OPEN_DESCRIPTION = "This is not a chat flow."; + +export const FLOW_NOT_BUILT_TITLE = "Flow not built"; + +export const FLOW_NOT_BUILT_DESCRIPTION = + "Please build the flow before chatting."; + /** * The base text for subtitle of Text Dialog * @constant @@ -180,7 +189,7 @@ export const EXPORT_CODE_DIALOG = export const COLUMN_DIV_STYLE = " w-full h-full flex overflow-auto flex-col bg-muted px-16 "; - export const NAV_DISPLAY_STYLE = +export const NAV_DISPLAY_STYLE = " w-full flex justify-between py-12 pb-2 px-6 "; /** @@ -210,7 +219,8 @@ export const DESCRIPTIONS: string[] = [ "Conversational Cartography Unlocked.", "Design, Develop, Dialogize.", ]; -export const BUTTON_DIV_STYLE = " flex gap-2 focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none "; +export const BUTTON_DIV_STYLE = + " flex gap-2 focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none "; /** * The base text for subtitle of code dialog @@ -513,3 +523,10 @@ export const NOUNS: string[] = [ * */ export const USER_PROJECTS_HEADER = "My Collection"; +/** + * CSS for highlight HTML + * @constant + * + */ +export const HIGHLIGH_CSS = + "block pl-3 pr-14 py-2 w-full h-full text-sm outline-0 border-0 break-all"; diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index bd1269a9f..2de15ca68 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -285,7 +285,11 @@ export function TabsProvider({ children }: { children: ReactNode }) { // create a link element and set its properties const link = document.createElement("a"); link.href = jsonString; - link.download = `${flowName && flowName != "" ? flowName : flows.find((f) => f.id === tabId).name}.json`; + link.download = `${ + flowName && flowName != "" + ? flowName + : flows.find((f) => f.id === tabId).name + }.json`; // simulate a click on the link element to trigger the download link.click(); diff --git a/src/frontend/src/modals/ApiModal/index.tsx b/src/frontend/src/modals/ApiModal/index.tsx index d069763ff..b37f6fe40 100644 --- a/src/frontend/src/modals/ApiModal/index.tsx +++ b/src/frontend/src/modals/ApiModal/index.tsx @@ -104,11 +104,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) { useEffect(() => { if (closeEdit !== "") { tweak.current = getTweak; - if(tweak.current.length > 0){ + if (tweak.current.length > 0) { setActiveTab("3"); openAccordions(); - } - else{ + } else { startTweaks(); } } else { @@ -250,25 +249,25 @@ export default function ApiModal({ flow }: { flow: FlowType }) { function openAccordions() { let accordionsToOpen = []; - tweak.current.forEach((el) => { - Object.keys(el).forEach((key) => { - if (Object.keys(el[key]).length > 0) { - accordionsToOpen.push(key); - setOpenAccordion(accordionsToOpen); - } - }); + tweak.current.forEach((el) => { + Object.keys(el).forEach((key) => { + if (Object.keys(el[key]).length > 0) { + accordionsToOpen.push(key); + setOpenAccordion(accordionsToOpen); + } }); + }); } return ( - + Code @@ -277,7 +276,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) { { setActiveTab(value); if (value === "3") { @@ -296,7 +295,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) { {Number(activeTab) < 3 && (