diff --git a/docs/docs/deployment/backend-only.md b/docs/docs/deployment/backend-only.md index 9c408ad17..43e412a46 100644 --- a/docs/docs/deployment/backend-only.md +++ b/docs/docs/deployment/backend-only.md @@ -4,7 +4,7 @@ You can run Langflow in `--backend-only` mode to expose your Langflow app as an Start langflow in backend-only mode with `python3 -m langflow run --backend-only`. -The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`. +The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`. Langflow will now serve requests to its API without the frontend running. ## Prerequisites @@ -42,7 +42,7 @@ Note the flow ID of `ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef`. You can find this ID 1. Stop Langflow with Ctrl+C. 2. Start langflow in backend-only mode with `python3 -m langflow run --backend-only`. - The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`. + The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`. Langflow will now serve requests to its API. 3. Run the curl code you copied from the UI. You should get a result like this: diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py index 6e2a4dd35..0ca6b4944 100644 --- a/src/backend/base/langflow/api/v1/chat.py +++ b/src/backend/base/langflow/api/v1/chat.py @@ -168,9 +168,9 @@ async def build_vertex( next_runnable_vertices, top_level_vertices, result_dict, - log_message, + params, valid, - log_type, + artifacts, vertex, ) = await graph.build_vertex( lock=lock, @@ -180,22 +180,22 @@ async def build_vertex( inputs_dict=inputs.model_dump() if inputs else {}, files=files, ) - + log_obj = Log(message=vertex.artifacts_raw, type=vertex.artifacts_type) result_data_response = ResultDataResponse(**result_dict.model_dump()) except Exception as exc: logger.exception(f"Error building vertex: {exc}") - log_message = format_exception_message(exc) - log_type = type(exc).__name__ + params = format_exception_message(exc) valid = False + log_obj = Log(message=params, type="error") result_data_response = ResultDataResponse(results={}) - log_object = Log(message=log_message, type=log_type) - + artifacts = {} # If there's an error building the vertex # we need to clear the cache await chat_service.clear_cache(flow_id_str) - result_data_response.logs.append(log_object) + result_data_response.message = artifacts + result_data_response.logs.append(log_obj) # Log the vertex build if not vertex.will_stream: @@ -204,8 +204,9 @@ async def build_vertex( flow_id=flow_id_str, vertex_id=vertex_id, valid=valid, - logs=result_data_response.logs, + params=params, data=result_data_response, + artifacts=artifacts, ) timedelta = time.perf_counter() - start_time @@ -231,6 +232,7 @@ async def build_vertex( next_vertices_ids=next_runnable_vertices, top_level_vertices=top_level_vertices, valid=valid, + params=params, id=vertex.id, data=result_data_response, ) diff --git a/src/backend/base/langflow/api/v1/schemas.py b/src/backend/base/langflow/api/v1/schemas.py index 4e8915842..c1cc041ae 100644 --- a/src/backend/base/langflow/api/v1/schemas.py +++ b/src/backend/base/langflow/api/v1/schemas.py @@ -2,6 +2,7 @@ from datetime import datetime, timezone from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Union +from typing_extensions import TypedDict from uuid import UUID from pydantic import BaseModel, ConfigDict, Field, field_validator, model_serializer @@ -244,10 +245,16 @@ class VerticesOrderResponse(BaseModel): vertices_to_run: List[str] +class Log(TypedDict): + message: Union[dict, str] + type: str + + class ResultDataResponse(BaseModel): results: Optional[Any] = Field(default_factory=dict) logs: List[Log | None] = Field(default_factory=list) - messages: List[ChatOutputResponse | None] = Field(default_factory=list) + message: Optional[Any] = Field(default_factory=dict) + artifacts: Optional[Any] = Field(default_factory=dict) timedelta: Optional[float] = None duration: Optional[str] = None used_frozen_result: Optional[bool] = False @@ -259,6 +266,8 @@ class VertexBuildResponse(BaseModel): next_vertices_ids: Optional[List[str]] = None top_level_vertices: Optional[List[str]] = None valid: bool + params: Optional[Any] = Field(default_factory=dict) + """JSON string of the params.""" data: ResultDataResponse """Mapping of vertex ids to result dict containing the param name and result value.""" timestamp: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/src/backend/base/langflow/base/models/model.py b/src/backend/base/langflow/base/models/model.py index 690dc01ba..72f7ca0a7 100644 --- a/src/backend/base/langflow/base/models/model.py +++ b/src/backend/base/langflow/base/models/model.py @@ -1,10 +1,13 @@ +import warnings from typing import Optional, Union from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.language_models.llms import LLM +from langchain_core.load import load from langchain_core.messages import AIMessage, HumanMessage, SystemMessage from langflow.custom import CustomComponent +from langflow.schema.schema import Record class LCModelComponent(CustomComponent): @@ -82,7 +85,7 @@ class LCModelComponent(CustomComponent): return status_message def get_chat_result( - self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None + self, runnable: BaseChatModel, stream: bool, input_value: str | Record, system_message: Optional[str] = None ): messages: list[Union[HumanMessage, SystemMessage]] = [] if not input_value and not system_message: @@ -90,7 +93,16 @@ class LCModelComponent(CustomComponent): if system_message: messages.append(SystemMessage(content=system_message)) if input_value: - messages.append(HumanMessage(content=input_value)) + if isinstance(input_value, Record): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + if "prompt" in input_value: + prompt = load(input_value.prompt) + runnable = prompt | runnable + else: + messages.append(input_value.to_lc_message()) + else: + messages.append(HumanMessage(content=input_value)) if stream: return runnable.stream(messages) else: diff --git a/src/backend/base/langflow/base/prompts/utils.py b/src/backend/base/langflow/base/prompts/utils.py index 2270035af..c4b4e08c2 100644 --- a/src/backend/base/langflow/base/prompts/utils.py +++ b/src/backend/base/langflow/base/prompts/utils.py @@ -1,9 +1,10 @@ +import base64 from copy import deepcopy - from langchain_core.documents import Document from langflow.schema import Record +from langflow.services.deps import get_storage_service def record_to_string(record: Record) -> str: @@ -19,7 +20,7 @@ def record_to_string(record: Record) -> str: return record.get_text() -def dict_values_to_string(d: dict) -> dict: +async def dict_values_to_string(d: dict) -> dict: """ Converts the values of a dictionary to strings. @@ -36,16 +37,43 @@ def dict_values_to_string(d: dict) -> dict: if isinstance(value, list): for i, item in enumerate(value): if isinstance(item, Record): - d_copy[key][i] = record_to_string(item) + d_copy[key][i] = item.to_lc_message() elif isinstance(item, Document): d_copy[key][i] = document_to_string(item) elif isinstance(value, Record): - d_copy[key] = record_to_string(value) + if "files" in value and value.files: + files = await get_file_paths(value.files) + value.files = files + d_copy[key] = value.to_lc_message() elif isinstance(value, Document): d_copy[key] = document_to_string(value) return d_copy +async def get_file_paths(files: list[str]): + storage_service = get_storage_service() + file_paths = [] + for file in files: + flow_id, file_name = file.split("/") + file_paths.append(storage_service.build_full_path(flow_id=flow_id, file_name=file_name)) + return file_paths + + +async def get_files( + file_paths: str, + convert_to_base64: bool = False, +): + storage_service = get_storage_service() + file_objects = [] + for file_path in file_paths: + flow_id, file_name = file_path.split("/") + file_object = await storage_service.get_file(flow_id=flow_id, file_name=file_name) + if convert_to_base64: + file_object = base64.b64encode(file_object).decode("utf-8") + file_objects.append(file_object) + return file_objects + + def document_to_string(document: Document) -> str: """ Convert a document to a string. diff --git a/src/backend/base/langflow/components/inputs/Prompt.py b/src/backend/base/langflow/components/inputs/Prompt.py index 2c76e6132..f51c1aaab 100644 --- a/src/backend/base/langflow/components/inputs/Prompt.py +++ b/src/backend/base/langflow/components/inputs/Prompt.py @@ -1,7 +1,9 @@ -from langchain_core.prompts import PromptTemplate +from langchain_core.prompts import ChatPromptTemplate +from langflow.base.prompts.utils import dict_values_to_string from langflow.custom import CustomComponent from langflow.field_typing import Prompt, TemplateField, Text +from langflow.schema.schema import Record class PromptComponent(CustomComponent): @@ -15,19 +17,14 @@ class PromptComponent(CustomComponent): "code": TemplateField(advanced=True), } - def build( + async def build( self, template: Prompt, **kwargs, - ) -> Text: - from langflow.base.prompts.utils import dict_values_to_string - - prompt_template = PromptTemplate.from_template(Text(template)) - kwargs = dict_values_to_string(kwargs) - kwargs = {k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()} - try: - formated_prompt = prompt_template.format(**kwargs) - except Exception as exc: - raise ValueError(f"Error formatting prompt: {exc}") from exc - self.status = f'Prompt:\n"{formated_prompt}"' - return formated_prompt + ) -> Record: + prompt_template = ChatPromptTemplate.from_template(Text(template)) + kwargs = await dict_values_to_string(kwargs) + messages = list(kwargs.values()) + prompt = prompt_template + messages + self.status = f'Prompt:\n"{template}"' + return Record(data={"prompt": prompt.to_json()}) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index b0e43557d..57ccc6a9d 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -738,7 +738,9 @@ class Graph: # Check the cache for the vertex cached_result = await chat_service.get_cache(key=vertex.id) if isinstance(cached_result, CacheMiss): - await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars) + await vertex.build( + user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files + ) await chat_service.set_cache(key=vertex.id, data=vertex) else: cached_vertex = cached_result["result"] @@ -752,7 +754,9 @@ class Graph: vertex.result.used_frozen_result = True else: - await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars) + await vertex.build( + user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files + ) if vertex.result is not None: params = f"{vertex._built_object_repr()}{params}" diff --git a/src/backend/base/langflow/graph/utils.py b/src/backend/base/langflow/graph/utils.py index 066d7511a..fd7f1e122 100644 --- a/src/backend/base/langflow/graph/utils.py +++ b/src/backend/base/langflow/graph/utils.py @@ -2,6 +2,7 @@ from enum import Enum from typing import Any, Generator, Union from langchain_core.documents import Document +from langflow.schema.schema import Record from pydantic import BaseModel from langflow.interface.utils import extract_input_variables_from_prompt diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index be5c85861..d3c9b1a9a 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -628,9 +628,8 @@ class Vertex: self._built_object, self.artifacts = result elif len(result) == 3: self._custom_component, self._built_object, self.artifacts = result - self.artifacts_raw = self.artifacts.get("raw") - self.artifacts_type = self.artifacts.get("type") or ArtifactType.UNKNOWN.value - + self.artifacts_raw = self.artifacts.pop("raw", None) + self.artifacts_type = self.artifacts.pop("type", None) or ArtifactType.UNKNOWN.value else: self._built_object = result diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index 16a2a0f0e..c2e6ad4af 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -111,12 +111,7 @@ class InterfaceVertex(Vertex): message = self._built_object artifact_type = ArtifactType.STREAM if stream_url is not None else ArtifactType.OBJECT artifacts = ChatOutputResponse( - message=message, - sender=sender, - sender_name=sender_name, - stream_url=stream_url, - files=files, - type=artifact_type.value, + message=message, sender=sender, sender_name=sender_name, stream_url=stream_url, files=files ) self.will_stream = stream_url is not None @@ -213,9 +208,9 @@ class InterfaceVertex(Vertex): flow_id=self.graph.flow_id, vertex_id=self.id, valid=True, - logs=self._built_object_repr(), + params=self._built_object_repr(), data=self.result, - messages=self.artifacts, + artifacts=self.artifacts, ) self._validate_built_object() diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting (Hello, world!).json b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting (Hello, world!).json index 9bab93817..4b0fc24e3 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting (Hello, world!).json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Basic Prompting (Hello, world!).json @@ -20,7 +20,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Blog Writter.json b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Blog Writter.json index 49fa386e6..2a7d0ff06 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Blog Writter.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Blog Writter.json @@ -20,7 +20,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Document QA.json b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Document QA.json index 361676b66..f559c5236 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Document QA.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Document QA.json @@ -20,7 +20,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Memory Conversation.json b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Memory Conversation.json index 1083e08cb..c16b47d39 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Memory Conversation.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Memory Conversation.json @@ -524,7 +524,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Prompt Chaining.json b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Prompt Chaining.json index 7182b97a1..04b4607c8 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Langflow Prompt Chaining.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Langflow Prompt Chaining.json @@ -20,7 +20,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, @@ -130,7 +130,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, diff --git a/src/backend/base/langflow/initial_setup/starter_projects/VectorStore-RAG-Flows.json b/src/backend/base/langflow/initial_setup/starter_projects/VectorStore-RAG-Flows.json index 4bd5931f1..a49f9a744 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/VectorStore-RAG-Flows.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/VectorStore-RAG-Flows.json @@ -1034,7 +1034,7 @@ "list": false, "show": true, "multiline": true, - "value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n", + "value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n", "fileTypes": [], "file_path": "", "password": false, diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index 163587fa0..42fa4e7a1 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -125,7 +125,6 @@ async def instantiate_custom_component(params, user_id, vertex, fallback_to_env_ custom_repr = build_result if not isinstance(custom_repr, str): custom_repr = str(custom_repr) - raw = custom_component.repr_value if hasattr(raw, "data"): raw = raw.data diff --git a/src/backend/base/langflow/schema/schema.py b/src/backend/base/langflow/schema/schema.py index 749180755..209cb51c8 100644 --- a/src/backend/base/langflow/schema/schema.py +++ b/src/backend/base/langflow/schema/schema.py @@ -1,11 +1,12 @@ import copy import json from typing import Literal, Optional, cast +from typing_extensions import TypedDict from langchain_core.documents import Document from langchain_core.messages import AIMessage, BaseMessage, HumanMessage -from pydantic import BaseModel, model_validator -from typing_extensions import TypedDict +from langchain_core.prompts.image import ImagePromptTemplate +from pydantic import BaseModel, model_serializer, model_validator class Record(BaseModel): @@ -30,6 +31,11 @@ class Record(BaseModel): values["data"][key] = values[key] return values + @model_serializer(mode="plain", when_used="json") + def serialize_model(self): + data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()} + return data + def get_text(self): """ Retrieves the text value from the data dictionary. @@ -103,7 +109,9 @@ class Record(BaseModel): text = self.data.pop(self.text_key, self.default_value) return Document(page_content=text, metadata=self.data) - def to_lc_message(self) -> BaseMessage: + def to_lc_message( + self, + ) -> BaseMessage: """ Converts the Record to a BaseMessage. @@ -119,8 +127,22 @@ class Record(BaseModel): raise ValueError(f"Missing required keys ('text', 'sender') in Record: {self.data}") sender = self.data.get("sender", "Machine") text = self.data.get("text", "") + files = self.data.get("files", []) if sender == "User": - return HumanMessage(content=text) + if files: + contents = [{"type": "text", "text": text}] + for file_path in files: + image_template = ImagePromptTemplate() + image_prompt_value = image_template.invoke(input={"path": file_path}) + contents.append({"type": "image_url", "image_url": image_prompt_value.image_url}) + human_message = HumanMessage(content=contents) + else: + human_message = HumanMessage( + content=[{"type": "text", "text": text}], + ) + + return human_message + return AIMessage(content=text) def __getattr__(self, key): @@ -170,8 +192,14 @@ class Record(BaseModel): def __str__(self) -> str: # return a JSON string representation of the Record atributes + try: + data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()} + return json.dumps(data, indent=4) + except Exception: + return str(self.data) - return json.dumps(self.data) + def __contains__(self, key): + return key in self.data INPUT_FIELD_NAME = "input_value" diff --git a/src/backend/base/langflow/services/cache/utils.py b/src/backend/base/langflow/services/cache/utils.py index ff19836ef..a89963f56 100644 --- a/src/backend/base/langflow/services/cache/utils.py +++ b/src/backend/base/langflow/services/cache/utils.py @@ -23,6 +23,9 @@ class CacheMiss: def __repr__(self): return "" + def __bool__(self): + return False + def create_cache_folder(func): def wrapper(*args, **kwargs): diff --git a/src/backend/base/langflow/services/monitor/schema.py b/src/backend/base/langflow/services/monitor/schema.py index 349d5fc2a..70a20534f 100644 --- a/src/backend/base/langflow/services/monitor/schema.py +++ b/src/backend/base/langflow/services/monitor/schema.py @@ -76,6 +76,7 @@ class MessageModel(BaseModel): session_id: str message: str files: list[str] = [] + artifacts: dict class Config: from_attributes = True @@ -87,6 +88,12 @@ class MessageModel(BaseModel): return json.loads(v) return v + @field_validator("artifacts", mode="before") + def validate_target_args(cls, v): + if isinstance(v, str): + return json.loads(v) + return v + @classmethod def from_record(cls, record: "Record", flow_id: Optional[str] = None): # first check if the record has all the required fields @@ -107,6 +114,12 @@ class MessageModel(BaseModel): class MessageModelResponse(MessageModel): index: Optional[int] = Field(default=None) + @field_validator("artifacts", mode="before") + def serialize_artifacts(v): + if isinstance(v, str): + return json.loads(v) + return v + @field_validator("index", mode="before") def validate_id(cls, v): if isinstance(v, float): @@ -129,15 +142,16 @@ class VertexBuildModel(BaseModel): id: Optional[str] = Field(default=None, alias="id") flow_id: str valid: bool - logs: Any + params: Any data: dict + artifacts: dict timestamp: datetime = Field(default_factory=datetime.now) class Config: from_attributes = True populate_by_name = True - @field_serializer("data") + @field_serializer("data", "artifacts") def serialize_dict(v): if isinstance(v, dict): # check if the value of each key is a BaseModel or a list of BaseModels @@ -151,8 +165,8 @@ class VertexBuildModel(BaseModel): return v.model_dump_json() return v - @field_validator("logs", mode="before") - def validate_logs(cls, v): + @field_validator("params", mode="before") + def validate_params(cls, v): if isinstance(v, str): try: return json.loads(v) @@ -160,7 +174,7 @@ class VertexBuildModel(BaseModel): return v return v - @field_serializer("logs") + @field_serializer("params") def serialize_params(v): if isinstance(v, list) and all(isinstance(i, BaseModel) for i in v): return json.dumps([i.model_dump() for i in v]) @@ -172,11 +186,17 @@ class VertexBuildModel(BaseModel): return json.loads(v) return v + @field_validator("artifacts", mode="before") + def validate_artifacts(cls, v): + if isinstance(v, str): + return json.loads(v) + elif isinstance(v, BaseModel): + return v.model_dump() + return v + class VertexBuildResponseModel(VertexBuildModel): - messages: list[MessageModel] = [] - - @field_serializer("data") + @field_serializer("data", "artifacts") def serialize_dict(v): return v diff --git a/src/backend/base/langflow/services/monitor/utils.py b/src/backend/base/langflow/services/monitor/utils.py index 447a58b92..f603b3fde 100644 --- a/src/backend/base/langflow/services/monitor/utils.py +++ b/src/backend/base/langflow/services/monitor/utils.py @@ -147,9 +147,9 @@ async def log_vertex_build( flow_id: str, vertex_id: str, valid: bool, - logs: Any, + params: Any, data: "ResultDataResponse", - messages: Optional[dict] = None, + artifacts: Optional[dict] = None, ): try: monitor_service = get_monitor_service() @@ -158,9 +158,9 @@ async def log_vertex_build( "flow_id": flow_id, "id": vertex_id, "valid": valid, - "logs": logs, + "params": params, "data": data.model_dump(), - "messages": messages or {}, + "artifacts": artifacts or {}, "timestamp": monitor_service.get_timestamp(), } monitor_service.add_row(table_name="vertex_builds", data=row) diff --git a/src/backend/base/langflow/services/socket/utils.py b/src/backend/base/langflow/services/socket/utils.py index bed00e28f..c1f012e18 100644 --- a/src/backend/base/langflow/services/socket/utils.py +++ b/src/backend/base/langflow/services/socket/utils.py @@ -90,9 +90,9 @@ async def build_vertex( flow_id=flow_id, vertex_id=vertex_id, valid=valid, - logs=params, + params=params, data=result_dict, - messages=artifacts, + artifacts=artifacts, ) # Emit the vertex build response diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 0dd8d9694..270480b50 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -55,10 +55,10 @@ export default function GenericNode({ const setErrorData = useAlertStore((state) => state.setErrorData); const isDark = useDarkStore((state) => state.dark); const buildStatus = useFlowStore( - (state) => state.flowBuildStatus[data.id]?.status, + (state) => state.flowBuildStatus[data.id]?.status ); const lastRunTime = useFlowStore( - (state) => state.flowBuildStatus[data.id]?.timestamp, + (state) => state.flowBuildStatus[data.id]?.timestamp ); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); @@ -66,7 +66,7 @@ export default function GenericNode({ const [nodeName, setNodeName] = useState(data.node!.display_name); const [inputDescription, setInputDescription] = useState(false); const [nodeDescription, setNodeDescription] = useState( - data.node?.description!, + data.node?.description! ); const [isOutdated, setIsOutdated] = useState(false); const [validationStatus, setValidationStatus] = @@ -84,7 +84,7 @@ export default function GenericNode({ data.node!, setNode, setIsOutdated, - updateNodeInternals, + updateNodeInternals ); const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; @@ -115,12 +115,12 @@ export default function GenericNode({ selected: boolean, showNode: boolean, buildStatus: BuildStatus | undefined, - validationStatus: VertexBuildTypeAPI | null, + validationStatus: VertexBuildTypeAPI | null ) => { const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( buildStatus, validationStatus, - isDark, + isDark ); const baseBorderClass = getBaseBorderClass(selected); @@ -129,7 +129,7 @@ export default function GenericNode({ baseBorderClass, nodeSizeClass, "generic-node-div group/node", - specificClassFromBuildStatus, + specificClassFromBuildStatus ); return names; }; @@ -170,7 +170,7 @@ export default function GenericNode({ showNode, isEmoji, nodeIconFragment, - checkNodeIconFragment, + checkNodeIconFragment ); function countHandles(): void { @@ -247,7 +247,7 @@ export default function GenericNode({ selected, showNode, buildStatus, - validationStatus, + validationStatus )} > {data.node?.beta && showNode && ( @@ -378,7 +378,7 @@ export default function GenericNode({ } title={getFieldTitle( data.node?.template!, - templateField, + templateField )} info={data.node?.template[templateField].info} name={templateField} @@ -406,7 +406,7 @@ export default function GenericNode({ proxy={data.node?.template[templateField].proxy} showNode={showNode} /> - ), + ) )} { setInputDescription(true); @@ -614,13 +614,13 @@ export default function GenericNode({ } title={getFieldTitle( data.node?.template!, - templateField, + templateField )} info={data.node?.template[templateField].info} name={templateField} tooltipTitle={ data.node?.template[templateField].input_types?.join( - "\n", + "\n" ) ?? data.node?.template[templateField].type } required={data.node!.template[templateField].required} @@ -647,7 +647,7 @@ export default function GenericNode({
{" "} diff --git a/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx b/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx index 4289c7cbe..b2cbe9b62 100644 --- a/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx +++ b/src/frontend/src/components/sidebarComponent/components/sideBarFolderButtons/index.tsx @@ -32,7 +32,7 @@ const SideBarFoldersButtonsComponent = ({ const [foldersNames, setFoldersNames] = useState({}); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const [editFolders, setEditFolderName] = useState( - folders.map((obj) => ({ name: obj.name, edit: false })), + folders.map((obj) => ({ name: obj.name, edit: false })) ); const uploadFolder = useFolderStore((state) => state.uploadFolder); const currentFolder = pathname.split("/"); @@ -59,7 +59,7 @@ const SideBarFoldersButtonsComponent = ({ const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop( folderId, - handleFolderChange, + handleFolderChange ); const handleUploadFlowsToFolder = () => { @@ -87,7 +87,7 @@ const SideBarFoldersButtonsComponent = ({ addFolder({ name: "New Folder", parent_id: null, description: "" }).then( (res) => { getFoldersApi(true); - }, + } ); } @@ -133,7 +133,7 @@ const SideBarFoldersButtonsComponent = ({ <> {folders.map((item, index) => { const editFolderName = editFolders?.filter( - (folder) => folder.name === item.name, + (folder) => folder.name === item.name )[0]; return (
handleChangeFolder!(item.id!)} > @@ -219,7 +219,7 @@ const SideBarFoldersButtonsComponent = ({ folders.map((obj) => ({ name: obj.name, edit: false, - })), + })) ); } if (e.key === "Enter") { @@ -252,10 +252,10 @@ const SideBarFoldersButtonsComponent = ({ }; const updatedFolder = await updateFolder( body, - item.id!, + item.id! ); const updateFolders = folders.filter( - (f) => f.name !== item.name, + (f) => f.name !== item.name ); setFolders([...updateFolders, updatedFolder]); setFoldersNames({}); @@ -263,7 +263,7 @@ const SideBarFoldersButtonsComponent = ({ folders.map((obj) => ({ name: obj.name, edit: false, - })), + })) ); } else { setFoldersNames((old) => ({ diff --git a/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx b/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx index 86db7de9c..6351d5d98 100644 --- a/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx +++ b/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx @@ -63,8 +63,8 @@ export default function TableNodeCellRender({ ...id, proxy: templateData.proxy, } - : id, - ), + : id + ) ) ?? false; function getCellType() { switch (templateData.type) { @@ -135,7 +135,7 @@ export default function TableNodeCellRender({
1 ? "my-3" : "", + templateValue?.length > 1 ? "my-3" : "" )} > ; diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index ae73d15c2..28a87bcaf 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -62,7 +62,7 @@ export async function sendAll(data: sendAllProps) { } export async function postValidateCode( - code: string, + code: string ): Promise> { return await api.post(`${BASE_URL_API}validate/code`, { code }); } @@ -77,7 +77,7 @@ export async function postValidateCode( export async function postValidatePrompt( name: string, template: string, - frontend_node: APIClassType, + frontend_node: APIClassType ): Promise> { return api.post(`${BASE_URL_API}validate/prompt`, { name, @@ -150,7 +150,7 @@ export async function saveFlowToDatabase(newFlow: { * @throws Will throw an error if the update fails. */ export async function updateFlowInDatabase( - updatedFlow: FlowType, + updatedFlow: FlowType ): Promise { try { const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, { @@ -328,7 +328,7 @@ export async function getHealth() { * */ export async function getBuildStatus( - flowId: string, + flowId: string ): Promise> { return await api.get(`${BASE_URL_API}build/${flowId}/status`); } @@ -341,7 +341,7 @@ export async function getBuildStatus( * */ export async function postBuildInit( - flow: FlowType, + flow: FlowType ): Promise> { return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow); } @@ -357,7 +357,7 @@ export async function postBuildInit( */ export async function uploadFile( file: File, - id: string, + id: string ): Promise> { const formData = new FormData(); formData.append("file", file); @@ -366,7 +366,7 @@ export async function uploadFile( export async function postCustomComponent( code: string, - apiClass: APIClassType, + apiClass: APIClassType ): Promise> { // let template = apiClass.template; return await api.post(`${BASE_URL_API}custom_component`, { @@ -379,7 +379,7 @@ export async function postCustomComponentUpdate( code: string, template: APITemplateType, field: string, - field_value: any, + field_value: any ): Promise> { return await api.post(`${BASE_URL_API}custom_component/update`, { code, @@ -401,7 +401,7 @@ export async function onLogin(user: LoginType) { headers: { "Content-Type": "application/x-www-form-urlencoded", }, - }, + } ); if (response.status === 200) { @@ -463,11 +463,11 @@ export async function addUser(user: UserInputType): Promise> { export async function getUsersPage( skip: number, - limit: number, + limit: number ): Promise> { try { const res = await api.get( - `${BASE_URL_API}users/?skip=${skip}&limit=${limit}`, + `${BASE_URL_API}users/?skip=${skip}&limit=${limit}` ); if (res.status === 200) { return res.data; @@ -504,7 +504,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) { try { const res = await api.patch( `${BASE_URL_API}users/${user_id}/reset-password`, - user, + user ); if (res.status === 200) { return res.data; @@ -578,7 +578,7 @@ export async function saveFlowStore( last_tested_version?: string; }, tags: string[], - publicFlow = false, + publicFlow = false ): Promise { try { const response = await api.post(`${BASE_URL_API}store/components/`, { @@ -707,7 +707,7 @@ export async function postStoreComponents(component: Component) { export async function getComponent(component_id: string) { try { const res = await api.get( - `${BASE_URL_API}store/components/${component_id}`, + `${BASE_URL_API}store/components/${component_id}` ); if (res.status === 200) { return res.data; @@ -722,7 +722,7 @@ export async function searchComponent( page?: number | null, limit?: number | null, status?: string | null, - tags?: string[], + tags?: string[] ): Promise { try { let url = `${BASE_URL_API}store/components/`; @@ -834,7 +834,7 @@ export async function updateFlowStore( }, tags: string[], publicFlow = false, - id: string, + id: string ): Promise { try { const response = await api.patch(`${BASE_URL_API}store/components/${id}`, { @@ -918,7 +918,7 @@ export async function deleteGlobalVariable(id: string) { export async function updateGlobalVariable( name: string, value: string, - id: string, + id: string ) { try { const response = api.patch(`${BASE_URL_API}variables/${id}`, { @@ -937,7 +937,7 @@ export async function getVerticesOrder( startNodeId?: string | null, stopNodeId?: string | null, nodes?: Node[], - Edges?: Edge[], + Edges?: Edge[] ): Promise> { // nodeId is optional and is a query parameter // if nodeId is not provided, the API will return all vertices @@ -957,7 +957,7 @@ export async function getVerticesOrder( return await api.post( `${BASE_URL_API}build/${flowId}/vertices`, data, - config, + config ); } @@ -965,18 +965,16 @@ export async function postBuildVertex( flowId: string, vertexId: string, input_value: string, - files?: string[], + files?: string[] ): Promise> { // input_value is optional and is a query parameter - const data = input_value - ? { inputs: { input_value: input_value } } - : undefined; + const data = { inputs: { input_value: input_value ?? "" } }; if (data && files) { data["files"] = files; } return await api.post( `${BASE_URL_API}build/${flowId}/vertices/${vertexId}`, - data, + data ); } @@ -1000,7 +998,7 @@ export async function getFlowPool({ } export async function deleteFlowPool( - flowId: string, + flowId: string ): Promise> { const config = {}; config["params"] = { flow_id: flowId }; @@ -1014,7 +1012,7 @@ export async function deleteFlowPool( * @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses. */ export async function multipleDeleteFlowsComponents( - flowIds: string[], + flowIds: string[] ): Promise[]> { const batches: string[][] = []; @@ -1037,7 +1035,7 @@ export async function multipleDeleteFlowsComponents( // Execute all delete requests const responses: Promise>[] = batches.map((batch) => - deleteBatch(batch), + deleteBatch(batch) ); // Return the responses after all requests are completed @@ -1047,7 +1045,7 @@ export async function multipleDeleteFlowsComponents( export async function getTransactionTable( id: string, mode: "intersection" | "union", - params = {}, + params = {} ): Promise<{ rows: Array; columns: Array }> { const config = {}; config["params"] = { flow_id: id }; @@ -1063,7 +1061,7 @@ export async function getMessagesTable( mode: "intersection" | "union", id?: string, excludedFields?: string[], - params = {}, + params = {} ): Promise<{ rows: Array; columns: Array }> { const config = {}; if (id) { diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/index.tsx new file mode 100644 index 000000000..ff93a9f7b --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/buttonSendWrapper/index.tsx @@ -0,0 +1,52 @@ +import IconComponent from "../../../../../../../components/genericIconComponent"; +import { Case } from "../../../../../../../shared/components/caseComponent"; +import { classNames } from "../../../../../../../utils/utils"; + +const ButtonSendWrapper = ({ + send, + lockChat, + noInput, + saveLoading, + chatValue, +}) => { + return ( + + ); +}; + +export default ButtonSendWrapper; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/index.tsx new file mode 100644 index 000000000..29f88453a --- /dev/null +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/components/textAreaWrapper/index.tsx @@ -0,0 +1,81 @@ +import { Textarea } from "../../../../../../../components/ui/textarea"; +import { classNames } from "../../../../../../../utils/utils"; + +const TextAreaWrapper = ({ + checkSendingOk, + send, + lockChat, + noInput, + saveLoading, + chatValue, + setChatValue, + CHAT_INPUT_PLACEHOLDER, + CHAT_INPUT_PLACEHOLDER_SEND, + inputRef, + setInputFocus, + files, + isDragging, +}) => { + const getPlaceholderText = ( + isDragging: boolean, + noInput: boolean, + ): string => { + if (isDragging) { + return "Drop here"; + } else if (noInput) { + return CHAT_INPUT_PLACEHOLDER; + } else { + return CHAT_INPUT_PLACEHOLDER_SEND; + } + }; + + const lockClass = + lockChat || saveLoading + ? "form-modal-lock-true bg-input" + : noInput + ? "form-modal-no-input bg-input" + : "form-modal-lock-false bg-background"; + + const fileClass = + files.length > 0 + ? "rounded-b-lg ring-0 focus:ring-0 focus:border-2 rounded-t-none border-t-0 border-border focus:border-t-0 focus:border-ring" + : "rounded-md border-t border-border focus:ring-0 focus:border-2 focus:border-ring"; + + const additionalClassNames = "form-modal-lockchat pl-14"; + + return ( +