merge fix dev

This commit is contained in:
Cristhian Zanforlin Lousa 2023-07-03 09:15:40 -03:00
commit b3addfbeee
128 changed files with 4531 additions and 2367 deletions

1
.gitignore vendored
View file

@ -242,3 +242,4 @@ dmypy.json
# Poetry # Poetry
.testenv/* .testenv/*
langflow.db langflow.db
langchain.db

1437
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.2.2" version = "0.2.10"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -22,25 +22,24 @@ include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
langflow = "langflow.__main__:main" langflow = "langflow.__main__:main"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<3.12" python = ">=3.9,<3.11"
fastapi = "^0.98.0" fastapi = "^0.98.0"
uvicorn = "^0.22.0" uvicorn = "^0.22.0"
beautifulsoup4 = "^4.11.2" beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1" google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0" google-api-python-client = "^2.79.0"
typer = "^0.9.0" typer = "^0.9.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
langchain = "^0.0.215" langchain = "^0.0.219"
openai = "^0.27.8" openai = "^0.27.8"
types-pyyaml = "^6.0.12.8" pandas = "^2.0.0"
pandas = "^1.5.3"
chromadb = "^0.3.21" chromadb = "^0.3.21"
huggingface-hub = "^0.13.3" huggingface-hub = "^0.15.0"
rich = "^13.4.2" rich = "^13.4.2"
llama-cpp-python = "~0.1.0" llama-cpp-python = "~0.1.0"
networkx = "^3.1" networkx = "^3.1"
unstructured = "^0.5.11" unstructured = "^0.7.0"
pypdf = "^3.7.1" pypdf = "^3.11.0"
lxml = "^4.9.2" lxml = "^4.9.2"
pysrt = "^1.1.2" pysrt = "^1.1.2"
fake-useragent = "^1.1.3" fake-useragent = "^1.1.3"
@ -49,18 +48,18 @@ psycopg2-binary = "^2.9.6"
pyarrow = "^12.0.0" pyarrow = "^12.0.0"
tiktoken = "~0.4.0" tiktoken = "~0.4.0"
wikipedia = "^1.4.0" wikipedia = "^1.4.0"
langchain-serve = { version = ">0.0.39", optional = true } langchain-serve = { version = ">0.0.47", optional = true }
qdrant-client = "^1.2.0" qdrant-client = "^1.3.0"
websockets = "^10.3" websockets = "^10.3"
weaviate-client = "^3.21.0" weaviate-client = "^3.21.0"
jina = "3.15.2" jina = "3.15.2"
sentence-transformers = "^2.2.2" sentence-transformers = "^2.2.2"
ctransformers = "^0.2.2" ctransformers = "^0.2.10"
cohere = "^4.6.0" cohere = "^4.11.0"
python-multipart = "^0.0.6" python-multipart = "^0.0.6"
sqlmodel = "^0.0.8" sqlmodel = "^0.0.8"
faiss-cpu = "^1.7.4" faiss-cpu = "^1.7.4"
anthropic = "^0.2.10" anthropic = "^0.3.0"
orjson = "^3.9.1" orjson = "^3.9.1"
multiprocess = "^0.70.14" multiprocess = "^0.70.14"
cachetools = "^5.3.1" cachetools = "^5.3.1"
@ -70,7 +69,9 @@ pinecone-client = "^2.2.2"
supabase = "^1.0.3" supabase = "^1.0.3"
pymongo = "^4.4.0" pymongo = "^4.4.0"
certifi = "^2023.5.7" certifi = "^2023.5.7"
google-cloud-aiplatform = "^1.26.1"
psycopg = "^3.1.9"
psycopg-binary = "^3.1.9"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = "^23.1.0" black = "^23.1.0"
@ -85,6 +86,7 @@ pytest-cov = "^4.0.0"
pandas-stubs = "^2.0.0.230412" pandas-stubs = "^2.0.0.230412"
types-pillow = "^9.5.0.2" types-pillow = "^9.5.0.2"
types-appdirs = "^1.4.3.5" types-appdirs = "^1.4.3.5"
types-pyyaml = "^6.0.12.8"
[tool.poetry.extras] [tool.poetry.extras]

View file

@ -1,6 +1,5 @@
import sys import sys
import time import time
from fastapi import FastAPI
import httpx import httpx
from multiprocess import Process, cpu_count # type: ignore from multiprocess import Process, cpu_count # type: ignore
import platform import platform
@ -11,9 +10,7 @@ from rich.panel import Panel
from rich import box from rich import box
from rich import print as rprint from rich import print as rprint
import typer import typer
from fastapi.staticfiles import StaticFiles from langflow.main import setup_app
from fastapi.responses import FileResponse
from langflow.main import create_app
from langflow.settings import settings from langflow.settings import settings
from langflow.utils.logger import configure, logger from langflow.utils.logger import configure, logger
import webbrowser import webbrowser
@ -144,15 +141,9 @@ def serve(
remove_api_keys=remove_api_keys, remove_api_keys=remove_api_keys,
cache=cache, cache=cache,
) )
# get the directory of the current file # create path object if path is provided
if not path: static_files_dir: Optional[Path] = Path(path) if path else None
frontend_path = Path(__file__).parent app = setup_app(static_files_dir=static_files_dir)
static_files_dir = frontend_path / "frontend"
else:
static_files_dir = Path(path)
app = create_app()
setup_static_files(app, static_files_dir)
# check if port is being used # check if port is being used
if is_port_in_use(port, host): if is_port_in_use(port, host):
port = get_free_port(port) port = get_free_port(port)
@ -200,29 +191,6 @@ def run_on_windows(host, port, log_level, options, app):
run_langflow(host, port, log_level, options, app) run_langflow(host, port, log_level, options, app)
def setup_static_files(app: FastAPI, static_files_dir: Path):
"""
Setup the static files directory.
Args:
app (FastAPI): FastAPI app.
path (str): Path to the static files directory.
"""
app.mount(
"/",
StaticFiles(directory=static_files_dir, html=True),
name="static",
)
@app.exception_handler(404)
async def custom_404_handler(request, __):
path = static_files_dir / "index.html"
if not path.exists():
raise RuntimeError(f"File at path {path} does not exist.")
return FileResponse(path)
def is_port_in_use(port, host="localhost"): def is_port_in_use(port, host="localhost"):
""" """
Check if a port is in use. Check if a port is in use.
@ -303,7 +271,7 @@ def run_langflow(host, port, log_level, options, app):
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
except Exception as e: except Exception as e:
logger.error(e) logger.exception(e)
sys.exit(1) sys.exit(1)

View file

@ -1,12 +1,6 @@
from fastapi import ( from fastapi import APIRouter, HTTPException, WebSocket, WebSocketException, status
APIRouter,
HTTPException,
WebSocket,
WebSocketException,
status,
)
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from langflow.api.v1.schemas import BuiltResponse, InitResponse, StreamData from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
from langflow.chat.manager import ChatManager from langflow.chat.manager import ChatManager
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
@ -25,22 +19,39 @@ async def chat(client_id: str, websocket: WebSocket):
if client_id in chat_manager.in_memory_cache: if client_id in chat_manager.in_memory_cache:
await chat_manager.handle_websocket(client_id, websocket) await chat_manager.handle_websocket(client_id, websocket)
else: else:
# We accept the connection but close it immediately
# if the flow is not built yet
await websocket.accept()
message = "Please, build the flow before sending messages" message = "Please, build the flow before sending messages"
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason=message) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=message)
except WebSocketException as exc: except WebSocketException as exc:
logger.error(exc) logger.error(exc)
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc)) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
@router.post("/build/init", response_model=InitResponse, status_code=201) @router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201)
async def init_build(graph_data: dict): async def init_build(graph_data: dict, flow_id: str):
"""Initialize the build by storing graph data and returning a unique session ID.""" """Initialize the build by storing graph data and returning a unique session ID."""
try: try:
flow_id = graph_data.get("id")
if flow_id is None: if flow_id is None:
raise ValueError("No ID provided") raise ValueError("No ID provided")
flow_data_store[flow_id] = graph_data # Check if already building
if (
flow_id in flow_data_store
and flow_data_store[flow_id]["status"] == BuildStatus.IN_PROGRESS
):
return InitResponse(flowId=flow_id)
# Delete from cache if already exists
if flow_id in chat_manager.in_memory_cache:
with chat_manager.in_memory_cache._lock:
chat_manager.in_memory_cache.delete(flow_id)
logger.debug(f"Deleted flow {flow_id} from cache")
flow_data_store[flow_id] = {
"graph_data": graph_data,
"status": BuildStatus.STARTED,
}
return InitResponse(flowId=flow_id) return InitResponse(flowId=flow_id)
except Exception as exc: except Exception as exc:
@ -52,8 +63,9 @@ async def init_build(graph_data: dict):
async def build_status(flow_id: str): async def build_status(flow_id: str):
"""Check the flow_id is in the flow_data_store.""" """Check the flow_id is in the flow_data_store."""
try: try:
built = flow_id in flow_data_store and not isinstance( built = (
flow_data_store[flow_id], dict flow_id in flow_data_store
and flow_data_store[flow_id]["status"] == BuildStatus.SUCCESS
) )
return BuiltResponse( return BuiltResponse(
@ -77,7 +89,12 @@ async def stream_build(flow_id: str):
yield str(StreamData(event="error", data={"error": error_message})) yield str(StreamData(event="error", data={"error": error_message}))
return return
graph_data = flow_data_store[flow_id].get("data") if flow_data_store[flow_id].get("status") == BuildStatus.IN_PROGRESS:
error_message = "Already building"
yield str(StreamData(event="error", data={"error": error_message}))
return
graph_data = flow_data_store[flow_id].get("graph_data")
if not graph_data: if not graph_data:
error_message = "No data provided" error_message = "No data provided"
@ -95,6 +112,7 @@ async def stream_build(flow_id: str):
return return
number_of_nodes = len(graph.nodes) number_of_nodes = len(graph.nodes)
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
for i, vertex in enumerate(graph.generator_build(), 1): for i, vertex in enumerate(graph.generator_build(), 1):
try: try:
log_dict = { log_dict = {
@ -110,6 +128,7 @@ async def stream_build(flow_id: str):
except Exception as exc: except Exception as exc:
params = str(exc) params = str(exc)
valid = False valid = False
flow_data_store[flow_id]["status"] = BuildStatus.FAILURE
response = { response = {
"valid": valid, "valid": valid,
@ -121,8 +140,10 @@ async def stream_build(flow_id: str):
yield str(StreamData(event="message", data=response)) yield str(StreamData(event="message", data=response))
chat_manager.set_cache(flow_id, graph.build()) chat_manager.set_cache(flow_id, graph.build())
flow_data_store[flow_id]["status"] = BuildStatus.SUCCESS
except Exception as exc: except Exception as exc:
logger.error("Error while building the flow: %s", exc) logger.error("Error while building the flow: %s", exc)
flow_data_store[flow_id]["status"] = BuildStatus.FAILURE
yield str(StreamData(event="error", data={"error": str(exc)})) yield str(StreamData(event="error", data={"error": str(exc)}))
finally: finally:
yield str(StreamData(event="message", data=final_response)) yield str(StreamData(event="message", data=final_response))

View file

@ -11,7 +11,7 @@ from langflow.api.v1.schemas import (
UploadFileResponse, UploadFileResponse,
) )
from langflow.interface.types import build_langchain_types_dict from langflow.interface.types import langchain_types_dict
from langflow.database.base import get_session from langflow.database.base import get_session
from sqlmodel import Session from sqlmodel import Session
@ -21,7 +21,7 @@ router = APIRouter(tags=["Base"])
@router.get("/all") @router.get("/all")
def get_all(): def get_all():
return build_langchain_types_dict() return langchain_types_dict
# For backwards compatibility we will keep the old endpoint # For backwards compatibility we will keep the old endpoint

View file

@ -1,3 +1,4 @@
from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from langflow.database.models.flow import FlowCreate, FlowRead from langflow.database.models.flow import FlowCreate, FlowRead
@ -5,6 +6,15 @@ from pydantic import BaseModel, Field, validator
import json import json
class BuildStatus(Enum):
"""Status of the build."""
SUCCESS = "success"
FAILURE = "failure"
STARTED = "started"
IN_PROGRESS = "in_progress"
class GraphData(BaseModel): class GraphData(BaseModel):
"""Data inside the exported flow.""" """Data inside the exported flow."""

View file

@ -0,0 +1,2 @@
class ChatConfig:
streaming: bool = True

View file

@ -1,141 +1,275 @@
---
agents: agents:
- ZeroShotAgent ZeroShotAgent:
- JsonAgent documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent"
- CSVAgent JsonAgent:
- AgentInitializer documentation: "https://python.langchain.com/docs/modules/agents/toolkits/openapi"
- VectorStoreAgent CSVAgent:
- VectorStoreRouterAgent documentation: "https://python.langchain.com/docs/modules/agents/toolkits/csv"
- SQLAgent AgentInitializer:
documentation: "https://python.langchain.com/docs/modules/agents/agent_types/"
VectorStoreAgent:
documentation: ""
VectorStoreRouterAgent:
documentation: ""
SQLAgent:
documentation: ""
chains: chains:
- LLMChain LLMChain:
- LLMMathChain documentation: "https://python.langchain.com/docs/modules/chains/foundational/llm_chain"
- LLMCheckerChain LLMMathChain:
- ConversationChain documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_math"
- SeriesCharacterChain LLMCheckerChain:
- MidJourneyPromptChain documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
- TimeTravelGuideChain ConversationChain:
- SQLDatabaseChain documentation: ""
- RetrievalQA SeriesCharacterChain:
- RetrievalQAWithSourcesChain documentation: ""
- ConversationalRetrievalChain MidJourneyPromptChain:
- CombineDocsChain documentation: ""
TimeTravelGuideChain:
documentation: ""
SQLDatabaseChain:
documentation: ""
RetrievalQA:
documentation: "https://python.langchain.com/docs/modules/chains/popular/vector_db_qa"
RetrievalQAWithSourcesChain:
documentation: ""
ConversationalRetrievalChain:
documentation: "https://python.langchain.com/docs/modules/chains/popular/chat_vector_db"
CombineDocsChain:
documentation: ""
documentloaders: documentloaders:
- AirbyteJSONLoader AirbyteJSONLoader:
- CoNLLULoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/airbyte_json"
- CSVLoader CoNLLULoader:
- UnstructuredEmailLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/conll-u"
- EverNoteLoader CSVLoader:
- FacebookChatLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/csv"
- GutenbergLoader UnstructuredEmailLoader:
- BSHTMLLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/email"
- UnstructuredHTMLLoader EverNoteLoader:
# - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83) documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/evernote"
- UnstructuredMarkdownLoader FacebookChatLoader:
- PyPDFLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/facebook_chat"
- UnstructuredPowerPointLoader GutenbergLoader:
- SRTLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/gutenberg"
- TelegramChatLoader BSHTMLLoader:
- TextLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/html"
- UnstructuredWordDocumentLoader UnstructuredHTMLLoader:
- WebBaseLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/html"
- AZLyricsLoader UnstructuredMarkdownLoader:
- CollegeConfidentialLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/markdown"
- HNLoader PyPDFDirectoryLoader:
- IFixitLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/pdf"
- IMSDbLoader PyPDFLoader:
- GitbookLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/pdf"
- ReadTheDocsLoader UnstructuredPowerPointLoader:
- SlackDirectoryLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/microsoft_powerpoint"
- NotionDirectoryLoader SRTLoader:
- DirectoryLoader documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/subtitle"
- GitLoader TelegramChatLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/telegram"
TextLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/"
UnstructuredWordDocumentLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/microsoft_word"
WebBaseLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/web_base"
AZLyricsLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/azlyrics"
CollegeConfidentialLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/college_confidential"
HNLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/hacker_news"
IFixitLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/ifixit"
IMSDbLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/imsdb"
GitbookLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/gitbook"
ReadTheDocsLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/readthedocs_documentation"
SlackDirectoryLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/slack"
NotionDirectoryLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/notion"
DirectoryLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/file_directory"
GitLoader:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/git"
embeddings: embeddings:
- OpenAIEmbeddings OpenAIEmbeddings:
- HuggingFaceEmbeddings documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/openai"
- CohereEmbeddings HuggingFaceEmbeddings:
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
CohereEmbeddings:
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/cohere"
llms: llms:
- OpenAI OpenAI:
# - AzureOpenAI documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/openai"
# - AzureChatOpenAI ChatOpenAI:
- ChatOpenAI documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/openai"
- LlamaCpp LlamaCpp:
- CTransformers documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/llamacpp"
- Cohere CTransformers:
- Anthropic documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/ctransformers"
- ChatAnthropic Cohere:
- HuggingFaceHub documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
Anthropic:
documentation: ""
ChatAnthropic:
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic"
HuggingFaceHub:
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/huggingface_hub"
VertexAI:
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/google_vertex_ai_palm"
###
# There's a bug in this component deactivating until we get it sorted: _language_models.py", line 804, in send_message
# is_blocked=safety_attributes.get("blocked", False),
# AttributeError: 'list' object has no attribute 'get'
# ChatVertexAI:
# documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/google_vertex_ai_palm"
###
memories: memories:
- ConversationBufferMemory # https://github.com/supabase-community/supabase-py/issues/482
- ConversationSummaryMemory # ZepChatMessageHistory:
- ConversationKGMemory # documentation: "https://python.langchain.com/docs/modules/memory/integrations/zep_memory"
ConversationEntityMemory:
documentation: "https://python.langchain.com/docs/modules/memory/integrations/entity_memory_with_sqlite"
# https://github.com/hwchase17/langchain/issues/6091
# SQLiteEntityStore:
# documentation: "https://python.langchain.com/docs/modules/memory/integrations/entity_memory_with_sqlite"
PostgresChatMessageHistory:
documentation: "https://python.langchain.com/docs/modules/memory/integrations/postgres_chat_message_history"
ConversationBufferMemory:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/buffer"
ConversationSummaryMemory:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/summary"
ConversationKGMemory:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/kg"
ConversationBufferWindowMemory:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/buffer_window"
VectorStoreRetrieverMemory:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/vectorstore_retriever_memory"
prompts: prompts:
- PromptTemplate PromptTemplate:
- FewShotPromptTemplate documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/"
- ZeroShotPrompt ZeroShotPrompt:
documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent"
textsplitters: textsplitters:
- CharacterTextSplitter CharacterTextSplitter:
- RecursiveCharacterTextSplitter documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
# - LatexTextSplitter RecursiveCharacterTextSplitter:
# - PythonCodeTextSplitter documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter"
toolkits: toolkits:
- OpenAPIToolkit OpenAPIToolkit:
- JsonToolkit documentation: ""
- VectorStoreInfo JsonToolkit:
- VectorStoreRouterToolkit documentation: ""
- VectorStoreToolkit VectorStoreInfo:
documentation: ""
VectorStoreRouterToolkit:
documentation: ""
VectorStoreToolkit:
documentation: ""
tools: tools:
- Search Search:
- PAL-MATH documentation: ""
- Calculator PAL-MATH:
- Serper Search documentation: ""
- Tool Calculator:
- PythonFunctionTool documentation: ""
- PythonFunction Serper Search:
- JsonSpec documentation: ""
- News API Tool:
- TMDB API documentation: ""
- Podcast API PythonFunctionTool:
- QuerySQLDataBaseTool documentation: ""
- InfoSQLDatabaseTool PythonFunction:
- ListSQLDatabaseTool documentation: ""
# - QueryCheckerTool JsonSpec:
- BingSearchRun documentation: ""
- GoogleSearchRun News API:
- GoogleSearchResults documentation: ""
- GoogleSerperRun TMDB API:
- JsonListKeysTool documentation: ""
- JsonGetValueTool Podcast API:
- PythonREPLTool documentation: ""
- PythonAstREPLTool QuerySQLDataBaseTool:
- RequestsGetTool documentation: ""
- RequestsPostTool InfoSQLDatabaseTool:
- RequestsPatchTool documentation: ""
- RequestsPutTool ListSQLDatabaseTool:
- RequestsDeleteTool documentation: ""
- WikipediaQueryRun BingSearchRun:
- WolframAlphaQueryRun documentation: ""
GoogleSearchRun:
documentation: ""
GoogleSearchResults:
documentation: ""
GoogleSerperRun:
documentation: ""
JsonListKeysTool:
documentation: ""
JsonGetValueTool:
documentation: ""
PythonREPLTool:
documentation: ""
PythonAstREPLTool:
documentation: ""
RequestsGetTool:
documentation: ""
RequestsPostTool:
documentation: ""
RequestsPatchTool:
documentation: ""
RequestsPutTool:
documentation: ""
RequestsDeleteTool:
documentation: ""
WikipediaQueryRun:
documentation: ""
WolframAlphaQueryRun:
documentation: ""
utilities: utilities:
- BingSearchAPIWrapper BingSearchAPIWrapper:
- GoogleSearchAPIWrapper documentation: ""
- GoogleSerperAPIWrapper GoogleSearchAPIWrapper:
- SearxResults documentation: ""
- SearxSearchWrapper GoogleSerperAPIWrapper:
- SerpAPIWrapper documentation: ""
- WikipediaAPIWrapper SearxResults:
- WolframAlphaAPIWrapper documentation: ""
# - ZapierNLAWrapper SearxSearchWrapper:
- SQLDatabase documentation: ""
SerpAPIWrapper:
documentation: ""
WikipediaAPIWrapper:
documentation: ""
WolframAlphaAPIWrapper:
documentation: ""
retrievers:
MultiQueryRetriever:
documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/how_to/MultiQueryRetriever"
# https://github.com/supabase-community/supabase-py/issues/482
# ZepRetriever:
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
vectorstores: vectorstores:
- Chroma Chroma:
- Qdrant documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
- Weaviate Qdrant:
- FAISS documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
- Pinecone Weaviate:
- SupabaseVectorStore documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/weaviate"
- MongoDBAtlasVectorSearch FAISS:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
Pinecone:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/pinecone"
SupabaseVectorStore:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase"
MongoDBAtlasVectorSearch:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas_vector_search"
wrappers: wrappers:
- RequestsWrapper RequestsWrapper:
# - ChatPromptTemplate documentation: ""
# - SystemMessagePromptTemplate
# - HumanMessagePromptTemplate

View file

@ -21,6 +21,9 @@ CUSTOM_NODES = {
"utilities": { "utilities": {
"SQLDatabase": frontend_node.agents.SQLDatabaseNode(), "SQLDatabase": frontend_node.agents.SQLDatabaseNode(),
}, },
"memories": {
"PostgresChatMessageHistory": frontend_node.memories.PostgresChatMessageHistoryFrontendNode(),
},
"chains": { "chains": {
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(), "SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),
"TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(), "TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(),

View file

@ -1,6 +1,6 @@
from langflow.settings import settings from langflow.settings import settings
from sqlmodel import SQLModel, Session, create_engine from sqlmodel import SQLModel, Session, create_engine
from langflow.utils.logger import logger
if settings.database_url.startswith("sqlite"): if settings.database_url.startswith("sqlite"):
connect_args = {"check_same_thread": False} connect_args = {"check_same_thread": False}
@ -10,7 +10,9 @@ engine = create_engine(settings.database_url, connect_args=connect_args)
def create_db_and_tables(): def create_db_and_tables():
logger.debug("Creating database and tables")
SQLModel.metadata.create_all(engine) SQLModel.metadata.create_all(engine)
logger.debug("Database and tables created")
def get_session(): def get_session():

View file

@ -14,6 +14,7 @@ from langflow.graph.vertex.types import (
ToolkitVertex, ToolkitVertex,
VectorStoreVertex, VectorStoreVertex,
WrapperVertex, WrapperVertex,
RetrieverVertex,
) )
__all__ = [ __all__ = [
@ -32,4 +33,5 @@ __all__ = [
"ToolkitVertex", "ToolkitVertex",
"VectorStoreVertex", "VectorStoreVertex",
"WrapperVertex", "WrapperVertex",
"RetrieverVertex",
] ]

View file

@ -27,12 +27,7 @@ class Edge:
# Get what type of input the target node is expecting # Get what type of input the target node is expecting
self.matched_type = next( self.matched_type = next(
( (output for output in self.source_types if output in self.target_reqs),
output
for output in self.source_types
for target_req in self.target_reqs
if output in target_req
),
None, None,
) )
no_matched_type = self.matched_type is None no_matched_type = self.matched_type is None

View file

@ -1,18 +1,5 @@
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ( from langflow.graph.vertex import types
AgentVertex,
ChainVertex,
DocumentLoaderVertex,
EmbeddingVertex,
LLMVertex,
MemoryVertex,
PromptVertex,
TextSplitterVertex,
ToolVertex,
ToolkitVertex,
VectorStoreVertex,
WrapperVertex,
)
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.document_loaders.base import documentloader_creator from langflow.interface.document_loaders.base import documentloader_creator
@ -25,22 +12,23 @@ from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
from langflow.interface.vector_store.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.retrievers.base import retriever_creator
from typing import Dict, Type from typing import Dict, Type
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = { VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
**{t: PromptVertex for t in prompt_creator.to_list()}, **{t: types.PromptVertex for t in prompt_creator.to_list()},
**{t: AgentVertex for t in agent_creator.to_list()}, **{t: types.AgentVertex for t in agent_creator.to_list()},
**{t: ChainVertex for t in chain_creator.to_list()}, **{t: types.ChainVertex for t in chain_creator.to_list()},
**{t: ToolVertex for t in tool_creator.to_list()}, **{t: types.ToolVertex for t in tool_creator.to_list()},
**{t: ToolkitVertex for t in toolkits_creator.to_list()}, **{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
**{t: WrapperVertex for t in wrapper_creator.to_list()}, **{t: types.WrapperVertex for t in wrapper_creator.to_list()},
**{t: LLMVertex for t in llm_creator.to_list()}, **{t: types.LLMVertex for t in llm_creator.to_list()},
**{t: MemoryVertex for t in memory_creator.to_list()}, **{t: types.MemoryVertex for t in memory_creator.to_list()},
**{t: EmbeddingVertex for t in embedding_creator.to_list()}, **{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
**{t: VectorStoreVertex for t in vectorstore_creator.to_list()}, **{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
**{t: DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
} }

View file

@ -112,6 +112,11 @@ class MemoryVertex(Vertex):
super().__init__(data, base_type="memory") super().__init__(data, base_type="memory")
class RetrieverVertex(Vertex):
def __init__(self, data: Dict):
super().__init__(data, base_type="retrievers")
class TextSplitterVertex(Vertex): class TextSplitterVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="textsplitters") super().__init__(data, base_type="textsplitters")

View file

@ -8,6 +8,7 @@ from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template from langflow.template.template.base import Template
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.settings import settings
# Assuming necessary imports for Field, Template, and FrontendNode classes # Assuming necessary imports for Field, Template, and FrontendNode classes
@ -15,12 +16,29 @@ from langflow.utils.logger import logger
class LangChainTypeCreator(BaseModel, ABC): class LangChainTypeCreator(BaseModel, ABC):
type_name: str type_name: str
type_dict: Optional[Dict] = None type_dict: Optional[Dict] = None
name_docs_dict: Optional[Dict[str, str]] = None
@property @property
def frontend_node_class(self) -> Type[FrontendNode]: def frontend_node_class(self) -> Type[FrontendNode]:
"""The class type of the FrontendNode created in frontend_node.""" """The class type of the FrontendNode created in frontend_node."""
return FrontendNode return FrontendNode
@property
def docs_map(self) -> Dict[str, str]:
"""A dict with the name of the component as key and the documentation link as value."""
if self.name_docs_dict is None:
try:
type_settings = getattr(settings, self.type_name)
self.name_docs_dict = {
name: value_dict["documentation"]
for name, value_dict in type_settings.items()
}
except AttributeError as exc:
logger.error(exc)
self.name_docs_dict = {}
return self.name_docs_dict
@property @property
@abstractmethod @abstractmethod
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
@ -83,7 +101,7 @@ class LangChainTypeCreator(BaseModel, ABC):
signature.add_extra_fields() signature.add_extra_fields()
signature.add_extra_base_classes() signature.add_extra_base_classes()
signature.set_documentation(self.docs_map.get(name, ""))
return signature return signature

View file

@ -10,8 +10,12 @@ from langchain import (
text_splitter, text_splitter,
) )
from langchain.agents import agent_toolkits from langchain.agents import agent_toolkits
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI from langchain.chat_models import (
from langchain.chat_models import ChatAnthropic AzureChatOpenAI,
ChatOpenAI,
ChatVertexAI,
ChatAnthropic,
)
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
@ -22,6 +26,7 @@ llm_type_to_cls_dict = llms.type_to_cls_dict
llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
llm_type_to_cls_dict["vertexai-chat"] = ChatVertexAI # type: ignore
# Toolkits # Toolkits

View file

@ -44,6 +44,7 @@ def import_by_type(_type: str, name: str) -> Any:
"documentloaders": import_documentloader, "documentloaders": import_documentloader,
"textsplitters": import_textsplitter, "textsplitters": import_textsplitter,
"utilities": import_utility, "utilities": import_utility,
"retrievers": import_retriever,
} }
if _type == "llms": if _type == "llms":
key = "chat" if "chat" in name.lower() else "llm" key = "chat" if "chat" in name.lower() else "llm"
@ -59,6 +60,11 @@ def import_chat_llm(llm: str) -> BaseChatModel:
return import_class(f"langchain.chat_models.{llm}") return import_class(f"langchain.chat_models.{llm}")
def import_retriever(retriever: str) -> Any:
"""Import retriever from retriever name"""
return import_module(f"from langchain.retrievers import {retriever}")
def import_memory(memory: str) -> Any: def import_memory(memory: str) -> Any:
"""Import memory from memory name""" """Import memory from memory name"""
return import_module(f"from langchain.memory import {memory}") return import_module(f"from langchain.memory import {memory}")

View file

@ -0,0 +1,9 @@
def initialize_vertexai(class_object, params):
if credentials_path := params.get("credentials"):
from google.oauth2 import service_account # type: ignore
credentials_object = service_account.Credentials.from_service_account_file(
filename=credentials_path
)
params["credentials"] = credentials_object
return class_object(**params)

View file

@ -1,11 +1,13 @@
import json import json
from typing import Any, Callable, Dict, Sequence from typing import Any, Callable, Dict, Sequence, Type
from langchain.agents import ZeroShotAgent from langchain.agents import ZeroShotAgent
from langchain.agents import agent as agent_module from langchain.agents import agent as agent_module
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
from langchain.agents.agent_toolkits.base import BaseToolkit from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.agents.tools import BaseTool from langchain.agents.tools import BaseTool
from langflow.interface.initialize.llm import initialize_vertexai
from langflow.interface.initialize.vector_store import vecstore_initializer from langflow.interface.initialize.vector_store import vecstore_initializer
from pydantic import ValidationError from pydantic import ValidationError
@ -14,8 +16,13 @@ from langflow.interface.custom_lists import CUSTOM_NODES
from langflow.interface.importing.utils import get_function, import_by_type from langflow.interface.importing.utils import get_function, import_by_type
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.retrievers.base import retriever_creator
from langflow.interface.utils import load_file_into_dict from langflow.interface.utils import load_file_into_dict
from langflow.utils import validate from langflow.utils import validate
from langchain.chains.base import Chain
from langchain.vectorstores.base import VectorStore
from langchain.document_loaders.base import BaseLoader
from langchain.prompts.base import BasePromptTemplate
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
@ -43,8 +50,8 @@ def convert_params_to_sets(params):
def convert_kwargs(params): def convert_kwargs(params):
# if *kwargs are passed as a string, convert to dict # if *kwargs are passed as a string, convert to dict
# first find any key that has kwargs in it # first find any key that has kwargs or config in it
kwargs_keys = [key for key in params.keys() if "kwargs" in key] kwargs_keys = [key for key in params.keys() if "kwargs" in key or "config" in key]
for key in kwargs_keys: for key in kwargs_keys:
if isinstance(params[key], str): if isinstance(params[key], str):
params[key] = json.loads(params[key]) params[key] = json.loads(params[key])
@ -72,11 +79,57 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
return instantiate_utility(node_type, class_object, params) return instantiate_utility(node_type, class_object, params)
elif base_type == "chains": elif base_type == "chains":
return instantiate_chains(node_type, class_object, params) return instantiate_chains(node_type, class_object, params)
elif base_type == "llms":
return instantiate_llm(node_type, class_object, params)
elif base_type == "retrievers":
return instantiate_retriever(node_type, class_object, params)
elif base_type == "memory":
return instantiate_memory(node_type, class_object, params)
else: else:
return class_object(**params) return class_object(**params)
def instantiate_chains(node_type, class_object, params): def instantiate_llm(node_type, class_object, params: Dict):
# This is a workaround so JinaChat works until streaming is implemented
# if "openai_api_base" in params and "jina" in params["openai_api_base"]:
# False if condition is True
if node_type == "VertexAI":
return initialize_vertexai(class_object=class_object, params=params)
return class_object(**params)
def instantiate_memory(node_type, class_object, params):
try:
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever()
return class_object(**params)
# I want to catch a specific attribute error that happens
# when the object does not have a cursor attribute
except Exception as exc:
if "object has no attribute 'cursor'" in str(
exc
) or 'object has no field "conn"' in str(exc):
raise AttributeError(
(
"Failed to build connection to database."
f" Please check your connection string and try again. Error: {exc}"
)
) from exc
raise exc
def instantiate_retriever(node_type, class_object, params):
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever()
if node_type in retriever_creator.from_method_nodes:
method = retriever_creator.from_method_nodes[node_type]
if class_method := getattr(class_object, method, None):
return class_method(**params)
raise ValueError(f"Method {method} not found in {class_object}")
return class_object(**params)
def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
if "retriever" in params and hasattr(params["retriever"], "as_retriever"): if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever() params["retriever"] = params["retriever"].as_retriever()
if node_type in chain_creator.from_method_nodes: if node_type in chain_creator.from_method_nodes:
@ -88,11 +141,11 @@ def instantiate_chains(node_type, class_object, params):
return class_object(**params) return class_object(**params)
def instantiate_agent(class_object, params): def instantiate_agent(class_object: Type[agent_module.Agent], params: Dict):
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)
def instantiate_prompt(node_type, class_object, params): def instantiate_prompt(node_type, class_object: Type[BasePromptTemplate], params: Dict):
if node_type == "ZeroShotPrompt": if node_type == "ZeroShotPrompt":
if "tools" not in params: if "tools" not in params:
params["tools"] = [] params["tools"] = []
@ -100,7 +153,7 @@ def instantiate_prompt(node_type, class_object, params):
return class_object(**params) return class_object(**params)
def instantiate_tool(node_type, class_object, params): def instantiate_tool(node_type, class_object: Type[BaseTool], params: Dict):
if node_type == "JsonSpec": if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path")) params["dict_"] = load_file_into_dict(params.pop("path"))
return class_object(**params) return class_object(**params)
@ -118,7 +171,7 @@ def instantiate_tool(node_type, class_object, params):
return class_object(**params) return class_object(**params)
def instantiate_toolkit(node_type, class_object, params): def instantiate_toolkit(node_type, class_object: Type[BaseToolkit], params: Dict):
loaded_toolkit = class_object(**params) loaded_toolkit = class_object(**params)
# Commenting this out for now to use toolkits as normal tools # Commenting this out for now to use toolkits as normal tools
# if toolkits_creator.has_create_function(node_type): # if toolkits_creator.has_create_function(node_type):
@ -128,7 +181,7 @@ def instantiate_toolkit(node_type, class_object, params):
return loaded_toolkit return loaded_toolkit
def instantiate_embedding(class_object, params): def instantiate_embedding(class_object, params: Dict):
params.pop("model", None) params.pop("model", None)
params.pop("headers", None) params.pop("headers", None)
try: try:
@ -142,7 +195,7 @@ def instantiate_embedding(class_object, params):
return class_object(**params) return class_object(**params)
def instantiate_vectorstore(class_object, params): def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
search_kwargs = params.pop("search_kwargs", {}) search_kwargs = params.pop("search_kwargs", {})
if initializer := vecstore_initializer.get(class_object.__name__): if initializer := vecstore_initializer.get(class_object.__name__):
vecstore = initializer(class_object, params) vecstore = initializer(class_object, params)
@ -158,7 +211,7 @@ def instantiate_vectorstore(class_object, params):
return vecstore return vecstore
def instantiate_documentloader(class_object, params): def instantiate_documentloader(class_object: Type[BaseLoader], params: Dict):
if "file_filter" in params: if "file_filter" in params:
# file_filter will be a string but we need a function # file_filter will be a string but we need a function
# that will be used to filter the files using file_filter # that will be used to filter the files using file_filter
@ -171,35 +224,55 @@ def instantiate_documentloader(class_object, params):
extension.strip() in x for extension in extensions extension.strip() in x for extension in extensions
) )
metadata = params.pop("metadata", None) metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError as exc:
raise ValueError(
"The metadata you provided is not a valid JSON string."
) from exc
docs = class_object(**params).load() docs = class_object(**params).load()
# Now if metadata is an empty dict, we will not add it to the documents
if metadata: if metadata:
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError as exc:
raise ValueError(
"The metadata you provided is not a valid JSON string."
) from exc
for doc in docs: for doc in docs:
doc.metadata = metadata # If the document already has metadata, we will not overwrite it
if not doc.metadata:
doc.metadata = metadata
else:
doc.metadata.update(metadata)
return docs return docs
def instantiate_textsplitter(class_object, params): def instantiate_textsplitter(
class_object,
params: Dict,
):
try: try:
documents = params.pop("documents") documents = params.pop("documents")
except KeyError as e: except KeyError as exc:
raise ValueError( raise ValueError(
"The source you provided did not load correctly or was empty." "The source you provided did not load correctly or was empty."
"Try changing the chunk_size of the Text Splitter." "Try changing the chunk_size of the Text Splitter."
) from e ) from exc
text_splitter = class_object(**params)
if (
"separator_type" in params and params["separator_type"] == "Text"
) or "separator_type" not in params:
params.pop("separator_type", None)
text_splitter = class_object(**params)
else:
from langchain.text_splitter import Language
language = params.pop("separator_type", None)
params["language"] = Language(language)
params.pop("separators", None)
text_splitter = class_object.from_language(**params)
return text_splitter.split_documents(documents) return text_splitter.split_documents(documents)
def instantiate_utility(node_type, class_object, params): def instantiate_utility(node_type, class_object, params: Dict):
if node_type == "SQLDatabase": if node_type == "SQLDatabase":
return class_object.from_uri(params.pop("uri")) return class_object.from_uri(params.pop("uri"))
return class_object(**params) return class_object(**params)
@ -225,6 +298,8 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
"""Load agent executor from agent class, tools and chain""" """Load agent executor from agent class, tools and chain"""
allowed_tools: Sequence[BaseTool] = params.get("allowed_tools", []) allowed_tools: Sequence[BaseTool] = params.get("allowed_tools", [])
llm_chain = params["llm_chain"] llm_chain = params["llm_chain"]
# agent has hidden args for memory. might need to be support
# memory = params["memory"]
# if allowed_tools is not a list or set, make it a list # if allowed_tools is not a list or set, make it a list
if not isinstance(allowed_tools, (list, set)) and isinstance( if not isinstance(allowed_tools, (list, set)) and isinstance(
allowed_tools, BaseTool allowed_tools, BaseTool
@ -237,6 +312,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, agent=agent,
tools=allowed_tools, tools=allowed_tools,
# memory=memory,
**kwargs, **kwargs,
) )

View file

@ -11,6 +11,7 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.utilities.base import utility_creator from langflow.interface.utilities.base import utility_creator
from langflow.interface.vector_store.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.retrievers.base import retriever_creator
def get_type_dict(): def get_type_dict():
@ -28,6 +29,7 @@ def get_type_dict():
"embeddings": embedding_creator.to_list(), "embeddings": embedding_creator.to_list(),
"textSplitters": textsplitter_creator.to_list(), "textSplitters": textsplitter_creator.to_list(),
"utilities": utility_creator.to_list(), "utilities": utility_creator.to_list(),
"retrievers": retriever_creator.to_list(),
} }

View file

@ -6,12 +6,18 @@ from langflow.settings import settings
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.memories import MemoryFrontendNode from langflow.template.frontend_node.memories import MemoryFrontendNode
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class, build_template_from_method
from langflow.custom.customs import get_custom_nodes
class MemoryCreator(LangChainTypeCreator): class MemoryCreator(LangChainTypeCreator):
type_name: str = "memories" type_name: str = "memories"
from_method_nodes = {
"ZepChatMessageHistory": "__init__",
"SQLiteEntityStore": "__init__",
}
@property @property
def frontend_node_class(self) -> Type[FrontendNode]: def frontend_node_class(self) -> Type[FrontendNode]:
"""The class type of the FrontendNode created in frontend_node.""" """The class type of the FrontendNode created in frontend_node."""
@ -26,6 +32,14 @@ class MemoryCreator(LangChainTypeCreator):
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a memory.""" """Get the signature of a memory."""
try: try:
if name in get_custom_nodes(self.type_name).keys():
return get_custom_nodes(self.type_name)[name]
elif name in self.from_method_nodes:
return build_template_from_method(
name,
type_to_cls_dict=memory_type_to_cls_dict,
method_name=self.from_method_nodes[name],
)
return build_template_from_class(name, memory_type_to_cls_dict) return build_template_from_class(name, memory_type_to_cls_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Memory not found") from exc raise ValueError("Memory not found") from exc

View file

@ -0,0 +1,58 @@
from typing import Any, Dict, List, Optional, Type
from langchain import retrievers
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_method, build_template_from_class
class RetrieverCreator(LangChainTypeCreator):
type_name: str = "retrievers"
from_method_nodes = {"MultiQueryRetriever": "from_llm", "ZepRetriever": "__init__"}
@property
def frontend_node_class(self) -> Type[RetrieverFrontendNode]:
return RetrieverFrontendNode
@property
def type_to_loader_dict(self) -> Dict:
if self.type_dict is None:
self.type_dict: dict[str, Any] = {
retriever_name: import_class(f"langchain.retrievers.{retriever_name}")
for retriever_name in retrievers.__all__
}
return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of an embedding."""
try:
if name in self.from_method_nodes:
return build_template_from_method(
name,
type_to_cls_dict=self.type_to_loader_dict,
method_name=self.from_method_nodes[name],
)
else:
return build_template_from_class(
name, type_to_cls_dict=self.type_to_loader_dict
)
except ValueError as exc:
raise ValueError(f"Retriever {name} not found") from exc
except AttributeError as exc:
logger.error(f"Retriever {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
retriever
for retriever in self.type_to_loader_dict.keys()
if retriever in settings.retrievers or settings.dev
]
retriever_creator = RetrieverCreator()

View file

@ -62,6 +62,10 @@ def update_memory_keys(langchain_object, possible_new_mem_key):
if key not in [langchain_object.memory.memory_key, possible_new_mem_key] if key not in [langchain_object.memory.memory_key, possible_new_mem_key]
][0] ][0]
langchain_object.memory.input_key = input_key keys = [input_key, output_key, possible_new_mem_key]
langchain_object.memory.output_key = output_key attrs = ["input_key", "output_key", "memory_key"]
langchain_object.memory.memory_key = possible_new_mem_key for key, attr in zip(keys, attrs):
try:
setattr(langchain_object.memory, attr, key)
except ValueError as exc:
logger.debug(f"{langchain_object.memory} has no attribute {attr} ({exc})")

View file

@ -11,6 +11,7 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.utilities.base import utility_creator from langflow.interface.utilities.base import utility_creator
from langflow.interface.vector_store.base import vectorstore_creator from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.interface.retrievers.base import retriever_creator
def get_type_list(): def get_type_list():
@ -44,6 +45,7 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
documentloader_creator, documentloader_creator,
textsplitter_creator, textsplitter_creator,
utility_creator, utility_creator,
retriever_creator,
] ]
all_types = {} all_types = {}
@ -52,3 +54,6 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
if created_types[creator.type_name].values(): if created_types[creator.type_name].values():
all_types.update(created_types) all_types.update(created_types)
return all_types return all_types
langchain_types_dict = build_langchain_types_dict()

View file

@ -4,10 +4,12 @@ import os
from io import BytesIO from io import BytesIO
import re import re
import yaml import yaml
from langchain.base_language import BaseLanguageModel from langchain.base_language import BaseLanguageModel
from PIL.Image import Image from PIL.Image import Image
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.chat.config import ChatConfig
def load_file_into_dict(file_path: str) -> dict: def load_file_into_dict(file_path: str) -> dict:
@ -49,9 +51,9 @@ def try_setting_streaming_options(langchain_object, websocket):
if isinstance(llm, BaseLanguageModel): if isinstance(llm, BaseLanguageModel):
if hasattr(llm, "streaming") and isinstance(llm.streaming, bool): if hasattr(llm, "streaming") and isinstance(llm.streaming, bool):
llm.streaming = True llm.streaming = ChatConfig.streaming
elif hasattr(llm, "stream") and isinstance(llm.stream, bool): elif hasattr(llm, "stream") and isinstance(llm.stream, bool):
llm.stream = True llm.stream = ChatConfig.streaming
return langchain_object return langchain_object

View file

@ -1,5 +1,9 @@
from pathlib import Path
from typing import Optional
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from langflow.api import router from langflow.api import router
from langflow.database.base import create_db_and_tables from langflow.database.base import create_db_and_tables
@ -33,6 +37,42 @@ def create_app():
return app return app
def setup_static_files(app: FastAPI, static_files_dir: Path):
"""
Setup the static files directory.
Args:
app (FastAPI): FastAPI app.
path (str): Path to the static files directory.
"""
app.mount(
"/",
StaticFiles(directory=static_files_dir, html=True),
name="static",
)
@app.exception_handler(404)
async def custom_404_handler(request, __):
path = static_files_dir / "index.html"
if not path.exists():
raise RuntimeError(f"File at path {path} does not exist.")
return FileResponse(path)
# app = create_app()
# setup_static_files(app, static_files_dir)
def setup_app(static_files_dir: Optional[Path]) -> FastAPI:
"""Setup the FastAPI app."""
# get the directory of the current file
if not static_files_dir:
frontend_path = Path(__file__).parent
static_files_dir = frontend_path / "frontend"
app = create_app()
setup_static_files(app, static_files_dir)
return app
app = create_app() app = create_app()

View file

@ -51,5 +51,6 @@ async def get_result_and_steps(langchain_object, message: str, **kwargs):
) )
thought = format_actions(intermediate_steps) if intermediate_steps else "" thought = format_actions(intermediate_steps) if intermediate_steps else ""
except Exception as exc: except Exception as exc:
logger.exception(exc)
raise ValueError(f"Error: {str(exc)}") from exc raise ValueError(f"Error: {str(exc)}") from exc
return result, thought return result, thought

View file

@ -107,6 +107,10 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
elif isinstance(langchain_object, VectorStore): elif isinstance(langchain_object, VectorStore):
class_name = langchain_object.__class__.__name__ class_name = langchain_object.__class__.__name__
result = {"message": f"Processed {class_name} successfully"} result = {"message": f"Processed {class_name} successfully"}
else:
raise ValueError(
f"Unknown langchain_object type: {type(langchain_object).__name__}"
)
return result return result

View file

@ -1,29 +1,41 @@
import os import os
from typing import List
import yaml import yaml
from pydantic import BaseSettings, root_validator from pydantic import BaseSettings, root_validator
from langflow.utils.logger import logger
class Settings(BaseSettings): class Settings(BaseSettings):
chains: List[str] = [] chains: dict = {}
agents: List[str] = [] agents: dict = {}
prompts: List[str] = [] prompts: dict = {}
llms: List[str] = [] llms: dict = {}
tools: List[str] = [] tools: dict = {}
memories: List[str] = [] memories: dict = {}
embeddings: List[str] = [] embeddings: dict = {}
vectorstores: List[str] = [] vectorstores: dict = {}
documentloaders: List[str] = [] documentloaders: dict = {}
wrappers: List[str] = [] wrappers: dict = {}
toolkits: List[str] = [] retrievers: dict = {}
textsplitters: List[str] = [] toolkits: dict = {}
utilities: List[str] = [] textsplitters: dict = {}
utilities: dict = {}
dev: bool = False dev: bool = False
database_url: str = "sqlite:///./langflow.db" database_url: str
cache: str = "InMemoryCache" cache: str = "InMemoryCache"
remove_api_keys: bool = False remove_api_keys: bool = False
@root_validator(pre=True)
def set_database_url(cls, values):
if "database_url" not in values:
logger.debug("No database_url provided, trying DATABASE_URL env variable")
if database_url := os.getenv("DATABASE_URL"):
values["database_url"] = database_url
else:
logger.debug("No DATABASE_URL env variable, using sqlite database")
values["database_url"] = "sqlite:///./langflow.db"
return values
class Config: class Config:
validate_assignment = True validate_assignment = True
extra = "ignore" extra = "ignore"
@ -38,16 +50,21 @@ class Settings(BaseSettings):
def update_from_yaml(self, file_path: str, dev: bool = False): def update_from_yaml(self, file_path: str, dev: bool = False):
new_settings = load_settings_from_yaml(file_path) new_settings = load_settings_from_yaml(file_path)
self.chains = new_settings.chains or [] self.chains = new_settings.chains or {}
self.agents = new_settings.agents or [] self.agents = new_settings.agents or {}
self.prompts = new_settings.prompts or [] self.prompts = new_settings.prompts or {}
self.llms = new_settings.llms or [] self.llms = new_settings.llms or {}
self.tools = new_settings.tools or [] self.tools = new_settings.tools or {}
self.memories = new_settings.memories or [] self.memories = new_settings.memories or {}
self.wrappers = new_settings.wrappers or [] self.wrappers = new_settings.wrappers or {}
self.toolkits = new_settings.toolkits or [] self.toolkits = new_settings.toolkits or {}
self.textsplitters = new_settings.textsplitters or [] self.textsplitters = new_settings.textsplitters or {}
self.utilities = new_settings.utilities or [] self.utilities = new_settings.utilities or {}
self.embeddings = new_settings.embeddings or {}
self.vectorstores = new_settings.vectorstores or {}
self.documentloaders = new_settings.documentloaders or {}
self.retrievers = new_settings.retrievers or {}
self.dev = dev self.dev = dev
def update_settings(self, **kwargs): def update_settings(self, **kwargs):

View file

@ -21,6 +21,7 @@ class TemplateFieldCreator(BaseModel, ABC):
name: str = "" name: str = ""
display_name: Optional[str] = None display_name: Optional[str] = None
advanced: bool = False advanced: bool = False
info: Optional[str] = ""
def to_dict(self): def to_dict(self):
result = self.dict() result = self.dict()

View file

@ -1,12 +1,43 @@
import re import re
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel from pydantic import BaseModel, Field
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.template.base import Template from langflow.template.template.base import Template
from langflow.utils import constants from langflow.utils import constants
from langflow.template.frontend_node.formatter import field_formatters
CLASSES_TO_REMOVE = ["Serializable", "BaseModel"]
class FieldFormatters(BaseModel):
formatters = {
"openai_api_key": field_formatters.OpenAIAPIKeyFormatter(),
}
base_formatters = {
"kwargs": field_formatters.KwargsFormatter(),
"optional": field_formatters.RemoveOptionalFormatter(),
"list": field_formatters.ListTypeFormatter(),
"dict": field_formatters.DictTypeFormatter(),
"union": field_formatters.UnionTypeFormatter(),
"multiline": field_formatters.MultilineFieldFormatter(),
"show": field_formatters.ShowFieldFormatter(),
"password": field_formatters.PasswordFieldFormatter(),
"default": field_formatters.DefaultValueFormatter(),
"headers": field_formatters.HeadersDefaultValueFormatter(),
"dict_code_file": field_formatters.DictCodeFileFormatter(),
"model_fields": field_formatters.ModelSpecificFieldFormatter(),
}
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
for key, formatter in self.base_formatters.items():
formatter.format(field, name)
for key, formatter in self.formatters.items():
if key == field.name:
formatter.format(field, name)
class FrontendNode(BaseModel): class FrontendNode(BaseModel):
@ -15,14 +46,37 @@ class FrontendNode(BaseModel):
base_classes: List[str] base_classes: List[str]
name: str = "" name: str = ""
display_name: str = "" display_name: str = ""
documentation: str = ""
field_formatters: FieldFormatters = Field(default_factory=FieldFormatters)
def process_base_classes(self) -> None:
"""Removes unwanted base classes from the list of base classes."""
self.base_classes = [
base_class
for base_class in self.base_classes
if base_class not in CLASSES_TO_REMOVE
]
# field formatters is an instance attribute but it is not used in the class
# so we need to create a method to get it
@staticmethod
def get_field_formatters() -> FieldFormatters:
return FieldFormatters()
def set_documentation(self, documentation: str) -> None:
"""Sets the documentation of the frontend node."""
self.documentation = documentation
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""Returns a dict representation of the frontend node."""
self.process_base_classes()
return { return {
self.name: { self.name: {
"template": self.template.to_dict(self.format_field), "template": self.template.to_dict(self.format_field),
"description": self.description, "description": self.description,
"base_classes": self.base_classes, "base_classes": self.base_classes,
"display_name": self.display_name or self.name, "display_name": self.display_name or self.name,
"documentation": self.documentation,
}, },
} }
@ -35,33 +89,8 @@ class FrontendNode(BaseModel):
@staticmethod @staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:
"""Formats a given field based on its attributes and value.""" """Formats a given field based on its attributes and value."""
SPECIAL_FIELD_HANDLERS = {
"allowed_tools": lambda field: "Tool",
"max_value_length": lambda field: "int",
}
key = field.name FrontendNode.get_field_formatters().format(field, name)
value = field.to_dict()
_type = value["type"]
_type = FrontendNode.remove_optional(_type)
_type, is_list = FrontendNode.check_for_list_type(_type)
field.is_list = is_list or field.is_list
_type = FrontendNode.replace_mapping_with_dict(_type)
_type = FrontendNode.handle_union_type(_type)
field.field_type = FrontendNode.handle_special_field(
field, key, _type, SPECIAL_FIELD_HANDLERS
)
field.field_type = FrontendNode.handle_dict_type(field, _type)
field.show = FrontendNode.should_show_field(key, field.required)
field.password = FrontendNode.should_be_password(key, field.show)
field.multiline = FrontendNode.should_be_multiline(key)
FrontendNode.replace_default_value(field, value)
FrontendNode.handle_specific_field_values(field, key, name)
FrontendNode.handle_kwargs_field(field)
FrontendNode.handle_api_key_field(field, key)
@staticmethod @staticmethod
def remove_optional(_type: str) -> str: def remove_optional(_type: str) -> str:

View file

@ -49,6 +49,10 @@ class ChainFrontendNode(FrontendNode):
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name) FrontendNode.format_field(field, name)
if "name" == "RetrievalQA" and field.name == "memory":
field.show = False
field.required = False
field.advanced = False field.advanced = False
if "key" in field.name: if "key" in field.name:
field.password = False field.password = False

View file

@ -32,3 +32,29 @@ You are a good listener and you can talk about anything.
HUMAN_PROMPT = "{input}" HUMAN_PROMPT = "{input}"
QA_CHAIN_TYPES = ["stuff", "map_reduce", "map_rerank", "refine"] QA_CHAIN_TYPES = ["stuff", "map_reduce", "map_rerank", "refine"]
CTRANSFORMERS_DEFAULT_CONFIG = {
"top_k": 40,
"top_p": 0.95,
"temperature": 0.8,
"repetition_penalty": 1.1,
"last_n_tokens": 64,
"seed": -1,
"max_new_tokens": 256,
"stop": None,
"stream": False,
"reset": True,
"batch_size": 8,
"threads": -1,
"context_length": -1,
"gpu_layers": 0,
}
# This variable is used to tell the user
# that it can be changed to use other APIs
# like Prem and LocalAI
OPENAI_API_BASE_INFO = """
The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.
You can change this to use other APIs like JinaChat, LocalAI and Prem.
"""

View file

@ -120,29 +120,23 @@ class DocumentLoaderFrontNode(FrontendNode):
"DirectoryLoader", "DirectoryLoader",
"ReadTheDocsLoader", "ReadTheDocsLoader",
"NotionDirectoryLoader", "NotionDirectoryLoader",
"PyPDFDirectoryLoader",
}: }:
name = "path" name = "path"
display_name = "Local directory" display_name = "Local directory"
if name: if name:
self.template.add_field(
TemplateField(
field_type="str",
required=True,
show=True,
name=name,
value="",
display_name=display_name,
)
)
if self.template.type_name in {"DirectoryLoader"}: if self.template.type_name in {"DirectoryLoader"}:
for field in build_directory_loader_fields():
self.template.add_field(field)
else:
self.template.add_field( self.template.add_field(
TemplateField( TemplateField(
field_type="str", field_type="str",
required=True, required=True,
show=True, show=True,
name="glob", name=name,
value="**/*.txt", value="",
display_name="glob", display_name=display_name,
) )
) )
# add a metadata field of type dict # add a metadata field of type dict
@ -165,3 +159,101 @@ class DocumentLoaderFrontNode(FrontendNode):
field.show = True field.show = True
field.advanced = False field.advanced = False
field.show = True field.show = True
def build_directory_loader_fields():
# if loader_kwargs is None:
# loader_kwargs = {}
# self.path = path
# self.glob = glob
# self.load_hidden = load_hidden
# self.loader_cls = loader_cls
# self.loader_kwargs = loader_kwargs
# self.silent_errors = silent_errors
# self.recursive = recursive
# self.show_progress = show_progress
# self.use_multithreading = use_multithreading
# self.max_concurrency = max_concurrency
# Based on the above fields, we can build the following fields:
# path, glob, load_hidden, silent_errors, recursive, show_progress, use_multithreading, max_concurrency
# path
path = TemplateField(
field_type="str",
required=True,
show=True,
name="path",
value="",
display_name="Local directory",
advanced=False,
)
# glob
glob = TemplateField(
field_type="str",
required=True,
show=True,
name="glob",
value="**/*.txt",
display_name="glob",
advanced=False,
)
# load_hidden
load_hidden = TemplateField(
field_type="bool",
required=False,
show=True,
name="load_hidden",
value="False",
display_name="Load hidden files",
advanced=True,
)
# silent_errors
silent_errors = TemplateField(
field_type="bool",
required=False,
show=True,
name="silent_errors",
value="False",
display_name="Silent errors",
advanced=True,
)
# recursive
recursive = TemplateField(
field_type="bool",
required=False,
show=True,
name="recursive",
value="True",
display_name="Recursive",
advanced=True,
)
# use_multithreading
use_multithreading = TemplateField(
field_type="bool",
required=False,
show=True,
name="use_multithreading",
value="True",
display_name="Use multithreading",
advanced=True,
)
# max_concurrency
max_concurrency = TemplateField(
field_type="int",
required=False,
show=True,
name="max_concurrency",
value=10,
display_name="Max concurrency",
advanced=True,
)
return (
path,
glob,
load_hidden,
silent_errors,
recursive,
use_multithreading,
max_concurrency,
)

View file

@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from typing import Optional
from langflow.template.field.base import TemplateField
class FieldFormatter(ABC):
@abstractmethod
def format(self, field: TemplateField, name: Optional[str]) -> None:
pass

View file

@ -0,0 +1,162 @@
from typing import Optional
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
from langflow.template.frontend_node.formatter.base import FieldFormatter
import re
from langflow.utils.constants import (
ANTHROPIC_MODELS,
CHAT_OPENAI_MODELS,
OPENAI_MODELS,
)
class OpenAIAPIKeyFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
if "api_key" in field.name and "OpenAI" in str(name):
field.display_name = "OpenAI API Key"
field.required = False
if field.value is None:
field.value = ""
class ModelSpecificFieldFormatter(FieldFormatter):
MODEL_DICT = {
"OpenAI": OPENAI_MODELS,
"ChatOpenAI": CHAT_OPENAI_MODELS,
"Anthropic": ANTHROPIC_MODELS,
"ChatAnthropic": ANTHROPIC_MODELS,
}
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
if name in self.MODEL_DICT and field.name == "model_name":
field.options = self.MODEL_DICT[name]
field.is_list = True
class KwargsFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
if "kwargs" in field.name.lower():
field.advanced = True
field.required = False
field.show = False
class APIKeyFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
if "api" in field.name.lower() and "key" in field.name.lower():
field.required = False
field.advanced = False
field.display_name = field.name.replace("_", " ").title()
field.display_name = field.display_name.replace("Api", "API")
class RemoveOptionalFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
_type = field.field_type
field.field_type = re.sub(r"Optional\[(.*)\]", r"\1", _type)
class ListTypeFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
_type = field.field_type
is_list = "List" in _type or "Sequence" in _type
if is_list:
_type = re.sub(r"(List|Sequence)\[(.*)\]", r"\2", _type)
field.is_list = True
field.field_type = _type
class DictTypeFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
_type = field.field_type
_type = _type.replace("Mapping", "dict")
field.field_type = _type
class UnionTypeFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
_type = field.field_type
if "Union" in _type:
_type = _type.replace("Union[", "")[:-1]
_type = _type.split(",")[0]
_type = _type.replace("]", "").replace("[", "")
field.field_type = _type
class SpecialFieldFormatter(FieldFormatter):
SPECIAL_FIELD_HANDLERS = {
"allowed_tools": lambda field: "Tool",
"max_value_length": lambda field: "int",
}
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
handler = self.SPECIAL_FIELD_HANDLERS.get(field.name)
field.field_type = handler(field) if handler else field.field_type
class ShowFieldFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
key = field.name
required = field.required
field.show = (
(required and key not in ["input_variables"])
or key in FORCE_SHOW_FIELDS
or "api" in key
or ("key" in key and "input" not in key and "output" not in key)
)
class PasswordFieldFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
key = field.name
show = field.show
if (
any(text in key.lower() for text in {"password", "token", "api", "key"})
and show
):
field.password = True
class MultilineFieldFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
key = field.name
if key in {
"suffix",
"prefix",
"template",
"examples",
"code",
"headers",
"description",
}:
field.multiline = True
class DefaultValueFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
value = field.to_dict()
if "default" in value:
field.value = value["default"]
class HeadersDefaultValueFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
key = field.name
if key == "headers":
field.value = """{'Authorization': 'Bearer <token>'}"""
class DictCodeFileFormatter(FieldFormatter):
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
key = field.name
value = field.to_dict()
_type = value["type"]
if "dict" in _type.lower():
if key == "dict_":
field.field_type = "file"
field.suffixes = [".json", ".yaml", ".yml"]
field.file_types = ["json", "yaml", "yml"]
else:
field.field_type = "code"

View file

@ -1,10 +1,56 @@
import json
from typing import Optional from typing import Optional
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.constants import CTRANSFORMERS_DEFAULT_CONFIG
from langflow.template.frontend_node.constants import OPENAI_API_BASE_INFO
class LLMFrontendNode(FrontendNode): class LLMFrontendNode(FrontendNode):
def add_extra_fields(self) -> None:
if "VertexAI" in self.template.type_name:
# Add credentials field which should of type file.
self.template.add_field(
TemplateField(
field_type="file",
required=False,
show=True,
name="credentials",
value="",
suffixes=[".json"],
fileTypes=["json"],
)
)
@staticmethod
def format_vertex_field(field: TemplateField, name: str):
if "VertexAI" in name:
advanced_fields = [
"tuned_model_name",
"verbose",
"top_p",
"top_k",
"max_output_tokens",
]
if field.name in advanced_fields:
field.advanced = True
show_fields = [
"tuned_model_name",
"verbose",
"project",
"location",
"credentials",
"max_output_tokens",
"model_name",
"temperature",
"top_p",
"top_k",
]
if field.name in show_fields:
field.show = True
@staticmethod @staticmethod
def format_openai_field(field: TemplateField): def format_openai_field(field: TemplateField):
if "openai" in field.name.lower(): if "openai" in field.name.lower():
@ -15,6 +61,13 @@ class LLMFrontendNode(FrontendNode):
if "key" not in field.name.lower() and "token" not in field.name.lower(): if "key" not in field.name.lower() and "token" not in field.name.lower():
field.password = False field.password = False
if field.name == "openai_api_base":
field.info = OPENAI_API_BASE_INFO
def add_extra_base_classes(self) -> None:
if "BaseLLM" not in self.base_classes:
self.base_classes.append("BaseLLM")
@staticmethod @staticmethod
def format_azure_field(field: TemplateField): def format_azure_field(field: TemplateField):
if field.name == "model_name": if field.name == "model_name":
@ -31,6 +84,13 @@ class LLMFrontendNode(FrontendNode):
field.show = True field.show = True
field.advanced = not field.required field.advanced = not field.required
@staticmethod
def format_ctransformers_field(field: TemplateField):
if field.name == "config":
field.show = True
field.advanced = True
field.value = json.dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent=2)
@staticmethod @staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:
display_names_dict = { display_names_dict = {
@ -38,10 +98,13 @@ class LLMFrontendNode(FrontendNode):
} }
FrontendNode.format_field(field, name) FrontendNode.format_field(field, name)
LLMFrontendNode.format_openai_field(field) LLMFrontendNode.format_openai_field(field)
LLMFrontendNode.format_ctransformers_field(field)
if name and "azure" in name.lower(): if name and "azure" in name.lower():
LLMFrontendNode.format_azure_field(field) LLMFrontendNode.format_azure_field(field)
if name and "llama" in name.lower(): if name and "llama" in name.lower():
LLMFrontendNode.format_llama_field(field) LLMFrontendNode.format_llama_field(field)
if name and "vertex" in name.lower():
LLMFrontendNode.format_vertex_field(field, name)
SHOW_FIELDS = ["repo_id"] SHOW_FIELDS = ["repo_id"]
if field.name in SHOW_FIELDS: if field.name in SHOW_FIELDS:
field.show = True field.show = True
@ -77,6 +140,17 @@ class LLMFrontendNode(FrontendNode):
"model_file", "model_file",
"model_type", "model_type",
"deployment_name", "deployment_name",
"credentials",
]: ]:
field.advanced = False field.advanced = False
field.show = True field.show = True
if field.name == "credentials":
field.field_type = "file"
if name == "VertexAI" and field.name not in [
"callbacks",
"client",
"stop",
"tags",
"cache",
]:
field.show = True

View file

@ -2,11 +2,19 @@ from typing import Optional
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template
from langchain.memory.chat_message_histories.postgres import DEFAULT_CONNECTION_STRING
class MemoryFrontendNode(FrontendNode): class MemoryFrontendNode(FrontendNode):
#! Needs testing #! Needs testing
def add_extra_fields(self) -> None: def add_extra_fields(self) -> None:
# chat history should have another way to add common field?
# prevent adding incorect field in ChatMessageHistory
base_message_classes = ["BaseEntityStore", "BaseChatMessageHistory"]
if any(base_class in self.base_classes for base_class in base_message_classes):
return
# add return_messages field # add return_messages field
self.template.add_field( self.template.add_field(
TemplateField( TemplateField(
@ -29,16 +37,17 @@ class MemoryFrontendNode(FrontendNode):
value="", value="",
) )
) )
self.template.add_field( if self.template.type_name not in {"VectorStoreRetrieverMemory"}:
TemplateField( self.template.add_field(
field_type="str", TemplateField(
required=False, field_type="str",
show=True, required=False,
name="output_key", show=True,
advanced=True, name="output_key",
value="", advanced=True,
value="",
)
) )
)
@staticmethod @staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:
@ -64,3 +73,50 @@ class MemoryFrontendNode(FrontendNode):
field.value = "" field.value = ""
if field.name == "memory_key": if field.name == "memory_key":
field.value = "chat_history" field.value = "chat_history"
if field.name == "chat_memory":
field.show = True
field.advanced = False
field.required = False
if field.name == "url":
field.show = True
if field.name == "entity_store":
field.show = True
if name == "SQLiteEntityStore":
field.show = True
class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):
name: str = "PostgresChatMessageHistory"
template: Template = Template(
type_name="PostgresChatMessageHistory",
fields=[
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
name="session_id",
),
TemplateField(
field_type="str",
required=True,
show=True,
name="connection_string",
value=DEFAULT_CONNECTION_STRING,
),
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value="message_store",
name="table_name",
),
],
)
description: str = "Memory store with Postgres"
base_classes: list[str] = ["PostgresChatMessageHistory", "BaseChatMessageHistory"]

View file

@ -0,0 +1,15 @@
from typing import Optional
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
class RetrieverFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)
# Define common field attributes
field.show = True
if field.name == "parser_key":
field.display_name = "Parser Key"
field.password = False

View file

@ -1,5 +1,6 @@
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langchain.text_splitter import Language
class TextSplittersFrontendNode(FrontendNode): class TextSplittersFrontendNode(FrontendNode):
@ -17,6 +18,24 @@ class TextSplittersFrontendNode(FrontendNode):
name = "separator" name = "separator"
elif self.template.type_name == "RecursiveCharacterTextSplitter": elif self.template.type_name == "RecursiveCharacterTextSplitter":
name = "separators" name = "separators"
# Add a field for type of separator
# which will have Text or any value from the
# Language enum
options = [x.value for x in Language] + ["Text"]
options.sort()
self.template.add_field(
TemplateField(
field_type="str",
required=True,
show=True,
name="separator_type",
advanced=False,
is_list=True,
options=options,
value="Text",
display_name="Separator Type",
)
)
self.template.add_field( self.template.add_field(
TemplateField( TemplateField(
field_type="str", field_type="str",

View file

@ -51,7 +51,7 @@ class VectorStoreFrontendNode(FrontendNode):
required=False, required=False,
show=True, show=True,
advanced=False, advanced=False,
value=True, value=False,
display_name="Persist", display_name="Persist",
) )
extra_fields.append(extra_field) extra_fields.append(extra_field)
@ -200,7 +200,7 @@ class VectorStoreFrontendNode(FrontendNode):
self.template.add_field(field) self.template.add_field(field)
def add_extra_base_classes(self) -> None: def add_extra_base_classes(self) -> None:
self.base_classes.append("BaseRetriever") self.base_classes.extend(("BaseRetriever", "VectorStoreRetriever"))
@staticmethod @staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None: def format_field(field: TemplateField, name: Optional[str] = None) -> None:

View file

@ -165,6 +165,7 @@ def build_template_from_method(
"required": param.default == param.empty, "required": param.default == param.empty,
} }
for name, param in params.items() for name, param in params.items()
if name not in ["self", "kwargs", "args"]
}, },
} }
@ -233,6 +234,9 @@ def format_dict(d, name: Optional[str] = None):
_type = value["type"] _type = value["type"]
if not isinstance(_type, str):
_type = _type.__name__
# Remove 'Optional' wrapper # Remove 'Optional' wrapper
if "Optional" in _type: if "Optional" in _type:
_type = _type.replace("Optional[", "")[:-1] _type = _type.replace("Optional[", "")[:-1]

View file

@ -13,9 +13,11 @@
"@headlessui/react": "^1.7.10", "@headlessui/react": "^1.7.10",
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.15",
"@mui/material": "^5.11.9", "@mui/material": "^5.11.9",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3", "@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
@ -27,6 +29,7 @@
"@tabler/icons-react": "^2.18.0", "@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"accordion": "^3.0.2",
"ace-builds": "^1.16.0", "ace-builds": "^1.16.0",
"add": "^2.0.6", "add": "^2.0.6",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
@ -34,6 +37,7 @@
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0", "class-variance-authority": "^0.6.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"dompurify": "^3.0.3",
"esbuild": "^0.17.18", "esbuild": "^0.17.18",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.233.0",
@ -53,7 +57,7 @@
"rehype-mathjax": "^4.0.2", "rehype-mathjax": "^4.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"shadcn-ui": "^0.1.3", "shadcn-ui": "^0.2.2",
"short-unique-id": "^4.4.4", "short-unique-id": "^4.4.4",
"switch": "^0.0.0", "switch": "^0.0.0",
"table": "^6.8.1", "table": "^6.8.1",
@ -80,6 +84,8 @@
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"daisyui": "^3.1.1", "daisyui": "^3.1.1",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.3.9" "vite": "^4.3.9"
@ -114,6 +120,20 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@antfu/ni": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.4.tgz",
"integrity": "sha512-O0Uv9LbLDSoEg26fnMDdDRiPwFJnQSoD4WnrflDwKCJm8Cx/0mV4cGxwBLXan5mGIrpK4Dd7vizf4rQm0QCEAA==",
"bin": {
"na": "bin/na.mjs",
"nci": "bin/nci.mjs",
"ni": "bin/ni.mjs",
"nlx": "bin/nlx.mjs",
"nr": "bin/nr.mjs",
"nu": "bin/nu.mjs",
"nun": "bin/nun.mjs"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.21.4", "version": "7.21.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
@ -1212,15 +1232,15 @@
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
}, },
"node_modules/@mui/system": { "node_modules/@mui/system": {
"version": "5.13.5", "version": "5.13.6",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.5.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.6.tgz",
"integrity": "sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA==", "integrity": "sha512-G3Xr28uLqU3DyF6r2LQkHGw/ku4P0AHzlKVe7FGXOPl7X1u+hoe2xxj8Vdiq/69II/mh9OP21i38yBWgWb7WgQ==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.21.0", "@babel/runtime": "^7.22.5",
"@mui/private-theming": "^5.13.1", "@mui/private-theming": "^5.13.1",
"@mui/styled-engine": "^5.13.2", "@mui/styled-engine": "^5.13.2",
"@mui/types": "^7.2.4", "@mui/types": "^7.2.4",
"@mui/utils": "^5.13.1", "@mui/utils": "^5.13.6",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"csstype": "^3.1.2", "csstype": "^3.1.2",
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
@ -1280,11 +1300,11 @@
} }
}, },
"node_modules/@mui/utils": { "node_modules/@mui/utils": {
"version": "5.13.1", "version": "5.13.6",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.6.tgz",
"integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==", "integrity": "sha512-ggNlxl5NPSbp+kNcQLmSig6WVB0Id+4gOxhx644987v4fsji+CSXc+MFYLocFB/x4oHtzCUlSzbVHlJfP/fXoQ==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.21.0", "@babel/runtime": "^7.22.5",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",
"@types/react-is": "^18.2.0", "@types/react-is": "^18.2.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
@ -1366,6 +1386,37 @@
"@babel/runtime": "^7.13.10" "@babel/runtime": "^7.13.10"
} }
}, },
"node_modules/@radix-ui/react-accordion": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.1.2.tgz",
"integrity": "sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-collapsible": "1.0.3",
"@radix-ui/react-collection": "1.0.3",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-direction": "1.0.1",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-controllable-state": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": { "node_modules/@radix-ui/react-arrow": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
@ -1419,6 +1470,36 @@
} }
} }
}, },
"node_modules/@radix-ui/react-collapsible": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz",
"integrity": "sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-controllable-state": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": { "node_modules/@radix-ui/react-collection": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
@ -1630,6 +1711,14 @@
} }
} }
}, },
"node_modules/@radix-ui/react-icons": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz",
"integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==",
"peerDependencies": {
"react": "^16.x || ^17.x || ^18.x"
}
},
"node_modules/@radix-ui/react-id": { "node_modules/@radix-ui/react-id": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
@ -3106,6 +3195,39 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@ts-morph/common": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.19.0.tgz",
"integrity": "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==",
"dependencies": {
"fast-glob": "^3.2.12",
"minimatch": "^7.4.3",
"mkdirp": "^2.1.6",
"path-browserify": "^1.0.1"
}
},
"node_modules/@ts-morph/common/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@ts-morph/common/node_modules/minimatch": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
"integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@types/aria-query": { "node_modules/@types/aria-query": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
@ -3547,6 +3669,11 @@
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
}, },
"node_modules/accordion": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/accordion/-/accordion-3.0.2.tgz",
"integrity": "sha512-jbQfFaw+57OBwPt7qSNHuW+RA8smmRwkWRS1Ozh6K/QxUspBgBV/LpdSzlY7vee8TomS6j3D33B9rIeH1qMwsA=="
},
"node_modules/ace-builds": { "node_modules/ace-builds": {
"version": "1.16.0", "version": "1.16.0",
"resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.16.0.tgz", "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.16.0.tgz",
@ -3714,9 +3841,9 @@
} }
}, },
"node_modules/aria-query": { "node_modules/aria-query": {
"version": "5.2.1", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.2.1.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"dequal": "^2.0.3" "dequal": "^2.0.3"
@ -4491,6 +4618,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/code-block-writer": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
"integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w=="
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -5009,10 +5141,15 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/dompurify": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz",
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ=="
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.438", "version": "1.4.440",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.438.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.440.tgz",
"integrity": "sha512-x94U0FhphEsHsOloCvlsujHCvoir0ZQ73ZAs/QN4PLx98uNvyEU79F75rq1db75Bx/atvuh7KPeuxelh+xfYJw==" "integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw=="
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
@ -6745,9 +6882,9 @@
} }
}, },
"node_modules/katex": { "node_modules/katex": {
"version": "0.16.7", "version": "0.16.8",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz",
"integrity": "sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==", "integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==",
"funding": [ "funding": [
"https://opencollective.com/katex", "https://opencollective.com/katex",
"https://github.com/sponsors/katex" "https://github.com/sponsors/katex"
@ -6863,9 +7000,9 @@
} }
}, },
"node_modules/log-symbols/node_modules/chalk": { "node_modules/log-symbols/node_modules/chalk": {
"version": "5.2.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"engines": { "engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0" "node": "^12.17.0 || ^14.13 || >=16.0.0"
}, },
@ -7838,11 +7975,33 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mj-context-menu": { "node_modules/mj-context-menu": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
}, },
"node_modules/mkdirp": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
"integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -8111,9 +8270,9 @@
} }
}, },
"node_modules/ora/node_modules/chalk": { "node_modules/ora/node_modules/chalk": {
"version": "5.2.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"engines": { "engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0" "node": "^12.17.0 || ^14.13 || >=16.0.0"
}, },
@ -8210,6 +8369,11 @@
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
}, },
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
},
"node_modules/path-is-absolute": { "node_modules/path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -8437,6 +8601,95 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.3.0.tgz",
"integrity": "sha512-009/Xqdy7UmkcTBpwlq7jsViDqXAYSOMLDrHAdTMlVZOrKfM2o9Ci7EMWTMZ7SkKBFTG04UM9F9iM2+4i6boDA==",
"dev": true,
"engines": {
"node": ">=12.17.0"
},
"peerDependencies": {
"@ianvs/prettier-plugin-sort-imports": "*",
"@prettier/plugin-pug": "*",
"@shopify/prettier-plugin-liquid": "*",
"@shufo/prettier-plugin-blade": "*",
"@trivago/prettier-plugin-sort-imports": "*",
"prettier": ">=2.2.0",
"prettier-plugin-astro": "*",
"prettier-plugin-css-order": "*",
"prettier-plugin-import-sort": "*",
"prettier-plugin-jsdoc": "*",
"prettier-plugin-marko": "*",
"prettier-plugin-organize-attributes": "*",
"prettier-plugin-organize-imports": "*",
"prettier-plugin-style-order": "*",
"prettier-plugin-svelte": "*",
"prettier-plugin-twig-melody": "*"
},
"peerDependenciesMeta": {
"@ianvs/prettier-plugin-sort-imports": {
"optional": true
},
"@prettier/plugin-pug": {
"optional": true
},
"@shopify/prettier-plugin-liquid": {
"optional": true
},
"@shufo/prettier-plugin-blade": {
"optional": true
},
"@trivago/prettier-plugin-sort-imports": {
"optional": true
},
"prettier-plugin-astro": {
"optional": true
},
"prettier-plugin-css-order": {
"optional": true
},
"prettier-plugin-import-sort": {
"optional": true
},
"prettier-plugin-jsdoc": {
"optional": true
},
"prettier-plugin-marko": {
"optional": true
},
"prettier-plugin-organize-attributes": {
"optional": true
},
"prettier-plugin-organize-imports": {
"optional": true
},
"prettier-plugin-style-order": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
},
"prettier-plugin-twig-melody": {
"optional": true
}
}
},
"node_modules/pretty-format": { "node_modules/pretty-format": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
@ -9286,23 +9539,40 @@
} }
}, },
"node_modules/shadcn-ui": { "node_modules/shadcn-ui": {
"version": "0.1.3", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.1.3.tgz", "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.2.2.tgz",
"integrity": "sha512-f6Wa4ZIxsigfOonC3yyJkPb2JXJnuGFyUn1fJJrDUHvIJOydUukcdQsZg7Lp6F6llkmfRjra1dZOo0KpSfdjuQ==", "integrity": "sha512-T76EeZymSB45Yz63gkYOv9P0Ke+UA9IZenysx+975nyNzXxU7HRBgfwuHiMcrcubtOLrzRVedTLX3lcOMqDeRQ==",
"dependencies": { "dependencies": {
"@antfu/ni": "^0.21.4",
"chalk": "5.2.0", "chalk": "5.2.0",
"commander": "^10.0.0", "commander": "^10.0.0",
"cosmiconfig": "^8.1.3",
"diff": "^5.1.0",
"execa": "^7.0.0", "execa": "^7.0.0",
"fs-extra": "^11.1.0", "fs-extra": "^11.1.0",
"https-proxy-agent": "^6.2.0",
"node-fetch": "^3.3.0", "node-fetch": "^3.3.0",
"ora": "^6.1.2", "ora": "^6.1.2",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"ts-morph": "^18.0.0",
"tsconfig-paths": "^4.2.0",
"zod": "^3.20.2" "zod": "^3.20.2"
}, },
"bin": { "bin": {
"shadcn-ui": "dist/index.js" "shadcn-ui": "dist/index.js"
} }
}, },
"node_modules/shadcn-ui/node_modules/agent-base": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/shadcn-ui/node_modules/chalk": { "node_modules/shadcn-ui/node_modules/chalk": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz",
@ -9322,6 +9592,35 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/shadcn-ui/node_modules/cosmiconfig": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
"integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
"dependencies": {
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
}
},
"node_modules/shadcn-ui/node_modules/https-proxy-agent": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-6.2.1.tgz",
"integrity": "sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA==",
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -9575,6 +9874,14 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1" "url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"engines": {
"node": ">=4"
}
},
"node_modules/strip-eof": { "node_modules/strip-eof": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
@ -9950,6 +10257,28 @@
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
}, },
"node_modules/ts-morph": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz",
"integrity": "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==",
"dependencies": {
"@ts-morph/common": "~0.19.0",
"code-block-writer": "^12.0.0"
}
},
"node_modules/tsconfig-paths": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
"dependencies": {
"json5": "^2.2.2",
"minimist": "^1.2.6",
"strip-bom": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.5.3", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",

View file

@ -8,9 +8,11 @@
"@headlessui/react": "^1.7.10", "@headlessui/react": "^1.7.10",
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.15",
"@mui/material": "^5.11.9", "@mui/material": "^5.11.9",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3", "@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
@ -22,6 +24,7 @@
"@tabler/icons-react": "^2.18.0", "@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"accordion": "^3.0.2",
"ace-builds": "^1.16.0", "ace-builds": "^1.16.0",
"add": "^2.0.6", "add": "^2.0.6",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
@ -29,6 +32,7 @@
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0", "class-variance-authority": "^0.6.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"dompurify": "^3.0.3",
"esbuild": "^0.17.18", "esbuild": "^0.17.18",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.233.0",
@ -48,7 +52,7 @@
"rehype-mathjax": "^4.0.2", "rehype-mathjax": "^4.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"shadcn-ui": "^0.1.3", "shadcn-ui": "^0.2.2",
"short-unique-id": "^4.4.4", "short-unique-id": "^4.4.4",
"switch": "^0.0.0", "switch": "^0.0.0",
"table": "^6.8.1", "table": "^6.8.1",
@ -101,6 +105,8 @@
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"daisyui": "^3.1.1", "daisyui": "^3.1.1",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.3.9" "vite": "^4.3.9"

View file

@ -0,0 +1,3 @@
module.exports = {
plugins: [require("prettier-plugin-tailwindcss")],
};

View file

@ -51,6 +51,13 @@ export default function App() {
useEffect(() => { useEffect(() => {
// If there is an error alert open with data, add it to the alertsList // If there is an error alert open with data, add it to the alertsList
if (errorOpen && errorData) { if (errorOpen && errorData) {
if (
alertsList.length > 0 &&
JSON.stringify(alertsList[alertsList.length - 1].data) ===
JSON.stringify(errorData)
) {
return;
}
setErrorOpen(false); setErrorOpen(false);
setAlertsList((old) => { setAlertsList((old) => {
let newAlertsList = [ let newAlertsList = [
@ -62,6 +69,13 @@ export default function App() {
} }
// If there is a notice alert open with data, add it to the alertsList // If there is a notice alert open with data, add it to the alertsList
else if (noticeOpen && noticeData) { else if (noticeOpen && noticeData) {
if (
alertsList.length > 0 &&
JSON.stringify(alertsList[alertsList.length - 1].data) ===
JSON.stringify(noticeData)
) {
return;
}
setNoticeOpen(false); setNoticeOpen(false);
setAlertsList((old) => { setAlertsList((old) => {
let newAlertsList = [ let newAlertsList = [
@ -73,6 +87,13 @@ export default function App() {
} }
// If there is a success alert open with data, add it to the alertsList // If there is a success alert open with data, add it to the alertsList
else if (successOpen && successData) { else if (successOpen && successData) {
if (
alertsList.length > 0 &&
JSON.stringify(alertsList[alertsList.length - 1].data) ===
JSON.stringify(successData)
) {
return;
}
setSuccessOpen(false); setSuccessOpen(false);
setAlertsList((old) => { setAlertsList((old) => {
let newAlertsList = [ let newAlertsList = [
@ -103,7 +124,7 @@ export default function App() {
return ( return (
//need parent component with width and height //need parent component with width and height
<div className="h-full flex flex-col"> <div className="flex h-full flex-col">
<ErrorBoundary <ErrorBoundary
onReset={() => { onReset={() => {
window.localStorage.removeItem("tabsData"); window.localStorage.removeItem("tabsData");
@ -118,7 +139,7 @@ export default function App() {
</ErrorBoundary> </ErrorBoundary>
<div></div> <div></div>
<div <div
className="flex flex-col-reverse fixed bottom-5 left-5" className="fixed bottom-5 left-5 flex flex-col-reverse"
style={{ zIndex: 999 }} style={{ zIndex: 999 }}
> >
{alertsList.map((alert) => ( {alertsList.map((alert) => (

View file

@ -25,6 +25,7 @@ import { nodeColors } from "../../../../utils";
import ShadTooltip from "../../../../components/ShadTooltipComponent"; import ShadTooltip from "../../../../components/ShadTooltipComponent";
import { PopUpContext } from "../../../../contexts/popUpContext"; import { PopUpContext } from "../../../../contexts/popUpContext";
import ToggleShadComponent from "../../../../components/toggleShadComponent"; import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Info } from "lucide-react";
export default function ParameterComponent({ export default function ParameterComponent({
left, left,
@ -36,9 +37,11 @@ export default function ParameterComponent({
type, type,
name = "", name = "",
required = false, required = false,
info = "",
}: ParameterComponentType) { }: ParameterComponentType) {
const ref = useRef(null); const ref = useRef(null);
const refHtml = useRef(null); const refHtml = useRef(null);
const infoHtml = useRef(null);
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0); const [position, setPosition] = useState(0);
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
@ -79,6 +82,18 @@ export default function ParameterComponent({
}); });
}; };
useEffect(() => {
infoHtml.current = (
<div className="h-full w-full break-words">
{info.split("\n").map((line, i) => (
<p key={i} className="block">
{line}
</p>
))}
</div>
);
}, [info]);
useEffect(() => { useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle); const groupedObj = groupByFamily(myData, tooltipTitle);
@ -86,7 +101,7 @@ export default function ParameterComponent({
<span <span
key={getRandomKeyByssmm()} key={getRandomKeyByssmm()}
className={classNames( className={classNames(
i > 0 ? "items-center flex mt-3" : "items-center flex" i > 0 ? "mt-3 flex items-center" : "flex items-center"
)} )}
> >
<div <div
@ -123,12 +138,25 @@ export default function ParameterComponent({
return ( return (
<div <div
ref={ref} ref={ref}
className="w-full flex flex-wrap justify-between items-center bg-muted mt-1 px-5 py-2" className="mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2"
> >
<> <>
<div className={"text-sm truncate w-full " + (left ? "" : "text-end")}> <div
className={
"w-full truncate text-sm" +
(left ? "" : " text-end") +
(info !== "" ? " flex items-center" : "")
}
>
{title} {title}
<span className="text-destructive">{required ? " *" : ""}</span> <span className="text-destructive">{required ? " *" : ""}</span>
<div className="">
{info !== "" && (
<ShadTooltip content={infoHtml.current}>
<Info className="relative bottom-0.5 ml-2 h-3 w-3" />
</ShadTooltip>
)}
</div>
</div> </div>
{left && {left &&
(type === "str" || (type === "str" ||
@ -144,7 +172,6 @@ export default function ParameterComponent({
delayDuration={0} delayDuration={0}
content={refHtml.current} content={refHtml.current}
side={left ? "left" : "right"} side={left ? "left" : "right"}
open={refHtml?.current?.length > 0}
> >
<Handle <Handle
type={left ? "target" : "source"} type={left ? "target" : "source"}
@ -155,7 +182,7 @@ export default function ParameterComponent({
} }
className={classNames( className={classNames(
left ? "-ml-0.5 " : "-mr-0.5 ", left ? "-ml-0.5 " : "-mr-0.5 ",
"w-3 h-3 rounded-full border-2 bg-background" "h-3 w-3 rounded-full border-2 bg-background"
)} )}
style={{ style={{
borderColor: color, borderColor: color,
@ -220,7 +247,7 @@ export default function ParameterComponent({
) : left === true && ) : left === true &&
type === "str" && type === "str" &&
data.node.template[name].options ? ( data.node.template[name].options ? (
<div className="w-full mt-2"> <div className="mt-2 w-full">
<Dropdown <Dropdown
options={data.node.template[name].options} options={data.node.template[name].options}
onSelect={handleOnNewValue} onSelect={handleOnNewValue}

View file

@ -6,16 +6,7 @@ import {
} from "../../utils"; } from "../../utils";
import ParameterComponent from "./components/parameterComponent"; import ParameterComponent from "./components/parameterComponent";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import { import { useContext, useState, useEffect, useRef } from "react";
useContext,
useState,
useEffect,
useRef,
ForwardRefExoticComponent,
ComponentType,
SVGProps,
ReactNode,
} from "react";
import { NodeDataType } from "../../types/flow"; import { NodeDataType } from "../../types/flow";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
@ -23,10 +14,8 @@ import NodeModal from "../../modals/NodeModal";
import Tooltip from "../../components/TooltipComponent"; import Tooltip from "../../components/TooltipComponent";
import { NodeToolbar } from "reactflow"; import { NodeToolbar } from "reactflow";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import { useSSE } from "../../contexts/SSEContext"; import { useSSE } from "../../contexts/SSEContext";
import { ReactElement } from "react-markdown/lib/react-markdown";
export default function GenericNode({ export default function GenericNode({
data, data,
@ -46,6 +35,7 @@ export default function GenericNode({
const [validationStatus, setValidationStatus] = useState(null); const [validationStatus, setValidationStatus] = useState(null);
// State for outline color // State for outline color
const { sseData, isBuilding } = useSSE(); const { sseData, isBuilding } = useSSE();
const refHtml = useRef(null);
// useEffect(() => { // useEffect(() => {
// if (reactFlowInstance) { // if (reactFlowInstance) {
@ -140,7 +130,7 @@ export default function GenericNode({
) )
} }
> >
<div className="w-5 h-5 relative top-[3px]"> <div className="relative top-[3px] h-5 w-5">
<div <div
className={classNames( className={classNames(
validationStatus && validationStatus.valid validationStatus && validationStatus.valid
@ -215,6 +205,7 @@ export default function GenericNode({
? toTitleCase(data.node.template[t].name) ? toTitleCase(data.node.template[t].name)
: toTitleCase(t) : toTitleCase(t)
} }
info={data.node.template[t].info}
name={t} name={t}
tooltipTitle={data.node.template[t].type} tooltipTitle={data.node.template[t].type}
required={data.node.template[t].required} required={data.node.template[t].required}

View file

@ -25,14 +25,14 @@ export default function SingleAlert({
> >
{type === "error" ? ( {type === "error" ? (
<div <div
className="flex bg-error-background rounded-md p-3 mb-2 mx-2" className="mx-2 mb-2 flex rounded-md bg-error-background p-3"
key={dropItem.id} key={dropItem.id}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<XCircle className="h-5 w-5 text-status-red" aria-hidden="true" /> <XCircle className="h-5 w-5 text-status-red" aria-hidden="true" />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="text-sm break-words font-medium text-error-foreground"> <h3 className="break-words text-sm font-medium text-error-foreground">
{dropItem.title} {dropItem.title}
</h3> </h3>
{dropItem.list ? ( {dropItem.list ? (
@ -72,17 +72,17 @@ export default function SingleAlert({
</div> </div>
) : type === "notice" ? ( ) : type === "notice" ? (
<div <div
className="flex rounded-md bg-info-background p-3 mb-2 mx-2" className="mx-2 mb-2 flex rounded-md bg-info-background p-3"
key={dropItem.id} key={dropItem.id}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<Info className="h-5 w-5 text-status-blue " aria-hidden="true" /> <Info className="h-5 w-5 text-status-blue " aria-hidden="true" />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground font-medium"> <p className="text-sm font-medium text-info-foreground">
{dropItem.title} {dropItem.title}
</p> </p>
<p className="mt-3 text-sm md:mt-0 md:ml-6"> <p className="mt-3 text-sm md:ml-6 md:mt-0">
{dropItem.link ? ( {dropItem.link ? (
<Link <Link
to={dropItem.link} to={dropItem.link}
@ -118,7 +118,7 @@ export default function SingleAlert({
</div> </div>
) : ( ) : (
<div <div
className="flex bg-success-background p-3 mb-2 mx-2 rounded-md" className="mx-2 mb-2 flex rounded-md bg-success-background p-3"
key={dropItem.id} key={dropItem.id}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">

View file

@ -24,9 +24,9 @@ export default function AlertDropdown({}: AlertDropdownType) {
return ( return (
<div <div
ref={componentRef} ref={componentRef}
className="z-10 py-3 pb-4 px-2 rounded-md bg-background ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[400px] h-[500px] flex flex-col" className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-background px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
> >
<div className="flex pl-3 flex-row justify-between text-md font-medium text-foreground"> <div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
Notifications Notifications
<div className="flex gap-3 pr-3 "> <div className="flex gap-3 pr-3 ">
<button <button
@ -36,7 +36,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
setTimeout(clearNotificationList, 100); setTimeout(clearNotificationList, 100);
}} }}
> >
<Trash2 className="w-[1.1rem] h-[1.1rem]" /> <Trash2 className="h-[1.1rem] w-[1.1rem]" />
</button> </button>
<button <button
className="text-foreground hover:text-status-red" className="text-foreground hover:text-status-red"
@ -46,7 +46,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
</button> </button>
</div> </div>
</div> </div>
<div className="mt-3 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-high-foreground"> <div className="text-high-foreground mt-3 flex h-full w-full flex-col overflow-y-scroll scrollbar-hide">
{notificationList.length !== 0 ? ( {notificationList.length !== 0 ? (
notificationList.map((alertItem, index) => ( notificationList.map((alertItem, index) => (
<SingleAlert <SingleAlert
@ -56,7 +56,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
/> />
)) ))
) : ( ) : (
<div className="h-full w-full pb-16 text-ring flex justify-center items-center"> <div className="flex h-full w-full items-center justify-center pb-16 text-ring">
No new notifications No new notifications
</div> </div>
)} )}

View file

@ -39,7 +39,7 @@ export default function ErrorAlert({
removeAlert(id); removeAlert(id);
}, 500); }, 500);
}} }}
className="rounded-md w-96 mt-6 shadow-xl bg-error-background p-4 cursor-pointer" className="mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">

View file

@ -36,7 +36,7 @@ export default function NoticeAlert({
setShow(false); setShow(false);
removeAlert(id); removeAlert(id);
}} }}
className="rounded-md w-96 mt-6 shadow-xl bg-info-background p-4" className="mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
@ -44,7 +44,7 @@ export default function NoticeAlert({
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground">{title}</p> <p className="text-sm text-info-foreground">{title}</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6"> <p className="mt-3 text-sm md:ml-6 md:mt-0">
{link !== "" ? ( {link !== "" ? (
<Link <Link
to={link} to={link}

View file

@ -34,7 +34,7 @@ export default function SuccessAlert({
setShow(false); setShow(false);
removeAlert(id); removeAlert(id);
}} }}
className="rounded-md w-96 mt-6 shadow-xl bg-success-background p-4" className="mt-6 w-96 rounded-md bg-success-background p-4 shadow-xl"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">

View file

@ -0,0 +1,56 @@
import { ReactElement, useContext, useEffect, useRef, useState } from "react";
import {
AccordionComponentType,
ProgressBarType,
} from "../../types/components";
import { Progress } from "../../components/ui/progress";
import { setInterval } from "timers/promises";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "../../components/ui/accordion";
export default function AccordionComponent({
trigger,
children,
open = [],
}: AccordionComponentType) {
const [value, setValue] = useState(
open.length == 0 ? "" : getOpenAccordion()
);
function getOpenAccordion() {
let value = "";
open.forEach((el) => {
if (el == trigger) {
value = trigger;
}
});
return value;
}
function handleClick() {
value == "" ? setValue(trigger) : setValue("");
}
return (
<>
<Accordion type="single" value={value} onValueChange={setValue}>
<AccordionItem value={trigger} className="border-none">
<AccordionTrigger
onClick={() => {
handleClick();
}}
className="ml-3"
>
{trigger}
</AccordionTrigger>
<AccordionContent>{children}</AccordionContent>
</AccordionItem>
</Accordion>
</>
);
}

View file

@ -1,11 +1,11 @@
export default function CrashErrorComponent({ error, resetErrorBoundary }) { export default function CrashErrorComponent({ error, resetErrorBoundary }) {
return ( return (
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-foreground bg-opacity-50 z-50"> <div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-foreground bg-opacity-50">
<div className="bg-background max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly"> <div className="flex h-1/3 min-h-fit max-w-4xl flex-col justify-evenly rounded-lg bg-background p-8 text-start shadow-lg">
<h1 className="text-status-red text-3xl mb-4"> <h1 className="mb-4 text-3xl text-status-red">
Oops! An unknown error has occurred. Oops! An unknown error has occurred.
</h1> </h1>
<p className="text-foreground mb-4 text-xl"> <p className="mb-4 text-xl text-foreground">
Please click the 'Reset Application' button to restore the Please click the 'Reset Application' button to restore the
application's state. If the error persists, please create an issue on application's state. If the error persists, please create an issue on
our GitHub page. We apologize for any inconvenience this may have our GitHub page. We apologize for any inconvenience this may have
@ -14,7 +14,7 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
<div className="flex justify-center"> <div className="flex justify-center">
<button <button
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
className="bg-primary hover:bg-ring text-background font-bold py-2 px-4 rounded mr-4" className="mr-4 rounded bg-primary px-4 py-2 font-bold text-background hover:bg-ring"
> >
Reset Application Reset Application
</button> </button>
@ -22,7 +22,7 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
href="https://github.com/logspace-ai/langflow/issues/new" href="https://github.com/logspace-ai/langflow/issues/new"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="bg-status-red hover:bg-error-foreground text-background font-bold py-2 px-4 rounded" className="rounded bg-status-red px-4 py-2 font-bold text-background hover:bg-error-foreground"
> >
Create Issue Create Issue
</a> </a>

View file

@ -47,7 +47,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
<div className="flex justify-between"> <div className="flex justify-between">
<span className="font-medium">Name</span>{" "} <span className="font-medium">Name</span>{" "}
{isMaxLength && ( {isMaxLength && (
<span className="text-status-red animate-pulse ml-10"> <span className="ml-10 animate-pulse text-status-red">
Character limit reached Character limit reached
</span> </span>
)} )}
@ -71,7 +71,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
value={description ?? ""} value={description ?? ""}
placeholder="Flow description" placeholder="Flow description"
className="max-h-[100px] mt-2 font-normal" className="mt-2 max-h-[100px] font-normal"
rows={3} rows={3}
/> />
</Label> </Label>

View file

@ -18,10 +18,10 @@ export default function ExtraSidebar() {
<aside <aside
className={` ${ className={` ${
isStackedOpen ? "w-52" : "w-0 " isStackedOpen ? "w-52" : "w-0 "
} flex-shrink-0 flex overflow-hidden flex-col border-r transition-all duration-500`} } flex flex-shrink-0 flex-col overflow-hidden border-r transition-all duration-500`}
> >
<div className="w-52 border overflow-y-auto scrollbar-hide h-full flex flex-col items-start bg-background"> <div className="flex h-full w-52 flex-col items-start overflow-y-auto border bg-background scrollbar-hide">
<div className="flex flex-grow flex-col w-full"> <div className="flex w-full flex-grow flex-col">
{extraNavigation.options ? ( {extraNavigation.options ? (
<div className="p-4"> <div className="p-4">
<nav className="flex-1 space-y-1"> <nav className="flex-1 space-y-1">
@ -34,7 +34,7 @@ export default function ExtraSidebar() {
item.href.split("/")[2] === current[4] item.href.split("/")[2] === current[4]
? "bg-muted text-foreground" ? "bg-muted text-foreground"
: "bg-background text-muted-foreground hover:bg-muted hover:text-foreground", : "bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md" "group flex w-full items-center rounded-md py-2 pl-2 text-sm font-medium"
)} )}
> >
<item.icon <item.icon
@ -61,7 +61,7 @@ export default function ExtraSidebar() {
item.href.split("/")[2] === current[4] item.href.split("/")[2] === current[4]
? "bg-muted text-foreground" ? "bg-muted text-foreground"
: "bg-background text-muted-foreground hover:bg-muted hover:text-foreground", : "bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-1 focus:ring-ring" "group flex w-full items-center rounded-md py-2 pl-2 pr-1 text-left text-sm font-medium focus:outline-none focus:ring-1 focus:ring-ring"
)} )}
> >
<item.icon <item.icon

View file

@ -37,7 +37,7 @@ const TooltipReact: FC<TooltipProps> = ({
id={selector} id={selector}
content={content} content={content}
className={classNames( className={classNames(
"!bg-white !text-xs !font-normal !text-foreground !shadow-md !opacity-100 z-[9999]", "z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
className className
)} )}
place={position} place={position}

View file

@ -1,3 +1,4 @@
import { ShadTooltipProps } from "../../types/components";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
@ -5,18 +6,19 @@ import {
TooltipTrigger, TooltipTrigger,
} from "../ui/tooltip"; } from "../ui/tooltip";
const ShadTooltip = (props) => { const ShadTooltip = ({
delayDuration = 500,
side,
content,
children,
}: ShadTooltipProps) => {
return ( return (
<TooltipProvider> <TooltipProvider>
<Tooltip delayDuration={props.delayDuration}> <Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild>{props.children}</TooltipTrigger> <TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipContent <TooltipContent side={side} avoidCollisions={false} sticky="always">
side={props.side} {content}
avoidCollisions={false}
sticky="always"
>
{props.content}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>

View file

@ -31,20 +31,20 @@ export const CardComponent = ({
<CardTitle className="flex w-full items-center gap-4"> <CardTitle className="flex w-full items-center gap-4">
<span <span
className={ className={
"rounded-full w-7 h-7 flex items-center justify-center text-2xl " + "flex h-7 w-7 items-center justify-center rounded-full text-2xl " +
gradients[parseInt(flow.id.slice(0, 12), 16) % gradients.length] gradients[parseInt(flow.id.slice(0, 12), 16) % gradients.length]
} }
></span> ></span>
<span className="flex-1 w-full inline-block truncate-doubleline break-words"> <span className="inline-block w-full flex-1 break-words truncate-doubleline">
{flow.name} {flow.name}
</span> </span>
{onDelete && ( {onDelete && (
<button className="flex self-start" onClick={onDelete}> <button className="flex self-start" onClick={onDelete}>
<Trash2 className="w-4 h-4 text-primary opacity-0 group-hover:opacity-100 transition-all" /> <Trash2 className="h-4 w-4 text-primary opacity-0 transition-all group-hover:opacity-100" />
</button> </button>
)} )}
</CardTitle> </CardTitle>
<CardDescription className="pt-2 pb-2"> <CardDescription className="pb-2 pt-2">
<div className="truncate-doubleline"> <div className="truncate-doubleline">
{flow.description} {flow.description}
{/* {flow.description} */} {/* {flow.description} */}
@ -53,7 +53,7 @@ export const CardComponent = ({
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<div className="flex gap-2 w-full justify-between items-end"> <div className="flex w-full items-end justify-between gap-2">
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{/* <Badge variant="secondary">Agent</Badge> {/* <Badge variant="secondary">Agent</Badge>
<Badge variant="secondary"> <Badge variant="secondary">

View file

@ -158,7 +158,7 @@ export default function BuildTrigger({
> >
<div className={`fixed right-4` + (isBuilt ? " bottom-20" : " bottom-4")}> <div className={`fixed right-4` + (isBuilt ? " bottom-20" : " bottom-4")}>
<div <div
className={`${eventClick} flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-round-btn-shadow hover:shadow-round-btn-shadow bg-border cursor-pointer`} className={`${eventClick} align-center shadow-round-btn-shadow hover:shadow-round-btn-shadow flex h-12 w-12 cursor-pointer justify-center rounded-full bg-border px-3 py-1 shadow-md`}
onClick={() => { onClick={() => {
handleBuild(flow); handleBuild(flow);
}} }}
@ -166,7 +166,7 @@ export default function BuildTrigger({
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
> >
<button> <button>
<div className="flex gap-3 items-center"> <div className="flex items-center gap-3">
{isBuilding && progress < 1 ? ( {isBuilding && progress < 1 ? (
// Render your loading animation here when isBuilding is true // Render your loading animation here when isBuilding is true
<RadialProgressComponent <RadialProgressComponent

View file

@ -2,9 +2,9 @@ import { useState } from "react";
import { ChatMessageType } from "../../../types/chat"; import { ChatMessageType } from "../../../types/chat";
import { nodeColors } from "../../../utils"; import { nodeColors } from "../../../utils";
import Convert from "ansi-to-html"; import Convert from "ansi-to-html";
const convert = new Convert({ newline: true });
import { MessageCircle } from "lucide-react"; import { MessageCircle } from "lucide-react";
import DOMPurify from "dompurify";
const convert = new Convert({ newline: true });
export default function ChatMessage({ chat }: { chat: ChatMessageType }) { export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
const [hidden, setHidden] = useState(true); const [hidden, setHidden] = useState(true);
return ( return (
@ -13,29 +13,30 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
<div className="w-full text-start"> <div className="w-full text-start">
<div <div
style={{ backgroundColor: nodeColors["chat"] }} style={{ backgroundColor: nodeColors["chat"] }}
className=" relative text-start inline-block text-background rounded-xl overflow-hidden w-fit max-w-[280px] text-sm font-normal rounded-tl-none" className=" relative inline-block w-fit max-w-[280px] overflow-hidden rounded-xl rounded-tl-none text-start text-sm font-normal text-background"
> >
{hidden && chat.thought && chat.thought !== "" && ( {hidden && chat.thought && chat.thought !== "" && (
<div <div
onClick={() => setHidden((prev) => !prev)} onClick={() => setHidden((prev) => !prev)}
className="absolute top-2 right-2 cursor-pointer" className="absolute right-2 top-2 cursor-pointer"
> >
<MessageCircle className="w-5 h-5 animate-bounce" /> <MessageCircle className="h-5 w-5 animate-bounce" />
</div> </div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && ( {chat.thought && chat.thought !== "" && !hidden && (
<div <div
onClick={() => setHidden((prev) => !prev)} onClick={() => setHidden((prev) => !prev)}
style={{ backgroundColor: nodeColors["thought"] }} style={{ backgroundColor: nodeColors["thought"] }}
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer" className="inline-block w-full cursor-pointer px-5 pb-3 pt-3 text-start"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought), __html: DOMPurify.sanitize(convert.toHtml(chat.thought)),
}} }}
></div> ></div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && <br></br>} {chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div <div
className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8" className="w-full rounded-b-md px-4 pb-3 pr-8 pt-3"
style={{ backgroundColor: nodeColors["chat"] }} style={{ backgroundColor: nodeColors["chat"] }}
> >
{chat.message} {chat.message}
@ -44,7 +45,7 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
</div> </div>
) : ( ) : (
<div className="w-full text-end"> <div className="w-full text-end">
<div className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-black bg-input font-normal rounded-tr-none"> <div className="inline-block w-fit max-w-[280px] overflow-hidden rounded-xl rounded-tr-none bg-input p-3 px-5 text-start text-sm font-normal text-black">
{chat.message} {chat.message}
</div> </div>
</div> </div>

View file

@ -32,14 +32,14 @@ export default function ChatTrigger({ open, setOpen, isBuilt }) {
> >
<div className="absolute bottom-4 right-3"> <div className="absolute bottom-4 right-3">
<div <div
className="flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-round-btn-shadow hover:shadow-round-btn-shadow className="align-center shadow-round-btn-shadow hover:shadow-round-btn-shadow flex h-12 w-12 cursor-pointer justify-center rounded-full bg-border px-3
bg-border cursor-pointer" py-1 shadow-md"
onClick={handleClick} onClick={handleClick}
> >
<button> <button>
<div className="flex gap-3"> <div className="flex gap-3">
<MessagesSquare <MessagesSquare
className="pth-6 w-6 fill-chat-trigger stroke-1 stroke-chat-trigger" className="pth-6 w-6 fill-chat-trigger stroke-chat-trigger stroke-1"
style={{ color: "white" }} style={{ color: "white" }}
strokeWidth={1.5} strokeWidth={1.5}
/> />

View file

@ -12,7 +12,9 @@ export default function CodeAreaComponent({
disabled, disabled,
editNode = false, editNode = false,
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value); const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value)
);
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
@ -22,16 +24,16 @@ export default function CodeAreaComponent({
}, [disabled, onChange]); }, [disabled, onChange]);
useEffect(() => { useEffect(() => {
setMyValue(value); setMyValue(typeof value == "string" ? value : JSON.stringify(value));
}, [value]); }, [value]);
return ( return (
<div <div
className={ className={
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full" disabled ? "pointer-events-none w-full cursor-not-allowed" : "w-full"
} }
> >
<div className="w-full flex items-center"> <div className="flex w-full items-center">
<span <span
onClick={() => { onClick={() => {
openPopUp( openPopUp(
@ -46,9 +48,9 @@ export default function CodeAreaComponent({
}} }}
className={ className={
editNode editNode
? "truncate cursor-pointer placeholder:text-center text-ring block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring border-1 shadow-sm text-sm bg-transparent sm:text-sm" + ? "form-input block w-full cursor-pointer truncate rounded-md border border-ring bg-transparent pb-0.5 pt-0.5 text-sm text-ring shadow-sm placeholder:text-center sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "truncate block w-full text-ring px-3 py-2 rounded-md border border-ring shadow-sm sm:text-sm placeholder:text-muted-foreground" + : "block w-full truncate rounded-md border border-ring px-3 py-2 text-ring shadow-sm placeholder:text-muted-foreground sm:text-sm" +
INPUT_STYLE + INPUT_STYLE +
(disabled ? " bg-input" : "") (disabled ? " bg-input" : "")
} }

View file

@ -1,9 +1,11 @@
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import { Fragment, useEffect, useState } from "react"; import { Fragment, useContext, useEffect, useState } from "react";
import { DropDownComponentType } from "../../types/components"; import { DropDownComponentType } from "../../types/components";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { INPUT_STYLE } from "../../constants"; import { INPUT_STYLE } from "../../constants";
import { ChevronsUpDown, Check } from "lucide-react"; import { ChevronsUpDown, Check } from "lucide-react";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
export default function Dropdown({ export default function Dropdown({
value, value,
@ -11,13 +13,17 @@ export default function Dropdown({
onSelect, onSelect,
editNode = false, editNode = false,
numberOfOptions = 0, numberOfOptions = 0,
apiModal = false,
}: DropDownComponentType) { }: DropDownComponentType) {
const { closePopUp } = useContext(PopUpContext);
let [internalValue, setInternalValue] = useState( let [internalValue, setInternalValue] = useState(
value === "" || !value ? "Choose an option" : value value === "" || !value ? "Choose an option" : value
); );
useEffect(() => { useEffect(() => {
setInternalValue(value === "" || !value ? "Choose an option" : value); setInternalValue(value === "" || !value ? "Choose an option" : value);
}, [value]); }, [closePopUp]);
return ( return (
<> <>
@ -34,13 +40,13 @@ export default function Dropdown({
<Listbox.Button <Listbox.Button
className={ className={
editNode editNode
? "relative pr-8 placeholder:text-center block w-full pt-0.5 pb-0.5 form-input rounded-md shadow-sm sm:text-sm border-ring border-1" + ? "form-input relative block w-full rounded-md border pb-0.5 pr-8 pt-0.5 shadow-sm placeholder:text-center sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "ring-1 ring-ring placeholder:text-muted-foreground w-full py-2 pl-3 pr-10 text-left focus-visible:outline-none rounded-md border-ring shadow-sm sm:text-sm bg-background" + : "w-full rounded-md border py-2 pl-3 pr-10 text-left shadow-sm placeholder:text-muted-foreground focus-visible:outline-none sm:text-sm" +
INPUT_STYLE INPUT_STYLE
} }
> >
<span className="block bg-background truncate w-full"> <span className="block w-full truncate bg-background">
{internalValue} {internalValue}
</span> </span>
<span <span
@ -63,11 +69,12 @@ export default function Dropdown({
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options <Listbox.Options
className={ className={classNames(
editNode editNode
? "absolute z-10 mt-1 max-h-60 overflow-auto rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm w-[215px]" ? "z-10 mt-1 max-h-60 w-[215px] overflow-auto rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
: "nowheel absolute z-10 mt-1 max-h-60 w-full overflow-auto overflow-y rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm " : "nowheel overflow-y z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm ",
} apiModal ? "mb-2 w-[250px]" : "absolute"
)}
> >
{options.map((option, id) => ( {options.map((option, id) => (
<Listbox.Option <Listbox.Option

View file

@ -2,6 +2,7 @@ import { useContext, useEffect, useState } from "react";
import { FloatComponentType } from "../../types/components"; import { FloatComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { INPUT_STYLE } from "../../constants"; import { INPUT_STYLE } from "../../constants";
import { PopUpContext } from "../../contexts/popUpContext";
export default function FloatComponent({ export default function FloatComponent({
value, value,
@ -12,6 +13,7 @@ export default function FloatComponent({
}: FloatComponentType) { }: FloatComponentType) {
const [myValue, setMyValue] = useState(value ?? ""); const [myValue, setMyValue] = useState(value ?? "");
const { setDisableCopyPaste } = useContext(TabsContext); const { setDisableCopyPaste } = useContext(TabsContext);
const { closePopUp } = useContext(PopUpContext);
const step = 0.1; const step = 0.1;
const min = 0; const min = 0;
@ -26,7 +28,7 @@ export default function FloatComponent({
useEffect(() => { useEffect(() => {
setMyValue(value); setMyValue(value);
}, [value]); }, [closePopUp]);
return ( return (
<div <div
@ -56,9 +58,9 @@ export default function FloatComponent({
value={myValue} value={myValue}
className={ className={
editNode editNode
? "focus:placeholder-transparent text-center placeholder:text-center border-1 block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring shadow-sm sm:text-sm placeholder:text-muted-foreground" + ? "form-input block w-full rounded-md border border-border pb-0.5 pt-0.5 text-center shadow-sm placeholder:text-center placeholder:text-muted-foreground focus:placeholder-transparent sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "focus:placeholder-transparent block w-full form-input placeholder:text-muted-foreground bg-background rounded-md border-ring shadow-sm ring-offset-input sm:text-sm" + : "form-input block w-full rounded-md border-border bg-background shadow-sm ring-offset-input placeholder:text-muted-foreground focus:placeholder-transparent sm:text-sm" +
INPUT_STYLE + INPUT_STYLE +
(disabled ? " bg-input" : "") (disabled ? " bg-input" : "")
} }

View file

@ -47,20 +47,20 @@ export const MenuBar = ({ flows, tabId }) => {
let current_flow = flows.find((flow) => flow.id === tabId); let current_flow = flows.find((flow) => flow.id === tabId);
return ( return (
<div className="flex gap-2 items-center"> <div className="flex items-center gap-2">
<Link to="/"> <Link to="/">
<ChevronLeft className="w-4" /> <ChevronLeft className="w-4" />
</Link> </Link>
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5"> <div className="flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
className="gap-2 flex items-center max-w-[200px]" className="flex max-w-[200px] items-center gap-2"
variant="primary" variant="primary"
size="sm" size="sm"
> >
<div className="truncate flex-1">{current_flow.name}</div> <div className="flex-1 truncate">{current_flow.name}</div>
<ChevronDown className="w-4 h-4" /> <ChevronDown className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-44"> <DropdownMenuContent className="w-44">
@ -71,7 +71,7 @@ export const MenuBar = ({ flows, tabId }) => {
}} }}
className="cursor-pointer" className="cursor-pointer"
> >
<Plus className="w-4 h-4 mr-2" /> <Plus className="mr-2 h-4 w-4" />
New New
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@ -80,7 +80,7 @@ export const MenuBar = ({ flows, tabId }) => {
}} }}
className="cursor-pointer" className="cursor-pointer"
> >
<Settings2 className="w-4 h-4 mr-2 " /> <Settings2 className="mr-2 h-4 w-4 " />
Settings Settings
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@ -89,7 +89,7 @@ export const MenuBar = ({ flows, tabId }) => {
}} }}
className="cursor-pointer" className="cursor-pointer"
> >
<Undo className="w-4 h-4 mr-2 " /> <Undo className="mr-2 h-4 w-4 " />
Undo Undo
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@ -98,7 +98,7 @@ export const MenuBar = ({ flows, tabId }) => {
}} }}
className="cursor-pointer" className="cursor-pointer"
> >
<Redo className="w-4 h-4 mr-2 " /> <Redo className="mr-2 h-4 w-4 " />
Redo Redo
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />

View file

@ -36,23 +36,23 @@ export default function Header() {
fetchStars(); fetchStars();
}, []); }, []);
return ( return (
<div className="w-full h-12 flex justify-between items-center border-b bg-muted"> <div className="flex h-12 w-full items-center justify-between border-b bg-muted">
<div className="flex gap-2 justify-start items-center w-96"> <div className="flex w-96 items-center justify-start gap-2">
<Link to="/"> <Link to="/">
<span className="text-2xl ml-4">⛓️</span> <span className="ml-4 text-2xl">⛓️</span>
</Link> </Link>
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar flows={flows} tabId={tabId} /> <MenuBar flows={flows} tabId={tabId} />
)} )}
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex items-center gap-2">
<Link to="/"> <Link to="/">
<Button <Button
className="gap-2" className="gap-2"
variant={location.pathname === "/" ? "primary" : "secondary"} variant={location.pathname === "/" ? "primary" : "secondary"}
size="sm" size="sm"
> >
<Home className="w-4 h-4" /> <Home className="h-4 w-4" />
<div className="flex-1">{USER_PROJECTS_HEADER}</div> <div className="flex-1">{USER_PROJECTS_HEADER}</div>
</Button> </Button>
</Link> </Link>
@ -64,22 +64,22 @@ export default function Header() {
} }
size="sm" size="sm"
> >
<Users2 className="w-4 h-4" /> <Users2 className="h-4 w-4" />
<div className="flex-1">Community Examples</div> <div className="flex-1">Community Examples</div>
</Button> </Button>
</Link> </Link>
</div> </div>
<div className="flex justify-end px-2 w-96"> <div className="flex w-96 justify-end px-2">
<div className="ml-auto mr-2 flex gap-5 items-center"> <div className="ml-auto mr-2 flex items-center gap-5">
<a <a
href="https://github.com/logspace-ai/langflow" href="https://github.com/logspace-ai/langflow"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="inline-flex shadow-sm items-center justify-center text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-muted-foreground border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md" className="inline-flex shadow-sm items-center justify-center text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-muted-foreground border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md"
> >
<FaGithub className="h-5 w-5 mr-2" /> <FaGithub className="mr-2 h-5 w-5" />
Star Star
<div className="ml-2 flex text-sm bg-background rounded-md rounded-l-none border px-2 h-9 -mr-px items-center justify-center"> <div className="-mr-px ml-2 flex h-9 items-center justify-center rounded-md rounded-l-none border bg-background px-2 text-sm">
{stars} {stars}
</div> </div>
</a> </a>
@ -123,18 +123,18 @@ export default function Header() {
openPopUp( openPopUp(
<> <>
<div <div
className="z-10 absolute" className="absolute z-10"
style={{ top: top + 34, left: left - AlertWidth }} style={{ top: top + 34, left: left - AlertWidth }}
> >
<AlertDropdown /> <AlertDropdown />
</div> </div>
<div className="h-screen w-screen fixed top-0 left-0"></div> <div className="fixed left-0 top-0 h-screen w-screen"></div>
</> </>
); );
}} }}
> >
{notificationCenter && ( {notificationCenter && (
<div className="absolute w-1.5 h-1.5 rounded-full bg-destructive right-[3px]"></div> <div className="absolute right-[3px] h-1.5 w-1.5 rounded-full bg-destructive"></div>
)} )}
<Bell className="h-5 w-5" aria-hidden="true" /> <Bell className="h-5 w-5" aria-hidden="true" />
</button> </button>

View file

@ -33,7 +33,7 @@ export default function InputComponent({
<div <div
className={ className={
disabled disabled
? "relative pointer-events-none cursor-not-allowed" ? "pointer-events-none relative cursor-not-allowed"
: "relative" : "relative"
} }
> >
@ -46,11 +46,11 @@ export default function InputComponent({
if (disableCopyPaste) setDisableCopyPaste(false); if (disableCopyPaste) setDisableCopyPaste(false);
}} }}
className={classNames( className={classNames(
"block w-full pr-12 form-input rounded-md bg-background border-ring shadow-sm sm:text-sm focus:placeholder-transparent placeholder:text-muted-foreground", "form-input block w-full rounded-md border-ring bg-background pr-12 shadow-sm placeholder:text-muted-foreground focus:placeholder-transparent sm:text-sm",
disabled ? " bg-input" : "", disabled ? " bg-input" : "",
password && !pwdVisible && myValue !== "" ? "password" : "", password && !pwdVisible && myValue !== "" ? "password" : "",
editNode editNode
? "border-1 block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring shadow-sm sm:text-sm text-center" + ? "form-input block w-full rounded-md border border-ring pb-0.5 pt-0.5 text-center shadow-sm sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "ring-offset-input" + INPUT_STYLE, : "ring-offset-input" + INPUT_STYLE,
password && editNode ? "pr-8" : "pr-3" password && editNode ? "pr-8" : "pr-3"
@ -65,7 +65,7 @@ export default function InputComponent({
<button <button
className={classNames( className={classNames(
editNode editNode
? "absolute inset-y-0 right-0 pr-2 items-center text-muted-foreground" ? "absolute inset-y-0 right-0 items-center pr-2 text-muted-foreground"
: "absolute inset-y-0 right-0 items-center px-4 text-muted-foreground" : "absolute inset-y-0 right-0 items-center px-4 text-muted-foreground"
)} )}
onClick={() => { onClick={() => {
@ -82,8 +82,8 @@ export default function InputComponent({
stroke="currentColor" stroke="currentColor"
className={classNames( className={classNames(
editNode editNode
? "w-5 h-5 absolute bottom-0.5 right-2" ? "absolute bottom-0.5 right-2 h-5 w-5"
: "w-5 h-5 absolute bottom-2 right-3" : "absolute bottom-2 right-3 h-5 w-5"
)} )}
> >
<path <path
@ -101,8 +101,8 @@ export default function InputComponent({
stroke="currentColor" stroke="currentColor"
className={classNames( className={classNames(
editNode editNode
? "w-5 h-5 absolute bottom-0.5 right-2" ? "absolute bottom-0.5 right-2 h-5 w-5"
: "w-5 h-5 absolute bottom-2 right-3" : "absolute bottom-2 right-3 h-5 w-5"
)} )}
> >
<path <path

View file

@ -95,17 +95,17 @@ export default function InputFileComponent({
return ( return (
<div <div
className={ className={
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full" disabled ? "pointer-events-none w-full cursor-not-allowed" : "w-full"
} }
> >
<div className="w-full flex items-center gap-2"> <div className="flex w-full items-center">
<span <span
onClick={handleButtonClick} onClick={handleButtonClick}
className={ className={
editNode editNode
? "truncate placeholder:text-center text-ring block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring shadow-sm sm:text-sm border-1" + ? "form-input block w-full truncate rounded-md border border-ring pb-0.5 pt-0.5 text-ring shadow-sm placeholder:text-center sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "truncate block w-full text-ring px-3 py-2 rounded-md border border-ring shadow-sm sm:text-sm" + : "block w-full truncate rounded-md border border-ring px-3 py-2 text-ring shadow-sm sm:text-sm" +
INPUT_STYLE + INPUT_STYLE +
(disabled ? " bg-input" : "") (disabled ? " bg-input" : "")
} }
@ -117,7 +117,7 @@ export default function InputFileComponent({
<FileSearch2 strokeWidth={1.5} className="w-6 h-6 hover:text-accent-foreground" /> <FileSearch2 strokeWidth={1.5} className="w-6 h-6 hover:text-accent-foreground" />
)} )}
{!editNode && loading && ( {!editNode && loading && (
<span className="loading loading-spinner loading-sm pl-3 h-8 pointer-events-none"></span> <span className="loading loading-spinner loading-sm pointer-events-none h-8 pl-3"></span>
)} )}
</button> </button>
</div> </div>

View file

@ -5,20 +5,29 @@ import { TabsContext } from "../../contexts/tabsContext";
import _ from "lodash"; import _ from "lodash";
import { INPUT_STYLE } from "../../constants"; import { INPUT_STYLE } from "../../constants";
import { X, Plus } from "lucide-react"; import { X, Plus } from "lucide-react";
import { PopUpContext } from "../../contexts/popUpContext";
export default function InputListComponent({ export default function InputListComponent({
value, value,
onChange, onChange,
disabled, disabled,
editNode = false, editNode = false,
onAddInput,
}: InputListComponentType) { }: InputListComponentType) {
const [inputList, setInputList] = useState(value ?? [""]); const [inputList, setInputList] = useState(value ?? [""]);
const { closePopUp } = useContext(PopUpContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
setInputList([""]); setInputList([""]);
onChange([""]); onChange([""]);
} }
}, [disabled, onChange]); }, [disabled, onChange]);
useEffect(() => {
setInputList(value);
}, [closePopUp]);
return ( return (
<div <div
className={ className={
@ -27,15 +36,15 @@ export default function InputListComponent({
} }
> >
{inputList.map((i, idx) => ( {inputList.map((i, idx) => (
<div key={idx} className="w-full flex gap-3"> <div key={idx} className="flex w-full gap-3">
<input <input
type="text" type="text"
value={i} value={i}
className={ className={
editNode editNode
? "border-[1px] truncate cursor-pointer text-center placeholder:text-center text-ring block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring shadow-sm sm:text-sm" + ? "form-input block w-full cursor-pointer truncate rounded-md border-[1px] border-ring pb-0.5 pt-0.5 text-center text-ring shadow-sm placeholder:text-center sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "block w-full form-input bg-background rounded-md border-ring shadow-sm focus:border-ring focus:ring-ring sm:text-sm" + : "form-input block w-full rounded-md border-ring bg-background shadow-sm focus:border-ring focus:ring-ring sm:text-sm" +
(disabled ? " bg-input" : "") + (disabled ? " bg-input" : "") +
"focus:placeholder-transparent" "focus:placeholder-transparent"
} }
@ -44,9 +53,9 @@ export default function InputListComponent({
setInputList((old) => { setInputList((old) => {
let newInputList = _.cloneDeep(old); let newInputList = _.cloneDeep(old);
newInputList[idx] = e.target.value; newInputList[idx] = e.target.value;
onChange(newInputList);
return newInputList; return newInputList;
}); });
onChange(inputList);
}} }}
/> />
{idx === inputList.length - 1 ? ( {idx === inputList.length - 1 ? (
@ -55,6 +64,7 @@ export default function InputListComponent({
setInputList((old) => { setInputList((old) => {
let newInputList = _.cloneDeep(old); let newInputList = _.cloneDeep(old);
newInputList.push(""); newInputList.push("");
onAddInput(newInputList);
return newInputList; return newInputList;
}); });
onChange(inputList); onChange(inputList);
@ -68,12 +78,13 @@ export default function InputListComponent({
setInputList((old) => { setInputList((old) => {
let newInputList = _.cloneDeep(old); let newInputList = _.cloneDeep(old);
newInputList.splice(idx, 1); newInputList.splice(idx, 1);
onAddInput(newInputList);
return newInputList; return newInputList;
}); });
onChange(inputList); onChange(inputList);
}} }}
> >
<X className="w-4 h-4 hover:text-status-red" /> <X className="h-4 w-4 hover:text-status-red" />
</button> </button>
)} )}
</div> </div>

View file

@ -3,6 +3,7 @@ import { FloatComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { INPUT_STYLE } from "../../constants"; import { INPUT_STYLE } from "../../constants";
import { PopUpContext } from "../../contexts/popUpContext";
export default function IntComponent({ export default function IntComponent({
value, value,
@ -14,6 +15,7 @@ export default function IntComponent({
const [myValue, setMyValue] = useState(value ?? ""); const [myValue, setMyValue] = useState(value ?? "");
const { setDisableCopyPaste } = useContext(TabsContext); const { setDisableCopyPaste } = useContext(TabsContext);
const min = 0; const min = 0;
const { closePopUp } = useContext(PopUpContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
@ -24,13 +26,13 @@ export default function IntComponent({
useEffect(() => { useEffect(() => {
setMyValue(value); setMyValue(value);
}, [value]); }, [closePopUp]);
return ( return (
<div <div
className={ className={
"w-full " + "w-full " +
(disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full") (disabled ? "pointer-events-none w-full cursor-not-allowed" : "w-full")
} }
> >
<input <input
@ -70,9 +72,9 @@ export default function IntComponent({
value={myValue} value={myValue}
className={ className={
editNode editNode
? "focus:placeholder-transparent text-center placeholder:text-center border-1 block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring shadow-sm sm:text-sm placeholder:text-muted-foreground" + ? "form-input block w-full rounded-md border pb-0.5 pt-0.5 text-center shadow-sm placeholder:text-center placeholder:text-muted-foreground focus:placeholder-transparent sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "focus:placeholder-transparent block w-full form-input bg-background rounded-md border-ring shadow-sm ring-offset-background sm:text-sm placeholder:text-muted-foreground" + : "form-input block w-full rounded-md bg-background shadow-sm ring-offset-background placeholder:text-muted-foreground focus:placeholder-transparent sm:text-sm" +
INPUT_STYLE + INPUT_STYLE +
(disabled ? " bg-input" : "") (disabled ? " bg-input" : "")
} }

View file

@ -4,10 +4,10 @@ type LoadingComponentProps = {
export default function LoadingComponent({ remSize }: LoadingComponentProps) { export default function LoadingComponent({ remSize }: LoadingComponentProps) {
return ( return (
<div role="status" className="w-min m-auto"> <div role="status" className="m-auto w-min">
<svg <svg
aria-hidden="true" aria-hidden="true"
className={`w-${remSize} h-${remSize} mr-2 text-muted animate-spin fill-almost-medium-blue`} className={`w-${remSize} h-${remSize} mr-2 animate-spin fill-almost-medium-blue text-muted`}
viewBox="0 0 100 101" viewBox="0 0 100 101"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -22,7 +22,7 @@ export default function LoadingComponent({ remSize }: LoadingComponentProps) {
/> />
</svg> </svg>
<br></br> <br></br>
<span className="animate-pulse text-almost-medium-blue text-lg"> <span className="animate-pulse text-lg text-almost-medium-blue">
Loading... Loading...
</span> </span>
</div> </div>

View file

@ -28,7 +28,7 @@ export default function PromptAreaComponent({
return ( return (
<div <div
className={ className={
disabled ? "pointer-events-none cursor-not-allowed w-full" : " w-full" disabled ? "pointer-events-none w-full cursor-not-allowed" : " w-full"
} }
> >
<div className="w-full flex items-center"> <div className="w-full flex items-center">
@ -51,7 +51,7 @@ export default function PromptAreaComponent({
editNode editNode
? "cursor-pointer truncate placeholder:text-center text-ring border-1 block w-full pt-0.5 pb-0.5 form-input text-sm rounded-md border-ring shadow-sm sm:text-sm" + ? "cursor-pointer truncate placeholder:text-center text-ring border-1 block w-full pt-0.5 pb-0.5 form-input text-sm rounded-md border-ring shadow-sm sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "truncate block w-full text-ring px-3 py-2 rounded-md border border-ring shadow-sm sm:text-sm" + : "block w-full truncate rounded-md border border-ring px-3 py-2 text-ring shadow-sm sm:text-sm" +
(disabled ? " bg-input" : "") (disabled ? " bg-input" : "")
} }
> >

View file

@ -52,9 +52,9 @@ export default function TextAreaComponent({
}} }}
className={ className={
editNode editNode
? "truncate cursor-pointer placeholder:text-center text-ring border-1 block w-full pt-0.5 pb-0.5 form-input rounded-md border-ring bg-transparent shadow-sm sm:text-sm" + ? "form-input block w-full cursor-pointer truncate rounded-md border border-ring bg-transparent pb-0.5 pt-0.5 text-ring shadow-sm placeholder:text-center sm:text-sm" +
INPUT_STYLE INPUT_STYLE
: "truncate block w-full text-ring px-3 py-2 rounded-md border border-ring shadow-sm sm:text-sm" + : "block w-full truncate rounded-md border border-ring px-3 py-2 text-ring shadow-sm sm:text-sm" +
(disabled ? " bg-input" : "") (disabled ? " bg-input" : "")
} }
> >

View file

@ -36,8 +36,8 @@ export default function ToggleComponent({
<span <span
className={classNames( className={classNames(
enabled enabled
? "opacity-0 ease-out duration-100" ? "opacity-0 duration-100 ease-out"
: "opacity-100 ease-in duration-200", : "opacity-100 duration-200 ease-in",
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" "absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
)} )}
aria-hidden="true" aria-hidden="true"
@ -45,8 +45,8 @@ export default function ToggleComponent({
<span <span
className={classNames( className={classNames(
enabled enabled
? "opacity-100 ease-in duration-200" ? "opacity-100 duration-200 ease-in"
: "opacity-0 ease-out duration-100", : "opacity-0 duration-100 ease-out",
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" "absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
)} )}
aria-hidden="true" aria-hidden="true"

View file

@ -0,0 +1,59 @@
"use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { cn } from "../../utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
className
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View file

@ -8,7 +8,7 @@ const Card = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-lg flex flex-col justify-between border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-all", "flex flex-col justify-between rounded-lg border bg-card text-card-foreground shadow-sm transition-all hover:shadow-lg",
className className
)} )}
{...props} {...props}

View file

@ -146,7 +146,7 @@ const DropdownMenuLabel = React.forwardRef<
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"px-2 pl-2 py-1.5 text-sm font-semibold", "px-2 py-1.5 pl-2 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className className
)} )}

View file

@ -57,7 +57,7 @@ export default function RenameLabel(props) {
ref={inputRef} ref={inputRef}
onInput={resizeInput} onInput={resizeInput}
className={cn( className={cn(
"px-2 bg-transparent focus:border-none active:outline hover:outline focus:outline outline-ring rounded-md", "rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
props.className props.className
)} )}
onBlur={() => { onBlur={() => {
@ -74,7 +74,7 @@ export default function RenameLabel(props) {
) : ( ) : (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
className={cn("px-2 text-left truncate", props.className)} className={cn("truncate px-2 text-left", props.className)}
onDoubleClick={() => { onDoubleClick={() => {
setIsRename(true); setIsRename(true);
setMyValue(props.value); setMyValue(props.value);

View file

@ -28,7 +28,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-secondary/80 data-[state=active]:border data-[state=inactive]:border data-[state=inactive]:border-muted", "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:border data-[state=inactive]:border data-[state=inactive]:border-muted data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-secondary/80",
className className
)} )}
{...props} {...props}

View file

@ -54,7 +54,7 @@ export const TEXT_DIALOG_SUBTITLE = "Edit your text.";
* @param {string} flowId - The id of the flow * @param {string} flowId - The id of the flow
* @returns {string} - The python code * @returns {string} - The python code
*/ */
export const getPythonApiCode = (flow: FlowType): string => { export const getPythonApiCode = (flow: FlowType, tweak?): string => {
const flowId = flow.id; const flowId = flow.id;
// create a dictionary of node ids and the values is an empty dictionary // create a dictionary of node ids and the values is an empty dictionary
@ -70,7 +70,11 @@ BASE_API_URL = "${window.location.protocol}//${
FLOW_ID = "${flowId}" FLOW_ID = "${flowId}"
# You can tweak the flow by adding a tweaks dictionary # You can tweak the flow by adding a tweaks dictionary
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}} # e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
TWEAKS = ${JSON.stringify(tweaks, null, 2)} TWEAKS = ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict: def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
""" """
@ -100,7 +104,7 @@ print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))`;
* @param {string} flowId - The id of the flow * @param {string} flowId - The id of the flow
* @returns {string} - The curl code * @returns {string} - The curl code
*/ */
export const getCurlCode = (flow: FlowType): string => { export const getCurlCode = (flow: FlowType, tweak?): string => {
const flowId = flow.id; const flowId = flow.id;
const tweaks = buildTweaks(flow); const tweaks = buildTweaks(flow);
return `curl -X POST \\ return `curl -X POST \\
@ -108,27 +112,46 @@ export const getCurlCode = (flow: FlowType): string => {
window.location.host window.location.host
}/api/v1/process/${flowId} \\ }/api/v1/process/${flowId} \\
-H 'Content-Type: application/json' \\ -H 'Content-Type: application/json' \\
-d '{"inputs": {"input": message}, "tweaks": ${JSON.stringify( -d '{"inputs": {"input": message}, "tweaks": ${
tweaks, tweak && tweak.length > 0
null, ? buildTweakObject(tweak)
2 : JSON.stringify(tweaks, null, 2)
)}}'`; }}'`;
}; };
/** /**
* Function to get the python code for the API * Function to get the python code for the API
* @param {string} flowName - The name of the flow * @param {string} flowName - The name of the flow
* @returns {string} - The python code * @returns {string} - The python code
*/ */
export const getPythonCode = (flow: FlowType): string => { export const getPythonCode = (flow: FlowType, tweak?): string => {
const flowName = flow.name; const flowName = flow.name;
const tweaks = buildTweaks(flow); const tweaks = buildTweaks(flow);
return `from langflow import load_flow_from_json return `from langflow import load_flow_from_json
TWEAKS = ${JSON.stringify(tweaks, null, 2)} TWEAKS = ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS) flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS)
# Now you can use it like any chain # Now you can use it like any chain
flow("Hey, have you heard of LangFlow?")`; flow("Hey, have you heard of LangFlow?")`;
}; };
function buildTweakObject(tweak) {
tweak.forEach((el) => {
Object.keys(el).forEach((key) => {
for (let kp in el[key]) {
try {
el[key][kp] = JSON.parse(el[key][kp]);
} catch {}
}
});
});
const tweakString = JSON.stringify(tweak, null, 2);
return tweakString;
}
/** /**
* The base text for subtitle of Import Dialog * The base text for subtitle of Import Dialog
* @constant * @constant
@ -148,7 +171,7 @@ export const EXPORT_CODE_DIALOG =
* @constant * @constant
*/ */
export const INPUT_STYLE = export const INPUT_STYLE =
"focus:tw-ring-none focus-visible:outline-none focus:ring-ring bg-background focus:outline-none"; " focus:tw-ring-none border-border focus-visible:outline-none focus:ring-ring focus:ring-1 bg-background focus:outline-none";
/** /**
* The base text for subtitle of code dialog * The base text for subtitle of code dialog
@ -171,257 +194,6 @@ export const NAV_DISPLAY_STYLE =
export const BUTTON_DIV_STYLE = " flex gap-2 "; export const BUTTON_DIV_STYLE = " flex gap-2 ";
(" focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none "); (" focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none ");
/**
* Default description for the flow
* @constant
*/
export const DESCRIPTIONS: string[] = [
"Chain the Words, Master Language!",
"Language Architect at Work!",
"Empowering Language Engineering.",
"Craft Language Connections Here.",
"Create, Connect, Converse.",
"Smart Chains, Smarter Conversations.",
"Bridging Prompts for Brilliance.",
"Language Models, Unleashed.",
"Your Hub for Text Generation.",
"Promptly Ingenious!",
"Building Linguistic Labyrinths.",
"LangFlow: Create, Chain, Communicate.",
"Connect the Dots, Craft Language.",
"Interactive Language Weaving.",
"Generate, Innovate, Communicate.",
"Conversation Catalyst Engine.",
"Language Chainlink Master.",
"Design Dialogues with LangFlow.",
"Nurture NLP Nodes Here.",
"Conversational Cartography Unlocked.",
"Design, Develop, Dialogize.",
];
/**
* Adjectives for the name of the flow
* @constant
*
*/
export const ADJECTIVES: string[] = [
"admiring",
"adoring",
"agitated",
"amazing",
"angry",
"awesome",
"backstabbing",
"berserk",
"big",
"boring",
"clever",
"cocky",
"compassionate",
"condescending",
"cranky",
"desperate",
"determined",
"distracted",
"dreamy",
"drunk",
"ecstatic",
"elated",
"elegant",
"evil",
"fervent",
"focused",
"furious",
"gigantic",
"gloomy",
"goofy",
"grave",
"happy",
"high",
"hopeful",
"hungry",
"insane",
"jolly",
"jovial",
"kickass",
"lonely",
"loving",
"mad",
"modest",
"naughty",
"nauseous",
"nostalgic",
"pedantic",
"pensive",
"prickly",
"reverent",
"romantic",
"sad",
"serene",
"sharp",
"sick",
"silly",
"sleepy",
"small",
"stoic",
"stupefied",
"suspicious",
"tender",
"thirsty",
"tiny",
"trusting",
];
/**
* Nouns for the name of the flow
* @constant
*
*/
export const NOUNS: string[] = [
"albattani",
"allen",
"almeida",
"archimedes",
"ardinghelli",
"aryabhata",
"austin",
"babbage",
"banach",
"bardeen",
"bartik",
"bassi",
"bell",
"bhabha",
"bhaskara",
"blackwell",
"bohr",
"booth",
"borg",
"bose",
"boyd",
"brahmagupta",
"brattain",
"brown",
"carson",
"chandrasekhar",
"colden",
"cori",
"cray",
"curie",
"darwin",
"davinci",
"dijkstra",
"dubinsky",
"easley",
"einstein",
"elion",
"engelbart",
"euclid",
"euler",
"fermat",
"fermi",
"feynman",
"franklin",
"galileo",
"gates",
"goldberg",
"goldstine",
"goldwasser",
"golick",
"goodall",
"hamilton",
"hawking",
"heisenberg",
"heyrovsky",
"hodgkin",
"hoover",
"hopper",
"hugle",
"hypatia",
"jang",
"jennings",
"jepsen",
"joliot",
"jones",
"kalam",
"kare",
"keller",
"khorana",
"kilby",
"kirch",
"knuth",
"kowalevski",
"lalande",
"lamarr",
"leakey",
"leavitt",
"lichterman",
"liskov",
"lovelace",
"lumiere",
"mahavira",
"mayer",
"mccarthy",
"mcclintock",
"mclean",
"mcnulty",
"meitner",
"meninsky",
"mestorf",
"minsky",
"mirzakhani",
"morse",
"murdock",
"newton",
"nobel",
"noether",
"northcutt",
"noyce",
"panini",
"pare",
"pasteur",
"payne",
"perlman",
"pike",
"poincare",
"poitras",
"ptolemy",
"raman",
"ramanujan",
"ride",
"ritchie",
"roentgen",
"rosalind",
"saha",
"sammet",
"shaw",
"shirley",
"shockley",
"sinoussi",
"snyder",
"spence",
"stallman",
"stonebraker",
"swanson",
"swartz",
"swirles",
"tesla",
"thompson",
"torvalds",
"turing",
"varahamihira",
"visvesvaraya",
"volhard",
"wescoff",
"williams",
"wilson",
"wing",
"wozniak",
"wright",
"yalow",
"yonath",
];
/** /**
* Header text for user projects * Header text for user projects
* @constant * @constant

View file

@ -78,9 +78,9 @@ export function AlertProvider({ children }: { children: ReactNode }) {
* @param newState An object containing the new error data, including title and optional list of error messages * @param newState An object containing the new error data, including title and optional list of error messages
*/ */
function setErrorData(newState: { title: string; list?: Array<string> }) { function setErrorData(newState: { title: string; list?: Array<string> }) {
setErrorDataState(newState);
setErrorOpen(true);
if (newState.title && newState.title !== "") { if (newState.title && newState.title !== "") {
setErrorDataState(newState);
setErrorOpen(true);
setNotificationCenter(true); setNotificationCenter(true);
pushNotificationList({ pushNotificationList({
type: "error", type: "error",
@ -95,9 +95,9 @@ export function AlertProvider({ children }: { children: ReactNode }) {
* @param newState An object containing the title of the notice and optionally a link. * @param newState An object containing the title of the notice and optionally a link.
*/ */
function setNoticeData(newState: { title: string; link?: string }) { function setNoticeData(newState: { title: string; link?: string }) {
setNoticeDataState(newState);
setNoticeOpen(true);
if (newState.title && newState.title !== "") { if (newState.title && newState.title !== "") {
setNoticeDataState(newState);
setNoticeOpen(true);
// Add new notice to notification center // Add new notice to notification center
setNotificationCenter(true); setNotificationCenter(true);
pushNotificationList({ pushNotificationList({
@ -113,11 +113,10 @@ export function AlertProvider({ children }: { children: ReactNode }) {
* @param newState - A state object with a "title" property to set in the success data state. * @param newState - A state object with a "title" property to set in the success data state.
*/ */
function setSuccessData(newState: { title: string }) { function setSuccessData(newState: { title: string }) {
setSuccessDataState(newState); // update the success data state with the provided new state
setSuccessOpen(true); // open the success alert
// If the new state has a "title" property, add a new success notification to the list // If the new state has a "title" property, add a new success notification to the list
if (newState.title && newState.title !== "") { if (newState.title && newState.title !== "") {
setSuccessDataState(newState); // update the success data state with the provided new state
setSuccessOpen(true); // open the success alert
setNotificationCenter(true); // show the notification center setNotificationCenter(true); // show the notification center
pushNotificationList({ pushNotificationList({
// add the new notification to the list // add the new notification to the list

View file

@ -5,6 +5,8 @@ import React, { useState } from "react";
export const PopUpContext = createContext({ export const PopUpContext = createContext({
openPopUp: (popUpElement: JSX.Element) => {}, openPopUp: (popUpElement: JSX.Element) => {},
closePopUp: () => {}, closePopUp: () => {},
setCloseEdit: (value: string) => {},
closeEdit: "",
}); });
interface PopUpProviderProps { interface PopUpProviderProps {
@ -22,8 +24,12 @@ const PopUpProvider = ({ children }: PopUpProviderProps) => {
setPopUpElements((prevPopUps) => prevPopUps.slice(1)); setPopUpElements((prevPopUps) => prevPopUps.slice(1));
}; };
const [closeEdit, setCloseEdit] = useState("");
return ( return (
<PopUpContext.Provider value={{ openPopUp, closePopUp }}> <PopUpContext.Provider
value={{ openPopUp, closePopUp, closeEdit, setCloseEdit }}
>
{children} {children}
{popUpElements[0]} {popUpElements[0]}
</PopUpContext.Provider> </PopUpContext.Provider>

View file

@ -53,6 +53,8 @@ const TabsContextInitialValue: TabsContextType = {
tabsState: {}, tabsState: {},
setTabsState: (state: TabsState) => {}, setTabsState: (state: TabsState) => {},
getNodeId: (nodeType: string) => "", getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {},
getTweak: {},
paste: ( paste: (
selection: { nodes: any; edges: any }, selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number } position: { x: number; y: number; paneX?: number; paneY?: number }
@ -73,6 +75,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const { templates, reactFlowInstance } = useContext(typesContext); const { templates, reactFlowInstance } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null); const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const [tabsState, setTabsState] = useState<TabsState>({}); const [tabsState, setTabsState] = useState<TabsState>({});
const [getTweak, setTweak] = useState({});
const newNodeId = useRef(uid()); const newNodeId = useRef(uid());
function incrementNodeId() { function incrementNodeId() {
@ -198,10 +201,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
edge.style = { stroke: "#555555" }; edge.style = { stroke: "#555555" };
}); });
} }
function updateDisplay_name(node: NodeType, template: APIClassType) { function updateDisplay_name(node: NodeType, template: APIClassType) {
node.data.node.display_name = template["display_name"] node.data.node.display_name = template["display_name"] || node.data.type;
? template["display_name"] }
: node.data.type;
function updateNodeDocumentation(node: NodeType, template: APIClassType) {
node.data.node.documentation = template["documentation"];
} }
function processFlowNodes(flow) { function processFlowNodes(flow) {
@ -218,6 +224,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
updateNodeEdges(flow, node, template); updateNodeEdges(flow, node, template);
updateNodeDescription(node, template); updateNodeDescription(node, template);
updateNodeTemplate(node, template); updateNodeTemplate(node, template);
updateNodeDocumentation(node, template);
} }
}); });
} }
@ -268,16 +275,20 @@ export function TabsProvider({ children }: { children: ReactNode }) {
/** /**
* Downloads the current flow as a JSON file * Downloads the current flow as a JSON file
*/ */
function downloadFlow(flow: FlowType, fileName?: string) { function downloadFlow(
flow: FlowType,
flowName: string,
flowDescription?: string
) {
// create a data URI with the current flow data // create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flow) JSON.stringify({ ...flow, name: flowName, description: flowDescription })
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
const link = document.createElement("a"); const link = document.createElement("a");
link.href = jsonString; link.href = jsonString;
link.download = `${fileName && fileName != "" ? fileName : flows.find((f) => f.id === tabId).name}.json`; link.download = `${flowName && flowName != "" ? flowName : flows.find((f) => f.id === tabId).name}.json`;
// simulate a click on the link element to trigger the download // simulate a click on the link element to trigger the download
link.click(); link.click();
@ -410,7 +421,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
y: insidePosition.y + n.position.y - minimumY, y: insidePosition.y + n.position.y - minimumY,
}, },
data: { data: {
...n.data, ..._.cloneDeep(n.data),
id: newId, id: newId,
}, },
}; };
@ -640,6 +651,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
tabsState, tabsState,
setTabsState, setTabsState,
paste, paste,
getTweak,
setTweak,
}} }}
> >
{children} {children}

View file

@ -311,7 +311,7 @@ export async function getBuildStatus(
export async function postBuildInit( export async function postBuildInit(
flow: FlowType flow: FlowType
): Promise<AxiosResponse<InitTypeAPI>> { ): Promise<AxiosResponse<InitTypeAPI>> {
return await axios.post(`/api/v1/build/init`, flow); return await axios.post(`/api/v1/build/init/${flow.id}`, flow);
} }
// fetch(`/upload/${id}`, { // fetch(`/upload/${id}`, {

View file

@ -0,0 +1,364 @@
/**
* Default description for the flow
* @constant
*/
export const DESCRIPTIONS: string[] = [
"Chain the Words, Master Language!",
"Language Architect at Work!",
"Empowering Language Engineering.",
"Craft Language Connections Here.",
"Create, Connect, Converse.",
"Smart Chains, Smarter Conversations.",
"Bridging Prompts for Brilliance.",
"Language Models, Unleashed.",
"Your Hub for Text Generation.",
"Promptly Ingenious!",
"Building Linguistic Labyrinths.",
"Create, Chain, Communicate.",
"Connect the Dots, Craft Language.",
"Interactive Language Weaving.",
"Generate, Innovate, Communicate.",
"Conversation Catalyst Engine.",
"Language Chainlink Master.",
"Design Dialogues with LangFlow.",
"Nurture NLP Nodes Here.",
"Conversational Cartography Unlocked.",
"Design, Develop, Dialogize.",
"Unleashing Linguistic Creativity.",
"Graph Your Way to Great Conversations.",
"The Power of Language at Your Fingertips.",
"Sculpting Language with Precision.",
"Where Language Meets Logic.",
"Building Intelligent Interactions.",
"Your Passport to Linguistic Landscapes.",
"Create, Curate, Communicate with LangFlow.",
"Flow into the Future of Language.",
"Mapping Meaningful Conversations.",
"Unravel the Art of Articulation.",
"Language Engineering Excellence.",
"Navigate the Networks of Conversation.",
"Crafting Conversations, One Node at a Time.",
"The Pinnacle of Prompt Generation.",
"Language Models, Mapped and Mastered.",
"Powerful Prompts, Perfectly Positioned.",
"Innovation in Interaction with LangFlow.",
"Your Toolkit for Text Generation.",
"Unfolding Linguistic Possibilities.",
"Building Powerful Solutions with Language Models.",
"Uncover Business Opportunities with NLP.",
"Harness the Power of Conversational AI.",
"Transform Your Business with Smart Dialogues.",
"Craft Meaningful Interactions, Generate Value.",
"Unleashing Business Potential through Language Engineering.",
"Empowering Enterprises with Intelligent Interactions.",
"Driving Innovation in Business Communication.",
"Catalyzing Business Growth through Conversational AI.",
"Text Generation Meets Business Transformation.",
"Navigate the Linguistic Landscape, Discover Opportunities.",
"Create Powerful Connections, Boost Business Value.",
"Empowering Communication, Enabling Opportunities.",
"Advanced NLP for Groundbreaking Business Solutions.",
"Innovation in Interaction, Revolution in Revenue.",
"Maximize Impact with Intelligent Conversations.",
"Beyond Text Generation - Unleashing Business Opportunities.",
"Unlock the Power of AI in Your Business Conversations.",
"Crafting Dialogues that Drive Business Success.",
"Engineered for Excellence, Built for Business.",
];
/**
* Adjectives for the name of the flow
* @constant
*
*/
export const ADJECTIVES: string[] = [
"admiring",
"adoring",
"agitated",
"amazing",
"angry",
"awesome",
"backstabbing",
"berserk",
"big",
"boring",
"clever",
"cocky",
"compassionate",
"condescending",
"cranky",
"desperate",
"determined",
"distracted",
"dreamy",
"drunk",
"ecstatic",
"elated",
"elegant",
"evil",
"fervent",
"focused",
"furious",
"gigantic",
"gloomy",
"goofy",
"grave",
"happy",
"high",
"hopeful",
"hungry",
"insane",
"jolly",
"jovial",
"kickass",
"lonely",
"loving",
"mad",
"modest",
"naughty",
"nauseous",
"nostalgic",
"pedantic",
"pensive",
"prickly",
"reverent",
"romantic",
"sad",
"serene",
"sharp",
"sick",
"silly",
"sleepy",
"small",
"stoic",
"stupefied",
"suspicious",
"tender",
"thirsty",
"tiny",
"trusting",
"bubbly",
"charming",
"cheerful",
"comical",
"dazzling",
"delighted",
"dynamic",
"effervescent",
"enthusiastic",
"exuberant",
"fluffy",
"friendly",
"funky",
"giddy",
"giggly",
"gleeful",
"goofy",
"graceful",
"grinning",
"hilarious",
"inquisitive",
"joyous",
"jubilant",
"lively",
"mirthful",
"mischievous",
"optimistic",
"peppy",
"perky",
"playful",
"quirky",
"radiant",
"sassy",
"silly",
"spirited",
"sprightly",
"twinkly",
"upbeat",
"vibrant",
"witty",
"zany",
"zealous",
];
/**
* Nouns for the name of the flow
* @constant
*
*/
export const NOUNS: string[] = [
"albattani",
"allen",
"almeida",
"archimedes",
"ardinghelli",
"aryabhata",
"austin",
"babbage",
"banach",
"bardeen",
"bartik",
"bassi",
"bell",
"bhabha",
"bhaskara",
"blackwell",
"bohr",
"booth",
"borg",
"bose",
"boyd",
"brahmagupta",
"brattain",
"brown",
"carson",
"chandrasekhar",
"colden",
"cori",
"cray",
"curie",
"darwin",
"davinci",
"dijkstra",
"dubinsky",
"easley",
"einstein",
"elion",
"engelbart",
"euclid",
"euler",
"fermat",
"fermi",
"feynman",
"franklin",
"galileo",
"gates",
"goldberg",
"goldstine",
"goldwasser",
"golick",
"goodall",
"hamilton",
"hawking",
"heisenberg",
"heyrovsky",
"hodgkin",
"hoover",
"hopper",
"hugle",
"hypatia",
"jang",
"jennings",
"jepsen",
"joliot",
"jones",
"kalam",
"kare",
"keller",
"khorana",
"kilby",
"kirch",
"knuth",
"kowalevski",
"lalande",
"lamarr",
"leakey",
"leavitt",
"lichterman",
"liskov",
"lovelace",
"lumiere",
"mahavira",
"mayer",
"mccarthy",
"mcclintock",
"mclean",
"mcnulty",
"meitner",
"meninsky",
"mestorf",
"minsky",
"mirzakhani",
"morse",
"murdock",
"newton",
"nobel",
"noether",
"northcutt",
"noyce",
"panini",
"pare",
"pasteur",
"payne",
"perlman",
"pike",
"poincare",
"poitras",
"ptolemy",
"raman",
"ramanujan",
"ride",
"ritchie",
"roentgen",
"rosalind",
"saha",
"sammet",
"shaw",
"shirley",
"shockley",
"sinoussi",
"snyder",
"spence",
"stallman",
"stonebraker",
"swanson",
"swartz",
"swirles",
"tesla",
"thompson",
"torvalds",
"turing",
"varahamihira",
"visvesvaraya",
"volhard",
"wescoff",
"williams",
"wilson",
"wing",
"wozniak",
"wright",
"yalow",
"yonath",
"coulomb",
"degrasse",
"dewey",
"edison",
"eratosthenes",
"faraday",
"galton",
"gauss",
"herschel",
"hubble",
"joule",
"kaku",
"kepler",
"khayyam",
"lavoisier",
"maxwell",
"mendel",
"mendeleev",
"ohm",
"pascal",
"planck",
"riemann",
"schrodinger",
"sagan",
"tesla",
"tyson",
"volta",
"watt",
"weber",
"wien",
"zoBell",
"zuse",
"carroll",
];

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import { ReactComponent as VertexAISVG } from "./vertex_ai.svg";
export const VertexAIIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <VertexAISVG ref={ref} {...props} />;
});

View file

@ -0,0 +1 @@
<svg viewBox="0 0 32 32" fit="" height="100%" width="100%" preserveAspectRatio="xMidYMid meet" focusable="false"><path d="M26.69 18.53a1 1 0 00-1.4-.22L16 25.17v.29a1 1 0 110 1.91v.05a1 1 0 00.6-.19l9.88-7.3a1 1 0 00.21-1.4z" fill="#80868b"></path><path d="M16 27.37a1 1 0 110-1.91v-.29l-9.29-6.86a1 1 0 00-1.4.22 1 1 0 00.21 1.4l9.89 7.3a1 1 0 00.59.19v-.05z" fill="#9ba0a5"></path><path d="M16 24.46a2 2 0 102 2 2 2 0 00-2-2zm0 2.91a1 1 0 111-.95 1 1 0 01-1 .95z" fill="#606368"></path><path d="M8 8.14a1 1 0 01-1-1V4.63a1 1 0 112 0v2.51a1 1 0 01-1 1z" fill="#9ba0a5"></path><circle cx="7.97" cy="16" r="1.01" fill="#9ba0a5"></circle><circle cx="7.97" cy="13.05" r="1.01" fill="#9ba0a5"></circle><circle cx="7.97" cy="10.09" r="1.01" fill="#9ba0a5"></circle><path d="M24 11.07a1 1 0 01-1-1V7.55a1 1 0 012 0v2.52a1 1 0 01-1 1z" fill="#606368"></path><circle cx="24.03" cy="16.01" r="1.01" fill="#606368"></circle><circle cx="24.03" cy="13.02" r="1.01" fill="#606368"></circle><circle cx="24.03" cy="4.63" r="1.01" fill="#606368"></circle><path d="M16 20a1 1 0 01-1-1v-2.54a1 1 0 012 0V19a1 1 0 01-1 1z" fill="#80868b"></path><circle cx="16" cy="21.93" r="1.01" fill="#80868b"></circle><circle cx="16" cy="13.51" r="1.01" fill="#80868b"></circle><circle cx="16" cy="10.56" r="1.01" fill="#80868b"></circle><path d="M20 14.05a1 1 0 01-1-1v-2.51a1 1 0 112 0v2.51a1 1 0 01-1 1z" fill="#606368"></path><circle cx="20.02" cy="7.58" r="1.01" fill="#606368"></circle><circle cx="20.02" cy="18.92" r="1.01" fill="#606368"></circle><circle cx="20.02" cy="15.97" r="1.01" fill="#606368"></circle><circle cx="11.98" cy="18.92" r="1.01" fill="#9ba0a5"></circle><circle cx="11.98" cy="10.56" r="1.01" fill="#9ba0a5"></circle><circle cx="11.98" cy="7.58" r="1.01" fill="#9ba0a5"></circle><path d="M12 17a1 1 0 01-1-1v-2.54a1 1 0 012 0V16a1 1 0 01-1 1z" fill="#9ba0a5"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1,4 +1,4 @@
import { useContext, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import "ace-builds/src-noconflict/mode-python"; import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-github";
@ -26,14 +26,42 @@ import {
TabsTrigger, TabsTrigger,
} from "../../components/ui/tabs"; } from "../../components/ui/tabs";
import { Check, Clipboard, Code2 } from "lucide-react"; import { Check, Clipboard, Code2 } from "lucide-react";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../components/ui/table";
import { buildTweaks, classNames, limitScrollFieldsModal } from "../../utils";
import AccordionComponent from "../../components/AccordionComponent";
import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
import InputComponent from "../../components/inputComponent";
import InputFileComponent from "../../components/inputFileComponent";
import InputListComponent from "../../components/inputListComponent";
import IntComponent from "../../components/intComponent";
import PromptAreaComponent from "../../components/promptComponent";
import TextAreaComponent from "../../components/textAreaComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import { cloneDeep, filter } from "lodash";
import { TabsContext } from "../../contexts/tabsContext";
export default function ApiModal({ flow }: { flow: FlowType }) { export default function ApiModal({ flow }: { flow: FlowType }) {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const { dark } = useContext(darkContext); const { dark } = useContext(darkContext);
const { closePopUp } = useContext(PopUpContext); const { closePopUp, closeEdit, setCloseEdit } = useContext(PopUpContext);
const [activeTab, setActiveTab] = useState("0"); const [activeTab, setActiveTab] = useState("0");
const [isCopied, setIsCopied] = useState<Boolean>(false); const [isCopied, setIsCopied] = useState<Boolean>(false);
const [enabled, setEnabled] = useState(null);
const [openAccordion, setOpenAccordion] = useState([]);
const tweak = useRef([]);
const tweaksList = useRef([]);
const { setTweak, getTweak } = useContext(TabsContext);
const copyToClipboard = () => { const copyToClipboard = () => {
if (!navigator.clipboard || !navigator.clipboard.writeText) { if (!navigator.clipboard || !navigator.clipboard.writeText) {
return; return;
@ -47,18 +75,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
}, 2000); }, 2000);
}); });
}; };
function setModalOpen(x: boolean) { const pythonApiCode = getPythonApiCode(flow, tweak.current);
setOpen(x); const curl_code = getCurlCode(flow, tweak.current);
if (x === false) { const pythonCode = getPythonCode(flow, tweak.current);
closePopUp(); const tweaksCode = buildTweaks(flow);
}
}
const pythonApiCode = getPythonApiCode(flow);
const curl_code = getCurlCode(flow);
const pythonCode = getPythonCode(flow);
const tabs = [ const tabs = [
{ {
name: "cURL", name: "cURL",
@ -80,10 +100,169 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
code: pythonCode, code: pythonCode,
}, },
]; ];
useEffect(() => {
if (closeEdit !== "") {
tweak.current = getTweak;
if (tweak.current.length > 0) {
setActiveTab("3");
openAccordions();
} else {
startTweaks();
}
} else {
startTweaks();
}
}, [closeEdit]);
useEffect(() => {
filterNodes();
}, []);
if (Object.keys(tweaksCode).length > 0) {
tabs.push({
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
});
}
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setCloseEdit("");
setTweak([]);
closePopUp();
}
}
function startTweaks() {
tweak.current.push(buildTweaks(flow));
}
function filterNodes() {
let arrNodesWithValues = [];
flow["data"]["nodes"].forEach((t) => {
Object.keys(t["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type === "str" ||
t.data.node.template[n].type === "bool" ||
t.data.node.template[n].type === "float" ||
t.data.node.template[n].type === "code" ||
t.data.node.template[n].type === "prompt" ||
t.data.node.template[n].type === "file" ||
t.data.node.template[n].type === "int")
)
.map((n, i) => {
arrNodesWithValues.push(t["id"]);
});
});
tweaksList.current = arrNodesWithValues.filter((value, index, self) => {
return self.indexOf(value) === index;
});
}
function buildTweakObject(tw, changes, template) {
if (template.type === "float") {
changes = parseFloat(changes);
}
if (template.type === "int") {
changes = parseInt(changes);
}
if (template.list === true && Array.isArray(changes)) {
changes = changes?.filter((x) => x !== "");
}
const existingTweak = tweak.current.find((element) =>
element.hasOwnProperty(tw)
);
if (existingTweak) {
existingTweak[tw][template["name"]] = changes;
if (existingTweak[tw][template["name"]] == template.value) {
tweak.current.forEach((element) => {
if (element[tw] && Object.keys(element[tw])?.length === 0) {
tweak.current = tweak.current.filter((obj) => {
const prop = obj[Object.keys(obj)[0]].prop;
return prop !== undefined && prop !== null && prop !== "";
});
}
});
}
} else {
const newTweak = {
[tw]: {
[template["name"]]: changes,
},
};
tweak.current.push(newTweak);
}
const pythonApiCode = getPythonApiCode(flow, tweak.current);
const curl_code = getCurlCode(flow, tweak.current);
const pythonCode = getPythonCode(flow, tweak.current);
tabs[0].code = curl_code;
tabs[1].code = pythonApiCode;
tabs[2].code = pythonCode;
setTweak(tweak.current);
}
function buildContent(value) {
const htmlContent = (
<div className="w-[200px]">
<span>{value != null && value != "" ? value : "None"}</span>
</div>
);
return htmlContent;
}
function getValue(value, node, template) {
let returnValue = value ?? "";
if (getTweak.length > 0) {
for (const obj of getTweak) {
Object.keys(obj).forEach((key) => {
const value = obj[key];
if (key == node["id"]) {
Object.keys(value).forEach((key) => {
if (key == template["name"]) {
returnValue = value[key];
}
});
}
});
}
} else {
return value ?? "";
}
return returnValue;
}
function openAccordions() {
let accordionsToOpen = [];
tweak.current.forEach((el) => {
Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key);
setOpenAccordion(accordionsToOpen);
}
});
});
}
return ( return (
<Dialog open={true} onOpenChange={setModalOpen}> <Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger> <DialogTrigger></DialogTrigger>
<DialogContent className="lg:max-w-[800px] sm:max-w-[600px] h-[580px]"> <DialogContent className="h-[580px] sm:max-w-[700px] lg:max-w-[850px]">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<span className="pr-2">Code</span> <span className="pr-2">Code</span>
@ -93,9 +272,14 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
</DialogHeader> </DialogHeader>
<Tabs <Tabs
defaultValue={"0"} value={activeTab}
className="w-full h-full overflow-hidden text-center bg-muted rounded-md border" className="h-full w-full overflow-hidden rounded-md border bg-muted text-center"
onValueChange={(value) => setActiveTab(value)} onValueChange={(value) => {
setActiveTab(value);
if (value === "3") {
openAccordions();
}
}}
> >
<div className="flex items-center justify-between px-2"> <div className="flex items-center justify-between px-2">
<TabsList> <TabsList>
@ -119,15 +303,449 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<TabsContent <TabsContent
value={index.toString()} value={index.toString()}
className="overflow-hidden w-full h-full px-4 pb-4 -mt-1" className="-mt-1 h-full w-full overflow-hidden px-4 pb-4"
key={index} // Remember to add a unique key prop
> >
<SyntaxHighlighter {index < 3 ? (
className="h-[400px] w-full overflow-auto" <SyntaxHighlighter
language={tab.mode} className="h-[400px] w-full overflow-auto"
style={oneDark} language={tab.mode}
> style={oneDark}
{tab.code} >
</SyntaxHighlighter> {tab.code}
</SyntaxHighlighter>
) : index === 3 ? (
<>
<div className="mt-2 flex h-[400px] w-full">
<div
className={classNames(
"w-full rounded-lg border-[1px] border-gray-200 bg-muted",
1 == 1
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
{flow["data"]["nodes"].map((t: any, index) => (
<div className="px-3" key={index}>
{tweaksList.current.includes(t["data"]["id"]) && (
<AccordionComponent
trigger={t["data"]["id"]}
open={openAccordion}
>
<div className="flex h-fit flex-col gap-5">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="h-10 border-gray-200 text-xs font-medium text-gray-500">
<TableRow className="dark:border-b-muted">
<TableHead className="h-7 text-center">
PARAM
</TableHead>
<TableHead className="h-7 p-0 text-center">
VALUE
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(t["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type ===
"str" ||
t.data.node.template[n].type ===
"bool" ||
t.data.node.template[n].type ===
"float" ||
t.data.node.template[n].type ===
"code" ||
t.data.node.template[n].type ===
"prompt" ||
t.data.node.template[n].type ===
"file" ||
t.data.node.template[n].type ===
"int")
)
.map((n, i) => {
//console.log(t.data.node.template[n]);
return (
<TableRow
key={i}
className="h-10 dark:border-b-muted"
>
<TableCell className="p-0 text-center text-sm text-gray-900">
{n}
</TableCell>
<TableCell className="p-0 text-center text-xs text-gray-900 dark:text-gray-300">
<div className="m-auto w-[250px]">
{t.data.node.template[n]
.type === "str" &&
!t.data.node.template[n]
.options ? (
<div className="mx-auto">
{t.data.node.template[n]
.list ? (
<InputListComponent
editNode={true}
disabled={false}
value={
!t.data.node.template[
n
].value ||
t.data.node.template[
n
].value === ""
? [""]
: t.data.node
.template[n]
.value
}
onChange={(k) => {}}
onAddInput={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
) : t.data.node.template[n]
.multiline ? (
<ShadTooltip
delayDuration={1000}
content={buildContent(
t.data.node.template[
n
].value
)}
>
<div>
<TextAreaComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node
.template[n]
.value,
t.data,
t.data.node
.template[n]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : (
<InputComponent
editNode={true}
disabled={false}
password={
t.data.node.template[
n
].password ?? false
}
value={getValue(
t.data.node.template[
n
].value,
t.data,
t.data.node.template[
n
]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
)}
</div>
) : t.data.node.template[n]
.type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleShadComponent
enabled={
t.data.node.template[n]
.value
}
setEnabled={(e) => {
t.data.node.template[
n
].value = e;
setEnabled(e);
buildTweakObject(
t["data"]["id"],
e,
t.data.node.template[
n
]
);
}}
size="small"
disabled={false}
/>
</div>
) : t.data.node.template[n]
.type === "file" ? (
<ShadTooltip
delayDuration={1000}
content={buildContent(
getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={false}
value={
t.data.node.template[
n
].value ?? ""
}
onChange={(
k: any
) => {}}
fileTypes={
t.data.node.template[
n
].fileTypes
}
suffixes={
t.data.node.template[
n
].suffixes
}
onFileChange={(
k: any
) => {}}
></InputFileComponent>
</div>
</ShadTooltip>
) : t.data.node.template[n]
.type === "float" ? (
<div className="mx-auto">
<FloatComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[
n
]
);
}}
/>
</div>
) : t.data.node.template[n]
.type === "str" &&
t.data.node.template[n]
.options ? (
<div className="mx-auto">
<Dropdown
editNode={true}
apiModal={true}
options={
t.data.node.template[n]
.options
}
onSelect={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[
n
]
);
}}
value={getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)}
></Dropdown>
</div>
) : t.data.node.template[n]
.type === "int" ? (
<div className="mx-auto">
<IntComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[
n
]
);
}}
/>
</div>
) : t.data.node.template[n]
.type === "prompt" ? (
<ShadTooltip
delayDuration={1000}
content={buildContent(
getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<PromptAreaComponent
editNode={true}
disabled={false}
value={getValue(
t.data.node.template[
n
].value,
t.data,
t.data.node.template[
n
]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n]
.type === "code" ? (
<ShadTooltip
delayDuration={1000}
content={buildContent(
getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
)
)}
>
<div className="mx-auto">
<CodeAreaComponent
disabled={false}
editNode={true}
value={getValue(
t.data.node.template[
n
].value,
t.data,
t.data.node.template[
n
]
)}
onChange={(k) => {
buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n]
.type === "Any" ? (
"-"
) : (
<div className="hidden"></div>
)}
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
</AccordionComponent>
)}
{tweaksList.current.length === 0 && (
<>
<div className="pt-3">
No tweaks are available for this flow.
</div>
</>
)}
</div>
))}
{/*
<div className="flex flex-col gap-5 bg-muted">
<Table className="table-fixed bg-muted outline-1">
<TableHeader className="border-gray-200 text-gray-500 text-xs font-medium h-10">
<TableRow className="dark:border-b-muted">
<TableHead className="h-5 text-center">
TWEAK
</TableHead>
<TableHead className="p-0 h-5 text-center">
VALUE
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow className="p-0 text-center text-gray-900 text-sm">
<TableCell className="p-2 text-center text-gray-900 text-sm truncate">
{invoice.paymentStatus}
</TableCell>
<TableCell className="p-2 text-center text-gray-900 text-sm truncate">
{invoice.paymentMethod}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div> */}
</div>
</div>
</>
) : null}
</TabsContent> </TabsContent>
))} ))}
</Tabs> </Tabs>

View file

@ -90,7 +90,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<DialogDescription> <DialogDescription>
{data.node?.description} {data.node?.description}
<div className="flex pt-4"> <div className="flex pt-4">
<Variable className="w-5 h-5 pe-1 text-muted-foreground stroke-2 "></Variable> <Variable className="h-5 w-5 stroke-2 pe-1 text-muted-foreground "></Variable>
<span className="text-sm font-semibold text-primary "> <span className="text-sm font-semibold text-primary ">
Parameters Parameters
</span> </span>
@ -98,25 +98,25 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex w-full max-h-[350px] h-fit"> <div className="flex h-fit max-h-[350px] w-full">
<div <div
className={classNames( className={classNames(
"w-full rounded-lg bg-background border-[1px] border-input", "w-full rounded-lg border-[1px] border-input bg-background",
nodeLength > limitScrollFieldsModal nodeLength > limitScrollFieldsModal
? "overflow-scroll overflow-x-hidden custom-scroll" ? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden" : "overflow-hidden"
)} )}
> >
{nodeLength > 0 && ( {nodeLength > 0 && (
<div className="flex flex-col gap-5 h-fit"> <div className="flex h-fit flex-col gap-5">
<Table className="table-fixed bg-muted outline-1"> <Table className="table-fixed bg-muted outline-1">
<TableHeader className="border-input text-ring text-xs font-medium h-10"> <TableHeader className="h-10 border-input text-xs font-medium text-ring">
<TableRow className=""> <TableRow className="">
<TableHead className="h-7 text-center">PARAM</TableHead> <TableHead className="h-7 text-center">PARAM</TableHead>
<TableHead className="p-0 h-7 text-center"> <TableHead className="h-7 p-0 text-center">
VALUE VALUE
</TableHead> </TableHead>
<TableHead className="text-center h-7">SHOW</TableHead> <TableHead className="h-7 text-center">SHOW</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody className="p-0"> <TableBody className="p-0">
@ -135,12 +135,12 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
) )
.map((n, i) => ( .map((n, i) => (
<TableRow key={i} className="h-10 "> <TableRow key={i} className="h-10 ">
<TableCell className="p-0 text-center text-foreground text-sm"> <TableCell className="p-0 text-center text-sm text-foreground">
{data.node.template[n].name {data.node.template[n].name
? data.node.template[n].name ? data.node.template[n].name
: data.node.template[n].display_name} : data.node.template[n].display_name}
</TableCell> </TableCell>
<TableCell className="p-0 text-center text-foreground text-xs w-[300px] "> <TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{data.node.template[n].type === "str" && {data.node.template[n].type === "str" &&
!data.node.template[n].options ? ( !data.node.template[n].options ? (
<div className="mx-auto"> <div className="mx-auto">

View file

@ -36,7 +36,7 @@ export default function ModalField({
return ( return (
<div <div
className={classNames( className={classNames(
"flex flex-row w-full items-center justify-between", "flex w-full flex-row items-center justify-between",
display ? "" : "hidden", display ? "" : "hidden",
Object.keys(data.node.template).filter( Object.keys(data.node.template).filter(
(t) => (t) =>

View file

@ -6,8 +6,7 @@ import {
classNames, classNames,
limitScrollFieldsModal, limitScrollFieldsModal,
nodeColors, nodeColors,
nodeIcons, nodeIconsLucide,
toNormalCase,
toTitleCase, toTitleCase,
} from "../../utils"; } from "../../utils";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
@ -28,7 +27,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
} }
} }
// any to avoid type conflict // any to avoid type conflict
const Icon: any = nodeIcons[types[data.type]]; const Icon: any = nodeIconsLucide[types[data.type]];
return ( return (
<Transition.Root show={open} appear={true} as={Fragment}> <Transition.Root show={open} appear={true} as={Fragment}>
<Dialog <Dialog
@ -60,8 +59,8 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-background text-left shadow-xl transition-all sm:my-8 w-[700px]"> <Dialog.Panel className="relative flex h-[600px] w-[700px] transform flex-col justify-between overflow-hidden rounded-lg bg-background text-left shadow-xl transition-all sm:my-8">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block"> <div className=" absolute right-0 top-0 z-50 hidden pr-4 pt-4 sm:block">
<button <button
type="button" type="button"
className="rounded-md text-ring hover:text-accent-foreground" className="rounded-md text-ring hover:text-accent-foreground"
@ -73,11 +72,11 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
<X className="h-6 w-6" aria-hidden="true" /> <X className="h-6 w-6" aria-hidden="true" />
</button> </button>
</div> </div>
<div className="h-full w-full flex flex-col justify-center items-center"> <div className="flex h-full w-full flex-col items-center justify-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm"> <div className="z-10 flex w-full justify-center pb-4 shadow-sm">
<Icon <Icon
strokeWidth={1.5} strokeWidth={1.5}
className="w-10 mt-4 h-10 p-1 rounded" className="mt-4 h-10 w-10 rounded p-1"
style={{ style={{
color: color:
nodeColors[types[data.type]] ?? nodeColors.unknown, nodeColors[types[data.type]] ?? nodeColors.unknown,
@ -92,11 +91,11 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
</Dialog.Title> </Dialog.Title>
</div> </div>
</div> </div>
<div className="h-full w-full bg-input p-4 gap-4 flex flex-row justify-center items-center"> <div className="flex h-full w-full flex-row items-center justify-center gap-4 bg-input p-4">
<div className="flex w-full h-[445px]"> <div className="flex h-[445px] w-full">
<div <div
className={classNames( className={classNames(
"px-4 sm:p-4 w-full rounded-lg bg-background shadow", "w-full rounded-lg bg-background px-4 shadow sm:p-4",
Object.keys(data.node.template).filter( Object.keys(data.node.template).filter(
(t) => (t) =>
t.charAt(0) !== "_" && t.charAt(0) !== "_" &&
@ -107,7 +106,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
: "overflow-hidden" : "overflow-hidden"
)} )}
> >
<div className="flex flex-col h-full gap-5"> <div className="flex h-full flex-col gap-5">
{Object.keys(data.node.template) {Object.keys(data.node.template)
.filter( .filter(
(t) => (t) =>
@ -145,7 +144,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
</div> </div>
</div> </div>
</div> </div>
<div className="bg-input w-full pb-3 flex flex-row-reverse px-4"> <div className="flex w-full flex-row-reverse bg-input px-4 pb-3">
<button <button
type="button" type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-status-red px-4 py-2 text-base font-medium text-background shadow-sm hover:bg-ring focus:outline-none focus:ring-1 focus:ring-ring focus:ring-offset-1 sm:ml-3 sm:w-auto sm:text-sm" className="inline-flex w-full justify-center rounded-md border border-transparent bg-status-red px-4 py-2 text-base font-medium text-background shadow-sm hover:bg-ring focus:outline-none focus:ring-1 focus:ring-ring focus:ring-offset-1 sm:ml-3 sm:w-auto sm:text-sm"

View file

@ -53,7 +53,7 @@ export default function ChatInput({
lockChat lockChat
? " bg-input text-foreground " ? " bg-input text-foreground "
: " bg-background text-foreground ", : " bg-background text-foreground ",
"form-input block w-full custom-scroll rounded-md border-ring pr-10 sm:text-sm" + "form-input block w-full rounded-md border-ring pr-10 custom-scroll sm:text-sm" +
INPUT_STYLE INPUT_STYLE
)} )}
placeholder={"Send a message..."} placeholder={"Send a message..."}
@ -62,7 +62,7 @@ export default function ChatInput({
<button disabled={lockChat} onClick={() => sendMessage()}> <button disabled={lockChat} onClick={() => sendMessage()}>
{lockChat ? ( {lockChat ? (
<Lock <Lock
className="h-5 w-5 text-ring animate-pulse" className="h-5 w-5 animate-pulse text-ring"
aria-hidden="true" aria-hidden="true"
/> />
) : ( ) : (

View file

@ -48,12 +48,12 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
}; };
return ( return (
<div className="codeblock font-sans text-[16px]"> <div className="codeblock font-sans text-[16px]">
<div className="flex items-center justify-between py-1.5 px-4"> <div className="flex items-center justify-between px-4 py-1.5">
<span className="text-xs lowercase text-background">{language}</span> <span className="text-xs lowercase text-background">{language}</span>
<div className="flex items-center"> <div className="flex items-center">
<button <button
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-background" className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-background"
onClick={copyToClipboard} onClick={copyToClipboard}
> >
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />} {isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}

View file

@ -11,7 +11,7 @@ import remarkMath from "remark-math";
import { CodeBlock } from "./codeBlock"; import { CodeBlock } from "./codeBlock";
import Convert from "ansi-to-html"; import Convert from "ansi-to-html";
import { User2, MessageCircle } from "lucide-react"; import { User2, MessageCircle } from "lucide-react";
import DOMPurify from "dompurify";
export default function ChatMessage({ export default function ChatMessage({
chat, chat,
lockChat, lockChat,
@ -31,58 +31,58 @@ export default function ChatMessage({
return ( return (
<div <div
className={classNames( className={classNames(
"w-full py-2 pl-2 flex", "flex w-full py-2 pl-2",
chat.isSend ? "bg-background " : "bg-input" chat.isSend ? "bg-background " : "bg-input"
)} )}
> >
<div <div
className={classNames( className={classNames(
"rounded-full overflow-hidden w-8 h-8 flex items-center my-3 justify-center" "my-3 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full"
)} )}
> >
{!chat.isSend && ( {!chat.isSend && (
<div className="relative w-8 h-8"> <div className="relative h-8 w-8">
<img <img
className={ className={
"absolute transition-opacity duration-500 scale-150 " + "absolute scale-150 transition-opacity duration-500 " +
(lockChat ? "opacity-100" : "opacity-0") (lockChat ? "opacity-100" : "opacity-0")
} }
src={lastMessage ? AiIcon : AiIconStill} src={lastMessage ? AiIcon : AiIconStill}
/> />
<img <img
className={ className={
"absolute transition-opacity duration-500 scale-150 " + "absolute scale-150 transition-opacity duration-500 " +
(lockChat ? "opacity-0" : "opacity-100") (lockChat ? "opacity-0" : "opacity-100")
} }
src={AiIconStill} src={AiIconStill}
/> />
</div> </div>
)} )}
{chat.isSend && <User2 className="w-6 h-6 -mb-1 text-primary " />} {chat.isSend && <User2 className="-mb-1 h-6 w-6 text-primary " />}
</div> </div>
{!chat.isSend ? ( {!chat.isSend ? (
<div className="w-full text-start flex items-center"> <div className="flex w-full items-center text-start">
<div className="w-full relative text-start inline-block text-muted-foreground text-sm font-normal"> <div className="relative inline-block w-full text-start text-sm font-normal text-muted-foreground">
{hidden && chat.thought && chat.thought !== "" && ( {hidden && chat.thought && chat.thought !== "" && (
<div <div
onClick={() => setHidden((prev) => !prev)} onClick={() => setHidden((prev) => !prev)}
className="absolute -top-1 -left-2 cursor-pointer" className="absolute -left-2 -top-1 cursor-pointer"
> >
<MessageCircle className="w-5 h-5 animate-bounce " /> <MessageCircle className="h-5 w-5 animate-bounce " />
</div> </div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && ( {chat.thought && chat.thought !== "" && !hidden && (
<div <div
onClick={() => setHidden((prev) => !prev)} onClick={() => setHidden((prev) => !prev)}
className=" text-start inline-block rounded-md text-muted-foreground h-full border border-ring className=" ml-3 inline-block h-full w-[95%] cursor-pointer overflow-scroll rounded-md
bg-muted w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll" border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground scrollbar-hide"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought), __html: DOMPurify.sanitize(convert.toHtml(chat.thought)),
}} }}
></div> ></div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && <br></br>} {chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full px-4 pb-3 pt-3 pr-8"> <div className="w-full px-4 pb-3 pr-8 pt-3">
<div className="w-full"> <div className="w-full">
<div className="w-full"> <div className="w-full">
<ReactMarkdown <ReactMarkdown
@ -94,7 +94,7 @@ export default function ChatMessage({
if (children.length) { if (children.length) {
if (children[0] == "▍") { if (children[0] == "▍") {
return ( return (
<span className="animate-pulse cursor-default mt-1"> <span className="mt-1 animate-pulse cursor-default">
▍ ▍
</span> </span>
); );
@ -146,14 +146,14 @@ export default function ChatMessage({
</div> </div>
</div> </div>
) : ( ) : (
<div className="w-full flex items-center"> <div className="flex w-full items-center">
<div className="text-start inline-block px-3 text-muted-foreground "> <div className="inline-block px-3 text-start text-muted-foreground ">
<span {message.split("\n").map((line, index) => (
className="text-muted-foreground " <span key={index} className="text-muted-foreground ">
dangerouslySetInnerHTML={{ {line}
__html: message.replace(/\n/g, "<br>"), <br />
}} </span>
></span> ))}
</div> </div>
</div> </div>
)} )}

View file

@ -26,21 +26,21 @@ export default function FileCard({ fileName, content, fileType }) {
if (fileType === "image") { if (fileType === "image") {
return ( return (
<div <div
className="relative w-1/4 h-1/4" className="relative h-1/4 w-1/4"
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
> >
<img <img
src={`data:image/png;base64,${content}`} src={`data:image/png;base64,${content}`}
alt="generated image" alt="generated image"
className="rounded-lg w-full h-full" className="h-full w-full rounded-lg"
/> />
{isHovered && ( {isHovered && (
<div <div
className={`absolute top-0 right-0 bg-muted text-foreground rounded-bl-lg px-1 text-sm font-bold `} className={`absolute right-0 top-0 rounded-bl-lg bg-muted px-1 text-sm font-bold text-foreground `}
> >
<button className="text-ring py-1 px-2 " onClick={handleDownload}> <button className="px-2 py-1 text-ring " onClick={handleDownload}>
<DownloadCloud className="hover:scale-110 w-5 h-5 text-current" /> <DownloadCloud className="h-5 w-5 text-current hover:scale-110" />
</button> </button>
</div> </div>
)} )}
@ -51,25 +51,25 @@ export default function FileCard({ fileName, content, fileType }) {
return ( return (
<button <button
onClick={handleDownload} onClick={handleDownload}
className="bg-muted shadow rounded w-1/2 text-foreground hover:drop-shadow-lg px-2 py-2 flex justify-between items-center border border-ring" className="flex w-1/2 items-center justify-between rounded border border-ring bg-muted px-2 py-2 text-foreground shadow hover:drop-shadow-lg"
> >
<div className="flex gap-2 text-current items-center w-full mr-2"> <div className="mr-2 flex w-full items-center gap-2 text-current">
{" "} {" "}
{fileType === "image" ? ( {fileType === "image" ? (
<img <img
src={`data:image/png;base64,${content}`} src={`data:image/png;base64,${content}`}
alt="" alt=""
className="w-8 h-8" className="h-8 w-8"
/> />
) : ( ) : (
<File className="w-8 h-8" /> <File className="h-8 w-8" />
)} )}
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
{" "} {" "}
<div className="truncate text-sm text-current">{fileName}</div> <div className="truncate text-sm text-current">{fileName}</div>
<div className="truncate text-xs text-ring">{fileType}</div> <div className="truncate text-xs text-ring">{fileType}</div>
</div> </div>
<DownloadCloud className="w-6 h-6 text-current ml-auto" /> <DownloadCloud className="ml-auto h-6 w-6 text-current" />
</div> </div>
</button> </button>
); );

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