feat: redesign sidebar with new components and features (#4307)

* Added required ShadCN Components

* Added required colors

* Added required icons

* Changed backend to not return beta tag

* Added sidebar categories api call

* Added required use-mobile hook

* Refactored icons to allow sizing

* Added type for sidebar category

* Allowed no name on shortcut display

* Added flow sidebar component that uses ShadCN sidebar

* Added SidebarDraggableComponent with the new style

* Replaced sidebar with current one on FlowPage

* Added sidebar fixed footer with options to go to store and add custom component

* Updated sidebar categories

* Updated background color for sidebar

* Changed size of Components title

* Added color to PageComponent

* Added required icons

* added CustomComponent category and removed custom component from helpers

* create hook useAddComponent

* Use hook to add components both in PageComponent and flowSidebarComponent

* Implement search

* Implement searching and filtering by clicking on edge

* Added check to see if store is present

* Updated colors to match new color schema

* Changed styling of filter component

* Added Beta and Legacy badges

* Implement Show Beta Components and Show Legacy Components

* Fixed styling for sidebar config when collapsed

* Refactored search to filter for tags

* Refactor useeffect

* Updated config button styling

* Implemented keyboard navigation

* Fixed filtering

* Updated color of canvas

* Implemented disclosure on sidebar settings and fetched bundles

* Added temp sidebar bundles

* Fixed badge styling

* Added bundles to categories response in frontend

* Added legacy to components

* Added link to store instead of langflow.store

* Added required data-testids

* Fixed tests to use new data-testids and new sidebar disposition

* Fix github star bug

* Fixed tests that used the custom component

* Changed test to test beta and legacy checkers

* added a test for keyboard navigation on sidebar

* Added a test to check component add by hover the plus button

* [autofix.ci] apply automated fixes

* updated sidebar switch change

* Removed changes on Backend and used only Frontend constants for categories

* merge fix

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* 📝 (custom_component): Add CustomComponent class with input and output definitions
📝 (custom_component): Create CustomComponent class with display name, description, and documentation link
📝 (custom_component): Define input and output properties for CustomComponent class
📝 (custom_component): Implement build_output method in CustomComponent class
📝 (Ollama): Remove unnecessary whitespace in SvgOllama component

* formatting

* ♻️ (custom_component/__init__.py): refactor import statement to match the correct case of the file name for better consistency and readability

* 🔧 (FlowPage/index.tsx): remove FlowToolbar component when view prop is false to improve UI consistency

* 📝 (integration-side-bar.spec.ts): Comment out unnecessary code blocks related to API requests and modals to improve test readability and focus on the main test scenarios.

* [autofix.ci] apply automated fixes

* ✨ (flowSidebarComponent/index.tsx): add data-testid attribute to Sidebar component for testing purposes
🔧 (integration-side-bar.spec.ts): update test to use new data-testid attribute 'shad-sidebar' for Sidebar component to match changes in the codebase

* ✨ (stop-building.spec.ts): refactor test case to use a more descriptive test element for clicking on the sidebar custom component button

* format

* 🐛 (AstraDB): Fix variable naming inconsistency for isDark to isdark in AstraDB component
🐛 (HCD): Fix variable naming inconsistency for isDark to isdark in HCD component
🐛 (index.tsx): Fix variable naming inconsistency for isDark to isdark in index.tsx files
📝 (flowSidebarComponent): Refactor search functionality to normalize search terms and improve metadata search
📝 (filterEdge-shard-1.spec.ts): Update test to check visibility of specific model specs in the sidebar

* [autofix.ci] apply automated fixes

* ✅ (decisionFlow.spec.ts): add a delay of 500ms to ensure proper timing in the test case execution

* ✨ (decisionFlow.spec.ts): Update the X positions of elements in the flow to improve the visual representation and alignment of the elements.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
Lucas Oliveira 2024-10-31 19:51:36 -03:00 • committed by GitHub
commit 794848d5e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
122 changed files with 2365 additions and 802 deletions

View file

@ -6,7 +6,16 @@ from typing import TYPE_CHECKING, Annotated
from uuid import UUID from uuid import UUID
import sqlalchemy as sa import sqlalchemy as sa
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, Request, UploadFile, status from fastapi import (
APIRouter,
BackgroundTasks,
Body,
Depends,
HTTPException,
Request,
UploadFile,
status,
)
from loguru import logger from loguru import logger
from sqlmodel import select from sqlmodel import select
@ -17,7 +26,6 @@ from langflow.api.v1.schemas import (
CustomComponentResponse, CustomComponentResponse,
InputValueRequest, InputValueRequest,
RunResponse, RunResponse,
SidebarCategoriesResponse,
SimplifiedAPIRequest, SimplifiedAPIRequest,
TaskStatusResponse, TaskStatusResponse,
UpdateCustomComponentRequest, UpdateCustomComponentRequest,
@ -37,7 +45,9 @@ from langflow.services.auth.utils import api_key_security, get_current_active_us
from langflow.services.cache.utils import save_uploaded_file from langflow.services.cache.utils import save_uploaded_file
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.services.database.models.flow.model import FlowRead from langflow.services.database.models.flow.model import FlowRead
from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow from langflow.services.database.models.flow.utils import (
get_all_webhook_components_in_flow,
)
from langflow.services.database.models.user.model import User, UserRead from langflow.services.database.models.user.model import User, UserRead
from langflow.services.deps import ( from langflow.services.deps import (
get_session_service, get_session_service,
@ -47,7 +57,6 @@ from langflow.services.deps import (
) )
from langflow.services.settings.feature_flags import FEATURE_FLAGS from langflow.services.settings.feature_flags import FEATURE_FLAGS
from langflow.services.telemetry.schema import RunPayload from langflow.services.telemetry.schema import RunPayload
from langflow.utils.constants import SIDEBAR_CATEGORIES
from langflow.utils.version import get_version_info from langflow.utils.version import get_version_info
if TYPE_CHECKING: if TYPE_CHECKING:
@ -110,7 +119,11 @@ async def simple_run_flow(
graph_data = process_tweaks(graph_data, input_request.tweaks or {}, stream=stream) graph_data = process_tweaks(graph_data, input_request.tweaks or {}, stream=stream)
graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(user_id), flow_name=flow.name) graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(user_id), flow_name=flow.name)
inputs = [ inputs = [
InputValueRequest(components=[], input_value=input_request.input_value, type=input_request.input_type) InputValueRequest(
components=[],
input_value=input_request.input_value,
type=input_request.input_type,
)
] ]
if input_request.output_component: if input_request.output_component:
outputs = [input_request.output_component] outputs = [input_request.output_component]
@ -248,7 +261,10 @@ async def simplified_run_flow(
background_tasks.add_task( background_tasks.add_task(
telemetry_service.log_package_run, telemetry_service.log_package_run,
RunPayload( RunPayload(
run_is_webhook=False, run_seconds=int(end_time - start_time), run_success=True, run_error_message="" run_is_webhook=False,
run_seconds=int(end_time - start_time),
run_success=True,
run_error_message="",
), ),
) )
@ -360,7 +376,11 @@ async def webhook_run_flow(
return {"message": "Task started in the background", "status": "in progress"} return {"message": "Task started in the background", "status": "in progress"}
@router.post("/run/advanced/{flow_id}", response_model=RunResponse, response_model_exclude_none=True) @router.post(
"/run/advanced/{flow_id}",
response_model=RunResponse,
response_model_exclude_none=True,
)
async def experimental_run_flow( async def experimental_run_flow(
*, *,
session: DbSession, session: DbSession,
@ -631,11 +651,9 @@ async def get_config():
settings_service: SettingsService = get_settings_service() settings_service: SettingsService = get_settings_service()
return {"feature_flags": FEATURE_FLAGS, **settings_service.settings.model_dump()} return {
"feature_flags": FEATURE_FLAGS,
**settings_service.settings.model_dump(),
}
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.get("/sidebar_categories")
async def get_sidebar_categories() -> SidebarCategoriesResponse:
return SidebarCategoriesResponse(categories=SIDEBAR_CATEGORIES)

View file

@ -4,7 +4,14 @@ from pathlib import Path
from typing import Any from typing import Any
from uuid import UUID from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer from pydantic import (
BaseModel,
ConfigDict,
Field,
field_serializer,
field_validator,
model_serializer,
)
from langflow.graph.schema import RunOutputs from langflow.graph.schema import RunOutputs
from langflow.graph.utils import serialize_field from langflow.graph.utils import serialize_field
@ -316,7 +323,11 @@ class InputValueRequest(BaseModel):
}, },
{"components": ["Component Name"], "input_value": "input_value"}, {"components": ["Component Name"], "input_value": "input_value"},
{"input_value": "input_value"}, {"input_value": "input_value"},
{"components": ["Component Name"], "input_value": "input_value", "session": "session_id"}, {
"components": ["Component Name"],
"input_value": "input_value",
"session": "session_id",
},
{"input_value": "input_value", "session": "session_id"}, {"input_value": "input_value", "session": "session_id"},
{"type": "chat", "input_value": "input_value"}, {"type": "chat", "input_value": "input_value"},
{"type": "json", "input_value": '{"key": "value"}'}, {"type": "json", "input_value": '{"key": "value"}'},
@ -357,14 +368,3 @@ class ConfigResponse(BaseModel):
auto_saving_interval: int auto_saving_interval: int
health_check_max_retries: int health_check_max_retries: int
max_file_size_upload: int max_file_size_upload: int
class SidebarCategory(BaseModel):
display_name: str
name: str
icon: str
beta: bool = False
class SidebarCategoriesResponse(BaseModel):
categories: list[SidebarCategory]

View file

@ -10,7 +10,15 @@ import orjson
STREAM_INFO_TEXT = "Stream the response from the model. Streaming works only in Chat." STREAM_INFO_TEXT = "Stream the response from the model. Streaming works only in Chat."
NODE_FORMAT_ATTRIBUTES = ["beta", "icon", "display_name", "output_types", "edited", "metadata"] NODE_FORMAT_ATTRIBUTES = [
"beta",
"legacy",
"icon",
"display_name",
"output_types",
"edited",
"metadata",
]
FIELD_FORMAT_ATTRIBUTES = [ FIELD_FORMAT_ATTRIBUTES = [

View file

@ -1,6 +1,7 @@
from . import ( from . import (
agents, agents,
chains, chains,
custom_component,
documentloaders, documentloaders,
embeddings, embeddings,
helpers, helpers,
@ -26,6 +27,7 @@ __all__ = [
"helpers", "helpers",
"inputs", "inputs",
"link_extractors", "link_extractors",
"custom_component",
"memories", "memories",
"models", "models",
"output_parsers", "output_parsers",

View file

@ -14,11 +14,22 @@ class JsonAgentComponent(LCAgentComponent):
display_name = "JsonAgent" display_name = "JsonAgent"
description = "Construct a json agent from an LLM and tools." description = "Construct a json agent from an LLM and tools."
name = "JsonAgent" name = "JsonAgent"
legacy: bool = True
inputs = [ inputs = [
*LCAgentComponent._base_inputs, *LCAgentComponent._base_inputs,
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), HandleInput(
FileInput(name="path", display_name="File Path", file_types=["json", "yaml", "yml"], required=True), name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
),
FileInput(
name="path",
display_name="File Path",
file_types=["json", "yaml", "yml"],
required=True,
),
] ]
def build_agent(self) -> AgentExecutor: def build_agent(self) -> AgentExecutor:

View file

@ -9,11 +9,22 @@ class VectorStoreAgentComponent(LCAgentComponent):
display_name = "VectorStoreAgent" display_name = "VectorStoreAgent"
description = "Construct an agent from a Vector Store." description = "Construct an agent from a Vector Store."
name = "VectorStoreAgent" name = "VectorStoreAgent"
legacy: bool = True
inputs = [ inputs = [
*LCAgentComponent._base_inputs, *LCAgentComponent._base_inputs,
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), HandleInput(
HandleInput(name="vectorstore", display_name="Vector Store", input_types=["VectorStoreInfo"], required=True), name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
),
HandleInput(
name="vectorstore",
display_name="Vector Store",
input_types=["VectorStoreInfo"],
required=True,
),
] ]
def build_agent(self) -> AgentExecutor: def build_agent(self) -> AgentExecutor:

View file

@ -9,10 +9,16 @@ class VectorStoreRouterAgentComponent(LCAgentComponent):
display_name = "VectorStoreRouterAgent" display_name = "VectorStoreRouterAgent"
description = "Construct an agent from a Vector Store Router." description = "Construct an agent from a Vector Store Router."
name = "VectorStoreRouterAgent" name = "VectorStoreRouterAgent"
legacy: bool = True
inputs = [ inputs = [
*LCAgentComponent._base_inputs, *LCAgentComponent._base_inputs,
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), HandleInput(
name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
),
HandleInput( HandleInput(
name="vectorstores", name="vectorstores",
display_name="Vector Stores", display_name="Vector Stores",

View file

@ -9,12 +9,21 @@ class ConversationChainComponent(LCChainComponent):
display_name = "ConversationChain" display_name = "ConversationChain"
description = "Chain to have a conversation and load context from memory." description = "Chain to have a conversation and load context from memory."
name = "ConversationChain" name = "ConversationChain"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="input_value", display_name="Input", info="The input value to pass to the chain.", required=True name="input_value",
display_name="Input",
info="The input value to pass to the chain.",
required=True,
),
HandleInput(
name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
), ),
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
HandleInput( HandleInput(
name="memory", name="memory",
display_name="Memory", display_name="Memory",
@ -28,7 +37,10 @@ class ConversationChainComponent(LCChainComponent):
else: else:
chain = ConversationChain(llm=self.llm, memory=self.memory) chain = ConversationChain(llm=self.llm, memory=self.memory)
result = chain.invoke({"input": self.input_value}, config={"callbacks": self.get_langchain_callbacks()}) result = chain.invoke(
{"input": self.input_value},
config={"callbacks": self.get_langchain_callbacks()},
)
if isinstance(result, dict): if isinstance(result, dict):
result = result.get(chain.output_key, "") result = result.get(chain.output_key, "")

View file

@ -10,18 +10,28 @@ class LLMCheckerChainComponent(LCChainComponent):
description = "Chain for question-answering with self-verification." description = "Chain for question-answering with self-verification."
documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_checker" documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
name = "LLMCheckerChain" name = "LLMCheckerChain"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="input_value", display_name="Input", info="The input value to pass to the chain.", required=True name="input_value",
display_name="Input",
info="The input value to pass to the chain.",
required=True,
),
HandleInput(
name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
), ),
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
] ]
def invoke_chain(self) -> Message: def invoke_chain(self) -> Message:
chain = LLMCheckerChain.from_llm(llm=self.llm) chain = LLMCheckerChain.from_llm(llm=self.llm)
response = chain.invoke( response = chain.invoke(
{chain.input_key: self.input_value}, config={"callbacks": self.get_langchain_callbacks()} {chain.input_key: self.input_value},
config={"callbacks": self.get_langchain_callbacks()},
) )
result = response.get(chain.output_key, "") result = response.get(chain.output_key, "")
result = str(result) result = str(result)

View file

@ -11,12 +11,21 @@ class LLMMathChainComponent(LCChainComponent):
description = "Chain that interprets a prompt and executes python code to do math." description = "Chain that interprets a prompt and executes python code to do math."
documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_math" documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_math"
name = "LLMMathChain" name = "LLMMathChain"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="input_value", display_name="Input", info="The input value to pass to the chain.", required=True name="input_value",
display_name="Input",
info="The input value to pass to the chain.",
required=True,
),
HandleInput(
name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
), ),
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
] ]
outputs = [Output(display_name="Text", name="text", method="invoke_chain")] outputs = [Output(display_name="Text", name="text", method="invoke_chain")]
@ -24,7 +33,8 @@ class LLMMathChainComponent(LCChainComponent):
def invoke_chain(self) -> Message: def invoke_chain(self) -> Message:
chain = LLMMathChain.from_llm(llm=self.llm) chain = LLMMathChain.from_llm(llm=self.llm)
response = chain.invoke( response = chain.invoke(
{chain.input_key: self.input_value}, config={"callbacks": self.get_langchain_callbacks()} {chain.input_key: self.input_value},
config={"callbacks": self.get_langchain_callbacks()},
) )
result = response.get(chain.output_key, "") result = response.get(chain.output_key, "")
result = str(result) result = str(result)

View file

@ -9,10 +9,14 @@ class RetrievalQAComponent(LCChainComponent):
display_name = "Retrieval QA" display_name = "Retrieval QA"
description = "Chain for question-answering querying sources from a retriever." description = "Chain for question-answering querying sources from a retriever."
name = "RetrievalQA" name = "RetrievalQA"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="input_value", display_name="Input", info="The input value to pass to the chain.", required=True name="input_value",
display_name="Input",
info="The input value to pass to the chain.",
required=True,
), ),
DropdownInput( DropdownInput(
name="chain_type", name="chain_type",
@ -22,8 +26,18 @@ class RetrievalQAComponent(LCChainComponent):
value="Stuff", value="Stuff",
advanced=True, advanced=True,
), ),
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), HandleInput(
HandleInput(name="retriever", display_name="Retriever", input_types=["Retriever"], required=True), name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
),
HandleInput(
name="retriever",
display_name="Retriever",
input_types=["Retriever"],
required=True,
),
HandleInput( HandleInput(
name="memory", name="memory",
display_name="Memory", display_name="Memory",
@ -52,7 +66,10 @@ class RetrievalQAComponent(LCChainComponent):
return_source_documents=True, return_source_documents=True,
) )
result = runnable.invoke({"query": self.input_value}, config={"callbacks": self.get_langchain_callbacks()}) result = runnable.invoke(
{"query": self.input_value},
config={"callbacks": self.get_langchain_callbacks()},
)
source_docs = self.to_data(result.get("source_documents", keys=[])) source_docs = self.to_data(result.get("source_documents", keys=[]))
result_str = str(result.get("result", "")) result_str = str(result.get("result", ""))

View file

@ -16,17 +16,38 @@ class SQLGeneratorComponent(LCChainComponent):
display_name = "Natural Language to SQL" display_name = "Natural Language to SQL"
description = "Generate SQL from natural language." description = "Generate SQL from natural language."
name = "SQLGenerator" name = "SQLGenerator"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="input_value", display_name="Input", info="The input value to pass to the chain.", required=True name="input_value",
display_name="Input",
info="The input value to pass to the chain.",
required=True,
),
HandleInput(
name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
required=True,
),
HandleInput(
name="db",
display_name="SQLDatabase",
input_types=["SQLDatabase"],
required=True,
), ),
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
HandleInput(name="db", display_name="SQLDatabase", input_types=["SQLDatabase"], required=True),
IntInput( IntInput(
name="top_k", display_name="Top K", info="The number of results per select statement to return.", value=5 name="top_k",
display_name="Top K",
info="The number of results per select statement to return.",
value=5,
),
MultilineInput(
name="prompt",
display_name="Prompt",
info="The prompt must contain `{question}`.",
), ),
MultilineInput(name="prompt", display_name="Prompt", info="The prompt must contain `{question}`."),
] ]
outputs = [Output(display_name="Text", name="text", method="invoke_chain")] outputs = [Output(display_name="Text", name="text", method="invoke_chain")]
@ -48,7 +69,8 @@ class SQLGeneratorComponent(LCChainComponent):
sql_query_chain = create_sql_query_chain(llm=self.llm, db=self.db, prompt=prompt_template, k=self.top_k) sql_query_chain = create_sql_query_chain(llm=self.llm, db=self.db, prompt=prompt_template, k=self.top_k)
query_writer: Runnable = sql_query_chain | {"query": lambda x: x.replace("SQLQuery:", "").strip()} query_writer: Runnable = sql_query_chain | {"query": lambda x: x.replace("SQLQuery:", "").strip()}
response = query_writer.invoke( response = query_writer.invoke(
{"question": self.input_value}, config={"callbacks": self.get_langchain_callbacks()} {"question": self.input_value},
config={"callbacks": self.get_langchain_callbacks()},
) )
query = response.get("query") query = response.get("query")
self.status = query self.status = query

View file

@ -0,0 +1,5 @@
from .custom_component import CustomComponent
__all__ = [
"CustomComponent",
]

View file

@ -2,7 +2,6 @@ from .combine_text import CombineTextComponent
from .create_list import CreateListComponent from .create_list import CreateListComponent
from .csv_to_data import CSVToDataComponent from .csv_to_data import CSVToDataComponent
from .current_date import CurrentDateComponent from .current_date import CurrentDateComponent
from .custom_component import CustomComponent
from .data_conditional_router import DataConditionalRouterComponent from .data_conditional_router import DataConditionalRouterComponent
from .extract_key import ExtractDataKeyComponent from .extract_key import ExtractDataKeyComponent
from .filter_data import FilterDataComponent from .filter_data import FilterDataComponent
@ -25,7 +24,6 @@ __all__ = [
"CombineTextComponent", "CombineTextComponent",
"CreateListComponent", "CreateListComponent",
"CurrentDateComponent", "CurrentDateComponent",
"CustomComponent",
"DataConditionalRouterComponent", "DataConditionalRouterComponent",
"DataFilterComponent", "DataFilterComponent",
"ExtractDataKeyComponent", "ExtractDataKeyComponent",

View file

@ -21,6 +21,7 @@ class JSONDocumentBuilder(CustomComponent):
display_name: str = "JSON Document Builder" display_name: str = "JSON Document Builder"
description: str = "Build a Document containing a JSON object using a key and another Document page content." description: str = "Build a Document containing a JSON object using a key and another Document page content."
name = "JSONDocumentBuilder" name = "JSONDocumentBuilder"
legacy: bool = True
output_types: list[str] = ["Document"] output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder" documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder"

View file

@ -11,6 +11,7 @@ class AmazonKendraRetrieverComponent(CustomComponent):
description: str = "Retriever that uses the Amazon Kendra API." description: str = "Retriever that uses the Amazon Kendra API."
name = "AmazonKendra" name = "AmazonKendra"
icon = "Amazon" icon = "Amazon"
legacy: bool = True
def build_config(self): def build_config(self):
return { return {

View file

@ -3,7 +3,10 @@ from typing import cast
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank from langchain_cohere import CohereRerank
from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store from langflow.base.vectorstores.model import (
LCVectorStoreComponent,
check_cached_vector_store,
)
from langflow.field_typing import Retriever, VectorStore from langflow.field_typing import Retriever, VectorStore
from langflow.io import ( from langflow.io import (
DropdownInput, DropdownInput,
@ -22,6 +25,7 @@ class CohereRerankComponent(LCVectorStoreComponent):
description = "Rerank documents using the Cohere API and a retriever." description = "Rerank documents using the Cohere API and a retriever."
name = "CohereRerank" name = "CohereRerank"
icon = "Cohere" icon = "Cohere"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(

View file

@ -11,6 +11,7 @@ class MetalRetrieverComponent(CustomComponent):
display_name: str = "Metal Retriever" display_name: str = "Metal Retriever"
description: str = "Retriever that uses the Metal API." description: str = "Retriever that uses the Metal API."
name = "MetalRetriever" name = "MetalRetriever"
legacy: bool = True
def build_config(self): def build_config(self):
return { return {

View file

@ -9,6 +9,7 @@ class MultiQueryRetrieverComponent(CustomComponent):
description = "Initialize from llm using default template." description = "Initialize from llm using default template."
documentation = "https://python.langchain.com/docs/modules/data_connection/retrievers/how_to/MultiQueryRetriever" documentation = "https://python.langchain.com/docs/modules/data_connection/retrievers/how_to/MultiQueryRetriever"
name = "MultiQueryRetriever" name = "MultiQueryRetriever"
legacy: bool = True
def build_config(self): def build_config(self):
return { return {

View file

@ -2,9 +2,18 @@ from typing import Any, cast
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers import ContextualCompressionRetriever
from langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store from langflow.base.vectorstores.model import (
LCVectorStoreComponent,
check_cached_vector_store,
)
from langflow.field_typing import Retriever, VectorStore from langflow.field_typing import Retriever, VectorStore
from langflow.io import DropdownInput, HandleInput, MultilineInput, SecretStrInput, StrInput from langflow.io import (
DropdownInput,
HandleInput,
MultilineInput,
SecretStrInput,
StrInput,
)
from langflow.schema import Data from langflow.schema import Data
from langflow.schema.dotdict import dotdict from langflow.schema.dotdict import dotdict
from langflow.template.field.base import Output from langflow.template.field.base import Output
@ -14,6 +23,7 @@ class NvidiaRerankComponent(LCVectorStoreComponent):
display_name = "NVIDIA Rerank" display_name = "NVIDIA Rerank"
description = "Rerank documents using the NVIDIA API and a retriever." description = "Rerank documents using the NVIDIA API and a retriever."
icon = "NVIDIA" icon = "NVIDIA"
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
@ -28,7 +38,10 @@ class NvidiaRerankComponent(LCVectorStoreComponent):
info="The base URL of the NVIDIA API. Defaults to https://integrate.api.nvidia.com/v1.", info="The base URL of the NVIDIA API. Defaults to https://integrate.api.nvidia.com/v1.",
), ),
DropdownInput( DropdownInput(
name="model", display_name="Model", options=["nv-rerank-qa-mistral-4b:1"], value="nv-rerank-qa-mistral-4b:1" name="model",
display_name="Model",
options=["nv-rerank-qa-mistral-4b:1"],
value="nv-rerank-qa-mistral-4b:1",
), ),
SecretStrInput(name="api_key", display_name="API Key"), SecretStrInput(name="api_key", display_name="API Key"),
HandleInput(name="retriever", display_name="Retriever", input_types=["Retriever"]), HandleInput(name="retriever", display_name="Retriever", input_types=["Retriever"]),

View file

@ -13,6 +13,7 @@ class SelfQueryRetrieverComponent(Component):
description = "Retriever that uses a vector store and an LLM to generate the vector store queries." description = "Retriever that uses a vector store and an LLM to generate the vector store queries."
name = "SelfQueryRetriever" name = "SelfQueryRetriever"
icon = "LangChain" icon = "LangChain"
legacy: bool = True
inputs = [ inputs = [
HandleInput( HandleInput(
@ -48,7 +49,11 @@ class SelfQueryRetrieverComponent(Component):
] ]
outputs = [ outputs = [
Output(display_name="Retrieved Documents", name="documents", method="retrieve_documents"), Output(
display_name="Retrieved Documents",
name="documents",
method="retrieve_documents",
),
] ]
def retrieve_documents(self) -> list[Data]: def retrieve_documents(self) -> list[Data]:

View file

@ -8,6 +8,7 @@ class VectoStoreRetrieverComponent(CustomComponent):
display_name = "VectorStore Retriever" display_name = "VectorStore Retriever"
description = "A vector store retriever" description = "A vector store retriever"
name = "VectorStoreRetriever" name = "VectorStoreRetriever"
legacy: bool = True
def build_config(self): def build_config(self):
return { return {

View file

@ -9,6 +9,7 @@ class VectorStoreInfoComponent(Component):
display_name = "VectorStoreInfo" display_name = "VectorStoreInfo"
description = "Information about a VectorStore" description = "Information about a VectorStore"
name = "VectorStoreInfo" name = "VectorStoreInfo"
legacy: bool = True
inputs = [ inputs = [
MessageTextInput( MessageTextInput(
@ -41,5 +42,7 @@ class VectorStoreInfoComponent(Component):
"description": self.vectorstore_description, "description": self.vectorstore_description,
} }
return VectorStoreInfo( return VectorStoreInfo(
vectorstore=self.input_vectorstore, description=self.vectorstore_description, name=self.vectorstore_name vectorstore=self.input_vectorstore,
description=self.vectorstore_description,
name=self.vectorstore_name,
) )

View file

@ -10,7 +10,14 @@ from pydantic.v1.fields import Undefined
from typing_extensions import override from typing_extensions import override
from langflow.base.langchain_utilities.model import LCToolComponent from langflow.base.langchain_utilities.model import LCToolComponent
from langflow.inputs.inputs import BoolInput, DropdownInput, FieldTypes, HandleInput, MessageTextInput, MultilineInput from langflow.inputs.inputs import (
BoolInput,
DropdownInput,
FieldTypes,
HandleInput,
MessageTextInput,
MultilineInput,
)
from langflow.io import Output from langflow.io import Output
from langflow.schema import Data from langflow.schema import Data
from langflow.schema.dotdict import dotdict from langflow.schema.dotdict import dotdict
@ -36,6 +43,7 @@ class PythonCodeStructuredTool(LCToolComponent):
name = "PythonCodeStructuredTool" name = "PythonCodeStructuredTool"
icon = "🐍" icon = "🐍"
field_order = ["name", "description", "tool_code", "return_direct", "tool_function"] field_order = ["name", "description", "tool_code", "return_direct", "tool_function"]
legacy: bool = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
@ -47,7 +55,12 @@ class PythonCodeStructuredTool(LCToolComponent):
real_time_refresh=True, real_time_refresh=True,
refresh_button=True, refresh_button=True,
), ),
MessageTextInput(name="tool_name", display_name="Tool Name", info="Enter the name of the tool.", required=True), MessageTextInput(
name="tool_name",
display_name="Tool Name",
info="Enter the name of the tool.",
required=True,
),
MessageTextInput( MessageTextInput(
name="tool_description", name="tool_description",
display_name="Description", display_name="Description",
@ -192,7 +205,10 @@ class PythonCodeStructuredTool(LCToolComponent):
schema_annotation = Any schema_annotation = Any
schema_fields[field_name] = ( schema_fields[field_name] = (
schema_annotation, schema_annotation,
Field(default=func_arg.get("default", Undefined), description=field_description), Field(
default=func_arg.get("default", Undefined),
description=field_description,
),
) )
if "temp_annotation_type" in _globals: if "temp_annotation_type" in _globals:
@ -214,7 +230,9 @@ class PythonCodeStructuredTool(LCToolComponent):
"""This function is called after the code validation is done.""" """This function is called after the code validation is done."""
frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node) frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)
frontend_node["template"] = self.update_build_config( frontend_node["template"] = self.update_build_config(
frontend_node["template"], frontend_node["template"]["tool_code"]["value"], "tool_code" frontend_node["template"],
frontend_node["template"]["tool_code"]["value"],
"tool_code",
) )
frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node) frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)
for key in frontend_node["template"]: for key in frontend_node["template"]:

View file

@ -19,6 +19,7 @@ class SearXNGToolComponent(LCToolComponent):
display_name = "SearXNG Search Tool" display_name = "SearXNG Search Tool"
description = "A component that searches for tools using SearXNG." description = "A component that searches for tools using SearXNG."
name = "SearXNGTool" name = "SearXNGTool"
legacy: bool = True
inputs = [ inputs = [
MessageTextInput( MessageTextInput(
@ -127,7 +128,10 @@ class SearXNGToolComponent(LCToolComponent):
schema_fields = { schema_fields = {
"query": (str, Field(..., description="The query to search for.")), "query": (str, Field(..., description="The query to search for.")),
"categories": (list[str], Field(default=[], description="The categories to search in.")), "categories": (
list[str],
Field(default=[], description="The categories to search in."),
),
} }
searx_search_schema = create_model("SearxSearchSchema", **schema_fields) searx_search_schema = create_model("SearxSearchSchema", **schema_fields)

View file

@ -61,6 +61,7 @@ ATTR_FUNC_MAPPING: dict[str, Callable] = {
"display_name": getattr_return_str, "display_name": getattr_return_str,
"description": getattr_return_str, "description": getattr_return_str,
"beta": getattr_return_bool, "beta": getattr_return_bool,
"legacy": getattr_return_bool,
"documentation": getattr_return_str, "documentation": getattr_return_str,
"icon": validate_icon, "icon": validate_icon,
"frozen": getattr_return_bool, "frozen": getattr_return_bool,

View file

@ -158,6 +158,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"lf_version": "1.0.16", "lf_version": "1.0.16",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -380,6 +381,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"lf_version": "1.0.16", "lf_version": "1.0.16",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -589,6 +591,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"lf_version": "1.0.16", "lf_version": "1.0.16",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -1203,6 +1206,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "calculator", "icon": "calculator",
"legacy": false,
"lf_version": "1.0.16", "lf_version": "1.0.16",
"metadata": {}, "metadata": {},
"official": false, "official": false,
@ -1322,6 +1326,7 @@
"code" "code"
], ],
"frozen": false, "frozen": false,
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -108,6 +108,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -324,6 +325,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -445,6 +447,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -647,6 +650,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -156,6 +156,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "layout-template", "icon": "layout-template",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -288,6 +289,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "braces", "icon": "braces",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -425,6 +427,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -667,6 +670,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -869,6 +873,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -629,6 +629,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -876,6 +877,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1202,6 +1204,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1388,6 +1391,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1528,6 +1532,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1783,6 +1788,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2043,6 +2049,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2373,6 +2380,7 @@
"is_composition": null, "is_composition": null,
"is_input": null, "is_input": null,
"is_output": null, "is_output": null,
"legacy": false,
"metadata": {}, "metadata": {},
"name": "", "name": "",
"output_types": [], "output_types": [],
@ -2488,6 +2496,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2814,6 +2823,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -3264,6 +3274,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -3738,6 +3749,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -4070,6 +4082,7 @@
"is_composition": null, "is_composition": null,
"is_input": null, "is_input": null,
"is_output": null, "is_output": null,
"legacy": false,
"metadata": {}, "metadata": {},
"name": "", "name": "",
"output_types": [], "output_types": [],
@ -4237,6 +4250,7 @@
"search_params" "search_params"
], ],
"frozen": false, "frozen": false,
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -161,6 +161,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -306,6 +307,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -523,6 +525,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -725,6 +728,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1048,6 +1052,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "braces", "icon": "braces",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1181,6 +1186,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "file-text", "icon": "file-text",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -326,6 +326,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -576,6 +577,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -906,6 +908,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1097,6 +1100,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1242,6 +1246,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1501,6 +1506,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1763,6 +1769,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2097,6 +2104,7 @@
"is_composition": null, "is_composition": null,
"is_input": null, "is_input": null,
"is_output": null, "is_output": null,
"legacy": false,
"metadata": {}, "metadata": {},
"name": "", "name": "",
"output_types": [], "output_types": [],
@ -2216,6 +2224,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2436,6 +2445,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2687,6 +2697,7 @@
"search_params" "search_params"
], ],
"frozen": false, "frozen": false,
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -135,6 +135,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -280,6 +281,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -504,6 +506,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -830,6 +833,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1023,6 +1027,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "message-square-more", "icon": "message-square-more",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -389,6 +389,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -588,6 +589,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -915,6 +917,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -1199,6 +1202,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -1320,6 +1324,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -1441,6 +1446,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -1569,6 +1575,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -1920,6 +1927,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -2271,6 +2279,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "CrewAI", "icon": "CrewAI",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],

View file

@ -398,6 +398,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -620,6 +621,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
@ -829,6 +831,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1442,6 +1445,7 @@
"search_params" "search_params"
], ],
"frozen": false, "frozen": false,
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"official": false, "official": false,
@ -2319,6 +2323,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "calculator", "icon": "calculator",
"legacy": false,
"lf_version": "1.0.15", "lf_version": "1.0.15",
"metadata": {}, "metadata": {},
"official": false, "official": false,

View file

@ -291,6 +291,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -524,6 +525,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "AstraDB", "icon": "AstraDB",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1020,6 +1022,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "braces", "icon": "braces",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1157,6 +1160,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "prompts", "icon": "prompts",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1302,6 +1306,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "MessagesSquare", "icon": "MessagesSquare",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1494,6 +1499,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "scissors-line-dashed", "icon": "scissors-line-dashed",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1639,6 +1645,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "file-text", "icon": "file-text",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -1788,6 +1795,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "AstraDB", "icon": "AstraDB",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2302,6 +2310,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -2778,6 +2787,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [
@ -3246,6 +3256,7 @@
], ],
"frozen": false, "frozen": false,
"icon": "OpenAI", "icon": "OpenAI",
"legacy": false,
"metadata": {}, "metadata": {},
"output_types": [], "output_types": [],
"outputs": [ "outputs": [

View file

@ -49,6 +49,8 @@ class FrontendNode(BaseModel):
"""Order of the fields in the frontend node.""" """Order of the fields in the frontend node."""
beta: bool = False beta: bool = False
"""Whether the frontend node is in beta.""" """Whether the frontend node is in beta."""
legacy: bool = False
"""Whether the frontend node is legacy."""
error: str | None = None error: str | None = None
"""Error message for the frontend node.""" """Error message for the frontend node."""
edited: bool = False edited: bool = False
@ -81,7 +83,10 @@ class FrontendNode(BaseModel):
if "output_types" in result and not result.get("outputs"): if "output_types" in result and not result.get("outputs"):
for base_class in result["output_types"]: for base_class in result["output_types"]:
output = Output( output = Output(
display_name=base_class, name=base_class.lower(), types=[base_class], selected=base_class display_name=base_class,
name=base_class.lower(),
types=[base_class],
selected=base_class,
) )
result["outputs"].append(output.model_dump()) result["outputs"].append(output.model_dump())

View file

@ -47,6 +47,7 @@ class CustomComponentFrontendNode(FrontendNode):
name: str = "CustomComponent" name: str = "CustomComponent"
display_name: str | None = "CustomComponent" display_name: str | None = "CustomComponent"
beta: bool = False beta: bool = False
legacy: bool = False
template: Template = Template( template: Template = Template(
type_name="CustomComponent", type_name="CustomComponent",
fields=[ fields=[
@ -72,6 +73,7 @@ class ComponentFrontendNode(FrontendNode):
name: str = "Component" name: str = "Component"
display_name: str | None = "Component" display_name: str | None = "Component"
beta: bool = False beta: bool = False
legacy: bool = False
template: Template = Template( template: Template = Template(
type_name="Component", type_name="Component",
fields=[ fields=[

View file

@ -52,7 +52,18 @@ def python_function(text: str) -> str:
PYTHON_BASIC_TYPES = [str, bool, int, float, tuple, list, dict, set] PYTHON_BASIC_TYPES = [str, bool, int, float, tuple, list, dict, set]
DIRECT_TYPES = ["str", "bool", "dict", "int", "float", "Any", "prompt", "code", "NestedDict", "table"] DIRECT_TYPES = [
"str",
"bool",
"dict",
"int",
"float",
"Any",
"prompt",
"code",
"NestedDict",
"table",
]
LOADERS_INFO: list[dict[str, Any]] = [ LOADERS_INFO: list[dict[str, Any]] = [
@ -175,29 +186,3 @@ MESSAGE_SENDER_NAME_AI = "AI"
MESSAGE_SENDER_NAME_USER = "User" MESSAGE_SENDER_NAME_USER = "User"
MAX_TEXT_LENGTH = 99999 MAX_TEXT_LENGTH = 99999
SIDEBAR_CATEGORIES = [
{"display_name": "Saved", "name": "saved_components", "icon": "GradientSave"},
{"display_name": "Inputs", "name": "inputs", "icon": "Download"},
{"display_name": "Outputs", "name": "outputs", "icon": "Upload"},
{"display_name": "Prompts", "name": "prompts", "icon": "TerminalSquare"},
{"display_name": "Data", "name": "data", "icon": "Database"},
{"display_name": "Models", "name": "models", "icon": "BrainCircuit"},
{"display_name": "Helpers", "name": "helpers", "icon": "Wand2"},
{"display_name": "Vector Stores", "name": "vectorstores", "icon": "Layers"},
{"display_name": "Embeddings", "name": "embeddings", "icon": "Binary"},
{"display_name": "Agents", "name": "agents", "icon": "Bot"},
{"display_name": "Astra Assistants", "name": "astra_assistants", "icon": "Sparkles"},
{"display_name": "Chains", "name": "chains", "icon": "Link"},
{"display_name": "Loaders", "name": "documentloaders", "icon": "Paperclip"},
{"display_name": "Utilities", "name": "langchain_utilities", "icon": "PocketKnife"},
{"display_name": "Link Extractors", "name": "link_extractors", "icon": "Link2"},
{"display_name": "Memories", "name": "memories", "icon": "Cpu"},
{"display_name": "Output Parsers", "name": "output_parsers", "icon": "Compass"},
{"display_name": "Prototypes", "name": "prototypes", "icon": "FlaskConical"},
{"display_name": "Retrievers", "name": "retrievers", "icon": "FileSearch"},
{"display_name": "Text Splitters", "name": "textsplitters", "icon": "Scissors"},
{"display_name": "Toolkits", "name": "toolkits", "icon": "Package2"},
{"display_name": "Tools", "name": "tools", "icon": "Hammer"},
]

View file

@ -23,14 +23,3 @@ async def test_get_config(client: AsyncClient):
assert "auto_saving" in result, "The dictionary must contain a key called 'auto_saving'" assert "auto_saving" in result, "The dictionary must contain a key called 'auto_saving'"
assert "health_check_max_retries" in result, "The dictionary must contain a 'health_check_max_retries' key" assert "health_check_max_retries" in result, "The dictionary must contain a 'health_check_max_retries' key"
assert "max_file_size_upload" in result, "The dictionary must contain a key called 'max_file_size_upload'" assert "max_file_size_upload" in result, "The dictionary must contain a key called 'max_file_size_upload'"
async def test_get_sidebar_components(client: AsyncClient):
response = await client.get("api/v1/sidebar_categories")
result = response.json()
assert response.status_code == status.HTTP_200_OK
assert isinstance(result, dict), "The result must be a dictionary"
assert "categories" in result, "The dictionary must contain a key called 'categories'"
assert len(result["categories"]) > 0, "The categories list must not be empty"
assert isinstance(result["categories"], list), "The categories must be a list"

View file

@ -14,7 +14,8 @@
"@million/lint": "^1.0.0-rc.26", "@million/lint": "^1.0.0-rc.26",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-form": "^0.0.3", "@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",

View file

@ -9,7 +9,8 @@
"@million/lint": "^1.0.0-rc.26", "@million/lint": "^1.0.0-rc.26",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-form": "^0.0.3", "@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",

View file

@ -9,7 +9,7 @@ export const GithubStarComponent = () => {
<FaGithub className="h-4 w-4 text-black dark:text-[white]" /> <FaGithub className="h-4 w-4 text-black dark:text-[white]" />
<div className="hidden text-black dark:text-[white] lg:block">Star</div> <div className="hidden text-black dark:text-[white] lg:block">Star</div>
<div className="header-github-display text-black dark:text-[white]"> <div className="header-github-display text-black dark:text-[white]">
{stars.toLocaleString() ?? 0} {stars?.toLocaleString() ?? 0}
</div> </div>
</div> </div>
); );

View file

@ -15,13 +15,15 @@ const badgeVariants = cva(
destructive: destructive:
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground", "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
outline: "text-primary/80 border-ring/60", outline: "text-primary/80 border-ring/60",
secondaryStatic: "bg-input text-muted-foreground",
pinkStatic: "bg-accent-pink text-accent-pink-foreground",
}, },
size: { size: {
sm: "h-4 text-xs", sm: "h-4 text-xs",
md: "h-5 text-sm", md: "h-5 text-sm",
lg: "h-6 text-base", lg: "h-6 text-base",
sq: "h-6 text-sm font-normal rounded-md", sq: "h-6 px-1.5 text-sm font-medium rounded-md",
xq: "h-5 text-xs font-normal rounded-md", xq: "h-5 px-1 text-xs font-medium rounded-md",
}, },
}, },
defaultVariants: { defaultVariants: {

View file

@ -18,8 +18,10 @@ const buttonVariants = cva(
"border bg-background text-secondary-foreground hover:bg-muted dark:hover:bg-muted hover:shadow-sm", "border bg-background text-secondary-foreground hover:bg-muted dark:hover:bg-muted hover:shadow-sm",
secondary: secondary:
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5", "border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
menu: "hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0", ghostActive:
"bg-muted text-foreground hover:bg-accent hover:text-accent-foreground",
menu: "hover:bg-muted hover:text-accent-foreground focus:!ring-0 focus-visible:!ring-0",
"menu-active": "menu-active":
"font-semibold hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0", "font-semibold hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0",
link: "underline-offset-4 hover:underline text-primary", link: "underline-offset-4 hover:underline text-primary",
@ -29,7 +31,9 @@ const buttonVariants = cva(
sm: "h-9 px-3 rounded-md", sm: "h-9 px-3 rounded-md",
xs: "py-0.5 px-3 rounded-md", xs: "py-0.5 px-3 rounded-md",
lg: "h-11 px-8 rounded-md", lg: "h-11 px-8 rounded-md",
icon: "py-1 px-1 rounded-md", iconMd: "p-1.5 rounded-md",
icon: "p-1 rounded-md",
iconSm: "p-0.5 rounded-md",
"node-toolbar": "py-[6px] px-[6px] rounded-md", "node-toolbar": "py-[6px] px-[6px] rounded-md",
}, },
}, },

View file

@ -0,0 +1,11 @@
"use client";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleContent, CollapsibleTrigger };

View file

@ -0,0 +1,191 @@
"use client";
import {
AnimatePresence,
motion,
MotionConfig,
Transition,
Variant,
Variants,
} from "framer-motion";
import * as React from "react";
import { createContext, useContext, useEffect, useId, useState } from "react";
import { cn } from "../../utils/utils";
type DisclosureContextType = {
open: boolean;
toggle: () => void;
variants?: { expanded: Variant; collapsed: Variant };
};
const DisclosureContext = createContext<DisclosureContextType | undefined>(
undefined,
);
type DisclosureProviderProps = {
children: React.ReactNode;
open: boolean;
onOpenChange?: (open: boolean) => void;
variants?: { expanded: Variant; collapsed: Variant };
};
function DisclosureProvider({
children,
open: openProp,
onOpenChange,
variants,
}: DisclosureProviderProps) {
const [internalOpenValue, setInternalOpenValue] = useState<boolean>(openProp);
useEffect(() => {
setInternalOpenValue(openProp);
}, [openProp]);
const toggle = () => {
const newOpen = !internalOpenValue;
setInternalOpenValue(newOpen);
if (onOpenChange) {
onOpenChange(newOpen);
}
};
return (
<DisclosureContext.Provider
value={{
open: internalOpenValue,
toggle,
variants,
}}
>
{children}
</DisclosureContext.Provider>
);
}
function useDisclosure() {
const context = useContext(DisclosureContext);
if (!context) {
throw new Error("useDisclosure must be used within a DisclosureProvider");
}
return context;
}
type DisclosureProps = {
open?: boolean;
onOpenChange?: (open: boolean) => void;
children: React.ReactNode;
className?: string;
variants?: { expanded: Variant; collapsed: Variant };
transition?: Transition;
};
export function Disclosure({
open: openProp = false,
onOpenChange,
children,
className,
transition,
variants,
}: DisclosureProps) {
return (
<MotionConfig transition={transition}>
<div className={className}>
<DisclosureProvider
open={openProp}
onOpenChange={onOpenChange}
variants={variants}
>
{React.Children.toArray(children)[0]}
{React.Children.toArray(children)[1]}
</DisclosureProvider>
</div>
</MotionConfig>
);
}
export function DisclosureTrigger({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
const { toggle, open } = useDisclosure();
return (
<>
{React.Children.map(children, (child) => {
return React.isValidElement(child)
? React.cloneElement(child, {
onClick: toggle,
role: "button",
"aria-expanded": open,
tabIndex: 0,
onKeyDown: (e: { key: string; preventDefault: () => void }) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
},
className: cn(
className,
(child as React.ReactElement).props.className,
),
...(child as React.ReactElement).props,
})
: child;
})}
</>
);
}
export function DisclosureContent({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
const { open, variants } = useDisclosure();
const uniqueId = useId();
const BASE_VARIANTS: Variants = {
expanded: {
height: "auto",
opacity: 1,
},
collapsed: {
height: 0,
opacity: 0,
},
};
const combinedVariants = {
expanded: { ...BASE_VARIANTS.expanded, ...variants?.expanded },
collapsed: { ...BASE_VARIANTS.collapsed, ...variants?.collapsed },
};
return (
<div className={cn("overflow-hidden", className)}>
<AnimatePresence initial={false}>
{open && (
<motion.div
id={uniqueId}
initial="collapsed"
animate="expanded"
exit="collapsed"
variants={combinedVariants}
>
{children}
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default {
Disclosure,
DisclosureProvider,
DisclosureTrigger,
DisclosureContent,
};

View file

@ -65,7 +65,7 @@ const SheetContent = React.forwardRef<
{...props} {...props}
> >
{children} {children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</SheetPrimitive.Close> </SheetPrimitive.Close>

View file

@ -5,10 +5,12 @@ import { VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react"; import { PanelLeft } from "lucide-react";
import * as React from "react"; import * as React from "react";
import { useIsMobile } from "../../hooks/use-mobile";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import { Button } from "./button"; import { Button } from "./button";
import { Input } from "./input"; import { Input } from "./input";
import { Separator } from "./separator"; import { Separator } from "./separator";
import { Sheet, SheetContent } from "./sheet";
import { Skeleton } from "./skeleton"; import { Skeleton } from "./skeleton";
import { import {
Tooltip, Tooltip,
@ -20,13 +22,17 @@ import {
const SIDEBAR_COOKIE_NAME = "sidebar:state"; const SIDEBAR_COOKIE_NAME = "sidebar:state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "19rem"; const SIDEBAR_WIDTH = "19rem";
const SIDEBAR_WIDTH_ICON = "4rem"; const SIDEBAR_WIDTH_MOBILE = "18rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b"; const SIDEBAR_KEYBOARD_SHORTCUT = "b";
type SidebarContext = { type SidebarContext = {
state: "expanded" | "collapsed"; state: "expanded" | "collapsed";
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
openMobile: boolean;
setOpenMobile: (open: boolean) => void;
isMobile: boolean;
toggleSidebar: () => void; toggleSidebar: () => void;
}; };
@ -47,7 +53,6 @@ const SidebarProvider = React.forwardRef<
defaultOpen?: boolean; defaultOpen?: boolean;
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
width?: string;
} }
>( >(
( (
@ -58,11 +63,13 @@ const SidebarProvider = React.forwardRef<
className, className,
style, style,
children, children,
width = SIDEBAR_WIDTH,
...props ...props
}, },
ref, ref,
) => { ) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
// This is the internal state of the sidebar. // This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component. // We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen); const [_open, _setOpen] = React.useState(defaultOpen);
@ -85,8 +92,10 @@ const SidebarProvider = React.forwardRef<
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return setOpen((open) => !open); return isMobile
}, [setOpen]); ? setOpenMobile((open) => !open)
: setOpen((open) => !open);
}, [isMobile, setOpen, setOpenMobile]);
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
@ -113,9 +122,20 @@ const SidebarProvider = React.forwardRef<
state, state,
open, open,
setOpen, setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, toggleSidebar], [
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
],
); );
return ( return (
@ -124,7 +144,7 @@ const SidebarProvider = React.forwardRef<
<div <div
style={ style={
{ {
"--sidebar-width": width, "--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON, "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style, ...style,
} as React.CSSProperties } as React.CSSProperties
@ -164,33 +184,47 @@ const Sidebar = React.forwardRef<
}, },
ref, ref,
) => { ) => {
const { state } = useSidebar(); const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
if (collapsible === "none") { if (collapsible === "none") {
return ( return (
<div <div
className={cn( className={cn(
"group flex h-full w-[--sidebar-width] flex-col bg-background text-foreground", "flex h-full w-[--sidebar-width] flex-col bg-background text-foreground",
className, className,
)} )}
data-side={side}
ref={ref} ref={ref}
{...props} {...props}
>
<div
data-sidebar="sidebar"
className="flex h-full w-full flex-col group-data-[side=left]:border-r group-data-[side=right]:border-l"
> >
{children} {children}
</div> </div>
</div> );
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
className="w-[--sidebar-width] bg-background p-0 text-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
); );
} }
return ( return (
<div <div
ref={ref} ref={ref}
className="group peer relative block h-full flex-col" className="group peer relative flex h-full flex-col md:block"
data-state={state} data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""} data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant} data-variant={variant}
@ -205,16 +239,11 @@ const Sidebar = React.forwardRef<
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]", : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
// Keep icon width on mobile when collapsed
"max-sm:w-[--sidebar-width-icon]",
)} )}
/> />
<div <div
className={cn( className={cn(
"absolute inset-y-0 z-50 flex h-full transition-[left,right,width] duration-200 ease-linear", "absolute inset-y-0 z-10 hidden h-full w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
// Adjust width based on state and device
"w-[--sidebar-width]",
"max-sm:group-data-[state=collapsed]:w-[--sidebar-width-icon]",
side === "left" side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
@ -222,20 +251,13 @@ const Sidebar = React.forwardRef<
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l", : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
// Position absolute relative to parent container on mobile
"max-sm:absolute max-sm:h-[100%] max-sm:group-data-[state=expanded]:bg-background/80",
className, className,
)} )}
{...props} {...props}
> >
<div <div
data-sidebar="sidebar" data-sidebar="sidebar"
className={cn( className="flex h-full w-full flex-col bg-background group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-border group-data-[variant=floating]:shadow"
"flex h-full w-full flex-col bg-background",
"group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-border group-data-[variant=floating]:shadow",
// Add shadow on mobile
"max-sm:shadow-lg",
)}
> >
{children} {children}
</div> </div>
@ -258,7 +280,7 @@ const SidebarTrigger = React.forwardRef<
data-sidebar="trigger" data-sidebar="trigger"
variant="ghost" variant="ghost"
size="icon" size="icon"
className={cn("h-8 w-8", className)} className={cn("h-7 w-7", className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event); onClick?.(event);
toggleSidebar(); toggleSidebar();
@ -310,7 +332,7 @@ const SidebarInset = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex min-h-svh flex-1 flex-col bg-background", "relative flex min-h-svh flex-1 flex-col bg-background",
"peer-data-[variant=inset]:m-2 peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 peer-data-[variant=inset]:ml-0 peer-data-[variant=inset]:rounded-xl peer-data-[variant=inset]:shadow", "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className, className,
)} )}
{...props} {...props}
@ -328,7 +350,7 @@ const SidebarInput = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="input" data-sidebar="input"
className={cn( className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-1 focus-visible:ring-ring", "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-ring",
className, className,
)} )}
{...props} {...props}
@ -426,7 +448,7 @@ const SidebarGroupLabel = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="group-label" data-sidebar="group-label"
className={cn( className={cn(
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0", "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-foreground/70 outline-none ring-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className, className,
)} )}
@ -447,9 +469,9 @@ const SidebarGroupAction = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="group-action" data-sidebar="group-action"
className={cn( className={cn(
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 [&>svg]:size-4 [&>svg]:shrink-0", "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
"after:-inset-2 after:hidden", "after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className, className,
)} )}
@ -499,12 +521,11 @@ const SidebarMenuItem = React.forwardRef<
SidebarMenuItem.displayName = "SidebarMenuItem"; SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ring transition-[width,height,padding] hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:hover:text-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ring transition-[width,height,padding] hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:hover:text-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: default: "hover:bg-accent hover:text-accent-foreground",
"text-secondary-foreground hover:bg-accent hover:text-accent-foreground ",
outline: outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--border))] hover:bg-accent hover:text-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--accent))]", "bg-background shadow-[0_0_0_1px_hsl(var(--border))] hover:bg-accent hover:text-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--accent))]",
}, },
@ -542,7 +563,7 @@ const SidebarMenuButton = React.forwardRef<
ref, ref,
) => { ) => {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button";
const { state } = useSidebar(); const { isMobile, state } = useSidebar();
const button = ( const button = (
<Comp <Comp
@ -571,7 +592,7 @@ const SidebarMenuButton = React.forwardRef<
<TooltipContent <TooltipContent
side="right" side="right"
align="center" align="center"
hidden={state !== "collapsed"} hidden={state !== "collapsed" || isMobile}
{...tooltip} {...tooltip}
/> />
</Tooltip> </Tooltip>
@ -594,9 +615,9 @@ const SidebarMenuAction = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="menu-action" data-sidebar="menu-action"
className={cn( className={cn(
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 peer-hover/menu-button:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0", "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
"after:-inset-2 after:hidden", "after:absolute after:-inset-2 after:md:hidden",
"peer-data-[size=sm]/menu-button:top-1", "peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5", "peer-data-[size=lg]/menu-button:top-2.5",
@ -710,7 +731,7 @@ const SidebarMenuSubButton = React.forwardRef<
data-size={size} data-size={size}
data-active={isActive} data-active={isActive}
className={cn( className={cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-foreground outline-none ring-ring hover:bg-accent hover:text-accent-foreground focus-visible:ring-1 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-accent-foreground", "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-foreground outline-none ring-ring hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-accent-foreground",
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground", "data-[active=true]:bg-accent data-[active=true]:text-accent-foreground",
size === "sm" && "text-xs", size === "sm" && "text-xs",
size === "md" && "text-sm", size === "md" && "text-sm",

View file

@ -908,7 +908,7 @@ export const LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV =
Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) - Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) -
Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) * 0.1; Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) * 0.1;
export const TEXT_FIELD_TYPES: string[] = ["str", "SecretStr"]; export const TEXT_FIELD_TYPES: string[] = ["str", "SecretStr"];
export const NODE_WIDTH = 400; export const NODE_WIDTH = 384;
export const NODE_HEIGHT = NODE_WIDTH * 3; export const NODE_HEIGHT = NODE_WIDTH * 3;
export const SHORTCUT_KEYS = ["cmd", "ctrl", "alt", "shift"]; export const SHORTCUT_KEYS = ["cmd", "ctrl", "alt", "shift"];

View file

@ -21,6 +21,7 @@ export const URLs = {
VALIDATE: `validate`, VALIDATE: `validate`,
CONFIG: `config`, CONFIG: `config`,
STARTER_PROJECTS: `starter-projects`, STARTER_PROJECTS: `starter-projects`,
SIDEBAR_CATEGORIES: `sidebar_categories`,
} as const; } as const;
export function getURL(key: keyof typeof URLs, params: any = {}) { export function getURL(key: keyof typeof URLs, params: any = {}) {

View file

@ -1,22 +1,21 @@
import { useEffect, useState } from "react"; import * as React from "react";
export function useMobile(breakpoint: number = 768) { const MOBILE_BREAKPOINT = 768;
const [isMobile, setIsMobile] = useState(false);
useEffect(() => { export function useIsMobile() {
const checkMobile = () => { const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
setIsMobile(window.innerWidth < breakpoint); undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
}; };
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}, []);
// Check initially return !!isMobile;
checkMobile();
// Add event listener
window.addEventListener("resize", checkMobile);
// Cleanup
return () => window.removeEventListener("resize", checkMobile);
}, [breakpoint]);
return isMobile;
} }

View file

@ -0,0 +1,70 @@
import { NODE_WIDTH } from "@/constants/constants";
import { track } from "@/customization/utils/analytics";
import useFlowStore from "@/stores/flowStore";
import { APIClassType } from "@/types/api";
import { NodeType } from "@/types/flow";
import { getNodeId } from "@/utils/reactflowUtils";
import { getNodeRenderType } from "@/utils/utils";
import { useCallback } from "react";
import { useStoreApi } from "reactflow";
export function useAddComponent() {
const store = useStoreApi();
const paste = useFlowStore((state) => state.paste);
const addComponent = useCallback(
(
component: APIClassType,
type: string,
position?: { x: number; y: number },
) => {
track("Component Added", { componentType: component.display_name });
const {
height,
width,
transform: [transformX, transformY, zoomLevel],
} = store.getState();
const zoomMultiplier = 1 / zoomLevel;
let pos;
if (position) {
pos = position;
} else {
let centerX, centerY;
centerX = -transformX * zoomMultiplier + (width * zoomMultiplier) / 2;
centerY = -transformY * zoomMultiplier + (height * zoomMultiplier) / 2;
const nodeOffset = NODE_WIDTH / 2;
pos = {
x: -nodeOffset,
y: -nodeOffset,
paneX: centerX,
paneY: centerY,
};
}
const newId = getNodeId(type);
const newNode: NodeType = {
id: newId,
type: getNodeRenderType("genericnode"),
position: { x: 0, y: 0 },
data: {
node: component,
type: type,
id: newId,
},
};
paste({ nodes: [newNode], edges: [] }, pos);
},
[store, paste],
);
return addComponent;
}

View file

@ -9,11 +9,11 @@ const AstraSVG = (props) => (
> >
<path <path
d="M60.2338 0.25H0.000244141V67.75H60.2338L75.365 56.0752V11.9248L60.2338 0.25ZM11.6732 11.9248H63.692V56.0874H11.6732V11.9248Z" d="M60.2338 0.25H0.000244141V67.75H60.2338L75.365 56.0752V11.9248L60.2338 0.25ZM11.6732 11.9248H63.692V56.0874H11.6732V11.9248Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"} fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/> />
<path <path
d="M162.038 12.415V1H106.964L92.0097 12.415V28.088L106.964 39.503H154.962V55.585H94.9883V67H151.546L166.5 55.585V39.503L151.546 28.088H103.547V12.415H162.038Z" d="M162.038 12.415V1H106.964L92.0097 12.415V28.088L106.964 39.503H154.962V55.585H94.9883V67H151.546L166.5 55.585V39.503L151.546 28.088H103.547V12.415H162.038Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"} fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/> />
</svg> </svg>
); );

View file

@ -6,6 +6,6 @@ export const AstraDBIcon = forwardRef<
SVGSVGElement, SVGSVGElement,
React.PropsWithChildren<{}> React.PropsWithChildren<{}>
>((props, ref) => { >((props, ref) => {
const isDark = useDarkStore((state) => state.dark); const isdark = useDarkStore((state) => state.dark);
return <AstraSVG ref={ref} isDark={isDark} {...props} />; return <AstraSVG ref={ref} isdark={isdark} {...props} />;
}); });

View file

@ -6,6 +6,7 @@ export const SvgAzure = (props) => (
height="35" height="35"
version="1" version="1"
viewBox="0 0 750 750" viewBox="0 0 750 750"
{...props}
> >
<defs> <defs>
<filter id="ac4759936b" width="100%" height="100%" x="0%" y="0%"> <filter id="ac4759936b" width="100%" height="100%" x="0%" y="0%">

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
const SvgGroqLogo = ({ color, ...props }) => ( const SvgGroqLogo = ({ ...props }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
aria-label="groq logo" aria-label="groq logo"
@ -7,7 +7,8 @@ const SvgGroqLogo = ({ color, ...props }) => (
height="48" height="48"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
class="fill-foreground" className="fill-foreground"
{...props}
> >
<path <path
fill="#F55036" fill="#F55036"

View file

@ -11,11 +11,11 @@ const HCDSVG = (props) => (
{/* <rect width="96" height="96" rx="6" fill="white"/> */} {/* <rect width="96" height="96" rx="6" fill="white"/> */}
<path <path
d="M38.0469 33H12V62.1892H38.0469L44.5902 57.1406V38.0485L38.0469 33ZM17.0478 38.0485H39.5424V57.1459H17.0478V38.0485Z" d="M38.0469 33H12V62.1892H38.0469L44.5902 57.1406V38.0485L38.0469 33ZM17.0478 38.0485H39.5424V57.1459H17.0478V38.0485Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"} fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/> />
<path <path
d="M82.0705 38.2605V33.3243H58.2546L51.788 38.2605V45.038L58.2546 49.9742H79.0107V56.9286H53.076V61.8648H77.5334L84 56.9286V49.9742L77.5334 45.038H56.7772V38.2605H82.0705Z" d="M82.0705 38.2605V33.3243H58.2546L51.788 38.2605V45.038L58.2546 49.9742H79.0107V56.9286H53.076V61.8648H77.5334L84 56.9286V49.9742L77.5334 45.038H56.7772V38.2605H82.0705Z"
fill={props.isDark ? "#ffffff" : "#0A0A0A"} fill={props.isdark ? "#ffffff" : "#0A0A0A"}
/> />
</g> </g>
<defs> <defs>

View file

@ -4,8 +4,8 @@ import HCDSVG from "./HCD";
export const HCDIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>( export const HCDIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => { (props, ref) => {
const isDark = useDarkStore((state) => state.dark); const isdark = useDarkStore((state) => state.dark);
return <HCDSVG ref={ref} isDark={isDark} {...props} />; return <HCDSVG ref={ref} isdark={isdark} {...props} />;
}, },
); );

View file

@ -1,10 +1,10 @@
export const SvgOllama = (props) => ( export const SvgOllama = (props) => (
<svg <svg
width="646" xmlnsXlink="http://www.w3.org/1999/xlink"
height="854" width="40"
viewBox="0 0 646 854" height="40"
fill="none" version="1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 550 850"
{...props} {...props}
> >
<path <path

View file

@ -6,6 +6,7 @@ const SvgMistralIcon = (props) => (
viewBox="0 0 256 233" viewBox="0 0 256 233"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid" preserveAspectRatio="xMidYMid"
{...props}
> >
<title>Mistral AI</title> <title>Mistral AI</title>
<g> <g>

View file

@ -11,12 +11,13 @@ import {
SidebarMenuItem, SidebarMenuItem,
SidebarTrigger, SidebarTrigger,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { useMobile } from "@/hooks/use-mobile"; import { useIsMobile } from "../../../../hooks/use-mobile";
import { cn } from "@/utils/utils"; import { cn } from "@/utils/utils";
import { NavProps } from "../../../../types/templates/types"; import { NavProps } from "../../../../types/templates/types";
export function Nav({ categories, currentTab, setCurrentTab }: NavProps) { export function Nav({ categories, currentTab, setCurrentTab }: NavProps) {
const isMobile = useMobile(); const isMobile = useIsMobile();
return ( return (
<Sidebar collapsible={isMobile ? "icon" : "none"}> <Sidebar collapsible={isMobile ? "icon" : "none"}>

View file

@ -14,8 +14,9 @@ import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
import { track } from "@/customization/utils/analytics"; import { track } from "@/customization/utils/analytics";
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow"; import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
import useUploadFlow from "@/hooks/flows/use-upload-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { nodeColors, nodeColorsName } from "@/utils/styleUtils"; import { useAddComponent } from "@/hooks/useAddComponent";
import { getNodeRenderType, isSupportedNodeTypes } from "@/utils/utils"; import { nodeColorsName } from "@/utils/styleUtils";
import { isSupportedNodeTypes } from "@/utils/utils";
import _, { cloneDeep } from "lodash"; import _, { cloneDeep } from "lodash";
import { import {
KeyboardEvent, KeyboardEvent,
@ -120,6 +121,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
const [isAddingNote, setIsAddingNote] = useState(false); const [isAddingNote, setIsAddingNote] = useState(false);
const [isHighlightingCursor, setIsHighlightingCursor] = useState(false); const [isHighlightingCursor, setIsHighlightingCursor] = useState(false);
const addComponent = useAddComponent();
const { zoomIn, zoomOut, fitView } = useReactFlow(); const { zoomIn, zoomOut, fitView } = useReactFlow();
const { zoom } = useViewport(); const { zoom } = useViewport();
@ -439,23 +441,10 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
event.dataTransfer.getData(datakey!), event.dataTransfer.getData(datakey!),
); );
track("Component Added", { componentType: data.node?.display_name }); addComponent(data.node!, data.type, {
x: event.clientX,
const newId = getNodeId(data.type); y: event.clientY,
});
const newNode: NodeType = {
id: newId,
type: getNodeRenderType(datakey!),
position: { x: 0, y: 0 },
data: {
...data,
id: newId,
},
};
paste(
{ nodes: [newNode], edges: [] },
{ x: event.clientX, y: event.clientY },
);
} else if (event.dataTransfer.types.some((types) => types === "Files")) { } else if (event.dataTransfer.types.some((types) => types === "Files")) {
takeSnapshot(); takeSnapshot();
const position = { const position = {
@ -478,8 +467,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
}); });
} }
}, },
// Specify dependencies for useCallback [takeSnapshot, addComponent],
[getNodeId, setNodes, takeSnapshot, paste],
); );
const onEdgeUpdateStart = useCallback(() => { const onEdgeUpdateStart = useCallback(() => {
@ -596,7 +584,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
}; };
return ( return (
<div className="h-full w-full" ref={reactFlowWrapper}> <div className="h-full w-full bg-canvas" ref={reactFlowWrapper}>
{showCanvas ? ( {showCanvas ? (
<div id="react-flow-id" className="h-full w-full bg-canvas"> <div id="react-flow-id" className="h-full w-full bg-canvas">
<ReactFlow <ReactFlow

View file

@ -12,7 +12,7 @@ export function SidebarFilterComponent({
resetFilters: () => void; resetFilters: () => void;
}) { }) {
return ( return (
<div className="mb-0.5 flex w-full items-center justify-between rounded border bg-muted p-1 px-2 text-xs font-medium text-primary"> <div className="mb-0.5 flex w-full items-center justify-between rounded border bg-accent-indigo p-2 text-sm text-accent-indigo-foreground">
<div className="flex flex-1 items-center gap-1.5"> <div className="flex flex-1 items-center gap-1.5">
<ForwardedIconComponent <ForwardedIconComponent
name="ListFilter" name="ListFilter"
@ -30,7 +30,12 @@ export function SidebarFilterComponent({
styleClasses="max-w-full" styleClasses="max-w-full"
content="Remove filter" content="Remove filter"
> >
<Button unstyled className="shrink-0" onClick={resetFilters}> <Button
unstyled
className="shrink-0"
onClick={resetFilters}
data-testid="sidebar-filter-reset"
>
<ForwardedIconComponent <ForwardedIconComponent
name="X" name="X"
className="h-4 w-4 stroke-2" className="h-4 w-4 stroke-2"

View file

@ -0,0 +1,218 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import { useAddComponent } from "@/hooks/useAddComponent";
import { DragEventHandler, forwardRef, useRef, useState } from "react";
import IconComponent, {
ForwardedIconComponent,
} from "../../../../../../components/genericIconComponent";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "../../../../../../components/ui/select-custom";
import { useDarkStore } from "../../../../../../stores/darkStore";
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
import { APIClassType } from "../../../../../../types/api";
import {
createFlowComponent,
downloadNode,
getNodeId,
} from "../../../../../../utils/reactflowUtils";
import { cn, removeCountFromString } from "../../../../../../utils/utils";
export const SidebarDraggableComponent = forwardRef(
(
{
sectionName,
display_name,
icon,
itemName,
error,
color,
onDragStart,
apiClass,
official,
beta,
legacy,
}: {
sectionName: string;
apiClass: APIClassType;
icon: string;
display_name: string;
itemName: string;
error: boolean;
color: string;
onDragStart: DragEventHandler<HTMLDivElement>;
official: boolean;
beta: boolean;
legacy: boolean;
},
ref,
) => {
const [open, setOpen] = useState(false);
const { deleteFlow } = useDeleteFlow();
const flows = useFlowsManagerStore((state) => state.flows);
const addComponent = useAddComponent();
const version = useDarkStore((state) => state.version);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const popoverRef = useRef<HTMLDivElement>(null);
const handlePointerDown = (e) => {
if (!open) {
const rect = popoverRef.current?.getBoundingClientRect() ?? {
left: 0,
top: 0,
};
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}
};
function handleSelectChange(value: string) {
switch (value) {
case "download":
const type = removeCountFromString(itemName);
downloadNode(
createFlowComponent(
{ id: getNodeId(type), type, node: apiClass },
version,
),
);
break;
case "delete":
const flowId = flows?.find((f) => f.name === display_name);
if (flowId) deleteFlow({ id: flowId.id });
break;
}
}
const handleKeyDown = (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
addComponent(apiClass, itemName);
}
};
return (
<Select
onValueChange={handleSelectChange}
onOpenChange={(change) => setOpen(change)}
open={open}
key={itemName}
>
<div
onPointerDown={handlePointerDown}
onContextMenuCapture={(e) => {
e.preventDefault();
setOpen(true);
}}
key={itemName}
data-tooltip-id={itemName}
tabIndex={0}
onKeyDown={handleKeyDown}
className="rounded-md outline-none ring-ring focus-visible:ring-2"
>
<div
data-testid={sectionName + display_name}
id={sectionName + display_name}
className={cn(
"group/draggable flex cursor-grab items-center gap-2 rounded-md bg-muted p-3 hover:bg-accent",
error ? "cursor-not-allowed select-none" : "",
)}
draggable={!error}
style={{
borderLeftColor: color,
}}
onDragStart={onDragStart}
onDragEnd={() => {
document.body.removeChild(
document.getElementsByClassName("cursor-grabbing")[0],
);
}}
>
<ForwardedIconComponent name={icon} className="h-5 w-5 shrink-0" />
<div className="flex flex-1 items-center overflow-hidden">
<span className="truncate text-sm font-semibold">
{display_name}
</span>
{beta && (
<Badge
variant="pinkStatic"
size="sq"
className="ml-1.5 shrink-0"
>
BETA
</Badge>
)}
{legacy && (
<Badge
variant="secondaryStatic"
size="sq"
className="ml-1.5 shrink-0"
>
LEGACY
</Badge>
)}
</div>
<div className="flex shrink-0 items-center gap-1">
<Button
variant="ghost"
size="icon"
tabIndex={-1}
className="text-muted-foreground hover:text-primary group-focus/draggable:text-primary"
onClick={() => addComponent(apiClass, itemName)}
>
<ForwardedIconComponent
name="Plus"
className="h-4 w-4 shrink-0 opacity-0 transition-all group-hover/draggable:opacity-100 group-focus/draggable:opacity-100"
/>
</Button>
<div ref={popoverRef}>
<ForwardedIconComponent
name="GripVertical"
className="h-4 w-4 shrink-0 text-muted-foreground"
/>
<SelectTrigger tabIndex={-1}></SelectTrigger>
<SelectContent
position="popper"
side="bottom"
sideOffset={-25}
style={{
position: "absolute",
left: cursorPos.x,
top: cursorPos.y,
}}
>
<SelectItem value={"download"}>
<div className="flex">
<IconComponent
name="Download"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Download{" "}
</div>{" "}
</SelectItem>
{!official && (
<SelectItem value={"delete"}>
<div className="flex">
<IconComponent
name="Trash2"
className="relative top-0.5 mr-2 h-4 w-4"
/>{" "}
Delete{" "}
</div>{" "}
</SelectItem>
)}
</SelectContent>
</div>
</div>
</div>
</div>
</Select>
);
},
);
export default SidebarDraggableComponent;

View file

@ -0,0 +1,708 @@
import Fuse from "fuse.js";
import { useEffect, useMemo, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook"; // Import useHotkeys
import ForwardedIconComponent from "@/components/genericIconComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Disclosure,
DisclosureContent,
DisclosureTrigger,
} from "@/components/ui/disclosure";
import { Input } from "@/components/ui/input";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
} from "@/components/ui/sidebar";
import { Switch } from "@/components/ui/switch";
import { CustomLink } from "@/customization/components/custom-link";
import { useAddComponent } from "@/hooks/useAddComponent";
import { useStoreStore } from "@/stores/storeStore";
import {
nodeColors,
SIDEBAR_BUNDLES,
SIDEBAR_CATEGORIES,
} from "@/utils/styleUtils";
import { removeCountFromString } from "@/utils/utils";
import { cloneDeep } from "lodash";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { SidebarFilterComponent } from "../extraSidebarComponent/sidebarFilterComponent";
import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort";
import ShortcutDisplay from "../nodeToolbarComponent/shortcutDisplay";
import SidebarDraggableComponent from "./components/sidebarDraggableComponent";
export function FlowSidebarComponent() {
const [isInputFocused, setIsInputFocused] = useState(false);
const searchInputRef = useRef<HTMLInputElement | null>(null);
useHotkeys("/", (event) => {
event.preventDefault();
searchInputRef.current?.focus();
});
// Add this new useHotkeys hook
useHotkeys(
"esc",
(event) => {
event.preventDefault();
searchInputRef.current?.blur();
},
{
// Only enable this hotkey when the input is focused
enableOnFormTags: true,
enabled: isInputFocused,
},
);
const categories = SIDEBAR_CATEGORIES;
const bundles = SIDEBAR_BUNDLES;
const data = useTypesStore((state) => state.data);
const templates = useTypesStore((state) => state.templates);
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const hasStore = useStoreStore((state) => state.hasStore);
const filterType = useFlowStore((state) => state.filterType);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
const addComponent = useAddComponent();
const [fuse, setFuse] = useState<Fuse<any> | null>(null);
const [openCategories, setOpenCategories] = useState<string[]>([]);
const [showConfig, setShowConfig] = useState(false);
const [showBeta, setShowBeta] = useState(true);
const [showLegacy, setShowLegacy] = useState(false);
const hasResults = useMemo(() => {
return Object.values(dataFilter).some(
(category) => Object.keys(category).length > 0,
);
}, [dataFilter]);
useEffect(() => {
filterComponents();
}, [data, search, filterType, getFilterEdge, showBeta, showLegacy]);
function normalizeString(str: string): string {
return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, "");
}
function searchInMetadata(metadata: any, searchTerm: string): boolean {
if (!metadata || typeof metadata !== "object") return false;
return Object.entries(metadata).some(([key, value]) => {
if (typeof value === "string") {
return (
normalizeString(key).includes(searchTerm) ||
normalizeString(value).includes(searchTerm)
);
}
if (typeof value === "object") {
return searchInMetadata(value, searchTerm);
}
return false;
});
}
const filterComponents = () => {
let filteredData = cloneDeep(data);
// Apply search filter
if (search && fuse) {
const results = fuse.search(search);
filteredData = Object.fromEntries(
Object.entries(data).map(([category, items]) => {
const categoryResults = results.filter(
(result) => result.item.category === category,
);
const filteredItems = Object.fromEntries(
categoryResults.map((result) => [result.item.key, result.item]),
);
return [category, filteredItems];
}),
);
} else {
// Fallback to traditional search if Fuse.js is not available
const searchTerm = normalizeString(search);
filteredData = Object.fromEntries(
Object.entries(data).map(([category, items]) => {
const filteredItems = Object.fromEntries(
Object.entries(items).filter(
([key, item]) =>
normalizeString(key).includes(searchTerm) ||
normalizeString(item.display_name).includes(searchTerm) ||
normalizeString(category).includes(searchTerm) ||
(item.metadata && searchInMetadata(item.metadata, searchTerm)),
),
);
return [category, filteredItems];
}),
);
}
// Apply edge filter
if (getFilterEdge?.length > 0) {
filteredData = Object.fromEntries(
Object.entries(filteredData).map(([family, familyData]) => {
const edgeFilter = getFilterEdge.find((x) => x.family === family);
if (!edgeFilter) return [family, {}];
const filteredTypes = edgeFilter.type
.split(",")
.map((t) => t.trim())
.filter((t) => t !== "");
if (filteredTypes.length === 0) return [family, familyData];
const filteredFamilyData = Object.fromEntries(
Object.entries(familyData).filter(([key]) =>
filteredTypes.includes(key),
),
);
return [family, filteredFamilyData];
}),
);
}
// Apply beta filter
if (!showBeta) {
filteredData = Object.fromEntries(
Object.entries(filteredData).map(([category, items]) => [
category,
Object.fromEntries(
Object.entries(items).filter(([_, value]) => !value.beta),
),
]),
);
}
// Apply legacy filter
if (!showLegacy) {
filteredData = Object.fromEntries(
Object.entries(filteredData).map(([category, items]) => [
category,
Object.fromEntries(
Object.entries(items).filter(([_, value]) => !value.legacy),
),
]),
);
}
setFilterData(filteredData);
if (search !== "" || filterType || getFilterEdge.length > 0) {
setOpenCategories(
Object.keys(filteredData).filter(
(cat) => Object.keys(filteredData[cat]).length > 0,
),
);
}
};
useEffect(() => {
if (search === "" && getFilterEdge.length === 0) {
setOpenCategories([]);
}
}, [search, getFilterEdge]);
function handleSearchInput(e: string) {
setSearch(e);
filterComponents();
}
function onDragStart(
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType },
): void {
//start drag event
var crt = event.currentTarget.cloneNode(true);
crt.style.position = "absolute";
crt.style.width = "215px";
crt.style.top = "-500px";
crt.style.right = "-500px";
crt.classList.add("cursor-grabbing");
document.body.appendChild(crt);
event.dataTransfer.setDragImage(crt, 0, 0);
event.dataTransfer.setData("genericNode", JSON.stringify(data));
}
useEffect(() => {
// show components with error on load
let errors: string[] = [];
Object.keys(templates).forEach((component) => {
if (templates[component].error) {
errors.push(component);
}
});
if (errors.length > 0)
setErrorData({ title: " Components with errors: ", list: errors });
}, []);
useEffect(() => {
if (getFilterEdge.length !== 0) {
setSearch("");
}
}, [getFilterEdge, data]);
useEffect(() => {
const options = {
keys: ["display_name", "description", "type"],
threshold: 0.3,
};
const fuseData = Object.entries(data).flatMap(([category, items]) =>
Object.entries(items).map(([key, value]) => ({
...value,
category,
key,
})),
);
setFuse(new Fuse(fuseData, options));
handleSearchInput(search);
}, [data]);
const customComponent = useMemo(() => {
return data?.["custom_component"]?.["CustomComponent"] ?? null;
}, [data]);
const handleKeyDown = (
e: React.KeyboardEvent<HTMLDivElement>,
name: string,
) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setOpenCategories((prev) =>
prev.includes(name)
? prev.filter((cat) => cat !== name)
: [...prev, name],
);
}
};
return (
<Sidebar data-testid="shad-sidebar">
<SidebarHeader className="flex w-full flex-col gap-4 p-4 pb-1">
<Disclosure open={showConfig} onOpenChange={setShowConfig}>
<div className="flex w-full items-center justify-between">
<h3 className="text-sm font-semibold">Components</h3>
<DisclosureTrigger>
<Button
variant={showConfig ? "ghostActive" : "ghost"}
size="iconMd"
data-testid="sidebar-options-trigger"
>
<ForwardedIconComponent
name="SlidersHorizontal"
className="h-4 w-4"
/>
</Button>
</DisclosureTrigger>
</div>
<DisclosureContent>
<div className="flex flex-col gap-7 border-b pb-7 pt-5">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium">
Show{" "}
<Badge variant="pinkStatic" size="sq">
BETA
</Badge>{" "}
Components
</span>
</div>
<Switch
checked={showBeta}
onCheckedChange={setShowBeta}
data-testid="sidebar-beta-switch"
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium">
Show{" "}
<Badge variant="secondaryStatic" size="sq">
LEGACY
</Badge>{" "}
Components
</span>
</div>
<Switch
checked={showLegacy}
onCheckedChange={setShowLegacy}
data-testid="sidebar-legacy-switch"
/>
</div>
</div>
</DisclosureContent>
</Disclosure>
<div className="relative w-full flex-1">
<ForwardedIconComponent
name="Search"
className="absolute inset-y-0 left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-primary"
/>
<Input
ref={searchInputRef}
type="search"
data-testid="sidebar-search-input"
className="w-full rounded-lg bg-background pl-8 text-sm"
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
value={search}
onChange={(e) => handleSearchInput(e.target.value)}
/>
{!isInputFocused && search === "" && (
<div className="pointer-events-none absolute inset-y-0 left-8 top-1/2 flex -translate-y-1/2 items-center gap-2 text-sm text-muted-foreground">
Type{" "}
<span>
<ShortcutDisplay sidebar shortcut="/" />
</span>{" "}
to search components
</div>
)}
</div>
{filterType && (
<SidebarFilterComponent
isInput={!!filterType.source}
type={filterType.type}
resetFilters={() => {
setFilterEdge([]);
setFilterData(data);
}}
/>
)}
</SidebarHeader>
<SidebarContent className="p-2">
{hasResults ? (
<>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{!data
? Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton />
</SidebarMenuItem>
))
: categories.map(
(item) =>
dataFilter[item.name] &&
Object.keys(dataFilter[item.name]).length > 0 && (
<Collapsible
key={item.name}
className="group/collapsible"
open={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<div
data-testid={`disclosure-${item.display_name.toLocaleLowerCase()}`}
tabIndex={0}
onKeyDown={(e) =>
handleKeyDown(e, item.name)
}
className="flex cursor-pointer items-center gap-2"
>
<ForwardedIconComponent
name={item.icon}
className="h-4 w-4 text-muted-foreground group-data-[state=open]/collapsible:text-pink-600 group-data-[state=open]/collapsible:dark:text-pink-400"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
{item.display_name}
</span>
<ForwardedIconComponent
name="ChevronRight"
className="h-4 w-4 text-muted-foreground transition-all group-data-[state=open]/collapsible:rotate-90"
/>
</div>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="flex flex-col gap-1 py-2">
{Object.keys(dataFilter[item.name])
.sort((a, b) =>
sensitiveSort(
dataFilter[item.name][a].display_name,
dataFilter[item.name][b].display_name,
),
)
.map((SBItemName: string, idx) => (
<ShadTooltip
content={
dataFilter[item.name][SBItemName]
.display_name
}
side="right"
key={idx}
>
<SidebarDraggableComponent
sectionName={item.name as string}
apiClass={
dataFilter[item.name][SBItemName]
}
icon={
dataFilter[item.name][SBItemName]
.icon ??
item.icon ??
"Unknown"
}
key={idx}
onDragStart={(event) =>
onDragStart(event, {
type: removeCountFromString(
SBItemName,
),
node: dataFilter[item.name][
SBItemName
],
})
}
color={nodeColors[item.name]}
itemName={SBItemName}
error={
!!dataFilter[item.name][
SBItemName
].error
}
display_name={
dataFilter[item.name][SBItemName]
.display_name
}
official={
dataFilter[item.name][SBItemName]
.official === false
? false
: true
}
beta={
dataFilter[item.name][SBItemName]
.beta ?? false
}
legacy={
dataFilter[item.name][SBItemName]
.legacy ?? false
}
/>
</ShadTooltip>
))}
</div>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
),
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>Bundles</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{!data
? Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index}>
<SidebarMenuSkeleton />
</SidebarMenuItem>
))
: bundles.map(
(item) =>
dataFilter[item.name] &&
Object.keys(dataFilter[item.name]).length > 0 && (
<Collapsible
key={item.name}
className="group/collapsible"
open={openCategories.includes(item.name)}
onOpenChange={(isOpen) => {
setOpenCategories((prev) =>
isOpen
? [...prev, item.name]
: prev.filter((cat) => cat !== item.name),
);
}}
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<div
tabIndex={0}
onKeyDown={(e) =>
handleKeyDown(e, item.name)
}
className="flex cursor-pointer items-center gap-2"
>
<ForwardedIconComponent
name={item.icon}
className="h-4 w-4 text-muted-foreground group-data-[state=open]/collapsible:text-pink-600 group-data-[state=open]/collapsible:dark:text-pink-400"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
{item.display_name}
</span>
<ForwardedIconComponent
name="ChevronRight"
className="h-4 w-4 text-muted-foreground transition-all group-data-[state=open]/collapsible:rotate-90"
/>
</div>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="flex flex-col gap-1 py-2">
{Object.keys(dataFilter[item.name])
.sort((a, b) =>
sensitiveSort(
dataFilter[item.name][a].display_name,
dataFilter[item.name][b].display_name,
),
)
.map((SBItemName: string, idx) => (
<ShadTooltip
content={
dataFilter[item.name][SBItemName]
.display_name
}
side="right"
key={idx}
>
<SidebarDraggableComponent
sectionName={item.name as string}
apiClass={
dataFilter[item.name][SBItemName]
}
icon={
dataFilter[item.name][SBItemName]
.icon ??
item.icon ??
"Unknown"
}
key={idx}
onDragStart={(event) =>
onDragStart(event, {
type: removeCountFromString(
SBItemName,
),
node: dataFilter[item.name][
SBItemName
],
})
}
color={nodeColors[item.name]}
itemName={SBItemName}
error={
!!dataFilter[item.name][
SBItemName
].error
}
display_name={
dataFilter[item.name][SBItemName]
.display_name
}
official={
dataFilter[item.name][SBItemName]
.official === false
? false
: true
}
beta={
dataFilter[item.name][SBItemName]
.beta ?? false
}
legacy={
dataFilter[item.name][SBItemName]
.legacy ?? false
}
/>
</ShadTooltip>
))}
</div>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
),
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</>
) : (
<div className="flex h-full flex-col items-center justify-center p-4 text-center">
<ForwardedIconComponent
name="Search"
className="mb-4 h-8 w-8 text-muted-foreground"
/>
<h3 className="mb-2 text-lg font-semibold">No results found</h3>
<p className="text-sm text-muted-foreground">
Try adjusting your search or filter to find what you're looking
for.
</p>
</div>
)}
</SidebarContent>
<SidebarFooter className="border-t p-4 py-3">
{hasStore && (
<SidebarMenuButton asChild>
<CustomLink to="/store">
<div className="flex items-center gap-2">
<ForwardedIconComponent
name="Store"
className="h-4 w-4 text-muted-foreground"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
Discover more components
</span>
</div>
</CustomLink>
</SidebarMenuButton>
)}
<SidebarMenuButton asChild>
<Button
unstyled
onClick={() => {
if (customComponent) {
addComponent(customComponent, "CustomComponent");
}
}}
data-testid="sidebar-custom-component-button"
className="flex items-center gap-2"
>
<ForwardedIconComponent
name="Plus"
className="h-4 w-4 text-muted-foreground"
/>
<span className="group-data-[state=open]/collapsible:font-semibold">
Custom Component
</span>
</Button>
</SidebarMenuButton>
</SidebarFooter>
</Sidebar>
);
}

View file

@ -1,14 +1,31 @@
import RenderIcons from "@/components/renderIconComponent"; import RenderIcons from "@/components/renderIconComponent";
import { cn } from "@/utils/utils";
export default function ShortcutDisplay({ export default function ShortcutDisplay({
name, name,
shortcut, shortcut,
sidebar = false,
}: { }: {
name: string; name?: string;
shortcut: string; shortcut: string;
sidebar?: boolean;
}): JSX.Element { }): JSX.Element {
const fixedShortcut = shortcut?.split("+"); const fixedShortcut = shortcut?.split("+");
return ( return (
<>
{sidebar ? (
<div className="flex justify-center">
{name && <span> {name} </span>}
<span
className={cn(
"flex items-center rounded-sm bg-muted px-1.5 py-[0.1em] text-lg text-muted-foreground",
name && "ml-3",
)}
>
<RenderIcons filteredShortcut={fixedShortcut} />
</span>
</div>
) : (
<div className="flex content-center items-center justify-center self-center text-[12px]"> <div className="flex content-center items-center justify-center self-center text-[12px]">
<span> {name} </span> <span> {name} </span>
<span <span
@ -17,5 +34,7 @@ export default function ShortcutDisplay({
<RenderIcons filteredShortcut={fixedShortcut} /> <RenderIcons filteredShortcut={fixedShortcut} />
</span> </span>
</div> </div>
)}
</>
); );
} }

View file

@ -1,6 +1,6 @@
import { SidebarProvider } from "@/components/ui/sidebar";
import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow"; import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow";
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows"; import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { ENABLE_BRANDING } from "@/customization/feature-flags";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useSaveFlow from "@/hooks/flows/use-save-flow"; import useSaveFlow from "@/hooks/flows/use-save-flow";
import { SaveChangesModal } from "@/modals/saveChangesModal"; import { SaveChangesModal } from "@/modals/saveChangesModal";
@ -14,7 +14,7 @@ import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore"; import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent"; import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent"; import { FlowSidebarComponent } from "./components/flowSidebarComponent";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element { export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
@ -165,16 +165,17 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
<div className="flow-page-positioning"> <div className="flow-page-positioning">
{currentFlow && ( {currentFlow && (
<div className="flex h-full overflow-hidden"> <div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />} <SidebarProvider>
{!view && <FlowSidebarComponent />}
<main className="flex flex-1"> <main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full"> <div className="h-full w-full">
<Page /> <Page />
</div> </div>
</main> </main>
</SidebarProvider>
</div> </div>
)} )}
{ENABLE_BRANDING && version && ( {/* {ENABLE_BRANDING && version && (
<a <a
target={"_blank"} target={"_blank"}
href="https://medium.com/logspace/langflow-datastax-better-together-1b7462cebc4d" href="https://medium.com/logspace/langflow-datastax-better-together-1b7462cebc4d"
@ -184,7 +185,7 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
<div className={version ? "mt-2" : "mt-1"}>⛓️ v{version}</div> <div className={version ? "mt-2" : "mt-1"}>⛓️ v{version}</div>
</a> </a>
)} )} */}
</div> </div>
{blocker.state === "blocked" && ( {blocker.state === "blocked" && (
<> <>

View file

@ -7,7 +7,6 @@ import {
templatesGenerator, templatesGenerator,
typesGenerator, typesGenerator,
} from "../utils/reactflowUtils"; } from "../utils/reactflowUtils";
import useAlertStore from "./alertStore";
import useFlowsManagerStore from "./flowsManagerStore"; import useFlowsManagerStore from "./flowsManagerStore";
export const useTypesStore = create<TypesStoreType>((set, get) => ({ export const useTypesStore = create<TypesStoreType>((set, get) => ({

View file

@ -260,8 +260,7 @@ pre {
-webkit-appearance: none; -webkit-appearance: none;
background-color: hsl(var(--primary)); background-color: hsl(var(--primary));
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23777'><path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23777'><path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>");
background-size: 16px 16px; background-size: 20px 20px;
height: 16px; height: 20px;
opacity: 1 !important; width: 20px;
width: 16px;
} }

View file

@ -248,6 +248,14 @@
--chat-bot-icon: #235d70; --chat-bot-icon: #235d70;
--chat-user-icon: #4f3d6e; --chat-user-icon: #4f3d6e;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--emerald-success: 160.1 84.1% 39.4%; --emerald-success: 160.1 84.1% 39.4%;
--accent-emerald-foreground: 161.4 93.5% 30.4%; --accent-emerald-foreground: 161.4 93.5% 30.4%;
--placeholder: 240 5% 64.9%; --placeholder: 240 5% 64.9%;

View file

@ -43,6 +43,7 @@ export type APIClassType = {
output_types?: Array<string>; output_types?: Array<string>;
custom_fields?: CustomFieldsType; custom_fields?: CustomFieldsType;
beta?: boolean; beta?: boolean;
legacy?: boolean;
documentation: string; documentation: string;
error?: string; error?: string;
official?: boolean; official?: boolean;

View file

@ -0,0 +1,5 @@
export interface SidebarCategory {
display_name: string;
name: string;
icon: string;
}

View file

@ -84,6 +84,7 @@ import {
GitFork, GitFork,
GithubIcon, GithubIcon,
Globe, Globe,
GripVertical,
Group, Group,
Hammer, Hammer,
Heart, Heart,
@ -416,41 +417,49 @@ export const nodeColorsName: { [char: string]: string } = {
BaseChatMemory: "cyan", BaseChatMemory: "cyan",
}; };
export const nodeNames: { [char: string]: string } = { export const SIDEBAR_CATEGORIES = [
inputs: "Inputs", { display_name: "Saved", name: "saved_components", icon: "GradientSave" },
outputs: "Outputs", { display_name: "Inputs", name: "inputs", icon: "Download" },
data: "Data", { display_name: "Outputs", name: "outputs", icon: "Upload" },
prompts: "Prompts", { display_name: "Prompts", name: "prompts", icon: "TerminalSquare" },
models: "Models", { display_name: "Data", name: "data", icon: "Database" },
notion: "Notion", { display_name: "Models", name: "models", icon: "BrainCircuit" },
Notion: "Notion", { display_name: "Helpers", name: "helpers", icon: "Wand2" },
AssemblyAI: "AssemblyAI", { display_name: "Vector Stores", name: "vectorstores", icon: "Layers" },
assemblyai: "AssemblyAI", { display_name: "Embeddings", name: "embeddings", icon: "Binary" },
model_specs: "Model Specs", { display_name: "Agents", name: "agents", icon: "Bot" },
chains: "Chains", { display_name: "Chains", name: "chains", icon: "Link" },
agents: "Agents", { display_name: "Loaders", name: "documentloaders", icon: "Paperclip" },
tools: "Tools", {
memories: "Memories", display_name: "Utilities",
saved_components: "Saved", name: "langchain_utilities",
advanced: "Advanced", icon: "PocketKnife",
chat: "Chat", },
embeddings: "Embeddings", { display_name: "Link Extractors", name: "link_extractors", icon: "Link2" },
documentloaders: "Loaders", { display_name: "Memories", name: "memories", icon: "Cpu" },
vectorstores: "Vector Stores", { display_name: "Output Parsers", name: "output_parsers", icon: "Compass" },
vectorsearch: "Vector Search", { display_name: "Prototypes", name: "prototypes", icon: "FlaskConical" },
toolkits: "Toolkits", { display_name: "Retrievers", name: "retrievers", icon: "FileSearch" },
wrappers: "Wrappers", { display_name: "Text Splitters", name: "textsplitters", icon: "Scissors" },
textsplitters: "Text Splitters", { display_name: "Toolkits", name: "toolkits", icon: "Package2" },
retrievers: "Retrievers", { display_name: "Tools", name: "tools", icon: "Hammer" },
helpers: "Helpers", ];
prototypes: "Prototypes",
astra_assistants: "Astra Assistants", export const SIDEBAR_BUNDLES = [
langchain_utilities: "Utilities", { display_name: "CrewAI", name: "crewai", icon: "CrewAi" },
output_parsers: "Output Parsers", { display_name: "LangChain", name: "langchain_utilities", icon: "LangChain" },
custom_components: "Custom", { display_name: "AssemblyAI", name: "assemblyai", icon: "AssemblyAI" },
link_extractors: "Link Extractors", {
unknown: "Other", display_name: "Astra Assistants",
}; name: "astra_assistants",
icon: "Sparkles",
},
{ display_name: "Google", name: "google", icon: "Google" },
{ display_name: "Firecrawl", name: "firecrawl", icon: "FirecrawlCrawlApi" },
{ display_name: "Notion", name: "Notion", icon: "Notion" },
{ display_name: "NVIDIA", name: "nvidia", icon: "NVIDIA" },
{ display_name: "Vectara", name: "vectara", icon: "Vectara" },
];
export const categoryIcons = { export const categoryIcons = {
saved_components: GradientSave, saved_components: GradientSave,
@ -669,6 +678,16 @@ export const nodeIconsLucide: iconsType = {
Blocks, Blocks,
ChevronDown, ChevronDown,
ArrowLeft, ArrowLeft,
BrainCircuit,
Wand2,
Layers,
Binary,
Paperclip,
PocketKnife,
Scissors,
Cpu,
Hammer,
GradientSave,
Shield, Shield,
Plus, Plus,
Redo, Redo,
@ -743,6 +762,10 @@ export const nodeIconsLucide: iconsType = {
Loader2, Loader2,
BookmarkPlus, BookmarkPlus,
Heart, Heart,
Package2,
FileSearch,
Compass,
Link2,
Pin, Pin,
Link, Link,
ToyBrick, ToyBrick,
@ -768,6 +791,7 @@ export const nodeIconsLucide: iconsType = {
note: StickyNote, note: StickyNote,
RotateCcw, RotateCcw,
Wrench, Wrench,
GripVertical,
FolderPlusIcon, FolderPlusIcon,
PaperclipIcon, PaperclipIcon,
Settings, Settings,

View file

@ -0,0 +1,73 @@
import { expect, test } from "@playwright/test";
test("user can add components by hovering and clicking the plus icon", async ({
page,
}) => {
// Navigate to homepage and handle initial modal
await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
// Start with blank flow
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Search for a component
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(500);
// Hover over the component and verify plus icon
const componentLocator = page.getByTestId("inputsChat Input");
// Find the plus icon within the specific component container
const plusIcon = componentLocator.getByTestId("icon-Plus");
// Get the opacity
const opacity = await plusIcon.evaluate((el) =>
window.getComputedStyle(el).getPropertyValue("opacity"),
);
await expect(plusIcon).toBeVisible();
await expect(opacity).toBe("0");
// Hover over the component
await componentLocator.hover();
// Check if the plus icon is visible and has full opacity
await expect(plusIcon).toBeVisible();
await page.waitForTimeout(500);
const opacityAfterHover = await plusIcon.evaluate((el) =>
window.getComputedStyle(el).getPropertyValue("opacity"),
);
await expect(opacityAfterHover).toBe("1");
// Click the plus icon associated with this component
await plusIcon.click();
await page.waitForTimeout(500);
// Verify component was added to the flow
const addedComponent = await page.locator(".react-flow__node").first();
await expect(addedComponent).toBeVisible();
});

View file

@ -23,12 +23,14 @@ test("user must see on handle hover a tooltip with possibility connections", asy
} }
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("retrievalqa");
});
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-options-trigger").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByPlaceholder("Search").fill("retrievalqa"); await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -32,13 +32,8 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForTimeout(3000); await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 100000, await page.getByTestId("sidebar-search-input").fill("api request");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("api request");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -77,6 +72,14 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("disclosure-models")).toBeVisible(); await expect(page.getByTestId("disclosure-models")).toBeVisible();
await expect(page.getByTestId("disclosure-helpers")).toBeVisible(); await expect(page.getByTestId("disclosure-helpers")).toBeVisible();
await expect(page.getByTestId("disclosure-agents")).toBeVisible(); await expect(page.getByTestId("disclosure-agents")).toBeVisible();
await expect(page.getByTestId("disclosure-chains")).not.toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-legacy-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-legacy-switch").click();
await expect(page.getByTestId("sidebar-legacy-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("disclosure-chains")).toBeVisible(); await expect(page.getByTestId("disclosure-chains")).toBeVisible();
await expect(page.getByTestId("disclosure-prototypes")).toBeVisible(); await expect(page.getByTestId("disclosure-prototypes")).toBeVisible();
@ -89,7 +92,17 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("chainsConversationChain")).toBeVisible(); await expect(page.getByTestId("chainsConversationChain")).toBeVisible();
await expect(page.getByTestId("prototypesConditional Router")).toBeVisible(); await expect(page.getByTestId("prototypesConditional Router")).toBeVisible();
await page.getByPlaceholder("Search").click(); await expect(page.getByTestId("helpersCurrent Date")).toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-beta-switch").click();
await expect(page.getByTestId("sidebar-beta-switch")).not.toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("helpersCurrent Date")).not.toBeVisible();
await page.getByTestId("sidebar-filter-reset").click();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible(); await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
await expect(page.getByTestId("outputsChat Output")).not.toBeVisible(); await expect(page.getByTestId("outputsChat Output")).not.toBeVisible();
@ -117,7 +130,16 @@ test("user must see on handle click the possibility connections - LLMChain", asy
await expect(page.getByTestId("helpersChat Memory")).toBeVisible(); await expect(page.getByTestId("helpersChat Memory")).toBeVisible();
await expect(page.getByTestId("vectorstoresAstra DB")).toBeVisible(); await expect(page.getByTestId("vectorstoresAstra DB")).toBeVisible();
await expect(page.getByTestId("toolsSearch API")).toBeVisible(); await expect(page.getByTestId("toolsSearch API")).toBeVisible();
await expect(page.getByTestId("prototypesSub Flow")).not.toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
await page.getByTestId("sidebar-beta-switch").click();
await expect(page.getByTestId("sidebar-beta-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
await expect(page.getByTestId("prototypesSub Flow")).toBeVisible(); await expect(page.getByTestId("prototypesSub Flow")).toBeVisible();
await expect( await expect(
page.getByTestId("retrieversSelf Query Retriever"), page.getByTestId("retrieversSelf Query Retriever"),
).toBeVisible(); ).toBeVisible();

View file

@ -30,9 +30,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//first component //first component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("text input");
await page.getByPlaceholder("Search").fill("text input");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -52,9 +51,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//second component //second component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("url");
await page.getByPlaceholder("Search").fill("url");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -74,9 +72,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//third component //third component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("split text");
await page.getByPlaceholder("Search").fill("split text");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -96,9 +93,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//fourth component //fourth component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("parse data");
await page.getByPlaceholder("Search").fill("parse data");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -118,9 +114,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
//fifth component //fifth component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("chat output");
await page.getByPlaceholder("Search").fill("chat output");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -31,12 +31,8 @@ test("user must be able to save or delete a global variable", async ({
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("openai");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -0,0 +1,96 @@
import { expect, test } from "@playwright/test";
test("user can search and add components using keyboard shortcuts", async ({
page,
}) => {
// Navigate to homepage and handle initial modal
await page.goto("/");
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
// Start with blank flow
await page.getByTestId("blank-flow").click();
await page.waitForTimeout(1000);
// Press "/" to activate search
await page.keyboard.press("/");
await page.waitForTimeout(500);
// Verify search is focused and disclosures are closed when search is empty
await expect(page.getByTestId("sidebar-search-input")).toBeFocused();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
// Type "chat" to search for chat components
await page.keyboard.type("chat");
await page.waitForTimeout(500);
// Verify disclosures open when search has content
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
// Press Tab to focus first result
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
// Verify some expected chat-related components are visible
await expect(page.getByTestId("inputsChat Input")).toBeVisible();
await expect(page.getByTestId("outputsChat Output")).toBeVisible();
// Press Space to select the component
await page.keyboard.press("Space");
await page.waitForTimeout(500);
// Verify component was added to flow
const addedComponent = await page.locator(".react-flow__node").first();
await expect(addedComponent).toBeVisible();
// Clear search input and verify disclosures are closed
await page.getByTestId("sidebar-search-input").clear();
await page.waitForTimeout(500);
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
// Test Enter key selection
await page.keyboard.press("/");
await page.keyboard.type("prompt");
await page.waitForTimeout(500);
// Verify disclosures open with new search
await expect(page.getByTestId("promptsPrompt")).toBeVisible();
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.waitForTimeout(500);
// Verify second component was added
const nodeCount = await page.locator(".react-flow__node").count();
expect(nodeCount).toBe(2);
// Verify search is cleared and disclosures are closed after adding component
await page.keyboard.press("/");
await page.getByTestId("sidebar-search-input").clear();
await page.waitForTimeout(500);
await expect(page.getByTestId("sidebar-search-input")).toHaveValue("");
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
await expect(page.getByTestId("sidebar-search-input")).toBeFocused();
await page.keyboard.press("Escape");
await expect(page.getByTestId("sidebar-search-input")).not.toBeFocused();
await expect(page.getByTestId("inputsChat Input")).not.toBeVisible();
});

View file

@ -38,12 +38,9 @@ test("fresh start playground", async ({ page }) => {
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000, await page.getByTestId("sidebar-search-input").click();
}); await page.getByTestId("sidebar-search-input").fill("chat output");
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -52,8 +49,8 @@ test("fresh start playground", async ({ page }) => {
await page.mouse.up(); await page.mouse.up();
await page.mouse.down(); await page.mouse.down();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("chat input"); await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -103,12 +103,8 @@ test.describe("save component tests", () => {
if (replaceButton) { if (replaceButton) {
await page.getByTestId("replace-button").click(); await page.getByTestId("replace-button").click();
} }
await page.getByTestId("sidebar-search-input").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").fill("group");
timeout: 30000,
});
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("group");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -33,9 +33,8 @@ test("user must be able to stop a building", async ({ page }) => {
//first component //first component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("text input");
await page.getByPlaceholder("Search").fill("text input");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -55,9 +54,8 @@ test("user must be able to stop a building", async ({ page }) => {
//second component //second component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("url");
await page.getByPlaceholder("Search").fill("url");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -77,9 +75,8 @@ test("user must be able to stop a building", async ({ page }) => {
//third component //third component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("split text");
await page.getByPlaceholder("Search").fill("split text");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -99,9 +96,8 @@ test("user must be able to stop a building", async ({ page }) => {
//fourth component //fourth component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("parse data");
await page.getByPlaceholder("Search").fill("parse data");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -121,9 +117,8 @@ test("user must be able to stop a building", async ({ page }) => {
//fifth component //fifth component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("chat output");
await page.getByPlaceholder("Search").fill("chat output");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -248,17 +243,7 @@ class CustomComponent(Component):
return data return data
`; `;
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-custom-component-button").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("custom component");
await page.waitForTimeout(1000);
await page
.locator('//*[@id="helpersCustom Component"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click(); await page.getByTestId("zoom_out").click();

View file

@ -88,12 +88,8 @@ test("check if tweaks are updating when someothing on the flow changes", async (
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("Chroma");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -1,83 +1,150 @@
import { expect, test } from "@playwright/test"; import { Page, test } from "@playwright/test";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import path from "path"; import path from "path";
// Helper function to wait for element to be ready
async function waitForElement(page: Page, elementId: string, nth: number) {
const element = page.getByTestId(`title-${elementId}`).nth(nth);
// Add this function at the beginning of the file, after the imports // Wait for element to be visible and stable
await element.waitFor({
state: "visible",
timeout: 30000,
});
// Additional wait to ensure element is fully rendered and interactive
await page.waitForTimeout(1000);
return element;
}
// Improved version of moveElementByX with better error handling and waits
async function moveElementByX( async function moveElementByX(
page: any, page: Page,
elementId: string, elementId: string,
moveX: number, moveX: number,
nth: number, nth: number,
) { ) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth); try {
const element = await waitForElement(page, elementId, nth);
await element.hover(); await element.hover();
const boundingBox = await element.boundingBox(); const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2; const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2; const startY = boundingBox.y + boundingBox.height / 2;
// Break down mouse movements into smaller steps for more reliability
await page.mouse.move(startX, startY); await page.mouse.move(startX, startY);
await page.waitForTimeout(50);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(startX + moveX, startY); await page.waitForTimeout(50);
// Move in smaller increments
const steps = 3;
const stepX = moveX / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX + stepX * i, startY);
await page.waitForTimeout(50);
}
await page.mouse.up(); await page.mouse.up();
} else { await page.waitForTimeout(100);
throw new Error(`Unable to get bounding box for the element: ${elementId}`); } catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
} }
} }
// Add this function at the beginning of the file, after the imports // Improved version of moveElementByY with better error handling and waits
async function moveElementByY( async function moveElementByY(
page: any, page: Page,
elementId: string, elementId: string,
moveY: number, moveY: number,
nth: number, nth: number,
) { ) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth); try {
const element = await waitForElement(page, elementId, nth);
await element.hover(); await element.hover();
const boundingBox = await element.boundingBox(); const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2; const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2; const startY = boundingBox.y + boundingBox.height / 2;
await page.mouse.move(startX, startY); await page.mouse.move(startX, startY);
await page.waitForTimeout(100);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(startX, startY + moveY); await page.waitForTimeout(100);
// Move in smaller increments
const steps = 5;
const stepY = moveY / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX, startY + stepY * i);
await page.waitForTimeout(50);
}
await page.mouse.up(); await page.mouse.up();
} else { await page.waitForTimeout(100);
throw new Error(`Unable to get bounding box for the element: ${elementId}`); } catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
} }
} }
// Add this function at the beginning of the file, after the imports // Improved version of moveElementByXY with better error handling and waits
async function moveElementByXY( async function moveElementByXY(
page: any, page: Page,
elementId: string, elementId: string,
moveX: number, moveX: number,
moveY: number, moveY: number,
nth: number, nth: number,
) { ) {
const element = await page.getByTestId(`title-${elementId}`).nth(nth); try {
const element = await waitForElement(page, elementId, nth);
await element.hover(); await element.hover();
const boundingBox = await element.boundingBox(); const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error(
`Unable to get bounding box for the element: ${elementId}`,
);
}
if (boundingBox) {
const startX = boundingBox.x + boundingBox.width / 2; const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2; const startY = boundingBox.y + boundingBox.height / 2;
await page.mouse.move(startX, startY); await page.mouse.move(startX, startY);
await page.waitForTimeout(100);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(startX + moveX, startY + moveY); await page.waitForTimeout(100);
await page.mouse.up();
} else { // Move in smaller increments
throw new Error(`Unable to get bounding box for the element: ${elementId}`); const steps = 5;
} const stepX = moveX / steps;
const stepY = moveY / steps;
for (let i = 1; i <= steps; i++) {
await page.mouse.move(startX + stepX * i, startY + stepY * i);
await page.waitForTimeout(50);
} }
await page.mouse.up();
await page.waitForTimeout(100);
} catch (error) {
console.error(`Failed to move element ${elementId}:`, error);
throw error;
}
}
test("should create a flow with decision", async ({ page }) => { test("should create a flow with decision", async ({ page }) => {
test.skip( test.skip(
!process?.env?.OPENAI_API_KEY, !process?.env?.OPENAI_API_KEY,
@ -111,21 +178,17 @@ test("should create a flow with decision", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click();
//---------------------------------- CHAT INPUT //---------------------------------- CHAT INPUT
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("chat input"); await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("inputsChat Input") .getByTestId("inputsChat Input")
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CREATE LIST //---------------------------------- CREATE LIST
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("list"); await page.getByTestId("sidebar-search-input").fill("list");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("helpersCreate List") .getByTestId("helpersCreate List")
@ -158,8 +221,8 @@ test("should create a flow with decision", async ({ page }) => {
await page.getByTestId("inputlist_str_texts_2").last().fill("not cool.."); await page.getByTestId("inputlist_str_texts_2").last().fill("not cool..");
//---------------------------------- PARSE DATA //---------------------------------- PARSE DATA
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("parse data"); await page.getByTestId("sidebar-search-input").fill("parse data");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
@ -170,8 +233,8 @@ test("should create a flow with decision", async ({ page }) => {
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- PASS //---------------------------------- PASS
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("pass"); await page.getByTestId("sidebar-search-input").fill("pass");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("prototypesPass") .getByTestId("prototypesPass")
@ -185,30 +248,30 @@ test("should create a flow with decision", async ({ page }) => {
.getByTestId("prototypesPass") .getByTestId("prototypesPass")
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- PROMPT //---------------------------------- PROMPT
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("prompt"); await page.getByTestId("sidebar-search-input").fill("prompt");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("promptsPrompt") .getByTestId("promptsPrompt")
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- OPENAI //---------------------------------- OPENAI
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("openai"); await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("modelsOpenAI") .getByTestId("modelsOpenAI")
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CONDITIONAL ROUTER //---------------------------------- CONDITIONAL ROUTER
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("conditional router"); await page.getByTestId("sidebar-search-input").fill("conditional router");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("prototypesConditional Router") .getByTestId("prototypesConditional Router")
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
//---------------------------------- CHAT OUTPUT //---------------------------------- CHAT OUTPUT
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("chat output"); await page.getByTestId("sidebar-search-input").fill("chat output");
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page await page
.getByTestId("outputsChat Output") .getByTestId("outputsChat Output")
@ -222,32 +285,42 @@ test("should create a flow with decision", async ({ page }) => {
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await moveElementByX(page, "Chat Output", 500, 1);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByX(page, "Chat Output", 1000, 0);
await moveElementByX(page, "Chat Output", 400, 1);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByX(page, "Conditional Router", 1500, 0); await moveElementByX(page, "Chat Output", 700, 0);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByX(page, "OpenAI", 2000, 0); await moveElementByX(page, "Conditional Router", 1000, 0);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByX(page, "Prompt", 2500, 0); await page.getByTestId("fit_view").click();
await moveElementByX(page, "OpenAI", 980, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByX(page, "Pass", 3000, 2); await moveElementByX(page, "Prompt", 990, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500);
await moveElementByX(page, "Pass", 1000, 2);
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByXY(page, "Pass", 0, 200, 1); await moveElementByXY(page, "Pass", 0, 200, 1);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByXY(page, "Pass", 150, 200, 0); await moveElementByXY(page, "Pass", 150, 200, 0);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByXY(page, "Parse Data", 300, 200, 1); await moveElementByXY(page, "Parse Data", 300, 200, 1);
await page.getByTestId("fit_view").click();
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByXY(page, "Parse Data", 450, 200, 0); await moveElementByXY(page, "Parse Data", 450, 200, 0);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByXY(page, "Create List", 600, 200, 1); await moveElementByXY(page, "Create List", 600, 200, 1);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await moveElementByXY(page, "Create List", 800, 200, 0); await moveElementByXY(page, "Create List", 800, 200, 0);
await page.waitForTimeout(500);
await moveElementByXY(page, "Chat Input", 1000, 200, 0);
await page.waitForTimeout(500); await page.waitForTimeout(500);
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();

View file

@ -31,9 +31,8 @@ test("user must be able to check similarity between embedding texts", async ({
//first component //first component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("openai");
await page.getByPlaceholder("Search").fill("openai");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -70,9 +69,8 @@ test("user must be able to check similarity between embedding texts", async ({
//third component //third component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("text embedder");
await page.getByPlaceholder("Search").fill("text embedder");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -109,9 +107,8 @@ test("user must be able to check similarity between embedding texts", async ({
//fifth component //fifth component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("embedding similarity");
await page.getByPlaceholder("Search").fill("embedding similarity");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -131,9 +128,8 @@ test("user must be able to check similarity between embedding texts", async ({
//sisxth component //sisxth component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("parse data");
await page.getByPlaceholder("Search").fill("parse data");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -153,9 +149,8 @@ test("user must be able to check similarity between embedding texts", async ({
//seventh component //seventh component
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("text output");
await page.getByPlaceholder("Search").fill("text output");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page
@ -173,9 +168,8 @@ test("user must be able to check similarity between embedding texts", async ({
await page.mouse.up(); await page.mouse.up();
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("filter data");
await page.getByPlaceholder("Search").fill("filter data");
// await page.waitForTimeout(1000); // await page.waitForTimeout(1000);
await page await page

View file

@ -41,19 +41,9 @@ test("TextInputOutputComponent", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
const focusElementsOnBoard = async ({ page }) => { await page.getByTestId("sidebar-search-input").click();
const focusElements = await page.getByTestId("extended-disclosure"); await page.getByTestId("sidebar-search-input").fill("text input");
focusElements.click();
};
await focusElementsOnBoard({ page });
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("text input");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -62,8 +52,8 @@ test("TextInputOutputComponent", async ({ page }) => {
await page.mouse.up(); await page.mouse.up();
await page.mouse.down(); await page.mouse.down();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("openai"); await page.getByTestId("sidebar-search-input").fill("openai");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -120,8 +110,8 @@ test("TextInputOutputComponent", async ({ page }) => {
// Release the mouse // Release the mouse
await page.mouse.up(); await page.mouse.up();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("text output"); await page.getByTestId("sidebar-search-input").fill("text output");
await page await page
.getByTestId("outputsText Output") .getByTestId("outputsText Output")

View file

@ -30,13 +30,9 @@ test("should be able to see output preview from grouped components and connect c
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("text input");
await page.getByPlaceholder("Search").fill("text input");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -61,9 +57,8 @@ test("should be able to see output preview from grouped components and connect c
.getByTestId("inputsText Input") .getByTestId("inputsText Input")
.dragTo(page.locator('//*[@id="react-flow-id"]')); .dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("combine text");
await page.getByPlaceholder("Search").fill("combine text");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -107,9 +102,8 @@ test("should be able to see output preview from grouped components and connect c
await page.mouse.up(); await page.mouse.up();
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("text output");
await page.getByPlaceholder("Search").fill("text output");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -38,9 +38,8 @@ test("memory should work as expect", async ({ page }) => {
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("chat memory");
await page.getByPlaceholder("Search").fill("chat memory");
// Locate the canvas element // Locate the canvas element
const canvas = page.locator("#react-flow-id"); // Update the selector if needed const canvas = page.locator("#react-flow-id"); // Update the selector if needed

View file

@ -32,12 +32,8 @@ test("chat_io_teste", async ({ page }) => {
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("chat output");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("chat output");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page
@ -46,8 +42,8 @@ test("chat_io_teste", async ({ page }) => {
await page.mouse.up(); await page.mouse.up();
await page.mouse.down(); await page.mouse.down();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("chat input"); await page.getByTestId("sidebar-search-input").fill("chat input");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -31,12 +31,8 @@ test("CodeAreaModalComponent", async ({ page }) => {
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("python function");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("python function");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -30,13 +30,9 @@ test("dropDownComponent", async ({ page }) => {
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("amazon");
await page.getByPlaceholder("Search").fill("amazon");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -30,13 +30,9 @@ test("should be able to upload a file", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', {
timeout: 30000,
});
await page.getByTestId("extended-disclosure").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").fill("file");
await page.getByPlaceholder("Search").fill("file");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
@ -58,8 +54,8 @@ test("should be able to upload a file", async ({ page }) => {
); );
await page.getByText("test_file.txt").isVisible(); await page.getByText("test_file.txt").isVisible();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("text output"); await page.getByTestId("sidebar-search-input").fill("text output");
await page await page
.getByTestId("outputsText Output") .getByTestId("outputsText Output")
@ -72,8 +68,8 @@ test("should be able to upload a file", async ({ page }) => {
await page.getByTestId("zoom_out").click(); await page.getByTestId("zoom_out").click();
await page.getByTestId("zoom_out").click(); await page.getByTestId("zoom_out").click();
await page.getByPlaceholder("Search").click(); await page.getByTestId("sidebar-search-input").click();
await page.getByPlaceholder("Search").fill("parse data"); await page.getByTestId("sidebar-search-input").fill("parse data");
await page await page
.getByTestId("helpersParse Data") .getByTestId("helpersParse Data")
.first() .first()

View file

@ -29,12 +29,8 @@ test("FloatComponent", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("ollama");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("ollama");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -29,12 +29,8 @@ test("InputComponent", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("Chroma");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("Chroma");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -21,12 +21,8 @@ test("InputListComponent", async ({ page }) => {
} }
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("url");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("url");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page await page

View file

@ -29,12 +29,8 @@ test("IntComponent", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("openai");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("openai");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

View file

@ -29,12 +29,8 @@ test("KeypairListComponent", async ({ page }) => {
timeout: 30000, timeout: 30000,
}); });
await page.getByTestId("blank-flow").click(); await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="extended-disclosure"]', { await page.getByTestId("sidebar-search-input").click();
timeout: 30000, await page.getByTestId("sidebar-search-input").fill("amazon bedrock");
});
await page.getByTestId("extended-disclosure").click();
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("amazon bedrock");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);

Some files were not shown because too many files have changed in this diff Show more