diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5ad948a3c5..c78ba4c71f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ on: - "src/backend/**" env: - POETRY_VERSION: "1.7.0" + POETRY_VERSION: "1.8.2" jobs: lint: @@ -22,7 +22,6 @@ jobs: strategy: matrix: python-version: - - "3.9" - "3.10" - "3.11" steps: diff --git a/Makefile b/Makefile index 308db934ed..13aa20b495 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ format: lint: make install_backend - poetry run mypy src/backend + poetry run mypy --namespace-packages -p "langflow" poetry run ruff . --fix install_frontend: diff --git a/base.Dockerfile b/base.Dockerfile index 2293c35dd0..936943cd13 100644 --- a/base.Dockerfile +++ b/base.Dockerfile @@ -23,7 +23,7 @@ ENV PYTHONUNBUFFERED=1 \ \ # poetry # https://python-poetry.org/docs/configuration/#using-environment-variables - POETRY_VERSION=1.7.1 \ + POETRY_VERSION=1.8.2 \ # make poetry install to this location POETRY_HOME="/opt/poetry" \ # make poetry create the virtual environment in the project's root diff --git a/build_and_push.Dockerfile b/build_and_push.Dockerfile index f296d83aab..3cce7804b6 100644 --- a/build_and_push.Dockerfile +++ b/build_and_push.Dockerfile @@ -23,7 +23,7 @@ ENV PYTHONUNBUFFERED=1 \ \ # poetry # https://python-poetry.org/docs/configuration/#using-environment-variables - POETRY_VERSION=1.7.1 \ + POETRY_VERSION=1.8.2 \ # make poetry install to this location POETRY_HOME="/opt/poetry" \ # make poetry create the virtual environment in the project's root diff --git a/deploy/base.Dockerfile b/deploy/base.Dockerfile index 323663283f..58fae3dab3 100644 --- a/deploy/base.Dockerfile +++ b/deploy/base.Dockerfile @@ -23,7 +23,7 @@ ENV PYTHONUNBUFFERED=1 \ \ # poetry # https://python-poetry.org/docs/configuration/#using-environment-variables - POETRY_VERSION=1.5.1 \ + POETRY_VERSION=1.8.2 \ # make poetry install to this location POETRY_HOME="/opt/poetry" \ # make poetry create the virtual environment in the project's root diff --git a/src/backend/base/langflow/__main__.py b/src/backend/base/langflow/__main__.py index 4b22609586..c3fb5bd377 100644 --- a/src/backend/base/langflow/__main__.py +++ b/src/backend/base/langflow/__main__.py @@ -285,7 +285,12 @@ def run_langflow(host, port, log_level, options, app): # MacOS requires an env variable to be set to use gunicorn import uvicorn - uvicorn.run(app, host=host, port=port, log_level=log_level) + uvicorn.run( + app, + host=host, + port=port, + log_level=log_level.lower(), + ) else: from langflow.server import LangflowApplication diff --git a/src/backend/base/langflow/alembic/helpers/flow.py b/src/backend/base/langflow/alembic/helpers/flow.py deleted file mode 100644 index 328e99ca16..0000000000 --- a/src/backend/base/langflow/alembic/helpers/flow.py +++ /dev/null @@ -1,199 +0,0 @@ -from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Union - -from pydantic.v1 import BaseModel, Field, create_model -from sqlmodel import select - -from langflow.schema.schema import INPUT_FIELD_NAME, Record -from langflow.services.database.models.flow.model import Flow -from langflow.services.deps import session_scope - -if TYPE_CHECKING: - from langflow.graph.graph.base import Graph - from langflow.graph.vertex.base import Vertex - -INPUT_TYPE_MAP = { - "ChatInput": {"type_hint": "Optional[str]", "default": '""'}, - "TextInput": {"type_hint": "Optional[str]", "default": '""'}, - "JSONInput": {"type_hint": "Optional[dict]", "default": "{}"}, -} - - -def list_flows(*, user_id: Optional[str] = None) -> List[Record]: - if not user_id: - raise ValueError("Session is invalid") - try: - with session_scope() as session: - flows = session.exec( - select(Flow).where(Flow.user_id == user_id).where(Flow.is_component == False) # noqa - ).all() - - flows_records = [flow.to_record() for flow in flows] - return flows_records - except Exception as e: - raise ValueError(f"Error listing flows: {e}") - - -async def load_flow( - user_id: str, flow_id: Optional[str] = None, flow_name: Optional[str] = None, tweaks: Optional[dict] = None -) -> "Graph": - from langflow.graph.graph.base import Graph - from langflow.processing.process import process_tweaks - - if not flow_id and not flow_name: - raise ValueError("Flow ID or Flow Name is required") - if not flow_id and flow_name: - flow_id = find_flow(flow_name, user_id) - if not flow_id: - raise ValueError(f"Flow {flow_name} not found") - - with session_scope() as session: - graph_data = flow.data if (flow := session.get(Flow, flow_id)) else None - if not graph_data: - raise ValueError(f"Flow {flow_id} not found") - if tweaks: - graph_data = process_tweaks(graph_data=graph_data, tweaks=tweaks) - graph = Graph.from_payload(graph_data, flow_id=flow_id) - return graph - - -def find_flow(flow_name: str, user_id: str) -> Optional[str]: - with session_scope() as session: - flow = session.exec(select(Flow).where(Flow.name == flow_name).where(Flow.user_id == user_id)).first() - return flow.id if flow else None - - -async def run_flow( - inputs: Union[dict, List[dict]] = None, - tweaks: Optional[dict] = None, - flow_id: Optional[str] = None, - flow_name: Optional[str] = None, - user_id: Optional[str] = None, -) -> Any: - graph = await load_flow(user_id, flow_id, flow_name, tweaks) - - if inputs is None: - inputs = [] - inputs_list = [] - inputs_components = [] - types = [] - for input_dict in inputs: - inputs_list.append({INPUT_FIELD_NAME: input_dict.get("input_value")}) - inputs_components.append(input_dict.get("components", [])) - types.append(input_dict.get("type", [])) - - return await graph.arun(inputs_list, inputs_components=inputs_components, types=types) - - -def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Coroutine: - """ - Generate a dynamic flow function based on the given inputs and flow ID. - - Args: - inputs (List[Vertex]): The list of input vertices for the flow. - flow_id (str): The ID of the flow. - - Returns: - Coroutine: The dynamic flow function. - - Raises: - None - - Example: - inputs = [vertex1, vertex2] - flow_id = "my_flow" - function = generate_function_for_flow(inputs, flow_id) - result = function(input1, input2) - """ - # Prepare function arguments with type hints and default values - args = [ - f"{input_.display_name.lower().replace(' ', '_')}: {INPUT_TYPE_MAP[input_.base_name]['type_hint']} = {INPUT_TYPE_MAP[input_.base_name]['default']}" - for input_ in inputs - ] - - # Maintain original argument names for constructing the tweaks dictionary - original_arg_names = [input_.display_name for input_ in inputs] - - # Prepare a Pythonic, valid function argument string - func_args = ", ".join(args) - - # Map original argument names to their corresponding Pythonic variable names in the function - arg_mappings = ", ".join( - f'"{original_name}": {name}' - for original_name, name in zip(original_arg_names, [arg.split(":")[0] for arg in args]) - ) - - func_body = f""" -from typing import Optional -async def flow_function({func_args}): - tweaks = {{ {arg_mappings} }} - from langflow.helpers.flow import run_flow - from langchain_core.tools import ToolException - try: - return await run_flow( - tweaks={{key: {{'input_value': value}} for key, value in tweaks.items()}}, - flow_id="{flow_id}", - ) - except Exception as e: - raise ToolException(f'Error running flow: ' + e) -""" - - compiled_func = compile(func_body, "", "exec") - local_scope = {} - exec(compiled_func, globals(), local_scope) - return local_scope["flow_function"] - - -def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, BaseModel]: - """ - Builds a dynamic function and schema for a given flow. - - Args: - flow_record (Record): The flow record containing information about the flow. - graph (Graph): The graph representing the flow. - - Returns: - Tuple[Callable, BaseModel]: A tuple containing the dynamic function and the schema. - """ - flow_id = flow_record.id - inputs = get_flow_inputs(graph) - dynamic_flow_function = generate_function_for_flow(inputs, flow_id) - schema = build_schema_from_inputs(flow_record.name, inputs) - return dynamic_flow_function, schema - - -def get_flow_inputs(graph: "Graph") -> List["Vertex"]: - """ - Retrieves the flow inputs from the given graph. - - Args: - graph (Graph): The graph object representing the flow. - - Returns: - List[Record]: A list of input records, where each record contains the ID, name, and description of the input vertex. - """ - inputs = [] - for vertex in graph.vertices: - if vertex.is_input: - inputs.append(vertex) - return inputs - - -def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> BaseModel: - """ - Builds a schema from the given inputs. - - Args: - name (str): The name of the schema. - inputs (List[tuple[str, str, str]]): A list of tuples representing the inputs. - Each tuple contains three elements: the input name, the input type, and the input description. - - Returns: - BaseModel: The schema model. - - """ - fields = {} - for input_ in inputs: - field_name = input_.display_name.lower().replace(" ", "_") - description = input_.description - fields[field_name] = (str, Field(default="", description=description)) - return create_model(name, **fields) diff --git a/src/backend/base/langflow/alembic/helpers/record.py b/src/backend/base/langflow/alembic/helpers/record.py deleted file mode 100644 index d7b1d2b060..0000000000 --- a/src/backend/base/langflow/alembic/helpers/record.py +++ /dev/null @@ -1,34 +0,0 @@ -from langchain_core.documents import Document - -from langflow.schema import Record - - -def docs_to_records(documents: list[Document]) -> list[Record]: - """ - Converts a list of Documents to a list of Records. - - Args: - documents (list[Document]): The list of Documents to convert. - - Returns: - list[Record]: The converted list of Records. - """ - return [Record.from_document(document) for document in documents] - - -def records_to_text(template: str, records: list[Record]) -> str: - """ - Converts a list of Records to a list of texts. - - Args: - records (list[Record]): The list of Records to convert. - - Returns: - list[str]: The converted list of texts. - """ - if isinstance(records, Record): - records = [records] - # Check if there are any format strings in the template - - formated_records = [template.format(data=record.data, **record.data) for record in records] - return "\n".join(formated_records) diff --git a/src/backend/base/langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py b/src/backend/base/langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py new file mode 100644 index 0000000000..e27d088d0c --- /dev/null +++ b/src/backend/base/langflow/alembic/versions/1a110b568907_replace_credential_table_with_variable.py @@ -0,0 +1,65 @@ +"""Replace Credential table with Variable + +Revision ID: 1a110b568907 +Revises: 63b9c451fd30 +Create Date: 2024-03-25 09:40:02.743453 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +import sqlmodel +from alembic import op +from sqlalchemy.engine.reflection import Inspector + +# revision identifiers, used by Alembic. +revision: str = "1a110b568907" +down_revision: Union[str, None] = "63b9c451fd30" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + conn = op.get_bind() + inspector = Inspector.from_engine(conn) # type: ignore + table_names = inspector.get_table_names() + # ### commands auto generated by Alembic - please adjust! ### + if "variable" not in table_names: + op.create_table( + "variable", + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("value", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("type", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=True), + sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_variable_user_id"), + sa.PrimaryKeyConstraint("id"), + ) + if "credential" in table_names: + op.drop_table("credential") + # ### end Alembic commands ### + + +def downgrade() -> None: + conn = op.get_bind() + inspector = Inspector.from_engine(conn) # type: ignore + table_names = inspector.get_table_names() + # ### commands auto generated by Alembic - please adjust! ### + if "credential" not in table_names: + op.create_table( + "credential", + sa.Column("name", sa.VARCHAR(), nullable=True), + sa.Column("value", sa.VARCHAR(), nullable=True), + sa.Column("provider", sa.VARCHAR(), nullable=True), + sa.Column("user_id", sa.CHAR(length=32), nullable=False), + sa.Column("id", sa.CHAR(length=32), nullable=False), + sa.Column("created_at", sa.DATETIME(), nullable=False), + sa.Column("updated_at", sa.DATETIME(), nullable=True), + sa.ForeignKeyConstraint(["user_id"], ["user.id"], name="fk_credential_user_id"), + sa.PrimaryKeyConstraint("id"), + ) + if "variable" in table_names: + op.drop_table("variable") + # ### end Alembic commands ### diff --git a/src/backend/base/langflow/api/router.py b/src/backend/base/langflow/api/router.py index d117e9d114..569014acf5 100644 --- a/src/backend/base/langflow/api/router.py +++ b/src/backend/base/langflow/api/router.py @@ -4,7 +4,6 @@ from langflow.api.v1 import ( api_key_router, chat_router, - credentials_router, endpoints_router, files_router, flows_router, @@ -13,6 +12,7 @@ store_router, users_router, validate_router, + variables_router, ) router = APIRouter( @@ -26,6 +26,6 @@ router.include_router(users_router) router.include_router(api_key_router) router.include_router(login_router) -router.include_router(credentials_router) +router.include_router(variables_router) router.include_router(files_router) router.include_router(monitor_router) diff --git a/src/backend/base/langflow/api/utils.py b/src/backend/base/langflow/api/utils.py index d576db9527..27576e9714 100644 --- a/src/backend/base/langflow/api/utils.py +++ b/src/backend/base/langflow/api/utils.py @@ -125,6 +125,9 @@ def update_template_field(frontend_template, key, value_dict): template_field["value"] = "" template_field["file_path"] = file_path_value + if "load_from_db" in value_dict and value_dict["load_from_db"]: + template_field["load_from_db"] = value_dict["load_from_db"] + def get_file_path_value(file_path): """Get the file path value if the file exists, else return empty string.""" @@ -161,7 +164,7 @@ def get_is_component_from_data(data: dict): async def check_langflow_version(component: StoreComponentCreate): - from langflow import __version__ as current_version + from langflow.version.version import __version__ as current_version # type: ignore if not component.last_tested_version: component.last_tested_version = current_version diff --git a/src/backend/base/langflow/api/v1/__init__.py b/src/backend/base/langflow/api/v1/__init__.py index 39659748e5..0342c76c82 100644 --- a/src/backend/base/langflow/api/v1/__init__.py +++ b/src/backend/base/langflow/api/v1/__init__.py @@ -1,6 +1,5 @@ from langflow.api.v1.api_key import router as api_key_router from langflow.api.v1.chat import router as chat_router -from langflow.api.v1.credential import router as credentials_router from langflow.api.v1.endpoints import router as endpoints_router from langflow.api.v1.files import router as files_router from langflow.api.v1.flows import router as flows_router @@ -9,6 +8,7 @@ from langflow.api.v1.store import router as store_router from langflow.api.v1.users import router as users_router from langflow.api.v1.validate import router as validate_router +from langflow.api.v1.variable import router as variables_router __all__ = [ "chat_router", @@ -19,7 +19,7 @@ "users_router", "api_key_router", "login_router", - "credentials_router", + "variables_router", "monitor_router", "files_router", ] diff --git a/src/backend/base/langflow/api/v1/credential.py b/src/backend/base/langflow/api/v1/credential.py deleted file mode 100644 index fbbb488bc5..0000000000 --- a/src/backend/base/langflow/api/v1/credential.py +++ /dev/null @@ -1,111 +0,0 @@ -from datetime import datetime -from uuid import UUID - -from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import Session, select - -from langflow.services.auth import utils as auth_utils -from langflow.services.auth.utils import get_current_active_user -from langflow.services.database.models.credential import Credential, CredentialCreate, CredentialRead, CredentialUpdate -from langflow.services.database.models.user.model import User -from langflow.services.deps import get_session, get_settings_service - -router = APIRouter(prefix="/credentials", tags=["Credentials"]) - - -@router.post("/", response_model=CredentialRead, status_code=201) -def create_credential( - *, - session: Session = Depends(get_session), - credential: CredentialCreate, - current_user: User = Depends(get_current_active_user), - settings_service=Depends(get_settings_service), -): - """Create a new credential.""" - try: - # check if credential name already exists - credential_exists = session.exec( - select(Credential).where(Credential.name == credential.name, Credential.user_id == current_user.id) - ).first() - if credential_exists: - raise HTTPException(status_code=400, detail="Credential name already exists") - - credential_dict = credential.model_dump() - credential_dict["user_id"] = current_user.id - - db_credential = Credential.model_validate(credential_dict) - if not db_credential.value: - raise HTTPException(status_code=400, detail="Credential value cannot be empty") - encrypted = auth_utils.encrypt_api_key(db_credential.value, settings_service=settings_service) - db_credential.value = encrypted - db_credential.user_id = current_user.id - session.add(db_credential) - session.commit() - session.refresh(db_credential) - return db_credential - except Exception as e: - if isinstance(e, HTTPException): - raise e - raise HTTPException(status_code=500, detail=str(e)) from e - - -@router.get("/", response_model=list[CredentialRead], status_code=200) -def read_credentials( - *, - session: Session = Depends(get_session), - current_user: User = Depends(get_current_active_user), -): - """Read all credentials.""" - try: - credentials = session.exec(select(Credential).where(Credential.user_id == current_user.id)).all() - return credentials - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) from e - - -@router.patch("/{credential_id}", response_model=CredentialRead, status_code=200) -def update_credential( - *, - session: Session = Depends(get_session), - credential_id: UUID, - credential: CredentialUpdate, - current_user: User = Depends(get_current_active_user), -): - """Update a credential.""" - try: - db_credential = session.exec( - select(Credential).where(Credential.id == credential_id, Credential.user_id == current_user.id) - ).first() - if not db_credential: - raise HTTPException(status_code=404, detail="Credential not found") - - credential_data = credential.model_dump(exclude_unset=True) - for key, value in credential_data.items(): - setattr(db_credential, key, value) - db_credential.updated_at = datetime.utcnow() - session.commit() - session.refresh(db_credential) - return db_credential - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) from e - - -@router.delete("/{credential_id}", response_model=CredentialRead, status_code=200) -def delete_credential( - *, - session: Session = Depends(get_session), - credential_id: UUID, - current_user: User = Depends(get_current_active_user), -): - """Delete a credential.""" - try: - db_credential = session.exec( - select(Credential).where(Credential.id == credential_id, Credential.user_id == current_user.id) - ).first() - if not db_credential: - raise HTTPException(status_code=404, detail="Credential not found") - session.delete(db_credential) - session.commit() - return db_credential - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) from e diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index 7886321f73..16dcdca708 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -239,7 +239,7 @@ async def create_upload_file( # get endpoint to return version of langflow @router.get("/version") def get_version(): - from langflow.version import __version__ + from langflow.version import __version__ # type: ignore return {"version": __version__} diff --git a/src/backend/base/langflow/api/v1/variable.py b/src/backend/base/langflow/api/v1/variable.py new file mode 100644 index 0000000000..1230866766 --- /dev/null +++ b/src/backend/base/langflow/api/v1/variable.py @@ -0,0 +1,113 @@ +from datetime import datetime +from uuid import UUID + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select + +from langflow.services.auth import utils as auth_utils +from langflow.services.auth.utils import get_current_active_user +from langflow.services.database.models.user.model import User +from langflow.services.database.models.variable import Variable, VariableCreate, VariableRead, VariableUpdate +from langflow.services.deps import get_session, get_settings_service + +router = APIRouter(prefix="/variables", tags=["Variables"]) + + +@router.post("/", response_model=VariableRead, status_code=201) +def create_variable( + *, + session: Session = Depends(get_session), + variable: VariableCreate, + current_user: User = Depends(get_current_active_user), + settings_service=Depends(get_settings_service), +): + """Create a new variable.""" + try: + # check if variable name already exists + variable_exists = session.exec( + select(Variable).where( + Variable.name == variable.name, + Variable.user_id == current_user.id, + ) + ).first() + if variable_exists: + raise HTTPException(status_code=400, detail="Variable name already exists") + + variable_dict = variable.model_dump() + variable_dict["user_id"] = current_user.id + + db_variable = Variable.model_validate(variable_dict) + if not db_variable.value: + raise HTTPException(status_code=400, detail="Variable value cannot be empty") + encrypted = auth_utils.encrypt_api_key(db_variable.value, settings_service=settings_service) + db_variable.value = encrypted + db_variable.user_id = current_user.id + session.add(db_variable) + session.commit() + session.refresh(db_variable) + return db_variable + except Exception as e: + if isinstance(e, HTTPException): + raise e + raise HTTPException(status_code=500, detail=str(e)) from e + + +@router.get("/", response_model=list[VariableRead], status_code=200) +def read_variables( + *, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_active_user), +): + """Read all variables.""" + try: + variables = session.exec(select(Variable).where(Variable.user_id == current_user.id)).all() + return variables + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e + + +@router.patch("/{variable_id}", response_model=VariableRead, status_code=200) +def update_variable( + *, + session: Session = Depends(get_session), + variable_id: UUID, + variable: VariableUpdate, + current_user: User = Depends(get_current_active_user), +): + """Update a variable.""" + try: + db_variable = session.exec( + select(Variable).where(Variable.id == variable_id, Variable.user_id == current_user.id) + ).first() + if not db_variable: + raise HTTPException(status_code=404, detail="Variable not found") + + variable_data = variable.model_dump(exclude_unset=True) + for key, value in variable_data.items(): + setattr(db_variable, key, value) + db_variable.updated_at = datetime.utcnow() + session.commit() + session.refresh(db_variable) + return db_variable + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e + + +@router.delete("/{variable_id}", status_code=204) +def delete_variable( + *, + session: Session = Depends(get_session), + variable_id: UUID, + current_user: User = Depends(get_current_active_user), +): + """Delete a variable.""" + try: + db_variable = session.exec( + select(Variable).where(Variable.id == variable_id, Variable.user_id == current_user.id) + ).first() + if not db_variable: + raise HTTPException(status_code=404, detail="Variable not found") + session.delete(db_variable) + session.commit() + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) from e diff --git a/src/backend/base/langflow/base/agents/agent.py b/src/backend/base/langflow/base/agents/agent.py index 029ab4913b..5ef498b665 100644 --- a/src/backend/base/langflow/base/agents/agent.py +++ b/src/backend/base/langflow/base/agents/agent.py @@ -1,8 +1,10 @@ -from typing import List, Union +from typing import List, Optional, Union, cast from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent +from langchain_core.runnables import Runnable + +from langflow.custom import CustomComponent from langflow.field_typing import BaseMemory, Text, Tool -from langflow.interface.custom.custom_component import CustomComponent class LCAgentComponent(CustomComponent): @@ -38,11 +40,11 @@ def build_config(self): async def run_agent( self, - agent: Union[BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor], + agent: Union[Runnable, BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor], inputs: str, input_variables: list[str], tools: List[Tool], - memory: BaseMemory = None, + memory: Optional[BaseMemory] = None, handle_parsing_errors: bool = True, output_key: str = "output", ) -> Text: @@ -50,7 +52,11 @@ async def run_agent( runnable = agent else: runnable = AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=handle_parsing_errors + agent=agent, # type: ignore + tools=tools, + verbose=True, + memory=memory, + handle_parsing_errors=handle_parsing_errors, ) input_dict = {"input": inputs} for var in input_variables: @@ -59,11 +65,11 @@ async def run_agent( result = await runnable.ainvoke(input_dict) self.status = result if output_key in result: - return result.get(output_key) + return cast(str, result.get(output_key)) elif "output" not in result: if output_key != "output": raise ValueError(f"Output key not found in result. Tried '{output_key}' and 'output'.") else: raise ValueError("Output key not found in result. Tried 'output'.") - return result.get("output") + return cast(str, result.get("output")) diff --git a/src/backend/base/langflow/base/models/model.py b/src/backend/base/langflow/base/models/model.py index 4e3a422b2b..7bbd614d68 100644 --- a/src/backend/base/langflow/base/models/model.py +++ b/src/backend/base/langflow/base/models/model.py @@ -1,10 +1,10 @@ -from typing import Optional +from typing import Optional, Union from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.language_models.llms import LLM from langchain_core.messages import HumanMessage, SystemMessage -from langflow.interface.custom.custom_component import CustomComponent +from langflow.custom import CustomComponent class LCModelComponent(CustomComponent): @@ -34,15 +34,15 @@ def get_result(self, runnable: LLM, stream: bool, input_value: str): def get_chat_result( self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None ): - messages = [] + messages: list[Union[HumanMessage, SystemMessage]] = [] if system_message: - messages.append(SystemMessage(system_message)) + messages.append(SystemMessage(content=system_message)) if input_value: - messages.append(HumanMessage(input_value)) + messages.append(HumanMessage(content=input_value)) if stream: - result = runnable.stream(messages) + return runnable.stream(messages) else: message = runnable.invoke(messages) result = message.content self.status = result - return result + return result diff --git a/src/backend/base/langflow/components/agents/XMLAgent.py b/src/backend/base/langflow/components/agents/XMLAgent.py index efd5cc0105..687bfff6fc 100644 --- a/src/backend/base/langflow/components/agents/XMLAgent.py +++ b/src/backend/base/langflow/components/agents/XMLAgent.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from langchain.agents import create_xml_agent from langchain_core.prompts import PromptTemplate @@ -69,7 +69,7 @@ async def build( llm: BaseLLM, tools: List[Tool], prompt: str, - memory: BaseMemory = None, + memory: Optional[BaseMemory] = None, tool_template: str = "{name}: {description}", handle_parsing_errors: bool = True, ) -> Text: diff --git a/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py b/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py index 07b21dabe2..e7c7739075 100644 --- a/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py +++ b/src/backend/base/langflow/components/embeddings/CohereEmbeddings.py @@ -22,7 +22,7 @@ def build( self, request_timeout: Optional[float] = None, cohere_api_key: str = "", - max_retries: Optional[int] = None, + max_retries: int = 3, model: str = "embed-english-v2.0", truncate: Optional[str] = None, user_agent: str = "langchain", diff --git a/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py b/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py index 3578caef79..a7fc838b33 100644 --- a/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py +++ b/src/backend/base/langflow/components/embeddings/OpenAIEmbeddings.py @@ -1,7 +1,6 @@ from typing import Any, Callable, Dict, List, Optional, Union from langchain_openai.embeddings.base import OpenAIEmbeddings -from pydantic.v1.types import SecretStr from langflow.field_typing import NestedDict from langflow.interface.custom.custom_component import CustomComponent @@ -100,8 +99,6 @@ def build( if disallowed_special == ["all"]: disallowed_special = "all" # type: ignore - api_key = SecretStr(openai_api_key) if openai_api_key else None - return OpenAIEmbeddings( tiktoken_enabled=tiktoken_enable, default_headers=default_headers, @@ -116,7 +113,7 @@ def build( model=model, model_kwargs=model_kwargs, base_url=openai_api_base, - api_key=api_key, + api_key=openai_api_key, openai_api_type=openai_api_type, api_version=openai_api_version, organization=openai_organization, diff --git a/src/backend/base/langflow/components/experimental/FlowTool.py b/src/backend/base/langflow/components/experimental/FlowTool.py index 541341d85a..ac48a0f038 100644 --- a/src/backend/base/langflow/components/experimental/FlowTool.py +++ b/src/backend/base/langflow/components/experimental/FlowTool.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, Text +from typing import Any, List, Optional from langchain_core.tools import StructuredTool from loguru import logger @@ -8,6 +8,7 @@ from langflow.graph.graph.base import Graph from langflow.helpers.flow import build_function_and_schema from langflow.schema.dotdict import dotdict +from langflow.schema.schema import Record class FlowToolComponent(CustomComponent): @@ -19,7 +20,7 @@ def get_flow_names(self) -> List[str]: flow_records = self.list_flows() return [flow_record.data["name"] for flow_record in flow_records] - def get_flow(self, flow_name: str) -> Optional[Text]: + def get_flow(self, flow_name: str) -> Optional[Record]: """ Retrieves a flow by its name. @@ -82,4 +83,4 @@ async def build(self, flow_name: str, name: str, description: str, return_direct description_repr = repr(tool.description).strip("'") args_str = "\n".join([f"- {arg_name}: {arg_data['description']}" for arg_name, arg_data in tool.args.items()]) self.status = f"{description_repr}\nArguments:\n{args_str}" - return tool + return tool # type: ignore diff --git a/src/backend/base/langflow/components/experimental/SubFlow.py b/src/backend/base/langflow/components/experimental/SubFlow.py index 150461275b..af309fa883 100644 --- a/src/backend/base/langflow/components/experimental/SubFlow.py +++ b/src/backend/base/langflow/components/experimental/SubFlow.py @@ -1,10 +1,12 @@ -from typing import Any, List, Optional, Text, Tuple +from typing import Any, List, Optional +from langflow.helpers.flow import get_flow_inputs from loguru import logger from langflow.custom import CustomComponent from langflow.graph.graph.base import Graph from langflow.graph.schema import ResultData, RunOutputs +from langflow.graph.vertex.base import Vertex from langflow.schema import Record from langflow.schema.dotdict import dotdict from langflow.template.field.base import TemplateField @@ -20,7 +22,7 @@ def get_flow_names(self) -> List[str]: flow_records = self.list_flows() return [flow_record.data["name"] for flow_record in flow_records] - def get_flow(self, flow_name: str) -> Optional[Text]: + def get_flow(self, flow_name: str) -> Optional[Record]: flow_records = self.list_flows() for flow_record in flow_records: if flow_record.data["name"] == flow_name: @@ -42,7 +44,7 @@ def update_build_config(self, build_config: dotdict, field_value: Any, field_nam raise ValueError(f"Flow {field_value} not found.") graph = Graph.from_payload(flow_record.data["data"]) # Get all inputs from the graph - inputs = self.get_flow_inputs(graph) + inputs = get_flow_inputs(graph) # Add inputs to the build config build_config = self.add_inputs_to_build_config(inputs, build_config) except Exception as e: @@ -50,21 +52,13 @@ def update_build_config(self, build_config: dotdict, field_value: Any, field_nam return build_config - def get_flow_inputs(self, graph: Graph) -> List[Record]: - inputs = [] - for vertex in graph.vertices: - if vertex.is_input: - inputs.append((vertex.id, vertex.display_name, vertex.description)) - logger.debug(inputs) - return inputs - - def add_inputs_to_build_config(self, inputs: List[Tuple], build_config: dotdict): + def add_inputs_to_build_config(self, inputs: List[Vertex], build_config: dotdict): new_fields: list[TemplateField] = [] - for input_id, input_display_name, input_description in inputs: + for vertex in inputs: field = TemplateField( - display_name=input_display_name, - name=input_id, - info=input_description, + display_name=vertex.display_name, + name=vertex.id, + info=vertex.description, field_type="str", default=None, ) @@ -110,12 +104,15 @@ async def build(self, flow_name: str, **kwargs) -> List[Record]: tweaks=tweaks, flow_name=flow_name, ) + if not run_outputs: + return [] run_output = run_outputs[0] records = [] - for output in run_output.outputs: - if output: - records.extend(self.build_records_from_result_data(output)) + if run_output is not None: + for output in run_output.outputs: + if output: + records.extend(self.build_records_from_result_data(output)) self.status = records logger.debug(records) diff --git a/src/backend/base/langflow/components/experimental/__init__.py b/src/backend/base/langflow/components/experimental/__init__.py index d23a731063..2d59a063da 100644 --- a/src/backend/base/langflow/components/experimental/__init__.py +++ b/src/backend/base/langflow/components/experimental/__init__.py @@ -1,6 +1,6 @@ from .ClearMessageHistory import ClearMessageHistoryComponent from .ExtractDataFromRecord import ExtractKeyFromRecordComponent -from .Listen import GetNotifiedComponent +from .Listen import ListenComponent from .ListFlows import ListFlowsComponent from .MergeRecords import MergeRecordsComponent from .Notify import NotifyComponent @@ -11,7 +11,7 @@ __all__ = [ "ClearMessageHistoryComponent", "ExtractKeyFromRecordComponent", - "GetNotifiedComponent", + "ListenComponent", "ListFlowsComponent", "MergeRecordsComponent", "MessageHistoryComponent", diff --git a/src/backend/base/langflow/components/models/AzureOpenAIModel.py b/src/backend/base/langflow/components/models/AzureOpenAIModel.py index eeb488f039..58cc3f2c26 100644 --- a/src/backend/base/langflow/components/models/AzureOpenAIModel.py +++ b/src/backend/base/langflow/components/models/AzureOpenAIModel.py @@ -2,7 +2,6 @@ from langchain.llms.base import BaseLanguageModel from langchain_openai import AzureChatOpenAI -from pydantic.v1 import SecretStr from langflow.base.models.model import LCModelComponent from langflow.field_typing import Text @@ -91,21 +90,20 @@ def build( azure_endpoint: str, input_value: Text, azure_deployment: str, - api_key: str, api_version: str, + api_key: Optional[str] = None, system_message: Optional[str] = None, temperature: float = 0.7, max_tokens: Optional[int] = 1000, stream: bool = False, ) -> BaseLanguageModel: - secret_api_key = SecretStr(api_key) try: output = AzureChatOpenAI( model=model, azure_endpoint=azure_endpoint, azure_deployment=azure_deployment, api_version=api_version, - api_key=secret_api_key, + api_key=api_key, temperature=temperature, max_tokens=max_tokens, ) diff --git a/src/backend/base/langflow/components/models/base/__init__.py b/src/backend/base/langflow/components/models/base/__init__.py deleted file mode 100644 index 921f10336a..0000000000 --- a/src/backend/base/langflow/components/models/base/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .model import LCModelComponent - -__all__ = ["LCModelComponent"] diff --git a/src/backend/base/langflow/components/tools/SearchAPITool.py b/src/backend/base/langflow/components/tools/SearchAPITool.py index 6ec2f693ca..e0658c8c8d 100644 --- a/src/backend/base/langflow/components/tools/SearchAPITool.py +++ b/src/backend/base/langflow/components/tools/SearchAPITool.py @@ -34,4 +34,4 @@ def build( tool = SearchAPIRun(api_wrapper=search_api_wrapper) self.status = tool - return tool + return tool # type: ignore diff --git a/src/backend/base/langflow/components/vectorstores/AstraDB.py b/src/backend/base/langflow/components/vectorstores/AstraDB.py index 5ac7f85b46..51146d7293 100644 --- a/src/backend/base/langflow/components/vectorstores/AstraDB.py +++ b/src/backend/base/langflow/components/vectorstores/AstraDB.py @@ -1,6 +1,7 @@ from typing import List, Optional from langchain_astradb import AstraDBVectorStore +from langchain_astradb.utils.astradb import SetupMode from langflow.custom import CustomComponent from langflow.field_typing import Embeddings, VectorStore @@ -85,6 +86,10 @@ def build( metadata_indexing_exclude: Optional[List[str]] = None, collection_indexing_policy: Optional[dict] = None, ) -> VectorStore: + try: + setup_mode_value = SetupMode[setup_mode.upper()] + except KeyError: + raise ValueError(f"Invalid setup mode: {setup_mode}") if inputs: documents = [_input.to_lc_document() for _input in inputs] @@ -100,7 +105,7 @@ def build( bulk_insert_batch_concurrency=bulk_insert_batch_concurrency, bulk_insert_overwrite_concurrency=bulk_insert_overwrite_concurrency, bulk_delete_concurrency=bulk_delete_concurrency, - setup_mode=setup_mode, + setup_mode=setup_mode_value, pre_delete_collection=pre_delete_collection, metadata_indexing_include=metadata_indexing_include, metadata_indexing_exclude=metadata_indexing_exclude, @@ -118,7 +123,7 @@ def build( bulk_insert_batch_concurrency=bulk_insert_batch_concurrency, bulk_insert_overwrite_concurrency=bulk_insert_overwrite_concurrency, bulk_delete_concurrency=bulk_delete_concurrency, - setup_mode=setup_mode, + setup_mode=setup_mode_value, pre_delete_collection=pre_delete_collection, metadata_indexing_include=metadata_indexing_include, metadata_indexing_exclude=metadata_indexing_exclude, diff --git a/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py b/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py index bc7e69a9a9..5ee71a1702 100644 --- a/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py +++ b/src/backend/base/langflow/components/vectorstores/AstraDBSearch.py @@ -6,7 +6,7 @@ from langflow.schema import Record -class AstraDBSearchComponent(AstraDBVectorStoreComponent, LCVectorStoreComponent): +class AstraDBSearchComponent(LCVectorStoreComponent): display_name = "AstraDB Search" description = "Searches an existing AstraDB Vector Store" icon = "AstraDB" @@ -76,7 +76,7 @@ def build( self, embedding: Embeddings, collection_name: str, - input_value: Optional[Text] = None, + input_value: Text, search_type: str = "Similarity", token: Optional[str] = None, api_endpoint: Optional[str] = None, @@ -92,7 +92,7 @@ def build( metadata_indexing_exclude: Optional[List[str]] = None, collection_indexing_policy: Optional[dict] = None, ) -> List[Record]: - vector_store = super().build( + vector_store = AstraDBVectorStoreComponent().build( embedding=embedding, collection_name=collection_name, token=token, diff --git a/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py b/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py index 10b69ce153..cbb583fb36 100644 --- a/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py +++ b/src/backend/base/langflow/components/vectorstores/MongoDBAtlasVectorSearch.py @@ -6,7 +6,7 @@ from langflow.schema import Record -class MongoDBAtlasSearchComponent(MongoDBAtlasComponent, LCVectorStoreComponent): +class MongoDBAtlasSearchComponent(LCVectorStoreComponent): display_name = "MongoDB Atlas Search" description = "Search a MongoDB Atlas Vector Store for similar documents." @@ -37,9 +37,10 @@ def build( # type: ignore[override] search_kwargs: Optional[NestedDict] = None, ) -> List[Record]: search_kwargs = search_kwargs or {} - vector_store = super().build( - connection_string=mongodb_atlas_cluster_uri, - namespace=f"{db_name}.{collection_name}", + vector_store = MongoDBAtlasComponent().build( + mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri, + collection_name=collection_name, + db_name=db_name, embedding=embedding, index_name=index_name, ) diff --git a/src/backend/base/langflow/field_typing/range_spec.py b/src/backend/base/langflow/field_typing/range_spec.py index 036d21fa9c..b81cf7a222 100644 --- a/src/backend/base/langflow/field_typing/range_spec.py +++ b/src/backend/base/langflow/field_typing/range_spec.py @@ -10,12 +10,12 @@ class RangeSpec(BaseModel): @classmethod def max_must_be_greater_than_min(cls, v, values, **kwargs): if "min" in values.data and v <= values.data["min"]: - raise ValueError("max must be greater than min") + raise ValueError("Max must be greater than min") return v @field_validator("step") @classmethod def step_must_be_positive(cls, v): if v <= 0: - raise ValueError("step must be positive") + raise ValueError("Step must be positive") return v diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 174ec9ce45..00bb69f674 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1,7 +1,7 @@ import asyncio from collections import defaultdict, deque from itertools import chain -from typing import TYPE_CHECKING, Coroutine, Dict, Generator, List, Optional, Type, Union +from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Generator, List, Literal, Optional, Type, Union from loguru import logger @@ -201,7 +201,7 @@ async def _run( self, inputs: Dict[str, str], input_components: list[str], - input_type: str, + input_type: Literal["chat", "text", "json", "any"] | None, outputs: list[str], stream: bool, session_id: str, @@ -236,7 +236,7 @@ async def _run( continue # If the input_type is not any and the input_type is not in the vertex id # Example: input_type = "chat" and vertex.id = "OpenAI-19ddn" - elif input_type != "any" and input_type not in vertex.id.lower(): + elif input_type is not None and input_type != "any" and input_type not in vertex.id.lower(): continue if vertex is None: raise ValueError(f"Vertex {vertex_id} not found") @@ -269,9 +269,9 @@ async def _run( def run( self, - inputs: Dict[str, str], - input_components: Optional[list[str]] = None, - types: Optional[list[str]] = None, + inputs: list[Dict[str, str]], + input_components: Optional[list[list[str]]] = None, + types: Optional[list[Literal["chat", "text", "json", "any"] | None]] = None, outputs: Optional[list[str]] = None, session_id: Optional[str] = None, stream: bool = False, @@ -309,7 +309,7 @@ async def arun( self, inputs: list[Dict[str, str]], inputs_components: Optional[list[list[str]]] = None, - types: Optional[list[str]] = None, + types: Optional[list[Literal["chat", "text", "json", "any"] | None]] = None, outputs: Optional[list[str]] = None, session_id: Optional[str] = None, stream: bool = False, @@ -338,8 +338,12 @@ async def arun( inputs = [{}] # Length of all should be the as inputs length # just add empty lists to complete the length + if inputs_components is None: + inputs_components = [] for _ in range(len(inputs) - len(inputs_components)): inputs_components.append([]) + if types is None: + types = [] for _ in range(len(inputs) - len(types)): types.append("any") for run_inputs, components, input_type in zip(inputs, inputs_components, types): @@ -650,7 +654,7 @@ def get_vertex(self, vertex_id: str) -> Vertex: async def build_vertex( self, lock: asyncio.Lock, - set_cache_coro: Coroutine, + set_cache_coro: Callable[["Graph", asyncio.Lock], Coroutine], vertex_id: str, inputs_dict: Optional[Dict[str, str]] = None, user_id: Optional[str] = None, @@ -693,7 +697,9 @@ async def build_vertex( logger.exception(f"Error building vertex: {exc}") raise exc - async def get_next_and_top_level_vertices(self, lock: asyncio.Lock, set_cache_coro: Coroutine, vertex: Vertex): + async def get_next_and_top_level_vertices( + self, lock: asyncio.Lock, set_cache_coro: Callable[["Graph", asyncio.Lock], Coroutine], vertex: Vertex + ): """ Retrieves the next runnable vertices and the top level vertices for a given vertex. diff --git a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py index 3fa9d3e913..d31f01fe75 100644 --- a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py +++ b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py @@ -1,6 +1,6 @@ import asyncio from collections import defaultdict -from typing import TYPE_CHECKING, Coroutine, List +from typing import TYPE_CHECKING, Awaitable, Callable, List if TYPE_CHECKING: from langflow.graph.graph.base import Graph @@ -55,7 +55,7 @@ def update_vertex_run_state(self, vertex_id: str, is_runnable: bool): async def get_next_runnable_vertices( self, lock: asyncio.Lock, - set_cache_coro: Coroutine, + set_cache_coro: Callable[["Graph", asyncio.Lock], Awaitable[None]], graph: "Graph", vertex: "Vertex", ): @@ -85,7 +85,7 @@ async def get_next_runnable_vertices( for v_id in set(next_runnable_vertices): # Use set to avoid duplicates self.update_vertex_run_state(v_id, is_runnable=False) self.remove_from_predecessors(v_id) - await set_cache_coro(data=graph, lock=lock) + await set_cache_coro(graph, lock) return next_runnable_vertices @staticmethod diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 3e8e34b837..f3891bb757 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -4,7 +4,6 @@ import types from enum import Enum from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional - from loguru import logger from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData @@ -68,6 +67,7 @@ def __init__( self.is_task = is_task self.params = params or {} self.parent_node_id: Optional[str] = self._data.get("parent_node_id") + self.load_from_db_fields: List[str] = [] self.parent_is_top_level = False self.layer = None self.should_run = True @@ -155,6 +155,7 @@ def __getstate__(self): "_built": False, "parent_node_id": self.parent_node_id, "parent_is_top_level": self.parent_is_top_level, + "load_from_db_fields": self.load_from_db_fields, "is_input": self.is_input, "is_output": self.is_output, } @@ -185,6 +186,7 @@ def __setstate__(self, state): self.task_id: Optional[str] = None self.parent_node_id = state["parent_node_id"] self.parent_is_top_level = state["parent_is_top_level"] + self.load_from_db_fields = state["load_from_db_fields"] self.layer = state.get("layer") self.steps = state.get("steps", [self._build]) @@ -285,20 +287,21 @@ def _build_params(self): else: params[param_key] = self.graph.get_vertex(edge.source_id) - for key, value in template_dict.items(): - if key in params: + load_from_db_fields = [] + for field_name, field in template_dict.items(): + if field_name in params: continue # Skip _type and any value that has show == False and is not code # If we don't want to show code but we want to use it - if key == "_type" or (not value.get("show") and key != "code"): + if field_name == "_type" or (not field.get("show") and field_name != "code"): continue # If the type is not transformable to a python base class # then we need to get the edge that connects to this node - if value.get("type") == "file": + if field.get("type") == "file": # Load the type in value.get('fileTypes') using # what is inside value.get('content') # value.get('value') is the file name - if file_path := value.get("file_path"): + if file_path := field.get("file_path"): storage_service = get_storage_service() try: flow_id, file_name = file_path.split("/") @@ -308,51 +311,58 @@ def _build_params(self): full_path = file_path else: raise e - params[key] = full_path - elif value.get("required"): + params[field_name] = full_path + elif field.get("required"): raise ValueError(f"File path not found for {self.display_name}") - elif value.get("type") in DIRECT_TYPES and params.get(key) is None: - val = value.get("value") - if value.get("type") == "code": + elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None: + val = field.get("value") + if field.get("type") == "code": try: - params[key] = ast.literal_eval(val) if val else None + params[field_name] = ast.literal_eval(val) if val else None except Exception: - params[key] = val - elif value.get("type") in ["dict", "NestedDict"]: + params[field_name] = val + elif field.get("type") in ["dict", "NestedDict"]: # When dict comes from the frontend it comes as a # list of dicts, so we need to convert it to a dict # before passing it to the build method if isinstance(val, list): - params[key] = {k: v for item in value.get("value", []) for k, v in item.items()} + params[field_name] = {k: v for item in field.get("value", []) for k, v in item.items()} elif isinstance(val, dict): - params[key] = val - elif value.get("type") == "int" and val is not None: + params[field_name] = val + elif field.get("type") == "int" and val is not None: try: - params[key] = int(val) + params[field_name] = int(val) except ValueError: - params[key] = val - elif value.get("type") == "float" and val is not None: + params[field_name] = val + elif field.get("type") == "float" and val is not None: try: - params[key] = float(val) + params[field_name] = float(val) except ValueError: - params[key] = val - elif value.get("type") == "str" and val is not None: + params[field_name] = val + params[field_name] = val + elif field.get("type") == "str" and val is not None: # val may contain escaped \n, \t, etc. # so we need to unescape it if isinstance(val, list): - params[key] = [unescape_string(v) for v in val] + params[field_name] = [unescape_string(v) for v in val] elif isinstance(val, str): - params[key] = unescape_string(val) + params[field_name] = unescape_string(val) + elif val is not None and val != "": + params[field_name] = val + elif val is not None and val != "": - params[key] = val + params[field_name] = val + if field.get("load_from_db"): + load_from_db_fields.append(field_name) - if not value.get("required") and params.get(key) is None: - if value.get("default"): - params[key] = value.get("default") + if not field.get("required") and params.get(field_name) is None: + if field.get("default"): + params[field_name] = field.get("default") else: - params.pop(key, None) + params.pop(field_name, None) # Add _type to params self.params = params + self.load_from_db_fields = load_from_db_fields self._raw_params = params.copy() def update_raw_params(self, new_params: Dict[str, str], overwrite: bool = False): diff --git a/src/backend/base/langflow/alembic/helpers/__init__.py b/src/backend/base/langflow/helpers/__init__.py similarity index 100% rename from src/backend/base/langflow/alembic/helpers/__init__.py rename to src/backend/base/langflow/helpers/__init__.py diff --git a/src/backend/base/langflow/helpers/__init__py b/src/backend/base/langflow/helpers/__init__py deleted file mode 100644 index b944a01788..0000000000 --- a/src/backend/base/langflow/helpers/__init__py +++ /dev/null @@ -1,3 +0,0 @@ -from .record import docs_to_records, records_to_text - -__all__ = ["docs_to_records", "records_to_text"] \ No newline at end of file diff --git a/src/backend/base/langflow/helpers/flow.py b/src/backend/base/langflow/helpers/flow.py index 328e99ca16..88c51efa30 100644 --- a/src/backend/base/langflow/helpers/flow.py +++ b/src/backend/base/langflow/helpers/flow.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional, Tuple, Type, Union, cast from pydantic.v1 import BaseModel, Field, create_model from sqlmodel import select @@ -63,28 +63,30 @@ def find_flow(flow_name: str, user_id: str) -> Optional[str]: async def run_flow( - inputs: Union[dict, List[dict]] = None, + inputs: Optional[Union[dict, List[dict]]] = None, tweaks: Optional[dict] = None, flow_id: Optional[str] = None, flow_name: Optional[str] = None, user_id: Optional[str] = None, ) -> Any: + if not user_id: + raise ValueError("Session is invalid") graph = await load_flow(user_id, flow_id, flow_name, tweaks) if inputs is None: inputs = [] - inputs_list = [] + inputs_list: list[dict[str, str]] = [] inputs_components = [] types = [] for input_dict in inputs: - inputs_list.append({INPUT_FIELD_NAME: input_dict.get("input_value")}) + inputs_list.append({INPUT_FIELD_NAME: cast(str, input_dict.get("input_value", ""))}) inputs_components.append(input_dict.get("components", [])) types.append(input_dict.get("type", [])) return await graph.arun(inputs_list, inputs_components=inputs_components, types=types) -def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Coroutine: +def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Callable[..., Awaitable[Any]]: """ Generate a dynamic flow function based on the given inputs and flow ID. @@ -138,12 +140,14 @@ async def flow_function({func_args}): """ compiled_func = compile(func_body, "", "exec") - local_scope = {} + local_scope: dict = {} exec(compiled_func, globals(), local_scope) return local_scope["flow_function"] -def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, BaseModel]: +def build_function_and_schema( + flow_record: Record, graph: "Graph" +) -> Tuple[Callable[..., Awaitable[Any]], Type[BaseModel]]: """ Builds a dynamic function and schema for a given flow. @@ -178,7 +182,7 @@ def get_flow_inputs(graph: "Graph") -> List["Vertex"]: return inputs -def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> BaseModel: +def build_schema_from_inputs(name: str, inputs: List["Vertex"]) -> Type[BaseModel]: """ Builds a schema from the given inputs. @@ -196,4 +200,4 @@ def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> B field_name = input_.display_name.lower().replace(" ", "_") description = input_.description fields[field_name] = (str, Field(default="", description=description)) - return create_model(name, **fields) + return create_model(name, **fields) # type: ignore diff --git a/src/backend/base/langflow/interface/custom/custom_component/component.py b/src/backend/base/langflow/interface/custom/custom_component/component.py index 8a9ba1a222..20b23980dd 100644 --- a/src/backend/base/langflow/interface/custom/custom_component/component.py +++ b/src/backend/base/langflow/interface/custom/custom_component/component.py @@ -43,8 +43,7 @@ def __init__(self, **data): def __setattr__(self, key, value): if key == "_user_id" and hasattr(self, "_user_id"): warnings.warn("user_id is immutable and cannot be changed.") - else: - super().__setattr__(key, value) + super().__setattr__(key, value) @cachedmethod(cache=operator.attrgetter("cache")) def get_code_tree(self, code: str): @@ -70,6 +69,12 @@ def get_function(self): return validate.create_function(self.code, self._function_entrypoint_name) def build_template_config(self) -> dict: + """ + Builds the template configuration for the custom component. + + Returns: + A dictionary representing the template configuration. + """ if not self.code: return {} diff --git a/src/backend/base/langflow/interface/custom/custom_component/custom_component.py b/src/backend/base/langflow/interface/custom/custom_component/custom_component.py index d97951f352..4276d4616e 100644 --- a/src/backend/base/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/interface/custom/custom_component/custom_component.py @@ -14,9 +14,10 @@ extract_union_types_from_generic_alias, ) from langflow.interface.custom.custom_component.component import Component -from langflow.schema import dotdict -from langflow.schema.schema import Record -from langflow.services.deps import get_credential_service, get_storage_service, session_scope +from langflow.schema import Record +from langflow.schema.dotdict import dotdict +from langflow.services.deps import get_storage_service, get_variable_service, session_scope +from langflow.services.storage.service import StorageService from langflow.utils import validate if TYPE_CHECKING: @@ -26,6 +27,23 @@ class CustomComponent(Component): + """ + Represents a custom component in Langflow. + + Attributes: + display_name (Optional[str]): The display name of the custom component. + description (Optional[str]): The description of the custom component. + code (Optional[str]): The code of the custom component. + field_config (dict): The field configuration of the custom component. + code_class_base_inheritance (ClassVar[str]): The base class name for the custom component. + function_entrypoint_name (ClassVar[str]): The name of the function entrypoint for the custom component. + function (Optional[Callable]): The function associated with the custom component. + repr_value (Optional[Any]): The representation value of the custom component. + user_id (Optional[Union[UUID, str]]): The user ID associated with the custom component. + status (Optional[Any]): The status of the custom component. + _tree (Optional[dict]): The code tree of the custom component. + """ + display_name: Optional[str] = None """The display name of the component. Defaults to None.""" description: Optional[str] = None @@ -88,6 +106,12 @@ def get_state(self, name: str): _tree: Optional[dict] = None def __init__(self, **data): + """ + Initializes a new instance of the CustomComponent class. + + Args: + **data: Additional keyword arguments to initialize the custom component. + """ self.cache = TTLCache(maxsize=1024, ttl=60) super().__init__(**data) @@ -115,6 +139,12 @@ def _get_field_order(self): return self.field_order or list(self.field_config.keys()) def custom_repr(self): + """ + Returns the custom representation of the custom component. + + Returns: + str: The custom representation of the custom component. + """ if self.repr_value == "": self.repr_value = self.status if isinstance(self.repr_value, dict): @@ -126,6 +156,12 @@ def custom_repr(self): return self.repr_value def build_config(self): + """ + Builds the configuration for the custom component. + + Returns: + dict: The configuration for the custom component. + """ return self.field_config def update_build_config( @@ -139,6 +175,12 @@ def update_build_config( @property def tree(self): + """ + Gets the code tree of the custom component. + + Returns: + dict: The code tree of the custom component. + """ return self.get_code_tree(self.code or "") def to_records(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False) -> List[Record]: @@ -215,6 +257,12 @@ def create_references_from_records(self, records: List[Record], include_data: bo @property def get_function_entrypoint_args(self) -> list: + """ + Gets the arguments of the function entrypoint for the custom component. + + Returns: + list: The arguments of the function entrypoint. + """ build_method = self.get_build_method() if not build_method: return [] @@ -228,6 +276,12 @@ def get_function_entrypoint_args(self) -> list: @cachedmethod(operator.attrgetter("cache")) def get_build_method(self): + """ + Gets the build method for the custom component. + + Returns: + dict: The build method for the custom component. + """ if not self.code: return {} @@ -245,6 +299,12 @@ def get_build_method(self): @property def get_function_entrypoint_return_type(self) -> List[Any]: + """ + Gets the return type of the function entrypoint for the custom component. + + Returns: + List[Any]: The return type of the function entrypoint. + """ build_method = self.get_build_method() if not build_method or not build_method.get("has_return"): return [] @@ -266,6 +326,12 @@ def get_function_entrypoint_return_type(self) -> List[Any]: @property def get_main_class_name(self): + """ + Gets the main class name of the custom component. + + Returns: + str: The main class name of the custom component. + """ if not self.code: return "" @@ -284,31 +350,63 @@ def get_main_class_name(self): @property def template_config(self): + """ + Gets the template configuration for the custom component. + + Returns: + dict: The template configuration for the custom component. + """ return self.build_template_config() @property def keys(self): + """ + Returns the credential for the current user with the specified name. + + Raises: + ValueError: If the user id is not set. + + Returns: + The credential for the current user with the specified name. + """ + def get_credential(name: str): if hasattr(self, "_user_id") and not self._user_id: raise ValueError(f"User id is not set for {self.__class__.__name__}") - credential_service = get_credential_service() # Get service instance + variable_service = get_variable_service() # Get service instance # Retrieve and decrypt the credential by name for the current user - with session_scope() as session: - return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session) + return variable_service.get_credential(user_id=self._user_id or "", name=name, session=session) return get_credential def list_key_names(self): + """ + Lists the names of the variables for the current user. + + Raises: + ValueError: If the user id is not set. + + Returns: + List[str]: The names of the variables for the current user. + """ if hasattr(self, "_user_id") and not self._user_id: raise ValueError(f"User id is not set for {self.__class__.__name__}") - credential_service = get_credential_service() + variable_service = get_variable_service() with session_scope() as session: - return credential_service.list_credentials(user_id=self._user_id, session=session) + return variable_service.list_variables(user_id=self._user_id, session=session) def index(self, value: int = 0): - """Returns a function that returns the value at the given index in the iterable.""" + """ + Returns a function that returns the value at the given index in the iterable. + + Args: + value (int): The index value. + + Returns: + Callable: A function that returns the value at the given index. + """ def get_index(iterable: List[Any]): return iterable[value] if iterable else iterable @@ -316,14 +414,22 @@ def get_index(iterable: List[Any]): return get_index def get_function(self): + """ + Gets the function associated with the custom component. + + Returns: + Callable: The function associated with the custom component. + """ return validate.create_function(self.code, self.function_entrypoint_name) async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> "Graph": - return await load_flow(flow_id, tweaks) + if not self._user_id: + raise ValueError("Session is invalid") + return await load_flow(user_id=self._user_id, flow_id=flow_id, tweaks=tweaks) async def run_flow( self, - inputs: Union[dict, List[dict]] = None, + inputs: Optional[Union[dict, List[dict]]] = None, flow_id: Optional[str] = None, flow_name: Optional[str] = None, tweaks: Optional[dict] = None, @@ -339,4 +445,14 @@ def list_flows(self) -> List[Record]: raise ValueError(f"Error listing flows: {e}") def build(self, *args: Any, **kwargs: Any) -> Any: + """ + Builds the custom component. + + Args: + *args: The positional arguments. + **kwargs: The keyword arguments. + + Returns: + Any: The result of the build process. + """ raise NotImplementedError diff --git a/src/backend/base/langflow/interface/custom/directory_reader/directory_reader.py b/src/backend/base/langflow/interface/custom/directory_reader/directory_reader.py index 412bad4554..9ffb9825db 100644 --- a/src/backend/base/langflow/interface/custom/directory_reader/directory_reader.py +++ b/src/backend/base/langflow/interface/custom/directory_reader/directory_reader.py @@ -3,9 +3,8 @@ import zlib from pathlib import Path -from loguru import logger - from langflow.interface.custom.custom_component import CustomComponent +from loguru import logger class CustomComponentPathValueError(ValueError): diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index 79fc60a803..b1be1b6b6d 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -29,8 +29,8 @@ from langflow.utils.util import unescape_string if TYPE_CHECKING: + from langflow.custom import CustomComponent from langflow.graph.vertex.base import Vertex - from langflow.interface.custom.custom_component import CustomComponent async def instantiate_class( @@ -38,7 +38,7 @@ async def instantiate_class( user_id=None, ) -> Any: """Instantiate class from module type and key, and params""" - from langflow.legacy_custom.customs import CUSTOM_NODES + from langflow.interface.custom_lists import CUSTOM_NODES vertex_type = vertex.vertex_type base_type = vertex.base_type @@ -50,7 +50,9 @@ async def instantiate_class( if custom_node := CUSTOM_NODES.get(vertex_type): if hasattr(custom_node, "initialize"): return custom_node.initialize(**params) - return custom_node(**params) + if callable(custom_node): + return custom_node(**params) + raise ValueError(f"Custom node {vertex_type} is not callable") logger.debug(f"Instantiating {vertex_type} of type {base_type}") if not base_type: raise ValueError("No base type provided for vertex") @@ -143,6 +145,21 @@ async def instantiate_based_on_type( return class_object(**params) +def update_params_with_load_from_db_fields(custom_component: "CustomComponent", params, load_from_db_fields): + # For each field in load_from_db_fields, we will check if it's in the params + # and if it is, we will get the value from the custom_component.keys(name) + # and update the params with the value + for field in load_from_db_fields: + if field in params: + try: + key = custom_component.keys(params[field]) + params[field] = key if key else params[field] + except Exception as exc: + logger.error(f"Failed to get value for {field} from custom component. Error: {exc}") + pass + return params + + async def instantiate_custom_component(params, user_id, vertex): params_copy = params.copy() class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code")) @@ -152,6 +169,7 @@ async def instantiate_custom_component(params, user_id, vertex): vertex=vertex, selected_output_type=vertex.selected_output_type, ) + params_copy = update_params_with_load_from_db_fields(custom_component, params_copy, vertex.load_from_db_fields) if "retriever" in params_copy and hasattr(params_copy["retriever"], "as_retriever"): params_copy["retriever"] = params_copy["retriever"].as_retriever() diff --git a/src/backend/base/langflow/interface/tools/base.py b/src/backend/base/langflow/interface/tools/base.py index 2506c01706..ddaccb4bbd 100644 --- a/src/backend/base/langflow/interface/tools/base.py +++ b/src/backend/base/langflow/interface/tools/base.py @@ -5,8 +5,6 @@ from langflow.interface.tools.constants import ALL_TOOLS_NAMES, CUSTOM_TOOLS, FILE_TOOLS, OTHER_TOOLS from langflow.interface.tools.util import get_tool_params from langflow.legacy_custom import customs -from langflow.interface.tools.util import get_tool_params -from langflow.legacy_custom import customs from langflow.services.deps import get_settings_service from langflow.template.field.base import TemplateField from langflow.template.template.base import Template diff --git a/src/backend/base/langflow/legacy_custom/customs.py b/src/backend/base/langflow/legacy_custom/customs.py index 82e87f1e55..ff69064ffd 100644 --- a/src/backend/base/langflow/legacy_custom/customs.py +++ b/src/backend/base/langflow/legacy_custom/customs.py @@ -1,7 +1,7 @@ from langflow.template import frontend_node # These should always be instantiated -CUSTOM_NODES = { +CUSTOM_NODES: dict[str, dict[str, frontend_node.base.FrontendNode]] = { # "prompts": { # "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(), # }, diff --git a/src/backend/base/langflow/processing/process.py b/src/backend/base/langflow/processing/process.py index fa499472fa..c7e496dd77 100644 --- a/src/backend/base/langflow/processing/process.py +++ b/src/backend/base/langflow/processing/process.py @@ -125,7 +125,7 @@ class Result(BaseModel): async def run_graph( - graph: Union["Graph", dict], + graph: "Graph", flow_id: str, stream: bool, session_id: Optional[str] = None, diff --git a/src/backend/base/langflow/services/credentials/__init__.py b/src/backend/base/langflow/py.typed similarity index 100% rename from src/backend/base/langflow/services/credentials/__init__.py rename to src/backend/base/langflow/py.typed diff --git a/src/backend/base/langflow/services/database/models/__init__.py b/src/backend/base/langflow/services/database/models/__init__.py index 9dd3e0285b..1f19435bd4 100644 --- a/src/backend/base/langflow/services/database/models/__init__.py +++ b/src/backend/base/langflow/services/database/models/__init__.py @@ -1,6 +1,6 @@ from .api_key import ApiKey -from .credential import Credential from .flow import Flow from .user import User +from .variable import Variable -__all__ = ["Flow", "User", "ApiKey", "Credential"] +__all__ = ["Flow", "User", "ApiKey", "Variable"] diff --git a/src/backend/base/langflow/services/database/models/credential/__init__.py b/src/backend/base/langflow/services/database/models/credential/__init__.py deleted file mode 100644 index 2f8b6cd014..0000000000 --- a/src/backend/base/langflow/services/database/models/credential/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .model import Credential, CredentialCreate, CredentialRead, CredentialUpdate - -__all__ = ["Credential", "CredentialCreate", "CredentialRead", "CredentialUpdate"] diff --git a/src/backend/base/langflow/services/database/models/credential/model.py b/src/backend/base/langflow/services/database/models/credential/model.py deleted file mode 100644 index 95bd4b829b..0000000000 --- a/src/backend/base/langflow/services/database/models/credential/model.py +++ /dev/null @@ -1,43 +0,0 @@ -from datetime import datetime -from typing import TYPE_CHECKING, Optional -from uuid import UUID, uuid4 - -from sqlmodel import Field, Relationship, SQLModel - -from langflow.services.database.models.credential.schema import CredentialType - -if TYPE_CHECKING: - from langflow.services.database.models.user import User - - -class CredentialBase(SQLModel): - name: Optional[str] = Field(None, description="Name of the credential") - value: Optional[str] = Field(None, description="Encrypted value of the credential") - provider: Optional[str] = Field(None, description="Provider of the credential (e.g OpenAI)") - - -class Credential(CredentialBase, table=True): - id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True, description="Unique ID for the credential") - # name is unique per user - created_at: datetime = Field(default_factory=datetime.utcnow, description="Creation time of the credential") - updated_at: Optional[datetime] = Field(None, description="Last update time of the credential") - # foreign key to user table - user_id: UUID = Field(description="User ID associated with this credential", foreign_key="user.id") - user: "User" = Relationship(back_populates="credentials") - - -class CredentialCreate(CredentialBase): - # AcceptedProviders is a custom Enum - provider: CredentialType = Field(description="Provider of the credential (e.g OpenAI)") - - -class CredentialRead(SQLModel): - id: UUID - name: Optional[str] = Field(None, description="Name of the credential") - provider: Optional[str] = Field(None, description="Provider of the credential (e.g OpenAI)") - - -class CredentialUpdate(SQLModel): - id: UUID # Include the ID for updating - name: Optional[str] = Field(None, description="Name of the credential") - value: Optional[str] = Field(None, description="Encrypted value of the credential") diff --git a/src/backend/base/langflow/services/database/models/credential/schema.py b/src/backend/base/langflow/services/database/models/credential/schema.py deleted file mode 100644 index 9859153406..0000000000 --- a/src/backend/base/langflow/services/database/models/credential/schema.py +++ /dev/null @@ -1,8 +0,0 @@ -from enum import Enum - - -class CredentialType(str, Enum): - """CredentialType is an Enum of the accepted providers""" - - OPENAI_API_KEY = "OPENAI_API_KEY" - ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY" diff --git a/src/backend/base/langflow/services/database/models/flow/model.py b/src/backend/base/langflow/services/database/models/flow/model.py index 95311909c2..cbfdec563c 100644 --- a/src/backend/base/langflow/services/database/models/flow/model.py +++ b/src/backend/base/langflow/services/database/models/flow/model.py @@ -1,4 +1,4 @@ -# Path: src/backend/langflow/database/models/flow.py +# Path: src/backend/langflow/services/database/models/flow/model.py from datetime import datetime from typing import TYPE_CHECKING, Dict, Optional diff --git a/src/backend/base/langflow/services/database/models/user/model.py b/src/backend/base/langflow/services/database/models/user/model.py index dccd3c3056..fa229b4b30 100644 --- a/src/backend/base/langflow/services/database/models/user/model.py +++ b/src/backend/base/langflow/services/database/models/user/model.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from langflow.services.database.models.api_key import ApiKey - from langflow.services.database.models.credential import Credential + from langflow.services.database.models.variable import Variable from langflow.services.database.models.flow import Flow @@ -26,7 +26,7 @@ class User(SQLModel, table=True): ) store_api_key: Optional[str] = Field(default=None, nullable=True) flows: list["Flow"] = Relationship(back_populates="user") - credentials: list["Credential"] = Relationship( + variables: list["Variable"] = Relationship( back_populates="user", sa_relationship_kwargs={"cascade": "delete"}, ) diff --git a/src/backend/base/langflow/services/database/models/variable/__init__.py b/src/backend/base/langflow/services/database/models/variable/__init__.py new file mode 100644 index 0000000000..62ecc4f870 --- /dev/null +++ b/src/backend/base/langflow/services/database/models/variable/__init__.py @@ -0,0 +1,3 @@ +from .model import Variable, VariableCreate, VariableRead, VariableUpdate + +__all__ = ["Variable", "VariableCreate", "VariableRead", "VariableUpdate"] diff --git a/src/backend/base/langflow/services/database/models/variable/model.py b/src/backend/base/langflow/services/database/models/variable/model.py new file mode 100644 index 0000000000..5ad2af23b1 --- /dev/null +++ b/src/backend/base/langflow/services/database/models/variable/model.py @@ -0,0 +1,55 @@ +from datetime import datetime,timezone +from typing import TYPE_CHECKING, Optional +from uuid import UUID, uuid4 + +from sqlmodel import Field, Relationship, SQLModel + + +if TYPE_CHECKING: + from langflow.services.database.models.user.model import User + + +def utc_now(): + return datetime.now(timezone.utc) + + +class VariableBase(SQLModel): + name: Optional[str] = Field(None, description="Name of the variable") + value: Optional[str] = Field(None, description="Encrypted value of the variable") + type: Optional[str] = Field(None, description="Type of the variable") + + +class Variable(VariableBase, table=True): + id: Optional[UUID] = Field( + default_factory=uuid4, + primary_key=True, + description="Unique ID for the variable", + ) + # name is unique per user + created_at: datetime = Field( + default_factory=utc_now, description="Creation time of the variable" + ) + updated_at: Optional[datetime] = Field( + None, description="Last update time of the variable" + ) + # foreign key to user table + user_id: UUID = Field( + description="User ID associated with this variable", foreign_key="user.id" + ) + user: "User" = Relationship(back_populates="variables") + + +class VariableCreate(VariableBase): + type: Optional[str] = Field(None, description="Type of the variable") + + +class VariableRead(SQLModel): + id: UUID + name: Optional[str] = Field(None, description="Name of the variable") + type: Optional[str] = Field(None, description="Type of the variable") + + +class VariableUpdate(SQLModel): + id: UUID # Include the ID for updating + name: Optional[str] = Field(None, description="Name of the variable") + value: Optional[str] = Field(None, description="Encrypted value of the variable") diff --git a/src/backend/base/langflow/services/deps.py b/src/backend/base/langflow/services/deps.py index ba990eb702..ec3a5ec845 100644 --- a/src/backend/base/langflow/services/deps.py +++ b/src/backend/base/langflow/services/deps.py @@ -8,7 +8,6 @@ from langflow.services.cache.service import CacheService from langflow.services.chat.service import ChatService - from langflow.services.credentials.service import CredentialService from langflow.services.database.service import DatabaseService from langflow.services.monitor.service import MonitorService from langflow.services.plugins.service import PluginService @@ -18,6 +17,7 @@ from langflow.services.storage.service import StorageService from langflow.services.store.service import StoreService from langflow.services.task.service import TaskService + from langflow.services.variable.service import VariableService def get_socket_service() -> "SocketIOService": @@ -28,8 +28,8 @@ def get_storage_service() -> "StorageService": return service_manager.get(ServiceType.STORAGE_SERVICE) # type: ignore -def get_credential_service() -> "CredentialService": - return service_manager.get(ServiceType.CREDENTIAL_SERVICE) # type: ignore +def get_variable_service() -> "VariableService": + return service_manager.get(ServiceType.VARIABLE_SERVICE) # type: ignore def get_plugins_service() -> "PluginService": diff --git a/src/backend/base/langflow/services/factory.py b/src/backend/base/langflow/services/factory.py index 4780b53c78..49bea7db24 100644 --- a/src/backend/base/langflow/services/factory.py +++ b/src/backend/base/langflow/services/factory.py @@ -23,7 +23,7 @@ def create(self, *args, **kwargs) -> "Service": raise self.service_class(*args, **kwargs) -def hash_factory(factory: ServiceFactory) -> str: +def hash_factory(factory: Type[ServiceFactory]) -> str: return factory.service_class.__name__ @@ -38,7 +38,7 @@ def hash_infer_service_types_args(factory_class: Type[ServiceFactory], available @cached(cache=LRUCache(maxsize=10), key=hash_infer_service_types_args) -def infer_service_types(factory_class: Type[ServiceFactory], available_services=None) -> "ServiceType": +def infer_service_types(factory_class: Type[ServiceFactory], available_services=None) -> list["ServiceType"]: create_method = factory_class.create type_hints = get_type_hints(create_method, globalns=available_services) service_types = [] diff --git a/src/backend/base/langflow/services/schema.py b/src/backend/base/langflow/services/schema.py index b5104767a4..1600c399e6 100644 --- a/src/backend/base/langflow/services/schema.py +++ b/src/backend/base/langflow/services/schema.py @@ -16,7 +16,7 @@ class ServiceType(str, Enum): TASK_SERVICE = "task_service" PLUGINS_SERVICE = "plugins_service" STORE_SERVICE = "store_service" - CREDENTIALS_SERVICE = "credentials_service" + VARIABLE_SERVICE = "variable_service" STORAGE_SERVICE = "storage_service" MONITOR_SERVICE = "monitor_service" SOCKETIO_SERVICE = "socket_service" diff --git a/src/backend/base/langflow/services/settings/auth.py b/src/backend/base/langflow/services/settings/auth.py index 3dd92a04ab..a5bd7ed804 100644 --- a/src/backend/base/langflow/services/settings/auth.py +++ b/src/backend/base/langflow/services/settings/auth.py @@ -1,5 +1,6 @@ import secrets from pathlib import Path +from typing import Literal from loguru import logger from passlib.context import CryptContext @@ -14,7 +15,7 @@ class AuthSettings(BaseSettings): # Login settings CONFIG_DIR: str SECRET_KEY: SecretStr = Field( - default=None, + default=SecretStr(""), description="Secret key for JWT. If not provided, a random one will be generated.", frozen=False, ) @@ -33,13 +34,13 @@ class AuthSettings(BaseSettings): SUPERUSER: str = DEFAULT_SUPERUSER SUPERUSER_PASSWORD: str = DEFAULT_SUPERUSER_PASSWORD - REFRESH_SAME_SITE: str = "none" + REFRESH_SAME_SITE: Literal["lax", "strict", "none"] = "none" """The SameSite attribute of the refresh token cookie.""" REFRESH_SECURE: bool = True """The Secure attribute of the refresh token cookie.""" REFRESH_HTTPONLY: bool = True """The HttpOnly attribute of the refresh token cookie.""" - ACCESS_SAME_SITE: str = "none" + ACCESS_SAME_SITE: Literal["lax", "strict", "none"] = "none" """The SameSite attribute of the access token cookie.""" ACCESS_SECURE: bool = True """The Secure attribute of the access token cookie.""" @@ -85,9 +86,10 @@ def get_secret_key(cls, value, values): secret_key_path = Path(config_dir) / "secret_key" - if value: + if value and isinstance(value, SecretStr): logger.debug("Secret key provided") - write_secret_to_file(secret_key_path, value) + secret_value = value.get_secret_value() + write_secret_to_file(secret_key_path, secret_value) else: logger.debug("No secret key provided, generating a random one") @@ -103,4 +105,4 @@ def get_secret_key(cls, value, values): write_secret_to_file(secret_key_path, value) logger.debug("Saved secret key") - return value + return value if isinstance(value, SecretStr) else SecretStr(value) diff --git a/src/backend/langflow/base/agents/__init__.py b/src/backend/base/langflow/services/variable/__init__.py similarity index 100% rename from src/backend/langflow/base/agents/__init__.py rename to src/backend/base/langflow/services/variable/__init__.py diff --git a/src/backend/base/langflow/services/credentials/factory.py b/src/backend/base/langflow/services/variable/factory.py similarity index 55% rename from src/backend/base/langflow/services/credentials/factory.py rename to src/backend/base/langflow/services/variable/factory.py index c44a43da42..aac384807e 100644 --- a/src/backend/base/langflow/services/credentials/factory.py +++ b/src/backend/base/langflow/services/variable/factory.py @@ -1,15 +1,15 @@ from typing import TYPE_CHECKING -from langflow.services.credentials.service import CredentialService from langflow.services.factory import ServiceFactory +from langflow.services.variable.service import VariableService if TYPE_CHECKING: from langflow.services.settings.service import SettingsService -class CredentialServiceFactory(ServiceFactory): +class VariableServiceFactory(ServiceFactory): def __init__(self): - super().__init__(CredentialService) + super().__init__(VariableService) def create(self, settings_service: "SettingsService"): - return CredentialService(settings_service) + return VariableService(settings_service) diff --git a/src/backend/base/langflow/services/credentials/service.py b/src/backend/base/langflow/services/variable/service.py similarity index 69% rename from src/backend/base/langflow/services/credentials/service.py rename to src/backend/base/langflow/services/variable/service.py index 74d650687f..a555f3177e 100644 --- a/src/backend/base/langflow/services/credentials/service.py +++ b/src/backend/base/langflow/services/variable/service.py @@ -6,25 +6,23 @@ from langflow.services.auth import utils as auth_utils from langflow.services.base import Service -from langflow.services.database.models.credential.model import Credential +from langflow.services.database.models.variable.model import Variable from langflow.services.deps import get_session if TYPE_CHECKING: from langflow.services.settings.service import SettingsService -class CredentialService(Service): - name = "credential_service" +class VariableService(Service): + name = "variable_service" def __init__(self, settings_service: "SettingsService"): self.settings_service = settings_service def get_credential(self, user_id: Union[UUID, str], name: str, session: Session = Depends(get_session)) -> str: # we get the credential from the database - # credential = session.query(Credential).filter(Credential.user_id == user_id, Credential.name == name).first() - credential = session.exec( - select(Credential).where(Credential.user_id == user_id, Credential.name == name) - ).first() + # credential = session.query(Variable).filter(Variable.user_id == user_id, Variable.name == name).first() + credential = session.exec(select(Variable).where(Variable.user_id == user_id, Variable.name == name)).first() # we decrypt the value if not credential or not credential.value: raise ValueError(f"{name} credential not found.") @@ -34,5 +32,5 @@ def get_credential(self, user_id: Union[UUID, str], name: str, session: Session def list_credentials( self, user_id: Union[UUID, str], session: Session = Depends(get_session) ) -> list[Optional[str]]: - credentials = session.exec(select(Credential).where(Credential.user_id == user_id)).all() + credentials = session.exec(select(Variable).where(Variable.user_id == user_id)).all() return [credential.name for credential in credentials] diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index 28d960298d..85c9affdd7 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -7,6 +7,7 @@ class TemplateField(BaseModel): model_config = ConfigDict() + field_type: str = Field(default="str", serialization_alias="type") """The type of field this is. Default is a string.""" @@ -69,6 +70,8 @@ class TemplateField(BaseModel): range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec") """Range specification for the field. Defaults to None.""" + load_from_db: bool = False + """Specifies if the field should be loaded from the database. Defaults to False.""" title_case: bool = False """Specifies if the field should be displayed in title case. Defaults to True.""" diff --git a/src/backend/base/langflow/template/frontend_node/__init__.py b/src/backend/base/langflow/template/frontend_node/__init__.py index 00c2766962..ceb2e0cb9b 100644 --- a/src/backend/base/langflow/template/frontend_node/__init__.py +++ b/src/backend/base/langflow/template/frontend_node/__init__.py @@ -10,10 +10,12 @@ textsplitters, tools, vectorstores, + base, ) __all__ = [ "agents", + "base", "chains", "embeddings", "memories", diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index 3e829d4b7b..f56dd104a5 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -17,10 +17,11 @@ repository = "https://github.com/logspace-ai/langflow" license = "MIT" readme = "README.md" keywords = ["nlp", "langchain", "openai", "gpt", "gui"] -packages = [{ include = "langflow" }] +packages = [{ include = "langflow" }, { include = "langflow/py.typed" }] include = ["pyproject.toml", "README.md", "langflow/**/*"] documentation = "https://docs.langflow.org" + [tool.poetry.scripts] langflow-base = "langflow.__main__:main" @@ -106,6 +107,11 @@ filterwarnings = ["ignore::DeprecationWarning"] log_cli = true markers = ["async_test"] +[tool.mypy] +namespace_packages = true +mypy_path = "langflow" +ignore_missing_imports = true + [tool.ruff] exclude = ["src/backend/langflow/alembic/*"] diff --git a/src/backend/langflow/base/agents/agent.py b/src/backend/langflow/base/agents/agent.py deleted file mode 100644 index 8dac73e61c..0000000000 --- a/src/backend/langflow/base/agents/agent.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import List, Union - -from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent - -from langflow import CustomComponent -from langflow.field_typing import BaseMemory, Text, Tool - - -class LCAgentComponent(CustomComponent): - def build_config(self): - return { - "lc": { - "display_name": "LangChain", - "info": "The LangChain to interact with.", - }, - "handle_parsing_errors": { - "display_name": "Handle Parsing Errors", - "info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.", - "advanced": True, - }, - "output_key": { - "display_name": "Output Key", - "info": "The key to use to get the output from the agent.", - "advanced": True, - }, - "memory": { - "display_name": "Memory", - "info": "Memory to use for the agent.", - }, - "tools": { - "display_name": "Tools", - "info": "Tools the agent can use.", - }, - "input_value": { - "display_name": "Input", - "info": "Input text to pass to the agent.", - }, - } - - async def run_agent( - self, - agent: Union[BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor], - inputs: str, - input_variables: list[str], - tools: List[Tool], - memory: BaseMemory = None, - handle_parsing_errors: bool = True, - output_key: str = "output", - ) -> Text: - if isinstance(agent, AgentExecutor): - runnable = agent - else: - runnable = AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=handle_parsing_errors - ) - input_dict = {"input": inputs} - for var in input_variables: - if var not in ["agent_scratchpad", "input"]: - input_dict[var] = "" - result = await runnable.ainvoke(input_dict) - self.status = result - if output_key in result: - return result.get(output_key) - elif "output" not in result: - if output_key != "output": - raise ValueError(f"Output key not found in result. Tried '{output_key}' and 'output'.") - else: - raise ValueError("Output key not found in result. Tried 'output'.") - - return result.get("output") diff --git a/src/backend/langflow/base/models/__init__.py b/src/backend/langflow/base/models/__init__.py deleted file mode 100644 index 921f10336a..0000000000 --- a/src/backend/langflow/base/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .model import LCModelComponent - -__all__ = ["LCModelComponent"] diff --git a/src/backend/langflow/base/models/model.py b/src/backend/langflow/base/models/model.py deleted file mode 100644 index e2ab4b6cf8..0000000000 --- a/src/backend/langflow/base/models/model.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Optional - -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.language_models.llms import LLM -from langchain_core.messages import HumanMessage, SystemMessage - -from langflow import CustomComponent - - -class LCModelComponent(CustomComponent): - display_name: str = "Model Name" - description: str = "Model Description" - - def get_result(self, runnable: LLM, stream: bool, input_value: str): - """ - Retrieves the result from the output of a Runnable object. - - Args: - output (Runnable): The output object to retrieve the result from. - stream (bool): Indicates whether to use streaming or invocation mode. - input_value (str): The input value to pass to the output object. - - Returns: - The result obtained from the output object. - """ - if stream: - result = runnable.stream(input_value) - else: - message = runnable.invoke(input_value) - result = message.content if hasattr(message, "content") else message - self.status = result - return result - - def get_chat_result( - self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None - ): - messages = [] - if input_value: - messages.append(HumanMessage(input_value)) - if system_message: - messages.append(SystemMessage(system_message)) - if stream: - result = runnable.stream(messages) - else: - message = runnable.invoke(messages) - result = message.content - self.status = result - return result diff --git a/src/backend/langflow/components/experimental/FlowTool.py b/src/backend/langflow/components/experimental/FlowTool.py deleted file mode 100644 index 5f504ff027..0000000000 --- a/src/backend/langflow/components/experimental/FlowTool.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Any, List, Optional, Text - -from langchain_core.tools import StructuredTool -from loguru import logger - -from langflow import CustomComponent -from langflow.field_typing import Tool -from langflow.graph.graph.base import Graph -from langflow.helpers.flow import build_function_and_schema -from langflow.schema.dotdict import dotdict - - -class FlowToolComponent(CustomComponent): - display_name = "Flow as Tool" - description = "Construct a Tool from a function that runs the loaded Flow." - field_order = ["flow_name", "name", "description", "return_direct"] - - def get_flow_names(self) -> List[str]: - flow_records = self.list_flows() - return [flow_record.data["name"] for flow_record in flow_records] - - def get_flow(self, flow_name: str) -> Optional[Text]: - """ - Retrieves a flow by its name. - - Args: - flow_name (str): The name of the flow to retrieve. - - Returns: - Optional[Text]: The flow record if found, None otherwise. - """ - flow_records = self.list_flows() - for flow_record in flow_records: - if flow_record.data["name"] == flow_name: - return flow_record - return None - - def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None): - logger.debug(f"Updating build config with field value {field_value} and field name {field_name}") - if field_name == "flow_name": - build_config["flow_name"]["options"] = self.get_flow_names() - - return build_config - - def build_config(self): - return { - "flow_name": { - "display_name": "Flow Name", - "info": "The name of the flow to run.", - "options": [], - "real_time_refresh": True, - "refresh_button": True, - }, - "name": { - "display_name": "Name", - "description": "The name of the tool.", - }, - "description": { - "display_name": "Description", - "description": "The description of the tool.", - }, - "return_direct": { - "display_name": "Return Direct", - "description": "Return the result directly from the Tool.", - "advanced": True, - }, - } - - async def build(self, flow_name: str, name: str, description: str, return_direct: bool = False) -> Tool: - flow_record = self.get_flow(flow_name) - if not flow_record: - raise ValueError("Flow not found.") - graph = Graph.from_payload(flow_record.data["data"]) - dynamic_flow_function, schema = build_function_and_schema(flow_record, graph) - tool = StructuredTool.from_function( - coroutine=dynamic_flow_function, - name=name, - description=description, - return_direct=return_direct, - args_schema=schema, - ) - description_repr = repr(tool.description).strip("'") - args_str = "\n".join([f"- {arg_name}: {arg_data['description']}" for arg_name, arg_data in tool.args.items()]) - self.status = f"{description_repr}\nArguments:\n{args_str}" - return tool diff --git a/src/backend/langflow/components/experimental/Schema.py b/src/backend/langflow/components/experimental/Schema.py deleted file mode 100644 index 72cbec23e2..0000000000 --- a/src/backend/langflow/components/experimental/Schema.py +++ /dev/null @@ -1,25 +0,0 @@ -from langflow.custom import CustomComponent - - -class SchemaComponent(CustomComponent): - display_name = "Schema" - description = "Construct a Schema from a list of fields." - - def build_config(self): - return { - "fields": { - "display_name": "Fields", - "info": "The fields to include in the schema.", - }, - "name": { - "display_name": "Name", - "info": "The name of the schema.", - }, - } - - def build(self, name: str, fields: list[dict]): - # The idea for this component is to use create_model from pydantic to create a schema - # from a list of fields. This will be useful for creating schemas for the flow tool. - pass - - # field is a simple list of dictionaries with the field name and diff --git a/src/backend/langflow/components/tools/SearchAPITool.py b/src/backend/langflow/components/tools/SearchAPITool.py deleted file mode 100644 index 76c82ad9c5..0000000000 --- a/src/backend/langflow/components/tools/SearchAPITool.py +++ /dev/null @@ -1,37 +0,0 @@ -from langchain_community.tools.searchapi import SearchAPIRun -from langchain_community.utilities.searchapi import SearchApiAPIWrapper - -from langflow import CustomComponent -from langflow.field_typing import Tool - - -class SearchApiToolComponent(CustomComponent): - display_name: str = "SearchApi Tool" - description: str = "Real-time search engine results API." - documentation: str = "https://www.searchapi.io/docs/google" - field_config = { - "engine": { - "display_name": "Engine", - "field_type": "str", - "info": "The search engine to use.", - }, - "api_key": { - "display_name": "API Key", - "field_type": "str", - "required": True, - "password": True, - "info": "The API key to use SearchApi.", - }, - } - - def build( - self, - engine: str, - api_key: str, - ) -> Tool: - search_api_wrapper = SearchApiAPIWrapper(engine=engine, searchapi_api_key=api_key) - - tool = SearchAPIRun(api_wrapper=search_api_wrapper) - - self.status = tool - return tool diff --git a/src/backend/langflow/version/__init__.py b/src/backend/langflow/version/__init__.py new file mode 100644 index 0000000000..5c0cf285cb --- /dev/null +++ b/src/backend/langflow/version/__init__.py @@ -0,0 +1 @@ +from .version import __version__ # noqa: F401 diff --git a/src/backend/base/langflow/version.py b/src/backend/langflow/version/version.py similarity index 53% rename from src/backend/base/langflow/version.py rename to src/backend/langflow/version/version.py index c8bb365cac..3aaf5f9737 100644 --- a/src/backend/base/langflow/version.py +++ b/src/backend/langflow/version/version.py @@ -3,6 +3,5 @@ try: __version__ = metadata.version(__package__) except metadata.PackageNotFoundError: - # Case where package metadata is not available. __version__ = "" -del metadata # optional, avoids polluting the results of dir(__package__) +del metadata diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 10ad180738..117ee8daba 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -42,6 +42,7 @@ "base64-js": "^1.5.1", "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", + "cmdk": "^1.0.0", "dompurify": "^3.0.5", "esbuild": "^0.17.19", "framer-motion": "^11.0.6", @@ -124,12 +125,12 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -150,104 +151,40 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", - "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.9", - "@babel/parser": "^7.23.9", - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -276,13 +213,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -357,11 +294,11 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -408,9 +345,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "engines": { "node": ">=6.9.0" } @@ -432,26 +369,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -522,9 +460,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -533,9 +471,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -544,31 +482,31 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -577,9 +515,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -625,9 +563,9 @@ "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", - "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "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==", "dependencies": { "@emotion/memoize": "^0.8.1" } @@ -638,9 +576,9 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/react": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz", - "integrity": "sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==", + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.11.0", @@ -1148,13 +1086,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1169,9 +1107,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } @@ -1182,9 +1120,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1210,14 +1148,14 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz", - "integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", "dependencies": { "@babel/runtime": "^7.23.9", "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", "clsx": "^2.1.0", "prop-types": "^15.8.1" @@ -1249,25 +1187,25 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.10.tgz", - "integrity": "sha512-qPv7B+LeMatYuzRjB3hlZUHqinHx/fX4YFBiaS19oC02A1e9JFuDKDvlyRQQ5oRSbJJt0QlaLTlr0IcauVcJRQ==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", + "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/material": { - "version": "5.15.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.10.tgz", - "integrity": "sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", + "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.36", - "@mui/core-downloads-tracker": "^5.15.10", - "@mui/system": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.14", + "@mui/system": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", "csstype": "^3.1.3", @@ -1310,12 +1248,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.9.tgz", - "integrity": "sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", + "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.9", + "@mui/utils": "^5.15.14", "prop-types": "^15.8.1" }, "engines": { @@ -1336,9 +1274,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.9.tgz", - "integrity": "sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", "dependencies": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", @@ -1367,15 +1305,15 @@ } }, "node_modules/@mui/system": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.9.tgz", - "integrity": "sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", + "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.9", - "@mui/styled-engine": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/private-theming": "^5.15.14", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1414,9 +1352,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.13", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", - "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0" }, @@ -1427,9 +1365,9 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.9.tgz", - "integrity": "sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", + "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", "dependencies": { "@babel/runtime": "^7.23.9", "@types/prop-types": "^15.7.11", @@ -1495,12 +1433,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.0.tgz", - "integrity": "sha512-2k1HzC28Fs+HiwbJOQDUwrWMttqSLUVdjCqitBOjdCD0svWOMQUVqrXX6iFD7POps6xXAojsX/dGBpKnjZctLA==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", + "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", "dev": true, "dependencies": { - "playwright": "1.42.0" + "playwright": "1.42.1" }, "bin": { "playwright": "cli.js" @@ -1519,20 +1457,20 @@ } }, "node_modules/@preact/signals-core": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.5.1.tgz", - "integrity": "sha512-dE6f+WCX5ZUDwXzUIWNMhhglmuLpqJhuy3X3xHrhZYI0Hm2LyQwOu0l9mdPiWrVNsE+Q7txOnJPgtIqHCYoBVA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.6.0.tgz", + "integrity": "sha512-O/XGxwP85h1F7+ouqTMOIZ3+V1whfaV9ToIVcuyGriD4JkSD00cQo54BKdqjvBJxbenvp7ynfqRHEwI6e+NIhw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, "node_modules/@preact/signals-react": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-2.0.0.tgz", - "integrity": "sha512-tMVi2SXFXlojaiPNWa8dlYaidR/XvEgMSp+iymKJgMssBM/QVtUQrodKZek1BJju+dkVHiyeuQHmkuLOI9oyNw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-2.0.1.tgz", + "integrity": "sha512-QQoxAPiliH6oeZPF5OZHmyala5zyFya0ZCp/A0lR4y6R9wO4lT7GdtYg5UFlmKNWMcFN6PiAjNPMTiNFMmnCpw==", "dependencies": { - "@preact/signals-core": "^1.5.1", + "@preact/signals-core": "^1.6.0", "use-sync-external-store": "^1.2.0" }, "funding": { @@ -2762,9 +2700,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz", - "integrity": "sha512-zcU0gM3z+3iqj8UX45AmWY810l3oUmXM7uH4dt5xtzvMhRtYVhKGOmgOd1877dOPPepfCjUv57w+syamWIYe7w==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", "engines": { "node": ">=14.0.0" } @@ -3081,9 +3019,9 @@ } }, "node_modules/@swc/core": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.2.tgz", - "integrity": "sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.8.tgz", + "integrity": "sha512-uY2RSJcFPgNOEg12RQZL197LZX+MunGiKxsbxmh22VfVxrOYGRvh4mPANFlrD1yb38CgmW1wI6YgIi8LkIwmWg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -3098,16 +3036,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.4.2", - "@swc/core-darwin-x64": "1.4.2", - "@swc/core-linux-arm-gnueabihf": "1.4.2", - "@swc/core-linux-arm64-gnu": "1.4.2", - "@swc/core-linux-arm64-musl": "1.4.2", - "@swc/core-linux-x64-gnu": "1.4.2", - "@swc/core-linux-x64-musl": "1.4.2", - "@swc/core-win32-arm64-msvc": "1.4.2", - "@swc/core-win32-ia32-msvc": "1.4.2", - "@swc/core-win32-x64-msvc": "1.4.2" + "@swc/core-darwin-arm64": "1.4.8", + "@swc/core-darwin-x64": "1.4.8", + "@swc/core-linux-arm-gnueabihf": "1.4.8", + "@swc/core-linux-arm64-gnu": "1.4.8", + "@swc/core-linux-arm64-musl": "1.4.8", + "@swc/core-linux-x64-gnu": "1.4.8", + "@swc/core-linux-x64-musl": "1.4.8", + "@swc/core-win32-arm64-msvc": "1.4.8", + "@swc/core-win32-ia32-msvc": "1.4.8", + "@swc/core-win32-x64-msvc": "1.4.8" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -3119,9 +3057,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.2.tgz", - "integrity": "sha512-1uSdAn1MRK5C1m/TvLZ2RDvr0zLvochgrZ2xL+lRzugLlCTlSA+Q4TWtrZaOz+vnnFVliCpw7c7qu0JouhgQIw==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.8.tgz", + "integrity": "sha512-hhQCffRTgzpTIbngSnC30vV6IJVTI9FFBF954WEsshsecVoCGFiMwazBbrkLG+RwXENTrMhgeREEFh6R3KRgKQ==", "cpu": [ "arm64" ], @@ -3135,9 +3073,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.2.tgz", - "integrity": "sha512-TYD28+dCQKeuxxcy7gLJUCFLqrwDZnHtC2z7cdeGfZpbI2mbfppfTf2wUPzqZk3gEC96zHd4Yr37V3Tvzar+lQ==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.8.tgz", + "integrity": "sha512-P3ZBw8Jr8rKhY/J8d+6WqWriqngGTgHwtFeJ8MIakQJTbdYbFgXSZxcvDiERg3psbGeFXaUaPI0GO6BXv9k/OQ==", "cpu": [ "x64" ], @@ -3151,9 +3089,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.2.tgz", - "integrity": "sha512-Eyqipf7ZPGj0vplKHo8JUOoU1un2sg5PjJMpEesX0k+6HKE2T8pdyeyXODN0YTFqzndSa/J43EEPXm+rHAsLFQ==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.8.tgz", + "integrity": "sha512-PP9JIJt19bUWhAGcQW6qMwTjZOcMyzkvZa0/LWSlDm0ORYVLmDXUoeQbGD3e0Zju9UiZxyulnpjEN0ZihJgPTA==", "cpu": [ "arm" ], @@ -3167,9 +3105,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.2.tgz", - "integrity": "sha512-wZn02DH8VYPv3FC0ub4my52Rttsus/rFw+UUfzdb3tHMHXB66LqN+rR0ssIOZrH6K+VLN6qpTw9VizjyoH0BxA==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.8.tgz", + "integrity": "sha512-HvEWnwKHkoVUr5iftWirTApFJ13hGzhAY2CMw4lz9lur2m+zhPviRRED0FCI6T95Knpv7+8eUOr98Z7ctrG6DQ==", "cpu": [ "arm64" ], @@ -3183,9 +3121,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.2.tgz", - "integrity": "sha512-3G0D5z9hUj9bXNcwmA1eGiFTwe5rWkuL3DsoviTj73TKLpk7u64ND0XjEfO0huVv4vVu9H1jodrKb7nvln/dlw==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.8.tgz", + "integrity": "sha512-kY8+qa7k/dEeBq9p0Hrta18QnJPpsiJvDQSLNaTIFpdM3aEM9zbkshWz8gaX5VVGUEALowCBUWqmzO4VaqM+2w==", "cpu": [ "arm64" ], @@ -3199,9 +3137,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.2.tgz", - "integrity": "sha512-LFxn9U8cjmYHw3jrdPNqPAkBGglKE3tCZ8rA7hYyp0BFxuo7L2ZcEnPm4RFpmSCCsExFH+LEJWuMGgWERoktvg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.8.tgz", + "integrity": "sha512-0WWyIw432wpO/zeGblwq4f2YWam4pn8Z/Ig4KzHMgthR/KmiLU3f0Z7eo45eVmq5vcU7Os1zi/Zb65OOt09q/w==", "cpu": [ "x64" ], @@ -3215,9 +3153,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.2.tgz", - "integrity": "sha512-dp0fAmreeVVYTUcb4u9njTPrYzKnbIH0EhH2qvC9GOYNNREUu2GezSIDgonjOXkHiTCvopG4xU7y56XtXj4VrQ==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.8.tgz", + "integrity": "sha512-p4yxvVS05rBNCrBaSTa20KK88vOwtg8ifTW7ec/yoab0bD5EwzzB8KbDmLLxE6uziFa0sdjF0dfRDwSZPex37Q==", "cpu": [ "x64" ], @@ -3231,9 +3169,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.2.tgz", - "integrity": "sha512-HlVIiLMQkzthAdqMslQhDkoXJ5+AOLUSTV6fm6shFKZKqc/9cJvr4S8UveNERL9zUficA36yM3bbfo36McwnvQ==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.8.tgz", + "integrity": "sha512-jKuXihxAaqUnbFfvPxtmxjdJfs87F1GdBf33il+VUmSyWCP4BE6vW+/ReDAe8sRNsKyrZ3UH1vI5q1n64csBUA==", "cpu": [ "arm64" ], @@ -3247,9 +3185,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.2.tgz", - "integrity": "sha512-WCF8faPGjCl4oIgugkp+kL9nl3nUATlzKXCEGFowMEmVVCFM0GsqlmGdPp1pjZoWc9tpYanoXQDnp5IvlDSLhA==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.8.tgz", + "integrity": "sha512-O0wT4AGHrX8aBeH6c2ADMHgagAJc5Kf6W48U5moyYDAkkVnKvtSc4kGhjWhe1Yl0sI0cpYh2In2FxvYsb44eWw==", "cpu": [ "ia32" ], @@ -3263,9 +3201,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.2.tgz", - "integrity": "sha512-oV71rwiSpA5xre2C5570BhCsg1HF97SNLsZ/12xv7zayGzqr3yvFALFJN8tHKpqUdCB4FGPjoP3JFdV3i+1wUw==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.8.tgz", + "integrity": "sha512-C2AYc3A2o+ECciqsJWRgIpp83Vk5EaRzHe7ed/xOWzVd0MsWR+fweEsyOjlmzHfpUxJSi46Ak3/BIZJlhZbXbg==", "cpu": [ "x64" ], @@ -3285,10 +3223,13 @@ "dev": true }, "node_modules/@swc/types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", - "dev": true + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", + "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", @@ -3362,11 +3303,11 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.1.2.tgz", - "integrity": "sha512-qibmxtctgOZo2I+3Rw5GR9kXgaa15U5r3/idDY1ItUKW15UK7GhCfyIfE6qYuJ1fxQF6dJDsD8SbpPyuJgpxuA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.2.0.tgz", + "integrity": "sha512-OEdMByf2hEfDa6XDbGlZN8qO6bTjlNKqjM3im9JG+u3mCL8jALy0T/67oDI001raUUPh1Bdmfn4ZvPOV5knpcg==", "dependencies": { - "@tanstack/virtual-core": "3.1.2" + "@tanstack/virtual-core": "3.2.0" }, "funding": { "type": "github", @@ -3378,9 +3319,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.1.2.tgz", - "integrity": "sha512-DATZJs8iejkIUqXZe6ruDAnjFo78BKnIIgqQZrc7CmEFqfLEN/TPD91n4hRfo6hpRB6xC00bwKxv7vdjFNEmOg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.2.0.tgz", + "integrity": "sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -3697,9 +3638,9 @@ } }, "node_modules/@types/d3-hierarchy": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", - "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==" + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", @@ -3853,9 +3794,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", "dev": true }, "node_modules/@types/mathjax": { @@ -3877,9 +3818,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "16.18.82", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.82.tgz", - "integrity": "sha512-pcDZtkx9z8XYV+ius2P3Ot2VVrcYOfXffBQUBuiszrlUzKSmoDYqo+mV+IoL8iIiIjjtOMvNSmH1hwJ+Q+f96Q==", + "version": "16.18.91", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.91.tgz", + "integrity": "sha512-h8Q4klc8xzc9kJKr7UYNtJde5TU2qEePVyH3WyzJaUC+3ptyc5kPQbWOIUcn8ZsG5+KSkq+P0py0kC0VqxgAXw==", "devOptional": true }, "node_modules/@types/parse-json": { @@ -3893,9 +3834,9 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { - "version": "18.2.57", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.57.tgz", - "integrity": "sha512-ZvQsktJgSYrQiMirAN60y4O/LRevIV8hUzSOSNB6gfR3/o3wCBFQx3sPwIYtuDMeiVgsSS3UzCV26tEzgnfvQw==", + "version": "18.2.67", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.67.tgz", + "integrity": "sha512-vkIE2vTIMHQ/xL0rgmuoECBCkZFZeHr49HeWSc24AptMbNRo7pwSBvj73rlJJs9fGKj0koS+V7kQB1jHS0uCgw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3903,9 +3844,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", - "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==", + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", "devOptional": true, "dependencies": { "@types/react": "*" @@ -3977,9 +3918,9 @@ "integrity": "sha512-jbQfFaw+57OBwPt7qSNHuW+RA8smmRwkWRS1Ozh6K/QxUspBgBV/LpdSzlY7vee8TomS6j3D33B9rIeH1qMwsA==" }, "node_modules/ace-builds": { - "version": "1.32.6", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.32.6.tgz", - "integrity": "sha512-dO5BnyDOhCnznhOpILzXq4jqkbhRXxNkf3BuVTmyxGyRLrhddfdyk6xXgy+7A8LENrcYoFi/sIxMuH3qjNUN4w==" + "version": "1.32.7", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.32.7.tgz", + "integrity": "sha512-ziv35kaYELFw4suWlotz/Xsl1/1LhWAbwFoD3zIgCgP9gXGECEsAM4GhiB0T0xZdmQjyv6hmAzO280g0+n4vGw==" }, "node_modules/acorn": { "version": "8.11.3", @@ -4124,9 +4065,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", - "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", "dependencies": { "tslib": "^2.0.0" }, @@ -4173,9 +4114,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "funding": [ { @@ -4192,8 +4133,8 @@ } ], "dependencies": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -4225,11 +4166,11 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -4446,11 +4387,14 @@ } }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bl": { @@ -4625,9 +4569,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001588", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", - "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", + "version": "1.0.30001599", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", + "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", "funding": [ { "type": "opencollective", @@ -4797,6 +4741,19 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz", + "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", + "dependencies": { + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/code-block-writer": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", @@ -5057,9 +5014,9 @@ } }, "node_modules/daisyui": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.7.2.tgz", - "integrity": "sha512-9UCss12Zmyk/22u+JbkVrHHxOzFOyY17HuqP5LeswI4hclbj6qbjJTovdj2zRy8cCH6/n6Wh0lTLjriGnyGh0g==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.7.3.tgz", + "integrity": "sha512-R8jUpBMAUm4rSyxzGa9QqFdJTkzREtb1QahXdDoOfElGiF4VbSuu5bfqQoOro1kkSagPy+aTKu5WtSSXmH3u3g==", "dev": true, "dependencies": { "css-selector-tokenizer": "^0.8", @@ -5332,9 +5289,9 @@ } }, "node_modules/dompurify": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", - "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.10.tgz", + "integrity": "sha512-WZDL8ZHTliEVP3Lk4phtvjg8SNQ3YMc5WVstxE8cszKZrFjzI4PF4ZTIk9VGAc9vZADO7uGO2V/ZiStcRSAT4Q==" }, "node_modules/dot-case": { "version": "3.0.4", @@ -5351,9 +5308,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.4.679", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.679.tgz", - "integrity": "sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==" + "version": "1.4.711", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.711.tgz", + "integrity": "sha512-hRg81qzvUEibX2lDxnFlVCHACa+LtrCPIsWAxo161LDYIB3jauf57RGsMZV9mvGwE98yGH06icj3zBEoOkxd/w==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -5923,20 +5880,21 @@ } }, "node_modules/framer-motion": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.6.tgz", - "integrity": "sha512-BpO3mWF8UwxzO3Ca5AmSkrg14QYTeJa9vKgoLOoBdBdTPj0e81i1dMwnX6EQJXRieUx20uiDBXq8bA6y7N6b8Q==", + "version": "11.0.18", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.18.tgz", + "integrity": "sha512-TPKJ1KqQzTQ//cGFAb/jrpbbNY8UVh2q69vAcVRA8AxsKYkl5nrW1nsmrHRc5dLsRdwlXSV1kuo1Khylhph7kw==", "dependencies": { "tslib": "^2.4.0" }, - "optionalDependencies": { - "@emotion/is-prop-valid": "^0.8.2" - }, "peerDependencies": { + "@emotion/is-prop-valid": "*", "react": "^18.0.0", "react-dom": "^18.0.0" }, "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, "react": { "optional": true }, @@ -5945,21 +5903,6 @@ } } }, - "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "optional": true, - "dependencies": { - "@emotion/memoize": "0.7.4" - } - }, - "node_modules/framer-motion/node_modules/@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "optional": true - }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -6199,9 +6142,9 @@ } }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -6737,10 +6680,13 @@ } }, "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6799,10 +6745,13 @@ } }, "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6873,22 +6822,28 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8386,13 +8341,13 @@ } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -8722,11 +8677,11 @@ } }, "node_modules/playwright": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz", - "integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", + "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", "dependencies": { - "playwright-core": "1.42.0" + "playwright-core": "1.42.1" }, "bin": { "playwright": "cli.js" @@ -8739,9 +8694,9 @@ } }, "node_modules/playwright-core": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz", - "integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==", + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", + "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", "bin": { "playwright-core": "cli.js" }, @@ -8759,9 +8714,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.37", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.37.tgz", + "integrity": "sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==", "funding": [ { "type": "opencollective", @@ -8779,7 +8734,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -8865,9 +8820,12 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } @@ -8891,9 +8849,9 @@ } }, "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -9394,9 +9352,9 @@ } }, "node_modules/react-error-boundary": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.12.tgz", - "integrity": "sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==", + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz", + "integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==", "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -9484,9 +9442,9 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz", - "integrity": "sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", "dependencies": { "react-style-singleton": "^2.2.1", "tslib": "^2.0.0" @@ -9505,11 +9463,11 @@ } }, "node_modules/react-router": { - "version": "6.22.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.1.tgz", - "integrity": "sha512-0pdoRGwLtemnJqn1K0XHUbnKiX0S4X8CgvVVmHGOWmofESj31msHo/1YiqcJWK7Wxfq2a4uvvtS01KAQyWK/CQ==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", "dependencies": { - "@remix-run/router": "1.15.1" + "@remix-run/router": "1.15.3" }, "engines": { "node": ">=14.0.0" @@ -9519,12 +9477,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.1.tgz", - "integrity": "sha512-iwMyyyrbL7zkKY7MRjOVRy+TMnS/OPusaFVxM2P11x9dzSzGmLsebkCvYirGq0DWB9K9hOspHYYtDz33gE5Duw==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", "dependencies": { - "@remix-run/router": "1.15.1", - "react-router": "6.22.1" + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" }, "engines": { "node": ">=14.0.0" @@ -10059,17 +10017,17 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.2", + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -10258,9 +10216,9 @@ } }, "node_modules/shadcn-ui/node_modules/npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dependencies": { "path-key": "^4.0.0" }, @@ -10379,12 +10337,12 @@ } }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" @@ -10473,9 +10431,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -10877,9 +10835,9 @@ } }, "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11056,9 +11014,9 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -11269,9 +11227,9 @@ } }, "node_modules/use-callback-ref": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", - "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", "dependencies": { "tslib": "^2.0.0" }, @@ -11929,31 +11887,34 @@ } }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", - "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -12121,9 +12082,9 @@ } }, "node_modules/zustand": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.1.tgz", - "integrity": "sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", "dependencies": { "use-sync-external-store": "1.2.0" }, diff --git a/src/frontend/package.json b/src/frontend/package.json index dd865530e5..8b20d5e6b0 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -37,6 +37,7 @@ "base64-js": "^1.5.1", "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", + "cmdk": "^1.0.0", "dompurify": "^3.0.5", "esbuild": "^0.17.19", "framer-motion": "^11.0.6", diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index fe4b9628d6..da3c50325a 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -15,11 +15,12 @@ import { FETCH_ERROR_MESSAGE, } from "./constants/constants"; import { AuthContext } from "./contexts/authContext"; -import { getHealth } from "./controllers/API"; +import { getGlobalVariables, getHealth } from "./controllers/API"; import Router from "./routes"; import useAlertStore from "./stores/alertStore"; import { useDarkStore } from "./stores/darkStore"; import useFlowsManagerStore from "./stores/flowsManagerStore"; +import { useGlobalVariablesStore } from "./stores/globalVariables"; import { useStoreStore } from "./stores/storeStore"; import { useTypesStore } from "./stores/typesStore"; @@ -43,6 +44,9 @@ export default function App() { const getTypes = useTypesStore((state) => state.getTypes); const refreshVersion = useDarkStore((state) => state.refreshVersion); const refreshStars = useDarkStore((state) => state.refreshStars); + const setGlobalVariables = useGlobalVariablesStore( + (state) => state.setGlobalVariables + ); const checkHasStore = useStoreStore((state) => state.checkHasStore); const navigate = useNavigate(); @@ -58,6 +62,9 @@ export default function App() { getTypes().then(() => { refreshFlows(); }); + getGlobalVariables().then((res) => { + setGlobalVariables(res); + }); checkHasStore(); fetchApiData(); } diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 15ab94c169..90783f5bbd 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -6,9 +6,9 @@ import CodeAreaComponent from "../../../../components/codeAreaComponent"; import DictComponent from "../../../../components/dictComponent"; import Dropdown from "../../../../components/dropdownComponent"; import FloatComponent from "../../../../components/floatComponent"; -import IconComponent from "../../../../components/genericIconComponent"; -import InputComponent from "../../../../components/inputComponent"; +import { default as IconComponent } from "../../../../components/genericIconComponent"; import InputFileComponent from "../../../../components/inputFileComponent"; +import InputGlobalComponent from "../../../../components/inputGlobalComponent"; import InputListComponent from "../../../../components/inputListComponent"; import IntComponent from "../../../../components/intComponent"; import KeypairListComponent from "../../../../components/keypairListComponent"; @@ -71,6 +71,7 @@ export default function ParameterComponent({ const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); const setNode = useFlowStore((state) => state.setNode); + const [isLoading, setIsLoading] = useState(false); const flow = currentFlow?.data?.nodes ?? null; @@ -381,9 +382,8 @@ export default function ParameterComponent({ <>
{!left && data.node?.frozen && ( @@ -536,12 +536,21 @@ export default function ParameterComponent({ (data.node?.template[name].refresh_button ? "w-5/6" : "") } > - { + setNode(data.id, (oldNode) => { + let newNode = cloneDeep(oldNode); + newNode.data = { + ...newNode.data, + }; + newNode.data.node.template[name].load_from_db = value; + return newNode; + }); + }} + name={name} + data={data} />
{data.node?.template[name].refresh_button && ( diff --git a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx new file mode 100644 index 0000000000..6c80a8acaa --- /dev/null +++ b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx @@ -0,0 +1,99 @@ +import { useState } from "react"; +import { registerGlobalVariable } from "../../controllers/API"; +import BaseModal from "../../modals/baseModal"; +import useAlertStore from "../../stores/alertStore"; +import { useGlobalVariablesStore } from "../../stores/globalVariables"; +import { ResponseErrorDetailAPI } from "../../types/api"; +import ForwardedIconComponent from "../genericIconComponent"; +import InputComponent from "../inputComponent"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { Textarea } from "../ui/textarea"; + +//TODO IMPLEMENT FORM LOGIC + +export default function AddNewVariableButton({ children }): JSX.Element { + const [key, setKey] = useState(""); + const [value, setValue] = useState(""); + const [type, setType] = useState(""); + const [open, setOpen] = useState(false); + const setErrorData = useAlertStore((state) => state.setErrorData); + const addGlobalVariable = useGlobalVariablesStore( + (state) => state.addGlobalVariable + ); + function handleSaveVariable() { + let data: { name: string; value: string; type?: string } = { + name: key, + type, + value, + }; + registerGlobalVariable(data) + .then((res) => { + const { name, id, type } = res.data; + addGlobalVariable(name, id, type); + setKey(""); + setValue(""); + setType(""); + setOpen(false); + }) + .catch((error) => { + let responseError = error as ResponseErrorDetailAPI; + setErrorData({ + title: "Error creating variable", + list: [responseError.response.data.detail ?? "Unknown error"], + }); + }); + } + return ( + + + Create Variable + + {children} + +
+ + { + setKey(e.target.value); + }} + placeholder="Insert a name for the variable..." + > + + { + setType(e); + }} + selectedOption={type} + password={false} + options={["Variable", "Credential"]} + placeholder="Choose a type for the variable..." + > + +