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:
parent
e965fd3b7f
commit
794848d5e9
122 changed files with 2365 additions and 802 deletions
|
|
@ -6,7 +6,16 @@ from typing import TYPE_CHECKING, Annotated
|
|||
from uuid import UUID
|
||||
|
||||
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 sqlmodel import select
|
||||
|
||||
|
|
@ -17,7 +26,6 @@ from langflow.api.v1.schemas import (
|
|||
CustomComponentResponse,
|
||||
InputValueRequest,
|
||||
RunResponse,
|
||||
SidebarCategoriesResponse,
|
||||
SimplifiedAPIRequest,
|
||||
TaskStatusResponse,
|
||||
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.database.models.flow import Flow
|
||||
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.deps import (
|
||||
get_session_service,
|
||||
|
|
@ -47,7 +57,6 @@ from langflow.services.deps import (
|
|||
)
|
||||
from langflow.services.settings.feature_flags import FEATURE_FLAGS
|
||||
from langflow.services.telemetry.schema import RunPayload
|
||||
from langflow.utils.constants import SIDEBAR_CATEGORIES
|
||||
from langflow.utils.version import get_version_info
|
||||
|
||||
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 = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(user_id), flow_name=flow.name)
|
||||
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:
|
||||
outputs = [input_request.output_component]
|
||||
|
|
@ -248,7 +261,10 @@ async def simplified_run_flow(
|
|||
background_tasks.add_task(
|
||||
telemetry_service.log_package_run,
|
||||
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"}
|
||||
|
||||
|
||||
@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(
|
||||
*,
|
||||
session: DbSession,
|
||||
|
|
@ -631,11 +651,9 @@ async def get_config():
|
|||
|
||||
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:
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,14 @@ from pathlib import Path
|
|||
from typing import Any
|
||||
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.utils import serialize_field
|
||||
|
|
@ -316,7 +323,11 @@ class InputValueRequest(BaseModel):
|
|||
},
|
||||
{"components": ["Component Name"], "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"},
|
||||
{"type": "chat", "input_value": "input_value"},
|
||||
{"type": "json", "input_value": '{"key": "value"}'},
|
||||
|
|
@ -357,14 +368,3 @@ class ConfigResponse(BaseModel):
|
|||
auto_saving_interval: int
|
||||
health_check_max_retries: 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]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,15 @@ import orjson
|
|||
|
||||
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 = [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from . import (
|
||||
agents,
|
||||
chains,
|
||||
custom_component,
|
||||
documentloaders,
|
||||
embeddings,
|
||||
helpers,
|
||||
|
|
@ -26,6 +27,7 @@ __all__ = [
|
|||
"helpers",
|
||||
"inputs",
|
||||
"link_extractors",
|
||||
"custom_component",
|
||||
"memories",
|
||||
"models",
|
||||
"output_parsers",
|
||||
|
|
|
|||
|
|
@ -14,11 +14,22 @@ class JsonAgentComponent(LCAgentComponent):
|
|||
display_name = "JsonAgent"
|
||||
description = "Construct a json agent from an LLM and tools."
|
||||
name = "JsonAgent"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
*LCAgentComponent._base_inputs,
|
||||
HandleInput(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),
|
||||
HandleInput(
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -9,11 +9,22 @@ class VectorStoreAgentComponent(LCAgentComponent):
|
|||
display_name = "VectorStoreAgent"
|
||||
description = "Construct an agent from a Vector Store."
|
||||
name = "VectorStoreAgent"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
*LCAgentComponent._base_inputs,
|
||||
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
|
||||
HandleInput(name="vectorstore", display_name="Vector Store", input_types=["VectorStoreInfo"], required=True),
|
||||
HandleInput(
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -9,10 +9,16 @@ class VectorStoreRouterAgentComponent(LCAgentComponent):
|
|||
display_name = "VectorStoreRouterAgent"
|
||||
description = "Construct an agent from a Vector Store Router."
|
||||
name = "VectorStoreRouterAgent"
|
||||
legacy: bool = True
|
||||
|
||||
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(
|
||||
name="vectorstores",
|
||||
display_name="Vector Stores",
|
||||
|
|
|
|||
|
|
@ -9,12 +9,21 @@ class ConversationChainComponent(LCChainComponent):
|
|||
display_name = "ConversationChain"
|
||||
description = "Chain to have a conversation and load context from memory."
|
||||
name = "ConversationChain"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
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(
|
||||
name="memory",
|
||||
display_name="Memory",
|
||||
|
|
@ -28,7 +37,10 @@ class ConversationChainComponent(LCChainComponent):
|
|||
else:
|
||||
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):
|
||||
result = result.get(chain.output_key, "")
|
||||
|
||||
|
|
|
|||
|
|
@ -10,18 +10,28 @@ class LLMCheckerChainComponent(LCChainComponent):
|
|||
description = "Chain for question-answering with self-verification."
|
||||
documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
|
||||
name = "LLMCheckerChain"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
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:
|
||||
chain = LLMCheckerChain.from_llm(llm=self.llm)
|
||||
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 = str(result)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,21 @@ class LLMMathChainComponent(LCChainComponent):
|
|||
description = "Chain that interprets a prompt and executes python code to do math."
|
||||
documentation = "https://python.langchain.com/docs/modules/chains/additional/llm_math"
|
||||
name = "LLMMathChain"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
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")]
|
||||
|
|
@ -24,7 +33,8 @@ class LLMMathChainComponent(LCChainComponent):
|
|||
def invoke_chain(self) -> Message:
|
||||
chain = LLMMathChain.from_llm(llm=self.llm)
|
||||
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 = str(result)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@ class RetrievalQAComponent(LCChainComponent):
|
|||
display_name = "Retrieval QA"
|
||||
description = "Chain for question-answering querying sources from a retriever."
|
||||
name = "RetrievalQA"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
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(
|
||||
name="chain_type",
|
||||
|
|
@ -22,8 +26,18 @@ class RetrievalQAComponent(LCChainComponent):
|
|||
value="Stuff",
|
||||
advanced=True,
|
||||
),
|
||||
HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
|
||||
HandleInput(name="retriever", display_name="Retriever", input_types=["Retriever"], required=True),
|
||||
HandleInput(
|
||||
name="llm",
|
||||
display_name="Language Model",
|
||||
input_types=["LanguageModel"],
|
||||
required=True,
|
||||
),
|
||||
HandleInput(
|
||||
name="retriever",
|
||||
display_name="Retriever",
|
||||
input_types=["Retriever"],
|
||||
required=True,
|
||||
),
|
||||
HandleInput(
|
||||
name="memory",
|
||||
display_name="Memory",
|
||||
|
|
@ -52,7 +66,10 @@ class RetrievalQAComponent(LCChainComponent):
|
|||
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=[]))
|
||||
result_str = str(result.get("result", ""))
|
||||
|
|
|
|||
|
|
@ -16,17 +16,38 @@ class SQLGeneratorComponent(LCChainComponent):
|
|||
display_name = "Natural Language to SQL"
|
||||
description = "Generate SQL from natural language."
|
||||
name = "SQLGenerator"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
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(
|
||||
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")]
|
||||
|
|
@ -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)
|
||||
query_writer: Runnable = sql_query_chain | {"query": lambda x: x.replace("SQLQuery:", "").strip()}
|
||||
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")
|
||||
self.status = query
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
from .custom_component import CustomComponent
|
||||
|
||||
__all__ = [
|
||||
"CustomComponent",
|
||||
]
|
||||
|
|
@ -2,7 +2,6 @@ from .combine_text import CombineTextComponent
|
|||
from .create_list import CreateListComponent
|
||||
from .csv_to_data import CSVToDataComponent
|
||||
from .current_date import CurrentDateComponent
|
||||
from .custom_component import CustomComponent
|
||||
from .data_conditional_router import DataConditionalRouterComponent
|
||||
from .extract_key import ExtractDataKeyComponent
|
||||
from .filter_data import FilterDataComponent
|
||||
|
|
@ -25,7 +24,6 @@ __all__ = [
|
|||
"CombineTextComponent",
|
||||
"CreateListComponent",
|
||||
"CurrentDateComponent",
|
||||
"CustomComponent",
|
||||
"DataConditionalRouterComponent",
|
||||
"DataFilterComponent",
|
||||
"ExtractDataKeyComponent",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class JSONDocumentBuilder(CustomComponent):
|
|||
display_name: str = "JSON Document Builder"
|
||||
description: str = "Build a Document containing a JSON object using a key and another Document page content."
|
||||
name = "JSONDocumentBuilder"
|
||||
legacy: bool = True
|
||||
|
||||
output_types: list[str] = ["Document"]
|
||||
documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class AmazonKendraRetrieverComponent(CustomComponent):
|
|||
description: str = "Retriever that uses the Amazon Kendra API."
|
||||
name = "AmazonKendra"
|
||||
icon = "Amazon"
|
||||
legacy: bool = True
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ from typing import cast
|
|||
from langchain.retrievers import ContextualCompressionRetriever
|
||||
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.io import (
|
||||
DropdownInput,
|
||||
|
|
@ -22,6 +25,7 @@ class CohereRerankComponent(LCVectorStoreComponent):
|
|||
description = "Rerank documents using the Cohere API and a retriever."
|
||||
name = "CohereRerank"
|
||||
icon = "Cohere"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class MetalRetrieverComponent(CustomComponent):
|
|||
display_name: str = "Metal Retriever"
|
||||
description: str = "Retriever that uses the Metal API."
|
||||
name = "MetalRetriever"
|
||||
legacy: bool = True
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class MultiQueryRetrieverComponent(CustomComponent):
|
|||
description = "Initialize from llm using default template."
|
||||
documentation = "https://python.langchain.com/docs/modules/data_connection/retrievers/how_to/MultiQueryRetriever"
|
||||
name = "MultiQueryRetriever"
|
||||
legacy: bool = True
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,18 @@ from typing import Any, cast
|
|||
|
||||
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.io import DropdownInput, HandleInput, MultilineInput, SecretStrInput, StrInput
|
||||
from langflow.io import (
|
||||
DropdownInput,
|
||||
HandleInput,
|
||||
MultilineInput,
|
||||
SecretStrInput,
|
||||
StrInput,
|
||||
)
|
||||
from langflow.schema import Data
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.template.field.base import Output
|
||||
|
|
@ -14,6 +23,7 @@ class NvidiaRerankComponent(LCVectorStoreComponent):
|
|||
display_name = "NVIDIA Rerank"
|
||||
description = "Rerank documents using the NVIDIA API and a retriever."
|
||||
icon = "NVIDIA"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
|
|
@ -28,7 +38,10 @@ class NvidiaRerankComponent(LCVectorStoreComponent):
|
|||
info="The base URL of the NVIDIA API. Defaults to https://integrate.api.nvidia.com/v1.",
|
||||
),
|
||||
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"),
|
||||
HandleInput(name="retriever", display_name="Retriever", input_types=["Retriever"]),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class SelfQueryRetrieverComponent(Component):
|
|||
description = "Retriever that uses a vector store and an LLM to generate the vector store queries."
|
||||
name = "SelfQueryRetriever"
|
||||
icon = "LangChain"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
HandleInput(
|
||||
|
|
@ -48,7 +49,11 @@ class SelfQueryRetrieverComponent(Component):
|
|||
]
|
||||
|
||||
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]:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class VectoStoreRetrieverComponent(CustomComponent):
|
|||
display_name = "VectorStore Retriever"
|
||||
description = "A vector store retriever"
|
||||
name = "VectorStoreRetriever"
|
||||
legacy: bool = True
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class VectorStoreInfoComponent(Component):
|
|||
display_name = "VectorStoreInfo"
|
||||
description = "Information about a VectorStore"
|
||||
name = "VectorStoreInfo"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(
|
||||
|
|
@ -41,5 +42,7 @@ class VectorStoreInfoComponent(Component):
|
|||
"description": self.vectorstore_description,
|
||||
}
|
||||
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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@ from pydantic.v1.fields import Undefined
|
|||
from typing_extensions import override
|
||||
|
||||
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.schema import Data
|
||||
from langflow.schema.dotdict import dotdict
|
||||
|
|
@ -36,6 +43,7 @@ class PythonCodeStructuredTool(LCToolComponent):
|
|||
name = "PythonCodeStructuredTool"
|
||||
icon = "🐍"
|
||||
field_order = ["name", "description", "tool_code", "return_direct", "tool_function"]
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
|
|
@ -47,7 +55,12 @@ class PythonCodeStructuredTool(LCToolComponent):
|
|||
real_time_refresh=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(
|
||||
name="tool_description",
|
||||
display_name="Description",
|
||||
|
|
@ -192,7 +205,10 @@ class PythonCodeStructuredTool(LCToolComponent):
|
|||
schema_annotation = Any
|
||||
schema_fields[field_name] = (
|
||||
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:
|
||||
|
|
@ -214,7 +230,9 @@ class PythonCodeStructuredTool(LCToolComponent):
|
|||
"""This function is called after the code validation is done."""
|
||||
frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)
|
||||
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)
|
||||
for key in frontend_node["template"]:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class SearXNGToolComponent(LCToolComponent):
|
|||
display_name = "SearXNG Search Tool"
|
||||
description = "A component that searches for tools using SearXNG."
|
||||
name = "SearXNGTool"
|
||||
legacy: bool = True
|
||||
|
||||
inputs = [
|
||||
MessageTextInput(
|
||||
|
|
@ -127,7 +128,10 @@ class SearXNGToolComponent(LCToolComponent):
|
|||
|
||||
schema_fields = {
|
||||
"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)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ ATTR_FUNC_MAPPING: dict[str, Callable] = {
|
|||
"display_name": getattr_return_str,
|
||||
"description": getattr_return_str,
|
||||
"beta": getattr_return_bool,
|
||||
"legacy": getattr_return_bool,
|
||||
"documentation": getattr_return_str,
|
||||
"icon": validate_icon,
|
||||
"frozen": getattr_return_bool,
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.16",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -380,6 +381,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.16",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -589,6 +591,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.16",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -1203,6 +1206,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "calculator",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.16",
|
||||
"metadata": {},
|
||||
"official": false,
|
||||
|
|
@ -1322,6 +1326,7 @@
|
|||
"code"
|
||||
],
|
||||
"frozen": false,
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -324,6 +325,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -445,6 +447,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -647,6 +650,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "layout-template",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -288,6 +289,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "braces",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -425,6 +427,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -667,6 +670,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -869,6 +873,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -629,6 +629,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -876,6 +877,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1202,6 +1204,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1388,6 +1391,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1528,6 +1532,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1783,6 +1788,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2043,6 +2049,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2373,6 +2380,7 @@
|
|||
"is_composition": null,
|
||||
"is_input": null,
|
||||
"is_output": null,
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"name": "",
|
||||
"output_types": [],
|
||||
|
|
@ -2488,6 +2496,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2814,6 +2823,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -3264,6 +3274,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -3738,6 +3749,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -4070,6 +4082,7 @@
|
|||
"is_composition": null,
|
||||
"is_input": null,
|
||||
"is_output": null,
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"name": "",
|
||||
"output_types": [],
|
||||
|
|
@ -4237,6 +4250,7 @@
|
|||
"search_params"
|
||||
],
|
||||
"frozen": false,
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -306,6 +307,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -523,6 +525,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -725,6 +728,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1048,6 +1052,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "braces",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1181,6 +1186,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "file-text",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -576,6 +577,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -906,6 +908,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1097,6 +1100,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1242,6 +1246,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1501,6 +1506,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1763,6 +1769,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2097,6 +2104,7 @@
|
|||
"is_composition": null,
|
||||
"is_input": null,
|
||||
"is_output": null,
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"name": "",
|
||||
"output_types": [],
|
||||
|
|
@ -2216,6 +2224,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2436,6 +2445,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2687,6 +2697,7 @@
|
|||
"search_params"
|
||||
],
|
||||
"frozen": false,
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -280,6 +281,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -504,6 +506,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -830,6 +833,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1023,6 +1027,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "message-square-more",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -389,6 +389,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -588,6 +589,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -915,6 +917,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -1199,6 +1202,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -1320,6 +1324,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -1441,6 +1446,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -1569,6 +1575,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -1920,6 +1927,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -2271,6 +2279,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "CrewAI",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
|
|||
|
|
@ -398,6 +398,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -620,6 +621,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
|
|
@ -829,6 +831,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1442,6 +1445,7 @@
|
|||
"search_params"
|
||||
],
|
||||
"frozen": false,
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"official": false,
|
||||
|
|
@ -2319,6 +2323,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "calculator",
|
||||
"legacy": false,
|
||||
"lf_version": "1.0.15",
|
||||
"metadata": {},
|
||||
"official": false,
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -524,6 +525,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "AstraDB",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1020,6 +1022,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "braces",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1157,6 +1160,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "prompts",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1302,6 +1306,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "MessagesSquare",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1494,6 +1499,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "scissors-line-dashed",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1639,6 +1645,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "file-text",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -1788,6 +1795,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "AstraDB",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2302,6 +2310,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -2778,6 +2787,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
@ -3246,6 +3256,7 @@
|
|||
],
|
||||
"frozen": false,
|
||||
"icon": "OpenAI",
|
||||
"legacy": false,
|
||||
"metadata": {},
|
||||
"output_types": [],
|
||||
"outputs": [
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ class FrontendNode(BaseModel):
|
|||
"""Order of the fields in the frontend node."""
|
||||
beta: bool = False
|
||||
"""Whether the frontend node is in beta."""
|
||||
legacy: bool = False
|
||||
"""Whether the frontend node is legacy."""
|
||||
error: str | None = None
|
||||
"""Error message for the frontend node."""
|
||||
edited: bool = False
|
||||
|
|
@ -81,7 +83,10 @@ class FrontendNode(BaseModel):
|
|||
if "output_types" in result and not result.get("outputs"):
|
||||
for base_class in result["output_types"]:
|
||||
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())
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class CustomComponentFrontendNode(FrontendNode):
|
|||
name: str = "CustomComponent"
|
||||
display_name: str | None = "CustomComponent"
|
||||
beta: bool = False
|
||||
legacy: bool = False
|
||||
template: Template = Template(
|
||||
type_name="CustomComponent",
|
||||
fields=[
|
||||
|
|
@ -72,6 +73,7 @@ class ComponentFrontendNode(FrontendNode):
|
|||
name: str = "Component"
|
||||
display_name: str | None = "Component"
|
||||
beta: bool = False
|
||||
legacy: bool = False
|
||||
template: Template = Template(
|
||||
type_name="Component",
|
||||
fields=[
|
||||
|
|
|
|||
|
|
@ -52,7 +52,18 @@ def python_function(text: str) -> str:
|
|||
|
||||
|
||||
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]] = [
|
||||
|
|
@ -175,29 +186,3 @@ MESSAGE_SENDER_NAME_AI = "AI"
|
|||
MESSAGE_SENDER_NAME_USER = "User"
|
||||
|
||||
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"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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 "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'"
|
||||
|
||||
|
||||
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"
|
||||
|
|
|
|||
3
src/frontend/package-lock.json
generated
3
src/frontend/package-lock.json
generated
|
|
@ -14,7 +14,8 @@
|
|||
"@million/lint": "^1.0.0-rc.26",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@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-form": "^0.0.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
"@million/lint": "^1.0.0-rc.26",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@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-form": "^0.0.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const GithubStarComponent = () => {
|
|||
<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="header-github-display text-black dark:text-[white]">
|
||||
{stars.toLocaleString() ?? 0}
|
||||
{stars?.toLocaleString() ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ const badgeVariants = cva(
|
|||
destructive:
|
||||
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
|
||||
outline: "text-primary/80 border-ring/60",
|
||||
secondaryStatic: "bg-input text-muted-foreground",
|
||||
pinkStatic: "bg-accent-pink text-accent-pink-foreground",
|
||||
},
|
||||
size: {
|
||||
sm: "h-4 text-xs",
|
||||
md: "h-5 text-sm",
|
||||
lg: "h-6 text-base",
|
||||
sq: "h-6 text-sm font-normal rounded-md",
|
||||
xq: "h-5 text-xs font-normal rounded-md",
|
||||
sq: "h-6 px-1.5 text-sm font-medium rounded-md",
|
||||
xq: "h-5 px-1 text-xs font-medium rounded-md",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ const buttonVariants = cva(
|
|||
"border bg-background text-secondary-foreground hover:bg-muted dark:hover:bg-muted hover:shadow-sm",
|
||||
secondary:
|
||||
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
menu: "hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0",
|
||||
ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
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":
|
||||
"font-semibold hover:bg-muted hover:text-accent-foreground focus-visible:!ring-offset-0",
|
||||
link: "underline-offset-4 hover:underline text-primary",
|
||||
|
|
@ -29,7 +31,9 @@ const buttonVariants = cva(
|
|||
sm: "h-9 px-3 rounded-md",
|
||||
xs: "py-0.5 px-3 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",
|
||||
},
|
||||
},
|
||||
|
|
|
|||
11
src/frontend/src/components/ui/collapsible.tsx
Normal file
11
src/frontend/src/components/ui/collapsible.tsx
Normal 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 };
|
||||
191
src/frontend/src/components/ui/disclosure.tsx
Normal file
191
src/frontend/src/components/ui/disclosure.tsx
Normal 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,
|
||||
};
|
||||
|
|
@ -65,7 +65,7 @@ const SheetContent = React.forwardRef<
|
|||
{...props}
|
||||
>
|
||||
{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" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ import { VariantProps, cva } from "class-variance-authority";
|
|||
import { PanelLeft } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { useIsMobile } from "../../hooks/use-mobile";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { Button } from "./button";
|
||||
import { Input } from "./input";
|
||||
import { Separator } from "./separator";
|
||||
import { Sheet, SheetContent } from "./sheet";
|
||||
import { Skeleton } from "./skeleton";
|
||||
import {
|
||||
Tooltip,
|
||||
|
|
@ -20,13 +22,17 @@ import {
|
|||
const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||
const SIDEBAR_WIDTH = "19rem";
|
||||
const SIDEBAR_WIDTH_ICON = "4rem";
|
||||
const SIDEBAR_WIDTH_MOBILE = "18rem";
|
||||
const SIDEBAR_WIDTH_ICON = "3rem";
|
||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
||||
|
||||
type SidebarContext = {
|
||||
state: "expanded" | "collapsed";
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
openMobile: boolean;
|
||||
setOpenMobile: (open: boolean) => void;
|
||||
isMobile: boolean;
|
||||
toggleSidebar: () => void;
|
||||
};
|
||||
|
||||
|
|
@ -47,7 +53,6 @@ const SidebarProvider = React.forwardRef<
|
|||
defaultOpen?: boolean;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
width?: string;
|
||||
}
|
||||
>(
|
||||
(
|
||||
|
|
@ -58,11 +63,13 @@ const SidebarProvider = React.forwardRef<
|
|||
className,
|
||||
style,
|
||||
children,
|
||||
width = SIDEBAR_WIDTH,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const isMobile = useIsMobile();
|
||||
const [openMobile, setOpenMobile] = React.useState(false);
|
||||
|
||||
// This is the internal state of the sidebar.
|
||||
// We use openProp and setOpenProp for control from outside the component.
|
||||
const [_open, _setOpen] = React.useState(defaultOpen);
|
||||
|
|
@ -85,8 +92,10 @@ const SidebarProvider = React.forwardRef<
|
|||
|
||||
// Helper to toggle the sidebar.
|
||||
const toggleSidebar = React.useCallback(() => {
|
||||
return setOpen((open) => !open);
|
||||
}, [setOpen]);
|
||||
return isMobile
|
||||
? setOpenMobile((open) => !open)
|
||||
: setOpen((open) => !open);
|
||||
}, [isMobile, setOpen, setOpenMobile]);
|
||||
|
||||
// Adds a keyboard shortcut to toggle the sidebar.
|
||||
React.useEffect(() => {
|
||||
|
|
@ -113,9 +122,20 @@ const SidebarProvider = React.forwardRef<
|
|||
state,
|
||||
open,
|
||||
setOpen,
|
||||
isMobile,
|
||||
openMobile,
|
||||
setOpenMobile,
|
||||
toggleSidebar,
|
||||
}),
|
||||
[state, open, setOpen, toggleSidebar],
|
||||
[
|
||||
state,
|
||||
open,
|
||||
setOpen,
|
||||
isMobile,
|
||||
openMobile,
|
||||
setOpenMobile,
|
||||
toggleSidebar,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -124,7 +144,7 @@ const SidebarProvider = React.forwardRef<
|
|||
<div
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": width,
|
||||
"--sidebar-width": SIDEBAR_WIDTH,
|
||||
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
||||
...style,
|
||||
} as React.CSSProperties
|
||||
|
|
@ -164,33 +184,47 @@ const Sidebar = React.forwardRef<
|
|||
},
|
||||
ref,
|
||||
) => {
|
||||
const { state } = useSidebar();
|
||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
||||
|
||||
if (collapsible === "none") {
|
||||
return (
|
||||
<div
|
||||
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,
|
||||
)}
|
||||
data-side={side}
|
||||
ref={ref}
|
||||
{...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}
|
||||
</div>
|
||||
{children}
|
||||
</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 (
|
||||
<div
|
||||
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-collapsible={state === "collapsed" ? collapsible : ""}
|
||||
data-variant={variant}
|
||||
|
|
@ -205,16 +239,11 @@ const Sidebar = React.forwardRef<
|
|||
variant === "floating" || variant === "inset"
|
||||
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
|
||||
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
|
||||
// Keep icon width on mobile when collapsed
|
||||
"max-sm:w-[--sidebar-width-icon]",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 z-50 flex h-full transition-[left,right,width] duration-200 ease-linear",
|
||||
// Adjust width based on state and device
|
||||
"w-[--sidebar-width]",
|
||||
"max-sm:group-data-[state=collapsed]:w-[--sidebar-width-icon]",
|
||||
"absolute inset-y-0 z-10 hidden h-full w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex",
|
||||
side === "left"
|
||||
? "left-0 group-data-[collapsible=offcanvas]:left-[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"
|
||||
? "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",
|
||||
// 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,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
data-sidebar="sidebar"
|
||||
className={cn(
|
||||
"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",
|
||||
)}
|
||||
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"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
|
@ -258,7 +280,7 @@ const SidebarTrigger = React.forwardRef<
|
|||
data-sidebar="trigger"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn("h-8 w-8", className)}
|
||||
className={cn("h-7 w-7", className)}
|
||||
onClick={(event) => {
|
||||
onClick?.(event);
|
||||
toggleSidebar();
|
||||
|
|
@ -310,7 +332,7 @@ const SidebarInset = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"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,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -328,7 +350,7 @@ const SidebarInput = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="input"
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -426,7 +448,7 @@ const SidebarGroupLabel = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="group-label"
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
|
|
@ -447,9 +469,9 @@ const SidebarGroupAction = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="group-action"
|
||||
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.
|
||||
"after:-inset-2 after:hidden",
|
||||
"after:absolute after:-inset-2 after:md:hidden",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className,
|
||||
)}
|
||||
|
|
@ -499,12 +521,11 @@ const SidebarMenuItem = React.forwardRef<
|
|||
SidebarMenuItem.displayName = "SidebarMenuItem";
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
default:
|
||||
"text-secondary-foreground hover:bg-accent hover:text-accent-foreground ",
|
||||
default: "hover:bg-accent hover:text-accent-foreground",
|
||||
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))]",
|
||||
},
|
||||
|
|
@ -542,7 +563,7 @@ const SidebarMenuButton = React.forwardRef<
|
|||
ref,
|
||||
) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
const { state } = useSidebar();
|
||||
const { isMobile, state } = useSidebar();
|
||||
|
||||
const button = (
|
||||
<Comp
|
||||
|
|
@ -571,7 +592,7 @@ const SidebarMenuButton = React.forwardRef<
|
|||
<TooltipContent
|
||||
side="right"
|
||||
align="center"
|
||||
hidden={state !== "collapsed"}
|
||||
hidden={state !== "collapsed" || isMobile}
|
||||
{...tooltip}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
|
@ -594,9 +615,9 @@ const SidebarMenuAction = React.forwardRef<
|
|||
ref={ref}
|
||||
data-sidebar="menu-action"
|
||||
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.
|
||||
"after:-inset-2 after:hidden",
|
||||
"after:absolute after:-inset-2 after:md:hidden",
|
||||
"peer-data-[size=sm]/menu-button:top-1",
|
||||
"peer-data-[size=default]/menu-button:top-1.5",
|
||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||
|
|
@ -710,7 +731,7 @@ const SidebarMenuSubButton = React.forwardRef<
|
|||
data-size={size}
|
||||
data-active={isActive}
|
||||
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",
|
||||
size === "sm" && "text-xs",
|
||||
size === "md" && "text-sm",
|
||||
|
|
|
|||
|
|
@ -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) * 0.1;
|
||||
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 SHORTCUT_KEYS = ["cmd", "ctrl", "alt", "shift"];
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export const URLs = {
|
|||
VALIDATE: `validate`,
|
||||
CONFIG: `config`,
|
||||
STARTER_PROJECTS: `starter-projects`,
|
||||
SIDEBAR_CATEGORIES: `sidebar_categories`,
|
||||
} as const;
|
||||
|
||||
export function getURL(key: keyof typeof URLs, params: any = {}) {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import * as React from "react";
|
||||
|
||||
export function useMobile(breakpoint: number = 768) {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
setIsMobile(window.innerWidth < breakpoint);
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||
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
|
||||
checkMobile();
|
||||
|
||||
// Add event listener
|
||||
window.addEventListener("resize", checkMobile);
|
||||
|
||||
// Cleanup
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, [breakpoint]);
|
||||
|
||||
return isMobile;
|
||||
return !!isMobile;
|
||||
}
|
||||
|
|
|
|||
70
src/frontend/src/hooks/useAddComponent.ts
Normal file
70
src/frontend/src/hooks/useAddComponent.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -9,11 +9,11 @@ const AstraSVG = (props) => (
|
|||
>
|
||||
<path
|
||||
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
|
||||
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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ export const AstraDBIcon = forwardRef<
|
|||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
const isDark = useDarkStore((state) => state.dark);
|
||||
return <AstraSVG ref={ref} isDark={isDark} {...props} />;
|
||||
const isdark = useDarkStore((state) => state.dark);
|
||||
return <AstraSVG ref={ref} isdark={isdark} {...props} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const SvgAzure = (props) => (
|
|||
height="35"
|
||||
version="1"
|
||||
viewBox="0 0 750 750"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<filter id="ac4759936b" width="100%" height="100%" x="0%" y="0%">
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
const SvgGroqLogo = ({ color, ...props }) => (
|
||||
const SvgGroqLogo = ({ ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="groq logo"
|
||||
|
|
@ -7,7 +7,8 @@ const SvgGroqLogo = ({ color, ...props }) => (
|
|||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
class="fill-foreground"
|
||||
className="fill-foreground"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="#F55036"
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ const HCDSVG = (props) => (
|
|||
{/* <rect width="96" height="96" rx="6" fill="white"/> */}
|
||||
<path
|
||||
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
|
||||
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>
|
||||
<defs>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import HCDSVG from "./HCD";
|
|||
|
||||
export const HCDIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||
(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} />;
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export const SvgOllama = (props) => (
|
||||
<svg
|
||||
width="646"
|
||||
height="854"
|
||||
viewBox="0 0 646 854"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="40"
|
||||
height="40"
|
||||
version="1"
|
||||
viewBox="0 0 550 850"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const SvgMistralIcon = (props) => (
|
|||
viewBox="0 0 256 233"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
{...props}
|
||||
>
|
||||
<title>Mistral AI</title>
|
||||
<g>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import {
|
|||
SidebarMenuItem,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useMobile } from "@/hooks/use-mobile";
|
||||
import { useIsMobile } from "../../../../hooks/use-mobile";
|
||||
|
||||
import { cn } from "@/utils/utils";
|
||||
import { NavProps } from "../../../../types/templates/types";
|
||||
|
||||
export function Nav({ categories, currentTab, setCurrentTab }: NavProps) {
|
||||
const isMobile = useMobile();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Sidebar collapsible={isMobile ? "icon" : "none"}>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ import { useGetBuildsQuery } from "@/controllers/API/queries/_builds";
|
|||
import { track } from "@/customization/utils/analytics";
|
||||
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
|
||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { nodeColors, nodeColorsName } from "@/utils/styleUtils";
|
||||
import { getNodeRenderType, isSupportedNodeTypes } from "@/utils/utils";
|
||||
import { useAddComponent } from "@/hooks/useAddComponent";
|
||||
import { nodeColorsName } from "@/utils/styleUtils";
|
||||
import { isSupportedNodeTypes } from "@/utils/utils";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import {
|
||||
KeyboardEvent,
|
||||
|
|
@ -120,6 +121,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
|||
const [isAddingNote, setIsAddingNote] = useState(false);
|
||||
const [isHighlightingCursor, setIsHighlightingCursor] = useState(false);
|
||||
|
||||
const addComponent = useAddComponent();
|
||||
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
||||
const { zoom } = useViewport();
|
||||
|
||||
|
|
@ -439,23 +441,10 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
|||
event.dataTransfer.getData(datakey!),
|
||||
);
|
||||
|
||||
track("Component Added", { componentType: data.node?.display_name });
|
||||
|
||||
const newId = getNodeId(data.type);
|
||||
|
||||
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 },
|
||||
);
|
||||
addComponent(data.node!, data.type, {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
||||
takeSnapshot();
|
||||
const position = {
|
||||
|
|
@ -478,8 +467,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
|||
});
|
||||
}
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
[getNodeId, setNodes, takeSnapshot, paste],
|
||||
[takeSnapshot, addComponent],
|
||||
);
|
||||
|
||||
const onEdgeUpdateStart = useCallback(() => {
|
||||
|
|
@ -596,7 +584,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="h-full w-full" ref={reactFlowWrapper}>
|
||||
<div className="h-full w-full bg-canvas" ref={reactFlowWrapper}>
|
||||
{showCanvas ? (
|
||||
<div id="react-flow-id" className="h-full w-full bg-canvas">
|
||||
<ReactFlow
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function SidebarFilterComponent({
|
|||
resetFilters: () => void;
|
||||
}) {
|
||||
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">
|
||||
<ForwardedIconComponent
|
||||
name="ListFilter"
|
||||
|
|
@ -30,7 +30,12 @@ export function SidebarFilterComponent({
|
|||
styleClasses="max-w-full"
|
||||
content="Remove filter"
|
||||
>
|
||||
<Button unstyled className="shrink-0" onClick={resetFilters}>
|
||||
<Button
|
||||
unstyled
|
||||
className="shrink-0"
|
||||
onClick={resetFilters}
|
||||
data-testid="sidebar-filter-reset"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="X"
|
||||
className="h-4 w-4 stroke-2"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,21 +1,40 @@
|
|||
import RenderIcons from "@/components/renderIconComponent";
|
||||
import { cn } from "@/utils/utils";
|
||||
|
||||
export default function ShortcutDisplay({
|
||||
name,
|
||||
shortcut,
|
||||
sidebar = false,
|
||||
}: {
|
||||
name: string;
|
||||
name?: string;
|
||||
shortcut: string;
|
||||
sidebar?: boolean;
|
||||
}): JSX.Element {
|
||||
const fixedShortcut = shortcut?.split("+");
|
||||
return (
|
||||
<div className="flex content-center items-center justify-center self-center text-[12px]">
|
||||
<span> {name} </span>
|
||||
<span
|
||||
className={`ml-3 flex items-center rounded-sm bg-primary-hover px-1.5 py-[0.1em] text-muted`}
|
||||
>
|
||||
<RenderIcons filteredShortcut={fixedShortcut} />
|
||||
</span>
|
||||
</div>
|
||||
<>
|
||||
{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]">
|
||||
<span> {name} </span>
|
||||
<span
|
||||
className={`ml-3 flex items-center rounded-sm bg-primary-hover px-1.5 py-[0.1em] text-muted`}
|
||||
>
|
||||
<RenderIcons filteredShortcut={fixedShortcut} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { useGetFlow } from "@/controllers/API/queries/flows/use-get-flow";
|
||||
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 useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||
import { SaveChangesModal } from "@/modals/saveChangesModal";
|
||||
|
|
@ -14,7 +14,7 @@ import { useDarkStore } from "../../stores/darkStore";
|
|||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import Page from "./components/PageComponent";
|
||||
import ExtraSidebar from "./components/extraSidebarComponent";
|
||||
import { FlowSidebarComponent } from "./components/flowSidebarComponent";
|
||||
|
||||
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||
|
|
@ -165,16 +165,17 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
|||
<div className="flow-page-positioning">
|
||||
{currentFlow && (
|
||||
<div className="flex h-full overflow-hidden">
|
||||
{!view && <ExtraSidebar />}
|
||||
<main className="flex flex-1">
|
||||
{/* Primary column */}
|
||||
<div className="h-full w-full">
|
||||
<Page />
|
||||
</div>
|
||||
</main>
|
||||
<SidebarProvider>
|
||||
{!view && <FlowSidebarComponent />}
|
||||
<main className="flex flex-1">
|
||||
<div className="h-full w-full">
|
||||
<Page />
|
||||
</div>
|
||||
</main>
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
)}
|
||||
{ENABLE_BRANDING && version && (
|
||||
{/* {ENABLE_BRANDING && version && (
|
||||
<a
|
||||
target={"_blank"}
|
||||
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>
|
||||
</a>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
{blocker.state === "blocked" && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
templatesGenerator,
|
||||
typesGenerator,
|
||||
} from "../utils/reactflowUtils";
|
||||
import useAlertStore from "./alertStore";
|
||||
import useFlowsManagerStore from "./flowsManagerStore";
|
||||
|
||||
export const useTypesStore = create<TypesStoreType>((set, get) => ({
|
||||
|
|
|
|||
|
|
@ -260,8 +260,7 @@ pre {
|
|||
-webkit-appearance: none;
|
||||
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>");
|
||||
background-size: 16px 16px;
|
||||
height: 16px;
|
||||
opacity: 1 !important;
|
||||
width: 16px;
|
||||
background-size: 20px 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,6 +248,14 @@
|
|||
--chat-bot-icon: #235d70;
|
||||
--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%;
|
||||
--accent-emerald-foreground: 161.4 93.5% 30.4%;
|
||||
--placeholder: 240 5% 64.9%;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export type APIClassType = {
|
|||
output_types?: Array<string>;
|
||||
custom_fields?: CustomFieldsType;
|
||||
beta?: boolean;
|
||||
legacy?: boolean;
|
||||
documentation: string;
|
||||
error?: string;
|
||||
official?: boolean;
|
||||
|
|
|
|||
5
src/frontend/src/types/sidebar/index.ts
Normal file
5
src/frontend/src/types/sidebar/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export interface SidebarCategory {
|
||||
display_name: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
|
@ -84,6 +84,7 @@ import {
|
|||
GitFork,
|
||||
GithubIcon,
|
||||
Globe,
|
||||
GripVertical,
|
||||
Group,
|
||||
Hammer,
|
||||
Heart,
|
||||
|
|
@ -295,7 +296,7 @@ export const gradients = [
|
|||
"bg-gradient-to-br from-lime-600 via-yellow-300 to-red-600",
|
||||
];
|
||||
|
||||
/*
|
||||
/*
|
||||
Specifications
|
||||
#FF3276 -> #F480FF
|
||||
#1A0250 -> #2F10FE
|
||||
|
|
@ -416,41 +417,49 @@ export const nodeColorsName: { [char: string]: string } = {
|
|||
BaseChatMemory: "cyan",
|
||||
};
|
||||
|
||||
export const nodeNames: { [char: string]: string } = {
|
||||
inputs: "Inputs",
|
||||
outputs: "Outputs",
|
||||
data: "Data",
|
||||
prompts: "Prompts",
|
||||
models: "Models",
|
||||
notion: "Notion",
|
||||
Notion: "Notion",
|
||||
AssemblyAI: "AssemblyAI",
|
||||
assemblyai: "AssemblyAI",
|
||||
model_specs: "Model Specs",
|
||||
chains: "Chains",
|
||||
agents: "Agents",
|
||||
tools: "Tools",
|
||||
memories: "Memories",
|
||||
saved_components: "Saved",
|
||||
advanced: "Advanced",
|
||||
chat: "Chat",
|
||||
embeddings: "Embeddings",
|
||||
documentloaders: "Loaders",
|
||||
vectorstores: "Vector Stores",
|
||||
vectorsearch: "Vector Search",
|
||||
toolkits: "Toolkits",
|
||||
wrappers: "Wrappers",
|
||||
textsplitters: "Text Splitters",
|
||||
retrievers: "Retrievers",
|
||||
helpers: "Helpers",
|
||||
prototypes: "Prototypes",
|
||||
astra_assistants: "Astra Assistants",
|
||||
langchain_utilities: "Utilities",
|
||||
output_parsers: "Output Parsers",
|
||||
custom_components: "Custom",
|
||||
link_extractors: "Link Extractors",
|
||||
unknown: "Other",
|
||||
};
|
||||
export const 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: "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" },
|
||||
];
|
||||
|
||||
export const SIDEBAR_BUNDLES = [
|
||||
{ display_name: "CrewAI", name: "crewai", icon: "CrewAi" },
|
||||
{ display_name: "LangChain", name: "langchain_utilities", icon: "LangChain" },
|
||||
{ display_name: "AssemblyAI", name: "assemblyai", icon: "AssemblyAI" },
|
||||
{
|
||||
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 = {
|
||||
saved_components: GradientSave,
|
||||
|
|
@ -669,6 +678,16 @@ export const nodeIconsLucide: iconsType = {
|
|||
Blocks,
|
||||
ChevronDown,
|
||||
ArrowLeft,
|
||||
BrainCircuit,
|
||||
Wand2,
|
||||
Layers,
|
||||
Binary,
|
||||
Paperclip,
|
||||
PocketKnife,
|
||||
Scissors,
|
||||
Cpu,
|
||||
Hammer,
|
||||
GradientSave,
|
||||
Shield,
|
||||
Plus,
|
||||
Redo,
|
||||
|
|
@ -743,6 +762,10 @@ export const nodeIconsLucide: iconsType = {
|
|||
Loader2,
|
||||
BookmarkPlus,
|
||||
Heart,
|
||||
Package2,
|
||||
FileSearch,
|
||||
Compass,
|
||||
Link2,
|
||||
Pin,
|
||||
Link,
|
||||
ToyBrick,
|
||||
|
|
@ -768,6 +791,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
note: StickyNote,
|
||||
RotateCcw,
|
||||
Wrench,
|
||||
GripVertical,
|
||||
FolderPlusIcon,
|
||||
PaperclipIcon,
|
||||
Settings,
|
||||
|
|
|
|||
73
src/frontend/tests/core/features/componentHoverAdd.spec.ts
Normal file
73
src/frontend/tests/core/features/componentHoverAdd.spec.ts
Normal 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();
|
||||
});
|
||||
|
|
@ -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.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("retrievalqa");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("retrievalqa");
|
||||
|
||||
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 page.waitForTimeout(1000);
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -32,13 +32,8 @@ test("user must see on handle click the possibility connections - LLMChain", asy
|
|||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("api request");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("api request");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
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-helpers")).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-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("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("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("vectorstoresAstra DB")).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("retrieversSelf Query Retriever"),
|
||||
).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -30,9 +30,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
|
|||
|
||||
//first component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text input");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -52,9 +51,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
|
|||
|
||||
//second component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("url");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("url");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -74,9 +72,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
|
|||
|
||||
//third component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("split text");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("split text");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -96,9 +93,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
|
|||
|
||||
//fourth component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("parse data");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("parse data");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -118,9 +114,8 @@ test.skip("user must be able to freeze a component", async ({ page }) => {
|
|||
|
||||
//fifth component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat output");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -31,12 +31,8 @@ test("user must be able to save or delete a global variable", async ({
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("openai");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("openai");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -38,12 +38,9 @@ test("fresh start playground", async ({ page }) => {
|
|||
});
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat output");
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat output");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -52,8 +49,8 @@ test("fresh start playground", async ({ page }) => {
|
|||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat input");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -103,12 +103,8 @@ test.describe("save component tests", () => {
|
|||
if (replaceButton) {
|
||||
await page.getByTestId("replace-button").click();
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("group");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("group");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -33,9 +33,8 @@ test("user must be able to stop a building", async ({ page }) => {
|
|||
|
||||
//first component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text input");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -55,9 +54,8 @@ test("user must be able to stop a building", async ({ page }) => {
|
|||
|
||||
//second component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("url");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("url");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -77,9 +75,8 @@ test("user must be able to stop a building", async ({ page }) => {
|
|||
|
||||
//third component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("split text");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("split text");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -99,9 +96,8 @@ test("user must be able to stop a building", async ({ page }) => {
|
|||
|
||||
//fourth component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("parse data");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("parse data");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -121,9 +117,8 @@ test("user must be able to stop a building", async ({ page }) => {
|
|||
|
||||
//fifth component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat output");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -248,17 +243,7 @@ class CustomComponent(Component):
|
|||
return data
|
||||
`;
|
||||
|
||||
await page.getByTestId("extended-disclosure").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("sidebar-custom-component-button").click();
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.getByTestId("zoom_out").click();
|
||||
|
||||
|
|
|
|||
|
|
@ -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.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("Chroma");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("Chroma");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,83 +1,150 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import { Page, test } from "@playwright/test";
|
||||
import * as dotenv from "dotenv";
|
||||
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(
|
||||
page: any,
|
||||
page: Page,
|
||||
elementId: string,
|
||||
moveX: number,
|
||||
nth: number,
|
||||
) {
|
||||
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
|
||||
await element.hover();
|
||||
try {
|
||||
const element = await waitForElement(page, elementId, nth);
|
||||
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 startY = boundingBox.y + boundingBox.height / 2;
|
||||
|
||||
// Break down mouse movements into smaller steps for more reliability
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.waitForTimeout(50);
|
||||
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();
|
||||
} else {
|
||||
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
|
||||
await page.waitForTimeout(100);
|
||||
} 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(
|
||||
page: any,
|
||||
page: Page,
|
||||
elementId: string,
|
||||
moveY: number,
|
||||
nth: number,
|
||||
) {
|
||||
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
|
||||
await element.hover();
|
||||
try {
|
||||
const element = await waitForElement(page, elementId, nth);
|
||||
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 startY = boundingBox.y + boundingBox.height / 2;
|
||||
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.waitForTimeout(100);
|
||||
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();
|
||||
} else {
|
||||
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
|
||||
await page.waitForTimeout(100);
|
||||
} 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(
|
||||
page: any,
|
||||
page: Page,
|
||||
elementId: string,
|
||||
moveX: number,
|
||||
moveY: number,
|
||||
nth: number,
|
||||
) {
|
||||
const element = await page.getByTestId(`title-${elementId}`).nth(nth);
|
||||
await element.hover();
|
||||
try {
|
||||
const element = await waitForElement(page, elementId, nth);
|
||||
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 startY = boundingBox.y + boundingBox.height / 2;
|
||||
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.waitForTimeout(100);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(startX + moveX, startY + moveY);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Move in smaller increments
|
||||
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();
|
||||
} else {
|
||||
throw new Error(`Unable to get bounding box for the element: ${elementId}`);
|
||||
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.skip(
|
||||
!process?.env?.OPENAI_API_KEY,
|
||||
|
|
@ -111,21 +178,17 @@ test("should create a flow with decision", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
//---------------------------------- CHAT INPUT
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat input");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.getByTestId("inputsChat Input")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
|
||||
//---------------------------------- CREATE LIST
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("list");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("list");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.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..");
|
||||
|
||||
//---------------------------------- PARSE DATA
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("parse data");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("parse data");
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page
|
||||
|
|
@ -170,8 +233,8 @@ test("should create a flow with decision", async ({ page }) => {
|
|||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
|
||||
//---------------------------------- PASS
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("pass");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("pass");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.getByTestId("prototypesPass")
|
||||
|
|
@ -185,30 +248,30 @@ test("should create a flow with decision", async ({ page }) => {
|
|||
.getByTestId("prototypesPass")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
//---------------------------------- PROMPT
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("prompt");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("prompt");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.getByTestId("promptsPrompt")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
//---------------------------------- OPENAI
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("openai");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("openai");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.getByTestId("modelsOpenAI")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
|
||||
//---------------------------------- CONDITIONAL ROUTER
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("conditional router");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("conditional router");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.getByTestId("prototypesConditional Router")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
//---------------------------------- CHAT OUTPUT
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat output");
|
||||
await page.waitForTimeout(500);
|
||||
await page
|
||||
.getByTestId("outputsChat Output")
|
||||
|
|
@ -222,32 +285,42 @@ test("should create a flow with decision", async ({ page }) => {
|
|||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await moveElementByX(page, "Chat Output", 500, 1);
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByX(page, "Chat Output", 1000, 0);
|
||||
|
||||
await moveElementByX(page, "Chat Output", 400, 1);
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByX(page, "Conditional Router", 1500, 0);
|
||||
await moveElementByX(page, "Chat Output", 700, 0);
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByX(page, "OpenAI", 2000, 0);
|
||||
await moveElementByX(page, "Conditional Router", 1000, 0);
|
||||
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 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.waitForTimeout(500);
|
||||
await moveElementByXY(page, "Pass", 0, 200, 1);
|
||||
await page.getByTestId("fit_view").click();
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByXY(page, "Pass", 150, 200, 0);
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByXY(page, "Parse Data", 300, 200, 1);
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByXY(page, "Parse Data", 450, 200, 0);
|
||||
await page.waitForTimeout(500);
|
||||
await moveElementByXY(page, "Create List", 600, 200, 1);
|
||||
await page.waitForTimeout(500);
|
||||
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.getByTestId("fit_view").click();
|
||||
|
|
|
|||
|
|
@ -31,9 +31,8 @@ test("user must be able to check similarity between embedding texts", async ({
|
|||
|
||||
//first component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("openai");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("openai");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -70,9 +69,8 @@ test("user must be able to check similarity between embedding texts", async ({
|
|||
|
||||
//third component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text embedder");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text embedder");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -109,9 +107,8 @@ test("user must be able to check similarity between embedding texts", async ({
|
|||
|
||||
//fifth component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("embedding similarity");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("embedding similarity");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -131,9 +128,8 @@ test("user must be able to check similarity between embedding texts", async ({
|
|||
|
||||
//sisxth component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("parse data");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("parse data");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -153,9 +149,8 @@ test("user must be able to check similarity between embedding texts", async ({
|
|||
|
||||
//seventh component
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text output");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -173,9 +168,8 @@ test("user must be able to check similarity between embedding texts", async ({
|
|||
|
||||
await page.mouse.up();
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("filter data");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("filter data");
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -41,19 +41,9 @@ test("TextInputOutputComponent", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
const focusElementsOnBoard = async ({ page }) => {
|
||||
const focusElements = await page.getByTestId("extended-disclosure");
|
||||
focusElements.click();
|
||||
};
|
||||
|
||||
await focusElementsOnBoard({ page });
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text input");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -62,8 +52,8 @@ test("TextInputOutputComponent", async ({ page }) => {
|
|||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("openai");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("openai");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -120,8 +110,8 @@ test("TextInputOutputComponent", async ({ page }) => {
|
|||
// Release the mouse
|
||||
await page.mouse.up();
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text output");
|
||||
|
||||
await page
|
||||
.getByTestId("outputsText Output")
|
||||
|
|
|
|||
|
|
@ -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.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text input");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -61,9 +57,8 @@ test("should be able to see output preview from grouped components and connect c
|
|||
.getByTestId("inputsText Input")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("combine text");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("combine text");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
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.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text output");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -38,9 +38,8 @@ test("memory should work as expect", async ({ page }) => {
|
|||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat memory");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat memory");
|
||||
|
||||
// Locate the canvas element
|
||||
const canvas = page.locator("#react-flow-id"); // Update the selector if needed
|
||||
|
|
|
|||
|
|
@ -32,12 +32,8 @@ test("chat_io_teste", async ({ page }) => {
|
|||
});
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat output");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
@ -46,8 +42,8 @@ test("chat_io_teste", async ({ page }) => {
|
|||
await page.mouse.up();
|
||||
await page.mouse.down();
|
||||
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("chat input");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("chat input");
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -31,12 +31,8 @@ test("CodeAreaModalComponent", async ({ page }) => {
|
|||
});
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("python function");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("python function");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,13 +30,9 @@ test("dropDownComponent", async ({ page }) => {
|
|||
});
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("amazon");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("amazon");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,13 +30,9 @@ test("should be able to upload a file", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("file");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("file");
|
||||
|
||||
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.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("text output");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("text output");
|
||||
|
||||
await page
|
||||
.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.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("parse data");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("parse data");
|
||||
await page
|
||||
.getByTestId("helpersParse Data")
|
||||
.first()
|
||||
|
|
|
|||
|
|
@ -29,12 +29,8 @@ test("FloatComponent", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("ollama");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("ollama");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,8 @@ test("InputComponent", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("Chroma");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("Chroma");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,8 @@ test("InputListComponent", async ({ page }) => {
|
|||
}
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("url");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("url");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page
|
||||
|
|
|
|||
|
|
@ -29,12 +29,8 @@ test("IntComponent", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("openai");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("openai");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,8 @@ test("KeypairListComponent", async ({ page }) => {
|
|||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="extended-disclosure"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("extended-disclosure").click();
|
||||
await page.getByPlaceholder("Search").click();
|
||||
await page.getByPlaceholder("Search").fill("amazon bedrock");
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
await page.getByTestId("sidebar-search-input").fill("amazon bedrock");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue