Adds better prompt experience and many other improvements (#621)

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-07-11 18:35:56 -03:00 • committed by GitHub
commit ee55d729e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
177 changed files with 4904 additions and 2981 deletions

0
.githooks/pre-commit Normal file → Executable file
View file

3
.gitignore vendored
View file

@ -1,3 +1,5 @@
# This is to avoid Opencommit hook from getting pushed
prepare-commit-msg
# Logs # Logs
logs logs
*.log *.log
@ -242,4 +244,5 @@ dmypy.json
# Poetry # Poetry
.testenv/* .testenv/*
langflow.db langflow.db
.githooks/prepare-commit-msg
langchain.db langchain.db

View file

@ -5,6 +5,8 @@ all: help
init: init:
@echo 'Installing pre-commit hooks' @echo 'Installing pre-commit hooks'
git config core.hooksPath .githooks git config core.hooksPath .githooks
@echo 'Making pre-commit hook executable'
chmod +x .githooks/pre-commit
@echo 'Installing backend dependencies' @echo 'Installing backend dependencies'
make install_backend make install_backend
@echo 'Installing frontend dependencies' @echo 'Installing frontend dependencies'

View file

@ -31,6 +31,7 @@
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [📦 Installation](#-installation) - [📦 Installation](#-installation)
- [Locally](#locally) - [Locally](#locally)
- [HuggingFace Spaces](#huggingface-spaces)
- [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli) - [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli)
- [Usage](#usage) - [Usage](#usage)
- [Environment Variables](#environment-variables) - [Environment Variables](#environment-variables)
@ -61,6 +62,8 @@ or
langflow # or langflow --help langflow # or langflow --help
``` ```
### HuggingFace Spaces
You can also check it out on [HuggingFace Spaces](https://huggingface.co/spaces/Logspace/Langflow) and run it in your browser! You can even clone it and have your own copy of Langflow to play with.
# 🖥️ Command Line Interface (CLI) # 🖥️ Command Line Interface (CLI)
@ -103,7 +106,7 @@ A sample `.env` file named `.env.example` is included with the project. Copy thi
# Deployment # Deployment
### Deploy Langflow on Google Cloud Platform ## Deploy Langflow on Google Cloud Platform
Follow our step-by-step guide to deploy Langflow on Google Cloud Platform (GCP) using Google Cloud Shell. The guide is available in the [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) document. Follow our step-by-step guide to deploy Langflow on Google Cloud Platform (GCP) using Google Cloud Shell. The guide is available in the [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) document.
@ -112,7 +115,7 @@ Alternatively, click the **"Open in Cloud Shell"** button below to launch Google
[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md) [![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
### Deploy Langflow on [Jina AI Cloud](https://github.com/jina-ai/langchain-serve) ## Deploy Langflow on [Jina AI Cloud](https://github.com/jina-ai/langchain-serve)
Langflow integrates with langchain-serve to provide a one-command deployment to Jina AI Cloud. Langflow integrates with langchain-serve to provide a one-command deployment to Jina AI Cloud.
@ -219,6 +222,13 @@ print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))
> Read more about resource customization, cost, and management of Langflow apps on Jina AI Cloud in the **[langchain-serve](https://github.com/jina-ai/langchain-serve)** repository. > Read more about resource customization, cost, and management of Langflow apps on Jina AI Cloud in the **[langchain-serve](https://github.com/jina-ai/langchain-serve)** repository.
## Deploy on Railway
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/Emy2sU?referralCode=MnPSdg)
## Deploy on Render
<a href="https://render.com/deploy?repo=https://github.com/logspace-ai/langflow/tree/main">
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" />
</a>
# 🎨 Creating Flows # 🎨 Creating Flows

771
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -23,14 +23,14 @@ langflow = "langflow.__main__:main"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.9,<3.11" python = ">=3.9,<3.11"
fastapi = "^0.99.0" fastapi = "^0.100.0"
uvicorn = "^0.22.0" uvicorn = "^0.22.0"
beautifulsoup4 = "^4.12.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.219" langchain = "^0.0.229"
openai = "^0.27.8" openai = "^0.27.8"
pandas = "^2.0.0" pandas = "^2.0.0"
chromadb = "^0.3.21" chromadb = "^0.3.21"
@ -78,7 +78,7 @@ black = "^23.1.0"
ipykernel = "^6.21.2" ipykernel = "^6.21.2"
mypy = "^1.1.1" mypy = "^1.1.1"
ruff = "^0.0.254" ruff = "^0.0.254"
httpx = "^0.23.3" httpx = "*"
pytest = "^7.2.2" pytest = "^7.2.2"
types-requests = "^2.28.11" types-requests = "^2.28.11"
requests = "^2.28.0" requests = "^2.28.0"

11
render.yaml Normal file
View file

@ -0,0 +1,11 @@
services:
# A Docker web service
- type: web
name: langflow
runtime: docker
plan: free
dockerfilePath: ./Dockerfile
repo: https://github.com/logspace-ai/langflow
branch: main
healthCheckPath: /health
autoDeploy: false

View file

@ -22,3 +22,38 @@ def remove_api_keys(flow: dict):
value["value"] = None value["value"] = None
return flow return flow
def build_input_keys_response(langchain_object, artifacts):
"""Build the input keys response."""
input_keys_response = {
"input_keys": {key: "" for key in langchain_object.input_keys},
"memory_keys": [],
"handle_keys": artifacts.get("handle_keys", []),
}
# Set the input keys values from artifacts
for key, value in artifacts.items():
if key in input_keys_response["input_keys"]:
input_keys_response["input_keys"][key] = value
# If the object has memory, that memory will have a memory_variables attribute
# memory variables should be removed from the input keys
if hasattr(langchain_object, "memory") and hasattr(
langchain_object.memory, "memory_variables"
):
# Remove memory variables from input keys
input_keys_response["input_keys"] = {
key: value
for key, value in input_keys_response["input_keys"].items()
if key not in langchain_object.memory.memory_variables
}
# Add memory variables to memory_keys
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
if hasattr(langchain_object, "prompt") and hasattr(
langchain_object.prompt, "template"
):
input_keys_response["template"] = langchain_object.prompt.template
return input_keys_response

View file

@ -1,6 +1,8 @@
from langflow.template.frontend_node.base import FrontendNode
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt
from langchain.prompts import PromptTemplate
class CacheResponse(BaseModel): class CacheResponse(BaseModel):
@ -11,8 +13,14 @@ class Code(BaseModel):
code: str code: str
class Prompt(BaseModel): class FrontendNodeRequest(FrontendNode):
template: dict # type: ignore
class ValidatePromptRequest(BaseModel):
name: str
template: str template: str
frontend_node: FrontendNodeRequest
# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}} # Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
@ -31,6 +39,7 @@ class CodeValidationResponse(BaseModel):
class PromptValidationResponse(BaseModel): class PromptValidationResponse(BaseModel):
input_variables: list input_variables: list
frontend_node: FrontendNodeRequest
INVALID_CHARACTERS = { INVALID_CHARACTERS = {
@ -51,34 +60,93 @@ INVALID_CHARACTERS = {
"}", "}",
} }
INVALID_NAMES = {
"input_variables",
"output_parser",
"partial_variables",
"template",
"template_format",
"validate_template",
}
def validate_prompt(template: str): def validate_prompt(template: str):
input_variables = extract_input_variables_from_prompt(template) input_variables = extract_input_variables_from_prompt(template)
# Check if there are invalid characters in the input_variables # Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables) input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables):
raise ValueError(
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
)
return PromptValidationResponse(input_variables=input_variables) try:
PromptTemplate(template=template, input_variables=input_variables)
except Exception as exc:
raise ValueError(str(exc)) from exc
return input_variables
def check_input_variables(input_variables: list): def check_input_variables(input_variables: list):
invalid_chars = [] invalid_chars = []
fixed_variables = [] fixed_variables = []
wrong_variables = []
empty_variables = []
for variable in input_variables: for variable in input_variables:
new_var = variable new_var = variable
# if variable is empty, then we should add that to the wrong variables
if not variable:
empty_variables.append(variable)
continue
# if variable starts with a number we should add that to the invalid chars
# and wrong variables
if variable[0].isdigit():
invalid_chars.append(variable[0])
new_var = new_var.replace(variable[0], "")
wrong_variables.append(variable)
else:
for char in INVALID_CHARACTERS: for char in INVALID_CHARACTERS:
if char in variable: if char in variable:
invalid_chars.append(char) invalid_chars.append(char)
new_var = new_var.replace(char, "") new_var = new_var.replace(char, "")
wrong_variables.append(variable)
fixed_variables.append(new_var) fixed_variables.append(new_var)
if new_var != variable:
input_variables.remove(variable)
input_variables.append(new_var)
# If any of the input_variables is not in the fixed_variables, then it means that # If any of the input_variables is not in the fixed_variables, then it means that
# there are invalid characters in the input_variables # there are invalid characters in the input_variables
if any(var not in fixed_variables for var in input_variables):
raise ValueError(
f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead."
)
if any(var not in fixed_variables for var in input_variables):
error_message = build_error_message(
input_variables,
invalid_chars,
wrong_variables,
fixed_variables,
empty_variables,
)
raise ValueError(error_message)
return input_variables return input_variables
def build_error_message(
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
):
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
error_string = f"Invalid input variables: {input_variables_str}. "
if wrong_variables and invalid_chars:
# fix the wrong variables replacing invalid chars and find them in the fixed variables
error_string_vars = "You can fix them by replacing the invalid characters: "
wvars = wrong_variables.copy()
for i, wrong_var in enumerate(wvars):
for char in invalid_chars:
wrong_var = wrong_var.replace(char, "")
if wrong_var in fixed_variables:
error_string_vars += f"'{wrong_variables[i]}' -> '{wrong_var}'"
error_string += error_string_vars
elif empty_variables:
error_string += f" There are {len(empty_variables)} empty variable{'s' if len(empty_variables) > 1 else ''}."
elif len(set(fixed_variables)) != len(fixed_variables):
error_string += "There are duplicate variables."
return error_string

View file

@ -1,22 +1,132 @@
import asyncio import asyncio
from typing import Any
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langflow.api.v1.schemas import ChatResponse from langflow.api.v1.schemas import ChatResponse
from typing import Any, Dict, List, Union
from fastapi import WebSocket
from langchain.schema import AgentAction, LLMResult, AgentFinish
from langflow.utils.logger import logger
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py # https://github.com/hwchase17/chat-langchain/blob/master/callback.py
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
"""Callback handler for streaming LLM responses.""" """Callback handler for streaming LLM responses."""
def __init__(self, websocket): def __init__(self, websocket: WebSocket):
self.websocket = websocket self.websocket = websocket
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
resp = ChatResponse(message=token, type="stream", intermediate_steps="") resp = ChatResponse(message=token, type="stream", intermediate_steps="")
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.dict())
async def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> Any:
"""Run when LLM starts running."""
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
"""Run when LLM ends running."""
async def on_llm_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> Any:
"""Run when LLM errors."""
async def on_chain_start(
self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
) -> Any:
"""Run when chain starts running."""
async def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
"""Run when chain ends running."""
async def on_chain_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> Any:
"""Run when chain errors."""
async def on_tool_start(
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
) -> Any:
"""Run when tool starts running."""
resp = ChatResponse(
message="",
type="stream",
intermediate_steps=f"Tool input: {input_str}",
)
await self.websocket.send_json(resp.dict())
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
"""Run when tool ends running."""
observation_prefix = kwargs.get("observation_prefix", "Tool output: ")
split_output = output.split()
first_word = split_output[0]
rest_of_output = split_output[1:]
# Create a formatted message.
intermediate_steps = f"{observation_prefix}{first_word}"
# Create a ChatResponse instance.
resp = ChatResponse(
message="",
type="stream",
intermediate_steps=intermediate_steps,
)
rest_of_resps = [
ChatResponse(
message="",
type="stream",
intermediate_steps=f"{word}",
)
for word in rest_of_output
]
resps = [resp] + rest_of_resps
# Try to send the response, handle potential errors.
try:
# This is to emulate the stream of tokens
for resp in resps:
await self.websocket.send_json(resp.dict())
except Exception as e:
logger.error(e)
async def on_tool_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> Any:
"""Run when tool errors."""
async def on_text(self, text: str, **kwargs: Any) -> Any:
"""Run on arbitrary text."""
# This runs when first sending the prompt
# to the LLM, adding it will send the final prompt
# to the frontend
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
log = f"Thought: {action.log}"
# if there are line breaks, split them and send them
# as separate messages
if "\n" in log:
logs = log.split("\n")
for log in logs:
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
await self.websocket.send_json(resp.dict())
else:
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
await self.websocket.send_json(resp.dict())
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
"""Run on agent end."""
resp = ChatResponse(
message="",
type="stream",
intermediate_steps=finish.log,
)
await self.websocket.send_json(resp.dict())
class StreamingLLMCallbackHandler(BaseCallbackHandler): class StreamingLLMCallbackHandler(BaseCallbackHandler):
"""Callback handler for streaming LLM responses.""" """Callback handler for streaming LLM responses."""

View file

@ -1,5 +1,6 @@
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketException, status from fastapi import APIRouter, HTTPException, WebSocket, WebSocketException, status
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from langflow.api.utils import build_input_keys_response
from langflow.api.v1.schemas import BuildStatus, 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
@ -83,6 +84,7 @@ async def stream_build(flow_id: str):
async def event_stream(flow_id): async def event_stream(flow_id):
final_response = {"end_of_stream": True} final_response = {"end_of_stream": True}
artifacts = {}
try: try:
if flow_id not in flow_data_store: if flow_id not in flow_data_store:
error_message = "Invalid session ID" error_message = "Invalid session ID"
@ -113,13 +115,6 @@ async def stream_build(flow_id: str):
number_of_nodes = len(graph.nodes) number_of_nodes = len(graph.nodes)
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
# To deal with the ZeroShotAgent case
# we need to build the root node first
# and then the rest of the graph
# This is a big problem because certain nodes require
# params that are not connected to it.
# We should consider connecting the tools to the ZeroShotPrompt
graph.build()
for i, vertex in enumerate(graph.generator_build(), 1): for i, vertex in enumerate(graph.generator_build(), 1):
try: try:
@ -131,8 +126,13 @@ async def stream_build(flow_id: str):
params = vertex._built_object_repr() params = vertex._built_object_repr()
valid = True valid = True
logger.debug( logger.debug(
f"Building node {params[:50]}{'...' if len(params) > 50 else ''}" f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}"
) )
if vertex.artifacts:
# The artifacts will be prompt variables
# passed to build_input_keys_response
# to set the input_keys values
artifacts.update(vertex.artifacts)
except Exception as exc: except Exception as exc:
params = str(exc) params = str(exc)
valid = False valid = False
@ -147,9 +147,26 @@ 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()) langchain_object = graph.build()
# Now we need to check the input_keys to send them to the client
if hasattr(langchain_object, "input_keys"):
input_keys_response = build_input_keys_response(
langchain_object, artifacts
)
else:
input_keys_response = {
"input_keys": {},
"memory_keys": [],
"handle_keys": [],
}
yield str(StreamData(event="message", data=input_keys_response))
chat_manager.set_cache(flow_id, langchain_object)
# We need to reset the chat history
chat_manager.chat_history.empty_history(flow_id)
flow_data_store[flow_id]["status"] = BuildStatus.SUCCESS flow_data_store[flow_id]["status"] = BuildStatus.SUCCESS
except Exception as exc: except Exception as exc:
logger.exception(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 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)}))

View file

@ -53,7 +53,7 @@ class ChatMessage(BaseModel):
"""Chat message schema.""" """Chat message schema."""
is_bot: bool = False is_bot: bool = False
message: Union[str, None] = None message: Union[str, None, dict] = None
type: str = "human" type: str = "human"

View file

@ -3,10 +3,11 @@ from fastapi import APIRouter, HTTPException
from langflow.api.v1.base import ( from langflow.api.v1.base import (
Code, Code,
CodeValidationResponse, CodeValidationResponse,
Prompt, ValidatePromptRequest,
PromptValidationResponse, PromptValidationResponse,
validate_prompt, validate_prompt,
) )
from langflow.template.field.base import TemplateField
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.validate import validate_code from langflow.utils.validate import validate_code
@ -27,9 +28,100 @@ def post_validate_code(code: Code):
@router.post("/prompt", status_code=200, response_model=PromptValidationResponse) @router.post("/prompt", status_code=200, response_model=PromptValidationResponse)
def post_validate_prompt(prompt: Prompt): def post_validate_prompt(prompt_request: ValidatePromptRequest):
try: try:
return validate_prompt(prompt.template) input_variables = validate_prompt(prompt_request.template)
old_custom_fields = get_old_custom_fields(prompt_request)
add_new_variables_to_template(input_variables, prompt_request)
remove_old_variables_from_template(
old_custom_fields, input_variables, prompt_request
)
update_input_variables_field(input_variables, prompt_request)
return PromptValidationResponse(
input_variables=input_variables,
frontend_node=prompt_request.frontend_node,
)
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
raise HTTPException(status_code=500, detail=str(e)) from e raise HTTPException(status_code=500, detail=str(e)) from e
def get_old_custom_fields(prompt_request):
try:
old_custom_fields = prompt_request.frontend_node.custom_fields[
prompt_request.name
].copy()
except KeyError:
old_custom_fields = []
prompt_request.frontend_node.custom_fields[prompt_request.name] = []
return old_custom_fields
def add_new_variables_to_template(input_variables, prompt_request):
for variable in input_variables:
try:
template_field = TemplateField(
name=variable,
display_name=variable,
field_type="str",
show=True,
advanced=False,
multiline=True,
input_types=["Document", "BaseOutputParser"],
value="", # Set the value to empty string
)
if variable in prompt_request.frontend_node.template:
# Set the new field with the old value
template_field.value = prompt_request.frontend_node.template[variable][
"value"
]
prompt_request.frontend_node.template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending
if (
variable
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
):
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
variable
)
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template(
old_custom_fields, input_variables, prompt_request
):
for variable in old_custom_fields:
if variable not in input_variables:
try:
# Remove the variable from custom_fields associated with the given name
if (
variable
in prompt_request.frontend_node.custom_fields[prompt_request.name]
):
prompt_request.frontend_node.custom_fields[
prompt_request.name
].remove(variable)
# Remove the variable from the template
prompt_request.frontend_node.template.pop(variable, None)
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
def update_input_variables_field(input_variables, prompt_request):
if "input_variables" in prompt_request.frontend_node.template:
prompt_request.frontend_node.template["input_variables"][
"value"
] = input_variables

View file

@ -104,16 +104,22 @@ class ChatManager:
async def close_connection(self, client_id: str, code: int, reason: str): async def close_connection(self, client_id: str, code: int, reason: str):
if websocket := self.active_connections[client_id]: if websocket := self.active_connections[client_id]:
try:
await websocket.close(code=code, reason=reason) await websocket.close(code=code, reason=reason)
self.disconnect(client_id) self.disconnect(client_id)
except RuntimeError as exc:
# This is to catch the following error:
# Unexpected ASGI message 'websocket.close', after sending 'websocket.close'
if "after sending" in str(exc):
logger.error(exc)
async def process_message( async def process_message(
self, client_id: str, payload: Dict, langchain_object: Any self, client_id: str, payload: Dict, langchain_object: Any
): ):
# Process the graph data and chat message # Process the graph data and chat message
chat_message = payload.pop("message", "") chat_inputs = payload.pop("inputs", "")
chat_message = ChatMessage(message=chat_message) chat_inputs = ChatMessage(message=chat_inputs)
self.chat_history.add_message(client_id, chat_message) self.chat_history.add_message(client_id, chat_inputs)
# graph_data = payload # graph_data = payload
start_resp = ChatResponse(message=None, type="start", intermediate_steps="") start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
@ -126,7 +132,7 @@ class ChatManager:
result, intermediate_steps = await process_graph( result, intermediate_steps = await process_graph(
langchain_object=langchain_object, langchain_object=langchain_object,
chat_message=chat_message, chat_inputs=chat_inputs,
websocket=self.active_connections[client_id], websocket=self.active_connections[client_id],
) )
except Exception as e: except Exception as e:

View file

@ -7,7 +7,7 @@ from langflow.utils.logger import logger
async def process_graph( async def process_graph(
langchain_object, langchain_object,
chat_message: ChatMessage, chat_inputs: ChatMessage,
websocket: WebSocket, websocket: WebSocket,
): ):
langchain_object = try_setting_streaming_options(langchain_object, websocket) langchain_object = try_setting_streaming_options(langchain_object, websocket)
@ -21,9 +21,13 @@ async def process_graph(
# Generate result and thought # Generate result and thought
try: try:
if not chat_inputs.message:
logger.debug("No message provided")
raise ValueError("No message provided")
logger.debug("Generating result and thought") logger.debug("Generating result and thought")
result, intermediate_steps = await get_result_and_steps( result, intermediate_steps = await get_result_and_steps(
langchain_object, chat_message.message or "", websocket=websocket langchain_object, chat_inputs.message, websocket=websocket
) )
logger.debug("Generated result and intermediate_steps") logger.debug("Generated result and intermediate_steps")
return result, intermediate_steps return result, intermediate_steps

View file

@ -151,12 +151,19 @@ memories:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/buffer_window" documentation: "https://python.langchain.com/docs/modules/memory/how_to/buffer_window"
VectorStoreRetrieverMemory: VectorStoreRetrieverMemory:
documentation: "https://python.langchain.com/docs/modules/memory/how_to/vectorstore_retriever_memory" documentation: "https://python.langchain.com/docs/modules/memory/how_to/vectorstore_retriever_memory"
MongoDBChatMessageHistory:
documentation: "https://python.langchain.com/docs/modules/memory/integrations/mongodb_chat_message_history"
prompts: prompts:
ChatMessagePromptTemplate:
documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/msg_prompt_templates"
HumanMessagePromptTemplate:
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/how_to/prompts"
SystemMessagePromptTemplate:
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/how_to/prompts"
ChatPromptTemplate:
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/how_to/prompts"
PromptTemplate: PromptTemplate:
documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/" documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/"
ZeroShotPrompt:
documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent"
textsplitters: textsplitters:
CharacterTextSplitter: CharacterTextSplitter:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter" documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
@ -269,7 +276,17 @@ vectorstores:
SupabaseVectorStore: SupabaseVectorStore:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase" documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase"
MongoDBAtlasVectorSearch: MongoDBAtlasVectorSearch:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas_vector_search" documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas"
# Requires docarray >=0.32.0 but langchain-serve requires jina 3.15.2 which doesn't support docarray >=0.32.0
# DocArrayInMemorySearch:
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/docarray_in_memory"
wrappers: wrappers:
RequestsWrapper: RequestsWrapper:
documentation: "" documentation: ""
SQLDatabase:
documentation: ""
output_parsers:
StructuredOutputParser:
documentation: "https://python.langchain.com/docs/modules/model_io/output_parsers/structured"
ResponseSchema:
documentation: "https://python.langchain.com/docs/modules/model_io/output_parsers/structured"

View file

@ -2,9 +2,9 @@ from langflow.template import frontend_node
# These should always be instantiated # These should always be instantiated
CUSTOM_NODES = { CUSTOM_NODES = {
"prompts": { # "prompts": {
"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(), # "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
}, # },
"tools": { "tools": {
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(), "PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"PythonFunction": frontend_node.tools.PythonFunctionNode(), "PythonFunction": frontend_node.tools.PythonFunctionNode(),
@ -23,6 +23,7 @@ CUSTOM_NODES = {
}, },
"memories": { "memories": {
"PostgresChatMessageHistory": frontend_node.memories.PostgresChatMessageHistoryFrontendNode(), "PostgresChatMessageHistory": frontend_node.memories.PostgresChatMessageHistoryFrontendNode(),
"MongoDBChatMessageHistory": frontend_node.memories.MongoDBChatMessageHistoryFrontendNode(),
}, },
"chains": { "chains": {
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(), "SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),

View file

@ -6,9 +6,15 @@ if TYPE_CHECKING:
class Edge: class Edge:
def __init__(self, source: "Vertex", target: "Vertex"): def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
self.source: "Vertex" = source self.source: "Vertex" = source
self.target: "Vertex" = target self.target: "Vertex" = target
self.source_handle = edge.get("sourceHandle", "")
self.target_handle = edge.get("targetHandle", "")
# 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD'
# target_param is documents
self.target_param = self.target_handle.split("|")[1]
self.validate_edge() self.validate_edge()
def validate_edge(self) -> None: def validate_edge(self) -> None:
@ -42,6 +48,16 @@ class Edge:
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}" f"Edge(source={self.source.id}, target={self.target.id}, target_param={self.target_param}"
f", matched_type={self.matched_type})" f", matched_type={self.matched_type})"
) )
def __hash__(self) -> int:
return hash(self.__repr__())
def __eq__(self, __value: object) -> bool:
return (
self.__repr__() == __value.__repr__()
if isinstance(__value, Edge)
else False
)

View file

@ -179,7 +179,7 @@ class Graph:
raise ValueError(f"Source node {edge['source']} not found") raise ValueError(f"Source node {edge['source']} not found")
if target is None: if target is None:
raise ValueError(f"Target node {edge['target']} not found") raise ValueError(f"Target node {edge['target']} not found")
edges.append(Edge(source, target)) edges.append(Edge(source, target, edge))
return edges return edges
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]: def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
@ -214,3 +214,10 @@ class Graph:
if node_type in node_types: if node_type in node_types:
children.append(node) children.append(node)
return children return children
def __repr__(self):
node_ids = [node.id for node in self.nodes]
edges_repr = "\n".join(
[f"{edge.source.id} --> {edge.target.id}" for edge in self.edges]
)
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"

View file

@ -12,6 +12,7 @@ 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.output_parsers.base import output_parser_creator
from langflow.interface.retrievers.base import retriever_creator from langflow.interface.retrievers.base import retriever_creator
from typing import Dict, Type from typing import Dict, Type
@ -30,5 +31,6 @@ VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()}, **{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()}, **{t: types.RetrieverVertex for t in retriever_creator.to_list()},
} }

View file

@ -1,14 +1,12 @@
from langflow.utils.constants import DIRECT_TYPES
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
from langflow.interface.listing import ALL_TYPES_DICT from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.constants import DIRECT_TYPES
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import sync_to_async from langflow.utils.util import sync_to_async
import contextlib
import inspect import inspect
import types import types
import warnings
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -25,6 +23,7 @@ class Vertex:
self._parse_data() self._parse_data()
self._built_object = None self._built_object = None
self._built = False self._built = False
self.artifacts: Dict[str, Any] = {}
def _parse_data(self) -> None: def _parse_data(self) -> None:
self.data = self._data["data"] self.data = self._data["data"]
@ -45,6 +44,14 @@ class Vertex:
for key, value in template_dicts.items() for key, value in template_dicts.items()
if not value["required"] if not value["required"]
] ]
# Add the template_dicts[key]["input_types"] to the optional_inputs
self.optional_inputs.extend(
[
input_type
for value in template_dicts.values()
for input_type in value.get("input_types", [])
]
)
template_dict = self.data["node"]["template"] template_dict = self.data["node"]["template"]
self.vertex_type = ( self.vertex_type = (
@ -60,6 +67,7 @@ class Vertex:
break break
def _build_params(self): def _build_params(self):
# sourcery skip: merge-list-append, remove-redundant-if
# Some params are required, some are optional # Some params are required, some are optional
# but most importantly, some params are python base classes # but most importantly, some params are python base classes
# like str and others are LangChain objects like LLMChain, BasePromptTemplate # like str and others are LangChain objects like LLMChain, BasePromptTemplate
@ -80,8 +88,19 @@ class Vertex:
if isinstance(value, dict) if isinstance(value, dict)
} }
params = {} params = {}
for edge in self.edges:
param_key = edge.target_param
if param_key in template_dict:
if template_dict[param_key]["list"]:
if param_key not in params:
params[param_key] = []
params[param_key].append(edge.source)
elif edge.target.id == self.id:
params[param_key] = edge.source
for key, value in template_dict.items(): for key, value in template_dict.items():
if key == "_type": if key == "_type" or not value.get("show"):
continue continue
# If the type is not transformable to a python base class # If the type is not transformable to a python base class
# then we need to get the edge that connects to this node # then we need to get the edge that connects to this node
@ -92,75 +111,81 @@ class Vertex:
file_path = value.get("file_path") file_path = value.get("file_path")
params[key] = file_path params[key] = file_path
elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
params[key] = value.get("value")
elif value.get("type") not in DIRECT_TYPES: if not value.get("required") and params.get(key) is None:
# Get the edge that connects to this node if value.get("default"):
edges = [ params[key] = value.get("default")
edge else:
for edge in self.edges params.pop(key, None)
if edge.target == self and edge.matched_type in value["type"]
]
# Get the output of the node that the edge connects to
# if the value['list'] is True, then there will be more
# than one time setting to params[key]
# so we need to append to a list if it exists
# or create a new list if it doesn't
if value["required"] and not edges:
# If a required parameter is not found, raise an error
raise ValueError(
f"Required input {key} for module {self.vertex_type} not found"
)
elif value["list"]:
# If this is a list parameter, append all sources to a list
params[key] = [edge.source for edge in edges]
elif edges:
# If a single parameter is found, use its source
params[key] = edges[0].source
elif value["required"] or value.get("value"):
# If value does not have value this still passes
# but then gives a keyError
# so we need to check if value has value
new_value = value.get("value")
if new_value is None:
warnings.warn(f"Value for {key} in {self.vertex_type} is None. ")
if value.get("type") == "int":
with contextlib.suppress(TypeError, ValueError):
new_value = int(new_value) # type: ignore
params[key] = new_value
# Add _type to params # Add _type to params
self.params = params self.params = params
def _build(self): def _build(self):
# The params dict is used to build the module """
# it contains values and keys that point to nodes which Initiate the build process.
# have their own params dict """
# When build is called, we iterate through the params dict
# and if the value is a node, we call build on that node
# and use the output of that build as the value for the param
# if the value is not a node, then we use the value as the param
# and continue
# Another aspect is that the node_type is the class that we need to import
# and instantiate with these built params
logger.debug(f"Building {self.vertex_type}") logger.debug(f"Building {self.vertex_type}")
# Build each node in the params dict self._build_each_node_in_params_dict()
self._get_and_instantiate_class()
self._validate_built_object()
self._built = True
def _build_each_node_in_params_dict(self):
"""
Iterates over each node in the params dictionary and builds it.
"""
for key, value in self.params.copy().items(): for key, value in self.params.copy().items():
# Check if Node or list of Nodes and not self if self._is_node(value):
# to avoid recursion
if isinstance(value, Vertex):
if value == self: if value == self:
del self.params[key] del self.params[key]
continue continue
result = value.build() self._build_node_and_update_params(key, value)
# If the key is "func", then we need to use the run method elif isinstance(value, list) and self._is_list_of_nodes(value):
self._build_list_of_nodes_and_update_params(key, value)
def _is_node(self, value):
"""
Checks if the provided value is an instance of Vertex.
"""
return isinstance(value, Vertex)
def _is_list_of_nodes(self, value):
"""
Checks if the provided value is a list of Vertex instances.
"""
return all(self._is_node(node) for node in value)
def _build_node_and_update_params(self, key, node):
"""
Builds a given node and updates the params dictionary accordingly.
"""
result = node.build()
self._handle_func(key, result)
if isinstance(result, list):
self._extend_params_list_with_result(key, result)
self.params[key] = result
def _build_list_of_nodes_and_update_params(self, key, nodes):
"""
Iterates over a list of nodes, builds each and updates the params dictionary.
"""
self.params[key] = []
for node in nodes:
built = node.build()
if isinstance(built, list):
self.params[key].extend(built)
else:
self.params[key].append(built)
def _handle_func(self, key, result):
"""
Handles 'func' key by checking if the result is a function and setting it as coroutine.
"""
if key == "func": if key == "func":
if not isinstance(result, types.FunctionType): if not isinstance(result, types.FunctionType):
# func can be
# PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n')
# so we need to check if there is an attribute called run
if hasattr(result, "run"): if hasattr(result, "run"):
result = result.run # type: ignore result = result.run # type: ignore
elif hasattr(result, "get_function"): elif hasattr(result, "get_function"):
@ -168,48 +193,49 @@ class Vertex:
elif inspect.iscoroutinefunction(result): elif inspect.iscoroutinefunction(result):
self.params["coroutine"] = result self.params["coroutine"] = result
else: else:
# turn result which is a function into a coroutine
# so that it can be awaited
self.params["coroutine"] = sync_to_async(result) self.params["coroutine"] = sync_to_async(result)
if isinstance(result, list):
# If the result is a list, then we need to extend the list def _extend_params_list_with_result(self, key, result):
# with the result but first check if the key exists """
# if it doesn't, then we need to create a new list Extends a list in the params dictionary with the given result if it exists.
"""
if isinstance(self.params[key], list): if isinstance(self.params[key], list):
self.params[key].extend(result) self.params[key].extend(result)
self.params[key] = result def _get_and_instantiate_class(self):
elif isinstance(value, list) and all( """
isinstance(node, Vertex) for node in value Gets the class from a dictionary and instantiates it with the params.
): """
self.params[key] = [] if self.base_type is None:
for node in value: raise ValueError(f"Base type for node {self.vertex_type} not found")
built = node.build()
if isinstance(built, list):
self.params[key].extend(built)
else:
self.params[key].append(built)
# Get the class from LANGCHAIN_TYPES_DICT
# and instantiate it with the params
# and return the instance
try: try:
self._built_object = loading.instantiate_class( result = loading.instantiate_class(
node_type=self.vertex_type, node_type=self.vertex_type,
base_type=self.base_type, base_type=self.base_type,
params=self.params, params=self.params,
) )
self._update_built_object_and_artifacts(result)
except Exception as exc: except Exception as exc:
raise ValueError( raise ValueError(
f"Error building node {self.vertex_type}: {str(exc)}" f"Error building node {self.vertex_type}: {str(exc)}"
) from exc ) from exc
def _update_built_object_and_artifacts(self, result):
"""
Updates the built object and its artifacts.
"""
if isinstance(result, tuple):
self._built_object, self.artifacts = result
else:
self._built_object = result
def _validate_built_object(self):
"""
Checks if the built object is None and raises a ValueError if so.
"""
if self._built_object is None: if self._built_object is None:
raise ValueError(f"Node type {self.vertex_type} not found") raise ValueError(f"Node type {self.vertex_type} not found")
self._built = True
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
if not self._built or force: if not self._built or force:
self._build() self._build()
@ -217,6 +243,7 @@ class Vertex:
return self._built_object return self._built_object
def add_edge(self, edge: "Edge") -> None: def add_edge(self, edge: "Edge") -> None:
if edge not in self.edges:
self.edges.append(edge) self.edges.append(edge)
def __repr__(self) -> str: def __repr__(self) -> str:
@ -229,4 +256,5 @@ class Vertex:
return id(self) return id(self)
def _built_object_repr(self): def _built_object_repr(self):
return repr(self._built_object) # Add a message with an emoji, stars for sucess,
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵‍💫"

View file

@ -1,3 +1,4 @@
import ast
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
@ -79,7 +80,7 @@ class WrapperVertex(Vertex):
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
if not self._built or force: if not self._built or force:
if "headers" in self.params: if "headers" in self.params:
self.params["headers"] = eval(self.params["headers"]) self.params["headers"] = ast.literal_eval(self.params["headers"])
self._build() self._build()
return self._built_object return self._built_object
@ -91,8 +92,13 @@ class DocumentLoaderVertex(Vertex):
def _built_object_repr(self): def _built_object_repr(self):
# This built_object is a list of documents. Maybe we should # This built_object is a list of documents. Maybe we should
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {avg_length}
Documents: {self._built_object[:3]}...""" Documents: {self._built_object[:3]}..."""
return f"{self.vertex_type}()" return f"{self.vertex_type}()"
@ -124,8 +130,13 @@ class TextSplitterVertex(Vertex):
def _built_object_repr(self): def _built_object_repr(self):
# This built_object is a list of documents. Maybe we should # This built_object is a list of documents. Maybe we should
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {avg_length}
\nDocuments: {self._built_object[:3]}...""" \nDocuments: {self._built_object[:3]}..."""
return f"{self.vertex_type}()" return f"{self.vertex_type}()"
@ -185,11 +196,46 @@ class PromptVertex(Vertex):
] ]
else: else:
prompt_params = ["template"] prompt_params = ["template"]
if "prompt" not in self.params and "messages" not in self.params:
for param in prompt_params: for param in prompt_params:
prompt_text = self.params[param] prompt_text = self.params[param]
variables = extract_input_variables_from_prompt(prompt_text) variables = extract_input_variables_from_prompt(prompt_text)
self.params["input_variables"].extend(variables) self.params["input_variables"].extend(variables)
self.params["input_variables"] = list(set(self.params["input_variables"])) self.params["input_variables"] = list(
set(self.params["input_variables"])
)
else:
self.params.pop("input_variables", None)
self._build() self._build()
return self._built_object return self._built_object
def _built_object_repr(self):
if (
not self.artifacts
or self._built_object is None
or not hasattr(self._built_object, "format")
):
return super()._built_object_repr()
# We'll build the prompt with the artifacts
# to show the user what the prompt looks like
# with the variables filled in
artifacts = self.artifacts.copy()
# Remove the handle_keys from the artifacts
# so the prompt format doesn't break
artifacts.pop("handle_keys", None)
try:
template = self._built_object.format(**artifacts)
return (
template
if isinstance(template, str)
else f"{self.vertex_type}({template})"
)
except KeyError:
return str(self._built_object)
class OutputParserVertex(Vertex):
def __init__(self, data: Dict):
super().__init__(data, base_type="output_parsers")

View file

@ -6,13 +6,20 @@ from langflow.custom.customs import get_custom_nodes
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.settings import settings from langflow.settings import settings
from langflow.template.frontend_node.agents import AgentFrontendNode
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
class AgentCreator(LangChainTypeCreator): class AgentCreator(LangChainTypeCreator):
type_name: str = "agents" type_name: str = "agents"
from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"}
@property
def frontend_node_class(self) -> type[AgentFrontendNode]:
return AgentFrontendNode
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
@ -27,6 +34,13 @@ class AgentCreator(LangChainTypeCreator):
try: try:
if name in get_custom_nodes(self.type_name).keys(): if name in get_custom_nodes(self.type_name).keys():
return get_custom_nodes(self.type_name)[name] 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=self.type_to_loader_dict,
add_function=True,
method_name=self.from_method_nodes[name],
)
return build_template_from_class( return build_template_from_class(
name, self.type_to_loader_dict, add_function=True name, self.type_to_loader_dict, add_function=True
) )

View file

@ -157,7 +157,7 @@ class VectorStoreAgent(CustomAgentExecutor):
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
) )
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
) )
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
@ -232,6 +232,7 @@ class SQLAgent(CustomAgentExecutor):
verbose=True, verbose=True,
max_iterations=15, max_iterations=15,
early_stopping_method="force", early_stopping_method="force",
handle_parsing_errors=True,
) )
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
@ -276,7 +277,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
) )
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
) )
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
@ -308,6 +309,7 @@ class InitializeAgent(CustomAgentExecutor):
agent=agent, # type: ignore agent=agent, # type: ignore
memory=memory, memory=memory,
return_intermediate_steps=True, return_intermediate_steps=True,
handle_parsing_errors=True,
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View file

@ -23,6 +23,7 @@ class ChainCreator(LangChainTypeCreator):
from_method_nodes = { from_method_nodes = {
"ConversationalRetrievalChain": "from_llm", "ConversationalRetrievalChain": "from_llm",
"LLMCheckerChain": "from_llm", "LLMCheckerChain": "from_llm",
"SQLDatabaseChain": "from_llm",
} }
@property @property

View file

@ -10,6 +10,7 @@ from langchain.chains.base import Chain
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.tools import BaseTool from langchain.tools import BaseTool
from langflow.utils import validate from langflow.utils import validate
from langflow.interface.wrappers.base import wrapper_creator
def import_module(module_path: str) -> Any: def import_module(module_path: str) -> Any:
@ -44,6 +45,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,
"output_parsers": import_output_parser,
"retrievers": import_retriever, "retrievers": import_retriever,
} }
if _type == "llms": if _type == "llms":
@ -55,6 +57,11 @@ def import_by_type(_type: str, name: str) -> Any:
return loaded_func(name) return loaded_func(name)
def import_output_parser(output_parser: str) -> Any:
"""Import output parser from output parser name"""
return import_module(f"from langchain.output_parsers import {output_parser}")
def import_chat_llm(llm: str) -> BaseChatModel: def import_chat_llm(llm: str) -> BaseChatModel:
"""Import chat llm from llm name""" """Import chat llm from llm name"""
return import_class(f"langchain.chat_models.{llm}") return import_class(f"langchain.chat_models.{llm}")
@ -90,7 +97,11 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]:
def import_wrapper(wrapper: str) -> Any: def import_wrapper(wrapper: str) -> Any:
"""Import wrapper from wrapper name""" """Import wrapper from wrapper name"""
return import_module(f"from langchain.requests import {wrapper}") if (
isinstance(wrapper_creator.type_dict, dict)
and wrapper in wrapper_creator.type_dict
):
return wrapper_creator.type_dict.get(wrapper)
def import_toolkit(toolkit: str) -> Any: def import_toolkit(toolkit: str) -> Any:

View file

@ -1,5 +1,6 @@
import contextlib
import json import json
from typing import Any, Callable, Dict, Sequence, Type from typing import Any, Callable, Dict, List, 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
@ -10,19 +11,22 @@ 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 langchain.schema import Document, BaseOutputParser
from pydantic import ValidationError from pydantic import ValidationError
from langflow.interface.custom_lists import CUSTOM_NODES 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.agents.base import agent_creator
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.output_parsers.base import output_parser_creator
from langflow.interface.retrievers.base import retriever_creator from langflow.interface.retrievers.base import retriever_creator
from langflow.interface.wrappers.base import wrapper_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.chains.base import Chain
from langchain.vectorstores.base import VectorStore from langchain.vectorstores.base import VectorStore
from langchain.document_loaders.base import BaseLoader 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:
@ -60,11 +64,15 @@ def convert_kwargs(params):
def instantiate_based_on_type(class_object, base_type, node_type, params): def instantiate_based_on_type(class_object, base_type, node_type, params):
if base_type == "agents": if base_type == "agents":
return instantiate_agent(class_object, params) return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts": elif base_type == "prompts":
return instantiate_prompt(node_type, class_object, params) return instantiate_prompt(node_type, class_object, params)
elif base_type == "tools": elif base_type == "tools":
return instantiate_tool(node_type, class_object, params) tool = instantiate_tool(node_type, class_object, params)
if hasattr(tool, "name") and isinstance(tool, BaseTool):
# tool name shouldn't contain spaces
tool.name = tool.name.replace(" ", "_")
return tool
elif base_type == "toolkits": elif base_type == "toolkits":
return instantiate_toolkit(node_type, class_object, params) return instantiate_toolkit(node_type, class_object, params)
elif base_type == "embeddings": elif base_type == "embeddings":
@ -79,26 +87,63 @@ 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 == "output_parsers":
return instantiate_output_parser(node_type, class_object, params)
elif base_type == "llms": elif base_type == "llms":
return instantiate_llm(node_type, class_object, params) return instantiate_llm(node_type, class_object, params)
elif base_type == "retrievers": elif base_type == "retrievers":
return instantiate_retriever(node_type, class_object, params) return instantiate_retriever(node_type, class_object, params)
elif base_type == "memory": elif base_type == "memory":
return instantiate_memory(node_type, class_object, params) return instantiate_memory(node_type, class_object, params)
elif base_type == "wrappers":
return instantiate_wrapper(node_type, class_object, params)
else: else:
return class_object(**params) return class_object(**params)
def instantiate_wrapper(node_type, class_object, params):
if node_type in wrapper_creator.from_method_nodes:
method = wrapper_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_output_parser(node_type, class_object, params):
if node_type in output_parser_creator.from_method_nodes:
method = output_parser_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_llm(node_type, class_object, params: Dict): def instantiate_llm(node_type, class_object, params: Dict):
# This is a workaround so JinaChat works until streaming is implemented # This is a workaround so JinaChat works until streaming is implemented
# if "openai_api_base" in params and "jina" in params["openai_api_base"]: # if "openai_api_base" in params and "jina" in params["openai_api_base"]:
# False if condition is True # False if condition is True
if node_type == "VertexAI": if node_type == "VertexAI":
return initialize_vertexai(class_object=class_object, params=params) return initialize_vertexai(class_object=class_object, params=params)
# max_tokens sometimes is a string and should be an int
if "max_tokens" in params:
if isinstance(params["max_tokens"], str) and params["max_tokens"].isdigit():
params["max_tokens"] = int(params["max_tokens"])
elif not isinstance(params.get("max_tokens"), int):
params.pop("max_tokens", None)
return class_object(**params) return class_object(**params)
def instantiate_memory(node_type, class_object, params): def instantiate_memory(node_type, class_object, params):
# process input_key and output_key to remove them if
# they are empty strings
if node_type == "ConversationEntityMemory":
params.pop("memory_key", None)
for key in ["input_key", "output_key"]:
if key in params and (params[key] == "" or not params[key]):
params.pop(key)
try: try:
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()
@ -141,26 +186,94 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
return class_object(**params) return class_object(**params)
def instantiate_agent(class_object: Type[agent_module.Agent], params: Dict): def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
if node_type in agent_creator.from_method_nodes:
method = agent_creator.from_method_nodes[node_type]
if class_method := getattr(class_object, method, None):
agent = class_method(**params)
tools = params.get("tools", [])
return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, handle_parsing_errors=True
)
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)
def instantiate_prompt(node_type, class_object: Type[BasePromptTemplate], params: Dict): def instantiate_prompt(node_type, class_object, params: Dict):
if node_type == "ZeroShotPrompt": if node_type == "ZeroShotPrompt":
if "tools" not in params: if "tools" not in params:
params["tools"] = [] params["tools"] = []
return ZeroShotAgent.create_prompt(**params) return ZeroShotAgent.create_prompt(**params)
return class_object(**params) elif "MessagePromptTemplate" in node_type:
# Then we only need the template
from_template_params = {
"template": params.pop("prompt", params.pop("template", ""))
}
if not from_template_params.get("template"):
raise ValueError("Prompt template is required")
prompt = class_object.from_template(**from_template_params)
elif node_type == "ChatPromptTemplate":
prompt = class_object.from_messages(**params)
else:
prompt = class_object(**params)
format_kwargs: Dict[str, Any] = {}
for input_variable in prompt.input_variables:
if input_variable in params:
variable = params[input_variable]
if isinstance(variable, str):
format_kwargs[input_variable] = variable
elif isinstance(variable, BaseOutputParser) and hasattr(
variable, "get_format_instructions"
):
format_kwargs[input_variable] = variable.get_format_instructions()
elif isinstance(variable, List) and all(
isinstance(item, Document) for item in variable
):
# Format document to contain page_content and metadata
# as one string separated by a newline
if len(variable) > 1:
content = "\n".join(
[item.page_content for item in variable if item.page_content]
)
else:
content = variable[0].page_content
# content could be a json list of strings
with contextlib.suppress(json.JSONDecodeError):
content = json.loads(content)
if isinstance(content, list):
content = ",".join([str(item) for item in content])
format_kwargs[input_variable] = content
# handle_keys will be a list but it does not exist yet
# so we need to create it
if (
isinstance(variable, List)
and all(isinstance(item, Document) for item in variable)
) or (
isinstance(variable, BaseOutputParser)
and hasattr(variable, "get_format_instructions")
):
if "handle_keys" not in format_kwargs:
format_kwargs["handle_keys"] = []
# Add the handle_keys to the list
format_kwargs["handle_keys"].append(input_variable)
return prompt, format_kwargs
def instantiate_tool(node_type, class_object: Type[BaseTool], params: Dict): 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")) if file_dict := load_file_into_dict(params.pop("path")):
params["dict_"] = file_dict
else:
raise ValueError("Invalid file")
return class_object(**params) return class_object(**params)
elif node_type == "PythonFunctionTool": elif node_type == "PythonFunctionTool":
params["func"] = get_function(params.get("code")) params["func"] = get_function(params.get("code"))
return class_object(**params) return class_object(**params)
# For backward compatibility
elif node_type == "PythonFunction": elif node_type == "PythonFunction":
function_string = params["code"] function_string = params["code"]
if isinstance(function_string, str): if isinstance(function_string, str):
@ -218,7 +331,7 @@ def instantiate_documentloader(class_object: Type[BaseLoader], params: Dict):
# like lambda x: x.endswith(".txt") but as we don't know # like lambda x: x.endswith(".txt") but as we don't know
# anything besides the string, we will simply check if the string is # anything besides the string, we will simply check if the string is
# in x and if it is, we will return True # in x and if it is, we will return True
file_filter = params.pop("file_filter", None) file_filter = params.pop("file_filter")
extensions = file_filter.split(",") extensions = file_filter.split(",")
params["file_filter"] = lambda x: any( params["file_filter"] = lambda x: any(
extension.strip() in x for extension in extensions extension.strip() in x for extension in extensions
@ -260,6 +373,12 @@ def instantiate_textsplitter(
"separator_type" in params and params["separator_type"] == "Text" "separator_type" in params and params["separator_type"] == "Text"
) or "separator_type" not in params: ) or "separator_type" not in params:
params.pop("separator_type", None) params.pop("separator_type", None)
# separators might come in as an escaped string like \\n
# so we need to convert it to a string
if "separators" in params:
params["separators"] = (
params["separators"].encode().decode("unicode-escape")
)
text_splitter = class_object(**params) text_splitter = class_object(**params)
else: else:
from langchain.text_splitter import Language from langchain.text_splitter import Language
@ -312,6 +431,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,
handle_parsing_errors=True,
# memory=memory, # 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.output_parsers.base import output_parser_creator
from langflow.interface.retrievers.base import retriever_creator from langflow.interface.retrievers.base import retriever_creator
@ -29,6 +30,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(),
"outputParsers": output_parser_creator.to_list(),
"retrievers": retriever_creator.to_list(), "retrievers": retriever_creator.to_list(),
} }

View file

@ -0,0 +1,64 @@
from typing import Dict, List, Optional, Type
from langchain import output_parsers
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class, build_template_from_method
class OutputParserCreator(LangChainTypeCreator):
type_name: str = "output_parsers"
from_method_nodes = {
"StructuredOutputParser": "from_response_schemas",
}
@property
def frontend_node_class(self) -> Type[OutputParserFrontendNode]:
return OutputParserFrontendNode
@property
def type_to_loader_dict(self) -> Dict:
if self.type_dict is None:
self.type_dict = {
output_parser_name: import_class(
f"langchain.output_parsers.{output_parser_name}"
)
# if output_parser_name is not lower case it is a class
for output_parser_name in output_parsers.__all__
}
self.type_dict = {
name: output_parser
for name, output_parser in self.type_dict.items()
if name in settings.output_parsers or settings.dev
}
return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]:
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("OutputParser not found") from exc
logger.error(f"OutputParser {name} not found: {exc}")
except AttributeError as exc:
logger.error(f"OutputParser {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys())
output_parser_creator = OutputParserCreator()

View file

@ -14,6 +14,23 @@ def build_langchain_object_with_caching(data_graph):
return graph.build() return graph.build()
@memoize_dict(maxsize=10)
def build_sorted_vertices_with_caching(data_graph):
"""
Build langchain object from data_graph.
"""
logger.debug("Building langchain object")
graph = Graph.from_payload(data_graph)
sorted_vertices = graph.topological_sort()
artifacts = {}
for vertex in sorted_vertices:
vertex.build()
if vertex.artifacts:
artifacts.update(vertex.artifacts)
return graph.build(), artifacts
def build_langchain_object(data_graph): def build_langchain_object(data_graph):
""" """
Build langchain object from data_graph. Build langchain object from data_graph.

View file

@ -90,7 +90,7 @@ class ToolCreator(LangChainTypeCreator):
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a tool.""" """Get the signature of a tool."""
base_classes = ["Tool"] base_classes = ["Tool", "BaseTool"]
fields = [] fields = []
params = [] params = []
tool_params = {} tool_params = {}

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.output_parsers.base import output_parser_creator
from langflow.interface.retrievers.base import retriever_creator from langflow.interface.retrievers.base import retriever_creator
@ -45,6 +46,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,
output_parser_creator,
retriever_creator, retriever_creator,
] ]

View file

@ -16,17 +16,15 @@ def load_file_into_dict(file_path: str) -> dict:
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}") raise FileNotFoundError(f"File not found: {file_path}")
file_extension = os.path.splitext(file_path)[1].lower() # Files names are UUID, so we can't find the extension
with open(file_path, "r") as file:
if file_extension == ".json": try:
with open(file_path, "r") as json_file: data = json.load(file)
data = json.load(json_file) except json.JSONDecodeError:
elif file_extension in [".yaml", ".yml"]: file.seek(0)
with open(file_path, "r") as yaml_file: data = yaml.safe_load(file)
data = yaml.safe_load(yaml_file) except ValueError as exc:
else: raise ValueError("Invalid file type. Expected .json or .yaml.") from exc
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
return data return data

View file

@ -1,25 +1,36 @@
from typing import Dict, List, Optional from typing import Dict, List, Optional
from langchain import requests from langchain import requests, sql_database
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
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
class WrapperCreator(LangChainTypeCreator): class WrapperCreator(LangChainTypeCreator):
type_name: str = "wrappers" type_name: str = "wrappers"
from_method_nodes = {"SQLDatabase": "from_uri"}
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
self.type_dict = { self.type_dict = {
wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper] wrapper.__name__: wrapper
for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
} }
return self.type_dict return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:
try: try:
if name in self.from_method_nodes:
return build_template_from_method(
name,
type_to_cls_dict=self.type_to_loader_dict,
add_function=True,
method_name=self.from_method_nodes[name],
)
return build_template_from_class(name, self.type_to_loader_dict) return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc: except ValueError as exc:
raise ValueError("Wrapper not found") from exc raise ValueError("Wrapper not found") from exc

View file

@ -3,9 +3,9 @@
import os import os
# Use the JCLOUD_WORKSPACE for db URL if it's provided by JCloud. # Use the JCLOUD_WORKSPACE for db URL if it's provided by JCloud.
if 'JCLOUD_WORKSPACE' in os.environ: if "JCLOUD_WORKSPACE" in os.environ:
os.environ[ os.environ[
'LANGFLOW_DATABASE_URL' "LANGFLOW_DATABASE_URL"
] = f"sqlite:///{os.environ['JCLOUD_WORKSPACE']}/langflow.db" ] = f"sqlite:///{os.environ['JCLOUD_WORKSPACE']}/langflow.db"
from langflow.main import setup_app from langflow.main import setup_app

View file

@ -1,3 +1,4 @@
from typing import Union
from langflow.api.v1.callback import ( from langflow.api.v1.callback import (
AsyncStreamingLLMCallbackHandler, AsyncStreamingLLMCallbackHandler,
StreamingLLMCallbackHandler, StreamingLLMCallbackHandler,
@ -6,39 +7,31 @@ from langflow.processing.process import fix_memory_inputs, format_actions
from langflow.utils.logger import logger from langflow.utils.logger import logger
async def get_result_and_steps(langchain_object, message: str, **kwargs): async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwargs):
"""Get result and thought from extracted json""" """Get result and thought from extracted json"""
try: try:
if hasattr(langchain_object, "verbose"): if hasattr(langchain_object, "verbose"):
langchain_object.verbose = True langchain_object.verbose = True
chat_input = None
memory_key = ""
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
memory_key = langchain_object.memory.memory_key
if hasattr(langchain_object, "input_keys"):
for key in langchain_object.input_keys:
if key not in [memory_key, "chat_history"]:
chat_input = {key: message}
else:
chat_input = message # type: ignore
if hasattr(langchain_object, "return_intermediate_steps"): if hasattr(langchain_object, "return_intermediate_steps"):
# https://github.com/hwchase17/langchain/issues/2068 # https://github.com/hwchase17/langchain/issues/2068
# Deactivating until we have a frontend solution # Deactivating until we have a frontend solution
# to display intermediate steps # to display intermediate steps
langchain_object.return_intermediate_steps = True langchain_object.return_intermediate_steps = True
try:
fix_memory_inputs(langchain_object) fix_memory_inputs(langchain_object)
except Exception as exc:
logger.error(exc)
try: try:
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)] async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
output = await langchain_object.acall(chat_input, callbacks=async_callbacks) output = await langchain_object.acall(inputs, callbacks=async_callbacks)
except Exception as exc: except Exception as exc:
# make the error message more informative # make the error message more informative
logger.debug(f"Error: {str(exc)}") logger.debug(f"Error: {str(exc)}")
sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)] sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)]
output = langchain_object(chat_input, callbacks=sync_callbacks) output = langchain_object(inputs, callbacks=sync_callbacks)
intermediate_steps = ( intermediate_steps = (
output.get("intermediate_steps", []) if isinstance(output, dict) else [] output.get("intermediate_steps", []) if isinstance(output, dict) else []
@ -49,7 +42,11 @@ async def get_result_and_steps(langchain_object, message: str, **kwargs):
if isinstance(output, dict) if isinstance(output, dict)
else output else output
) )
try:
thought = format_actions(intermediate_steps) if intermediate_steps else "" thought = format_actions(intermediate_steps) if intermediate_steps else ""
except Exception as exc:
logger.exception(exc)
thought = ""
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
raise ValueError(f"Error: {str(exc)}") from exc raise ValueError(f"Error: {str(exc)}") from exc

View file

@ -2,7 +2,7 @@ from pathlib import Path
from langchain.schema import AgentAction from langchain.schema import AgentAction
import json import json
from langflow.interface.run import ( from langflow.interface.run import (
build_langchain_object_with_caching, build_sorted_vertices_with_caching,
get_memory_key, get_memory_key,
update_memory_keys, update_memory_keys,
) )
@ -22,7 +22,10 @@ def fix_memory_inputs(langchain_object):
if not hasattr(langchain_object, "memory") or langchain_object.memory is None: if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
return return
try: try:
if langchain_object.memory.memory_key in langchain_object.input_variables: if (
hasattr(langchain_object.memory, "memory_key")
and langchain_object.memory.memory_key in langchain_object.input_variables
):
return return
except AttributeError: except AttributeError:
input_variables = ( input_variables = (
@ -88,8 +91,20 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
with PromptTemplate,then run the graph and return the result and thought. with PromptTemplate,then run the graph and return the result and thought.
""" """
# Load langchain object # Load langchain object
langchain_object = build_langchain_object_with_caching(data_graph) langchain_object, artifacts = build_sorted_vertices_with_caching(data_graph)
logger.debug("Loaded LangChain object") logger.debug("Loaded LangChain object")
if inputs is None:
inputs = {}
# Add artifacts to inputs
# artifacts can be documents loaded when building
# the flow
for (
key,
value,
) in artifacts.items():
if key not in inputs or not inputs[key]:
inputs[key] = value
if langchain_object is None: if langchain_object is None:
# Raise user facing error # Raise user facing error
@ -105,8 +120,7 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
result = get_result_and_thought(langchain_object, inputs) result = get_result_and_thought(langchain_object, inputs)
logger.debug("Generated result and thought") logger.debug("Generated result and thought")
elif isinstance(langchain_object, VectorStore): elif isinstance(langchain_object, VectorStore):
class_name = langchain_object.__class__.__name__ result = langchain_object.search(**inputs)
result = {"message": f"Processed {class_name} successfully"}
else: else:
raise ValueError( raise ValueError(
f"Unknown langchain_object type: {type(langchain_object).__name__}" f"Unknown langchain_object type: {type(langchain_object).__name__}"
@ -115,23 +129,23 @@ def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = No
def load_flow_from_json( def load_flow_from_json(
input: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
): ):
""" """
Load flow from a JSON file or a JSON object. Load flow from a JSON file or a JSON object.
:param input: JSON file path or JSON object :param flow: JSON file path or JSON object
:param tweaks: Optional tweaks to be processed :param tweaks: Optional tweaks to be processed
:param build: If True, build the graph, otherwise return the graph object :param build: If True, build the graph, otherwise return the graph object
:return: Langchain object or Graph object depending on the build parameter :return: Langchain object or Graph object depending on the build parameter
""" """
# If input is a file path, load JSON from the file # If input is a file path, load JSON from the file
if isinstance(input, (str, Path)): if isinstance(flow, (str, Path)):
with open(input, "r", encoding="utf-8") as f: with open(flow, "r", encoding="utf-8") as f:
flow_graph = json.load(f) flow_graph = json.load(f)
# If input is a dictionary, assume it's a JSON object # If input is a dictionary, assume it's a JSON object
elif isinstance(input, dict): elif isinstance(flow, dict):
flow_graph = input flow_graph = flow
else: else:
raise TypeError( raise TypeError(
"Input must be either a file path (str) or a JSON object (dict)" "Input must be either a file path (str) or a JSON object (dict)"

View file

@ -21,6 +21,7 @@ class Settings(BaseSettings):
toolkits: dict = {} toolkits: dict = {}
textsplitters: dict = {} textsplitters: dict = {}
utilities: dict = {} utilities: dict = {}
output_parsers: dict = {}
dev: bool = False dev: bool = False
database_url: Optional[str] = None database_url: Optional[str] = None
cache: str = "InMemoryCache" cache: str = "InMemoryCache"
@ -66,7 +67,7 @@ class Settings(BaseSettings):
self.vectorstores = new_settings.vectorstores or {} self.vectorstores = new_settings.vectorstores or {}
self.documentloaders = new_settings.documentloaders or {} self.documentloaders = new_settings.documentloaders or {}
self.retrievers = new_settings.retrievers or {} self.retrievers = new_settings.retrievers or {}
self.output_parsers = new_settings.output_parsers 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
input_types: list[str] = []
info: Optional[str] = "" info: Optional[str] = ""
def to_dict(self): def to_dict(self):

View file

@ -13,6 +13,16 @@ NON_CHAT_AGENTS = {
} }
class AgentFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
if field.name in ["suffix", "prefix"]:
field.show = True
if field.name == "Tools" and name == "ZeroShotAgent":
field.field_type = "BaseTool"
field.is_list = True
class SQLAgentNode(FrontendNode): class SQLAgentNode(FrontendNode):
name: str = "SQLAgent" name: str = "SQLAgent"
template: Template = Template( template: Template = Template(

View file

@ -1,15 +1,16 @@
from collections import defaultdict
import re import re
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from langflow.template.frontend_node.formatter import field_formatters
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"] CLASSES_TO_REMOVE = ["Serializable", "BaseModel", "object"]
class FieldFormatters(BaseModel): class FieldFormatters(BaseModel):
@ -47,6 +48,8 @@ class FrontendNode(BaseModel):
name: str = "" name: str = ""
display_name: str = "" display_name: str = ""
documentation: str = "" documentation: str = ""
custom_fields: defaultdict = defaultdict(list)
output_types: List[str] = []
field_formatters: FieldFormatters = Field(default_factory=FieldFormatters) field_formatters: FieldFormatters = Field(default_factory=FieldFormatters)
def process_base_classes(self) -> None: def process_base_classes(self) -> None:
@ -76,6 +79,8 @@ class FrontendNode(BaseModel):
"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,
"custom_fields": self.custom_fields,
"output_types": self.output_types,
"documentation": self.documentation, "documentation": self.documentation,
}, },
} }

View file

@ -81,7 +81,7 @@ class ChainFrontendNode(FrontendNode):
field.advanced = False field.advanced = False
if field.name == "verbose": if field.name == "verbose":
field.required = False field.required = False
field.show = True field.show = False
field.advanced = True field.advanced = True
if field.name == "llm": if field.name == "llm":
field.required = True field.required = True

View file

@ -58,3 +58,7 @@ 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. You can change this to use other APIs like JinaChat, LocalAI and Prem.
""" """
INPUT_KEY_INFO = """The variable to be used as Chat Input when more than one variable is available."""
OUTPUT_KEY_INFO = """The variable to be used as Chat Output (e.g. answer in a ConversationalRetrievalChain)"""

View file

@ -19,6 +19,10 @@ def build_file_field(
class DocumentLoaderFrontNode(FrontendNode): class DocumentLoaderFrontNode(FrontendNode):
def add_extra_base_classes(self) -> None:
self.base_classes = ["Document"]
self.output_types = ["Document"]
file_path_templates = { file_path_templates = {
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]), "AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]), "CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),

View file

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

View file

@ -2,8 +2,13 @@ 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 INPUT_KEY_INFO, OUTPUT_KEY_INFO
from langflow.template.template.base import Template from langflow.template.template.base import Template
from langchain.memory.chat_message_histories.postgres import DEFAULT_CONNECTION_STRING from langchain.memory.chat_message_histories.postgres import DEFAULT_CONNECTION_STRING
from langchain.memory.chat_message_histories.mongodb import (
DEFAULT_COLLECTION_NAME,
DEFAULT_DBNAME,
)
class MemoryFrontendNode(FrontendNode): class MemoryFrontendNode(FrontendNode):
@ -66,11 +71,15 @@ class MemoryFrontendNode(FrontendNode):
field.required = False field.required = False
field.show = True field.show = True
field.advanced = False field.advanced = False
if field.name in ["input_key", "output_key"]: if field.name in {"input_key", "output_key"}:
field.required = False field.required = False
field.show = True field.show = True
field.advanced = False field.advanced = False
field.value = "" field.value = ""
field.info = (
INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
)
if field.name == "memory_key": if field.name == "memory_key":
field.value = "chat_history" field.value = "chat_history"
if field.name == "chat_memory": if field.name == "chat_memory":
@ -80,9 +89,10 @@ class MemoryFrontendNode(FrontendNode):
if field.name == "url": if field.name == "url":
field.show = True field.show = True
if field.name == "entity_store": if field.name == "entity_store":
field.show = True field.show = False
if name == "SQLiteEntityStore": if name == "ConversationEntityMemory" and field.name == "memory_key":
field.show = True field.show = False
field.required = False
class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode): class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):
@ -120,3 +130,56 @@ class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):
) )
description: str = "Memory store with Postgres" description: str = "Memory store with Postgres"
base_classes: list[str] = ["PostgresChatMessageHistory", "BaseChatMessageHistory"] base_classes: list[str] = ["PostgresChatMessageHistory", "BaseChatMessageHistory"]
class MongoDBChatMessageHistoryFrontendNode(MemoryFrontendNode):
name: str = "MongoDBChatMessageHistory"
template: Template = Template(
# langchain/memory/chat_message_histories/mongodb.py
# connection_string: str,
# session_id: str,
# database_name: str = DEFAULT_DBNAME,
# collection_name: str = DEFAULT_COLLECTION_NAME,
type_name="MongoDBChatMessageHistory",
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="",
info="MongoDB connection string (e.g mongodb://mongo_user:password123@mongo:27017)",
),
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value=DEFAULT_DBNAME,
name="database_name",
),
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value=DEFAULT_COLLECTION_NAME,
name="collection_name",
),
],
)
description: str = "Memory store with MongoDB"
base_classes: list[str] = ["MongoDBChatMessageHistory", "BaseChatMessageHistory"]

View file

@ -0,0 +1,10 @@
from typing import Optional
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
class OutputParserFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
FrontendNode.format_field(field, name)
field.show = True

View file

@ -4,10 +4,14 @@ from langchain.text_splitter import Language
class TextSplittersFrontendNode(FrontendNode): class TextSplittersFrontendNode(FrontendNode):
def add_extra_base_classes(self) -> None:
self.base_classes = ["Document"]
self.output_types = ["Document"]
def add_extra_fields(self) -> None: def add_extra_fields(self) -> None:
self.template.add_field( self.template.add_field(
TemplateField( TemplateField(
field_type="BaseLoader", field_type="Document",
required=True, required=True,
show=True, show=True,
name="documents", name="documents",
@ -41,7 +45,7 @@ class TextSplittersFrontendNode(FrontendNode):
field_type="str", field_type="str",
required=True, required=True,
show=True, show=True,
value=".", value="\\n",
name=name, name=name,
display_name="Separator", display_name="Separator",
) )

View file

@ -53,7 +53,7 @@ class ToolNode(FrontendNode):
], ],
) )
description: str = "Converts a chain, agent or function into a tool." description: str = "Converts a chain, agent or function into a tool."
base_classes: list[str] = ["Tool"] base_classes: list[str] = ["Tool", "BaseTool"]
def to_dict(self): def to_dict(self):
return super().to_dict() return super().to_dict()
@ -109,7 +109,7 @@ class PythonFunctionToolNode(FrontendNode):
], ],
) )
description: str = "Python function to be executed." description: str = "Python function to be executed."
base_classes: list[str] = ["Tool"] base_classes: list[str] = ["BaseTool", "Tool"]
def to_dict(self): def to_dict(self):
return super().to_dict() return super().to_dict()

View file

@ -254,7 +254,7 @@ class VectorStoreFrontendNode(FrontendNode):
# when instantiating the vectorstores # when instantiating the vectorstores
field.name = "documents" field.name = "documents"
field.field_type = "TextSplitter" field.field_type = "Document"
field.display_name = "Documents" field.display_name = "Documents"
field.required = False field.required = False
field.show = True field.show = True

View file

@ -243,7 +243,11 @@ def format_dict(d, name: Optional[str] = None):
# Check for list type # Check for list type
if "List" in _type or "Sequence" in _type or "Set" in _type: if "List" in _type or "Sequence" in _type or "Set" in _type:
_type = _type.replace("List[", "")[:-1] _type = (
_type.replace("List[", "")
.replace("Sequence[", "")
.replace("Set[", "")[:-1]
)
value["list"] = True value["list"] = True
else: else:
value["list"] = False value["list"] = False

View file

@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<script src="/node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script>
<title>Langflow</title> <title>Langflow</title>
</head> </head>
<body id='body' style="width: 100%; height:100%"> <body id='body' style="width: 100%; height:100%">

View file

@ -37,7 +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", "dompurify": "^3.0.4",
"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",
@ -85,6 +85,7 @@
"daisyui": "^3.1.1", "daisyui": "^3.1.1",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-tailwindcss": "^0.3.0", "prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.0.2", "typescript": "^5.0.2",
@ -146,33 +147,33 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.22.5", "version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
"integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.22.5", "version": "7.22.8",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.8.tgz",
"integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "integrity": "sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.5", "@babel/code-frame": "^7.22.5",
"@babel/generator": "^7.22.5", "@babel/generator": "^7.22.7",
"@babel/helper-compilation-targets": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.6",
"@babel/helper-module-transforms": "^7.22.5", "@babel/helper-module-transforms": "^7.22.5",
"@babel/helpers": "^7.22.5", "@babel/helpers": "^7.22.6",
"@babel/parser": "^7.22.5", "@babel/parser": "^7.22.7",
"@babel/template": "^7.22.5", "@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.5", "@babel/traverse": "^7.22.8",
"@babel/types": "^7.22.5", "@babel/types": "^7.22.5",
"@nicolo-ribaudo/semver-v6": "^6.3.3",
"convert-source-map": "^1.7.0", "convert-source-map": "^1.7.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
"json5": "^2.2.2", "json5": "^2.2.2"
"semver": "^6.3.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -194,9 +195,9 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.22.5", "version": "7.22.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.7.tgz",
"integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "integrity": "sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5", "@babel/types": "^7.22.5",
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
@ -208,15 +209,15 @@
} }
}, },
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.22.5", "version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz",
"integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.22.5", "@babel/compat-data": "^7.22.6",
"@babel/helper-validator-option": "^7.22.5", "@babel/helper-validator-option": "^7.22.5",
"browserslist": "^4.21.3", "@nicolo-ribaudo/semver-v6": "^6.3.3",
"lru-cache": "^5.1.1", "browserslist": "^4.21.9",
"semver": "^6.3.0" "lru-cache": "^5.1.1"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -297,9 +298,9 @@
} }
}, },
"node_modules/@babel/helper-split-export-declaration": { "node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.5", "version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
}, },
@ -332,12 +333,12 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.22.5", "version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
"integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
"dependencies": { "dependencies": {
"@babel/template": "^7.22.5", "@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.5", "@babel/traverse": "^7.22.6",
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
}, },
"engines": { "engines": {
@ -358,9 +359,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.22.5", "version": "7.22.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
"integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -404,17 +405,17 @@
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.22.5", "version": "7.22.8",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz",
"integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.5", "@babel/code-frame": "^7.22.5",
"@babel/generator": "^7.22.5", "@babel/generator": "^7.22.7",
"@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5", "@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.22.5", "@babel/parser": "^7.22.7",
"@babel/types": "^7.22.5", "@babel/types": "^7.22.5",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0" "globals": "^11.1.0"
@ -1337,6 +1338,14 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
}, },
"node_modules/@nicolo-ribaudo/semver-v6": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
"integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -2662,39 +2671,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@swc/cli/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/cli/node_modules/semver": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/cli/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@swc/core": { "node_modules/@swc/core": {
"version": "1.3.62", "version": "1.3.62",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz",
@ -4124,14 +4100,14 @@
} }
}, },
"node_modules/bin-version-check": { "node_modules/bin-version-check": {
"version": "5.0.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.0.0.tgz", "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz",
"integrity": "sha512-Q3FMQnS5eZmrBGqmDXLs4dbAn/f+52voP6ykJYmweSA60t6DyH4UTSwZhtbK5UH+LBoWvDljILUQMLRUtsynsA==", "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"bin-version": "^6.0.0", "bin-version": "^6.0.0",
"semver": "^7.3.5", "semver": "^7.5.3",
"semver-truncate": "^2.0.0" "semver-truncate": "^3.0.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@ -4140,39 +4116,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bin-version-check/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/bin-version-check/node_modules/semver": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/bin-version-check/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/bin-version/node_modules/execa": { "node_modules/bin-version/node_modules/execa": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@ -4306,9 +4249,9 @@
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.21.5", "version": "4.21.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -4317,13 +4260,17 @@
{ {
"type": "tidelift", "type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist" "url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001449", "caniuse-lite": "^1.0.30001503",
"electron-to-chromium": "^1.4.284", "electron-to-chromium": "^1.4.431",
"node-releases": "^2.0.8", "node-releases": "^2.0.12",
"update-browserslist-db": "^1.0.10" "update-browserslist-db": "^1.0.11"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@ -4438,9 +4385,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001486", "version": "1.0.30001513",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001513.tgz",
"integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", "integrity": "sha512-pnjGJo7SOOjAGytZZ203Em95MRM8Cr6jhCXNF/FAXTpCTRTECnqQWLpiTRqrFtdYcth8hf4WECUpkezuYsMVww==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -5142,9 +5089,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.0.3", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.3.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.4.tgz",
"integrity": "sha512-axQ9zieHLnAnHh0sfAamKYiqXMJAVwu+LM/alQ7WDagoWessyWvMSFyW65CqF3owufNu8HBcE4cM2Vflu7YWcQ==" "integrity": "sha512-ae0mA+Qiqp6C29pqZX3fQgK+F91+F7wobM/v8DRzDqJdZJELXiFUx4PP4pK/mzUS0xkiSEx3Ncd9gr69jg3YsQ=="
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.440", "version": "1.4.440",
@ -8616,6 +8563,26 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/prettier-plugin-organize-imports": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.2.tgz",
"integrity": "sha512-e97lE6odGSiHonHJMTYC0q0iLXQyw0u5z/PJpvP/3vRy6/Zi9kLBwFAbEGjDzIowpjQv8b+J04PDamoUSQbzGA==",
"dev": true,
"peerDependencies": {
"@volar/vue-language-plugin-pug": "^1.0.4",
"@volar/vue-typescript": "^1.0.4",
"prettier": ">=2.0",
"typescript": ">=2.9"
},
"peerDependenciesMeta": {
"@volar/vue-language-plugin-pug": {
"optional": true
},
"@volar/vue-typescript": {
"optional": true
}
}
},
"node_modules/prettier-plugin-tailwindcss": { "node_modules/prettier-plugin-tailwindcss": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.3.0.tgz", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.3.0.tgz",
@ -9507,11 +9474,18 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "6.3.0", "version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
} }
}, },
"node_modules/semver-regex": { "node_modules/semver-regex": {
@ -9527,17 +9501,38 @@
} }
}, },
"node_modules/semver-truncate": { "node_modules/semver-truncate": {
"version": "2.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-2.0.0.tgz", "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz",
"integrity": "sha512-Rh266MLDYNeML5h90ttdMwfXe1+Nc4LAWd9X1KdJe8pPHP4kFmvLZALtsMNHNdvTyQygbEC0D59sIz47DIaq8w==", "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"semver": "^6.0.0" "semver": "^7.3.5"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/semver/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/shadcn-ui": { "node_modules/shadcn-ui": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.2.2.tgz", "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.2.2.tgz",

View file

@ -32,7 +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", "dompurify": "^3.0.4",
"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",
@ -106,6 +106,7 @@
"daisyui": "^3.1.1", "daisyui": "^3.1.1",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-tailwindcss": "^0.3.0", "prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.0.2", "typescript": "^5.0.2",

View file

@ -1,20 +1,19 @@
import "reactflow/dist/style.css";
import { useState, useEffect, useContext } from "react";
import "./App.css";
import { useLocation } from "react-router-dom";
import _ from "lodash"; import _ from "lodash";
import { useContext, useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import "reactflow/dist/style.css";
import "./App.css";
import { ErrorBoundary } from "react-error-boundary";
import ErrorAlert from "./alerts/error"; import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice"; import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success"; import SuccessAlert from "./alerts/success";
import CrashErrorComponent from "./components/CrashErrorComponent";
import Header from "./components/headerComponent";
import { alertContext } from "./contexts/alertContext"; import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext"; import { locationContext } from "./contexts/locationContext";
import { ErrorBoundary } from "react-error-boundary";
import CrashErrorComponent from "./components/CrashErrorComponent";
import { TabsContext } from "./contexts/tabsContext"; import { TabsContext } from "./contexts/tabsContext";
import { getVersion } from "./controllers/API";
import Router from "./routes"; import Router from "./routes";
import Header from "./components/headerComponent";
export default function App() { export default function App() {
let { setCurrent, setShowSideBar, setIsStackedOpen } = let { setCurrent, setShowSideBar, setIsStackedOpen } =
@ -118,7 +117,7 @@ export default function App() {
const removeAlert = (id: string) => { const removeAlert = (id: string) => {
setAlertsList((prevAlertsList) => setAlertsList((prevAlertsList) =>
prevAlertsList.filter((alert) => alert.id !== id), prevAlertsList.filter((alert) => alert.id !== id)
); );
}; };
@ -138,10 +137,7 @@ export default function App() {
<Router /> <Router />
</ErrorBoundary> </ErrorBoundary>
<div></div> <div></div>
<div <div className="app-div" style={{ zIndex: 999 }}>
className="app-div"
style={{ zIndex: 999 }}
>
{alertsList.map((alert) => ( {alertsList.map((alert) => (
<div key={alert.id}> <div key={alert.id}>
{alert.type === "error" ? ( {alert.type === "error" ? (

View file

@ -1,31 +1,32 @@
import { Info } from "lucide-react";
import React, { useContext, useEffect, useRef, useState } from "react";
import { Handle, Position, useUpdateNodeInternals } from "reactflow"; import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
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 { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import { cleanEdges } from "../../../../util/reactflowUtils";
import { import {
classNames, classNames,
getRandomKeyByssmm, getRandomKeyByssmm,
groupByFamily, groupByFamily,
isValidConnection, isValidConnection,
nodeColors,
nodeIconsLucide, nodeIconsLucide,
nodeNames,
} from "../../../../utils"; } from "../../../../utils";
import { useContext, useEffect, useRef, useState } from "react";
import InputComponent from "../../../../components/inputComponent";
import InputListComponent from "../../../../components/inputListComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import FloatComponent from "../../../../components/floatComponent";
import Dropdown from "../../../../components/dropdownComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import InputFileComponent from "../../../../components/inputFileComponent";
import { TabsContext } from "../../../../contexts/tabsContext";
import IntComponent from "../../../../components/intComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import { nodeNames } from "../../../../utils";
import React from "react";
import { nodeColors } from "../../../../utils";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import { PopUpContext } from "../../../../contexts/popUpContext";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Info } from "lucide-react";
export default function ParameterComponent({ export default function ParameterComponent({
left, left,
@ -37,10 +38,12 @@ export default function ParameterComponent({
type, type,
name = "", name = "",
required = false, required = false,
optionalHandle = null,
info = "", info = "",
}: ParameterComponentType) { }: ParameterComponentType) {
const ref = useRef(null); const ref = useRef(null);
const refHtml = useRef(null); const refHtml = useRef(null);
const refNumberComponents = useRef(0);
const infoHtml = useRef(null); const infoHtml = useRef(null);
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0); const [position, setPosition] = useState(0);
@ -58,10 +61,6 @@ export default function ParameterComponent({
updateNodeInternals(data.id); updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]); }, [data.id, position, updateNodeInternals]);
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
useEffect(() => {}, [closePopUp, data.node.template]); useEffect(() => {}, [closePopUp, data.node.template]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
@ -76,6 +75,7 @@ export default function ParameterComponent({
return { return {
...prev, ...prev,
[tabId]: { [tabId]: {
...prev[tabId],
isPending: true, isPending: true,
}, },
}; };
@ -95,28 +95,39 @@ export default function ParameterComponent({
}, [info]); }, [info]);
useEffect(() => { useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle); const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
refHtml.current = groupedObj.map((item, i) => ( refNumberComponents.current = groupedObj[0]?.type?.length;
refHtml.current = groupedObj.map((item, i) => {
const Icon: any = nodeIconsLucide[item.family];
return (
<span <span
key={getRandomKeyByssmm()} key={getRandomKeyByssmm() + item.family + i}
className={classNames( className={classNames(
i > 0 ? "mt-3 flex items-center" : "flex items-center" i > 0 ? "mt-2 flex items-center" : "flex items-center"
)} )}
> >
<div <div
className="h-6 w-6" className="h-5 w-5"
style={{ style={{
color: nodeColors[item.family], color: nodeColors[item.family],
}} }}
> >
{React.createElement(nodeIconsLucide[item.family])} <Icon
className="h-5 w-5"
strokeWidth={1.5}
style={{
color: nodeColors[item.family] ?? nodeColors.unknown,
}}
/>
</div> </div>
<span className="ps-2 text-foreground"> <span className="ps-2 text-xs text-foreground">
{nodeNames[item.family] ?? ""}{" "} {nodeNames[item.family] ?? ""}{" "}
<span className={classNames(left ? "hidden" : "")}> <span className="text-xs">
{" "} {" "}
-&nbsp; {item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2 {item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => ( ? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}> <React.Fragment key={el + i}>
@ -125,14 +136,14 @@ export default function ParameterComponent({
? el ? el
: (el += `, `)} : (el += `, `)}
</span> </span>
{i % 2 === 0 && i > 0 && <br />}
</React.Fragment> </React.Fragment>
)) ))
: item.type} : item.type}
</span> </span>
</span> </span>
</span> </span>
)); );
});
}, [tooltipTitle]); }, [tooltipTitle]);
return ( return (
@ -149,7 +160,7 @@ export default function ParameterComponent({
} }
> >
{title} {title}
<span className="text-destructive">{required ? " *" : ""}</span> <span className="text-status-red">{required ? " *" : ""}</span>
<div className=""> <div className="">
{info !== "" && ( {info !== "" && (
<ShadTooltip content={infoHtml.current}> <ShadTooltip content={infoHtml.current}>
@ -165,10 +176,16 @@ export default function ParameterComponent({
type === "code" || type === "code" ||
type === "prompt" || type === "prompt" ||
type === "file" || type === "file" ||
type === "int") ? ( type === "int") &&
!optionalHandle ? (
<></> <></>
) : ( ) : (
<ShadTooltip <ShadTooltip
styleClasses={
refNumberComponents.current > MAX_LENGTH_TO_SCROLL_TOOLTIP
? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel"
: "tooltip-fixed-width"
}
delayDuration={0} delayDuration={0}
content={refHtml.current} content={refHtml.current}
side={left ? "left" : "right"} side={left ? "left" : "right"}
@ -227,10 +244,9 @@ export default function ParameterComponent({
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<ToggleShadComponent <ToggleShadComponent
disabled={disabled} disabled={disabled}
enabled={enabled} enabled={data.node.template[name].value}
setEnabled={(t) => { setEnabled={(t) => {
handleOnNewValue(t); handleOnNewValue(t);
setEnabled(t);
}} }}
size="large" size="large"
/> />
@ -257,6 +273,10 @@ export default function ParameterComponent({
) : left === true && type === "code" ? ( ) : left === true && type === "code" ? (
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<CodeAreaComponent <CodeAreaComponent
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
nodeClass={data.node}
disabled={disabled} disabled={disabled}
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue} onChange={handleOnNewValue}
@ -288,6 +308,20 @@ export default function ParameterComponent({
) : left === true && type === "prompt" ? ( ) : left === true && type === "prompt" ? (
<div className="mt-2 w-full"> <div className="mt-2 w-full">
<PromptAreaComponent <PromptAreaComponent
field_name={name}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
if (reactFlowInstance) {
cleanEdges({
flow: {
edges: reactFlowInstance.getEdges(),
nodes: reactFlowInstance.getNodes(),
},
updateEdge: (edge) => reactFlowInstance.setEdges(edge),
});
}
}}
nodeClass={data.node}
disabled={disabled} disabled={disabled}
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue} onChange={handleOnNewValue}

View file

@ -1,3 +1,15 @@
import { Zap } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react";
import { NodeToolbar } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import Tooltip from "../../components/TooltipComponent";
import { useSSE } from "../../contexts/SSEContext";
import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { typesContext } from "../../contexts/typesContext";
import NodeModal from "../../modals/NodeModal";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { NodeDataType } from "../../types/flow";
import { import {
classNames, classNames,
nodeColors, nodeColors,
@ -5,17 +17,6 @@ import {
toTitleCase, toTitleCase,
} from "../../utils"; } from "../../utils";
import ParameterComponent from "./components/parameterComponent"; import ParameterComponent from "./components/parameterComponent";
import { typesContext } from "../../contexts/typesContext";
import { useContext, useState, useEffect, useRef } from "react";
import { NodeDataType } from "../../types/flow";
import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import NodeModal from "../../modals/NodeModal";
import Tooltip from "../../components/TooltipComponent";
import { NodeToolbar } from "reactflow";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import { useSSE } from "../../contexts/SSEContext";
export default function GenericNode({ export default function GenericNode({
data, data,
@ -66,9 +67,7 @@ export default function GenericNode({
deleteNode(data.id); deleteNode(data.id);
return; return;
} }
useEffect(() => {}, [closePopUp, data.node.template]); useEffect(() => {}, [closePopUp, data.node.template]);
return ( return (
<> <>
<NodeToolbar> <NodeToolbar>
@ -95,10 +94,7 @@ export default function GenericNode({
}} }}
/> />
<div className="generic-node-tooltip-div"> <div className="generic-node-tooltip-div">
<ShadTooltip <ShadTooltip content={data.node.display_name}>
delayDuration={1500}
content={data.node.display_name}
>
<div className="generic-node-tooltip-div text-primary"> <div className="generic-node-tooltip-div text-primary">
{data.node.display_name} {data.node.display_name}
</div> </div>
@ -118,14 +114,24 @@ export default function GenericNode({
<div> <div>
<Tooltip <Tooltip
title={ title={
!validationStatus ? ( isBuilding ? (
"Validating..." <span>Building...</span>
) : !validationStatus ? (
<span className="flex">
Build{" "}
<Zap
className="mx-0.5 h-5 fill-build-trigger stroke-build-trigger stroke-1"
strokeWidth={1.5}
/>{" "}
flow to validate status.
</span>
) : ( ) : (
<div className="generic-node-validation-div"> <div className="max-h-96 overflow-auto">
{validationStatus.params || {validationStatus.params
"" ? validationStatus.params
.split("\n") .split("\n")
.map((line, index) => <div key={index}>{line}</div>)} .map((line, index) => <div key={index}>{line}</div>)
: ""}
</div> </div>
) )
} }
@ -162,9 +168,7 @@ export default function GenericNode({
</div> </div>
<div className="generic-node-desc"> <div className="generic-node-desc">
<div className="generic-node-desc-text"> <div className="generic-node-desc-text">{data.node.description}</div>
{data.node.description}
</div>
<> <>
{Object.keys(data.node.template) {Object.keys(data.node.template)
@ -196,6 +200,7 @@ export default function GenericNode({
data={data} data={data}
color={ color={
nodeColors[types[data.node.template[t].type]] ?? nodeColors[types[data.node.template[t].type]] ??
nodeColors[data.node.template[t].type] ??
nodeColors.unknown nodeColors.unknown
} }
title={ title={
@ -207,11 +212,22 @@ export default function GenericNode({
} }
info={data.node.template[t].info} info={data.node.template[t].info}
name={t} name={t}
tooltipTitle={data.node.template[t].type} tooltipTitle={
data.node.template[t].input_types?.join("\n") ??
data.node.template[t].type
}
required={data.node.template[t].required} required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id} id={
(data.node.template[t].input_types?.join(";") ??
data.node.template[t].type) +
"|" +
t +
"|" +
data.id
}
left={true} left={true}
type={data.node.template[t].type} type={data.node.template[t].type}
optionalHandle={data.node.template[t].input_types}
/> />
) : ( ) : (
<></> <></>
@ -232,8 +248,12 @@ export default function GenericNode({
<ParameterComponent <ParameterComponent
data={data} data={data}
color={nodeColors[types[data.type]] ?? nodeColors.unknown} color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={data.type} title={
tooltipTitle={`${data.node.base_classes.join("\n")}`} data.node.output_types && data.node.output_types.length > 0
? data.node.output_types.join("|")
: data.type
}
tooltipTitle={data.node.base_classes.join("\n")}
id={[data.type, data.id, ...data.node.base_classes].join("|")} id={[data.type, data.id, ...data.node.base_classes].join("|")}
type={data.node.base_classes.join("|")} type={data.node.base_classes.join("|")}
left={false} left={false}

View file

@ -1,8 +1,8 @@
import { Link } from "react-router-dom";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { CheckCircle2, Info, X, XCircle } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom";
import { SingleAlertComponentType } from "../../../../types/alerts"; import { SingleAlertComponentType } from "../../../../types/alerts";
import { X, CheckCircle2, Info, XCircle } from "lucide-react";
export default function SingleAlert({ export default function SingleAlert({
dropItem, dropItem,

View file

@ -1,10 +1,10 @@
import { useContext, useEffect, useRef } from "react"; import { Trash2, X } from "lucide-react";
import { useContext, useRef } from "react";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import SingleAlert from "./components/singleAlertComponent";
import { AlertDropdownType } from "../../types/alerts";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { AlertDropdownType } from "../../types/alerts";
import { useOnClickOutside } from "../hooks/useOnClickOutside"; import { useOnClickOutside } from "../hooks/useOnClickOutside";
import { X, Trash2 } from "lucide-react"; import SingleAlert from "./components/singleAlertComponent";
export default function AlertDropdown({}: AlertDropdownType) { export default function AlertDropdown({}: AlertDropdownType) {
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);

View file

@ -1,7 +1,7 @@
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { XCircle } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { ErrorAlertType } from "../../types/alerts"; import { ErrorAlertType } from "../../types/alerts";
import { XCircle } from "lucide-react";
export default function ErrorAlert({ export default function ErrorAlert({
title, title,
@ -20,6 +20,7 @@ export default function ErrorAlert({
}, 5000); }, 5000);
} }
}, [id, removeAlert, show]); }, [id, removeAlert, show]);
return ( return (
<Transition <Transition
className="relative" className="relative"
@ -43,13 +44,15 @@ export default function ErrorAlert({
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<XCircle className="error-build-message-circle" aria-hidden="true" /> <XCircle
className="error-build-message-circle"
aria-hidden="true"
/>
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="error-build-foreground"> <h3 className="error-build-foreground">{title}</h3>
{title} {list?.length !== 0 &&
</h3> list?.some((item) => item !== null && item !== undefined) ? (
{list.length !== 0 ? (
<div className="error-build-message-div"> <div className="error-build-message-div">
<ul className="error-build-message-list"> <ul className="error-build-message-list">
{list.map((item, index) => ( {list.map((item, index) => (

View file

@ -1,8 +1,8 @@
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { Info } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { NoticeAlertType } from "../../types/alerts"; import { NoticeAlertType } from "../../types/alerts";
import { Info } from "lucide-react";
export default function NoticeAlert({ export default function NoticeAlert({
title, title,

View file

@ -1,7 +1,7 @@
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { CheckCircle2 } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { SuccessAlertType } from "../../types/alerts"; import { SuccessAlertType } from "../../types/alerts";
import { CheckCircle2 } from "lucide-react";
export default function SuccessAlert({ export default function SuccessAlert({
title, title,
@ -38,15 +38,10 @@ export default function SuccessAlert({
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<CheckCircle2 <CheckCircle2 className="success-alert-icon" aria-hidden="true" />
className="success-alert-icon"
aria-hidden="true"
/>
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="success-alert-message"> <p className="success-alert-message">{title}</p>
{title}
</p>
</div> </div>
</div> </div>
</div> </div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -1,16 +1,11 @@
import { ReactElement, useContext, useEffect, useRef, useState } from "react"; import { useState } from "react";
import {
AccordionComponentType,
ProgressBarType,
} from "../../types/components";
import { Progress } from "../../components/ui/progress";
import { setInterval } from "timers/promises";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
} from "../../components/ui/accordion"; } from "../../components/ui/accordion";
import { AccordionComponentType } from "../../types/components";
export default function AccordionComponent({ export default function AccordionComponent({
trigger, trigger,
@ -18,7 +13,7 @@ export default function AccordionComponent({
open = [], open = [],
}: AccordionComponentType) { }: AccordionComponentType) {
const [value, setValue] = useState( const [value, setValue] = useState(
open.length == 0 ? "" : getOpenAccordion(), open.length === 0 ? "" : getOpenAccordion()
); );
function getOpenAccordion() { function getOpenAccordion() {

View file

@ -1,7 +1,7 @@
import React, { useState, ChangeEvent } from "react"; import React, { ChangeEvent, useState } from "react";
import { Textarea } from "../../components/ui/textarea";
import { Label } from "../../components/ui/label";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
type InputProps = { type InputProps = {
name: string | null; name: string | null;
@ -47,9 +47,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
<div className="edit-flow-arrangement"> <div className="edit-flow-arrangement">
<span className="font-medium">Name</span>{" "} <span className="font-medium">Name</span>{" "}
{isMaxLength && ( {isMaxLength && (
<span className="edit-flow-span"> <span className="edit-flow-span">Character limit reached</span>
Character limit reached
</span>
)} )}
</div> </div>
<Input <Input

View file

@ -1,8 +1,8 @@
import { Disclosure } from "@headlessui/react"; import { Disclosure } from "@headlessui/react";
import { useContext } from "react"; import { useContext } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { classNames } from "../../utils";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
import { classNames } from "../../utils";
export default function ExtraSidebar() { export default function ExtraSidebar() {
const { const {
@ -16,9 +16,7 @@ export default function ExtraSidebar() {
return ( return (
<> <>
<aside <aside
className={` ${ className={` ${isStackedOpen ? "w-52" : "w-0 "} unused-side-bar-aside`}
isStackedOpen ? "w-52" : "w-0 "
} unused-side-bar-aside`}
> >
<div className="unused-side-bar-arrangement"> <div className="unused-side-bar-arrangement">
<div className="unused-side-bar-division"> <div className="unused-side-bar-division">
@ -71,7 +69,9 @@ export default function ExtraSidebar() {
<span className="flex-1">{item.name}</span> <span className="flex-1">{item.name}</span>
<svg <svg
className={classNames( className={classNames(
open ? "unused-side-bar-svg-true" : "text-ring", open
? "unused-side-bar-svg-true"
: "text-ring",
"unused-side-bar-svg" "unused-side-bar-svg"
)} )}
viewBox="0 0 20 20" viewBox="0 0 20 20"

View file

@ -1,5 +1,5 @@
import { styled } from "@mui/material/styles";
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip"; import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
import { styled } from "@mui/material/styles";
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} /> <Tooltip {...props} classes={{ popper: className }} />

View file

@ -1,6 +1,3 @@
import { useContext, useEffect, useRef, useState } from "react";
import { RadialProgressType } from "../../types/components";
export default function LoadingSpinner({}) { export default function LoadingSpinner({}) {
return <></>; return <></>;
} }

View file

@ -1,21 +0,0 @@
import { ReactElement, useContext, useEffect, useRef, useState } from "react";
import { ProgressBarType } from "../../types/components";
import { Progress } from "../../components/ui/progress";
import { progressContext } from "../../contexts/ProgressContext";
import { setInterval } from "timers/promises";
export default function ProgressBarComponent({
value,
children,
}: ProgressBarType) {
const ref = useRef(0);
const reff = useRef();
const { progress } = useContext(progressContext);
useEffect(() => {
ref.current = progress * 100;
console.log(progress);
}, [progress]);
return <Progress className="h-2.5" value={ref.current} />;
}

View file

@ -1,4 +1,3 @@
import { useContext, useEffect, useRef, useState } from "react";
import { RadialProgressType } from "../../types/components"; import { RadialProgressType } from "../../types/components";
export default function RadialProgressComponent({ export default function RadialProgressComponent({

View file

@ -0,0 +1,21 @@
import DOMPurify from "dompurify";
const SanitizedHTMLWrapper = ({
className,
content,
onClick,
suppressWarning = false,
}) => {
const sanitizedHTML = DOMPurify.sanitize(content);
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
suppressContentEditableWarning={suppressWarning}
onClick={onClick}
/>
);
};
export default SanitizedHTMLWrapper;

View file

@ -1,28 +1,26 @@
import { ShadTooltipProps } from "../../types/components"; import { ShadToolTipType } from "../../types/components";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
const ShadTooltip = ({ export default function ShadTooltip({
delayDuration = 500,
side,
content, content,
side,
asChild = true,
children, children,
}: ShadTooltipProps) => { styleClasses,
delayDuration = 500,
}: ShadToolTipType) {
return ( return (
<TooltipProvider>
<Tooltip delayDuration={delayDuration}> <Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild>{children}</TooltipTrigger> <TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent side={side} avoidCollisions={false} sticky="always"> <TooltipContent
className={styleClasses}
side={side}
avoidCollisions={false}
sticky="always"
>
{content} {content}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
}; }
export default ShadTooltip;

View file

@ -1,6 +1,5 @@
import { ReactElement } from "react";
import { LightTooltip } from "../LightTooltipComponent";
import { TooltipComponentType } from "../../types/components"; import { TooltipComponentType } from "../../types/components";
import { LightTooltip } from "../LightTooltipComponent";
export default function Tooltip({ export default function Tooltip({
children, children,

View file

@ -1,15 +1,14 @@
import { Trash2, ExternalLink } from "lucide-react"; import { Trash2 } from "lucide-react";
import { useContext } from "react"; import { useContext } from "react";
import { Link } from "react-router-dom";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { FlowType } from "../../types/flow"; import { FlowType } from "../../types/flow";
import { gradients } from "../../utils"; import { gradients } from "../../utils";
import { import {
CardTitle, Card,
CardDescription, CardDescription,
CardFooter, CardFooter,
Card,
CardHeader, CardHeader,
CardTitle,
} from "../ui/card"; } from "../ui/card";
export const CardComponent = ({ export const CardComponent = ({
@ -35,9 +34,7 @@ export const CardComponent = ({
gradients[parseInt(flow.id.slice(0, 12), 16) % gradients.length] gradients[parseInt(flow.id.slice(0, 12), 16) % gradients.length]
} }
></span> ></span>
<span className="card-component-title-size"> <span className="card-component-title-size">{flow.name}</span>
{flow.name}
</span>
{onDelete && ( {onDelete && (
<button className="card-component-delete-button" onClick={onDelete}> <button className="card-component-delete-button" onClick={onDelete}>
<Trash2 className="card-component-delete-icon" /> <Trash2 className="card-component-delete-icon" />

View file

@ -1,22 +1,21 @@
import { useContext, useState } from "react";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { Zap } from "lucide-react"; import { Zap } from "lucide-react";
import { validateNodes } from "../../../utils"; import { useContext, useState } from "react";
import { FlowType } from "../../../types/flow";
import Loading from "../../../components/ui/loading"; import Loading from "../../../components/ui/loading";
import { useSSE } from "../../../contexts/SSEContext"; import { useSSE } from "../../../contexts/SSEContext";
import { typesContext } from "../../../contexts/typesContext";
import { alertContext } from "../../../contexts/alertContext"; import { alertContext } from "../../../contexts/alertContext";
import { typesContext } from "../../../contexts/typesContext";
import { postBuildInit } from "../../../controllers/API"; import { postBuildInit } from "../../../controllers/API";
import ShadTooltip from "../../ShadTooltipComponent"; import { FlowType } from "../../../types/flow";
import { validateNodes } from "../../../utils";
import { TabsContext } from "../../../contexts/tabsContext";
import RadialProgressComponent from "../../RadialProgress"; import RadialProgressComponent from "../../RadialProgress";
export default function BuildTrigger({ export default function BuildTrigger({
open, open,
flow, flow,
setIsBuilt, setIsBuilt,
isBuilt,
}: { }: {
open: boolean; open: boolean;
flow: FlowType; flow: FlowType;
@ -25,6 +24,7 @@ export default function BuildTrigger({
}) { }) {
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE(); const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const { setTabsState } = useContext(TabsContext);
const { setErrorData, setSuccessData } = useContext(alertContext); const { setErrorData, setSuccessData } = useContext(alertContext);
const [isIconTouched, setIsIconTouched] = useState(false); const [isIconTouched, setIsIconTouched] = useState(false);
const eventClick = isBuilding ? "pointer-events-none" : ""; const eventClick = isBuilding ? "pointer-events-none" : "";
@ -88,6 +88,16 @@ export default function BuildTrigger({
} else if (parsedData.log) { } else if (parsedData.log) {
// If the event is a log, log it // If the event is a log, log it
setSuccessData({ title: parsedData.log }); setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys) {
setTabsState((old) => {
return {
...old,
[flowId]: {
...old[flowId],
formKeysData: parsedData,
},
};
});
} else { } else {
// Otherwise, process the data // Otherwise, process the data
const isValid = processStreamResult(parsedData); const isValid = processStreamResult(parsedData);
@ -127,7 +137,7 @@ export default function BuildTrigger({
async function enforceMinimumLoadingTime( async function enforceMinimumLoadingTime(
startTime: number, startTime: number,
minimumLoadingTime: number, minimumLoadingTime: number
) { ) {
const elapsedTime = Date.now() - startTime; const elapsedTime = Date.now() - startTime;
const remainingTime = minimumLoadingTime - elapsedTime; const remainingTime = minimumLoadingTime - elapsedTime;
@ -156,11 +166,7 @@ export default function BuildTrigger({
leaveFrom="translate-y-0" leaveFrom="translate-y-0"
leaveTo="translate-y-96" leaveTo="translate-y-96"
> >
<div <div className="fixed bottom-20 right-4">
className={
"round-buttons-position" + (isBuilt ? " bottom-20" : " bottom-4")
}
>
<div <div
className={`${eventClick} round-button-form`} className={`${eventClick} round-button-form`}
onClick={() => { onClick={() => {
@ -184,7 +190,10 @@ export default function BuildTrigger({
className="build-trigger-loading-icon" className="build-trigger-loading-icon"
/> />
) : ( ) : (
<Zap strokeWidth={1.5} className="build-trigger-icon" /> <Zap
strokeWidth={1.5}
className="sh-6 w-6 fill-build-trigger stroke-build-trigger stroke-1"
/>
)} )}
</div> </div>
</button> </button>

View file

@ -1,55 +0,0 @@
import { useState } from "react";
import { ChatMessageType } from "../../../types/chat";
import { nodeColors } from "../../../utils";
import Convert from "ansi-to-html";
import { MessageCircle } from "lucide-react";
import DOMPurify from "dompurify";
const convert = new Convert({ newline: true });
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
const [hidden, setHidden] = useState(true);
return (
<div>
{!chat.isSend ? (
<div className="w-full text-start">
<div
style={{ backgroundColor: nodeColors["chat"] }}
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 !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="absolute right-2 top-2 cursor-pointer"
>
<MessageCircle className="h-5 w-5 animate-bounce" />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
onClick={() => setHidden((prev) => !prev)}
style={{ backgroundColor: nodeColors["thought"] }}
className=" inline-block w-full cursor-pointer px-5 pb-3 pt-3 text-start"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(convert.toHtml(chat.thought)),
}}
></div>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div
className="w-full rounded-b-md px-4 pb-3 pr-8 pt-3"
style={{ backgroundColor: nodeColors["chat"] }}
>
{chat.message}
</div>
</div>
</div>
) : (
<div className="w-full text-end">
<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}
</div>
</div>
)}
</div>
);
}

View file

@ -1,20 +1,32 @@
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { MessagesSquare } from "lucide-react"; import { MessagesSquare } from "lucide-react";
import { alertContext } from "../../../contexts/alertContext";
import { useContext } from "react"; import { useContext } from "react";
import ShadTooltip from "../../ShadTooltipComponent"; import {
CHAT_CANNOT_OPEN_DESCRIPTION,
CHAT_CANNOT_OPEN_TITLE,
FLOW_NOT_BUILT_DESCRIPTION,
FLOW_NOT_BUILT_TITLE,
} from "../../../constants";
import { alertContext } from "../../../contexts/alertContext";
export default function ChatTrigger({ open, setOpen, isBuilt }) { export default function ChatTrigger({ open, setOpen, isBuilt, canOpen }) {
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
function handleClick() { function handleClick() {
if (isBuilt) { if (isBuilt) {
if (canOpen) {
setOpen(true); setOpen(true);
} else { } else {
setErrorData({ setErrorData({
title: "Flow not built", title: CHAT_CANNOT_OPEN_TITLE,
list: ["Please build the flow before chatting"], list: [CHAT_CANNOT_OPEN_DESCRIPTION],
});
}
} else {
setErrorData({
title: FLOW_NOT_BUILT_TITLE,
list: [FLOW_NOT_BUILT_DESCRIPTION],
}); });
} }
} }
@ -30,19 +42,26 @@ export default function ChatTrigger({ open, setOpen, isBuilt }) {
leaveFrom="translate-y-0" leaveFrom="translate-y-0"
leaveTo="translate-y-96" leaveTo="translate-y-96"
> >
<div className="message-button-position"> <button
<div className="round-button-form" onClick={handleClick}> onClick={handleClick}
<button> className={
<div className="round-button-div"> "shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button " +
(!isBuilt || !canOpen ? "cursor-not-allowed" : "cursor-pointer")
}
>
<div className="flex gap-3">
<MessagesSquare <MessagesSquare
className="message-button-icon" className={
"h-6 w-6 transition-all " +
(isBuilt && canOpen
? "message-button-icon"
: "disabled-message-button-icon")
}
style={{ color: "white" }} style={{ color: "white" }}
strokeWidth={1.5} strokeWidth={1.5}
/> />
</div> </div>
</button> </button>
</div>
</div>
</Transition> </Transition>
); );
} }

View file

@ -1,16 +1,20 @@
import { useEffect, useRef, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import { useNodes } from "reactflow"; import { useNodes } from "reactflow";
import { ChatType } from "../../types/chat"; import { ChatType } from "../../types/chat";
import ChatTrigger from "./chatTrigger";
import BuildTrigger from "./buildTrigger"; import BuildTrigger from "./buildTrigger";
import ChatModal from "../../modals/chatModal"; import ChatTrigger from "./chatTrigger";
import * as _ from "lodash";
import { TabsContext } from "../../contexts/tabsContext";
import { getBuildStatus } from "../../controllers/API"; import { getBuildStatus } from "../../controllers/API";
import FormModal from "../../modals/formModal";
import { NodeType } from "../../types/flow"; import { NodeType } from "../../types/flow";
export default function Chat({ flow }: ChatType) { export default function Chat({ flow }: ChatType) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [isBuilt, setIsBuilt] = useState(false); const [isBuilt, setIsBuilt] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState } = useContext(TabsContext);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
@ -44,23 +48,34 @@ export default function Chat({ flow }: ChatType) {
const nodes = useNodes(); const nodes = useNodes();
useEffect(() => { useEffect(() => {
const prevNodes = prevNodesRef.current; const prevNodes = prevNodesRef.current;
const currentNodes = nodes.map( const currentNodes = nodes.map((node: NodeType) =>
(node: NodeType) => node.data.node.template.value, _.cloneDeep(node.data.node.template)
); );
if ( if (
prevNodes && tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].isPending &&
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes) JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
) { ) {
setIsBuilt(false); setIsBuilt(false);
} }
if (
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
tabsState[flow.id].formKeysData.input_keys &&
Object.keys(tabsState[flow.id].formKeysData.input_keys).length > 0
) {
setCanOpen(true);
} else {
setCanOpen(false);
}
prevNodesRef.current = currentNodes; prevNodesRef.current = currentNodes;
}, [nodes]); }, [tabsState, flow.id, nodes]);
return ( return (
<> <>
{isBuilt ? (
<div> <div>
<BuildTrigger <BuildTrigger
open={open} open={open}
@ -68,17 +83,24 @@ export default function Chat({ flow }: ChatType) {
setIsBuilt={setIsBuilt} setIsBuilt={setIsBuilt}
isBuilt={isBuilt} isBuilt={isBuilt}
/> />
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} /> {isBuilt &&
<ChatTrigger open={open} setOpen={setOpen} isBuilt={isBuilt} /> tabsState[flow.id] &&
</div> tabsState[flow.id].formKeysData &&
) : ( canOpen && (
<BuildTrigger <FormModal
open={open} key={flow.id}
flow={flow} flow={flow}
setIsBuilt={setIsBuilt} open={open}
isBuilt={isBuilt} setOpen={setOpen}
/> />
)} )}
<ChatTrigger
canOpen={canOpen}
open={open}
setOpen={setOpen}
isBuilt={isBuilt}
/>
</div>
</> </>
); );
} }

View file

@ -1,7 +1,6 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import CodeAreaModal from "../../modals/codeAreaModal"; import CodeAreaModal from "../../modals/codeAreaModal";
import TextAreaModal from "../../modals/textAreaModal";
import { TextAreaComponentType } from "../../types/components"; import { TextAreaComponentType } from "../../types/components";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
@ -11,9 +10,11 @@ export default function CodeAreaComponent({
onChange, onChange,
disabled, disabled,
editNode = false, editNode = false,
nodeClass,
setNodeClass,
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState( const [myValue, setMyValue] = useState(
typeof value == "string" ? value : JSON.stringify(value), typeof value == "string" ? value : JSON.stringify(value)
); );
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
useEffect(() => { useEffect(() => {
@ -28,29 +29,27 @@ export default function CodeAreaComponent({
}, [value]); }, [value]);
return ( return (
<div <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
className={ <div className="flex w-full items-center">
disabled ? "code-area-component" : "w-full"
}
>
<div className="code-area-input-positioning">
<span <span
onClick={() => { onClick={() => {
openPopUp( openPopUp(
<CodeAreaModal <CodeAreaModal
value={myValue} value={myValue}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
setValue={(t: string) => { setValue={(t: string) => {
setMyValue(t); setMyValue(t);
onChange(t); onChange(t);
}} }}
/>, />
); );
}} }}
className={ className={
editNode editNode
? "input-edit-node input-dialog" ? "input-edit-node input-dialog"
: "input-dialog input-primary " + : (disabled ? " input-disable input-ring " : "") +
(disabled ? "input-disable" : "") " input-primary text-muted-foreground "
} }
> >
{myValue !== "" ? myValue : "Type something..."} {myValue !== "" ? myValue : "Type something..."}
@ -59,17 +58,25 @@ export default function CodeAreaComponent({
onClick={() => { onClick={() => {
openPopUp( openPopUp(
<CodeAreaModal <CodeAreaModal
setNodeClass={setNodeClass}
value={myValue} value={myValue}
nodeClass={nodeClass}
setValue={(t: string) => { setValue={(t: string) => {
setMyValue(t); setMyValue(t);
onChange(t); onChange(t);
}} }}
/>, />
); );
}} }}
> >
{!editNode && ( {!editNode && (
<ExternalLink strokeWidth={1.5} className="code-area-external-link" /> <ExternalLink
strokeWidth={1.5}
className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
)} )}
</button> </button>
</div> </div>

View file

@ -1,9 +1,9 @@
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
import { Check, ChevronsUpDown } from "lucide-react";
import { Fragment, useContext, useEffect, useState } from "react"; import { Fragment, useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { DropDownComponentType } from "../../types/components"; import { DropDownComponentType } from "../../types/components";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { ChevronsUpDown, Check } from "lucide-react";
import { PopUpContext } from "../../contexts/popUpContext";
export default function Dropdown({ export default function Dropdown({
value, value,
@ -45,11 +45,7 @@ export default function Dropdown({
<span className="dropdown-component-display"> <span className="dropdown-component-display">
{internalValue} {internalValue}
</span> </span>
<span <span className={"dropdown-component-arrow"}>
className={
"dropdown-component-arrow"
}
>
<ChevronsUpDown <ChevronsUpDown
className="dropdown-component-arrow-color" className="dropdown-component-arrow-color"
aria-hidden="true" aria-hidden="true"
@ -67,8 +63,8 @@ export default function Dropdown({
<Listbox.Options <Listbox.Options
className={classNames( className={classNames(
editNode editNode
? "dropdown-component-true-options " ? "dropdown-component-true-options nowheel custom-scroll"
: "dropdown-component-false-options ", : "dropdown-component-false-options nowheel custom-scroll",
apiModal ? "mb-2 w-[250px]" : "absolute" apiModal ? "mb-2 w-[250px]" : "absolute"
)} )}
> >

View file

@ -1,7 +1,7 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { FloatComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { FloatComponentType } from "../../types/components";
export default function FloatComponent({ export default function FloatComponent({
value, value,
@ -30,11 +30,7 @@ export default function FloatComponent({
}, [closePopUp]); }, [closePopUp]);
return ( return (
<div <div className={"w-full " + (disabled ? "float-component-pointer" : "")}>
className={
"w-full " + (disabled ? "float-component-pointer" : "")
}
>
<input <input
onFocus={() => { onFocus={() => {
if (disableCopyPaste) setDisableCopyPaste(true); if (disableCopyPaste) setDisableCopyPaste(true);

View file

@ -1,27 +1,24 @@
import { useContext } from "react";
import { TabsContext } from "../../../../contexts/tabsContext";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { import {
Plus,
ChevronDown, ChevronDown,
ChevronLeft, ChevronLeft,
Undo, Plus,
Redo, Redo,
Settings2, Settings2,
Undo,
} from "lucide-react"; } from "lucide-react";
import { useContext } from "react";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuTrigger,
} from "../../../ui/dropdown-menu"; } from "../../../ui/dropdown-menu";
import { alertContext } from "../../../../contexts/alertContext";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { alertContext } from "../../../../contexts/alertContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import FlowSettingsModal from "../../../../modals/flowSettingsModal"; import FlowSettingsModal from "../../../../modals/flowSettingsModal";
import { Button } from "../../../ui/button"; import { Button } from "../../../ui/button";
@ -54,13 +51,11 @@ export const MenuBar = ({ flows, tabId }) => {
<div className="header-menu-bar"> <div className="header-menu-bar">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button asChild variant="primary" size="sm">
className="header-menu-bar-display" <div className="header-menu-bar-display">
variant="primary"
size="sm"
>
<div className="header-menu-flow-name">{current_flow.name}</div> <div className="header-menu-flow-name">{current_flow.name}</div>
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</div>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-44"> <DropdownMenuContent className="w-44">
@ -101,7 +96,7 @@ export const MenuBar = ({ flows, tabId }) => {
<Redo className="header-menu-options " /> <Redo className="header-menu-options " />
Redo Redo
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> {/* <DropdownMenuSeparator /> */}
{/* <DropdownMenuLabel>Projects</DropdownMenuLabel> */} {/* <DropdownMenuLabel>Projects</DropdownMenuLabel> */}
{/* <DropdownMenuRadioGroup className="max-h-full overflow-scroll" {/* <DropdownMenuRadioGroup className="max-h-full overflow-scroll"
value={tabId} value={tabId}

View file

@ -1,19 +1,18 @@
import { Home, MoonIcon, SunIcon, Users2 } from "lucide-react"; import { Bell, Home, MoonIcon, SunIcon, Users2 } from "lucide-react";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa"; import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
import { Button } from "../ui/button"; import { Link, useLocation, useParams } from "react-router-dom";
import { TabsContext } from "../../contexts/tabsContext";
import AlertDropdown from "../../alerts/alertDropDown"; import AlertDropdown from "../../alerts/alertDropDown";
import { USER_PROJECTS_HEADER } from "../../constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import MenuBar from "./components/menuBar";
import { Link, useLocation, useParams } from "react-router-dom";
import { USER_PROJECTS_HEADER } from "../../constants";
import { getRepoStars } from "../../controllers/API"; import { getRepoStars } from "../../controllers/API";
import { Button } from "../ui/button";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
import { Bell } from "lucide-react"; import MenuBar from "./components/menuBar";
export default function Header() { export default function Header() {
const { flows, addFlow, tabId } = useContext(TabsContext); const { flows, addFlow, tabId } = useContext(TabsContext);
@ -79,9 +78,7 @@ export default function Header() {
> >
<FaGithub className="mr-2 h-5 w-5" /> <FaGithub className="mr-2 h-5 w-5" />
Star Star
<div className="header-github-display"> <div className="header-github-display">{stars}</div>
{stars}
</div>
</a> </a>
<a <a
href="https://twitter.com/logspace_ai" href="https://twitter.com/logspace_ai"
@ -133,9 +130,7 @@ export default function Header() {
); );
}} }}
> >
{notificationCenter && ( {notificationCenter && <div className="header-notifications"></div>}
<div className="header-notifications"></div>
)}
<Bell className="side-bar-button-size" aria-hidden="true" /> <Bell className="side-bar-button-size" aria-hidden="true" />
</button> </button>
</div> </div>

View file

@ -1,8 +1,8 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { InputComponentType } from "../../types/components"; import { InputComponentType } from "../../types/components";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { TabsContext } from "../../contexts/tabsContext";
import { PopUpContext } from "../../contexts/popUpContext";
export default function InputComponent({ export default function InputComponent({
value, value,
@ -29,13 +29,7 @@ export default function InputComponent({
}, [closePopUp]); }, [closePopUp]);
return ( return (
<div <div className={disabled ? "input-component-div" : "relative"}>
className={
disabled
? "input-component-div"
: "relative"
}
>
<input <input
value={myValue} value={myValue}
onFocus={() => { onFocus={() => {
@ -45,11 +39,13 @@ export default function InputComponent({
if (disableCopyPaste) setDisableCopyPaste(false); if (disableCopyPaste) setDisableCopyPaste(false);
}} }}
className={classNames( className={classNames(
" pr-9 ",
disabled ? " input-disable " : "", disabled ? " input-disable " : "",
password && !pwdVisible && myValue !== "" ? "password" : "", password && !pwdVisible && myValue !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : " input-primary ", editNode ? " input-edit-node " : " input-primary ",
password && editNode ? "pr-8" : "pr-3" password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : ""
)} )}
placeholder={password && editNode ? "Key" : "Type something..."} placeholder={password && editNode ? "Key" : "Type something..."}
onChange={(e) => { onChange={(e) => {

View file

@ -1,9 +1,9 @@
import { FileSearch2 } from "lucide-react";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { FileComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { FileSearch2 } from "lucide-react";
import { uploadFile } from "../../controllers/API"; import { uploadFile } from "../../controllers/API";
import { FileComponentType } from "../../types/components";
export default function InputFileComponent({ export default function InputFileComponent({
value, value,
@ -92,18 +92,16 @@ export default function InputFileComponent({
}; };
return ( return (
<div <div className={disabled ? "input-component-div" : "w-full"}>
className={
disabled ? "input-component-div" : "w-full"
}
>
<div className="input-file-component"> <div className="input-file-component">
<span <span
onClick={handleButtonClick} onClick={handleButtonClick}
className={ className={
editNode editNode
? "input-edit-node " + "input-primary " ? "input-edit-node input-dialog text-muted-foreground"
: "input-primary " + (disabled ? "input-disable " : "") : disabled
? "input-disable input-dialog input-primary"
: "input-dialog input-primary text-muted-foreground"
} }
> >
{myValue !== "" ? myValue : "No file"} {myValue !== "" ? myValue : "No file"}
@ -112,7 +110,10 @@ export default function InputFileComponent({
{!editNode && !loading && ( {!editNode && !loading && (
<FileSearch2 <FileSearch2
strokeWidth={1.5} strokeWidth={1.5}
className="h-6 w-6 hover:text-accent-foreground" className={
"icons-parameters-comp" +
(disabled ? " text-ring " : " hover:text-accent-foreground")
}
/> />
)} )}
{!editNode && loading && ( {!editNode && loading && (

View file

@ -1,9 +1,8 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { InputListComponentType } from "../../types/components"; import { InputListComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext";
import _ from "lodash"; import _ from "lodash";
import { X, Plus } from "lucide-react"; import { Plus, X } from "lucide-react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
export default function InputListComponent({ export default function InputListComponent({

View file

@ -1,8 +1,7 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { FloatComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext";
import { classNames } from "../../utils";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { FloatComponentType } from "../../types/components";
export default function IntComponent({ export default function IntComponent({
value, value,
@ -31,7 +30,7 @@ export default function IntComponent({
<div <div
className={ className={
"w-full " + "w-full " +
(disabled ? "pointer-events-none w-full cursor-not-allowed" : "w-full") (disabled ? "pointer-events-none w-full cursor-not-allowed" : "")
} }
> >
<input <input
@ -42,7 +41,6 @@ export default function IntComponent({
if (disableCopyPaste) setDisableCopyPaste(false); if (disableCopyPaste) setDisableCopyPaste(false);
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
// console.log(event);
if ( if (
event.key !== "Backspace" && event.key !== "Backspace" &&
event.key !== "Enter" && event.key !== "Enter" &&
@ -72,7 +70,7 @@ export default function IntComponent({
className={ className={
editNode editNode
? " input-edit-node " ? " input-edit-node "
: " input-primary " + (disabled ? " input-disable " : "") : " input-primary " + (disabled ? " input-disable" : "")
} }
placeholder={editNode ? "Integer number" : "Type an integer number"} placeholder={editNode ? "Integer number" : "Type an integer number"}
onChange={(e) => { onChange={(e) => {

View file

@ -1,12 +1,17 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { TextAreaComponentType } from "../../types/components";
import GenericModal from "../../modals/genericModal"; import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
import { TypeModal } from "../../utils"; import { TypeModal } from "../../utils";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { typesContext } from "../../contexts/typesContext";
import { postValidatePrompt } from "../../controllers/API";
export default function PromptAreaComponent({ export default function PromptAreaComponent({
field_name,
setNodeClass,
nodeClass,
value, value,
onChange, onChange,
disabled, disabled,
@ -14,6 +19,7 @@ export default function PromptAreaComponent({
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value); const [myValue, setMyValue] = useState(value);
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
const { reactFlowInstance } = useContext(typesContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
setMyValue(""); setMyValue("");
@ -23,14 +29,36 @@ export default function PromptAreaComponent({
useEffect(() => { useEffect(() => {
setMyValue(value); setMyValue(value);
}, [value]); if (value !== "" && !editNode) {
postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
// need to update reactFlowInstance to re-render the nodes.
}
});
}
}, [value, reactFlowInstance]);
// useEffect(() => {
// if (value !== "" && myValue !== value && reactFlowInstance) {
// // only executed once
// setMyValue(value);
// postValidatePrompt(field_name, value, nodeClass)
// .then((apiReturn) => {
// if (apiReturn.data) {
// setNodeClass(apiReturn.data.frontend_node);
// // need to update reactFlowInstance to re-render the nodes.
// reactFlowInstance.setEdges(
// _.cloneDeep(reactFlowInstance.getEdges())
// );
// }
// })
// .catch((error) => {});
// }
// }, [reactFlowInstance, field_name, myValue, nodeClass, setNodeClass, value]);
return ( return (
<div <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
className={
disabled ? "pointer-events-none w-full cursor-not-allowed" : " w-full"
}
>
<div className="flex w-full items-center"> <div className="flex w-full items-center">
<span <span
onClick={() => { onClick={() => {
@ -44,15 +72,16 @@ export default function PromptAreaComponent({
setMyValue(t); setMyValue(t);
onChange(t); onChange(t);
}} }}
/>, nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
); );
}} }}
className={ className={
editNode editNode
? " input-edit-node " + " input-dialog " ? "input-edit-node input-dialog"
: (disabled ? " input-disable " : "") + : (disabled ? " input-disable text-ring " : "") +
" input-primary " + " input-primary text-muted-foreground "
" input-dialog "
} }
> >
{myValue !== "" ? myValue : "Type your prompt here"} {myValue !== "" ? myValue : "Type your prompt here"}
@ -61,6 +90,7 @@ export default function PromptAreaComponent({
onClick={() => { onClick={() => {
openPopUp( openPopUp(
<GenericModal <GenericModal
field_name={field_name}
type={TypeModal.PROMPT} type={TypeModal.PROMPT}
value={myValue} value={myValue}
buttonText="Check & Save" buttonText="Check & Save"
@ -69,14 +99,19 @@ export default function PromptAreaComponent({
setMyValue(t); setMyValue(t);
onChange(t); onChange(t);
}} }}
/>, nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
); );
}} }}
> >
{!editNode && ( {!editNode && (
<ExternalLink <ExternalLink
strokeWidth={1.5} strokeWidth={1.5}
className="ml-3 h-6 w-6 hover:text-accent-foreground" className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/> />
)} )}
</button> </button>

View file

@ -1,10 +1,11 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { TextAreaComponentType } from "../../types/components";
import GenericModal from "../../modals/genericModal"; import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
import { TypeModal } from "../../utils"; import { TypeModal } from "../../utils";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { TabsContext } from "../../contexts/tabsContext";
export default function TextAreaComponent({ export default function TextAreaComponent({
value, value,
@ -14,6 +15,7 @@ export default function TextAreaComponent({
}: TextAreaComponentType) { }: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value); const [myValue, setMyValue] = useState(value);
const { openPopUp, closePopUp } = useContext(PopUpContext); const { openPopUp, closePopUp } = useContext(PopUpContext);
const { setDisableCopyPaste } = useContext(TabsContext);
useEffect(() => { useEffect(() => {
if (disabled) { if (disabled) {
@ -27,37 +29,28 @@ export default function TextAreaComponent({
}, [closePopUp]); }, [closePopUp]);
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> <div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div <div className="flex w-full items-center">
className={ <input
editNode ? "w-full items-center" : "flex w-full items-center gap-3"
}
>
<span
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.TEXT}
buttonText="Finishing Editing"
modalTitle="Edit Text"
value={myValue} value={myValue}
setValue={(t: string) => { onFocus={() => {
setMyValue(t); setDisableCopyPaste(true);
onChange(t);
}} }}
/>, onBlur={() => {
); setDisableCopyPaste(false);
}} }}
className={ className={
editNode editNode
? "input-edit-node " + " input-dialog " ? " input-edit-node "
: " input_dialog " + : " input-primary " + (disabled ? " input-disable" : "")
"px-3 py-2" +
(disabled ? " input-disable " : "")
} }
> placeholder={"Type something..."}
{myValue !== "" ? myValue : "Type something..."} onChange={(e) => {
</span> setMyValue(e.target.value);
onChange(e.target.value);
}}
/>
<button <button
onClick={() => { onClick={() => {
openPopUp( openPopUp(
@ -70,14 +63,17 @@ export default function TextAreaComponent({
setMyValue(t); setMyValue(t);
onChange(t); onChange(t);
}} }}
/>, />
); );
}} }}
> >
{!editNode && ( {!editNode && (
<ExternalLink <ExternalLink
strokeWidth={1.5} strokeWidth={1.5}
className="ml-3 h-6 w-6 hover:text-accent-foreground" className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/> />
)} )}
</button> </button>

View file

@ -1,7 +1,7 @@
import { Switch } from "@headlessui/react"; import { Switch } from "@headlessui/react";
import { classNames } from "../../utils";
import { useEffect } from "react"; import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components"; import { ToggleComponentType } from "../../types/components";
import { classNames } from "../../utils";
export default function ToggleComponent({ export default function ToggleComponent({
enabled, enabled,

View file

@ -1,4 +1,3 @@
import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components"; import { ToggleComponentType } from "../../types/components";
import { Switch } from "../ui/switch"; import { Switch } from "../ui/switch";
@ -8,11 +7,6 @@ export default function ToggleShadComponent({
disabled, disabled,
size, size,
}: ToggleComponentType) { }: ToggleComponentType) {
useEffect(() => {
if (disabled) {
setEnabled(false);
}
}, [disabled, setEnabled]);
let scaleX, scaleY; let scaleX, scaleY;
switch (size) { switch (size) {
case "small": case "small":
@ -32,11 +26,12 @@ export default function ToggleShadComponent({
scaleY = 1; scaleY = 1;
} }
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> <div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch <Switch
style={{ style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`, transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}} }}
disabled={disabled}
className="" className=""
checked={enabled} checked={enabled}
onCheckedChange={(x: boolean) => { onCheckedChange={(x: boolean) => {

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion"; import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons"; import { ChevronDownIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { cn } from "../../utils"; import { cn } from "../../utils";
const Accordion = AccordionPrimitive.Root; const Accordion = AccordionPrimitive.Root;
@ -27,8 +27,8 @@ const AccordionTrigger = React.forwardRef<
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
ref={ref} ref={ref}
className={cn( 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", "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className, className
)} )}
{...props} {...props}
> >
@ -47,7 +47,7 @@ const AccordionContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down", "overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
className, className
)} )}
{...props} {...props}
> >

View file

@ -1,34 +1,43 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "../../utils"; import { cn } from "../../utils";
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center border rounded-full px-2.5 h-6 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center border rounded-full px-2.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary hover:bg-primary/80 border-transparent text-primary-foreground", "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
gray: "bg-border hover:bg-border/80 text-secondary-foreground",
secondary: secondary:
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
destructive: destructive:
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground", "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
outline: "text-foreground", outline: "text-foreground",
}, },
size: {
sm: "h-4 text-xs",
md: "h-5 text-sm",
lg: "h-6 text-base",
},
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
}, }
); );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {} VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, size, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <div
className={cn(badgeVariants({ variant, size }), className)}
{...props}
/>
); );
} }

View file

@ -1,6 +1,6 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "../../utils"; import { cn } from "../../utils";
const buttonVariants = cva( const buttonVariants = cva(
@ -30,7 +30,7 @@ const buttonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
}, }
); );
export interface ButtonProps export interface ButtonProps
@ -49,7 +49,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{...props} {...props}
/> />
); );
}, }
); );
Button.displayName = "Button"; Button.displayName = "Button";

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react"; import { Check } from "lucide-react";
import * as React from "react";
import { cn } from "../../utils"; import { cn } from "../../utils";
const Checkbox = React.forwardRef< const Checkbox = React.forwardRef<
@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className, className
)} )}
{...props} {...props}
> >

View file

@ -1,6 +1,6 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog"; import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react"; import { X } from "lucide-react";
import * as React from "react";
import { cn } from "../../utils"; import { cn } from "../../utils";
const Dialog = DialogPrimitive.Root; const Dialog = DialogPrimitive.Root;

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { Check, ChevronRight, Circle } from "lucide-react"; import { Check, ChevronRight, Circle } from "lucide-react";
import * as React from "react";
import { cn } from "../../utils"; import { cn } from "../../utils";
const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenu = DropdownMenuPrimitive.Root;
@ -28,7 +28,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8", inset && "pl-8",
className, className
)} )}
{...props} {...props}
> >
@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className, className
)} )}
{...props} {...props}
/> />
@ -65,7 +65,7 @@ const DropdownMenuContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className, className
)} )}
{...props} {...props}
/> />
@ -84,7 +84,7 @@ const DropdownMenuItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className, className
)} )}
{...props} {...props}
/> />
@ -99,7 +99,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className, className
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -123,7 +123,7 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className, className
)} )}
{...props} {...props}
> >
@ -148,7 +148,7 @@ const DropdownMenuLabel = React.forwardRef<
className={cn( className={cn(
"px-2 py-1.5 pl-2 text-sm font-semibold", "px-2 py-1.5 pl-2 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className, className
)} )}
{...props} {...props}
/> />

View file

@ -11,13 +11,13 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
type={type} type={type}
className={cn( className={cn(
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className, className
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
); );
}, }
); );
Input.displayName = "Input"; Input.displayName = "Input";

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