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

* Added required ShadCN Components

* Added required colors

* Added required icons

* Changed backend to not return beta tag

* Added sidebar categories api call

* Added required use-mobile hook

* Refactored icons to allow sizing

* Added type for sidebar category

* Allowed no name on shortcut display

* Added flow sidebar component that uses ShadCN sidebar

* Added SidebarDraggableComponent with the new style

* Replaced sidebar with current one on FlowPage

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

* Updated sidebar categories

* Updated background color for sidebar

* Changed size of Components title

* Added color to PageComponent

* Added required icons

* added CustomComponent category and removed custom component from helpers

* create hook useAddComponent

* Use hook to add components both in PageComponent and flowSidebarComponent

* Implement search

* Implement searching and filtering by clicking on edge

* Added check to see if store is present

* Updated colors to match new color schema

* Changed styling of filter component

* Added Beta and Legacy badges

* Implement Show Beta Components and Show Legacy Components

* Fixed styling for sidebar config when collapsed

* Refactored search to filter for tags

* Refactor useeffect

* Updated config button styling

* Implemented keyboard navigation

* Fixed filtering

* Updated color of canvas

* Implemented disclosure on sidebar settings and fetched bundles

* Added temp sidebar bundles

* Fixed badge styling

* Added bundles to categories response in frontend

* Added legacy to components

* Added link to store instead of langflow.store

* Added required data-testids

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

* Fix github star bug

* Fixed tests that used the custom component

* Changed test to test beta and legacy checkers

* added a test for keyboard navigation on sidebar

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

* [autofix.ci] apply automated fixes

* updated sidebar switch change

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

* merge fix

* [autofix.ci] apply automated fixes

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

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

* formatting

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

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

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

* [autofix.ci] apply automated fixes

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

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

* format

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

* [autofix.ci] apply automated fixes

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

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

---------

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

View file

@ -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)

View file

@ -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]

View file

@ -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 = [

View file

@ -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",

View file

@ -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:

View file

@ -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:

View file

@ -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",

View file

@ -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, "")

View file

@ -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)

View file

@ -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)

View file

@ -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", ""))

View file

@ -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

View file

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

View file

@ -0,0 +1,25 @@
# from langflow.field_typing import Data
from langflow.custom import Component
from langflow.io import MessageTextInput, Output
from langflow.schema import Data
class CustomComponent(Component):
display_name = "Custom Component"
description = "Use as a template to create your own component."
documentation: str = "http://docs.langflow.org/components/custom"
icon = "code"
name = "CustomComponent"
inputs = [
MessageTextInput(name="input_value", display_name="Input Value", value="Hello, World!"),
]
outputs = [
Output(display_name="Output", name="output", method="build_output"),
]
def build_output(self) -> Data:
data = Data(value=self.input_value)
self.status = data
return data

View file

@ -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",

View file

@ -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"

View file

@ -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 {

View file

@ -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(

View file

@ -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 {

View file

@ -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 {

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