Adds better prompt experience and many other improvements (#621)
This commit is contained in:
commit
ee55d729e3
177 changed files with 4904 additions and 2981 deletions
0
.githooks/pre-commit
Normal file → Executable file
0
.githooks/pre-commit
Normal file → Executable file
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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
|
||||||
2
Makefile
2
Makefile
|
|
@ -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'
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -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
|
||||||
[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
|
[](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
|
||||||
|
[](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
771
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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
11
render.yaml
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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."""
|
||||||
|
|
|
||||||
|
|
@ -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)}))
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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()},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 😵💫"
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
64
src/backend/langflow/interface/output_parsers/base.py
Normal file
64
src/backend/langflow/interface/output_parsers/base.py
Normal 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()
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)"
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)"""
|
||||||
|
|
|
||||||
|
|
@ -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"]),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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%">
|
||||||
|
|
|
||||||
259
src/frontend/package-lock.json
generated
259
src/frontend/package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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" ? (
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
{" "}
|
{" "}
|
||||||
-
|
{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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
BIN
src/frontend/src/assets/male-technologist.png
Normal file
BIN
src/frontend/src/assets/male-technologist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
src/frontend/src/assets/robot.png
Normal file
BIN
src/frontend/src/assets/robot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 }} />
|
||||||
|
|
|
||||||
|
|
@ -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 <></>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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} />;
|
|
||||||
}
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
21
src/frontend/src/components/SanitizedHTMLWrapper/index.tsx
Normal file
21
src/frontend/src/components/SanitizedHTMLWrapper/index.tsx
Normal 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;
|
||||||
|
|
@ -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;
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue