Hotfix: fix bug on refresh and and for sending files to backend (#384)
This commit is contained in:
commit
4ef3e1a0aa
118 changed files with 7090 additions and 5905 deletions
2
.githooks/pre-commit
Executable file
2
.githooks/pre-commit
Executable file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
make format
|
||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
|
@ -9,7 +9,7 @@ env:
|
||||||
POETRY_VERSION: "1.4.0"
|
POETRY_VERSION: "1.4.0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -5,12 +5,13 @@ npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
qdrant_storage
|
||||||
|
|
||||||
# Mac
|
# Mac
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# VSCode
|
# VSCode
|
||||||
.vscode
|
.vscode/settings.json
|
||||||
.chroma
|
.chroma
|
||||||
.ruff_cache
|
.ruff_cache
|
||||||
|
|
||||||
|
|
|
||||||
35
.vscode/launch.json
vendored
Normal file
35
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: FastAPI",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "uvicorn",
|
||||||
|
"args": [
|
||||||
|
"langflow.main:app",
|
||||||
|
"--port",
|
||||||
|
"7860",
|
||||||
|
"--reload",
|
||||||
|
"--log-level",
|
||||||
|
"debug"
|
||||||
|
],
|
||||||
|
"jinja": true,
|
||||||
|
"justMyCode": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Python: Remote Attach",
|
||||||
|
"type": "python",
|
||||||
|
"request": "attach",
|
||||||
|
"justMyCode": true,
|
||||||
|
"connect": {
|
||||||
|
"port": 5678
|
||||||
|
},
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,35 @@ the system we use to tag our issues and pull requests.
|
||||||
### Local development
|
### Local development
|
||||||
You can develop LangFlow using docker compose, or locally.
|
You can develop LangFlow using docker compose, or locally.
|
||||||
|
|
||||||
|
We provide a .vscode/launch.json file for debugging the backend in VSCode, which is a lot faster than using docker compose.
|
||||||
|
|
||||||
|
Setting up hooks:
|
||||||
|
```bash
|
||||||
|
make init
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install the pre-commit hooks, which will run `make format` on every commit.
|
||||||
|
|
||||||
|
It is advised to run `make lint` before pushing to the repository.
|
||||||
|
|
||||||
|
#### **Locally**
|
||||||
|
Run locally by cloning the repository and installing the dependencies. We recommend using a virtual environment to isolate the dependencies from your system.
|
||||||
|
|
||||||
|
Before you start, make sure you have the following installed:
|
||||||
|
- Poetry (>=1.4)
|
||||||
|
- Node.js
|
||||||
|
|
||||||
|
For the backend, you will need to install the dependencies and start the development server.
|
||||||
|
```bash
|
||||||
|
make install_backend
|
||||||
|
make backend
|
||||||
|
```
|
||||||
|
For the frontend, you will need to install the dependencies and start the development server.
|
||||||
|
```bash
|
||||||
|
make frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
#### **Docker compose**
|
#### **Docker compose**
|
||||||
This will run the backend and frontend in separate containers. The frontend will be available at `localhost:3000` and the backend at `localhost:7860`.
|
This will run the backend and frontend in separate containers. The frontend will be available at `localhost:3000` and the backend at `localhost:7860`.
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -50,22 +79,4 @@ docker compose up --build
|
||||||
make dev build=1
|
make dev build=1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### **Locally**
|
|
||||||
Run locally by cloning the repository and installing the dependencies. We recommend using a virtual environment to isolate the dependencies from your system.
|
|
||||||
|
|
||||||
Before you start, make sure you have the following installed:
|
|
||||||
- Poetry
|
|
||||||
- Node.js
|
|
||||||
|
|
||||||
For the backend, you will need to install the dependencies and start the development server.
|
|
||||||
```bash
|
|
||||||
poetry install
|
|
||||||
make run_backend
|
|
||||||
```
|
|
||||||
For the frontend, you will need to install the dependencies and start the development server.
|
|
||||||
```bash
|
|
||||||
cd src/frontend
|
|
||||||
npm install
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -1,7 +1,11 @@
|
||||||
.PHONY: all format lint build build_frontend install_frontend run_frontend run_backend dev help tests coverage
|
.PHONY: all init format lint build build_frontend install_frontend run_frontend run_backend dev help tests coverage
|
||||||
|
|
||||||
all: help
|
all: help
|
||||||
|
|
||||||
|
init:
|
||||||
|
@echo 'Installing pre-commit hooks'
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
poetry run pytest --cov \
|
poetry run pytest --cov \
|
||||||
--cov-config=.coveragerc \
|
--cov-config=.coveragerc \
|
||||||
|
|
@ -13,7 +17,8 @@ tests:
|
||||||
|
|
||||||
format:
|
format:
|
||||||
poetry run black .
|
poetry run black .
|
||||||
poetry run ruff --select I --fix .
|
poetry run ruff . --fix
|
||||||
|
cd src/frontend && npm run format
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
poetry run mypy .
|
poetry run mypy .
|
||||||
|
|
|
||||||
1202
poetry.lock
generated
1202
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,7 +21,7 @@ include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
|
||||||
langflow = "langflow.__main__:main"
|
langflow = "langflow.__main__:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = ">=3.9,<3.12"
|
||||||
fastapi = "^0.92.0"
|
fastapi = "^0.92.0"
|
||||||
uvicorn = "^0.20.0"
|
uvicorn = "^0.20.0"
|
||||||
beautifulsoup4 = "^4.11.2"
|
beautifulsoup4 = "^4.11.2"
|
||||||
|
|
@ -47,11 +47,14 @@ fake-useragent = "^1.1.3"
|
||||||
docstring-parser = "^0.15"
|
docstring-parser = "^0.15"
|
||||||
psycopg2-binary = "^2.9.6"
|
psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^11.0.0"
|
pyarrow = "^11.0.0"
|
||||||
websockets = "^11.0.2"
|
|
||||||
tiktoken = "^0.3.3"
|
tiktoken = "^0.3.3"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
gptcache = "^0.1.23"
|
|
||||||
langchain-serve = { version = "^0.0.33", optional = true }
|
langchain-serve = { version = "^0.0.33", optional = true }
|
||||||
|
qdrant-client = "^1.2.0"
|
||||||
|
websockets = "^11.0.3"
|
||||||
|
weaviate-client = "^3.19.2"
|
||||||
|
jina = "3.15.2"
|
||||||
|
sentence-transformers = "^2.2.2"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
|
|
|
||||||
|
|
@ -18,59 +18,10 @@ def get_number_of_workers(workers=None):
|
||||||
return workers
|
return workers
|
||||||
|
|
||||||
|
|
||||||
def update_settings(config: str):
|
def update_settings(config: str, dev: bool = False):
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
if config:
|
if config:
|
||||||
settings.update_from_yaml(config)
|
settings.update_from_yaml(config, dev=dev)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
|
||||||
def serve(
|
|
||||||
host: str = typer.Option("127.0.0.1", help="Host to bind the server to."),
|
|
||||||
workers: int = typer.Option(1, help="Number of worker processes."),
|
|
||||||
timeout: int = typer.Option(60, help="Worker timeout in seconds."),
|
|
||||||
port: int = typer.Option(7860, help="Port to listen on."),
|
|
||||||
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
|
|
||||||
log_level: str = typer.Option("info", help="Logging level."),
|
|
||||||
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
|
|
||||||
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Run the Langflow server.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if jcloud:
|
|
||||||
return serve_on_jcloud()
|
|
||||||
|
|
||||||
configure(log_level=log_level, log_file=log_file)
|
|
||||||
update_settings(config)
|
|
||||||
app = create_app()
|
|
||||||
# get the directory of the current file
|
|
||||||
path = Path(__file__).parent
|
|
||||||
static_files_dir = path / "frontend"
|
|
||||||
app.mount(
|
|
||||||
"/",
|
|
||||||
StaticFiles(directory=static_files_dir, html=True),
|
|
||||||
name="static",
|
|
||||||
)
|
|
||||||
options = {
|
|
||||||
"bind": f"{host}:{port}",
|
|
||||||
"workers": get_number_of_workers(workers),
|
|
||||||
"worker_class": "uvicorn.workers.UvicornWorker",
|
|
||||||
"timeout": timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if platform.system() in ["Darwin", "Windows"]:
|
|
||||||
# Run using uvicorn on MacOS and Windows
|
|
||||||
# Windows doesn't support gunicorn
|
|
||||||
# MacOS requires an env variable to be set to use gunicorn
|
|
||||||
import uvicorn
|
|
||||||
|
|
||||||
uvicorn.run(app, host=host, port=port, log_level=log_level)
|
|
||||||
else:
|
|
||||||
from langflow.server import LangflowApplication
|
|
||||||
|
|
||||||
LangflowApplication(app, options).run()
|
|
||||||
|
|
||||||
|
|
||||||
def serve_on_jcloud():
|
def serve_on_jcloud():
|
||||||
|
|
@ -119,6 +70,56 @@ def serve_on_jcloud():
|
||||||
click.secho("https://github.com/jina-ai/langchain-serve", fg="blue")
|
click.secho("https://github.com/jina-ai/langchain-serve", fg="blue")
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def serve(
|
||||||
|
host: str = typer.Option("127.0.0.1", help="Host to bind the server to."),
|
||||||
|
workers: int = typer.Option(1, help="Number of worker processes."),
|
||||||
|
timeout: int = typer.Option(60, help="Worker timeout in seconds."),
|
||||||
|
port: int = typer.Option(7860, help="Port to listen on."),
|
||||||
|
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
|
||||||
|
log_level: str = typer.Option("info", help="Logging level."),
|
||||||
|
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
|
||||||
|
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
|
||||||
|
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Run the Langflow server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if jcloud:
|
||||||
|
return serve_on_jcloud()
|
||||||
|
|
||||||
|
configure(log_level=log_level, log_file=log_file)
|
||||||
|
update_settings(config, dev=dev)
|
||||||
|
app = create_app()
|
||||||
|
# get the directory of the current file
|
||||||
|
path = Path(__file__).parent
|
||||||
|
static_files_dir = path / "frontend"
|
||||||
|
app.mount(
|
||||||
|
"/",
|
||||||
|
StaticFiles(directory=static_files_dir, html=True),
|
||||||
|
name="static",
|
||||||
|
)
|
||||||
|
options = {
|
||||||
|
"bind": f"{host}:{port}",
|
||||||
|
"workers": get_number_of_workers(workers),
|
||||||
|
"worker_class": "uvicorn.workers.UvicornWorker",
|
||||||
|
"timeout": timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if platform.system() in ["Darwin", "Windows"]:
|
||||||
|
# Run using uvicorn on MacOS and Windows
|
||||||
|
# Windows doesn't support gunicorn
|
||||||
|
# MacOS requires an env variable to be set to use gunicorn
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host=host, port=port, log_level=log_level)
|
||||||
|
else:
|
||||||
|
from langflow.server import LangflowApplication
|
||||||
|
|
||||||
|
LangflowApplication(app, options).run()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app()
|
app()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ class ChatHistory(Subject):
|
||||||
if not isinstance(message, FileResponse):
|
if not isinstance(message, FileResponse):
|
||||||
self.notify()
|
self.notify()
|
||||||
|
|
||||||
def get_history(self, client_id: str, filter=True) -> List[ChatMessage]:
|
def get_history(self, client_id: str, filter_messages=True) -> List[ChatMessage]:
|
||||||
"""Get the chat history for a client."""
|
"""Get the chat history for a client."""
|
||||||
if history := self.history.get(client_id, []):
|
if history := self.history.get(client_id, []):
|
||||||
if filter:
|
if filter_messages:
|
||||||
return [msg for msg in history if msg.type not in ["start", "stream"]]
|
return [msg for msg in history if msg.type not in ["start", "stream"]]
|
||||||
return history
|
return history
|
||||||
else:
|
else:
|
||||||
|
|
@ -54,7 +54,9 @@ class ChatManager:
|
||||||
"""Send the last chat message to the client."""
|
"""Send the last chat message to the client."""
|
||||||
client_id = self.cache_manager.current_client_id
|
client_id = self.cache_manager.current_client_id
|
||||||
if client_id in self.active_connections:
|
if client_id in self.active_connections:
|
||||||
chat_response = self.chat_history.get_history(client_id, filter=False)[-1]
|
chat_response = self.chat_history.get_history(
|
||||||
|
client_id, filter_messages=False
|
||||||
|
)[-1]
|
||||||
if chat_response.is_bot:
|
if chat_response.is_bot:
|
||||||
# Process FileResponse
|
# Process FileResponse
|
||||||
if isinstance(chat_response, FileResponse):
|
if isinstance(chat_response, FileResponse):
|
||||||
|
|
@ -128,7 +130,7 @@ class ChatManager:
|
||||||
raise e
|
raise e
|
||||||
# Send a response back to the frontend, if needed
|
# Send a response back to the frontend, if needed
|
||||||
intermediate_steps = intermediate_steps or ""
|
intermediate_steps = intermediate_steps or ""
|
||||||
history = self.chat_history.get_history(client_id, filter=False)
|
history = self.chat_history.get_history(client_id, filter_messages=False)
|
||||||
file_responses = []
|
file_responses = []
|
||||||
if history:
|
if history:
|
||||||
# Iterate backwards through the history
|
# Iterate backwards through the history
|
||||||
|
|
|
||||||
2
src/backend/langflow/cache/base.py
vendored
2
src/backend/langflow/cache/base.py
vendored
|
|
@ -120,7 +120,7 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
|
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
||||||
if content is None:
|
if not content:
|
||||||
raise ValueError("Please, reload the file in the loader.")
|
raise ValueError("Please, reload the file in the loader.")
|
||||||
data = content.split(",")[1]
|
data = content.split(",")[1]
|
||||||
decoded_bytes = base64.b64decode(data)
|
decoded_bytes = base64.b64decode(data)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,12 @@
|
||||||
|
---
|
||||||
|
agents:
|
||||||
|
- ZeroShotAgent
|
||||||
|
- JsonAgent
|
||||||
|
- CSVAgent
|
||||||
|
- initialize_agent
|
||||||
|
- VectorStoreAgent
|
||||||
|
- VectorStoreRouterAgent
|
||||||
|
- SQLAgent
|
||||||
chains:
|
chains:
|
||||||
- LLMChain
|
- LLMChain
|
||||||
- LLMMathChain
|
- LLMMathChain
|
||||||
|
|
@ -7,24 +16,35 @@ chains:
|
||||||
- MidJourneyPromptChain
|
- MidJourneyPromptChain
|
||||||
- TimeTravelGuideChain
|
- TimeTravelGuideChain
|
||||||
- SQLDatabaseChain
|
- SQLDatabaseChain
|
||||||
|
documentloaders:
|
||||||
agents:
|
- AirbyteJSONLoader
|
||||||
- ZeroShotAgent
|
- CoNLLULoader
|
||||||
- JsonAgent
|
- CSVLoader
|
||||||
- CSVAgent
|
- UnstructuredEmailLoader
|
||||||
- initialize_agent
|
- EverNoteLoader
|
||||||
- VectorStoreAgent
|
- FacebookChatLoader
|
||||||
- VectorStoreRouterAgent
|
- GutenbergLoader
|
||||||
- SQLAgent
|
- BSHTMLLoader
|
||||||
|
- UnstructuredHTMLLoader
|
||||||
prompts:
|
# - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83)
|
||||||
- PromptTemplate
|
- UnstructuredMarkdownLoader
|
||||||
- FewShotPromptTemplate
|
- PyPDFLoader
|
||||||
- ZeroShotPrompt
|
- UnstructuredPowerPointLoader
|
||||||
# Wait more tests
|
- SRTLoader
|
||||||
# - ChatPromptTemplate
|
- TelegramChatLoader
|
||||||
# - SystemMessagePromptTemplate
|
- TextLoader
|
||||||
# - HumanMessagePromptTemplate
|
- UnstructuredWordDocumentLoader
|
||||||
|
- WebBaseLoader
|
||||||
|
- AZLyricsLoader
|
||||||
|
- CollegeConfidentialLoader
|
||||||
|
- HNLoader
|
||||||
|
- IFixitLoader
|
||||||
|
- IMSDbLoader
|
||||||
|
- GitbookLoader
|
||||||
|
- ReadTheDocsLoader
|
||||||
|
embeddings:
|
||||||
|
- OpenAIEmbeddings
|
||||||
|
- HuggingFaceEmbeddings
|
||||||
|
|
||||||
llms:
|
llms:
|
||||||
- OpenAI
|
- OpenAI
|
||||||
|
|
@ -32,7 +52,21 @@ llms:
|
||||||
- ChatOpenAI
|
- ChatOpenAI
|
||||||
- HuggingFaceHub
|
- HuggingFaceHub
|
||||||
- LlamaCpp
|
- LlamaCpp
|
||||||
|
memories:
|
||||||
|
- ConversationBufferMemory
|
||||||
|
- ConversationSummaryMemory
|
||||||
|
- ConversationKGMemory
|
||||||
|
prompts:
|
||||||
|
- PromptTemplate
|
||||||
|
- FewShotPromptTemplate
|
||||||
|
- ZeroShotPrompt
|
||||||
|
textsplitters:
|
||||||
|
- CharacterTextSplitter
|
||||||
|
toolkits:
|
||||||
|
- OpenAPIToolkit
|
||||||
|
- JsonToolkit
|
||||||
|
- VectorStoreInfo
|
||||||
|
- VectorStoreRouterToolkit
|
||||||
tools:
|
tools:
|
||||||
- Search
|
- Search
|
||||||
- PAL-MATH
|
- PAL-MATH
|
||||||
|
|
@ -63,57 +97,6 @@ tools:
|
||||||
- RequestsDeleteTool
|
- RequestsDeleteTool
|
||||||
- WikipediaQueryRun
|
- WikipediaQueryRun
|
||||||
- WolframAlphaQueryRun
|
- WolframAlphaQueryRun
|
||||||
|
|
||||||
wrappers:
|
|
||||||
- RequestsWrapper
|
|
||||||
|
|
||||||
toolkits:
|
|
||||||
- OpenAPIToolkit
|
|
||||||
- JsonToolkit
|
|
||||||
- VectorStoreInfo
|
|
||||||
- VectorStoreRouterToolkit
|
|
||||||
|
|
||||||
memories:
|
|
||||||
- ConversationBufferMemory
|
|
||||||
- ConversationSummaryMemory
|
|
||||||
- ConversationKGMemory
|
|
||||||
|
|
||||||
embeddings:
|
|
||||||
- OpenAIEmbeddings
|
|
||||||
|
|
||||||
vectorstores:
|
|
||||||
- Chroma
|
|
||||||
|
|
||||||
documentloaders:
|
|
||||||
- AirbyteJSONLoader
|
|
||||||
- CoNLLULoader
|
|
||||||
- CSVLoader
|
|
||||||
- UnstructuredEmailLoader
|
|
||||||
- EverNoteLoader
|
|
||||||
- FacebookChatLoader
|
|
||||||
- GutenbergLoader
|
|
||||||
- BSHTMLLoader
|
|
||||||
- UnstructuredHTMLLoader
|
|
||||||
# - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83)
|
|
||||||
- UnstructuredMarkdownLoader
|
|
||||||
- PyPDFLoader
|
|
||||||
- UnstructuredPowerPointLoader
|
|
||||||
- SRTLoader
|
|
||||||
- TelegramChatLoader
|
|
||||||
- TextLoader
|
|
||||||
- UnstructuredWordDocumentLoader
|
|
||||||
- WebBaseLoader
|
|
||||||
- AZLyricsLoader
|
|
||||||
- CollegeConfidentialLoader
|
|
||||||
- HNLoader
|
|
||||||
- IFixitLoader
|
|
||||||
- IMSDbLoader
|
|
||||||
- GitbookLoader
|
|
||||||
- ReadTheDocsLoader
|
|
||||||
|
|
||||||
textsplitters:
|
|
||||||
- CharacterTextSplitter
|
|
||||||
|
|
||||||
utilities:
|
utilities:
|
||||||
- BingSearchAPIWrapper
|
- BingSearchAPIWrapper
|
||||||
- GoogleSearchAPIWrapper
|
- GoogleSearchAPIWrapper
|
||||||
|
|
@ -125,5 +108,12 @@ utilities:
|
||||||
- WolframAlphaAPIWrapper
|
- WolframAlphaAPIWrapper
|
||||||
# - ZapierNLAWrapper
|
# - ZapierNLAWrapper
|
||||||
- SQLDatabase
|
- SQLDatabase
|
||||||
|
vectorstores:
|
||||||
dev: false
|
- Chroma
|
||||||
|
- Qdrant
|
||||||
|
- Weaviate
|
||||||
|
wrappers:
|
||||||
|
- RequestsWrapper # Wait more tests
|
||||||
|
# - ChatPromptTemplate
|
||||||
|
# - SystemMessagePromptTemplate
|
||||||
|
# - HumanMessagePromptTemplate
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,27 @@
|
||||||
from langflow.template import nodes
|
from langflow.template import frontend_node
|
||||||
|
|
||||||
# These should always be instantiated
|
# These should always be instantiated
|
||||||
CUSTOM_NODES = {
|
CUSTOM_NODES = {
|
||||||
"prompts": {"ZeroShotPrompt": nodes.ZeroShotPromptNode()},
|
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
|
||||||
"tools": {"PythonFunction": nodes.PythonFunctionNode(), "Tool": nodes.ToolNode()},
|
"tools": {
|
||||||
|
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
|
||||||
|
"Tool": frontend_node.tools.ToolNode(),
|
||||||
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"JsonAgent": nodes.JsonAgentNode(),
|
"JsonAgent": frontend_node.agents.JsonAgentNode(),
|
||||||
"CSVAgent": nodes.CSVAgentNode(),
|
"CSVAgent": frontend_node.agents.CSVAgentNode(),
|
||||||
"initialize_agent": nodes.InitializeAgentNode(),
|
"initialize_agent": frontend_node.agents.InitializeAgentNode(),
|
||||||
"VectorStoreAgent": nodes.VectorStoreAgentNode(),
|
"VectorStoreAgent": frontend_node.agents.VectorStoreAgentNode(),
|
||||||
"VectorStoreRouterAgent": nodes.VectorStoreRouterAgentNode(),
|
"VectorStoreRouterAgent": frontend_node.agents.VectorStoreRouterAgentNode(),
|
||||||
"SQLAgent": nodes.SQLAgentNode(),
|
"SQLAgent": frontend_node.agents.SQLAgentNode(),
|
||||||
},
|
},
|
||||||
"utilities": {
|
"utilities": {
|
||||||
"SQLDatabase": nodes.SQLDatabaseNode(),
|
"SQLDatabase": frontend_node.agents.SQLDatabaseNode(),
|
||||||
},
|
},
|
||||||
"chains": {
|
"chains": {
|
||||||
"SeriesCharacterChain": nodes.SeriesCharacterChainNode(),
|
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),
|
||||||
"TimeTravelGuideChain": nodes.TimeTravelGuideChainNode(),
|
"TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(),
|
||||||
"MidJourneyPromptChain": nodes.MidJourneyPromptChainNode(),
|
"MidJourneyPromptChain": frontend_node.chains.MidJourneyPromptChainNode(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,13 @@ class Node:
|
||||||
elif isinstance(value, list) and all(
|
elif isinstance(value, list) and all(
|
||||||
isinstance(node, Node) for node in value
|
isinstance(node, Node) for node in value
|
||||||
):
|
):
|
||||||
self.params[key] = [node.build() for node in value] # type: ignore
|
self.params[key] = []
|
||||||
|
for node in value:
|
||||||
|
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
|
# Get the class from LANGCHAIN_TYPES_DICT
|
||||||
# and instantiate it with the params
|
# and instantiate it with the params
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from copy import deepcopy
|
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from langflow.graph.base import Node
|
from langflow.graph.base import Node
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ from langchain.agents.agent_toolkits.vectorstore.prompt import (
|
||||||
ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX,
|
ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX,
|
||||||
)
|
)
|
||||||
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
||||||
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
|
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
from langchain.memory.chat_memory import BaseChatMemory
|
from langchain.memory.chat_memory import BaseChatMemory
|
||||||
from langchain.sql_database import SQLDatabase
|
from langchain.sql_database import SQLDatabase
|
||||||
|
|
@ -220,7 +219,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
QuerySQLDataBaseTool(db=db), # type: ignore
|
QuerySQLDataBaseTool(db=db), # type: ignore
|
||||||
InfoSQLDatabaseTool(db=db), # type: ignore
|
InfoSQLDatabaseTool(db=db), # type: ignore
|
||||||
ListSQLDatabaseTool(db=db), # type: ignore
|
ListSQLDatabaseTool(db=db), # type: ignore
|
||||||
QueryCheckerTool(db=db, llm_chain=llmchain), # type: ignore
|
QueryCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
||||||
]
|
]
|
||||||
|
|
||||||
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
||||||
|
|
@ -228,7 +227,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
tools=tools, # type: ignore
|
tools=tools, # type: ignore
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
suffix=SQL_SUFFIX,
|
suffix=SQL_SUFFIX,
|
||||||
format_instructions=SQL_FORMAT_INSTRUCTIONS,
|
format_instructions=FORMAT_INSTRUCTIONS,
|
||||||
)
|
)
|
||||||
llm_chain = LLMChain(
|
llm_chain = LLMChain(
|
||||||
llm=llm,
|
llm=llm,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ from typing import Any, Dict, List, Optional, Type, Union
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.base import FrontendNode, Template, TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
from langflow.template.template.base import Template
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
||||||
|
|
@ -42,7 +44,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
# so we should update the result dict
|
# so we should update the result dict
|
||||||
node = self.frontend_node(name)
|
node = self.frontend_node(name)
|
||||||
if node is not None:
|
if node is not None:
|
||||||
node = node.to_dict()
|
node = node.to_dict() # type: ignore
|
||||||
result[self.type_name].update(node)
|
result[self.type_name].update(node)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import chain_type_to_cls_dict
|
from langflow.interface.custom_lists import chain_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.template.nodes import ChainFrontendNode
|
from langflow.template.frontend_node.chains import ChainFrontendNode
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,9 @@ from langchain import (
|
||||||
memory,
|
memory,
|
||||||
requests,
|
requests,
|
||||||
text_splitter,
|
text_splitter,
|
||||||
utilities,
|
|
||||||
vectorstores,
|
|
||||||
)
|
)
|
||||||
from langchain.agents import agent_toolkits
|
from langchain.agents import agent_toolkits
|
||||||
from langchain.chat_models import ChatOpenAI
|
from langchain.chat_models import ChatOpenAI
|
||||||
from langchain.sql_database import SQLDatabase
|
|
||||||
|
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
|
|
||||||
|
|
@ -60,11 +57,6 @@ embedding_type_to_cls_dict: dict[str, Any] = {
|
||||||
for embedding_name in embeddings.__all__
|
for embedding_name in embeddings.__all__
|
||||||
}
|
}
|
||||||
|
|
||||||
## Vector Stores
|
|
||||||
vectorstores_type_to_cls_dict: dict[str, Any] = {
|
|
||||||
vectorstore_name: import_class(f"langchain.vectorstores.{vectorstore_name}")
|
|
||||||
for vectorstore_name in vectorstores.__all__
|
|
||||||
}
|
|
||||||
|
|
||||||
## Document Loaders
|
## Document Loaders
|
||||||
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
||||||
|
|
@ -78,9 +70,3 @@ documentloaders_type_to_cls_dict: dict[str, Any] = {
|
||||||
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
|
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
|
||||||
inspect.getmembers(text_splitter, inspect.isclass)
|
inspect.getmembers(text_splitter, inspect.isclass)
|
||||||
)
|
)
|
||||||
|
|
||||||
## Utilities
|
|
||||||
utility_type_to_cls_dict: dict[str, Any] = dict(
|
|
||||||
inspect.getmembers(utilities, inspect.isclass)
|
|
||||||
)
|
|
||||||
utility_type_to_cls_dict["SQLDatabase"] = SQLDatabase
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from typing import Dict, List, Optional, Type
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.template.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.nodes import EmbeddingFrontendNode
|
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,9 @@ def import_class(class_path: str) -> Any:
|
||||||
|
|
||||||
|
|
||||||
def import_prompt(prompt: str) -> Type[PromptTemplate]:
|
def import_prompt(prompt: str) -> Type[PromptTemplate]:
|
||||||
|
"""Import prompt from prompt name"""
|
||||||
from langflow.interface.prompts.custom import CUSTOM_PROMPTS
|
from langflow.interface.prompts.custom import CUSTOM_PROMPTS
|
||||||
|
|
||||||
"""Import prompt from prompt name"""
|
|
||||||
if prompt == "ZeroShotPrompt":
|
if prompt == "ZeroShotPrompt":
|
||||||
return import_class("langchain.prompts.PromptTemplate")
|
return import_class("langchain.prompts.PromptTemplate")
|
||||||
elif prompt in CUSTOM_PROMPTS:
|
elif prompt in CUSTOM_PROMPTS:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from typing import Dict, List, Optional, Type
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.template.nodes import LLMFrontendNode
|
from langflow.template.frontend_node.llms import LLMFrontendNode
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,10 +152,10 @@ def instantiate_utility(node_type, class_object, params):
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(path: str, build=True):
|
def load_flow_from_json(path: str, build=True):
|
||||||
|
"""Load flow from json file"""
|
||||||
# This is done to avoid circular imports
|
# This is done to avoid circular imports
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
|
|
||||||
"""Load flow from json file"""
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
flow_graph = json.load(f)
|
flow_graph = json.load(f)
|
||||||
data_graph = flow_graph["data"]
|
data_graph = flow_graph["data"]
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ from typing import Dict, List, Optional, Type
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.template.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.nodes import MemoryFrontendNode
|
from langflow.template.frontend_node.memories import MemoryFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.template.nodes import PromptFrontendNode
|
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ from langflow.interface.tools.constants import (
|
||||||
)
|
)
|
||||||
from langflow.interface.tools.util import get_tool_params
|
from langflow.interface.tools.util import get_tool_params
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.template.base import Template, TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.template.base import Template
|
||||||
from langflow.utils import util
|
from langflow.utils import util
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
|
from langchain import SQLDatabase, utilities
|
||||||
|
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import utility_type_to_cls_dict
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
|
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -11,16 +14,39 @@ from langflow.utils.util import build_template_from_class
|
||||||
class UtilityCreator(LangChainTypeCreator):
|
class UtilityCreator(LangChainTypeCreator):
|
||||||
type_name: str = "utilities"
|
type_name: str = "utilities"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[UtilitiesFrontendNode]:
|
||||||
|
return UtilitiesFrontendNode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
return utility_type_to_cls_dict
|
"""
|
||||||
|
Returns a dictionary mapping utility names to their corresponding loader classes.
|
||||||
|
If the dictionary has not been created yet, it is created by importing all utility classes
|
||||||
|
from the langchain.chains module and filtering them according to the settings.utilities list.
|
||||||
|
"""
|
||||||
|
if self.type_dict is None:
|
||||||
|
self.type_dict = {
|
||||||
|
utility_name: import_class(f"langchain.utilities.{utility_name}")
|
||||||
|
for utility_name in utilities.__all__
|
||||||
|
}
|
||||||
|
self.type_dict["SQLDatabase"] = SQLDatabase
|
||||||
|
# Filter according to settings.utilities
|
||||||
|
self.type_dict = {
|
||||||
|
name: utility
|
||||||
|
for name, utility in self.type_dict.items()
|
||||||
|
if name in settings.utilities or settings.dev
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.type_dict
|
||||||
|
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
"""Get the signature of a utility."""
|
"""Get the signature of a utility."""
|
||||||
try:
|
try:
|
||||||
if name in get_custom_nodes(self.type_name).keys():
|
custom_nodes = get_custom_nodes(self.type_name)
|
||||||
return get_custom_nodes(self.type_name)[name]
|
if name in custom_nodes.keys():
|
||||||
return build_template_from_class(name, utility_type_to_cls_dict)
|
return custom_nodes[name]
|
||||||
|
return build_template_from_class(name, self.type_to_loader_dict)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(f"Utility {name} not found") from exc
|
raise ValueError(f"Utility {name} not found") from exc
|
||||||
|
|
||||||
|
|
@ -29,11 +55,7 @@ class UtilityCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
return [
|
return list(self.type_to_loader_dict.keys())
|
||||||
utility.__name__
|
|
||||||
for utility in self.type_to_loader_dict.values()
|
|
||||||
if utility.__name__ in settings.utilities or settings.dev
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
utility_creator = UtilityCreator()
|
utility_creator = UtilityCreator()
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,41 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Any, Dict, List, Optional, Type
|
||||||
|
|
||||||
|
from langchain import vectorstores
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import vectorstores_type_to_cls_dict
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
|
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
|
||||||
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_method
|
||||||
|
|
||||||
|
|
||||||
class VectorstoreCreator(LangChainTypeCreator):
|
class VectorstoreCreator(LangChainTypeCreator):
|
||||||
type_name: str = "vectorstores"
|
type_name: str = "vectorstores"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[VectorStoreFrontendNode]:
|
||||||
|
return VectorStoreFrontendNode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
return vectorstores_type_to_cls_dict
|
if self.type_dict is None:
|
||||||
|
self.type_dict: dict[str, Any] = {
|
||||||
|
vectorstore_name: import_class(
|
||||||
|
f"langchain.vectorstores.{vectorstore_name}"
|
||||||
|
)
|
||||||
|
for vectorstore_name in vectorstores.__all__
|
||||||
|
}
|
||||||
|
return self.type_dict
|
||||||
|
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
"""Get the signature of an embedding."""
|
"""Get the signature of an embedding."""
|
||||||
try:
|
try:
|
||||||
signature = build_template_from_class(name, vectorstores_type_to_cls_dict)
|
return build_template_from_method(
|
||||||
|
name,
|
||||||
# TODO: Use FrontendendNode class to build the signature
|
type_to_cls_dict=self.type_to_loader_dict,
|
||||||
signature["template"] = {
|
method_name="from_texts",
|
||||||
"documents": {
|
)
|
||||||
"type": "TextSplitter",
|
|
||||||
"required": True,
|
|
||||||
"show": True,
|
|
||||||
"name": "documents",
|
|
||||||
"display_name": "Text Splitter",
|
|
||||||
},
|
|
||||||
"embedding": {
|
|
||||||
"type": "Embeddings",
|
|
||||||
"required": True,
|
|
||||||
"show": True,
|
|
||||||
"name": "embedding",
|
|
||||||
"display_name": "Embedding",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return signature
|
|
||||||
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(f"Vector Store {name} not found") from exc
|
raise ValueError(f"Vector Store {name} not found") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class Settings(BaseSettings):
|
||||||
values[key] = []
|
values[key] = []
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def update_from_yaml(self, file_path: str):
|
def update_from_yaml(self, file_path: str, dev: bool = False):
|
||||||
new_settings = load_settings_from_yaml(file_path)
|
new_settings = load_settings_from_yaml(file_path)
|
||||||
self.chains = new_settings.chains or []
|
self.chains = new_settings.chains or []
|
||||||
self.agents = new_settings.agents or []
|
self.agents = new_settings.agents or []
|
||||||
|
|
@ -44,7 +44,7 @@ class Settings(BaseSettings):
|
||||||
self.toolkits = new_settings.toolkits or []
|
self.toolkits = new_settings.toolkits or []
|
||||||
self.textsplitters = new_settings.textsplitters or []
|
self.textsplitters = new_settings.textsplitters or []
|
||||||
self.utilities = new_settings.utilities or []
|
self.utilities = new_settings.utilities or []
|
||||||
self.dev = new_settings.dev or False
|
self.dev = dev
|
||||||
|
|
||||||
|
|
||||||
def save_settings_to_yaml(settings: Settings, file_path: str):
|
def save_settings_to_yaml(settings: Settings, file_path: str):
|
||||||
|
|
|
||||||
|
|
@ -1,251 +1 @@
|
||||||
from abc import ABC
|
|
||||||
from typing import Any, Callable, Dict, Optional, Union
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from langflow.template.constants import FORCE_SHOW_FIELDS
|
|
||||||
from langflow.utils import constants
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateFieldCreator(BaseModel, ABC):
|
|
||||||
field_type: str = "str"
|
|
||||||
required: bool = False
|
|
||||||
placeholder: str = ""
|
|
||||||
is_list: bool = False
|
|
||||||
show: bool = True
|
|
||||||
multiline: bool = False
|
|
||||||
value: Any = None
|
|
||||||
suffixes: list[str] = []
|
|
||||||
fileTypes: list[str] = []
|
|
||||||
file_types: list[str] = []
|
|
||||||
content: Union[str, None] = None
|
|
||||||
password: bool = False
|
|
||||||
options: list[str] = []
|
|
||||||
name: str = ""
|
|
||||||
display_name: Optional[str] = None
|
|
||||||
advanced: bool = False
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
result = self.dict()
|
|
||||||
# Remove key if it is None
|
|
||||||
for key in list(result.keys()):
|
|
||||||
if result[key] is None or result[key] == []:
|
|
||||||
del result[key]
|
|
||||||
result["type"] = result.pop("field_type")
|
|
||||||
result["list"] = result.pop("is_list")
|
|
||||||
|
|
||||||
if result.get("file_types"):
|
|
||||||
result["fileTypes"] = result.pop("file_types")
|
|
||||||
|
|
||||||
if self.field_type == "file":
|
|
||||||
result["content"] = self.content
|
|
||||||
return result
|
|
||||||
|
|
||||||
def process_field(
|
|
||||||
self, key: str, value: Dict[str, Any], name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
_type = value["type"]
|
|
||||||
|
|
||||||
# Remove 'Optional' wrapper
|
|
||||||
if "Optional" in _type:
|
|
||||||
_type = _type.replace("Optional[", "")[:-1]
|
|
||||||
|
|
||||||
# Check for list type
|
|
||||||
if "List" in _type:
|
|
||||||
_type = _type.replace("List[", "")[:-1]
|
|
||||||
self.is_list = True
|
|
||||||
|
|
||||||
# Replace 'Mapping' with 'dict'
|
|
||||||
if "Mapping" in _type:
|
|
||||||
_type = _type.replace("Mapping", "dict")
|
|
||||||
|
|
||||||
# Change type from str to Tool
|
|
||||||
self.field_type = "Tool" if key in {"allowed_tools"} else self.field_type
|
|
||||||
|
|
||||||
self.field_type = "int" if key in {"max_value_length"} else self.field_type
|
|
||||||
|
|
||||||
# Show or not field
|
|
||||||
self.show = bool(
|
|
||||||
(self.required and key not in ["input_variables"])
|
|
||||||
or key in FORCE_SHOW_FIELDS
|
|
||||||
or "api_key" in key
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add password field
|
|
||||||
self.password = any(
|
|
||||||
text in key.lower() for text in {"password", "token", "api", "key"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add multline
|
|
||||||
self.multiline = key in {
|
|
||||||
"suffix",
|
|
||||||
"prefix",
|
|
||||||
"template",
|
|
||||||
"examples",
|
|
||||||
"code",
|
|
||||||
"headers",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Replace dict type with str
|
|
||||||
if "dict" in self.field_type.lower():
|
|
||||||
self.field_type = "code"
|
|
||||||
|
|
||||||
if key == "dict_":
|
|
||||||
self.field_type = "file"
|
|
||||||
self.suffixes = [".json", ".yaml", ".yml"]
|
|
||||||
self.file_types = ["json", "yaml", "yml"]
|
|
||||||
|
|
||||||
# Replace default value with actual value
|
|
||||||
if "default" in value:
|
|
||||||
self.value = value["default"]
|
|
||||||
|
|
||||||
if key == "headers":
|
|
||||||
self.value = """{'Authorization':
|
|
||||||
'Bearer <token>'}"""
|
|
||||||
|
|
||||||
# Add options to openai
|
|
||||||
if name == "OpenAI" and key == "model_name":
|
|
||||||
self.options = constants.OPENAI_MODELS
|
|
||||||
self.is_list = True
|
|
||||||
elif name == "ChatOpenAI" and key == "model_name":
|
|
||||||
self.options = constants.CHAT_OPENAI_MODELS
|
|
||||||
self.is_list = True
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateField(TemplateFieldCreator):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Template(BaseModel):
|
|
||||||
type_name: str
|
|
||||||
fields: list[TemplateField]
|
|
||||||
|
|
||||||
def process_fields(
|
|
||||||
self,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
format_field_func: Union[Callable, None] = None,
|
|
||||||
):
|
|
||||||
if format_field_func:
|
|
||||||
for field in self.fields:
|
|
||||||
format_field_func(field, name)
|
|
||||||
|
|
||||||
def to_dict(self, format_field_func=None):
|
|
||||||
self.process_fields(self.type_name, format_field_func)
|
|
||||||
result = {field.name: field.to_dict() for field in self.fields}
|
|
||||||
result["_type"] = self.type_name # type: ignore
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class FrontendNode(BaseModel):
|
|
||||||
template: Template
|
|
||||||
description: str
|
|
||||||
base_classes: list
|
|
||||||
name: str = ""
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
self.name: {
|
|
||||||
"template": self.template.to_dict(self.format_field),
|
|
||||||
"description": self.description,
|
|
||||||
"base_classes": self.base_classes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
key = field.name
|
|
||||||
value = field.to_dict()
|
|
||||||
_type = value["type"]
|
|
||||||
|
|
||||||
# Remove 'Optional' wrapper
|
|
||||||
if "Optional" in _type:
|
|
||||||
_type = _type.replace("Optional[", "")[:-1]
|
|
||||||
|
|
||||||
# Check for list type
|
|
||||||
if "List" in _type or "Sequence" in _type:
|
|
||||||
_type = _type.replace("List[", "")
|
|
||||||
_type = _type.replace("Sequence[", "")[:-1]
|
|
||||||
field.is_list = True
|
|
||||||
|
|
||||||
# Replace 'Mapping' with 'dict'
|
|
||||||
if "Mapping" in _type:
|
|
||||||
_type = _type.replace("Mapping", "dict")
|
|
||||||
|
|
||||||
# {'type': 'Union[float, Tuple[float, float], NoneType]'} != {'type': 'float'}
|
|
||||||
if "Union" in _type:
|
|
||||||
_type = _type.replace("Union[", "")[:-1]
|
|
||||||
_type = _type.split(",")[0]
|
|
||||||
_type = _type.replace("]", "").replace("[", "")
|
|
||||||
|
|
||||||
field.field_type = _type
|
|
||||||
|
|
||||||
# Change type from str to Tool
|
|
||||||
field.field_type = "Tool" if key in {"allowed_tools"} else field.field_type
|
|
||||||
|
|
||||||
field.field_type = "int" if key in {"max_value_length"} else field.field_type
|
|
||||||
|
|
||||||
# Show or not field
|
|
||||||
field.show = bool(
|
|
||||||
(field.required and key not in ["input_variables"])
|
|
||||||
or key in FORCE_SHOW_FIELDS
|
|
||||||
or "api" in key
|
|
||||||
or ("key" in key and "input" not in key and "output" not in key)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add password field
|
|
||||||
field.password = (
|
|
||||||
any(text in key.lower() for text in {"password", "token", "api", "key"})
|
|
||||||
and field.show
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add multline
|
|
||||||
field.multiline = key in {
|
|
||||||
"suffix",
|
|
||||||
"prefix",
|
|
||||||
"template",
|
|
||||||
"examples",
|
|
||||||
"code",
|
|
||||||
"headers",
|
|
||||||
"description",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Replace dict type with str
|
|
||||||
if "dict" in field.field_type.lower():
|
|
||||||
field.field_type = "code"
|
|
||||||
|
|
||||||
if key == "dict_":
|
|
||||||
field.field_type = "file"
|
|
||||||
field.suffixes = [".json", ".yaml", ".yml"]
|
|
||||||
field.file_types = ["json", "yaml", "yml"]
|
|
||||||
|
|
||||||
# Replace default value with actual value
|
|
||||||
if "default" in value:
|
|
||||||
field.value = value["default"]
|
|
||||||
|
|
||||||
if key == "headers":
|
|
||||||
field.value = """{'Authorization':
|
|
||||||
'Bearer <token>'}"""
|
|
||||||
|
|
||||||
# Add options to openai
|
|
||||||
if name == "OpenAI" and key == "model_name":
|
|
||||||
field.options = constants.OPENAI_MODELS
|
|
||||||
field.is_list = True
|
|
||||||
elif name == "ChatOpenAI":
|
|
||||||
if key == "model_name":
|
|
||||||
field.options = constants.CHAT_OPENAI_MODELS
|
|
||||||
field.is_list = True
|
|
||||||
if "api_key" in key and "OpenAI" in str(name):
|
|
||||||
field.display_name = "OpenAI API Key"
|
|
||||||
field.required = False
|
|
||||||
if field.value is None:
|
|
||||||
field.value = ""
|
|
||||||
|
|
||||||
if "kwargs" in field.name.lower():
|
|
||||||
field.advanced = True
|
|
||||||
field.required = False
|
|
||||||
field.show = False
|
|
||||||
# If the field.name contains api or api and key, then it might be an api key
|
|
||||||
# other conditions are to make sure that it is not an input or output variable
|
|
||||||
if "api" in key.lower() and "key" in key.lower():
|
|
||||||
field.required = False
|
|
||||||
field.advanced = False
|
|
||||||
|
|
|
||||||
0
src/backend/langflow/template/field/__init__.py
Normal file
0
src/backend/langflow/template/field/__init__.py
Normal file
43
src/backend/langflow/template/field/base.py
Normal file
43
src/backend/langflow/template/field/base.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
from abc import ABC
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateFieldCreator(BaseModel, ABC):
|
||||||
|
field_type: str = "str"
|
||||||
|
required: bool = False
|
||||||
|
placeholder: str = ""
|
||||||
|
is_list: bool = False
|
||||||
|
show: bool = True
|
||||||
|
multiline: bool = False
|
||||||
|
value: Any = None
|
||||||
|
suffixes: list[str] = []
|
||||||
|
fileTypes: list[str] = []
|
||||||
|
file_types: list[str] = []
|
||||||
|
content: Union[str, None] = None
|
||||||
|
password: bool = False
|
||||||
|
options: list[str] = []
|
||||||
|
name: str = ""
|
||||||
|
display_name: Optional[str] = None
|
||||||
|
advanced: bool = False
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
result = self.dict()
|
||||||
|
# Remove key if it is None
|
||||||
|
for key in list(result.keys()):
|
||||||
|
if result[key] is None or result[key] == []:
|
||||||
|
del result[key]
|
||||||
|
result["type"] = result.pop("field_type")
|
||||||
|
result["list"] = result.pop("is_list")
|
||||||
|
|
||||||
|
if result.get("file_types"):
|
||||||
|
result["fileTypes"] = result.pop("file_types")
|
||||||
|
|
||||||
|
if self.field_type == "file":
|
||||||
|
result["content"] = self.content
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateField(TemplateFieldCreator):
|
||||||
|
pass
|
||||||
21
src/backend/langflow/template/frontend_node/__init__.py
Normal file
21
src/backend/langflow/template/frontend_node/__init__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
from langflow.template.frontend_node import (
|
||||||
|
agents,
|
||||||
|
chains,
|
||||||
|
embeddings,
|
||||||
|
llms,
|
||||||
|
memories,
|
||||||
|
prompts,
|
||||||
|
tools,
|
||||||
|
vectorstores,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"agents",
|
||||||
|
"chains",
|
||||||
|
"embeddings",
|
||||||
|
"memories",
|
||||||
|
"tools",
|
||||||
|
"llms",
|
||||||
|
"prompts",
|
||||||
|
"vectorstores",
|
||||||
|
]
|
||||||
233
src/backend/langflow/template/frontend_node/agents.py
Normal file
233
src/backend/langflow/template/frontend_node/agents.py
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.agents import types
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
|
||||||
|
NON_CHAT_AGENTS = {
|
||||||
|
agent_type: agent_class
|
||||||
|
for agent_type, agent_class in types.AGENT_TO_CLASS.items()
|
||||||
|
if "chat" not in agent_type.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SQLAgentNode(FrontendNode):
|
||||||
|
name: str = "SQLAgent"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="sql_agent",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
name="database_uri",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """Construct a sql agent from an LLM and tools."""
|
||||||
|
base_classes: list[str] = ["AgentExecutor"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class VectorStoreRouterAgentNode(FrontendNode):
|
||||||
|
name: str = "VectorStoreRouterAgent"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="vectorstorerouter_agent",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="VectorStoreRouterToolkit",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="vectorstoreroutertoolkit",
|
||||||
|
display_name="Vector Store Router Toolkit",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """Construct an agent from a Vector Store Router."""
|
||||||
|
base_classes: list[str] = ["AgentExecutor"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class VectorStoreAgentNode(FrontendNode):
|
||||||
|
name: str = "VectorStoreAgent"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="vectorstore_agent",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="VectorStoreInfo",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="vectorstoreinfo",
|
||||||
|
display_name="Vector Store Info",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """Construct an agent from a Vector Store."""
|
||||||
|
base_classes: list[str] = ["AgentExecutor"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class SQLDatabaseNode(FrontendNode):
|
||||||
|
name: str = "SQLDatabase"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="sql_database",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
name="uri",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """SQLAlchemy wrapper around a database."""
|
||||||
|
base_classes: list[str] = ["SQLDatabase"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class CSVAgentNode(FrontendNode):
|
||||||
|
name: str = "CSVAgent"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="csv_agent",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="file",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="path",
|
||||||
|
value="",
|
||||||
|
suffixes=[".csv"],
|
||||||
|
fileTypes=["csv"],
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """Construct a json agent from a CSV and tools."""
|
||||||
|
base_classes: list[str] = ["AgentExecutor"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class InitializeAgentNode(FrontendNode):
|
||||||
|
name: str = "initialize_agent"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="initailize_agent",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
is_list=True,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
options=list(NON_CHAT_AGENTS.keys()),
|
||||||
|
value=list(NON_CHAT_AGENTS.keys())[0],
|
||||||
|
name="agent",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseChatMemory",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="memory",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="Tool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="tools",
|
||||||
|
is_list=True,
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """Construct a json agent from an LLM and tools."""
|
||||||
|
base_classes: list[str] = ["AgentExecutor", "function"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
# do nothing and don't return anything
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JsonAgentNode(FrontendNode):
|
||||||
|
name: str = "JsonAgent"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="json_agent",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseToolkit",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="toolkit",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = """Construct a json agent from an LLM and tools."""
|
||||||
|
base_classes: list[str] = ["AgentExecutor"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
200
src/backend/langflow/template/frontend_node/base.py
Normal file
200
src/backend/langflow/template/frontend_node/base.py
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
import re
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from langflow.template.constants import FORCE_SHOW_FIELDS
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
from langflow.utils import constants
|
||||||
|
|
||||||
|
|
||||||
|
class FrontendNode(BaseModel):
|
||||||
|
template: Template
|
||||||
|
description: str
|
||||||
|
base_classes: List[str]
|
||||||
|
name: str = ""
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
self.name: {
|
||||||
|
"template": self.template.to_dict(self.format_field),
|
||||||
|
"description": self.description,
|
||||||
|
"base_classes": self.base_classes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
"""Formats a given field based on its attributes and value."""
|
||||||
|
SPECIAL_FIELD_HANDLERS = {
|
||||||
|
"allowed_tools": lambda field: "Tool",
|
||||||
|
"max_value_length": lambda field: "int",
|
||||||
|
}
|
||||||
|
|
||||||
|
key = field.name
|
||||||
|
value = field.to_dict()
|
||||||
|
_type = value["type"]
|
||||||
|
|
||||||
|
_type = FrontendNode.remove_optional(_type)
|
||||||
|
_type, is_list = FrontendNode.check_for_list_type(_type)
|
||||||
|
field.is_list = is_list or field.is_list
|
||||||
|
_type = FrontendNode.replace_mapping_with_dict(_type)
|
||||||
|
_type = FrontendNode.handle_union_type(_type)
|
||||||
|
|
||||||
|
field.field_type = FrontendNode.handle_special_field(
|
||||||
|
field, key, _type, SPECIAL_FIELD_HANDLERS
|
||||||
|
)
|
||||||
|
field.field_type = FrontendNode.handle_dict_type(field, _type)
|
||||||
|
field.show = FrontendNode.should_show_field(key, field.required)
|
||||||
|
field.password = FrontendNode.should_be_password(key, field.show)
|
||||||
|
field.multiline = FrontendNode.should_be_multiline(key)
|
||||||
|
|
||||||
|
FrontendNode.replace_default_value(field, value)
|
||||||
|
FrontendNode.handle_specific_field_values(field, key, name)
|
||||||
|
FrontendNode.handle_kwargs_field(field)
|
||||||
|
FrontendNode.handle_api_key_field(field, key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_optional(_type: str) -> str:
|
||||||
|
"""Removes 'Optional' wrapper from the type if present."""
|
||||||
|
return re.sub(r"Optional\[(.*)\]", r"\1", _type)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_for_list_type(_type: str) -> tuple:
|
||||||
|
"""Checks for list type and returns the modified type and a boolean indicating if it's a list."""
|
||||||
|
is_list = "List" in _type or "Sequence" in _type
|
||||||
|
if is_list:
|
||||||
|
_type = re.sub(r"(List|Sequence)\[(.*)\]", r"\2", _type)
|
||||||
|
return _type, is_list
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def replace_mapping_with_dict(_type: str) -> str:
|
||||||
|
"""Replaces 'Mapping' with 'dict'."""
|
||||||
|
return _type.replace("Mapping", "dict")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handle_union_type(_type: str) -> str:
|
||||||
|
"""Simplifies the 'Union' type to the first type in the Union."""
|
||||||
|
if "Union" in _type:
|
||||||
|
_type = _type.replace("Union[", "")[:-1]
|
||||||
|
_type = _type.split(",")[0]
|
||||||
|
_type = _type.replace("]", "").replace("[", "")
|
||||||
|
return _type
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handle_special_field(
|
||||||
|
field, key: str, _type: str, SPECIAL_FIELD_HANDLERS
|
||||||
|
) -> str:
|
||||||
|
"""Handles special field by using the respective handler if present."""
|
||||||
|
handler = SPECIAL_FIELD_HANDLERS.get(key)
|
||||||
|
return handler(field) if handler else _type
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handle_dict_type(field: TemplateField, _type: str) -> str:
|
||||||
|
"""Handles 'dict' type by replacing it with 'code' or 'file' based on the field name."""
|
||||||
|
if "dict" in _type.lower():
|
||||||
|
if field.name == "dict_":
|
||||||
|
field.field_type = "file"
|
||||||
|
field.suffixes = [".json", ".yaml", ".yml"]
|
||||||
|
field.file_types = ["json", "yaml", "yml"]
|
||||||
|
else:
|
||||||
|
field.field_type = "code"
|
||||||
|
return _type
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def replace_default_value(field: TemplateField, value: dict) -> None:
|
||||||
|
"""Replaces default value with actual value if 'default' is present in value."""
|
||||||
|
if "default" in value:
|
||||||
|
field.value = value["default"]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handle_specific_field_values(
|
||||||
|
field: TemplateField, key: str, name: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
|
"""Handles specific field values for certain fields."""
|
||||||
|
if key == "headers":
|
||||||
|
field.value = """{'Authorization':
|
||||||
|
'Bearer <token>'}"""
|
||||||
|
if name == "OpenAI" and key == "model_name":
|
||||||
|
field.options = constants.OPENAI_MODELS
|
||||||
|
field.is_list = True
|
||||||
|
elif name == "ChatOpenAI" and key == "model_name":
|
||||||
|
field.options = constants.CHAT_OPENAI_MODELS
|
||||||
|
field.is_list = True
|
||||||
|
if "api_key" in key and "OpenAI" in str(name):
|
||||||
|
field.display_name = "OpenAI API Key"
|
||||||
|
field.required = False
|
||||||
|
if field.value is None:
|
||||||
|
field.value = ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handle_kwargs_field(field: TemplateField) -> None:
|
||||||
|
"""Handles kwargs field by setting certain attributes."""
|
||||||
|
if "kwargs" in field.name.lower():
|
||||||
|
field.advanced = True
|
||||||
|
field.required = False
|
||||||
|
field.show = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def handle_api_key_field(field: TemplateField, key: str) -> None:
|
||||||
|
"""Handles api key field by setting certain attributes."""
|
||||||
|
if "api" in key.lower() and "key" in key.lower():
|
||||||
|
field.required = False
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
field.display_name = key.replace("_", " ").title()
|
||||||
|
field.display_name = field.display_name.replace("Api", "API")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_show_field(key: str, required: bool) -> bool:
|
||||||
|
"""Determines whether the field should be shown."""
|
||||||
|
return (
|
||||||
|
(required and key not in ["input_variables"])
|
||||||
|
or key in FORCE_SHOW_FIELDS
|
||||||
|
or "api" in key
|
||||||
|
or ("key" in key and "input" not in key and "output" not in key)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_be_password(key: str, show: bool) -> bool:
|
||||||
|
"""Determines whether the field should be a password field."""
|
||||||
|
return (
|
||||||
|
any(text in key.lower() for text in {"password", "token", "api", "key"})
|
||||||
|
and show
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_be_multiline(key: str) -> bool:
|
||||||
|
"""Determines whether the field should be multiline."""
|
||||||
|
return key in {
|
||||||
|
"suffix",
|
||||||
|
"prefix",
|
||||||
|
"template",
|
||||||
|
"examples",
|
||||||
|
"code",
|
||||||
|
"headers",
|
||||||
|
"description",
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def replace_dict_with_code_or_file(
|
||||||
|
field: TemplateField, _type: str, key: str
|
||||||
|
) -> str:
|
||||||
|
"""Replaces 'dict' type with 'code' or 'file'."""
|
||||||
|
if "dict" in _type.lower():
|
||||||
|
if key == "dict_":
|
||||||
|
field.field_type = "file"
|
||||||
|
field.suffixes = [".json", ".yaml", ".yml"]
|
||||||
|
field.file_types = ["json", "yaml", "yml"]
|
||||||
|
else:
|
||||||
|
field.field_type = "code"
|
||||||
|
return field.field_type
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_field_default_value(field: TemplateField, value: dict, key: str) -> None:
|
||||||
|
"""Sets the field value with the default value if present."""
|
||||||
|
if "default" in value:
|
||||||
|
field.value = value["default"]
|
||||||
|
if key == "headers":
|
||||||
|
field.value = """{'Authorization': 'Bearer <token>'}"""
|
||||||
157
src/backend/langflow/template/frontend_node/chains.py
Normal file
157
src/backend/langflow/template/frontend_node/chains.py
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
|
||||||
|
|
||||||
|
class ChainFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
|
||||||
|
field.advanced = False
|
||||||
|
if "key" in field.name:
|
||||||
|
field.password = False
|
||||||
|
field.show = False
|
||||||
|
if field.name in ["input_key", "output_key"]:
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
|
||||||
|
# Separated for possible future changes
|
||||||
|
if field.name == "prompt" and field.value is None:
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
if field.name == "memory":
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
if field.name == "verbose":
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
if field.name == "llm":
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
|
||||||
|
class SeriesCharacterChainNode(FrontendNode):
|
||||||
|
name: str = "SeriesCharacterChain"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="SeriesCharacterChain",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
multiline=False,
|
||||||
|
name="character",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
multiline=False,
|
||||||
|
name="series",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
multiline=False,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa
|
||||||
|
base_classes: list[str] = [
|
||||||
|
"LLMChain",
|
||||||
|
"BaseCustomChain",
|
||||||
|
"Chain",
|
||||||
|
"ConversationChain",
|
||||||
|
"SeriesCharacterChain",
|
||||||
|
"function",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TimeTravelGuideChainNode(FrontendNode):
|
||||||
|
name: str = "TimeTravelGuideChain"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="TimeTravelGuideChain",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
multiline=False,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseChatMemory",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="memory",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "Time travel guide chain to be used in the flow."
|
||||||
|
base_classes: list[str] = [
|
||||||
|
"LLMChain",
|
||||||
|
"BaseCustomChain",
|
||||||
|
"TimeTravelGuideChain",
|
||||||
|
"Chain",
|
||||||
|
"ConversationChain",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MidJourneyPromptChainNode(FrontendNode):
|
||||||
|
name: str = "MidJourneyPromptChain"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="MidJourneyPromptChain",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseLanguageModel",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
multiline=False,
|
||||||
|
name="llm",
|
||||||
|
display_name="LLM",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseChatMemory",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="memory",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."
|
||||||
|
base_classes: list[str] = [
|
||||||
|
"LLMChain",
|
||||||
|
"BaseCustomChain",
|
||||||
|
"Chain",
|
||||||
|
"ConversationChain",
|
||||||
|
"MidJourneyPromptChain",
|
||||||
|
]
|
||||||
38
src/backend/langflow/template/frontend_node/embeddings.py
Normal file
38
src/backend/langflow/template/frontend_node/embeddings.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class EmbeddingFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_jina_fields(field: TemplateField):
|
||||||
|
if "jina" in field.name:
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
if "auth" in field.name or "token" in field.name:
|
||||||
|
field.password = True
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
if field.name == "jina_api_url":
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
field.display_name = "Jina API URL"
|
||||||
|
field.password = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
field.advanced = not field.required
|
||||||
|
field.show = True
|
||||||
|
if field.name == "headers":
|
||||||
|
field.show = False
|
||||||
|
|
||||||
|
if "openai" in field.name:
|
||||||
|
field.show = True
|
||||||
|
field.advanced = "api_key" not in field.name
|
||||||
|
|
||||||
|
# Format Jina fields
|
||||||
|
EmbeddingFrontendNode.format_jina_fields(field)
|
||||||
50
src/backend/langflow/template/frontend_node/llms.py
Normal file
50
src/backend/langflow/template/frontend_node/llms.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class LLMFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_openai_field(field: TemplateField):
|
||||||
|
if "openai" in field.name.lower():
|
||||||
|
field.display_name = (
|
||||||
|
field.name.title().replace("Openai", "OpenAI").replace("_", " ")
|
||||||
|
).replace("Api", "API")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
display_names_dict = {
|
||||||
|
"huggingfacehub_api_token": "HuggingFace Hub API Token",
|
||||||
|
}
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
SHOW_FIELDS = ["repo_id"]
|
||||||
|
if field.name in SHOW_FIELDS:
|
||||||
|
field.show = True
|
||||||
|
|
||||||
|
if "api" in field.name and ("key" in field.name or "token" in field.name):
|
||||||
|
field.password = True
|
||||||
|
field.show = True
|
||||||
|
# Required should be False to support
|
||||||
|
# loading the API key from environment variables
|
||||||
|
field.required = False
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
if field.name == "task":
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.is_list = True
|
||||||
|
field.options = ["text-generation", "text2text-generation"]
|
||||||
|
field.advanced = True
|
||||||
|
|
||||||
|
if display_name := display_names_dict.get(field.name):
|
||||||
|
field.display_name = display_name
|
||||||
|
if field.name == "model_kwargs":
|
||||||
|
field.field_type = "code"
|
||||||
|
field.advanced = True
|
||||||
|
field.show = True
|
||||||
|
elif field.name in ["model_name", "temperature"]:
|
||||||
|
field.advanced = False
|
||||||
|
field.show = True
|
||||||
|
|
||||||
|
LLMFrontendNode.format_openai_field(field)
|
||||||
20
src/backend/langflow/template/frontend_node/memories.py
Normal file
20
src/backend/langflow/template/frontend_node/memories.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
|
||||||
|
if not isinstance(field.value, str):
|
||||||
|
field.value = None
|
||||||
|
if field.name == "k":
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.field_type = "int"
|
||||||
|
field.value = 10
|
||||||
|
field.display_name = "Memory Size"
|
||||||
|
field.password = False
|
||||||
111
src/backend/langflow/template/frontend_node/prompts.py
Normal file
111
src/backend/langflow/template/frontend_node/prompts.py
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langchain.agents.mrkl import prompt
|
||||||
|
|
||||||
|
from langflow.template.constants import DEFAULT_PROMPT, HUMAN_PROMPT, SYSTEM_PROMPT
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
|
||||||
|
|
||||||
|
class PromptFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
# if field.field_type == "StringPromptTemplate"
|
||||||
|
# change it to str
|
||||||
|
PROMPT_FIELDS = [
|
||||||
|
"template",
|
||||||
|
"suffix",
|
||||||
|
"prefix",
|
||||||
|
"examples",
|
||||||
|
"format_instructions",
|
||||||
|
]
|
||||||
|
if field.field_type == "StringPromptTemplate" and "Message" in str(name):
|
||||||
|
field.field_type = "prompt"
|
||||||
|
field.multiline = True
|
||||||
|
field.value = HUMAN_PROMPT if "Human" in field.name else SYSTEM_PROMPT
|
||||||
|
if field.name == "template" and field.value == "":
|
||||||
|
field.value = DEFAULT_PROMPT
|
||||||
|
|
||||||
|
if field.name in PROMPT_FIELDS:
|
||||||
|
field.field_type = "prompt"
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
if (
|
||||||
|
"Union" in field.field_type
|
||||||
|
and "BaseMessagePromptTemplate" in field.field_type
|
||||||
|
):
|
||||||
|
field.field_type = "BaseMessagePromptTemplate"
|
||||||
|
|
||||||
|
# All prompt fields should be password=False
|
||||||
|
field.password = False
|
||||||
|
|
||||||
|
|
||||||
|
class PromptTemplateNode(FrontendNode):
|
||||||
|
name: str = "PromptTemplate"
|
||||||
|
template: Template
|
||||||
|
description: str
|
||||||
|
base_classes: list[str] = ["BasePromptTemplate"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
if field.name == "examples":
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
|
||||||
|
class BasePromptFrontendNode(FrontendNode):
|
||||||
|
name: str
|
||||||
|
template: Template
|
||||||
|
description: str
|
||||||
|
base_classes: list[str]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroShotPromptNode(BasePromptFrontendNode):
|
||||||
|
name: str = "ZeroShotPrompt"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="zero_shot",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=True,
|
||||||
|
value=prompt.PREFIX,
|
||||||
|
name="prefix",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=True,
|
||||||
|
value=prompt.SUFFIX,
|
||||||
|
name="suffix",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=True,
|
||||||
|
value=prompt.FORMAT_INSTRUCTIONS,
|
||||||
|
name="format_instructions",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "Prompt template for Zero Shot Agent."
|
||||||
|
base_classes: list[str] = ["BasePromptTemplate"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
83
src/backend/langflow/template/frontend_node/tools.py
Normal file
83
src/backend/langflow/template/frontend_node/tools.py
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
|
||||||
|
|
||||||
|
|
||||||
|
class ToolNode(FrontendNode):
|
||||||
|
name: str = "Tool"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="Tool",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=True,
|
||||||
|
value="",
|
||||||
|
name="name",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=True,
|
||||||
|
value="",
|
||||||
|
name="description",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
name="func",
|
||||||
|
field_type="function",
|
||||||
|
required=True,
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=True,
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value=False,
|
||||||
|
name="return_direct",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "Tool to be used in the flow."
|
||||||
|
base_classes: list[str] = ["Tool"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class PythonFunctionNode(FrontendNode):
|
||||||
|
name: str = "PythonFunction"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="python_function",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="code",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
value=DEFAULT_PYTHON_FUNCTION,
|
||||||
|
name="code",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "Python function to be executed."
|
||||||
|
base_classes: list[str] = ["function"]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
22
src/backend/langflow/template/frontend_node/utilities.py
Normal file
22
src/backend/langflow/template/frontend_node/utilities.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import ast
|
||||||
|
import json
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class UtilitiesFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
# field.field_type could be "Literal['news', 'search', 'places', 'images']
|
||||||
|
# we need to convert it to a list
|
||||||
|
if "Literal" in field.field_type:
|
||||||
|
field.options = ast.literal_eval(field.field_type.replace("Literal", ""))
|
||||||
|
field.is_list = True
|
||||||
|
field.field_type = "str"
|
||||||
|
|
||||||
|
if isinstance(field.value, dict):
|
||||||
|
field.field_type = "code"
|
||||||
|
field.value = json.dumps(field.value, indent=4)
|
||||||
64
src/backend/langflow/template/frontend_node/vectorstores.py
Normal file
64
src/backend/langflow/template/frontend_node/vectorstores.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class VectorStoreFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
# Define common field attributes
|
||||||
|
basic_fields = ["work_dir", "collection_name", "api_key", "location"]
|
||||||
|
advanced_fields = [
|
||||||
|
"n_dim",
|
||||||
|
"key",
|
||||||
|
"prefix",
|
||||||
|
"distance_func",
|
||||||
|
"content_payload_key",
|
||||||
|
"metadata_payload_key",
|
||||||
|
"timeout",
|
||||||
|
"host",
|
||||||
|
"path",
|
||||||
|
"url",
|
||||||
|
"port",
|
||||||
|
"https",
|
||||||
|
"prefer_grpc",
|
||||||
|
"grpc_port",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check and set field attributes
|
||||||
|
if field.name == "texts":
|
||||||
|
field.name = "documents"
|
||||||
|
field.field_type = "TextSplitter"
|
||||||
|
field.display_name = "Text Splitter"
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
elif "embedding" in field.name:
|
||||||
|
# for backwards compatibility
|
||||||
|
field.name = "embedding"
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.display_name = "Embedding"
|
||||||
|
field.field_type = "Embeddings"
|
||||||
|
|
||||||
|
elif field.name in basic_fields:
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
if field.name == "api_key":
|
||||||
|
field.display_name = "API Key"
|
||||||
|
field.password = True
|
||||||
|
elif field.name == "location":
|
||||||
|
field.value = ":memory:"
|
||||||
|
field.placeholder = ":memory:"
|
||||||
|
|
||||||
|
elif field.name in advanced_fields:
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
if "key" in field.name:
|
||||||
|
field.password = False
|
||||||
|
# TODO: Weaviate requires weaviate_url to be passed as it is not part of
|
||||||
|
# the class or from_texts method. We need the add_extra_fields to fix this
|
||||||
|
|
@ -1,630 +1 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from langchain.agents import loading
|
|
||||||
from langchain.agents.mrkl import prompt
|
|
||||||
|
|
||||||
from langflow.template.base import FrontendNode, Template, TemplateField
|
|
||||||
from langflow.template.constants import DEFAULT_PROMPT, HUMAN_PROMPT, SYSTEM_PROMPT
|
|
||||||
from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
|
|
||||||
|
|
||||||
NON_CHAT_AGENTS = {
|
|
||||||
agent_type: agent_class
|
|
||||||
for agent_type, agent_class in loading.AGENT_TO_CLASS.items()
|
|
||||||
if "chat" not in agent_type.value
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class BasePromptFrontendNode(FrontendNode):
|
|
||||||
name: str
|
|
||||||
template: Template
|
|
||||||
description: str
|
|
||||||
base_classes: list[str]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class ZeroShotPromptNode(BasePromptFrontendNode):
|
|
||||||
name: str = "ZeroShotPrompt"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="zero_shot",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=False,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=True,
|
|
||||||
value=prompt.PREFIX,
|
|
||||||
name="prefix",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=True,
|
|
||||||
value=prompt.SUFFIX,
|
|
||||||
name="suffix",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=False,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=True,
|
|
||||||
value=prompt.FORMAT_INSTRUCTIONS,
|
|
||||||
name="format_instructions",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = "Prompt template for Zero Shot Agent."
|
|
||||||
base_classes: list[str] = ["BasePromptTemplate"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class PromptTemplateNode(FrontendNode):
|
|
||||||
name: str = "PromptTemplate"
|
|
||||||
template: Template
|
|
||||||
description: str
|
|
||||||
base_classes: list[str] = ["BasePromptTemplate"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
FrontendNode.format_field(field, name)
|
|
||||||
if field.name == "examples":
|
|
||||||
field.advanced = False
|
|
||||||
|
|
||||||
|
|
||||||
class PythonFunctionNode(FrontendNode):
|
|
||||||
name: str = "PythonFunction"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="python_function",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="code",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
value=DEFAULT_PYTHON_FUNCTION,
|
|
||||||
name="code",
|
|
||||||
advanced=False,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = "Python function to be executed."
|
|
||||||
base_classes: list[str] = ["function"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class MidJourneyPromptChainNode(FrontendNode):
|
|
||||||
name: str = "MidJourneyPromptChain"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="MidJourneyPromptChain",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
advanced=False,
|
|
||||||
multiline=False,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseChatMemory",
|
|
||||||
required=False,
|
|
||||||
show=True,
|
|
||||||
name="memory",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."
|
|
||||||
base_classes: list[str] = [
|
|
||||||
"LLMChain",
|
|
||||||
"BaseCustomChain",
|
|
||||||
"Chain",
|
|
||||||
"ConversationChain",
|
|
||||||
"MidJourneyPromptChain",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TimeTravelGuideChainNode(FrontendNode):
|
|
||||||
name: str = "TimeTravelGuideChain"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="TimeTravelGuideChain",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
advanced=False,
|
|
||||||
multiline=False,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseChatMemory",
|
|
||||||
required=False,
|
|
||||||
show=True,
|
|
||||||
name="memory",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = "Time travel guide chain to be used in the flow."
|
|
||||||
base_classes: list[str] = [
|
|
||||||
"LLMChain",
|
|
||||||
"BaseCustomChain",
|
|
||||||
"TimeTravelGuideChain",
|
|
||||||
"Chain",
|
|
||||||
"ConversationChain",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterChainNode(FrontendNode):
|
|
||||||
name: str = "SeriesCharacterChain"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="SeriesCharacterChain",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
advanced=False,
|
|
||||||
multiline=False,
|
|
||||||
name="character",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
advanced=False,
|
|
||||||
multiline=False,
|
|
||||||
name="series",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
advanced=False,
|
|
||||||
multiline=False,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = "SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa
|
|
||||||
base_classes: list[str] = [
|
|
||||||
"LLMChain",
|
|
||||||
"BaseCustomChain",
|
|
||||||
"Chain",
|
|
||||||
"ConversationChain",
|
|
||||||
"SeriesCharacterChain",
|
|
||||||
"function",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ToolNode(FrontendNode):
|
|
||||||
name: str = "Tool"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="Tool",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=True,
|
|
||||||
value="",
|
|
||||||
name="name",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=True,
|
|
||||||
value="",
|
|
||||||
name="description",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
name="func",
|
|
||||||
field_type="function",
|
|
||||||
required=True,
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=True,
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="bool",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=False,
|
|
||||||
value=False,
|
|
||||||
name="return_direct",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = "Tool to be used in the flow."
|
|
||||||
base_classes: list[str] = ["Tool"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class JsonAgentNode(FrontendNode):
|
|
||||||
name: str = "JsonAgent"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="json_agent",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseToolkit",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="toolkit",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """Construct a json agent from an LLM and tools."""
|
|
||||||
base_classes: list[str] = ["AgentExecutor"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class InitializeAgentNode(FrontendNode):
|
|
||||||
name: str = "initialize_agent"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="initailize_agent",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
is_list=True,
|
|
||||||
show=True,
|
|
||||||
multiline=False,
|
|
||||||
options=list(NON_CHAT_AGENTS.keys()),
|
|
||||||
value=list(NON_CHAT_AGENTS.keys())[0],
|
|
||||||
name="agent",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseChatMemory",
|
|
||||||
required=False,
|
|
||||||
show=True,
|
|
||||||
name="memory",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="Tool",
|
|
||||||
required=False,
|
|
||||||
show=True,
|
|
||||||
name="tools",
|
|
||||||
is_list=True,
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
advanced=False,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """Construct a json agent from an LLM and tools."""
|
|
||||||
base_classes: list[str] = ["AgentExecutor", "function"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
# do nothing and don't return anything
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CSVAgentNode(FrontendNode):
|
|
||||||
name: str = "CSVAgent"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="csv_agent",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="file",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="path",
|
|
||||||
value="",
|
|
||||||
suffixes=[".csv"],
|
|
||||||
fileTypes=["csv"],
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """Construct a json agent from a CSV and tools."""
|
|
||||||
base_classes: list[str] = ["AgentExecutor"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class SQLDatabaseNode(FrontendNode):
|
|
||||||
name: str = "SQLDatabase"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="sql_database",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=False,
|
|
||||||
value="",
|
|
||||||
name="uri",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """SQLAlchemy wrapper around a database."""
|
|
||||||
base_classes: list[str] = ["SQLDatabase"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class VectorStoreAgentNode(FrontendNode):
|
|
||||||
name: str = "VectorStoreAgent"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="vectorstore_agent",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="VectorStoreInfo",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="vectorstoreinfo",
|
|
||||||
display_name="Vector Store Info",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """Construct an agent from a Vector Store."""
|
|
||||||
base_classes: list[str] = ["AgentExecutor"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class VectorStoreRouterAgentNode(FrontendNode):
|
|
||||||
name: str = "VectorStoreRouterAgent"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="vectorstorerouter_agent",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="VectorStoreRouterToolkit",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="vectorstoreroutertoolkit",
|
|
||||||
display_name="Vector Store Router Toolkit",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """Construct an agent from a Vector Store Router."""
|
|
||||||
base_classes: list[str] = ["AgentExecutor"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAgentNode(FrontendNode):
|
|
||||||
name: str = "SQLAgent"
|
|
||||||
template: Template = Template(
|
|
||||||
type_name="sql_agent",
|
|
||||||
fields=[
|
|
||||||
TemplateField(
|
|
||||||
field_type="str",
|
|
||||||
required=True,
|
|
||||||
placeholder="",
|
|
||||||
is_list=False,
|
|
||||||
show=True,
|
|
||||||
multiline=False,
|
|
||||||
value="",
|
|
||||||
name="database_uri",
|
|
||||||
),
|
|
||||||
TemplateField(
|
|
||||||
field_type="BaseLanguageModel",
|
|
||||||
required=True,
|
|
||||||
show=True,
|
|
||||||
name="llm",
|
|
||||||
display_name="LLM",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
description: str = """Construct an agent from a Vector Store Router."""
|
|
||||||
base_classes: list[str] = ["AgentExecutor"]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return super().to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class PromptFrontendNode(FrontendNode):
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
# if field.field_type == "StringPromptTemplate"
|
|
||||||
# change it to str
|
|
||||||
PROMPT_FIELDS = [
|
|
||||||
"template",
|
|
||||||
"suffix",
|
|
||||||
"prefix",
|
|
||||||
"examples",
|
|
||||||
"format_instructions",
|
|
||||||
]
|
|
||||||
if field.field_type == "StringPromptTemplate" and "Message" in str(name):
|
|
||||||
field.field_type = "prompt"
|
|
||||||
field.multiline = True
|
|
||||||
field.value = HUMAN_PROMPT if "Human" in field.name else SYSTEM_PROMPT
|
|
||||||
if field.name == "template" and field.value == "":
|
|
||||||
field.value = DEFAULT_PROMPT
|
|
||||||
|
|
||||||
if field.name in PROMPT_FIELDS:
|
|
||||||
field.field_type = "prompt"
|
|
||||||
field.advanced = False
|
|
||||||
|
|
||||||
if (
|
|
||||||
"Union" in field.field_type
|
|
||||||
and "BaseMessagePromptTemplate" in field.field_type
|
|
||||||
):
|
|
||||||
field.field_type = "BaseMessagePromptTemplate"
|
|
||||||
|
|
||||||
# All prompt fields should be password=False
|
|
||||||
field.password = False
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryFrontendNode(FrontendNode):
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
FrontendNode.format_field(field, name)
|
|
||||||
|
|
||||||
if not isinstance(field.value, str):
|
|
||||||
field.value = None
|
|
||||||
if field.name == "k":
|
|
||||||
field.required = True
|
|
||||||
field.show = True
|
|
||||||
field.field_type = "int"
|
|
||||||
field.value = 10
|
|
||||||
field.display_name = "Memory Size"
|
|
||||||
field.password = False
|
|
||||||
|
|
||||||
|
|
||||||
class ChainFrontendNode(FrontendNode):
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
FrontendNode.format_field(field, name)
|
|
||||||
|
|
||||||
field.advanced = False
|
|
||||||
if "key" in field.name:
|
|
||||||
field.password = False
|
|
||||||
field.show = False
|
|
||||||
if field.name in ["input_key", "output_key"]:
|
|
||||||
field.required = True
|
|
||||||
field.show = True
|
|
||||||
field.advanced = True
|
|
||||||
|
|
||||||
# Separated for possible future changes
|
|
||||||
if field.name == "prompt" and field.value is None:
|
|
||||||
# if no prompt is provided, use the default prompt
|
|
||||||
field.required = False
|
|
||||||
field.show = True
|
|
||||||
field.advanced = False
|
|
||||||
if field.name == "memory":
|
|
||||||
field.required = False
|
|
||||||
field.show = True
|
|
||||||
field.advanced = False
|
|
||||||
if field.name == "verbose":
|
|
||||||
field.required = False
|
|
||||||
field.show = True
|
|
||||||
field.advanced = True
|
|
||||||
if field.name == "llm":
|
|
||||||
field.required = True
|
|
||||||
field.show = True
|
|
||||||
field.advanced = False
|
|
||||||
|
|
||||||
|
|
||||||
class LLMFrontendNode(FrontendNode):
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
display_names_dict = {
|
|
||||||
"huggingfacehub_api_token": "HuggingFace Hub API Token",
|
|
||||||
}
|
|
||||||
FrontendNode.format_field(field, name)
|
|
||||||
SHOW_FIELDS = ["repo_id"]
|
|
||||||
if field.name in SHOW_FIELDS:
|
|
||||||
field.show = True
|
|
||||||
|
|
||||||
if "api" in field.name and ("key" in field.name or "token" in field.name):
|
|
||||||
field.password = True
|
|
||||||
field.show = True
|
|
||||||
# Required should be False to support
|
|
||||||
# loading the API key from environment variables
|
|
||||||
field.required = False
|
|
||||||
field.advanced = False
|
|
||||||
|
|
||||||
if field.name == "task":
|
|
||||||
field.required = True
|
|
||||||
field.show = True
|
|
||||||
field.is_list = True
|
|
||||||
field.options = ["text-generation", "text2text-generation"]
|
|
||||||
field.advanced = True
|
|
||||||
|
|
||||||
if display_name := display_names_dict.get(field.name):
|
|
||||||
field.display_name = display_name
|
|
||||||
if field.name == "model_kwargs":
|
|
||||||
field.field_type = "code"
|
|
||||||
field.advanced = True
|
|
||||||
field.show = True
|
|
||||||
elif field.name in ["model_name", "temperature"]:
|
|
||||||
field.advanced = False
|
|
||||||
field.show = True
|
|
||||||
|
|
||||||
|
|
||||||
class EmbeddingFrontendNode(FrontendNode):
|
|
||||||
@staticmethod
|
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
|
||||||
FrontendNode.format_field(field, name)
|
|
||||||
if field.name == "headers":
|
|
||||||
field.show = False
|
|
||||||
|
|
|
||||||
0
src/backend/langflow/template/template/__init__.py
Normal file
0
src/backend/langflow/template/template/__init__.py
Normal file
25
src/backend/langflow/template/template/base.py
Normal file
25
src/backend/langflow/template/template/base.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
from typing import Callable, Optional, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
|
||||||
|
|
||||||
|
class Template(BaseModel):
|
||||||
|
type_name: str
|
||||||
|
fields: list[TemplateField]
|
||||||
|
|
||||||
|
def process_fields(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
format_field_func: Union[Callable, None] = None,
|
||||||
|
):
|
||||||
|
if format_field_func:
|
||||||
|
for field in self.fields:
|
||||||
|
format_field_func(field, name)
|
||||||
|
|
||||||
|
def to_dict(self, format_field_func=None):
|
||||||
|
self.process_fields(self.type_name, format_field_func)
|
||||||
|
result = {field.name: field.to_dict() for field in self.fields}
|
||||||
|
result["_type"] = self.type_name # type: ignore
|
||||||
|
return result
|
||||||
|
|
@ -10,49 +10,6 @@ from langflow.template.constants import FORCE_SHOW_FIELDS
|
||||||
from langflow.utils import constants
|
from langflow.utils import constants
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_parameters(
|
|
||||||
name: str, type_to_loader_dict: Dict, add_function: bool = False
|
|
||||||
):
|
|
||||||
# Retrieve the function that matches the provided name
|
|
||||||
func = None
|
|
||||||
for _, v in type_to_loader_dict.items():
|
|
||||||
if v.__name__ == name:
|
|
||||||
func = v
|
|
||||||
break
|
|
||||||
|
|
||||||
if func is None:
|
|
||||||
raise ValueError(f"{name} not found")
|
|
||||||
|
|
||||||
# Process parameters
|
|
||||||
parameters = func.__annotations__
|
|
||||||
variables = {}
|
|
||||||
for param_name, param_type in parameters.items():
|
|
||||||
if param_name in ["return", "kwargs"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
variables[param_name] = {
|
|
||||||
"type": param_type.__name__,
|
|
||||||
"default": parameters[param_name].__repr_args__()[0][1],
|
|
||||||
# Op
|
|
||||||
"placeholder": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get the base classes of the return type
|
|
||||||
return_type = parameters.get("return")
|
|
||||||
base_classes = get_base_classes(return_type) if return_type else []
|
|
||||||
if add_function:
|
|
||||||
base_classes.append("function")
|
|
||||||
|
|
||||||
# Get the function's docstring
|
|
||||||
docs = inspect.getdoc(func) or ""
|
|
||||||
|
|
||||||
return {
|
|
||||||
"template": format_dict(variables, name),
|
|
||||||
"description": docs["Description"], # type: ignore
|
|
||||||
"base_classes": base_classes,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_function(
|
def build_template_from_function(
|
||||||
name: str, type_to_loader_dict: Dict, add_function: bool = False
|
name: str, type_to_loader_dict: Dict, add_function: bool = False
|
||||||
):
|
):
|
||||||
|
|
@ -160,6 +117,70 @@ def build_template_from_class(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_template_from_method(
|
||||||
|
class_name: str,
|
||||||
|
method_name: str,
|
||||||
|
type_to_cls_dict: Dict,
|
||||||
|
add_function: bool = False,
|
||||||
|
):
|
||||||
|
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
||||||
|
|
||||||
|
# Raise error if class_name is not in classes
|
||||||
|
if class_name not in classes:
|
||||||
|
raise ValueError(f"{class_name} not found.")
|
||||||
|
|
||||||
|
for _type, v in type_to_cls_dict.items():
|
||||||
|
if v.__name__ == class_name:
|
||||||
|
_class = v
|
||||||
|
|
||||||
|
# Check if the method exists in this class
|
||||||
|
if not hasattr(_class, method_name):
|
||||||
|
raise ValueError(
|
||||||
|
f"Method {method_name} not found in class {class_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the method
|
||||||
|
method = getattr(_class, method_name)
|
||||||
|
|
||||||
|
# Get the docstring
|
||||||
|
docs = parse(method.__doc__)
|
||||||
|
|
||||||
|
# Get the signature of the method
|
||||||
|
sig = inspect.signature(method)
|
||||||
|
|
||||||
|
# Get the parameters of the method
|
||||||
|
params = sig.parameters
|
||||||
|
|
||||||
|
# Initialize the variables dictionary with method parameters
|
||||||
|
variables = {
|
||||||
|
"_type": _type,
|
||||||
|
**{
|
||||||
|
name: {
|
||||||
|
"default": param.default
|
||||||
|
if param.default != param.empty
|
||||||
|
else None,
|
||||||
|
"type": param.annotation
|
||||||
|
if param.annotation != param.empty
|
||||||
|
else None,
|
||||||
|
"required": param.default == param.empty,
|
||||||
|
}
|
||||||
|
for name, param in params.items()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
base_classes = get_base_classes(_class)
|
||||||
|
|
||||||
|
# Adding function to base classes to allow the output to be a function
|
||||||
|
if add_function:
|
||||||
|
base_classes.append("function")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"template": format_dict(variables, class_name),
|
||||||
|
"description": docs.short_description or "",
|
||||||
|
"base_classes": base_classes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_base_classes(cls):
|
def get_base_classes(cls):
|
||||||
"""Get the base classes of a class.
|
"""Get the base classes of a class.
|
||||||
These are used to determine the output of the nodes.
|
These are used to determine the output of the nodes.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@
|
||||||
"dev:docker": "vite --host 0.0.0.0",
|
"dev:docker": "vite --host 0.0.0.0",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview"
|
"serve": "vite preview",
|
||||||
|
"format": "npx prettier --write \"src/**/*.{js,jsx,ts,tsx,json,md}\""
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ export default function GenericNode({
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
let jsonResponse = await response.json();
|
let jsonResponse = await response.json();
|
||||||
let jsonResponseParsed = await JSON.parse(jsonResponse);
|
let jsonResponseParsed = await JSON.parse(jsonResponse);
|
||||||
console.log(jsonResponseParsed);
|
|
||||||
setValidationStatus(jsonResponseParsed);
|
setValidationStatus(jsonResponseParsed);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -86,6 +85,7 @@ export default function GenericNode({
|
||||||
deleteNode(data.id);
|
deleteNode(data.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,10 @@ export default function SingleAlert({
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon className="h-5 w-5 text-red-400 dark:text-red-50" aria-hidden="true" />
|
<XCircleIcon
|
||||||
|
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm break-words font-medium text-red-800 dark:text-white/80">
|
<h3 className="text-sm break-words font-medium text-red-800 dark:text-white/80">
|
||||||
|
|
@ -44,7 +47,9 @@ export default function SingleAlert({
|
||||||
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
||||||
<ul className="list-disc space-y-1 pl-5">
|
<ul className="list-disc space-y-1 pl-5">
|
||||||
{dropItem.list.map((item, idx) => (
|
{dropItem.list.map((item, idx) => (
|
||||||
<li className="break-words" key={idx}>{item}</li>
|
<li className="break-words" key={idx}>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -82,7 +87,9 @@ export default function SingleAlert({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p className="text-sm text-blue-700 dark:text-white/80">{dropItem.title}</p>
|
<p className="text-sm text-blue-700 dark:text-white/80">
|
||||||
|
{dropItem.title}
|
||||||
|
</p>
|
||||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||||
{dropItem.link ? (
|
{dropItem.link ? (
|
||||||
<Link
|
<Link
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,15 @@ export default function ErrorAlert({
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon className="h-5 w-5 text-red-400 dark:text-red-50" aria-hidden="true" />
|
<XCircleIcon
|
||||||
|
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-red-800 dark:text-white/80">{title}</h3>
|
<h3 className="text-sm font-medium text-red-800 dark:text-white/80">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
{list.length !== 0 ? (
|
{list.length !== 0 ? (
|
||||||
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
||||||
<ul className="list-disc space-y-1 pl-5">
|
<ul className="list-disc space-y-1 pl-5">
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ export default function SuccessAlert({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<p className="text-sm font-medium text-green-800 dark:text-white/80">{title}</p>
|
<p className="text-sm font-medium text-green-800 dark:text-white/80">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ export default function FloatComponent({
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
const {setDisableCopyPaste} = useContext(TabsContext)
|
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||||
<input
|
<input
|
||||||
|
|
@ -29,12 +28,6 @@ export default function FloatComponent({
|
||||||
setMyValue(e.target.value);
|
setMyValue(e.target.value);
|
||||||
onChange(e.target.value);
|
onChange(e.target.value);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false)
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ export default function InputComponent({
|
||||||
}: InputComponentType) {
|
}: InputComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value ?? "");
|
const [myValue, setMyValue] = useState(value ?? "");
|
||||||
const [pwdVisible, setPwdVisible] = useState(false);
|
const [pwdVisible, setPwdVisible] = useState(false);
|
||||||
const {setDisableCopyPaste} = useContext(TabsContext)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
|
|
@ -28,12 +27,6 @@ export default function InputComponent({
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
value={myValue}
|
value={myValue}
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false)
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"block w-full pr-12 form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
"block w-full pr-12 form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
||||||
disabled ? " bg-gray-200 dark:bg-gray-700" : "",
|
disabled ? " bg-gray-200 dark:bg-gray-700" : "",
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ export default function InputListComponent({
|
||||||
onChange([""]);
|
onChange([""]);
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
const {setDisableCopyPaste} = useContext(TabsContext)
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
|
|
@ -43,12 +42,6 @@ export default function InputListComponent({
|
||||||
});
|
});
|
||||||
onChange(inputList);
|
onChange(inputList);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false)
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{idx === inputList.length - 1 ? (
|
{idx === inputList.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ export default function IntComponent({
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
const {setDisableCopyPaste} =useContext(TabsContext)
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
|
|
@ -45,14 +44,7 @@ export default function IntComponent({
|
||||||
setMyValue(e.target.value);
|
setMyValue(e.target.value);
|
||||||
onChange(e.target.value);
|
onChange(e.target.value);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false)
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import CodeAreaModal from "../../modals/codeAreaModal";
|
import CodeAreaModal from "../../modals/codeAreaModal";
|
||||||
import TextAreaModal from "../../modals/textAreaModal";
|
import TextAreaModal from "../../modals/textAreaModal";
|
||||||
import { TextAreaComponentType } from "../../types/components";
|
import { TextAreaComponentType } from "../../types/components";
|
||||||
import PromptAreaModal from "../../modals/promptModal";
|
import GenericModal from "../../modals/genericModal";
|
||||||
|
|
||||||
export default function PromptAreaComponent({
|
export default function PromptAreaComponent({
|
||||||
value,
|
value,
|
||||||
|
|
@ -29,8 +29,10 @@ export default function PromptAreaComponent({
|
||||||
<span
|
<span
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openPopUp(
|
openPopUp(
|
||||||
<PromptAreaModal
|
<GenericModal
|
||||||
value={myValue}
|
value={myValue}
|
||||||
|
buttonText="Check & Save"
|
||||||
|
modalTitle="Edit Prompt"
|
||||||
setValue={(t: string) => {
|
setValue={(t: string) => {
|
||||||
setMyValue(t);
|
setMyValue(t);
|
||||||
onChange(t);
|
onChange(t);
|
||||||
|
|
@ -48,8 +50,10 @@ export default function PromptAreaComponent({
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openPopUp(
|
openPopUp(
|
||||||
<PromptAreaModal
|
<GenericModal
|
||||||
value={myValue}
|
value={myValue}
|
||||||
|
buttonText="Check & Save"
|
||||||
|
modalTitle="Edit Prompt"
|
||||||
setValue={(t: string) => {
|
setValue={(t: string) => {
|
||||||
setMyValue(t);
|
setMyValue(t);
|
||||||
onChange(t);
|
onChange(t);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import TextAreaModal from "../../modals/textAreaModal";
|
|
||||||
import { TextAreaComponentType } from "../../types/components";
|
import { TextAreaComponentType } from "../../types/components";
|
||||||
|
import GenericModal from "../../modals/genericModal";
|
||||||
|
|
||||||
export default function TextAreaComponent({ value, onChange, disabled }:TextAreaComponentType) {
|
export default function TextAreaComponent({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled,
|
||||||
|
}: TextAreaComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { openPopUp } = useContext(PopUpContext);
|
const { openPopUp } = useContext(PopUpContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -16,15 +20,42 @@ export default function TextAreaComponent({ value, onChange, disabled }:TextArea
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||||
<div className="w-full flex items-center gap-3">
|
<div className="w-full flex items-center gap-3">
|
||||||
<span onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}
|
<span
|
||||||
|
onClick={() => {
|
||||||
|
openPopUp(
|
||||||
|
<GenericModal
|
||||||
|
buttonText="Finishing Editing"
|
||||||
|
modalTitle="Edit Text"
|
||||||
|
value={myValue}
|
||||||
|
setValue={(t: string) => {
|
||||||
|
setMyValue(t);
|
||||||
|
onChange(t);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
className={
|
className={
|
||||||
"truncate block w-full text-gray-500 dark:text-gray-100 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
"truncate block w-full text-gray-500 dark:text-gray-100 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||||
(disabled ? " bg-gray-200" : "")
|
(disabled ? " bg-gray-200" : "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{myValue !== "" ? myValue : 'Text empty'}
|
{myValue !== "" ? myValue : "Text empty"}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
openPopUp(
|
||||||
|
<GenericModal
|
||||||
|
buttonText="Finishing Editing"
|
||||||
|
modalTitle="Edit Text"
|
||||||
|
value={myValue}
|
||||||
|
setValue={(t: string) => {
|
||||||
|
setMyValue(t);
|
||||||
|
onChange(t);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600 dark:text-gray-300" />
|
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600 dark:text-gray-300" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ export default function ToggleComponent({
|
||||||
setEnabled(x);
|
setEnabled(x);
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled ? 'bg-indigo-600' : 'bg-gray-200',
|
enabled ? "bg-indigo-600" : "bg-gray-200",
|
||||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2'
|
"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Use setting</span>
|
<span className="sr-only">Use setting</span>
|
||||||
|
|
@ -43,8 +43,7 @@ export default function ToggleComponent({
|
||||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
></span>
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled
|
enabled
|
||||||
|
|
@ -53,8 +52,7 @@ export default function ToggleComponent({
|
||||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
></span>
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { typesContext } from "./typesContext";
|
||||||
import { APITemplateType, TemplateVariableType } from "../types/api";
|
import { APITemplateType, TemplateVariableType } from "../types/api";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { addEdge } from "reactflow";
|
import { addEdge } from "reactflow";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const TabsContextInitialValue: TabsContextType = {
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
save: () => {},
|
save: () => {},
|
||||||
|
|
@ -30,7 +31,10 @@ const TabsContextInitialValue: TabsContextType = {
|
||||||
disableCopyPaste: false,
|
disableCopyPaste: false,
|
||||||
setDisableCopyPaste: (state: boolean) => {},
|
setDisableCopyPaste: (state: boolean) => {},
|
||||||
getNodeId: () => "",
|
getNodeId: () => "",
|
||||||
paste: (selection: {nodes: any, edges: any}, position: {x: number, y: number}) => {},
|
paste: (
|
||||||
|
selection: { nodes: any; edges: any },
|
||||||
|
position: { x: number; y: number }
|
||||||
|
) => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TabsContext = createContext<TabsContextType>(
|
export const TabsContext = createContext<TabsContextType>(
|
||||||
|
|
@ -50,30 +54,30 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
return newNodeId.current;
|
return newNodeId.current;
|
||||||
}
|
}
|
||||||
function save() {
|
function save() {
|
||||||
let Saveflows = [...flows];
|
// added clone deep to avoid mutating the original object
|
||||||
if (Saveflows.length !== 0)
|
let Saveflows = _.cloneDeep(flows);
|
||||||
|
if (Saveflows.length !== 0) {
|
||||||
Saveflows.forEach((flow) => {
|
Saveflows.forEach((flow) => {
|
||||||
if(flow.data && flow.data?.nodes) flow.data?.nodes.forEach((node) => {
|
if (flow.data && flow.data?.nodes)
|
||||||
console.log(node.data.type)
|
flow.data?.nodes.forEach((node) => {
|
||||||
|
console.log(node.data.type);
|
||||||
|
//looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
|
||||||
Object.keys(node.data.node.template).forEach((key) => {
|
Object.keys(node.data.node.template).forEach((key) => {
|
||||||
console.log(node.data.node.template[key].type)
|
console.log(node.data.node.template[key].type);
|
||||||
if (node.data.node.template[key].type === "file") {
|
if (node.data.node.template[key].type === "file") {
|
||||||
console.log(node.data.node.template[key])
|
console.log(node.data.node.template[key]);
|
||||||
node.data.node.template[key].content = "";
|
node.data.node.template[key].content = null;
|
||||||
|
node.data.node.template[key].value = "";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"tabsData",
|
"tabsData",
|
||||||
JSON.stringify({ tabIndex, flows: Saveflows, id })
|
JSON.stringify({ tabIndex, flows: Saveflows, id })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
}
|
||||||
//save tabs locally
|
|
||||||
// console.log(id)
|
|
||||||
save();
|
|
||||||
}, [flows, id, tabIndex, newNodeId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//get tabs locally saved
|
//get tabs locally saved
|
||||||
|
|
@ -99,6 +103,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
}, [templates]);
|
}, [templates]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//save tabs locally
|
||||||
|
console.log(id);
|
||||||
|
save();
|
||||||
|
}, [flows, id, tabIndex, newNodeId]);
|
||||||
|
|
||||||
function hardReset() {
|
function hardReset() {
|
||||||
newNodeId.current = uuidv4();
|
newNodeId.current = uuidv4();
|
||||||
setTabIndex(0);
|
setTabIndex(0);
|
||||||
|
|
@ -186,8 +196,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function paste(selectionInstance, position) {
|
function paste(selectionInstance, position) {
|
||||||
console.log(position);
|
|
||||||
console.log(selectionInstance)
|
|
||||||
let minimumX = Infinity;
|
let minimumX = Infinity;
|
||||||
let minimumY = Infinity;
|
let minimumY = Infinity;
|
||||||
let idsMap = {};
|
let idsMap = {};
|
||||||
|
|
@ -226,8 +234,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
// Add the new node to the list of nodes in state
|
// Add the new node to the list of nodes in state
|
||||||
nodes = nodes
|
nodes = nodes
|
||||||
.map((e) => ({ ...e, selected: false }))
|
.map((e) => ({ ...e, selected: false }))
|
||||||
.concat({ ...newNode, selected: false })
|
.concat({ ...newNode, selected: false });
|
||||||
console.log(nodes);
|
|
||||||
});
|
});
|
||||||
reactFlowInstance.setNodes(nodes);
|
reactFlowInstance.setNodes(nodes);
|
||||||
|
|
||||||
|
|
@ -263,10 +270,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
},
|
},
|
||||||
edges.map((e) => ({ ...e, selected: false }))
|
edges.map((e) => ({ ...e, selected: false }))
|
||||||
);
|
);
|
||||||
console.log(edges);
|
|
||||||
});
|
});
|
||||||
reactFlowInstance.setEdges(edges);
|
reactFlowInstance.setEdges(edges);
|
||||||
};
|
}
|
||||||
|
|
||||||
function addFlow(flow?: FlowType) {
|
function addFlow(flow?: FlowType) {
|
||||||
// Get data from the flow or set it to null if there's no flow provided.
|
// Get data from the flow or set it to null if there's no flow provided.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import reportWebVitals from "./reportWebVitals";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import ContextWrapper from "./contexts";
|
import ContextWrapper from "./contexts";
|
||||||
|
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById("root") as HTMLElement
|
document.getElementById("root") as HTMLElement
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { IconCheck, IconClipboard, IconDownload } from '@tabler/icons-react';
|
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
|
||||||
import { XMarkIcon, CommandLineIcon, CodeBracketSquareIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
XMarkIcon,
|
||||||
|
CommandLineIcon,
|
||||||
|
CodeBracketSquareIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { Fragment, useContext, useRef, useState } from "react";
|
import { Fragment, useContext, useRef, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import "ace-builds/src-noconflict/mode-python";
|
import "ace-builds/src-noconflict/mode-python";
|
||||||
|
|
@ -9,10 +13,8 @@ import "ace-builds/src-noconflict/theme-twilight";
|
||||||
import "ace-builds/src-noconflict/ext-language_tools";
|
import "ace-builds/src-noconflict/ext-language_tools";
|
||||||
// import "ace-builds/webpack-resolver";
|
// import "ace-builds/webpack-resolver";
|
||||||
import { darkContext } from "../../contexts/darkContext";
|
import { darkContext } from "../../contexts/darkContext";
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function ApiModal({ flowName }) {
|
export default function ApiModal({ flowName }) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
|
@ -68,7 +70,8 @@ flow("Hey, have you heard of LangFlow?")`;
|
||||||
{
|
{
|
||||||
name: "Python API",
|
name: "Python API",
|
||||||
mode: "python",
|
mode: "python",
|
||||||
image: "https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
image:
|
||||||
|
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||||
code: pythonApiCode,
|
code: pythonApiCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -77,8 +80,7 @@ flow("Hey, have you heard of LangFlow?")`;
|
||||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||||
code: pythonCode,
|
code: pythonCode,
|
||||||
},
|
},
|
||||||
|
];
|
||||||
]
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|
@ -144,9 +146,17 @@ flow("Hey, have you heard of LangFlow?")`;
|
||||||
<div className="flex flex-col h-full w-full ">
|
<div className="flex flex-col h-full w-full ">
|
||||||
<div className="flex px-5 z-10">
|
<div className="flex px-5 z-10">
|
||||||
{tabs.map((tab, index) => (
|
{tabs.map((tab, index) => (
|
||||||
<button onClick={() => {
|
<button
|
||||||
|
onClick={() => {
|
||||||
setActiveTab(index);
|
setActiveTab(index);
|
||||||
}} className={"p-2 rounded-t-lg w-44 border border-b-0 border-gray-300 dark:border-gray-700 dark:text-gray-300 -mr-px flex justify-center items-center gap-4 " + (activeTab === index ? " bg-white dark:bg-gray-800" : "bg-gray-100 dark:bg-gray-900")}>
|
}}
|
||||||
|
className={
|
||||||
|
"p-2 rounded-t-lg w-44 border border-b-0 border-gray-300 dark:border-gray-700 dark:text-gray-300 -mr-px flex justify-center items-center gap-4 " +
|
||||||
|
(activeTab === index
|
||||||
|
? " bg-white dark:bg-gray-800"
|
||||||
|
: "bg-gray-100 dark:bg-gray-900")
|
||||||
|
}
|
||||||
|
>
|
||||||
{tab.name}
|
{tab.name}
|
||||||
<img src={tab.image} className="w-6" />
|
<img src={tab.image} className="w-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -161,8 +171,12 @@ flow("Hey, have you heard of LangFlow?")`;
|
||||||
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
|
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
>
|
>
|
||||||
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
|
{isCopied ? (
|
||||||
{isCopied ? 'Copied!' : 'Copy code'}
|
<IconCheck size={18} />
|
||||||
|
) : (
|
||||||
|
<IconClipboard size={18} />
|
||||||
|
)}
|
||||||
|
{isCopied ? "Copied!" : "Copy code"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ export default function ChatInput({
|
||||||
}
|
}
|
||||||
}, [chatValue]);
|
}, [chatValue]);
|
||||||
|
|
||||||
const { setDisableCopyPaste } = useContext(TabsContext);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -27,12 +25,6 @@ export default function ChatInput({
|
||||||
sendMessage();
|
sendMessage();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false)
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
rows={1}
|
rows={1}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
disabled={lockChat}
|
disabled={lockChat}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from "react";
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
import { classNames } from "../../../utils";
|
import { classNames } from "../../../utils";
|
||||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||||
import AiIconStill from "../../../assets/froze-flow.png"
|
import AiIconStill from "../../../assets/froze-flow.png";
|
||||||
import { UserIcon } from "@heroicons/react/24/solid";
|
import { UserIcon } from "@heroicons/react/24/solid";
|
||||||
import FileCard from "../fileComponent";
|
import FileCard from "../fileComponent";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
|
|
@ -13,7 +13,13 @@ import remarkMath from "remark-math";
|
||||||
import { CodeBlock } from "./codeBlock";
|
import { CodeBlock } from "./codeBlock";
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
|
|
||||||
export default function ChatMessage({ chat, lockChat }: { chat: ChatMessageType, lockChat: boolean }) {
|
export default function ChatMessage({
|
||||||
|
chat,
|
||||||
|
lockChat,
|
||||||
|
}: {
|
||||||
|
chat: ChatMessageType;
|
||||||
|
lockChat: boolean;
|
||||||
|
}) {
|
||||||
const convert = new Convert({ newline: true });
|
const convert = new Convert({ newline: true });
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const imgRef = useRef(null);
|
const imgRef = useRef(null);
|
||||||
|
|
@ -35,11 +41,27 @@ export default function ChatMessage({ chat, lockChat }: { chat: ChatMessageType,
|
||||||
"rounded-full overflow-hidden w-8 h-8 flex items-center my-3 justify-center"
|
"rounded-full overflow-hidden w-8 h-8 flex items-center my-3 justify-center"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!chat.isSend && <div className="relative w-8 h-8">
|
{!chat.isSend && (
|
||||||
<img className={"absolute transition-opacity duration-500 scale-150 " + (lockChat ? "opacity-100" : "opacity-0")} src={AiIcon} />
|
<div className="relative w-8 h-8">
|
||||||
<img className={"absolute transition-opacity duration-500 scale-150 " + (lockChat ? "opacity-0" : "opacity-100")} src={AiIconStill} />
|
<img
|
||||||
</div>}
|
className={
|
||||||
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />}
|
"absolute transition-opacity duration-500 scale-150 " +
|
||||||
|
(lockChat ? "opacity-100" : "opacity-0")
|
||||||
|
}
|
||||||
|
src={AiIcon}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className={
|
||||||
|
"absolute transition-opacity duration-500 scale-150 " +
|
||||||
|
(lockChat ? "opacity-0" : "opacity-100")
|
||||||
|
}
|
||||||
|
src={AiIconStill}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{chat.isSend && (
|
||||||
|
<UserIcon className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!chat.isSend ? (
|
{!chat.isSend ? (
|
||||||
<div className="w-full text-start flex items-center">
|
<div className="w-full text-start flex items-center">
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { sendAllProps } from "../../types/api";
|
||||||
import { ChatMessageType, ChatType } from "../../types/chat";
|
import { ChatMessageType, ChatType } from "../../types/chat";
|
||||||
import ChatInput from "./chatInput";
|
import ChatInput from "./chatInput";
|
||||||
|
|
||||||
import _ from "lodash";
|
import _, { set } from "lodash";
|
||||||
|
|
||||||
export default function ChatModal({
|
export default function ChatModal({
|
||||||
flow,
|
flow,
|
||||||
|
|
@ -100,9 +100,9 @@ export default function ChatModal({
|
||||||
function handleOnClose(event: CloseEvent) {
|
function handleOnClose(event: CloseEvent) {
|
||||||
if (isOpen.current) {
|
if (isOpen.current) {
|
||||||
setErrorData({ title: event.reason });
|
setErrorData({ title: event.reason });
|
||||||
setLockChat(false);
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
connectWS();
|
connectWS();
|
||||||
|
setLockChat(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +183,6 @@ export default function ChatModal({
|
||||||
newWs.onopen = () => {
|
newWs.onopen = () => {
|
||||||
console.log("WebSocket connection established!");
|
console.log("WebSocket connection established!");
|
||||||
};
|
};
|
||||||
console.log(flow.id);
|
|
||||||
newWs.onmessage = (event) => {
|
newWs.onmessage = (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
console.log("Received data:", data);
|
console.log("Received data:", data);
|
||||||
|
|
@ -236,6 +235,16 @@ export default function ChatModal({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
ws.current.readyState === ws.current.CLOSED ||
|
||||||
|
ws.current.readyState === ws.current.CLOSING
|
||||||
|
) {
|
||||||
|
connectWS();
|
||||||
|
setLockChat(false);
|
||||||
|
}
|
||||||
|
}, [lockChat]);
|
||||||
|
|
||||||
async function sendAll(data: sendAllProps) {
|
async function sendAll(data: sendAllProps) {
|
||||||
try {
|
try {
|
||||||
if (ws) {
|
if (ws) {
|
||||||
|
|
@ -340,6 +349,7 @@ export default function ChatModal({
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
setChatHistory([]);
|
setChatHistory([]);
|
||||||
ws.current.send(JSON.stringify({ clear_history: true }));
|
ws.current.send(JSON.stringify({ clear_history: true }));
|
||||||
|
if (lockChat) setLockChat(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ export default function CodeAreaModal({
|
||||||
const [code, setCode] = useState(value);
|
const [code, setCode] = useState(value);
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
const { setDisableCopyPaste } = useContext(TabsContext);
|
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
|
|
@ -111,12 +110,6 @@ export default function CodeAreaModal({
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setCode(value);
|
setCode(value);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false)
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true)
|
|
||||||
}}
|
|
||||||
className="h-full w-full rounded-lg"
|
className="h-full w-full rounded-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -129,9 +122,7 @@ export default function CodeAreaModal({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
checkCode(code)
|
checkCode(code)
|
||||||
.then((apiReturn) => {
|
.then((apiReturn) => {
|
||||||
console.log(apiReturn);
|
|
||||||
if (apiReturn.data) {
|
if (apiReturn.data) {
|
||||||
console.log(apiReturn.data);
|
|
||||||
let importsErrors = apiReturn.data.imports.errors;
|
let importsErrors = apiReturn.data.imports.errors;
|
||||||
let funcErrors = apiReturn.data.function.errors;
|
let funcErrors = apiReturn.data.function.errors;
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function ExportModal() {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { flows, tabIndex, updateFlow, downloadFlow,setDisableCopyPaste } = useContext(TabsContext);
|
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
|
|
@ -113,12 +113,6 @@ export default function ExportModal() {
|
||||||
placeholder="File name"
|
placeholder="File name"
|
||||||
id="name"
|
id="name"
|
||||||
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100"
|
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100"
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false);
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
@ -133,12 +127,6 @@ export default function ExportModal() {
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false);
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true);
|
|
||||||
}}
|
|
||||||
name="description"
|
name="description"
|
||||||
id="description"
|
id="description"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
|
|
@ -163,12 +151,6 @@ export default function ExportModal() {
|
||||||
id="checkbox"
|
id="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="h-4 w-4 text-blue-600 border-gray-300 rounded dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
||||||
onBlur={() => {
|
|
||||||
setDisableCopyPaste(false);
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<span className="ml-2 font-medium text-gray-700 dark:text-white">
|
<span className="ml-2 font-medium text-gray-700 dark:text-white">
|
||||||
Save with my API keys
|
Save with my API keys
|
||||||
|
|
|
||||||
160
src/frontend/src/modals/genericModal/index.tsx
Normal file
160
src/frontend/src/modals/genericModal/index.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { XMarkIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { Fragment, useContext, useRef, useState } from "react";
|
||||||
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
import { darkContext } from "../../contexts/darkContext";
|
||||||
|
import { checkPrompt } from "../../controllers/API";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
export default function PromptAreaModal({
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
buttonText,
|
||||||
|
modalTitle,
|
||||||
|
}: {
|
||||||
|
setValue: (value: string) => void;
|
||||||
|
value: string;
|
||||||
|
buttonText: string;
|
||||||
|
modalTitle: string;
|
||||||
|
}) {
|
||||||
|
const [myButtonText, setmyButtonText] = useState(buttonText);
|
||||||
|
const [myModalTitle, setMyModalTitle] = useState(modalTitle);
|
||||||
|
const [open, setOpen] = useState(true);
|
||||||
|
const [myValue, setMyValue] = useState(value);
|
||||||
|
const { dark } = useContext(darkContext);
|
||||||
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
|
const ref = useRef();
|
||||||
|
function setModalOpen(x: boolean) {
|
||||||
|
setOpen(x);
|
||||||
|
if (x === false) {
|
||||||
|
setTimeout(() => {
|
||||||
|
closePopUp();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
|
<Dialog
|
||||||
|
as="div"
|
||||||
|
className="relative z-10"
|
||||||
|
onClose={setModalOpen}
|
||||||
|
initialFocus={ref}
|
||||||
|
>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||||
|
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||||
|
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-md text-gray-400 hover:text-gray-500"
|
||||||
|
onClick={() => {
|
||||||
|
setModalOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||||
|
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||||
|
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<DocumentTextIcon
|
||||||
|
className="h-6 w-6 text-blue-600"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||||
|
<Dialog.Title
|
||||||
|
as="h3"
|
||||||
|
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||||
|
>
|
||||||
|
{myModalTitle}
|
||||||
|
</Dialog.Title>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-full w-full bg-gray-200 overflow-auto dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||||
|
<div className="flex h-full w-full">
|
||||||
|
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||||
|
<textarea
|
||||||
|
ref={ref}
|
||||||
|
className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
|
||||||
|
value={myValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
setMyValue(e.target.value);
|
||||||
|
setValue(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
|
||||||
|
onClick={() => {
|
||||||
|
checkPrompt(myValue)
|
||||||
|
.then((apiReturn) => {
|
||||||
|
if (apiReturn.data) {
|
||||||
|
let inputVariables =
|
||||||
|
apiReturn.data.input_variables;
|
||||||
|
if (inputVariables.length === 0) {
|
||||||
|
setErrorData({
|
||||||
|
title:
|
||||||
|
"The template you are attempting to use does not contain any variables for data entry.",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setSuccessData({
|
||||||
|
title: "Prompt is ready",
|
||||||
|
});
|
||||||
|
setModalOpen(false);
|
||||||
|
setValue(myValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setErrorData({
|
||||||
|
title: "Something went wrong, please try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return setErrorData({
|
||||||
|
title:
|
||||||
|
"There is something wrong with this prompt, please review it",
|
||||||
|
list: [error.response.data.detail],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{myButtonText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -75,7 +75,6 @@ export default function ButtonBox({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button disabled={deactivate} onClick={onClick}>
|
<button disabled={deactivate} onClick={onClick}>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,6 @@ export default function PromptAreaModal({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
checkPrompt(myValue)
|
checkPrompt(myValue)
|
||||||
.then((apiReturn) => {
|
.then((apiReturn) => {
|
||||||
console.log(apiReturn);
|
|
||||||
if (apiReturn.data) {
|
if (apiReturn.data) {
|
||||||
let inputVariables =
|
let inputVariables =
|
||||||
apiReturn.data.input_variables;
|
apiReturn.data.input_variables;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function TabComponent({
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { removeFlow, updateFlow, flows, setDisableCopyPaste } = useContext(TabsContext);
|
const { removeFlow, updateFlow, flows } = useContext(TabsContext);
|
||||||
const [isRename, setIsRename] = useState(false);
|
const [isRename, setIsRename] = useState(false);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
return (
|
return (
|
||||||
|
|
@ -40,14 +40,10 @@ export default function TabComponent({
|
||||||
<div className="bg-white dark:text-white dark:bg-gray-700/60 flex select-none justify-between w-44 items-center border border-b-0 border-gray-300 dark:border-gray-600 px-4 py-1 rounded-t-xl -ml-px">
|
<div className="bg-white dark:text-white dark:bg-gray-700/60 flex select-none justify-between w-44 items-center border border-b-0 border-gray-300 dark:border-gray-600 px-4 py-1 rounded-t-xl -ml-px">
|
||||||
{isRename ? (
|
{isRename ? (
|
||||||
<input
|
<input
|
||||||
onFocus={() => {
|
|
||||||
setDisableCopyPaste(true);
|
|
||||||
}}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
|
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setIsRename(false);
|
setIsRename(false);
|
||||||
setDisableCopyPaste(false);
|
|
||||||
if (value !== "") {
|
if (value !== "") {
|
||||||
let newFlow = _.cloneDeep(flow);
|
let newFlow = _.cloneDeep(flow);
|
||||||
newFlow.name = value;
|
newFlow.name = value;
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,9 @@ export default function TabsManagerComponent() {
|
||||||
/>
|
/>
|
||||||
<div className="ml-auto mr-2 flex gap-3">
|
<div className="ml-auto mr-2 flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => openPopUp(<ApiModal flowName={flows[tabIndex].name} />)}
|
onClick={() =>
|
||||||
|
openPopUp(<ApiModal flowName={flows[tabIndex].name} />)
|
||||||
|
}
|
||||||
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
|
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
|
||||||
>
|
>
|
||||||
Code <CodeBracketSquareIcon className="w-5 h-5" />
|
Code <CodeBracketSquareIcon className="w-5 h-5" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import { Edge, Node, useReactFlow } from 'reactflow';
|
import { Edge, Node, useReactFlow } from "reactflow";
|
||||||
|
import { TabsContext } from "../../../contexts/tabsContext";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
type UseUndoRedoOptions = {
|
type UseUndoRedoOptions = {
|
||||||
maxHistorySize: number;
|
maxHistorySize: number;
|
||||||
|
|
@ -30,47 +32,112 @@ export const useUndoRedo: UseUndoRedo = ({
|
||||||
enableShortcuts = defaultOptions.enableShortcuts,
|
enableShortcuts = defaultOptions.enableShortcuts,
|
||||||
} = defaultOptions) => {
|
} = defaultOptions) => {
|
||||||
// the past and future arrays store the states that we can jump to
|
// the past and future arrays store the states that we can jump to
|
||||||
const [past, setPast] = useState<HistoryItem[]>([]);
|
const { tabIndex, flows } = useContext(TabsContext);
|
||||||
const [future, setFuture] = useState<HistoryItem[]>([]);
|
|
||||||
|
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
|
||||||
|
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// whenever the flows variable changes, we need to add one array to the past and future states
|
||||||
|
setPast((old) => flows.map((f, i) => (old[i] ? old[i] : [])));
|
||||||
|
setFuture((old) => flows.map((f, i) => (old[i] ? old[i] : [])));
|
||||||
|
}, [flows]);
|
||||||
|
|
||||||
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();
|
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();
|
||||||
|
|
||||||
const takeSnapshot = useCallback(() => {
|
const takeSnapshot = useCallback(() => {
|
||||||
// push the current graph to the past state
|
// push the current graph to the past state
|
||||||
setPast((past) => [
|
setPast((old) => {
|
||||||
...past.slice(past.length - maxHistorySize + 1, past.length),
|
let newPast = cloneDeep(old);
|
||||||
{ nodes: getNodes(), edges: getEdges() },
|
newPast[tabIndex] = old[tabIndex].slice(
|
||||||
]);
|
old[tabIndex].length - maxHistorySize + 1,
|
||||||
|
old[tabIndex].length
|
||||||
|
);
|
||||||
|
newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
|
||||||
|
return newPast;
|
||||||
|
});
|
||||||
|
|
||||||
// whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches
|
// whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches
|
||||||
setFuture([]);
|
setFuture((old) => {
|
||||||
}, [getNodes, getEdges, maxHistorySize]);
|
let newFuture = cloneDeep(old);
|
||||||
|
newFuture[tabIndex] = [];
|
||||||
|
return newFuture;
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
getNodes,
|
||||||
|
getEdges,
|
||||||
|
past,
|
||||||
|
future,
|
||||||
|
tabIndex,
|
||||||
|
setPast,
|
||||||
|
setFuture,
|
||||||
|
maxHistorySize,
|
||||||
|
]);
|
||||||
|
|
||||||
const undo = useCallback(() => {
|
const undo = useCallback(() => {
|
||||||
// get the last state that we want to go back to
|
// get the last state that we want to go back to
|
||||||
const pastState = past[past.length - 1];
|
const pastState = past[tabIndex][past[tabIndex].length - 1];
|
||||||
|
|
||||||
if (pastState) {
|
if (pastState) {
|
||||||
// first we remove the state from the history
|
// first we remove the state from the history
|
||||||
setPast((past) => past.slice(0, past.length - 1));
|
setPast((old) => {
|
||||||
|
let newPast = cloneDeep(old);
|
||||||
|
newPast[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
|
||||||
|
return newPast;
|
||||||
|
});
|
||||||
// we store the current graph for the redo operation
|
// we store the current graph for the redo operation
|
||||||
setFuture((future) => [...future, { nodes: getNodes(), edges: getEdges() }]);
|
setFuture((old) => {
|
||||||
|
let newFuture = cloneDeep(old);
|
||||||
|
newFuture[tabIndex] = old[tabIndex];
|
||||||
|
newFuture[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
|
||||||
|
return newFuture;
|
||||||
|
});
|
||||||
// now we can set the graph to the past state
|
// now we can set the graph to the past state
|
||||||
setNodes(pastState.nodes);
|
setNodes(pastState.nodes);
|
||||||
setEdges(pastState.edges);
|
setEdges(pastState.edges);
|
||||||
}
|
}
|
||||||
}, [setNodes, setEdges, getNodes, getEdges, past]);
|
}, [
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
getNodes,
|
||||||
|
getEdges,
|
||||||
|
future,
|
||||||
|
past,
|
||||||
|
setFuture,
|
||||||
|
setPast,
|
||||||
|
tabIndex,
|
||||||
|
]);
|
||||||
|
|
||||||
const redo = useCallback(() => {
|
const redo = useCallback(() => {
|
||||||
const futureState = future[future.length - 1];
|
const futureState = future[tabIndex][future[tabIndex].length - 1];
|
||||||
|
|
||||||
if (futureState) {
|
if (futureState) {
|
||||||
setFuture((future) => future.slice(0, future.length - 1));
|
setFuture((old) => {
|
||||||
setPast((past) => [...past, { nodes: getNodes(), edges: getEdges() }]);
|
let newFuture = cloneDeep(old);
|
||||||
|
newFuture[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
|
||||||
|
return newFuture;
|
||||||
|
});
|
||||||
|
setPast((old) => {
|
||||||
|
let newPast = cloneDeep(old);
|
||||||
|
newPast[tabIndex] = old[tabIndex];
|
||||||
|
newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
|
||||||
|
return newPast;
|
||||||
|
});
|
||||||
setNodes(futureState.nodes);
|
setNodes(futureState.nodes);
|
||||||
setEdges(futureState.edges);
|
setEdges(futureState.edges);
|
||||||
}
|
}
|
||||||
}, [setNodes, setEdges, getNodes, getEdges, future]);
|
}, [
|
||||||
|
future,
|
||||||
|
past,
|
||||||
|
setFuture,
|
||||||
|
setPast,
|
||||||
|
setNodes,
|
||||||
|
setEdges,
|
||||||
|
getNodes,
|
||||||
|
getEdges,
|
||||||
|
future,
|
||||||
|
tabIndex,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// this effect is used to attach the global event handlers
|
// this effect is used to attach the global event handlers
|
||||||
|
|
@ -79,21 +146,24 @@ export const useUndoRedo: UseUndoRedo = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyDownHandler = (event: KeyboardEvent) => {
|
const keyDownHandler = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'z' && (event.ctrlKey || event.metaKey) && event.shiftKey) {
|
if (
|
||||||
|
event.key === "z" &&
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.shiftKey
|
||||||
|
) {
|
||||||
redo();
|
redo();
|
||||||
}
|
} else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
|
||||||
else if (event.key === 'y' && (event.ctrlKey || event.metaKey)) {
|
|
||||||
event.preventDefault(); // prevent the default action
|
event.preventDefault(); // prevent the default action
|
||||||
redo();
|
redo();
|
||||||
} else if (event.key === 'z' && (event.ctrlKey || event.metaKey)) {
|
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
|
||||||
undo();
|
undo();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('keydown', keyDownHandler);
|
document.addEventListener("keydown", keyDownHandler);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', keyDownHandler);
|
document.removeEventListener("keydown", keyDownHandler);
|
||||||
};
|
};
|
||||||
}, [undo, redo, enableShortcuts]);
|
}, [undo, redo, enableShortcuts]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||||
// this effect is used to attach the global event handlers
|
// this effect is used to attach the global event handlers
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
console.log("keydownou", lastCopiedSelection, position);
|
|
||||||
if (
|
if (
|
||||||
(event.ctrlKey || event.metaKey) &&
|
(event.ctrlKey || event.metaKey) &&
|
||||||
event.key === "c" &&
|
event.key === "c" &&
|
||||||
|
|
@ -156,10 +155,6 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||||
params.targetHandle.split("|")[0] === "Text"
|
params.targetHandle.split("|")[0] === "Text"
|
||||||
? { stroke: "#333333", strokeWidth: 2 }
|
? { stroke: "#333333", strokeWidth: 2 }
|
||||||
: { stroke: "#222222" },
|
: { stroke: "#222222" },
|
||||||
className:
|
|
||||||
params.targetHandle.split("|")[0] === "Text"
|
|
||||||
? ""
|
|
||||||
: "animate-pulse",
|
|
||||||
animated: params.targetHandle.split("|")[0] === "Text",
|
animated: params.targetHandle.split("|")[0] === "Text",
|
||||||
},
|
},
|
||||||
eds
|
eds
|
||||||
|
|
@ -307,6 +302,8 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||||
setLastSelection(flow);
|
setLastSelection(flow);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const { setDisableCopyPaste } = useContext(TabsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full" ref={reactFlowWrapper}>
|
<div className="w-full h-full" ref={reactFlowWrapper}>
|
||||||
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
||||||
|
|
@ -317,6 +314,15 @@ export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||||
updateFlow({ ...flow, data: reactFlowInstance.toObject() });
|
updateFlow({ ...flow, data: reactFlowInstance.toObject() });
|
||||||
}}
|
}}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
|
onPaneClick={() => {
|
||||||
|
setDisableCopyPaste(false);
|
||||||
|
}}
|
||||||
|
onNodeClick={() => {
|
||||||
|
setDisableCopyPaste(true);
|
||||||
|
}}
|
||||||
|
onPaneMouseLeave={() => {
|
||||||
|
setDisableCopyPaste(true);
|
||||||
|
}}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChangeMod}
|
onEdgesChange={onEdgesChangeMod}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import { ForwardRefExoticComponent, ReactElement, ReactFragment, ReactNode } from "react";
|
import {
|
||||||
|
ForwardRefExoticComponent,
|
||||||
|
ReactElement,
|
||||||
|
ReactFragment,
|
||||||
|
ReactNode,
|
||||||
|
} from "react";
|
||||||
import { NodeDataType } from "../flow/index";
|
import { NodeDataType } from "../flow/index";
|
||||||
export type InputComponentType = {
|
export type InputComponentType = {
|
||||||
value: string;
|
value: string;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ export type TabsContextType = {
|
||||||
disableCopyPaste: boolean;
|
disableCopyPaste: boolean;
|
||||||
setDisableCopyPaste: (value: boolean) => void;
|
setDisableCopyPaste: (value: boolean) => void;
|
||||||
getNodeId: () => string;
|
getNodeId: () => string;
|
||||||
paste: (selection: {nodes: any, edges: any}, position: {x: number, y: number}) => void;
|
paste: (
|
||||||
|
selection: { nodes: any; edges: any },
|
||||||
|
position: { x: number; y: number }
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LangFlowState = {
|
export type LangFlowState = {
|
||||||
|
|
|
||||||
|
|
@ -317,11 +317,9 @@ export function toFirstUpperCase(str: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function snakeToSpaces(str: string) {
|
export function snakeToSpaces(str: string) {
|
||||||
let result = str
|
let result = str.split("_").join(" ");
|
||||||
.split("_")
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toNormalCase(str: string) {
|
export function toNormalCase(str: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
from langflow.template.base import FrontendNode, Template, TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue