diff --git a/.github/workflows/typescript_test.yml b/.github/workflows/typescript_test.yml index f0c131c7d64..4803762b117 100644 --- a/.github/workflows/typescript_test.yml +++ b/.github/workflows/typescript_test.yml @@ -29,8 +29,30 @@ jobs: strategy: fail-fast: false matrix: - shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - shardTotal: [15] + shardIndex: + [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + ] + shardTotal: [20] env: OPENAI_API_KEY: ${{ inputs.openai_api_key || secrets.OPENAI_API_KEY }} STORE_API_KEY: ${{ inputs.store_api_key || secrets.STORE_API_KEY }} @@ -123,6 +145,7 @@ jobs: merge-reports: needs: setup-and-test runs-on: ubuntu-latest + if: always() steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 19d554a5d4d..5905f360777 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -192,6 +192,7 @@ line-length = 120 [tool.mypy] plugins = ["pydantic.mypy"] follow_imports = "silent" +disable_error_code = ["type-var"] [build-system] requires = ["poetry-core"] diff --git a/src/backend/base/langflow/api/v1/api_key.py b/src/backend/base/langflow/api/v1/api_key.py index 77466960ddf..f8dab0dc199 100644 --- a/src/backend/base/langflow/api/v1/api_key.py +++ b/src/backend/base/langflow/api/v1/api_key.py @@ -71,6 +71,7 @@ def save_store_api_key( # Encrypt the API key encrypted = auth_utils.encrypt_api_key(api_key, settings_service=settings_service) current_user.store_api_key = encrypted + db.add(current_user) db.commit() return {"detail": "API Key saved"} except Exception as e: diff --git a/src/backend/base/langflow/api/v1/flows.py b/src/backend/base/langflow/api/v1/flows.py index e01af05ac3b..b66201fad35 100644 --- a/src/backend/base/langflow/api/v1/flows.py +++ b/src/backend/base/langflow/api/v1/flows.py @@ -293,10 +293,12 @@ async def upload_file( session: Session = Depends(get_session), file: UploadFile = File(...), current_user: User = Depends(get_current_active_user), + folder_id: UUID | None = None, ): """Upload flows from a file.""" contents = await file.read() data = orjson.loads(contents) + response_list = [] if "flows" in data: flow_list = FlowListCreate(**data) else: @@ -304,8 +306,12 @@ async def upload_file( # Now we set the user_id for all flows for flow in flow_list.flows: flow.user_id = current_user.id + if folder_id: + flow.folder_id = folder_id + response = create_flow(session=session, flow=flow, current_user=current_user) + response_list.append(response) - return create_flows(session=session, flow_list=flow_list, current_user=current_user) + return response_list @router.get("/download/", response_model=FlowListRead, status_code=200) diff --git a/src/backend/base/langflow/base/models/model.py b/src/backend/base/langflow/base/models/model.py index 395631df3da..35853b2c285 100644 --- a/src/backend/base/langflow/base/models/model.py +++ b/src/backend/base/langflow/base/models/model.py @@ -1,13 +1,16 @@ import json import warnings from abc import abstractmethod -from typing import Optional, Union +from typing import Optional, Union, List from langchain_core.language_models.llms import LLM from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage +from langflow.base.constants import STREAM_INFO_TEXT from langflow.custom import Component from langflow.field_typing import LanguageModel +from langflow.inputs import MessageInput, MessageTextInput +from langflow.inputs.inputs import InputTypes, BoolInput from langflow.schema.message import Message from langflow.template.field.base import Output @@ -17,6 +20,17 @@ class LCModelComponent(Component): description: str = "Model Description" trace_type = "llm" + _base_inputs: List[InputTypes] = [ + MessageInput(name="input_value", display_name="Input"), + MessageTextInput( + name="system_message", + display_name="System Message", + info="System message to pass to the model.", + advanced=True, + ), + BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), + ] + outputs = [ Output(display_name="Text", name="text_output", method="text_response"), Output(display_name="Language Model", name="model_output", method="build_model"), @@ -142,6 +156,7 @@ def get_chat_result( messages.append(input_value.to_lc_message()) else: messages.append(HumanMessage(content=input_value)) + inputs: Union[list, dict] = messages or {} try: runnable = runnable.with_config( # type: ignore diff --git a/src/backend/base/langflow/components/embeddings/VertexAIEmbeddings.py b/src/backend/base/langflow/components/embeddings/VertexAIEmbeddings.py index 7b2374482aa..7149c61730a 100644 --- a/src/backend/base/langflow/components/embeddings/VertexAIEmbeddings.py +++ b/src/backend/base/langflow/components/embeddings/VertexAIEmbeddings.py @@ -1,6 +1,6 @@ from langflow.base.models.model import LCModelComponent from langflow.field_typing import Embeddings -from langflow.io import BoolInput, DictInput, FileInput, FloatInput, IntInput, MessageTextInput, Output +from langflow.io import BoolInput, FileInput, FloatInput, IntInput, MessageTextInput, Output class VertexAIEmbeddingsComponent(LCModelComponent): @@ -13,81 +13,22 @@ class VertexAIEmbeddingsComponent(LCModelComponent): FileInput( name="credentials", display_name="Credentials", + info="JSON credentials file. Leave empty to fallback to environment variables", value="", - file_types=["json"], # Removed the dot - ), - DictInput( - name="instance", - display_name="Instance", - advanced=True, - ), - MessageTextInput( - name="location", - display_name="Location", - value="us-central1", - advanced=True, - ), - IntInput( - name="max_output_tokens", - display_name="Max Output Tokens", - value=128, - ), - IntInput( - name="max_retries", - display_name="Max Retries", - value=6, - advanced=True, - ), - MessageTextInput( - name="model_name", - display_name="Model Name", - value="textembedding-gecko", - ), - IntInput( - name="n", - display_name="N", - value=1, - advanced=True, - ), - MessageTextInput( - name="project", - display_name="Project", - advanced=True, - ), - IntInput( - name="request_parallelism", - display_name="Request Parallelism", - value=5, - advanced=True, - ), - MessageTextInput( - name="stop", - display_name="Stop", - advanced=True, - ), - BoolInput( - name="streaming", - display_name="Streaming", - value=False, - advanced=True, - ), - FloatInput( - name="temperature", - display_name="Temperature", - value=0.0, - ), - IntInput( - name="top_k", - display_name="Top K", - value=40, - advanced=True, - ), - FloatInput( - name="top_p", - display_name="Top P", - value=0.95, - advanced=True, + file_types=["json"], ), + MessageTextInput(name="location", display_name="Location", advanced=True), + MessageTextInput(name="project", display_name="Project", info="The project ID.", advanced=True), + IntInput(name="max_output_tokens", display_name="Max Output Tokens", advanced=True), + IntInput(name="max_retries", display_name="Max Retries", value=1, advanced=True), + MessageTextInput(name="model_name", display_name="Model Name", value="textembedding-gecko"), + IntInput(name="n", display_name="N", value=1, advanced=True), + IntInput(name="request_parallelism", value=5, display_name="Request Parallelism", advanced=True), + MessageTextInput(name="stop_sequences", display_name="Stop", advanced=True, is_list=True), + BoolInput(name="streaming", display_name="Streaming", value=False, advanced=True), + FloatInput(name="temperature", value=0.0, display_name="Temperature"), + IntInput(name="top_k", display_name="Top K", advanced=True), + FloatInput(name="top_p", display_name="Top P", value=0.95, advanced=True), ] outputs = [ @@ -102,9 +43,15 @@ def build_embeddings(self) -> Embeddings: "Please install the langchain-google-vertexai package to use the VertexAIEmbeddings component." ) + from google.oauth2 import service_account + + if self.credentials: + gcloud_credentials = service_account.Credentials.from_service_account_file(self.credentials) + else: + # will fallback to environment variable or inferred from gcloud CLI + gcloud_credentials = None return VertexAIEmbeddings( - instance=self.instance, - credentials=self.credentials, + credentials=gcloud_credentials, location=self.location, max_output_tokens=self.max_output_tokens, max_retries=self.max_retries, @@ -112,7 +59,7 @@ def build_embeddings(self) -> Embeddings: n=self.n, project=self.project, request_parallelism=self.request_parallelism, - stop=self.stop, + stop=self.stop_sequences or None, streaming=self.streaming, temperature=self.temperature, top_k=self.top_k, diff --git a/src/backend/base/langflow/components/models/AmazonBedrockModel.py b/src/backend/base/langflow/components/models/AmazonBedrockModel.py index d21546502be..deee5798a39 100644 --- a/src/backend/base/langflow/components/models/AmazonBedrockModel.py +++ b/src/backend/base/langflow/components/models/AmazonBedrockModel.py @@ -1,10 +1,9 @@ from langchain_aws import ChatBedrock -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel from langflow.inputs import MessageTextInput -from langflow.io import BoolInput, DictInput, DropdownInput, MessageInput +from langflow.io import DictInput, DropdownInput class AmazonBedrockComponent(LCModelComponent): @@ -13,8 +12,7 @@ class AmazonBedrockComponent(LCModelComponent): icon = "Amazon" name = "AmazonBedrockModel" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ DropdownInput( name="model_id", display_name="Model ID", @@ -57,13 +55,6 @@ class AmazonBedrockComponent(LCModelComponent): MessageTextInput(name="region_name", display_name="Region Name", value="us-east-1"), DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True, is_list=True), MessageTextInput(name="endpoint_url", display_name="Endpoint URL", advanced=True), - MessageTextInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/AnthropicModel.py b/src/backend/base/langflow/components/models/AnthropicModel.py index 0972dd57747..e41b401d156 100644 --- a/src/backend/base/langflow/components/models/AnthropicModel.py +++ b/src/backend/base/langflow/components/models/AnthropicModel.py @@ -1,10 +1,9 @@ from langchain_anthropic.chat_models import ChatAnthropic from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput +from langflow.io import DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput class AnthropicModelComponent(LCModelComponent): @@ -13,8 +12,7 @@ class AnthropicModelComponent(LCModelComponent): icon = "Anthropic" name = "AnthropicModel" - inputs = [ - MessageTextInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ IntInput( name="max_tokens", display_name="Max Tokens", @@ -46,13 +44,6 @@ class AnthropicModelComponent(LCModelComponent): advanced=True, info="Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.", ), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True, value=False), - MessageTextInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), MessageTextInput( name="prefill", display_name="Prefill", diff --git a/src/backend/base/langflow/components/models/AzureOpenAIModel.py b/src/backend/base/langflow/components/models/AzureOpenAIModel.py index 57b488cfae7..c55e7cbc901 100644 --- a/src/backend/base/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/base/langflow/components/models/AzureOpenAIModel.py @@ -1,9 +1,8 @@ from langchain_openai import AzureChatOpenAI -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel from langflow.inputs import MessageTextInput -from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, SecretStrInput, StrInput +from langflow.io import DropdownInput, FloatInput, IntInput, SecretStrInput class AzureChatOpenAIComponent(LCModelComponent): @@ -26,7 +25,7 @@ class AzureChatOpenAIComponent(LCModelComponent): "2024-05-13", ] - inputs = [ + inputs = LCModelComponent._base_inputs + [ MessageTextInput( name="azure_endpoint", display_name="Azure Endpoint", @@ -48,14 +47,6 @@ class AzureChatOpenAIComponent(LCModelComponent): advanced=True, info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.", ), - MessageInput(name="input_value", display_name="Input"), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - advanced=True, - info="System message to pass to the model.", - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/BaiduQianfanChatModel.py b/src/backend/base/langflow/components/models/BaiduQianfanChatModel.py index 15ede57f7a3..6e638424858 100644 --- a/src/backend/base/langflow/components/models/BaiduQianfanChatModel.py +++ b/src/backend/base/langflow/components/models/BaiduQianfanChatModel.py @@ -1,10 +1,9 @@ from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing.constants import LanguageModel -from langflow.io import BoolInput, DropdownInput, FloatInput, MessageTextInput, SecretStrInput +from langflow.io import DropdownInput, FloatInput, MessageTextInput, SecretStrInput class QianfanChatEndpointComponent(LCModelComponent): @@ -14,11 +13,7 @@ class QianfanChatEndpointComponent(LCModelComponent): icon = "BaiduQianfan" name = "BaiduQianfanChatModel" - inputs = [ - MessageTextInput( - name="input_value", - display_name="Input", - ), + inputs = LCModelComponent._base_inputs + [ DropdownInput( name="model", display_name="Model Name", @@ -72,18 +67,6 @@ class QianfanChatEndpointComponent(LCModelComponent): display_name="Endpoint", info="Endpoint of the Qianfan LLM, required if custom model used.", ), - BoolInput( - name="stream", - display_name="Stream", - info=STREAM_INFO_TEXT, - advanced=True, - ), - MessageTextInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/CohereModel.py b/src/backend/base/langflow/components/models/CohereModel.py index 890f916061a..498e47fd4ae 100644 --- a/src/backend/base/langflow/components/models/CohereModel.py +++ b/src/backend/base/langflow/components/models/CohereModel.py @@ -1,10 +1,9 @@ from langchain_cohere import ChatCohere from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, FloatInput, MessageInput, SecretStrInput, StrInput +from langflow.io import FloatInput, SecretStrInput class CohereComponent(LCModelComponent): @@ -14,7 +13,7 @@ class CohereComponent(LCModelComponent): icon = "Cohere" name = "CohereModel" - inputs = [ + inputs = LCModelComponent._base_inputs + [ SecretStrInput( name="cohere_api_key", display_name="Cohere API Key", @@ -23,14 +22,6 @@ class CohereComponent(LCModelComponent): value="COHERE_API_KEY", ), FloatInput(name="temperature", display_name="Temperature", value=0.75), - MessageInput(name="input_value", display_name="Input"), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/GoogleGenerativeAIModel.py b/src/backend/base/langflow/components/models/GoogleGenerativeAIModel.py index e20f3150c1e..91683143066 100644 --- a/src/backend/base/langflow/components/models/GoogleGenerativeAIModel.py +++ b/src/backend/base/langflow/components/models/GoogleGenerativeAIModel.py @@ -1,9 +1,8 @@ from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.inputs import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, SecretStrInput, StrInput +from langflow.inputs import DropdownInput, FloatInput, IntInput, SecretStrInput class GoogleGenerativeAIComponent(LCModelComponent): @@ -12,8 +11,7 @@ class GoogleGenerativeAIComponent(LCModelComponent): icon = "GoogleGenerativeAI" name = "GoogleGenerativeAIModel" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ IntInput( name="max_output_tokens", display_name="Max Output Tokens", @@ -38,19 +36,12 @@ class GoogleGenerativeAIComponent(LCModelComponent): advanced=True, ), FloatInput(name="temperature", display_name="Temperature", value=0.1), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), IntInput( name="n", display_name="N", info="Number of chat completions to generate for each prompt. Note that the API may not return the full n completions if duplicates are generated.", advanced=True, ), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), IntInput( name="top_k", display_name="Top K", diff --git a/src/backend/base/langflow/components/models/GroqModel.py b/src/backend/base/langflow/components/models/GroqModel.py index d24ba3cf622..7bad1bcf1b7 100644 --- a/src/backend/base/langflow/components/models/GroqModel.py +++ b/src/backend/base/langflow/components/models/GroqModel.py @@ -1,11 +1,10 @@ from langchain_groq import ChatGroq from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.groq_constants import MODEL_NAMES from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput +from langflow.io import DropdownInput, FloatInput, IntInput, MessageTextInput, SecretStrInput class GroqModel(LCModelComponent): @@ -14,7 +13,7 @@ class GroqModel(LCModelComponent): icon = "Groq" name = "GroqModel" - inputs = [ + inputs = LCModelComponent._base_inputs + [ SecretStrInput( name="groq_api_key", display_name="Groq API Key", @@ -50,23 +49,6 @@ class GroqModel(LCModelComponent): info="The name of the model to use.", options=MODEL_NAMES, ), - MessageTextInput( - name="input_value", - display_name="Input", - info="The input to the model.", - ), - BoolInput( - name="stream", - display_name="Stream", - info=STREAM_INFO_TEXT, - advanced=True, - ), - MessageTextInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/HuggingFaceModel.py b/src/backend/base/langflow/components/models/HuggingFaceModel.py index 6995eb76502..313d440019d 100644 --- a/src/backend/base/langflow/components/models/HuggingFaceModel.py +++ b/src/backend/base/langflow/components/models/HuggingFaceModel.py @@ -1,10 +1,9 @@ from langchain_community.chat_models.huggingface import ChatHuggingFace from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, DictInput, DropdownInput, MessageInput, SecretStrInput, StrInput +from langflow.io import DictInput, DropdownInput, SecretStrInput, StrInput class HuggingFaceEndpointsComponent(LCModelComponent): @@ -13,8 +12,7 @@ class HuggingFaceEndpointsComponent(LCModelComponent): icon = "HuggingFace" name = "HuggingFaceModel" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ SecretStrInput(name="endpoint_url", display_name="Endpoint URL", password=True), StrInput( name="model_id", @@ -28,13 +26,6 @@ class HuggingFaceEndpointsComponent(LCModelComponent): ), SecretStrInput(name="huggingfacehub_api_token", display_name="API token", password=True), DictInput(name="model_kwargs", display_name="Model Keyword Arguments", advanced=True), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/Maritalk.py b/src/backend/base/langflow/components/models/Maritalk.py index c0740d2f77c..e6b7c052e93 100644 --- a/src/backend/base/langflow/components/models/Maritalk.py +++ b/src/backend/base/langflow/components/models/Maritalk.py @@ -1,10 +1,9 @@ from langchain_community.chat_models import ChatMaritalk -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel from langflow.field_typing.range_spec import RangeSpec -from langflow.inputs import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, SecretStrInput, StrInput +from langflow.inputs import DropdownInput, FloatInput, IntInput, SecretStrInput class MaritalkModelComponent(LCModelComponent): @@ -12,8 +11,7 @@ class MaritalkModelComponent(LCModelComponent): description = "Generates text using Maritalk LLMs." icon = "Maritalk" name = "Maritalk" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ IntInput( name="max_tokens", display_name="Max Tokens", @@ -35,13 +33,6 @@ class MaritalkModelComponent(LCModelComponent): advanced=False, ), FloatInput(name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=1)), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, value=False, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/MistralModel.py b/src/backend/base/langflow/components/models/MistralModel.py index 37339a851ca..41d84e04355 100644 --- a/src/backend/base/langflow/components/models/MistralModel.py +++ b/src/backend/base/langflow/components/models/MistralModel.py @@ -1,10 +1,9 @@ from langchain_mistralai import ChatMistralAI from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, SecretStrInput, StrInput +from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, SecretStrInput, StrInput class MistralAIModelComponent(LCModelComponent): @@ -13,8 +12,7 @@ class MistralAIModelComponent(LCModelComponent): icon = "MistralAI" name = "MistralModel" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ IntInput( name="max_tokens", display_name="Max Tokens", @@ -51,13 +49,6 @@ class MistralAIModelComponent(LCModelComponent): advanced=False, ), FloatInput(name="temperature", display_name="Temperature", advanced=False, value=0.5), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), IntInput(name="max_retries", display_name="Max Retries", advanced=True, value=5), IntInput(name="timeout", display_name="Timeout", advanced=True, value=60), IntInput(name="max_concurrent_requests", display_name="Max Concurrent Requests", advanced=True, value=3), diff --git a/src/backend/base/langflow/components/models/NvidiaModel.py b/src/backend/base/langflow/components/models/NvidiaModel.py index 8d7871ae447..40a841d7c51 100644 --- a/src/backend/base/langflow/components/models/NvidiaModel.py +++ b/src/backend/base/langflow/components/models/NvidiaModel.py @@ -1,9 +1,8 @@ from typing import Any -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.inputs import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, SecretStrInput, StrInput +from langflow.inputs import DropdownInput, FloatInput, IntInput, SecretStrInput, StrInput from langflow.schema.dotdict import dotdict @@ -12,8 +11,7 @@ class NVIDIAModelComponent(LCModelComponent): description = "Generates text using NVIDIA LLMs." icon = "NVIDIA" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ IntInput( name="max_tokens", display_name="Max Tokens", @@ -42,13 +40,6 @@ class NVIDIAModelComponent(LCModelComponent): value="NVIDIA_API_KEY", ), FloatInput(name="temperature", display_name="Temperature", value=0.1), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), IntInput( name="seed", display_name="Seed", diff --git a/src/backend/base/langflow/components/models/OllamaModel.py b/src/backend/base/langflow/components/models/OllamaModel.py index ec8c01ff802..8edba33741e 100644 --- a/src/backend/base/langflow/components/models/OllamaModel.py +++ b/src/backend/base/langflow/components/models/OllamaModel.py @@ -3,10 +3,9 @@ import httpx from langchain_community.chat_models import ChatOllama -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageInput, StrInput +from langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, StrInput class ChatOllamaComponent(LCModelComponent): @@ -68,7 +67,7 @@ def get_model(self, url: str) -> list[str]: except Exception as e: raise ValueError("Could not retrieve models. Please, make sure Ollama is running.") from e - inputs = [ + inputs = LCModelComponent._base_inputs + [ StrInput( name="base_url", display_name="Base URL", @@ -204,21 +203,6 @@ def get_model(self, url: str) -> list[str]: info="Template to use for generating text.", advanced=True, ), - MessageInput( - name="input_value", - display_name="Input", - ), - BoolInput( - name="stream", - display_name="Stream", - info=STREAM_INFO_TEXT, - ), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] def build_model(self) -> LanguageModel: # type: ignore[type-var] diff --git a/src/backend/base/langflow/components/models/OpenAIModel.py b/src/backend/base/langflow/components/models/OpenAIModel.py index d6051dba618..355ab567b89 100644 --- a/src/backend/base/langflow/components/models/OpenAIModel.py +++ b/src/backend/base/langflow/components/models/OpenAIModel.py @@ -4,7 +4,6 @@ from langchain_openai import ChatOpenAI from pydantic.v1 import SecretStr -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.base.models.openai_constants import MODEL_NAMES from langflow.field_typing import LanguageModel @@ -14,7 +13,6 @@ DropdownInput, FloatInput, IntInput, - MessageInput, SecretStrInput, StrInput, ) @@ -26,8 +24,7 @@ class OpenAIModelComponent(LCModelComponent): icon = "OpenAI" name = "OpenAIModel" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ IntInput( name="max_tokens", display_name="Max Tokens", @@ -65,13 +62,6 @@ class OpenAIModelComponent(LCModelComponent): value="OPENAI_API_KEY", ), FloatInput(name="temperature", display_name="Temperature", value=0.1), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), IntInput( name="seed", display_name="Seed", diff --git a/src/backend/base/langflow/components/models/VertexAiModel.py b/src/backend/base/langflow/components/models/VertexAiModel.py index 32cb8f51dd0..b52650b0bdf 100644 --- a/src/backend/base/langflow/components/models/VertexAiModel.py +++ b/src/backend/base/langflow/components/models/VertexAiModel.py @@ -1,9 +1,9 @@ -from langchain_google_vertexai import ChatVertexAI +from typing import cast -from langflow.base.constants import STREAM_INFO_TEXT from langflow.base.models.model import LCModelComponent from langflow.field_typing import LanguageModel -from langflow.io import BoolInput, FileInput, FloatInput, IntInput, MessageInput, MultilineInput, StrInput +from langflow.inputs import MessageTextInput +from langflow.io import BoolInput, FileInput, FloatInput, IntInput, StrInput class ChatVertexAIComponent(LCModelComponent): @@ -12,64 +12,60 @@ class ChatVertexAIComponent(LCModelComponent): icon = "VertexAI" name = "VertexAiModel" - inputs = [ - MessageInput(name="input_value", display_name="Input"), + inputs = LCModelComponent._base_inputs + [ FileInput( name="credentials", display_name="Credentials", - info="Path to the JSON file containing the credentials.", + info="JSON credentials file. Leave empty to fallback to environment variables", file_types=["json"], - advanced=True, ), - StrInput(name="project", display_name="Project", info="The project ID."), - MultilineInput( - name="examples", - display_name="Examples", - info="Examples to pass to the model.", - advanced=True, - ), - StrInput(name="location", display_name="Location", value="us-central1", advanced=True), - IntInput( - name="max_output_tokens", - display_name="Max Output Tokens", - value=128, - advanced=True, - ), - StrInput(name="model_name", display_name="Model Name", value="gemini-1.5-pro"), - FloatInput(name="temperature", display_name="Temperature", value=0.0), - IntInput(name="top_k", display_name="Top K", value=40, advanced=True), + MessageTextInput(name="model_name", display_name="Model Name", value="gemini-1.5-pro"), + StrInput(name="project", display_name="Project", info="The project ID.", advanced=True), + StrInput(name="location", display_name="Location", advanced=True), + IntInput(name="max_output_tokens", display_name="Max Output Tokens", advanced=True), + IntInput(name="max_retries", display_name="Max Retries", value=1, advanced=True), + FloatInput(name="temperature", value=0.0, display_name="Temperature"), + IntInput(name="top_k", display_name="Top K", advanced=True), FloatInput(name="top_p", display_name="Top P", value=0.95, advanced=True), BoolInput(name="verbose", display_name="Verbose", value=False, advanced=True), - BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), - StrInput( - name="system_message", - display_name="System Message", - info="System message to pass to the model.", - advanced=True, - ), ] - def build_model(self) -> LanguageModel: # type: ignore[type-var] - credentials = self.credentials - location = self.location - max_output_tokens = self.max_output_tokens - model_name = self.model_name - project = self.project - temperature = self.temperature - top_k = self.top_k - top_p = self.top_p - verbose = self.verbose + def build_model(self) -> LanguageModel: + try: + from langchain_google_vertexai import ChatVertexAI + except ImportError: + raise ImportError( + "Please install the langchain-google-vertexai package to use the VertexAIEmbeddings component." + ) + location = self.location or None + if self.credentials: + from google.cloud import aiplatform + from google.oauth2 import service_account - output = ChatVertexAI( - credentials=credentials, - location=location, - max_output_tokens=max_output_tokens, - model_name=model_name, - project=project, - temperature=temperature, - top_k=top_k, - top_p=top_p, - verbose=verbose, - ) + credentials = service_account.Credentials.from_service_account_file(self.credentials) + project = self.project or credentials.project_id + # ChatVertexAI sometimes skip manual credentials initialization + aiplatform.init( + project=project, + location=location, + credentials=credentials, + ) + else: + project = self.project or None + credentials = None - return output # type: ignore + return cast( + LanguageModel, + ChatVertexAI( + credentials=credentials, + location=location, + project=project, + max_output_tokens=self.max_output_tokens, + max_retries=self.max_retries, + model_name=self.model_name, + temperature=self.temperature, + top_k=self.top_k, + top_p=self.top_p, + verbose=self.verbose, + ), + ) diff --git a/src/backend/base/langflow/components/vectorstores/Pinecone.py b/src/backend/base/langflow/components/vectorstores/Pinecone.py index e09711d0878..51c201fa8b6 100644 --- a/src/backend/base/langflow/components/vectorstores/Pinecone.py +++ b/src/backend/base/langflow/components/vectorstores/Pinecone.py @@ -22,6 +22,7 @@ class PineconeVectorStoreComponent(LCVectorStoreComponent): documentation = "https://python.langchain.com/v0.2/docs/integrations/vectorstores/pinecone/" name = "Pinecone" icon = "Pinecone" + pinecone_instance = None inputs = [ StrInput(name="index_name", display_name="Index Name", required=True), @@ -61,6 +62,8 @@ def build_vector_store(self) -> Pinecone: return self._build_pinecone() def _build_pinecone(self) -> Pinecone: + if self.pinecone_instance is not None: + return self.pinecone_instance from langchain_pinecone._utilities import DistanceStrategy from langchain_pinecone.vectorstores import Pinecone @@ -85,7 +88,7 @@ def _build_pinecone(self) -> Pinecone: if documents: pinecone.add_documents(documents) - + self.pinecone_instance = pinecone return pinecone def search_documents(self) -> List[Data]: diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 348559c0faf..3baeac8ae84 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -869,8 +869,7 @@ async def build_vertex( self.run_manager.add_to_vertices_being_run(vertex_id) try: params = "" - parent_vertex = self.get_vertex(vertex.parent_node_id) if vertex.parent_node_id else None - if vertex.frozen or (parent_vertex and parent_vertex.frozen): + if vertex.frozen: # Check the cache for the vertex cached_result = await chat_service.get_cache(key=vertex.id) if isinstance(cached_result, CacheMiss): diff --git a/src/backend/base/langflow/graph/graph/utils.py b/src/backend/base/langflow/graph/graph/utils.py index 0addab0b642..5500f210c88 100644 --- a/src/backend/base/langflow/graph/graph/utils.py +++ b/src/backend/base/langflow/graph/graph/utils.py @@ -42,7 +42,7 @@ def add_frozen(nodes, frozen): This function receives a list of nodes and adds a frozen to each node. """ for node in nodes: - node["frozen"] = frozen + node["data"]["node"]["frozen"] = frozen def ungroup_node(group_node_data, base_flow): diff --git a/src/backend/base/langflow/services/monitor/service.py b/src/backend/base/langflow/services/monitor/service.py index f644fd87160..de19700067d 100644 --- a/src/backend/base/langflow/services/monitor/service.py +++ b/src/backend/base/langflow/services/monitor/service.py @@ -21,7 +21,7 @@ def __init__(self, settings_service: "SettingsService"): from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel self.settings_service = settings_service - self.base_cache_dir = Path(user_cache_dir("langflow")) + self.base_cache_dir = Path(user_cache_dir("langflow"), ensure_exists=True) self.db_path = self.base_cache_dir / "monitor.duckdb" self.table_map: dict[str, type[TransactionModel | DuckDbMessageModel | VertexBuildModel]] = { "transactions": TransactionModel, diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index 9e51aac667b..e2787617d4a 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -97,6 +97,7 @@ markers = ["async_test"] namespace_packages = true mypy_path = "langflow" ignore_missing_imports = true +disable_error_code = ["type-var"] [tool.ruff] diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index f23b03c30fe..ed2b933c3ec 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -8,7 +8,6 @@ "name": "langflow", "version": "0.1.2", "dependencies": { - "@chakra-ui/number-input": "^2.1.2", "@headlessui/react": "^2.0.4", "@hookform/resolvers": "^3.6.0", "@million/lint": "^1.0.0-rc.26", @@ -765,293 +764,6 @@ "node": ">=6.9.0" } }, - "node_modules/@chakra-ui/anatomy": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", - "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==", - "license": "MIT", - "peer": true - }, - "node_modules/@chakra-ui/color-mode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", - "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/react-use-safe-layout-effect": "2.1.0" - }, - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/counter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", - "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", - "license": "MIT", - "dependencies": { - "@chakra-ui/number-utils": "2.0.7", - "@chakra-ui/react-use-callback-ref": "2.1.0", - "@chakra-ui/shared-utils": "2.0.5" - }, - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/form-control": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", - "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", - "license": "MIT", - "dependencies": { - "@chakra-ui/icon": "3.2.0", - "@chakra-ui/react-context": "2.1.0", - "@chakra-ui/react-types": "2.0.7", - "@chakra-ui/react-use-merge-refs": "2.1.0", - "@chakra-ui/shared-utils": "2.0.5" - }, - "peerDependencies": { - "@chakra-ui/system": ">=2.0.0", - "react": ">=18" - } - }, - "node_modules/@chakra-ui/icon": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", - "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", - "license": "MIT", - "dependencies": { - "@chakra-ui/shared-utils": "2.0.5" - }, - "peerDependencies": { - "@chakra-ui/system": ">=2.0.0", - "react": ">=18" - } - }, - "node_modules/@chakra-ui/number-input": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", - "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", - "license": "MIT", - "dependencies": { - "@chakra-ui/counter": "2.1.0", - "@chakra-ui/form-control": "2.2.0", - "@chakra-ui/icon": "3.2.0", - "@chakra-ui/react-context": "2.1.0", - "@chakra-ui/react-types": "2.0.7", - "@chakra-ui/react-use-callback-ref": "2.1.0", - "@chakra-ui/react-use-event-listener": "2.1.0", - "@chakra-ui/react-use-interval": "2.1.0", - "@chakra-ui/react-use-merge-refs": "2.1.0", - "@chakra-ui/react-use-safe-layout-effect": "2.1.0", - "@chakra-ui/react-use-update-effect": "2.1.0", - "@chakra-ui/shared-utils": "2.0.5" - }, - "peerDependencies": { - "@chakra-ui/system": ">=2.0.0", - "react": ">=18" - } - }, - "node_modules/@chakra-ui/number-utils": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", - "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==", - "license": "MIT" - }, - "node_modules/@chakra-ui/object-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", - "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@chakra-ui/react-context": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", - "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", - "license": "MIT", - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", - "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-use-callback-ref": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", - "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-use-event-listener": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", - "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", - "license": "MIT", - "dependencies": { - "@chakra-ui/react-use-callback-ref": "2.1.0" - }, - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-use-interval": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", - "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", - "license": "MIT", - "dependencies": { - "@chakra-ui/react-use-callback-ref": "2.1.0" - }, - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-use-merge-refs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", - "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-use-safe-layout-effect": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", - "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", - "license": "MIT", - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-use-update-effect": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", - "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", - "license": "MIT", - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/react-utils": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", - "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/utils": "2.0.15" - }, - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/@chakra-ui/shared-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", - "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==", - "license": "MIT" - }, - "node_modules/@chakra-ui/styled-system": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", - "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/shared-utils": "2.0.5", - "csstype": "^3.1.2", - "lodash.mergewith": "4.6.2" - } - }, - "node_modules/@chakra-ui/system": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", - "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/color-mode": "2.2.0", - "@chakra-ui/object-utils": "2.1.0", - "@chakra-ui/react-utils": "2.0.12", - "@chakra-ui/styled-system": "2.9.2", - "@chakra-ui/theme-utils": "2.0.21", - "@chakra-ui/utils": "2.0.15", - "react-fast-compare": "3.2.2" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0", - "@emotion/styled": "^11.0.0", - "react": ">=18" - } - }, - "node_modules/@chakra-ui/theme": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", - "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/anatomy": "2.2.2", - "@chakra-ui/shared-utils": "2.0.5", - "@chakra-ui/theme-tools": "2.1.2" - }, - "peerDependencies": { - "@chakra-ui/styled-system": ">=2.8.0" - } - }, - "node_modules/@chakra-ui/theme-tools": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", - "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/anatomy": "2.2.2", - "@chakra-ui/shared-utils": "2.0.5", - "color2k": "^2.0.2" - }, - "peerDependencies": { - "@chakra-ui/styled-system": ">=2.0.0" - } - }, - "node_modules/@chakra-ui/theme-utils": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", - "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@chakra-ui/shared-utils": "2.0.5", - "@chakra-ui/styled-system": "2.9.2", - "@chakra-ui/theme": "3.3.1", - "lodash.mergewith": "4.6.2" - } - }, - "node_modules/@chakra-ui/utils": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", - "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/lodash.mergewith": "4.6.7", - "css-box-model": "1.2.1", - "framesync": "6.1.2", - "lodash.mergewith": "4.6.2" - } - }, "node_modules/@clack/core": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz", @@ -1086,195 +798,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/styled": { - "version": "11.11.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", - "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.2", - "@emotion/serialize": "^1.1.4", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", - "license": "MIT", - "peer": true - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", - "license": "MIT", - "peer": true - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -5474,17 +4997,8 @@ "node_modules/@types/lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==" - }, - "node_modules/@types/lodash.mergewith": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", - "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/lodash": "*" - } + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", + "dev": true }, "node_modules/@types/mathjax": { "version": "0.0.37", @@ -5513,13 +5027,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT", - "peer": true - }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -5881,49 +5388,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/babel-plugin-macros/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.21.1.tgz", @@ -6880,13 +6344,6 @@ "color-support": "bin.js" } }, - "node_modules/color2k": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", - "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==", - "license": "MIT", - "peer": true - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6990,16 +6447,6 @@ "node": ">= 8" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "license": "MIT", - "peer": true, - "dependencies": { - "tiny-invariant": "^1.0.6" - } - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -8110,13 +7557,6 @@ "node": ">=8" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT", - "peer": true - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8281,23 +7721,6 @@ } } }, - "node_modules/framesync": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", - "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "2.4.0" - } - }, - "node_modules/framesync/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "license": "0BSD", - "peer": true - }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -9803,13 +9226,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "license": "MIT", - "peer": true - }, "node_modules/lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -12789,13 +12205,6 @@ "react": ">=16.13.1" } }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT", - "peer": true - }, "node_modules/react-hook-form": { "version": "7.52.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.0.tgz", @@ -14205,13 +13614,6 @@ "inline-style-parser": "0.1.1" } }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT", - "peer": true - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 3da2e1c5fdd..0b29f0c70a7 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -3,7 +3,6 @@ "version": "0.1.2", "private": true, "dependencies": { - "@chakra-ui/number-input": "^2.1.2", "@headlessui/react": "^2.0.4", "@hookform/resolvers": "^3.6.0", "@million/lint": "^1.0.0-rc.26", diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index e8eb15eefee..7da923df604 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -1,7 +1,3 @@ -import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template"; -import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; -import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value"; -import useAlertStore from "@/stores/alertStore"; import { cloneDeep } from "lodash"; import { ReactNode, useEffect, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; @@ -31,6 +27,10 @@ import { useShortcutsStore } from "../../../../stores/shortcuts"; import { useTypesStore } from "../../../../stores/typesStore"; import { APIClassType } from "../../../../types/api"; import { ParameterComponentType } from "../../../../types/components"; +import { + debouncedHandleUpdateValues, + handleUpdateValues, +} from "../../../../utils/parameterUtils"; import { convertObjToArray, convertValuesToNumbers, @@ -48,6 +48,8 @@ import { } from "../../../../utils/utils"; import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount"; import useHandleOnNewValue from "../../../hooks/use-handle-new-value"; +import useHandleNodeClass from "../../../hooks/use-handle-node-class"; +import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons"; import OutputComponent from "../OutputComponent"; import HandleRenderComponent from "../handleRenderComponent"; import OutputModal from "../outputModal"; @@ -79,12 +81,7 @@ export default function ParameterComponent({ const setNode = useFlowStore((state) => state.setNode); const myData = useTypesStore((state) => state.data); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); - const postTemplateValue = usePostTemplateValue({ - node: data.node!, - nodeId: data.id, - parameterId: name, - }); - const isLoading = postTemplateValue.isPending; + const [isLoading, setIsLoading] = useState(false); const updateNodeInternals = useUpdateNodeInternals(); const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); const setFilterEdge = useFlowStore((state) => state.setFilterEdge); @@ -134,25 +131,29 @@ export default function ParameterComponent({ } } - const setErrorData = useAlertStore((state) => state.setErrorData); - const output = useShortcutsStore((state) => state.output); useHotkeys(output, handleOutputWShortcut, { preventDefault }); + const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue( + data, + name, + takeSnapshot, + handleUpdateValues, + debouncedHandleUpdateValues, + setNode, + setIsLoading, + ); + const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass( + data, + name, takeSnapshot, setNode, - data.id, + updateNodeInternals, ); - const handleRefreshButtonPress = () => - mutateTemplate( - data.node?.template[name]?.value, - data.node!, - handleNodeClass, - postTemplateValue, - setErrorData, - ); + const { handleRefreshButtonPress: handleRefreshButtonPressHook } = + useHandleRefreshButtonPress(setIsLoading, setNode); let disabled = edges.some( @@ -166,24 +167,18 @@ export default function ParameterComponent({ edge.sourceHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id), ) ?? false; - const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue({ - node: data.node!, - nodeId: data.id, - name, - }); + const handleRefreshButtonPress = async (name, data) => { + handleRefreshButtonPressHook(name, data); + }; + + useFetchDataOnMount(data, name, handleUpdateValues, setNode, setIsLoading); - const handleOnNewValue = ( - value: any, + const handleOnNewValue = async ( + newValue: string | string[] | boolean | Object[], dbValue?: boolean, - skipSnapshot?: boolean, - ) => { - handleOnNewValueHook( - { - value, - load_from_db: dbValue, - }, - { skipSnapshot }, - ); + skipSnapshot: boolean | undefined = false, + ): Promise => { + handleOnNewValueHook(newValue, dbValue, skipSnapshot); }; const handleNodeClass = ( @@ -191,11 +186,9 @@ export default function ParameterComponent({ code?: string, type?: string, ): void => { - handleNodeClassHook(newNodeClass, name, code, type); + handleNodeClassHook(newNodeClass, code, type); }; - useFetchDataOnMount(data.node!, handleNodeClass, name, postTemplateValue); - useEffect(() => { // @ts-ignore infoHtml.current = ( @@ -621,7 +614,7 @@ export default function ParameterComponent({
, - setErrorData, - ) => { - try { - const newNode = cloneDeep(node); - const newTemplate = await postTemplateValue.mutateAsync({ - value: newValue, - }); - if (newTemplate) { - newNode.template = newTemplate; - } - setNodeClass(newNode); - } catch (e) { - const error = e as ResponseErrorDetailAPI; - setErrorData({ - title: TITLE_ERROR_UPDATING_COMPONENT, - list: [error.response?.data?.detail || ERROR_UPDATING_COMPONENT], - }); - } - }, - SAVE_DEBOUNCE_TIME, -); diff --git a/src/frontend/src/CustomNodes/hooks/use-fetch-data-on-mount.tsx b/src/frontend/src/CustomNodes/hooks/use-fetch-data-on-mount.tsx index 6379202fd79..497c560c2bd 100644 --- a/src/frontend/src/CustomNodes/hooks/use-fetch-data-on-mount.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-fetch-data-on-mount.tsx @@ -1,40 +1,55 @@ -import { - APIClassType, - APITemplateType, - ResponseErrorDetailAPI, -} from "@/types/api"; -import { UseMutationResult } from "@tanstack/react-query"; +import { cloneDeep } from "lodash"; import { useEffect } from "react"; +import { + ERROR_UPDATING_COMPONENT, + TITLE_ERROR_UPDATING_COMPONENT, +} from "../../constants/constants"; import useAlertStore from "../../stores/alertStore"; -import { mutateTemplate } from "../helpers/mutate-template"; +import { ResponseErrorDetailAPI } from "../../types/api"; +import { NodeDataType } from "../../types/flow"; const useFetchDataOnMount = ( - node: APIClassType, - setNodeClass: (node: APIClassType) => void, + data: NodeDataType, name: string, - postTemplateValue: UseMutationResult< - APITemplateType | undefined, - ResponseErrorDetailAPI, - any - >, + handleUpdateValues: (name: string, data: NodeDataType) => Promise, + setNode: (id: string, callback: (oldNode: any) => any) => void, + setIsLoading: (value: boolean) => void, ) => { const setErrorData = useAlertStore((state) => state.setErrorData); useEffect(() => { async function fetchData() { - const template = node.template[name]; if ( - (template?.real_time_refresh || template?.refresh_button) && + (data.node?.template[name]?.real_time_refresh || + data.node?.template[name]?.refresh_button) && // options can be undefined but not an empty array - (template?.options?.length ?? 0) === 0 + (data.node?.template[name]?.options?.length ?? 0) === 0 ) { - mutateTemplate( - template?.value, - node, - setNodeClass, - postTemplateValue, - setErrorData, - ); + setIsLoading(true); + try { + let newTemplate = await handleUpdateValues(name, data); + + if (newTemplate) { + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + }; + newNode.data.node.template = newTemplate; + return newNode; + }); + } + } catch (error) { + let responseError = error as ResponseErrorDetailAPI; + + setErrorData({ + title: TITLE_ERROR_UPDATING_COMPONENT, + list: [ + responseError?.response?.data?.detail ?? ERROR_UPDATING_COMPONENT, + ], + }); + } + setIsLoading(false); } } fetchData(); diff --git a/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx index bf457ff348d..5f294af5bc2 100644 --- a/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-handle-new-value.tsx @@ -1,91 +1,79 @@ -import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value"; -import useAlertStore from "@/stores/alertStore"; -import useFlowStore from "@/stores/flowStore"; -import useFlowsManagerStore from "@/stores/flowsManagerStore"; -import { APIClassType, InputFieldType } from "@/types/api"; import { cloneDeep } from "lodash"; -import { mutateTemplate } from "../helpers/mutate-template"; -const useHandleOnNewValue = ({ - node, - nodeId, - name, -}: { - node: APIClassType; - nodeId: string; - name: string; -}) => { - const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); - - const setNode = useFlowStore((state) => state.setNode); +import { + ERROR_UPDATING_COMPONENT, + TITLE_ERROR_UPDATING_COMPONENT, +} from "../../constants/constants"; +import useAlertStore from "../../stores/alertStore"; +import { ResponseErrorTypeAPI } from "../../types/api"; +import { NodeDataType } from "../../types/flow"; +const useHandleOnNewValue = ( + data: NodeDataType, + name: string, + takeSnapshot: () => void, + handleUpdateValues: (name: string, data: NodeDataType) => Promise, + debouncedHandleUpdateValues: any, + setNode: (id: string, callback: (oldNode: any) => any) => void, + setIsLoading: (value: boolean) => void, +) => { const setErrorData = useAlertStore((state) => state.setErrorData); - const postTemplateValue = usePostTemplateValue({ - parameterId: name, - nodeId: nodeId, - node: node, - }); - - const handleOnNewValue = async ( - changes: Partial, - options?: { - skipSnapshot?: boolean; - setNodeClass?: (node: APIClassType) => void; - }, - ) => { - const newNode = cloneDeep(node); - const template = newNode.template; + const handleOnNewValue = async (newValue, dbValue, skipSnapshot = false) => { + const nodeTemplate = data.node!.template[name]; + const currentValue = nodeTemplate.value; - if (!template) { - setErrorData({ title: "Template not found in the component" }); - return; + if (currentValue !== newValue && !skipSnapshot) { + takeSnapshot(); } - const parameter = template[name]; + const shouldUpdate = + data.node?.template[name].real_time_refresh && + !data.node?.template[name].refresh_button && + currentValue !== newValue; - if (!parameter) { - setErrorData({ title: "Parameter not found in the template" }); - return; - } + const typeToDebounce = nodeTemplate.type; - if (!options?.skipSnapshot) takeSnapshot(); + nodeTemplate.value = newValue; - Object.entries(changes).forEach(([key, value]) => { - parameter[key] = value; - }); + let newTemplate; + if (shouldUpdate) { + setIsLoading(true); + try { + if (["int"].includes(typeToDebounce)) { + newTemplate = await handleUpdateValues(name, data); + } else { + newTemplate = await debouncedHandleUpdateValues(name, data); + } + } catch (error) { + let responseError = error as ResponseErrorTypeAPI; + setErrorData({ + title: TITLE_ERROR_UPDATING_COMPONENT, + list: [ + responseError?.response?.data?.detail.error ?? + ERROR_UPDATING_COMPONENT, + ], + }); + } + setIsLoading(false); + } - const shouldUpdate = - parameter.real_time_refresh && !parameter.refresh_button; + setNode(data.id, (oldNode) => { + const newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + }; - const setNodeClass = (newNodeClass: APIClassType) => { - options?.setNodeClass && options.setNodeClass(newNodeClass); - setNode(nodeId, (oldNode) => { - const newData = cloneDeep(oldNode.data); - newData.node = newNodeClass; - return { - ...oldNode, - data: newData, - }; - }); - }; + if (dbValue !== undefined) { + newNode.data.node.template[name].load_from_db = dbValue; + } - if (shouldUpdate && changes.value) { - mutateTemplate( - changes.value, - newNode, - setNodeClass, - postTemplateValue, - setErrorData, - ); - } + if (data.node?.template[name].real_time_refresh && newTemplate) { + newNode.data.node.template = newTemplate; + } else { + newNode.data.node.template[name].value = newValue; + } - setNode(nodeId, (oldNode) => { - const newData = cloneDeep(oldNode.data); - newData.node = newNode; - return { - ...oldNode, - data: newData, - }; + return newNode; }); }; diff --git a/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx index 6c65ae4a302..0827c0b4ebb 100644 --- a/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-handle-node-class.tsx @@ -1,31 +1,37 @@ import { cloneDeep } from "lodash"; +import { NodeDataType } from "../../types/flow"; const useHandleNodeClass = ( + data: NodeDataType, + name: string, takeSnapshot: () => void, setNode: (id: string, callback: (oldNode: any) => any) => void, - nodeId: string, + updateNodeInternals: (id: string) => void, ) => { - const handleNodeClass = (newNodeClass, name, code, type?: string) => { - if (code) { + const handleNodeClass = (newNodeClass, code, type?: string) => { + if (!data.node) return; + if (data.node!.template[name].value !== code) { takeSnapshot(); } - setNode(nodeId, (oldNode) => { + setNode(data.id, (oldNode) => { let newNode = cloneDeep(oldNode); newNode.data = { ...newNode.data, - node: cloneDeep(newNodeClass), + node: newNodeClass, + description: newNodeClass.description ?? data.node!.description, + display_name: newNodeClass.display_name ?? data.node!.display_name, }; if (type) { newNode.data.type = type; } - if (code) { - newNode.data.node.template[name].value = code; - } + newNode.data.node.template[name].value = code; return newNode; }); + + updateNodeInternals(data.id); }; return { handleNodeClass }; diff --git a/src/frontend/src/CustomNodes/hooks/use-handle-refresh-buttons.tsx b/src/frontend/src/CustomNodes/hooks/use-handle-refresh-buttons.tsx new file mode 100644 index 00000000000..65345fceac7 --- /dev/null +++ b/src/frontend/src/CustomNodes/hooks/use-handle-refresh-buttons.tsx @@ -0,0 +1,47 @@ +import { cloneDeep } from "lodash"; +import { + ERROR_UPDATING_COMPONENT, + TITLE_ERROR_UPDATING_COMPONENT, +} from "../../constants/constants"; +import useAlertStore from "../../stores/alertStore"; +import { ResponseErrorDetailAPI } from "../../types/api"; +import { handleUpdateValues } from "../../utils/parameterUtils"; + +const useHandleRefreshButtonPress = ( + setIsLoading: (value: boolean) => void, + setNode: (id: string, callback: (oldNode: any) => any) => void, +) => { + const setErrorData = useAlertStore((state) => state.setErrorData); + + const handleRefreshButtonPress = async (name, data) => { + setIsLoading(true); + try { + let newTemplate = await handleUpdateValues(name, data); + + if (newTemplate) { + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + }; + newNode.data.node.template = newTemplate; + return newNode; + }); + } + } catch (error) { + let responseError = error as ResponseErrorDetailAPI; + + setErrorData({ + title: TITLE_ERROR_UPDATING_COMPONENT, + list: [ + responseError?.response?.data?.detail ?? ERROR_UPDATING_COMPONENT, + ], + }); + } + setIsLoading(false); + }; + + return { handleRefreshButtonPress }; +}; + +export default useHandleRefreshButtonPress; diff --git a/src/frontend/src/components/floatComponent/index.tsx b/src/frontend/src/components/floatComponent/index.tsx index 07de1fbb253..3470cc137c3 100644 --- a/src/frontend/src/components/floatComponent/index.tsx +++ b/src/frontend/src/components/floatComponent/index.tsx @@ -24,6 +24,7 @@ export default function FloatComponent({
(null); - const ref = useRef(null); - - useEffect(() => { - ref.current?.setSelectionRange(cursor, cursor); - }, [ref, cursor, value]); - - const handleChangeInput = (e: React.ChangeEvent) => { - setCursor(e.target.selectionStart); - onChange(e.target.value); - }; - return (
- { + handleOnlyIntegerInput(event); + handleKeyDown(event, value, ""); + }} + type="number" step={rangeSpec?.step ?? 1} min={rangeSpec?.min ?? min} max={rangeSpec?.max ?? undefined} - onChange={(value) => { - onChange(value); + onInput={(event: React.ChangeEvent) => { + if (Number(event.target.value) < min) { + event.target.value = min.toString(); + } }} value={value ?? ""} - > - { - handleKeyDown(event, value, ""); - }} - onInput={(event: React.ChangeEvent) => { - if (Number(event.target.value) < min) { - event.target.value = min.toString(); - } - }} - disabled={disabled} - placeholder={editNode ? "Integer number" : "Type an integer number"} - data-testid={id} - ref={ref} - /> - - - - - + className={editNode ? "input-edit-node" : ""} + disabled={disabled} + placeholder={editNode ? "Integer number" : "Type an integer number"} + onChange={(event) => { + onChange(event.target.value); + }} + data-testid={id} + />
); } diff --git a/src/frontend/src/components/multiselectComponent/index.tsx b/src/frontend/src/components/multiselectComponent/index.tsx index 69d137dfe98..e294c4edd69 100644 --- a/src/frontend/src/components/multiselectComponent/index.tsx +++ b/src/frontend/src/components/multiselectComponent/index.tsx @@ -132,9 +132,9 @@ export const Multiselect = forwardRef< ) => { // if elements in values are strings, create the multiselectValue object // otherwise, use the values as is - const value = values?.map((v) => - typeof v === "string" ? { label: v, value: v } : v, - ); + const value = Array.isArray(values) + ? values?.map((v) => (typeof v === "string" ? { label: v, value: v } : v)) + : []; const [selectedValues, setSelectedValues] = useState( value || [], diff --git a/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx b/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx index 4dd87fc50d6..182e1d32583 100644 --- a/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx +++ b/src/frontend/src/components/sidebarComponent/hooks/use-on-file-drop.tsx @@ -3,7 +3,10 @@ import { WRONG_FILE_ERROR_ALERT, } from "../../../constants/alerts_constants"; import { updateFlowInDatabase } from "../../../controllers/API"; -import { uploadFlowsFromFolders } from "../../../pages/MainPage/services"; +import { + uploadFlowToFolder, + uploadFlowsFromFolders, +} from "../../../pages/MainPage/services"; import useAlertStore from "../../../stores/alertStore"; import useFlowsManagerStore from "../../../stores/flowsManagerStore"; import { useFolderStore } from "../../../stores/foldersStore"; @@ -131,7 +134,8 @@ const useFileDrop = ( formData.append("file", data); setFolderDragging(false); setFolderIdDragging(""); - uploadFlowsFromFolders(formData).then(() => { + + uploadFlowToFolder(formData, folderId).then(() => { refreshFolders(); triggerFolderChange(folderId); }); diff --git a/src/frontend/src/components/tableComponent/components/tableNodeCellRender/cellTypeStr.tsx b/src/frontend/src/components/tableComponent/components/tableNodeCellRender/cellTypeStr.tsx index 260a86242cb..7b32ab2324f 100644 --- a/src/frontend/src/components/tableComponent/components/tableNodeCellRender/cellTypeStr.tsx +++ b/src/frontend/src/components/tableComponent/components/tableNodeCellRender/cellTypeStr.tsx @@ -51,7 +51,7 @@ export function renderStrType({ editNode={true} disabled={disabled} options={templateData.options || []} - values={[templateValue ?? "Choose an option"]} + value={templateValue ?? "Choose an option"} id={"multiselect-" + templateData.name} onValueChange={(value) => handleOnNewValue(value, templateData.key)} /> diff --git a/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx b/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx index 60689451f58..83c1d1d253e 100644 --- a/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx +++ b/src/frontend/src/components/tableComponent/components/tableNodeCellRender/index.tsx @@ -1,4 +1,3 @@ -import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value"; import { CustomCellRendererProps } from "ag-grid-react"; import { cloneDeep } from "lodash"; import { useState } from "react"; @@ -13,18 +12,39 @@ import { import { classNames } from "../../../../utils/utils"; import CodeAreaComponent from "../../../codeAreaComponent"; import DictComponent from "../../../dictComponent"; +import Dropdown from "../../../dropdownComponent"; import FloatComponent from "../../../floatComponent"; import InputFileComponent from "../../../inputFileComponent"; +import InputGlobalComponent from "../../../inputGlobalComponent"; +import InputListComponent from "../../../inputListComponent"; import IntComponent from "../../../intComponent"; import KeypairListComponent from "../../../keypairListComponent"; import PromptAreaComponent from "../../../promptComponent"; +import TextAreaComponent from "../../../textAreaComponent"; import ToggleShadComponent from "../../../toggleShadComponent"; import { renderStrType } from "./cellTypeStr"; export default function TableNodeCellRender({ node: { data }, - value: { value, nodeId, nodeClass, handleNodeClass }, + value: { + value, + nodeClass, + handleOnNewValue: handleOnNewValueNode, + handleNodeClass, + }, }: CustomCellRendererProps) { + const handleOnNewValue = (newValue: any, name: string, dbValue?: boolean) => { + handleOnNewValueNode(newValue, name, dbValue); + setTemplateData((old) => { + let newData = cloneDeep(old); + newData.value = newValue; + if (dbValue !== undefined) { + newData.load_from_db = newValue; + } + return newData; + }); + setTemplateValue(newValue); + }; const setNodeClass = (value: APIClassType, code?: string, type?: string) => { handleNodeClass(value, templateData.key, code, type); }; @@ -34,34 +54,6 @@ export default function TableNodeCellRender({ const [errorDuplicateKey, setErrorDuplicateKey] = useState(false); const edges = useFlowStore((state) => state.edges); - const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue({ - node: nodeClass, - nodeId: nodeId, - name: data.key, - }); - const handleOnNewValue = ( - value: any, - dbValue?: boolean, - skipSnapshot?: boolean, - ) => { - handleOnNewValueHook( - { - value, - load_from_db: dbValue, - }, - { skipSnapshot, setNodeClass }, - ); - setTemplateData((old) => { - let newData = cloneDeep(old); - newData.value = value; - if (dbValue !== undefined) { - newData.load_from_db = value; - } - return newData; - }); - setTemplateValue(value); - }; - const id = { inputTypes: templateData.input_types, type: templateData.type, diff --git a/src/frontend/src/controllers/API/helpers/constants.ts b/src/frontend/src/controllers/API/helpers/constants.ts index 4775befc0cf..8717078878e 100644 --- a/src/frontend/src/controllers/API/helpers/constants.ts +++ b/src/frontend/src/controllers/API/helpers/constants.ts @@ -13,7 +13,6 @@ export const URLs = { AUTOLOGIN: "auto_login", REFRESH: "refresh", BUILD: `build`, - CUSTOM_COMPONENT: `custom_component`, } as const; export function getURL(key: keyof typeof URLs, params: any = {}) { diff --git a/src/frontend/src/controllers/API/queries/nodes/use-post-template-value.ts b/src/frontend/src/controllers/API/queries/nodes/use-post-template-value.ts deleted file mode 100644 index 73d339bbbaf..00000000000 --- a/src/frontend/src/controllers/API/queries/nodes/use-post-template-value.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - APIClassType, - APITemplateType, - ResponseErrorDetailAPI, - useMutationFunctionType, -} from "@/types/api"; -import { UseMutationResult } from "@tanstack/react-query"; -import { api } from "../../api"; -import { getURL } from "../../helpers/constants"; -import { UseRequestProcessor } from "../../services/request-processor"; - -interface IPostTemplateValue { - value: any; -} - -interface IPostTemplateValueParams { - node: APIClassType; - nodeId: string; - parameterId: string; -} - -export const usePostTemplateValue: useMutationFunctionType< - IPostTemplateValueParams, - IPostTemplateValue, - APITemplateType | undefined, - ResponseErrorDetailAPI -> = ({ parameterId, nodeId, node }, options?) => { - const { mutate } = UseRequestProcessor(); - - const postTemplateValueFn = async ( - payload: IPostTemplateValue, - ): Promise => { - const template = node.template; - - if (!template) return; - - const response = await api.post( - getURL("CUSTOM_COMPONENT", { update: "update" }), - { - code: template.code.value, - template: template, - field: parameterId, - field_value: payload.value, - }, - ); - - return response.data.template; - }; - - const mutation: UseMutationResult< - APITemplateType | undefined, - ResponseErrorDetailAPI, - IPostTemplateValue - > = mutate( - ["usePostTemplateValue", { parameterId, nodeId }], - postTemplateValueFn, - options, - ); - - return mutation; -}; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx index 36ba4803a18..e6a2af27aa9 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx @@ -1,17 +1,4 @@ -import ShortUniqueId from "short-unique-id"; -import { - ALLOWED_IMAGE_INPUT_EXTENSIONS, - FS_ERROR_TEXT, - SN_ERROR_TEXT, -} from "../../../../../../constants/constants"; -// import useFileUpload from "./use-file-upload"; - -const useDragAndDrop = ( - setIsDragging: (value: boolean) => void, - setFiles: (value: any) => void, - currentFlowId: string, - setErrorData: (value: any) => void, -) => { +const useDragAndDrop = (setIsDragging: (value: boolean) => void) => { const dragOver = (e) => { e.preventDefault(); if (e.dataTransfer.types.some((type) => type === "Files")) { diff --git a/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.tsx b/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.tsx index c6ccd1b54ea..b6230e3a67a 100644 --- a/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.tsx +++ b/src/frontend/src/modals/editNodeModal/hooks/use-column-defs.tsx @@ -1,18 +1,19 @@ import { ColDef, ValueGetterParams } from "ag-grid-community"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import TableNodeCellRender from "../../../components/tableComponent/components/tableNodeCellRender"; import TableToggleCellRender from "../../../components/tableComponent/components/tableToggleCellRender"; import { APIClassType } from "../../../types/api"; +import { NodeDataType } from "../../../types/flow"; const useColumnDefs = ( nodeClass: APIClassType, + handleOnNewValue: (newValue: any, name: string, setDb?: boolean) => void, handleNodeClass: ( newNodeClass: APIClassType, name: string, code: string, type?: string, ) => void, - nodeId: string, changeAdvanced: (n: string) => void, open: boolean, ) => { @@ -52,8 +53,8 @@ const useColumnDefs = ( valueGetter: (params: ValueGetterParams) => { return { value: params.data.value, - nodeId: nodeId, nodeClass: nodeClass, + handleOnNewValue: handleOnNewValue, handleNodeClass: handleNodeClass, }; }, diff --git a/src/frontend/src/modals/editNodeModal/hooks/use-handle-new-value.tsx b/src/frontend/src/modals/editNodeModal/hooks/use-handle-new-value.tsx new file mode 100644 index 00000000000..58351fd2d46 --- /dev/null +++ b/src/frontend/src/modals/editNodeModal/hooks/use-handle-new-value.tsx @@ -0,0 +1,84 @@ +import { cloneDeep } from "lodash"; +import { + ERROR_UPDATING_COMPONENT, + TITLE_ERROR_UPDATING_COMPONENT, +} from "../../../constants/constants"; +import useAlertStore from "../../../stores/alertStore"; +import { ResponseErrorTypeAPI } from "../../../types/api"; +import { NodeDataType } from "../../../types/flow"; + +const useHandleOnNewValue = ( + data: NodeDataType, + takeSnapshot: () => void, + handleUpdateValues: (name: string, data: NodeDataType) => Promise, + debouncedHandleUpdateValues: any, + setNode: (id: string, callback: (oldNode: any) => any) => void, +) => { + const setErrorData = useAlertStore((state) => state.setErrorData); + + const handleOnNewValue = async ( + newValue, + name, + dbValue, + skipSnapshot = false, + ) => { + const nodeTemplate = data.node!.template[name]; + const currentValue = nodeTemplate.value; + + if (currentValue !== newValue && !skipSnapshot) { + takeSnapshot(); + } + + const shouldUpdate = + data.node?.template[name].real_time_refresh && + !data.node?.template[name].refresh_button && + currentValue !== newValue; + + const typeToDebounce = nodeTemplate.type; + + nodeTemplate.value = newValue; + + let newTemplate; + if (shouldUpdate) { + try { + if (["int"].includes(typeToDebounce)) { + newTemplate = await handleUpdateValues(name, data); + } else { + newTemplate = await debouncedHandleUpdateValues(name, data); + } + } catch (error) { + let responseError = error as ResponseErrorTypeAPI; + setErrorData({ + title: TITLE_ERROR_UPDATING_COMPONENT, + list: [ + responseError?.response?.data?.detail.error ?? + ERROR_UPDATING_COMPONENT, + ], + }); + } + } + + setNode(data.id, (oldNode) => { + const newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + }; + + if (dbValue !== undefined) { + newNode.data.node.template[name].load_from_db = dbValue; + } + + if (data.node?.template[name].real_time_refresh && newTemplate) { + newNode.data.node.template = newTemplate; + } else { + newNode.data.node.template[name].value = newValue; + } + + return newNode; + }); + }; + + return { handleOnNewValue }; +}; + +export default useHandleOnNewValue; diff --git a/src/frontend/src/modals/editNodeModal/hooks/use-handle-node-class.tsx b/src/frontend/src/modals/editNodeModal/hooks/use-handle-node-class.tsx new file mode 100644 index 00000000000..9ad9eec62b7 --- /dev/null +++ b/src/frontend/src/modals/editNodeModal/hooks/use-handle-node-class.tsx @@ -0,0 +1,39 @@ +import { cloneDeep } from "lodash"; +import { NodeDataType } from "../../../types/flow"; + +const useHandleNodeClass = ( + data: NodeDataType, + takeSnapshot: () => void, + setNode: (id: string, callback: (oldNode: any) => any) => void, + updateNodeInternals: (id: string) => void, +) => { + const handleNodeClass = (newNodeClass, name, code, type?: string) => { + if (!data.node) return; + if (data.node!.template[name].value !== code) { + takeSnapshot(); + } + + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + + newNode.data = { + ...newNode.data, + node: newNodeClass, + description: newNodeClass.description ?? data.node!.description, + display_name: newNodeClass.display_name ?? data.node!.display_name, + }; + if (type) { + newNode.data.node.template[name].type = type; + } + newNode.data.node.template[name].value = code; + + return newNode; + }); + + updateNodeInternals(data.id); + }; + + return { handleNodeClass }; +}; + +export default useHandleNodeClass; diff --git a/src/frontend/src/modals/editNodeModal/index.tsx b/src/frontend/src/modals/editNodeModal/index.tsx index 881158df1c5..0fb52b128e2 100644 --- a/src/frontend/src/modals/editNodeModal/index.tsx +++ b/src/frontend/src/modals/editNodeModal/index.tsx @@ -1,7 +1,6 @@ import { ColDef } from "ag-grid-community"; import { forwardRef, useState } from "react"; import { useUpdateNodeInternals } from "reactflow"; -import useHandleNodeClass from "../../CustomNodes/hooks/use-handle-node-class"; import TableComponent from "../../components/tableComponent"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; @@ -10,9 +9,15 @@ import useFlowStore from "../../stores/flowStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; import { APIClassType } from "../../types/api"; import { NodeDataType } from "../../types/flow"; +import { + debouncedHandleUpdateValues, + handleUpdateValues, +} from "../../utils/parameterUtils"; import BaseModal from "../baseModal"; import useColumnDefs from "./hooks/use-column-defs"; import useHandleChangeAdvanced from "./hooks/use-handle-change-advanced"; +import useHandleOnNewValue from "./hooks/use-handle-new-value"; +import useHandleNodeClass from "./hooks/use-handle-node-class"; import useRowData from "./hooks/use-row-data"; const EditNodeModal = forwardRef( @@ -35,10 +40,19 @@ const EditNodeModal = forwardRef( const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const updateNodeInternals = useUpdateNodeInternals(); + const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue( + data, + takeSnapshot, + handleUpdateValues, + debouncedHandleUpdateValues, + setNode, + ); + const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass( + data, takeSnapshot, setNode, - data.id, + updateNodeInternals, ); const [nodeClass, setNodeClass] = useState(data.node!); @@ -60,8 +74,8 @@ const EditNodeModal = forwardRef( const columnDefs: ColDef[] = useColumnDefs( nodeClass, + handleOnNewValueHook, handleNodeClass, - data.id, handleChangeAdvancedHook, open, ); diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 50a8fa23e8f..f3cbebf961c 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,7 +1,4 @@ -import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value"; -import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex"; -import { APIClassType } from "@/types/api"; import _, { cloneDeep } from "lodash"; import { useEffect, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; @@ -25,6 +22,7 @@ import useFlowStore from "../../../../stores/flowStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import { useShortcutsStore } from "../../../../stores/shortcuts"; import { useStoreStore } from "../../../../stores/storeStore"; +import { APIClassType } from "../../../../types/api"; import { nodeToolbarPropsType } from "../../../../types/components"; import { FlowType } from "../../../../types/flow"; import { @@ -33,7 +31,7 @@ import { expandGroupNode, updateFlowPosition, } from "../../../../utils/reactflowUtils"; -import { classNames, cn } from "../../../../utils/utils"; +import { classNames, cn, isThereModal } from "../../../../utils/utils"; import isWrappedWithClass from "../PageComponent/utils/is-wrapped-with-class"; import ToolbarSelectItem from "./toolbarSelectItem"; @@ -386,28 +384,57 @@ export default function NodeToolbarComponent({ const setNode = useFlowStore((state) => state.setNode); - const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue({ - node: data.node!, - nodeId: data.id, - name, - }); + const handleOnNewValue = ( + newValue: string | string[] | boolean | Object[], + ): void => { + if (data.node!.template[name].value !== newValue) { + takeSnapshot(); + } - const handleOnNewValue = (value: string | string[]) => { - handleOnNewValueHook({ value }); - }; + data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input - const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass( - takeSnapshot, - setNode, - data.id, - ); + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + + newNode.data = { + ...newNode.data, + }; + + newNode.data.node.template[name].value = newValue; + + return newNode; + }); + }; const handleNodeClass = ( newNodeClass: APIClassType, - code: string, - type: string, - ) => { - handleNodeClassHook(newNodeClass, name, code, type); + code?: string, + type?: string, + ): void => { + if (!data.node) return; + if (data.node!.template[name].value !== code) { + takeSnapshot(); + } + + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + + newNode.data = { + ...newNode.data, + node: newNodeClass, + description: newNodeClass.description ?? data.node!.description, + display_name: newNodeClass.display_name ?? data.node!.display_name, + }; + + if (type) { + newNode.data.type = type; + } + + newNode.data.node.template[name].value = code; + + return newNode; + }); + updateNodeInternals(data.id); }; const [openModal, setOpenModal] = useState(false); diff --git a/src/frontend/src/pages/MainPage/services/index.ts b/src/frontend/src/pages/MainPage/services/index.ts index 3e1286d5ef1..f9750fe424e 100644 --- a/src/frontend/src/pages/MainPage/services/index.ts +++ b/src/frontend/src/pages/MainPage/services/index.ts @@ -110,3 +110,22 @@ export async function moveFlowToFolder( throw error; } } + +export async function uploadFlowToFolder( + flows: FormData, + folderId: string, +): Promise { + try { + const url = `${BASE_URL_API}flows/upload/?folder_id=${encodeURIComponent(folderId)}`; + + const response = await api.post(url, flows); + + if (response?.status !== 201) { + throw new Error(`HTTP error! status: ${response?.status}`); + } + return response.data; + } catch (error) { + console.error(error); + throw error; + } +} diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 85deddbc508..7c5c60579f0 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -171,7 +171,7 @@ export type CodeAreaComponentType = { value: string; editNode?: boolean; nodeClass?: APIClassType; - setNodeClass?: (value: APIClassType, code: string, type: string) => void; + setNodeClass?: (value: APIClassType, code?: string) => void; dynamic?: boolean; id?: string; readonly?: boolean; @@ -612,8 +612,8 @@ export type codeAreaModalPropsType = { nodeClass: APIClassType | undefined; setNodeClass: ( Class: APIClassType, - code: string, - type: string, + code?: string, + type?: string, ) => void | undefined; children: ReactNode; dynamic?: boolean; diff --git a/src/frontend/tests/end-to-end/Basic Prompting.spec.ts b/src/frontend/tests/end-to-end/Basic Prompting.spec.ts index 12c962424fa..d1776ce8083 100644 --- a/src/frontend/tests/end-to-end/Basic Prompting.spec.ts +++ b/src/frontend/tests/end-to-end/Basic Prompting.spec.ts @@ -42,8 +42,16 @@ test("Basic Prompting (Hello, World)", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); diff --git a/src/frontend/tests/end-to-end/Blog Writer.spec.ts b/src/frontend/tests/end-to-end/Blog Writer.spec.ts index 4c9b26cccd4..ea8564a90db 100644 --- a/src/frontend/tests/end-to-end/Blog Writer.spec.ts +++ b/src/frontend/tests/end-to-end/Blog Writer.spec.ts @@ -39,12 +39,20 @@ test("Blog Writer", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); await page diff --git a/src/frontend/tests/end-to-end/Document QA.spec.ts b/src/frontend/tests/end-to-end/Document QA.spec.ts index a9e733b2d00..4480b5ca532 100644 --- a/src/frontend/tests/end-to-end/Document QA.spec.ts +++ b/src/frontend/tests/end-to-end/Document QA.spec.ts @@ -39,12 +39,20 @@ test("Document QA", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); const fileChooserPromise = page.waitForEvent("filechooser"); diff --git a/src/frontend/tests/end-to-end/Memory Chatbot.spec.ts b/src/frontend/tests/end-to-end/Memory Chatbot.spec.ts index 8f07f0ba600..c43b58792a6 100644 --- a/src/frontend/tests/end-to-end/Memory Chatbot.spec.ts +++ b/src/frontend/tests/end-to-end/Memory Chatbot.spec.ts @@ -39,12 +39,22 @@ test("Memory Chatbot", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + await page.waitForTimeout(1000); + + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); diff --git a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts index 598a4caf2a7..0a390a000c3 100644 --- a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts +++ b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts @@ -43,12 +43,20 @@ test("user must be able to send an image on chat", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForSelector("text=Chat Input", { timeout: 30000 }); diff --git a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-1.spec.ts b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-1.spec.ts index 2eefc6985c3..55771b9acfc 100644 --- a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-1.spec.ts +++ b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-1.spec.ts @@ -43,17 +43,29 @@ test("user must be able to see output inspection", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); await page.getByTestId("button_run_chat output").last().click(); - await page.waitForTimeout(5000); + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); await page.waitForSelector('[data-testid="icon-ScanEye"]', { timeout: 30000, diff --git a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-2.spec.ts b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-2.spec.ts index 9a5232a5fd5..c2f55866513 100644 --- a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-2.spec.ts +++ b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-2.spec.ts @@ -43,12 +43,20 @@ test("user must interact with chat with Input/Output", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); await page.getByText("Playground", { exact: true }).click(); diff --git a/src/frontend/tests/end-to-end/decisionFlow.spec.ts b/src/frontend/tests/end-to-end/decisionFlow.spec.ts index 30cdd41b9f0..d5c90394044 100644 --- a/src/frontend/tests/end-to-end/decisionFlow.spec.ts +++ b/src/frontend/tests/end-to-end/decisionFlow.spec.ts @@ -548,9 +548,12 @@ AI: await page.locator('//*[@id="react-flow-id"]').hover(); await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); + await page.getByTestId("dropdown-model_name").click(); + await page.getByTestId("gpt-4o-1-option").click(); + await page.getByLabel("fit view").click(); await page.getByText("Playground", { exact: true }).click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { diff --git a/src/frontend/tests/end-to-end/filterSidebar.spec.ts b/src/frontend/tests/end-to-end/filterSidebar.spec.ts index c07cffd4bf8..e4a30ae281e 100644 --- a/src/frontend/tests/end-to-end/filterSidebar.spec.ts +++ b/src/frontend/tests/end-to-end/filterSidebar.spec.ts @@ -60,7 +60,7 @@ test("LLMChain - Filter", async ({ page }) => { await expect(page.getByTestId("promptsPrompt")).toBeVisible(); await expect(page.getByTestId("modelsAmazon Bedrock")).toBeVisible(); await expect(page.getByTestId("helpersChat Memory")).toBeVisible(); - await expect(page.getByTestId("agentsTool Calling Agent")).toBeVisible(); + await expect(page.getByTestId("agentsCSVAgent")).toBeVisible(); await expect(page.getByTestId("chainsConversationChain")).toBeVisible(); await expect(page.getByTestId("prototypesConditional Router")).toBeVisible(); diff --git a/src/frontend/tests/end-to-end/folders.spec.ts b/src/frontend/tests/end-to-end/folders.spec.ts index d8e4e5dc48f..f7e44258793 100644 --- a/src/frontend/tests/end-to-end/folders.spec.ts +++ b/src/frontend/tests/end-to-end/folders.spec.ts @@ -67,13 +67,19 @@ test("CRUD folders", async ({ page }) => { test("add folder by drag and drop", async ({ page }) => { await page.goto("/"); - await page.waitForTimeout(2000); + + await page.waitForTimeout(5000); // Consider using a more reliable waiting mechanism const jsonContent = readFileSync( "tests/end-to-end/assets/collection.json", "utf-8", ); + // Wait for the target element to be available before evaluation + await page.waitForSelector( + '//*[@id="root"]/div/div[1]/div[2]/div[3]/aside/nav/div/div[2]', + ); + // Create the DataTransfer and File const dataTransfer = await page.evaluateHandle((data) => { const dt = new DataTransfer(); diff --git a/src/frontend/tests/end-to-end/freeze-path.spec.ts b/src/frontend/tests/end-to-end/freeze-path.spec.ts new file mode 100644 index 00000000000..4960ef85b73 --- /dev/null +++ b/src/frontend/tests/end-to-end/freeze-path.spec.ts @@ -0,0 +1,286 @@ +import { expect, test } from "@playwright/test"; +import * as dotenv from "dotenv"; +import path from "path"; + +test("user must be able to freeze a path", async ({ page }) => { + test.skip( + !process?.env?.OPENAI_API_KEY, + "OPENAI_API_KEY required to run this test", + ); + + const codeOpenAI = ` +import operator +from functools import reduce + +from langchain_openai import ChatOpenAI +from pydantic.v1 import SecretStr + +from langflow.base.constants import STREAM_INFO_TEXT +from langflow.base.models.model import LCModelComponent +from langflow.base.models.openai_constants import MODEL_NAMES +from langflow.field_typing import LanguageModel +from langflow.inputs import ( + BoolInput, + DictInput, + DropdownInput, + FloatInput, + IntInput, + MessageInput, + SecretStrInput, + StrInput, +) + + +class OpenAIModelComponent(LCModelComponent): + display_name = "OpenAI" + description = "Generates text using OpenAI LLMs." + icon = "OpenAI" + name = "OpenAIModel" + + inputs = [ + MessageInput(name="input_value", display_name="Input"), + IntInput( + name="max_tokens", + display_name="Max Tokens", + advanced=True, + info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.", + ), + DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True), + BoolInput( + name="json_mode", + display_name="JSON Mode", + advanced=True, + info="If True, it will output JSON regardless of passing a schema.", + ), + DictInput( + name="output_schema", + is_list=True, + display_name="Schema", + advanced=True, + info="The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.", + ), + DropdownInput( + name="model_name", display_name="Model Name", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0] + ), + StrInput( + name="openai_api_base", + display_name="OpenAI API Base", + advanced=True, + info="The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.", + ), + SecretStrInput( + name="api_key", + display_name="OpenAI API Key", + info="The OpenAI API Key to use for the OpenAI model.", + advanced=False, + value="OPENAI_API_KEY", + ), + FloatInput(name="temperature", display_name="Temperature", value=0.1), + BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True), + StrInput( + name="system_message", + display_name="System Message", + info="System message to pass to the model.", + advanced=True, + ), + IntInput( + name="seed", + display_name="Seed", + info="The seed controls the reproducibility of the job.", + advanced=True, + value=1, + ), + ] + + def build_model(self) -> LanguageModel: # type: ignore[type-var] + # self.output_schema is a list of dictionaries + # let's convert it to a dictionary + output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {}) + openai_api_key = self.api_key + temperature = self.temperature + model_name: str = self.model_name + max_tokens = self.max_tokens + model_kwargs = self.model_kwargs or {} + openai_api_base = self.openai_api_base or "https://api.openai.com/v1" + json_mode = bool(output_schema_dict) or self.json_mode + seed = self.seed + + if openai_api_key: + api_key = SecretStr(openai_api_key) + else: + api_key = None + output = ChatOpenAI( + max_tokens=max_tokens or None, + model_kwargs=model_kwargs, + model=model_name, + base_url=openai_api_base, + api_key=api_key, + temperature=0.8, + seed=seed, + ) + if json_mode: + if output_schema_dict: + output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore + else: + output = output.bind(response_format={"type": "json_object"}) # type: ignore + + return output # type: ignore + + def _get_exception_message(self, e: Exception): + """ + Get a message from an OpenAI exception. + + Args: + exception (Exception): The exception to get the message from. + + Returns: + str: The message from the exception. + """ + + try: + from openai import BadRequestError + except ImportError: + return + if isinstance(e, BadRequestError): + message = e.body.get("message") # type: ignore + if message: + return message + return + + `; + + if (!process.env.CI) { + dotenv.config({ path: path.resolve(__dirname, "../../.env") }); + } + + await page.goto("/"); + await page.waitForTimeout(2000); + + let modalCount = 0; + try { + const modalTitleElement = await page?.getByTestId("modal-title"); + if (modalTitleElement) { + modalCount = await modalTitleElement.count(); + } + } catch (error) { + modalCount = 0; + } + + while (modalCount === 0) { + await page.getByText("New Project", { exact: true }).click(); + await page.waitForTimeout(5000); + modalCount = await page.getByTestId("modal-title")?.count(); + } + + await page.getByRole("heading", { name: "Basic Prompting" }).click(); + + await page.waitForSelector('[title="fit view"]', { + timeout: 100000, + }); + + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + + await page + .getByTestId("popover-anchor-input-api_key") + .fill(process.env.OPENAI_API_KEY ?? ""); + + await page + .getByTestId("textarea-input_value") + .first() + .fill( + "say a random number between 1 and 100000 and a random animal that lives in the sea", + ); + + await page.getByTestId("dropdown-model_name").click(); + await page.getByTestId("gpt-4o-1-option").click(); + + await page.getByText("OpenAI").first().click(); + + await page.getByTestId("code-button-modal").first().click(); + + await page.locator("textarea").press("Control+a"); + await page.locator("textarea").fill(codeOpenAI); + await page.locator('//*[@id="checkAndSaveBtn"]').click(); + + await page.waitForTimeout(2000); + + await page.getByTestId("button_run_chat output").click(); + + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); + + await page.getByTestId("output-inspection-text").first().click(); + + const randomTextGeneratedByAI = await page + .getByPlaceholder("Empty") + .first() + .inputValue(); + + await page.getByText("Close").first().click(); + + await page.waitForTimeout(3000); + + await page.getByTestId("button_run_chat output").click(); + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); + + await page.getByTestId("output-inspection-text").first().click(); + + const secondRandomTextGeneratedByAI = await page + .getByPlaceholder("Empty") + .first() + .inputValue(); + + await page.getByText("Close").first().click(); + + await page.waitForTimeout(3000); + + await page.getByText("openai").first().click(); + + await page.waitForTimeout(1000); + + await page.getByTestId("icon-FreezeAll").click(); + + await page.waitForTimeout(1000); + + expect(await page.getByTestId("icon-Snowflake").count()).toBeGreaterThan(0); + + await page.waitForTimeout(1000); + await page.getByTestId("button_run_chat output").click(); + + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); + + await page.getByTestId("output-inspection-text").first().click(); + + const thirdRandomTextGeneratedByAI = await page + .getByPlaceholder("Empty") + .first() + .inputValue(); + + await page.getByText("Close").first().click(); + + expect(randomTextGeneratedByAI).not.toEqual(secondRandomTextGeneratedByAI); + expect(randomTextGeneratedByAI).not.toEqual(thirdRandomTextGeneratedByAI); + expect(secondRandomTextGeneratedByAI).toEqual(thirdRandomTextGeneratedByAI); +}); diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-0.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-0.spec.ts index faa6d322278..8678a21b0b0 100644 --- a/src/frontend/tests/end-to-end/generalBugs-shard-0.spec.ts +++ b/src/frontend/tests/end-to-end/generalBugs-shard-0.spec.ts @@ -80,12 +80,20 @@ test("erase button should clear the chat messages", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); await page.getByText("Playground", { exact: true }).click(); diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts index 9d5d8682a83..fe7829ba311 100644 --- a/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts +++ b/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts @@ -40,12 +40,20 @@ test("should delete rows from table message", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts index 579c096e6a8..575b553f19c 100644 --- a/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts +++ b/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts @@ -91,10 +91,21 @@ test("should copy code from playground modal", async ({ page }) => { await page.getByTitle("zoom out").click(); await page.getByTitle("zoom out").click(); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); + await page.getByTestId("dropdown-model_name").click(); + await page.getByTestId("gpt-4o-1-option").click(); + const elementsChatInput = await page .locator('[data-testid="handle-chatinput-shownode-message-right"]') .all(); diff --git a/src/frontend/tests/end-to-end/inputComponent.spec.ts b/src/frontend/tests/end-to-end/inputComponent.spec.ts index 07ea3245161..123cc1f58c3 100644 --- a/src/frontend/tests/end-to-end/inputComponent.spec.ts +++ b/src/frontend/tests/end-to-end/inputComponent.spec.ts @@ -90,11 +90,6 @@ test("InputComponent", async ({ page }) => { await page.locator('//*[@id="showchroma_server_ssl_enabled"]').isChecked(), ).toBeTruthy(); - await page.locator('//*[@id="showcollection_name"]').click(); - expect( - await page.locator('//*[@id="showcollection_name"]').isChecked(), - ).toBeFalsy(); - await page.locator('//*[@id="showchroma_server_cors_allow_origins"]').click(); expect( await page diff --git a/src/frontend/tests/end-to-end/intComponent.spec.ts b/src/frontend/tests/end-to-end/intComponent.spec.ts index 20804a9b6ac..82e687fbd94 100644 --- a/src/frontend/tests/end-to-end/intComponent.spec.ts +++ b/src/frontend/tests/end-to-end/intComponent.spec.ts @@ -112,11 +112,6 @@ test("IntComponent", async ({ page }) => { await page.locator('//*[@id="showopenai_api_base"]').isChecked(), ).toBeTruthy(); - await page.locator('//*[@id="showopenai_api_key"]').click(); - expect( - await page.locator('//*[@id="showopenai_api_key"]').isChecked(), - ).toBeFalsy(); - await page.locator('//*[@id="showtemperature"]').click(); expect( await page.locator('//*[@id="showtemperature"]').isChecked(), @@ -137,11 +132,6 @@ test("IntComponent", async ({ page }) => { await page.locator('//*[@id="showopenai_api_base"]').isChecked(), ).toBeFalsy(); - await page.locator('//*[@id="showopenai_api_key"]').click(); - expect( - await page.locator('//*[@id="showopenai_api_key"]').isChecked(), - ).toBeTruthy(); - await page.locator('//*[@id="showtemperature"]').click(); expect( await page.locator('//*[@id="showtemperature"]').isChecked(), @@ -162,11 +152,6 @@ test("IntComponent", async ({ page }) => { await page.locator('//*[@id="showopenai_api_base"]').isChecked(), ).toBeTruthy(); - await page.locator('//*[@id="showopenai_api_key"]').click(); - expect( - await page.locator('//*[@id="showopenai_api_key"]').isChecked(), - ).toBeFalsy(); - await page.locator('//*[@id="showtemperature"]').click(); expect( await page.locator('//*[@id="showtemperature"]').isChecked(), diff --git a/src/frontend/tests/end-to-end/logs.spec.ts b/src/frontend/tests/end-to-end/logs.spec.ts index 8e713cc1ec1..2ee2779cc52 100644 --- a/src/frontend/tests/end-to-end/logs.spec.ts +++ b/src/frontend/tests/end-to-end/logs.spec.ts @@ -35,22 +35,35 @@ test("should able to see and interact with logs", async ({ page }) => { await page.getByRole("heading", { name: "Basic Prompting" }).click(); await page.waitForTimeout(2000); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page.getByTestId("icon-ChevronDown").click(); await page.getByText("Logs").click(); await page.getByText("No Data Available", { exact: true }).isVisible(); await page.keyboard.press("Escape"); await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); await page.getByTestId("button_run_chat output").first().click(); - await page.waitForTimeout(2000); + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); + await page .getByText("Chat Output built successfully", { exact: true }) .isVisible(); diff --git a/src/frontend/tests/end-to-end/textInputOutput.spec.ts b/src/frontend/tests/end-to-end/textInputOutput.spec.ts index 1b7b5799610..c7c32621b05 100644 --- a/src/frontend/tests/end-to-end/textInputOutput.spec.ts +++ b/src/frontend/tests/end-to-end/textInputOutput.spec.ts @@ -181,12 +181,20 @@ test("TextInputOutputComponent", async ({ page }) => { .nth(0) .fill("This is a test!"); + let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + + while (outdatedComponents > 0) { + await page.getByTestId("icon-AlertTriangle").first().click(); + await page.waitForTimeout(1000); + outdatedComponents = await page.getByTestId("icon-AlertTriangle").count(); + } + await page - .getByTestId("popover-anchor-input-openai_api_key") + .getByTestId("popover-anchor-input-api_key") .fill(process.env.OPENAI_API_KEY ?? ""); await page.getByTestId("dropdown-model_name").click(); - await page.getByTestId("gpt-4o-0-option").click(); + await page.getByTestId("gpt-4o-1-option").click(); await page.waitForTimeout(2000); await page.getByText("Playground", { exact: true }).click();